summaryrefslogtreecommitdiffstats
path: root/modules
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 06:33:50 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 06:33:50 +0000
commitfe39ffb8b90ae4e002ed73fe98617cd590abb467 (patch)
treeb80e5956907d8aeaaffe4e4f0c068c0e6157ce8b /modules
parentInitial commit. (diff)
downloadapache2-fe39ffb8b90ae4e002ed73fe98617cd590abb467.tar.xz
apache2-fe39ffb8b90ae4e002ed73fe98617cd590abb467.zip
Adding upstream version 2.4.56.upstream/2.4.56
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'modules')
-rw-r--r--modules/Makefile.in6
-rw-r--r--modules/NWGNUmakefile121
-rw-r--r--modules/README67
-rw-r--r--modules/aaa/.indent.pro54
-rw-r--r--modules/aaa/Makefile.in3
-rw-r--r--modules/aaa/NWGNUaccesscompat248
-rw-r--r--modules/aaa/NWGNUallowmethods248
-rw-r--r--modules/aaa/NWGNUauthbasc248
-rw-r--r--modules/aaa/NWGNUauthdigt248
-rw-r--r--modules/aaa/NWGNUauthform250
-rw-r--r--modules/aaa/NWGNUauthnano248
-rw-r--r--modules/aaa/NWGNUauthndbd249
-rw-r--r--modules/aaa/NWGNUauthndbm248
-rw-r--r--modules/aaa/NWGNUauthnfil248
-rw-r--r--modules/aaa/NWGNUauthnsocache248
-rw-r--r--modules/aaa/NWGNUauthnzldap264
-rw-r--r--modules/aaa/NWGNUauthzdbd249
-rw-r--r--modules/aaa/NWGNUauthzdbm248
-rw-r--r--modules/aaa/NWGNUauthzgrp247
-rw-r--r--modules/aaa/NWGNUauthzusr247
-rw-r--r--modules/aaa/NWGNUmakefile270
-rw-r--r--modules/aaa/config.m484
-rw-r--r--modules/aaa/mod_access_compat.c377
-rw-r--r--modules/aaa/mod_access_compat.dep59
-rw-r--r--modules/aaa/mod_access_compat.dsp111
-rw-r--r--modules/aaa/mod_access_compat.mak353
-rw-r--r--modules/aaa/mod_allowmethods.c158
-rw-r--r--modules/aaa/mod_allowmethods.dep56
-rw-r--r--modules/aaa/mod_allowmethods.dsp111
-rw-r--r--modules/aaa/mod_allowmethods.mak353
-rw-r--r--modules/aaa/mod_auth_basic.c508
-rw-r--r--modules/aaa/mod_auth_basic.dep63
-rw-r--r--modules/aaa/mod_auth_basic.dsp111
-rw-r--r--modules/aaa/mod_auth_basic.mak353
-rw-r--r--modules/aaa/mod_auth_digest.c1983
-rw-r--r--modules/aaa/mod_auth_digest.dep68
-rw-r--r--modules/aaa/mod_auth_digest.dsp111
-rw-r--r--modules/aaa/mod_auth_digest.mak353
-rw-r--r--modules/aaa/mod_auth_form.c1332
-rw-r--r--modules/aaa/mod_auth_form.dep66
-rw-r--r--modules/aaa/mod_auth_form.dsp111
-rw-r--r--modules/aaa/mod_auth_form.mak353
-rw-r--r--modules/aaa/mod_authn_anon.c215
-rw-r--r--modules/aaa/mod_authn_anon.dep58
-rw-r--r--modules/aaa/mod_authn_anon.dsp111
-rw-r--r--modules/aaa/mod_authn_anon.mak381
-rw-r--r--modules/aaa/mod_authn_core.c427
-rw-r--r--modules/aaa/mod_authn_core.dep58
-rw-r--r--modules/aaa/mod_authn_core.dsp111
-rw-r--r--modules/aaa/mod_authn_core.mak381
-rw-r--r--modules/aaa/mod_authn_dbd.c307
-rw-r--r--modules/aaa/mod_authn_dbd.dep56
-rw-r--r--modules/aaa/mod_authn_dbd.dsp115
-rw-r--r--modules/aaa/mod_authn_dbd.mak409
-rw-r--r--modules/aaa/mod_authn_dbm.c231
-rw-r--r--modules/aaa/mod_authn_dbm.dep61
-rw-r--r--modules/aaa/mod_authn_dbm.dsp111
-rw-r--r--modules/aaa/mod_authn_dbm.mak381
-rw-r--r--modules/aaa/mod_authn_file.c194
-rw-r--r--modules/aaa/mod_authn_file.dep60
-rw-r--r--modules/aaa/mod_authn_file.dsp111
-rw-r--r--modules/aaa/mod_authn_file.mak381
-rw-r--r--modules/aaa/mod_authn_socache.c475
-rw-r--r--modules/aaa/mod_authn_socache.dep62
-rw-r--r--modules/aaa/mod_authn_socache.dsp111
-rw-r--r--modules/aaa/mod_authn_socache.mak353
-rw-r--r--modules/aaa/mod_authnz_fcgi.c1364
-rw-r--r--modules/aaa/mod_authnz_fcgi.dep61
-rw-r--r--modules/aaa/mod_authnz_fcgi.dsp119
-rw-r--r--modules/aaa/mod_authnz_fcgi.mak353
-rw-r--r--modules/aaa/mod_authnz_ldap.c1977
-rw-r--r--modules/aaa/mod_authnz_ldap.dep70
-rw-r--r--modules/aaa/mod_authnz_ldap.dsp111
-rw-r--r--modules/aaa/mod_authnz_ldap.mak381
-rw-r--r--modules/aaa/mod_authz_core.c1173
-rw-r--r--modules/aaa/mod_authz_core.dep60
-rw-r--r--modules/aaa/mod_authz_core.dsp111
-rw-r--r--modules/aaa/mod_authz_core.mak381
-rw-r--r--modules/aaa/mod_authz_dbd.c409
-rw-r--r--modules/aaa/mod_authz_dbd.dep61
-rw-r--r--modules/aaa/mod_authz_dbd.dsp119
-rw-r--r--modules/aaa/mod_authz_dbd.h44
-rw-r--r--modules/aaa/mod_authz_dbd.mak409
-rw-r--r--modules/aaa/mod_authz_dbm.c356
-rw-r--r--modules/aaa/mod_authz_dbm.dep62
-rw-r--r--modules/aaa/mod_authz_dbm.dsp111
-rw-r--r--modules/aaa/mod_authz_dbm.mak381
-rw-r--r--modules/aaa/mod_authz_groupfile.c333
-rw-r--r--modules/aaa/mod_authz_groupfile.dep61
-rw-r--r--modules/aaa/mod_authz_groupfile.dsp111
-rw-r--r--modules/aaa/mod_authz_groupfile.mak381
-rw-r--r--modules/aaa/mod_authz_host.c410
-rw-r--r--modules/aaa/mod_authz_host.dep60
-rw-r--r--modules/aaa/mod_authz_host.dsp111
-rw-r--r--modules/aaa/mod_authz_host.mak381
-rw-r--r--modules/aaa/mod_authz_owner.c189
-rw-r--r--modules/aaa/mod_authz_owner.dep59
-rw-r--r--modules/aaa/mod_authz_owner.dsp111
-rw-r--r--modules/aaa/mod_authz_owner.h27
-rw-r--r--modules/aaa/mod_authz_owner.mak381
-rw-r--r--modules/aaa/mod_authz_user.c146
-rw-r--r--modules/aaa/mod_authz_user.dep58
-rw-r--r--modules/aaa/mod_authz_user.dsp111
-rw-r--r--modules/aaa/mod_authz_user.mak381
-rw-r--r--modules/arch/netware/libprews.c79
-rw-r--r--modules/arch/netware/mod_netware.c206
-rw-r--r--modules/arch/netware/mod_nw_ssl.c1285
-rw-r--r--modules/arch/unix/Makefile.in3
-rw-r--r--modules/arch/unix/config5.m433
-rw-r--r--modules/arch/unix/mod_privileges.c588
-rw-r--r--modules/arch/unix/mod_systemd.c119
-rw-r--r--modules/arch/unix/mod_unixd.c433
-rw-r--r--modules/arch/unix/mod_unixd.h41
-rw-r--r--modules/arch/win32/Makefile.in3
-rw-r--r--modules/arch/win32/config.m49
-rw-r--r--modules/arch/win32/mod_isapi.c1728
-rw-r--r--modules/arch/win32/mod_isapi.dep61
-rw-r--r--modules/arch/win32/mod_isapi.dsp115
-rw-r--r--modules/arch/win32/mod_isapi.h271
-rw-r--r--modules/arch/win32/mod_isapi.mak353
-rw-r--r--modules/arch/win32/mod_win32.c563
-rw-r--r--modules/cache/.indent.pro54
-rw-r--r--modules/cache/Makefile.in3
-rw-r--r--modules/cache/NWGNUcach_dsk262
-rw-r--r--modules/cache/NWGNUcach_socache263
-rw-r--r--modules/cache/NWGNUmakefile250
-rw-r--r--modules/cache/NWGNUmod_cach265
-rw-r--r--modules/cache/NWGNUsocachdbm261
-rw-r--r--modules/cache/NWGNUsocachmem261
-rw-r--r--modules/cache/NWGNUsocachshmcb261
-rw-r--r--modules/cache/cache_common.h56
-rw-r--r--modules/cache/cache_disk_common.h68
-rw-r--r--modules/cache/cache_socache_common.h57
-rw-r--r--modules/cache/cache_storage.c790
-rw-r--r--modules/cache/cache_storage.h76
-rw-r--r--modules/cache/cache_util.c1344
-rw-r--r--modules/cache/cache_util.h341
-rw-r--r--modules/cache/config.m4143
-rw-r--r--modules/cache/mod_cache.c2717
-rw-r--r--modules/cache/mod_cache.dep194
-rw-r--r--modules/cache/mod_cache.dsp131
-rw-r--r--modules/cache/mod_cache.h192
-rw-r--r--modules/cache/mod_cache.mak370
-rw-r--r--modules/cache/mod_cache_disk.c1585
-rw-r--r--modules/cache/mod_cache_disk.dep59
-rw-r--r--modules/cache/mod_cache_disk.dsp115
-rw-r--r--modules/cache/mod_cache_disk.h91
-rw-r--r--modules/cache/mod_cache_disk.mak381
-rw-r--r--modules/cache/mod_cache_socache.c1543
-rw-r--r--modules/cache/mod_cache_socache.dep67
-rw-r--r--modules/cache/mod_cache_socache.dsp115
-rw-r--r--modules/cache/mod_cache_socache.mak381
-rw-r--r--modules/cache/mod_file_cache.c414
-rw-r--r--modules/cache/mod_file_cache.dep56
-rw-r--r--modules/cache/mod_file_cache.dsp111
-rw-r--r--modules/cache/mod_file_cache.exp1
-rw-r--r--modules/cache/mod_file_cache.mak353
-rw-r--r--modules/cache/mod_socache_dbm.c764
-rw-r--r--modules/cache/mod_socache_dbm.dep60
-rw-r--r--modules/cache/mod_socache_dbm.dsp111
-rw-r--r--modules/cache/mod_socache_dbm.mak353
-rw-r--r--modules/cache/mod_socache_dc.c198
-rw-r--r--modules/cache/mod_socache_dc.dep55
-rw-r--r--modules/cache/mod_socache_dc.dsp111
-rw-r--r--modules/cache/mod_socache_dc.mak353
-rw-r--r--modules/cache/mod_socache_memcache.c418
-rw-r--r--modules/cache/mod_socache_memcache.dep59
-rw-r--r--modules/cache/mod_socache_memcache.dsp111
-rw-r--r--modules/cache/mod_socache_memcache.mak353
-rw-r--r--modules/cache/mod_socache_redis.c486
-rw-r--r--modules/cache/mod_socache_redis.dep5
-rw-r--r--modules/cache/mod_socache_redis.dsp111
-rw-r--r--modules/cache/mod_socache_redis.mak353
-rw-r--r--modules/cache/mod_socache_shmcb.c1087
-rw-r--r--modules/cache/mod_socache_shmcb.dep56
-rw-r--r--modules/cache/mod_socache_shmcb.dsp111
-rw-r--r--modules/cache/mod_socache_shmcb.mak353
-rw-r--r--modules/cluster/Makefile.in3
-rw-r--r--modules/cluster/NWGNUmakefile246
-rw-r--r--modules/cluster/NWGNUmodheartbeat257
-rw-r--r--modules/cluster/NWGNUmodheartmonitor257
-rw-r--r--modules/cluster/README.heartbeat33
-rw-r--r--modules/cluster/README.heartmonitor30
-rw-r--r--modules/cluster/config5.m417
-rw-r--r--modules/cluster/mod_heartbeat.c228
-rw-r--r--modules/cluster/mod_heartbeat.dep55
-rw-r--r--modules/cluster/mod_heartbeat.dsp123
-rw-r--r--modules/cluster/mod_heartbeat.mak380
-rw-r--r--modules/cluster/mod_heartmonitor.c920
-rw-r--r--modules/cluster/mod_heartmonitor.dep63
-rw-r--r--modules/cluster/mod_heartmonitor.dsp123
-rw-r--r--modules/cluster/mod_heartmonitor.mak380
-rw-r--r--modules/config7.m456
-rw-r--r--modules/core/Makefile.in3
-rw-r--r--modules/core/NWGNUmakefile257
-rw-r--r--modules/core/config.m460
-rw-r--r--modules/core/mod_macro.c950
-rw-r--r--modules/core/mod_macro.dep45
-rw-r--r--modules/core/mod_macro.dsp111
-rw-r--r--modules/core/mod_macro.mak353
-rw-r--r--modules/core/mod_so.c442
-rw-r--r--modules/core/mod_so.h38
-rw-r--r--modules/core/mod_watchdog.c718
-rw-r--r--modules/core/mod_watchdog.dep59
-rw-r--r--modules/core/mod_watchdog.dsp115
-rw-r--r--modules/core/mod_watchdog.h213
-rw-r--r--modules/core/mod_watchdog.mak353
-rw-r--r--modules/core/test/Makefile67
-rw-r--r--modules/core/test/conf/inc63_1.conf5
-rw-r--r--modules/core/test/conf/inc63_2.conf3
-rw-r--r--modules/core/test/conf/test01.conf3
-rw-r--r--modules/core/test/conf/test02.conf3
-rw-r--r--modules/core/test/conf/test03.conf5
-rw-r--r--modules/core/test/conf/test04.conf5
-rw-r--r--modules/core/test/conf/test05.conf5
-rw-r--r--modules/core/test/conf/test06.conf6
-rw-r--r--modules/core/test/conf/test07.conf3
-rw-r--r--modules/core/test/conf/test08.conf3
-rw-r--r--modules/core/test/conf/test09.conf6
-rw-r--r--modules/core/test/conf/test10.conf10
-rw-r--r--modules/core/test/conf/test11.conf15
-rw-r--r--modules/core/test/conf/test12.conf12
-rw-r--r--modules/core/test/conf/test13.conf18
-rw-r--r--modules/core/test/conf/test14.conf23
-rw-r--r--modules/core/test/conf/test15.conf9
-rw-r--r--modules/core/test/conf/test16.conf11
-rw-r--r--modules/core/test/conf/test17.conf10
-rw-r--r--modules/core/test/conf/test18.conf10
-rw-r--r--modules/core/test/conf/test19.conf26
-rw-r--r--modules/core/test/conf/test20.conf11
-rw-r--r--modules/core/test/conf/test21.conf11
-rw-r--r--modules/core/test/conf/test22.conf11
-rw-r--r--modules/core/test/conf/test23.conf15
-rw-r--r--modules/core/test/conf/test24.conf23
-rw-r--r--modules/core/test/conf/test25.conf27
-rw-r--r--modules/core/test/conf/test26.conf19
-rw-r--r--modules/core/test/conf/test27.conf22
-rw-r--r--modules/core/test/conf/test28.conf13
-rw-r--r--modules/core/test/conf/test29.conf10
-rw-r--r--modules/core/test/conf/test30.conf12
-rw-r--r--modules/core/test/conf/test31.conf16
-rw-r--r--modules/core/test/conf/test32.conf7
-rw-r--r--modules/core/test/conf/test33.conf3
-rw-r--r--modules/core/test/conf/test34.conf14
-rw-r--r--modules/core/test/conf/test35.conf10
-rw-r--r--modules/core/test/conf/test36.conf12
-rw-r--r--modules/core/test/conf/test37.conf7
-rw-r--r--modules/core/test/conf/test38.conf10
-rw-r--r--modules/core/test/conf/test39.conf23
-rw-r--r--modules/core/test/conf/test40.conf33
-rw-r--r--modules/core/test/conf/test41.conf20
-rw-r--r--modules/core/test/conf/test42.conf13
-rw-r--r--modules/core/test/conf/test43.conf29
-rw-r--r--modules/core/test/conf/test44.conf19
-rw-r--r--modules/core/test/conf/test45.conf7
-rw-r--r--modules/core/test/conf/test46.conf11
-rw-r--r--modules/core/test/conf/test47.conf15
-rw-r--r--modules/core/test/conf/test48.conf23
-rw-r--r--modules/core/test/conf/test49.conf2
-rw-r--r--modules/core/test/conf/test50.conf5
-rw-r--r--modules/core/test/conf/test51.conf9
-rw-r--r--modules/core/test/conf/test52.conf8
-rw-r--r--modules/core/test/conf/test53.conf2
-rw-r--r--modules/core/test/conf/test54.conf6
-rw-r--r--modules/core/test/conf/test55.conf11
-rw-r--r--modules/core/test/conf/test56.conf18
-rw-r--r--modules/core/test/conf/test57.conf4
-rw-r--r--modules/core/test/conf/test58.conf4
-rw-r--r--modules/core/test/conf/test59.conf4
-rw-r--r--modules/core/test/conf/test60.conf17
-rw-r--r--modules/core/test/conf/test61.conf18
-rw-r--r--modules/core/test/conf/test62.conf25
-rw-r--r--modules/core/test/conf/test63.conf9
-rw-r--r--modules/core/test/conf/test64.conf5
-rw-r--r--modules/core/test/conf/test65.conf11
-rw-r--r--modules/core/test/conf/test66.conf7
-rw-r--r--modules/core/test/conf/test67.conf1
-rw-r--r--modules/core/test/conf/test68.conf5
-rw-r--r--modules/core/test/conf/test69.conf14
-rw-r--r--modules/core/test/ref/test01.out3
-rw-r--r--modules/core/test/ref/test02.out3
-rw-r--r--modules/core/test/ref/test03.out3
-rw-r--r--modules/core/test/ref/test04.out3
-rw-r--r--modules/core/test/ref/test05.out3
-rw-r--r--modules/core/test/ref/test06.out3
-rw-r--r--modules/core/test/ref/test07.out3
-rw-r--r--modules/core/test/ref/test08.out3
-rw-r--r--modules/core/test/ref/test09.out3
-rw-r--r--modules/core/test/ref/test10.out3
-rw-r--r--modules/core/test/ref/test11.out6
-rw-r--r--modules/core/test/ref/test12.out7
-rw-r--r--modules/core/test/ref/test13.out8
-rw-r--r--modules/core/test/ref/test14.out14
-rw-r--r--modules/core/test/ref/test15.out6
-rw-r--r--modules/core/test/ref/test16.out5
-rw-r--r--modules/core/test/ref/test17.out7
-rw-r--r--modules/core/test/ref/test18.out7
-rw-r--r--modules/core/test/ref/test19.out9
-rw-r--r--modules/core/test/ref/test20.out4
-rw-r--r--modules/core/test/ref/test21.out5
-rw-r--r--modules/core/test/ref/test22.out6
-rw-r--r--modules/core/test/ref/test23.out7
-rw-r--r--modules/core/test/ref/test24.out8
-rw-r--r--modules/core/test/ref/test25.out9
-rw-r--r--modules/core/test/ref/test26.out11
-rw-r--r--modules/core/test/ref/test27.out8
-rw-r--r--modules/core/test/ref/test28.out6
-rw-r--r--modules/core/test/ref/test29.out4
-rw-r--r--modules/core/test/ref/test30.out7
-rw-r--r--modules/core/test/ref/test31.out23
-rw-r--r--modules/core/test/ref/test32.out3
-rw-r--r--modules/core/test/ref/test33.out3
-rw-r--r--modules/core/test/ref/test34.out13
-rw-r--r--modules/core/test/ref/test35.out13
-rw-r--r--modules/core/test/ref/test36.out20
-rw-r--r--modules/core/test/ref/test37.out3
-rw-r--r--modules/core/test/ref/test38.out6
-rw-r--r--modules/core/test/ref/test39.out7
-rw-r--r--modules/core/test/ref/test40.out18
-rw-r--r--modules/core/test/ref/test41.out9
-rw-r--r--modules/core/test/ref/test42.out15
-rw-r--r--modules/core/test/ref/test43.out8
-rw-r--r--modules/core/test/ref/test44.out5
-rw-r--r--modules/core/test/ref/test45.out19
-rw-r--r--modules/core/test/ref/test46.out9
-rw-r--r--modules/core/test/ref/test47.out8
-rw-r--r--modules/core/test/ref/test48.out20
-rw-r--r--modules/core/test/ref/test49.out3
-rw-r--r--modules/core/test/ref/test50.out3
-rw-r--r--modules/core/test/ref/test51.out3
-rw-r--r--modules/core/test/ref/test52.out6
-rw-r--r--modules/core/test/ref/test53.out3
-rw-r--r--modules/core/test/ref/test54.out6
-rw-r--r--modules/core/test/ref/test55.out8
-rw-r--r--modules/core/test/ref/test56.out12
-rw-r--r--modules/core/test/ref/test57.out3
-rw-r--r--modules/core/test/ref/test58.out3
-rw-r--r--modules/core/test/ref/test59.out3
-rw-r--r--modules/core/test/ref/test60.out15
-rw-r--r--modules/core/test/ref/test61.out9
-rw-r--r--modules/core/test/ref/test62.out15
-rw-r--r--modules/core/test/ref/test63.out10
-rw-r--r--modules/core/test/ref/test64.out7
-rw-r--r--modules/core/test/ref/test65.out7
-rw-r--r--modules/core/test/ref/test66.out7
-rw-r--r--modules/core/test/ref/test67.out5
-rw-r--r--modules/core/test/ref/test68.out6
-rw-r--r--modules/core/test/ref/test69.out10
-rw-r--r--modules/database/Makefile.in3
-rw-r--r--modules/database/NWGNUmakefile262
-rw-r--r--modules/database/config.m48
-rw-r--r--modules/database/mod_dbd.c995
-rw-r--r--modules/database/mod_dbd.dep58
-rw-r--r--modules/database/mod_dbd.dsp115
-rw-r--r--modules/database/mod_dbd.h123
-rw-r--r--modules/database/mod_dbd.mak353
-rw-r--r--modules/dav/fs/Makefile.in3
-rw-r--r--modules/dav/fs/NWGNUmakefile269
-rw-r--r--modules/dav/fs/config6.m423
-rw-r--r--modules/dav/fs/dbm.c800
-rw-r--r--modules/dav/fs/lock.c1445
-rw-r--r--modules/dav/fs/mod_dav_fs.c108
-rw-r--r--modules/dav/fs/mod_dav_fs.dep203
-rw-r--r--modules/dav/fs/mod_dav_fs.dsp135
-rw-r--r--modules/dav/fs/mod_dav_fs.mak407
-rw-r--r--modules/dav/fs/repos.c2269
-rw-r--r--modules/dav/fs/repos.h84
-rw-r--r--modules/dav/lock/Makefile.in3
-rw-r--r--modules/dav/lock/NWGNUmakefile259
-rw-r--r--modules/dav/lock/config6.m417
-rw-r--r--modules/dav/lock/locks.c1237
-rw-r--r--modules/dav/lock/locks.h33
-rw-r--r--modules/dav/lock/mod_dav_lock.c104
-rw-r--r--modules/dav/lock/mod_dav_lock.dep100
-rw-r--r--modules/dav/lock/mod_dav_lock.dsp127
-rw-r--r--modules/dav/lock/mod_dav_lock.mak389
-rw-r--r--modules/dav/main/Makefile.in3
-rw-r--r--modules/dav/main/NWGNUmakefile268
-rw-r--r--modules/dav/main/config5.m421
-rw-r--r--modules/dav/main/liveprop.c140
-rw-r--r--modules/dav/main/mod_dav.c5237
-rw-r--r--modules/dav/main/mod_dav.dep354
-rw-r--r--modules/dav/main/mod_dav.dsp147
-rw-r--r--modules/dav/main/mod_dav.h2670
-rw-r--r--modules/dav/main/mod_dav.mak406
-rw-r--r--modules/dav/main/props.c1179
-rw-r--r--modules/dav/main/providers.c58
-rw-r--r--modules/dav/main/std_liveprop.c226
-rw-r--r--modules/dav/main/util.c2213
-rw-r--r--modules/dav/main/util_lock.c798
-rw-r--r--modules/debugging/Makefile.in3
-rw-r--r--modules/debugging/NWGNUmakefile246
-rw-r--r--modules/debugging/NWGNUmodbucketeer248
-rw-r--r--modules/debugging/NWGNUmoddumpio248
-rw-r--r--modules/debugging/README1
-rw-r--r--modules/debugging/config.m47
-rw-r--r--modules/debugging/mod_bucketeer.c187
-rw-r--r--modules/debugging/mod_bucketeer.dep53
-rw-r--r--modules/debugging/mod_bucketeer.dsp111
-rw-r--r--modules/debugging/mod_bucketeer.mak353
-rw-r--r--modules/debugging/mod_dumpio.c255
-rw-r--r--modules/debugging/mod_dumpio.dep50
-rw-r--r--modules/debugging/mod_dumpio.dsp111
-rw-r--r--modules/debugging/mod_dumpio.mak353
-rw-r--r--modules/echo/.indent.pro54
-rw-r--r--modules/echo/Makefile.in3
-rw-r--r--modules/echo/NWGNUmakefile257
-rw-r--r--modules/echo/config.m49
-rw-r--r--modules/echo/mod_echo.c218
-rw-r--r--modules/echo/mod_echo.dep56
-rw-r--r--modules/echo/mod_echo.dsp111
-rw-r--r--modules/echo/mod_echo.mak353
-rw-r--r--modules/examples/Makefile.in3
-rw-r--r--modules/examples/NWGNUcase_flt256
-rw-r--r--modules/examples/NWGNUcase_flt_in256
-rw-r--r--modules/examples/NWGNUexample_hooks257
-rw-r--r--modules/examples/NWGNUexample_ipc257
-rw-r--r--modules/examples/NWGNUmakefile257
-rw-r--r--modules/examples/README54
-rw-r--r--modules/examples/config.m49
-rw-r--r--modules/examples/mod_case_filter.c139
-rw-r--r--modules/examples/mod_case_filter.dep46
-rw-r--r--modules/examples/mod_case_filter.dsp111
-rw-r--r--modules/examples/mod_case_filter.mak353
-rw-r--r--modules/examples/mod_case_filter_in.c160
-rw-r--r--modules/examples/mod_case_filter_in.dep46
-rw-r--r--modules/examples/mod_case_filter_in.dsp111
-rw-r--r--modules/examples/mod_case_filter_in.mak353
-rw-r--r--modules/examples/mod_example_hooks.c1552
-rw-r--r--modules/examples/mod_example_hooks.dep62
-rw-r--r--modules/examples/mod_example_hooks.dsp111
-rw-r--r--modules/examples/mod_example_hooks.mak353
-rw-r--r--modules/examples/mod_example_ipc.c356
-rw-r--r--modules/examples/mod_example_ipc.dep56
-rw-r--r--modules/examples/mod_example_ipc.dsp111
-rw-r--r--modules/examples/mod_example_ipc.mak353
-rw-r--r--modules/experimental/.indent.pro54
-rw-r--r--modules/experimental/Makefile.in3
-rw-r--r--modules/experimental/NWGNUmakefile253
-rw-r--r--modules/experimental/config.m44
-rw-r--r--modules/filters/.indent.pro54
-rw-r--r--modules/filters/Makefile.in3
-rw-r--r--modules/filters/NWGNUcharsetl257
-rw-r--r--modules/filters/NWGNUdeflate279
-rw-r--r--modules/filters/NWGNUextfiltr248
-rw-r--r--modules/filters/NWGNUmakefile273
-rw-r--r--modules/filters/NWGNUmod_data248
-rw-r--r--modules/filters/NWGNUmod_filter248
-rw-r--r--modules/filters/NWGNUmod_request248
-rw-r--r--modules/filters/NWGNUmodbuffer256
-rw-r--r--modules/filters/NWGNUmodsed259
-rw-r--r--modules/filters/NWGNUproxyhtml261
-rw-r--r--modules/filters/NWGNUratelimit256
-rw-r--r--modules/filters/NWGNUreflector256
-rw-r--r--modules/filters/NWGNUreqtimeout256
-rw-r--r--modules/filters/NWGNUsubstitute256
-rw-r--r--modules/filters/NWGNUxml2enc258
-rw-r--r--modules/filters/config.m4197
-rw-r--r--modules/filters/libsed.h172
-rw-r--r--modules/filters/mod_brotli.c608
-rw-r--r--modules/filters/mod_brotli.dep45
-rw-r--r--modules/filters/mod_brotli.dsp111
-rw-r--r--modules/filters/mod_brotli.mak353
-rw-r--r--modules/filters/mod_buffer.c353
-rw-r--r--modules/filters/mod_buffer.dep48
-rw-r--r--modules/filters/mod_buffer.dsp111
-rw-r--r--modules/filters/mod_buffer.mak353
-rw-r--r--modules/filters/mod_charset_lite.c1142
-rw-r--r--modules/filters/mod_charset_lite.dep60
-rw-r--r--modules/filters/mod_charset_lite.dsp111
-rw-r--r--modules/filters/mod_charset_lite.exp1
-rw-r--r--modules/filters/mod_charset_lite.mak353
-rw-r--r--modules/filters/mod_data.c255
-rw-r--r--modules/filters/mod_data.dep55
-rw-r--r--modules/filters/mod_data.dsp111
-rw-r--r--modules/filters/mod_data.mak353
-rw-r--r--modules/filters/mod_deflate.c1936
-rw-r--r--modules/filters/mod_deflate.dep52
-rw-r--r--modules/filters/mod_deflate.dsp111
-rw-r--r--modules/filters/mod_deflate.exp1
-rw-r--r--modules/filters/mod_deflate.mak353
-rw-r--r--modules/filters/mod_ext_filter.c954
-rw-r--r--modules/filters/mod_ext_filter.dep58
-rw-r--r--modules/filters/mod_ext_filter.dsp111
-rw-r--r--modules/filters/mod_ext_filter.exp1
-rw-r--r--modules/filters/mod_ext_filter.mak353
-rw-r--r--modules/filters/mod_filter.c767
-rw-r--r--modules/filters/mod_filter.dep50
-rw-r--r--modules/filters/mod_filter.dsp111
-rw-r--r--modules/filters/mod_filter.mak353
-rw-r--r--modules/filters/mod_include.c4238
-rw-r--r--modules/filters/mod_include.dep63
-rw-r--r--modules/filters/mod_include.dsp115
-rw-r--r--modules/filters/mod_include.exp1
-rw-r--r--modules/filters/mod_include.h120
-rw-r--r--modules/filters/mod_include.mak353
-rw-r--r--modules/filters/mod_proxy_html.c1353
-rw-r--r--modules/filters/mod_proxy_html.dep58
-rw-r--r--modules/filters/mod_proxy_html.dsp123
-rw-r--r--modules/filters/mod_proxy_html.mak352
-rw-r--r--modules/filters/mod_ratelimit.c340
-rw-r--r--modules/filters/mod_ratelimit.dep45
-rw-r--r--modules/filters/mod_ratelimit.dsp115
-rw-r--r--modules/filters/mod_ratelimit.h51
-rw-r--r--modules/filters/mod_ratelimit.mak353
-rw-r--r--modules/filters/mod_reflector.c231
-rw-r--r--modules/filters/mod_reflector.dep57
-rw-r--r--modules/filters/mod_reflector.dsp111
-rw-r--r--modules/filters/mod_reflector.mak353
-rw-r--r--modules/filters/mod_reqtimeout.c670
-rw-r--r--modules/filters/mod_reqtimeout.dep58
-rw-r--r--modules/filters/mod_reqtimeout.dsp111
-rw-r--r--modules/filters/mod_reqtimeout.mak353
-rw-r--r--modules/filters/mod_request.c393
-rw-r--r--modules/filters/mod_request.dep55
-rw-r--r--modules/filters/mod_request.dsp115
-rw-r--r--modules/filters/mod_request.mak353
-rw-r--r--modules/filters/mod_sed.c537
-rw-r--r--modules/filters/mod_sed.dep109
-rw-r--r--modules/filters/mod_sed.dsp135
-rw-r--r--modules/filters/mod_sed.mak380
-rw-r--r--modules/filters/mod_substitute.c766
-rw-r--r--modules/filters/mod_substitute.dep53
-rw-r--r--modules/filters/mod_substitute.dsp111
-rw-r--r--modules/filters/mod_substitute.mak353
-rw-r--r--modules/filters/mod_xml2enc.c676
-rw-r--r--modules/filters/mod_xml2enc.dep54
-rw-r--r--modules/filters/mod_xml2enc.dsp123
-rw-r--r--modules/filters/mod_xml2enc.h55
-rw-r--r--modules/filters/mod_xml2enc.mak352
-rw-r--r--modules/filters/regexp.c599
-rw-r--r--modules/filters/regexp.h112
-rw-r--r--modules/filters/sed.h61
-rw-r--r--modules/filters/sed0.c1026
-rw-r--r--modules/filters/sed1.c1110
-rw-r--r--modules/generators/.indent.pro54
-rw-r--r--modules/generators/Makefile.in3
-rw-r--r--modules/generators/NWGNUautoindex249
-rw-r--r--modules/generators/NWGNUinfo248
-rw-r--r--modules/generators/NWGNUmakefile249
-rw-r--r--modules/generators/NWGNUmod_asis249
-rw-r--r--modules/generators/NWGNUmod_cgi249
-rw-r--r--modules/generators/NWGNUstatus248
-rw-r--r--modules/generators/config5.m481
-rw-r--r--modules/generators/mod_asis.c128
-rw-r--r--modules/generators/mod_asis.dep56
-rw-r--r--modules/generators/mod_asis.dsp111
-rw-r--r--modules/generators/mod_asis.exp1
-rw-r--r--modules/generators/mod_asis.mak353
-rw-r--r--modules/generators/mod_autoindex.c2350
-rw-r--r--modules/generators/mod_autoindex.dep61
-rw-r--r--modules/generators/mod_autoindex.dsp111
-rw-r--r--modules/generators/mod_autoindex.exp1
-rw-r--r--modules/generators/mod_autoindex.mak353
-rw-r--r--modules/generators/mod_cgi.c1277
-rw-r--r--modules/generators/mod_cgi.dep64
-rw-r--r--modules/generators/mod_cgi.dsp115
-rw-r--r--modules/generators/mod_cgi.exp1
-rw-r--r--modules/generators/mod_cgi.h67
-rw-r--r--modules/generators/mod_cgi.mak353
-rw-r--r--modules/generators/mod_cgid.c1978
-rw-r--r--modules/generators/mod_cgid.exp1
-rw-r--r--modules/generators/mod_info.c1018
-rw-r--r--modules/generators/mod_info.dep66
-rw-r--r--modules/generators/mod_info.dsp111
-rw-r--r--modules/generators/mod_info.exp1
-rw-r--r--modules/generators/mod_info.mak353
-rw-r--r--modules/generators/mod_status.c1051
-rw-r--r--modules/generators/mod_status.dep60
-rw-r--r--modules/generators/mod_status.dsp111
-rw-r--r--modules/generators/mod_status.exp1
-rw-r--r--modules/generators/mod_status.h64
-rw-r--r--modules/generators/mod_status.mak353
-rw-r--r--modules/generators/mod_suexec.c139
-rw-r--r--modules/generators/mod_suexec.h33
-rw-r--r--modules/http/.indent.pro54
-rw-r--r--modules/http/Makefile.in3
-rw-r--r--modules/http/byterange_filter.c610
-rw-r--r--modules/http/chunk_filter.c197
-rw-r--r--modules/http/config.m420
-rw-r--r--modules/http/http_core.c326
-rw-r--r--modules/http/http_etag.c413
-rw-r--r--modules/http/http_filters.c1941
-rw-r--r--modules/http/http_protocol.c1671
-rw-r--r--modules/http/http_request.c861
-rw-r--r--modules/http/mod_mime.c1024
-rw-r--r--modules/http/mod_mime.dep55
-rw-r--r--modules/http/mod_mime.dsp111
-rw-r--r--modules/http/mod_mime.exp1
-rw-r--r--modules/http/mod_mime.mak353
-rw-r--r--modules/http2/Makefile.in20
-rw-r--r--modules/http2/NWGNUmakefile246
-rw-r--r--modules/http2/NWGNUmod_http2395
-rw-r--r--modules/http2/NWGNUproxyht2288
-rw-r--r--modules/http2/README.h270
-rw-r--r--modules/http2/config2.m4238
-rw-r--r--modules/http2/h2.h192
-rw-r--r--modules/http2/h2_bucket_beam.c825
-rw-r--r--modules/http2/h2_bucket_beam.h248
-rw-r--r--modules/http2/h2_bucket_eos.c112
-rw-r--r--modules/http2/h2_bucket_eos.h32
-rw-r--r--modules/http2/h2_c1.c323
-rw-r--r--modules/http2/h2_c1.h83
-rw-r--r--modules/http2/h2_c1_io.c545
-rw-r--r--modules/http2/h2_c1_io.h100
-rw-r--r--modules/http2/h2_c2.c864
-rw-r--r--modules/http2/h2_c2.h57
-rw-r--r--modules/http2/h2_c2_filter.c1034
-rw-r--r--modules/http2/h2_c2_filter.h68
-rw-r--r--modules/http2/h2_config.c943
-rw-r--r--modules/http2/h2_config.h98
-rw-r--r--modules/http2/h2_conn_ctx.c123
-rw-r--r--modules/http2/h2_conn_ctx.h98
-rw-r--r--modules/http2/h2_headers.c207
-rw-r--r--modules/http2/h2_headers.h107
-rw-r--r--modules/http2/h2_mplx.c1191
-rw-r--r--modules/http2/h2_mplx.h218
-rw-r--r--modules/http2/h2_private.h28
-rw-r--r--modules/http2/h2_protocol.c485
-rw-r--r--modules/http2/h2_protocol.h56
-rw-r--r--modules/http2/h2_proxy_session.c1719
-rw-r--r--modules/http2/h2_proxy_session.h133
-rw-r--r--modules/http2/h2_proxy_util.c1355
-rw-r--r--modules/http2/h2_proxy_util.h257
-rw-r--r--modules/http2/h2_push.c876
-rw-r--r--modules/http2/h2_push.h158
-rw-r--r--modules/http2/h2_request.c519
-rw-r--r--modules/http2/h2_request.h59
-rw-r--r--modules/http2/h2_session.c1991
-rw-r--r--modules/http2/h2_session.h205
-rw-r--r--modules/http2/h2_stream.c1712
-rw-r--r--modules/http2/h2_stream.h326
-rw-r--r--modules/http2/h2_switch.c232
-rw-r--r--modules/http2/h2_switch.h30
-rw-r--r--modules/http2/h2_util.c1929
-rw-r--r--modules/http2/h2_util.h519
-rw-r--r--modules/http2/h2_version.h41
-rw-r--r--modules/http2/h2_workers.c626
-rw-r--r--modules/http2/h2_workers.h129
-rw-r--r--modules/http2/mod_http2.c349
-rw-r--r--modules/http2/mod_http2.dep1431
-rw-r--r--modules/http2/mod_http2.dsp187
-rw-r--r--modules/http2/mod_http2.h79
-rw-r--r--modules/http2/mod_http2.mak533
-rw-r--r--modules/http2/mod_proxy_http2.c458
-rw-r--r--modules/http2/mod_proxy_http2.dep208
-rw-r--r--modules/http2/mod_proxy_http2.dsp119
-rw-r--r--modules/http2/mod_proxy_http2.h21
-rw-r--r--modules/http2/mod_proxy_http2.mak427
-rw-r--r--modules/ldap/Makefile.in3
-rw-r--r--modules/ldap/NWGNUmakefile264
-rw-r--r--modules/ldap/README.ldap47
-rw-r--r--modules/ldap/config.m425
-rw-r--r--modules/ldap/mod_ldap.dep192
-rw-r--r--modules/ldap/mod_ldap.dsp127
-rw-r--r--modules/ldap/mod_ldap.mak371
-rw-r--r--modules/ldap/util_ldap.c3240
-rw-r--r--modules/ldap/util_ldap_cache.c467
-rw-r--r--modules/ldap/util_ldap_cache.h206
-rw-r--r--modules/ldap/util_ldap_cache_mgr.c905
-rw-r--r--modules/loggers/.indent.pro54
-rw-r--r--modules/loggers/Makefile.in3
-rw-r--r--modules/loggers/NWGNUforensic258
-rw-r--r--modules/loggers/NWGNUlogdebug258
-rw-r--r--modules/loggers/NWGNUmakefile247
-rw-r--r--modules/loggers/NWGNUmodlogio258
-rw-r--r--modules/loggers/config.m420
-rw-r--r--modules/loggers/mod_log_config.c1857
-rw-r--r--modules/loggers/mod_log_config.dep62
-rw-r--r--modules/loggers/mod_log_config.dsp111
-rw-r--r--modules/loggers/mod_log_config.exp1
-rw-r--r--modules/loggers/mod_log_config.h74
-rw-r--r--modules/loggers/mod_log_config.mak353
-rw-r--r--modules/loggers/mod_log_debug.c295
-rw-r--r--modules/loggers/mod_log_debug.dep54
-rw-r--r--modules/loggers/mod_log_debug.dsp111
-rw-r--r--modules/loggers/mod_log_debug.mak325
-rw-r--r--modules/loggers/mod_log_forensic.c289
-rw-r--r--modules/loggers/mod_log_forensic.dep53
-rw-r--r--modules/loggers/mod_log_forensic.dsp111
-rw-r--r--modules/loggers/mod_log_forensic.exp1
-rw-r--r--modules/loggers/mod_log_forensic.mak353
-rw-r--r--modules/loggers/mod_logio.c284
-rw-r--r--modules/loggers/mod_logio.dep59
-rw-r--r--modules/loggers/mod_logio.dsp111
-rw-r--r--modules/loggers/mod_logio.mak353
-rw-r--r--modules/lua/Makefile.in3
-rw-r--r--modules/lua/NWGNUmakefile287
-rw-r--r--modules/lua/README54
-rw-r--r--modules/lua/config.m4130
-rw-r--r--modules/lua/docs/README12
-rw-r--r--modules/lua/docs/basic-configuration.txt141
-rw-r--r--modules/lua/docs/building-from-subversion.txt72
-rw-r--r--modules/lua/docs/running-developer-tests.txt16
-rw-r--r--modules/lua/docs/writing-handlers.txt49
-rw-r--r--modules/lua/lua_apr.c110
-rw-r--r--modules/lua/lua_apr.h36
-rw-r--r--modules/lua/lua_config.c277
-rw-r--r--modules/lua/lua_config.h31
-rw-r--r--modules/lua/lua_dbd.c843
-rw-r--r--modules/lua/lua_dbd.h66
-rw-r--r--modules/lua/lua_passwd.c178
-rw-r--r--modules/lua/lua_passwd.h90
-rw-r--r--modules/lua/lua_request.c3043
-rw-r--r--modules/lua/lua_request.h58
-rw-r--r--modules/lua/lua_vmprep.c552
-rw-r--r--modules/lua/lua_vmprep.h147
-rw-r--r--modules/lua/mod_lua.c2197
-rw-r--r--modules/lua/mod_lua.dep418
-rw-r--r--modules/lua/mod_lua.dsp163
-rw-r--r--modules/lua/mod_lua.h187
-rw-r--r--modules/lua/mod_lua.mak407
-rw-r--r--modules/lua/test/helpers.lua36
-rw-r--r--modules/lua/test/htdocs/config_tests.lua37
-rw-r--r--modules/lua/test/htdocs/filters.lua7
-rw-r--r--modules/lua/test/htdocs/find_me.txt1
-rw-r--r--modules/lua/test/htdocs/headers.lua6
-rw-r--r--modules/lua/test/htdocs/hooks.lua29
-rw-r--r--modules/lua/test/htdocs/other.lua21
-rw-r--r--modules/lua/test/htdocs/simple.lua4
-rw-r--r--modules/lua/test/htdocs/test.lua129
-rw-r--r--modules/lua/test/lib/kangaroo.lua19
-rw-r--r--modules/lua/test/moonunit.lua52
-rwxr-xr-xmodules/lua/test/test.lua126
-rw-r--r--modules/lua/test/test_httpd.conf31
-rw-r--r--modules/mappers/.indent.pro54
-rw-r--r--modules/mappers/Makefile.in3
-rw-r--r--modules/mappers/NWGNUactions248
-rw-r--r--modules/mappers/NWGNUimagemap249
-rw-r--r--modules/mappers/NWGNUmakefile250
-rw-r--r--modules/mappers/NWGNUrewrite250
-rw-r--r--modules/mappers/NWGNUspeling248
-rw-r--r--modules/mappers/NWGNUuserdir249
-rw-r--r--modules/mappers/NWGNUvhost249
-rw-r--r--modules/mappers/config9.m419
-rw-r--r--modules/mappers/mod_actions.c230
-rw-r--r--modules/mappers/mod_actions.dep58
-rw-r--r--modules/mappers/mod_actions.dsp111
-rw-r--r--modules/mappers/mod_actions.exp1
-rw-r--r--modules/mappers/mod_actions.mak353
-rw-r--r--modules/mappers/mod_alias.c727
-rw-r--r--modules/mappers/mod_alias.dep51
-rw-r--r--modules/mappers/mod_alias.dsp111
-rw-r--r--modules/mappers/mod_alias.exp1
-rw-r--r--modules/mappers/mod_alias.mak353
-rw-r--r--modules/mappers/mod_dir.c417
-rw-r--r--modules/mappers/mod_dir.dep60
-rw-r--r--modules/mappers/mod_dir.dsp111
-rw-r--r--modules/mappers/mod_dir.exp1
-rw-r--r--modules/mappers/mod_dir.mak353
-rw-r--r--modules/mappers/mod_imagemap.c897
-rw-r--r--modules/mappers/mod_imagemap.dep60
-rw-r--r--modules/mappers/mod_imagemap.dsp111
-rw-r--r--modules/mappers/mod_imagemap.exp1
-rw-r--r--modules/mappers/mod_imagemap.mak353
-rw-r--r--modules/mappers/mod_negotiation.c3223
-rw-r--r--modules/mappers/mod_negotiation.dep58
-rw-r--r--modules/mappers/mod_negotiation.dsp111
-rw-r--r--modules/mappers/mod_negotiation.exp1
-rw-r--r--modules/mappers/mod_negotiation.mak353
-rw-r--r--modules/mappers/mod_rewrite.c5406
-rw-r--r--modules/mappers/mod_rewrite.dep65
-rw-r--r--modules/mappers/mod_rewrite.dsp111
-rw-r--r--modules/mappers/mod_rewrite.exp1
-rw-r--r--modules/mappers/mod_rewrite.h42
-rw-r--r--modules/mappers/mod_rewrite.mak353
-rw-r--r--modules/mappers/mod_speling.c528
-rw-r--r--modules/mappers/mod_speling.dep51
-rw-r--r--modules/mappers/mod_speling.dsp111
-rw-r--r--modules/mappers/mod_speling.exp1
-rw-r--r--modules/mappers/mod_speling.mak353
-rw-r--r--modules/mappers/mod_userdir.c390
-rw-r--r--modules/mappers/mod_userdir.dep46
-rw-r--r--modules/mappers/mod_userdir.dsp111
-rw-r--r--modules/mappers/mod_userdir.exp1
-rw-r--r--modules/mappers/mod_userdir.mak353
-rw-r--r--modules/mappers/mod_vhost_alias.c457
-rw-r--r--modules/mappers/mod_vhost_alias.dep50
-rw-r--r--modules/mappers/mod_vhost_alias.dsp111
-rw-r--r--modules/mappers/mod_vhost_alias.exp1
-rw-r--r--modules/mappers/mod_vhost_alias.mak353
-rw-r--r--modules/md/Makefile.in20
-rw-r--r--modules/md/config2.m4311
-rw-r--r--modules/md/md.h330
-rw-r--r--modules/md/md_acme.c797
-rw-r--r--modules/md/md_acme.h317
-rw-r--r--modules/md/md_acme_acct.c749
-rw-r--r--modules/md/md_acme_acct.h148
-rw-r--r--modules/md/md_acme_authz.c697
-rw-r--r--modules/md/md_acme_authz.h79
-rw-r--r--modules/md/md_acme_drive.c1106
-rw-r--r--modules/md/md_acme_drive.h55
-rw-r--r--modules/md/md_acme_order.c562
-rw-r--r--modules/md/md_acme_order.h91
-rw-r--r--modules/md/md_acmev2_drive.c181
-rw-r--r--modules/md/md_acmev2_drive.h27
-rw-r--r--modules/md/md_core.c462
-rw-r--r--modules/md/md_crypt.c2140
-rw-r--r--modules/md/md_crypt.h253
-rw-r--r--modules/md/md_curl.c653
-rw-r--r--modules/md/md_curl.h24
-rw-r--r--modules/md/md_event.c89
-rw-r--r--modules/md/md_event.h46
-rw-r--r--modules/md/md_http.c397
-rw-r--r--modules/md/md_http.h272
-rw-r--r--modules/md/md_json.c1311
-rw-r--r--modules/md/md_json.h157
-rw-r--r--modules/md/md_jws.c148
-rw-r--r--modules/md/md_jws.h52
-rw-r--r--modules/md/md_log.c78
-rw-r--r--modules/md/md_log.h60
-rw-r--r--modules/md/md_ocsp.c1063
-rw-r--r--modules/md/md_ocsp.h71
-rw-r--r--modules/md/md_reg.c1323
-rw-r--r--modules/md/md_reg.h313
-rw-r--r--modules/md/md_result.c285
-rw-r--r--modules/md/md_result.h87
-rw-r--r--modules/md/md_status.c653
-rw-r--r--modules/md/md_status.h126
-rw-r--r--modules/md/md_store.c385
-rw-r--r--modules/md/md_store.h343
-rw-r--r--modules/md/md_store_fs.c1169
-rw-r--r--modules/md/md_store_fs.h65
-rw-r--r--modules/md/md_tailscale.c383
-rw-r--r--modules/md/md_tailscale.h25
-rw-r--r--modules/md/md_time.c325
-rw-r--r--modules/md/md_time.h77
-rw-r--r--modules/md/md_util.c1561
-rw-r--r--modules/md/md_util.h253
-rw-r--r--modules/md/md_version.h43
-rw-r--r--modules/md/mod_md.c1535
-rw-r--r--modules/md/mod_md.dep5
-rw-r--r--modules/md/mod_md.dsp223
-rw-r--r--modules/md/mod_md.h20
-rw-r--r--modules/md/mod_md.mak618
-rw-r--r--modules/md/mod_md_config.c1388
-rw-r--r--modules/md/mod_md_config.h132
-rw-r--r--modules/md/mod_md_drive.c345
-rw-r--r--modules/md/mod_md_drive.h35
-rw-r--r--modules/md/mod_md_ocsp.c272
-rw-r--r--modules/md/mod_md_ocsp.h33
-rw-r--r--modules/md/mod_md_os.c88
-rw-r--r--modules/md/mod_md_os.h37
-rw-r--r--modules/md/mod_md_private.h24
-rw-r--r--modules/md/mod_md_status.c987
-rw-r--r--modules/md/mod_md_status.h27
-rw-r--r--modules/metadata/.indent.pro54
-rw-r--r--modules/metadata/Makefile.in3
-rw-r--r--modules/metadata/NWGNUcernmeta248
-rw-r--r--modules/metadata/NWGNUexpires248
-rw-r--r--modules/metadata/NWGNUheaders249
-rw-r--r--modules/metadata/NWGNUmakefile254
-rw-r--r--modules/metadata/NWGNUmimemagi248
-rw-r--r--modules/metadata/NWGNUmodident248
-rw-r--r--modules/metadata/NWGNUmodversion248
-rw-r--r--modules/metadata/NWGNUremoteip248
-rw-r--r--modules/metadata/NWGNUuniqueid257
-rw-r--r--modules/metadata/NWGNUusertrk248
-rw-r--r--modules/metadata/config.m424
-rw-r--r--modules/metadata/mod_cern_meta.c371
-rw-r--r--modules/metadata/mod_cern_meta.dep55
-rw-r--r--modules/metadata/mod_cern_meta.dsp111
-rw-r--r--modules/metadata/mod_cern_meta.exp1
-rw-r--r--modules/metadata/mod_cern_meta.mak353
-rw-r--r--modules/metadata/mod_env.c189
-rw-r--r--modules/metadata/mod_env.dep47
-rw-r--r--modules/metadata/mod_env.dsp111
-rw-r--r--modules/metadata/mod_env.exp1
-rw-r--r--modules/metadata/mod_env.mak353
-rw-r--r--modules/metadata/mod_expires.c571
-rw-r--r--modules/metadata/mod_expires.dep54
-rw-r--r--modules/metadata/mod_expires.dsp111
-rw-r--r--modules/metadata/mod_expires.exp1
-rw-r--r--modules/metadata/mod_expires.mak353
-rw-r--r--modules/metadata/mod_headers.c1010
-rw-r--r--modules/metadata/mod_headers.dep57
-rw-r--r--modules/metadata/mod_headers.dsp111
-rw-r--r--modules/metadata/mod_headers.exp1
-rw-r--r--modules/metadata/mod_headers.mak353
-rw-r--r--modules/metadata/mod_ident.c344
-rw-r--r--modules/metadata/mod_ident.dep52
-rw-r--r--modules/metadata/mod_ident.dsp111
-rw-r--r--modules/metadata/mod_ident.exp1
-rw-r--r--modules/metadata/mod_ident.mak353
-rw-r--r--modules/metadata/mod_mime_magic.c2471
-rw-r--r--modules/metadata/mod_mime_magic.dep58
-rw-r--r--modules/metadata/mod_mime_magic.dsp111
-rw-r--r--modules/metadata/mod_mime_magic.exp1
-rw-r--r--modules/metadata/mod_mime_magic.mak353
-rw-r--r--modules/metadata/mod_remoteip.c1267
-rw-r--r--modules/metadata/mod_remoteip.dep53
-rw-r--r--modules/metadata/mod_remoteip.dsp111
-rw-r--r--modules/metadata/mod_remoteip.mak353
-rw-r--r--modules/metadata/mod_setenvif.c658
-rw-r--r--modules/metadata/mod_setenvif.dep56
-rw-r--r--modules/metadata/mod_setenvif.dsp111
-rw-r--r--modules/metadata/mod_setenvif.exp1
-rw-r--r--modules/metadata/mod_setenvif.mak353
-rw-r--r--modules/metadata/mod_unique_id.c336
-rw-r--r--modules/metadata/mod_unique_id.dep50
-rw-r--r--modules/metadata/mod_unique_id.dsp111
-rw-r--r--modules/metadata/mod_unique_id.exp1
-rw-r--r--modules/metadata/mod_unique_id.mak353
-rw-r--r--modules/metadata/mod_usertrack.c504
-rw-r--r--modules/metadata/mod_usertrack.dep51
-rw-r--r--modules/metadata/mod_usertrack.dsp111
-rw-r--r--modules/metadata/mod_usertrack.exp1
-rw-r--r--modules/metadata/mod_usertrack.mak353
-rw-r--r--modules/metadata/mod_version.c313
-rw-r--r--modules/metadata/mod_version.dep45
-rw-r--r--modules/metadata/mod_version.dsp111
-rw-r--r--modules/metadata/mod_version.exp1
-rw-r--r--modules/metadata/mod_version.mak353
-rw-r--r--modules/proxy/.indent.pro58
-rw-r--r--modules/proxy/CHANGES223
-rw-r--r--modules/proxy/Makefile.in4
-rw-r--r--modules/proxy/NWGNUmakefile259
-rw-r--r--modules/proxy/NWGNUproxy337
-rw-r--r--modules/proxy/NWGNUproxyajp264
-rw-r--r--modules/proxy/NWGNUproxybalancer260
-rw-r--r--modules/proxy/NWGNUproxycon251
-rw-r--r--modules/proxy/NWGNUproxyexpress256
-rw-r--r--modules/proxy/NWGNUproxyfcgi261
-rw-r--r--modules/proxy/NWGNUproxyftp260
-rw-r--r--modules/proxy/NWGNUproxyhcheck254
-rw-r--r--modules/proxy/NWGNUproxyhtp260
-rw-r--r--modules/proxy/NWGNUproxylbm_busy250
-rw-r--r--modules/proxy/NWGNUproxylbm_hb251
-rw-r--r--modules/proxy/NWGNUproxylbm_req251
-rw-r--r--modules/proxy/NWGNUproxylbm_traf251
-rw-r--r--modules/proxy/NWGNUproxyscgi260
-rw-r--r--modules/proxy/NWGNUproxywstunnel250
-rw-r--r--modules/proxy/ajp.h522
-rw-r--r--modules/proxy/ajp_header.c893
-rw-r--r--modules/proxy/ajp_header.h195
-rw-r--r--modules/proxy/ajp_link.c115
-rw-r--r--modules/proxy/ajp_msg.c641
-rw-r--r--modules/proxy/ajp_utils.c137
-rw-r--r--modules/proxy/balancers/Makefile.in3
-rw-r--r--modules/proxy/balancers/config2.m48
-rw-r--r--modules/proxy/balancers/mod_lbmethod_bybusyness.c124
-rw-r--r--modules/proxy/balancers/mod_lbmethod_bybusyness.dep76
-rw-r--r--modules/proxy/balancers/mod_lbmethod_bybusyness.dsp123
-rw-r--r--modules/proxy/balancers/mod_lbmethod_bybusyness.mak408
-rw-r--r--modules/proxy/balancers/mod_lbmethod_byrequests.c165
-rw-r--r--modules/proxy/balancers/mod_lbmethod_byrequests.dep76
-rw-r--r--modules/proxy/balancers/mod_lbmethod_byrequests.dsp123
-rw-r--r--modules/proxy/balancers/mod_lbmethod_byrequests.mak408
-rw-r--r--modules/proxy/balancers/mod_lbmethod_bytraffic.c135
-rw-r--r--modules/proxy/balancers/mod_lbmethod_bytraffic.dep76
-rw-r--r--modules/proxy/balancers/mod_lbmethod_bytraffic.dsp123
-rw-r--r--modules/proxy/balancers/mod_lbmethod_bytraffic.mak408
-rw-r--r--modules/proxy/balancers/mod_lbmethod_heartbeat.c466
-rw-r--r--modules/proxy/balancers/mod_lbmethod_heartbeat.dep77
-rw-r--r--modules/proxy/balancers/mod_lbmethod_heartbeat.dsp123
-rw-r--r--modules/proxy/balancers/mod_lbmethod_heartbeat.mak408
-rw-r--r--modules/proxy/config.m479
-rw-r--r--modules/proxy/libproxy.exp1
-rw-r--r--modules/proxy/mod_proxy.c3474
-rw-r--r--modules/proxy/mod_proxy.dep153
-rw-r--r--modules/proxy/mod_proxy.dsp127
-rw-r--r--modules/proxy/mod_proxy.h1510
-rw-r--r--modules/proxy/mod_proxy.mak361
-rw-r--r--modules/proxy/mod_proxy_ajp.c874
-rw-r--r--modules/proxy/mod_proxy_ajp.dep356
-rw-r--r--modules/proxy/mod_proxy_ajp.dsp151
-rw-r--r--modules/proxy/mod_proxy_ajp.mak416
-rw-r--r--modules/proxy/mod_proxy_balancer.c2087
-rw-r--r--modules/proxy/mod_proxy_balancer.dep76
-rw-r--r--modules/proxy/mod_proxy_balancer.dsp123
-rw-r--r--modules/proxy/mod_proxy_balancer.mak380
-rw-r--r--modules/proxy/mod_proxy_connect.c404
-rw-r--r--modules/proxy/mod_proxy_connect.dep73
-rw-r--r--modules/proxy/mod_proxy_connect.dsp123
-rw-r--r--modules/proxy/mod_proxy_connect.mak380
-rw-r--r--modules/proxy/mod_proxy_express.c250
-rw-r--r--modules/proxy/mod_proxy_express.dep74
-rw-r--r--modules/proxy/mod_proxy_express.dsp123
-rw-r--r--modules/proxy/mod_proxy_express.mak380
-rw-r--r--modules/proxy/mod_proxy_fcgi.c1330
-rw-r--r--modules/proxy/mod_proxy_fcgi.dep75
-rw-r--r--modules/proxy/mod_proxy_fcgi.dsp123
-rw-r--r--modules/proxy/mod_proxy_fcgi.mak380
-rw-r--r--modules/proxy/mod_proxy_fdpass.c241
-rw-r--r--modules/proxy/mod_proxy_fdpass.h41
-rw-r--r--modules/proxy/mod_proxy_ftp.c2138
-rw-r--r--modules/proxy/mod_proxy_ftp.dep74
-rw-r--r--modules/proxy/mod_proxy_ftp.dsp123
-rw-r--r--modules/proxy/mod_proxy_ftp.mak380
-rw-r--r--modules/proxy/mod_proxy_hcheck.c1351
-rw-r--r--modules/proxy/mod_proxy_hcheck.dep5
-rw-r--r--modules/proxy/mod_proxy_hcheck.dsp123
-rw-r--r--modules/proxy/mod_proxy_hcheck.mak380
-rw-r--r--modules/proxy/mod_proxy_http.c2132
-rw-r--r--modules/proxy/mod_proxy_http.dep73
-rw-r--r--modules/proxy/mod_proxy_http.dsp123
-rw-r--r--modules/proxy/mod_proxy_http.mak380
-rw-r--r--modules/proxy/mod_proxy_scgi.c674
-rw-r--r--modules/proxy/mod_proxy_scgi.dep75
-rw-r--r--modules/proxy/mod_proxy_scgi.dsp123
-rw-r--r--modules/proxy/mod_proxy_scgi.mak380
-rw-r--r--modules/proxy/mod_proxy_uwsgi.c576
-rw-r--r--modules/proxy/mod_proxy_uwsgi.dep75
-rw-r--r--modules/proxy/mod_proxy_uwsgi.dsp123
-rw-r--r--modules/proxy/mod_proxy_uwsgi.mak380
-rw-r--r--modules/proxy/mod_proxy_wstunnel.c499
-rw-r--r--modules/proxy/mod_proxy_wstunnel.dep73
-rw-r--r--modules/proxy/mod_proxy_wstunnel.dsp123
-rw-r--r--modules/proxy/mod_proxy_wstunnel.mak380
-rw-r--r--modules/proxy/proxy_util.c5080
-rw-r--r--modules/proxy/proxy_util.h45
-rw-r--r--modules/proxy/scgi.h36
-rw-r--r--modules/session/Makefile.in4
-rw-r--r--modules/session/NWGNUmakefile257
-rw-r--r--modules/session/NWGNUsession254
-rw-r--r--modules/session/NWGNUsession_cookie253
-rw-r--r--modules/session/NWGNUsession_crypto255
-rw-r--r--modules/session/NWGNUsession_dbd253
-rw-r--r--modules/session/config.m468
-rw-r--r--modules/session/mod_session.c711
-rw-r--r--modules/session/mod_session.dep56
-rw-r--r--modules/session/mod_session.dsp115
-rw-r--r--modules/session/mod_session.h189
-rw-r--r--modules/session/mod_session.mak353
-rw-r--r--modules/session/mod_session_cookie.c284
-rw-r--r--modules/session/mod_session_cookie.dep49
-rw-r--r--modules/session/mod_session_cookie.dsp111
-rw-r--r--modules/session/mod_session_cookie.mak381
-rw-r--r--modules/session/mod_session_crypto.c807
-rw-r--r--modules/session/mod_session_crypto.dep57
-rw-r--r--modules/session/mod_session_crypto.dsp111
-rw-r--r--modules/session/mod_session_crypto.mak381
-rw-r--r--modules/session/mod_session_dbd.c640
-rw-r--r--modules/session/mod_session_dbd.dep60
-rw-r--r--modules/session/mod_session_dbd.dsp111
-rw-r--r--modules/session/mod_session_dbd.mak409
-rw-r--r--modules/slotmem/Makefile.in3
-rw-r--r--modules/slotmem/NWGNUmakefile246
-rw-r--r--modules/slotmem/NWGNUslotmem_plain250
-rw-r--r--modules/slotmem/NWGNUslotmem_shm250
-rw-r--r--modules/slotmem/config.m410
-rw-r--r--modules/slotmem/mod_slotmem_plain.c343
-rw-r--r--modules/slotmem/mod_slotmem_plain.dep51
-rw-r--r--modules/slotmem/mod_slotmem_plain.dsp111
-rw-r--r--modules/slotmem/mod_slotmem_plain.mak353
-rw-r--r--modules/slotmem/mod_slotmem_shm.c788
-rw-r--r--modules/slotmem/mod_slotmem_shm.dep57
-rw-r--r--modules/slotmem/mod_slotmem_shm.dsp111
-rw-r--r--modules/slotmem/mod_slotmem_shm.mak353
-rw-r--r--modules/ssl/Makefile.in20
-rw-r--r--modules/ssl/NWGNUmakefile327
-rw-r--r--modules/ssl/README106
-rw-r--r--modules/ssl/README.dsov.fig346
-rw-r--r--modules/ssl/README.dsov.ps1138
-rw-r--r--modules/ssl/config.m457
-rw-r--r--modules/ssl/mod_ssl.c775
-rw-r--r--modules/ssl/mod_ssl.dep1086
-rw-r--r--modules/ssl/mod_ssl.dsp195
-rw-r--r--modules/ssl/mod_ssl.h120
-rw-r--r--modules/ssl/mod_ssl.mak500
-rw-r--r--modules/ssl/mod_ssl_openssl.h113
-rw-r--r--modules/ssl/ssl_engine_config.c2158
-rw-r--r--modules/ssl/ssl_engine_init.c2367
-rw-r--r--modules/ssl/ssl_engine_io.c2419
-rw-r--r--modules/ssl/ssl_engine_kernel.c2855
-rw-r--r--modules/ssl/ssl_engine_log.c246
-rw-r--r--modules/ssl/ssl_engine_mutex.c111
-rw-r--r--modules/ssl/ssl_engine_ocsp.c312
-rw-r--r--modules/ssl/ssl_engine_pphrase.c911
-rw-r--r--modules/ssl/ssl_engine_rand.c177
-rw-r--r--modules/ssl/ssl_engine_vars.c1230
-rw-r--r--modules/ssl/ssl_private.h1174
-rw-r--r--modules/ssl/ssl_scache.c239
-rw-r--r--modules/ssl/ssl_util.c485
-rw-r--r--modules/ssl/ssl_util_ocsp.c422
-rw-r--r--modules/ssl/ssl_util_ssl.c591
-rw-r--r--modules/ssl/ssl_util_ssl.h107
-rw-r--r--modules/ssl/ssl_util_stapling.c975
-rw-r--r--modules/test/.indent.pro54
-rw-r--r--modules/test/Makefile.in3
-rw-r--r--modules/test/NWGNUmakefile257
-rw-r--r--modules/test/NWGNUoptfnexport256
-rw-r--r--modules/test/NWGNUoptfnimport256
-rw-r--r--modules/test/NWGNUopthookexport256
-rw-r--r--modules/test/NWGNUopthookimport256
-rw-r--r--modules/test/README1
-rw-r--r--modules/test/config.m413
-rw-r--r--modules/test/mod_dialup.c306
-rw-r--r--modules/test/mod_optional_fn_export.c48
-rw-r--r--modules/test/mod_optional_fn_export.h19
-rw-r--r--modules/test/mod_optional_fn_import.c55
-rw-r--r--modules/test/mod_optional_hook_export.c44
-rw-r--r--modules/test/mod_optional_hook_export.h24
-rw-r--r--modules/test/mod_optional_hook_import.c45
-rw-r--r--modules/tls/Makefile.in20
-rw-r--r--modules/tls/config2.m4172
-rw-r--r--modules/tls/mod_tls.c288
-rw-r--r--modules/tls/mod_tls.h19
-rw-r--r--modules/tls/tls_cache.c310
-rw-r--r--modules/tls/tls_cache.h63
-rw-r--r--modules/tls/tls_cert.c564
-rw-r--r--modules/tls/tls_cert.h211
-rw-r--r--modules/tls/tls_conf.c780
-rw-r--r--modules/tls/tls_conf.h185
-rw-r--r--modules/tls/tls_core.c1433
-rw-r--r--modules/tls/tls_core.h184
-rw-r--r--modules/tls/tls_filter.c1017
-rw-r--r--modules/tls/tls_filter.h90
-rw-r--r--modules/tls/tls_ocsp.c120
-rw-r--r--modules/tls/tls_ocsp.h47
-rw-r--r--modules/tls/tls_proto.c603
-rw-r--r--modules/tls/tls_proto.h124
-rw-r--r--modules/tls/tls_util.c367
-rw-r--r--modules/tls/tls_util.h157
-rw-r--r--modules/tls/tls_var.c397
-rw-r--r--modules/tls/tls_var.h39
-rw-r--r--modules/tls/tls_version.h39
1117 files changed, 334205 insertions, 0 deletions
diff --git a/modules/Makefile.in b/modules/Makefile.in
new file mode 100644
index 0000000..1320ec2
--- /dev/null
+++ b/modules/Makefile.in
@@ -0,0 +1,6 @@
+
+SUBDIRS = $(MODULE_DIRS)
+CLEAN_SUBDIRS = $(MODULE_CLEANDIRS)
+
+include $(top_builddir)/build/rules.mk
+
diff --git a/modules/NWGNUmakefile b/modules/NWGNUmakefile
new file mode 100644
index 0000000..ac8ee3e
--- /dev/null
+++ b/modules/NWGNUmakefile
@@ -0,0 +1,121 @@
+#
+# To build with exerimental modules set the environment
+# variable WITH_EXPERIMENTAL=1
+# To build with the mod_ssl module set the environment
+# variable WITH_SSL=1
+# To build with the mod_lua module set the environment
+# variable WITH_LUA=1
+# To build with the mod_http2 module set the environment
+# variable WITH_HTTP2=1
+#
+# Check if LDAP is enabled in APR-UTIL
+#
+include $(AP_WORK)/build/NWGNUenvironment.inc
+ifeq "$(wildcard $(APRUTIL)/include/apr_ldap.h)" "$(APRUTIL)/include/apr_ldap.h"
+WITH_LDAP = $(shell $(AWK) '/^\#define APR_HAS_LDAP /{print $$3}' $(APRUTIL)/include/apr_ldap.h)
+else
+WITH_LDAP = 1
+ifneq "$(MAKECMDGOALS)" "clean"
+ifneq "$(findstring clobber_,$(MAKECMDGOALS))" "clobber_"
+WITH_LDAP = 0
+endif
+endif
+endif
+
+# If USE_STDSOCKETS is defined we always build mod_ssl
+ifdef USE_STDSOCKETS
+WITH_SSL = 1
+endif
+
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ aaa \
+ cache \
+ cluster \
+ core \
+ dav/main \
+ dav/fs \
+ dav/lock \
+ echo \
+ examples \
+ generators \
+ loggers \
+ mappers \
+ metadata \
+ proxy \
+ filters \
+ database \
+ session \
+ slotmem \
+ $(EOLIST)
+
+# If WITH_LDAP and LDAPSDK have been defined then build the util_ldap module
+ifeq "$(WITH_LDAP)" "1"
+ifneq "$(LDAPSDK)" ""
+SUBDIRS += ldap
+endif
+endif
+
+# If WITH_SSL and OSSLSDK have been defined then build the mod_ssl module
+ifeq "$(WITH_SSL)" "1"
+ifneq "$(OSSLSDK)" ""
+SUBDIRS += ssl
+endif
+endif
+
+# If WITH_LUA and LUASRC have been defined then build the mod_lua module
+ifeq "$(WITH_LUA)" "1"
+ifneq "$(LUASRC)" ""
+SUBDIRS += lua
+endif
+endif
+
+# Allow the mod_http2 module to be built if WITH_HTTP2 is defined
+ifeq "$(WITH_HTTP2)" "1"
+ifneq "$(NGH2SRC)" ""
+SUBDIRS += http2
+endif
+endif
+
+# Allow the experimental modules to be built if WITH_EXPERIMENTAL is defined
+ifeq "$(WITH_EXPERIMENTAL)" "1"
+SUBDIRS += experimental
+endif
+
+# Allow the debugging modules to be built if WITH_DEBUGGING is defined
+ifeq "$(WITH_DEBUGGING)" "1"
+SUBDIRS += debugging
+endif
+
+# Allow the test modules to be built if WITH_TEST is defined
+ifeq "$(WITH_TEST)" "1"
+SUBDIRS += test
+endif
+
+#If the mod_edir directory exists then build the mod_edir module
+ifeq "$(wildcard $(STDMOD)/mod_edir)" "$(STDMOD)/mod_edir"
+SUBDIRS += mod_edir
+endif
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+ifeq "$(wildcard NWGNUmakefile.mak)" "NWGNUmakefile.mak"
+include NWGNUmakefile.mak
+endif
+
+#
+# You can use this target if all that is needed is to copy files to the
+# installation area
+#
+install :: nlms FORCE
+
diff --git a/modules/README b/modules/README
new file mode 100644
index 0000000..2dee079
--- /dev/null
+++ b/modules/README
@@ -0,0 +1,67 @@
+The directory structure for this level is as follows:
+
+aaa/
+ This directory contains modules dealing with authorization and
+ authentication.
+
+arch/
+
+cache/
+ This directory houses modules that implement file and data caching
+ capability.
+
+database/
+ The apache DBD framework manages connections to SQL backends efficiently.
+
+cluster/
+ Modules for working with multiple servers.
+
+dav/
+ This directory houses modules that implement WebDAV functionality.
+
+echo/
+
+examples/
+ This directory contains some sample code that should help you on your
+ way to develop your own Apache modules.
+
+experimental/
+ In this directory we've placed some modules which we think
+ provide some pretty interesting functionality, but which
+ are still in the early stages of development and could
+ evolve radically in the future. This code isn't supported
+ officially.
+
+filters/
+ This directory houses modules that perform general inline data filtering.
+
+generators/
+ This directory houses modules that perform data generation functions.
+
+http/
+ This directory houses modules that basic HTTP protocol implementation.
+
+http2/
+ This directory houses modules that provide HTTP/2 protocol implementation.
+
+loggers/
+ This directory houses modules that handle logging functions.
+
+mappers/
+ This directory houses modules that handle URL mapping and
+ rewriting.
+
+metadata/
+ This directory houses modules that deal with Header metadata.
+
+proxy/
+ This houses the code for the proxy module for Apache.
+
+ssl/
+ This directory houses code for OpenSSL functionality.
+
+test/
+ This directory houses modules which test various components
+ of Apache. You should not compile these into a production
+ server.
+
diff --git a/modules/aaa/.indent.pro b/modules/aaa/.indent.pro
new file mode 100644
index 0000000..a9fbe9f
--- /dev/null
+++ b/modules/aaa/.indent.pro
@@ -0,0 +1,54 @@
+-i4 -npsl -di0 -br -nce -d0 -cli0 -npcs -nfc1
+-TBUFF
+-TFILE
+-TTRANS
+-TUINT4
+-T_trans
+-Tallow_options_t
+-Tapache_sfio
+-Tarray_header
+-Tbool_int
+-Tbuf_area
+-Tbuff_struct
+-Tbuffy
+-Tcmd_how
+-Tcmd_parms
+-Tcommand_rec
+-Tcommand_struct
+-Tconn_rec
+-Tcore_dir_config
+-Tcore_server_config
+-Tdir_maker_func
+-Tevent
+-Tglobals_s
+-Thandler_func
+-Thandler_rec
+-Tjoblist_s
+-Tlisten_rec
+-Tmerger_func
+-Tmode_t
+-Tmodule
+-Tmodule_struct
+-Tmutex
+-Tn_long
+-Tother_child_rec
+-Toverrides_t
+-Tparent_score
+-Tpid_t
+-Tpiped_log
+-Tpool
+-Trequest_rec
+-Trequire_line
+-Trlim_t
+-Tscoreboard
+-Tsemaphore
+-Tserver_addr_rec
+-Tserver_rec
+-Tserver_rec_chain
+-Tshort_score
+-Ttable
+-Ttable_entry
+-Tthread
+-Tu_wide_int
+-Tvtime_t
+-Twide_int
diff --git a/modules/aaa/Makefile.in b/modules/aaa/Makefile.in
new file mode 100644
index 0000000..167b343
--- /dev/null
+++ b/modules/aaa/Makefile.in
@@ -0,0 +1,3 @@
+
+include $(top_srcdir)/build/special.mk
+
diff --git a/modules/aaa/NWGNUaccesscompat b/modules/aaa/NWGNUaccesscompat
new file mode 100644
index 0000000..bfa7b20
--- /dev/null
+++ b/modules/aaa/NWGNUaccesscompat
@@ -0,0 +1,248 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = accesscompat
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) host access control compatibility Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = AccessCompModule
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/accesscompat.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_access_compat.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ access_compat_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/aaa/NWGNUallowmethods b/modules/aaa/NWGNUallowmethods
new file mode 100644
index 0000000..c2373f5
--- /dev/null
+++ b/modules/aaa/NWGNUallowmethods
@@ -0,0 +1,248 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = allowmethods
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Method Restriction Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Allowmethods Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/allowmethods.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_allowmethods.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ allowmethods_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/aaa/NWGNUauthbasc b/modules/aaa/NWGNUauthbasc
new file mode 100644
index 0000000..1f43464
--- /dev/null
+++ b/modules/aaa/NWGNUauthbasc
@@ -0,0 +1,248 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = authbasc
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Basic Authentication Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = AuthBasic Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/authbasc.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_auth_basic.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ auth_basic_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/aaa/NWGNUauthdigt b/modules/aaa/NWGNUauthdigt
new file mode 100644
index 0000000..39738f2
--- /dev/null
+++ b/modules/aaa/NWGNUauthdigt
@@ -0,0 +1,248 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = authdigt
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Digest Authentication Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Digest Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/authdigt.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_auth_digest.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ auth_digest_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/aaa/NWGNUauthform b/modules/aaa/NWGNUauthform
new file mode 100644
index 0000000..5b42ab5
--- /dev/null
+++ b/modules/aaa/NWGNUauthform
@@ -0,0 +1,250 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(STDMOD)/session \
+ $(STDMOD)/filters \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = authform
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Basic Authentication Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = AuthBasic Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_auth_form.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ auth_form_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/aaa/NWGNUauthnano b/modules/aaa/NWGNUauthnano
new file mode 100644
index 0000000..b115314
--- /dev/null
+++ b/modules/aaa/NWGNUauthnano
@@ -0,0 +1,248 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = authnano
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Anonymous Authentication Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = AuthAnon Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/authnano.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_authn_anon.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ authn_anon_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/aaa/NWGNUauthndbd b/modules/aaa/NWGNUauthndbd
new file mode 100644
index 0000000..151bc1c
--- /dev/null
+++ b/modules/aaa/NWGNUauthndbd
@@ -0,0 +1,249 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(AP_WORK)/modules/database \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = authndbd
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) SQL Database Authentication Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = AuthnDBD Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/authndbd.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_authn_dbd.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ authn_dbd_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/aaa/NWGNUauthndbm b/modules/aaa/NWGNUauthndbm
new file mode 100644
index 0000000..1f9880e
--- /dev/null
+++ b/modules/aaa/NWGNUauthndbm
@@ -0,0 +1,248 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = authndbm
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Database Authentication Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = AuthnDBM Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/authndbm.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_authn_dbm.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ authn_dbm_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/aaa/NWGNUauthnfil b/modules/aaa/NWGNUauthnfil
new file mode 100644
index 0000000..cfab287
--- /dev/null
+++ b/modules/aaa/NWGNUauthnfil
@@ -0,0 +1,248 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = authnfil
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) File Authentication Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = AuthnFile Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/authnfil.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_authn_file.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ authn_file_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/aaa/NWGNUauthnsocache b/modules/aaa/NWGNUauthnsocache
new file mode 100644
index 0000000..8d1b347
--- /dev/null
+++ b/modules/aaa/NWGNUauthnsocache
@@ -0,0 +1,248 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = authnsocache
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Authentication Cache Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = AuthnSOCache Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/authnsocache.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_authn_socache.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ authn_socache_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/aaa/NWGNUauthnzldap b/modules/aaa/NWGNUauthnzldap
new file mode 100644
index 0000000..bfe21db
--- /dev/null
+++ b/modules/aaa/NWGNUauthnzldap
@@ -0,0 +1,264 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+#LDAP client requires the use of Winsock
+#
+ifdef USE_STDSOCKETS
+XDEFINES += -DUSE_WINSOCK \
+ $(EOLIST)
+endif
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = authnzldap
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) LDAP Authentication Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = AuthnzLDAP Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/authnzldap.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_authnz_ldap.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ lldapsdk \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ util_ldap_connection_find \
+ util_ldap_connection_close \
+ util_ldap_cache_checkuserid \
+ util_ldap_cache_getuserdn \
+ util_ldap_cache_compare \
+ util_ldap_cache_comparedn \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ @lldapsdk.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ authnz_ldap_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/aaa/NWGNUauthzdbd b/modules/aaa/NWGNUauthzdbd
new file mode 100644
index 0000000..3ea2ba7
--- /dev/null
+++ b/modules/aaa/NWGNUauthzdbd
@@ -0,0 +1,249 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(STDMOD)/database \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = authzdbd
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) DBD Database Authorization Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = AuthzDBD Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/authzdbd.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_authz_dbd.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ authz_dbd_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/aaa/NWGNUauthzdbm b/modules/aaa/NWGNUauthzdbm
new file mode 100644
index 0000000..3d0ed8f
--- /dev/null
+++ b/modules/aaa/NWGNUauthzdbm
@@ -0,0 +1,248 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = authzdbm
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Database Authorization Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = AuthzDBM Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/authzdbm.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_authz_dbm.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ authz_dbm_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/aaa/NWGNUauthzgrp b/modules/aaa/NWGNUauthzgrp
new file mode 100644
index 0000000..4a8df2c
--- /dev/null
+++ b/modules/aaa/NWGNUauthzgrp
@@ -0,0 +1,247 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = authzgrp
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Group File Authorization Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = AuthzGrp Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/authzgrp.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_authz_groupfile.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ authz_groupfile_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
diff --git a/modules/aaa/NWGNUauthzusr b/modules/aaa/NWGNUauthzusr
new file mode 100644
index 0000000..aa79053
--- /dev/null
+++ b/modules/aaa/NWGNUauthzusr
@@ -0,0 +1,247 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = authzusr
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) User Authorization Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = AuthzUser Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/authzusr.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_authz_user.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ authz_user_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
diff --git a/modules/aaa/NWGNUmakefile b/modules/aaa/NWGNUmakefile
new file mode 100644
index 0000000..fdb23f6
--- /dev/null
+++ b/modules/aaa/NWGNUmakefile
@@ -0,0 +1,270 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+ifeq "$(wildcard $(APRUTIL)/include/apr_ldap.h)" "$(APRUTIL)/include/apr_ldap.h"
+WITH_LDAP = $(shell $(AWK) '/^\#define APR_HAS_LDAP /{print $$3}' $(APRUTIL)/include/apr_ldap.h)
+else
+WITH_LDAP = 0
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME =
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION =
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME =
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE =
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/authbasc.nlm \
+ $(OBJDIR)/authdigt.nlm \
+ $(OBJDIR)/authform.nlm \
+ $(OBJDIR)/authnano.nlm \
+ $(OBJDIR)/authndbd.nlm \
+ $(OBJDIR)/authndbm.nlm \
+ $(OBJDIR)/authnfil.nlm \
+ $(OBJDIR)/authnsocache.nlm \
+ $(OBJDIR)/authzdbd.nlm \
+ $(OBJDIR)/authzdbm.nlm \
+ $(OBJDIR)/authzgrp.nlm \
+ $(OBJDIR)/authzusr.nlm \
+ $(OBJDIR)/allowmethods.nlm \
+ $(OBJDIR)/accesscompat.nlm \
+ $(EOLIST)
+
+# If WITH_LDAP and LDAPSDK have been defined then build the authnz_ldap module
+ifeq "$(WITH_LDAP)" "1"
+ifneq "$(LDAPSDK)" ""
+TARGET_nlm += $(OBJDIR)/authnzldap.nlm
+endif
+endif
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/aaa/config.m4 b/modules/aaa/config.m4
new file mode 100644
index 0000000..b443761
--- /dev/null
+++ b/modules/aaa/config.m4
@@ -0,0 +1,84 @@
+dnl modules enabled in this directory by default
+
+dnl Authentication (authn), Access, and Authorization (authz)
+
+dnl APACHE_MODULE(name, helptext[, objects[, structname[, default[, config]]]])
+
+APACHE_MODPATH_INIT(aaa)
+
+dnl Authentication modules; modules checking a username and password against a
+dnl file, database, or other similar magic.
+dnl
+APACHE_MODULE(authn_file, file-based authentication control, , , yes)
+APACHE_MODULE(authn_dbm, DBM-based authentication control, , , most)
+APACHE_MODULE(authn_anon, anonymous user authentication control, , , most)
+APACHE_MODULE(authn_dbd, SQL-based authentication control, , , most)
+APACHE_MODULE(authn_socache, Cached authentication control, , , most)
+
+dnl General Authentication modules; module which implements the
+dnl non-authn module specific directives.
+dnl
+APACHE_MODULE(authn_core, core authentication module, , , yes)
+
+dnl Authorization modules: modules which verify a certain property such as
+dnl membership of a group, value of the IP address against a list of pre
+dnl configured directives (e.g. require, allow) or against an external file
+dnl or database.
+dnl
+APACHE_MODULE(authz_host, host-based authorization control, , , yes)
+APACHE_MODULE(authz_groupfile, 'require group' authorization control, , , yes)
+APACHE_MODULE(authz_user, 'require user' authorization control, , , yes)
+APACHE_MODULE(authz_dbm, DBM-based authorization control, , , most)
+APACHE_MODULE(authz_owner, 'require file-owner' authorization control, , , most)
+APACHE_MODULE(authz_dbd, SQL based authorization and Login/Session support, , , most)
+
+dnl General Authorization modules; provider module which implements the
+dnl non-authz module specific directives.
+dnl
+APACHE_MODULE(authz_core, core authorization provider vector module, , , yes)
+
+dnl LDAP authentication module. This module has both the authn and authz
+dnl modules in one, so as to share the LDAP server config directives.
+APACHE_MODULE(authnz_ldap, LDAP based authentication, , , most, [
+ APACHE_CHECK_APR_HAS_LDAP
+ if test "$ac_cv_APR_HAS_LDAP" = "yes" ; then
+ if test -z "$apu_config" ; then
+ LDAP_LIBS="`$apr_config --ldap-libs`"
+ else
+ LDAP_LIBS="`$apu_config --ldap-libs`"
+ fi
+ APR_ADDTO(MOD_AUTHNZ_LDAP_LDADD, [$LDAP_LIBS])
+ AC_SUBST(MOD_AUTHNZ_LDAP_LDADD)
+ else
+ AC_MSG_WARN([apr/apr-util is compiled without ldap support])
+ enable_authnz_ldap=no
+ fi
+])
+
+dnl FastCGI authorizer interface, supporting authn and authz.
+APACHE_MODULE(authnz_fcgi,
+ FastCGI authorizer-based authentication and authorization, , , no)
+
+dnl - host access control compatibility modules. Implements Order, Allow,
+dnl Deny, Satisfy for backward compatibility. These directives have been
+dnl deprecated in 2.4.
+APACHE_MODULE(access_compat, mod_access compatibility, , , yes)
+
+dnl these are the front-end authentication modules
+
+APACHE_MODULE(auth_basic, basic authentication, , , yes)
+APACHE_MODULE(auth_form, form authentication, , , most)
+APACHE_MODULE(auth_digest, RFC2617 Digest authentication, , , most, [
+ APR_CHECK_APR_DEFINE(APR_HAS_RANDOM)
+ if test $ac_cv_define_APR_HAS_RANDOM = "no"; then
+ echo "You need APR random support to use mod_auth_digest."
+ echo "Look at APR configure options --with-egd and --with-devrandom."
+ enable_auth_digest="no"
+ fi
+])
+
+APACHE_MODULE(allowmethods, restrict allowed HTTP methods, , , most)
+
+APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/$modpath_current])
+
+APACHE_MODPATH_FINISH
diff --git a/modules/aaa/mod_access_compat.c b/modules/aaa/mod_access_compat.c
new file mode 100644
index 0000000..e9f1abe
--- /dev/null
+++ b/modules/aaa/mod_access_compat.c
@@ -0,0 +1,377 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Security options etc.
+ *
+ * Module derived from code originally written by Rob McCool
+ *
+ */
+
+#include "apr_strings.h"
+#include "apr_network_io.h"
+#include "apr_md5.h"
+
+#define APR_WANT_STRFUNC
+#define APR_WANT_BYTEFUNC
+#include "apr_want.h"
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_core.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "http_request.h"
+
+#include "mod_auth.h"
+
+#if APR_HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+
+enum allowdeny_type {
+ T_ENV,
+ T_NENV,
+ T_ALL,
+ T_IP,
+ T_HOST,
+ T_FAIL
+};
+
+typedef struct {
+ apr_int64_t limited;
+ union {
+ char *from;
+ apr_ipsubnet_t *ip;
+ } x;
+ enum allowdeny_type type;
+} allowdeny;
+
+/* things in the 'order' array */
+#define DENY_THEN_ALLOW 0
+#define ALLOW_THEN_DENY 1
+#define MUTUAL_FAILURE 2
+
+typedef struct {
+ int order[METHODS];
+ apr_array_header_t *allows;
+ apr_array_header_t *denys;
+ int *satisfy; /* for every method one */
+} access_compat_dir_conf;
+
+module AP_MODULE_DECLARE_DATA access_compat_module;
+
+static void *create_access_compat_dir_config(apr_pool_t *p, char *dummy)
+{
+ int i;
+ access_compat_dir_conf *conf =
+ (access_compat_dir_conf *)apr_pcalloc(p, sizeof(access_compat_dir_conf));
+
+ for (i = 0; i < METHODS; ++i) {
+ conf->order[i] = DENY_THEN_ALLOW;
+ }
+ conf->allows = apr_array_make(p, 1, sizeof(allowdeny));
+ conf->denys = apr_array_make(p, 1, sizeof(allowdeny));
+ conf->satisfy = apr_palloc(p, sizeof(*conf->satisfy) * METHODS);
+ for (i = 0; i < METHODS; ++i) {
+ conf->satisfy[i] = SATISFY_NOSPEC;
+ }
+
+ return (void *)conf;
+}
+
+static const char *order(cmd_parms *cmd, void *dv, const char *arg)
+{
+ access_compat_dir_conf *d = (access_compat_dir_conf *) dv;
+ int i, o;
+
+ if (!strcasecmp(arg, "allow,deny"))
+ o = ALLOW_THEN_DENY;
+ else if (!strcasecmp(arg, "deny,allow"))
+ o = DENY_THEN_ALLOW;
+ else if (!strcasecmp(arg, "mutual-failure"))
+ o = MUTUAL_FAILURE;
+ else
+ return "unknown order";
+
+ for (i = 0; i < METHODS; ++i)
+ if (cmd->limited & (AP_METHOD_BIT << i))
+ d->order[i] = o;
+
+ return NULL;
+}
+
+static const char *satisfy(cmd_parms *cmd, void *dv, const char *arg)
+{
+ access_compat_dir_conf *d = (access_compat_dir_conf *) dv;
+ int satisfy = SATISFY_NOSPEC;
+ int i;
+
+ if (!strcasecmp(arg, "all")) {
+ satisfy = SATISFY_ALL;
+ }
+ else if (!strcasecmp(arg, "any")) {
+ satisfy = SATISFY_ANY;
+ }
+ else {
+ return "Satisfy either 'any' or 'all'.";
+ }
+
+ for (i = 0; i < METHODS; ++i) {
+ if (cmd->limited & (AP_METHOD_BIT << i)) {
+ d->satisfy[i] = satisfy;
+ }
+ }
+
+ return NULL;
+}
+
+static const char *allow_cmd(cmd_parms *cmd, void *dv, const char *from,
+ const char *where_c)
+{
+ access_compat_dir_conf *d = (access_compat_dir_conf *) dv;
+ allowdeny *a;
+ char *where = apr_pstrdup(cmd->pool, where_c);
+ char *s;
+ apr_status_t rv;
+
+ if (strcasecmp(from, "from"))
+ return "allow and deny must be followed by 'from'";
+
+ a = (allowdeny *) apr_array_push(cmd->info ? d->allows : d->denys);
+ a->x.from = where;
+ a->limited = cmd->limited;
+
+ if (!strncasecmp(where, "env=!", 5)) {
+ a->type = T_NENV;
+ a->x.from += 5;
+
+ }
+ else if (!strncasecmp(where, "env=", 4)) {
+ a->type = T_ENV;
+ a->x.from += 4;
+
+ }
+ else if (!strcasecmp(where, "all")) {
+ a->type = T_ALL;
+ }
+ else if ((s = ap_strchr(where, '/'))) {
+ *s++ = '\0';
+ rv = apr_ipsubnet_create(&a->x.ip, where, s, cmd->pool);
+ if(APR_STATUS_IS_EINVAL(rv)) {
+ /* looked nothing like an IP address */
+ return "An IP address was expected";
+ }
+ else if (rv != APR_SUCCESS) {
+ return apr_psprintf(cmd->pool, "%pm", &rv);
+ }
+ a->type = T_IP;
+ }
+ else if (!APR_STATUS_IS_EINVAL(rv = apr_ipsubnet_create(&a->x.ip, where,
+ NULL, cmd->pool))) {
+ if (rv != APR_SUCCESS)
+ return apr_psprintf(cmd->pool, "%pm", &rv);
+ a->type = T_IP;
+ }
+ else if (ap_strchr(where, '#')) {
+ return "No comments are allowed here";
+ }
+ else { /* no slash, didn't look like an IP address => must be a host */
+ a->type = T_HOST;
+ }
+
+ return NULL;
+}
+
+static char its_an_allow;
+
+static const command_rec access_compat_cmds[] =
+{
+ AP_INIT_TAKE1("order", order, NULL, OR_LIMIT,
+ "'allow,deny', 'deny,allow', or 'mutual-failure'"),
+ AP_INIT_ITERATE2("allow", allow_cmd, &its_an_allow, OR_LIMIT,
+ "'from' followed by hostnames or IP-address wildcards"),
+ AP_INIT_ITERATE2("deny", allow_cmd, NULL, OR_LIMIT,
+ "'from' followed by hostnames or IP-address wildcards"),
+ AP_INIT_TAKE1("Satisfy", satisfy, NULL, OR_AUTHCFG,
+ "access policy if both allow and require used ('all' or 'any')"),
+ {NULL}
+};
+
+static int in_domain(const char *domain, const char *what)
+{
+ int dl = strlen(domain);
+ int wl = strlen(what);
+
+ if ((wl - dl) >= 0) {
+ if (strcasecmp(domain, &what[wl - dl]) != 0) {
+ return 0;
+ }
+
+ /* Make sure we matched an *entire* subdomain --- if the user
+ * said 'allow from good.com', we don't want people from nogood.com
+ * to be able to get in.
+ */
+
+ if (wl == dl) {
+ return 1; /* matched whole thing */
+ }
+ else {
+ return (domain[0] == '.' || what[wl - dl - 1] == '.');
+ }
+ }
+ else {
+ return 0;
+ }
+}
+
+static int find_allowdeny(request_rec *r, apr_array_header_t *a, int method)
+{
+
+ allowdeny *ap = (allowdeny *) a->elts;
+ apr_int64_t mmask = (AP_METHOD_BIT << method);
+ int i;
+ int gothost = 0;
+ const char *remotehost = NULL;
+
+ for (i = 0; i < a->nelts; ++i) {
+ if (!(mmask & ap[i].limited)) {
+ continue;
+ }
+
+ switch (ap[i].type) {
+ case T_ENV:
+ if (apr_table_get(r->subprocess_env, ap[i].x.from)) {
+ return 1;
+ }
+ break;
+
+ case T_NENV:
+ if (!apr_table_get(r->subprocess_env, ap[i].x.from)) {
+ return 1;
+ }
+ break;
+
+ case T_ALL:
+ return 1;
+
+ case T_IP:
+ if (apr_ipsubnet_test(ap[i].x.ip, r->useragent_addr)) {
+ return 1;
+ }
+ break;
+
+ case T_HOST:
+ if (!gothost) {
+ int remotehost_is_ip;
+
+ remotehost = ap_get_useragent_host(r, REMOTE_DOUBLE_REV,
+ &remotehost_is_ip);
+
+ if ((remotehost == NULL) || remotehost_is_ip) {
+ gothost = 1;
+ }
+ else {
+ gothost = 2;
+ }
+ }
+
+ if ((gothost == 2) && in_domain(ap[i].x.from, remotehost)) {
+ return 1;
+ }
+ break;
+
+ case T_FAIL:
+ /* do nothing? */
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int access_compat_ap_satisfies(request_rec *r)
+{
+ access_compat_dir_conf *conf = (access_compat_dir_conf *)
+ ap_get_module_config(r->per_dir_config, &access_compat_module);
+
+ return conf->satisfy[r->method_number];
+}
+
+static int check_dir_access(request_rec *r)
+{
+ int method = r->method_number;
+ int ret = OK;
+ access_compat_dir_conf *a = (access_compat_dir_conf *)
+ ap_get_module_config(r->per_dir_config, &access_compat_module);
+
+ if (a->order[method] == ALLOW_THEN_DENY) {
+ ret = HTTP_FORBIDDEN;
+ if (find_allowdeny(r, a->allows, method)) {
+ ret = OK;
+ }
+ if (find_allowdeny(r, a->denys, method)) {
+ ret = HTTP_FORBIDDEN;
+ }
+ }
+ else if (a->order[method] == DENY_THEN_ALLOW) {
+ if (find_allowdeny(r, a->denys, method)) {
+ ret = HTTP_FORBIDDEN;
+ }
+ if (find_allowdeny(r, a->allows, method)) {
+ ret = OK;
+ }
+ }
+ else {
+ if (find_allowdeny(r, a->allows, method)
+ && !find_allowdeny(r, a->denys, method)) {
+ ret = OK;
+ }
+ else {
+ ret = HTTP_FORBIDDEN;
+ }
+ }
+
+ if (ret == HTTP_FORBIDDEN) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01797)
+ "client denied by server configuration: %s%s",
+ r->filename ? "" : "uri ",
+ r->filename ? r->filename : r->uri);
+ }
+
+ return ret;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ APR_REGISTER_OPTIONAL_FN(access_compat_ap_satisfies);
+
+ /* This can be access checker since we don't require r->user to be set. */
+ ap_hook_check_access(check_dir_access, NULL, NULL, APR_HOOK_MIDDLE,
+ AP_AUTH_INTERNAL_PER_CONF);
+}
+
+AP_DECLARE_MODULE(access_compat) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_access_compat_dir_config, /* dir config creater */
+ NULL, /* dir merger --- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ access_compat_cmds,
+ register_hooks /* register hooks */
+};
diff --git a/modules/aaa/mod_access_compat.dep b/modules/aaa/mod_access_compat.dep
new file mode 100644
index 0000000..e3f5e97
--- /dev/null
+++ b/modules/aaa/mod_access_compat.dep
@@ -0,0 +1,59 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_access_compat.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_access_compat.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/aaa/mod_access_compat.dsp b/modules/aaa/mod_access_compat.dsp
new file mode 100644
index 0000000..c0cef15
--- /dev/null
+++ b/modules/aaa/mod_access_compat.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_access_compat" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_access_compat - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_access_compat.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_access_compat.mak" CFG="mod_access_compat - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_access_compat - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_access_compat - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_access_compat - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "AAA_DECLARE_EXPORT" /Fd"Release\mod_access_compat_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_access_compat.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_access_compat.so" /d LONG_NAME="access_compat_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_access_compat.so" /base:@..\..\os\win32\BaseAddr.ref,mod_access_compat.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_access_compat.so" /base:@..\..\os\win32\BaseAddr.ref,mod_access_compat.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_access_compat.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_access_compat - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "AAA_DECLARE_EXPORT" /Fd"Debug\mod_access_compat_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_access_compat.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_access_compat.so" /d LONG_NAME="access_compat_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_access_compat.so" /base:@..\..\os\win32\BaseAddr.ref,mod_access_compat.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_access_compat.so" /base:@..\..\os\win32\BaseAddr.ref,mod_access_compat.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_access_compat.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_access_compat - Win32 Release"
+# Name "mod_access_compat - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_access_compat.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/aaa/mod_access_compat.mak b/modules/aaa/mod_access_compat.mak
new file mode 100644
index 0000000..4d80704
--- /dev/null
+++ b/modules/aaa/mod_access_compat.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_access_compat.dsp
+!IF "$(CFG)" == ""
+CFG=mod_access_compat - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_access_compat - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_access_compat - Win32 Release" && "$(CFG)" != "mod_access_compat - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_access_compat.mak" CFG="mod_access_compat - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_access_compat - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_access_compat - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_access_compat - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_access_compat.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_access_compat.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_access_compat.obj"
+ -@erase "$(INTDIR)\mod_access_compat.res"
+ -@erase "$(INTDIR)\mod_access_compat_src.idb"
+ -@erase "$(INTDIR)\mod_access_compat_src.pdb"
+ -@erase "$(OUTDIR)\mod_access_compat.exp"
+ -@erase "$(OUTDIR)\mod_access_compat.lib"
+ -@erase "$(OUTDIR)\mod_access_compat.pdb"
+ -@erase "$(OUTDIR)\mod_access_compat.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "AAA_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_access_compat_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_access_compat.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_access_compat.so" /d LONG_NAME="access_compat_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_access_compat.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_access_compat.pdb" /debug /out:"$(OUTDIR)\mod_access_compat.so" /implib:"$(OUTDIR)\mod_access_compat.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_access_compat.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_access_compat.obj" \
+ "$(INTDIR)\mod_access_compat.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_access_compat.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_access_compat.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_access_compat.so"
+ if exist .\Release\mod_access_compat.so.manifest mt.exe -manifest .\Release\mod_access_compat.so.manifest -outputresource:.\Release\mod_access_compat.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_access_compat - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_access_compat.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_access_compat.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_access_compat.obj"
+ -@erase "$(INTDIR)\mod_access_compat.res"
+ -@erase "$(INTDIR)\mod_access_compat_src.idb"
+ -@erase "$(INTDIR)\mod_access_compat_src.pdb"
+ -@erase "$(OUTDIR)\mod_access_compat.exp"
+ -@erase "$(OUTDIR)\mod_access_compat.lib"
+ -@erase "$(OUTDIR)\mod_access_compat.pdb"
+ -@erase "$(OUTDIR)\mod_access_compat.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "AAA_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_access_compat_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_access_compat.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_access_compat.so" /d LONG_NAME="access_compat_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_access_compat.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_access_compat.pdb" /debug /out:"$(OUTDIR)\mod_access_compat.so" /implib:"$(OUTDIR)\mod_access_compat.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_access_compat.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_access_compat.obj" \
+ "$(INTDIR)\mod_access_compat.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_access_compat.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_access_compat.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_access_compat.so"
+ if exist .\Debug\mod_access_compat.so.manifest mt.exe -manifest .\Debug\mod_access_compat.so.manifest -outputresource:.\Debug\mod_access_compat.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_access_compat.dep")
+!INCLUDE "mod_access_compat.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_access_compat.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_access_compat - Win32 Release" || "$(CFG)" == "mod_access_compat - Win32 Debug"
+
+!IF "$(CFG)" == "mod_access_compat - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_access_compat - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_access_compat - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_access_compat - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_access_compat - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_access_compat - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_access_compat - Win32 Release"
+
+
+"$(INTDIR)\mod_access_compat.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_access_compat.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_access_compat.so" /d LONG_NAME="access_compat_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_access_compat - Win32 Debug"
+
+
+"$(INTDIR)\mod_access_compat.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_access_compat.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_access_compat.so" /d LONG_NAME="access_compat_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_access_compat.c
+
+"$(INTDIR)\mod_access_compat.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/aaa/mod_allowmethods.c b/modules/aaa/mod_allowmethods.c
new file mode 100644
index 0000000..dd41196
--- /dev/null
+++ b/modules/aaa/mod_allowmethods.c
@@ -0,0 +1,158 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "httpd.h"
+#include "http_core.h"
+#include "http_config.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "http_log.h"
+#include "apr_strings.h"
+
+/**
+ * This module makes it easy to restrict what HTTP methods can be ran against
+ * a server.
+ *
+ * It provides one command:
+ * AllowMethods
+ * This command takes a list of HTTP methods to allow.
+ *
+ * The most common configuration should be like this:
+ * <Directory />
+ * AllowMethods GET HEAD OPTIONS
+ * </Directory>
+ * <Directory /special/cgi-bin>
+ * AllowMethods GET HEAD OPTIONS POST
+ * </Directory>
+ * Non-matching methods will be returned a status 405 (method not allowed)
+ *
+ * To allow all methods, and effectively turn off mod_allowmethods, use:
+ * AllowMethods reset
+ */
+
+typedef struct am_conf_t {
+ int allowed_set;
+ apr_int64_t allowed;
+} am_conf_t;
+
+module AP_MODULE_DECLARE_DATA allowmethods_module;
+
+static int am_check_access(request_rec *r)
+{
+ int method = r->method_number;
+ am_conf_t *conf;
+
+ conf = (am_conf_t *) ap_get_module_config(r->per_dir_config,
+ &allowmethods_module);
+ if (!conf || conf->allowed == 0) {
+ return DECLINED;
+ }
+
+ r->allowed = conf->allowed;
+
+ if (conf->allowed & (AP_METHOD_BIT << method)) {
+ return DECLINED;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01623)
+ "client method denied by server configuration: '%s' to %s%s",
+ r->method,
+ r->filename ? "" : "uri ",
+ r->filename ? r->filename : r->uri);
+
+ return HTTP_METHOD_NOT_ALLOWED;
+}
+
+static void *am_create_conf(apr_pool_t *p, char *dummy)
+{
+ am_conf_t *conf = apr_pcalloc(p, sizeof(am_conf_t));
+
+ conf->allowed = 0;
+ conf->allowed_set = 0;
+ return conf;
+}
+
+static void *am_merge_conf(apr_pool_t *pool, void *a, void *b)
+{
+ am_conf_t *base = (am_conf_t *)a;
+ am_conf_t *add = (am_conf_t *)b;
+ am_conf_t *conf = apr_palloc(pool, sizeof(am_conf_t));
+
+ if (add->allowed_set) {
+ conf->allowed = add->allowed;
+ conf->allowed_set = add->allowed_set;
+ }
+ else {
+ conf->allowed = base->allowed;
+ conf->allowed_set = base->allowed_set;
+ }
+
+ return conf;
+}
+
+static const char *am_allowmethods(cmd_parms *cmd, void *d, int argc,
+ char *const argv[])
+{
+ int i;
+ am_conf_t *conf = (am_conf_t *)d;
+
+ if (argc == 0) {
+ return "AllowMethods: No method or 'reset' keyword given";
+ }
+ if (argc == 1) {
+ if (strcasecmp("reset", argv[0]) == 0) {
+ conf->allowed = 0;
+ conf->allowed_set = 1;
+ return NULL;
+ }
+ }
+
+ for (i = 0; i < argc; i++) {
+ int m;
+
+ m = ap_method_number_of(argv[i]);
+ if (m == M_INVALID) {
+ return apr_pstrcat(cmd->pool, "AllowMethods: Invalid Method '",
+ argv[i], "'", NULL);
+ }
+
+ conf->allowed |= (AP_METHOD_BIT << m);
+ }
+ conf->allowed_set = 1;
+ return NULL;
+}
+
+static void am_register_hooks(apr_pool_t * p)
+{
+ ap_hook_access_checker(am_check_access, NULL, NULL, APR_HOOK_REALLY_FIRST);
+}
+
+static const command_rec am_cmds[] = {
+ AP_INIT_TAKE_ARGV("AllowMethods", am_allowmethods, NULL,
+ ACCESS_CONF,
+ "only allow specific methods"),
+ {NULL}
+};
+
+AP_DECLARE_MODULE(allowmethods) = {
+ STANDARD20_MODULE_STUFF,
+ am_create_conf,
+ am_merge_conf,
+ NULL,
+ NULL,
+ am_cmds,
+ am_register_hooks,
+};
diff --git a/modules/aaa/mod_allowmethods.dep b/modules/aaa/mod_allowmethods.dep
new file mode 100644
index 0000000..04db445
--- /dev/null
+++ b/modules/aaa/mod_allowmethods.dep
@@ -0,0 +1,56 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_allowmethods.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_allowmethods.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/aaa/mod_allowmethods.dsp b/modules/aaa/mod_allowmethods.dsp
new file mode 100644
index 0000000..446f8dc
--- /dev/null
+++ b/modules/aaa/mod_allowmethods.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_allowmethods" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_allowmethods - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_allowmethods.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_allowmethods.mak" CFG="mod_allowmethods - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_allowmethods - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_allowmethods - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_allowmethods - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_allowmethods_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_allowmethods.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_allowmethods.so" /d LONG_NAME="allowmethods_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_allowmethods.so" /base:@..\..\os\win32\BaseAddr.ref,mod_allowmethods.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_allowmethods.so" /base:@..\..\os\win32\BaseAddr.ref,mod_allowmethods.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_allowmethods.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_allowmethods - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_allowmethods_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_allowmethods.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_allowmethods.so" /d LONG_NAME="allowmethods_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_allowmethods.so" /base:@..\..\os\win32\BaseAddr.ref,mod_allowmethods.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_allowmethods.so" /base:@..\..\os\win32\BaseAddr.ref,mod_allowmethods.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_allowmethods.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_allowmethods - Win32 Release"
+# Name "mod_allowmethods - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_allowmethods.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/aaa/mod_allowmethods.mak b/modules/aaa/mod_allowmethods.mak
new file mode 100644
index 0000000..1049515
--- /dev/null
+++ b/modules/aaa/mod_allowmethods.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_allowmethods.dsp
+!IF "$(CFG)" == ""
+CFG=mod_allowmethods - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_allowmethods - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_allowmethods - Win32 Release" && "$(CFG)" != "mod_allowmethods - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_allowmethods.mak" CFG="mod_allowmethods - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_allowmethods - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_allowmethods - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_allowmethods - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_allowmethods.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_allowmethods.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_allowmethods.obj"
+ -@erase "$(INTDIR)\mod_allowmethods.res"
+ -@erase "$(INTDIR)\mod_allowmethods_src.idb"
+ -@erase "$(INTDIR)\mod_allowmethods_src.pdb"
+ -@erase "$(OUTDIR)\mod_allowmethods.exp"
+ -@erase "$(OUTDIR)\mod_allowmethods.lib"
+ -@erase "$(OUTDIR)\mod_allowmethods.pdb"
+ -@erase "$(OUTDIR)\mod_allowmethods.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_allowmethods_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_allowmethods.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_allowmethods.so" /d LONG_NAME="allowmethods_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_allowmethods.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_allowmethods.pdb" /debug /out:"$(OUTDIR)\mod_allowmethods.so" /implib:"$(OUTDIR)\mod_allowmethods.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_allowmethods.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_allowmethods.obj" \
+ "$(INTDIR)\mod_allowmethods.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_allowmethods.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_allowmethods.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_allowmethods.so"
+ if exist .\Release\mod_allowmethods.so.manifest mt.exe -manifest .\Release\mod_allowmethods.so.manifest -outputresource:.\Release\mod_allowmethods.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_allowmethods - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_allowmethods.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_allowmethods.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_allowmethods.obj"
+ -@erase "$(INTDIR)\mod_allowmethods.res"
+ -@erase "$(INTDIR)\mod_allowmethods_src.idb"
+ -@erase "$(INTDIR)\mod_allowmethods_src.pdb"
+ -@erase "$(OUTDIR)\mod_allowmethods.exp"
+ -@erase "$(OUTDIR)\mod_allowmethods.lib"
+ -@erase "$(OUTDIR)\mod_allowmethods.pdb"
+ -@erase "$(OUTDIR)\mod_allowmethods.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_allowmethods_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_allowmethods.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_allowmethods.so" /d LONG_NAME="allowmethods_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_allowmethods.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_allowmethods.pdb" /debug /out:"$(OUTDIR)\mod_allowmethods.so" /implib:"$(OUTDIR)\mod_allowmethods.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_allowmethods.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_allowmethods.obj" \
+ "$(INTDIR)\mod_allowmethods.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_allowmethods.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_allowmethods.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_allowmethods.so"
+ if exist .\Debug\mod_allowmethods.so.manifest mt.exe -manifest .\Debug\mod_allowmethods.so.manifest -outputresource:.\Debug\mod_allowmethods.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_allowmethods.dep")
+!INCLUDE "mod_allowmethods.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_allowmethods.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_allowmethods - Win32 Release" || "$(CFG)" == "mod_allowmethods - Win32 Debug"
+
+!IF "$(CFG)" == "mod_allowmethods - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_allowmethods - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_allowmethods - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_allowmethods - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_allowmethods - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_allowmethods - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_allowmethods - Win32 Release"
+
+
+"$(INTDIR)\mod_allowmethods.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_allowmethods.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_allowmethods.so" /d LONG_NAME="allowmethods_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_allowmethods - Win32 Debug"
+
+
+"$(INTDIR)\mod_allowmethods.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_allowmethods.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_allowmethods.so" /d LONG_NAME="allowmethods_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_allowmethods.c
+
+"$(INTDIR)\mod_allowmethods.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/aaa/mod_auth_basic.c b/modules/aaa/mod_auth_basic.c
new file mode 100644
index 0000000..4e1d47f
--- /dev/null
+++ b/modules/aaa/mod_auth_basic.c
@@ -0,0 +1,508 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr_strings.h"
+#include "apr_lib.h" /* for apr_isspace */
+#include "apr_base64.h" /* for apr_base64_decode et al */
+#define APR_WANT_STRFUNC /* for strcasecmp */
+#include "apr_want.h"
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "util_md5.h"
+#include "ap_provider.h"
+#include "ap_expr.h"
+
+#include "mod_auth.h"
+
+typedef struct {
+ authn_provider_list *providers;
+ char *dir; /* unused variable */
+ int authoritative;
+ ap_expr_info_t *fakeuser;
+ ap_expr_info_t *fakepass;
+ const char *use_digest_algorithm;
+ int fake_set:1;
+ int use_digest_algorithm_set:1;
+ int authoritative_set:1;
+} auth_basic_config_rec;
+
+static void *create_auth_basic_dir_config(apr_pool_t *p, char *d)
+{
+ auth_basic_config_rec *conf = apr_pcalloc(p, sizeof(*conf));
+
+ /* Any failures are fatal. */
+ conf->authoritative = 1;
+
+ return conf;
+}
+
+static void *merge_auth_basic_dir_config(apr_pool_t *p, void *basev, void *overridesv)
+{
+ auth_basic_config_rec *newconf = apr_pcalloc(p, sizeof(*newconf));
+ auth_basic_config_rec *base = basev;
+ auth_basic_config_rec *overrides = overridesv;
+
+ newconf->authoritative =
+ overrides->authoritative_set ? overrides->authoritative :
+ base->authoritative;
+ newconf->authoritative_set = overrides->authoritative_set
+ || base->authoritative_set;
+
+ newconf->fakeuser =
+ overrides->fake_set ? overrides->fakeuser : base->fakeuser;
+ newconf->fakepass =
+ overrides->fake_set ? overrides->fakepass : base->fakepass;
+ newconf->fake_set = overrides->fake_set || base->fake_set;
+
+ newconf->use_digest_algorithm =
+ overrides->use_digest_algorithm_set ? overrides->use_digest_algorithm
+ : base->use_digest_algorithm;
+ newconf->use_digest_algorithm_set =
+ overrides->use_digest_algorithm_set || base->use_digest_algorithm_set;
+
+ newconf->providers = overrides->providers ? overrides->providers : base->providers;
+
+ return newconf;
+}
+
+static const char *add_authn_provider(cmd_parms *cmd, void *config,
+ const char *arg)
+{
+ auth_basic_config_rec *conf = (auth_basic_config_rec*)config;
+ authn_provider_list *newp;
+
+ newp = apr_pcalloc(cmd->pool, sizeof(authn_provider_list));
+ newp->provider_name = arg;
+
+ /* lookup and cache the actual provider now */
+ newp->provider = ap_lookup_provider(AUTHN_PROVIDER_GROUP,
+ newp->provider_name,
+ AUTHN_PROVIDER_VERSION);
+
+ if (newp->provider == NULL) {
+ /* by the time they use it, the provider should be loaded and
+ registered with us. */
+ return apr_psprintf(cmd->pool,
+ "Unknown Authn provider: %s",
+ newp->provider_name);
+ }
+
+ if (!newp->provider->check_password) {
+ /* if it doesn't provide the appropriate function, reject it */
+ return apr_psprintf(cmd->pool,
+ "The '%s' Authn provider doesn't support "
+ "Basic Authentication", newp->provider_name);
+ }
+
+ /* Add it to the list now. */
+ if (!conf->providers) {
+ conf->providers = newp;
+ }
+ else {
+ authn_provider_list *last = conf->providers;
+
+ while (last->next) {
+ last = last->next;
+ }
+ last->next = newp;
+ }
+
+ return NULL;
+}
+
+static const char *set_authoritative(cmd_parms * cmd, void *config, int flag)
+{
+ auth_basic_config_rec *conf = (auth_basic_config_rec *) config;
+
+ conf->authoritative = flag;
+ conf->authoritative_set = 1;
+
+ return NULL;
+}
+
+static const char *add_basic_fake(cmd_parms * cmd, void *config,
+ const char *user, const char *pass)
+{
+ auth_basic_config_rec *conf = (auth_basic_config_rec *) config;
+ const char *err;
+
+ if (!strcasecmp(user, "off")) {
+ conf->fakeuser = NULL;
+ conf->fakepass = NULL;
+ conf->fake_set = 1;
+ }
+ else {
+ /* if password is unspecified, set it to the fixed string "password" to
+ * be compatible with the behaviour of mod_ssl.
+ */
+ if (!pass) {
+ pass = "password";
+ }
+
+ conf->fakeuser =
+ ap_expr_parse_cmd(cmd, user, AP_EXPR_FLAG_STRING_RESULT,
+ &err, NULL);
+ if (err) {
+ return apr_psprintf(cmd->pool,
+ "Could not parse fake username expression '%s': %s", user,
+ err);
+ }
+ conf->fakepass =
+ ap_expr_parse_cmd(cmd, pass, AP_EXPR_FLAG_STRING_RESULT,
+ &err, NULL);
+ if (err) {
+ return apr_psprintf(cmd->pool,
+ "Could not parse fake password expression associated to user '%s': %s",
+ user, err);
+ }
+ conf->fake_set = 1;
+ }
+
+ return NULL;
+}
+
+static const char *set_use_digest_algorithm(cmd_parms *cmd, void *config,
+ const char *alg)
+{
+ auth_basic_config_rec *conf = (auth_basic_config_rec *)config;
+
+ if (strcasecmp(alg, "Off") && strcasecmp(alg, "MD5")) {
+ return apr_pstrcat(cmd->pool,
+ "Invalid algorithm in "
+ "AuthBasicUseDigestAlgorithm: ", alg, NULL);
+ }
+
+ conf->use_digest_algorithm = alg;
+ conf->use_digest_algorithm_set = 1;
+
+ return NULL;
+}
+
+static const command_rec auth_basic_cmds[] =
+{
+ AP_INIT_ITERATE("AuthBasicProvider", add_authn_provider, NULL, OR_AUTHCFG,
+ "specify the auth providers for a directory or location"),
+ AP_INIT_FLAG("AuthBasicAuthoritative", set_authoritative, NULL, OR_AUTHCFG,
+ "Set to 'Off' to allow access control to be passed along to "
+ "lower modules if the UserID is not known to this module"),
+ AP_INIT_TAKE12("AuthBasicFake", add_basic_fake, NULL, OR_AUTHCFG,
+ "Fake basic authentication using the given expressions for "
+ "username and password, 'off' to disable. Password defaults "
+ "to 'password' if missing."),
+ AP_INIT_TAKE1("AuthBasicUseDigestAlgorithm", set_use_digest_algorithm,
+ NULL, OR_AUTHCFG,
+ "Set to 'MD5' to use the auth provider's authentication "
+ "check for digest auth, using a hash of 'user:realm:pass'"),
+ {NULL}
+};
+
+module AP_MODULE_DECLARE_DATA auth_basic_module;
+
+/* These functions return 0 if client is OK, and proper error status
+ * if not... either HTTP_UNAUTHORIZED, if we made a check, and it failed, or
+ * HTTP_INTERNAL_SERVER_ERROR, if things are so totally confused that we
+ * couldn't figure out how to tell if the client is authorized or not.
+ *
+ * If they return DECLINED, and all other modules also decline, that's
+ * treated by the server core as a configuration error, logged and
+ * reported as such.
+ */
+
+static void note_basic_auth_failure(request_rec *r)
+{
+ apr_table_setn(r->err_headers_out,
+ (PROXYREQ_PROXY == r->proxyreq) ? "Proxy-Authenticate"
+ : "WWW-Authenticate",
+ apr_pstrcat(r->pool, "Basic realm=\"", ap_auth_name(r),
+ "\"", NULL));
+}
+
+static int hook_note_basic_auth_failure(request_rec *r, const char *auth_type)
+{
+ if (ap_cstr_casecmp(auth_type, "Basic"))
+ return DECLINED;
+
+ note_basic_auth_failure(r);
+ return OK;
+}
+
+static int get_basic_auth(request_rec *r, const char **user,
+ const char **pw)
+{
+ const char *auth_line;
+ char *decoded_line;
+
+ /* Get the appropriate header */
+ auth_line = apr_table_get(r->headers_in, (PROXYREQ_PROXY == r->proxyreq)
+ ? "Proxy-Authorization"
+ : "Authorization");
+
+ if (!auth_line) {
+ note_basic_auth_failure(r);
+ return HTTP_UNAUTHORIZED;
+ }
+
+ if (ap_cstr_casecmp(ap_getword(r->pool, &auth_line, ' '), "Basic")) {
+ /* Client tried to authenticate using wrong auth scheme */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01614)
+ "client used wrong authentication scheme: %s", r->uri);
+ note_basic_auth_failure(r);
+ return HTTP_UNAUTHORIZED;
+ }
+
+ /* Skip leading spaces. */
+ while (*auth_line == ' ' || *auth_line == '\t') {
+ auth_line++;
+ }
+
+ decoded_line = ap_pbase64decode(r->pool, auth_line);
+
+ *user = ap_getword_nulls(r->pool, (const char**)&decoded_line, ':');
+ *pw = decoded_line;
+
+ /* set the user, even though the user is unauthenticated at this point */
+ r->user = (char *) *user;
+
+ return OK;
+}
+
+/* Determine user ID, and check if it really is that user, for HTTP
+ * basic authentication...
+ */
+static int authenticate_basic_user(request_rec *r)
+{
+ auth_basic_config_rec *conf = ap_get_module_config(r->per_dir_config,
+ &auth_basic_module);
+ const char *sent_user, *sent_pw, *current_auth;
+ const char *realm = NULL;
+ const char *digest = NULL;
+ int res;
+ authn_status auth_result;
+ authn_provider_list *current_provider;
+
+ /* Are we configured to be Basic auth? */
+ current_auth = ap_auth_type(r);
+ if (!current_auth || ap_cstr_casecmp(current_auth, "Basic")) {
+ return DECLINED;
+ }
+
+ /* We need an authentication realm. */
+ if (!ap_auth_name(r)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01615)
+ "need AuthName: %s", r->uri);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ r->ap_auth_type = (char*)current_auth;
+
+ res = get_basic_auth(r, &sent_user, &sent_pw);
+ if (res) {
+ return res;
+ }
+
+ if (conf->use_digest_algorithm
+ && !ap_cstr_casecmp(conf->use_digest_algorithm, "MD5")) {
+ realm = ap_auth_name(r);
+ digest = ap_md5(r->pool,
+ (unsigned char *)apr_pstrcat(r->pool, sent_user, ":",
+ realm, ":",
+ sent_pw, NULL));
+ }
+
+ current_provider = conf->providers;
+ do {
+ const authn_provider *provider;
+
+ /* For now, if a provider isn't set, we'll be nice and use the file
+ * provider.
+ */
+ if (!current_provider) {
+ provider = ap_lookup_provider(AUTHN_PROVIDER_GROUP,
+ AUTHN_DEFAULT_PROVIDER,
+ AUTHN_PROVIDER_VERSION);
+
+ if (!provider || !provider->check_password) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01616)
+ "No Authn provider configured");
+ auth_result = AUTH_GENERAL_ERROR;
+ break;
+ }
+ apr_table_setn(r->notes, AUTHN_PROVIDER_NAME_NOTE, AUTHN_DEFAULT_PROVIDER);
+ }
+ else {
+ provider = current_provider->provider;
+ apr_table_setn(r->notes, AUTHN_PROVIDER_NAME_NOTE, current_provider->provider_name);
+ }
+
+ if (digest) {
+ char *password;
+
+ if (!provider->get_realm_hash) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02493)
+ "Authn provider does not support "
+ "AuthBasicUseDigestAlgorithm");
+ auth_result = AUTH_GENERAL_ERROR;
+ break;
+ }
+ /* We expect the password to be hash of user:realm:password */
+ auth_result = provider->get_realm_hash(r, sent_user, realm,
+ &password);
+ if (auth_result == AUTH_USER_FOUND) {
+ auth_result = strcmp(digest, password) ? AUTH_DENIED
+ : AUTH_GRANTED;
+ }
+ }
+ else {
+ auth_result = provider->check_password(r, sent_user, sent_pw);
+ }
+
+ apr_table_unset(r->notes, AUTHN_PROVIDER_NAME_NOTE);
+
+ /* Something occurred. Stop checking. */
+ if (auth_result != AUTH_USER_NOT_FOUND) {
+ break;
+ }
+
+ /* If we're not really configured for providers, stop now. */
+ if (!conf->providers) {
+ break;
+ }
+
+ current_provider = current_provider->next;
+ } while (current_provider);
+
+ if (auth_result != AUTH_GRANTED) {
+ int return_code;
+
+ /* If we're not authoritative, then any error is ignored. */
+ if (!(conf->authoritative) && auth_result != AUTH_DENIED) {
+ return DECLINED;
+ }
+
+ switch (auth_result) {
+ case AUTH_DENIED:
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01617)
+ "user %s: authentication failure for \"%s\": "
+ "Password Mismatch",
+ sent_user, r->uri);
+ return_code = HTTP_UNAUTHORIZED;
+ break;
+ case AUTH_USER_NOT_FOUND:
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01618)
+ "user %s not found: %s", sent_user, r->uri);
+ return_code = HTTP_UNAUTHORIZED;
+ break;
+ case AUTH_GENERAL_ERROR:
+ default:
+ /* We'll assume that the module has already said what its error
+ * was in the logs.
+ */
+ return_code = HTTP_INTERNAL_SERVER_ERROR;
+ break;
+ }
+
+ /* If we're returning 401, tell them to try again. */
+ if (return_code == HTTP_UNAUTHORIZED) {
+ note_basic_auth_failure(r);
+ }
+ return return_code;
+ }
+
+ return OK;
+}
+
+/* If requested, create a fake basic authentication header for the benefit
+ * of a proxy or application running behind this server.
+ */
+static int authenticate_basic_fake(request_rec *r)
+{
+ const char *auth_line, *user, *pass, *err;
+ auth_basic_config_rec *conf = ap_get_module_config(r->per_dir_config,
+ &auth_basic_module);
+
+ if (!conf->fakeuser) {
+ return DECLINED;
+ }
+
+ user = ap_expr_str_exec(r, conf->fakeuser, &err);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02455)
+ "AuthBasicFake: could not evaluate user expression for URI '%s': %s", r->uri, err);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ if (!user || !*user) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02458)
+ "AuthBasicFake: empty username expression for URI '%s', ignoring", r->uri);
+
+ apr_table_unset(r->headers_in, "Authorization");
+
+ return DECLINED;
+ }
+
+ pass = ap_expr_str_exec(r, conf->fakepass, &err);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02456)
+ "AuthBasicFake: could not evaluate password expression for URI '%s': %s", r->uri, err);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ if (!pass || !*pass) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02459)
+ "AuthBasicFake: empty password expression for URI '%s', ignoring", r->uri);
+
+ apr_table_unset(r->headers_in, "Authorization");
+
+ return DECLINED;
+ }
+
+ auth_line = apr_pstrcat(r->pool, "Basic ",
+ ap_pbase64encode(r->pool,
+ apr_pstrcat(r->pool, user,
+ ":", pass, NULL)),
+ NULL);
+ apr_table_setn(r->headers_in, "Authorization", auth_line);
+
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02457)
+ "AuthBasicFake: \"Authorization: %s\"",
+ auth_line);
+
+ return OK;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_hook_check_authn(authenticate_basic_user, NULL, NULL, APR_HOOK_MIDDLE,
+ AP_AUTH_INTERNAL_PER_CONF);
+ ap_hook_fixups(authenticate_basic_fake, NULL, NULL, APR_HOOK_LAST);
+ ap_hook_note_auth_failure(hook_note_basic_auth_failure, NULL, NULL,
+ APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(auth_basic) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_auth_basic_dir_config, /* dir config creater */
+ merge_auth_basic_dir_config, /* dir merger --- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ auth_basic_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/aaa/mod_auth_basic.dep b/modules/aaa/mod_auth_basic.dep
new file mode 100644
index 0000000..6c1291c
--- /dev/null
+++ b/modules/aaa/mod_auth_basic.dep
@@ -0,0 +1,63 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_auth_basic.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_auth_basic.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_md5.h"\
+ "..\..\srclib\apr-util\include\apr_base64.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/aaa/mod_auth_basic.dsp b/modules/aaa/mod_auth_basic.dsp
new file mode 100644
index 0000000..b45264e
--- /dev/null
+++ b/modules/aaa/mod_auth_basic.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_auth_basic" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_auth_basic - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_auth_basic.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_auth_basic - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_auth_basic - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_auth_basic - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "AAA_DECLARE_EXPORT" /Fd"Release\mod_auth_basic_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_auth_basic.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_auth_basic.so" /d LONG_NAME="auth_basic_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_auth_basic.so" /base:@..\..\os\win32\BaseAddr.ref,mod_auth_basic.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_auth_basic.so" /base:@..\..\os\win32\BaseAddr.ref,mod_auth_basic.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_auth_basic.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_auth_basic - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "AAA_DECLARE_EXPORT" /Fd"Debug\mod_auth_basic_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_auth_basic.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_auth_basic.so" /d LONG_NAME="auth_basic_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_auth_basic.so" /base:@..\..\os\win32\BaseAddr.ref,mod_auth_basic.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_auth_basic.so" /base:@..\..\os\win32\BaseAddr.ref,mod_auth_basic.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_auth_basic.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_auth_basic - Win32 Release"
+# Name "mod_auth_basic - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_auth_basic.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/aaa/mod_auth_basic.mak b/modules/aaa/mod_auth_basic.mak
new file mode 100644
index 0000000..ddd5198
--- /dev/null
+++ b/modules/aaa/mod_auth_basic.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_auth_basic.dsp
+!IF "$(CFG)" == ""
+CFG=mod_auth_basic - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_auth_basic - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_auth_basic - Win32 Release" && "$(CFG)" != "mod_auth_basic - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_auth_basic - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_auth_basic - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_auth_basic - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_auth_basic.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_auth_basic.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_auth_basic.obj"
+ -@erase "$(INTDIR)\mod_auth_basic.res"
+ -@erase "$(INTDIR)\mod_auth_basic_src.idb"
+ -@erase "$(INTDIR)\mod_auth_basic_src.pdb"
+ -@erase "$(OUTDIR)\mod_auth_basic.exp"
+ -@erase "$(OUTDIR)\mod_auth_basic.lib"
+ -@erase "$(OUTDIR)\mod_auth_basic.pdb"
+ -@erase "$(OUTDIR)\mod_auth_basic.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "AAA_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_auth_basic_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_auth_basic.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_auth_basic.so" /d LONG_NAME="auth_basic_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_auth_basic.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_auth_basic.pdb" /debug /out:"$(OUTDIR)\mod_auth_basic.so" /implib:"$(OUTDIR)\mod_auth_basic.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_auth_basic.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_auth_basic.obj" \
+ "$(INTDIR)\mod_auth_basic.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_auth_basic.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_auth_basic.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_auth_basic.so"
+ if exist .\Release\mod_auth_basic.so.manifest mt.exe -manifest .\Release\mod_auth_basic.so.manifest -outputresource:.\Release\mod_auth_basic.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_auth_basic - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_auth_basic.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_auth_basic.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_auth_basic.obj"
+ -@erase "$(INTDIR)\mod_auth_basic.res"
+ -@erase "$(INTDIR)\mod_auth_basic_src.idb"
+ -@erase "$(INTDIR)\mod_auth_basic_src.pdb"
+ -@erase "$(OUTDIR)\mod_auth_basic.exp"
+ -@erase "$(OUTDIR)\mod_auth_basic.lib"
+ -@erase "$(OUTDIR)\mod_auth_basic.pdb"
+ -@erase "$(OUTDIR)\mod_auth_basic.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "AAA_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_auth_basic_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_auth_basic.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_auth_basic.so" /d LONG_NAME="auth_basic_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_auth_basic.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_auth_basic.pdb" /debug /out:"$(OUTDIR)\mod_auth_basic.so" /implib:"$(OUTDIR)\mod_auth_basic.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_auth_basic.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_auth_basic.obj" \
+ "$(INTDIR)\mod_auth_basic.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_auth_basic.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_auth_basic.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_auth_basic.so"
+ if exist .\Debug\mod_auth_basic.so.manifest mt.exe -manifest .\Debug\mod_auth_basic.so.manifest -outputresource:.\Debug\mod_auth_basic.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_auth_basic.dep")
+!INCLUDE "mod_auth_basic.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_auth_basic.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_auth_basic - Win32 Release" || "$(CFG)" == "mod_auth_basic - Win32 Debug"
+
+!IF "$(CFG)" == "mod_auth_basic - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_auth_basic - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_auth_basic - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_auth_basic - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_auth_basic - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_auth_basic - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_auth_basic - Win32 Release"
+
+
+"$(INTDIR)\mod_auth_basic.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_auth_basic.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_auth_basic.so" /d LONG_NAME="auth_basic_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_auth_basic - Win32 Debug"
+
+
+"$(INTDIR)\mod_auth_basic.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_auth_basic.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_auth_basic.so" /d LONG_NAME="auth_basic_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_auth_basic.c
+
+"$(INTDIR)\mod_auth_basic.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/aaa/mod_auth_digest.c b/modules/aaa/mod_auth_digest.c
new file mode 100644
index 0000000..791cec2
--- /dev/null
+++ b/modules/aaa/mod_auth_digest.c
@@ -0,0 +1,1983 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * mod_auth_digest: MD5 digest authentication
+ *
+ * Originally by Alexei Kosut <akosut@nueva.pvt.k12.ca.us>
+ * Updated to RFC-2617 by Ronald Tschal�r <ronald@innovation.ch>
+ * based on mod_auth, by Rob McCool and Robert S. Thau
+ *
+ * This module an updated version of modules/standard/mod_digest.c
+ * It is still fairly new and problems may turn up - submit problem
+ * reports to the Apache bug-database, or send them directly to me
+ * at ronald@innovation.ch.
+ *
+ * Open Issues:
+ * - qop=auth-int (when streams and trailer support available)
+ * - nonce-format configurability
+ * - Proxy-Authorization-Info header is set by this module, but is
+ * currently ignored by mod_proxy (needs patch to mod_proxy)
+ * - The source of the secret should be run-time directive (with server
+ * scope: RSRC_CONF)
+ * - shared-mem not completely tested yet. Seems to work ok for me,
+ * but... (definitely won't work on Windoze)
+ * - Sharing a realm among multiple servers has following problems:
+ * o Server name and port can't be included in nonce-hash
+ * (we need two nonce formats, which must be configured explicitly)
+ * o Nonce-count check can't be for equal, or then nonce-count checking
+ * must be disabled. What we could do is the following:
+ * (expected < received) ? set expected = received : issue error
+ * The only problem is that it allows replay attacks when somebody
+ * captures a packet sent to one server and sends it to another
+ * one. Should we add "AuthDigestNcCheck Strict"?
+ * - expired nonces give amaya fits.
+ * - MD5-sess and auth-int are not yet implemented. An incomplete
+ * implementation has been removed and can be retrieved from svn history.
+ */
+
+#include "apr_sha1.h"
+#include "apr_base64.h"
+#include "apr_lib.h"
+#include "apr_time.h"
+#include "apr_errno.h"
+#include "apr_global_mutex.h"
+#include "apr_strings.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_request.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "apr_uri.h"
+#include "util_md5.h"
+#include "util_mutex.h"
+#include "apr_shm.h"
+#include "apr_rmm.h"
+#include "ap_provider.h"
+
+#include "mod_auth.h"
+
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+/* struct to hold the configuration info */
+
+typedef struct digest_config_struct {
+ const char *dir_name;
+ authn_provider_list *providers;
+ const char *realm;
+ apr_array_header_t *qop_list;
+ apr_sha1_ctx_t nonce_ctx;
+ apr_time_t nonce_lifetime;
+ int check_nc;
+ const char *algorithm;
+ char *uri_list;
+} digest_config_rec;
+
+
+#define DFLT_ALGORITHM "MD5"
+
+#define DFLT_NONCE_LIFE apr_time_from_sec(300)
+#define NEXTNONCE_DELTA apr_time_from_sec(30)
+
+
+#define NONCE_TIME_LEN (((sizeof(apr_time_t)+2)/3)*4)
+#define NONCE_HASH_LEN (2*APR_SHA1_DIGESTSIZE)
+#define NONCE_LEN (int )(NONCE_TIME_LEN + NONCE_HASH_LEN)
+
+#define SECRET_LEN 20
+#define RETAINED_DATA_ID "mod_auth_digest"
+
+
+/* client list definitions */
+
+typedef struct hash_entry {
+ unsigned long key; /* the key for this entry */
+ struct hash_entry *next; /* next entry in the bucket */
+ unsigned long nonce_count; /* for nonce-count checking */
+ char last_nonce[NONCE_LEN+1]; /* for one-time nonce's */
+} client_entry;
+
+static struct hash_table {
+ client_entry **table;
+ unsigned long tbl_len;
+ unsigned long num_entries;
+ unsigned long num_created;
+ unsigned long num_removed;
+ unsigned long num_renewed;
+} *client_list;
+
+
+/* struct to hold a parsed Authorization header */
+
+enum hdr_sts { NO_HEADER, NOT_DIGEST, INVALID, VALID };
+
+typedef struct digest_header_struct {
+ const char *scheme;
+ const char *realm;
+ const char *username;
+ char *nonce;
+ const char *uri;
+ const char *method;
+ const char *digest;
+ const char *algorithm;
+ const char *cnonce;
+ const char *opaque;
+ unsigned long opaque_num;
+ const char *message_qop;
+ const char *nonce_count;
+ /* the following fields are not (directly) from the header */
+ const char *raw_request_uri;
+ apr_uri_t *psd_request_uri;
+ apr_time_t nonce_time;
+ enum hdr_sts auth_hdr_sts;
+ int needed_auth;
+ const char *ha1;
+ client_entry *client;
+} digest_header_rec;
+
+
+/* (mostly) nonce stuff */
+
+typedef union time_union {
+ apr_time_t time;
+ unsigned char arr[sizeof(apr_time_t)];
+} time_rec;
+
+static unsigned char *secret;
+
+/* client-list, opaque, and one-time-nonce stuff */
+
+static apr_shm_t *client_shm = NULL;
+static apr_rmm_t *client_rmm = NULL;
+static unsigned long *opaque_cntr;
+static apr_time_t *otn_counter; /* one-time-nonce counter */
+static apr_global_mutex_t *client_lock = NULL;
+static apr_global_mutex_t *opaque_lock = NULL;
+static const char *client_mutex_type = "authdigest-client";
+static const char *opaque_mutex_type = "authdigest-opaque";
+static const char *client_shm_filename;
+
+#define DEF_SHMEM_SIZE 1000L /* ~ 12 entries */
+#define DEF_NUM_BUCKETS 15L
+#define HASH_DEPTH 5
+
+static apr_size_t shmem_size = DEF_SHMEM_SIZE;
+static unsigned long num_buckets = DEF_NUM_BUCKETS;
+
+
+module AP_MODULE_DECLARE_DATA auth_digest_module;
+
+/*
+ * initialization code
+ */
+
+static apr_status_t cleanup_tables(void *not_used)
+{
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, APLOGNO(01756)
+ "cleaning up shared memory");
+
+ if (client_rmm) {
+ apr_rmm_destroy(client_rmm);
+ client_rmm = NULL;
+ }
+
+ if (client_shm) {
+ apr_shm_destroy(client_shm);
+ client_shm = NULL;
+ }
+
+ if (client_lock) {
+ apr_global_mutex_destroy(client_lock);
+ client_lock = NULL;
+ }
+
+ if (opaque_lock) {
+ apr_global_mutex_destroy(opaque_lock);
+ opaque_lock = NULL;
+ }
+
+ client_list = NULL;
+
+ return APR_SUCCESS;
+}
+
+static void log_error_and_cleanup(char *msg, apr_status_t sts, server_rec *s)
+{
+ ap_log_error(APLOG_MARK, APLOG_ERR, sts, s, APLOGNO(01760)
+ "%s - all nonce-count checking and one-time nonces "
+ "disabled", msg);
+
+ cleanup_tables(NULL);
+}
+
+/* RMM helper functions that behave like single-step malloc/free. */
+
+static void *rmm_malloc(apr_rmm_t *rmm, apr_size_t size)
+{
+ apr_rmm_off_t offset = apr_rmm_malloc(rmm, size);
+
+ if (!offset) {
+ return NULL;
+ }
+
+ return apr_rmm_addr_get(rmm, offset);
+}
+
+static apr_status_t rmm_free(apr_rmm_t *rmm, void *alloc)
+{
+ apr_rmm_off_t offset = apr_rmm_offset_get(rmm, alloc);
+
+ return apr_rmm_free(rmm, offset);
+}
+
+#if APR_HAS_SHARED_MEMORY
+
+static int initialize_tables(server_rec *s, apr_pool_t *ctx)
+{
+ unsigned long idx;
+ apr_status_t sts;
+
+ /* set up client list */
+
+ /* Create the shared memory segment */
+
+ client_shm = NULL;
+ client_rmm = NULL;
+ client_lock = NULL;
+ opaque_lock = NULL;
+ client_list = NULL;
+
+ /*
+ * Create a unique filename using our pid. This information is
+ * stashed in the global variable so the children inherit it.
+ */
+ client_shm_filename = ap_runtime_dir_relative(ctx, "authdigest_shm");
+ client_shm_filename = ap_append_pid(ctx, client_shm_filename, ".");
+
+ /* Use anonymous shm by default, fall back on name-based. */
+ sts = apr_shm_create(&client_shm, shmem_size, NULL, ctx);
+ if (APR_STATUS_IS_ENOTIMPL(sts)) {
+ /* For a name-based segment, remove it first in case of a
+ * previous unclean shutdown. */
+ apr_shm_remove(client_shm_filename, ctx);
+
+ /* Now create that segment */
+ sts = apr_shm_create(&client_shm, shmem_size,
+ client_shm_filename, ctx);
+ }
+
+ if (APR_SUCCESS != sts) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, sts, s, APLOGNO(01762)
+ "Failed to create shared memory segment on file %s",
+ client_shm_filename);
+ log_error_and_cleanup("failed to initialize shm", sts, s);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ sts = apr_rmm_init(&client_rmm,
+ NULL, /* no lock, we'll do the locking ourselves */
+ apr_shm_baseaddr_get(client_shm),
+ shmem_size, ctx);
+ if (sts != APR_SUCCESS) {
+ log_error_and_cleanup("failed to initialize rmm", sts, s);
+ return !OK;
+ }
+
+ client_list = rmm_malloc(client_rmm, sizeof(*client_list) +
+ sizeof(client_entry *) * num_buckets);
+ if (!client_list) {
+ log_error_and_cleanup("failed to allocate shared memory", -1, s);
+ return !OK;
+ }
+ client_list->table = (client_entry**) (client_list + 1);
+ for (idx = 0; idx < num_buckets; idx++) {
+ client_list->table[idx] = NULL;
+ }
+ client_list->tbl_len = num_buckets;
+ client_list->num_entries = 0;
+
+ sts = ap_global_mutex_create(&client_lock, NULL, client_mutex_type, NULL,
+ s, ctx, 0);
+ if (sts != APR_SUCCESS) {
+ log_error_and_cleanup("failed to create lock (client_lock)", sts, s);
+ return !OK;
+ }
+
+
+ /* setup opaque */
+
+ opaque_cntr = rmm_malloc(client_rmm, sizeof(*opaque_cntr));
+ if (opaque_cntr == NULL) {
+ log_error_and_cleanup("failed to allocate shared memory", -1, s);
+ return !OK;
+ }
+ *opaque_cntr = 1UL;
+
+ sts = ap_global_mutex_create(&opaque_lock, NULL, opaque_mutex_type, NULL,
+ s, ctx, 0);
+ if (sts != APR_SUCCESS) {
+ log_error_and_cleanup("failed to create lock (opaque_lock)", sts, s);
+ return !OK;
+ }
+
+
+ /* setup one-time-nonce counter */
+
+ otn_counter = rmm_malloc(client_rmm, sizeof(*otn_counter));
+ if (otn_counter == NULL) {
+ log_error_and_cleanup("failed to allocate shared memory", -1, s);
+ return !OK;
+ }
+ *otn_counter = 0;
+ /* no lock here */
+
+
+ /* success */
+ return OK;
+}
+
+#endif /* APR_HAS_SHARED_MEMORY */
+
+static int pre_init(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp)
+{
+ apr_status_t rv;
+ void *retained;
+
+ rv = ap_mutex_register(pconf, client_mutex_type, NULL, APR_LOCK_DEFAULT, 0);
+ if (rv != APR_SUCCESS)
+ return !OK;
+ rv = ap_mutex_register(pconf, opaque_mutex_type, NULL, APR_LOCK_DEFAULT, 0);
+ if (rv != APR_SUCCESS)
+ return !OK;
+
+ retained = ap_retained_data_get(RETAINED_DATA_ID);
+ if (retained == NULL) {
+ retained = ap_retained_data_create(RETAINED_DATA_ID, SECRET_LEN);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, APLOGNO(01757)
+ "generating secret for digest authentication");
+#if APR_HAS_RANDOM
+ rv = apr_generate_random_bytes(retained, SECRET_LEN);
+#else
+#error APR random number support is missing
+#endif
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, APLOGNO(01758)
+ "error generating secret");
+ return !OK;
+ }
+ }
+ secret = retained;
+ return OK;
+}
+
+static int initialize_module(apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ /* initialize_module() will be called twice, and if it's a DSO
+ * then all static data from the first call will be lost. Only
+ * set up our static data on the second call. */
+ if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG)
+ return OK;
+
+#if APR_HAS_SHARED_MEMORY
+ /* Note: this stuff is currently fixed for the lifetime of the server,
+ * i.e. even across restarts. This means that A) any shmem-size
+ * configuration changes are ignored, and B) certain optimizations,
+ * such as only allocating the smallest necessary entry for each
+ * client, can't be done. However, the alternative is a nightmare:
+ * we can't call apr_shm_destroy on a graceful restart because there
+ * will be children using the tables, and we also don't know when the
+ * last child dies. Therefore we can never clean up the old stuff,
+ * creating a creeping memory leak.
+ */
+ if (initialize_tables(s, p) != OK) {
+ return !OK;
+ }
+#endif /* APR_HAS_SHARED_MEMORY */
+ return OK;
+}
+
+static void initialize_child(apr_pool_t *p, server_rec *s)
+{
+ apr_status_t sts;
+
+ if (!client_shm) {
+ return;
+ }
+
+ /* Get access to rmm in child */
+ sts = apr_rmm_attach(&client_rmm,
+ NULL,
+ apr_shm_baseaddr_get(client_shm),
+ p);
+ if (sts != APR_SUCCESS) {
+ log_error_and_cleanup("failed to attach to rmm", sts, s);
+ return;
+ }
+
+ sts = apr_global_mutex_child_init(&client_lock,
+ apr_global_mutex_lockfile(client_lock),
+ p);
+ if (sts != APR_SUCCESS) {
+ log_error_and_cleanup("failed to create lock (client_lock)", sts, s);
+ return;
+ }
+ sts = apr_global_mutex_child_init(&opaque_lock,
+ apr_global_mutex_lockfile(opaque_lock),
+ p);
+ if (sts != APR_SUCCESS) {
+ log_error_and_cleanup("failed to create lock (opaque_lock)", sts, s);
+ return;
+ }
+}
+
+/*
+ * configuration code
+ */
+
+static void *create_digest_dir_config(apr_pool_t *p, char *dir)
+{
+ digest_config_rec *conf;
+
+ if (dir == NULL) {
+ return NULL;
+ }
+
+ conf = (digest_config_rec *) apr_pcalloc(p, sizeof(digest_config_rec));
+ if (conf) {
+ conf->qop_list = apr_array_make(p, 2, sizeof(char *));
+ conf->nonce_lifetime = DFLT_NONCE_LIFE;
+ conf->dir_name = apr_pstrdup(p, dir);
+ conf->algorithm = DFLT_ALGORITHM;
+ }
+
+ return conf;
+}
+
+static const char *set_realm(cmd_parms *cmd, void *config, const char *realm)
+{
+ digest_config_rec *conf = (digest_config_rec *) config;
+#ifdef AP_DEBUG
+ int i;
+
+ /* check that we got random numbers */
+ for (i = 0; i < SECRET_LEN; i++) {
+ if (secret[i] != 0)
+ break;
+ }
+ ap_assert(i < SECRET_LEN);
+#endif
+
+ /* The core already handles the realm, but it's just too convenient to
+ * grab it ourselves too and cache some setups. However, we need to
+ * let the core get at it too, which is why we decline at the end -
+ * this relies on the fact that http_core is last in the list.
+ */
+ conf->realm = realm;
+
+ /* we precompute the part of the nonce hash that is constant (well,
+ * the host:port would be too, but that varies for .htaccess files
+ * and directives outside a virtual host section)
+ */
+ apr_sha1_init(&conf->nonce_ctx);
+ apr_sha1_update_binary(&conf->nonce_ctx, secret, SECRET_LEN);
+ apr_sha1_update_binary(&conf->nonce_ctx, (const unsigned char *) realm,
+ strlen(realm));
+
+ return DECLINE_CMD;
+}
+
+static const char *add_authn_provider(cmd_parms *cmd, void *config,
+ const char *arg)
+{
+ digest_config_rec *conf = (digest_config_rec*)config;
+ authn_provider_list *newp;
+
+ newp = apr_pcalloc(cmd->pool, sizeof(authn_provider_list));
+ newp->provider_name = arg;
+
+ /* lookup and cache the actual provider now */
+ newp->provider = ap_lookup_provider(AUTHN_PROVIDER_GROUP,
+ newp->provider_name,
+ AUTHN_PROVIDER_VERSION);
+
+ if (newp->provider == NULL) {
+ /* by the time they use it, the provider should be loaded and
+ registered with us. */
+ return apr_psprintf(cmd->pool,
+ "Unknown Authn provider: %s",
+ newp->provider_name);
+ }
+
+ if (!newp->provider->get_realm_hash) {
+ /* if it doesn't provide the appropriate function, reject it */
+ return apr_psprintf(cmd->pool,
+ "The '%s' Authn provider doesn't support "
+ "Digest Authentication", newp->provider_name);
+ }
+
+ /* Add it to the list now. */
+ if (!conf->providers) {
+ conf->providers = newp;
+ }
+ else {
+ authn_provider_list *last = conf->providers;
+
+ while (last->next) {
+ last = last->next;
+ }
+ last->next = newp;
+ }
+
+ return NULL;
+}
+
+static const char *set_qop(cmd_parms *cmd, void *config, const char *op)
+{
+ digest_config_rec *conf = (digest_config_rec *) config;
+
+ if (!ap_cstr_casecmp(op, "none")) {
+ apr_array_clear(conf->qop_list);
+ *(const char **)apr_array_push(conf->qop_list) = "none";
+ return NULL;
+ }
+
+ if (!ap_cstr_casecmp(op, "auth-int")) {
+ return "AuthDigestQop auth-int is not implemented";
+ }
+ else if (ap_cstr_casecmp(op, "auth")) {
+ return apr_pstrcat(cmd->pool, "Unrecognized qop: ", op, NULL);
+ }
+
+ *(const char **)apr_array_push(conf->qop_list) = op;
+
+ return NULL;
+}
+
+static const char *set_nonce_lifetime(cmd_parms *cmd, void *config,
+ const char *t)
+{
+ char *endptr;
+ long lifetime;
+
+ lifetime = strtol(t, &endptr, 10);
+ if (endptr < (t+strlen(t)) && !apr_isspace(*endptr)) {
+ return apr_pstrcat(cmd->pool,
+ "Invalid time in AuthDigestNonceLifetime: ",
+ t, NULL);
+ }
+
+ ((digest_config_rec *) config)->nonce_lifetime = apr_time_from_sec(lifetime);
+ return NULL;
+}
+
+static const char *set_nonce_format(cmd_parms *cmd, void *config,
+ const char *fmt)
+{
+ return "AuthDigestNonceFormat is not implemented";
+}
+
+static const char *set_nc_check(cmd_parms *cmd, void *config, int flag)
+{
+#if !APR_HAS_SHARED_MEMORY
+ if (flag) {
+ return "AuthDigestNcCheck: ERROR: nonce-count checking "
+ "is not supported on platforms without shared-memory "
+ "support";
+ }
+#endif
+
+ ((digest_config_rec *) config)->check_nc = flag;
+ return NULL;
+}
+
+static const char *set_algorithm(cmd_parms *cmd, void *config, const char *alg)
+{
+ if (!ap_cstr_casecmp(alg, "MD5-sess")) {
+ return "AuthDigestAlgorithm: ERROR: algorithm `MD5-sess' "
+ "is not implemented";
+ }
+ else if (ap_cstr_casecmp(alg, "MD5")) {
+ return apr_pstrcat(cmd->pool, "Invalid algorithm in AuthDigestAlgorithm: ", alg, NULL);
+ }
+
+ ((digest_config_rec *) config)->algorithm = alg;
+ return NULL;
+}
+
+static const char *set_uri_list(cmd_parms *cmd, void *config, const char *uri)
+{
+ digest_config_rec *c = (digest_config_rec *) config;
+ if (c->uri_list) {
+ c->uri_list[strlen(c->uri_list)-1] = '\0';
+ c->uri_list = apr_pstrcat(cmd->pool, c->uri_list, " ", uri, "\"", NULL);
+ }
+ else {
+ c->uri_list = apr_pstrcat(cmd->pool, ", domain=\"", uri, "\"", NULL);
+ }
+ return NULL;
+}
+
+static const char *set_shmem_size(cmd_parms *cmd, void *config,
+ const char *size_str)
+{
+ char *endptr;
+ long size, min;
+
+ size = strtol(size_str, &endptr, 10);
+ while (apr_isspace(*endptr)) endptr++;
+ if (*endptr == '\0' || *endptr == 'b' || *endptr == 'B') {
+ ;
+ }
+ else if (*endptr == 'k' || *endptr == 'K') {
+ size *= 1024;
+ }
+ else if (*endptr == 'm' || *endptr == 'M') {
+ size *= 1048576;
+ }
+ else {
+ return apr_pstrcat(cmd->pool, "Invalid size in AuthDigestShmemSize: ",
+ size_str, NULL);
+ }
+
+ min = sizeof(*client_list) + sizeof(client_entry*) + sizeof(client_entry);
+ if (size < min) {
+ return apr_psprintf(cmd->pool, "size in AuthDigestShmemSize too small: "
+ "%ld < %ld", size, min);
+ }
+
+ shmem_size = size;
+ num_buckets = (size - sizeof(*client_list)) /
+ (sizeof(client_entry*) + HASH_DEPTH * sizeof(client_entry));
+ if (num_buckets == 0) {
+ num_buckets = 1;
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01763)
+ "Set shmem-size: %" APR_SIZE_T_FMT ", num-buckets: %ld",
+ shmem_size, num_buckets);
+
+ return NULL;
+}
+
+static const command_rec digest_cmds[] =
+{
+ AP_INIT_TAKE1("AuthName", set_realm, NULL, OR_AUTHCFG,
+ "The authentication realm (e.g. \"Members Only\")"),
+ AP_INIT_ITERATE("AuthDigestProvider", add_authn_provider, NULL, OR_AUTHCFG,
+ "specify the auth providers for a directory or location"),
+ AP_INIT_ITERATE("AuthDigestQop", set_qop, NULL, OR_AUTHCFG,
+ "A list of quality-of-protection options"),
+ AP_INIT_TAKE1("AuthDigestNonceLifetime", set_nonce_lifetime, NULL, OR_AUTHCFG,
+ "Maximum lifetime of the server nonce (seconds)"),
+ AP_INIT_TAKE1("AuthDigestNonceFormat", set_nonce_format, NULL, OR_AUTHCFG,
+ "The format to use when generating the server nonce"),
+ AP_INIT_FLAG("AuthDigestNcCheck", set_nc_check, NULL, OR_AUTHCFG,
+ "Whether or not to check the nonce-count sent by the client"),
+ AP_INIT_TAKE1("AuthDigestAlgorithm", set_algorithm, NULL, OR_AUTHCFG,
+ "The algorithm used for the hash calculation"),
+ AP_INIT_ITERATE("AuthDigestDomain", set_uri_list, NULL, OR_AUTHCFG,
+ "A list of URI's which belong to the same protection space as the current URI"),
+ AP_INIT_TAKE1("AuthDigestShmemSize", set_shmem_size, NULL, RSRC_CONF,
+ "The amount of shared memory to allocate for keeping track of clients"),
+ {NULL}
+};
+
+
+/*
+ * client list code
+ *
+ * Each client is assigned a number, which is transferred in the opaque
+ * field of the WWW-Authenticate and Authorization headers. The number
+ * is just a simple counter which is incremented for each new client.
+ * Clients can't forge this number because it is hashed up into the
+ * server nonce, and that is checked.
+ *
+ * The clients are kept in a simple hash table, which consists of an
+ * array of client_entry's, each with a linked list of entries hanging
+ * off it. The client's number modulo the size of the array gives the
+ * bucket number.
+ *
+ * The clients are garbage collected whenever a new client is allocated
+ * but there is not enough space left in the shared memory segment. A
+ * simple semi-LRU is used for this: whenever a client entry is accessed
+ * it is moved to the beginning of the linked list in its bucket (this
+ * also makes for faster lookups for current clients). The garbage
+ * collecter then just removes the oldest entry (i.e. the one at the
+ * end of the list) in each bucket.
+ *
+ * The main advantages of the above scheme are that it's easy to implement
+ * and it keeps the hash table evenly balanced (i.e. same number of entries
+ * in each bucket). The major disadvantage is that you may be throwing
+ * entries out which are in active use. This is not tragic, as these
+ * clients will just be sent a new client id (opaque field) and nonce
+ * with a stale=true (i.e. it will just look like the nonce expired,
+ * thereby forcing an extra round trip). If the shared memory segment
+ * has enough headroom over the current client set size then this should
+ * not occur too often.
+ *
+ * To help tune the size of the shared memory segment (and see if the
+ * above algorithm is really sufficient) a set of counters is kept
+ * indicating the number of clients held, the number of garbage collected
+ * clients, and the number of erroneously purged clients. These are printed
+ * out at each garbage collection run. Note that access to the counters is
+ * not synchronized because they are just indicaters, and whether they are
+ * off by a few doesn't matter; and for the same reason no attempt is made
+ * to guarantee the num_renewed is correct in the face of clients spoofing
+ * the opaque field.
+ */
+
+/*
+ * Get the client given its client number (the key). Returns the entry,
+ * or NULL if it's not found.
+ *
+ * Access to the list itself is synchronized via locks. However, access
+ * to the entry returned by get_client() is NOT synchronized. This means
+ * that there are potentially problems if a client uses multiple,
+ * simultaneous connections to access url's within the same protection
+ * space. However, these problems are not new: when using multiple
+ * connections you have no guarantee of the order the requests are
+ * processed anyway, so you have problems with the nonce-count and
+ * one-time nonces anyway.
+ */
+static client_entry *get_client(unsigned long key, const request_rec *r)
+{
+ int bucket;
+ client_entry *entry, *prev = NULL;
+
+
+ if (!key || !client_shm) return NULL;
+
+ bucket = key % client_list->tbl_len;
+ entry = client_list->table[bucket];
+
+ apr_global_mutex_lock(client_lock);
+
+ while (entry && key != entry->key) {
+ prev = entry;
+ entry = entry->next;
+ }
+
+ if (entry && prev) { /* move entry to front of list */
+ prev->next = entry->next;
+ entry->next = client_list->table[bucket];
+ client_list->table[bucket] = entry;
+ }
+
+ apr_global_mutex_unlock(client_lock);
+
+ if (entry) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01764)
+ "get_client(): client %lu found", key);
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01765)
+ "get_client(): client %lu not found", key);
+ }
+
+ return entry;
+}
+
+
+/* A simple garbage-collecter to remove unused clients. It removes the
+ * last entry in each bucket and updates the counters. Returns the
+ * number of removed entries.
+ */
+static long gc(server_rec *s)
+{
+ client_entry *entry, *prev;
+ unsigned long num_removed = 0, idx;
+
+ /* garbage collect all last entries */
+
+ for (idx = 0; idx < client_list->tbl_len; idx++) {
+ entry = client_list->table[idx];
+ prev = NULL;
+
+ if (!entry) {
+ /* This bucket is empty. */
+ continue;
+ }
+
+ while (entry->next) { /* find last entry */
+ prev = entry;
+ entry = entry->next;
+ }
+ if (prev) {
+ prev->next = NULL; /* cut list */
+ }
+ else {
+ client_list->table[idx] = NULL;
+ }
+ if (entry) { /* remove entry */
+ apr_status_t err;
+
+ err = rmm_free(client_rmm, entry);
+ num_removed++;
+
+ if (err) {
+ /* Nothing we can really do but log... */
+ ap_log_error(APLOG_MARK, APLOG_ERR, err, s, APLOGNO(10007)
+ "Failed to free auth_digest client allocation");
+ }
+ }
+ }
+
+ /* update counters and log */
+
+ client_list->num_entries -= num_removed;
+ client_list->num_removed += num_removed;
+
+ return num_removed;
+}
+
+
+/*
+ * Add a new client to the list. Returns the entry if successful, NULL
+ * otherwise. This triggers the garbage collection if memory is low.
+ */
+static client_entry *add_client(unsigned long key, client_entry *info,
+ server_rec *s)
+{
+ int bucket;
+ client_entry *entry;
+
+
+ if (!key || !client_shm) {
+ return NULL;
+ }
+
+ bucket = key % client_list->tbl_len;
+
+ apr_global_mutex_lock(client_lock);
+
+ /* try to allocate a new entry */
+
+ entry = rmm_malloc(client_rmm, sizeof(client_entry));
+ if (!entry) {
+ long num_removed = gc(s);
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(01766)
+ "gc'd %ld client entries. Total new clients: "
+ "%ld; Total removed clients: %ld; Total renewed clients: "
+ "%ld", num_removed,
+ client_list->num_created - client_list->num_renewed,
+ client_list->num_removed, client_list->num_renewed);
+ entry = rmm_malloc(client_rmm, sizeof(client_entry));
+ if (!entry) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(01767)
+ "unable to allocate new auth_digest client");
+ apr_global_mutex_unlock(client_lock);
+ return NULL; /* give up */
+ }
+ }
+
+ /* now add the entry */
+
+ memcpy(entry, info, sizeof(client_entry));
+ entry->key = key;
+ entry->next = client_list->table[bucket];
+ client_list->table[bucket] = entry;
+ client_list->num_created++;
+ client_list->num_entries++;
+
+ apr_global_mutex_unlock(client_lock);
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01768)
+ "allocated new client %lu", key);
+
+ return entry;
+}
+
+
+/*
+ * Authorization header parser code
+ */
+
+/* Parse the Authorization header, if it exists */
+static int get_digest_rec(request_rec *r, digest_header_rec *resp)
+{
+ const char *auth_line;
+ apr_size_t l;
+ int vk = 0, vv = 0;
+ char *key, *value;
+
+ auth_line = apr_table_get(r->headers_in,
+ (PROXYREQ_PROXY == r->proxyreq)
+ ? "Proxy-Authorization"
+ : "Authorization");
+ if (!auth_line) {
+ resp->auth_hdr_sts = NO_HEADER;
+ return !OK;
+ }
+
+ resp->scheme = ap_getword_white(r->pool, &auth_line);
+ if (ap_cstr_casecmp(resp->scheme, "Digest")) {
+ resp->auth_hdr_sts = NOT_DIGEST;
+ return !OK;
+ }
+
+ l = strlen(auth_line);
+
+ key = apr_palloc(r->pool, l+1);
+ value = apr_palloc(r->pool, l+1);
+
+ while (auth_line[0] != '\0') {
+
+ /* find key */
+
+ while (apr_isspace(auth_line[0])) {
+ auth_line++;
+ }
+ vk = 0;
+ while (auth_line[0] != '=' && auth_line[0] != ','
+ && auth_line[0] != '\0' && !apr_isspace(auth_line[0])) {
+ key[vk++] = *auth_line++;
+ }
+ key[vk] = '\0';
+ while (apr_isspace(auth_line[0])) {
+ auth_line++;
+ }
+
+ /* find value */
+
+ vv = 0;
+ if (auth_line[0] == '=') {
+ auth_line++;
+ while (apr_isspace(auth_line[0])) {
+ auth_line++;
+ }
+
+ if (auth_line[0] == '\"') { /* quoted string */
+ auth_line++;
+ while (auth_line[0] != '\"' && auth_line[0] != '\0') {
+ if (auth_line[0] == '\\' && auth_line[1] != '\0') {
+ auth_line++; /* escaped char */
+ }
+ value[vv++] = *auth_line++;
+ }
+ if (auth_line[0] != '\0') {
+ auth_line++;
+ }
+ }
+ else { /* token */
+ while (auth_line[0] != ',' && auth_line[0] != '\0'
+ && !apr_isspace(auth_line[0])) {
+ value[vv++] = *auth_line++;
+ }
+ }
+ }
+ value[vv] = '\0';
+
+ while (auth_line[0] != ',' && auth_line[0] != '\0') {
+ auth_line++;
+ }
+ if (auth_line[0] != '\0') {
+ auth_line++;
+ }
+
+ if (!ap_cstr_casecmp(key, "username"))
+ resp->username = apr_pstrdup(r->pool, value);
+ else if (!ap_cstr_casecmp(key, "realm"))
+ resp->realm = apr_pstrdup(r->pool, value);
+ else if (!ap_cstr_casecmp(key, "nonce"))
+ resp->nonce = apr_pstrdup(r->pool, value);
+ else if (!ap_cstr_casecmp(key, "uri"))
+ resp->uri = apr_pstrdup(r->pool, value);
+ else if (!ap_cstr_casecmp(key, "response"))
+ resp->digest = apr_pstrdup(r->pool, value);
+ else if (!ap_cstr_casecmp(key, "algorithm"))
+ resp->algorithm = apr_pstrdup(r->pool, value);
+ else if (!ap_cstr_casecmp(key, "cnonce"))
+ resp->cnonce = apr_pstrdup(r->pool, value);
+ else if (!ap_cstr_casecmp(key, "opaque"))
+ resp->opaque = apr_pstrdup(r->pool, value);
+ else if (!ap_cstr_casecmp(key, "qop"))
+ resp->message_qop = apr_pstrdup(r->pool, value);
+ else if (!ap_cstr_casecmp(key, "nc"))
+ resp->nonce_count = apr_pstrdup(r->pool, value);
+ }
+
+ if (!resp->username || !resp->realm || !resp->nonce || !resp->uri
+ || !resp->digest
+ || (resp->message_qop && (!resp->cnonce || !resp->nonce_count))) {
+ resp->auth_hdr_sts = INVALID;
+ return !OK;
+ }
+
+ if (resp->opaque) {
+ resp->opaque_num = (unsigned long) strtol(resp->opaque, NULL, 16);
+ }
+
+ resp->auth_hdr_sts = VALID;
+ return OK;
+}
+
+
+/* Because the browser may preemptively send auth info, incrementing the
+ * nonce-count when it does, and because the client does not get notified
+ * if the URI didn't need authentication after all, we need to be sure to
+ * update the nonce-count each time we receive an Authorization header no
+ * matter what the final outcome of the request. Furthermore this is a
+ * convenient place to get the request-uri (before any subrequests etc
+ * are initiated) and to initialize the request_config.
+ *
+ * Note that this must be called after mod_proxy had its go so that
+ * r->proxyreq is set correctly.
+ */
+static int parse_hdr_and_update_nc(request_rec *r)
+{
+ digest_header_rec *resp;
+ int res;
+
+ if (!ap_is_initial_req(r)) {
+ return DECLINED;
+ }
+
+ resp = apr_pcalloc(r->pool, sizeof(digest_header_rec));
+ resp->raw_request_uri = r->unparsed_uri;
+ resp->psd_request_uri = &r->parsed_uri;
+ resp->needed_auth = 0;
+ resp->method = r->method;
+ ap_set_module_config(r->request_config, &auth_digest_module, resp);
+
+ res = get_digest_rec(r, resp);
+ resp->client = get_client(resp->opaque_num, r);
+ if (res == OK && resp->client) {
+ resp->client->nonce_count++;
+ }
+
+ return DECLINED;
+}
+
+
+/*
+ * Nonce generation code
+ */
+
+/* The hash part of the nonce is a SHA-1 hash of the time, realm, server host
+ * and port, opaque, and our secret.
+ */
+static void gen_nonce_hash(char *hash, const char *timestr, const char *opaque,
+ const server_rec *server,
+ const digest_config_rec *conf)
+{
+ unsigned char sha1[APR_SHA1_DIGESTSIZE];
+ apr_sha1_ctx_t ctx;
+
+ memcpy(&ctx, &conf->nonce_ctx, sizeof(ctx));
+ /*
+ apr_sha1_update_binary(&ctx, (const unsigned char *) server->server_hostname,
+ strlen(server->server_hostname));
+ apr_sha1_update_binary(&ctx, (const unsigned char *) &server->port,
+ sizeof(server->port));
+ */
+ apr_sha1_update_binary(&ctx, (const unsigned char *) timestr, strlen(timestr));
+ if (opaque) {
+ apr_sha1_update_binary(&ctx, (const unsigned char *) opaque,
+ strlen(opaque));
+ }
+ apr_sha1_final(sha1, &ctx);
+
+ ap_bin2hex(sha1, APR_SHA1_DIGESTSIZE, hash);
+}
+
+
+/* The nonce has the format b64(time)+hash .
+ */
+static const char *gen_nonce(apr_pool_t *p, apr_time_t now, const char *opaque,
+ const server_rec *server,
+ const digest_config_rec *conf)
+{
+ char *nonce = apr_palloc(p, NONCE_LEN+1);
+ time_rec t;
+
+ if (conf->nonce_lifetime != 0) {
+ t.time = now;
+ }
+ else if (otn_counter) {
+ /* this counter is not synch'd, because it doesn't really matter
+ * if it counts exactly.
+ */
+ t.time = (*otn_counter)++;
+ }
+ else {
+ /* XXX: WHAT IS THIS CONSTANT? */
+ t.time = 42;
+ }
+ apr_base64_encode_binary(nonce, t.arr, sizeof(t.arr));
+ gen_nonce_hash(nonce+NONCE_TIME_LEN, nonce, opaque, server, conf);
+
+ return nonce;
+}
+
+
+/*
+ * Opaque and hash-table management
+ */
+
+/*
+ * Generate a new client entry, add it to the list, and return the
+ * entry. Returns NULL if failed.
+ */
+static client_entry *gen_client(const request_rec *r)
+{
+ unsigned long op;
+ client_entry new_entry = { 0, NULL, 0, "" }, *entry;
+
+ if (!opaque_cntr) {
+ return NULL;
+ }
+
+ apr_global_mutex_lock(opaque_lock);
+ op = (*opaque_cntr)++;
+ apr_global_mutex_unlock(opaque_lock);
+
+ if (!(entry = add_client(op, &new_entry, r->server))) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01769)
+ "failed to allocate client entry - ignoring client");
+ return NULL;
+ }
+
+ return entry;
+}
+
+
+/*
+ * Authorization challenge generation code (for WWW-Authenticate)
+ */
+
+static const char *ltox(apr_pool_t *p, unsigned long num)
+{
+ if (num != 0) {
+ return apr_psprintf(p, "%lx", num);
+ }
+ else {
+ return "";
+ }
+}
+
+static void note_digest_auth_failure(request_rec *r,
+ const digest_config_rec *conf,
+ digest_header_rec *resp, int stale)
+{
+ const char *qop, *opaque, *opaque_param, *domain, *nonce;
+
+ /* Setup qop */
+ if (apr_is_empty_array(conf->qop_list)) {
+ qop = ", qop=\"auth\"";
+ }
+ else if (!ap_cstr_casecmp(*(const char **)(conf->qop_list->elts), "none")) {
+ qop = "";
+ }
+ else {
+ qop = apr_pstrcat(r->pool, ", qop=\"",
+ apr_array_pstrcat(r->pool, conf->qop_list, ','),
+ "\"",
+ NULL);
+ }
+
+ /* Setup opaque */
+
+ if (resp->opaque == NULL) {
+ /* new client */
+ if ((conf->check_nc || conf->nonce_lifetime == 0)
+ && (resp->client = gen_client(r)) != NULL) {
+ opaque = ltox(r->pool, resp->client->key);
+ }
+ else {
+ opaque = ""; /* opaque not needed */
+ }
+ }
+ else if (resp->client == NULL) {
+ /* client info was gc'd */
+ resp->client = gen_client(r);
+ if (resp->client != NULL) {
+ opaque = ltox(r->pool, resp->client->key);
+ stale = 1;
+ client_list->num_renewed++;
+ }
+ else {
+ opaque = ""; /* ??? */
+ }
+ }
+ else {
+ opaque = resp->opaque;
+ /* we're generating a new nonce, so reset the nonce-count */
+ resp->client->nonce_count = 0;
+ }
+
+ if (opaque[0]) {
+ opaque_param = apr_pstrcat(r->pool, ", opaque=\"", opaque, "\"", NULL);
+ }
+ else {
+ opaque_param = NULL;
+ }
+
+ /* Setup nonce */
+
+ nonce = gen_nonce(r->pool, r->request_time, opaque, r->server, conf);
+ if (resp->client && conf->nonce_lifetime == 0) {
+ memcpy(resp->client->last_nonce, nonce, NONCE_LEN+1);
+ }
+
+ /* setup domain attribute. We want to send this attribute wherever
+ * possible so that the client won't send the Authorization header
+ * unnecessarily (it's usually > 200 bytes!).
+ */
+
+
+ /* don't send domain
+ * - for proxy requests
+ * - if it's not specified
+ */
+ if (r->proxyreq || !conf->uri_list) {
+ domain = NULL;
+ }
+ else {
+ domain = conf->uri_list;
+ }
+
+ apr_table_mergen(r->err_headers_out,
+ (PROXYREQ_PROXY == r->proxyreq)
+ ? "Proxy-Authenticate" : "WWW-Authenticate",
+ apr_psprintf(r->pool, "Digest realm=\"%s\", "
+ "nonce=\"%s\", algorithm=%s%s%s%s%s",
+ ap_auth_name(r), nonce, conf->algorithm,
+ opaque_param ? opaque_param : "",
+ domain ? domain : "",
+ stale ? ", stale=true" : "", qop));
+
+}
+
+static int hook_note_digest_auth_failure(request_rec *r, const char *auth_type)
+{
+ request_rec *mainreq;
+ digest_header_rec *resp;
+ digest_config_rec *conf;
+
+ if (ap_cstr_casecmp(auth_type, "Digest"))
+ return DECLINED;
+
+ /* get the client response and mark */
+
+ mainreq = r;
+ while (mainreq->main != NULL) {
+ mainreq = mainreq->main;
+ }
+ while (mainreq->prev != NULL) {
+ mainreq = mainreq->prev;
+ }
+ resp = (digest_header_rec *) ap_get_module_config(mainreq->request_config,
+ &auth_digest_module);
+ resp->needed_auth = 1;
+
+
+ /* get our conf */
+
+ conf = (digest_config_rec *) ap_get_module_config(r->per_dir_config,
+ &auth_digest_module);
+
+ note_digest_auth_failure(r, conf, resp, 0);
+
+ return OK;
+}
+
+
+/*
+ * Authorization header verification code
+ */
+
+static authn_status get_hash(request_rec *r, const char *user,
+ digest_config_rec *conf, const char **rethash)
+{
+ authn_status auth_result;
+ char *password;
+ authn_provider_list *current_provider;
+
+ current_provider = conf->providers;
+ do {
+ const authn_provider *provider;
+
+ /* For now, if a provider isn't set, we'll be nice and use the file
+ * provider.
+ */
+ if (!current_provider) {
+ provider = ap_lookup_provider(AUTHN_PROVIDER_GROUP,
+ AUTHN_DEFAULT_PROVIDER,
+ AUTHN_PROVIDER_VERSION);
+
+ if (!provider || !provider->get_realm_hash) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01770)
+ "No Authn provider configured");
+ auth_result = AUTH_GENERAL_ERROR;
+ break;
+ }
+ apr_table_setn(r->notes, AUTHN_PROVIDER_NAME_NOTE, AUTHN_DEFAULT_PROVIDER);
+ }
+ else {
+ provider = current_provider->provider;
+ apr_table_setn(r->notes, AUTHN_PROVIDER_NAME_NOTE, current_provider->provider_name);
+ }
+
+
+ /* We expect the password to be md5 hash of user:realm:password */
+ auth_result = provider->get_realm_hash(r, user, conf->realm,
+ &password);
+
+ apr_table_unset(r->notes, AUTHN_PROVIDER_NAME_NOTE);
+
+ /* Something occurred. Stop checking. */
+ if (auth_result != AUTH_USER_NOT_FOUND) {
+ break;
+ }
+
+ /* If we're not really configured for providers, stop now. */
+ if (!conf->providers) {
+ break;
+ }
+
+ current_provider = current_provider->next;
+ } while (current_provider);
+
+ if (auth_result == AUTH_USER_FOUND) {
+ *rethash = password;
+ }
+
+ return auth_result;
+}
+
+static int check_nc(const request_rec *r, const digest_header_rec *resp,
+ const digest_config_rec *conf)
+{
+ unsigned long nc;
+ const char *snc = resp->nonce_count;
+ char *endptr;
+
+ if (conf->check_nc && !client_shm) {
+ /* Shouldn't happen, but just in case... */
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01771)
+ "cannot check nonce count without shared memory");
+ return OK;
+ }
+
+ if (!conf->check_nc || !client_shm) {
+ return OK;
+ }
+
+ if (!apr_is_empty_array(conf->qop_list) &&
+ !ap_cstr_casecmp(*(const char **)(conf->qop_list->elts), "none")) {
+ /* qop is none, client must not send a nonce count */
+ if (snc != NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01772)
+ "invalid nc %s received - no nonce count allowed when qop=none",
+ snc);
+ return !OK;
+ }
+ /* qop is none, cannot check nonce count */
+ return OK;
+ }
+
+ nc = strtol(snc, &endptr, 16);
+ if (endptr < (snc+strlen(snc)) && !apr_isspace(*endptr)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01773)
+ "invalid nc %s received - not a number", snc);
+ return !OK;
+ }
+
+ if (!resp->client) {
+ return !OK;
+ }
+
+ if (nc != resp->client->nonce_count) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01774)
+ "Warning, possible replay attack: nonce-count "
+ "check failed: %lu != %lu", nc,
+ resp->client->nonce_count);
+ return !OK;
+ }
+
+ return OK;
+}
+
+static int check_nonce(request_rec *r, digest_header_rec *resp,
+ const digest_config_rec *conf)
+{
+ apr_time_t dt;
+ time_rec nonce_time;
+ char tmp, hash[NONCE_HASH_LEN+1];
+
+ /* Since the time part of the nonce is a base64 encoding of an
+ * apr_time_t (8 bytes), it should end with a '=', fail early otherwise.
+ */
+ if (strlen(resp->nonce) != NONCE_LEN
+ || resp->nonce[NONCE_TIME_LEN - 1] != '=') {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01775)
+ "invalid nonce '%s' received - length is not %d "
+ "or time encoding is incorrect",
+ resp->nonce, NONCE_LEN);
+ note_digest_auth_failure(r, conf, resp, 1);
+ return HTTP_UNAUTHORIZED;
+ }
+
+ tmp = resp->nonce[NONCE_TIME_LEN];
+ resp->nonce[NONCE_TIME_LEN] = '\0';
+ apr_base64_decode_binary(nonce_time.arr, resp->nonce);
+ gen_nonce_hash(hash, resp->nonce, resp->opaque, r->server, conf);
+ resp->nonce[NONCE_TIME_LEN] = tmp;
+ resp->nonce_time = nonce_time.time;
+
+ if (strcmp(hash, resp->nonce+NONCE_TIME_LEN)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01776)
+ "invalid nonce %s received - hash is not %s",
+ resp->nonce, hash);
+ note_digest_auth_failure(r, conf, resp, 1);
+ return HTTP_UNAUTHORIZED;
+ }
+
+ dt = r->request_time - nonce_time.time;
+ if (conf->nonce_lifetime > 0 && dt < 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01777)
+ "invalid nonce %s received - user attempted "
+ "time travel", resp->nonce);
+ note_digest_auth_failure(r, conf, resp, 1);
+ return HTTP_UNAUTHORIZED;
+ }
+
+ if (conf->nonce_lifetime > 0) {
+ if (dt > conf->nonce_lifetime) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0,r, APLOGNO(01778)
+ "user %s: nonce expired (%.2f seconds old "
+ "- max lifetime %.2f) - sending new nonce",
+ r->user, (double)apr_time_sec(dt),
+ (double)apr_time_sec(conf->nonce_lifetime));
+ note_digest_auth_failure(r, conf, resp, 1);
+ return HTTP_UNAUTHORIZED;
+ }
+ }
+ else if (conf->nonce_lifetime == 0 && resp->client) {
+ if (memcmp(resp->client->last_nonce, resp->nonce, NONCE_LEN)) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01779)
+ "user %s: one-time-nonce mismatch - sending "
+ "new nonce", r->user);
+ note_digest_auth_failure(r, conf, resp, 1);
+ return HTTP_UNAUTHORIZED;
+ }
+ }
+ /* else (lifetime < 0) => never expires */
+
+ return OK;
+}
+
+/* The actual MD5 code... whee */
+
+/* RFC-2069 */
+static const char *old_digest(const request_rec *r,
+ const digest_header_rec *resp)
+{
+ const char *ha2;
+
+ ha2 = ap_md5(r->pool, (unsigned char *)apr_pstrcat(r->pool, resp->method, ":",
+ resp->uri, NULL));
+ return ap_md5(r->pool,
+ (unsigned char *)apr_pstrcat(r->pool, resp->ha1, ":",
+ resp->nonce, ":", ha2, NULL));
+}
+
+/* RFC-2617 */
+static const char *new_digest(const request_rec *r,
+ digest_header_rec *resp)
+{
+ const char *ha1, *ha2, *a2;
+
+ ha1 = resp->ha1;
+
+ a2 = apr_pstrcat(r->pool, resp->method, ":", resp->uri, NULL);
+ ha2 = ap_md5(r->pool, (const unsigned char *)a2);
+
+ return ap_md5(r->pool,
+ (unsigned char *)apr_pstrcat(r->pool, ha1, ":", resp->nonce,
+ ":", resp->nonce_count, ":",
+ resp->cnonce, ":",
+ resp->message_qop, ":", ha2,
+ NULL));
+}
+
+static void copy_uri_components(apr_uri_t *dst,
+ apr_uri_t *src, request_rec *r) {
+ if (src->scheme && src->scheme[0] != '\0') {
+ dst->scheme = src->scheme;
+ }
+ else {
+ dst->scheme = (char *) "http";
+ }
+
+ if (src->hostname && src->hostname[0] != '\0') {
+ dst->hostname = apr_pstrdup(r->pool, src->hostname);
+ ap_unescape_url(dst->hostname);
+ }
+ else {
+ dst->hostname = (char *) ap_get_server_name(r);
+ }
+
+ if (src->port_str && src->port_str[0] != '\0') {
+ dst->port = src->port;
+ }
+ else {
+ dst->port = ap_get_server_port(r);
+ }
+
+ if (src->path && src->path[0] != '\0') {
+ dst->path = apr_pstrdup(r->pool, src->path);
+ ap_unescape_url(dst->path);
+ }
+ else {
+ dst->path = src->path;
+ }
+
+ if (src->query && src->query[0] != '\0') {
+ dst->query = apr_pstrdup(r->pool, src->query);
+ ap_unescape_url(dst->query);
+ }
+ else {
+ dst->query = src->query;
+ }
+
+ dst->hostinfo = src->hostinfo;
+}
+
+/* These functions return 0 if client is OK, and proper error status
+ * if not... either HTTP_UNAUTHORIZED, if we made a check, and it failed, or
+ * HTTP_INTERNAL_SERVER_ERROR, if things are so totally confused that we
+ * couldn't figure out how to tell if the client is authorized or not.
+ *
+ * If they return DECLINED, and all other modules also decline, that's
+ * treated by the server core as a configuration error, logged and
+ * reported as such.
+ */
+
+/* Determine user ID, and check if the attributes are correct, if it
+ * really is that user, if the nonce is correct, etc.
+ */
+
+static int authenticate_digest_user(request_rec *r)
+{
+ digest_config_rec *conf;
+ digest_header_rec *resp;
+ request_rec *mainreq;
+ const char *t;
+ int res;
+ authn_status return_code;
+
+ /* do we require Digest auth for this URI? */
+
+ if (!(t = ap_auth_type(r)) || ap_cstr_casecmp(t, "Digest")) {
+ return DECLINED;
+ }
+
+ if (!ap_auth_name(r)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01780)
+ "need AuthName: %s", r->uri);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+
+ /* get the client response and mark */
+
+ mainreq = r;
+ while (mainreq->main != NULL) {
+ mainreq = mainreq->main;
+ }
+ while (mainreq->prev != NULL) {
+ mainreq = mainreq->prev;
+ }
+ resp = (digest_header_rec *) ap_get_module_config(mainreq->request_config,
+ &auth_digest_module);
+ resp->needed_auth = 1;
+
+
+ /* get our conf */
+
+ conf = (digest_config_rec *) ap_get_module_config(r->per_dir_config,
+ &auth_digest_module);
+
+
+ /* check for existence and syntax of Auth header */
+
+ if (resp->auth_hdr_sts != VALID) {
+ if (resp->auth_hdr_sts == NOT_DIGEST) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01781)
+ "client used wrong authentication scheme `%s': %s",
+ resp->scheme, r->uri);
+ }
+ else if (resp->auth_hdr_sts == INVALID) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01782)
+ "missing user, realm, nonce, uri, digest, "
+ "cnonce, or nonce_count in authorization header: %s",
+ r->uri);
+ }
+ /* else (resp->auth_hdr_sts == NO_HEADER) */
+ note_digest_auth_failure(r, conf, resp, 0);
+ return HTTP_UNAUTHORIZED;
+ }
+
+ r->user = (char *) resp->username;
+ r->ap_auth_type = (char *) "Digest";
+
+ /* check the auth attributes */
+
+ if (strcmp(resp->uri, resp->raw_request_uri)) {
+ /* Hmm, the simple match didn't work (probably a proxy modified the
+ * request-uri), so lets do a more sophisticated match
+ */
+ apr_uri_t r_uri, d_uri;
+
+ copy_uri_components(&r_uri, resp->psd_request_uri, r);
+ if (apr_uri_parse(r->pool, resp->uri, &d_uri) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01783)
+ "invalid uri <%s> in Authorization header",
+ resp->uri);
+ return HTTP_BAD_REQUEST;
+ }
+
+ if (d_uri.hostname) {
+ ap_unescape_url(d_uri.hostname);
+ }
+ if (d_uri.path) {
+ ap_unescape_url(d_uri.path);
+ }
+
+ if (d_uri.query) {
+ ap_unescape_url(d_uri.query);
+ }
+ else if (r_uri.query) {
+ /* MSIE compatibility hack. MSIE has some RFC issues - doesn't
+ * include the query string in the uri Authorization component
+ * or when computing the response component. the second part
+ * works out ok, since we can hash the header and get the same
+ * result. however, the uri from the request line won't match
+ * the uri Authorization component since the header lacks the
+ * query string, leaving us incompatible with a (broken) MSIE.
+ *
+ * the workaround is to fake a query string match if in the proper
+ * environment - BrowserMatch MSIE, for example. the cool thing
+ * is that if MSIE ever fixes itself the simple match ought to
+ * work and this code won't be reached anyway, even if the
+ * environment is set.
+ */
+
+ if (apr_table_get(r->subprocess_env,
+ "AuthDigestEnableQueryStringHack")) {
+
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01784)
+ "applying AuthDigestEnableQueryStringHack "
+ "to uri <%s>", resp->raw_request_uri);
+
+ d_uri.query = r_uri.query;
+ }
+ }
+
+ if (r->method_number == M_CONNECT) {
+ if (!r_uri.hostinfo || strcmp(resp->uri, r_uri.hostinfo)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01785)
+ "uri mismatch - <%s> does not match "
+ "request-uri <%s>", resp->uri, r_uri.hostinfo);
+ return HTTP_BAD_REQUEST;
+ }
+ }
+ else if (
+ /* check hostname matches, if present */
+ (d_uri.hostname && d_uri.hostname[0] != '\0'
+ && strcasecmp(d_uri.hostname, r_uri.hostname))
+ /* check port matches, if present */
+ || (d_uri.port_str && d_uri.port != r_uri.port)
+ /* check that server-port is default port if no port present */
+ || (d_uri.hostname && d_uri.hostname[0] != '\0'
+ && !d_uri.port_str && r_uri.port != ap_default_port(r))
+ /* check that path matches */
+ || (d_uri.path != r_uri.path
+ /* either exact match */
+ && (!d_uri.path || !r_uri.path
+ || strcmp(d_uri.path, r_uri.path))
+ /* or '*' matches empty path in scheme://host */
+ && !(d_uri.path && !r_uri.path && resp->psd_request_uri->hostname
+ && d_uri.path[0] == '*' && d_uri.path[1] == '\0'))
+ /* check that query matches */
+ || (d_uri.query != r_uri.query
+ && (!d_uri.query || !r_uri.query
+ || strcmp(d_uri.query, r_uri.query)))
+ ) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01786)
+ "uri mismatch - <%s> does not match "
+ "request-uri <%s>", resp->uri, resp->raw_request_uri);
+ return HTTP_BAD_REQUEST;
+ }
+ }
+
+ if (resp->opaque && resp->opaque_num == 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01787)
+ "received invalid opaque - got `%s'",
+ resp->opaque);
+ note_digest_auth_failure(r, conf, resp, 0);
+ return HTTP_UNAUTHORIZED;
+ }
+
+ if (!conf->realm) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02533)
+ "realm mismatch - got `%s' but no realm specified",
+ resp->realm);
+ note_digest_auth_failure(r, conf, resp, 0);
+ return HTTP_UNAUTHORIZED;
+ }
+
+ if (!resp->realm || strcmp(resp->realm, conf->realm)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01788)
+ "realm mismatch - got `%s' but expected `%s'",
+ resp->realm, conf->realm);
+ note_digest_auth_failure(r, conf, resp, 0);
+ return HTTP_UNAUTHORIZED;
+ }
+
+ if (resp->algorithm != NULL
+ && ap_cstr_casecmp(resp->algorithm, "MD5")) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01789)
+ "unknown algorithm `%s' received: %s",
+ resp->algorithm, r->uri);
+ note_digest_auth_failure(r, conf, resp, 0);
+ return HTTP_UNAUTHORIZED;
+ }
+
+ return_code = get_hash(r, r->user, conf, &resp->ha1);
+
+ if (return_code == AUTH_USER_NOT_FOUND) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01790)
+ "user `%s' in realm `%s' not found: %s",
+ r->user, conf->realm, r->uri);
+ note_digest_auth_failure(r, conf, resp, 0);
+ return HTTP_UNAUTHORIZED;
+ }
+ else if (return_code == AUTH_USER_FOUND) {
+ /* we have a password, so continue */
+ }
+ else if (return_code == AUTH_DENIED) {
+ /* authentication denied in the provider before attempting a match */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01791)
+ "user `%s' in realm `%s' denied by provider: %s",
+ r->user, conf->realm, r->uri);
+ note_digest_auth_failure(r, conf, resp, 0);
+ return HTTP_UNAUTHORIZED;
+ }
+ else {
+ /* AUTH_GENERAL_ERROR (or worse)
+ * We'll assume that the module has already said what its error
+ * was in the logs.
+ */
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (resp->message_qop == NULL) {
+ /* old (rfc-2069) style digest */
+ if (strcmp(resp->digest, old_digest(r, resp))) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01792)
+ "user %s: password mismatch: %s", r->user,
+ r->uri);
+ note_digest_auth_failure(r, conf, resp, 0);
+ return HTTP_UNAUTHORIZED;
+ }
+ }
+ else {
+ const char *exp_digest;
+ int match = 0, idx;
+ const char **tmp = (const char **)(conf->qop_list->elts);
+ for (idx = 0; idx < conf->qop_list->nelts; idx++) {
+ if (!ap_cstr_casecmp(*tmp, resp->message_qop)) {
+ match = 1;
+ break;
+ }
+ ++tmp;
+ }
+
+ if (!match
+ && !(apr_is_empty_array(conf->qop_list)
+ && !ap_cstr_casecmp(resp->message_qop, "auth"))) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01793)
+ "invalid qop `%s' received: %s",
+ resp->message_qop, r->uri);
+ note_digest_auth_failure(r, conf, resp, 0);
+ return HTTP_UNAUTHORIZED;
+ }
+
+ exp_digest = new_digest(r, resp);
+ if (!exp_digest) {
+ /* we failed to allocate a client struct */
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ if (strcmp(resp->digest, exp_digest)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01794)
+ "user %s: password mismatch: %s", r->user,
+ r->uri);
+ note_digest_auth_failure(r, conf, resp, 0);
+ return HTTP_UNAUTHORIZED;
+ }
+ }
+
+ if (check_nc(r, resp, conf) != OK) {
+ note_digest_auth_failure(r, conf, resp, 0);
+ return HTTP_UNAUTHORIZED;
+ }
+
+ /* Note: this check is done last so that a "stale=true" can be
+ generated if the nonce is old */
+ if ((res = check_nonce(r, resp, conf))) {
+ return res;
+ }
+
+ return OK;
+}
+
+/*
+ * Authorization-Info header code
+ */
+
+static int add_auth_info(request_rec *r)
+{
+ const digest_config_rec *conf =
+ (digest_config_rec *) ap_get_module_config(r->per_dir_config,
+ &auth_digest_module);
+ digest_header_rec *resp =
+ (digest_header_rec *) ap_get_module_config(r->request_config,
+ &auth_digest_module);
+ const char *ai = NULL, *nextnonce = "";
+
+ if (resp == NULL || !resp->needed_auth || conf == NULL) {
+ return OK;
+ }
+
+ /* 2069-style entity-digest is not supported (it's too hard, and
+ * there are no clients which support 2069 but not 2617). */
+
+ /* setup nextnonce
+ */
+ if (conf->nonce_lifetime > 0) {
+ /* send nextnonce if current nonce will expire in less than 30 secs */
+ if ((r->request_time - resp->nonce_time) > (conf->nonce_lifetime-NEXTNONCE_DELTA)) {
+ nextnonce = apr_pstrcat(r->pool, ", nextnonce=\"",
+ gen_nonce(r->pool, r->request_time,
+ resp->opaque, r->server, conf),
+ "\"", NULL);
+ if (resp->client)
+ resp->client->nonce_count = 0;
+ }
+ }
+ else if (conf->nonce_lifetime == 0 && resp->client) {
+ const char *nonce = gen_nonce(r->pool, 0, resp->opaque, r->server,
+ conf);
+ nextnonce = apr_pstrcat(r->pool, ", nextnonce=\"", nonce, "\"", NULL);
+ memcpy(resp->client->last_nonce, nonce, NONCE_LEN+1);
+ }
+ /* else nonce never expires, hence no nextnonce */
+
+
+ /* do rfc-2069 digest
+ */
+ if (!apr_is_empty_array(conf->qop_list) &&
+ !ap_cstr_casecmp(*(const char **)(conf->qop_list->elts), "none")
+ && resp->message_qop == NULL) {
+ /* use only RFC-2069 format */
+ ai = nextnonce;
+ }
+ else {
+ const char *resp_dig, *ha1, *a2, *ha2;
+
+ /* calculate rspauth attribute
+ */
+ ha1 = resp->ha1;
+
+ a2 = apr_pstrcat(r->pool, ":", resp->uri, NULL);
+ ha2 = ap_md5(r->pool, (const unsigned char *)a2);
+
+ resp_dig = ap_md5(r->pool,
+ (unsigned char *)apr_pstrcat(r->pool, ha1, ":",
+ resp->nonce, ":",
+ resp->nonce_count, ":",
+ resp->cnonce, ":",
+ resp->message_qop ?
+ resp->message_qop : "",
+ ":", ha2, NULL));
+
+ /* assemble Authentication-Info header
+ */
+ ai = apr_pstrcat(r->pool,
+ "rspauth=\"", resp_dig, "\"",
+ nextnonce,
+ resp->cnonce ? ", cnonce=\"" : "",
+ resp->cnonce
+ ? ap_escape_quotes(r->pool, resp->cnonce)
+ : "",
+ resp->cnonce ? "\"" : "",
+ resp->nonce_count ? ", nc=" : "",
+ resp->nonce_count ? resp->nonce_count : "",
+ resp->message_qop ? ", qop=" : "",
+ resp->message_qop ? resp->message_qop : "",
+ NULL);
+ }
+
+ if (ai && ai[0]) {
+ apr_table_mergen(r->headers_out,
+ (PROXYREQ_PROXY == r->proxyreq)
+ ? "Proxy-Authentication-Info"
+ : "Authentication-Info",
+ ai);
+ }
+
+ return OK;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ static const char * const cfgPost[]={ "http_core.c", NULL };
+ static const char * const parsePre[]={ "mod_proxy.c", NULL };
+
+ ap_hook_pre_config(pre_init, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_post_config(initialize_module, NULL, cfgPost, APR_HOOK_MIDDLE);
+ ap_hook_child_init(initialize_child, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_post_read_request(parse_hdr_and_update_nc, parsePre, NULL, APR_HOOK_MIDDLE);
+ ap_hook_check_authn(authenticate_digest_user, NULL, NULL, APR_HOOK_MIDDLE,
+ AP_AUTH_INTERNAL_PER_CONF);
+
+ ap_hook_fixups(add_auth_info, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_note_auth_failure(hook_note_digest_auth_failure, NULL, NULL,
+ APR_HOOK_MIDDLE);
+
+}
+
+AP_DECLARE_MODULE(auth_digest) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_digest_dir_config, /* dir config creater */
+ NULL, /* dir merger --- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ digest_cmds, /* command table */
+ register_hooks /* register hooks */
+};
+
diff --git a/modules/aaa/mod_auth_digest.dep b/modules/aaa/mod_auth_digest.dep
new file mode 100644
index 0000000..81451de
--- /dev/null
+++ b/modules/aaa/mod_auth_digest.dep
@@ -0,0 +1,68 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_auth_digest.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_auth_digest.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_md5.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\srclib\apr-util\include\apr_anylock.h"\
+ "..\..\srclib\apr-util\include\apr_base64.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_rmm.h"\
+ "..\..\srclib\apr-util\include\apr_sha1.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_thread_rwlock.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/aaa/mod_auth_digest.dsp b/modules/aaa/mod_auth_digest.dsp
new file mode 100644
index 0000000..9d1bee1
--- /dev/null
+++ b/modules/aaa/mod_auth_digest.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_auth_digest" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_auth_digest - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_auth_digest.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_auth_digest.mak" CFG="mod_auth_digest - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_auth_digest - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_auth_digest - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_auth_digest - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_auth_digest_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_auth_digest.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_auth_digest.so" /d LONG_NAME="auth_digest_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_auth_digest.so" /base:@..\..\os\win32\BaseAddr.ref,mod_auth_digest.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_auth_digest.so" /base:@..\..\os\win32\BaseAddr.ref,mod_auth_digest.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_auth_digest.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_auth_digest - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_auth_digest_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_auth_digest.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_auth_digest.so" /d LONG_NAME="auth_digest_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_auth_digest.so" /base:@..\..\os\win32\BaseAddr.ref,mod_auth_digest.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_auth_digest.so" /base:@..\..\os\win32\BaseAddr.ref,mod_auth_digest.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_auth_digest.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_auth_digest - Win32 Release"
+# Name "mod_auth_digest - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_auth_digest.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/aaa/mod_auth_digest.mak b/modules/aaa/mod_auth_digest.mak
new file mode 100644
index 0000000..4b54375
--- /dev/null
+++ b/modules/aaa/mod_auth_digest.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_auth_digest.dsp
+!IF "$(CFG)" == ""
+CFG=mod_auth_digest - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_auth_digest - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_auth_digest - Win32 Release" && "$(CFG)" != "mod_auth_digest - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_auth_digest.mak" CFG="mod_auth_digest - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_auth_digest - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_auth_digest - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_auth_digest - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_auth_digest.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_auth_digest.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_auth_digest.obj"
+ -@erase "$(INTDIR)\mod_auth_digest.res"
+ -@erase "$(INTDIR)\mod_auth_digest_src.idb"
+ -@erase "$(INTDIR)\mod_auth_digest_src.pdb"
+ -@erase "$(OUTDIR)\mod_auth_digest.exp"
+ -@erase "$(OUTDIR)\mod_auth_digest.lib"
+ -@erase "$(OUTDIR)\mod_auth_digest.pdb"
+ -@erase "$(OUTDIR)\mod_auth_digest.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_auth_digest_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_auth_digest.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_auth_digest.so" /d LONG_NAME="auth_digest_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_auth_digest.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_auth_digest.pdb" /debug /out:"$(OUTDIR)\mod_auth_digest.so" /implib:"$(OUTDIR)\mod_auth_digest.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_auth_digest.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_auth_digest.obj" \
+ "$(INTDIR)\mod_auth_digest.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_auth_digest.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_auth_digest.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_auth_digest.so"
+ if exist .\Release\mod_auth_digest.so.manifest mt.exe -manifest .\Release\mod_auth_digest.so.manifest -outputresource:.\Release\mod_auth_digest.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_auth_digest - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_auth_digest.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_auth_digest.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_auth_digest.obj"
+ -@erase "$(INTDIR)\mod_auth_digest.res"
+ -@erase "$(INTDIR)\mod_auth_digest_src.idb"
+ -@erase "$(INTDIR)\mod_auth_digest_src.pdb"
+ -@erase "$(OUTDIR)\mod_auth_digest.exp"
+ -@erase "$(OUTDIR)\mod_auth_digest.lib"
+ -@erase "$(OUTDIR)\mod_auth_digest.pdb"
+ -@erase "$(OUTDIR)\mod_auth_digest.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_auth_digest_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_auth_digest.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_auth_digest.so" /d LONG_NAME="auth_digest_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_auth_digest.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_auth_digest.pdb" /debug /out:"$(OUTDIR)\mod_auth_digest.so" /implib:"$(OUTDIR)\mod_auth_digest.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_auth_digest.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_auth_digest.obj" \
+ "$(INTDIR)\mod_auth_digest.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_auth_digest.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_auth_digest.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_auth_digest.so"
+ if exist .\Debug\mod_auth_digest.so.manifest mt.exe -manifest .\Debug\mod_auth_digest.so.manifest -outputresource:.\Debug\mod_auth_digest.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_auth_digest.dep")
+!INCLUDE "mod_auth_digest.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_auth_digest.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_auth_digest - Win32 Release" || "$(CFG)" == "mod_auth_digest - Win32 Debug"
+
+!IF "$(CFG)" == "mod_auth_digest - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_auth_digest - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_auth_digest - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_auth_digest - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_auth_digest - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_auth_digest - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_auth_digest - Win32 Release"
+
+
+"$(INTDIR)\mod_auth_digest.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_auth_digest.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_auth_digest.so" /d LONG_NAME="auth_digest_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_auth_digest - Win32 Debug"
+
+
+"$(INTDIR)\mod_auth_digest.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_auth_digest.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_auth_digest.so" /d LONG_NAME="auth_digest_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_auth_digest.c
+
+"$(INTDIR)\mod_auth_digest.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/aaa/mod_auth_form.c b/modules/aaa/mod_auth_form.c
new file mode 100644
index 0000000..d443092
--- /dev/null
+++ b/modules/aaa/mod_auth_form.c
@@ -0,0 +1,1332 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr_strings.h"
+#include "apr_lib.h" /* for apr_isspace */
+#include "apr_base64.h" /* for apr_base64_decode et al */
+#define APR_WANT_STRFUNC /* for strcasecmp */
+#include "apr_want.h"
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "ap_provider.h"
+#include "util_md5.h"
+#include "ap_expr.h"
+
+#include "mod_auth.h"
+#include "mod_session.h"
+#include "mod_request.h"
+
+#define FORM_LOGIN_HANDLER "form-login-handler"
+#define FORM_LOGOUT_HANDLER "form-logout-handler"
+#define FORM_REDIRECT_HANDLER "form-redirect-handler"
+#define MOD_AUTH_FORM_HASH "site"
+
+static APR_OPTIONAL_FN_TYPE(ap_session_load) *ap_session_load_fn = NULL;
+static APR_OPTIONAL_FN_TYPE(ap_session_get) *ap_session_get_fn = NULL;
+static APR_OPTIONAL_FN_TYPE(ap_session_set) *ap_session_set_fn = NULL;
+
+static void (*ap_request_insert_filter_fn) (request_rec * r) = NULL;
+static void (*ap_request_remove_filter_fn) (request_rec * r) = NULL;
+
+typedef struct {
+ authn_provider_list *providers;
+ char *dir;
+ int authoritative;
+ int authoritative_set;
+ const char *site;
+ int site_set;
+ const char *username;
+ int username_set;
+ const char *password;
+ int password_set;
+ apr_size_t form_size;
+ int form_size_set;
+ int fakebasicauth;
+ int fakebasicauth_set;
+ const char *location;
+ int location_set;
+ const char *method;
+ int method_set;
+ const char *mimetype;
+ int mimetype_set;
+ const char *body;
+ int body_set;
+ int disable_no_store;
+ int disable_no_store_set;
+ ap_expr_info_t *loginsuccess;
+ int loginsuccess_set;
+ ap_expr_info_t *loginrequired;
+ int loginrequired_set;
+ ap_expr_info_t *logout;
+ int logout_set;
+} auth_form_config_rec;
+
+static void *create_auth_form_dir_config(apr_pool_t * p, char *d)
+{
+ auth_form_config_rec *conf = apr_pcalloc(p, sizeof(*conf));
+
+ conf->dir = d;
+ /* Any failures are fatal. */
+ conf->authoritative = 1;
+
+ /* form size defaults to 8k */
+ conf->form_size = HUGE_STRING_LEN;
+
+ /* default form field names */
+ conf->username = "httpd_username";
+ conf->password = "httpd_password";
+ conf->location = "httpd_location";
+ conf->method = "httpd_method";
+ conf->mimetype = "httpd_mimetype";
+ conf->body = "httpd_body";
+
+ return conf;
+}
+
+static void *merge_auth_form_dir_config(apr_pool_t * p, void *basev, void *addv)
+{
+ auth_form_config_rec *new = (auth_form_config_rec *) apr_pcalloc(p, sizeof(auth_form_config_rec));
+ auth_form_config_rec *add = (auth_form_config_rec *) addv;
+ auth_form_config_rec *base = (auth_form_config_rec *) basev;
+
+ new->providers = !add->providers ? base->providers : add->providers;
+ new->authoritative = (add->authoritative_set == 0) ? base->authoritative : add->authoritative;
+ new->authoritative_set = add->authoritative_set || base->authoritative_set;
+ new->site = (add->site_set == 0) ? base->site : add->site;
+ new->site_set = add->site_set || base->site_set;
+ new->username = (add->username_set == 0) ? base->username : add->username;
+ new->username_set = add->username_set || base->username_set;
+ new->password = (add->password_set == 0) ? base->password : add->password;
+ new->password_set = add->password_set || base->password_set;
+ new->location = (add->location_set == 0) ? base->location : add->location;
+ new->location_set = add->location_set || base->location_set;
+ new->form_size = (add->form_size_set == 0) ? base->form_size : add->form_size;
+ new->form_size_set = add->form_size_set || base->form_size_set;
+ new->fakebasicauth = (add->fakebasicauth_set == 0) ? base->fakebasicauth : add->fakebasicauth;
+ new->fakebasicauth_set = add->fakebasicauth_set || base->fakebasicauth_set;
+ new->method = (add->method_set == 0) ? base->method : add->method;
+ new->method_set = add->method_set || base->method_set;
+ new->mimetype = (add->mimetype_set == 0) ? base->mimetype : add->mimetype;
+ new->mimetype_set = add->mimetype_set || base->mimetype_set;
+ new->body = (add->body_set == 0) ? base->body : add->body;
+ new->body_set = add->body_set || base->body_set;
+ new->disable_no_store = (add->disable_no_store_set == 0) ? base->disable_no_store : add->disable_no_store;
+ new->disable_no_store_set = add->disable_no_store_set || base->disable_no_store_set;
+ new->loginsuccess = (add->loginsuccess_set == 0) ? base->loginsuccess : add->loginsuccess;
+ new->loginsuccess_set = add->loginsuccess_set || base->loginsuccess_set;
+ new->loginrequired = (add->loginrequired_set == 0) ? base->loginrequired : add->loginrequired;
+ new->loginrequired_set = add->loginrequired_set || base->loginrequired_set;
+ new->logout = (add->logout_set == 0) ? base->logout : add->logout;
+ new->logout_set = add->logout_set || base->logout_set;
+
+ return new;
+}
+
+static const char *add_authn_provider(cmd_parms * cmd, void *config,
+ const char *arg)
+{
+ auth_form_config_rec *conf = (auth_form_config_rec *) config;
+ authn_provider_list *newp;
+
+ newp = apr_pcalloc(cmd->pool, sizeof(authn_provider_list));
+ newp->provider_name = arg;
+
+ /* lookup and cache the actual provider now */
+ newp->provider = ap_lookup_provider(AUTHN_PROVIDER_GROUP,
+ newp->provider_name,
+ AUTHN_PROVIDER_VERSION);
+
+ if (newp->provider == NULL) {
+ /*
+ * by the time they use it, the provider should be loaded and
+ * registered with us.
+ */
+ return apr_psprintf(cmd->pool,
+ "Unknown Authn provider: %s",
+ newp->provider_name);
+ }
+
+ if (!newp->provider->check_password) {
+ /* if it doesn't provide the appropriate function, reject it */
+ return apr_psprintf(cmd->pool,
+ "The '%s' Authn provider doesn't support "
+ "Form Authentication", newp->provider_name);
+ }
+
+ /* Add it to the list now. */
+ if (!conf->providers) {
+ conf->providers = newp;
+ }
+ else {
+ authn_provider_list *last = conf->providers;
+
+ while (last->next) {
+ last = last->next;
+ }
+ last->next = newp;
+ }
+
+ return NULL;
+}
+
+/**
+ * Sanity check a given string that it exists, is not empty,
+ * and does not contain special characters.
+ */
+static const char *check_string(cmd_parms * cmd, const char *string)
+{
+ if (!string || !*string || ap_strchr_c(string, '=') || ap_strchr_c(string, '&')) {
+ return apr_pstrcat(cmd->pool, cmd->directive->directive,
+ " cannot be empty, or contain '=' or '&'.",
+ NULL);
+ }
+ return NULL;
+}
+
+static const char *set_cookie_form_location(cmd_parms * cmd, void *config, const char *location)
+{
+ auth_form_config_rec *conf = (auth_form_config_rec *) config;
+ conf->location = location;
+ conf->location_set = 1;
+ return check_string(cmd, location);
+}
+
+static const char *set_cookie_form_username(cmd_parms * cmd, void *config, const char *username)
+{
+ auth_form_config_rec *conf = (auth_form_config_rec *) config;
+ conf->username = username;
+ conf->username_set = 1;
+ return check_string(cmd, username);
+}
+
+static const char *set_cookie_form_password(cmd_parms * cmd, void *config, const char *password)
+{
+ auth_form_config_rec *conf = (auth_form_config_rec *) config;
+ conf->password = password;
+ conf->password_set = 1;
+ return check_string(cmd, password);
+}
+
+static const char *set_cookie_form_method(cmd_parms * cmd, void *config, const char *method)
+{
+ auth_form_config_rec *conf = (auth_form_config_rec *) config;
+ conf->method = method;
+ conf->method_set = 1;
+ return check_string(cmd, method);
+}
+
+static const char *set_cookie_form_mimetype(cmd_parms * cmd, void *config, const char *mimetype)
+{
+ auth_form_config_rec *conf = (auth_form_config_rec *) config;
+ conf->mimetype = mimetype;
+ conf->mimetype_set = 1;
+ return check_string(cmd, mimetype);
+}
+
+static const char *set_cookie_form_body(cmd_parms * cmd, void *config, const char *body)
+{
+ auth_form_config_rec *conf = (auth_form_config_rec *) config;
+ conf->body = body;
+ conf->body_set = 1;
+ return check_string(cmd, body);
+}
+
+static const char *set_cookie_form_size(cmd_parms * cmd, void *config,
+ const char *arg)
+{
+ auth_form_config_rec *conf = config;
+ apr_off_t size;
+
+ if (APR_SUCCESS != apr_strtoff(&size, arg, NULL, 10)
+ || size < 0 || size > APR_SIZE_MAX) {
+ return "AuthCookieFormSize must be a size in bytes, or zero.";
+ }
+ conf->form_size = (apr_size_t)size;
+ conf->form_size_set = 1;
+
+ return NULL;
+}
+
+static const char *set_login_required_location(cmd_parms * cmd, void *config, const char *loginrequired)
+{
+ auth_form_config_rec *conf = (auth_form_config_rec *) config;
+ const char *err;
+
+ conf->loginrequired = ap_expr_parse_cmd(cmd, loginrequired, AP_EXPR_FLAG_STRING_RESULT,
+ &err, NULL);
+ if (err) {
+ return apr_psprintf(cmd->pool,
+ "Could not parse login required expression '%s': %s",
+ loginrequired, err);
+ }
+ conf->loginrequired_set = 1;
+
+ return NULL;
+}
+
+static const char *set_login_success_location(cmd_parms * cmd, void *config, const char *loginsuccess)
+{
+ auth_form_config_rec *conf = (auth_form_config_rec *) config;
+ const char *err;
+
+ conf->loginsuccess = ap_expr_parse_cmd(cmd, loginsuccess, AP_EXPR_FLAG_STRING_RESULT,
+ &err, NULL);
+ if (err) {
+ return apr_psprintf(cmd->pool,
+ "Could not parse login success expression '%s': %s",
+ loginsuccess, err);
+ }
+ conf->loginsuccess_set = 1;
+
+ return NULL;
+}
+
+static const char *set_logout_location(cmd_parms * cmd, void *config, const char *logout)
+{
+ auth_form_config_rec *conf = (auth_form_config_rec *) config;
+ const char *err;
+
+ conf->logout = ap_expr_parse_cmd(cmd, logout, AP_EXPR_FLAG_STRING_RESULT,
+ &err, NULL);
+ if (err) {
+ return apr_psprintf(cmd->pool,
+ "Could not parse logout required expression '%s': %s",
+ logout, err);
+ }
+ conf->logout_set = 1;
+
+ return NULL;
+}
+
+static const char *set_site_passphrase(cmd_parms * cmd, void *config, const char *site)
+{
+ auth_form_config_rec *conf = (auth_form_config_rec *) config;
+ conf->site = site;
+ conf->site_set = 1;
+ return NULL;
+}
+
+static const char *set_authoritative(cmd_parms * cmd, void *config, int flag)
+{
+ auth_form_config_rec *conf = (auth_form_config_rec *) config;
+ conf->authoritative = flag;
+ conf->authoritative_set = 1;
+ return NULL;
+}
+
+static const char *set_fake_basic_auth(cmd_parms * cmd, void *config, int flag)
+{
+ auth_form_config_rec *conf = (auth_form_config_rec *) config;
+ conf->fakebasicauth = flag;
+ conf->fakebasicauth_set = 1;
+ return NULL;
+}
+
+static const char *set_disable_no_store(cmd_parms * cmd, void *config, int flag)
+{
+ auth_form_config_rec *conf = (auth_form_config_rec *) config;
+ conf->disable_no_store = flag;
+ conf->disable_no_store_set = 1;
+ return NULL;
+}
+
+static const command_rec auth_form_cmds[] =
+{
+ AP_INIT_ITERATE("AuthFormProvider", add_authn_provider, NULL, OR_AUTHCFG,
+ "specify the auth providers for a directory or location"),
+ AP_INIT_TAKE1("AuthFormUsername", set_cookie_form_username, NULL, OR_AUTHCFG,
+ "The field of the login form carrying the username"),
+ AP_INIT_TAKE1("AuthFormPassword", set_cookie_form_password, NULL, OR_AUTHCFG,
+ "The field of the login form carrying the password"),
+ AP_INIT_TAKE1("AuthFormLocation", set_cookie_form_location, NULL, OR_AUTHCFG,
+ "The field of the login form carrying the URL to redirect on "
+ "successful login."),
+ AP_INIT_TAKE1("AuthFormMethod", set_cookie_form_method, NULL, OR_AUTHCFG,
+ "The field of the login form carrying the original request method."),
+ AP_INIT_TAKE1("AuthFormMimetype", set_cookie_form_mimetype, NULL, OR_AUTHCFG,
+ "The field of the login form carrying the original request mimetype."),
+ AP_INIT_TAKE1("AuthFormBody", set_cookie_form_body, NULL, OR_AUTHCFG,
+ "The field of the login form carrying the urlencoded original request "
+ "body."),
+ AP_INIT_TAKE1("AuthFormSize", set_cookie_form_size, NULL, ACCESS_CONF,
+ "Maximum size of body parsed by the form parser"),
+ AP_INIT_TAKE1("AuthFormLoginRequiredLocation", set_login_required_location,
+ NULL, OR_AUTHCFG,
+ "If set, redirect the browser to this URL rather than "
+ "return 401 Not Authorized."),
+ AP_INIT_TAKE1("AuthFormLoginSuccessLocation", set_login_success_location,
+ NULL, OR_AUTHCFG,
+ "If set, redirect the browser to this URL when a login "
+ "processed by the login handler is successful."),
+ AP_INIT_TAKE1("AuthFormLogoutLocation", set_logout_location,
+ NULL, OR_AUTHCFG,
+ "The URL of the logout successful page. An attempt to access an "
+ "URL handled by the handler " FORM_LOGOUT_HANDLER " will result "
+ "in an redirect to this page after logout."),
+ AP_INIT_TAKE1("AuthFormSitePassphrase", set_site_passphrase,
+ NULL, OR_AUTHCFG,
+ "If set, use this passphrase to determine whether the user should "
+ "be authenticated. Bypasses the user authentication check on "
+ "every website hit, and is useful for high traffic sites."),
+ AP_INIT_FLAG("AuthFormAuthoritative", set_authoritative,
+ NULL, OR_AUTHCFG,
+ "Set to 'Off' to allow access control to be passed along to "
+ "lower modules if the UserID is not known to this module"),
+ AP_INIT_FLAG("AuthFormFakeBasicAuth", set_fake_basic_auth,
+ NULL, OR_AUTHCFG,
+ "Set to 'On' to pass through authentication to the rest of the "
+ "server as a basic authentication header."),
+ AP_INIT_FLAG("AuthFormDisableNoStore", set_disable_no_store,
+ NULL, OR_AUTHCFG,
+ "Set to 'on' to stop the sending of a Cache-Control no-store header with "
+ "the login screen. This allows the browser to cache the credentials, but "
+ "at the risk of it being possible for the login form to be resubmitted "
+ "and revealed to the backend server through XSS. Use at own risk."),
+ {NULL}
+};
+
+module AP_MODULE_DECLARE_DATA auth_form_module;
+
+static void note_cookie_auth_failure(request_rec * r)
+{
+ auth_form_config_rec *conf = ap_get_module_config(r->per_dir_config,
+ &auth_form_module);
+
+ if (conf->location && ap_strchr_c(conf->location, ':')) {
+ apr_table_setn(r->err_headers_out, "Location", conf->location);
+ }
+}
+
+static int hook_note_cookie_auth_failure(request_rec * r,
+ const char *auth_type)
+{
+ if (ap_cstr_casecmp(auth_type, "form"))
+ return DECLINED;
+
+ note_cookie_auth_failure(r);
+ return OK;
+}
+
+/**
+ * Set the auth username and password into the main request
+ * notes table.
+ */
+static void set_notes_auth(request_rec * r,
+ const char *user, const char *pw,
+ const char *method, const char *mimetype)
+{
+ apr_table_t *notes = NULL;
+ const char *authname;
+
+ /* find the main request */
+ while (r->main) {
+ r = r->main;
+ }
+ /* find the first redirect */
+ while (r->prev) {
+ r = r->prev;
+ }
+ notes = r->notes;
+
+ /* have we isolated the user and pw before? */
+ authname = ap_auth_name(r);
+ if (user) {
+ apr_table_setn(notes, apr_pstrcat(r->pool, authname, "-user", NULL), user);
+ }
+ if (pw) {
+ apr_table_setn(notes, apr_pstrcat(r->pool, authname, "-pw", NULL), pw);
+ }
+ if (method) {
+ apr_table_setn(notes, apr_pstrcat(r->pool, authname, "-method", NULL), method);
+ }
+ if (mimetype) {
+ apr_table_setn(notes, apr_pstrcat(r->pool, authname, "-mimetype", NULL), mimetype);
+ }
+
+}
+
+/**
+ * Get the auth username and password from the main request
+ * notes table, if present.
+ */
+static void get_notes_auth(request_rec *r,
+ const char **user, const char **pw,
+ const char **method, const char **mimetype)
+{
+ const char *authname;
+ request_rec *m = r;
+
+ /* find the main request */
+ while (m->main) {
+ m = m->main;
+ }
+ /* find the first redirect */
+ while (m->prev) {
+ m = m->prev;
+ }
+
+ /* have we isolated the user and pw before? */
+ authname = ap_auth_name(m);
+ if (user) {
+ *user = (char *) apr_table_get(m->notes, apr_pstrcat(m->pool, authname, "-user", NULL));
+ }
+ if (pw) {
+ *pw = (char *) apr_table_get(m->notes, apr_pstrcat(m->pool, authname, "-pw", NULL));
+ }
+ if (method) {
+ *method = (char *) apr_table_get(m->notes, apr_pstrcat(m->pool, authname, "-method", NULL));
+ }
+ if (mimetype) {
+ *mimetype = (char *) apr_table_get(m->notes, apr_pstrcat(m->pool, authname, "-mimetype", NULL));
+ }
+
+ /* set the user, even though the user is unauthenticated at this point */
+ if (user && *user) {
+ r->user = (char *) *user;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "from notes: user: %s, pw: %s, method: %s, mimetype: %s",
+ user ? *user : "<null>", pw ? *pw : "<null>",
+ method ? *method : "<null>", mimetype ? *mimetype : "<null>");
+
+}
+
+/**
+ * Set the auth username and password into the session.
+ *
+ * If either the username, or the password are NULL, the username
+ * and/or password will be removed from the session.
+ */
+static apr_status_t set_session_auth(request_rec * r,
+ const char *user, const char *pw, const char *site)
+{
+ const char *hash = NULL;
+ const char *authname = ap_auth_name(r);
+ session_rec *z = NULL;
+
+ if (site) {
+ hash = ap_md5(r->pool,
+ (unsigned char *) apr_pstrcat(r->pool, user, ":", site, NULL));
+ }
+
+ ap_session_load_fn(r, &z);
+ ap_session_set_fn(r, z, apr_pstrcat(r->pool, authname, "-" MOD_SESSION_USER, NULL), user);
+ ap_session_set_fn(r, z, apr_pstrcat(r->pool, authname, "-" MOD_SESSION_PW, NULL), pw);
+ ap_session_set_fn(r, z, apr_pstrcat(r->pool, authname, "-" MOD_AUTH_FORM_HASH, NULL), hash);
+
+ return APR_SUCCESS;
+
+}
+
+/**
+ * Get the auth username and password from the main request
+ * notes table, if present.
+ */
+static apr_status_t get_session_auth(request_rec * r,
+ const char **user, const char **pw, const char **hash)
+{
+ const char *authname = ap_auth_name(r);
+ session_rec *z = NULL;
+
+ ap_session_load_fn(r, &z);
+
+ if (user) {
+ ap_session_get_fn(r, z, apr_pstrcat(r->pool, authname, "-" MOD_SESSION_USER, NULL), user);
+ }
+ if (pw) {
+ ap_session_get_fn(r, z, apr_pstrcat(r->pool, authname, "-" MOD_SESSION_PW, NULL), pw);
+ }
+ if (hash) {
+ ap_session_get_fn(r, z, apr_pstrcat(r->pool, authname, "-" MOD_AUTH_FORM_HASH, NULL), hash);
+ }
+
+ /* set the user, even though the user is unauthenticated at this point */
+ if (user && *user) {
+ r->user = (char *) *user;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "from session: " MOD_SESSION_USER ": %s, " MOD_SESSION_PW
+ ": %s, " MOD_AUTH_FORM_HASH ": %s",
+ user ? *user : "<null>", pw ? *pw : "<null>",
+ hash ? *hash : "<null>");
+
+ return APR_SUCCESS;
+
+}
+
+/**
+ * Isolate the username and password in a POSTed form with the
+ * username in the "username" field, and the password in the
+ * "password" field.
+ *
+ * If either the username or the password is missing, this
+ * function will return HTTP_UNAUTHORIZED.
+ *
+ * The location field is considered optional, and will be returned
+ * if present.
+ */
+static int get_form_auth(request_rec * r,
+ const char *username,
+ const char *password,
+ const char *location,
+ const char *method,
+ const char *mimetype,
+ const char *body,
+ const char **sent_user,
+ const char **sent_pw,
+ const char **sent_loc,
+ const char **sent_method,
+ const char **sent_mimetype,
+ apr_bucket_brigade **sent_body,
+ auth_form_config_rec * conf)
+{
+ /* sanity check - are we a POST request? */
+
+ /* find the username and password in the form */
+ apr_array_header_t *pairs = NULL;
+ apr_off_t len;
+ apr_size_t size;
+ int res;
+ char *buffer;
+
+ /* have we isolated the user and pw before? */
+ get_notes_auth(r, sent_user, sent_pw, sent_method, sent_mimetype);
+ if (sent_user && *sent_user && sent_pw && *sent_pw) {
+ return OK;
+ }
+
+ res = ap_parse_form_data(r, NULL, &pairs, -1, conf->form_size);
+ if (res != OK) {
+ return res;
+ }
+ while (pairs && !apr_is_empty_array(pairs)) {
+ ap_form_pair_t *pair = (ap_form_pair_t *) apr_array_pop(pairs);
+ if (username && !strcmp(pair->name, username) && sent_user) {
+ apr_brigade_length(pair->value, 1, &len);
+ size = (apr_size_t) len;
+ buffer = apr_palloc(r->pool, size + 1);
+ apr_brigade_flatten(pair->value, buffer, &size);
+ buffer[len] = 0;
+ *sent_user = buffer;
+ }
+ else if (password && !strcmp(pair->name, password) && sent_pw) {
+ apr_brigade_length(pair->value, 1, &len);
+ size = (apr_size_t) len;
+ buffer = apr_palloc(r->pool, size + 1);
+ apr_brigade_flatten(pair->value, buffer, &size);
+ buffer[len] = 0;
+ *sent_pw = buffer;
+ }
+ else if (location && !strcmp(pair->name, location) && sent_loc) {
+ apr_brigade_length(pair->value, 1, &len);
+ size = (apr_size_t) len;
+ buffer = apr_palloc(r->pool, size + 1);
+ apr_brigade_flatten(pair->value, buffer, &size);
+ buffer[len] = 0;
+ *sent_loc = buffer;
+ }
+ else if (method && !strcmp(pair->name, method) && sent_method) {
+ apr_brigade_length(pair->value, 1, &len);
+ size = (apr_size_t) len;
+ buffer = apr_palloc(r->pool, size + 1);
+ apr_brigade_flatten(pair->value, buffer, &size);
+ buffer[len] = 0;
+ *sent_method = buffer;
+ }
+ else if (mimetype && !strcmp(pair->name, mimetype) && sent_mimetype) {
+ apr_brigade_length(pair->value, 1, &len);
+ size = (apr_size_t) len;
+ buffer = apr_palloc(r->pool, size + 1);
+ apr_brigade_flatten(pair->value, buffer, &size);
+ buffer[len] = 0;
+ *sent_mimetype = buffer;
+ }
+ else if (body && !strcmp(pair->name, body) && sent_body) {
+ *sent_body = pair->value;
+ }
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "from form: user: %s, pw: %s, method: %s, mimetype: %s, location: %s",
+ sent_user ? *sent_user : "<null>", sent_pw ? *sent_pw : "<null>",
+ sent_method ? *sent_method : "<null>",
+ sent_mimetype ? *sent_mimetype : "<null>",
+ sent_loc ? *sent_loc : "<null>");
+
+ /* set the user, even though the user is unauthenticated at this point */
+ if (sent_user && *sent_user) {
+ r->user = (char *) *sent_user;
+ }
+
+ /* a missing username or missing password means auth denied */
+ if (!sent_user || !*sent_user) {
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02982)
+ "form parsed, but username field '%s' was missing or empty, unauthorized",
+ username);
+
+ return HTTP_UNAUTHORIZED;
+ }
+ if (!sent_pw || !*sent_pw) {
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02983)
+ "form parsed, but password field '%s' was missing or empty, unauthorized",
+ password);
+
+ return HTTP_UNAUTHORIZED;
+ }
+
+ /*
+ * save away the username, password, mimetype and method, so that they
+ * are available should the auth need to be run again.
+ */
+ set_notes_auth(r, *sent_user, *sent_pw, sent_method ? *sent_method : NULL,
+ sent_mimetype ? *sent_mimetype : NULL);
+
+ return OK;
+}
+
+/* These functions return 0 if client is OK, and proper error status
+ * if not... either HTTP_UNAUTHORIZED, if we made a check, and it failed, or
+ * HTTP_INTERNAL_SERVER_ERROR, if things are so totally confused that we
+ * couldn't figure out how to tell if the client is authorized or not.
+ *
+ * If they return DECLINED, and all other modules also decline, that's
+ * treated by the server core as a configuration error, logged and
+ * reported as such.
+ */
+
+
+/**
+ * Given a username and site passphrase hash from the session, determine
+ * whether the site passphrase is valid for this session.
+ *
+ * If the site passphrase is NULL, or if the sent_hash is NULL, this
+ * function returns DECLINED.
+ *
+ * If the site passphrase hash does not match the sent hash, this function
+ * returns AUTH_USER_NOT_FOUND.
+ *
+ * On success, returns OK.
+ */
+static int check_site(request_rec * r, const char *site, const char *sent_user, const char *sent_hash)
+{
+
+ if (site && sent_user && sent_hash) {
+ const char *hash = ap_md5(r->pool,
+ (unsigned char *) apr_pstrcat(r->pool, sent_user, ":", site, NULL));
+
+ if (!strcmp(sent_hash, hash)) {
+ return OK;
+ }
+ else {
+ return AUTH_USER_NOT_FOUND;
+ }
+ }
+
+ return DECLINED;
+
+}
+
+/**
+ * Given a username and password (extracted externally from a cookie), run
+ * the authnz hooks to determine whether this request is authorized.
+ *
+ * Return an HTTP code.
+ */
+static int check_authn(request_rec * r, const char *sent_user, const char *sent_pw)
+{
+ authn_status auth_result;
+ authn_provider_list *current_provider;
+ auth_form_config_rec *conf = ap_get_module_config(r->per_dir_config,
+ &auth_form_module);
+
+ current_provider = conf->providers;
+ do {
+ const authn_provider *provider;
+
+ /*
+ * For now, if a provider isn't set, we'll be nice and use the file
+ * provider.
+ */
+ if (!current_provider) {
+ provider = ap_lookup_provider(AUTHN_PROVIDER_GROUP,
+ AUTHN_DEFAULT_PROVIDER,
+ AUTHN_PROVIDER_VERSION);
+
+ if (!provider || !provider->check_password) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01806)
+ "no authn provider configured");
+ auth_result = AUTH_GENERAL_ERROR;
+ break;
+ }
+ apr_table_setn(r->notes, AUTHN_PROVIDER_NAME_NOTE, AUTHN_DEFAULT_PROVIDER);
+ }
+ else {
+ provider = current_provider->provider;
+ apr_table_setn(r->notes, AUTHN_PROVIDER_NAME_NOTE, current_provider->provider_name);
+ }
+
+ if (!sent_user || !sent_pw) {
+ auth_result = AUTH_USER_NOT_FOUND;
+ break;
+ }
+
+ auth_result = provider->check_password(r, sent_user, sent_pw);
+
+ apr_table_unset(r->notes, AUTHN_PROVIDER_NAME_NOTE);
+
+ /* Something occurred. Stop checking. */
+ if (auth_result != AUTH_USER_NOT_FOUND) {
+ break;
+ }
+
+ /* If we're not really configured for providers, stop now. */
+ if (!conf->providers) {
+ break;
+ }
+
+ current_provider = current_provider->next;
+ } while (current_provider);
+
+ if (auth_result != AUTH_GRANTED) {
+ int return_code;
+
+ /* If we're not authoritative, then any error is ignored. */
+ if (!(conf->authoritative) && auth_result != AUTH_DENIED) {
+ return DECLINED;
+ }
+
+ switch (auth_result) {
+ case AUTH_DENIED:
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01807)
+ "user '%s': authentication failure for \"%s\": "
+ "password Mismatch",
+ sent_user, r->uri);
+ return_code = HTTP_UNAUTHORIZED;
+ break;
+ case AUTH_USER_NOT_FOUND:
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01808)
+ "user '%s' not found: %s", sent_user, r->uri);
+ return_code = HTTP_UNAUTHORIZED;
+ break;
+ case AUTH_GENERAL_ERROR:
+ default:
+ /*
+ * We'll assume that the module has already said what its error
+ * was in the logs.
+ */
+ return_code = HTTP_INTERNAL_SERVER_ERROR;
+ break;
+ }
+
+ /* If we're returning 401, tell them to try again. */
+ if (return_code == HTTP_UNAUTHORIZED) {
+ note_cookie_auth_failure(r);
+ }
+
+/* TODO: Flag the user somehow as to the reason for the failure */
+
+ return return_code;
+ }
+
+ return OK;
+
+}
+
+/* fake the basic authentication header if configured to do so */
+static void fake_basic_authentication(request_rec *r, auth_form_config_rec *conf,
+ const char *user, const char *pw)
+{
+ if (conf->fakebasicauth) {
+ char *basic = apr_pstrcat(r->pool, user, ":", pw, NULL);
+ apr_size_t size = (apr_size_t) strlen(basic);
+ char *base64 = apr_palloc(r->pool,
+ apr_base64_encode_len(size + 1) * sizeof(char));
+ apr_base64_encode(base64, basic, size);
+ apr_table_setn(r->headers_in, "Authorization",
+ apr_pstrcat(r->pool, "Basic ", base64, NULL));
+ }
+}
+
+/**
+ * Must we use form authentication? If so, extract the cookie and run
+ * the authnz hooks to determine if the login is valid.
+ *
+ * If the login is not valid, a 401 Not Authorized will be returned. It
+ * is up to the webmaster to ensure this screen displays a suitable login
+ * form to give the user the opportunity to log in.
+ */
+static int authenticate_form_authn(request_rec * r)
+{
+ auth_form_config_rec *conf = ap_get_module_config(r->per_dir_config,
+ &auth_form_module);
+ const char *sent_user = NULL, *sent_pw = NULL, *sent_hash = NULL;
+ const char *sent_loc = NULL, *sent_method = "GET", *sent_mimetype = NULL;
+ const char *current_auth = NULL;
+ const char *err;
+ apr_status_t res;
+ int rv = HTTP_UNAUTHORIZED;
+
+ /* Are we configured to be Form auth? */
+ current_auth = ap_auth_type(r);
+ if (!current_auth || ap_cstr_casecmp(current_auth, "form")) {
+ return DECLINED;
+ }
+
+ /*
+ * XSS security warning: using cookies to store private data only works
+ * when the administrator has full control over the source website. When
+ * in forward-proxy mode, websites are public by definition, and so can
+ * never be secure. Abort the auth attempt in this case.
+ */
+ if (PROXYREQ_PROXY == r->proxyreq) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01809)
+ "form auth cannot be used for proxy "
+ "requests due to XSS risk, access denied: %s", r->uri);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* We need an authentication realm. */
+ if (!ap_auth_name(r)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01810)
+ "need AuthName: %s", r->uri);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ r->ap_auth_type = (char *) current_auth;
+
+ /* try get the username and password from the notes, if present */
+ get_notes_auth(r, &sent_user, &sent_pw, &sent_method, &sent_mimetype);
+ if (!sent_user || !sent_pw || !*sent_user || !*sent_pw) {
+
+ /* otherwise try get the username and password from a session, if present */
+ res = get_session_auth(r, &sent_user, &sent_pw, &sent_hash);
+
+ }
+ else {
+ res = APR_SUCCESS;
+ }
+
+ /* first test whether the site passphrase matches */
+ if (APR_SUCCESS == res && sent_user && sent_hash && sent_pw) {
+ rv = check_site(r, conf->site, sent_user, sent_hash);
+ if (OK == rv) {
+ fake_basic_authentication(r, conf, sent_user, sent_pw);
+ return OK;
+ }
+ }
+
+ /* otherwise test for a normal password match */
+ if (APR_SUCCESS == res && sent_user && sent_pw) {
+ rv = check_authn(r, sent_user, sent_pw);
+ if (OK == rv) {
+ fake_basic_authentication(r, conf, sent_user, sent_pw);
+ return OK;
+ }
+ }
+
+ /*
+ * If we reach this point, the request should fail with access denied,
+ * except for one potential scenario:
+ *
+ * If the request is a POST, and the posted form contains user defined fields
+ * for a username and a password, and the username and password are correct,
+ * then return the response obtained by a GET to this URL.
+ *
+ * If an additional user defined location field is present in the form,
+ * instead of a GET of the current URL, redirect the browser to the new
+ * location.
+ *
+ * As a further option, if the user defined fields for the type of request,
+ * the mime type of the body of the request, and the body of the request
+ * itself are present, replace this request with a new request of the given
+ * type and with the given body.
+ *
+ * Otherwise access is denied.
+ *
+ * Reading the body requires some song and dance, because the input filters
+ * are not yet configured. To work around this problem, we create a
+ * subrequest and use that to create a sane filter stack we can read the
+ * form from.
+ *
+ * The main request is then capped with a kept_body input filter, which has
+ * the effect of guaranteeing the input stack can be safely read a second time.
+ *
+ */
+ if (HTTP_UNAUTHORIZED == rv && r->method_number == M_POST && ap_is_initial_req(r)) {
+ request_rec *rr;
+ apr_bucket_brigade *sent_body = NULL;
+
+ /* create a subrequest of our current uri */
+ rr = ap_sub_req_lookup_uri(r->uri, r, r->input_filters);
+ rr->headers_in = r->headers_in;
+
+ /* run the insert_filters hook on the subrequest to ensure a body read can
+ * be done properly.
+ */
+ ap_run_insert_filter(rr);
+
+ /* parse the form by reading the subrequest */
+ rv = get_form_auth(rr, conf->username, conf->password, conf->location,
+ conf->method, conf->mimetype, conf->body,
+ &sent_user, &sent_pw, &sent_loc, &sent_method,
+ &sent_mimetype, &sent_body, conf);
+
+ /* make sure any user detected within the subrequest is saved back to
+ * the main request.
+ */
+ r->user = apr_pstrdup(r->pool, rr->user);
+
+ /* we cannot clean up rr at this point, as memory allocated to rr is
+ * referenced from the main request. It will be cleaned up when the
+ * main request is cleaned up.
+ */
+
+ /* insert the kept_body filter on the main request to guarantee the
+ * input filter stack cannot be read a second time, optionally inject
+ * a saved body if one was specified in the login form.
+ */
+ if (sent_body && sent_mimetype) {
+ apr_table_set(r->headers_in, "Content-Type", sent_mimetype);
+ r->kept_body = sent_body;
+ }
+ else {
+ r->kept_body = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+ }
+ ap_request_insert_filter_fn(r);
+
+ /* did the form ask to change the method? if so, switch in the redirect handler
+ * to relaunch this request as the subrequest with the new method. If the
+ * form didn't specify a method, the default value GET will force a redirect.
+ */
+ if (sent_method && strcmp(r->method, sent_method)) {
+ r->handler = FORM_REDIRECT_HANDLER;
+ }
+
+ /* check the authn in the main request, based on the username found */
+ if (OK == rv) {
+ rv = check_authn(r, sent_user, sent_pw);
+ if (OK == rv) {
+ fake_basic_authentication(r, conf, sent_user, sent_pw);
+ set_session_auth(r, sent_user, sent_pw, conf->site);
+ if (sent_loc) {
+ apr_table_set(r->headers_out, "Location", sent_loc);
+ return HTTP_MOVED_TEMPORARILY;
+ }
+ if (conf->loginsuccess) {
+ const char *loginsuccess = ap_expr_str_exec(r,
+ conf->loginsuccess, &err);
+ if (!err) {
+ apr_table_set(r->headers_out, "Location", loginsuccess);
+ return HTTP_MOVED_TEMPORARILY;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02339)
+ "Can't evaluate login success expression: %s", err);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+ }
+ }
+
+ }
+
+ /*
+ * did the admin prefer to be redirected to the login page on failure
+ * instead?
+ */
+ if (HTTP_UNAUTHORIZED == rv && conf->loginrequired) {
+ const char *loginrequired = ap_expr_str_exec(r,
+ conf->loginrequired, &err);
+ if (!err) {
+ apr_table_set(r->headers_out, "Location", loginrequired);
+ return HTTP_MOVED_TEMPORARILY;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02340)
+ "Can't evaluate login required expression: %s", err);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ /* did the user ask to be redirected on login success? */
+ if (sent_loc) {
+ apr_table_set(r->headers_out, "Location", sent_loc);
+ rv = HTTP_MOVED_TEMPORARILY;
+ }
+
+
+ /*
+ * potential security issue: if we return a login to the browser, we must
+ * send a no-store to make sure a well behaved browser will not try and
+ * send the login details a second time if the back button is pressed.
+ *
+ * if the user has full control over the backend, the
+ * AuthCookieDisableNoStore can be used to turn this off.
+ */
+ if (HTTP_UNAUTHORIZED == rv && !conf->disable_no_store) {
+ apr_table_addn(r->headers_out, "Cache-Control", "no-store");
+ apr_table_addn(r->err_headers_out, "Cache-Control", "no-store");
+ }
+
+ return rv;
+
+}
+
+/**
+ * Handle a login attempt.
+ *
+ * If the login session is either missing or form authnz is unsuccessful, a
+ * 401 Not Authorized will be returned to the browser. The webmaster
+ * is expected to insert a login form into the 401 Not Authorized
+ * error screen.
+ *
+ * If the webmaster wishes, they can point the form submission at this
+ * handler, which will redirect the user to the correct page on success.
+ * On failure, the 401 Not Authorized error screen will be redisplayed,
+ * where the login attempt can be repeated.
+ *
+ */
+static int authenticate_form_login_handler(request_rec * r)
+{
+ auth_form_config_rec *conf;
+ const char *err;
+
+ const char *sent_user = NULL, *sent_pw = NULL, *sent_loc = NULL;
+ int rv;
+
+ if (strcmp(r->handler, FORM_LOGIN_HANDLER)) {
+ return DECLINED;
+ }
+
+ if (r->method_number != M_POST) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01811)
+ "the " FORM_LOGIN_HANDLER " only supports the POST method for %s",
+ r->uri);
+ return HTTP_METHOD_NOT_ALLOWED;
+ }
+
+ conf = ap_get_module_config(r->per_dir_config, &auth_form_module);
+
+ rv = get_form_auth(r, conf->username, conf->password, conf->location,
+ NULL, NULL, NULL,
+ &sent_user, &sent_pw, &sent_loc,
+ NULL, NULL, NULL, conf);
+ if (OK == rv) {
+ rv = check_authn(r, sent_user, sent_pw);
+ if (OK == rv) {
+ set_session_auth(r, sent_user, sent_pw, conf->site);
+ if (sent_loc) {
+ apr_table_set(r->headers_out, "Location", sent_loc);
+ return HTTP_MOVED_TEMPORARILY;
+ }
+ if (conf->loginsuccess) {
+ const char *loginsuccess = ap_expr_str_exec(r,
+ conf->loginsuccess, &err);
+ if (!err) {
+ apr_table_set(r->headers_out, "Location", loginsuccess);
+ return HTTP_MOVED_TEMPORARILY;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02341)
+ "Can't evaluate login success expression: %s", err);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+ return HTTP_OK;
+ }
+ }
+
+ /* did we prefer to be redirected to the login page on failure instead? */
+ if (HTTP_UNAUTHORIZED == rv && conf->loginrequired) {
+ const char *loginrequired = ap_expr_str_exec(r,
+ conf->loginrequired, &err);
+ if (!err) {
+ apr_table_set(r->headers_out, "Location", loginrequired);
+ return HTTP_MOVED_TEMPORARILY;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02342)
+ "Can't evaluate login required expression: %s", err);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ return rv;
+
+}
+
+/**
+ * Handle a logout attempt.
+ *
+ * If an attempt is made to access this URL, any username and password
+ * embedded in the session is deleted.
+ *
+ * This has the effect of logging the person out.
+ *
+ * If a logout URI has been specified, this function will create an
+ * internal redirect to this page.
+ */
+static int authenticate_form_logout_handler(request_rec * r)
+{
+ auth_form_config_rec *conf;
+ const char *err;
+
+ if (strcmp(r->handler, FORM_LOGOUT_HANDLER)) {
+ return DECLINED;
+ }
+
+ conf = ap_get_module_config(r->per_dir_config, &auth_form_module);
+
+ /* remove the username and password, effectively logging the user out */
+ set_session_auth(r, NULL, NULL, NULL);
+
+ /*
+ * make sure the logout page is never cached - otherwise the logout won't
+ * work!
+ */
+ apr_table_addn(r->headers_out, "Cache-Control", "no-store");
+ apr_table_addn(r->err_headers_out, "Cache-Control", "no-store");
+
+ /* if set, internal redirect to the logout page */
+ if (conf->logout) {
+ const char *logout = ap_expr_str_exec(r,
+ conf->logout, &err);
+ if (!err) {
+ apr_table_addn(r->headers_out, "Location", logout);
+ return HTTP_TEMPORARY_REDIRECT;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02343)
+ "Can't evaluate logout expression: %s", err);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ return HTTP_OK;
+
+}
+
+/**
+ * Handle a redirect attempt.
+ *
+ * If during a form login, the method, mimetype and request body are
+ * specified, this handler will ensure that this request is included
+ * as an internal redirect.
+ *
+ */
+static int authenticate_form_redirect_handler(request_rec * r)
+{
+
+ request_rec *rr = NULL;
+ const char *sent_method = NULL, *sent_mimetype = NULL;
+
+ if (strcmp(r->handler, FORM_REDIRECT_HANDLER)) {
+ return DECLINED;
+ }
+
+ /* get the method and mimetype from the notes */
+ get_notes_auth(r, NULL, NULL, &sent_method, &sent_mimetype);
+
+ if (r->kept_body && sent_method && sent_mimetype) {
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01812)
+ "internal redirect to method '%s' and body mimetype '%s' for the "
+ "uri: %s", sent_method, sent_mimetype, r->uri);
+
+ rr = ap_sub_req_method_uri(sent_method, r->uri, r, r->output_filters);
+ r->status = ap_run_sub_req(rr);
+
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01813)
+ "internal redirect requested but one or all of method, mimetype or "
+ "body are NULL: %s", r->uri);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* return the underlying error, or OK on success */
+ return r->status == HTTP_OK || r->status == OK ? OK : r->status;
+
+}
+
+static int authenticate_form_post_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+
+ if (!ap_session_load_fn || !ap_session_get_fn || !ap_session_set_fn) {
+ ap_session_load_fn = APR_RETRIEVE_OPTIONAL_FN(ap_session_load);
+ ap_session_get_fn = APR_RETRIEVE_OPTIONAL_FN(ap_session_get);
+ ap_session_set_fn = APR_RETRIEVE_OPTIONAL_FN(ap_session_set);
+ if (!ap_session_load_fn || !ap_session_get_fn || !ap_session_set_fn) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, NULL, APLOGNO(02617)
+ "You must load mod_session to enable the mod_auth_form "
+ "functions");
+ return !OK;
+ }
+ }
+
+ if (!ap_request_insert_filter_fn || !ap_request_remove_filter_fn) {
+ ap_request_insert_filter_fn = APR_RETRIEVE_OPTIONAL_FN(ap_request_insert_filter);
+ ap_request_remove_filter_fn = APR_RETRIEVE_OPTIONAL_FN(ap_request_remove_filter);
+ if (!ap_request_insert_filter_fn || !ap_request_remove_filter_fn) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, NULL, APLOGNO(02618)
+ "You must load mod_request to enable the mod_auth_form "
+ "functions");
+ return !OK;
+ }
+ }
+
+ return OK;
+}
+
+static void register_hooks(apr_pool_t * p)
+{
+ ap_hook_post_config(authenticate_form_post_config,NULL,NULL,APR_HOOK_MIDDLE);
+
+#if AP_MODULE_MAGIC_AT_LEAST(20080403,1)
+ ap_hook_check_authn(authenticate_form_authn, NULL, NULL, APR_HOOK_MIDDLE,
+ AP_AUTH_INTERNAL_PER_CONF);
+#else
+ ap_hook_check_user_id(authenticate_form_authn, NULL, NULL, APR_HOOK_MIDDLE);
+#endif
+ ap_hook_handler(authenticate_form_login_handler, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_handler(authenticate_form_logout_handler, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_handler(authenticate_form_redirect_handler, NULL, NULL, APR_HOOK_MIDDLE);
+
+ ap_hook_note_auth_failure(hook_note_cookie_auth_failure, NULL, NULL,
+ APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(auth_form) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_auth_form_dir_config, /* dir config creater */
+ merge_auth_form_dir_config, /* dir merger --- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ auth_form_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/aaa/mod_auth_form.dep b/modules/aaa/mod_auth_form.dep
new file mode 100644
index 0000000..190b2db
--- /dev/null
+++ b/modules/aaa/mod_auth_form.dep
@@ -0,0 +1,66 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_auth_form.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_auth_form.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\mod_request.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_md5.h"\
+ "..\..\srclib\apr-util\include\apr_base64.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ "..\session\mod_session.h"\
+
diff --git a/modules/aaa/mod_auth_form.dsp b/modules/aaa/mod_auth_form.dsp
new file mode 100644
index 0000000..f47c1a2
--- /dev/null
+++ b/modules/aaa/mod_auth_form.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_auth_form" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_auth_form - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_auth_form.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_auth_form.mak" CFG="mod_auth_form - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_auth_form - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_auth_form - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_auth_form - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../session" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "AAA_DECLARE_EXPORT" /Fd"Release\mod_auth_form_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_auth_form.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_auth_form.so" /d LONG_NAME="auth_form_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_auth_form.so" /base:@..\..\os\win32\BaseAddr.ref,mod_auth_form.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_auth_form.so" /base:@..\..\os\win32\BaseAddr.ref,mod_auth_form.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_auth_form.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_auth_form - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../session" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "AAA_DECLARE_EXPORT" /Fd"Debug\mod_auth_form_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_auth_form.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_auth_form.so" /d LONG_NAME="auth_form_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_auth_form.so" /base:@..\..\os\win32\BaseAddr.ref,mod_auth_form.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_auth_form.so" /base:@..\..\os\win32\BaseAddr.ref,mod_auth_form.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_auth_form.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_auth_form - Win32 Release"
+# Name "mod_auth_form - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_auth_form.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/aaa/mod_auth_form.mak b/modules/aaa/mod_auth_form.mak
new file mode 100644
index 0000000..3c6c67a
--- /dev/null
+++ b/modules/aaa/mod_auth_form.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_auth_form.dsp
+!IF "$(CFG)" == ""
+CFG=mod_auth_form - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_auth_form - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_auth_form - Win32 Release" && "$(CFG)" != "mod_auth_form - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_auth_form.mak" CFG="mod_auth_form - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_auth_form - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_auth_form - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_auth_form - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_auth_form.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_auth_form.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_auth_form.obj"
+ -@erase "$(INTDIR)\mod_auth_form.res"
+ -@erase "$(INTDIR)\mod_auth_form_src.idb"
+ -@erase "$(INTDIR)\mod_auth_form_src.pdb"
+ -@erase "$(OUTDIR)\mod_auth_form.exp"
+ -@erase "$(OUTDIR)\mod_auth_form.lib"
+ -@erase "$(OUTDIR)\mod_auth_form.pdb"
+ -@erase "$(OUTDIR)\mod_auth_form.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../session" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "AAA_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_auth_form_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_auth_form.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_auth_form.so" /d LONG_NAME="auth_form_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_auth_form.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_auth_form.pdb" /debug /out:"$(OUTDIR)\mod_auth_form.so" /implib:"$(OUTDIR)\mod_auth_form.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_auth_form.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_auth_form.obj" \
+ "$(INTDIR)\mod_auth_form.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_auth_form.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_auth_form.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_auth_form.so"
+ if exist .\Release\mod_auth_form.so.manifest mt.exe -manifest .\Release\mod_auth_form.so.manifest -outputresource:.\Release\mod_auth_form.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_auth_form - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_auth_form.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_auth_form.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_auth_form.obj"
+ -@erase "$(INTDIR)\mod_auth_form.res"
+ -@erase "$(INTDIR)\mod_auth_form_src.idb"
+ -@erase "$(INTDIR)\mod_auth_form_src.pdb"
+ -@erase "$(OUTDIR)\mod_auth_form.exp"
+ -@erase "$(OUTDIR)\mod_auth_form.lib"
+ -@erase "$(OUTDIR)\mod_auth_form.pdb"
+ -@erase "$(OUTDIR)\mod_auth_form.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../session" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "AAA_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_auth_form_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_auth_form.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_auth_form.so" /d LONG_NAME="auth_form_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_auth_form.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_auth_form.pdb" /debug /out:"$(OUTDIR)\mod_auth_form.so" /implib:"$(OUTDIR)\mod_auth_form.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_auth_form.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_auth_form.obj" \
+ "$(INTDIR)\mod_auth_form.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_auth_form.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_auth_form.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_auth_form.so"
+ if exist .\Debug\mod_auth_form.so.manifest mt.exe -manifest .\Debug\mod_auth_form.so.manifest -outputresource:.\Debug\mod_auth_form.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_auth_form.dep")
+!INCLUDE "mod_auth_form.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_auth_form.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_auth_form - Win32 Release" || "$(CFG)" == "mod_auth_form - Win32 Debug"
+
+!IF "$(CFG)" == "mod_auth_form - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_auth_form - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_auth_form - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_auth_form - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_auth_form - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_auth_form - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_auth_form - Win32 Release"
+
+
+"$(INTDIR)\mod_auth_form.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_auth_form.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_auth_form.so" /d LONG_NAME="auth_form_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_auth_form - Win32 Debug"
+
+
+"$(INTDIR)\mod_auth_form.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_auth_form.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_auth_form.so" /d LONG_NAME="auth_form_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_auth_form.c
+
+"$(INTDIR)\mod_auth_form.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/aaa/mod_authn_anon.c b/modules/aaa/mod_authn_anon.c
new file mode 100644
index 0000000..82559bc
--- /dev/null
+++ b/modules/aaa/mod_authn_anon.c
@@ -0,0 +1,215 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Adapted to allow anonymous logins, just like with Anon-FTP, when
+ * one gives the magic user name 'anonymous' and ones email address
+ * as the password.
+ *
+ * Just add the following tokes to your <directory> setup:
+ *
+ * Anonymous magic-userid [magic-userid]...
+ *
+ * Anonymous_MustGiveEmail [ on | off ] default = on
+ * Anonymous_LogEmail [ on | off ] default = on
+ * Anonymous_VerifyEmail [ on | off ] default = off
+ * Anonymous_NoUserId [ on | off ] default = off
+ *
+ * The magic user id is something like 'anonymous', it is NOT case sensitive.
+ *
+ * The MustGiveEmail flag can be used to force users to enter something
+ * in the password field (like an email address). Default is on.
+ *
+ * Furthermore the 'NoUserID' flag can be set to allow completely empty
+ * usernames in as well; this can be is convenient as a single return
+ * in broken GUIs like W95 is often given by the user. The Default is off.
+ *
+ * Dirk.vanGulik@jrc.it; http://ewse.ceo.org; http://me-www.jrc.it/~dirkx
+ *
+ */
+
+#include "apr_strings.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "ap_provider.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_request.h"
+#include "http_protocol.h"
+
+#include "mod_auth.h"
+
+typedef struct anon_auth_user {
+ const char *user;
+ struct anon_auth_user *next;
+} anon_auth_user;
+
+typedef struct {
+ anon_auth_user *users;
+ int nouserid;
+ int logemail;
+ int verifyemail;
+ int mustemail;
+ int anyuserid;
+} authn_anon_config_rec;
+
+static void *create_authn_anon_dir_config(apr_pool_t *p, char *d)
+{
+ authn_anon_config_rec *conf = apr_palloc(p, sizeof(*conf));
+
+ /* just to illustrate the defaults really. */
+ conf->users = NULL;
+
+ conf->nouserid = 0;
+ conf->anyuserid = 0;
+ conf->logemail = 1;
+ conf->verifyemail = 0;
+ conf->mustemail = 1;
+ return conf;
+}
+
+static const char *anon_set_string_slots(cmd_parms *cmd,
+ void *my_config, const char *arg)
+{
+ authn_anon_config_rec *conf = my_config;
+ anon_auth_user *first;
+
+ if (!*arg) {
+ return "Anonymous string cannot be empty, use Anonymous_NoUserId";
+ }
+
+ /* squeeze in a record */
+ if (!conf->anyuserid) {
+ if (!strcmp(arg, "*")) {
+ conf->anyuserid = 1;
+ }
+ else {
+ first = conf->users;
+ conf->users = apr_palloc(cmd->pool, sizeof(*conf->users));
+ conf->users->user = arg;
+ conf->users->next = first;
+ }
+ }
+
+ return NULL;
+}
+
+static const command_rec authn_anon_cmds[] =
+{
+ AP_INIT_ITERATE("Anonymous", anon_set_string_slots, NULL, OR_AUTHCFG,
+ "a space-separated list of user IDs"),
+ AP_INIT_FLAG("Anonymous_MustGiveEmail", ap_set_flag_slot,
+ (void *)APR_OFFSETOF(authn_anon_config_rec, mustemail),
+ OR_AUTHCFG, "Limited to 'on' or 'off'"),
+ AP_INIT_FLAG("Anonymous_NoUserId", ap_set_flag_slot,
+ (void *)APR_OFFSETOF(authn_anon_config_rec, nouserid),
+ OR_AUTHCFG, "Limited to 'on' or 'off'"),
+ AP_INIT_FLAG("Anonymous_VerifyEmail", ap_set_flag_slot,
+ (void *)APR_OFFSETOF(authn_anon_config_rec, verifyemail),
+ OR_AUTHCFG, "Limited to 'on' or 'off'"),
+ AP_INIT_FLAG("Anonymous_LogEmail", ap_set_flag_slot,
+ (void *)APR_OFFSETOF(authn_anon_config_rec, logemail),
+ OR_AUTHCFG, "Limited to 'on' or 'off'"),
+ {NULL}
+};
+
+module AP_MODULE_DECLARE_DATA authn_anon_module;
+
+static authn_status check_anonymous(request_rec *r, const char *user,
+ const char *sent_pw)
+{
+ authn_anon_config_rec *conf = ap_get_module_config(r->per_dir_config,
+ &authn_anon_module);
+ authn_status res = AUTH_USER_NOT_FOUND;
+
+ /* Ignore if we are not configured */
+ if (!conf->users && !conf->anyuserid) {
+ return AUTH_USER_NOT_FOUND;
+ }
+
+ /* Do we allow an empty userID and/or is it the magic one
+ */
+ if (!*user) {
+ if (conf->nouserid) {
+ res = AUTH_USER_FOUND;
+ }
+ }
+ else if (conf->anyuserid) {
+ res = AUTH_USER_FOUND;
+ }
+ else {
+ anon_auth_user *p = conf->users;
+
+ while (p) {
+ if (!strcasecmp(user, p->user)) {
+ res = AUTH_USER_FOUND;
+ break;
+ }
+ p = p->next;
+ }
+ }
+
+ /* Now if the supplied user-ID was ok, grant access if:
+ * (a) no passwd was sent and no password and no verification
+ * were configured.
+ * (b) password was sent and no verification was configured
+ * (c) verification was configured and the password (sent or not)
+ * looks like an email address
+ */
+ if ( (res == AUTH_USER_FOUND)
+ && (!conf->mustemail || *sent_pw)
+ && ( !conf->verifyemail
+ || (ap_strchr_c(sent_pw, '@') && ap_strchr_c(sent_pw, '.'))))
+ {
+ if (conf->logemail && ap_is_initial_req(r)) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, APR_SUCCESS, r, APLOGNO(01672)
+ "Anonymous: Passwd <%s> Accepted",
+ sent_pw ? sent_pw : "\'none\'");
+ }
+
+ return AUTH_GRANTED;
+ }
+
+ return (res == AUTH_USER_NOT_FOUND ? res : AUTH_DENIED);
+}
+
+static const authn_provider authn_anon_provider =
+{
+ &check_anonymous,
+ NULL
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_register_auth_provider(p, AUTHN_PROVIDER_GROUP, "anon",
+ AUTHN_PROVIDER_VERSION,
+ &authn_anon_provider, AP_AUTH_INTERNAL_PER_CONF);
+}
+
+AP_DECLARE_MODULE(authn_anon) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_authn_anon_dir_config, /* dir config creater */
+ NULL, /* dir merger ensure strictness */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ authn_anon_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/aaa/mod_authn_anon.dep b/modules/aaa/mod_authn_anon.dep
new file mode 100644
index 0000000..30bb436
--- /dev/null
+++ b/modules/aaa/mod_authn_anon.dep
@@ -0,0 +1,58 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_authn_anon.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_authn_anon.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/aaa/mod_authn_anon.dsp b/modules/aaa/mod_authn_anon.dsp
new file mode 100644
index 0000000..be88104
--- /dev/null
+++ b/modules/aaa/mod_authn_anon.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_authn_anon" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_authn_anon - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authn_anon.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authn_anon.mak" CFG="mod_authn_anon - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_authn_anon - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_authn_anon - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_authn_anon - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_authn_anon_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_authn_anon.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_authn_anon.so" /d LONG_NAME="authn_anon_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_authn_anon.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_anon.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_authn_anon.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_anon.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_authn_anon.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_authn_anon - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_authn_anon_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_authn_anon.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_authn_anon.so" /d LONG_NAME="authn_anon_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_authn_anon.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_anon.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_authn_anon.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_anon.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_authn_anon.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_authn_anon - Win32 Release"
+# Name "mod_authn_anon - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_authn_anon.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/aaa/mod_authn_anon.mak b/modules/aaa/mod_authn_anon.mak
new file mode 100644
index 0000000..6ff9972
--- /dev/null
+++ b/modules/aaa/mod_authn_anon.mak
@@ -0,0 +1,381 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_authn_anon.dsp
+!IF "$(CFG)" == ""
+CFG=mod_authn_anon - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_authn_anon - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_authn_anon - Win32 Release" && "$(CFG)" != "mod_authn_anon - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authn_anon.mak" CFG="mod_authn_anon - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_authn_anon - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_authn_anon - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_authn_anon - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_authn_anon.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_auth_basic - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_authn_anon.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_auth_basic - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_authn_anon.obj"
+ -@erase "$(INTDIR)\mod_authn_anon.res"
+ -@erase "$(INTDIR)\mod_authn_anon_src.idb"
+ -@erase "$(INTDIR)\mod_authn_anon_src.pdb"
+ -@erase "$(OUTDIR)\mod_authn_anon.exp"
+ -@erase "$(OUTDIR)\mod_authn_anon.lib"
+ -@erase "$(OUTDIR)\mod_authn_anon.pdb"
+ -@erase "$(OUTDIR)\mod_authn_anon.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_authn_anon_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_authn_anon.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_authn_anon.so" /d LONG_NAME="authn_anon_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_authn_anon.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_authn_anon.pdb" /debug /out:"$(OUTDIR)\mod_authn_anon.so" /implib:"$(OUTDIR)\mod_authn_anon.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_anon.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_authn_anon.obj" \
+ "$(INTDIR)\mod_authn_anon.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib" \
+ "$(OUTDIR)\mod_auth_basic.lib"
+
+"$(OUTDIR)\mod_authn_anon.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_authn_anon.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_authn_anon.so"
+ if exist .\Release\mod_authn_anon.so.manifest mt.exe -manifest .\Release\mod_authn_anon.so.manifest -outputresource:.\Release\mod_authn_anon.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_authn_anon - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_authn_anon.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_auth_basic - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_authn_anon.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_auth_basic - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_authn_anon.obj"
+ -@erase "$(INTDIR)\mod_authn_anon.res"
+ -@erase "$(INTDIR)\mod_authn_anon_src.idb"
+ -@erase "$(INTDIR)\mod_authn_anon_src.pdb"
+ -@erase "$(OUTDIR)\mod_authn_anon.exp"
+ -@erase "$(OUTDIR)\mod_authn_anon.lib"
+ -@erase "$(OUTDIR)\mod_authn_anon.pdb"
+ -@erase "$(OUTDIR)\mod_authn_anon.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_authn_anon_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_authn_anon.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_authn_anon.so" /d LONG_NAME="authn_anon_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_authn_anon.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_authn_anon.pdb" /debug /out:"$(OUTDIR)\mod_authn_anon.so" /implib:"$(OUTDIR)\mod_authn_anon.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_anon.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_authn_anon.obj" \
+ "$(INTDIR)\mod_authn_anon.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib" \
+ "$(OUTDIR)\mod_auth_basic.lib"
+
+"$(OUTDIR)\mod_authn_anon.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_authn_anon.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_authn_anon.so"
+ if exist .\Debug\mod_authn_anon.so.manifest mt.exe -manifest .\Debug\mod_authn_anon.so.manifest -outputresource:.\Debug\mod_authn_anon.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_authn_anon.dep")
+!INCLUDE "mod_authn_anon.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_authn_anon.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_authn_anon - Win32 Release" || "$(CFG)" == "mod_authn_anon - Win32 Debug"
+
+!IF "$(CFG)" == "mod_authn_anon - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authn_anon - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authn_anon - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authn_anon - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authn_anon - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authn_anon - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authn_anon - Win32 Release"
+
+"mod_auth_basic - Win32 Release" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Release"
+ cd "."
+
+"mod_auth_basic - Win32 ReleaseCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Release" RECURSE=1 CLEAN
+ cd "."
+
+!ELSEIF "$(CFG)" == "mod_authn_anon - Win32 Debug"
+
+"mod_auth_basic - Win32 Debug" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Debug"
+ cd "."
+
+"mod_auth_basic - Win32 DebugCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Debug" RECURSE=1 CLEAN
+ cd "."
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_authn_anon - Win32 Release"
+
+
+"$(INTDIR)\mod_authn_anon.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_authn_anon.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_authn_anon.so" /d LONG_NAME="authn_anon_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_authn_anon - Win32 Debug"
+
+
+"$(INTDIR)\mod_authn_anon.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_authn_anon.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_authn_anon.so" /d LONG_NAME="authn_anon_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_authn_anon.c
+
+"$(INTDIR)\mod_authn_anon.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/aaa/mod_authn_core.c b/modules/aaa/mod_authn_core.c
new file mode 100644
index 0000000..f3a494c
--- /dev/null
+++ b/modules/aaa/mod_authn_core.c
@@ -0,0 +1,427 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Security options etc.
+ *
+ * Module derived from code originally written by Rob McCool
+ *
+ */
+
+#include "apr_strings.h"
+#include "apr_network_io.h"
+#define APR_WANT_STRFUNC
+#define APR_WANT_BYTEFUNC
+#include "apr_want.h"
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_request.h"
+#include "http_protocol.h"
+#include "ap_expr.h"
+#include "ap_provider.h"
+
+#include "mod_auth.h"
+
+#if APR_HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+
+/* TODO List
+
+- Track down all of the references to r->ap_auth_type
+ and change them to ap_auth_type()
+- Remove ap_auth_type and ap_auth_name from the
+ request_rec
+
+*/
+
+typedef struct {
+ ap_expr_info_t *ap_auth_type;
+ int auth_type_set;
+ ap_expr_info_t *ap_auth_name;
+} authn_core_dir_conf;
+
+typedef struct provider_alias_rec {
+ char *provider_name;
+ char *provider_alias;
+ ap_conf_vector_t *sec_auth;
+ const authn_provider *provider;
+} provider_alias_rec;
+
+typedef struct authn_alias_srv_conf {
+ apr_hash_t *alias_rec;
+} authn_alias_srv_conf;
+
+
+module AP_MODULE_DECLARE_DATA authn_core_module;
+
+static void *create_authn_core_dir_config(apr_pool_t *p, char *dummy)
+{
+ authn_core_dir_conf *conf =
+ (authn_core_dir_conf *)apr_pcalloc(p, sizeof(authn_core_dir_conf));
+
+ return (void *)conf;
+}
+
+static void *merge_authn_core_dir_config(apr_pool_t *a, void *basev, void *newv)
+{
+ authn_core_dir_conf *base = (authn_core_dir_conf *)basev;
+ authn_core_dir_conf *new = (authn_core_dir_conf *)newv;
+ authn_core_dir_conf *conf =
+ (authn_core_dir_conf *)apr_pcalloc(a, sizeof(authn_core_dir_conf));
+
+ if (new->auth_type_set) {
+ conf->ap_auth_type = new->ap_auth_type;
+ conf->auth_type_set = 1;
+ }
+ else {
+ conf->ap_auth_type = base->ap_auth_type;
+ conf->auth_type_set = base->auth_type_set;
+ }
+
+ if (new->ap_auth_name) {
+ conf->ap_auth_name = new->ap_auth_name;
+ } else {
+ conf->ap_auth_name = base->ap_auth_name;
+ }
+
+ return (void*)conf;
+}
+
+static authn_status authn_alias_check_password(request_rec *r, const char *user,
+ const char *password)
+{
+ /* Look up the provider alias in the alias list */
+ /* Get the dir_config and call ap_Merge_per_dir_configs() */
+ /* Call the real provider->check_password() function */
+ /* return the result of the above function call */
+
+ const char *provider_name = apr_table_get(r->notes, AUTHN_PROVIDER_NAME_NOTE);
+ authn_status ret = AUTH_USER_NOT_FOUND;
+ authn_alias_srv_conf *authcfg =
+ (authn_alias_srv_conf *)ap_get_module_config(r->server->module_config,
+ &authn_core_module);
+
+ if (provider_name) {
+ provider_alias_rec *prvdraliasrec = apr_hash_get(authcfg->alias_rec,
+ provider_name, APR_HASH_KEY_STRING);
+ ap_conf_vector_t *orig_dir_config = r->per_dir_config;
+
+ /* If we found the alias provider in the list, then merge the directory
+ configurations and call the real provider */
+ if (prvdraliasrec) {
+ r->per_dir_config = ap_merge_per_dir_configs(r->pool, orig_dir_config,
+ prvdraliasrec->sec_auth);
+ ret = prvdraliasrec->provider->check_password(r,user,password);
+ r->per_dir_config = orig_dir_config;
+ }
+ }
+
+ return ret;
+}
+
+static authn_status authn_alias_get_realm_hash(request_rec *r, const char *user,
+ const char *realm, char **rethash)
+{
+ /* Look up the provider alias in the alias list */
+ /* Get the dir_config and call ap_Merge_per_dir_configs() */
+ /* Call the real provider->get_realm_hash() function */
+ /* return the result of the above function call */
+
+ const char *provider_name = apr_table_get(r->notes, AUTHN_PROVIDER_NAME_NOTE);
+ authn_status ret = AUTH_USER_NOT_FOUND;
+ authn_alias_srv_conf *authcfg =
+ (authn_alias_srv_conf *)ap_get_module_config(r->server->module_config,
+ &authn_core_module);
+
+ if (provider_name) {
+ provider_alias_rec *prvdraliasrec = apr_hash_get(authcfg->alias_rec,
+ provider_name, APR_HASH_KEY_STRING);
+ ap_conf_vector_t *orig_dir_config = r->per_dir_config;
+
+ /* If we found the alias provider in the list, then merge the directory
+ configurations and call the real provider */
+ if (prvdraliasrec) {
+ r->per_dir_config = ap_merge_per_dir_configs(r->pool, orig_dir_config,
+ prvdraliasrec->sec_auth);
+ ret = prvdraliasrec->provider->get_realm_hash(r,user,realm,rethash);
+ r->per_dir_config = orig_dir_config;
+ }
+ }
+
+ return ret;
+}
+
+static void *create_authn_alias_svr_config(apr_pool_t *p, server_rec *s)
+{
+
+ authn_alias_srv_conf *authcfg;
+
+ authcfg = (authn_alias_srv_conf *) apr_pcalloc(p, sizeof(authn_alias_srv_conf));
+ authcfg->alias_rec = apr_hash_make(p);
+
+ return (void *) authcfg;
+}
+
+/* Only per-server directive we have is GLOBAL_ONLY */
+static void *merge_authn_alias_svr_config(apr_pool_t *p, void *basev, void *overridesv)
+{
+ return basev;
+}
+
+static const authn_provider authn_alias_provider =
+{
+ &authn_alias_check_password,
+ &authn_alias_get_realm_hash,
+};
+
+static const authn_provider authn_alias_provider_nodigest =
+{
+ &authn_alias_check_password,
+ NULL,
+};
+
+static const char *authaliassection(cmd_parms *cmd, void *mconfig, const char *arg)
+{
+ const char *endp = ap_strrchr_c(arg, '>');
+ const char *args;
+ char *provider_alias;
+ char *provider_name;
+ int old_overrides = cmd->override;
+ const char *errmsg;
+ const authn_provider *provider = NULL;
+ ap_conf_vector_t *new_auth_config = ap_create_per_dir_config(cmd->pool);
+ authn_alias_srv_conf *authcfg =
+ (authn_alias_srv_conf *)ap_get_module_config(cmd->server->module_config,
+ &authn_core_module);
+
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ if (endp == NULL) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ "> directive missing closing '>'", NULL);
+ }
+
+ args = apr_pstrndup(cmd->temp_pool, arg, endp - arg);
+
+ if (!args[0]) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ "> directive requires additional arguments", NULL);
+ }
+
+ /* Pull the real provider name and the alias name from the block header */
+ provider_name = ap_getword_conf(cmd->pool, &args);
+ provider_alias = ap_getword_conf(cmd->pool, &args);
+
+ if (!provider_name[0] || !provider_alias[0]) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ "> directive requires additional arguments", NULL);
+ }
+
+ if (strcasecmp(provider_name, provider_alias) == 0) {
+ return apr_pstrcat(cmd->pool,
+ "The alias provider name must be different from the base provider name.", NULL);
+ }
+
+ /* Look up the alias provider to make sure that it hasn't already been registered. */
+ provider = ap_lookup_provider(AUTHN_PROVIDER_GROUP, provider_alias,
+ AUTHN_PROVIDER_VERSION);
+ if (provider) {
+ return apr_pstrcat(cmd->pool, "The alias provider ", provider_alias,
+ " has already be registered previously as either a base provider or an alias provider.",
+ NULL);
+ }
+
+ /* walk the subsection configuration to get the per_dir config that we will
+ merge just before the real provider is called. */
+ cmd->override = OR_AUTHCFG | ACCESS_CONF;
+ errmsg = ap_walk_config(cmd->directive->first_child, cmd, new_auth_config);
+ cmd->override = old_overrides;
+
+ if (!errmsg) {
+ provider_alias_rec *prvdraliasrec = apr_pcalloc(cmd->pool, sizeof(provider_alias_rec));
+ provider = ap_lookup_provider(AUTHN_PROVIDER_GROUP, provider_name,
+ AUTHN_PROVIDER_VERSION);
+
+ if (!provider) {
+ /* by the time they use it, the provider should be loaded and
+ registered with us. */
+ return apr_psprintf(cmd->pool,
+ "Unknown Authn provider: %s",
+ provider_name);
+ }
+
+ /* Save off the new directory config along with the original provider name
+ and function pointer data */
+ prvdraliasrec->sec_auth = new_auth_config;
+ prvdraliasrec->provider_name = provider_name;
+ prvdraliasrec->provider_alias = provider_alias;
+ prvdraliasrec->provider = provider;
+ apr_hash_set(authcfg->alias_rec, provider_alias, APR_HASH_KEY_STRING, prvdraliasrec);
+
+ /* Register the fake provider so that we get called first */
+ ap_register_auth_provider(cmd->pool, AUTHN_PROVIDER_GROUP,
+ provider_alias, AUTHN_PROVIDER_VERSION,
+ provider->get_realm_hash ?
+ &authn_alias_provider :
+ &authn_alias_provider_nodigest,
+ AP_AUTH_INTERNAL_PER_CONF);
+ }
+
+ return errmsg;
+}
+
+/*
+ * Load an authorisation realm into our location configuration, applying the
+ * usual rules that apply to realms.
+ */
+static const char *set_authname(cmd_parms *cmd, void *mconfig,
+ const char *word1)
+{
+ authn_core_dir_conf *aconfig = (authn_core_dir_conf *)mconfig;
+ const char *expr_err = NULL;
+
+ aconfig->ap_auth_name = ap_expr_parse_cmd(cmd, word1, AP_EXPR_FLAG_STRING_RESULT,
+ &expr_err, NULL);
+ if (expr_err) {
+ return apr_pstrcat(cmd->temp_pool,
+ "Cannot parse expression '", word1, "' in AuthName: ",
+ expr_err, NULL);
+ }
+
+ return NULL;
+}
+
+static const char *set_authtype(cmd_parms *cmd, void *mconfig,
+ const char *word1)
+{
+ authn_core_dir_conf *aconfig = (authn_core_dir_conf *)mconfig;
+ const char *expr_err = NULL;
+
+ aconfig->ap_auth_type = ap_expr_parse_cmd(cmd, word1, AP_EXPR_FLAG_STRING_RESULT,
+ &expr_err, NULL);
+ if (expr_err) {
+ return apr_pstrcat(cmd->temp_pool,
+ "Cannot parse expression '", word1, "' in AuthType: ",
+ expr_err, NULL);
+ }
+
+ aconfig->auth_type_set = 1;
+
+ return NULL;
+}
+
+static const char *authn_ap_auth_type(request_rec *r)
+{
+ authn_core_dir_conf *conf;
+
+ conf = (authn_core_dir_conf *) ap_get_module_config(r->per_dir_config,
+ &authn_core_module);
+
+ if (conf->ap_auth_type) {
+ const char *err = NULL, *type;
+ type = ap_expr_str_exec(r, conf->ap_auth_type, &err);
+ if (err) {
+ ap_log_rerror(
+ APLOG_MARK, APLOG_ERR, APR_SUCCESS, r, APLOGNO(02834) "AuthType expression could not be evaluated: %s", err);
+ return NULL;
+ }
+
+ return strcasecmp(type, "None") ? type : NULL;
+ }
+
+ return NULL;
+}
+
+static const char *authn_ap_auth_name(request_rec *r)
+{
+ authn_core_dir_conf *conf;
+ const char *err = NULL, *name;
+
+ conf = (authn_core_dir_conf *) ap_get_module_config(r->per_dir_config,
+ &authn_core_module);
+
+ if (conf->ap_auth_name) {
+ name = ap_expr_str_exec(r, conf->ap_auth_name, &err);
+ if (err) {
+ ap_log_rerror(
+ APLOG_MARK, APLOG_ERR, APR_SUCCESS, r, APLOGNO(02835) "AuthName expression could not be evaluated: %s", err);
+ return NULL;
+ }
+
+ return ap_escape_quotes(r->pool, name);
+ }
+
+ return NULL;
+}
+
+static const command_rec authn_cmds[] =
+{
+ AP_INIT_TAKE1("AuthType", set_authtype, NULL, OR_AUTHCFG,
+ "an HTTP authorization type (e.g., \"Basic\")"),
+ AP_INIT_TAKE1("AuthName", set_authname, NULL, OR_AUTHCFG,
+ "the authentication realm (e.g. \"Members Only\")"),
+ AP_INIT_RAW_ARGS("<AuthnProviderAlias", authaliassection, NULL, RSRC_CONF,
+ "container for grouping an authentication provider's "
+ "directives under a provider alias"),
+ {NULL}
+};
+
+static int authenticate_no_user(request_rec *r)
+{
+ /* if there isn't an AuthType, then assume that no authentication
+ is required so return OK */
+ if (!ap_auth_type(r)) {
+ return OK;
+ }
+
+ /* there's an AuthType configured, but no authentication module
+ * loaded to support it
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_SUCCESS, r, APLOGNO(01796)
+ "AuthType %s configured without corresponding module",
+ ap_auth_type(r));
+
+ return HTTP_INTERNAL_SERVER_ERROR;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ APR_REGISTER_OPTIONAL_FN(authn_ap_auth_type);
+ APR_REGISTER_OPTIONAL_FN(authn_ap_auth_name);
+
+ ap_hook_check_authn(authenticate_no_user, NULL, NULL, APR_HOOK_LAST,
+ AP_AUTH_INTERNAL_PER_CONF);
+}
+
+AP_DECLARE_MODULE(authn_core) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_authn_core_dir_config, /* dir config creater */
+ merge_authn_core_dir_config, /* dir merger --- default is to override */
+ create_authn_alias_svr_config, /* server config */
+ merge_authn_alias_svr_config, /* merge server config */
+ authn_cmds,
+ register_hooks /* register hooks */
+};
+
diff --git a/modules/aaa/mod_authn_core.dep b/modules/aaa/mod_authn_core.dep
new file mode 100644
index 0000000..18003b3
--- /dev/null
+++ b/modules/aaa/mod_authn_core.dep
@@ -0,0 +1,58 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_authn_core.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_authn_core.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/aaa/mod_authn_core.dsp b/modules/aaa/mod_authn_core.dsp
new file mode 100644
index 0000000..506c96e
--- /dev/null
+++ b/modules/aaa/mod_authn_core.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_authn_core" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_authn_core - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authn_core.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authn_core.mak" CFG="mod_authn_core - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_authn_core - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_authn_core - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_authn_core - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_authn_core_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_authn_core.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_authn_core.so" /d LONG_NAME="authn_core_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_authn_core.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_core.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_authn_core.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_core.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_authn_core.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_authn_core - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_authn_core_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_authn_core.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_authn_core.so" /d LONG_NAME="authn_core_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_authn_core.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_core.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_authn_core.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_core.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_authn_core.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_authn_core - Win32 Release"
+# Name "mod_authn_core - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_authn_core.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/aaa/mod_authn_core.mak b/modules/aaa/mod_authn_core.mak
new file mode 100644
index 0000000..ec88b2d
--- /dev/null
+++ b/modules/aaa/mod_authn_core.mak
@@ -0,0 +1,381 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_authn_core.dsp
+!IF "$(CFG)" == ""
+CFG=mod_authn_core - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_authn_core - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_authn_core - Win32 Release" && "$(CFG)" != "mod_authn_core - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authn_core.mak" CFG="mod_authn_core - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_authn_core - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_authn_core - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_authn_core - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_authn_core.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_auth_basic - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_authn_core.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_auth_basic - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_authn_core.obj"
+ -@erase "$(INTDIR)\mod_authn_core.res"
+ -@erase "$(INTDIR)\mod_authn_core_src.idb"
+ -@erase "$(INTDIR)\mod_authn_core_src.pdb"
+ -@erase "$(OUTDIR)\mod_authn_core.exp"
+ -@erase "$(OUTDIR)\mod_authn_core.lib"
+ -@erase "$(OUTDIR)\mod_authn_core.pdb"
+ -@erase "$(OUTDIR)\mod_authn_core.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_authn_core_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_authn_core.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_authn_core.so" /d LONG_NAME="authn_core_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_authn_core.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_authn_core.pdb" /debug /out:"$(OUTDIR)\mod_authn_core.so" /implib:"$(OUTDIR)\mod_authn_core.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_core.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_authn_core.obj" \
+ "$(INTDIR)\mod_authn_core.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib" \
+ "$(OUTDIR)\mod_auth_basic.lib"
+
+"$(OUTDIR)\mod_authn_core.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_authn_core.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_authn_core.so"
+ if exist .\Release\mod_authn_core.so.manifest mt.exe -manifest .\Release\mod_authn_core.so.manifest -outputresource:.\Release\mod_authn_core.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_authn_core - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_authn_core.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_auth_basic - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_authn_core.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_auth_basic - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_authn_core.obj"
+ -@erase "$(INTDIR)\mod_authn_core.res"
+ -@erase "$(INTDIR)\mod_authn_core_src.idb"
+ -@erase "$(INTDIR)\mod_authn_core_src.pdb"
+ -@erase "$(OUTDIR)\mod_authn_core.exp"
+ -@erase "$(OUTDIR)\mod_authn_core.lib"
+ -@erase "$(OUTDIR)\mod_authn_core.pdb"
+ -@erase "$(OUTDIR)\mod_authn_core.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_authn_core_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_authn_core.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_authn_core.so" /d LONG_NAME="authn_core_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_authn_core.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_authn_core.pdb" /debug /out:"$(OUTDIR)\mod_authn_core.so" /implib:"$(OUTDIR)\mod_authn_core.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_core.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_authn_core.obj" \
+ "$(INTDIR)\mod_authn_core.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib" \
+ "$(OUTDIR)\mod_auth_basic.lib"
+
+"$(OUTDIR)\mod_authn_core.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_authn_core.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_authn_core.so"
+ if exist .\Debug\mod_authn_core.so.manifest mt.exe -manifest .\Debug\mod_authn_core.so.manifest -outputresource:.\Debug\mod_authn_core.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_authn_core.dep")
+!INCLUDE "mod_authn_core.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_authn_core.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_authn_core - Win32 Release" || "$(CFG)" == "mod_authn_core - Win32 Debug"
+
+!IF "$(CFG)" == "mod_authn_core - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authn_core - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authn_core - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authn_core - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authn_core - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authn_core - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authn_core - Win32 Release"
+
+"mod_auth_basic - Win32 Release" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Release"
+ cd "."
+
+"mod_auth_basic - Win32 ReleaseCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Release" RECURSE=1 CLEAN
+ cd "."
+
+!ELSEIF "$(CFG)" == "mod_authn_core - Win32 Debug"
+
+"mod_auth_basic - Win32 Debug" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Debug"
+ cd "."
+
+"mod_auth_basic - Win32 DebugCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Debug" RECURSE=1 CLEAN
+ cd "."
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_authn_core - Win32 Release"
+
+
+"$(INTDIR)\mod_authn_core.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_authn_core.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_authn_core.so" /d LONG_NAME="authn_core_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_authn_core - Win32 Debug"
+
+
+"$(INTDIR)\mod_authn_core.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_authn_core.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_authn_core.so" /d LONG_NAME="authn_core_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_authn_core.c
+
+"$(INTDIR)\mod_authn_core.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/aaa/mod_authn_dbd.c b/modules/aaa/mod_authn_dbd.c
new file mode 100644
index 0000000..08e5993
--- /dev/null
+++ b/modules/aaa/mod_authn_dbd.c
@@ -0,0 +1,307 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ap_provider.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "http_request.h"
+#include "apr_lib.h"
+#include "apr_dbd.h"
+#include "mod_dbd.h"
+#include "apr_strings.h"
+#include "mod_auth.h"
+#include "apr_md5.h"
+#include "apu_version.h"
+
+module AP_MODULE_DECLARE_DATA authn_dbd_module;
+
+typedef struct {
+ const char *user;
+ const char *realm;
+} authn_dbd_conf;
+
+/* optional function - look it up once in post_config */
+static ap_dbd_t *(*authn_dbd_acquire_fn)(request_rec*) = NULL;
+static void (*authn_dbd_prepare_fn)(server_rec*, const char*, const char*) = NULL;
+static APR_OPTIONAL_FN_TYPE(ap_authn_cache_store) *authn_cache_store = NULL;
+#define AUTHN_CACHE_STORE(r,user,realm,data) \
+ if (authn_cache_store != NULL) \
+ authn_cache_store((r), "dbd", (user), (realm), (data))
+
+static void *authn_dbd_cr_conf(apr_pool_t *pool, char *dummy)
+{
+ authn_dbd_conf *ret = apr_pcalloc(pool, sizeof(authn_dbd_conf));
+ return ret;
+}
+
+static void *authn_dbd_merge_conf(apr_pool_t *pool, void *BASE, void *ADD)
+{
+ authn_dbd_conf *add = ADD;
+ authn_dbd_conf *base = BASE;
+ authn_dbd_conf *ret = apr_palloc(pool, sizeof(authn_dbd_conf));
+ ret->user = (add->user == NULL) ? base->user : add->user;
+ ret->realm = (add->realm == NULL) ? base->realm : add->realm;
+ return ret;
+}
+
+static const char *authn_dbd_prepare(cmd_parms *cmd, void *cfg, const char *query)
+{
+ static unsigned int label_num = 0;
+ char *label;
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_HTACCESS);
+ if (err)
+ return err;
+
+ if (authn_dbd_prepare_fn == NULL) {
+ authn_dbd_prepare_fn = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_prepare);
+ if (authn_dbd_prepare_fn == NULL) {
+ return "You must load mod_dbd to enable AuthDBD functions";
+ }
+ authn_dbd_acquire_fn = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_acquire);
+ }
+ label = apr_psprintf(cmd->pool, "authn_dbd_%d", ++label_num);
+
+ authn_dbd_prepare_fn(cmd->server, query, label);
+
+ /* save the label here for our own use */
+ return ap_set_string_slot(cmd, cfg, label);
+}
+
+static const command_rec authn_dbd_cmds[] =
+{
+ AP_INIT_TAKE1("AuthDBDUserPWQuery", authn_dbd_prepare,
+ (void *)APR_OFFSETOF(authn_dbd_conf, user), ACCESS_CONF,
+ "Query used to fetch password for user"),
+ AP_INIT_TAKE1("AuthDBDUserRealmQuery", authn_dbd_prepare,
+ (void *)APR_OFFSETOF(authn_dbd_conf, realm), ACCESS_CONF,
+ "Query used to fetch password for user+realm"),
+ {NULL}
+};
+
+static authn_status authn_dbd_password(request_rec *r, const char *user,
+ const char *password)
+{
+ apr_status_t rv;
+ const char *dbd_password = NULL;
+ apr_dbd_prepared_t *statement;
+ apr_dbd_results_t *res = NULL;
+ apr_dbd_row_t *row = NULL;
+ int ret;
+
+ authn_dbd_conf *conf = ap_get_module_config(r->per_dir_config,
+ &authn_dbd_module);
+ ap_dbd_t *dbd = authn_dbd_acquire_fn(r);
+ if (dbd == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01653)
+ "Failed to acquire database connection to look up "
+ "user '%s'", user);
+ return AUTH_GENERAL_ERROR;
+ }
+
+ if (conf->user == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01654)
+ "No AuthDBDUserPWQuery has been specified");
+ return AUTH_GENERAL_ERROR;
+ }
+
+ statement = apr_hash_get(dbd->prepared, conf->user, APR_HASH_KEY_STRING);
+ if (statement == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01655)
+ "A prepared statement could not be found for "
+ "AuthDBDUserPWQuery with the key '%s'", conf->user);
+ return AUTH_GENERAL_ERROR;
+ }
+ if ((ret = apr_dbd_pvselect(dbd->driver, r->pool, dbd->handle, &res,
+ statement, 0, user, NULL)) != 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01656)
+ "Query execution error looking up '%s' "
+ "in database [%s]",
+ user, apr_dbd_error(dbd->driver, dbd->handle, ret));
+ return AUTH_GENERAL_ERROR;
+ }
+ for (rv = apr_dbd_get_row(dbd->driver, r->pool, res, &row, -1);
+ rv != -1;
+ rv = apr_dbd_get_row(dbd->driver, r->pool, res, &row, -1)) {
+ if (rv != 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01657)
+ "Error retrieving results while looking up '%s' "
+ "in database", user);
+ return AUTH_GENERAL_ERROR;
+ }
+ if (dbd_password == NULL) {
+ /* add the rest of the columns to the environment */
+ int i = 1;
+ const char *name;
+ for (name = apr_dbd_get_name(dbd->driver, res, i);
+ name != NULL;
+ name = apr_dbd_get_name(dbd->driver, res, i)) {
+
+ char *str = apr_pstrcat(r->pool, AUTHN_PREFIX,
+ name,
+ NULL);
+ int j = sizeof(AUTHN_PREFIX)-1; /* string length of "AUTHENTICATE_", excluding the trailing NIL */
+ while (str[j]) {
+ if (!apr_isalnum(str[j])) {
+ str[j] = '_';
+ }
+ else {
+ str[j] = apr_toupper(str[j]);
+ }
+ j++;
+ }
+ apr_table_set(r->subprocess_env, str,
+ apr_dbd_get_entry(dbd->driver, row, i));
+ i++;
+ }
+
+ dbd_password = apr_pstrdup(r->pool,
+ apr_dbd_get_entry(dbd->driver, row, 0));
+ }
+ /* we can't break out here or row won't get cleaned up */
+ }
+
+ if (!dbd_password) {
+ return AUTH_USER_NOT_FOUND;
+ }
+ AUTHN_CACHE_STORE(r, user, NULL, dbd_password);
+
+ rv = apr_password_validate(password, dbd_password);
+
+ if (rv != APR_SUCCESS) {
+ return AUTH_DENIED;
+ }
+
+ return AUTH_GRANTED;
+}
+
+static authn_status authn_dbd_realm(request_rec *r, const char *user,
+ const char *realm, char **rethash)
+{
+ apr_status_t rv;
+ const char *dbd_hash = NULL;
+ apr_dbd_prepared_t *statement;
+ apr_dbd_results_t *res = NULL;
+ apr_dbd_row_t *row = NULL;
+ int ret;
+
+ authn_dbd_conf *conf = ap_get_module_config(r->per_dir_config,
+ &authn_dbd_module);
+ ap_dbd_t *dbd = authn_dbd_acquire_fn(r);
+ if (dbd == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01658)
+ "Failed to acquire database connection to look up "
+ "user '%s:%s'", user, realm);
+ return AUTH_GENERAL_ERROR;
+ }
+ if (conf->realm == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01659)
+ "No AuthDBDUserRealmQuery has been specified");
+ return AUTH_GENERAL_ERROR;
+ }
+ statement = apr_hash_get(dbd->prepared, conf->realm, APR_HASH_KEY_STRING);
+ if (statement == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01660)
+ "A prepared statement could not be found for "
+ "AuthDBDUserRealmQuery with the key '%s'", conf->realm);
+ return AUTH_GENERAL_ERROR;
+ }
+ if ((ret = apr_dbd_pvselect(dbd->driver, r->pool, dbd->handle, &res,
+ statement, 0, user, realm, NULL)) != 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01661)
+ "Query execution error looking up '%s:%s' "
+ "in database [%s]",
+ user, realm,
+ apr_dbd_error(dbd->driver, dbd->handle, ret));
+ return AUTH_GENERAL_ERROR;
+ }
+ for (rv = apr_dbd_get_row(dbd->driver, r->pool, res, &row, -1);
+ rv != -1;
+ rv = apr_dbd_get_row(dbd->driver, r->pool, res, &row, -1)) {
+ if (rv != 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01662)
+ "Error retrieving results while looking up '%s:%s' "
+ "in database", user, realm);
+ return AUTH_GENERAL_ERROR;
+ }
+ if (dbd_hash == NULL) {
+ /* add the rest of the columns to the environment */
+ int i = 1;
+ const char *name;
+ for (name = apr_dbd_get_name(dbd->driver, res, i);
+ name != NULL;
+ name = apr_dbd_get_name(dbd->driver, res, i)) {
+
+ char *str = apr_pstrcat(r->pool, AUTHN_PREFIX,
+ name,
+ NULL);
+ int j = sizeof(AUTHN_PREFIX)-1; /* string length of "AUTHENTICATE_", excluding the trailing NIL */
+ while (str[j]) {
+ if (!apr_isalnum(str[j])) {
+ str[j] = '_';
+ }
+ else {
+ str[j] = apr_toupper(str[j]);
+ }
+ j++;
+ }
+ apr_table_set(r->subprocess_env, str,
+ apr_dbd_get_entry(dbd->driver, row, i));
+ i++;
+ }
+
+ dbd_hash = apr_pstrdup(r->pool,
+ apr_dbd_get_entry(dbd->driver, row, 0));
+ }
+ /* we can't break out here or row won't get cleaned up */
+ }
+
+ if (!dbd_hash) {
+ return AUTH_USER_NOT_FOUND;
+ }
+ AUTHN_CACHE_STORE(r, user, realm, dbd_hash);
+ *rethash = apr_pstrdup(r->pool, dbd_hash);
+ return AUTH_USER_FOUND;
+}
+
+static void opt_retr(void)
+{
+ authn_cache_store = APR_RETRIEVE_OPTIONAL_FN(ap_authn_cache_store);
+}
+
+static void authn_dbd_hooks(apr_pool_t *p)
+{
+ static const authn_provider authn_dbd_provider = {
+ &authn_dbd_password,
+ &authn_dbd_realm
+ };
+
+ ap_register_auth_provider(p, AUTHN_PROVIDER_GROUP, "dbd",
+ AUTHN_PROVIDER_VERSION,
+ &authn_dbd_provider, AP_AUTH_INTERNAL_PER_CONF);
+ ap_hook_optional_fn_retrieve(opt_retr, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(authn_dbd) =
+{
+ STANDARD20_MODULE_STUFF,
+ authn_dbd_cr_conf,
+ authn_dbd_merge_conf,
+ NULL,
+ NULL,
+ authn_dbd_cmds,
+ authn_dbd_hooks
+};
diff --git a/modules/aaa/mod_authn_dbd.dep b/modules/aaa/mod_authn_dbd.dep
new file mode 100644
index 0000000..257ee49
--- /dev/null
+++ b/modules/aaa/mod_authn_dbd.dep
@@ -0,0 +1,56 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_authn_dbd.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_authn_dbd.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_dbd.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr-util\include\apu_version.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_version.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/aaa/mod_authn_dbd.dsp b/modules/aaa/mod_authn_dbd.dsp
new file mode 100644
index 0000000..9094d33
--- /dev/null
+++ b/modules/aaa/mod_authn_dbd.dsp
@@ -0,0 +1,115 @@
+# Microsoft Developer Studio Project File - Name="mod_authn_dbd" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_authn_dbd - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authn_dbd.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authn_dbd.mak" CFG="mod_authn_dbd - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_authn_dbd - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_authn_dbd - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_authn_dbd - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I ../database /D DBD_DECLARE_EXPORT /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_authn_dbd_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_authn_dbd.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_authn_dbd.so" /d LONG_NAME="authn_dbd_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_authn_dbd.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_dbd.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_authn_dbd.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_dbd.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_authn_dbd.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_authn_dbd - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I ../database /D DBD_DECLARE_EXPORT /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_authn_dbd_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_authn_dbd.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_authn_dbd.so" /d LONG_NAME="authn_dbd_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_authn_dbd.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_dbd.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_authn_dbd.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_dbd.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_authn_dbd.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_authn_dbd - Win32 Release"
+# Name "mod_authn_dbd - Win32 Debug"
+# Begin Source File
+
+SOURCE=..\database\mod_dbd.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\mod_authn_dbd.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/aaa/mod_authn_dbd.mak b/modules/aaa/mod_authn_dbd.mak
new file mode 100644
index 0000000..9c983dc
--- /dev/null
+++ b/modules/aaa/mod_authn_dbd.mak
@@ -0,0 +1,409 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_authn_dbd.dsp
+!IF "$(CFG)" == ""
+CFG=mod_authn_dbd - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_authn_dbd - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_authn_dbd - Win32 Release" && "$(CFG)" != "mod_authn_dbd - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authn_dbd.mak" CFG="mod_authn_dbd - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_authn_dbd - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_authn_dbd - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_authn_dbd - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_authn_dbd.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_dbd - Win32 Release" "mod_auth_basic - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_authn_dbd.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_auth_basic - Win32 ReleaseCLEAN" "mod_dbd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_authn_dbd.obj"
+ -@erase "$(INTDIR)\mod_authn_dbd.res"
+ -@erase "$(INTDIR)\mod_authn_dbd_src.idb"
+ -@erase "$(INTDIR)\mod_authn_dbd_src.pdb"
+ -@erase "$(OUTDIR)\mod_authn_dbd.exp"
+ -@erase "$(OUTDIR)\mod_authn_dbd.lib"
+ -@erase "$(OUTDIR)\mod_authn_dbd.pdb"
+ -@erase "$(OUTDIR)\mod_authn_dbd.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "DBD_DECLARE_EXPORT" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_authn_dbd_src" /FD /I ../database /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_authn_dbd.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_authn_dbd.so" /d LONG_NAME="authn_dbd_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_authn_dbd.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_authn_dbd.pdb" /debug /out:"$(OUTDIR)\mod_authn_dbd.so" /implib:"$(OUTDIR)\mod_authn_dbd.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_dbd.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_authn_dbd.obj" \
+ "$(INTDIR)\mod_authn_dbd.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib" \
+ "$(OUTDIR)\mod_auth_basic.lib" \
+ "..\database\Release\mod_dbd.lib"
+
+"$(OUTDIR)\mod_authn_dbd.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_authn_dbd.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_authn_dbd.so"
+ if exist .\Release\mod_authn_dbd.so.manifest mt.exe -manifest .\Release\mod_authn_dbd.so.manifest -outputresource:.\Release\mod_authn_dbd.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_authn_dbd - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_authn_dbd.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_dbd - Win32 Debug" "mod_auth_basic - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_authn_dbd.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_auth_basic - Win32 DebugCLEAN" "mod_dbd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_authn_dbd.obj"
+ -@erase "$(INTDIR)\mod_authn_dbd.res"
+ -@erase "$(INTDIR)\mod_authn_dbd_src.idb"
+ -@erase "$(INTDIR)\mod_authn_dbd_src.pdb"
+ -@erase "$(OUTDIR)\mod_authn_dbd.exp"
+ -@erase "$(OUTDIR)\mod_authn_dbd.lib"
+ -@erase "$(OUTDIR)\mod_authn_dbd.pdb"
+ -@erase "$(OUTDIR)\mod_authn_dbd.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "DBD_DECLARE_EXPORT" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_authn_dbd_src" /FD /EHsc /I ../database /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_authn_dbd.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_authn_dbd.so" /d LONG_NAME="authn_dbd_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_authn_dbd.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_authn_dbd.pdb" /debug /out:"$(OUTDIR)\mod_authn_dbd.so" /implib:"$(OUTDIR)\mod_authn_dbd.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_dbd.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_authn_dbd.obj" \
+ "$(INTDIR)\mod_authn_dbd.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib" \
+ "$(OUTDIR)\mod_auth_basic.lib" \
+ "..\database\Debug\mod_dbd.lib"
+
+"$(OUTDIR)\mod_authn_dbd.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_authn_dbd.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_authn_dbd.so"
+ if exist .\Debug\mod_authn_dbd.so.manifest mt.exe -manifest .\Debug\mod_authn_dbd.so.manifest -outputresource:.\Debug\mod_authn_dbd.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_authn_dbd.dep")
+!INCLUDE "mod_authn_dbd.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_authn_dbd.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_authn_dbd - Win32 Release" || "$(CFG)" == "mod_authn_dbd - Win32 Debug"
+
+!IF "$(CFG)" == "mod_authn_dbd - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authn_dbd - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authn_dbd - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authn_dbd - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authn_dbd - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authn_dbd - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authn_dbd - Win32 Release"
+
+"mod_auth_basic - Win32 Release" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Release"
+ cd "."
+
+"mod_auth_basic - Win32 ReleaseCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Release" RECURSE=1 CLEAN
+ cd "."
+
+!ELSEIF "$(CFG)" == "mod_authn_dbd - Win32 Debug"
+
+"mod_auth_basic - Win32 Debug" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Debug"
+ cd "."
+
+"mod_auth_basic - Win32 DebugCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Debug" RECURSE=1 CLEAN
+ cd "."
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authn_dbd - Win32 Release"
+
+"mod_dbd - Win32 Release" :
+ cd ".\..\database"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_dbd.mak" CFG="mod_dbd - Win32 Release"
+ cd "..\aaa"
+
+"mod_dbd - Win32 ReleaseCLEAN" :
+ cd ".\..\database"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_dbd.mak" CFG="mod_dbd - Win32 Release" RECURSE=1 CLEAN
+ cd "..\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authn_dbd - Win32 Debug"
+
+"mod_dbd - Win32 Debug" :
+ cd ".\..\database"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_dbd.mak" CFG="mod_dbd - Win32 Debug"
+ cd "..\aaa"
+
+"mod_dbd - Win32 DebugCLEAN" :
+ cd ".\..\database"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_dbd.mak" CFG="mod_dbd - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\aaa"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_authn_dbd - Win32 Release"
+
+
+"$(INTDIR)\mod_authn_dbd.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_authn_dbd.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_authn_dbd.so" /d LONG_NAME="authn_dbd_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_authn_dbd - Win32 Debug"
+
+
+"$(INTDIR)\mod_authn_dbd.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_authn_dbd.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_authn_dbd.so" /d LONG_NAME="authn_dbd_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_authn_dbd.c
+
+"$(INTDIR)\mod_authn_dbd.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/aaa/mod_authn_dbm.c b/modules/aaa/mod_authn_dbm.c
new file mode 100644
index 0000000..9f47350
--- /dev/null
+++ b/modules/aaa/mod_authn_dbm.c
@@ -0,0 +1,231 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * http_auth: authentication
+ *
+ * Rob McCool & Brian Behlendorf.
+ *
+ * Adapted to Apache by rst.
+ *
+ */
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+#include "apr_strings.h"
+#include "apr_dbm.h"
+#include "apr_md5.h" /* for apr_password_validate */
+
+#include "ap_provider.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "http_request.h" /* for ap_hook_(check_user_id | auth_checker)*/
+
+#include "mod_auth.h"
+
+#include "apr_version.h"
+#if !APR_VERSION_AT_LEAST(2,0,0)
+#include "apu_version.h"
+#endif
+
+static APR_OPTIONAL_FN_TYPE(ap_authn_cache_store) *authn_cache_store = NULL;
+#define AUTHN_CACHE_STORE(r,user,realm,data) \
+ if (authn_cache_store != NULL) \
+ authn_cache_store((r), "dbm", (user), (realm), (data))
+
+typedef struct {
+ const char *pwfile;
+ const char *dbmtype;
+} authn_dbm_config_rec;
+
+static void *create_authn_dbm_dir_config(apr_pool_t *p, char *d)
+{
+ authn_dbm_config_rec *conf = apr_palloc(p, sizeof(*conf));
+
+ conf->pwfile = NULL;
+ conf->dbmtype = "default";
+
+ return conf;
+}
+
+static const command_rec authn_dbm_cmds[] =
+{
+ AP_INIT_TAKE1("AuthDBMUserFile", ap_set_file_slot,
+ (void *)APR_OFFSETOF(authn_dbm_config_rec, pwfile),
+ OR_AUTHCFG, "dbm database file containing user IDs and passwords"),
+ AP_INIT_TAKE1("AuthDBMType", ap_set_string_slot,
+ (void *)APR_OFFSETOF(authn_dbm_config_rec, dbmtype),
+ OR_AUTHCFG, "what type of DBM file the user file is"),
+ {NULL}
+};
+
+module AP_MODULE_DECLARE_DATA authn_dbm_module;
+
+static apr_status_t fetch_dbm_value(request_rec *r, const char *dbmtype,
+ const char *dbmfile,
+ const char *user, char **value)
+{
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ const apr_dbm_driver_t *driver;
+ const apu_err_t *err;
+#endif
+ apr_dbm_t *f;
+ apr_datum_t key, val;
+ apr_status_t rv;
+
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ rv = apr_dbm_get_driver(&driver, dbmtype, &err, r->pool);
+
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10284)
+ "could not load '%s' dbm library: %s",
+ err->reason, err->msg);
+ return rv;
+ }
+
+ rv = apr_dbm_open2(&f, driver, dbmfile, APR_DBM_READONLY,
+ APR_OS_DEFAULT, r->pool);
+#else
+ rv = apr_dbm_open_ex(&f, dbmtype, dbmfile, APR_DBM_READONLY,
+ APR_OS_DEFAULT, r->pool);
+#endif
+
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10285)
+ "could not open dbm (type %s) file: %s",
+ dbmtype, dbmfile);
+ return rv;
+ }
+
+ key.dptr = (char*)user;
+#ifndef NETSCAPE_DBM_COMPAT
+ key.dsize = strlen(key.dptr);
+#else
+ key.dsize = strlen(key.dptr) + 1;
+#endif
+
+ *value = NULL;
+
+ if (apr_dbm_fetch(f, key, &val) == APR_SUCCESS && val.dptr) {
+ *value = apr_pstrmemdup(r->pool, val.dptr, val.dsize);
+ }
+
+ apr_dbm_close(f);
+
+ /* NOT FOUND is not an error case; this is indicated by a NULL result.
+ * Treat all NULL lookup/error results as success for the simple case
+ * of auth credential lookup, these are DECLINED in both cases.
+ */
+ return APR_SUCCESS;
+}
+
+static authn_status check_dbm_pw(request_rec *r, const char *user,
+ const char *password)
+{
+ authn_dbm_config_rec *conf = ap_get_module_config(r->per_dir_config,
+ &authn_dbm_module);
+ apr_status_t rv;
+ char *dbm_password;
+ char *colon_pw;
+
+ rv = fetch_dbm_value(r, conf->dbmtype, conf->pwfile, user, &dbm_password);
+
+ if (rv != APR_SUCCESS) {
+ return AUTH_GENERAL_ERROR;
+ }
+
+ if (!dbm_password) {
+ return AUTH_USER_NOT_FOUND;
+ }
+
+ colon_pw = ap_strchr(dbm_password, ':');
+ if (colon_pw) {
+ *colon_pw = '\0';
+ }
+ AUTHN_CACHE_STORE(r, user, NULL, dbm_password);
+
+ rv = apr_password_validate(password, dbm_password);
+
+ if (rv != APR_SUCCESS) {
+ return AUTH_DENIED;
+ }
+
+ return AUTH_GRANTED;
+}
+
+static authn_status get_dbm_realm_hash(request_rec *r, const char *user,
+ const char *realm, char **rethash)
+{
+ authn_dbm_config_rec *conf = ap_get_module_config(r->per_dir_config,
+ &authn_dbm_module);
+ apr_status_t rv;
+ char *dbm_hash;
+ char *colon_hash;
+
+ rv = fetch_dbm_value(r, conf->dbmtype, conf->pwfile,
+ apr_pstrcat(r->pool, user, ":", realm, NULL),
+ &dbm_hash);
+
+ if (rv != APR_SUCCESS) {
+ return AUTH_GENERAL_ERROR;
+ }
+
+ if (!dbm_hash) {
+ return AUTH_USER_NOT_FOUND;
+ }
+
+ colon_hash = ap_strchr(dbm_hash, ':');
+ if (colon_hash) {
+ *colon_hash = '\0';
+ }
+
+ *rethash = dbm_hash;
+ AUTHN_CACHE_STORE(r, user, realm, dbm_hash);
+
+ return AUTH_USER_FOUND;
+}
+
+static const authn_provider authn_dbm_provider =
+{
+ &check_dbm_pw,
+ &get_dbm_realm_hash,
+};
+
+static void opt_retr(void)
+{
+ authn_cache_store = APR_RETRIEVE_OPTIONAL_FN(ap_authn_cache_store);
+}
+static void register_hooks(apr_pool_t *p)
+{
+ ap_register_auth_provider(p, AUTHN_PROVIDER_GROUP, "dbm",
+ AUTHN_PROVIDER_VERSION,
+ &authn_dbm_provider, AP_AUTH_INTERNAL_PER_CONF);
+ ap_hook_optional_fn_retrieve(opt_retr, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(authn_dbm) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_authn_dbm_dir_config, /* dir config creater */
+ NULL, /* dir merger --- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ authn_dbm_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/aaa/mod_authn_dbm.dep b/modules/aaa/mod_authn_dbm.dep
new file mode 100644
index 0000000..2239b12
--- /dev/null
+++ b/modules/aaa/mod_authn_dbm.dep
@@ -0,0 +1,61 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_authn_dbm.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_authn_dbm.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/aaa/mod_authn_dbm.dsp b/modules/aaa/mod_authn_dbm.dsp
new file mode 100644
index 0000000..f2c3ff8
--- /dev/null
+++ b/modules/aaa/mod_authn_dbm.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_authn_dbm" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_authn_dbm - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authn_dbm.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authn_dbm.mak" CFG="mod_authn_dbm - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_authn_dbm - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_authn_dbm - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_authn_dbm - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_authn_dbm_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_authn_dbm.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_authn_dbm.so" /d LONG_NAME="authn_dbm_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_authn_dbm.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_dbm.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_authn_dbm.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_dbm.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_authn_dbm.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_authn_dbm - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_authn_dbm_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_authn_dbm.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_authn_dbm.so" /d LONG_NAME="authn_dbm_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_authn_dbm.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_dbm.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_authn_dbm.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_dbm.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_authn_dbm.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_authn_dbm - Win32 Release"
+# Name "mod_authn_dbm - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_authn_dbm.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/aaa/mod_authn_dbm.mak b/modules/aaa/mod_authn_dbm.mak
new file mode 100644
index 0000000..e15242a
--- /dev/null
+++ b/modules/aaa/mod_authn_dbm.mak
@@ -0,0 +1,381 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_authn_dbm.dsp
+!IF "$(CFG)" == ""
+CFG=mod_authn_dbm - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_authn_dbm - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_authn_dbm - Win32 Release" && "$(CFG)" != "mod_authn_dbm - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authn_dbm.mak" CFG="mod_authn_dbm - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_authn_dbm - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_authn_dbm - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_authn_dbm - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_authn_dbm.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_auth_basic - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_authn_dbm.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_auth_basic - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_authn_dbm.obj"
+ -@erase "$(INTDIR)\mod_authn_dbm.res"
+ -@erase "$(INTDIR)\mod_authn_dbm_src.idb"
+ -@erase "$(INTDIR)\mod_authn_dbm_src.pdb"
+ -@erase "$(OUTDIR)\mod_authn_dbm.exp"
+ -@erase "$(OUTDIR)\mod_authn_dbm.lib"
+ -@erase "$(OUTDIR)\mod_authn_dbm.pdb"
+ -@erase "$(OUTDIR)\mod_authn_dbm.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_authn_dbm_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_authn_dbm.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_authn_dbm.so" /d LONG_NAME="authn_dbm_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_authn_dbm.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_authn_dbm.pdb" /debug /out:"$(OUTDIR)\mod_authn_dbm.so" /implib:"$(OUTDIR)\mod_authn_dbm.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_dbm.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_authn_dbm.obj" \
+ "$(INTDIR)\mod_authn_dbm.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib" \
+ "$(OUTDIR)\mod_auth_basic.lib"
+
+"$(OUTDIR)\mod_authn_dbm.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_authn_dbm.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_authn_dbm.so"
+ if exist .\Release\mod_authn_dbm.so.manifest mt.exe -manifest .\Release\mod_authn_dbm.so.manifest -outputresource:.\Release\mod_authn_dbm.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_authn_dbm - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_authn_dbm.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_auth_basic - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_authn_dbm.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_auth_basic - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_authn_dbm.obj"
+ -@erase "$(INTDIR)\mod_authn_dbm.res"
+ -@erase "$(INTDIR)\mod_authn_dbm_src.idb"
+ -@erase "$(INTDIR)\mod_authn_dbm_src.pdb"
+ -@erase "$(OUTDIR)\mod_authn_dbm.exp"
+ -@erase "$(OUTDIR)\mod_authn_dbm.lib"
+ -@erase "$(OUTDIR)\mod_authn_dbm.pdb"
+ -@erase "$(OUTDIR)\mod_authn_dbm.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_authn_dbm_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_authn_dbm.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_authn_dbm.so" /d LONG_NAME="authn_dbm_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_authn_dbm.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_authn_dbm.pdb" /debug /out:"$(OUTDIR)\mod_authn_dbm.so" /implib:"$(OUTDIR)\mod_authn_dbm.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_dbm.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_authn_dbm.obj" \
+ "$(INTDIR)\mod_authn_dbm.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib" \
+ "$(OUTDIR)\mod_auth_basic.lib"
+
+"$(OUTDIR)\mod_authn_dbm.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_authn_dbm.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_authn_dbm.so"
+ if exist .\Debug\mod_authn_dbm.so.manifest mt.exe -manifest .\Debug\mod_authn_dbm.so.manifest -outputresource:.\Debug\mod_authn_dbm.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_authn_dbm.dep")
+!INCLUDE "mod_authn_dbm.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_authn_dbm.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_authn_dbm - Win32 Release" || "$(CFG)" == "mod_authn_dbm - Win32 Debug"
+
+!IF "$(CFG)" == "mod_authn_dbm - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authn_dbm - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authn_dbm - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authn_dbm - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authn_dbm - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authn_dbm - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authn_dbm - Win32 Release"
+
+"mod_auth_basic - Win32 Release" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Release"
+ cd "."
+
+"mod_auth_basic - Win32 ReleaseCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Release" RECURSE=1 CLEAN
+ cd "."
+
+!ELSEIF "$(CFG)" == "mod_authn_dbm - Win32 Debug"
+
+"mod_auth_basic - Win32 Debug" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Debug"
+ cd "."
+
+"mod_auth_basic - Win32 DebugCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Debug" RECURSE=1 CLEAN
+ cd "."
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_authn_dbm - Win32 Release"
+
+
+"$(INTDIR)\mod_authn_dbm.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_authn_dbm.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_authn_dbm.so" /d LONG_NAME="authn_dbm_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_authn_dbm - Win32 Debug"
+
+
+"$(INTDIR)\mod_authn_dbm.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_authn_dbm.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_authn_dbm.so" /d LONG_NAME="authn_dbm_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_authn_dbm.c
+
+"$(INTDIR)\mod_authn_dbm.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/aaa/mod_authn_file.c b/modules/aaa/mod_authn_file.c
new file mode 100644
index 0000000..9909e44
--- /dev/null
+++ b/modules/aaa/mod_authn_file.c
@@ -0,0 +1,194 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr_strings.h"
+#include "apr_md5.h" /* for apr_password_validate */
+
+#include "ap_config.h"
+#include "ap_provider.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "http_request.h"
+
+#include "mod_auth.h"
+
+typedef struct {
+ char *pwfile;
+} authn_file_config_rec;
+
+static APR_OPTIONAL_FN_TYPE(ap_authn_cache_store) *authn_cache_store = NULL;
+#define AUTHN_CACHE_STORE(r,user,realm,data) \
+ if (authn_cache_store != NULL) \
+ authn_cache_store((r), "file", (user), (realm), (data))
+
+static void *create_authn_file_dir_config(apr_pool_t *p, char *d)
+{
+ authn_file_config_rec *conf = apr_palloc(p, sizeof(*conf));
+
+ conf->pwfile = NULL; /* just to illustrate the default really */
+ return conf;
+}
+
+static const command_rec authn_file_cmds[] =
+{
+ AP_INIT_TAKE1("AuthUserFile", ap_set_file_slot,
+ (void *)APR_OFFSETOF(authn_file_config_rec, pwfile),
+ OR_AUTHCFG, "text file containing user IDs and passwords"),
+ {NULL}
+};
+
+module AP_MODULE_DECLARE_DATA authn_file_module;
+
+static authn_status check_password(request_rec *r, const char *user,
+ const char *password)
+{
+ authn_file_config_rec *conf = ap_get_module_config(r->per_dir_config,
+ &authn_file_module);
+ ap_configfile_t *f;
+ char l[MAX_STRING_LEN];
+ apr_status_t status;
+ char *file_password = NULL;
+
+ if (!conf->pwfile) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01619)
+ "AuthUserFile not specified in the configuration");
+ return AUTH_GENERAL_ERROR;
+ }
+
+ status = ap_pcfg_openfile(&f, r->pool, conf->pwfile);
+
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01620)
+ "Could not open password file: %s", conf->pwfile);
+ return AUTH_GENERAL_ERROR;
+ }
+
+ while (!(ap_cfg_getline(l, MAX_STRING_LEN, f))) {
+ const char *rpw, *w;
+
+ /* Skip # or blank lines. */
+ if ((l[0] == '#') || (!l[0])) {
+ continue;
+ }
+
+ rpw = l;
+ w = ap_getword(r->pool, &rpw, ':');
+
+ if (!strcmp(user, w)) {
+ file_password = ap_getword(r->pool, &rpw, ':');
+ break;
+ }
+ }
+ ap_cfg_closefile(f);
+
+ if (!file_password) {
+ return AUTH_USER_NOT_FOUND;
+ }
+ AUTHN_CACHE_STORE(r, user, NULL, file_password);
+
+ status = apr_password_validate(password, file_password);
+ if (status != APR_SUCCESS) {
+ return AUTH_DENIED;
+ }
+
+ return AUTH_GRANTED;
+}
+
+static authn_status get_realm_hash(request_rec *r, const char *user,
+ const char *realm, char **rethash)
+{
+ authn_file_config_rec *conf = ap_get_module_config(r->per_dir_config,
+ &authn_file_module);
+ ap_configfile_t *f;
+ char l[MAX_STRING_LEN];
+ apr_status_t status;
+ char *file_hash = NULL;
+
+ if (!conf->pwfile) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01621)
+ "AuthUserFile not specified in the configuration");
+ return AUTH_GENERAL_ERROR;
+ }
+
+ status = ap_pcfg_openfile(&f, r->pool, conf->pwfile);
+
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01622)
+ "Could not open password file: %s", conf->pwfile);
+ return AUTH_GENERAL_ERROR;
+ }
+
+ while (!(ap_cfg_getline(l, MAX_STRING_LEN, f))) {
+ const char *rpw, *w, *x;
+
+ /* Skip # or blank lines. */
+ if ((l[0] == '#') || (!l[0])) {
+ continue;
+ }
+
+ rpw = l;
+ w = ap_getword(r->pool, &rpw, ':');
+ x = ap_getword(r->pool, &rpw, ':');
+
+ if (x && w && !strcmp(user, w) && !strcmp(realm, x)) {
+ /* Remember that this is a md5 hash of user:realm:password. */
+ file_hash = ap_getword(r->pool, &rpw, ':');
+ break;
+ }
+ }
+ ap_cfg_closefile(f);
+
+ if (!file_hash) {
+ return AUTH_USER_NOT_FOUND;
+ }
+
+ *rethash = file_hash;
+ AUTHN_CACHE_STORE(r, user, realm, file_hash);
+
+ return AUTH_USER_FOUND;
+}
+
+static const authn_provider authn_file_provider =
+{
+ &check_password,
+ &get_realm_hash,
+};
+
+static void opt_retr(void)
+{
+ authn_cache_store = APR_RETRIEVE_OPTIONAL_FN(ap_authn_cache_store);
+}
+static void register_hooks(apr_pool_t *p)
+{
+ ap_register_auth_provider(p, AUTHN_PROVIDER_GROUP, "file",
+ AUTHN_PROVIDER_VERSION,
+ &authn_file_provider, AP_AUTH_INTERNAL_PER_CONF);
+ ap_hook_optional_fn_retrieve(opt_retr, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(authn_file) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_authn_file_dir_config, /* dir config creater */
+ NULL, /* dir merger --- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ authn_file_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/aaa/mod_authn_file.dep b/modules/aaa/mod_authn_file.dep
new file mode 100644
index 0000000..a841fa6
--- /dev/null
+++ b/modules/aaa/mod_authn_file.dep
@@ -0,0 +1,60 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_authn_file.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_authn_file.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/aaa/mod_authn_file.dsp b/modules/aaa/mod_authn_file.dsp
new file mode 100644
index 0000000..4dc03d5
--- /dev/null
+++ b/modules/aaa/mod_authn_file.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_authn_file" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_authn_file - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authn_file.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authn_file.mak" CFG="mod_authn_file - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_authn_file - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_authn_file - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_authn_file - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_authn_file_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_authn_file.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_authn_file.so" /d LONG_NAME="authn_file_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_authn_file.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_file.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_authn_file.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_file.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_authn_file.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_authn_file - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_authn_file_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_authn_file.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_authn_file.so" /d LONG_NAME="authn_file_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_authn_file.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_file.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_authn_file.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_file.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_authn_file.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_authn_file - Win32 Release"
+# Name "mod_authn_file - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_authn_file.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/aaa/mod_authn_file.mak b/modules/aaa/mod_authn_file.mak
new file mode 100644
index 0000000..3d98ce2
--- /dev/null
+++ b/modules/aaa/mod_authn_file.mak
@@ -0,0 +1,381 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_authn_file.dsp
+!IF "$(CFG)" == ""
+CFG=mod_authn_file - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_authn_file - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_authn_file - Win32 Release" && "$(CFG)" != "mod_authn_file - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authn_file.mak" CFG="mod_authn_file - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_authn_file - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_authn_file - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_authn_file - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_authn_file.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_auth_basic - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_authn_file.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_auth_basic - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_authn_file.obj"
+ -@erase "$(INTDIR)\mod_authn_file.res"
+ -@erase "$(INTDIR)\mod_authn_file_src.idb"
+ -@erase "$(INTDIR)\mod_authn_file_src.pdb"
+ -@erase "$(OUTDIR)\mod_authn_file.exp"
+ -@erase "$(OUTDIR)\mod_authn_file.lib"
+ -@erase "$(OUTDIR)\mod_authn_file.pdb"
+ -@erase "$(OUTDIR)\mod_authn_file.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_authn_file_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_authn_file.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_authn_file.so" /d LONG_NAME="authn_file_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_authn_file.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_authn_file.pdb" /debug /out:"$(OUTDIR)\mod_authn_file.so" /implib:"$(OUTDIR)\mod_authn_file.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_file.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_authn_file.obj" \
+ "$(INTDIR)\mod_authn_file.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib" \
+ "$(OUTDIR)\mod_auth_basic.lib"
+
+"$(OUTDIR)\mod_authn_file.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_authn_file.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_authn_file.so"
+ if exist .\Release\mod_authn_file.so.manifest mt.exe -manifest .\Release\mod_authn_file.so.manifest -outputresource:.\Release\mod_authn_file.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_authn_file - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_authn_file.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_auth_basic - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_authn_file.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_auth_basic - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_authn_file.obj"
+ -@erase "$(INTDIR)\mod_authn_file.res"
+ -@erase "$(INTDIR)\mod_authn_file_src.idb"
+ -@erase "$(INTDIR)\mod_authn_file_src.pdb"
+ -@erase "$(OUTDIR)\mod_authn_file.exp"
+ -@erase "$(OUTDIR)\mod_authn_file.lib"
+ -@erase "$(OUTDIR)\mod_authn_file.pdb"
+ -@erase "$(OUTDIR)\mod_authn_file.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_authn_file_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_authn_file.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_authn_file.so" /d LONG_NAME="authn_file_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_authn_file.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_authn_file.pdb" /debug /out:"$(OUTDIR)\mod_authn_file.so" /implib:"$(OUTDIR)\mod_authn_file.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_file.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_authn_file.obj" \
+ "$(INTDIR)\mod_authn_file.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib" \
+ "$(OUTDIR)\mod_auth_basic.lib"
+
+"$(OUTDIR)\mod_authn_file.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_authn_file.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_authn_file.so"
+ if exist .\Debug\mod_authn_file.so.manifest mt.exe -manifest .\Debug\mod_authn_file.so.manifest -outputresource:.\Debug\mod_authn_file.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_authn_file.dep")
+!INCLUDE "mod_authn_file.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_authn_file.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_authn_file - Win32 Release" || "$(CFG)" == "mod_authn_file - Win32 Debug"
+
+!IF "$(CFG)" == "mod_authn_file - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authn_file - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authn_file - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authn_file - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authn_file - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authn_file - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authn_file - Win32 Release"
+
+"mod_auth_basic - Win32 Release" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Release"
+ cd "."
+
+"mod_auth_basic - Win32 ReleaseCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Release" RECURSE=1 CLEAN
+ cd "."
+
+!ELSEIF "$(CFG)" == "mod_authn_file - Win32 Debug"
+
+"mod_auth_basic - Win32 Debug" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Debug"
+ cd "."
+
+"mod_auth_basic - Win32 DebugCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Debug" RECURSE=1 CLEAN
+ cd "."
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_authn_file - Win32 Release"
+
+
+"$(INTDIR)\mod_authn_file.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_authn_file.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_authn_file.so" /d LONG_NAME="authn_file_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_authn_file - Win32 Debug"
+
+
+"$(INTDIR)\mod_authn_file.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_authn_file.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_authn_file.so" /d LONG_NAME="authn_file_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_authn_file.c
+
+"$(INTDIR)\mod_authn_file.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/aaa/mod_authn_socache.c b/modules/aaa/mod_authn_socache.c
new file mode 100644
index 0000000..0e4454a
--- /dev/null
+++ b/modules/aaa/mod_authn_socache.c
@@ -0,0 +1,475 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr_strings.h"
+#include "apr_md5.h" /* for apr_password_validate */
+
+#include "ap_config.h"
+#include "ap_provider.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "http_request.h"
+
+#include "mod_auth.h"
+
+#include "ap_socache.h"
+#include "util_mutex.h"
+#include "apr_optional.h"
+
+module AP_MODULE_DECLARE_DATA authn_socache_module;
+
+typedef struct authn_cache_dircfg {
+ apr_interval_time_t timeout;
+ apr_array_header_t *providers;
+ const char *context;
+} authn_cache_dircfg;
+
+/* FIXME:
+ * I think the cache and mutex should be global
+ */
+static apr_global_mutex_t *authn_cache_mutex = NULL;
+static ap_socache_provider_t *socache_provider = NULL;
+static ap_socache_instance_t *socache_instance = NULL;
+static const char *const authn_cache_id = "authn-socache";
+static int configured;
+
+static apr_status_t remove_lock(void *data)
+{
+ if (authn_cache_mutex) {
+ apr_global_mutex_destroy(authn_cache_mutex);
+ authn_cache_mutex = NULL;
+ }
+ return APR_SUCCESS;
+}
+
+static apr_status_t destroy_cache(void *data)
+{
+ if (socache_instance) {
+ socache_provider->destroy(socache_instance, (server_rec*)data);
+ socache_instance = NULL;
+ }
+ return APR_SUCCESS;
+}
+
+static int authn_cache_precfg(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptmp)
+{
+ apr_status_t rv = ap_mutex_register(pconf, authn_cache_id,
+ NULL, APR_LOCK_DEFAULT, 0);
+ if (rv != APR_SUCCESS) {
+ ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog, APLOGNO(01673)
+ "failed to register %s mutex", authn_cache_id);
+ return 500; /* An HTTP status would be a misnomer! */
+ }
+ socache_provider = ap_lookup_provider(AP_SOCACHE_PROVIDER_GROUP,
+ AP_SOCACHE_DEFAULT_PROVIDER,
+ AP_SOCACHE_PROVIDER_VERSION);
+ configured = 0;
+ return OK;
+}
+
+static int authn_cache_post_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptmp, server_rec *s)
+{
+ apr_status_t rv;
+ static struct ap_socache_hints authn_cache_hints = {64, 32, 60000000};
+ const char *errmsg;
+
+ if (!configured) {
+ return OK; /* don't waste the overhead of creating mutex & cache */
+ }
+ if (socache_provider == NULL) {
+ ap_log_perror(APLOG_MARK, APLOG_CRIT, 0, plog, APLOGNO(01674)
+ "Please select a socache provider with AuthnCacheSOCache "
+ "(no default found on this platform). Maybe you need to "
+ "load mod_socache_shmcb or another socache module first");
+ return 500; /* An HTTP status would be a misnomer! */
+ }
+
+ /* We have socache_provider, but do not have socache_instance. This should
+ * happen only when using "default" socache_provider, so create default
+ * socache_instance in this case. */
+ if (socache_instance == NULL) {
+ errmsg = socache_provider->create(&socache_instance, NULL,
+ ptmp, pconf);
+ if (errmsg) {
+ ap_log_perror(APLOG_MARK, APLOG_CRIT, 0, plog, APLOGNO(02612)
+ "failed to create mod_socache_shmcb socache "
+ "instance: %s", errmsg);
+ return 500;
+ }
+ }
+
+ rv = ap_global_mutex_create(&authn_cache_mutex, NULL,
+ authn_cache_id, NULL, s, pconf, 0);
+ if (rv != APR_SUCCESS) {
+ ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog, APLOGNO(01675)
+ "failed to create %s mutex", authn_cache_id);
+ return 500; /* An HTTP status would be a misnomer! */
+ }
+ apr_pool_cleanup_register(pconf, NULL, remove_lock, apr_pool_cleanup_null);
+
+ rv = socache_provider->init(socache_instance, authn_cache_id,
+ &authn_cache_hints, s, pconf);
+ if (rv != APR_SUCCESS) {
+ ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog, APLOGNO(01677)
+ "failed to initialise %s cache", authn_cache_id);
+ return 500; /* An HTTP status would be a misnomer! */
+ }
+ apr_pool_cleanup_register(pconf, (void*)s, destroy_cache, apr_pool_cleanup_null);
+ return OK;
+}
+
+static void authn_cache_child_init(apr_pool_t *p, server_rec *s)
+{
+ const char *lock;
+ apr_status_t rv;
+ if (!configured) {
+ return; /* don't waste the overhead of creating mutex & cache */
+ }
+ lock = apr_global_mutex_lockfile(authn_cache_mutex);
+ rv = apr_global_mutex_child_init(&authn_cache_mutex, lock, p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(01678)
+ "failed to initialise mutex in child_init");
+ }
+}
+
+static const char *authn_cache_socache(cmd_parms *cmd, void *CFG,
+ const char *arg)
+{
+ const char *errmsg = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ const char *sep, *name;
+
+ if (errmsg)
+ return errmsg;
+
+ /* Argument is of form 'name:args' or just 'name'. */
+ sep = ap_strchr_c(arg, ':');
+ if (sep) {
+ name = apr_pstrmemdup(cmd->pool, arg, sep - arg);
+ sep++;
+ }
+ else {
+ name = arg;
+ }
+
+ socache_provider = ap_lookup_provider(AP_SOCACHE_PROVIDER_GROUP, name,
+ AP_SOCACHE_PROVIDER_VERSION);
+ if (socache_provider == NULL) {
+ errmsg = apr_psprintf(cmd->pool,
+ "Unknown socache provider '%s'. Maybe you need "
+ "to load the appropriate socache module "
+ "(mod_socache_%s?)", arg, arg);
+ }
+ else {
+ errmsg = socache_provider->create(&socache_instance, sep,
+ cmd->temp_pool, cmd->pool);
+ }
+
+ if (errmsg) {
+ errmsg = apr_psprintf(cmd->pool, "AuthnCacheSOCache: %s", errmsg);
+ }
+ return errmsg;
+}
+
+static const char *authn_cache_enable(cmd_parms *cmd, void *CFG)
+{
+ const char *errmsg = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ configured = 1;
+ return errmsg;
+}
+
+static const char *const directory = "directory";
+static void* authn_cache_dircfg_create(apr_pool_t *pool, char *s)
+{
+ authn_cache_dircfg *ret = apr_palloc(pool, sizeof(authn_cache_dircfg));
+ ret->timeout = apr_time_from_sec(300);
+ ret->providers = NULL;
+ ret->context = directory;
+ return ret;
+}
+
+/* not sure we want this. Might be safer to document use-all-or-none */
+static void* authn_cache_dircfg_merge(apr_pool_t *pool, void *BASE, void *ADD)
+{
+ authn_cache_dircfg *base = BASE;
+ authn_cache_dircfg *add = ADD;
+ authn_cache_dircfg *ret = apr_pmemdup(pool, add, sizeof(authn_cache_dircfg));
+ /* preserve context and timeout if not defaults */
+ if (add->context == directory) {
+ ret->context = base->context;
+ }
+ if (add->timeout == apr_time_from_sec(300)) {
+ ret->timeout = base->timeout;
+ }
+ if (add->providers == NULL) {
+ ret->providers = base->providers;
+ }
+ return ret;
+}
+
+static const char *authn_cache_setprovider(cmd_parms *cmd, void *CFG,
+ const char *arg)
+{
+ authn_cache_dircfg *cfg = CFG;
+ if (cfg->providers == NULL) {
+ cfg->providers = apr_array_make(cmd->pool, 4, sizeof(const char*));
+ }
+ APR_ARRAY_PUSH(cfg->providers, const char*) = arg;
+ configured = 1;
+ return NULL;
+}
+
+static const char *authn_cache_timeout(cmd_parms *cmd, void *CFG,
+ const char *arg)
+{
+ authn_cache_dircfg *cfg = CFG;
+ int secs = atoi(arg);
+ cfg->timeout = apr_time_from_sec(secs);
+ return NULL;
+}
+
+static const command_rec authn_cache_cmds[] =
+{
+ /* global stuff: cache and mutex */
+ AP_INIT_TAKE1("AuthnCacheSOCache", authn_cache_socache, NULL, RSRC_CONF,
+ "socache provider for authn cache"),
+ AP_INIT_NO_ARGS("AuthnCacheEnable", authn_cache_enable, NULL, RSRC_CONF,
+ "enable socache configuration in htaccess even if not enabled anywhere else"),
+ /* per-dir stuff */
+ AP_INIT_ITERATE("AuthnCacheProvideFor", authn_cache_setprovider, NULL,
+ OR_AUTHCFG, "Determine what authn providers to cache for"),
+ AP_INIT_TAKE1("AuthnCacheTimeout", authn_cache_timeout, NULL,
+ OR_AUTHCFG, "Timeout (secs) for cached credentials"),
+ AP_INIT_TAKE1("AuthnCacheContext", ap_set_string_slot,
+ (void*)APR_OFFSETOF(authn_cache_dircfg, context),
+ ACCESS_CONF, "Context for authn cache"),
+ {NULL}
+};
+
+static const char *construct_key(request_rec *r, const char *context,
+ const char *user, const char *realm)
+{
+ /* handle "special" context values */
+ if (!strcmp(context, directory)) {
+ /* FIXME: are we at risk of this blowing up? */
+ char *new_context;
+ char *slash = strrchr(r->uri, '/');
+ new_context = apr_palloc(r->pool, slash - r->uri +
+ strlen(r->server->server_hostname) + 1);
+ strcpy(new_context, r->server->server_hostname);
+ strncat(new_context, r->uri, slash - r->uri);
+ context = new_context;
+ }
+ else if (!strcmp(context, "server")) {
+ context = r->server->server_hostname;
+ }
+ /* any other context value is literal */
+
+ if (realm == NULL) { /* basic auth */
+ return apr_pstrcat(r->pool, context, ":", user, NULL);
+ }
+ else { /* digest auth */
+ return apr_pstrcat(r->pool, context, ":", user, ":", realm, NULL);
+ }
+}
+
+static void ap_authn_cache_store(request_rec *r, const char *module,
+ const char *user, const char *realm,
+ const char* data)
+{
+ apr_status_t rv;
+ authn_cache_dircfg *dcfg;
+ const char *key;
+ apr_time_t expiry;
+
+ /* first check whether we're caching for this module */
+ dcfg = ap_get_module_config(r->per_dir_config, &authn_socache_module);
+ if (!configured || !dcfg->providers) {
+ return;
+ }
+ if (!ap_array_str_contains(dcfg->providers, module)) {
+ return;
+ }
+
+ /* OK, we're on. Grab mutex to do our business */
+ rv = apr_global_mutex_trylock(authn_cache_mutex);
+ if (APR_STATUS_IS_EBUSY(rv)) {
+ /* don't wait around; just abandon it */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(01679)
+ "authn credentials for %s not cached (mutex busy)", user);
+ return;
+ }
+ else if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01680)
+ "Failed to cache authn credentials for %s in %s",
+ module, dcfg->context);
+ return;
+ }
+
+ /* We have the mutex, so go ahead */
+ /* first build our key and determine expiry time */
+ key = construct_key(r, dcfg->context, user, realm);
+ expiry = apr_time_now() + dcfg->timeout;
+
+ /* store it */
+ rv = socache_provider->store(socache_instance, r->server,
+ (unsigned char*)key, strlen(key), expiry,
+ (unsigned char*)data, strlen(data), r->pool);
+ if (rv == APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01681)
+ "Cached authn credentials for %s in %s",
+ user, dcfg->context);
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01682)
+ "Failed to cache authn credentials for %s in %s",
+ module, dcfg->context);
+ }
+
+ /* We're done with the mutex */
+ rv = apr_global_mutex_unlock(authn_cache_mutex);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01683) "Failed to release mutex!");
+ }
+}
+
+#define MAX_VAL_LEN 256
+static authn_status check_password(request_rec *r, const char *user,
+ const char *password)
+{
+ /* construct key
+ * look it up
+ * if found, test password
+ *
+ * mutexing here would be a big performance drag.
+ * It's definitely unnecessary with some backends (like ndbm or gdbm)
+ * Is there a risk in the general case? I guess the only risk we
+ * care about is a race condition that gets us a dangling pointer
+ * to no-longer-defined memory. Hmmm ...
+ */
+ apr_status_t rv;
+ const char *key;
+ authn_cache_dircfg *dcfg;
+ unsigned char val[MAX_VAL_LEN];
+ unsigned int vallen = MAX_VAL_LEN - 1;
+ dcfg = ap_get_module_config(r->per_dir_config, &authn_socache_module);
+ if (!configured || !dcfg->providers) {
+ return AUTH_USER_NOT_FOUND;
+ }
+ key = construct_key(r, dcfg->context, user, NULL);
+ rv = socache_provider->retrieve(socache_instance, r->server,
+ (unsigned char*)key, strlen(key),
+ val, &vallen, r->pool);
+
+ if (APR_STATUS_IS_NOTFOUND(rv)) {
+ /* not found - just return */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01684)
+ "Authn cache: no credentials found for %s", user);
+ return AUTH_USER_NOT_FOUND;
+ }
+ else if (rv == APR_SUCCESS) {
+ /* OK, we got a value */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01685)
+ "Authn cache: found credentials for %s", user);
+ val[vallen] = 0;
+ }
+ else {
+ /* error: give up and pass the buck */
+ /* FIXME: getting this for NOTFOUND - prolly a bug in mod_socache */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01686)
+ "Error accessing authentication cache");
+ return AUTH_USER_NOT_FOUND;
+ }
+
+ rv = apr_password_validate(password, (char*) val);
+ if (rv != APR_SUCCESS) {
+ return AUTH_DENIED;
+ }
+
+ return AUTH_GRANTED;
+}
+
+static authn_status get_realm_hash(request_rec *r, const char *user,
+ const char *realm, char **rethash)
+{
+ apr_status_t rv;
+ const char *key;
+ authn_cache_dircfg *dcfg;
+ unsigned char val[MAX_VAL_LEN];
+ unsigned int vallen = MAX_VAL_LEN - 1;
+ dcfg = ap_get_module_config(r->per_dir_config, &authn_socache_module);
+ if (!configured || !dcfg->providers) {
+ return AUTH_USER_NOT_FOUND;
+ }
+ key = construct_key(r, dcfg->context, user, realm);
+ rv = socache_provider->retrieve(socache_instance, r->server,
+ (unsigned char*)key, strlen(key),
+ val, &vallen, r->pool);
+
+ if (APR_STATUS_IS_NOTFOUND(rv)) {
+ /* not found - just return */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01687)
+ "Authn cache: no credentials found for %s", user);
+ return AUTH_USER_NOT_FOUND;
+ }
+ else if (rv == APR_SUCCESS) {
+ /* OK, we got a value */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01688)
+ "Authn cache: found credentials for %s", user);
+ }
+ else {
+ /* error: give up and pass the buck */
+ /* FIXME: getting this for NOTFOUND - prolly a bug in mod_socache */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01689)
+ "Error accessing authentication cache");
+ return AUTH_USER_NOT_FOUND;
+ }
+ *rethash = apr_pstrmemdup(r->pool, (char *)val, vallen);
+
+ return AUTH_USER_FOUND;
+}
+
+static const authn_provider authn_cache_provider =
+{
+ &check_password,
+ &get_realm_hash,
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_register_auth_provider(p, AUTHN_PROVIDER_GROUP, "socache",
+ AUTHN_PROVIDER_VERSION,
+ &authn_cache_provider, AP_AUTH_INTERNAL_PER_CONF);
+ APR_REGISTER_OPTIONAL_FN(ap_authn_cache_store);
+ ap_hook_pre_config(authn_cache_precfg, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_post_config(authn_cache_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_child_init(authn_cache_child_init, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(authn_socache) =
+{
+ STANDARD20_MODULE_STUFF,
+ authn_cache_dircfg_create,
+ authn_cache_dircfg_merge,
+ NULL,
+ NULL,
+ authn_cache_cmds,
+ register_hooks
+};
diff --git a/modules/aaa/mod_authn_socache.dep b/modules/aaa/mod_authn_socache.dep
new file mode 100644
index 0000000..c8b3d28
--- /dev/null
+++ b/modules/aaa/mod_authn_socache.dep
@@ -0,0 +1,62 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_authn_socache.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_authn_socache.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_socache.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/aaa/mod_authn_socache.dsp b/modules/aaa/mod_authn_socache.dsp
new file mode 100644
index 0000000..c0cad1c
--- /dev/null
+++ b/modules/aaa/mod_authn_socache.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_authn_socache" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_authn_socache - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authn_socache.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authn_socache.mak" CFG="mod_authn_socache - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_authn_socache - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_authn_socache - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_authn_socache - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_authn_socache_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_authn_socache.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_authn_socache.so" /d LONG_NAME="authn_socache_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_authn_socache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_socache.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_authn_socache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_socache.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_authn_socache.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_authn_socache - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_authn_socache_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_authn_socache.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_authn_socache.so" /d LONG_NAME="authn_socache_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_authn_socache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_socache.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_authn_socache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_socache.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_authn_socache.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_authn_socache - Win32 Release"
+# Name "mod_authn_socache - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_authn_socache.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/aaa/mod_authn_socache.mak b/modules/aaa/mod_authn_socache.mak
new file mode 100644
index 0000000..7d43473
--- /dev/null
+++ b/modules/aaa/mod_authn_socache.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_authn_socache.dsp
+!IF "$(CFG)" == ""
+CFG=mod_authn_socache - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_authn_socache - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_authn_socache - Win32 Release" && "$(CFG)" != "mod_authn_socache - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authn_socache.mak" CFG="mod_authn_socache - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_authn_socache - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_authn_socache - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_authn_socache - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_authn_socache.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_authn_socache.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_authn_socache.obj"
+ -@erase "$(INTDIR)\mod_authn_socache.res"
+ -@erase "$(INTDIR)\mod_authn_socache_src.idb"
+ -@erase "$(INTDIR)\mod_authn_socache_src.pdb"
+ -@erase "$(OUTDIR)\mod_authn_socache.exp"
+ -@erase "$(OUTDIR)\mod_authn_socache.lib"
+ -@erase "$(OUTDIR)\mod_authn_socache.pdb"
+ -@erase "$(OUTDIR)\mod_authn_socache.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_authn_socache_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_authn_socache.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_authn_socache.so" /d LONG_NAME="authn_socache_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_authn_socache.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_authn_socache.pdb" /debug /out:"$(OUTDIR)\mod_authn_socache.so" /implib:"$(OUTDIR)\mod_authn_socache.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_socache.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_authn_socache.obj" \
+ "$(INTDIR)\mod_authn_socache.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_authn_socache.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_authn_socache.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_authn_socache.so"
+ if exist .\Release\mod_authn_socache.so.manifest mt.exe -manifest .\Release\mod_authn_socache.so.manifest -outputresource:.\Release\mod_authn_socache.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_authn_socache - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_authn_socache.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_authn_socache.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_authn_socache.obj"
+ -@erase "$(INTDIR)\mod_authn_socache.res"
+ -@erase "$(INTDIR)\mod_authn_socache_src.idb"
+ -@erase "$(INTDIR)\mod_authn_socache_src.pdb"
+ -@erase "$(OUTDIR)\mod_authn_socache.exp"
+ -@erase "$(OUTDIR)\mod_authn_socache.lib"
+ -@erase "$(OUTDIR)\mod_authn_socache.pdb"
+ -@erase "$(OUTDIR)\mod_authn_socache.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_authn_socache_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_authn_socache.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_authn_socache.so" /d LONG_NAME="authn_socache_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_authn_socache.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_authn_socache.pdb" /debug /out:"$(OUTDIR)\mod_authn_socache.so" /implib:"$(OUTDIR)\mod_authn_socache.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_authn_socache.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_authn_socache.obj" \
+ "$(INTDIR)\mod_authn_socache.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_authn_socache.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_authn_socache.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_authn_socache.so"
+ if exist .\Debug\mod_authn_socache.so.manifest mt.exe -manifest .\Debug\mod_authn_socache.so.manifest -outputresource:.\Debug\mod_authn_socache.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_authn_socache.dep")
+!INCLUDE "mod_authn_socache.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_authn_socache.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_authn_socache - Win32 Release" || "$(CFG)" == "mod_authn_socache - Win32 Debug"
+
+!IF "$(CFG)" == "mod_authn_socache - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authn_socache - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authn_socache - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authn_socache - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authn_socache - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authn_socache - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_authn_socache - Win32 Release"
+
+
+"$(INTDIR)\mod_authn_socache.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_authn_socache.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_authn_socache.so" /d LONG_NAME="authn_socache_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_authn_socache - Win32 Debug"
+
+
+"$(INTDIR)\mod_authn_socache.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_authn_socache.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_authn_socache.so" /d LONG_NAME="authn_socache_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_authn_socache.c
+
+"$(INTDIR)\mod_authn_socache.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/aaa/mod_authnz_fcgi.c b/modules/aaa/mod_authnz_fcgi.c
new file mode 100644
index 0000000..1aadcc2
--- /dev/null
+++ b/modules/aaa/mod_authnz_fcgi.c
@@ -0,0 +1,1364 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr_hash.h"
+#include "apr_lib.h"
+#include "apr_strings.h"
+
+#include "ap_provider.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "http_log.h"
+#include "util_script.h"
+#include "ap_provider.h"
+#include "mod_auth.h"
+#include "util_fcgi.h"
+#include "ap_mmn.h"
+
+module AP_MODULE_DECLARE_DATA authnz_fcgi_module;
+
+typedef struct {
+ const char *name; /* provider name */
+ const char *backend; /* backend address, as configured */
+ const char *host;
+ apr_port_t port;
+ apr_sockaddr_t *backend_addrs;
+ int is_authn;
+ int is_authz;
+} fcgi_provider_conf;
+
+typedef struct {
+ const char *name; /* provider name */
+ const char *default_user; /* this is user if authorizer returns
+ * success and a user expression yields
+ * empty string
+ */
+ ap_expr_info_t *user_expr; /* expr to evaluate to set r->user */
+ char authoritative; /* fail request if user is rejected? */
+ char require_basic_auth; /* fail if client didn't send credentials? */
+} fcgi_dir_conf;
+
+typedef struct {
+ /* If an "authnz" provider successfully authenticates, record
+ * the provider name here for checking during authz.
+ */
+ const char *successful_authnz_provider;
+} fcgi_request_notes;
+
+static apr_hash_t *fcgi_authn_providers, *fcgi_authz_providers;
+
+#define FCGI_IO_TIMEOUT apr_time_from_sec(30)
+
+#ifndef NON200_RESPONSE_BUF_LEN
+#define NON200_RESPONSE_BUF_LEN 8192
+#endif
+
+/* fcgi://{hostname|IPv4|IPv6}:port[/] */
+#define FCGI_BACKEND_REGEX_STR "m%^fcgi://(.*):(\\d{1,5})/?$%"
+
+/*
+ * utility function to connect to a peer; generally useful, but
+ * wait for AF_UNIX support in this mod before thinking about how
+ * to make it available to other modules
+ */
+static apr_status_t connect_to_peer(apr_socket_t **newsock,
+ request_rec *r,
+ apr_sockaddr_t *backend_addrs,
+ const char *backend_name,
+ apr_interval_time_t timeout)
+{
+ apr_status_t rv = APR_EINVAL; /* returned if no backend addr was provided
+ */
+ int connected = 0;
+ apr_sockaddr_t *addr = backend_addrs;
+
+ while (addr && !connected) {
+ int loglevel = addr->next ? APLOG_DEBUG : APLOG_ERR;
+ rv = apr_socket_create(newsock, addr->family,
+ SOCK_STREAM, 0, r->pool);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, loglevel, rv, r,
+ APLOGNO(02494) "error creating family %d socket "
+ "for target %s",
+ addr->family, backend_name);
+ addr = addr->next;
+ continue;
+ }
+
+ apr_socket_opt_set(*newsock, APR_TCP_NODELAY, 1);
+ apr_socket_timeout_set(*newsock,
+ timeout ? timeout : r->server->timeout);
+
+ rv = apr_socket_connect(*newsock, addr);
+ if (rv != APR_SUCCESS) {
+ apr_socket_close(*newsock);
+ ap_log_rerror(APLOG_MARK, loglevel, rv, r,
+ APLOGNO(02495) "attempt to connect to %pI (%s) "
+ "failed", addr, backend_name);
+ addr = addr->next;
+ continue;
+ }
+
+ connected = 1;
+ }
+
+ return rv;
+#undef FN_LOG_MARK
+}
+
+static void log_provider_info(const fcgi_provider_conf *conf, request_rec *r)
+{
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ APLOGNO(02496) "name %s, backend %s, host %s, port %d, "
+ "first address %pI, %c%c",
+ conf->name,
+ conf->backend,
+ conf->host,
+ (int)conf->port,
+ conf->backend_addrs,
+ conf->is_authn ? 'N' : '_',
+ conf->is_authz ? 'Z' : '_');
+}
+
+static void setupenv(request_rec *r, const char *password, const char *apache_role)
+{
+ ap_add_common_vars(r);
+ ap_add_cgi_vars(r);
+ apr_table_setn(r->subprocess_env, "FCGI_ROLE", AP_FCGI_AUTHORIZER_STR);
+ if (apache_role) {
+ apr_table_setn(r->subprocess_env, "FCGI_APACHE_ROLE", apache_role);
+ }
+ if (password) {
+ apr_table_setn(r->subprocess_env, "REMOTE_PASSWD", password);
+ }
+ /* Drop the variables CONTENT_LENGTH, PATH_INFO, PATH_TRANSLATED,
+ * SCRIPT_NAME and most Hop-By-Hop headers - EXCEPT we will pass
+ * PROXY_AUTH to allow CGI to perform proxy auth for httpd
+ */
+ apr_table_unset(r->subprocess_env, "CONTENT_LENGTH");
+ apr_table_unset(r->subprocess_env, "PATH_INFO");
+ apr_table_unset(r->subprocess_env, "PATH_TRANSLATED");
+ apr_table_unset(r->subprocess_env, "SCRIPT_NAME");
+ apr_table_unset(r->subprocess_env, "HTTP_KEEP_ALIVE");
+ apr_table_unset(r->subprocess_env, "HTTP_TE");
+ apr_table_unset(r->subprocess_env, "HTTP_TRAILER");
+ apr_table_unset(r->subprocess_env, "HTTP_TRANSFER_ENCODING");
+ apr_table_unset(r->subprocess_env, "HTTP_UPGRADE");
+
+ /* Connection hop-by-hop header to prevent the CGI from hanging */
+ apr_table_setn(r->subprocess_env, "HTTP_CONNECTION", "close");
+}
+
+static apr_status_t recv_data(const fcgi_provider_conf *conf,
+ request_rec *r,
+ apr_socket_t *s,
+ char *buf,
+ apr_size_t *buflen)
+{
+ apr_status_t rv;
+
+ rv = apr_socket_recv(s, buf, buflen);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ APLOGNO(02497) "Couldn't read from backend %s",
+ conf->backend);
+ return rv;
+ }
+
+#if AP_MODULE_MAGIC_AT_LEAST(20130702,2)
+ ap_log_rdata(APLOG_MARK, APLOG_TRACE5, r, "FastCGI data received",
+ buf, *buflen, AP_LOG_DATA_SHOW_OFFSET);
+#endif
+ return APR_SUCCESS;
+}
+
+static apr_status_t recv_data_full(const fcgi_provider_conf *conf,
+ request_rec *r,
+ apr_socket_t *s,
+ char *buf,
+ apr_size_t buflen)
+{
+ apr_size_t readlen;
+ apr_size_t cumulative_len = 0;
+ apr_status_t rv;
+
+ do {
+ readlen = buflen - cumulative_len;
+ rv = recv_data(conf, r, s, buf + cumulative_len, &readlen);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ cumulative_len += readlen;
+ } while (cumulative_len < buflen);
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t sendv_data(const fcgi_provider_conf *conf,
+ request_rec *r,
+ apr_socket_t *s,
+ struct iovec *vec,
+ int nvec,
+ apr_size_t *len)
+{
+ apr_size_t to_write = 0, written = 0;
+ apr_status_t rv = APR_SUCCESS;
+ int i, offset;
+
+ for (i = 0; i < nvec; i++) {
+ to_write += vec[i].iov_len;
+#if AP_MODULE_MAGIC_AT_LEAST(20130702,2)
+ ap_log_rdata(APLOG_MARK, APLOG_TRACE5, r, "FastCGI data sent",
+ vec[i].iov_base, vec[i].iov_len, AP_LOG_DATA_SHOW_OFFSET);
+#endif
+ }
+
+ offset = 0;
+ while (to_write) {
+ apr_size_t n = 0;
+ rv = apr_socket_sendv(s, vec + offset, nvec - offset, &n);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ APLOGNO(02498) "Sending data to %s failed",
+ conf->backend);
+ break;
+ }
+ if (n > 0) {
+ written += n;
+ if (written >= to_write)
+ break; /* short circuit out */
+ for (i = offset; i < nvec; ) {
+ if (n >= vec[i].iov_len) {
+ offset++;
+ n -= vec[i++].iov_len;
+ } else {
+ vec[i].iov_len -= n;
+ vec[i].iov_base = (char *) vec[i].iov_base + n;
+ break;
+ }
+ }
+ }
+ }
+
+ *len = written;
+
+ return rv;
+}
+
+static apr_status_t send_begin_request(request_rec *r,
+ const fcgi_provider_conf *conf,
+ apr_socket_t *s, int role,
+ apr_uint16_t request_id)
+{
+ struct iovec vec[2];
+ ap_fcgi_header header;
+ unsigned char farray[AP_FCGI_HEADER_LEN];
+ ap_fcgi_begin_request_body brb;
+ unsigned char abrb[AP_FCGI_HEADER_LEN];
+ apr_size_t len;
+
+ ap_fcgi_fill_in_header(&header, AP_FCGI_BEGIN_REQUEST, request_id,
+ sizeof(abrb), 0);
+ ap_fcgi_fill_in_request_body(&brb, role, 0 /* *NOT* AP_FCGI_KEEP_CONN */);
+
+ ap_fcgi_header_to_array(&header, farray);
+ ap_fcgi_begin_request_body_to_array(&brb, abrb);
+
+ vec[0].iov_base = (void *)farray;
+ vec[0].iov_len = sizeof(farray);
+ vec[1].iov_base = (void *)abrb;
+ vec[1].iov_len = sizeof(abrb);
+
+ return sendv_data(conf, r, s, vec, 2, &len);
+}
+
+static apr_status_t send_environment(apr_socket_t *s,
+ const fcgi_provider_conf *conf,
+ request_rec *r, apr_uint16_t request_id,
+ apr_pool_t *temp_pool)
+{
+ const char *fn = "send_environment";
+ const apr_array_header_t *envarr;
+ const apr_table_entry_t *elts;
+ struct iovec vec[2];
+ ap_fcgi_header header;
+ unsigned char farray[AP_FCGI_HEADER_LEN];
+ char *body;
+ apr_status_t rv;
+ apr_size_t avail_len, len, required_len;
+ int i, next_elem, starting_elem;
+
+ envarr = apr_table_elts(r->subprocess_env);
+ elts = (const apr_table_entry_t *) envarr->elts;
+
+ if (APLOG_R_IS_LEVEL(r, APLOG_TRACE2)) {
+
+ for (i = 0; i < envarr->nelts; ++i) {
+ if (!elts[i].key) {
+ continue;
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+ "%s: '%s': '%s'",
+ fn, elts[i].key,
+ !strcmp(elts[i].key, "REMOTE_PASSWD") ?
+ "XXXXXXXX" : elts[i].val);
+ }
+ }
+
+ /* Send envvars over in as many FastCGI records as it takes, */
+ next_elem = 0; /* starting with the first one */
+
+ avail_len = 16 * 1024; /* our limit per record, which could have been up
+ * to AP_FCGI_MAX_CONTENT_LEN
+ */
+
+ while (next_elem < envarr->nelts) {
+ starting_elem = next_elem;
+ required_len = ap_fcgi_encoded_env_len(r->subprocess_env,
+ avail_len,
+ &next_elem);
+
+ if (!required_len) {
+ if (next_elem < envarr->nelts) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+ APLOGNO(02499) "couldn't encode envvar '%s' in %"
+ APR_SIZE_T_FMT " bytes",
+ elts[next_elem].key, avail_len);
+ /* skip this envvar and continue */
+ ++next_elem;
+ continue;
+ }
+ /* only an unused element at the end of the array */
+ break;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ APLOGNO(02500) "required len for encoding envvars: %"
+ APR_SIZE_T_FMT ", %d/%d elems processed so far",
+ required_len, next_elem, envarr->nelts);
+
+ body = apr_palloc(temp_pool, required_len);
+ rv = ap_fcgi_encode_env(r, r->subprocess_env, body, required_len,
+ &starting_elem);
+ /* we pre-compute, so we can't run out of space */
+ ap_assert(rv == APR_SUCCESS);
+ /* compute and encode must be in sync */
+ ap_assert(starting_elem == next_elem);
+
+ ap_fcgi_fill_in_header(&header, AP_FCGI_PARAMS, request_id,
+ (apr_uint16_t)required_len, 0);
+ ap_fcgi_header_to_array(&header, farray);
+
+ vec[0].iov_base = (void *)farray;
+ vec[0].iov_len = sizeof(farray);
+ vec[1].iov_base = body;
+ vec[1].iov_len = required_len;
+
+ rv = sendv_data(conf, r, s, vec, 2, &len);
+ apr_pool_clear(temp_pool);
+
+ if (rv) {
+ return rv;
+ }
+ }
+
+ /* Envvars sent, so say we're done */
+ ap_fcgi_fill_in_header(&header, AP_FCGI_PARAMS, request_id, 0, 0);
+ ap_fcgi_header_to_array(&header, farray);
+
+ vec[0].iov_base = (void *)farray;
+ vec[0].iov_len = sizeof(farray);
+
+ return sendv_data(conf, r, s, vec, 1, &len);
+}
+
+/*
+ * This header-state logic is from mod_proxy_fcgi.
+ */
+enum {
+ HDR_STATE_READING_HEADERS,
+ HDR_STATE_GOT_CR,
+ HDR_STATE_GOT_CRLF,
+ HDR_STATE_GOT_CRLFCR,
+ HDR_STATE_GOT_LF,
+ HDR_STATE_DONE_WITH_HEADERS
+};
+
+/* Try to find the end of the script headers in the response from the back
+ * end fastcgi server. STATE holds the current header parsing state for this
+ * request.
+ *
+ * Returns 0 if it can't find the end of the headers, and 1 if it found the
+ * end of the headers. */
+static int handle_headers(request_rec *r, int *state,
+ const char *readbuf, apr_size_t readlen)
+{
+ const char *itr = readbuf;
+
+ while (readlen--) {
+ if (*itr == '\r') {
+ switch (*state) {
+ case HDR_STATE_GOT_CRLF:
+ *state = HDR_STATE_GOT_CRLFCR;
+ break;
+
+ default:
+ *state = HDR_STATE_GOT_CR;
+ break;
+ }
+ }
+ else if (*itr == '\n') {
+ switch (*state) {
+ case HDR_STATE_GOT_LF:
+ *state = HDR_STATE_DONE_WITH_HEADERS;
+ break;
+
+ case HDR_STATE_GOT_CR:
+ *state = HDR_STATE_GOT_CRLF;
+ break;
+
+ case HDR_STATE_GOT_CRLFCR:
+ *state = HDR_STATE_DONE_WITH_HEADERS;
+ break;
+
+ default:
+ *state = HDR_STATE_GOT_LF;
+ break;
+ }
+ }
+ else {
+ *state = HDR_STATE_READING_HEADERS;
+ }
+
+ if (*state == HDR_STATE_DONE_WITH_HEADERS)
+ break;
+
+ ++itr;
+ }
+
+ if (*state == HDR_STATE_DONE_WITH_HEADERS) {
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * handle_response() is based on mod_proxy_fcgi's dispatch()
+ */
+static apr_status_t handle_response(const fcgi_provider_conf *conf,
+ request_rec *r, apr_socket_t *s,
+ apr_pool_t *temp_pool,
+ apr_uint16_t request_id,
+ char *rspbuf,
+ apr_size_t *rspbuflen)
+{
+ apr_bucket *b;
+ apr_bucket_brigade *ob;
+ apr_size_t orspbuflen = 0;
+ apr_status_t rv = APR_SUCCESS;
+ const char *fn = "handle_response";
+ int header_state = HDR_STATE_READING_HEADERS;
+ int seen_end_of_headers = 0, done = 0;
+
+ if (rspbuflen) {
+ orspbuflen = *rspbuflen;
+ *rspbuflen = 0; /* unless we actually read something */
+ }
+
+ ob = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+
+ while (!done && rv == APR_SUCCESS) { /* Keep reading FastCGI records until
+ * we get AP_FCGI_END_REQUEST (done)
+ * or an error occurs.
+ */
+ apr_size_t readbuflen;
+ apr_uint16_t clen;
+ apr_uint16_t rid;
+ char readbuf[AP_IOBUFSIZE + 1];
+ unsigned char farray[AP_FCGI_HEADER_LEN];
+ unsigned char plen;
+ unsigned char type;
+ unsigned char version;
+
+ rv = recv_data_full(conf, r, s, (char *)farray, AP_FCGI_HEADER_LEN);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ APLOGNO(02501) "%s: Error occurred before reading "
+ "entire header", fn);
+ break;
+ }
+
+ ap_fcgi_header_fields_from_array(&version, &type, &rid, &clen, &plen,
+ farray);
+
+ if (version != AP_FCGI_VERSION_1) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ APLOGNO(02502) "%s: Got bogus FastCGI header "
+ "version %d", fn, (int)version);
+ rv = APR_EINVAL;
+ break;
+ }
+
+ if (rid != request_id) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ APLOGNO(02503) "%s: Got bogus FastCGI header "
+ "request id %d, expected %d",
+ fn, rid, request_id);
+ rv = APR_EINVAL;
+ break;
+ }
+
+ recv_again: /* if we need to keep reading more of a record's content */
+
+ if (clen > sizeof(readbuf) - 1) {
+ readbuflen = sizeof(readbuf) - 1;
+ } else {
+ readbuflen = clen;
+ }
+
+ /*
+ * Now get the actual content of the record.
+ */
+ if (readbuflen != 0) {
+ rv = recv_data(conf, r, s, readbuf, &readbuflen);
+ if (rv != APR_SUCCESS) {
+ break;
+ }
+ readbuf[readbuflen] = '\0';
+ }
+
+ switch (type) {
+ case AP_FCGI_STDOUT: /* Response headers and optional body */
+ if (clen != 0) {
+ b = apr_bucket_transient_create(readbuf,
+ readbuflen,
+ r->connection->bucket_alloc);
+
+ APR_BRIGADE_INSERT_TAIL(ob, b);
+
+ if (!seen_end_of_headers) {
+ int st = handle_headers(r, &header_state,
+ readbuf, readbuflen);
+
+ if (st == 1) {
+ int status;
+
+ seen_end_of_headers = 1;
+
+ status =
+ ap_scan_script_header_err_brigade_ex(r, ob,
+ NULL,
+ APLOG_MODULE_INDEX);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ APLOGNO(02504) "%s: script header "
+ "parsing -> %d/%d",
+ fn, status, r->status);
+
+ if (rspbuf) { /* caller wants to see response body,
+ * if any
+ */
+ apr_status_t tmprv;
+
+ if (rspbuflen) {
+ *rspbuflen = orspbuflen;
+ }
+ tmprv = apr_brigade_flatten(ob, rspbuf, rspbuflen);
+ if (tmprv != APR_SUCCESS) {
+ /* should not occur for these bucket types;
+ * does not indicate overflow
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, tmprv, r,
+ APLOGNO(02505) "%s: error "
+ "flattening response body",
+ fn);
+ }
+ }
+
+ if (status != OK) {
+ r->status = status;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ APLOGNO(02506) "%s: Error parsing "
+ "script headers from %s",
+ fn, conf->backend);
+ rv = APR_EINVAL;
+ break;
+ }
+ apr_pool_clear(temp_pool);
+ }
+ else {
+ /* We're still looking for the end of the
+ * headers, so this part of the data will need
+ * to persist. */
+ apr_bucket_setaside(b, temp_pool);
+ }
+ }
+
+ /* If we didn't read all the data go back and get the
+ * rest of it. */
+ if (clen > readbuflen) {
+ clen -= readbuflen;
+ goto recv_again;
+ }
+ }
+ break;
+
+ case AP_FCGI_STDERR: /* Text to log */
+ if (clen) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+ APLOGNO(02507) "%s: Logged from %s: '%s'",
+ fn, conf->backend, readbuf);
+ }
+
+ if (clen > readbuflen) {
+ clen -= readbuflen;
+ goto recv_again; /* continue reading this record */
+ }
+ break;
+
+ case AP_FCGI_END_REQUEST:
+ done = 1;
+ break;
+
+ default:
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ APLOGNO(02508) "%s: Got bogus FastCGI record type "
+ "%d", fn, type);
+ break;
+ }
+ /* Leave on above switch's inner error. */
+ if (rv != APR_SUCCESS) {
+ break;
+ }
+
+ /*
+ * Read/discard any trailing padding.
+ */
+ if (plen) {
+ rv = recv_data_full(conf, r, s, readbuf, plen);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ APLOGNO(02509) "%s: Error occurred reading "
+ "padding",
+ fn);
+ break;
+ }
+ }
+ }
+
+ apr_brigade_cleanup(ob);
+
+ if (rv == APR_SUCCESS && !seen_end_of_headers) {
+ rv = APR_EINVAL;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ APLOGNO(02510) "%s: Never reached end of script headers",
+ fn);
+ }
+
+ return rv;
+}
+
+/* almost from mod_fcgid */
+static int mod_fcgid_modify_auth_header(void *vars,
+ const char *key, const char *val)
+{
+ /* When the application gives a 200 response, the server ignores response
+ headers whose names aren't prefixed with Variable- prefix, and ignores
+ any response content */
+ if (ap_cstr_casecmpn(key, "Variable-", 9) == 0)
+ apr_table_setn(vars, key, val);
+ return 1;
+}
+
+static int fix_auth_header(void *vr, const char *key, const char *val)
+{
+ request_rec *r = vr;
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "moving %s->%s", key, val);
+ apr_table_unset(r->err_headers_out, key);
+ apr_table_setn(r->subprocess_env, key + 9, val);
+ return 1;
+}
+
+static void req_rsp(request_rec *r, const fcgi_provider_conf *conf,
+ const char *password, const char *apache_role,
+ char *rspbuf, apr_size_t *rspbuflen)
+{
+ const char *fn = "req_rsp";
+ apr_pool_t *temp_pool;
+ apr_size_t orspbuflen = 0;
+ apr_socket_t *s;
+ apr_status_t rv;
+ apr_table_t *saved_subprocess_env =
+ apr_table_copy(r->pool, r->subprocess_env);
+
+ if (rspbuflen) {
+ orspbuflen = *rspbuflen;
+ *rspbuflen = 0; /* unless we actually read something */
+ }
+
+ apr_pool_create(&temp_pool, r->pool);
+ apr_pool_tag(temp_pool, "mod_authnz_fcgi (req_rsp)");
+
+ setupenv(r, password, apache_role);
+
+ rv = connect_to_peer(&s, r, conf->backend_addrs,
+ conf->backend, FCGI_IO_TIMEOUT);
+ if (rv == APR_SUCCESS) {
+ apr_uint16_t request_id = 1;
+
+ rv = send_begin_request(r, conf, s, AP_FCGI_AUTHORIZER, request_id);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ APLOGNO(02511) "%s: Failed writing request to %s",
+ fn, conf->backend);
+ }
+
+ if (rv == APR_SUCCESS) {
+ rv = send_environment(s, conf, r, request_id, temp_pool);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ APLOGNO(02512) "%s: Failed writing environment "
+ "to %s", fn, conf->backend);
+ }
+ }
+
+ /* The responder owns the request body, not the authorizer.
+ * Don't even send an empty AP_FCGI_STDIN block. libfcgi doesn't care,
+ * but it wasn't sent to authorizers by mod_fastcgi or mod_fcgi and
+ * may be unhandled by the app. Additionally, the FastCGI spec does
+ * not mention FCGI_STDIN in the Authorizer description, though it
+ * does describe FCGI_STDIN elsewhere in more general terms than
+ * simply a wrapper for the client's request body.
+ */
+
+ if (rv == APR_SUCCESS) {
+ if (rspbuflen) {
+ *rspbuflen = orspbuflen;
+ }
+ rv = handle_response(conf, r, s, temp_pool, request_id, rspbuf,
+ rspbuflen);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ APLOGNO(02514) "%s: Failed handling response "
+ "from %s", fn, conf->backend);
+ }
+ }
+
+ apr_socket_close(s);
+ }
+
+ if (rv != APR_SUCCESS) {
+ /* some sort of mechanical problem */
+ r->status = HTTP_INTERNAL_SERVER_ERROR;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ APLOGNO(02515) "%s: Received HTTP status %d",
+ fn, r->status);
+ }
+
+ r->subprocess_env = saved_subprocess_env;
+
+ if (r->status == HTTP_OK) {
+ /* An Authorizer application's 200 response may include headers
+ * whose names are prefixed with Variable-, and they should be
+ * available to subsequent phases via subprocess_env (and yanked
+ * from the client response).
+ */
+ apr_table_t *vars = apr_table_make(temp_pool, /* not used to allocate
+ * any values that end up
+ * in r->(anything)
+ */
+ 10);
+ apr_table_do(mod_fcgid_modify_auth_header, vars,
+ r->err_headers_out, NULL);
+ apr_table_do(fix_auth_header, r, vars, NULL);
+ }
+
+ apr_pool_destroy(temp_pool);
+}
+
+static int fcgi_check_authn(request_rec *r)
+{
+ const char *fn = "fcgi_check_authn";
+ fcgi_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
+ &authnz_fcgi_module);
+ const char *password = NULL;
+ const fcgi_provider_conf *conf;
+ const char *prov;
+ const char *auth_type;
+ char rspbuf[NON200_RESPONSE_BUF_LEN + 1]; /* extra byte for '\0' */
+ apr_size_t rspbuflen = sizeof rspbuf - 1;
+ int res;
+
+ prov = dconf && dconf->name ? dconf->name : NULL;
+
+ if (!prov || !ap_cstr_casecmp(prov, "None")) {
+ return DECLINED;
+ }
+
+ auth_type = ap_auth_type(r);
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ APLOGNO(02516) "%s, prov %s, authoritative %s, "
+ "require-basic %s, user expr? %s type %s",
+ fn, prov,
+ dconf->authoritative ? "yes" : "no",
+ dconf->require_basic_auth ? "yes" : "no",
+ dconf->user_expr ? "yes" : "no",
+ auth_type);
+
+ if (auth_type && !ap_cstr_casecmp(auth_type, "Basic")) {
+ if ((res = ap_get_basic_auth_pw(r, &password))) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ APLOGNO(02517) "%s: couldn't retrieve basic auth "
+ "password", fn);
+ if (dconf->require_basic_auth) {
+ return res;
+ }
+ password = NULL;
+ }
+ }
+
+ conf = apr_hash_get(fcgi_authn_providers, prov, APR_HASH_KEY_STRING);
+ if (!conf) {
+ ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r,
+ APLOGNO(02518) "%s: can't find config for provider %s",
+ fn, prov);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (APLOGrdebug(r)) {
+ log_provider_info(conf, r);
+ }
+
+ req_rsp(r, conf, password, AP_FCGI_APACHE_ROLE_AUTHENTICATOR_STR,
+ rspbuf, &rspbuflen);
+
+ if (r->status == HTTP_OK) {
+ if (dconf->user_expr) {
+ const char *err;
+ const char *user = ap_expr_str_exec(r, dconf->user_expr,
+ &err);
+ if (user && strlen(user)) {
+ r->user = apr_pstrdup(r->pool, user);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ APLOGNO(02519) "%s: Setting user to '%s'",
+ fn, r->user);
+ }
+ else if (user && dconf->default_user) {
+ r->user = apr_pstrdup(r->pool, dconf->default_user);
+ }
+ else if (user) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ APLOGNO(02520) "%s: Failure extracting user "
+ "after calling authorizer: user expression "
+ "yielded empty string (variable not set?)",
+ fn);
+ r->status = HTTP_INTERNAL_SERVER_ERROR;
+ }
+ else {
+ /* unexpected error, not even an empty string was returned */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ APLOGNO(02521) "%s: Failure extracting user "
+ "after calling authorizer: %s",
+ fn, err);
+ r->status = HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+ if (conf->is_authz) {
+ /* combined authn/authz phase, so app won't be invoked for authz
+ *
+ * Remember that the request was successfully authorized by this
+ * provider.
+ */
+ fcgi_request_notes *rnotes = apr_palloc(r->pool, sizeof(*rnotes));
+ rnotes->successful_authnz_provider = conf->name;
+ ap_set_module_config(r->request_config, &authnz_fcgi_module,
+ rnotes);
+ }
+ }
+ else {
+ /* From the spec:
+ * For Authorizer response status values other than "200" (OK), the
+ * Web server denies access and sends the response status, headers,
+ * and content back to the HTTP client.
+ * But:
+ * This only makes sense if this authorizer is authoritative.
+ */
+ if (rspbuflen > 0 && !dconf->authoritative) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+ APLOGNO(02522) "%s: Ignoring response body from non-"
+ "authoritative authorizer", fn);
+ }
+ else if (rspbuflen > 0) {
+ if (rspbuflen == sizeof rspbuf - 1) {
+ /* apr_brigade_flatten() interface :( */
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+ APLOGNO(02523) "%s: possible overflow handling "
+ "response body", fn);
+ }
+ rspbuf[rspbuflen] = '\0'; /* we reserved an extra byte for '\0' */
+ ap_custom_response(r, r->status, rspbuf); /* API makes a copy */
+ }
+ }
+
+ return r->status == HTTP_OK ?
+ OK : dconf->authoritative ? r->status : DECLINED;
+}
+
+static authn_status fcgi_check_password(request_rec *r, const char *user,
+ const char *password)
+{
+ const char *fn = "fcgi_check_password";
+ const char *prov = apr_table_get(r->notes, AUTHN_PROVIDER_NAME_NOTE);
+ const fcgi_provider_conf *conf;
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ APLOGNO(02524) "%s(%s, XXX): provider %s",
+ fn, user, prov);
+
+ if (!prov) {
+ ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r,
+ APLOGNO(02525) "%s: provider note isn't set", fn);
+ return AUTH_GENERAL_ERROR;
+ }
+
+ conf = apr_hash_get(fcgi_authn_providers, prov, APR_HASH_KEY_STRING);
+ if (!conf) {
+ ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r,
+ APLOGNO(02526) "%s: can't find config for provider %s",
+ fn, prov);
+ return AUTH_GENERAL_ERROR;
+ }
+
+ if (APLOGrdebug(r)) {
+ log_provider_info(conf, r);
+ }
+
+ req_rsp(r, conf, password,
+ /* combined authn and authz: FCGI_APACHE_ROLE not set */
+ conf->is_authz ? NULL : AP_FCGI_APACHE_ROLE_AUTHENTICATOR_STR,
+ NULL, NULL);
+
+ if (r->status == HTTP_OK) {
+ if (conf->is_authz) {
+ /* combined authn/authz phase, so app won't be invoked for authz
+ *
+ * Remember that the request was successfully authorized by this
+ * provider.
+ */
+ fcgi_request_notes *rnotes = apr_palloc(r->pool, sizeof(*rnotes));
+ rnotes->successful_authnz_provider = conf->name;
+ ap_set_module_config(r->request_config, &authnz_fcgi_module,
+ rnotes);
+ }
+ return AUTH_GRANTED;
+ }
+ else if (r->status == HTTP_INTERNAL_SERVER_ERROR) {
+ return AUTH_GENERAL_ERROR;
+ }
+ else {
+ return AUTH_DENIED;
+ }
+}
+
+static const authn_provider fcgi_authn_provider = {
+ &fcgi_check_password,
+ NULL /* get-realm-hash not supported */
+};
+
+static authz_status fcgi_authz_check(request_rec *r,
+ const char *require_line,
+ const void *parsed_require_line)
+{
+ const char *fn = "fcgi_authz_check";
+ const char *prov = apr_table_get(r->notes, AUTHZ_PROVIDER_NAME_NOTE);
+ const fcgi_provider_conf *conf;
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ APLOGNO(02527) "%s(%s)", fn, require_line);
+
+ if (!prov) {
+ ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r,
+ APLOGNO(02528) "%s: provider note isn't set", fn);
+ return AUTHZ_GENERAL_ERROR;
+ }
+
+ conf = apr_hash_get(fcgi_authz_providers, prov, APR_HASH_KEY_STRING);
+ if (!conf) {
+ ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r,
+ APLOGNO(02529) "%s: can't find config for provider %s",
+ fn, prov);
+ return AUTHZ_GENERAL_ERROR;
+ }
+
+ if (APLOGrdebug(r)) {
+ log_provider_info(conf, r);
+ }
+
+ if (!r->user) {
+ return AUTHZ_DENIED_NO_USER;
+ }
+
+ if (conf->is_authn) {
+ /* combined authn/authz phase, so app won't be invoked for authz
+ *
+ * If the provider already successfully authorized this request,
+ * success.
+ */
+ fcgi_request_notes *rnotes = ap_get_module_config(r->request_config,
+ &authnz_fcgi_module);
+ if (rnotes
+ && rnotes->successful_authnz_provider
+ && !strcmp(rnotes->successful_authnz_provider, conf->name)) {
+ return AUTHZ_GRANTED;
+ }
+ else {
+ return AUTHZ_DENIED;
+ }
+ }
+ else {
+ req_rsp(r, conf, NULL, AP_FCGI_APACHE_ROLE_AUTHORIZER_STR, NULL, NULL);
+
+ if (r->status == HTTP_OK) {
+ return AUTHZ_GRANTED;
+ }
+ else if (r->status == HTTP_INTERNAL_SERVER_ERROR) {
+ return AUTHZ_GENERAL_ERROR;
+ }
+ else {
+ return AUTHZ_DENIED;
+ }
+ }
+}
+
+static const char *fcgi_authz_parse(cmd_parms *cmd, const char *require_line,
+ const void **parsed_require_line)
+{
+ /* Allowed form: Require [not] registered-provider-name<EOS>
+ */
+ if (strcmp(require_line, "")) {
+ return "mod_authnz_fcgi doesn't support restrictions on providers "
+ "(i.e., multiple require args)";
+ }
+
+ return NULL;
+}
+
+static const authz_provider fcgi_authz_provider = {
+ &fcgi_authz_check,
+ &fcgi_authz_parse,
+};
+
+static const char *fcgi_check_authn_provider(cmd_parms *cmd,
+ void *d,
+ int argc,
+ char *const argv[])
+{
+ const char *dname = "AuthnzFcgiCheckAuthnProvider";
+ fcgi_dir_conf *dc = d;
+ int ca = 0;
+
+ if (ca >= argc) {
+ return apr_pstrcat(cmd->pool, dname, ": No provider given", NULL);
+ }
+
+ dc->name = argv[ca];
+ ca++;
+
+ if (!strcasecmp(dc->name, "None")) {
+ if (ca < argc) {
+ return "Options aren't supported with \"None\"";
+ }
+ }
+
+ while (ca < argc) {
+ const char *var = argv[ca], *val;
+ int badarg = 0;
+
+ ca++;
+
+ /* at present, everything needs an argument */
+ if (ca >= argc) {
+ return apr_pstrcat(cmd->pool, dname, ": ", var,
+ "needs an argument", NULL);
+ }
+
+ val = argv[ca];
+ ca++;
+
+ if (!strcasecmp(var, "Authoritative")) {
+ if (!strcasecmp(val, "On")) {
+ dc->authoritative = 1;
+ }
+ else if (!strcasecmp(val, "Off")) {
+ dc->authoritative = 0;
+ }
+ else {
+ badarg = 1;
+ }
+ }
+ else if (!strcasecmp(var, "DefaultUser")) {
+ dc->default_user = val;
+ }
+ else if (!strcasecmp(var, "RequireBasicAuth")) {
+ if (!strcasecmp(val, "On")) {
+ dc->require_basic_auth = 1;
+ }
+ else if (!strcasecmp(val, "Off")) {
+ dc->require_basic_auth = 0;
+ }
+ else {
+ badarg = 1;
+ }
+ }
+ else if (!strcasecmp(var, "UserExpr")) {
+ const char *err;
+ int flags = AP_EXPR_FLAG_DONT_VARY | AP_EXPR_FLAG_RESTRICTED
+ | AP_EXPR_FLAG_STRING_RESULT;
+
+ dc->user_expr = ap_expr_parse_cmd(cmd, val,
+ flags, &err, NULL);
+ if (err) {
+ return apr_psprintf(cmd->pool, "%s: Error parsing '%s': '%s'",
+ dname, val, err);
+ }
+ }
+ else {
+ return apr_pstrcat(cmd->pool, dname, ": Unexpected option '",
+ var, "'", NULL);
+ }
+ if (badarg) {
+ return apr_pstrcat(cmd->pool, dname, ": Bad argument '",
+ val, "' to option '", var, "'", NULL);
+ }
+ }
+
+ return NULL;
+}
+
+/* AuthnzFcgiAuthDefineProvider {authn|authz|authnz} provider-name \
+ * fcgi://backendhost:backendport/
+ */
+static const char *fcgi_define_provider(cmd_parms *cmd,
+ void *d,
+ int argc,
+ char *const argv[])
+{
+ const char *dname = "AuthnzFcgiDefineProvider";
+ ap_rxplus_t *fcgi_backend_regex;
+ apr_status_t rv;
+ char *host;
+ const char *err, *stype;
+ fcgi_provider_conf *conf = apr_pcalloc(cmd->pool, sizeof(*conf));
+ int ca = 0, rc, port;
+
+ fcgi_backend_regex = ap_rxplus_compile(cmd->pool, FCGI_BACKEND_REGEX_STR);
+ if (!fcgi_backend_regex) {
+ return apr_psprintf(cmd->pool,
+ "%s: failed to compile regexec '%s'",
+ dname, FCGI_BACKEND_REGEX_STR);
+ }
+
+ err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err)
+ return err;
+
+ if (ca >= argc) {
+ return apr_pstrcat(cmd->pool, dname, ": No type given", NULL);
+ }
+
+ stype = argv[ca];
+ ca++;
+
+ if (!strcasecmp(stype, "authn")) {
+ conf->is_authn = 1;
+ }
+ else if (!strcasecmp(stype, "authz")) {
+ conf->is_authz = 1;
+ }
+ else if (!strcasecmp(stype, "authnz")) {
+ conf->is_authn = conf->is_authz = 1;
+ }
+ else {
+ return apr_pstrcat(cmd->pool,
+ dname,
+ ": Invalid provider type ",
+ stype,
+ NULL);
+ }
+
+ if (ca >= argc) {
+ return apr_pstrcat(cmd->pool, dname, ": No provider name given", NULL);
+ }
+ conf->name = argv[ca];
+ ca++;
+
+ if (ca >= argc) {
+ return apr_pstrcat(cmd->pool, dname, ": No backend-address given",
+ NULL);
+ }
+
+ rc = ap_rxplus_exec(cmd->pool, fcgi_backend_regex, argv[ca], NULL);
+ if (!rc || ap_rxplus_nmatch(fcgi_backend_regex) != 3) {
+ return apr_pstrcat(cmd->pool,
+ dname, ": backend-address '",
+ argv[ca],
+ "' has invalid form",
+ NULL);
+ }
+
+ host = ap_rxplus_pmatch(cmd->pool, fcgi_backend_regex, 1);
+ if (host[0] == '[' && host[strlen(host) - 1] == ']') {
+ host += 1;
+ host[strlen(host) - 1] = '\0';
+ }
+
+ port = atoi(ap_rxplus_pmatch(cmd->pool, fcgi_backend_regex, 2));
+ if (port > 65535) {
+ return apr_pstrcat(cmd->pool,
+ dname, ": backend-address '",
+ argv[ca],
+ "' has invalid port",
+ NULL);
+ }
+
+ conf->backend = argv[ca];
+ conf->host = host;
+ conf->port = port;
+ ca++;
+
+ rv = apr_sockaddr_info_get(&conf->backend_addrs, conf->host,
+ APR_UNSPEC, conf->port, 0, cmd->pool);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_CRIT, rv, NULL,
+ APLOGNO(02530) "Address %s could not be resolved",
+ conf->backend);
+ return apr_pstrcat(cmd->pool,
+ dname,
+ ": Error resolving backend address",
+ NULL);
+ }
+
+ if (ca != argc) {
+ return apr_pstrcat(cmd->pool,
+ dname,
+ ": Unexpected parameter ",
+ argv[ca],
+ NULL);
+ }
+
+ if (conf->is_authn) {
+ apr_hash_set(fcgi_authn_providers, conf->name, APR_HASH_KEY_STRING,
+ conf);
+ ap_register_auth_provider(cmd->pool, AUTHN_PROVIDER_GROUP,
+ conf->name,
+ AUTHN_PROVIDER_VERSION,
+ &fcgi_authn_provider,
+ AP_AUTH_INTERNAL_PER_CONF);
+ }
+
+ if (conf->is_authz) {
+ apr_hash_set(fcgi_authz_providers, conf->name, APR_HASH_KEY_STRING,
+ conf);
+ ap_register_auth_provider(cmd->pool, AUTHZ_PROVIDER_GROUP,
+ conf->name,
+ AUTHZ_PROVIDER_VERSION,
+ &fcgi_authz_provider,
+ AP_AUTH_INTERNAL_PER_CONF);
+ }
+
+ return NULL;
+}
+
+static const command_rec fcgi_cmds[] = {
+ AP_INIT_TAKE_ARGV("AuthnzFcgiDefineProvider",
+ fcgi_define_provider,
+ NULL,
+ RSRC_CONF,
+ "Define a FastCGI authn and/or authz provider"),
+
+ AP_INIT_TAKE_ARGV("AuthnzFcgiCheckAuthnProvider",
+ fcgi_check_authn_provider,
+ NULL,
+ OR_FILEINFO,
+ "Enable/disable a FastCGI authorizer to handle "
+ "check_authn phase"),
+
+ {NULL}
+};
+
+static int fcgi_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp)
+{
+ fcgi_authn_providers = apr_hash_make(pconf);
+ fcgi_authz_providers = apr_hash_make(pconf);
+
+ return OK;
+}
+
+static void fcgi_register_hooks(apr_pool_t *p)
+{
+ static const char * const auth_basic_runs_after_me[] =
+ {"mod_auth_basic.c", NULL}; /* to allow for custom response */
+
+ ap_hook_pre_config(fcgi_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_check_authn(fcgi_check_authn, NULL, auth_basic_runs_after_me,
+ APR_HOOK_MIDDLE, AP_AUTH_INTERNAL_PER_CONF);
+}
+
+static void *create_dir_conf(apr_pool_t *p, char *dummy)
+{
+ fcgi_dir_conf *dconf = apr_pcalloc(p, sizeof(fcgi_dir_conf));
+
+ dconf->authoritative = 1;
+ return dconf;
+}
+
+static void *merge_dir_conf(apr_pool_t *p, void *basev, void *overridesv)
+{
+ fcgi_dir_conf *a = (fcgi_dir_conf *)apr_pcalloc(p, sizeof(*a));
+ fcgi_dir_conf *base = (fcgi_dir_conf *)basev,
+ *over = (fcgi_dir_conf *)overridesv;
+
+ /* currently we just have a single directive applicable to a
+ * directory, so if it is set then grab all fields from fcgi_dir_conf
+ */
+ if (over->name) {
+ memcpy(a, over, sizeof(*a));
+ }
+ else {
+ memcpy(a, base, sizeof(*a));
+ }
+
+ return a;
+}
+
+AP_DECLARE_MODULE(authnz_fcgi) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_dir_conf, /* dir config creater */
+ merge_dir_conf, /* dir merger */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ fcgi_cmds, /* command apr_table_t */
+ fcgi_register_hooks /* register hooks */
+};
diff --git a/modules/aaa/mod_authnz_fcgi.dep b/modules/aaa/mod_authnz_fcgi.dep
new file mode 100644
index 0000000..7424929
--- /dev/null
+++ b/modules/aaa/mod_authnz_fcgi.dep
@@ -0,0 +1,61 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_authnz_fcgi.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_authnz_fcgi.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_fcgi.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/aaa/mod_authnz_fcgi.dsp b/modules/aaa/mod_authnz_fcgi.dsp
new file mode 100644
index 0000000..a731e4f
--- /dev/null
+++ b/modules/aaa/mod_authnz_fcgi.dsp
@@ -0,0 +1,119 @@
+# Microsoft Developer Studio Project File - Name="mod_authnz_fcgi" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_authnz_fcgi - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authnz_fcgi.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authnz_fcgi.mak" CFG="mod_authnz_fcgi - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_authnz_fcgi - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_authnz_fcgi - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_authnz_fcgi - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../database" /D "authnz_fcgi_DECLARE_EXPORT" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_authnz_fcgi_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_authnz_fcgi.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_authnz_fcgi.so" /d LONG_NAME="authnz_fcgi_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_authnz_fcgi.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authnz_fcgi.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_authnz_fcgi.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authnz_fcgi.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_authnz_fcgi.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_authnz_fcgi - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../database" /D "authnz_fcgi_DECLARE_EXPORT" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_authnz_fcgi_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_authnz_fcgi.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_authnz_fcgi.so" /d LONG_NAME="authnz_fcgi_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_authnz_fcgi.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authnz_fcgi.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_authnz_fcgi.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authnz_fcgi.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_authnz_fcgi.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_authnz_fcgi - Win32 Release"
+# Name "mod_authnz_fcgi - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_authnz_fcgi.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\include\mod_auth.h
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\include\util_fcgi.h
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/aaa/mod_authnz_fcgi.mak b/modules/aaa/mod_authnz_fcgi.mak
new file mode 100644
index 0000000..772cae2
--- /dev/null
+++ b/modules/aaa/mod_authnz_fcgi.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_authnz_fcgi.dsp
+!IF "$(CFG)" == ""
+CFG=mod_authnz_fcgi - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_authnz_fcgi - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_authnz_fcgi - Win32 Release" && "$(CFG)" != "mod_authnz_fcgi - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authnz_fcgi.mak" CFG="mod_authnz_fcgi - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_authnz_fcgi - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_authnz_fcgi - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_authnz_fcgi - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_authnz_fcgi.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_authnz_fcgi.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_authnz_fcgi.obj"
+ -@erase "$(INTDIR)\mod_authnz_fcgi.res"
+ -@erase "$(INTDIR)\mod_authnz_fcgi_src.idb"
+ -@erase "$(INTDIR)\mod_authnz_fcgi_src.pdb"
+ -@erase "$(OUTDIR)\mod_authnz_fcgi.exp"
+ -@erase "$(OUTDIR)\mod_authnz_fcgi.lib"
+ -@erase "$(OUTDIR)\mod_authnz_fcgi.pdb"
+ -@erase "$(OUTDIR)\mod_authnz_fcgi.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../database" /D "authnz_fcgi_DECLARE_EXPORT" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_authnz_fcgi_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_authnz_fcgi.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_authnz_fcgi.so" /d LONG_NAME="authnz_fcgi_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_authnz_fcgi.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_authnz_fcgi.pdb" /debug /out:"$(OUTDIR)\mod_authnz_fcgi.so" /implib:"$(OUTDIR)\mod_authnz_fcgi.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_authnz_fcgi.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_authnz_fcgi.obj" \
+ "$(INTDIR)\mod_authnz_fcgi.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_authnz_fcgi.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_authnz_fcgi.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_authnz_fcgi.so"
+ if exist .\Release\mod_authnz_fcgi.so.manifest mt.exe -manifest .\Release\mod_authnz_fcgi.so.manifest -outputresource:.\Release\mod_authnz_fcgi.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_authnz_fcgi - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_authnz_fcgi.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_authnz_fcgi.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_authnz_fcgi.obj"
+ -@erase "$(INTDIR)\mod_authnz_fcgi.res"
+ -@erase "$(INTDIR)\mod_authnz_fcgi_src.idb"
+ -@erase "$(INTDIR)\mod_authnz_fcgi_src.pdb"
+ -@erase "$(OUTDIR)\mod_authnz_fcgi.exp"
+ -@erase "$(OUTDIR)\mod_authnz_fcgi.lib"
+ -@erase "$(OUTDIR)\mod_authnz_fcgi.pdb"
+ -@erase "$(OUTDIR)\mod_authnz_fcgi.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../database" /D "authnz_fcgi_DECLARE_EXPORT" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_authnz_fcgi_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_authnz_fcgi.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_authnz_fcgi.so" /d LONG_NAME="authnz_fcgi_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_authnz_fcgi.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_authnz_fcgi.pdb" /debug /out:"$(OUTDIR)\mod_authnz_fcgi.so" /implib:"$(OUTDIR)\mod_authnz_fcgi.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_authnz_fcgi.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_authnz_fcgi.obj" \
+ "$(INTDIR)\mod_authnz_fcgi.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_authnz_fcgi.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_authnz_fcgi.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_authnz_fcgi.so"
+ if exist .\Debug\mod_authnz_fcgi.so.manifest mt.exe -manifest .\Debug\mod_authnz_fcgi.so.manifest -outputresource:.\Debug\mod_authnz_fcgi.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_authnz_fcgi.dep")
+!INCLUDE "mod_authnz_fcgi.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_authnz_fcgi.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_authnz_fcgi - Win32 Release" || "$(CFG)" == "mod_authnz_fcgi - Win32 Debug"
+
+!IF "$(CFG)" == "mod_authnz_fcgi - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authnz_fcgi - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authnz_fcgi - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authnz_fcgi - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authnz_fcgi - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authnz_fcgi - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_authnz_fcgi - Win32 Release"
+
+
+"$(INTDIR)\mod_authnz_fcgi.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_authnz_fcgi.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_authnz_fcgi.so" /d LONG_NAME="authnz_fcgi_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_authnz_fcgi - Win32 Debug"
+
+
+"$(INTDIR)\mod_authnz_fcgi.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_authnz_fcgi.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_authnz_fcgi.so" /d LONG_NAME="authnz_fcgi_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_authnz_fcgi.c
+
+"$(INTDIR)\mod_authnz_fcgi.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/aaa/mod_authnz_ldap.c b/modules/aaa/mod_authnz_ldap.c
new file mode 100644
index 0000000..a7b4939
--- /dev/null
+++ b/modules/aaa/mod_authnz_ldap.c
@@ -0,0 +1,1977 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ap_provider.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "ap_provider.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "util_ldap.h"
+
+#include "mod_auth.h"
+
+#include "apr_strings.h"
+#include "apr_xlate.h"
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+#include "apr_lib.h"
+
+#include <ctype.h>
+
+#if !APR_HAS_LDAP
+#error mod_authnz_ldap requires APR-util to have LDAP support built in. To fix add --with-ldap to ./configure.
+#endif
+
+static char *default_attributes[3] = { "member", "uniqueMember", NULL };
+
+typedef struct {
+ apr_pool_t *pool; /* Pool that this config is allocated from */
+#if APR_HAS_THREADS
+ apr_thread_mutex_t *lock; /* Lock for this config */
+#endif
+
+ /* These parameters are all derived from the AuthLDAPURL directive */
+ char *url; /* String representation of the URL */
+
+ char *host; /* Name of the LDAP server (or space separated list) */
+ int port; /* Port of the LDAP server */
+ char *basedn; /* Base DN to do all searches from */
+ char *attribute; /* Attribute to search for */
+ char **attributes; /* Array of all the attributes to return */
+ int scope; /* Scope of the search */
+ char *filter; /* Filter to further limit the search */
+ deref_options deref; /* how to handle alias dereferening */
+ char *binddn; /* DN to bind to server (can be NULL) */
+ char *bindpw; /* Password to bind to server (can be NULL) */
+ int bind_authoritative; /* If true, will return errors when bind fails */
+
+ int user_is_dn; /* If true, r->user is replaced by DN during authn */
+ char *remote_user_attribute; /* If set, r->user is replaced by this attribute during authn */
+ int compare_dn_on_server; /* If true, will use server to do DN compare */
+
+ int have_ldap_url; /* Set if we have found an LDAP url */
+
+ apr_array_header_t *groupattr; /* List of Group attributes identifying user members. Default:"member uniqueMember" */
+ int group_attrib_is_dn; /* If true, the group attribute is the DN, otherwise,
+ it's the exact string passed by the HTTP client */
+ char **sgAttributes; /* Array of strings constructed (post-config) from subgroupattrs. Last entry is NULL. */
+ apr_array_header_t *subgroupclasses; /* List of object classes of sub-groups. Default:"groupOfNames groupOfUniqueNames" */
+ int maxNestingDepth; /* Maximum recursive nesting depth permitted during subgroup processing. Default: 10 */
+
+ int secure; /* True if SSL connections are requested */
+ char *authz_prefix; /* Prefix for environment variables added during authz */
+ int initial_bind_as_user; /* true if we should try to bind (to lookup DN) directly with the basic auth username */
+ ap_regex_t *bind_regex; /* basic auth -> bind'able username regex */
+ const char *bind_subst; /* basic auth -> bind'able username substitution */
+ int search_as_user; /* true if authz searches should be done with the users credentials (when we did authn) */
+ int compare_as_user; /* true if authz compares should be done with the users credentials (when we did authn) */
+} authn_ldap_config_t;
+
+typedef struct {
+ char *dn; /* The saved dn from a successful search */
+ char *user; /* The username provided by the client */
+ const char **vals; /* The additional values pulled during the DN search*/
+ char *password; /* if this module successfully authenticates, the basic auth password, else null */
+} authn_ldap_request_t;
+
+enum auth_ldap_phase {
+ LDAP_AUTHN, LDAP_AUTHZ
+};
+
+enum auth_ldap_optype {
+ LDAP_SEARCH, LDAP_COMPARE, LDAP_COMPARE_AND_SEARCH /* nested groups */
+};
+
+/* maximum group elements supported */
+#define GROUPATTR_MAX_ELTS 10
+
+module AP_MODULE_DECLARE_DATA authnz_ldap_module;
+
+static APR_OPTIONAL_FN_TYPE(uldap_connection_close) *util_ldap_connection_close;
+static APR_OPTIONAL_FN_TYPE(uldap_connection_find) *util_ldap_connection_find;
+static APR_OPTIONAL_FN_TYPE(uldap_cache_comparedn) *util_ldap_cache_comparedn;
+static APR_OPTIONAL_FN_TYPE(uldap_cache_compare) *util_ldap_cache_compare;
+static APR_OPTIONAL_FN_TYPE(uldap_cache_check_subgroups) *util_ldap_cache_check_subgroups;
+static APR_OPTIONAL_FN_TYPE(uldap_cache_checkuserid) *util_ldap_cache_checkuserid;
+static APR_OPTIONAL_FN_TYPE(uldap_cache_getuserdn) *util_ldap_cache_getuserdn;
+static APR_OPTIONAL_FN_TYPE(uldap_ssl_supported) *util_ldap_ssl_supported;
+
+static apr_hash_t *charset_conversions = NULL;
+static char *to_charset = NULL; /* UTF-8 identifier derived from the charset.conv file */
+
+
+/* Derive a code page ID give a language name or ID */
+static char* derive_codepage_from_lang (apr_pool_t *p, char *language)
+{
+ char *charset;
+
+ if (!language) /* our default codepage */
+ return apr_pstrdup(p, "ISO-8859-1");
+
+ charset = (char*) apr_hash_get(charset_conversions, language, APR_HASH_KEY_STRING);
+
+ /*
+ * Test if language values like 'en-US' return a match from the charset
+ * conversion map when shortened to 'en'.
+ */
+ if (!charset && strlen(language) > 3 && language[2] == '-') {
+ char *language_short = apr_pstrndup(p, language, 2);
+ charset = (char*) apr_hash_get(charset_conversions, language_short, APR_HASH_KEY_STRING);
+ }
+
+ if (charset) {
+ charset = apr_pstrdup(p, charset);
+ }
+
+ return charset;
+}
+
+static apr_xlate_t* get_conv_set (request_rec *r)
+{
+ char *lang_line = (char*)apr_table_get(r->headers_in, "accept-language");
+ char *lang;
+ apr_xlate_t *convset;
+
+ if (lang_line) {
+ lang_line = apr_pstrdup(r->pool, lang_line);
+ for (lang = lang_line;*lang;lang++) {
+ if ((*lang == ',') || (*lang == ';')) {
+ *lang = '\0';
+ break;
+ }
+ }
+ lang = derive_codepage_from_lang(r->pool, lang_line);
+
+ if (lang && (apr_xlate_open(&convset, to_charset, lang, r->pool) == APR_SUCCESS)) {
+ return convset;
+ }
+ }
+
+ return NULL;
+}
+
+
+static const char* authn_ldap_xlate_password(request_rec *r,
+ const char* sent_password)
+{
+ apr_xlate_t *convset = NULL;
+ apr_size_t inbytes;
+ apr_size_t outbytes;
+ char *outbuf;
+
+ if (charset_conversions && (convset = get_conv_set(r)) ) {
+ inbytes = strlen(sent_password);
+ outbytes = (inbytes+1)*3;
+ outbuf = apr_pcalloc(r->pool, outbytes);
+
+ /* Convert the password to UTF-8. */
+ if (apr_xlate_conv_buffer(convset, sent_password, &inbytes, outbuf,
+ &outbytes) == APR_SUCCESS)
+ return outbuf;
+ }
+
+ return sent_password;
+}
+
+
+/*
+ * Build the search filter, or at least as much of the search filter that
+ * will fit in the buffer. We don't worry about the buffer not being able
+ * to hold the entire filter. If the buffer wasn't big enough to hold the
+ * filter, ldap_search_s will complain, but the only situation where this
+ * is likely to happen is if the client sent a really, really long
+ * username, most likely as part of an attack.
+ *
+ * The search filter consists of the filter provided with the URL,
+ * combined with a filter made up of the attribute provided with the URL,
+ * and the actual username passed by the HTTP client. For example, assume
+ * that the LDAP URL is
+ *
+ * ldap://ldap.airius.com/ou=People, o=Airius?uid??(posixid=*)
+ *
+ * Further, assume that the userid passed by the client was `userj'. The
+ * search filter will be (&(posixid=*)(uid=userj)).
+ */
+#define FILTER_LENGTH MAX_STRING_LEN
+static void authn_ldap_build_filter(char *filtbuf,
+ request_rec *r,
+ const char* sent_user,
+ const char* sent_filter,
+ authn_ldap_config_t *sec)
+{
+ char *p, *q, *filtbuf_end;
+ char *user, *filter;
+ apr_xlate_t *convset = NULL;
+ apr_size_t inbytes;
+ apr_size_t outbytes;
+ char *outbuf;
+ int nofilter = 0;
+
+ if (sent_user != NULL) {
+ user = apr_pstrdup (r->pool, sent_user);
+ }
+ else
+ return;
+
+ if (sent_filter != NULL) {
+ filter = apr_pstrdup (r->pool, sent_filter);
+ }
+ else
+ filter = sec->filter;
+
+ if (charset_conversions) {
+ convset = get_conv_set(r);
+ }
+
+ if (convset) {
+ inbytes = strlen(user);
+ outbytes = (inbytes+1)*3;
+ outbuf = apr_pcalloc(r->pool, outbytes);
+
+ /* Convert the user name to UTF-8. This is only valid for LDAP v3 */
+ if (apr_xlate_conv_buffer(convset, user, &inbytes, outbuf, &outbytes) == APR_SUCCESS) {
+ user = apr_pstrdup(r->pool, outbuf);
+ }
+ }
+
+ /*
+ * Create the first part of the filter, which consists of the
+ * config-supplied portions.
+ */
+
+ if ((nofilter = (filter && !strcasecmp(filter, "none")))) {
+ apr_snprintf(filtbuf, FILTER_LENGTH, "(%s=", sec->attribute);
+ }
+ else {
+ apr_snprintf(filtbuf, FILTER_LENGTH, "(&(%s)(%s=", filter, sec->attribute);
+ }
+
+ /*
+ * Now add the client-supplied username to the filter, ensuring that any
+ * LDAP filter metachars are escaped.
+ */
+ filtbuf_end = filtbuf + FILTER_LENGTH - 1;
+#if APR_HAS_MICROSOFT_LDAPSDK
+ for (p = user, q=filtbuf + strlen(filtbuf);
+ *p && q < filtbuf_end; ) {
+ if (strchr("*()\\", *p) != NULL) {
+ if ( q + 3 >= filtbuf_end)
+ break; /* Don't write part of escape sequence if we can't write all of it */
+ *q++ = '\\';
+ switch ( *p++ )
+ {
+ case '*':
+ *q++ = '2';
+ *q++ = 'a';
+ break;
+ case '(':
+ *q++ = '2';
+ *q++ = '8';
+ break;
+ case ')':
+ *q++ = '2';
+ *q++ = '9';
+ break;
+ case '\\':
+ *q++ = '5';
+ *q++ = 'c';
+ break;
+ }
+ }
+ else
+ *q++ = *p++;
+ }
+#else
+ for (p = user, q=filtbuf + strlen(filtbuf);
+ *p && q < filtbuf_end; *q++ = *p++) {
+ if (strchr("*()\\", *p) != NULL) {
+ *q++ = '\\';
+ if (q >= filtbuf_end) {
+ break;
+ }
+ }
+ }
+#endif
+ *q = '\0';
+
+ /*
+ * Append the closing parens of the filter, unless doing so would
+ * overrun the buffer.
+ */
+
+ if (nofilter) {
+ if (q + 1 <= filtbuf_end)
+ strcat(filtbuf, ")");
+ }
+ else {
+ if (q + 2 <= filtbuf_end)
+ strcat(filtbuf, "))");
+ }
+
+}
+
+static void *create_authnz_ldap_dir_config(apr_pool_t *p, char *d)
+{
+ authn_ldap_config_t *sec =
+ (authn_ldap_config_t *)apr_pcalloc(p, sizeof(authn_ldap_config_t));
+
+ sec->pool = p;
+#if APR_HAS_THREADS
+ apr_thread_mutex_create(&sec->lock, APR_THREAD_MUTEX_DEFAULT, p);
+#endif
+/*
+ sec->authz_enabled = 1;
+*/
+ sec->groupattr = apr_array_make(p, GROUPATTR_MAX_ELTS,
+ sizeof(struct mod_auth_ldap_groupattr_entry_t));
+ sec->subgroupclasses = apr_array_make(p, GROUPATTR_MAX_ELTS,
+ sizeof(struct mod_auth_ldap_groupattr_entry_t));
+
+ sec->have_ldap_url = 0;
+ sec->url = "";
+ sec->host = NULL;
+ sec->binddn = NULL;
+ sec->bindpw = NULL;
+ sec->bind_authoritative = 1;
+ sec->deref = always;
+ sec->group_attrib_is_dn = 1;
+ sec->secure = -1; /*Initialize to unset*/
+ sec->maxNestingDepth = 10;
+ sec->sgAttributes = apr_pcalloc(p, sizeof (char *) * GROUPATTR_MAX_ELTS + 1);
+
+ sec->user_is_dn = 0;
+ sec->remote_user_attribute = NULL;
+ sec->compare_dn_on_server = 0;
+
+ sec->authz_prefix = AUTHZ_PREFIX;
+
+ return sec;
+}
+
+static apr_status_t authnz_ldap_cleanup_connection_close(void *param)
+{
+ util_ldap_connection_t *ldc = param;
+ util_ldap_connection_close(ldc);
+ return APR_SUCCESS;
+}
+
+static int set_request_vars(request_rec *r, enum auth_ldap_phase phase) {
+ char *prefix = NULL;
+ int prefix_len;
+ int remote_user_attribute_set = 0;
+ authn_ldap_request_t *req =
+ (authn_ldap_request_t *)ap_get_module_config(r->request_config, &authnz_ldap_module);
+ authn_ldap_config_t *sec =
+ (authn_ldap_config_t *)ap_get_module_config(r->per_dir_config, &authnz_ldap_module);
+ const char **vals = req->vals;
+
+ prefix = (phase == LDAP_AUTHN) ? AUTHN_PREFIX : sec->authz_prefix;
+ prefix_len = strlen(prefix);
+
+ if (sec->attributes && vals) {
+ apr_table_t *e = r->subprocess_env;
+ int i = 0;
+ while (sec->attributes[i]) {
+ char *str = apr_pstrcat(r->pool, prefix, sec->attributes[i], NULL);
+ int j = prefix_len;
+ while (str[j]) {
+ str[j] = apr_toupper(str[j]);
+ j++;
+ }
+ apr_table_setn(e, str, vals[i] ? vals[i] : "");
+
+ /* handle remote_user_attribute, if set */
+ if ((phase == LDAP_AUTHN) &&
+ sec->remote_user_attribute &&
+ !strcmp(sec->remote_user_attribute, sec->attributes[i])) {
+ r->user = (char *)apr_pstrdup(r->pool, vals[i]);
+ remote_user_attribute_set = 1;
+ }
+ i++;
+ }
+ }
+ return remote_user_attribute_set;
+}
+
+static const char *ldap_determine_binddn(request_rec *r, const char *user) {
+ authn_ldap_config_t *sec =
+ (authn_ldap_config_t *)ap_get_module_config(r->per_dir_config, &authnz_ldap_module);
+ const char *result = user;
+ ap_regmatch_t regm[AP_MAX_REG_MATCH];
+
+ if (NULL == user || NULL == sec || !sec->bind_regex || !sec->bind_subst) {
+ return result;
+ }
+
+ if (!ap_regexec(sec->bind_regex, user, AP_MAX_REG_MATCH, regm, 0)) {
+ char *substituted = ap_pregsub(r->pool, sec->bind_subst, user, AP_MAX_REG_MATCH, regm);
+ if (NULL != substituted) {
+ result = substituted;
+ }
+ }
+
+ apr_table_set(r->subprocess_env, "LDAP_BINDASUSER", result);
+
+ return result;
+}
+
+
+/* Some LDAP servers restrict who can search or compare, and the hard-coded ID
+ * might be good for the DN lookup but not for later operations.
+ */
+static util_ldap_connection_t *get_connection_for_authz(request_rec *r, enum auth_ldap_optype type) {
+ authn_ldap_request_t *req =
+ (authn_ldap_request_t *)ap_get_module_config(r->request_config, &authnz_ldap_module);
+ authn_ldap_config_t *sec =
+ (authn_ldap_config_t *)ap_get_module_config(r->per_dir_config, &authnz_ldap_module);
+
+ char *binddn = sec->binddn;
+ char *bindpw = sec->bindpw;
+
+ /* If the per-request config isn't set, we didn't authenticate this user, and leave the default credentials */
+ if (req && req->password &&
+ ((type == LDAP_SEARCH && sec->search_as_user) ||
+ (type == LDAP_COMPARE && sec->compare_as_user) ||
+ (type == LDAP_COMPARE_AND_SEARCH && sec->compare_as_user && sec->search_as_user))){
+ binddn = req->dn;
+ bindpw = req->password;
+ }
+
+ return util_ldap_connection_find(r, sec->host, sec->port,
+ binddn, bindpw,
+ sec->deref, sec->secure);
+}
+/*
+ * Authentication Phase
+ * --------------------
+ *
+ * This phase authenticates the credentials the user has sent with
+ * the request (ie the username and password are checked). This is done
+ * by making an attempt to bind to the LDAP server using this user's
+ * DN and the supplied password.
+ *
+ */
+static authn_status authn_ldap_check_password(request_rec *r, const char *user,
+ const char *password)
+{
+ char filtbuf[FILTER_LENGTH];
+ authn_ldap_config_t *sec =
+ (authn_ldap_config_t *)ap_get_module_config(r->per_dir_config, &authnz_ldap_module);
+
+ util_ldap_connection_t *ldc = NULL;
+ int result = 0;
+ int remote_user_attribute_set = 0;
+ const char *dn = NULL;
+ const char *utfpassword;
+
+ authn_ldap_request_t *req =
+ (authn_ldap_request_t *)apr_pcalloc(r->pool, sizeof(authn_ldap_request_t));
+ ap_set_module_config(r->request_config, &authnz_ldap_module, req);
+
+/*
+ if (!sec->enabled) {
+ return AUTH_USER_NOT_FOUND;
+ }
+*/
+
+ /*
+ * Basic sanity checks before any LDAP operations even happen.
+ */
+ if (!sec->have_ldap_url) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02558)
+ "no AuthLDAPURL");
+
+ return AUTH_GENERAL_ERROR;
+ }
+
+ /* Get the password that the client sent */
+ if (password == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01692)
+ "auth_ldap authenticate: no password specified");
+ return AUTH_GENERAL_ERROR;
+ }
+
+ if (user == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01693)
+ "auth_ldap authenticate: no user specified");
+ return AUTH_GENERAL_ERROR;
+ }
+
+ /*
+ * A bind to the server with an empty password always succeeds, so
+ * we check to ensure that the password is not empty. This implies
+ * that users who actually do have empty passwords will never be
+ * able to authenticate with this module. I don't see this as a big
+ * problem.
+ */
+ if (!(*password)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(10263)
+ "auth_ldap authenticate: empty password specified");
+ return AUTH_DENIED;
+ }
+
+ /* There is a good AuthLDAPURL, right? */
+ if (sec->host) {
+ const char *binddn = sec->binddn;
+ const char *bindpw = sec->bindpw;
+ if (sec->initial_bind_as_user) {
+ bindpw = password;
+ binddn = ldap_determine_binddn(r, user);
+ }
+
+ ldc = util_ldap_connection_find(r, sec->host, sec->port,
+ binddn, bindpw,
+ sec->deref, sec->secure);
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01690)
+ "auth_ldap authenticate: no sec->host - weird...?");
+ return AUTH_GENERAL_ERROR;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01691)
+ "auth_ldap authenticate: using URL %s", sec->url);
+
+ /* build the username filter */
+ authn_ldap_build_filter(filtbuf, r, user, NULL, sec);
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "auth_ldap authenticate: final authn filter is %s", filtbuf);
+
+ /* convert password to utf-8 */
+ utfpassword = authn_ldap_xlate_password(r, password);
+
+ /* do the user search */
+ result = util_ldap_cache_checkuserid(r, ldc, sec->url, sec->basedn, sec->scope,
+ sec->attributes, filtbuf, utfpassword,
+ &dn, &(req->vals));
+ util_ldap_connection_close(ldc);
+
+ /* handle bind failure */
+ if (result != LDAP_SUCCESS) {
+ if (!sec->bind_authoritative) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01694)
+ "auth_ldap authenticate: user %s authentication failed; "
+ "URI %s [%s][%s] (not authoritative)",
+ user, r->uri, ldc->reason, ldap_err2string(result));
+ return AUTH_USER_NOT_FOUND;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01695)
+ "auth_ldap authenticate: "
+ "user %s authentication failed; URI %s [%s][%s]",
+ user, r->uri, ldc->reason, ldap_err2string(result));
+
+ /* talking to a primitive LDAP server (like RACF-over-LDAP) that doesn't return specific errors */
+ if (!strcasecmp(sec->filter, "none") && LDAP_OTHER == result) {
+ return AUTH_USER_NOT_FOUND;
+ }
+
+ return (LDAP_NO_SUCH_OBJECT == result) ? AUTH_USER_NOT_FOUND
+#ifdef LDAP_SECURITY_ERROR
+ : (LDAP_SECURITY_ERROR(result)) ? AUTH_DENIED
+#else
+ : (LDAP_INAPPROPRIATE_AUTH == result) ? AUTH_DENIED
+ : (LDAP_INVALID_CREDENTIALS == result) ? AUTH_DENIED
+#ifdef LDAP_INSUFFICIENT_ACCESS
+ : (LDAP_INSUFFICIENT_ACCESS == result) ? AUTH_DENIED
+#endif
+#ifdef LDAP_INSUFFICIENT_RIGHTS
+ : (LDAP_INSUFFICIENT_RIGHTS == result) ? AUTH_DENIED
+#endif
+#endif
+#ifdef LDAP_CONSTRAINT_VIOLATION
+ /* At least Sun Directory Server sends this if a user is
+ * locked. This is not covered by LDAP_SECURITY_ERROR.
+ */
+ : (LDAP_CONSTRAINT_VIOLATION == result) ? AUTH_DENIED
+#endif
+ : AUTH_GENERAL_ERROR;
+ }
+
+ /* mark the user and DN */
+ req->dn = apr_pstrdup(r->pool, dn);
+ req->user = apr_pstrdup(r->pool, user);
+ req->password = apr_pstrdup(r->pool, password);
+ if (sec->user_is_dn) {
+ r->user = req->dn;
+ }
+
+ /* add environment variables */
+ remote_user_attribute_set = set_request_vars(r, LDAP_AUTHN);
+
+ /* sanity check */
+ if (sec->remote_user_attribute && !remote_user_attribute_set) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01696)
+ "auth_ldap authenticate: "
+ "REMOTE_USER was to be set with attribute '%s', "
+ "but this attribute was not requested for in the "
+ "LDAP query for the user. REMOTE_USER will fall "
+ "back to username or DN as appropriate.",
+ sec->remote_user_attribute);
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01697)
+ "auth_ldap authenticate: accepting %s", user);
+
+ return AUTH_GRANTED;
+}
+
+static authz_status ldapuser_check_authorization(request_rec *r,
+ const char *require_args,
+ const void *parsed_require_args)
+{
+ int result = 0;
+ authn_ldap_request_t *req =
+ (authn_ldap_request_t *)ap_get_module_config(r->request_config, &authnz_ldap_module);
+ authn_ldap_config_t *sec =
+ (authn_ldap_config_t *)ap_get_module_config(r->per_dir_config, &authnz_ldap_module);
+
+ util_ldap_connection_t *ldc = NULL;
+
+ const char *err = NULL;
+ const ap_expr_info_t *expr = parsed_require_args;
+ const char *require;
+
+ const char *t;
+ char *w;
+
+ char filtbuf[FILTER_LENGTH];
+ const char *dn = NULL;
+
+ if (!r->user) {
+ return AUTHZ_DENIED_NO_USER;
+ }
+
+ if (!sec->have_ldap_url) {
+ return AUTHZ_DENIED;
+ }
+
+ if (sec->host) {
+ ldc = get_connection_for_authz(r, LDAP_COMPARE);
+ apr_pool_cleanup_register(r->pool, ldc,
+ authnz_ldap_cleanup_connection_close,
+ apr_pool_cleanup_null);
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01698)
+ "auth_ldap authorize: no sec->host - weird...?");
+ return AUTHZ_DENIED;
+ }
+
+ /*
+ * If we have been authenticated by some other module than mod_authnz_ldap,
+ * the req structure needed for authorization needs to be created
+ * and populated with the userid and DN of the account in LDAP
+ */
+
+
+ if (!strlen(r->user)) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01699)
+ "ldap authorize: Userid is blank, AuthType=%s",
+ r->ap_auth_type);
+ }
+
+ if(!req) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01700)
+ "ldap authorize: Creating LDAP req structure");
+
+ req = (authn_ldap_request_t *)apr_pcalloc(r->pool,
+ sizeof(authn_ldap_request_t));
+
+ /* Build the username filter */
+ authn_ldap_build_filter(filtbuf, r, r->user, NULL, sec);
+
+ /* Search for the user DN */
+ result = util_ldap_cache_getuserdn(r, ldc, sec->url, sec->basedn,
+ sec->scope, sec->attributes, filtbuf, &dn, &(req->vals));
+
+ /* Search failed, log error and return failure */
+ if(result != LDAP_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01701)
+ "auth_ldap authorise: User DN not found, %s", ldc->reason);
+ return AUTHZ_DENIED;
+ }
+
+ ap_set_module_config(r->request_config, &authnz_ldap_module, req);
+ req->dn = apr_pstrdup(r->pool, dn);
+ req->user = r->user;
+
+ }
+
+ if (req->dn == NULL || strlen(req->dn) == 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01702)
+ "auth_ldap authorize: require user: user's DN has not "
+ "been defined; failing authorization");
+ return AUTHZ_DENIED;
+ }
+
+ require = ap_expr_str_exec(r, expr, &err);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02585)
+ "auth_ldap authorize: require user: Can't evaluate expression: %s",
+ err);
+ return AUTHZ_DENIED;
+ }
+
+ /*
+ * First do a whole-line compare, in case it's something like
+ * require user Babs Jensen
+ */
+ result = util_ldap_cache_compare(r, ldc, sec->url, req->dn, sec->attribute, require);
+ switch(result) {
+ case LDAP_COMPARE_TRUE: {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01703)
+ "auth_ldap authorize: require user: authorization "
+ "successful");
+ set_request_vars(r, LDAP_AUTHZ);
+ return AUTHZ_GRANTED;
+ }
+ default: {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01704)
+ "auth_ldap authorize: require user: "
+ "authorization failed [%s][%s]",
+ ldc->reason, ldap_err2string(result));
+ }
+ }
+
+ /*
+ * Now break apart the line and compare each word on it
+ */
+ t = require;
+ while ((w = ap_getword_conf(r->pool, &t)) && w[0]) {
+ result = util_ldap_cache_compare(r, ldc, sec->url, req->dn, sec->attribute, w);
+ switch(result) {
+ case LDAP_COMPARE_TRUE: {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01705)
+ "auth_ldap authorize: "
+ "require user: authorization successful");
+ set_request_vars(r, LDAP_AUTHZ);
+ return AUTHZ_GRANTED;
+ }
+ default: {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01706)
+ "auth_ldap authorize: "
+ "require user: authorization failed [%s][%s]",
+ ldc->reason, ldap_err2string(result));
+ }
+ }
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01707)
+ "auth_ldap authorize user: authorization denied for "
+ "user %s to %s",
+ r->user, r->uri);
+
+ return AUTHZ_DENIED;
+}
+
+static authz_status ldapgroup_check_authorization(request_rec *r,
+ const char *require_args,
+ const void *parsed_require_args)
+{
+ int result = 0;
+ authn_ldap_request_t *req =
+ (authn_ldap_request_t *)ap_get_module_config(r->request_config, &authnz_ldap_module);
+ authn_ldap_config_t *sec =
+ (authn_ldap_config_t *)ap_get_module_config(r->per_dir_config, &authnz_ldap_module);
+
+ util_ldap_connection_t *ldc = NULL;
+
+ const char *err = NULL;
+ const ap_expr_info_t *expr = parsed_require_args;
+ const char *require;
+
+ const char *t;
+
+ char filtbuf[FILTER_LENGTH];
+ const char *dn = NULL;
+ struct mod_auth_ldap_groupattr_entry_t *ent;
+ int i;
+
+ if (!r->user) {
+ return AUTHZ_DENIED_NO_USER;
+ }
+
+ if (!sec->have_ldap_url) {
+ return AUTHZ_DENIED;
+ }
+
+ if (sec->host) {
+ ldc = get_connection_for_authz(r, LDAP_COMPARE); /* for the top-level group only */
+ apr_pool_cleanup_register(r->pool, ldc,
+ authnz_ldap_cleanup_connection_close,
+ apr_pool_cleanup_null);
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01708)
+ "auth_ldap authorize: no sec->host - weird...?");
+ return AUTHZ_DENIED;
+ }
+
+ /*
+ * If there are no elements in the group attribute array, the default should be
+ * member and uniquemember; populate the array now.
+ */
+ if (sec->groupattr->nelts == 0) {
+ struct mod_auth_ldap_groupattr_entry_t *grp;
+#if APR_HAS_THREADS
+ apr_thread_mutex_lock(sec->lock);
+#endif
+ grp = apr_array_push(sec->groupattr);
+ grp->name = "member";
+ grp = apr_array_push(sec->groupattr);
+ grp->name = "uniqueMember";
+#if APR_HAS_THREADS
+ apr_thread_mutex_unlock(sec->lock);
+#endif
+ }
+
+ /*
+ * If there are no elements in the sub group classes array, the default
+ * should be groupOfNames and groupOfUniqueNames; populate the array now.
+ */
+ if (sec->subgroupclasses->nelts == 0) {
+ struct mod_auth_ldap_groupattr_entry_t *grp;
+#if APR_HAS_THREADS
+ apr_thread_mutex_lock(sec->lock);
+#endif
+ grp = apr_array_push(sec->subgroupclasses);
+ grp->name = "groupOfNames";
+ grp = apr_array_push(sec->subgroupclasses);
+ grp->name = "groupOfUniqueNames";
+#if APR_HAS_THREADS
+ apr_thread_mutex_unlock(sec->lock);
+#endif
+ }
+
+ /*
+ * If we have been authenticated by some other module than mod_auth_ldap,
+ * the req structure needed for authorization needs to be created
+ * and populated with the userid and DN of the account in LDAP
+ */
+
+ if (!strlen(r->user)) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01709)
+ "ldap authorize: Userid is blank, AuthType=%s",
+ r->ap_auth_type);
+ }
+
+ if(!req) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01710)
+ "ldap authorize: Creating LDAP req structure");
+
+ req = (authn_ldap_request_t *)apr_pcalloc(r->pool,
+ sizeof(authn_ldap_request_t));
+ /* Build the username filter */
+ authn_ldap_build_filter(filtbuf, r, r->user, NULL, sec);
+
+ /* Search for the user DN */
+ result = util_ldap_cache_getuserdn(r, ldc, sec->url, sec->basedn,
+ sec->scope, sec->attributes, filtbuf, &dn, &(req->vals));
+
+ /* Search failed, log error and return failure */
+ if(result != LDAP_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01711)
+ "auth_ldap authorise: User DN not found, %s", ldc->reason);
+ return AUTHZ_DENIED;
+ }
+
+ ap_set_module_config(r->request_config, &authnz_ldap_module, req);
+ req->dn = apr_pstrdup(r->pool, dn);
+ req->user = r->user;
+ }
+
+ ent = (struct mod_auth_ldap_groupattr_entry_t *) sec->groupattr->elts;
+
+ if (sec->group_attrib_is_dn) {
+ if (req->dn == NULL || strlen(req->dn) == 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01712)
+ "auth_ldap authorize: require group: user's DN has "
+ "not been defined; failing authorization for user %s",
+ r->user);
+ return AUTHZ_DENIED;
+ }
+ }
+ else {
+ if (req->user == NULL || strlen(req->user) == 0) {
+ /* We weren't called in the authentication phase, so we didn't have a
+ * chance to set the user field. Do so now. */
+ req->user = r->user;
+ }
+ }
+
+ require = ap_expr_str_exec(r, expr, &err);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02586)
+ "auth_ldap authorize: require group: Can't evaluate expression: %s",
+ err);
+ return AUTHZ_DENIED;
+ }
+
+ t = require;
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01713)
+ "auth_ldap authorize: require group: testing for group "
+ "membership in \"%s\"",
+ t);
+
+ /* PR52464 exhaust attrs in base group before checking subgroups */
+ for (i = 0; i < sec->groupattr->nelts; i++) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01714)
+ "auth_ldap authorize: require group: testing for %s: "
+ "%s (%s)",
+ ent[i].name,
+ sec->group_attrib_is_dn ? req->dn : req->user, t);
+
+ result = util_ldap_cache_compare(r, ldc, sec->url, t, ent[i].name,
+ sec->group_attrib_is_dn ? req->dn : req->user);
+ if (result == LDAP_COMPARE_TRUE) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01715)
+ "auth_ldap authorize: require group: "
+ "authorization successful (attribute %s) "
+ "[%s][%d - %s]",
+ ent[i].name, ldc->reason, result,
+ ldap_err2string(result));
+ set_request_vars(r, LDAP_AUTHZ);
+ return AUTHZ_GRANTED;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01719)
+ "auth_ldap authorize: require group \"%s\": "
+ "didn't match with attr %s [%s][%d - %s]",
+ t, ent[i].name, ldc->reason, result,
+ ldap_err2string(result));
+ }
+ }
+
+ for (i = 0; i < sec->groupattr->nelts; i++) {
+ /* nested groups need searches and compares, so grab a new handle */
+ authnz_ldap_cleanup_connection_close(ldc);
+ apr_pool_cleanup_kill(r->pool, ldc,authnz_ldap_cleanup_connection_close);
+
+ ldc = get_connection_for_authz(r, LDAP_COMPARE_AND_SEARCH);
+ apr_pool_cleanup_register(r->pool, ldc,
+ authnz_ldap_cleanup_connection_close,
+ apr_pool_cleanup_null);
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01716)
+ "auth_ldap authorise: require group \"%s\": "
+ "failed [%s][%d - %s], checking sub-groups",
+ t, ldc->reason, result, ldap_err2string(result));
+
+ result = util_ldap_cache_check_subgroups(r, ldc, sec->url, t, ent[i].name,
+ sec->group_attrib_is_dn ? req->dn : req->user,
+ sec->sgAttributes[0] ? sec->sgAttributes : default_attributes,
+ sec->subgroupclasses,
+ 0, sec->maxNestingDepth);
+ if (result == LDAP_COMPARE_TRUE) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01717)
+ "auth_ldap authorise: require group "
+ "(sub-group): authorisation successful "
+ "(attribute %s) [%s][%d - %s]",
+ ent[i].name, ldc->reason, result,
+ ldap_err2string(result));
+ set_request_vars(r, LDAP_AUTHZ);
+ return AUTHZ_GRANTED;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01718)
+ "auth_ldap authorise: require group "
+ "(sub-group) \"%s\": didn't match with attr %s "
+ "[%s][%d - %s]",
+ t, ldc->reason, ent[i].name, result,
+ ldap_err2string(result));
+ }
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01720)
+ "auth_ldap authorize group: authorization denied for "
+ "user %s to %s",
+ r->user, r->uri);
+
+ return AUTHZ_DENIED;
+}
+
+static authz_status ldapdn_check_authorization(request_rec *r,
+ const char *require_args,
+ const void *parsed_require_args)
+{
+ int result = 0;
+ authn_ldap_request_t *req =
+ (authn_ldap_request_t *)ap_get_module_config(r->request_config, &authnz_ldap_module);
+ authn_ldap_config_t *sec =
+ (authn_ldap_config_t *)ap_get_module_config(r->per_dir_config, &authnz_ldap_module);
+
+ util_ldap_connection_t *ldc = NULL;
+
+ const char *err = NULL;
+ const ap_expr_info_t *expr = parsed_require_args;
+ const char *require;
+
+ const char *t;
+
+ char filtbuf[FILTER_LENGTH];
+ const char *dn = NULL;
+
+ if (!r->user) {
+ return AUTHZ_DENIED_NO_USER;
+ }
+
+ if (!sec->have_ldap_url) {
+ return AUTHZ_DENIED;
+ }
+
+ if (sec->host) {
+ ldc = get_connection_for_authz(r, LDAP_SEARCH); /* _comparedn is a searche */
+ apr_pool_cleanup_register(r->pool, ldc,
+ authnz_ldap_cleanup_connection_close,
+ apr_pool_cleanup_null);
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01721)
+ "auth_ldap authorize: no sec->host - weird...?");
+ return AUTHZ_DENIED;
+ }
+
+ /*
+ * If we have been authenticated by some other module than mod_auth_ldap,
+ * the req structure needed for authorization needs to be created
+ * and populated with the userid and DN of the account in LDAP
+ */
+
+ if (!strlen(r->user)) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01722)
+ "ldap authorize: Userid is blank, AuthType=%s",
+ r->ap_auth_type);
+ }
+
+ if(!req) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01723)
+ "ldap authorize: Creating LDAP req structure");
+
+ req = (authn_ldap_request_t *)apr_pcalloc(r->pool,
+ sizeof(authn_ldap_request_t));
+ /* Build the username filter */
+ authn_ldap_build_filter(filtbuf, r, r->user, NULL, sec);
+
+ /* Search for the user DN */
+ result = util_ldap_cache_getuserdn(r, ldc, sec->url, sec->basedn,
+ sec->scope, sec->attributes, filtbuf, &dn, &(req->vals));
+
+ /* Search failed, log error and return failure */
+ if(result != LDAP_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01724)
+ "auth_ldap authorise: User DN not found with filter %s: %s", filtbuf, ldc->reason);
+ return AUTHZ_DENIED;
+ }
+
+ ap_set_module_config(r->request_config, &authnz_ldap_module, req);
+ req->dn = apr_pstrdup(r->pool, dn);
+ req->user = r->user;
+ }
+
+ require = ap_expr_str_exec(r, expr, &err);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02587)
+ "auth_ldap authorize: require dn: Can't evaluate expression: %s",
+ err);
+ return AUTHZ_DENIED;
+ }
+
+ t = require;
+
+ if (req->dn == NULL || strlen(req->dn) == 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01725)
+ "auth_ldap authorize: require dn: user's DN has not "
+ "been defined; failing authorization");
+ return AUTHZ_DENIED;
+ }
+
+ result = util_ldap_cache_comparedn(r, ldc, sec->url, req->dn, t, sec->compare_dn_on_server);
+ switch(result) {
+ case LDAP_COMPARE_TRUE: {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01726)
+ "auth_ldap authorize: "
+ "require dn: authorization successful");
+ set_request_vars(r, LDAP_AUTHZ);
+ return AUTHZ_GRANTED;
+ }
+ default: {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01727)
+ "auth_ldap authorize: "
+ "require dn \"%s\": LDAP error [%s][%s]",
+ t, ldc->reason, ldap_err2string(result));
+ }
+ }
+
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01728)
+ "auth_ldap authorize dn: authorization denied for "
+ "user %s to %s",
+ r->user, r->uri);
+
+ return AUTHZ_DENIED;
+}
+
+static authz_status ldapattribute_check_authorization(request_rec *r,
+ const char *require_args,
+ const void *parsed_require_args)
+{
+ int result = 0;
+ authn_ldap_request_t *req =
+ (authn_ldap_request_t *)ap_get_module_config(r->request_config, &authnz_ldap_module);
+ authn_ldap_config_t *sec =
+ (authn_ldap_config_t *)ap_get_module_config(r->per_dir_config, &authnz_ldap_module);
+
+ util_ldap_connection_t *ldc = NULL;
+
+ const char *err = NULL;
+ const ap_expr_info_t *expr = parsed_require_args;
+ const char *require;
+
+ const char *t;
+ char *w, *value;
+
+ char filtbuf[FILTER_LENGTH];
+ const char *dn = NULL;
+
+ if (!r->user) {
+ return AUTHZ_DENIED_NO_USER;
+ }
+
+ if (!sec->have_ldap_url) {
+ return AUTHZ_DENIED;
+ }
+
+ if (sec->host) {
+ ldc = get_connection_for_authz(r, LDAP_COMPARE);
+ apr_pool_cleanup_register(r->pool, ldc,
+ authnz_ldap_cleanup_connection_close,
+ apr_pool_cleanup_null);
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01729)
+ "auth_ldap authorize: no sec->host - weird...?");
+ return AUTHZ_DENIED;
+ }
+
+ /*
+ * If we have been authenticated by some other module than mod_auth_ldap,
+ * the req structure needed for authorization needs to be created
+ * and populated with the userid and DN of the account in LDAP
+ */
+
+ if (!strlen(r->user)) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01730)
+ "ldap authorize: Userid is blank, AuthType=%s",
+ r->ap_auth_type);
+ }
+
+ if(!req) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01731)
+ "ldap authorize: Creating LDAP req structure");
+
+ req = (authn_ldap_request_t *)apr_pcalloc(r->pool,
+ sizeof(authn_ldap_request_t));
+ /* Build the username filter */
+ authn_ldap_build_filter(filtbuf, r, r->user, NULL, sec);
+
+ /* Search for the user DN */
+ result = util_ldap_cache_getuserdn(r, ldc, sec->url, sec->basedn,
+ sec->scope, sec->attributes, filtbuf, &dn, &(req->vals));
+
+ /* Search failed, log error and return failure */
+ if(result != LDAP_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01732)
+ "auth_ldap authorise: User DN not found with filter %s: %s", filtbuf, ldc->reason);
+ return AUTHZ_DENIED;
+ }
+
+ ap_set_module_config(r->request_config, &authnz_ldap_module, req);
+ req->dn = apr_pstrdup(r->pool, dn);
+ req->user = r->user;
+ }
+
+ if (req->dn == NULL || strlen(req->dn) == 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01733)
+ "auth_ldap authorize: require ldap-attribute: user's DN "
+ "has not been defined; failing authorization");
+ return AUTHZ_DENIED;
+ }
+
+ require = ap_expr_str_exec(r, expr, &err);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02588)
+ "auth_ldap authorize: require ldap-attribute: Can't "
+ "evaluate expression: %s", err);
+ return AUTHZ_DENIED;
+ }
+
+ t = require;
+
+ while (t[0]) {
+ w = ap_getword(r->pool, &t, '=');
+ value = ap_getword_conf(r->pool, &t);
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01734)
+ "auth_ldap authorize: checking attribute %s has value %s",
+ w, value);
+ result = util_ldap_cache_compare(r, ldc, sec->url, req->dn, w, value);
+ switch(result) {
+ case LDAP_COMPARE_TRUE: {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01735)
+ "auth_ldap authorize: "
+ "require attribute: authorization successful");
+ set_request_vars(r, LDAP_AUTHZ);
+ return AUTHZ_GRANTED;
+ }
+ default: {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01736)
+ "auth_ldap authorize: require attribute: "
+ "authorization failed [%s][%s]",
+ ldc->reason, ldap_err2string(result));
+ }
+ }
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01737)
+ "auth_ldap authorize attribute: authorization denied for "
+ "user %s to %s",
+ r->user, r->uri);
+
+ return AUTHZ_DENIED;
+}
+
+static authz_status ldapfilter_check_authorization(request_rec *r,
+ const char *require_args,
+ const void *parsed_require_args)
+{
+ int result = 0;
+ authn_ldap_request_t *req =
+ (authn_ldap_request_t *)ap_get_module_config(r->request_config, &authnz_ldap_module);
+ authn_ldap_config_t *sec =
+ (authn_ldap_config_t *)ap_get_module_config(r->per_dir_config, &authnz_ldap_module);
+
+ util_ldap_connection_t *ldc = NULL;
+
+ const char *err = NULL;
+ const ap_expr_info_t *expr = parsed_require_args;
+ const char *require;
+
+ const char *t;
+
+ char filtbuf[FILTER_LENGTH];
+ const char *dn = NULL;
+
+ if (!r->user) {
+ return AUTHZ_DENIED_NO_USER;
+ }
+
+ if (!sec->have_ldap_url) {
+ return AUTHZ_DENIED;
+ }
+
+ if (sec->host) {
+ ldc = get_connection_for_authz(r, LDAP_SEARCH);
+ apr_pool_cleanup_register(r->pool, ldc,
+ authnz_ldap_cleanup_connection_close,
+ apr_pool_cleanup_null);
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01738)
+ "auth_ldap authorize: no sec->host - weird...?");
+ return AUTHZ_DENIED;
+ }
+
+ /*
+ * If we have been authenticated by some other module than mod_auth_ldap,
+ * the req structure needed for authorization needs to be created
+ * and populated with the userid and DN of the account in LDAP
+ */
+
+ if (!strlen(r->user)) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01739)
+ "ldap authorize: Userid is blank, AuthType=%s",
+ r->ap_auth_type);
+ }
+
+ if(!req) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01740)
+ "ldap authorize: Creating LDAP req structure");
+
+ req = (authn_ldap_request_t *)apr_pcalloc(r->pool,
+ sizeof(authn_ldap_request_t));
+ /* Build the username filter */
+ authn_ldap_build_filter(filtbuf, r, r->user, NULL, sec);
+
+ /* Search for the user DN */
+ result = util_ldap_cache_getuserdn(r, ldc, sec->url, sec->basedn,
+ sec->scope, sec->attributes, filtbuf, &dn, &(req->vals));
+
+ /* Search failed, log error and return failure */
+ if(result != LDAP_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01741)
+ "auth_ldap authorise: User DN not found with filter %s: %s", filtbuf, ldc->reason);
+ return AUTHZ_DENIED;
+ }
+
+ ap_set_module_config(r->request_config, &authnz_ldap_module, req);
+ req->dn = apr_pstrdup(r->pool, dn);
+ req->user = r->user;
+ }
+
+ if (req->dn == NULL || strlen(req->dn) == 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01742)
+ "auth_ldap authorize: require ldap-filter: user's DN "
+ "has not been defined; failing authorization");
+ return AUTHZ_DENIED;
+ }
+
+ require = ap_expr_str_exec(r, expr, &err);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02589)
+ "auth_ldap authorize: require ldap-filter: Can't "
+ "evaluate require expression: %s", err);
+ return AUTHZ_DENIED;
+ }
+
+ t = require;
+
+ if (t[0]) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01743)
+ "auth_ldap authorize: checking filter %s", t);
+
+ /* Build the username filter */
+ authn_ldap_build_filter(filtbuf, r, req->user, t, sec);
+
+ /* Search for the user DN */
+ result = util_ldap_cache_getuserdn(r, ldc, sec->url, sec->basedn,
+ sec->scope, sec->attributes, filtbuf, &dn, &(req->vals));
+
+ /* Make sure that the filtered search returned the correct user dn */
+ if (result == LDAP_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01744)
+ "auth_ldap authorize: checking dn match %s", dn);
+ if (sec->compare_as_user) {
+ /* ldap-filter is the only authz that requires a search and a compare */
+ apr_pool_cleanup_kill(r->pool, ldc, authnz_ldap_cleanup_connection_close);
+ authnz_ldap_cleanup_connection_close(ldc);
+ ldc = get_connection_for_authz(r, LDAP_COMPARE);
+ }
+ result = util_ldap_cache_comparedn(r, ldc, sec->url, req->dn, dn,
+ sec->compare_dn_on_server);
+ }
+
+ switch(result) {
+ case LDAP_COMPARE_TRUE: {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01745)
+ "auth_ldap authorize: require ldap-filter: "
+ "authorization successful");
+ set_request_vars(r, LDAP_AUTHZ);
+ return AUTHZ_GRANTED;
+ }
+ case LDAP_FILTER_ERROR: {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01746)
+ "auth_ldap authorize: require ldap-filter: "
+ "%s authorization failed [%s][%s]",
+ filtbuf, ldc->reason, ldap_err2string(result));
+ break;
+ }
+ default: {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01747)
+ "auth_ldap authorize: require ldap-filter: "
+ "authorization failed [%s][%s]",
+ ldc->reason, ldap_err2string(result));
+ }
+ }
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01748)
+ "auth_ldap authorize filter: authorization denied for "
+ "user %s to %s",
+ r->user, r->uri);
+
+ return AUTHZ_DENIED;
+}
+
+static const char *ldap_parse_config(cmd_parms *cmd, const char *require_line,
+ const void **parsed_require_line)
+{
+ const char *expr_err = NULL;
+ ap_expr_info_t *expr;
+
+ expr = ap_expr_parse_cmd(cmd, require_line, AP_EXPR_FLAG_STRING_RESULT,
+ &expr_err, NULL);
+
+ if (expr_err)
+ return apr_pstrcat(cmd->temp_pool,
+ "Cannot parse expression in require line: ",
+ expr_err, NULL);
+
+ *parsed_require_line = expr;
+
+ return NULL;
+}
+
+
+/*
+ * Use the ldap url parsing routines to break up the ldap url into
+ * host and port.
+ */
+static const char *mod_auth_ldap_parse_url(cmd_parms *cmd,
+ void *config,
+ const char *url,
+ const char *mode)
+{
+ int rc;
+ apr_ldap_url_desc_t *urld;
+ apr_ldap_err_t *result;
+
+ authn_ldap_config_t *sec = config;
+
+ rc = apr_ldap_url_parse(cmd->pool, url, &(urld), &(result));
+ if (rc != APR_SUCCESS) {
+ return result->reason;
+ }
+ sec->url = apr_pstrdup(cmd->pool, url);
+
+ /* Set all the values, or at least some sane defaults */
+ if (sec->host) {
+ sec->host = apr_pstrcat(cmd->pool, urld->lud_host, " ", sec->host, NULL);
+ }
+ else {
+ sec->host = urld->lud_host? apr_pstrdup(cmd->pool, urld->lud_host) : "localhost";
+ }
+ sec->basedn = urld->lud_dn? apr_pstrdup(cmd->pool, urld->lud_dn) : "";
+ if (urld->lud_attrs && urld->lud_attrs[0]) {
+ int i = 1;
+ while (urld->lud_attrs[i]) {
+ i++;
+ }
+ sec->attributes = apr_pcalloc(cmd->pool, sizeof(char *) * (i+1));
+ i = 0;
+ while (urld->lud_attrs[i]) {
+ sec->attributes[i] = apr_pstrdup(cmd->pool, urld->lud_attrs[i]);
+ i++;
+ }
+ sec->attribute = sec->attributes[0];
+ }
+ else {
+ sec->attribute = "uid";
+ }
+
+ sec->scope = urld->lud_scope == LDAP_SCOPE_ONELEVEL ?
+ LDAP_SCOPE_ONELEVEL : LDAP_SCOPE_SUBTREE;
+
+ if (urld->lud_filter) {
+ if (urld->lud_filter[0] == '(') {
+ /*
+ * Get rid of the surrounding parens; later on when generating the
+ * filter, they'll be put back.
+ */
+ sec->filter = apr_pstrmemdup(cmd->pool, urld->lud_filter+1,
+ strlen(urld->lud_filter)-2);
+ }
+ else {
+ sec->filter = apr_pstrdup(cmd->pool, urld->lud_filter);
+ }
+ }
+ else {
+ sec->filter = "objectclass=*";
+ }
+
+ if (mode) {
+ if (0 == strcasecmp("NONE", mode)) {
+ sec->secure = APR_LDAP_NONE;
+ }
+ else if (0 == strcasecmp("SSL", mode)) {
+ sec->secure = APR_LDAP_SSL;
+ }
+ else if (0 == strcasecmp("TLS", mode) || 0 == strcasecmp("STARTTLS", mode)) {
+ sec->secure = APR_LDAP_STARTTLS;
+ }
+ else {
+ return "Invalid LDAP connection mode setting: must be one of NONE, "
+ "SSL, or TLS/STARTTLS";
+ }
+ }
+
+ /* "ldaps" indicates secure ldap connections desired
+ */
+ if (strncasecmp(url, "ldaps", 5) == 0)
+ {
+ sec->secure = APR_LDAP_SSL;
+ sec->port = urld->lud_port? urld->lud_port : LDAPS_PORT;
+ }
+ else
+ {
+ sec->port = urld->lud_port? urld->lud_port : LDAP_PORT;
+ }
+
+ sec->have_ldap_url = 1;
+
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, cmd->server,
+ "auth_ldap url parse: `%s', Host: %s, Port: %d, DN: %s, "
+ "attrib: %s, scope: %s, filter: %s, connection mode: %s",
+ url,
+ urld->lud_host,
+ urld->lud_port,
+ urld->lud_dn,
+ urld->lud_attrs? urld->lud_attrs[0] : "(null)",
+ (urld->lud_scope == LDAP_SCOPE_SUBTREE? "subtree" :
+ urld->lud_scope == LDAP_SCOPE_BASE? "base" :
+ urld->lud_scope == LDAP_SCOPE_ONELEVEL? "onelevel" : "unknown"),
+ urld->lud_filter,
+ sec->secure == APR_LDAP_SSL ? "using SSL": "not using SSL"
+ );
+
+ return NULL;
+}
+
+static const char *mod_auth_ldap_set_deref(cmd_parms *cmd, void *config, const char *arg)
+{
+ authn_ldap_config_t *sec = config;
+
+ if (strcmp(arg, "never") == 0 || strcasecmp(arg, "off") == 0) {
+ sec->deref = never;
+ }
+ else if (strcmp(arg, "searching") == 0) {
+ sec->deref = searching;
+ }
+ else if (strcmp(arg, "finding") == 0) {
+ sec->deref = finding;
+ }
+ else if (strcmp(arg, "always") == 0 || strcasecmp(arg, "on") == 0) {
+ sec->deref = always;
+ }
+ else {
+ return "Unrecognized value for AuthLDAPDereferenceAliases directive";
+ }
+ return NULL;
+}
+
+static const char *mod_auth_ldap_add_subgroup_attribute(cmd_parms *cmd, void *config, const char *arg)
+{
+ int i = 0;
+
+ authn_ldap_config_t *sec = config;
+
+ for (i = 0; sec->sgAttributes[i]; i++) {
+ ;
+ }
+ if (i == GROUPATTR_MAX_ELTS)
+ return "Too many AuthLDAPSubGroupAttribute values";
+
+ sec->sgAttributes[i] = apr_pstrdup(cmd->pool, arg);
+
+ return NULL;
+}
+
+static const char *mod_auth_ldap_add_subgroup_class(cmd_parms *cmd, void *config, const char *arg)
+{
+ struct mod_auth_ldap_groupattr_entry_t *new;
+
+ authn_ldap_config_t *sec = config;
+
+ if (sec->subgroupclasses->nelts > GROUPATTR_MAX_ELTS)
+ return "Too many AuthLDAPSubGroupClass values";
+
+ new = apr_array_push(sec->subgroupclasses);
+ new->name = apr_pstrdup(cmd->pool, arg);
+
+ return NULL;
+}
+
+static const char *mod_auth_ldap_set_subgroup_maxdepth(cmd_parms *cmd,
+ void *config,
+ const char *max_depth)
+{
+ authn_ldap_config_t *sec = config;
+
+ sec->maxNestingDepth = atol(max_depth);
+
+ return NULL;
+}
+
+static const char *mod_auth_ldap_add_group_attribute(cmd_parms *cmd, void *config, const char *arg)
+{
+ struct mod_auth_ldap_groupattr_entry_t *new;
+
+ authn_ldap_config_t *sec = config;
+
+ if (sec->groupattr->nelts > GROUPATTR_MAX_ELTS)
+ return "Too many AuthLDAPGroupAttribute directives";
+
+ new = apr_array_push(sec->groupattr);
+ new->name = apr_pstrdup(cmd->pool, arg);
+
+ return NULL;
+}
+
+static const char *set_charset_config(cmd_parms *cmd, void *config, const char *arg)
+{
+ ap_set_module_config(cmd->server->module_config, &authnz_ldap_module,
+ (void *)arg);
+ return NULL;
+}
+
+static const char *set_bind_pattern(cmd_parms *cmd, void *_cfg, const char *exp, const char *subst)
+{
+ authn_ldap_config_t *sec = _cfg;
+ ap_regex_t *regexp;
+
+ regexp = ap_pregcomp(cmd->pool, exp, AP_REG_EXTENDED);
+
+ if (!regexp) {
+ return apr_pstrcat(cmd->pool, "AuthLDAPInitialBindPattern: cannot compile regular "
+ "expression '", exp, "'", NULL);
+ }
+
+ sec->bind_regex = regexp;
+ sec->bind_subst = subst;
+
+ return NULL;
+}
+
+static const char *set_bind_password(cmd_parms *cmd, void *_cfg, const char *arg)
+{
+ authn_ldap_config_t *sec = _cfg;
+ int arglen = strlen(arg);
+ char **argv;
+ char *result;
+
+ if ((arglen > 5) && strncmp(arg, "exec:", 5) == 0) {
+ if (apr_tokenize_to_argv(arg+5, &argv, cmd->temp_pool) != APR_SUCCESS) {
+ return apr_pstrcat(cmd->pool,
+ "Unable to parse exec arguments from ",
+ arg+5, NULL);
+ }
+ argv[0] = ap_server_root_relative(cmd->temp_pool, argv[0]);
+
+ if (!argv[0]) {
+ return apr_pstrcat(cmd->pool,
+ "Invalid AuthLDAPBindPassword exec location:",
+ arg+5, NULL);
+ }
+ result = ap_get_exec_line(cmd->pool,
+ (const char*)argv[0], (const char * const *)argv);
+
+ if (!result) {
+ return apr_pstrcat(cmd->pool,
+ "Unable to get bind password from exec of ",
+ arg+5, NULL);
+ }
+ sec->bindpw = result;
+ }
+ else {
+ sec->bindpw = (char *)arg;
+ }
+
+ if (!(*sec->bindpw)) {
+ return "Empty passwords are invalid for AuthLDAPBindPassword";
+ }
+
+ return NULL;
+}
+
+static const command_rec authnz_ldap_cmds[] =
+{
+ AP_INIT_TAKE12("AuthLDAPURL", mod_auth_ldap_parse_url, NULL, OR_AUTHCFG,
+ "URL to define LDAP connection. This should be an RFC 2255 compliant\n"
+ "URL of the form ldap://host[:port]/basedn[?attrib[?scope[?filter]]].\n"
+ "<ul>\n"
+ "<li>Host is the name of the LDAP server. Use a space separated list of hosts \n"
+ "to specify redundant servers.\n"
+ "<li>Port is optional, and specifies the port to connect to.\n"
+ "<li>basedn specifies the base DN to start searches from\n"
+ "<li>Attrib specifies what attribute to search for in the directory. If not "
+ "provided, it defaults to <b>uid</b>.\n"
+ "<li>Scope is the scope of the search, and can be either <b>sub</b> or "
+ "<b>one</b>. If not provided, the default is <b>sub</b>.\n"
+ "<li>Filter is a filter to use in the search. If not provided, "
+ "defaults to <b>(objectClass=*)</b>.\n"
+ "</ul>\n"
+ "Searches are performed using the attribute and the filter combined. "
+ "For example, assume that the\n"
+ "LDAP URL is <b>ldap://ldap.airius.com/ou=People, o=Airius?uid?sub?(posixid=*)</b>. "
+ "Searches will\n"
+ "be done using the filter <b>(&((posixid=*))(uid=<i>username</i>))</b>, "
+ "where <i>username</i>\n"
+ "is the user name passed by the HTTP client. The search will be a subtree "
+ "search on the branch <b>ou=People, o=Airius</b>."),
+
+ AP_INIT_TAKE1("AuthLDAPBindDN", ap_set_string_slot,
+ (void *)APR_OFFSETOF(authn_ldap_config_t, binddn), OR_AUTHCFG,
+ "DN to use to bind to LDAP server. If not provided, will do an anonymous bind."),
+
+ AP_INIT_TAKE1("AuthLDAPBindPassword", set_bind_password, NULL, OR_AUTHCFG,
+ "Password to use to bind to LDAP server. If not provided, will do an anonymous bind."),
+
+ AP_INIT_FLAG("AuthLDAPBindAuthoritative", ap_set_flag_slot,
+ (void *)APR_OFFSETOF(authn_ldap_config_t, bind_authoritative), OR_AUTHCFG,
+ "Set to 'on' to return failures when user-specific bind fails - defaults to on."),
+
+ AP_INIT_FLAG("AuthLDAPRemoteUserIsDN", ap_set_flag_slot,
+ (void *)APR_OFFSETOF(authn_ldap_config_t, user_is_dn), OR_AUTHCFG,
+ "Set to 'on' to set the REMOTE_USER environment variable to be the full "
+ "DN of the remote user. By default, this is set to off, meaning that "
+ "the REMOTE_USER variable will contain whatever value the remote user sent."),
+
+ AP_INIT_TAKE1("AuthLDAPRemoteUserAttribute", ap_set_string_slot,
+ (void *)APR_OFFSETOF(authn_ldap_config_t, remote_user_attribute), OR_AUTHCFG,
+ "Override the user supplied username and place the "
+ "contents of this attribute in the REMOTE_USER "
+ "environment variable."),
+
+ AP_INIT_FLAG("AuthLDAPCompareDNOnServer", ap_set_flag_slot,
+ (void *)APR_OFFSETOF(authn_ldap_config_t, compare_dn_on_server), OR_AUTHCFG,
+ "Set to 'on' to force auth_ldap to do DN compares (for the \"require dn\" "
+ "directive) using the server, and set it 'off' to do the compares locally "
+ "(at the expense of possible false matches). See the documentation for "
+ "a complete description of this option."),
+
+ AP_INIT_ITERATE("AuthLDAPSubGroupAttribute", mod_auth_ldap_add_subgroup_attribute, NULL, OR_AUTHCFG,
+ "Attribute labels used to define sub-group (or nested group) membership in groups - "
+ "defaults to member and uniqueMember"),
+
+ AP_INIT_ITERATE("AuthLDAPSubGroupClass", mod_auth_ldap_add_subgroup_class, NULL, OR_AUTHCFG,
+ "LDAP objectClass values used to identify sub-group instances - "
+ "defaults to groupOfNames and groupOfUniqueNames"),
+
+ AP_INIT_TAKE1("AuthLDAPMaxSubGroupDepth", mod_auth_ldap_set_subgroup_maxdepth, NULL, OR_AUTHCFG,
+ "Maximum subgroup nesting depth to be evaluated - defaults to 10 (top-level group = 0)"),
+
+ AP_INIT_ITERATE("AuthLDAPGroupAttribute", mod_auth_ldap_add_group_attribute, NULL, OR_AUTHCFG,
+ "A list of attribute labels used to identify the user members of groups - defaults to "
+ "member and uniquemember"),
+
+ AP_INIT_FLAG("AuthLDAPGroupAttributeIsDN", ap_set_flag_slot,
+ (void *)APR_OFFSETOF(authn_ldap_config_t, group_attrib_is_dn), OR_AUTHCFG,
+ "If set to 'on', auth_ldap uses the DN that is retrieved from the server for "
+ "subsequent group comparisons. If set to 'off', auth_ldap uses the string "
+ "provided by the client directly. Defaults to 'on'."),
+
+ AP_INIT_TAKE1("AuthLDAPDereferenceAliases", mod_auth_ldap_set_deref, NULL, OR_AUTHCFG,
+ "Determines how aliases are handled during a search. Can be one of the "
+ "values \"never\", \"searching\", \"finding\", or \"always\". "
+ "Defaults to always."),
+
+ AP_INIT_TAKE1("AuthLDAPCharsetConfig", set_charset_config, NULL, RSRC_CONF,
+ "Character set conversion configuration file. If omitted, character set "
+ "conversion is disabled."),
+
+ AP_INIT_TAKE1("AuthLDAPAuthorizePrefix", ap_set_string_slot,
+ (void *)APR_OFFSETOF(authn_ldap_config_t, authz_prefix), OR_AUTHCFG,
+ "The prefix to add to environment variables set during "
+ "successful authorization, default '" AUTHZ_PREFIX "'"),
+
+ AP_INIT_FLAG("AuthLDAPInitialBindAsUser", ap_set_flag_slot,
+ (void *)APR_OFFSETOF(authn_ldap_config_t, initial_bind_as_user), OR_AUTHCFG,
+ "Set to 'on' to perform the initial DN lookup with the basic auth credentials "
+ "instead of anonymous or hard-coded credentials"),
+
+ AP_INIT_TAKE2("AuthLDAPInitialBindPattern", set_bind_pattern, NULL, OR_AUTHCFG,
+ "The regex and substitution to determine a username that can bind based on an HTTP basic auth username"),
+
+ AP_INIT_FLAG("AuthLDAPSearchAsUser", ap_set_flag_slot,
+ (void *)APR_OFFSETOF(authn_ldap_config_t, search_as_user), OR_AUTHCFG,
+ "Set to 'on' to perform authorization-based searches with the users credentials, when this module "
+ "has also performed authentication. Does not affect nested groups lookup."),
+ AP_INIT_FLAG("AuthLDAPCompareAsUser", ap_set_flag_slot,
+ (void *)APR_OFFSETOF(authn_ldap_config_t, compare_as_user), OR_AUTHCFG,
+ "Set to 'on' to perform authorization-based compares with the users credentials, when this module "
+ "has also performed authentication. Does not affect nested groups lookups."),
+ {NULL}
+};
+
+static int authnz_ldap_post_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
+{
+ ap_configfile_t *f;
+ char l[MAX_STRING_LEN];
+ const char *charset_confname = ap_get_module_config(s->module_config,
+ &authnz_ldap_module);
+ apr_status_t status;
+
+ /*
+ authn_ldap_config_t *sec = (authn_ldap_config_t *)
+ ap_get_module_config(s->module_config,
+ &authnz_ldap_module);
+
+ if (sec->secure)
+ {
+ if (!util_ldap_ssl_supported(s))
+ {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s, APLOGNO(03159)
+ "LDAP: SSL connections (ldaps://) not supported by utilLDAP");
+ return(!OK);
+ }
+ }
+ */
+
+ /* make sure that mod_ldap (util_ldap) is loaded */
+ if (ap_find_linked_module("util_ldap.c") == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(01749)
+ "Module mod_ldap missing. Mod_ldap (aka. util_ldap) "
+ "must be loaded in order for mod_authnz_ldap to function properly");
+ return HTTP_INTERNAL_SERVER_ERROR;
+
+ }
+
+ if (!charset_confname) {
+ return OK;
+ }
+
+ charset_confname = ap_server_root_relative(p, charset_confname);
+ if (!charset_confname) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, APR_EBADPATH, s, APLOGNO(01750)
+ "Invalid charset conversion config path %s",
+ (const char *)ap_get_module_config(s->module_config,
+ &authnz_ldap_module));
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ if ((status = ap_pcfg_openfile(&f, ptemp, charset_confname))
+ != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, status, s, APLOGNO(01751)
+ "could not open charset conversion config file %s.",
+ charset_confname);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ charset_conversions = apr_hash_make(p);
+
+ while (!(ap_cfg_getline(l, MAX_STRING_LEN, f))) {
+ const char *ll = l;
+ char *lang;
+
+ if (l[0] == '#') {
+ continue;
+ }
+ lang = ap_getword_conf(p, &ll);
+ ap_str_tolower(lang);
+
+ if (ll[0]) {
+ char *charset = ap_getword_conf(p, &ll);
+ apr_hash_set(charset_conversions, lang, APR_HASH_KEY_STRING, charset);
+ }
+ }
+ ap_cfg_closefile(f);
+
+ to_charset = derive_codepage_from_lang (p, "utf-8");
+ if (to_charset == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, status, s, APLOGNO(01752)
+ "could not find the UTF-8 charset in the file %s.",
+ charset_confname);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ return OK;
+}
+
+static const authn_provider authn_ldap_provider =
+{
+ &authn_ldap_check_password,
+ NULL,
+};
+
+static const authz_provider authz_ldapuser_provider =
+{
+ &ldapuser_check_authorization,
+ &ldap_parse_config,
+};
+static const authz_provider authz_ldapgroup_provider =
+{
+ &ldapgroup_check_authorization,
+ &ldap_parse_config,
+};
+
+static const authz_provider authz_ldapdn_provider =
+{
+ &ldapdn_check_authorization,
+ &ldap_parse_config,
+};
+
+static const authz_provider authz_ldapattribute_provider =
+{
+ &ldapattribute_check_authorization,
+ &ldap_parse_config,
+};
+
+static const authz_provider authz_ldapfilter_provider =
+{
+ &ldapfilter_check_authorization,
+ &ldap_parse_config,
+};
+
+static void ImportULDAPOptFn(void)
+{
+ util_ldap_connection_close = APR_RETRIEVE_OPTIONAL_FN(uldap_connection_close);
+ util_ldap_connection_find = APR_RETRIEVE_OPTIONAL_FN(uldap_connection_find);
+ util_ldap_cache_comparedn = APR_RETRIEVE_OPTIONAL_FN(uldap_cache_comparedn);
+ util_ldap_cache_compare = APR_RETRIEVE_OPTIONAL_FN(uldap_cache_compare);
+ util_ldap_cache_checkuserid = APR_RETRIEVE_OPTIONAL_FN(uldap_cache_checkuserid);
+ util_ldap_cache_getuserdn = APR_RETRIEVE_OPTIONAL_FN(uldap_cache_getuserdn);
+ util_ldap_ssl_supported = APR_RETRIEVE_OPTIONAL_FN(uldap_ssl_supported);
+ util_ldap_cache_check_subgroups = APR_RETRIEVE_OPTIONAL_FN(uldap_cache_check_subgroups);
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ /* Register authn provider */
+ ap_register_auth_provider(p, AUTHN_PROVIDER_GROUP, "ldap",
+ AUTHN_PROVIDER_VERSION,
+ &authn_ldap_provider, AP_AUTH_INTERNAL_PER_CONF);
+
+ /* Register authz providers */
+ ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "ldap-user",
+ AUTHZ_PROVIDER_VERSION,
+ &authz_ldapuser_provider,
+ AP_AUTH_INTERNAL_PER_CONF);
+ ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "ldap-group",
+ AUTHZ_PROVIDER_VERSION,
+ &authz_ldapgroup_provider,
+ AP_AUTH_INTERNAL_PER_CONF);
+ ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "ldap-dn",
+ AUTHZ_PROVIDER_VERSION,
+ &authz_ldapdn_provider,
+ AP_AUTH_INTERNAL_PER_CONF);
+ ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "ldap-attribute",
+ AUTHZ_PROVIDER_VERSION,
+ &authz_ldapattribute_provider,
+ AP_AUTH_INTERNAL_PER_CONF);
+ ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "ldap-filter",
+ AUTHZ_PROVIDER_VERSION,
+ &authz_ldapfilter_provider,
+ AP_AUTH_INTERNAL_PER_CONF);
+
+ ap_hook_post_config(authnz_ldap_post_config,NULL,NULL,APR_HOOK_MIDDLE);
+
+ ap_hook_optional_fn_retrieve(ImportULDAPOptFn,NULL,NULL,APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(authnz_ldap) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_authnz_ldap_dir_config, /* dir config creater */
+ NULL, /* dir merger --- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ authnz_ldap_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/aaa/mod_authnz_ldap.dep b/modules/aaa/mod_authnz_ldap.dep
new file mode 100644
index 0000000..906e060
--- /dev/null
+++ b/modules/aaa/mod_authnz_ldap.dep
@@ -0,0 +1,70 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_authnz_ldap.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_authnz_ldap.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_ldap.h"\
+ "..\..\srclib\apr-util\include\apr_anylock.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_ldap.h"\
+ "..\..\srclib\apr-util\include\apr_ldap_init.h"\
+ "..\..\srclib\apr-util\include\apr_ldap_option.h"\
+ "..\..\srclib\apr-util\include\apr_ldap_rebind.h"\
+ "..\..\srclib\apr-util\include\apr_ldap_url.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_rmm.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_thread_rwlock.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_version.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/aaa/mod_authnz_ldap.dsp b/modules/aaa/mod_authnz_ldap.dsp
new file mode 100644
index 0000000..e2ed929
--- /dev/null
+++ b/modules/aaa/mod_authnz_ldap.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_authnz_ldap" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_authnz_ldap - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authnz_ldap.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authnz_ldap.mak" CFG="mod_authnz_ldap - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_authnz_ldap - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_authnz_ldap - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_authnz_ldap - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../ldap" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "LDAP_DECLARE_EXPORT" /Fd"Release\mod_authnz_ldap_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_authnz_ldap.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_authnz_ldap.so" /d LONG_NAME="authnz_ldap_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_authnz_ldap.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authnz_ldap.so
+# ADD LINK32 kernel32.lib wldap32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_authnz_ldap.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authnz_ldap.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_authnz_ldap.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_authnz_ldap - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../ldap" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "LDAP_DECLARE_EXPORT" /Fd"Debug\mod_authnz_ldap_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_authnz_ldap.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_authnz_ldap.so" /d LONG_NAME="authnz_ldap_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_authnz_ldap.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authnz_ldap.so
+# ADD LINK32 kernel32.lib wldap32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_authnz_ldap.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authnz_ldap.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_authnz_ldap.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_authnz_ldap - Win32 Release"
+# Name "mod_authnz_ldap - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_authnz_ldap.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/aaa/mod_authnz_ldap.mak b/modules/aaa/mod_authnz_ldap.mak
new file mode 100644
index 0000000..96cc044
--- /dev/null
+++ b/modules/aaa/mod_authnz_ldap.mak
@@ -0,0 +1,381 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_authnz_ldap.dsp
+!IF "$(CFG)" == ""
+CFG=mod_authnz_ldap - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_authnz_ldap - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_authnz_ldap - Win32 Release" && "$(CFG)" != "mod_authnz_ldap - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authnz_ldap.mak" CFG="mod_authnz_ldap - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_authnz_ldap - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_authnz_ldap - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_authnz_ldap - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_authnz_ldap.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_ldap - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_authnz_ldap.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_ldap - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_authnz_ldap.obj"
+ -@erase "$(INTDIR)\mod_authnz_ldap.res"
+ -@erase "$(INTDIR)\mod_authnz_ldap_src.idb"
+ -@erase "$(INTDIR)\mod_authnz_ldap_src.pdb"
+ -@erase "$(OUTDIR)\mod_authnz_ldap.exp"
+ -@erase "$(OUTDIR)\mod_authnz_ldap.lib"
+ -@erase "$(OUTDIR)\mod_authnz_ldap.pdb"
+ -@erase "$(OUTDIR)\mod_authnz_ldap.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../ldap" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "LDAP_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_authnz_ldap_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_authnz_ldap.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_authnz_ldap.so" /d LONG_NAME="authnz_ldap_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_authnz_ldap.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib wldap32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_authnz_ldap.pdb" /debug /out:"$(OUTDIR)\mod_authnz_ldap.so" /implib:"$(OUTDIR)\mod_authnz_ldap.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_authnz_ldap.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_authnz_ldap.obj" \
+ "$(INTDIR)\mod_authnz_ldap.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib" \
+ "..\ldap\Release\mod_ldap.lib"
+
+"$(OUTDIR)\mod_authnz_ldap.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_authnz_ldap.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_authnz_ldap.so"
+ if exist .\Release\mod_authnz_ldap.so.manifest mt.exe -manifest .\Release\mod_authnz_ldap.so.manifest -outputresource:.\Release\mod_authnz_ldap.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_authnz_ldap - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_authnz_ldap.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_ldap - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_authnz_ldap.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_ldap - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_authnz_ldap.obj"
+ -@erase "$(INTDIR)\mod_authnz_ldap.res"
+ -@erase "$(INTDIR)\mod_authnz_ldap_src.idb"
+ -@erase "$(INTDIR)\mod_authnz_ldap_src.pdb"
+ -@erase "$(OUTDIR)\mod_authnz_ldap.exp"
+ -@erase "$(OUTDIR)\mod_authnz_ldap.lib"
+ -@erase "$(OUTDIR)\mod_authnz_ldap.pdb"
+ -@erase "$(OUTDIR)\mod_authnz_ldap.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../ldap" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "LDAP_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_authnz_ldap_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_authnz_ldap.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_authnz_ldap.so" /d LONG_NAME="authnz_ldap_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_authnz_ldap.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib wldap32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_authnz_ldap.pdb" /debug /out:"$(OUTDIR)\mod_authnz_ldap.so" /implib:"$(OUTDIR)\mod_authnz_ldap.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_authnz_ldap.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_authnz_ldap.obj" \
+ "$(INTDIR)\mod_authnz_ldap.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib" \
+ "..\ldap\Debug\mod_ldap.lib"
+
+"$(OUTDIR)\mod_authnz_ldap.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_authnz_ldap.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_authnz_ldap.so"
+ if exist .\Debug\mod_authnz_ldap.so.manifest mt.exe -manifest .\Debug\mod_authnz_ldap.so.manifest -outputresource:.\Debug\mod_authnz_ldap.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_authnz_ldap.dep")
+!INCLUDE "mod_authnz_ldap.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_authnz_ldap.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_authnz_ldap - Win32 Release" || "$(CFG)" == "mod_authnz_ldap - Win32 Debug"
+
+!IF "$(CFG)" == "mod_authnz_ldap - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authnz_ldap - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authnz_ldap - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authnz_ldap - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authnz_ldap - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authnz_ldap - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authnz_ldap - Win32 Release"
+
+"mod_ldap - Win32 Release" :
+ cd ".\..\ldap"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_ldap.mak" CFG="mod_ldap - Win32 Release"
+ cd "..\aaa"
+
+"mod_ldap - Win32 ReleaseCLEAN" :
+ cd ".\..\ldap"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_ldap.mak" CFG="mod_ldap - Win32 Release" RECURSE=1 CLEAN
+ cd "..\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authnz_ldap - Win32 Debug"
+
+"mod_ldap - Win32 Debug" :
+ cd ".\..\ldap"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_ldap.mak" CFG="mod_ldap - Win32 Debug"
+ cd "..\aaa"
+
+"mod_ldap - Win32 DebugCLEAN" :
+ cd ".\..\ldap"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_ldap.mak" CFG="mod_ldap - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\aaa"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_authnz_ldap - Win32 Release"
+
+
+"$(INTDIR)\mod_authnz_ldap.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_authnz_ldap.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_authnz_ldap.so" /d LONG_NAME="authnz_ldap_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_authnz_ldap - Win32 Debug"
+
+
+"$(INTDIR)\mod_authnz_ldap.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_authnz_ldap.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_authnz_ldap.so" /d LONG_NAME="authnz_ldap_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_authnz_ldap.c
+
+"$(INTDIR)\mod_authnz_ldap.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/aaa/mod_authz_core.c b/modules/aaa/mod_authz_core.c
new file mode 100644
index 0000000..40e5fe1
--- /dev/null
+++ b/modules/aaa/mod_authz_core.c
@@ -0,0 +1,1173 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Security options etc.
+ *
+ * Module derived from code originally written by Rob McCool
+ *
+ */
+
+#include "apr_strings.h"
+#include "apr_network_io.h"
+#include "apr_md5.h"
+
+#define APR_WANT_STRFUNC
+#define APR_WANT_BYTEFUNC
+#include "apr_want.h"
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_request.h"
+#include "http_protocol.h"
+#include "ap_provider.h"
+#include "ap_expr.h"
+
+#include "mod_auth.h"
+
+#if APR_HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+
+#undef AUTHZ_EXTRA_CONFIGS
+
+typedef struct provider_alias_rec {
+ char *provider_name;
+ char *provider_alias;
+ char *provider_args;
+ const void *provider_parsed_args;
+ ap_conf_vector_t *sec_auth;
+ const authz_provider *provider;
+} provider_alias_rec;
+
+typedef enum {
+ AUTHZ_LOGIC_AND,
+ AUTHZ_LOGIC_OR,
+ AUTHZ_LOGIC_OFF,
+ AUTHZ_LOGIC_UNSET
+} authz_logic_op;
+
+typedef struct authz_section_conf authz_section_conf;
+
+struct authz_section_conf {
+ const char *provider_name;
+ const char *provider_args;
+ const void *provider_parsed_args;
+ const authz_provider *provider;
+ apr_int64_t limited;
+ authz_logic_op op;
+ int negate;
+ /** true if this is not a real container but produced by AuthMerging;
+ * only used for logging */
+ int is_merged;
+ authz_section_conf *first;
+ authz_section_conf *next;
+};
+
+typedef struct authz_core_dir_conf authz_core_dir_conf;
+
+struct authz_core_dir_conf {
+ authz_section_conf *section;
+ authz_core_dir_conf *next;
+ authz_logic_op op;
+ signed char authz_forbidden_on_fail;
+};
+
+#define UNSET -1
+
+typedef struct authz_core_srv_conf {
+ apr_hash_t *alias_rec;
+} authz_core_srv_conf;
+
+module AP_MODULE_DECLARE_DATA authz_core_module;
+
+static authz_core_dir_conf *authz_core_first_dir_conf;
+
+static void *create_authz_core_dir_config(apr_pool_t *p, char *dummy)
+{
+ authz_core_dir_conf *conf = apr_pcalloc(p, sizeof(*conf));
+
+ conf->op = AUTHZ_LOGIC_UNSET;
+ conf->authz_forbidden_on_fail = UNSET;
+
+ conf->next = authz_core_first_dir_conf;
+ authz_core_first_dir_conf = conf;
+
+ return (void *)conf;
+}
+
+static void *merge_authz_core_dir_config(apr_pool_t *p,
+ void *basev, void *newv)
+{
+ authz_core_dir_conf *base = (authz_core_dir_conf *)basev;
+ authz_core_dir_conf *new = (authz_core_dir_conf *)newv;
+ authz_core_dir_conf *conf;
+
+ if (new->op == AUTHZ_LOGIC_UNSET && !new->section && base->section ) {
+ /* Only authz_forbidden_on_fail has been set in new. Don't treat
+ * it as a new auth config w.r.t. AuthMerging */
+ conf = apr_pmemdup(p, base, sizeof(*base));
+ }
+ else if (new->op == AUTHZ_LOGIC_OFF || new->op == AUTHZ_LOGIC_UNSET ||
+ !(base->section || new->section)) {
+ conf = apr_pmemdup(p, new, sizeof(*new));
+ }
+ else {
+ authz_section_conf *section;
+
+ if (base->section) {
+ if (new->section) {
+ section = apr_pcalloc(p, sizeof(*section));
+
+ section->limited =
+ base->section->limited | new->section->limited;
+
+ section->op = new->op;
+ section->is_merged = 1;
+
+ section->first = apr_pmemdup(p, base->section,
+ sizeof(*base->section));
+ section->first->next = apr_pmemdup(p, new->section,
+ sizeof(*new->section));
+ } else {
+ section = apr_pmemdup(p, base->section,
+ sizeof(*base->section));
+ }
+ }
+ else {
+ section = apr_pmemdup(p, new->section, sizeof(*new->section));
+ }
+
+ conf = apr_pcalloc(p, sizeof(*conf));
+
+ conf->section = section;
+ conf->op = new->op;
+ }
+
+ if (new->authz_forbidden_on_fail == UNSET)
+ conf->authz_forbidden_on_fail = base->authz_forbidden_on_fail;
+ else
+ conf->authz_forbidden_on_fail = new->authz_forbidden_on_fail;
+
+ return (void*)conf;
+}
+
+/* Only per-server directive we have is GLOBAL_ONLY */
+static void *merge_authz_core_svr_config(apr_pool_t *p,
+ void *basev, void *newv)
+{
+ return basev;
+}
+
+static void *create_authz_core_svr_config(apr_pool_t *p, server_rec *s)
+{
+ authz_core_srv_conf *authcfg;
+
+ authcfg = apr_pcalloc(p, sizeof(*authcfg));
+ authcfg->alias_rec = apr_hash_make(p);
+
+ return (void *)authcfg;
+}
+
+/* This is a fake authz provider that really merges various authz alias
+ * configurations and then invokes them.
+ */
+static authz_status authz_alias_check_authorization(request_rec *r,
+ const char *require_args,
+ const void *parsed_require_args)
+{
+ const char *provider_name;
+
+ /* Look up the provider alias in the alias list.
+ * Get the dir_config and call ap_merge_per_dir_configs()
+ * Call the real provider->check_authorization() function
+ * Return the result of the above function call
+ */
+
+ provider_name = apr_table_get(r->notes, AUTHZ_PROVIDER_NAME_NOTE);
+
+ if (provider_name) {
+ authz_core_srv_conf *authcfg;
+ provider_alias_rec *prvdraliasrec;
+
+ authcfg = ap_get_module_config(r->server->module_config,
+ &authz_core_module);
+
+ prvdraliasrec = apr_hash_get(authcfg->alias_rec, provider_name,
+ APR_HASH_KEY_STRING);
+
+ /* If we found the alias provider in the list, then merge the directory
+ configurations and call the real provider */
+ if (prvdraliasrec) {
+ ap_conf_vector_t *orig_dir_config = r->per_dir_config;
+ authz_status ret;
+
+ r->per_dir_config =
+ ap_merge_per_dir_configs(r->pool, orig_dir_config,
+ prvdraliasrec->sec_auth);
+
+ ret = prvdraliasrec->provider->
+ check_authorization(r, prvdraliasrec->provider_args,
+ prvdraliasrec->provider_parsed_args);
+
+ r->per_dir_config = orig_dir_config;
+
+ return ret;
+ }
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02305)
+ "no alias provider found for '%s' (BUG?)",
+ provider_name ? provider_name : "n/a");
+
+ return AUTHZ_DENIED;
+}
+
+static const authz_provider authz_alias_provider =
+{
+ &authz_alias_check_authorization,
+ NULL,
+};
+
+static const char *authz_require_alias_section(cmd_parms *cmd, void *mconfig,
+ const char *args)
+{
+ const char *endp = ap_strrchr_c(args, '>');
+ char *provider_name;
+ char *provider_alias;
+ char *provider_args, *extra_args;
+ ap_conf_vector_t *new_authz_config;
+ int old_overrides = cmd->override;
+ const char *errmsg;
+
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ if (endp == NULL) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ "> directive missing closing '>'", NULL);
+ }
+
+ args = apr_pstrndup(cmd->temp_pool, args, endp - args);
+
+ if (!args[0]) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ "> directive requires additional arguments", NULL);
+ }
+
+ /* Pull the real provider name and the alias name from the block header */
+ provider_name = ap_getword_conf(cmd->pool, &args);
+ provider_alias = ap_getword_conf(cmd->pool, &args);
+ provider_args = ap_getword_conf(cmd->pool, &args);
+ extra_args = ap_getword_conf(cmd->pool, &args);
+
+ if (!provider_name[0] || !provider_alias[0]) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ "> directive requires additional arguments", NULL);
+ }
+
+ /* We only handle one "Require-Parameters" parameter. If several parameters
+ are needed, they must be enclosed between quotes */
+ if (extra_args && *extra_args) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(10142)
+ "When several arguments (%s %s...) are passed to a %s directive, "
+ "they must be enclosed in quotation marks. Otherwise, only the "
+ "first one is taken into account",
+ provider_args, extra_args, cmd->cmd->name);
+ }
+
+ new_authz_config = ap_create_per_dir_config(cmd->pool);
+
+ /* Walk the subsection configuration to get the per_dir config that we will
+ * merge just before the real provider is called.
+ */
+ cmd->override = OR_AUTHCFG | ACCESS_CONF;
+ errmsg = ap_walk_config(cmd->directive->first_child, cmd,
+ new_authz_config);
+ cmd->override = old_overrides;
+
+ if (!errmsg) {
+ provider_alias_rec *prvdraliasrec;
+ authz_core_srv_conf *authcfg;
+
+ prvdraliasrec = apr_pcalloc(cmd->pool, sizeof(*prvdraliasrec));
+
+ /* Save off the new directory config along with the original
+ * provider name and function pointer data
+ */
+ prvdraliasrec->provider_name = provider_name;
+ prvdraliasrec->provider_alias = provider_alias;
+ prvdraliasrec->provider_args = provider_args;
+ prvdraliasrec->sec_auth = new_authz_config;
+ prvdraliasrec->provider =
+ ap_lookup_provider(AUTHZ_PROVIDER_GROUP, provider_name,
+ AUTHZ_PROVIDER_VERSION);
+
+ /* by the time the config file is used, the provider should be loaded
+ * and registered with us.
+ */
+ if (!prvdraliasrec->provider) {
+ return apr_psprintf(cmd->pool,
+ "Unknown Authz provider: %s",
+ provider_name);
+ }
+ if (prvdraliasrec->provider->parse_require_line) {
+ err = prvdraliasrec->provider->parse_require_line(cmd,
+ provider_args, &prvdraliasrec->provider_parsed_args);
+ if (err)
+ return apr_psprintf(cmd->pool,
+ "Can't parse 'Require %s %s': %s",
+ provider_name, provider_args, err);
+ }
+
+ authcfg = ap_get_module_config(cmd->server->module_config,
+ &authz_core_module);
+
+ apr_hash_set(authcfg->alias_rec, provider_alias,
+ APR_HASH_KEY_STRING, prvdraliasrec);
+
+ /* Register the fake provider so that we get called first */
+ ap_register_auth_provider(cmd->pool, AUTHZ_PROVIDER_GROUP,
+ provider_alias, AUTHZ_PROVIDER_VERSION,
+ &authz_alias_provider,
+ AP_AUTH_INTERNAL_PER_CONF);
+ }
+
+ return errmsg;
+}
+
+static const char* format_authz_result(authz_status result)
+{
+ return ((result == AUTHZ_DENIED)
+ ? "denied"
+ : ((result == AUTHZ_GRANTED)
+ ? "granted"
+ : ((result == AUTHZ_DENIED_NO_USER)
+ ? "denied (no authenticated user yet)"
+ : "neutral")));
+}
+
+static const char* format_authz_command(apr_pool_t *p,
+ authz_section_conf *section)
+{
+ return (section->provider
+ ? apr_pstrcat(p, "Require ", (section->negate ? "not " : ""),
+ section->provider_name, " ",
+ section->provider_args, NULL)
+ : apr_pstrcat(p, section->is_merged ? "AuthMerging " : "<Require",
+ ((section->op == AUTHZ_LOGIC_AND)
+ ? (section->negate ? "NotAll" : "All")
+ : (section->negate ? "None" : "Any")),
+ section->is_merged ? "" : ">", NULL));
+}
+
+static authz_section_conf* create_default_section(apr_pool_t *p)
+{
+ authz_section_conf *section = apr_pcalloc(p, sizeof(*section));
+
+ section->op = AUTHZ_LOGIC_OR;
+
+ return section;
+}
+
+static const char *add_authz_provider(cmd_parms *cmd, void *config,
+ const char *args)
+{
+ authz_core_dir_conf *conf = (authz_core_dir_conf*)config;
+ authz_section_conf *section = apr_pcalloc(cmd->pool, sizeof(*section));
+ authz_section_conf *child;
+
+ section->provider_name = ap_getword_conf(cmd->pool, &args);
+
+ if (!strcasecmp(section->provider_name, "not")) {
+ section->provider_name = ap_getword_conf(cmd->pool, &args);
+ section->negate = 1;
+ }
+
+ section->provider_args = args;
+
+ /* lookup and cache the actual provider now */
+ section->provider = ap_lookup_provider(AUTHZ_PROVIDER_GROUP,
+ section->provider_name,
+ AUTHZ_PROVIDER_VERSION);
+
+ /* by the time the config file is used, the provider should be loaded
+ * and registered with us.
+ */
+ if (!section->provider) {
+ return apr_psprintf(cmd->pool,
+ "Unknown Authz provider: %s",
+ section->provider_name);
+ }
+
+ /* if the provider doesn't provide the appropriate function, reject it */
+ if (!section->provider->check_authorization) {
+ return apr_psprintf(cmd->pool,
+ "The '%s' Authz provider is not supported by any "
+ "of the loaded authorization modules",
+ section->provider_name);
+ }
+
+ section->limited = cmd->limited;
+
+ if (section->provider->parse_require_line) {
+ const char *err;
+ apr_pool_userdata_setn(section->provider_name,
+ AUTHZ_PROVIDER_NAME_NOTE,
+ apr_pool_cleanup_null,
+ cmd->temp_pool);
+ err = section->provider->parse_require_line(cmd, args,
+ &section->provider_parsed_args);
+
+ if (err)
+ return err;
+ }
+
+ if (!conf->section) {
+ conf->section = create_default_section(cmd->pool);
+ }
+
+ if (section->negate && conf->section->op == AUTHZ_LOGIC_OR) {
+ return apr_psprintf(cmd->pool, "negative %s directive has no effect "
+ "in %s directive",
+ cmd->cmd->name,
+ format_authz_command(cmd->pool, conf->section));
+ }
+
+ conf->section->limited |= section->limited;
+
+ child = conf->section->first;
+
+ if (child) {
+ while (child->next) {
+ child = child->next;
+ }
+
+ child->next = section;
+ }
+ else {
+ conf->section->first = section;
+ }
+
+ return NULL;
+}
+
+static const char *add_authz_section(cmd_parms *cmd, void *mconfig,
+ const char *args)
+{
+ authz_core_dir_conf *conf = mconfig;
+ const char *endp = ap_strrchr_c(args, '>');
+ authz_section_conf *old_section = conf->section;
+ authz_section_conf *section;
+ int old_overrides = cmd->override;
+ apr_int64_t old_limited = cmd->limited;
+ const char *errmsg;
+
+ if (endp == NULL) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ "> directive missing closing '>'", NULL);
+ }
+
+ args = apr_pstrndup(cmd->temp_pool, args, endp - args);
+
+ if (args[0]) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ "> directive doesn't take additional arguments",
+ NULL);
+ }
+
+ section = apr_pcalloc(cmd->pool, sizeof(*section));
+
+ if (!strcasecmp(cmd->cmd->name, "<RequireAll")) {
+ section->op = AUTHZ_LOGIC_AND;
+ }
+ else if (!strcasecmp(cmd->cmd->name, "<RequireAny")) {
+ section->op = AUTHZ_LOGIC_OR;
+ }
+ else if (!strcasecmp(cmd->cmd->name, "<RequireNotAll")) {
+ section->op = AUTHZ_LOGIC_AND;
+ section->negate = 1;
+ }
+ else {
+ section->op = AUTHZ_LOGIC_OR;
+ section->negate = 1;
+ }
+
+ conf->section = section;
+
+ /* trigger NOT_IN_LIMIT errors as if this were a <Limit> directive */
+ cmd->limited &= ~(AP_METHOD_BIT << (METHODS - 1));
+
+ cmd->override = OR_AUTHCFG;
+ errmsg = ap_walk_config(cmd->directive->first_child, cmd, cmd->context);
+ cmd->override = old_overrides;
+
+ cmd->limited = old_limited;
+
+ conf->section = old_section;
+
+ if (errmsg) {
+ return errmsg;
+ }
+
+ if (section->first) {
+ authz_section_conf *child;
+
+ if (!old_section) {
+ old_section = conf->section = create_default_section(cmd->pool);
+ }
+
+ if (section->negate && old_section->op == AUTHZ_LOGIC_OR) {
+ return apr_psprintf(cmd->pool, "%s directive has "
+ "no effect in %s directive",
+ format_authz_command(cmd->pool, section),
+ format_authz_command(cmd->pool, old_section));
+ }
+
+ old_section->limited |= section->limited;
+
+ if (!section->negate && section->op == old_section->op) {
+ /* be associative */
+ section = section->first;
+ }
+
+ child = old_section->first;
+
+ if (child) {
+ while (child->next) {
+ child = child->next;
+ }
+
+ child->next = section;
+ }
+ else {
+ old_section->first = section;
+ }
+ }
+ else {
+ return apr_pstrcat(cmd->pool,
+ format_authz_command(cmd->pool, section),
+ " directive contains no authorization directives",
+ NULL);
+ }
+
+ return NULL;
+}
+
+static const char *authz_merge_sections(cmd_parms *cmd, void *mconfig,
+ const char *arg)
+{
+ authz_core_dir_conf *conf = mconfig;
+
+ if (!strcasecmp(arg, "Off")) {
+ conf->op = AUTHZ_LOGIC_OFF;
+ }
+ else if (!strcasecmp(arg, "And")) {
+ conf->op = AUTHZ_LOGIC_AND;
+ }
+ else if (!strcasecmp(arg, "Or")) {
+ conf->op = AUTHZ_LOGIC_OR;
+ }
+ else {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name, " must be one of: "
+ "Off | And | Or", NULL);
+ }
+
+ return NULL;
+}
+
+static int authz_core_check_section(apr_pool_t *p, server_rec *s,
+ authz_section_conf *section, int is_conf)
+{
+ authz_section_conf *prev = NULL;
+ authz_section_conf *child = section->first;
+ int ret = !OK;
+
+ while (child) {
+ if (child->first) {
+ if (authz_core_check_section(p, s, child, 0) != OK) {
+ return !OK;
+ }
+
+ if (child->negate && child->op != section->op) {
+ authz_section_conf *next = child->next;
+
+ /* avoid one level of recursion when De Morgan permits */
+ child = child->first;
+
+ if (prev) {
+ prev->next = child;
+ }
+ else {
+ section->first = child;
+ }
+
+ do {
+ child->negate = !child->negate;
+ } while (child->next && (child = child->next));
+
+ child->next = next;
+ }
+ }
+
+ prev = child;
+ child = child->next;
+ }
+
+ child = section->first;
+
+ while (child) {
+ if (!child->negate) {
+ ret = OK;
+ break;
+ }
+
+ child = child->next;
+ }
+
+ if (ret != OK) {
+ ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, APR_SUCCESS, s, APLOGNO(01624)
+ "%s directive contains only negative authorization directives",
+ is_conf ? "<Directory>, <Location>, or similar"
+ : format_authz_command(p, section));
+ }
+
+ return ret;
+}
+
+static int authz_core_pre_config(apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp)
+{
+ authz_core_first_dir_conf = NULL;
+
+ return OK;
+}
+
+static int authz_core_check_config(apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ authz_core_dir_conf *conf = authz_core_first_dir_conf;
+
+ while (conf) {
+ if (conf->section) {
+ if (authz_core_check_section(p, s, conf->section, 1) != OK) {
+ return !OK;
+ }
+ }
+
+ conf = conf->next;
+ }
+
+ return OK;
+}
+
+static const command_rec authz_cmds[] =
+{
+ AP_INIT_RAW_ARGS("<AuthzProviderAlias", authz_require_alias_section,
+ NULL, RSRC_CONF,
+ "container for grouping an authorization provider's "
+ "directives under a provider alias"),
+ AP_INIT_RAW_ARGS("Require", add_authz_provider, NULL, OR_AUTHCFG,
+ "specifies authorization directives "
+ "which one must pass (or not) for a request to suceeed"),
+ AP_INIT_RAW_ARGS("<RequireAll", add_authz_section, NULL, OR_AUTHCFG,
+ "container for grouping authorization directives "
+ "of which none must fail and at least one must pass "
+ "for a request to succeed"),
+ AP_INIT_RAW_ARGS("<RequireAny", add_authz_section, NULL, OR_AUTHCFG,
+ "container for grouping authorization directives "
+ "of which one must pass "
+ "for a request to succeed"),
+#ifdef AUTHZ_EXTRA_CONFIGS
+ AP_INIT_RAW_ARGS("<RequireNotAll", add_authz_section, NULL, OR_AUTHCFG,
+ "container for grouping authorization directives "
+ "of which some must fail or none must pass "
+ "for a request to succeed"),
+#endif
+ AP_INIT_RAW_ARGS("<RequireNone", add_authz_section, NULL, OR_AUTHCFG,
+ "container for grouping authorization directives "
+ "of which none must pass "
+ "for a request to succeed"),
+ AP_INIT_TAKE1("AuthMerging", authz_merge_sections, NULL, OR_AUTHCFG,
+ "controls how a <Directory>, <Location>, or similar "
+ "directive's authorization directives are combined with "
+ "those of its predecessor"),
+ AP_INIT_FLAG("AuthzSendForbiddenOnFailure", ap_set_flag_slot_char,
+ (void *)APR_OFFSETOF(authz_core_dir_conf, authz_forbidden_on_fail),
+ OR_AUTHCFG,
+ "Controls if an authorization failure should result in a "
+ "'403 FORBIDDEN' response instead of the HTTP-conforming "
+ "'401 UNAUTHORIZED'"),
+ {NULL}
+};
+
+static authz_status apply_authz_sections(request_rec *r,
+ authz_section_conf *section,
+ authz_logic_op parent_op)
+{
+ authz_status auth_result;
+
+ /* check to make sure that the request method requires authorization */
+ if (!(section->limited & (AP_METHOD_BIT << r->method_number))) {
+ auth_result =
+ (parent_op == AUTHZ_LOGIC_AND) ? AUTHZ_GRANTED : AUTHZ_NEUTRAL;
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(01625)
+ "authorization result of %s: %s "
+ "(directive limited to other methods)",
+ format_authz_command(r->pool, section),
+ format_authz_result(auth_result));
+
+ return auth_result;
+ }
+
+ if (section->provider) {
+ apr_table_setn(r->notes, AUTHZ_PROVIDER_NAME_NOTE,
+ section->provider_name);
+
+ auth_result =
+ section->provider->check_authorization(r, section->provider_args,
+ section->provider_parsed_args);
+
+ apr_table_unset(r->notes, AUTHZ_PROVIDER_NAME_NOTE);
+ }
+ else {
+ authz_section_conf *child = section->first;
+
+ auth_result = AUTHZ_NEUTRAL;
+
+ while (child) {
+ authz_status child_result;
+
+ child_result = apply_authz_sections(r, child, section->op);
+
+ if (child_result == AUTHZ_GENERAL_ERROR) {
+ return AUTHZ_GENERAL_ERROR;
+ }
+
+ if (child_result != AUTHZ_NEUTRAL) {
+ /*
+ * Handling of AUTHZ_DENIED/AUTHZ_DENIED_NO_USER: Return
+ * AUTHZ_DENIED_NO_USER if providing a user may change the
+ * result, AUTHZ_DENIED otherwise.
+ */
+ if (section->op == AUTHZ_LOGIC_AND) {
+ if (child_result == AUTHZ_DENIED) {
+ auth_result = child_result;
+ break;
+ }
+ if ((child_result == AUTHZ_DENIED_NO_USER
+ && auth_result != AUTHZ_DENIED)
+ || (auth_result == AUTHZ_NEUTRAL)) {
+ auth_result = child_result;
+ }
+ }
+ else {
+ /* AUTHZ_LOGIC_OR */
+ if (child_result == AUTHZ_GRANTED) {
+ auth_result = child_result;
+ break;
+ }
+ if ((child_result == AUTHZ_DENIED_NO_USER
+ && auth_result == AUTHZ_DENIED)
+ || (auth_result == AUTHZ_NEUTRAL)) {
+ auth_result = child_result;
+ }
+ }
+ }
+
+ child = child->next;
+ }
+ }
+
+ if (section->negate) {
+ if (auth_result == AUTHZ_GRANTED) {
+ auth_result = AUTHZ_DENIED;
+ }
+ else if (auth_result == AUTHZ_DENIED ||
+ auth_result == AUTHZ_DENIED_NO_USER) {
+ /* For negated directives, if the original result was denied
+ * then the new result is neutral since we can not grant
+ * access simply because authorization was not rejected.
+ */
+ auth_result = AUTHZ_NEUTRAL;
+ }
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(01626)
+ "authorization result of %s: %s",
+ format_authz_command(r->pool, section),
+ format_authz_result(auth_result));
+
+ return auth_result;
+}
+
+static int authorize_user_core(request_rec *r, int after_authn)
+{
+ authz_core_dir_conf *conf;
+ authz_status auth_result;
+
+ conf = ap_get_module_config(r->per_dir_config, &authz_core_module);
+
+ if (!conf->section) {
+ if (ap_auth_type(r)) {
+ /* there's an AuthType configured, but no authorization
+ * directives applied to support it
+ */
+
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_SUCCESS, r, APLOGNO(01627)
+ "AuthType configured with no corresponding "
+ "authorization directives");
+
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(01628)
+ "authorization result: granted (no directives)");
+
+ return OK;
+ }
+
+ auth_result = apply_authz_sections(r, conf->section, AUTHZ_LOGIC_AND);
+
+ if (auth_result == AUTHZ_GRANTED) {
+ return OK;
+ }
+ else if (auth_result == AUTHZ_DENIED_NO_USER) {
+ if (after_authn) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_SUCCESS, r, APLOGNO(01629)
+ "authorization failure (no authenticated user): %s",
+ r->uri);
+ /*
+ * If we're returning 401 to an authenticated user, tell them to
+ * try again. If unauthenticated, note_auth_failure has already
+ * been called during auth.
+ */
+ if (r->user)
+ ap_note_auth_failure(r);
+
+ return HTTP_UNAUTHORIZED;
+ }
+ else {
+ /*
+ * We need a user before we can decide what to do.
+ * Get out of the way and proceed with authentication.
+ */
+ return DECLINED;
+ }
+ }
+ else if (auth_result == AUTHZ_DENIED || auth_result == AUTHZ_NEUTRAL) {
+ if (!after_authn || ap_auth_type(r) == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_SUCCESS, r, APLOGNO(01630)
+ "client denied by server configuration: %s%s",
+ r->filename ? "" : "uri ",
+ r->filename ? r->filename : r->uri);
+
+ return HTTP_FORBIDDEN;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_SUCCESS, r, APLOGNO(01631)
+ "user %s: authorization failure for \"%s\": ",
+ r->user, r->uri);
+
+ if (conf->authz_forbidden_on_fail > 0) {
+ return HTTP_FORBIDDEN;
+ }
+ else {
+ /*
+ * If we're returning 401 to an authenticated user, tell them to
+ * try again. If unauthenticated, note_auth_failure has already
+ * been called during auth.
+ */
+ if (r->user)
+ ap_note_auth_failure(r);
+ return HTTP_UNAUTHORIZED;
+ }
+ }
+ }
+ else {
+ /* We'll assume that the module has already said what its
+ * error was in the logs.
+ */
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+}
+
+static int authorize_userless(request_rec *r)
+{
+ return authorize_user_core(r, 0);
+}
+
+static int authorize_user(request_rec *r)
+{
+ return authorize_user_core(r, 1);
+}
+
+static int authz_some_auth_required(request_rec *r)
+{
+ authz_core_dir_conf *conf;
+
+ conf = ap_get_module_config(r->per_dir_config, &authz_core_module);
+
+ if (conf->section
+ && (conf->section->limited & (AP_METHOD_BIT << r->method_number))) {
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * env authz provider
+ */
+
+static authz_status env_check_authorization(request_rec *r,
+ const char *require_line,
+ const void *parsed_require_line)
+{
+ const char *t, *w;
+
+ /* The 'env' provider will allow the configuration to specify a list of
+ env variables to check rather than a single variable. This is different
+ from the previous host based syntax. */
+ t = require_line;
+ while ((w = ap_getword_conf(r->pool, &t)) && w[0]) {
+ if (apr_table_get(r->subprocess_env, w)) {
+ return AUTHZ_GRANTED;
+ }
+ }
+
+ return AUTHZ_DENIED;
+}
+
+static const authz_provider authz_env_provider =
+{
+ &env_check_authorization,
+ NULL,
+};
+
+
+/*
+ * all authz provider
+ */
+
+static authz_status all_check_authorization(request_rec *r,
+ const char *require_line,
+ const void *parsed_require_line)
+{
+ if (parsed_require_line) {
+ return AUTHZ_GRANTED;
+ }
+ return AUTHZ_DENIED;
+}
+
+static const char *all_parse_config(cmd_parms *cmd, const char *require_line,
+ const void **parsed_require_line)
+{
+ /*
+ * If the argument to the 'all' provider is 'granted' then just let
+ * everybody in. This would be equivalent to the previous syntax of
+ * 'allow from all'. If the argument is 'denied' we reject everybody,
+ * which is equivalent to 'deny from all'.
+ */
+ if (strcasecmp(require_line, "granted") == 0) {
+ *parsed_require_line = (void *)1;
+ return NULL;
+ }
+ else if (strcasecmp(require_line, "denied") == 0) {
+ /* *parsed_require_line is already NULL */
+ return NULL;
+ }
+ else {
+ return "Argument for 'Require all' must be 'granted' or 'denied'";
+ }
+}
+
+static const authz_provider authz_all_provider =
+{
+ &all_check_authorization,
+ &all_parse_config,
+};
+
+
+/*
+ * method authz provider
+ */
+
+static authz_status method_check_authorization(request_rec *r,
+ const char *require_line,
+ const void *parsed_require_line)
+{
+ const apr_int64_t *allowed = parsed_require_line;
+ if (*allowed & (AP_METHOD_BIT << r->method_number))
+ return AUTHZ_GRANTED;
+ else
+ return AUTHZ_DENIED;
+}
+
+static const char *method_parse_config(cmd_parms *cmd, const char *require_line,
+ const void **parsed_require_line)
+{
+ const char *w, *t;
+ apr_int64_t *allowed = apr_pcalloc(cmd->pool, sizeof(apr_int64_t));
+
+ t = require_line;
+
+ while ((w = ap_getword_conf(cmd->temp_pool, &t)) && w[0]) {
+ int m = ap_method_number_of(w);
+ if (m == M_INVALID) {
+ return apr_pstrcat(cmd->pool, "Invalid Method '", w, "'", NULL);
+ }
+
+ *allowed |= (AP_METHOD_BIT << m);
+ }
+
+ *parsed_require_line = allowed;
+ return NULL;
+}
+
+static const authz_provider authz_method_provider =
+{
+ &method_check_authorization,
+ &method_parse_config,
+};
+
+/*
+ * expr authz provider
+ */
+
+#define REQUIRE_EXPR_NOTE "Require_expr_info"
+struct require_expr_info {
+ ap_expr_info_t *expr;
+ int want_user;
+};
+
+static int expr_lookup_fn(ap_expr_lookup_parms *parms)
+{
+ if (parms->type == AP_EXPR_FUNC_VAR
+ && strcasecmp(parms->name, "REMOTE_USER") == 0) {
+ struct require_expr_info *info;
+ apr_pool_userdata_get((void**)&info, REQUIRE_EXPR_NOTE, parms->ptemp);
+ AP_DEBUG_ASSERT(info != NULL);
+ info->want_user = 1;
+ }
+ return ap_expr_lookup_default(parms);
+}
+
+static const char *expr_parse_config(cmd_parms *cmd, const char *require_line,
+ const void **parsed_require_line)
+{
+ const char *expr_err = NULL;
+ struct require_expr_info *info = apr_pcalloc(cmd->pool, sizeof(*info));
+
+ /* if the expression happens to be surrounded by quotes, skip them */
+ if (require_line[0] == '"') {
+ apr_size_t len = strlen(require_line);
+
+ if (require_line[len-1] == '"')
+ require_line = apr_pstrndup(cmd->temp_pool,
+ require_line + 1,
+ len - 2);
+ }
+
+ apr_pool_userdata_setn(info, REQUIRE_EXPR_NOTE, apr_pool_cleanup_null,
+ cmd->temp_pool);
+ info->expr = ap_expr_parse_cmd(cmd, require_line, 0, &expr_err,
+ expr_lookup_fn);
+
+ if (expr_err)
+ return apr_pstrcat(cmd->temp_pool,
+ "Cannot parse expression in require line: ",
+ expr_err, NULL);
+
+ *parsed_require_line = info;
+
+ return NULL;
+}
+
+static authz_status expr_check_authorization(request_rec *r,
+ const char *require_line,
+ const void *parsed_require_line)
+{
+ const char *err = NULL;
+ const struct require_expr_info *info = parsed_require_line;
+ int rc = ap_expr_exec(r, info->expr, &err);
+
+ if (rc < 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02320)
+ "Error evaluating expression in 'Require expr': %s",
+ err);
+ return AUTHZ_GENERAL_ERROR;
+ }
+ else if (rc == 0) {
+ if (info->want_user)
+ return AUTHZ_DENIED_NO_USER;
+ else
+ return AUTHZ_DENIED;
+ }
+ else {
+ return AUTHZ_GRANTED;
+ }
+}
+
+static const authz_provider authz_expr_provider =
+{
+ &expr_check_authorization,
+ &expr_parse_config,
+};
+
+
+static void register_hooks(apr_pool_t *p)
+{
+ APR_REGISTER_OPTIONAL_FN(authz_some_auth_required);
+
+ ap_hook_pre_config(authz_core_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_check_config(authz_core_check_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_check_authz(authorize_user, NULL, NULL, APR_HOOK_LAST,
+ AP_AUTH_INTERNAL_PER_CONF);
+ ap_hook_check_access_ex(authorize_userless, NULL, NULL, APR_HOOK_LAST,
+ AP_AUTH_INTERNAL_PER_CONF);
+
+ ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "env",
+ AUTHZ_PROVIDER_VERSION,
+ &authz_env_provider, AP_AUTH_INTERNAL_PER_CONF);
+ ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "all",
+ AUTHZ_PROVIDER_VERSION,
+ &authz_all_provider, AP_AUTH_INTERNAL_PER_CONF);
+ ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "method",
+ AUTHZ_PROVIDER_VERSION,
+ &authz_method_provider, AP_AUTH_INTERNAL_PER_CONF);
+ ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "expr",
+ AUTHZ_PROVIDER_VERSION,
+ &authz_expr_provider, AP_AUTH_INTERNAL_PER_CONF);
+}
+
+AP_DECLARE_MODULE(authz_core) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_authz_core_dir_config, /* dir config creater */
+ merge_authz_core_dir_config, /* dir merger */
+ create_authz_core_svr_config, /* server config */
+ merge_authz_core_svr_config , /* merge server config */
+ authz_cmds,
+ register_hooks /* register hooks */
+};
+
diff --git a/modules/aaa/mod_authz_core.dep b/modules/aaa/mod_authz_core.dep
new file mode 100644
index 0000000..04300cc
--- /dev/null
+++ b/modules/aaa/mod_authz_core.dep
@@ -0,0 +1,60 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_authz_core.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_authz_core.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/aaa/mod_authz_core.dsp b/modules/aaa/mod_authz_core.dsp
new file mode 100644
index 0000000..8e9f124
--- /dev/null
+++ b/modules/aaa/mod_authz_core.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_authz_core" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_authz_core - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authz_core.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authz_core.mak" CFG="mod_authz_core - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_authz_core - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_authz_core - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_authz_core - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_authz_core_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_authz_core.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_authz_core.so" /d LONG_NAME="authz_core_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_authz_core.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_core.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_authz_core.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_core.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_authz_core.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_authz_core - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_authz_core_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_authz_core.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_authz_core.so" /d LONG_NAME="authz_core_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_authz_core.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_core.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_authz_core.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_core.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_authz_core.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_authz_core - Win32 Release"
+# Name "mod_authz_core - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_authz_core.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/aaa/mod_authz_core.mak b/modules/aaa/mod_authz_core.mak
new file mode 100644
index 0000000..7351f67
--- /dev/null
+++ b/modules/aaa/mod_authz_core.mak
@@ -0,0 +1,381 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_authz_core.dsp
+!IF "$(CFG)" == ""
+CFG=mod_authz_core - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_authz_core - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_authz_core - Win32 Release" && "$(CFG)" != "mod_authz_core - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authz_core.mak" CFG="mod_authz_core - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_authz_core - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_authz_core - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_authz_core - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_authz_core.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_auth_basic - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_authz_core.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_auth_basic - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_authz_core.obj"
+ -@erase "$(INTDIR)\mod_authz_core.res"
+ -@erase "$(INTDIR)\mod_authz_core_src.idb"
+ -@erase "$(INTDIR)\mod_authz_core_src.pdb"
+ -@erase "$(OUTDIR)\mod_authz_core.exp"
+ -@erase "$(OUTDIR)\mod_authz_core.lib"
+ -@erase "$(OUTDIR)\mod_authz_core.pdb"
+ -@erase "$(OUTDIR)\mod_authz_core.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_authz_core_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_authz_core.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_authz_core.so" /d LONG_NAME="authz_core_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_authz_core.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_authz_core.pdb" /debug /out:"$(OUTDIR)\mod_authz_core.so" /implib:"$(OUTDIR)\mod_authz_core.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_core.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_authz_core.obj" \
+ "$(INTDIR)\mod_authz_core.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib" \
+ "$(OUTDIR)\mod_auth_basic.lib"
+
+"$(OUTDIR)\mod_authz_core.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_authz_core.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_authz_core.so"
+ if exist .\Release\mod_authz_core.so.manifest mt.exe -manifest .\Release\mod_authz_core.so.manifest -outputresource:.\Release\mod_authz_core.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_authz_core - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_authz_core.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_auth_basic - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_authz_core.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_auth_basic - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_authz_core.obj"
+ -@erase "$(INTDIR)\mod_authz_core.res"
+ -@erase "$(INTDIR)\mod_authz_core_src.idb"
+ -@erase "$(INTDIR)\mod_authz_core_src.pdb"
+ -@erase "$(OUTDIR)\mod_authz_core.exp"
+ -@erase "$(OUTDIR)\mod_authz_core.lib"
+ -@erase "$(OUTDIR)\mod_authz_core.pdb"
+ -@erase "$(OUTDIR)\mod_authz_core.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_authz_core_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_authz_core.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_authz_core.so" /d LONG_NAME="authz_core_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_authz_core.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_authz_core.pdb" /debug /out:"$(OUTDIR)\mod_authz_core.so" /implib:"$(OUTDIR)\mod_authz_core.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_core.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_authz_core.obj" \
+ "$(INTDIR)\mod_authz_core.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib" \
+ "$(OUTDIR)\mod_auth_basic.lib"
+
+"$(OUTDIR)\mod_authz_core.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_authz_core.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_authz_core.so"
+ if exist .\Debug\mod_authz_core.so.manifest mt.exe -manifest .\Debug\mod_authz_core.so.manifest -outputresource:.\Debug\mod_authz_core.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_authz_core.dep")
+!INCLUDE "mod_authz_core.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_authz_core.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_authz_core - Win32 Release" || "$(CFG)" == "mod_authz_core - Win32 Debug"
+
+!IF "$(CFG)" == "mod_authz_core - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authz_core - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authz_core - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authz_core - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authz_core - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authz_core - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authz_core - Win32 Release"
+
+"mod_auth_basic - Win32 Release" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Release"
+ cd "."
+
+"mod_auth_basic - Win32 ReleaseCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Release" RECURSE=1 CLEAN
+ cd "."
+
+!ELSEIF "$(CFG)" == "mod_authz_core - Win32 Debug"
+
+"mod_auth_basic - Win32 Debug" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Debug"
+ cd "."
+
+"mod_auth_basic - Win32 DebugCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Debug" RECURSE=1 CLEAN
+ cd "."
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_authz_core - Win32 Release"
+
+
+"$(INTDIR)\mod_authz_core.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_authz_core.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_authz_core.so" /d LONG_NAME="authz_core_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_authz_core - Win32 Debug"
+
+
+"$(INTDIR)\mod_authz_core.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_authz_core.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_authz_core.so" /d LONG_NAME="authz_core_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_authz_core.c
+
+"$(INTDIR)\mod_authz_core.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/aaa/mod_authz_dbd.c b/modules/aaa/mod_authz_dbd.c
new file mode 100644
index 0000000..5d169e1
--- /dev/null
+++ b/modules/aaa/mod_authz_dbd.c
@@ -0,0 +1,409 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "httpd.h"
+#include "http_log.h"
+#include "http_config.h"
+#include "ap_provider.h"
+#include "http_request.h"
+#include "http_protocol.h"
+#include "http_core.h"
+#include "apr_dbd.h"
+#include "mod_dbd.h"
+#include "apr_strings.h"
+#include "mod_authz_dbd.h"
+
+#include "mod_auth.h"
+
+
+module AP_MODULE_DECLARE_DATA authz_dbd_module;
+
+/* Export a hook for modules that manage clientside sessions
+ * (e.g. mod_auth_cookie)
+ * to deal with those when we successfully login/logout at the server
+ *
+ * XXX: WHY would this be specific to dbd_authz? Why wouldn't we track
+ * this across all authz user providers in a lower level mod, such as
+ * mod_auth_basic/digest?
+ */
+APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(authz_dbd, AUTHZ_DBD, int, client_login,
+ (request_rec *r, int code, const char *action),
+ (r, code, action), OK, DECLINED)
+
+
+typedef struct {
+ const char *query;
+ const char *redir_query;
+ int redirect;
+} authz_dbd_cfg ;
+
+static ap_dbd_t *(*dbd_handle)(request_rec*) = NULL;
+static void (*dbd_prepare)(server_rec*, const char*, const char*) = NULL;
+
+static const char *const noerror = "???";
+
+static void *authz_dbd_cr_cfg(apr_pool_t *pool, char *dummy)
+{
+ authz_dbd_cfg *ret = apr_pcalloc(pool, sizeof(authz_dbd_cfg));
+ ret->redirect = -1;
+ return ret;
+}
+
+static void *authz_dbd_merge_cfg(apr_pool_t *pool, void *BASE, void *ADD)
+{
+ authz_dbd_cfg *base = BASE;
+ authz_dbd_cfg *add = ADD;
+ authz_dbd_cfg *ret = apr_palloc(pool, sizeof(authz_dbd_cfg));
+
+ ret->query = (add->query == NULL) ? base->query : add->query;
+ ret->redir_query = (add->redir_query == NULL)
+ ? base->redir_query : add->redir_query;
+ ret->redirect = (add->redirect == -1) ? base->redirect : add->redirect;
+ return ret;
+}
+
+static const char *authz_dbd_prepare(cmd_parms *cmd, void *cfg,
+ const char *query)
+{
+ static unsigned int label_num = 0;
+ char *label;
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_HTACCESS);
+ if (err)
+ return err;
+
+ if (dbd_prepare == NULL) {
+ dbd_prepare = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_prepare);
+ if (dbd_prepare == NULL) {
+ return "You must load mod_dbd to enable AuthzDBD functions";
+ }
+ dbd_handle = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_acquire);
+ }
+ label = apr_psprintf(cmd->pool, "authz_dbd_%d", ++label_num);
+
+ dbd_prepare(cmd->server, query, label);
+
+ /* save the label here for our own use */
+ return ap_set_string_slot(cmd, cfg, label);
+}
+
+static const command_rec authz_dbd_cmds[] = {
+ AP_INIT_FLAG("AuthzDBDLoginToReferer", ap_set_flag_slot,
+ (void*)APR_OFFSETOF(authz_dbd_cfg, redirect), ACCESS_CONF,
+ "Whether to redirect to referer on successful login"),
+ AP_INIT_TAKE1("AuthzDBDQuery", authz_dbd_prepare,
+ (void*)APR_OFFSETOF(authz_dbd_cfg, query), ACCESS_CONF,
+ "SQL query for DBD Authz or login"),
+ AP_INIT_TAKE1("AuthzDBDRedirectQuery", authz_dbd_prepare,
+ (void*)APR_OFFSETOF(authz_dbd_cfg, redir_query), ACCESS_CONF,
+ "SQL query to get per-user redirect URL after login"),
+ {NULL}
+};
+
+static int authz_dbd_login(request_rec *r, authz_dbd_cfg *cfg,
+ const char *action)
+{
+ int rv;
+ const char *newuri = NULL;
+ int nrows;
+ const char *message;
+ ap_dbd_t *dbd;
+ apr_dbd_prepared_t *query;
+ apr_dbd_results_t *res = NULL;
+ apr_dbd_row_t *row = NULL;
+
+ if (cfg->query == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01642)
+ "No query configured for %s!", action);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ dbd = dbd_handle(r);
+ if (dbd == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02902)
+ "No db handle available for %s! "
+ "Check your database access",
+ action);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ query = apr_hash_get(dbd->prepared, cfg->query, APR_HASH_KEY_STRING);
+ if (query == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01643)
+ "Error retrieving Query for %s!", action);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ rv = apr_dbd_pvquery(dbd->driver, r->pool, dbd->handle, &nrows,
+ query, r->user, NULL);
+ if (rv == 0) {
+ if (nrows != 1) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01644)
+ "authz_dbd: %s of user %s updated %d rows",
+ action, r->user, nrows);
+ }
+ }
+ else {
+ message = apr_dbd_error(dbd->driver, dbd->handle, rv);
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01645)
+ "authz_dbd: query for %s failed; user %s [%s]",
+ action, r->user, message?message:noerror);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (cfg->redirect == 1) {
+ newuri = apr_table_get(r->headers_in, "Referer");
+ }
+
+ if (!newuri && cfg->redir_query) {
+ query = apr_hash_get(dbd->prepared, cfg->redir_query,
+ APR_HASH_KEY_STRING);
+ if (query == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01646)
+ "authz_dbd: no redirect query!");
+ /* OK, this is non-critical; we can just not-redirect */
+ }
+ else if ((rv = apr_dbd_pvselect(dbd->driver, r->pool, dbd->handle,
+ &res, query, 0, r->user, NULL)) == 0) {
+ for (rv = apr_dbd_get_row(dbd->driver, r->pool, res, &row, -1);
+ rv != -1;
+ rv = apr_dbd_get_row(dbd->driver, r->pool, res, &row, -1)) {
+ if (rv != 0) {
+ message = apr_dbd_error(dbd->driver, dbd->handle, rv);
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01647)
+ "authz_dbd in get_row; action=%s user=%s [%s]",
+ action, r->user, message?message:noerror);
+ }
+ else if (newuri == NULL) {
+ newuri =
+ apr_pstrdup(r->pool,
+ apr_dbd_get_entry(dbd->driver, row, 0));
+ }
+ /* we can't break out here or row won't get cleaned up */
+ }
+ }
+ else {
+ message = apr_dbd_error(dbd->driver, dbd->handle, rv);
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01648)
+ "authz_dbd/redirect for %s of %s [%s]",
+ action, r->user, message?message:noerror);
+ }
+ }
+ if (newuri != NULL) {
+ r->status = HTTP_MOVED_TEMPORARILY;
+ apr_table_set(r->err_headers_out, "Location", newuri);
+ }
+ authz_dbd_run_client_login(r, OK, action);
+ return OK;
+}
+
+static int authz_dbd_group_query(request_rec *r, authz_dbd_cfg *cfg,
+ apr_array_header_t *groups)
+{
+ /* SELECT user_group FROM authz WHERE user = %s */
+ int rv;
+ const char *message;
+ ap_dbd_t *dbd;
+ apr_dbd_prepared_t *query;
+ apr_dbd_results_t *res = NULL;
+ apr_dbd_row_t *row = NULL;
+
+ if (cfg->query == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01649)
+ "No query configured for dbd-group!");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ dbd = dbd_handle(r);
+ if (dbd == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02903)
+ "No db handle available for dbd-query! "
+ "Check your database access");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ query = apr_hash_get(dbd->prepared, cfg->query, APR_HASH_KEY_STRING);
+ if (query == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01650)
+ "Error retrieving query for dbd-group!");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ rv = apr_dbd_pvselect(dbd->driver, r->pool, dbd->handle, &res,
+ query, 0, r->user, NULL);
+ if (rv == 0) {
+ for (rv = apr_dbd_get_row(dbd->driver, r->pool, res, &row, -1);
+ rv != -1;
+ rv = apr_dbd_get_row(dbd->driver, r->pool, res, &row, -1)) {
+ if (rv == 0) {
+ APR_ARRAY_PUSH(groups, const char *) =
+ apr_pstrdup(r->pool,
+ apr_dbd_get_entry(dbd->driver, row, 0));
+ }
+ else {
+ message = apr_dbd_error(dbd->driver, dbd->handle, rv);
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01651)
+ "authz_dbd in get_row; user_group query for user=%s [%s]",
+ r->user, message?message:noerror);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+ }
+ else {
+ message = apr_dbd_error(dbd->driver, dbd->handle, rv);
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01652)
+ "authz_dbd, in groups query for %s [%s]",
+ r->user, message?message:noerror);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ return OK;
+}
+
+static authz_status dbdgroup_check_authorization(request_rec *r,
+ const char *require_args,
+ const void *parsed_require_args)
+{
+ int rv;
+ const char *w;
+ apr_array_header_t *groups;
+
+ const char *err = NULL;
+ const ap_expr_info_t *expr = parsed_require_args;
+ const char *require;
+
+ const char *t;
+ authz_dbd_cfg *cfg = ap_get_module_config(r->per_dir_config,
+ &authz_dbd_module);
+
+ if (!r->user) {
+ return AUTHZ_DENIED_NO_USER;
+ }
+
+ groups = apr_array_make(r->pool, 4, sizeof(const char*));
+ rv = authz_dbd_group_query(r, cfg, groups);
+ if (rv != OK) {
+ return AUTHZ_GENERAL_ERROR;
+ }
+
+ require = ap_expr_str_exec(r, expr, &err);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02590)
+ "authz_dbd authorize: require dbd-group: Can't "
+ "evaluate require expression: %s", err);
+ return AUTHZ_DENIED;
+ }
+
+ t = require;
+ while (t[0]) {
+ w = ap_getword_white(r->pool, &t);
+ if (ap_array_str_contains(groups, w)) {
+ return AUTHZ_GRANTED;
+ }
+ }
+
+ return AUTHZ_DENIED;
+}
+
+static authz_status dbdlogin_check_authorization(request_rec *r,
+ const char *require_args,
+ const void *parsed_require_args)
+{
+ authz_dbd_cfg *cfg = ap_get_module_config(r->per_dir_config,
+ &authz_dbd_module);
+
+ if (!r->user) {
+ return AUTHZ_DENIED_NO_USER;
+ }
+
+ return (authz_dbd_login(r, cfg, "login") == OK ? AUTHZ_GRANTED : AUTHZ_DENIED);
+}
+
+static authz_status dbdlogout_check_authorization(request_rec *r,
+ const char *require_args,
+ const void *parsed_require_args)
+{
+ authz_dbd_cfg *cfg = ap_get_module_config(r->per_dir_config,
+ &authz_dbd_module);
+
+ if (!r->user) {
+ return AUTHZ_DENIED_NO_USER;
+ }
+
+ return (authz_dbd_login(r, cfg, "logout") == OK ? AUTHZ_GRANTED : AUTHZ_DENIED);
+}
+
+static const char *dbd_parse_config(cmd_parms *cmd, const char *require_line,
+ const void **parsed_require_line)
+{
+ const char *expr_err = NULL;
+ ap_expr_info_t *expr;
+
+ expr = ap_expr_parse_cmd(cmd, require_line, AP_EXPR_FLAG_STRING_RESULT,
+ &expr_err, NULL);
+
+ if (expr_err) {
+ return apr_pstrcat(cmd->temp_pool,
+ "Cannot parse expression in require line: ",
+ expr_err, NULL);
+ }
+
+ *parsed_require_line = expr;
+
+ return NULL;
+}
+
+static const authz_provider authz_dbdgroup_provider =
+{
+ &dbdgroup_check_authorization,
+ &dbd_parse_config,
+};
+
+static const authz_provider authz_dbdlogin_provider =
+{
+ &dbdlogin_check_authorization,
+ NULL,
+};
+
+static const authz_provider authz_dbdlogout_provider =
+{
+ &dbdlogout_check_authorization,
+ NULL,
+};
+
+static void authz_dbd_hooks(apr_pool_t *p)
+{
+ ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "dbd-group",
+ AUTHZ_PROVIDER_VERSION,
+ &authz_dbdgroup_provider,
+ AP_AUTH_INTERNAL_PER_CONF);
+ ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "dbd-login",
+ AUTHZ_PROVIDER_VERSION,
+ &authz_dbdlogin_provider,
+ AP_AUTH_INTERNAL_PER_CONF);
+ ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "dbd-logout",
+ AUTHZ_PROVIDER_VERSION,
+ &authz_dbdlogout_provider,
+ AP_AUTH_INTERNAL_PER_CONF);
+}
+
+AP_DECLARE_MODULE(authz_dbd) =
+{
+ STANDARD20_MODULE_STUFF,
+ authz_dbd_cr_cfg,
+ authz_dbd_merge_cfg,
+ NULL,
+ NULL,
+ authz_dbd_cmds,
+ authz_dbd_hooks
+};
diff --git a/modules/aaa/mod_authz_dbd.dep b/modules/aaa/mod_authz_dbd.dep
new file mode 100644
index 0000000..6f0138b
--- /dev/null
+++ b/modules/aaa/mod_authz_dbd.dep
@@ -0,0 +1,61 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_authz_dbd.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_authz_dbd.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_dbd.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ "..\database\mod_dbd.h"\
+ ".\mod_authz_dbd.h"\
+
diff --git a/modules/aaa/mod_authz_dbd.dsp b/modules/aaa/mod_authz_dbd.dsp
new file mode 100644
index 0000000..abba141
--- /dev/null
+++ b/modules/aaa/mod_authz_dbd.dsp
@@ -0,0 +1,119 @@
+# Microsoft Developer Studio Project File - Name="mod_authz_dbd" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_authz_dbd - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authz_dbd.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authz_dbd.mak" CFG="mod_authz_dbd - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_authz_dbd - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_authz_dbd - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_authz_dbd - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../database" /D "AUTHZ_DBD_DECLARE_EXPORT" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_authz_dbd_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_authz_dbd.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_authz_dbd.so" /d LONG_NAME="authz_dbd_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_authz_dbd.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_dbd.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_authz_dbd.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_dbd.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_authz_dbd.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_authz_dbd - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../database" /D "AUTHZ_DBD_DECLARE_EXPORT" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_authz_dbd_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_authz_dbd.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_authz_dbd.so" /d LONG_NAME="authz_dbd_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_authz_dbd.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_dbd.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_authz_dbd.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_dbd.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_authz_dbd.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_authz_dbd - Win32 Release"
+# Name "mod_authz_dbd - Win32 Debug"
+# Begin Source File
+
+SOURCE=..\database\mod_dbd.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\mod_authz_dbd.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\mod_authz_dbd.h
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/aaa/mod_authz_dbd.h b/modules/aaa/mod_authz_dbd.h
new file mode 100644
index 0000000..5d55a8e
--- /dev/null
+++ b/modules/aaa/mod_authz_dbd.h
@@ -0,0 +1,44 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MOD_AUTHZ_DBD_H
+#define MOD_AUTHZ_DBD_H
+#include "httpd.h"
+
+/* Create a set of AUTHZ_DBD_DECLARE(type), AUTHZ_DBD_DECLARE_NONSTD(type) and
+ * AUTHZ_DBD_DECLARE_DATA with appropriate export and import tags
+ */
+#if !defined(WIN32)
+#define AUTHZ_DBD_DECLARE(type) type
+#define AUTHZ_DBD_DECLARE_NONSTD(type) type
+#define AUTHZ_DBD_DECLARE_DATA
+#elif defined(AUTHZ_DBD_DECLARE_STATIC)
+#define AUTHZ_DBD_DECLARE(type) type __stdcall
+#define AUTHZ_DBD_DECLARE_NONSTD(type) type
+#define AUTHZ_DBD_DECLARE_DATA
+#elif defined(AUTHZ_DBD_DECLARE_EXPORT)
+#define AUTHZ_DBD_DECLARE(type) __declspec(dllexport) type __stdcall
+#define AUTHZ_DBD_DECLARE_NONSTD(type) __declspec(dllexport) type
+#define AUTHZ_DBD_DECLARE_DATA __declspec(dllexport)
+#else
+#define AUTHZ_DBD_DECLARE(type) __declspec(dllimport) type __stdcall
+#define AUTHZ_DBD_DECLARE_NONSTD(type) __declspec(dllimport) type
+#define AUTHZ_DBD_DECLARE_DATA __declspec(dllimport)
+#endif
+
+APR_DECLARE_EXTERNAL_HOOK(authz_dbd, AUTHZ_DBD, int, client_login,
+ (request_rec *r, int code, const char *action))
+#endif
diff --git a/modules/aaa/mod_authz_dbd.mak b/modules/aaa/mod_authz_dbd.mak
new file mode 100644
index 0000000..da7a453
--- /dev/null
+++ b/modules/aaa/mod_authz_dbd.mak
@@ -0,0 +1,409 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_authz_dbd.dsp
+!IF "$(CFG)" == ""
+CFG=mod_authz_dbd - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_authz_dbd - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_authz_dbd - Win32 Release" && "$(CFG)" != "mod_authz_dbd - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authz_dbd.mak" CFG="mod_authz_dbd - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_authz_dbd - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_authz_dbd - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_authz_dbd - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_authz_dbd.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_dbd - Win32 Release" "mod_auth_basic - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_authz_dbd.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_auth_basic - Win32 ReleaseCLEAN" "mod_dbd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_authz_dbd.obj"
+ -@erase "$(INTDIR)\mod_authz_dbd.res"
+ -@erase "$(INTDIR)\mod_authz_dbd_src.idb"
+ -@erase "$(INTDIR)\mod_authz_dbd_src.pdb"
+ -@erase "$(OUTDIR)\mod_authz_dbd.exp"
+ -@erase "$(OUTDIR)\mod_authz_dbd.lib"
+ -@erase "$(OUTDIR)\mod_authz_dbd.pdb"
+ -@erase "$(OUTDIR)\mod_authz_dbd.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../database" /D "AUTHZ_DBD_DECLARE_EXPORT" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_authz_dbd_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_authz_dbd.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_authz_dbd.so" /d LONG_NAME="authz_dbd_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_authz_dbd.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_authz_dbd.pdb" /debug /out:"$(OUTDIR)\mod_authz_dbd.so" /implib:"$(OUTDIR)\mod_authz_dbd.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_dbd.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_authz_dbd.obj" \
+ "$(INTDIR)\mod_authz_dbd.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib" \
+ "$(OUTDIR)\mod_auth_basic.lib" \
+ "..\database\Release\mod_dbd.lib"
+
+"$(OUTDIR)\mod_authz_dbd.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_authz_dbd.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_authz_dbd.so"
+ if exist .\Release\mod_authz_dbd.so.manifest mt.exe -manifest .\Release\mod_authz_dbd.so.manifest -outputresource:.\Release\mod_authz_dbd.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_authz_dbd - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_authz_dbd.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_dbd - Win32 Debug" "mod_auth_basic - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_authz_dbd.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_auth_basic - Win32 DebugCLEAN" "mod_dbd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_authz_dbd.obj"
+ -@erase "$(INTDIR)\mod_authz_dbd.res"
+ -@erase "$(INTDIR)\mod_authz_dbd_src.idb"
+ -@erase "$(INTDIR)\mod_authz_dbd_src.pdb"
+ -@erase "$(OUTDIR)\mod_authz_dbd.exp"
+ -@erase "$(OUTDIR)\mod_authz_dbd.lib"
+ -@erase "$(OUTDIR)\mod_authz_dbd.pdb"
+ -@erase "$(OUTDIR)\mod_authz_dbd.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../database" /D "AUTHZ_DBD_DECLARE_EXPORT" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_authz_dbd_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_authz_dbd.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_authz_dbd.so" /d LONG_NAME="authz_dbd_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_authz_dbd.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_authz_dbd.pdb" /debug /out:"$(OUTDIR)\mod_authz_dbd.so" /implib:"$(OUTDIR)\mod_authz_dbd.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_dbd.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_authz_dbd.obj" \
+ "$(INTDIR)\mod_authz_dbd.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib" \
+ "$(OUTDIR)\mod_auth_basic.lib" \
+ "..\database\Debug\mod_dbd.lib"
+
+"$(OUTDIR)\mod_authz_dbd.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_authz_dbd.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_authz_dbd.so"
+ if exist .\Debug\mod_authz_dbd.so.manifest mt.exe -manifest .\Debug\mod_authz_dbd.so.manifest -outputresource:.\Debug\mod_authz_dbd.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_authz_dbd.dep")
+!INCLUDE "mod_authz_dbd.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_authz_dbd.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_authz_dbd - Win32 Release" || "$(CFG)" == "mod_authz_dbd - Win32 Debug"
+
+!IF "$(CFG)" == "mod_authz_dbd - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authz_dbd - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authz_dbd - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authz_dbd - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authz_dbd - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authz_dbd - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authz_dbd - Win32 Release"
+
+"mod_auth_basic - Win32 Release" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Release"
+ cd "."
+
+"mod_auth_basic - Win32 ReleaseCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Release" RECURSE=1 CLEAN
+ cd "."
+
+!ELSEIF "$(CFG)" == "mod_authz_dbd - Win32 Debug"
+
+"mod_auth_basic - Win32 Debug" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Debug"
+ cd "."
+
+"mod_auth_basic - Win32 DebugCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Debug" RECURSE=1 CLEAN
+ cd "."
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authz_dbd - Win32 Release"
+
+"mod_dbd - Win32 Release" :
+ cd ".\..\database"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_dbd.mak" CFG="mod_dbd - Win32 Release"
+ cd "..\aaa"
+
+"mod_dbd - Win32 ReleaseCLEAN" :
+ cd ".\..\database"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_dbd.mak" CFG="mod_dbd - Win32 Release" RECURSE=1 CLEAN
+ cd "..\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authz_dbd - Win32 Debug"
+
+"mod_dbd - Win32 Debug" :
+ cd ".\..\database"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_dbd.mak" CFG="mod_dbd - Win32 Debug"
+ cd "..\aaa"
+
+"mod_dbd - Win32 DebugCLEAN" :
+ cd ".\..\database"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_dbd.mak" CFG="mod_dbd - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\aaa"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_authz_dbd - Win32 Release"
+
+
+"$(INTDIR)\mod_authz_dbd.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_authz_dbd.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_authz_dbd.so" /d LONG_NAME="authz_dbd_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_authz_dbd - Win32 Debug"
+
+
+"$(INTDIR)\mod_authz_dbd.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_authz_dbd.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_authz_dbd.so" /d LONG_NAME="authz_dbd_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_authz_dbd.c
+
+"$(INTDIR)\mod_authz_dbd.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/aaa/mod_authz_dbm.c b/modules/aaa/mod_authz_dbm.c
new file mode 100644
index 0000000..f11de68
--- /dev/null
+++ b/modules/aaa/mod_authz_dbm.c
@@ -0,0 +1,356 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+#include "apr_strings.h"
+#include "apr_dbm.h"
+#include "apr_md5.h"
+
+#include "apr_version.h"
+#if !APR_VERSION_AT_LEAST(2,0,0)
+#include "apu_version.h"
+#endif
+
+#include "httpd.h"
+#include "http_config.h"
+#include "ap_provider.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "http_request.h" /* for ap_hook_(check_user_id | auth_checker)*/
+
+#include "mod_auth.h"
+#include "mod_authz_owner.h"
+
+typedef struct {
+ const char *grpfile;
+ const char *dbmtype;
+} authz_dbm_config_rec;
+
+
+/* This should go into APR; perhaps with some nice
+ * caching/locking/flocking of the open dbm file.
+ */
+static char *get_dbm_entry_as_str(apr_pool_t *pool, apr_dbm_t *f, char *key)
+{
+ apr_datum_t d, q;
+ q.dptr = key;
+
+#ifndef NETSCAPE_DBM_COMPAT
+ q.dsize = strlen(q.dptr);
+#else
+ q.dsize = strlen(q.dptr) + 1;
+#endif
+
+ if (apr_dbm_fetch(f, q, &d) == APR_SUCCESS && d.dptr) {
+ return apr_pstrmemdup(pool, d.dptr, d.dsize);
+ }
+
+ return NULL;
+}
+
+static void *create_authz_dbm_dir_config(apr_pool_t *p, char *d)
+{
+ authz_dbm_config_rec *conf = apr_palloc(p, sizeof(*conf));
+
+ conf->grpfile = NULL;
+ conf->dbmtype = "default";
+
+ return conf;
+}
+
+static const command_rec authz_dbm_cmds[] =
+{
+ AP_INIT_TAKE1("AuthDBMGroupFile", ap_set_file_slot,
+ (void *)APR_OFFSETOF(authz_dbm_config_rec, grpfile),
+ OR_AUTHCFG, "database file containing group names and member user IDs"),
+ AP_INIT_TAKE1("AuthzDBMType", ap_set_string_slot,
+ (void *)APR_OFFSETOF(authz_dbm_config_rec, dbmtype),
+ OR_AUTHCFG, "what type of DBM file the group file is"),
+ {NULL}
+};
+
+module AP_MODULE_DECLARE_DATA authz_dbm_module;
+
+/* We do something strange with the group file. If the group file
+ * contains any : we assume the format is
+ * key=username value=":"groupname [":"anything here is ignored]
+ * otherwise we now (0.8.14+) assume that the format is
+ * key=username value=groupname
+ * The first allows the password and group files to be the same
+ * physical DBM file; key=username value=password":"groupname[":"anything]
+ *
+ * mark@telescope.org, 22Sep95
+ */
+
+static apr_status_t get_dbm_grp(request_rec *r, char *key1, char *key2,
+ const char *dbmgrpfile, const char *dbtype,
+ const char ** out)
+{
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ const apr_dbm_driver_t *driver;
+ const apu_err_t *err;
+#endif
+ char *grp_colon, *val;
+ apr_status_t retval;
+ apr_dbm_t *f;
+
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ retval = apr_dbm_get_driver(&driver, dbtype, &err, r->pool);
+
+ if (retval != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, retval, r, APLOGNO(10286)
+ "could not load '%s' dbm library: %s",
+ err->reason, err->msg);
+ return retval;
+ }
+
+ retval = apr_dbm_open2(&f, driver, dbmgrpfile, APR_DBM_READONLY,
+ APR_OS_DEFAULT, r->pool);
+#else
+ retval = apr_dbm_open_ex(&f, dbtype, dbmgrpfile, APR_DBM_READONLY,
+ APR_OS_DEFAULT, r->pool);
+#endif
+
+ if (retval != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, retval, r, APLOGNO(01799)
+ "could not open dbm (type %s) group access "
+ "file: %s", dbtype, dbmgrpfile);
+ return retval;
+ }
+
+ /* Try key2 only if key1 failed */
+ if (!(val = get_dbm_entry_as_str(r->pool, f, key1))) {
+ val = get_dbm_entry_as_str(r->pool, f, key2);
+ }
+
+ apr_dbm_close(f);
+
+ if (val && (grp_colon = ap_strchr(val, ':')) != NULL) {
+ char *grp_colon2 = ap_strchr(++grp_colon, ':');
+
+ if (grp_colon2) {
+ *grp_colon2 = '\0';
+ }
+ *out = grp_colon;
+ }
+ else {
+ *out = val;
+ }
+
+ return retval;
+}
+
+static authz_status dbmgroup_check_authorization(request_rec *r,
+ const char *require_args,
+ const void *parsed_require_args)
+{
+ authz_dbm_config_rec *conf = ap_get_module_config(r->per_dir_config,
+ &authz_dbm_module);
+ char *user = r->user;
+
+ const char *err = NULL;
+ const ap_expr_info_t *expr = parsed_require_args;
+ const char *require;
+
+ const char *t;
+ char *w;
+ const char *orig_groups = NULL;
+ const char *realm = ap_auth_name(r);
+ const char *groups;
+ char *v;
+
+ if (!user) {
+ return AUTHZ_DENIED_NO_USER;
+ }
+
+ if (!conf->grpfile) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01798)
+ "No group file was specified in the configuration");
+ return AUTHZ_DENIED;
+ }
+
+ /* fetch group data from dbm file only once. */
+ if (!orig_groups) {
+ apr_status_t status;
+
+ status = get_dbm_grp(r, apr_pstrcat(r->pool, user, ":", realm, NULL),
+ user, conf->grpfile, conf->dbmtype, &groups);
+
+ if (status != APR_SUCCESS) {
+ return AUTHZ_GENERAL_ERROR;
+ }
+
+ if (groups == NULL) {
+ /* no groups available, so exit immediately */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01800)
+ "Authorization of user %s to access %s failed, reason: "
+ "user doesn't appear in DBM group file (%s).",
+ r->user, r->uri, conf->grpfile);
+ return AUTHZ_DENIED;
+ }
+
+ orig_groups = groups;
+ }
+
+ require = ap_expr_str_exec(r, expr, &err);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02591)
+ "authz_dbm authorize: require dbm-group: Can't "
+ "evaluate require expression: %s", err);
+ return AUTHZ_DENIED;
+ }
+
+ t = require;
+ while ((w = ap_getword_white(r->pool, &t)) && w[0]) {
+ groups = orig_groups;
+ while (groups[0]) {
+ v = ap_getword(r->pool, &groups, ',');
+ if (!strcmp(v, w)) {
+ return AUTHZ_GRANTED;
+ }
+ }
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01801)
+ "Authorization of user %s to access %s failed, reason: "
+ "user is not part of the 'require'ed group(s).",
+ r->user, r->uri);
+
+ return AUTHZ_DENIED;
+}
+
+static APR_OPTIONAL_FN_TYPE(authz_owner_get_file_group) *authz_owner_get_file_group;
+
+static authz_status dbmfilegroup_check_authorization(request_rec *r,
+ const char *require_args,
+ const void *parsed_require_args)
+{
+ authz_dbm_config_rec *conf = ap_get_module_config(r->per_dir_config,
+ &authz_dbm_module);
+ char *user = r->user;
+ const char *realm = ap_auth_name(r);
+ const char *filegroup = NULL;
+ apr_status_t status;
+ const char *groups;
+ char *v;
+
+ if (!user) {
+ return AUTHZ_DENIED_NO_USER;
+ }
+
+ if (!conf->grpfile) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01802)
+ "No group file was specified in the configuration");
+ return AUTHZ_DENIED;
+ }
+
+ /* fetch group data from dbm file. */
+ status = get_dbm_grp(r, apr_pstrcat(r->pool, user, ":", realm, NULL),
+ user, conf->grpfile, conf->dbmtype, &groups);
+
+ if (status != APR_SUCCESS) {
+ return AUTHZ_DENIED;
+ }
+
+ if (groups == NULL) {
+ /* no groups available, so exit immediately */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01804)
+ "Authorization of user %s to access %s failed, reason: "
+ "user doesn't appear in DBM group file (%s).",
+ r->user, r->uri, conf->grpfile);
+ return AUTHZ_DENIED;
+ }
+
+ filegroup = authz_owner_get_file_group(r);
+
+ if (filegroup) {
+ while (groups[0]) {
+ v = ap_getword(r->pool, &groups, ',');
+ if (!strcmp(v, filegroup)) {
+ return AUTHZ_GRANTED;
+ }
+ }
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01805)
+ "Authorization of user %s to access %s failed, reason: "
+ "user is not part of the 'require'ed group(s).",
+ r->user, r->uri);
+
+ return AUTHZ_DENIED;
+}
+
+static const char *dbm_parse_config(cmd_parms *cmd, const char *require_line,
+ const void **parsed_require_line)
+{
+ const char *expr_err = NULL;
+ ap_expr_info_t *expr;
+
+ expr = ap_expr_parse_cmd(cmd, require_line, AP_EXPR_FLAG_STRING_RESULT,
+ &expr_err, NULL);
+
+ if (expr_err)
+ return apr_pstrcat(cmd->temp_pool,
+ "Cannot parse expression in require line: ",
+ expr_err, NULL);
+
+ *parsed_require_line = expr;
+
+ return NULL;
+}
+
+static const authz_provider authz_dbmgroup_provider =
+{
+ &dbmgroup_check_authorization,
+ &dbm_parse_config,
+};
+
+static const authz_provider authz_dbmfilegroup_provider =
+{
+ &dbmfilegroup_check_authorization,
+ NULL,
+};
+
+static void authz_dbm_getfns(void)
+{
+ authz_owner_get_file_group = APR_RETRIEVE_OPTIONAL_FN(authz_owner_get_file_group);
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "dbm-group",
+ AUTHZ_PROVIDER_VERSION,
+ &authz_dbmgroup_provider,
+ AP_AUTH_INTERNAL_PER_CONF);
+ ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "dbm-file-group",
+ AUTHZ_PROVIDER_VERSION,
+ &authz_dbmfilegroup_provider,
+ AP_AUTH_INTERNAL_PER_CONF);
+ ap_hook_optional_fn_retrieve(authz_dbm_getfns, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(authz_dbm) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_authz_dbm_dir_config, /* dir config creater */
+ NULL, /* dir merger --- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ authz_dbm_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/aaa/mod_authz_dbm.dep b/modules/aaa/mod_authz_dbm.dep
new file mode 100644
index 0000000..1b4bf3a
--- /dev/null
+++ b/modules/aaa/mod_authz_dbm.dep
@@ -0,0 +1,62 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_authz_dbm.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_authz_dbm.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_authz_owner.h"\
+
diff --git a/modules/aaa/mod_authz_dbm.dsp b/modules/aaa/mod_authz_dbm.dsp
new file mode 100644
index 0000000..9ac2993
--- /dev/null
+++ b/modules/aaa/mod_authz_dbm.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_authz_dbm" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_authz_dbm - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authz_dbm.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authz_dbm.mak" CFG="mod_authz_dbm - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_authz_dbm - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_authz_dbm - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_authz_dbm - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_authz_dbm_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_authz_dbm.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_authz_dbm.so" /d LONG_NAME="authz_dbm_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_authz_dbm.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_dbm.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_authz_dbm.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_dbm.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_authz_dbm.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_authz_dbm - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_authz_dbm_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_authz_dbm.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_authz_dbm.so" /d LONG_NAME="authz_dbm_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_authz_dbm.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_dbm.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_authz_dbm.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_dbm.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_authz_dbm.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_authz_dbm - Win32 Release"
+# Name "mod_authz_dbm - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_authz_dbm.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/aaa/mod_authz_dbm.mak b/modules/aaa/mod_authz_dbm.mak
new file mode 100644
index 0000000..4be17d5
--- /dev/null
+++ b/modules/aaa/mod_authz_dbm.mak
@@ -0,0 +1,381 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_authz_dbm.dsp
+!IF "$(CFG)" == ""
+CFG=mod_authz_dbm - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_authz_dbm - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_authz_dbm - Win32 Release" && "$(CFG)" != "mod_authz_dbm - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authz_dbm.mak" CFG="mod_authz_dbm - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_authz_dbm - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_authz_dbm - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_authz_dbm - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_authz_dbm.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_auth_basic - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_authz_dbm.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_auth_basic - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_authz_dbm.obj"
+ -@erase "$(INTDIR)\mod_authz_dbm.res"
+ -@erase "$(INTDIR)\mod_authz_dbm_src.idb"
+ -@erase "$(INTDIR)\mod_authz_dbm_src.pdb"
+ -@erase "$(OUTDIR)\mod_authz_dbm.exp"
+ -@erase "$(OUTDIR)\mod_authz_dbm.lib"
+ -@erase "$(OUTDIR)\mod_authz_dbm.pdb"
+ -@erase "$(OUTDIR)\mod_authz_dbm.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_authz_dbm_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_authz_dbm.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_authz_dbm.so" /d LONG_NAME="authz_dbm_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_authz_dbm.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_authz_dbm.pdb" /debug /out:"$(OUTDIR)\mod_authz_dbm.so" /implib:"$(OUTDIR)\mod_authz_dbm.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_dbm.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_authz_dbm.obj" \
+ "$(INTDIR)\mod_authz_dbm.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib" \
+ "$(OUTDIR)\mod_auth_basic.lib"
+
+"$(OUTDIR)\mod_authz_dbm.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_authz_dbm.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_authz_dbm.so"
+ if exist .\Release\mod_authz_dbm.so.manifest mt.exe -manifest .\Release\mod_authz_dbm.so.manifest -outputresource:.\Release\mod_authz_dbm.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_authz_dbm - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_authz_dbm.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_auth_basic - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_authz_dbm.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_auth_basic - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_authz_dbm.obj"
+ -@erase "$(INTDIR)\mod_authz_dbm.res"
+ -@erase "$(INTDIR)\mod_authz_dbm_src.idb"
+ -@erase "$(INTDIR)\mod_authz_dbm_src.pdb"
+ -@erase "$(OUTDIR)\mod_authz_dbm.exp"
+ -@erase "$(OUTDIR)\mod_authz_dbm.lib"
+ -@erase "$(OUTDIR)\mod_authz_dbm.pdb"
+ -@erase "$(OUTDIR)\mod_authz_dbm.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_authz_dbm_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_authz_dbm.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_authz_dbm.so" /d LONG_NAME="authz_dbm_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_authz_dbm.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_authz_dbm.pdb" /debug /out:"$(OUTDIR)\mod_authz_dbm.so" /implib:"$(OUTDIR)\mod_authz_dbm.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_dbm.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_authz_dbm.obj" \
+ "$(INTDIR)\mod_authz_dbm.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib" \
+ "$(OUTDIR)\mod_auth_basic.lib"
+
+"$(OUTDIR)\mod_authz_dbm.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_authz_dbm.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_authz_dbm.so"
+ if exist .\Debug\mod_authz_dbm.so.manifest mt.exe -manifest .\Debug\mod_authz_dbm.so.manifest -outputresource:.\Debug\mod_authz_dbm.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_authz_dbm.dep")
+!INCLUDE "mod_authz_dbm.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_authz_dbm.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_authz_dbm - Win32 Release" || "$(CFG)" == "mod_authz_dbm - Win32 Debug"
+
+!IF "$(CFG)" == "mod_authz_dbm - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authz_dbm - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authz_dbm - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authz_dbm - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authz_dbm - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authz_dbm - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authz_dbm - Win32 Release"
+
+"mod_auth_basic - Win32 Release" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Release"
+ cd "."
+
+"mod_auth_basic - Win32 ReleaseCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Release" RECURSE=1 CLEAN
+ cd "."
+
+!ELSEIF "$(CFG)" == "mod_authz_dbm - Win32 Debug"
+
+"mod_auth_basic - Win32 Debug" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Debug"
+ cd "."
+
+"mod_auth_basic - Win32 DebugCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Debug" RECURSE=1 CLEAN
+ cd "."
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_authz_dbm - Win32 Release"
+
+
+"$(INTDIR)\mod_authz_dbm.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_authz_dbm.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_authz_dbm.so" /d LONG_NAME="authz_dbm_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_authz_dbm - Win32 Debug"
+
+
+"$(INTDIR)\mod_authz_dbm.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_authz_dbm.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_authz_dbm.so" /d LONG_NAME="authz_dbm_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_authz_dbm.c
+
+"$(INTDIR)\mod_authz_dbm.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/aaa/mod_authz_groupfile.c b/modules/aaa/mod_authz_groupfile.c
new file mode 100644
index 0000000..c2431e0
--- /dev/null
+++ b/modules/aaa/mod_authz_groupfile.c
@@ -0,0 +1,333 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* This module is triggered by an
+ *
+ * AuthGroupFile standard /path/to/file
+ *
+ * and the presence of a
+ *
+ * require group <list-of-groups>
+ *
+ * In an applicable limit/directory block for that method.
+ *
+ * If there are no AuthGroupFile directives valid for
+ * the request; we DECLINED.
+ *
+ * If the AuthGroupFile is defined; but somehow not
+ * accessible: we SERVER_ERROR (was DECLINED).
+ *
+ * If there are no 'require ' directives defined for
+ * this request then we DECLINED (was OK).
+ *
+ * If there are no 'require ' directives valid for
+ * this request method then we DECLINED. (was OK)
+ *
+ * If there are any 'require group' blocks and we
+ * are not in any group - we HTTP_UNAUTHORIZE
+ *
+ */
+
+#include "apr_strings.h"
+#include "apr_lib.h" /* apr_isspace */
+
+#include "ap_config.h"
+#include "ap_provider.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "util_varbuf.h"
+
+#include "mod_auth.h"
+#include "mod_authz_owner.h"
+
+typedef struct {
+ char *groupfile;
+} authz_groupfile_config_rec;
+
+static void *create_authz_groupfile_dir_config(apr_pool_t *p, char *d)
+{
+ authz_groupfile_config_rec *conf = apr_palloc(p, sizeof(*conf));
+
+ conf->groupfile = NULL;
+ return conf;
+}
+
+static const command_rec authz_groupfile_cmds[] =
+{
+ AP_INIT_TAKE1("AuthGroupFile", ap_set_file_slot,
+ (void *)APR_OFFSETOF(authz_groupfile_config_rec, groupfile),
+ OR_AUTHCFG,
+ "text file containing group names and member user IDs"),
+ {NULL}
+};
+
+module AP_MODULE_DECLARE_DATA authz_groupfile_module;
+
+#define VARBUF_INIT_LEN 512
+#define VARBUF_MAX_LEN (16*1024*1024)
+static apr_status_t groups_for_user(apr_pool_t *p, char *user, char *grpfile,
+ apr_table_t ** out)
+{
+ ap_configfile_t *f;
+ apr_table_t *grps = apr_table_make(p, 15);
+ apr_pool_t *sp;
+ struct ap_varbuf vb;
+ const char *group_name, *ll, *w;
+ apr_status_t status;
+ apr_size_t group_len;
+
+ if ((status = ap_pcfg_openfile(&f, p, grpfile)) != APR_SUCCESS) {
+ return status ;
+ }
+
+ apr_pool_create(&sp, p);
+ apr_pool_tag(sp, "authz_groupfile (groups_for_user)");
+
+ ap_varbuf_init(p, &vb, VARBUF_INIT_LEN);
+
+ while (!(ap_varbuf_cfg_getline(&vb, f, VARBUF_MAX_LEN))) {
+ if ((vb.buf[0] == '#') || (!vb.buf[0])) {
+ continue;
+ }
+ ll = vb.buf;
+ apr_pool_clear(sp);
+
+ group_name = ap_getword(sp, &ll, ':');
+ group_len = strlen(group_name);
+
+ while (group_len && apr_isspace(*(group_name + group_len - 1))) {
+ --group_len;
+ }
+
+ while (ll[0]) {
+ w = ap_getword_conf(sp, &ll);
+ if (!strcmp(w, user)) {
+ apr_table_setn(grps, apr_pstrmemdup(p, group_name, group_len),
+ "in");
+ break;
+ }
+ }
+ }
+ ap_cfg_closefile(f);
+ apr_pool_destroy(sp);
+ ap_varbuf_free(&vb);
+
+ *out = grps;
+ return APR_SUCCESS;
+}
+
+static authz_status group_check_authorization(request_rec *r,
+ const char *require_args,
+ const void *parsed_require_args)
+{
+ authz_groupfile_config_rec *conf = ap_get_module_config(r->per_dir_config,
+ &authz_groupfile_module);
+ char *user = r->user;
+
+ const char *err = NULL;
+ const ap_expr_info_t *expr = parsed_require_args;
+ const char *require;
+
+ const char *t, *w;
+ apr_table_t *grpstatus = NULL;
+ apr_status_t status;
+
+ if (!user) {
+ return AUTHZ_DENIED_NO_USER;
+ }
+
+ /* If there is no group file - then we are not
+ * configured. So decline.
+ */
+ if (!(conf->groupfile)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01664)
+ "No group file was specified in the configuration");
+ return AUTHZ_DENIED;
+ }
+
+ status = groups_for_user(r->pool, user, conf->groupfile,
+ &grpstatus);
+
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01665)
+ "Could not open group file: %s",
+ conf->groupfile);
+ return AUTHZ_DENIED;
+ }
+
+ if (apr_is_empty_table(grpstatus)) {
+ /* no groups available, so exit immediately */
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01666)
+ "Authorization of user %s to access %s failed, reason: "
+ "user doesn't appear in group file (%s).",
+ r->user, r->uri, conf->groupfile);
+ return AUTHZ_DENIED;
+ }
+
+ require = ap_expr_str_exec(r, expr, &err);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02592)
+ "authz_groupfile authorize: require group: Can't "
+ "evaluate require expression: %s", err);
+ return AUTHZ_DENIED;
+ }
+
+ t = require;
+ while ((w = ap_getword_conf(r->pool, &t)) && w[0]) {
+ if (apr_table_get(grpstatus, w)) {
+ return AUTHZ_GRANTED;
+ }
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01667)
+ "Authorization of user %s to access %s failed, reason: "
+ "user is not part of the 'require'ed group(s).",
+ r->user, r->uri);
+
+ return AUTHZ_DENIED;
+}
+
+static APR_OPTIONAL_FN_TYPE(authz_owner_get_file_group) *authz_owner_get_file_group;
+
+static authz_status filegroup_check_authorization(request_rec *r,
+ const char *require_args,
+ const void *parsed_require_args)
+{
+ authz_groupfile_config_rec *conf = ap_get_module_config(r->per_dir_config,
+ &authz_groupfile_module);
+ char *user = r->user;
+ apr_table_t *grpstatus = NULL;
+ apr_status_t status;
+ const char *filegroup = NULL;
+
+ if (!user) {
+ return AUTHZ_DENIED_NO_USER;
+ }
+
+ /* If there is no group file - then we are not
+ * configured. So decline.
+ */
+ if (!(conf->groupfile)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01668)
+ "No group file was specified in the configuration");
+ return AUTHZ_DENIED;
+ }
+
+ status = groups_for_user(r->pool, user, conf->groupfile,
+ &grpstatus);
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01669)
+ "Could not open group file: %s",
+ conf->groupfile);
+ return AUTHZ_DENIED;
+ }
+
+ if (apr_is_empty_table(grpstatus)) {
+ /* no groups available, so exit immediately */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01670)
+ "Authorization of user %s to access %s failed, reason: "
+ "user doesn't appear in group file (%s).",
+ r->user, r->uri, conf->groupfile);
+ return AUTHZ_DENIED;
+ }
+
+ filegroup = authz_owner_get_file_group(r);
+
+ if (filegroup) {
+ if (apr_table_get(grpstatus, filegroup)) {
+ return AUTHZ_GRANTED;
+ }
+ }
+ else {
+ /* No need to emit a error log entry because the call
+ to authz_owner_get_file_group already did it
+ for us.
+ */
+ return AUTHZ_DENIED;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01671)
+ "Authorization of user %s to access %s failed, reason: "
+ "user is not part of the 'require'ed file group.",
+ r->user, r->uri);
+
+ return AUTHZ_DENIED;
+}
+
+static const char *groupfile_parse_config(cmd_parms *cmd, const char *require_line,
+ const void **parsed_require_line)
+{
+ const char *expr_err = NULL;
+ ap_expr_info_t *expr;
+
+ expr = ap_expr_parse_cmd(cmd, require_line, AP_EXPR_FLAG_STRING_RESULT,
+ &expr_err, NULL);
+
+ if (expr_err)
+ return apr_pstrcat(cmd->temp_pool,
+ "Cannot parse expression in require line: ",
+ expr_err, NULL);
+
+ *parsed_require_line = expr;
+
+ return NULL;
+}
+
+static const authz_provider authz_group_provider =
+{
+ &group_check_authorization,
+ &groupfile_parse_config,
+};
+
+static const authz_provider authz_filegroup_provider =
+{
+ &filegroup_check_authorization,
+ NULL,
+};
+
+
+static void authz_groupfile_getfns(void)
+{
+ authz_owner_get_file_group = APR_RETRIEVE_OPTIONAL_FN(authz_owner_get_file_group);
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "group",
+ AUTHZ_PROVIDER_VERSION,
+ &authz_group_provider,
+ AP_AUTH_INTERNAL_PER_CONF);
+ ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "file-group",
+ AUTHZ_PROVIDER_VERSION,
+ &authz_filegroup_provider,
+ AP_AUTH_INTERNAL_PER_CONF);
+ ap_hook_optional_fn_retrieve(authz_groupfile_getfns, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(authz_groupfile) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_authz_groupfile_dir_config,/* dir config creater */
+ NULL, /* dir merger -- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ authz_groupfile_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/aaa/mod_authz_groupfile.dep b/modules/aaa/mod_authz_groupfile.dep
new file mode 100644
index 0000000..cb09628
--- /dev/null
+++ b/modules/aaa/mod_authz_groupfile.dep
@@ -0,0 +1,61 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_authz_groupfile.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_authz_groupfile.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_varbuf.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_authz_owner.h"\
+
diff --git a/modules/aaa/mod_authz_groupfile.dsp b/modules/aaa/mod_authz_groupfile.dsp
new file mode 100644
index 0000000..efea169
--- /dev/null
+++ b/modules/aaa/mod_authz_groupfile.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_authz_groupfile" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_authz_groupfile - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authz_groupfile.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authz_groupfile.mak" CFG="mod_authz_groupfile - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_authz_groupfile - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_authz_groupfile - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_authz_groupfile - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_authz_groupfile_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_authz_groupfile.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_authz_groupfile.so" /d LONG_NAME="authz_groupfile_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_authz_groupfile.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_groupfile.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_authz_groupfile.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_groupfile.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_authz_groupfile.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_authz_groupfile - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_authz_groupfile_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_authz_groupfile.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_authz_groupfile.so" /d LONG_NAME="authz_groupfile_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_authz_groupfile.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_groupfile.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_authz_groupfile.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_groupfile.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_authz_groupfile.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_authz_groupfile - Win32 Release"
+# Name "mod_authz_groupfile - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_authz_groupfile.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/aaa/mod_authz_groupfile.mak b/modules/aaa/mod_authz_groupfile.mak
new file mode 100644
index 0000000..37d729c
--- /dev/null
+++ b/modules/aaa/mod_authz_groupfile.mak
@@ -0,0 +1,381 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_authz_groupfile.dsp
+!IF "$(CFG)" == ""
+CFG=mod_authz_groupfile - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_authz_groupfile - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_authz_groupfile - Win32 Release" && "$(CFG)" != "mod_authz_groupfile - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authz_groupfile.mak" CFG="mod_authz_groupfile - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_authz_groupfile - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_authz_groupfile - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_authz_groupfile - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_authz_groupfile.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_auth_basic - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_authz_groupfile.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_auth_basic - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_authz_groupfile.obj"
+ -@erase "$(INTDIR)\mod_authz_groupfile.res"
+ -@erase "$(INTDIR)\mod_authz_groupfile_src.idb"
+ -@erase "$(INTDIR)\mod_authz_groupfile_src.pdb"
+ -@erase "$(OUTDIR)\mod_authz_groupfile.exp"
+ -@erase "$(OUTDIR)\mod_authz_groupfile.lib"
+ -@erase "$(OUTDIR)\mod_authz_groupfile.pdb"
+ -@erase "$(OUTDIR)\mod_authz_groupfile.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_authz_groupfile_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_authz_groupfile.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_authz_groupfile.so" /d LONG_NAME="authz_groupfile_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_authz_groupfile.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_authz_groupfile.pdb" /debug /out:"$(OUTDIR)\mod_authz_groupfile.so" /implib:"$(OUTDIR)\mod_authz_groupfile.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_groupfile.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_authz_groupfile.obj" \
+ "$(INTDIR)\mod_authz_groupfile.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib" \
+ "$(OUTDIR)\mod_auth_basic.lib"
+
+"$(OUTDIR)\mod_authz_groupfile.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_authz_groupfile.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_authz_groupfile.so"
+ if exist .\Release\mod_authz_groupfile.so.manifest mt.exe -manifest .\Release\mod_authz_groupfile.so.manifest -outputresource:.\Release\mod_authz_groupfile.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_authz_groupfile - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_authz_groupfile.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_auth_basic - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_authz_groupfile.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_auth_basic - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_authz_groupfile.obj"
+ -@erase "$(INTDIR)\mod_authz_groupfile.res"
+ -@erase "$(INTDIR)\mod_authz_groupfile_src.idb"
+ -@erase "$(INTDIR)\mod_authz_groupfile_src.pdb"
+ -@erase "$(OUTDIR)\mod_authz_groupfile.exp"
+ -@erase "$(OUTDIR)\mod_authz_groupfile.lib"
+ -@erase "$(OUTDIR)\mod_authz_groupfile.pdb"
+ -@erase "$(OUTDIR)\mod_authz_groupfile.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_authz_groupfile_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_authz_groupfile.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_authz_groupfile.so" /d LONG_NAME="authz_groupfile_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_authz_groupfile.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_authz_groupfile.pdb" /debug /out:"$(OUTDIR)\mod_authz_groupfile.so" /implib:"$(OUTDIR)\mod_authz_groupfile.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_groupfile.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_authz_groupfile.obj" \
+ "$(INTDIR)\mod_authz_groupfile.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib" \
+ "$(OUTDIR)\mod_auth_basic.lib"
+
+"$(OUTDIR)\mod_authz_groupfile.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_authz_groupfile.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_authz_groupfile.so"
+ if exist .\Debug\mod_authz_groupfile.so.manifest mt.exe -manifest .\Debug\mod_authz_groupfile.so.manifest -outputresource:.\Debug\mod_authz_groupfile.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_authz_groupfile.dep")
+!INCLUDE "mod_authz_groupfile.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_authz_groupfile.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_authz_groupfile - Win32 Release" || "$(CFG)" == "mod_authz_groupfile - Win32 Debug"
+
+!IF "$(CFG)" == "mod_authz_groupfile - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authz_groupfile - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authz_groupfile - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authz_groupfile - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authz_groupfile - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authz_groupfile - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authz_groupfile - Win32 Release"
+
+"mod_auth_basic - Win32 Release" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Release"
+ cd "."
+
+"mod_auth_basic - Win32 ReleaseCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Release" RECURSE=1 CLEAN
+ cd "."
+
+!ELSEIF "$(CFG)" == "mod_authz_groupfile - Win32 Debug"
+
+"mod_auth_basic - Win32 Debug" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Debug"
+ cd "."
+
+"mod_auth_basic - Win32 DebugCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Debug" RECURSE=1 CLEAN
+ cd "."
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_authz_groupfile - Win32 Release"
+
+
+"$(INTDIR)\mod_authz_groupfile.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_authz_groupfile.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_authz_groupfile.so" /d LONG_NAME="authz_groupfile_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_authz_groupfile - Win32 Debug"
+
+
+"$(INTDIR)\mod_authz_groupfile.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_authz_groupfile.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_authz_groupfile.so" /d LONG_NAME="authz_groupfile_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_authz_groupfile.c
+
+"$(INTDIR)\mod_authz_groupfile.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/aaa/mod_authz_host.c b/modules/aaa/mod_authz_host.c
new file mode 100644
index 0000000..b43414f
--- /dev/null
+++ b/modules/aaa/mod_authz_host.c
@@ -0,0 +1,410 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Security options etc.
+ *
+ * Module derived from code originally written by Rob McCool
+ *
+ */
+
+#include "apr_strings.h"
+#include "apr_network_io.h"
+#include "apr_md5.h"
+#include "apr_hash.h"
+
+#define APR_WANT_STRFUNC
+#define APR_WANT_BYTEFUNC
+#include "apr_want.h"
+
+#include "ap_config.h"
+#include "ap_provider.h"
+#include "httpd.h"
+#include "http_core.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "http_request.h"
+
+#include "mod_auth.h"
+
+#if APR_HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+
+/*
+ * To save memory if the same subnets are used in hundres of vhosts, we store
+ * each subnet only once and use this temporary hash to find it again.
+ */
+static apr_hash_t *parsed_subnets;
+
+static apr_ipsubnet_t *localhost_v4;
+#if APR_HAVE_IPV6
+static apr_ipsubnet_t *localhost_v6;
+#endif
+
+static int in_domain(const char *domain, const char *what)
+{
+ int dl = strlen(domain);
+ int wl = strlen(what);
+
+ if ((wl - dl) >= 0) {
+ if (strcasecmp(domain, &what[wl - dl]) != 0) {
+ return 0;
+ }
+
+ /* Make sure we matched an *entire* subdomain --- if the user
+ * said 'allow from good.com', we don't want people from nogood.com
+ * to be able to get in.
+ */
+
+ if (wl == dl) {
+ return 1; /* matched whole thing */
+ }
+ else {
+ return (domain[0] == '.' || what[wl - dl - 1] == '.');
+ }
+ }
+ else {
+ return 0;
+ }
+}
+
+static const char *ip_parse_config(cmd_parms *cmd,
+ const char *require_line,
+ const void **parsed_require_line)
+{
+ const char *t, *w;
+ int count = 0;
+ apr_ipsubnet_t **ip;
+ apr_pool_t *ptemp = cmd->temp_pool;
+ apr_pool_t *p = cmd->pool;
+
+ /* The 'ip' provider will allow the configuration to specify a list of
+ ip addresses to check rather than a single address. This is different
+ from the previous host based syntax. */
+
+ t = require_line;
+ while ((w = ap_getword_conf(ptemp, &t)) && w[0])
+ count++;
+
+ if (count == 0)
+ return "'require ip' requires an argument";
+
+ ip = apr_pcalloc(p, sizeof(apr_ipsubnet_t *) * (count + 1));
+ *parsed_require_line = ip;
+
+ t = require_line;
+ while ((w = ap_getword_conf(ptemp, &t)) && w[0]) {
+ char *addr = apr_pstrdup(ptemp, w);
+ char *mask;
+ apr_status_t rv;
+
+ if (parsed_subnets &&
+ (*ip = apr_hash_get(parsed_subnets, w, APR_HASH_KEY_STRING)) != NULL)
+ {
+ /* we already have parsed this subnet */
+ ip++;
+ continue;
+ }
+
+ if ((mask = ap_strchr(addr, '/')))
+ *mask++ = '\0';
+
+ rv = apr_ipsubnet_create(ip, addr, mask, p);
+
+ if(APR_STATUS_IS_EINVAL(rv)) {
+ /* looked nothing like an IP address */
+ return apr_psprintf(p, "ip address '%s' appears to be invalid", w);
+ }
+ else if (rv != APR_SUCCESS) {
+ return apr_psprintf(p, "ip address '%s' appears to be invalid: %pm",
+ w, &rv);
+ }
+
+ if (parsed_subnets)
+ apr_hash_set(parsed_subnets, w, APR_HASH_KEY_STRING, *ip);
+ ip++;
+ }
+
+ return NULL;
+}
+
+static authz_status ip_check_authorization(request_rec *r,
+ const char *require_line,
+ const void *parsed_require_line)
+{
+ /* apr_ipsubnet_test should accept const but doesn't */
+ apr_ipsubnet_t **ip = (apr_ipsubnet_t **)parsed_require_line;
+
+ while (*ip) {
+ if (apr_ipsubnet_test(*ip, r->useragent_addr))
+ return AUTHZ_GRANTED;
+ ip++;
+ }
+
+ /* authz_core will log the require line and the result at DEBUG */
+ return AUTHZ_DENIED;
+}
+
+static authz_status host_check_authorization(request_rec *r,
+ const char *require_line,
+ const void *parsed_require_line)
+{
+ const char *t, *w;
+ const char *remotehost = NULL;
+ int remotehost_is_ip;
+
+ remotehost = ap_get_useragent_host(r, REMOTE_DOUBLE_REV, &remotehost_is_ip);
+
+ if ((remotehost == NULL) || remotehost_is_ip) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01753)
+ "access check of '%s' to %s failed, reason: unable to get the "
+ "remote host name", require_line, r->uri);
+ }
+ else {
+ const char *err = NULL;
+ const ap_expr_info_t *expr = parsed_require_line;
+ const char *require;
+
+ require = ap_expr_str_exec(r, expr, &err);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02593)
+ "authz_host authorize: require host: Can't "
+ "evaluate require expression: %s", err);
+ return AUTHZ_DENIED;
+ }
+
+ /* The 'host' provider will allow the configuration to specify a list of
+ host names to check rather than a single name. This is different
+ from the previous host based syntax. */
+ t = require;
+
+ /* '#' is not a valid hostname character and admin could
+ * specify 'Require host localhost# Add example.com later'. We
+ * should not grant access to 'example.com' in that case. */
+ w = ap_strchr_c(t, '#');
+ if (w) {
+ if (w == t) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10120)
+ "authz_host authorize: dubious empty "
+ "'Require host %s' with only comment", t);
+ return AUTHZ_DENIED;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(10121)
+ "authz_host authorize: ignoring comment in "
+ "'Require host %s'", t);
+
+ /* Truncate the string at the #. */
+ t = apr_pstrmemdup(r->pool, t, w - t);
+ }
+
+ while ((w = ap_getword_conf(r->pool, &t)) && w[0]) {
+ if (in_domain(w, remotehost)) {
+ return AUTHZ_GRANTED;
+ }
+ }
+ }
+
+ /* authz_core will log the require line and the result at DEBUG */
+ return AUTHZ_DENIED;
+}
+
+static authz_status
+forward_dns_check_authorization(request_rec *r,
+ const char *require_line,
+ const void *parsed_require_line)
+{
+ const char *err = NULL;
+ const ap_expr_info_t *expr = parsed_require_line;
+ const char *require, *t;
+ char *w;
+
+ /* the require line is an expression, which is evaluated now. */
+ require = ap_expr_str_exec(r, expr, &err);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(03354)
+ "authz_host authorize: require forward-dns: "
+ "Can't evaluate require expression: %s", err);
+ return AUTHZ_DENIED;
+ }
+
+ /* tokenize expected list of names */
+ t = require;
+ while ((w = ap_getword_conf(r->pool, &t)) && w[0]) {
+
+ apr_sockaddr_t *sa;
+ apr_status_t rv;
+ char *hash_ptr;
+
+ /* stop on apache configuration file comments */
+ if ((hash_ptr = ap_strchr(w, '#'))) {
+ if (hash_ptr == w) {
+ break;
+ }
+ *hash_ptr = '\0';
+ }
+
+ /* does the client ip match one of the names? */
+ rv = apr_sockaddr_info_get(&sa, w, APR_UNSPEC, 0, 0, r->pool);
+ if (rv == APR_SUCCESS) {
+
+ while (sa) {
+ int match = apr_sockaddr_equal(sa, r->useragent_addr);
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03355)
+ "access check for %s as '%s': %s",
+ r->useragent_ip, w, match? "yes": "no");
+ if (match) {
+ return AUTHZ_GRANTED;
+ }
+
+ sa = sa->next;
+ }
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(03356)
+ "No sockaddr info for \"%s\"", w);
+ }
+
+ /* stop processing, we are in a comment */
+ if (hash_ptr) {
+ break;
+ }
+ }
+
+ return AUTHZ_DENIED;
+}
+
+static authz_status local_check_authorization(request_rec *r,
+ const char *require_line,
+ const void *parsed_require_line)
+{
+ if ( apr_sockaddr_equal(r->connection->local_addr,
+ r->useragent_addr)
+ || apr_ipsubnet_test(localhost_v4, r->useragent_addr)
+#if APR_HAVE_IPV6
+ || apr_ipsubnet_test(localhost_v6, r->useragent_addr)
+#endif
+ )
+ {
+ return AUTHZ_GRANTED;
+ }
+
+ return AUTHZ_DENIED;
+}
+
+static const char *host_parse_config(cmd_parms *cmd, const char *require_line,
+ const void **parsed_require_line)
+{
+ const char *expr_err = NULL;
+ ap_expr_info_t *expr;
+
+ expr = ap_expr_parse_cmd(cmd, require_line, AP_EXPR_FLAG_STRING_RESULT,
+ &expr_err, NULL);
+
+ if (expr_err)
+ return apr_pstrcat(cmd->temp_pool,
+ "Cannot parse expression in require line: ",
+ expr_err, NULL);
+
+ *parsed_require_line = expr;
+
+ return NULL;
+}
+
+static const authz_provider authz_ip_provider =
+{
+ &ip_check_authorization,
+ &ip_parse_config,
+};
+
+static const authz_provider authz_host_provider =
+{
+ &host_check_authorization,
+ &host_parse_config,
+};
+
+static const authz_provider authz_forward_dns_provider =
+{
+ &forward_dns_check_authorization,
+ &host_parse_config,
+};
+
+static const authz_provider authz_local_provider =
+{
+ &local_check_authorization,
+ NULL,
+};
+
+
+static int authz_host_pre_config(apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp)
+{
+ /* we only use this hash in the parse config phase, ptemp is enough */
+ parsed_subnets = apr_hash_make(ptemp);
+
+ apr_ipsubnet_create(&localhost_v4, "127.0.0.0", "8", p);
+ apr_hash_set(parsed_subnets, "127.0.0.0/8", APR_HASH_KEY_STRING, localhost_v4);
+
+#if APR_HAVE_IPV6
+ apr_ipsubnet_create(&localhost_v6, "::1", NULL, p);
+ apr_hash_set(parsed_subnets, "::1", APR_HASH_KEY_STRING, localhost_v6);
+#endif
+
+ return OK;
+}
+
+static int authz_host_post_config(apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ /* make sure we don't use this during .htaccess parsing */
+ parsed_subnets = NULL;
+
+ return OK;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_hook_pre_config(authz_host_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_post_config(authz_host_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+
+ ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "ip",
+ AUTHZ_PROVIDER_VERSION,
+ &authz_ip_provider, AP_AUTH_INTERNAL_PER_CONF);
+ ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "host",
+ AUTHZ_PROVIDER_VERSION,
+ &authz_host_provider, AP_AUTH_INTERNAL_PER_CONF);
+ ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "forward-dns",
+ AUTHZ_PROVIDER_VERSION,
+ &authz_forward_dns_provider,
+ AP_AUTH_INTERNAL_PER_CONF);
+ ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "local",
+ AUTHZ_PROVIDER_VERSION,
+ &authz_local_provider, AP_AUTH_INTERNAL_PER_CONF);
+}
+
+AP_DECLARE_MODULE(authz_host) =
+{
+ STANDARD20_MODULE_STUFF,
+ NULL, /* dir config creater */
+ NULL, /* dir merger --- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ NULL,
+ register_hooks /* register hooks */
+};
diff --git a/modules/aaa/mod_authz_host.dep b/modules/aaa/mod_authz_host.dep
new file mode 100644
index 0000000..fe5ce09
--- /dev/null
+++ b/modules/aaa/mod_authz_host.dep
@@ -0,0 +1,60 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_authz_host.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_authz_host.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/aaa/mod_authz_host.dsp b/modules/aaa/mod_authz_host.dsp
new file mode 100644
index 0000000..e417687
--- /dev/null
+++ b/modules/aaa/mod_authz_host.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_authz_host" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_authz_host - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authz_host.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authz_host.mak" CFG="mod_authz_host - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_authz_host - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_authz_host - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_authz_host - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_authz_host_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_authz_host.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_authz_host.so" /d LONG_NAME="authz_host_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_authz_host.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_host.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_authz_host.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_host.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_authz_host.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_authz_host - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_authz_host_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_authz_host.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_authz_host.so" /d LONG_NAME="authz_host_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_authz_host.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_host.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_authz_host.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_host.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_authz_host.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_authz_host - Win32 Release"
+# Name "mod_authz_host - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_authz_host.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/aaa/mod_authz_host.mak b/modules/aaa/mod_authz_host.mak
new file mode 100644
index 0000000..1ad9e85
--- /dev/null
+++ b/modules/aaa/mod_authz_host.mak
@@ -0,0 +1,381 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_authz_host.dsp
+!IF "$(CFG)" == ""
+CFG=mod_authz_host - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_authz_host - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_authz_host - Win32 Release" && "$(CFG)" != "mod_authz_host - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authz_host.mak" CFG="mod_authz_host - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_authz_host - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_authz_host - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_authz_host - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_authz_host.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_auth_basic - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_authz_host.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_auth_basic - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_authz_host.obj"
+ -@erase "$(INTDIR)\mod_authz_host.res"
+ -@erase "$(INTDIR)\mod_authz_host_src.idb"
+ -@erase "$(INTDIR)\mod_authz_host_src.pdb"
+ -@erase "$(OUTDIR)\mod_authz_host.exp"
+ -@erase "$(OUTDIR)\mod_authz_host.lib"
+ -@erase "$(OUTDIR)\mod_authz_host.pdb"
+ -@erase "$(OUTDIR)\mod_authz_host.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_authz_host_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_authz_host.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_authz_host.so" /d LONG_NAME="authz_host_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_authz_host.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_authz_host.pdb" /debug /out:"$(OUTDIR)\mod_authz_host.so" /implib:"$(OUTDIR)\mod_authz_host.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_host.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_authz_host.obj" \
+ "$(INTDIR)\mod_authz_host.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib" \
+ "$(OUTDIR)\mod_auth_basic.lib"
+
+"$(OUTDIR)\mod_authz_host.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_authz_host.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_authz_host.so"
+ if exist .\Release\mod_authz_host.so.manifest mt.exe -manifest .\Release\mod_authz_host.so.manifest -outputresource:.\Release\mod_authz_host.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_authz_host - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_authz_host.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_auth_basic - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_authz_host.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_auth_basic - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_authz_host.obj"
+ -@erase "$(INTDIR)\mod_authz_host.res"
+ -@erase "$(INTDIR)\mod_authz_host_src.idb"
+ -@erase "$(INTDIR)\mod_authz_host_src.pdb"
+ -@erase "$(OUTDIR)\mod_authz_host.exp"
+ -@erase "$(OUTDIR)\mod_authz_host.lib"
+ -@erase "$(OUTDIR)\mod_authz_host.pdb"
+ -@erase "$(OUTDIR)\mod_authz_host.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_authz_host_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_authz_host.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_authz_host.so" /d LONG_NAME="authz_host_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_authz_host.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_authz_host.pdb" /debug /out:"$(OUTDIR)\mod_authz_host.so" /implib:"$(OUTDIR)\mod_authz_host.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_host.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_authz_host.obj" \
+ "$(INTDIR)\mod_authz_host.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib" \
+ "$(OUTDIR)\mod_auth_basic.lib"
+
+"$(OUTDIR)\mod_authz_host.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_authz_host.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_authz_host.so"
+ if exist .\Debug\mod_authz_host.so.manifest mt.exe -manifest .\Debug\mod_authz_host.so.manifest -outputresource:.\Debug\mod_authz_host.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_authz_host.dep")
+!INCLUDE "mod_authz_host.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_authz_host.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_authz_host - Win32 Release" || "$(CFG)" == "mod_authz_host - Win32 Debug"
+
+!IF "$(CFG)" == "mod_authz_host - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authz_host - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authz_host - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authz_host - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authz_host - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authz_host - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authz_host - Win32 Release"
+
+"mod_auth_basic - Win32 Release" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Release"
+ cd "."
+
+"mod_auth_basic - Win32 ReleaseCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Release" RECURSE=1 CLEAN
+ cd "."
+
+!ELSEIF "$(CFG)" == "mod_authz_host - Win32 Debug"
+
+"mod_auth_basic - Win32 Debug" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Debug"
+ cd "."
+
+"mod_auth_basic - Win32 DebugCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Debug" RECURSE=1 CLEAN
+ cd "."
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_authz_host - Win32 Release"
+
+
+"$(INTDIR)\mod_authz_host.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_authz_host.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_authz_host.so" /d LONG_NAME="authz_host_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_authz_host - Win32 Debug"
+
+
+"$(INTDIR)\mod_authz_host.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_authz_host.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_authz_host.so" /d LONG_NAME="authz_host_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_authz_host.c
+
+"$(INTDIR)\mod_authz_host.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/aaa/mod_authz_owner.c b/modules/aaa/mod_authz_owner.c
new file mode 100644
index 0000000..4fd0b2a
--- /dev/null
+++ b/modules/aaa/mod_authz_owner.c
@@ -0,0 +1,189 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr_strings.h"
+#include "apr_file_info.h"
+#include "apr_user.h"
+
+#include "ap_config.h"
+#include "ap_provider.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "http_request.h"
+
+#include "mod_auth.h"
+#include "mod_authz_owner.h"
+
+static const command_rec authz_owner_cmds[] =
+{
+ {NULL}
+};
+
+module AP_MODULE_DECLARE_DATA authz_owner_module;
+
+static authz_status fileowner_check_authorization(request_rec *r,
+ const char *require_args,
+ const void *parsed_require_args)
+{
+ char *reason = NULL;
+ apr_status_t status = 0;
+
+#if !APR_HAS_USER
+ reason = "'Require file-owner' is not supported on this platform.";
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01632)
+ "Authorization of user %s to access %s failed, reason: %s",
+ r->user, r->uri, reason ? reason : "unknown");
+ return AUTHZ_DENIED;
+#else /* APR_HAS_USER */
+ char *owner = NULL;
+ apr_finfo_t finfo;
+
+ if (!r->user) {
+ return AUTHZ_DENIED_NO_USER;
+ }
+
+ if (!r->filename) {
+ reason = "no filename available";
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01633)
+ "Authorization of user %s to access %s failed, reason: %s",
+ r->user, r->uri, reason ? reason : "unknown");
+ return AUTHZ_DENIED;
+ }
+
+ status = apr_stat(&finfo, r->filename, APR_FINFO_USER, r->pool);
+ if (status != APR_SUCCESS) {
+ reason = apr_pstrcat(r->pool, "could not stat file ",
+ r->filename, NULL);
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01634)
+ "Authorization of user %s to access %s failed, reason: %s",
+ r->user, r->uri, reason ? reason : "unknown");
+ return AUTHZ_DENIED;
+ }
+
+ if (!(finfo.valid & APR_FINFO_USER)) {
+ reason = "no file owner information available";
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01635)
+ "Authorization of user %s to access %s failed, reason: %s",
+ r->user, r->uri, reason ? reason : "unknown");
+ return AUTHZ_DENIED;
+ }
+
+ status = apr_uid_name_get(&owner, finfo.user, r->pool);
+ if (status != APR_SUCCESS || !owner) {
+ reason = "could not get name of file owner";
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01636)
+ "Authorization of user %s to access %s failed, reason: %s",
+ r->user, r->uri, reason ? reason : "unknown");
+ return AUTHZ_DENIED;
+ }
+
+ if (strcmp(owner, r->user)) {
+ reason = apr_psprintf(r->pool, "file owner %s does not match.",
+ owner);
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01637)
+ "Authorization of user %s to access %s failed, reason: %s",
+ r->user, r->uri, reason ? reason : "unknown");
+ return AUTHZ_DENIED;
+ }
+
+ /* this user is authorized */
+ return AUTHZ_GRANTED;
+#endif /* APR_HAS_USER */
+}
+
+static char *authz_owner_get_file_group(request_rec *r)
+{
+ /* file-group only figures out the file's group and lets
+ * other modules do the actual authorization (against a group file/db).
+ * Thus, these modules have to hook themselves after
+ * mod_authz_owner and of course recognize 'file-group', too.
+ */
+#if !APR_HAS_USER
+ return NULL;
+#else /* APR_HAS_USER */
+ char *reason = NULL;
+ char *group = NULL;
+ apr_finfo_t finfo;
+ apr_status_t status = 0;
+
+ if (!r->filename) {
+ reason = "no filename available";
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01638)
+ "Authorization of user %s to access %s failed, reason: %s",
+ r->user, r->uri, reason ? reason : "unknown");
+ return NULL;
+ }
+
+ status = apr_stat(&finfo, r->filename, APR_FINFO_GROUP, r->pool);
+ if (status != APR_SUCCESS) {
+ reason = apr_pstrcat(r->pool, "could not stat file ",
+ r->filename, NULL);
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01639)
+ "Authorization of user %s to access %s failed, reason: %s",
+ r->user, r->uri, reason ? reason : "unknown");
+ return NULL;
+ }
+
+ if (!(finfo.valid & APR_FINFO_GROUP)) {
+ reason = "no file group information available";
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01640)
+ "Authorization of user %s to access %s failed, reason: %s",
+ r->user, r->uri, reason ? reason : "unknown");
+ return NULL;
+ }
+
+ status = apr_gid_name_get(&group, finfo.group, r->pool);
+ if (status != APR_SUCCESS || !group) {
+ reason = "could not get name of file group";
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01641)
+ "Authorization of user %s to access %s failed, reason: %s",
+ r->user, r->uri, reason ? reason : "unknown");
+ return NULL;
+ }
+
+ return group;
+#endif /* APR_HAS_USER */
+}
+
+static const authz_provider authz_fileowner_provider =
+{
+ &fileowner_check_authorization,
+ NULL,
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+ APR_REGISTER_OPTIONAL_FN(authz_owner_get_file_group);
+
+ ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "file-owner",
+ AUTHZ_PROVIDER_VERSION,
+ &authz_fileowner_provider,
+ AP_AUTH_INTERNAL_PER_CONF);
+}
+
+AP_DECLARE_MODULE(authz_owner) =
+{
+ STANDARD20_MODULE_STUFF,
+ NULL, /* dir config creater */
+ NULL, /* dir merger --- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ authz_owner_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/aaa/mod_authz_owner.dep b/modules/aaa/mod_authz_owner.dep
new file mode 100644
index 0000000..42a1056
--- /dev/null
+++ b/modules/aaa/mod_authz_owner.dep
@@ -0,0 +1,59 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_authz_owner.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_authz_owner.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_authz_owner.h"\
+
diff --git a/modules/aaa/mod_authz_owner.dsp b/modules/aaa/mod_authz_owner.dsp
new file mode 100644
index 0000000..e026a28
--- /dev/null
+++ b/modules/aaa/mod_authz_owner.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_authz_owner" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_authz_owner - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authz_owner.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authz_owner.mak" CFG="mod_authz_owner - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_authz_owner - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_authz_owner - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_authz_owner - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_authz_owner_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_authz_owner.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_authz_owner.so" /d LONG_NAME="authz_owner_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_authz_owner.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_owner.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_authz_owner.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_owner.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_authz_owner.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_authz_owner - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_authz_owner_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_authz_owner.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_authz_owner.so" /d LONG_NAME="authz_owner_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_authz_owner.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_owner.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_authz_owner.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_owner.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_authz_owner.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_authz_owner - Win32 Release"
+# Name "mod_authz_owner - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_authz_owner.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/aaa/mod_authz_owner.h b/modules/aaa/mod_authz_owner.h
new file mode 100644
index 0000000..799f336
--- /dev/null
+++ b/modules/aaa/mod_authz_owner.h
@@ -0,0 +1,27 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MOD_AUTHZ_OWNER_H
+#define MOD_AUTHZ_OWNER_H
+
+#include "http_request.h"
+
+/* mod_authz_owner exports an optional function which retrieves the
+ * group name of the file identified by r->filename, if available, or
+ * else returns NULL. */
+APR_DECLARE_OPTIONAL_FN(char*, authz_owner_get_file_group, (request_rec *r));
+
+#endif /* MOD_AUTHZ_OWNER_H */
diff --git a/modules/aaa/mod_authz_owner.mak b/modules/aaa/mod_authz_owner.mak
new file mode 100644
index 0000000..850a1f7
--- /dev/null
+++ b/modules/aaa/mod_authz_owner.mak
@@ -0,0 +1,381 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_authz_owner.dsp
+!IF "$(CFG)" == ""
+CFG=mod_authz_owner - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_authz_owner - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_authz_owner - Win32 Release" && "$(CFG)" != "mod_authz_owner - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authz_owner.mak" CFG="mod_authz_owner - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_authz_owner - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_authz_owner - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_authz_owner - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_authz_owner.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_auth_basic - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_authz_owner.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_auth_basic - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_authz_owner.obj"
+ -@erase "$(INTDIR)\mod_authz_owner.res"
+ -@erase "$(INTDIR)\mod_authz_owner_src.idb"
+ -@erase "$(INTDIR)\mod_authz_owner_src.pdb"
+ -@erase "$(OUTDIR)\mod_authz_owner.exp"
+ -@erase "$(OUTDIR)\mod_authz_owner.lib"
+ -@erase "$(OUTDIR)\mod_authz_owner.pdb"
+ -@erase "$(OUTDIR)\mod_authz_owner.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_authz_owner_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_authz_owner.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_authz_owner.so" /d LONG_NAME="authz_owner_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_authz_owner.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_authz_owner.pdb" /debug /out:"$(OUTDIR)\mod_authz_owner.so" /implib:"$(OUTDIR)\mod_authz_owner.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_owner.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_authz_owner.obj" \
+ "$(INTDIR)\mod_authz_owner.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib" \
+ "$(OUTDIR)\mod_auth_basic.lib"
+
+"$(OUTDIR)\mod_authz_owner.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_authz_owner.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_authz_owner.so"
+ if exist .\Release\mod_authz_owner.so.manifest mt.exe -manifest .\Release\mod_authz_owner.so.manifest -outputresource:.\Release\mod_authz_owner.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_authz_owner - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_authz_owner.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_auth_basic - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_authz_owner.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_auth_basic - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_authz_owner.obj"
+ -@erase "$(INTDIR)\mod_authz_owner.res"
+ -@erase "$(INTDIR)\mod_authz_owner_src.idb"
+ -@erase "$(INTDIR)\mod_authz_owner_src.pdb"
+ -@erase "$(OUTDIR)\mod_authz_owner.exp"
+ -@erase "$(OUTDIR)\mod_authz_owner.lib"
+ -@erase "$(OUTDIR)\mod_authz_owner.pdb"
+ -@erase "$(OUTDIR)\mod_authz_owner.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_authz_owner_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_authz_owner.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_authz_owner.so" /d LONG_NAME="authz_owner_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_authz_owner.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_authz_owner.pdb" /debug /out:"$(OUTDIR)\mod_authz_owner.so" /implib:"$(OUTDIR)\mod_authz_owner.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_owner.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_authz_owner.obj" \
+ "$(INTDIR)\mod_authz_owner.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib" \
+ "$(OUTDIR)\mod_auth_basic.lib"
+
+"$(OUTDIR)\mod_authz_owner.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_authz_owner.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_authz_owner.so"
+ if exist .\Debug\mod_authz_owner.so.manifest mt.exe -manifest .\Debug\mod_authz_owner.so.manifest -outputresource:.\Debug\mod_authz_owner.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_authz_owner.dep")
+!INCLUDE "mod_authz_owner.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_authz_owner.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_authz_owner - Win32 Release" || "$(CFG)" == "mod_authz_owner - Win32 Debug"
+
+!IF "$(CFG)" == "mod_authz_owner - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authz_owner - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authz_owner - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authz_owner - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authz_owner - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authz_owner - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authz_owner - Win32 Release"
+
+"mod_auth_basic - Win32 Release" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Release"
+ cd "."
+
+"mod_auth_basic - Win32 ReleaseCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Release" RECURSE=1 CLEAN
+ cd "."
+
+!ELSEIF "$(CFG)" == "mod_authz_owner - Win32 Debug"
+
+"mod_auth_basic - Win32 Debug" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Debug"
+ cd "."
+
+"mod_auth_basic - Win32 DebugCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Debug" RECURSE=1 CLEAN
+ cd "."
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_authz_owner - Win32 Release"
+
+
+"$(INTDIR)\mod_authz_owner.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_authz_owner.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_authz_owner.so" /d LONG_NAME="authz_owner_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_authz_owner - Win32 Debug"
+
+
+"$(INTDIR)\mod_authz_owner.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_authz_owner.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_authz_owner.so" /d LONG_NAME="authz_owner_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_authz_owner.c
+
+"$(INTDIR)\mod_authz_owner.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/aaa/mod_authz_user.c b/modules/aaa/mod_authz_user.c
new file mode 100644
index 0000000..881f77f
--- /dev/null
+++ b/modules/aaa/mod_authz_user.c
@@ -0,0 +1,146 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr_strings.h"
+
+#include "ap_config.h"
+#include "ap_provider.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "http_request.h"
+
+#include "mod_auth.h"
+
+typedef struct {
+ int dummy; /* just here to stop compiler warnings for now. */
+} authz_user_config_rec;
+
+static void *create_authz_user_dir_config(apr_pool_t *p, char *d)
+{
+ authz_user_config_rec *conf = apr_palloc(p, sizeof(*conf));
+
+ return conf;
+}
+
+static const command_rec authz_user_cmds[] =
+{
+ {NULL}
+};
+
+module AP_MODULE_DECLARE_DATA authz_user_module;
+
+static authz_status user_check_authorization(request_rec *r,
+ const char *require_args,
+ const void *parsed_require_args)
+{
+ const char *err = NULL;
+ const ap_expr_info_t *expr = parsed_require_args;
+ const char *require;
+
+ const char *t, *w;
+
+ if (!r->user) {
+ return AUTHZ_DENIED_NO_USER;
+ }
+
+ require = ap_expr_str_exec(r, expr, &err);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02594)
+ "authz_user authorize: require user: Can't "
+ "evaluate require expression: %s", err);
+ return AUTHZ_DENIED;
+ }
+
+ t = require;
+ while ((w = ap_getword_conf(r->pool, &t)) && w[0]) {
+ if (!strcmp(r->user, w)) {
+ return AUTHZ_GRANTED;
+ }
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01663)
+ "access to %s failed, reason: user '%s' does not meet "
+ "'require'ments for user to be allowed access",
+ r->uri, r->user);
+
+ return AUTHZ_DENIED;
+}
+
+static authz_status validuser_check_authorization(request_rec *r,
+ const char *require_line,
+ const void *parsed_require_line)
+{
+ if (!r->user) {
+ return AUTHZ_DENIED_NO_USER;
+ }
+
+ return AUTHZ_GRANTED;
+}
+
+static const char *user_parse_config(cmd_parms *cmd, const char *require_line,
+ const void **parsed_require_line)
+{
+ const char *expr_err = NULL;
+ ap_expr_info_t *expr;
+
+ expr = ap_expr_parse_cmd(cmd, require_line, AP_EXPR_FLAG_STRING_RESULT,
+ &expr_err, NULL);
+
+ if (expr_err)
+ return apr_pstrcat(cmd->temp_pool,
+ "Cannot parse expression in require line: ",
+ expr_err, NULL);
+
+ *parsed_require_line = expr;
+
+ return NULL;
+}
+
+static const authz_provider authz_user_provider =
+{
+ &user_check_authorization,
+ &user_parse_config,
+};
+static const authz_provider authz_validuser_provider =
+{
+ &validuser_check_authorization,
+ NULL,
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "user",
+ AUTHZ_PROVIDER_VERSION,
+ &authz_user_provider, AP_AUTH_INTERNAL_PER_CONF);
+ ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "valid-user",
+ AUTHZ_PROVIDER_VERSION,
+ &authz_validuser_provider,
+ AP_AUTH_INTERNAL_PER_CONF);
+}
+
+AP_DECLARE_MODULE(authz_user) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_authz_user_dir_config, /* dir config creater */
+ NULL, /* dir merger --- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ authz_user_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/aaa/mod_authz_user.dep b/modules/aaa/mod_authz_user.dep
new file mode 100644
index 0000000..648fa02
--- /dev/null
+++ b/modules/aaa/mod_authz_user.dep
@@ -0,0 +1,58 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_authz_user.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_authz_user.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/aaa/mod_authz_user.dsp b/modules/aaa/mod_authz_user.dsp
new file mode 100644
index 0000000..91d80a6
--- /dev/null
+++ b/modules/aaa/mod_authz_user.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_authz_user" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_authz_user - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authz_user.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authz_user.mak" CFG="mod_authz_user - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_authz_user - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_authz_user - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_authz_user - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_authz_user_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_authz_user.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_authz_user.so" /d LONG_NAME="authz_user_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_authz_user.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_user.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_authz_user.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_user.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_authz_user.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_authz_user - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_authz_user_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_authz_user.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_authz_user.so" /d LONG_NAME="authz_user_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_authz_user.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_user.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_authz_user.so" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_user.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_authz_user.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_authz_user - Win32 Release"
+# Name "mod_authz_user - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_authz_user.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/aaa/mod_authz_user.mak b/modules/aaa/mod_authz_user.mak
new file mode 100644
index 0000000..0989f6e
--- /dev/null
+++ b/modules/aaa/mod_authz_user.mak
@@ -0,0 +1,381 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_authz_user.dsp
+!IF "$(CFG)" == ""
+CFG=mod_authz_user - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_authz_user - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_authz_user - Win32 Release" && "$(CFG)" != "mod_authz_user - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_authz_user.mak" CFG="mod_authz_user - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_authz_user - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_authz_user - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_authz_user - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_authz_user.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_auth_basic - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_authz_user.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_auth_basic - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_authz_user.obj"
+ -@erase "$(INTDIR)\mod_authz_user.res"
+ -@erase "$(INTDIR)\mod_authz_user_src.idb"
+ -@erase "$(INTDIR)\mod_authz_user_src.pdb"
+ -@erase "$(OUTDIR)\mod_authz_user.exp"
+ -@erase "$(OUTDIR)\mod_authz_user.lib"
+ -@erase "$(OUTDIR)\mod_authz_user.pdb"
+ -@erase "$(OUTDIR)\mod_authz_user.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_authz_user_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_authz_user.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_authz_user.so" /d LONG_NAME="authz_user_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_authz_user.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_authz_user.pdb" /debug /out:"$(OUTDIR)\mod_authz_user.so" /implib:"$(OUTDIR)\mod_authz_user.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_user.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_authz_user.obj" \
+ "$(INTDIR)\mod_authz_user.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib" \
+ "$(OUTDIR)\mod_auth_basic.lib"
+
+"$(OUTDIR)\mod_authz_user.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_authz_user.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_authz_user.so"
+ if exist .\Release\mod_authz_user.so.manifest mt.exe -manifest .\Release\mod_authz_user.so.manifest -outputresource:.\Release\mod_authz_user.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_authz_user - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_authz_user.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_auth_basic - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_authz_user.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_auth_basic - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_authz_user.obj"
+ -@erase "$(INTDIR)\mod_authz_user.res"
+ -@erase "$(INTDIR)\mod_authz_user_src.idb"
+ -@erase "$(INTDIR)\mod_authz_user_src.pdb"
+ -@erase "$(OUTDIR)\mod_authz_user.exp"
+ -@erase "$(OUTDIR)\mod_authz_user.lib"
+ -@erase "$(OUTDIR)\mod_authz_user.pdb"
+ -@erase "$(OUTDIR)\mod_authz_user.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_authz_user_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_authz_user.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_authz_user.so" /d LONG_NAME="authz_user_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_authz_user.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_authz_user.pdb" /debug /out:"$(OUTDIR)\mod_authz_user.so" /implib:"$(OUTDIR)\mod_authz_user.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_authz_user.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_authz_user.obj" \
+ "$(INTDIR)\mod_authz_user.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib" \
+ "$(OUTDIR)\mod_auth_basic.lib"
+
+"$(OUTDIR)\mod_authz_user.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_authz_user.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_authz_user.so"
+ if exist .\Debug\mod_authz_user.so.manifest mt.exe -manifest .\Debug\mod_authz_user.so.manifest -outputresource:.\Debug\mod_authz_user.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_authz_user.dep")
+!INCLUDE "mod_authz_user.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_authz_user.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_authz_user - Win32 Release" || "$(CFG)" == "mod_authz_user - Win32 Debug"
+
+!IF "$(CFG)" == "mod_authz_user - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authz_user - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authz_user - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authz_user - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\aaa"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authz_user - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ELSEIF "$(CFG)" == "mod_authz_user - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\aaa"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\aaa"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_authz_user - Win32 Release"
+
+"mod_auth_basic - Win32 Release" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Release"
+ cd "."
+
+"mod_auth_basic - Win32 ReleaseCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Release" RECURSE=1 CLEAN
+ cd "."
+
+!ELSEIF "$(CFG)" == "mod_authz_user - Win32 Debug"
+
+"mod_auth_basic - Win32 Debug" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Debug"
+ cd "."
+
+"mod_auth_basic - Win32 DebugCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_auth_basic.mak" CFG="mod_auth_basic - Win32 Debug" RECURSE=1 CLEAN
+ cd "."
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_authz_user - Win32 Release"
+
+
+"$(INTDIR)\mod_authz_user.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_authz_user.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_authz_user.so" /d LONG_NAME="authz_user_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_authz_user - Win32 Debug"
+
+
+"$(INTDIR)\mod_authz_user.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_authz_user.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_authz_user.so" /d LONG_NAME="authz_user_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_authz_user.c
+
+"$(INTDIR)\mod_authz_user.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/arch/netware/libprews.c b/modules/arch/netware/libprews.c
new file mode 100644
index 0000000..9a0b90e
--- /dev/null
+++ b/modules/arch/netware/libprews.c
@@ -0,0 +1,79 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*------------------------------------------------------------------
+ These functions are to be called when the shared NLM starts and
+ stops. By using these functions instead of defining a main()
+ and calling ExitThread(TSR_THREAD, 0), the load time of the
+ shared NLM is faster and memory size reduced.
+
+ You may also want to override these in your own Apache module
+ to do any cleanup other than the mechanism Apache modules
+ provide.
+------------------------------------------------------------------*/
+#include <netware.h>
+#ifdef USE_WINSOCK
+#include <novsock2.h>
+#endif
+
+int _NonAppStart
+(
+ void *NLMHandle,
+ void *errorScreen,
+ const char *cmdLine,
+ const char *loadDirPath,
+ size_t uninitializedDataLength,
+ void *NLMFileHandle,
+ int (*readRoutineP)( int conn, void *fileHandle, size_t offset,
+ size_t nbytes, size_t *bytesRead, void *buffer ),
+ size_t customDataOffset,
+ size_t customDataSize,
+ int messageCount,
+ const char **messages
+)
+{
+#pragma unused(cmdLine)
+#pragma unused(loadDirPath)
+#pragma unused(uninitializedDataLength)
+#pragma unused(NLMFileHandle)
+#pragma unused(readRoutineP)
+#pragma unused(customDataOffset)
+#pragma unused(customDataSize)
+#pragma unused(messageCount)
+#pragma unused(messages)
+
+#ifdef USE_WINSOCK
+ WSADATA wsaData;
+
+ return WSAStartup((WORD) MAKEWORD(2, 0), &wsaData);
+#else
+ return 0;
+#endif
+}
+
+void _NonAppStop( void )
+{
+#ifdef USE_WINSOCK
+ WSACleanup();
+#else
+ return;
+#endif
+}
+
+int _NonAppCheckUnload( void )
+{
+ return 0;
+}
diff --git a/modules/arch/netware/mod_netware.c b/modules/arch/netware/mod_netware.c
new file mode 100644
index 0000000..f873827
--- /dev/null
+++ b/modules/arch/netware/mod_netware.c
@@ -0,0 +1,206 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr_strings.h"
+#include "apr_portable.h"
+#include "apr_buckets.h"
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "http_log.h"
+#include "util_script.h"
+#include "mod_core.h"
+#include "apr_optional.h"
+#include "apr_lib.h"
+#include "mod_cgi.h"
+#include "mpm_common.h"
+
+#ifdef NETWARE
+
+
+module AP_MODULE_DECLARE_DATA netware_module;
+
+typedef struct {
+ apr_table_t *file_type_handlers; /* CGI map from file types to CGI modules */
+ apr_table_t *file_handler_mode; /* CGI module mode (spawn in same address space or not) */
+ apr_table_t *extra_env_vars; /* Environment variables to be added to the CGI environment */
+} netware_dir_config;
+
+
+static void *create_netware_dir_config(apr_pool_t *p, char *dir)
+{
+ netware_dir_config *new = (netware_dir_config*) apr_palloc(p, sizeof(netware_dir_config));
+
+ new->file_type_handlers = apr_table_make(p, 10);
+ new->file_handler_mode = apr_table_make(p, 10);
+ new->extra_env_vars = apr_table_make(p, 10);
+
+ apr_table_setn(new->file_type_handlers, "NLM", "OS");
+
+ return new;
+}
+
+static void *merge_netware_dir_configs(apr_pool_t *p, void *basev, void *addv)
+{
+ netware_dir_config *base = (netware_dir_config *) basev;
+ netware_dir_config *add = (netware_dir_config *) addv;
+ netware_dir_config *new = (netware_dir_config *) apr_palloc(p, sizeof(netware_dir_config));
+
+ new->file_type_handlers = apr_table_overlay(p, add->file_type_handlers, base->file_type_handlers);
+ new->file_handler_mode = apr_table_overlay(p, add->file_handler_mode, base->file_handler_mode);
+ new->extra_env_vars = apr_table_overlay(p, add->extra_env_vars, base->extra_env_vars);
+
+ return new;
+}
+
+static const char *set_extension_map(cmd_parms *cmd, netware_dir_config *m,
+ char *CGIhdlr, char *ext, char *detach)
+{
+ int i, len;
+
+ if (*ext == '.')
+ ++ext;
+
+ if (CGIhdlr != NULL) {
+ len = strlen(CGIhdlr);
+ for (i=0; i<len; i++) {
+ if (CGIhdlr[i] == '\\') {
+ CGIhdlr[i] = '/';
+ }
+ }
+ }
+
+ apr_table_set(m->file_type_handlers, ext, CGIhdlr);
+ if (detach) {
+ apr_table_set(m->file_handler_mode, ext, "y");
+ }
+
+ return NULL;
+}
+
+static apr_status_t ap_cgi_build_command(const char **cmd, const char ***argv,
+ request_rec *r, apr_pool_t *p,
+ cgi_exec_info_t *e_info)
+{
+ char *ext = NULL;
+ char *cmd_only, *ptr;
+ const char *new_cmd;
+ netware_dir_config *d;
+ const char *args = "";
+
+ d = (netware_dir_config *)ap_get_module_config(r->per_dir_config,
+ &netware_module);
+
+ if (e_info->process_cgi) {
+ /* Handle the complete file name, we DON'T want to follow suexec, since
+ * an unrooted command is as predictable as shooting craps in Win32.
+ *
+ * Notice that unlike most mime extension parsing, we have to use the
+ * win32 parsing here, therefore the final extension is the only one
+ * we will consider
+ */
+ *cmd = r->filename;
+ if (r->args && r->args[0] && !ap_strchr_c(r->args, '=')) {
+ args = r->args;
+ }
+ }
+
+ cmd_only = apr_pstrdup(p, *cmd);
+ e_info->cmd_type = APR_PROGRAM;
+
+ /* truncate any arguments from the cmd */
+ for (ptr = cmd_only; *ptr && (*ptr != ' '); ptr++);
+ *ptr = '\0';
+
+ /* Figure out what the extension is so that we can match it. */
+ ext = strrchr(apr_filepath_name_get(cmd_only), '.');
+
+ /* If there isn't an extension then give it an empty string */
+ if (!ext) {
+ ext = "";
+ }
+
+ /* eliminate the '.' if there is one */
+ if (*ext == '.') {
+ ++ext;
+ }
+
+ /* check if we have a registered command for the extension*/
+ new_cmd = apr_table_get(d->file_type_handlers, ext);
+ e_info->detached = 1;
+ if (new_cmd == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02135)
+ "Could not find a command associated with the %s extension", ext);
+ return APR_EBADF;
+ }
+ if (stricmp(new_cmd, "OS")) {
+ /* If we have a registered command then add the file that was passed in as a
+ parameter to the registered command. */
+ *cmd = apr_pstrcat (p, new_cmd, " ", cmd_only, NULL);
+
+ /* Run in its own address space if specified */
+ if (apr_table_get(d->file_handler_mode, ext)) {
+ e_info->addrspace = 1;
+ }
+ }
+
+ /* Tokenize the full command string into its arguments */
+ apr_tokenize_to_argv(*cmd, (char***)argv, p);
+
+ /* The first argument should be the executible */
+ *cmd = ap_server_root_relative(p, *argv[0]);
+
+ return APR_SUCCESS;
+}
+
+static int
+netware_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp)
+{
+ ap_sys_privileges_handlers(1);
+ return OK;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ APR_REGISTER_OPTIONAL_FN(ap_cgi_build_command);
+ ap_hook_pre_config(netware_pre_config,
+ NULL, NULL, APR_HOOK_FIRST);
+}
+
+static const command_rec netware_cmds[] = {
+AP_INIT_TAKE23("CGIMapExtension", set_extension_map, NULL, OR_FILEINFO,
+ "Full path to the CGI NLM module followed by a file extension. If the "
+ "first parameter is set to \"OS\" then the following file extension is "
+ "treated as NLM. The optional parameter \"detach\" can be specified if "
+ "the NLM should be launched in its own address space."),
+{ NULL }
+};
+
+AP_DECLARE_MODULE(netware) = {
+ STANDARD20_MODULE_STUFF,
+ create_netware_dir_config, /* create per-dir config */
+ merge_netware_dir_configs, /* merge per-dir config */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ netware_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
+
+#endif
diff --git a/modules/arch/netware/mod_nw_ssl.c b/modules/arch/netware/mod_nw_ssl.c
new file mode 100644
index 0000000..298554a
--- /dev/null
+++ b/modules/arch/netware/mod_nw_ssl.c
@@ -0,0 +1,1285 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * based on mod_tls.c - Apache SSL/TLS module for NetWare by Mike Gardiner.
+ *
+ * This module gives Apache the ability to do SSL/TLS with a minimum amount
+ * of effort. All of the SSL/TLS logic is already on NetWare versions 5 and
+ * above and is interfaced through WinSock on NetWare. As you can see in
+ * the code below SSL/TLS sockets can be created with three WinSock calls.
+ *
+ * To load, simply place the module in the modules directory under the main
+ * apache tree. Then add a "SecureListen" with two arguments. The first
+ * argument is an address and/or port. The second argument is the key pair
+ * name as created in ConsoleOne.
+ *
+ * Examples:
+ *
+ * SecureListen 443 "SSL CertificateIP"
+ * SecureListen 123.45.67.89:443 mycert
+ *
+ * The module also supports RFC 2817 / TLS Upgrade for HTTP 1.1.
+ * For this add a "NWSSLUpgradeable" with two arguments. The first
+ * argument is an address and/or port. The second argument is the key pair
+ * name as created in ConsoleOne.
+ *
+ * Examples:
+ *
+ * NWSSLUpgradeable 8080 "SSL CertificateIP"
+ * NWSSLUpgradeable 123.45.67.89:8080 mycert
+ *
+ */
+
+#define WS_SSL
+
+#define MAX_ADDRESS 512
+#define MAX_KEY 80
+
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_connection.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "ap_listen.h"
+#include "apr_strings.h"
+#include "apr_portable.h"
+#include "apr_optional.h"
+
+#include <unilib.h>
+
+#ifndef SO_TLS_UNCLEAN_SHUTDOWN
+#define SO_TLS_UNCLEAN_SHUTDOWN 0
+#endif
+
+/* The ssl_var_lookup() optional function retrieves SSL environment
+ * variables. */
+APR_DECLARE_OPTIONAL_FN(char *, ssl_var_lookup,
+ (apr_pool_t *, server_rec *,
+ conn_rec *, request_rec *,
+ char *));
+
+/* An optional function which returns non-zero if the given connection
+ * is using SSL/TLS. */
+APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec *));
+
+/* The ssl_proxy_enable() and ssl_engine_disable() optional functions
+ * are used by mod_proxy to enable use of SSL for outgoing
+ * connections. */
+APR_DECLARE_OPTIONAL_FN(int, ssl_proxy_enable, (conn_rec *));
+APR_DECLARE_OPTIONAL_FN(int, ssl_engine_disable, (conn_rec *));
+
+#define strEQ(s1,s2) (strcmp(s1,s2) == 0)
+#define strNE(s1,s2) (strcmp(s1,s2) != 0)
+#define strEQn(s1,s2,n) (strncmp(s1,s2,n) == 0)
+#define strNEn(s1,s2,n) (strncmp(s1,s2,n) != 0)
+
+#define strcEQ(s1,s2) (strcasecmp(s1,s2) == 0)
+#define strcNE(s1,s2) (strcasecmp(s1,s2) != 0)
+#define strcEQn(s1,s2,n) (strncasecmp(s1,s2,n) == 0)
+#define strcNEn(s1,s2,n) (strncasecmp(s1,s2,n) != 0)
+
+#define strIsEmpty(s) (s == NULL || s[0] == NUL)
+
+
+module AP_MODULE_DECLARE_DATA nwssl_module;
+
+typedef struct NWSSLSrvConfigRec NWSSLSrvConfigRec;
+typedef struct seclisten_rec seclisten_rec;
+typedef struct seclistenup_rec seclistenup_rec;
+typedef struct secsocket_data secsocket_data;
+
+struct seclisten_rec {
+ seclisten_rec *next;
+ struct sockaddr_in local_addr; /* local IP address and port */
+ int fd;
+ int used; /* Only used during restart */
+ char key[MAX_KEY];
+ int mutual;
+ char *addr;
+ apr_port_t port;
+};
+
+struct seclistenup_rec {
+ seclistenup_rec *next;
+ char key[MAX_KEY];
+ char *addr;
+ apr_port_t port;
+};
+
+struct NWSSLSrvConfigRec {
+ apr_table_t *sltable;
+ apr_table_t *slutable;
+ apr_pool_t *pPool;
+};
+
+struct secsocket_data {
+ apr_socket_t* csd;
+ int is_secure;
+};
+
+static apr_array_header_t *certlist = NULL;
+static unicode_t** certarray = NULL;
+static int numcerts = 0;
+static seclisten_rec* ap_seclisteners = NULL;
+static seclistenup_rec* ap_seclistenersup = NULL;
+
+static ap_listen_rec *nw_old_listeners;
+
+#define get_nwssl_cfg(srv) (NWSSLSrvConfigRec *) ap_get_module_config(srv->module_config, &nwssl_module)
+
+
+static void build_cert_list(apr_pool_t *p)
+{
+ int i;
+ char **rootcerts = (char **)certlist->elts;
+
+ numcerts = certlist->nelts;
+ certarray = apr_palloc(p, sizeof(unicode_t*)*numcerts);
+
+ for (i = 0; i < numcerts; ++i) {
+ unicode_t *unistr;
+ unistr = (unicode_t*)apr_palloc(p, strlen(rootcerts[i])*4);
+ loc2uni (UNI_LOCAL_DEFAULT, unistr, rootcerts[i], 0, 2);
+ certarray[i] = unistr;
+ }
+}
+
+/*
+ * Parses a host of the form <address>[:port]
+ * :port is permitted if 'port' is not NULL
+ */
+static unsigned long parse_addr(const char *w, unsigned short *ports)
+{
+ struct hostent *hep;
+ unsigned long my_addr;
+ char *p;
+
+ p = strchr(w, ':');
+ if (ports != NULL) {
+ *ports = 0;
+ if (p != NULL && strcmp(p + 1, "*") != 0)
+ *ports = atoi(p + 1);
+ }
+
+ if (p != NULL)
+ *p = '\0';
+ if (strcmp(w, "*") == 0) {
+ if (p != NULL)
+ *p = ':';
+ return htonl(INADDR_ANY);
+ }
+
+ my_addr = apr_inet_addr((char *)w);
+ if (my_addr != INADDR_NONE) {
+ if (p != NULL)
+ *p = ':';
+ return my_addr;
+ }
+
+ hep = gethostbyname(w);
+
+ if ((!hep) || (hep->h_addrtype != AF_INET || !hep->h_addr_list[0])) {
+ /* XXX Should be echoing by h_errno the actual failure, no?
+ * ap_log_error would be good here. Better yet - APRize.
+ */
+ fprintf(stderr, "Cannot resolve host name %s --- exiting!\n", w);
+ exit(1);
+ }
+
+ if (hep->h_addr_list[1]) {
+ fprintf(stderr, "Host %s has multiple addresses ---\n", w);
+ fprintf(stderr, "you must choose one explicitly for use as\n");
+ fprintf(stderr, "a secure port. Exiting!!!\n");
+ exit(1);
+ }
+
+ if (p != NULL)
+ *p = ':';
+
+ return ((struct in_addr *) (hep->h_addr))->s_addr;
+}
+
+static int find_secure_listener(seclisten_rec *lr)
+{
+ seclisten_rec *sl;
+
+ for (sl = ap_seclisteners; sl; sl = sl->next) {
+ if (!memcmp(&sl->local_addr, &lr->local_addr, sizeof(sl->local_addr))) {
+ sl->used = 1;
+ return sl->fd;
+ }
+ }
+ return -1;
+}
+
+static char *get_port_key(conn_rec *c)
+{
+ seclistenup_rec *sl;
+
+ for (sl = ap_seclistenersup; sl; sl = sl->next) {
+ if ((sl->port == (c->local_addr)->port) &&
+ ((strcmp(sl->addr, "0.0.0.0") == 0) ||
+ (strcmp(sl->addr, c->local_ip) == 0))) {
+ return sl->key;
+ }
+ }
+ return NULL;
+}
+
+static int make_secure_socket(apr_pool_t *pconf,
+ const struct sockaddr_in *server,
+ char* key, int mutual, server_rec *sconf)
+{
+ int s;
+ char addr[MAX_ADDRESS];
+ struct sslserveropts opts;
+ unsigned int optParam;
+ WSAPROTOCOL_INFO SecureProtoInfo;
+
+ if (server->sin_addr.s_addr != htonl(INADDR_ANY))
+ apr_snprintf(addr, sizeof(addr), "address %s port %d",
+ inet_ntoa(server->sin_addr), ntohs(server->sin_port));
+ else
+ apr_snprintf(addr, sizeof(addr), "port %d", ntohs(server->sin_port));
+
+ /* note that because we're about to slack we don't use psocket */
+ memset(&SecureProtoInfo, 0, sizeof(WSAPROTOCOL_INFO));
+
+ SecureProtoInfo.iAddressFamily = AF_INET;
+ SecureProtoInfo.iSocketType = SOCK_STREAM;
+ SecureProtoInfo.iProtocol = IPPROTO_TCP;
+ SecureProtoInfo.iSecurityScheme = SECURITY_PROTOCOL_SSL;
+
+ s = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP,
+ (LPWSAPROTOCOL_INFO)&SecureProtoInfo, 0, 0);
+
+ if (s == INVALID_SOCKET) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, WSAGetLastError(), sconf,
+ APLOGNO(02120)
+ "make_secure_socket: failed to get a socket for %s",
+ addr);
+ return -1;
+ }
+
+ if (!mutual) {
+ optParam = SO_SSL_ENABLE | SO_SSL_SERVER;
+
+ if (WSAIoctl(s, SO_SSL_SET_FLAGS, (char *)&optParam,
+ sizeof(optParam), NULL, 0, NULL, NULL, NULL)) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, WSAGetLastError(), sconf,
+ APLOGNO(02121)
+ "make_secure_socket: for %s, WSAIoctl: "
+ "(SO_SSL_SET_FLAGS)", addr);
+ return -1;
+ }
+ }
+
+ opts.cert = key;
+ opts.certlen = strlen(key);
+ opts.sidtimeout = 0;
+ opts.sidentries = 0;
+ opts.siddir = NULL;
+
+ if (WSAIoctl(s, SO_SSL_SET_SERVER, (char *)&opts, sizeof(opts),
+ NULL, 0, NULL, NULL, NULL) != 0) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, WSAGetLastError(), sconf,
+ APLOGNO(02122)
+ "make_secure_socket: for %s, WSAIoctl: "
+ "(SO_SSL_SET_SERVER)", addr);
+ return -1;
+ }
+
+ if (mutual) {
+ optParam = 0x07; /* SO_SSL_AUTH_CLIENT */
+
+ if (WSAIoctl(s, SO_SSL_SET_FLAGS, (char*)&optParam, sizeof(optParam),
+ NULL, 0, NULL, NULL, NULL)) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, WSAGetLastError(), sconf,
+ APLOGNO(02123)
+ "make_secure_socket: for %s, WSAIoctl: "
+ "(SO_SSL_SET_FLAGS)", addr);
+ return -1;
+ }
+ }
+
+ optParam = SO_TLS_UNCLEAN_SHUTDOWN;
+ WSAIoctl(s, SO_SSL_SET_FLAGS, (char *)&optParam, sizeof(optParam),
+ NULL, 0, NULL, NULL, NULL);
+
+ return s;
+}
+
+static int convert_secure_socket(conn_rec *c, apr_socket_t *csd)
+{
+ int rcode;
+ struct tlsclientopts sWS2Opts;
+ struct nwtlsopts sNWTLSOpts;
+ struct sslserveropts opts;
+ unsigned long ulFlags;
+ SOCKET sock;
+ unicode_t keyFileName[60];
+
+ apr_os_sock_get(&sock, csd);
+
+ /* zero out buffers */
+ memset((char *)&sWS2Opts, 0, sizeof(struct tlsclientopts));
+ memset((char *)&sNWTLSOpts, 0, sizeof(struct nwtlsopts));
+
+ /* turn on ssl for the socket */
+ ulFlags = (numcerts ? SO_TLS_ENABLE : SO_TLS_ENABLE | SO_TLS_BLIND_ACCEPT);
+ rcode = WSAIoctl(sock, SO_TLS_SET_FLAGS, &ulFlags, sizeof(unsigned long),
+ NULL, 0, NULL, NULL, NULL);
+ if (SOCKET_ERROR == rcode) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, c->base_server, APLOGNO(02124)
+ "Error: %d with WSAIoctl(flag SO_TLS_ENABLE)",
+ WSAGetLastError());
+ return rcode;
+ }
+
+ ulFlags = SO_TLS_UNCLEAN_SHUTDOWN;
+ WSAIoctl(sock, SO_TLS_SET_FLAGS, &ulFlags, sizeof(unsigned long),
+ NULL, 0, NULL, NULL, NULL);
+
+ /* setup the socket for SSL */
+ memset (&sWS2Opts, 0, sizeof(sWS2Opts));
+ memset (&sNWTLSOpts, 0, sizeof(sNWTLSOpts));
+ sWS2Opts.options = &sNWTLSOpts;
+
+ if (numcerts) {
+ sNWTLSOpts.walletProvider = WAL_PROV_DER; /* the wallet provider defined in wdefs.h */
+ sNWTLSOpts.TrustedRootList = certarray; /* array of certs in UNICODE format */
+ sNWTLSOpts.numElementsInTRList = numcerts; /* number of certs in TRList */
+ }
+ else {
+ /* setup the socket for SSL */
+ unicpy(keyFileName, L"SSL CertificateIP");
+ sWS2Opts.wallet = keyFileName; /* no client certificate */
+ sWS2Opts.walletlen = unilen(keyFileName);
+
+ sNWTLSOpts.walletProvider = WAL_PROV_KMO; /* the wallet provider defined in wdefs.h */
+ }
+
+ /* make the IOCTL call */
+ rcode = WSAIoctl(sock, SO_TLS_SET_CLIENT, &sWS2Opts,
+ sizeof(struct tlsclientopts), NULL, 0, NULL,
+ NULL, NULL);
+
+ /* make sure that it was successful */
+ if (SOCKET_ERROR == rcode ) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, c->base_server, APLOGNO(02125)
+ "Error: %d with WSAIoctl(SO_TLS_SET_CLIENT)",
+ WSAGetLastError());
+ }
+ return rcode;
+}
+
+static int SSLize_Socket(SOCKET socketHnd, char *key, request_rec *r)
+{
+ int rcode;
+ struct tlsserveropts sWS2Opts;
+ struct nwtlsopts sNWTLSOpts;
+ unicode_t SASKey[512];
+ unsigned long ulFlag;
+
+ memset((char *)&sWS2Opts, 0, sizeof(struct tlsserveropts));
+ memset((char *)&sNWTLSOpts, 0, sizeof(struct nwtlsopts));
+
+ ulFlag = SO_TLS_ENABLE;
+ rcode = WSAIoctl(socketHnd, SO_TLS_SET_FLAGS, &ulFlag,
+ sizeof(unsigned long), NULL, 0, NULL, NULL, NULL);
+ if (rcode) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, APLOGNO(02126)
+ "Error: %d with WSAIoctl(SO_TLS_SET_FLAGS, SO_TLS_ENABLE)",
+ WSAGetLastError());
+ goto ERR;
+ }
+
+
+ ulFlag = SO_TLS_SERVER;
+ rcode = WSAIoctl(socketHnd, SO_TLS_SET_FLAGS, &ulFlag,
+ sizeof(unsigned long),NULL, 0, NULL, NULL, NULL);
+
+ if (rcode) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, APLOGNO(02127)
+ "Error: %d with WSAIoctl(SO_TLS_SET_FLAGS, SO_TLS_SERVER)",
+ WSAGetLastError());
+ goto ERR;
+ }
+
+ loc2uni(UNI_LOCAL_DEFAULT, SASKey, key, 0, 0);
+
+ /* setup the tlsserveropts struct */
+ sWS2Opts.wallet = SASKey;
+ sWS2Opts.walletlen = unilen(SASKey);
+ sWS2Opts.sidtimeout = 0;
+ sWS2Opts.sidentries = 0;
+ sWS2Opts.siddir = NULL;
+ sWS2Opts.options = &sNWTLSOpts;
+
+ /* setup the nwtlsopts structure */
+
+ sNWTLSOpts.walletProvider = WAL_PROV_KMO;
+ sNWTLSOpts.keysList = NULL;
+ sNWTLSOpts.numElementsInKeyList = 0;
+ sNWTLSOpts.reservedforfutureuse = NULL;
+ sNWTLSOpts.reservedforfutureCRL = NULL;
+ sNWTLSOpts.reservedforfutureCRLLen = 0;
+ sNWTLSOpts.reserved1 = NULL;
+ sNWTLSOpts.reserved2 = NULL;
+ sNWTLSOpts.reserved3 = NULL;
+
+ rcode = WSAIoctl(socketHnd,
+ SO_TLS_SET_SERVER,
+ &sWS2Opts,
+ sizeof(struct tlsserveropts),
+ NULL,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+ if (SOCKET_ERROR == rcode) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, APLOGNO(02128)
+ "Error: %d with WSAIoctl(SO_TLS_SET_SERVER)", WSAGetLastError());
+ goto ERR;
+ }
+
+ERR:
+ return rcode;
+}
+
+static const char *set_secure_listener(cmd_parms *cmd, void *dummy,
+ const char *ips, const char* key,
+ const char* mutual)
+{
+ NWSSLSrvConfigRec* sc = get_nwssl_cfg(cmd->server);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ char *ports, *addr;
+ unsigned short port;
+ seclisten_rec *new;
+ ap_listen_rec **walk;
+ apr_sockaddr_t *sa;
+ int found_listener = 0;
+
+
+ if (err != NULL)
+ return err;
+
+ ports = strchr(ips, ':');
+
+ if (ports != NULL) {
+ if (ports == ips)
+ return "Missing IP address";
+ else if (ports[1] == '\0')
+ return "Address must end in :<port-number>";
+
+ *(ports++) = '\0';
+ }
+ else {
+ ports = (char*)ips;
+ }
+
+ new = apr_pcalloc(cmd->server->process->pool, sizeof(seclisten_rec));
+ new->local_addr.sin_family = AF_INET;
+
+ if (ports == ips) {
+ new->local_addr.sin_addr.s_addr = htonl(INADDR_ANY);
+ addr = apr_pstrdup(cmd->server->process->pool, "0.0.0.0");
+ }
+ else {
+ new->local_addr.sin_addr.s_addr = parse_addr(ips, NULL);
+ addr = apr_pstrdup(cmd->server->process->pool, ips);
+ }
+
+ port = atoi(ports);
+
+ if (!port)
+ return "Port must be numeric";
+
+ /* If the specified addr:port was created previously, put the listen
+ socket record back on the ap_listeners list so that the socket
+ will be reused rather than recreated */
+ for (walk = &nw_old_listeners; *walk;) {
+ sa = (*walk)->bind_addr;
+ if (sa) {
+ ap_listen_rec *new;
+ apr_port_t oldport;
+
+ oldport = sa->port;
+ /* If both ports are equivalent, then if their names are equivalent,
+ * then we will re-use the existing record.
+ */
+ if (port == oldport &&
+ ((!addr && !sa->hostname) ||
+ ((addr && sa->hostname) && !strcmp(sa->hostname, addr)))) {
+ new = *walk;
+ *walk = new->next;
+ new->next = ap_listeners;
+ ap_listeners = new;
+ found_listener = 1;
+ continue;
+ }
+ }
+
+ walk = &(*walk)->next;
+ }
+
+ apr_table_add(sc->sltable, ports, addr);
+
+ /* If we found a pre-existing listen socket record, then there
+ is no need to create a new secure listen socket record. */
+ if (found_listener) {
+ return NULL;
+ }
+
+ new->local_addr.sin_port = htons(port);
+ new->fd = -1;
+ new->used = 0;
+ new->next = ap_seclisteners;
+ strcpy(new->key, key);
+ new->mutual = (mutual) ? 1 : 0;
+ new->addr = addr;
+ new->port = port;
+ ap_seclisteners = new;
+ return NULL;
+}
+
+static const char *set_secure_upgradeable_listener(cmd_parms *cmd, void *dummy,
+ const char *ips, const char* key)
+{
+ NWSSLSrvConfigRec* sc = get_nwssl_cfg(cmd->server);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ char *ports, *addr;
+ unsigned short port;
+ seclistenup_rec *new;
+
+ if (err != NULL)
+ return err;
+
+ ports = strchr(ips, ':');
+
+ if (ports != NULL) {
+ if (ports == ips)
+ return "Missing IP address";
+ else if (ports[1] == '\0')
+ return "Address must end in :<port-number>";
+
+ *(ports++) = '\0';
+ }
+ else {
+ ports = (char*)ips;
+ }
+
+ if (ports == ips) {
+ addr = apr_pstrdup(cmd->pool, "0.0.0.0");
+ }
+ else {
+ addr = apr_pstrdup(cmd->pool, ips);
+ }
+
+ port = atoi(ports);
+
+ if (!port)
+ return "Port must be numeric";
+
+ apr_table_set(sc->slutable, ports, addr);
+
+ new = apr_pcalloc(cmd->pool, sizeof(seclistenup_rec));
+ new->next = ap_seclistenersup;
+ strcpy(new->key, key);
+ new->addr = addr;
+ new->port = port;
+ ap_seclistenersup = new;
+
+ return err;
+}
+
+static apr_status_t nwssl_socket_cleanup(void *data)
+{
+ ap_listen_rec* slr = (ap_listen_rec*)data;
+ ap_listen_rec* lr;
+
+ /* Remove our secure listener from the listener list */
+ for (lr = ap_listeners; lr; lr = lr->next) {
+ /* slr is at the head of the list */
+ if (lr == slr) {
+ ap_listeners = slr->next;
+ break;
+ }
+ /* slr is somewhere in between or at the end*/
+ if (lr->next == slr) {
+ lr->next = slr->next;
+ break;
+ }
+ }
+ return APR_SUCCESS;
+}
+
+static const char *set_trusted_certs(cmd_parms *cmd, void *dummy, char *arg)
+{
+ char **ptr = (char **)apr_array_push(certlist);
+
+ *ptr = arg;
+ return NULL;
+}
+
+static int nwssl_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp)
+{
+ seclisten_rec* ap_old_seclisteners;
+ ap_listen_rec **walk;
+ seclisten_rec **secwalk;
+ apr_sockaddr_t *sa;
+ int found;
+
+ /* Pull all of the listeners that were created by mod_nw_ssl out of the
+ ap_listeners list so that the normal listen socket processing does
+ automatically close them */
+ nw_old_listeners = NULL;
+ ap_old_seclisteners = NULL;
+
+ for (secwalk = &ap_seclisteners; *secwalk;) {
+ found = 0;
+ for (walk = &ap_listeners; *walk;) {
+ sa = (*walk)->bind_addr;
+ if (sa) {
+ ap_listen_rec *new;
+ seclisten_rec *secnew;
+ apr_port_t oldport;
+
+ oldport = sa->port;
+ /* If both ports are equivalent, then if their names are equivalent,
+ * then we will re-use the existing record.
+ */
+ if ((*secwalk)->port == oldport &&
+ ((!(*secwalk)->addr && !sa->hostname) ||
+ (((*secwalk)->addr && sa->hostname) && !strcmp(sa->hostname, (*secwalk)->addr)))) {
+ /* Move the listen socket from ap_listeners to nw_old_listeners */
+ new = *walk;
+ *walk = new->next;
+ new->next = nw_old_listeners;
+ nw_old_listeners = new;
+
+ /* Move the secure socket record to ap_old_seclisterners */
+ secnew = *secwalk;
+ *secwalk = secnew->next;
+ secnew->next = ap_old_seclisteners;
+ ap_old_seclisteners = secnew;
+ found = 1;
+ break;
+ }
+ }
+
+ walk = &(*walk)->next;
+ }
+ if (!found && &(*secwalk)->next) {
+ secwalk = &(*secwalk)->next;
+ }
+ }
+
+ /* Restore the secure socket records list so that the post config can
+ process all of the sockets normally */
+ ap_seclisteners = ap_old_seclisteners;
+ ap_seclistenersup = NULL;
+ certlist = apr_array_make(pconf, 1, sizeof(char *));
+
+ /* Now that we have removed all of the mod_nw_ssl created socket records,
+ allow the normal listen socket handling to occur.
+ NOTE: If for any reason mod_nw_ssl is removed as a built-in module,
+ the following call must be put back into the pre-config handler of the
+ MPM. It is only here to ensure that mod_nw_ssl fixes up the listen
+ socket list before anything else looks at it. */
+ ap_listen_pre_config();
+
+ return OK;
+}
+
+static int nwssl_pre_connection(conn_rec *c, void *csd)
+{
+
+ if (apr_table_get(c->notes, "nwconv-ssl")) {
+ convert_secure_socket(c, (apr_socket_t*)csd);
+ }
+ else {
+ secsocket_data *csd_data = apr_palloc(c->pool, sizeof(secsocket_data));
+
+ csd_data->csd = (apr_socket_t*)csd;
+ csd_data->is_secure = 0;
+ ap_set_module_config(c->conn_config, &nwssl_module, (void*)csd_data);
+ }
+
+ return OK;
+}
+
+static int nwssl_post_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ seclisten_rec* sl;
+ ap_listen_rec* lr;
+ apr_socket_t* sd;
+ apr_status_t status;
+ seclistenup_rec *slu;
+ int found;
+ ap_listen_rec *walk;
+ seclisten_rec *secwalk, *lastsecwalk;
+ apr_sockaddr_t *sa;
+
+ /* Walk the old listeners list and compare it to the secure
+ listeners list and remove any secure listener records that
+ are not being reused */
+ for (walk = nw_old_listeners; walk; walk = walk->next) {
+ sa = walk->bind_addr;
+ if (sa) {
+ ap_listen_rec *new;
+ apr_port_t oldport;
+
+ oldport = sa->port;
+ for (secwalk = ap_seclisteners, lastsecwalk = ap_seclisteners; secwalk; secwalk = lastsecwalk->next) {
+ unsigned short port = secwalk->port;
+ char *addr = secwalk->addr;
+ /* If both ports are equivalent, then if their names are equivalent,
+ * then we will re-use the existing record.
+ */
+ if (port == oldport &&
+ ((!addr && !sa->hostname) ||
+ ((addr && sa->hostname) && !strcmp(sa->hostname, addr)))) {
+ if (secwalk == ap_seclisteners) {
+ ap_seclisteners = secwalk->next;
+ }
+ else {
+ lastsecwalk->next = secwalk->next;
+ }
+ apr_socket_close(walk->sd);
+ walk->active = 0;
+ break;
+ }
+ else {
+ lastsecwalk = secwalk;
+ }
+ }
+ }
+ }
+
+ for (sl = ap_seclisteners; sl != NULL; sl = sl->next) {
+ /* If we find a pre-existing listen socket and it has already been
+ created, then no need to go any further, just reuse it. */
+ if (((sl->fd = find_secure_listener(sl)) >= 0) && (sl->used)) {
+ continue;
+ }
+
+ if (sl->fd < 0)
+ sl->fd = make_secure_socket(s->process->pool, &sl->local_addr, sl->key, sl->mutual, s);
+
+ if (sl->fd >= 0) {
+ apr_os_sock_info_t sock_info;
+
+ sock_info.os_sock = &(sl->fd);
+ sock_info.local = (struct sockaddr*)&(sl->local_addr);
+ sock_info.remote = NULL;
+ sock_info.family = APR_INET;
+ sock_info.type = SOCK_STREAM;
+
+ apr_os_sock_make(&sd, &sock_info, s->process->pool);
+
+ lr = apr_pcalloc(s->process->pool, sizeof(ap_listen_rec));
+
+ if (lr) {
+ lr->sd = sd;
+ if ((status = apr_sockaddr_info_get(&lr->bind_addr, sl->addr, APR_UNSPEC, sl->port, 0,
+ s->process->pool)) != APR_SUCCESS) {
+ ap_log_perror(APLOG_MARK, APLOG_CRIT, status, pconf, APLOGNO(02129)
+ "alloc_listener: failed to set up sockaddr for %s:%d", sl->addr, sl->port);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ lr->next = ap_listeners;
+ ap_listeners = lr;
+ apr_pool_cleanup_register(s->process->pool, lr, nwssl_socket_cleanup, apr_pool_cleanup_null);
+ }
+ } else {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ for (slu = ap_seclistenersup; slu; slu = slu->next) {
+ /* Check the listener list for a matching upgradeable listener */
+ found = 0;
+ for (lr = ap_listeners; lr; lr = lr->next) {
+ if (slu->port == lr->bind_addr->port) {
+ found = 1;
+ break;
+ }
+ }
+ if (!found) {
+ ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, plog, APLOGNO(02130)
+ "No Listen directive found for upgradeable listener %s:%d", slu->addr, slu->port);
+ }
+ }
+
+ build_cert_list(s->process->pool);
+
+ return OK;
+}
+
+static void *nwssl_config_server_create(apr_pool_t *p, server_rec *s)
+{
+ NWSSLSrvConfigRec *new = apr_palloc(p, sizeof(NWSSLSrvConfigRec));
+ new->sltable = apr_table_make(p, 5);
+ new->slutable = apr_table_make(p, 5);
+ return new;
+}
+
+static void *nwssl_config_server_merge(apr_pool_t *p, void *basev, void *addv)
+{
+ NWSSLSrvConfigRec *base = (NWSSLSrvConfigRec *)basev;
+ NWSSLSrvConfigRec *add = (NWSSLSrvConfigRec *)addv;
+ NWSSLSrvConfigRec *merged = (NWSSLSrvConfigRec *)apr_palloc(p, sizeof(NWSSLSrvConfigRec));
+ return merged;
+}
+
+static int compare_ipports(void *rec, const char *key, const char *value)
+{
+ conn_rec *c = (conn_rec*)rec;
+
+ if (value &&
+ ((strcmp(value, "0.0.0.0") == 0) || (strcmp(value, c->local_ip) == 0)))
+ {
+ return 0;
+ }
+ return 1;
+}
+
+static int isSecureConnEx (const server_rec *s, const conn_rec *c, const apr_table_t *t)
+{
+ char port[8];
+
+ itoa((c->local_addr)->port, port, 10);
+ if (!apr_table_do(compare_ipports, (void*)c, t, port, NULL)) {
+ return 1;
+ }
+
+ return 0;
+}
+
+static int isSecureConn (const server_rec *s, const conn_rec *c)
+{
+ NWSSLSrvConfigRec *sc = get_nwssl_cfg(s);
+
+ return isSecureConnEx (s, c, sc->sltable);
+}
+
+static int isSecureConnUpgradeable (const server_rec *s, const conn_rec *c)
+{
+ NWSSLSrvConfigRec *sc = get_nwssl_cfg(s);
+
+ return isSecureConnEx (s, c, sc->slutable);
+}
+
+static int isSecure (const request_rec *r)
+{
+ return isSecureConn (r->server, r->connection);
+}
+
+static int isSecureUpgradeable (const request_rec *r)
+{
+ return isSecureConnUpgradeable (r->server, r->connection);
+}
+
+static int isSecureUpgraded (const request_rec *r)
+{
+ secsocket_data *csd_data = (secsocket_data*)ap_get_module_config(r->connection->conn_config, &nwssl_module);
+
+ return csd_data->is_secure;
+}
+
+static int nwssl_hook_Fixup(request_rec *r)
+{
+ if (!isSecure(r) && !isSecureUpgraded(r))
+ return DECLINED;
+
+ apr_table_setn(r->subprocess_env, "HTTPS", "on");
+
+ return DECLINED;
+}
+
+static const char *nwssl_hook_http_scheme(const request_rec *r)
+{
+ if (isSecure(r) && !isSecureUpgraded(r))
+ return "https";
+
+ return NULL;
+}
+
+static apr_port_t nwssl_hook_default_port(const request_rec *r)
+{
+ if (isSecure(r))
+ return DEFAULT_HTTPS_PORT;
+
+ return 0;
+}
+
+int ssl_proxy_enable(conn_rec *c)
+{
+ apr_table_setn(c->notes, "nwconv-ssl", "Y");
+
+ return 1;
+}
+
+int ssl_engine_disable(conn_rec *c)
+{
+ return 1;
+}
+
+static int ssl_is_https(conn_rec *c)
+{
+ secsocket_data *csd_data = (secsocket_data*)ap_get_module_config(c->conn_config, &nwssl_module);
+
+ return isSecureConn (c->base_server, c) || (csd_data && csd_data->is_secure);
+}
+
+/* This function must remain safe to use for a non-SSL connection. */
+char *ssl_var_lookup(apr_pool_t *p, server_rec *s, conn_rec *c, request_rec *r, char *var)
+{
+ NWSSLSrvConfigRec *mc = get_nwssl_cfg(s);
+ const char *result;
+ BOOL resdup;
+ apr_time_exp_t tm;
+
+ result = NULL;
+ resdup = TRUE;
+
+ /*
+ * When no pool is given try to find one
+ */
+ if (p == NULL) {
+ if (r != NULL)
+ p = r->pool;
+ else if (c != NULL)
+ p = c->pool;
+ else
+ p = mc->pPool;
+ }
+
+ /*
+ * Request dependent stuff
+ */
+ if (r != NULL) {
+ switch (var[0]) {
+ case 'H':
+ case 'h':
+ if (strcEQ(var, "HTTP_USER_AGENT"))
+ result = apr_table_get(r->headers_in, "User-Agent");
+ else if (strcEQ(var, "HTTP_REFERER"))
+ result = apr_table_get(r->headers_in, "Referer");
+ else if (strcEQ(var, "HTTP_COOKIE"))
+ result = apr_table_get(r->headers_in, "Cookie");
+ else if (strcEQ(var, "HTTP_FORWARDED"))
+ result = apr_table_get(r->headers_in, "Forwarded");
+ else if (strcEQ(var, "HTTP_HOST"))
+ result = apr_table_get(r->headers_in, "Host");
+ else if (strcEQ(var, "HTTP_PROXY_CONNECTION"))
+ result = apr_table_get(r->headers_in, "Proxy-Connection");
+ else if (strcEQ(var, "HTTP_ACCEPT"))
+ result = apr_table_get(r->headers_in, "Accept");
+ else if (strcEQ(var, "HTTPS")) {
+ if (isSecure(r) || isSecureUpgraded(r))
+ result = "on";
+ else
+ result = "off";
+ }
+ else if (strlen(var) > 5 && strcEQn(var, "HTTP:", 5))
+ /* all other headers from which we are still not know about */
+ result = apr_table_get(r->headers_in, var+5);
+ break;
+
+ case 'R':
+ case 'r':
+ if (strcEQ(var, "REQUEST_METHOD"))
+ result = r->method;
+ else if (strcEQ(var, "REQUEST_SCHEME"))
+ result = ap_http_scheme(r);
+ else if (strcEQ(var, "REQUEST_URI"))
+ result = r->uri;
+ else if (strcEQ(var, "REQUEST_FILENAME"))
+ result = r->filename;
+ else if (strcEQ(var, "REMOTE_ADDR"))
+ result = r->useragent_ip;
+ else if (strcEQ(var, "REMOTE_HOST"))
+ result = ap_get_useragent_host(r, REMOTE_NAME, NULL);
+ else if (strcEQ(var, "REMOTE_IDENT"))
+ result = ap_get_remote_logname(r);
+ else if (strcEQ(var, "REMOTE_USER"))
+ result = r->user;
+ break;
+
+ case 'S':
+ case 's':
+ if (strcEQn(var, "SSL", 3)) break; /* shortcut common case */
+
+ if (strcEQ(var, "SERVER_ADMIN"))
+ result = r->server->server_admin;
+ else if (strcEQ(var, "SERVER_NAME"))
+ result = ap_get_server_name_for_url(r);
+ else if (strcEQ(var, "SERVER_PORT"))
+ result = apr_psprintf(p, "%u", ap_get_server_port(r));
+ else if (strcEQ(var, "SERVER_PROTOCOL"))
+ result = r->protocol;
+ else if (strcEQ(var, "SCRIPT_FILENAME"))
+ result = r->filename;
+ break;
+
+ default:
+ if (strcEQ(var, "PATH_INFO"))
+ result = r->path_info;
+ else if (strcEQ(var, "QUERY_STRING"))
+ result = r->args;
+ else if (strcEQ(var, "IS_SUBREQ"))
+ result = (r->main != NULL ? "true" : "false");
+ else if (strcEQ(var, "DOCUMENT_ROOT"))
+ result = ap_document_root(r);
+ else if (strcEQ(var, "AUTH_TYPE"))
+ result = r->ap_auth_type;
+ else if (strcEQ(var, "THE_REQUEST"))
+ result = r->the_request;
+ break;
+ }
+ }
+
+ /*
+ * Connection stuff
+ */
+ if (result == NULL && c != NULL) {
+ /* XXX-Can't get specific SSL info from NetWare */
+ /* SSLConnRec *sslconn = myConnConfig(c);
+ if (strlen(var) > 4 && strcEQn(var, "SSL_", 4)
+ && sslconn && sslconn->ssl)
+ result = ssl_var_lookup_ssl(p, c, var+4);*/
+
+ if (strlen(var) > 4 && strcEQn(var, "SSL_", 4))
+ result = NULL;
+ }
+
+ /*
+ * Totally independent stuff
+ */
+ if (result == NULL) {
+ if (strlen(var) > 12 && strcEQn(var, "SSL_VERSION_", 12))
+ result = NULL;
+ /* XXX-Can't get specific SSL info from NetWare */
+ /*result = ssl_var_lookup_ssl_version(p, var+12);*/
+ else if (strcEQ(var, "SERVER_SOFTWARE"))
+ result = ap_get_server_banner();
+ else if (strcEQ(var, "API_VERSION")) {
+ result = apr_itoa(p, MODULE_MAGIC_NUMBER_MAJOR);
+ resdup = FALSE;
+ }
+ else if (strcEQ(var, "TIME_YEAR")) {
+ apr_time_exp_lt(&tm, apr_time_now());
+ result = apr_psprintf(p, "%02d%02d",
+ (tm.tm_year / 100) + 19, tm.tm_year % 100);
+ resdup = FALSE;
+ }
+#define MKTIMESTR(format, tmfield) \
+ apr_time_exp_lt(&tm, apr_time_now()); \
+ result = apr_psprintf(p, format, tm.tmfield); \
+ resdup = FALSE;
+ else if (strcEQ(var, "TIME_MON")) {
+ MKTIMESTR("%02d", tm_mon+1)
+ }
+ else if (strcEQ(var, "TIME_DAY")) {
+ MKTIMESTR("%02d", tm_mday)
+ }
+ else if (strcEQ(var, "TIME_HOUR")) {
+ MKTIMESTR("%02d", tm_hour)
+ }
+ else if (strcEQ(var, "TIME_MIN")) {
+ MKTIMESTR("%02d", tm_min)
+ }
+ else if (strcEQ(var, "TIME_SEC")) {
+ MKTIMESTR("%02d", tm_sec)
+ }
+ else if (strcEQ(var, "TIME_WDAY")) {
+ MKTIMESTR("%d", tm_wday)
+ }
+ else if (strcEQ(var, "TIME")) {
+ apr_time_exp_lt(&tm, apr_time_now());
+ result = apr_psprintf(p,
+ "%02d%02d%02d%02d%02d%02d%02d", (tm.tm_year / 100) + 19,
+ (tm.tm_year % 100), tm.tm_mon+1, tm.tm_mday,
+ tm.tm_hour, tm.tm_min, tm.tm_sec);
+ resdup = FALSE;
+ }
+ /* all other env-variables from the parent Apache process */
+ else if (strlen(var) > 4 && strcEQn(var, "ENV:", 4)) {
+ result = apr_table_get(r->notes, var+4);
+ if (result == NULL)
+ result = apr_table_get(r->subprocess_env, var+4);
+ if (result == NULL)
+ result = getenv(var+4);
+ }
+ }
+
+ if (result != NULL && resdup)
+ result = apr_pstrdup(p, result);
+ if (result == NULL)
+ result = "";
+ return (char *)result;
+}
+
+#define SWITCH_STATUS_LINE "HTTP/1.1 101 Switching Protocols"
+#define UPGRADE_HEADER "Upgrade: TLS/1.0, HTTP/1.1"
+#define CONNECTION_HEADER "Connection: Upgrade"
+
+static apr_status_t ssl_io_filter_Upgrade(ap_filter_t *f,
+ apr_bucket_brigade *bb)
+
+{
+ const char *upgrade;
+ apr_bucket_brigade *upgradebb;
+ request_rec *r = f->r;
+ apr_socket_t *csd = NULL;
+ char *key;
+ int ret;
+ secsocket_data *csd_data;
+ apr_bucket *b;
+ apr_status_t rv;
+
+ /* Just remove the filter, if it doesn't work the first time, it won't
+ * work at all for this request.
+ */
+ ap_remove_output_filter(f);
+
+ if (!r) {
+ /*
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, APLOGNO(02131)
+ "Unable to get upgradeable socket handle");
+ */
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ /* No need to ensure that this is a server with optional SSL, the filter
+ * is only inserted if that is true.
+ */
+
+ upgrade = apr_table_get(r->headers_in, "Upgrade");
+ if (upgrade == NULL
+ || strcmp(ap_getword(r->pool, &upgrade, ','), "TLS/1.0")) {
+ /* "Upgrade: TLS/1.0, ..." header not found, don't do Upgrade */
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ apr_table_unset(r->headers_out, "Upgrade");
+
+ csd_data = (secsocket_data*)ap_get_module_config(r->connection->conn_config, &nwssl_module);
+ csd = csd_data->csd;
+
+ /* Send the interim 101 response. */
+ upgradebb = apr_brigade_create(r->pool, f->c->bucket_alloc);
+
+ ap_fputs(f->next, upgradebb, SWITCH_STATUS_LINE CRLF
+ UPGRADE_HEADER CRLF CONNECTION_HEADER CRLF CRLF);
+
+ b = apr_bucket_flush_create(f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(upgradebb, b);
+
+ rv = ap_pass_brigade(f->next, upgradebb);
+ if (rv) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02132)
+ "could not send interim 101 Upgrade response");
+ return AP_FILTER_ERROR;
+ }
+
+ key = get_port_key(r->connection);
+
+ if (csd && key) {
+ int sockdes;
+ apr_os_sock_get(&sockdes, csd);
+
+
+ ret = SSLize_Socket(sockdes, key, r);
+ if (!ret) {
+ csd_data->is_secure = 1;
+ }
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, APLOGNO(02133)
+ "Upgradeable socket handle not found");
+ return AP_FILTER_ERROR;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server, APLOGNO(02134)
+ "Awaiting re-negotiation handshake");
+
+ /* Now that we have initialized the ssl connection which added the ssl_io_filter,
+ pass the brigade off to the connection based output filters so that the
+ request can complete encrypted */
+ return ap_pass_brigade(f->c->output_filters, bb);
+}
+
+static void ssl_hook_Insert_Filter(request_rec *r)
+{
+ NWSSLSrvConfigRec *sc = get_nwssl_cfg(r->server);
+
+ if (isSecureUpgradeable (r)) {
+ ap_add_output_filter("UPGRADE_FILTER", NULL, r, r->connection);
+ }
+}
+
+static const command_rec nwssl_module_cmds[] =
+{
+ AP_INIT_TAKE23("SecureListen", set_secure_listener, NULL, RSRC_CONF,
+ "specify an address and/or port with a key pair name.\n"
+ "Optional third parameter of MUTUAL configures the port for mutual authentication."),
+ AP_INIT_TAKE2("NWSSLUpgradeable", set_secure_upgradeable_listener, NULL, RSRC_CONF,
+ "specify an address and/or port with a key pair name, that can be upgraded to an SSL connection.\n"
+ "The address and/or port must have already be defined using a Listen directive."),
+ AP_INIT_ITERATE("NWSSLTrustedCerts", set_trusted_certs, NULL, RSRC_CONF,
+ "Adds trusted certificates that are used to create secure connections to proxied servers"),
+ {NULL}
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_register_output_filter ("UPGRADE_FILTER", ssl_io_filter_Upgrade, NULL, AP_FTYPE_PROTOCOL + 5);
+
+ ap_hook_pre_config(nwssl_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_pre_connection(nwssl_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_post_config(nwssl_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_fixups(nwssl_hook_Fixup, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_http_scheme(nwssl_hook_http_scheme, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_default_port(nwssl_hook_default_port, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_insert_filter(ssl_hook_Insert_Filter, NULL, NULL, APR_HOOK_MIDDLE);
+
+ APR_REGISTER_OPTIONAL_FN(ssl_is_https);
+ APR_REGISTER_OPTIONAL_FN(ssl_var_lookup);
+
+ APR_REGISTER_OPTIONAL_FN(ssl_proxy_enable);
+ APR_REGISTER_OPTIONAL_FN(ssl_engine_disable);
+}
+
+AP_DECLARE_MODULE(nwssl) =
+{
+ STANDARD20_MODULE_STUFF,
+ NULL, /* dir config creater */
+ NULL, /* dir merger --- default is to override */
+ nwssl_config_server_create, /* server config */
+ nwssl_config_server_merge, /* merge server config */
+ nwssl_module_cmds, /* command apr_table_t */
+ register_hooks
+};
+
diff --git a/modules/arch/unix/Makefile.in b/modules/arch/unix/Makefile.in
new file mode 100644
index 0000000..167b343
--- /dev/null
+++ b/modules/arch/unix/Makefile.in
@@ -0,0 +1,3 @@
+
+include $(top_srcdir)/build/special.mk
+
diff --git a/modules/arch/unix/config5.m4 b/modules/arch/unix/config5.m4
new file mode 100644
index 0000000..3d099f8
--- /dev/null
+++ b/modules/arch/unix/config5.m4
@@ -0,0 +1,33 @@
+
+APACHE_MODPATH_INIT(arch/unix)
+
+if ap_mpm_is_enabled "worker" \
+ || ap_mpm_is_enabled "event" \
+ || ap_mpm_is_enabled "prefork"; then
+ unixd_mods_enable=yes
+else
+ unixd_mods_enable=no
+fi
+
+APACHE_MODULE(unixd, unix specific support, , , $unixd_mods_enable)
+APACHE_MODULE(privileges, Per-virtualhost Unix UserIDs and enhanced security for Solaris, , , no, [
+ AC_CHECK_HEADERS(priv.h, [ap_HAVE_PRIV_H="yes"], [ap_HAVE_PRIV_H="no"])
+ if test $ap_HAVE_PRIV_H = "no"; then
+ AC_MSG_WARN([Your system does not support privileges.])
+ enable_privileges="no"
+ fi
+])
+
+APACHE_MODULE(systemd, Systemd support, , , no, [
+ if test "${ac_cv_header_systemd_sd_daemon_h}" = "no" || test -z "${SYSTEMD_LIBS}"; then
+ AC_MSG_WARN([Your system does not support systemd.])
+ enable_systemd="no"
+ else
+ APR_ADDTO(MOD_SYSTEMD_LDADD, [$SYSTEMD_LIBS])
+ fi
+])
+
+APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/$modpath_current])
+
+APACHE_MODPATH_FINISH
+
diff --git a/modules/arch/unix/mod_privileges.c b/modules/arch/unix/mod_privileges.c
new file mode 100644
index 0000000..fede3d8
--- /dev/null
+++ b/modules/arch/unix/mod_privileges.c
@@ -0,0 +1,588 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <priv.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_protocol.h"
+#include "http_log.h"
+#include "mpm_common.h"
+#include "ap_mpm.h"
+#include "apr_strings.h"
+
+/* TODO - get rid of unixd dependency */
+#include "unixd.h"
+
+#define CFG_CHECK(x) if ((x) == -1) { \
+ char msgbuf[128]; \
+ apr_strerror(errno, msgbuf, sizeof(msgbuf)); \
+ return apr_pstrdup(cmd->pool, msgbuf); \
+}
+#define CR_CHECK(x, y) if (x == -1) \
+ ap_log_error(APLOG_MARK, APLOG_CRIT, errno, 0, y \
+ "Failed to initialise privileges")
+
+module AP_MODULE_DECLARE_DATA privileges_module;
+
+/* #define BIG_SECURITY_HOLE 1 */
+
+typedef enum { PRIV_UNSET, PRIV_FAST, PRIV_SECURE, PRIV_SELECTIVE } priv_mode;
+
+typedef struct {
+ priv_set_t *priv;
+ priv_set_t *child_priv;
+ uid_t uid;
+ gid_t gid;
+ priv_mode mode;
+} priv_cfg;
+
+typedef struct {
+ priv_mode mode;
+} priv_dir_cfg;
+
+static priv_set_t *priv_setid;
+static priv_set_t *priv_default = NULL;
+static int dtrace_enabled = 0;
+
+static apr_status_t priv_cfg_cleanup(void *CFG)
+{
+ priv_cfg *cfg = CFG;
+ priv_freeset(cfg->priv);
+ priv_freeset(cfg->child_priv);
+ return APR_SUCCESS;
+}
+static void *privileges_merge_cfg(apr_pool_t *pool, void *BASE, void *ADD)
+{
+ /* inherit the mode if it's not set; the rest won't be inherited */
+ priv_cfg *base = BASE;
+ priv_cfg *add = ADD;
+ priv_cfg *ret = apr_pmemdup(pool, add, sizeof(priv_cfg));
+ ret->mode = (add->mode == PRIV_UNSET) ? base->mode : add->mode;
+ return ret;
+}
+static void *privileges_create_cfg(apr_pool_t *pool, server_rec *s)
+{
+ priv_cfg *cfg = apr_palloc(pool, sizeof(priv_cfg));
+
+ /* Start at basic privileges all round. */
+ cfg->priv = priv_str_to_set("basic", ",", NULL);
+ cfg->child_priv = priv_str_to_set("basic", ",", NULL);
+
+ /* By default, run in secure vhost mode.
+ * That means dropping basic privileges we don't usually need.
+ */
+ CR_CHECK(priv_delset(cfg->priv, PRIV_FILE_LINK_ANY), APLOGNO(03160));
+ CR_CHECK(priv_delset(cfg->priv, PRIV_PROC_INFO), APLOGNO(03161));
+ CR_CHECK(priv_delset(cfg->priv, PRIV_PROC_SESSION), APLOGNO(03162));
+
+/* Hmmm, should CGI default to secure too ? */
+/*
+ CR_CHECK(priv_delset(cfg->child_priv, PRIV_FILE_LINK_ANY), APLOGNO(03163));
+ CR_CHECK(priv_delset(cfg->child_priv, PRIV_PROC_INFO), APLOGNO(03164));
+ CR_CHECK(priv_delset(cfg->child_priv, PRIV_PROC_SESSION), APLOGNO(03165));
+ CR_CHECK(priv_delset(cfg->child_priv, PRIV_PROC_FORK), APLOGNO(03166));
+ CR_CHECK(priv_delset(cfg->child_priv, PRIV_PROC_EXEC), APLOGNO(03167));
+*/
+
+ /* we´ll use 0 for unset */
+ cfg->uid = 0;
+ cfg->gid = 0;
+ cfg->mode = PRIV_UNSET;
+ apr_pool_cleanup_register(pool, cfg, priv_cfg_cleanup,
+ apr_pool_cleanup_null);
+
+ /* top-level default_priv wants the top-level cfg */
+ if (priv_default == NULL) {
+ priv_default = cfg->priv;
+ }
+ return cfg;
+}
+static void *privileges_create_dir_cfg(apr_pool_t *pool, char *dummy)
+{
+ priv_dir_cfg *cfg = apr_palloc(pool, sizeof(priv_dir_cfg));
+ cfg->mode = PRIV_UNSET;
+ return cfg;
+}
+static void *privileges_merge_dir_cfg(apr_pool_t *pool, void *BASE, void *ADD)
+{
+ priv_dir_cfg *base = BASE;
+ priv_dir_cfg *add = ADD;
+ priv_dir_cfg *ret = apr_palloc(pool, sizeof(priv_dir_cfg));
+ ret->mode = (add->mode == PRIV_UNSET) ? base->mode : add->mode;
+ return ret;
+}
+
+static apr_status_t privileges_end_req(void *data)
+{
+ request_rec *r = data;
+ priv_cfg *cfg = ap_get_module_config(r->server->module_config,
+ &privileges_module);
+ priv_dir_cfg *dcfg = ap_get_module_config(r->per_dir_config,
+ &privileges_module);
+
+ /* ugly hack: grab default uid and gid from unixd */
+ extern unixd_config_rec ap_unixd_config;
+
+ /* If we forked a child, we dropped privilege to revert, so
+ * all we can do now is exit
+ */
+ if ((cfg->mode == PRIV_SECURE) ||
+ ((cfg->mode == PRIV_SELECTIVE) && (dcfg->mode == PRIV_SECURE))) {
+ exit(0);
+ }
+
+ /* if either user or group are not the default, restore them */
+ if (cfg->uid || cfg->gid) {
+ if (setppriv(PRIV_ON, PRIV_EFFECTIVE, priv_setid) == -1) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02136)
+ "PRIV_ON failed restoring default user/group");
+ }
+ if (cfg->uid && (setuid(ap_unixd_config.user_id) == -1)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02137)
+ "Error restoring default userid");
+ }
+ if (cfg->gid && (setgid(ap_unixd_config.group_id) == -1)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02138)
+ "Error restoring default group");
+ }
+ }
+
+ /* restore default privileges */
+ if (setppriv(PRIV_SET, PRIV_EFFECTIVE, priv_default) == -1) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, APLOGNO(02139)
+ "Error restoring default privileges");
+ }
+ return APR_SUCCESS;
+}
+static int privileges_req(request_rec *r)
+{
+ /* secure mode: fork a process to handle the request */
+ apr_proc_t proc;
+ apr_status_t rv;
+ int exitcode;
+ apr_exit_why_e exitwhy;
+ int fork_req;
+ priv_cfg *cfg = ap_get_module_config(r->server->module_config,
+ &privileges_module);
+
+ void *breadcrumb = ap_get_module_config(r->request_config,
+ &privileges_module);
+
+ if (!breadcrumb) {
+ /* first call: this is the vhost */
+ fork_req = (cfg->mode == PRIV_SECURE);
+
+ /* set breadcrumb */
+ ap_set_module_config(r->request_config, &privileges_module, &cfg->mode);
+
+ /* If we have per-dir config, defer doing anything */
+ if ((cfg->mode == PRIV_SELECTIVE)) {
+ /* Defer dropping privileges 'til we have a directory
+ * context that'll tell us whether to fork.
+ */
+ return DECLINED;
+ }
+ }
+ else {
+ /* second call is for per-directory. */
+ priv_dir_cfg *dcfg;
+ if ((cfg->mode != PRIV_SELECTIVE)) {
+ /* Our fate was already determined for the vhost -
+ * nothing to do per-directory
+ */
+ return DECLINED;
+ }
+ dcfg = ap_get_module_config(r->per_dir_config, &privileges_module);
+ fork_req = (dcfg->mode == PRIV_SECURE);
+ }
+
+ if (fork_req) {
+ rv = apr_proc_fork(&proc, r->pool);
+ switch (rv) {
+ case APR_INPARENT:
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02140)
+ "parent waiting for child");
+ /* FIXME - does the child need to run synchronously?
+ * esp. if we enable mod_privileges with threaded MPMs?
+ * We do need at least to ensure r outlives the child.
+ */
+ rv = apr_proc_wait(&proc, &exitcode, &exitwhy, APR_WAIT);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02141) "parent: child %s",
+ (rv == APR_CHILD_DONE) ? "done" : "notdone");
+
+ /* The child has taken responsibility for reading all input
+ * and sending all output. So we need to bow right out,
+ * and even abandon "normal" housekeeping.
+ */
+ r->eos_sent = 1;
+ apr_table_unset(r->headers_in, "Content-Type");
+ apr_table_unset(r->headers_in, "Content-Length");
+ /* Testing with ab and 100k requests reveals no nasties
+ * so I infer we're not leaking anything like memory
+ * or file descriptors. That's nice!
+ */
+ return DONE;
+ case APR_INCHILD:
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02142) "In child!");
+ break; /* now we'll drop privileges in the child */
+ default:
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02143)
+ "Failed to fork secure child process!");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ /* OK, now drop privileges. */
+
+ /* cleanup should happen even if something fails part-way through here */
+ apr_pool_cleanup_register(r->pool, r, privileges_end_req,
+ apr_pool_cleanup_null);
+ /* set user and group if configured */
+ if (cfg->uid || cfg->gid) {
+ if (setppriv(PRIV_ON, PRIV_EFFECTIVE, priv_setid) == -1) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02144)
+ "No privilege to set user/group");
+ }
+ /* if we should be able to set these but can't, it could be
+ * a serious security issue. Bail out rather than risk it!
+ */
+ if (cfg->uid && (setuid(cfg->uid) == -1)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02145)
+ "Error setting userid");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ if (cfg->gid && (setgid(cfg->gid) == -1)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02146)
+ "Error setting group");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+ /* set vhost's privileges */
+ if (setppriv(PRIV_SET, PRIV_EFFECTIVE, cfg->priv) == -1) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, APLOGNO(02147)
+ "Error setting effective privileges");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* ... including those of any subprocesses */
+ if (setppriv(PRIV_SET, PRIV_INHERITABLE, cfg->child_priv) == -1) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, APLOGNO(02148)
+ "Error setting inheritable privileges");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ if (setppriv(PRIV_SET, PRIV_LIMIT, cfg->child_priv) == -1) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, APLOGNO(02149)
+ "Error setting limit privileges");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* If we're in a child process, drop down PPERM too */
+ if (fork_req) {
+ if (setppriv(PRIV_SET, PRIV_PERMITTED, cfg->priv) == -1) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, APLOGNO(02150)
+ "Error setting permitted privileges");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ return OK;
+}
+#define PDROP_CHECK(x) if (x == -1) { \
+ ap_log_error(APLOG_MARK, APLOG_CRIT, errno, s, APLOGNO(02151) \
+ "Error dropping privileges"); \
+ return !OK; \
+ }
+
+static int privileges_drop_first(apr_pool_t *pool, server_rec *s)
+{
+ /* We need to set privileges before mod_unixd,
+ * 'cos otherwise setuid will wipe our privilege to do so
+ */
+ priv_cfg *spcfg;
+ server_rec *sp;
+ priv_set_t *ppriv = priv_allocset();
+
+ /* compute ppriv from the union of all the vhosts plus setid */
+ priv_copyset(priv_setid, ppriv);
+ for (sp = s; sp != NULL; sp=sp->next) {
+ spcfg = ap_get_module_config(sp->module_config, &privileges_module);
+ priv_union(spcfg->priv, ppriv);
+ }
+ PDROP_CHECK(setppriv(PRIV_SET, PRIV_PERMITTED, ppriv))
+ PDROP_CHECK(setppriv(PRIV_SET, PRIV_EFFECTIVE, ppriv))
+ priv_freeset(ppriv);
+
+ return OK;
+}
+static int privileges_drop_last(apr_pool_t *pool, server_rec *s)
+{
+ /* Our config stuff has set the privileges we need, so now
+ * we just set them to those of the parent server_rec
+ *
+ * This has to happen after mod_unixd, 'cos mod_unixd needs
+ * privileges we drop here.
+ */
+ priv_cfg *cfg = ap_get_module_config(s->module_config, &privileges_module);
+
+ /* defaults - the default vhost */
+ PDROP_CHECK(setppriv(PRIV_SET, PRIV_LIMIT, cfg->child_priv))
+ PDROP_CHECK(setppriv(PRIV_SET, PRIV_INHERITABLE, cfg->child_priv))
+ PDROP_CHECK(setppriv(PRIV_SET, PRIV_EFFECTIVE, cfg->priv))
+
+ return OK;
+}
+static apr_status_t privileges_term(void *rec)
+{
+ priv_freeset(priv_setid);
+ return APR_SUCCESS;
+}
+static int privileges_postconf(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ priv_cfg *cfg;
+ server_rec *sp;
+
+ /* if we have dtrace enabled, merge it into everything */
+ if (dtrace_enabled) {
+ for (sp = s; sp != NULL; sp = sp->next) {
+ cfg = ap_get_module_config(sp->module_config, &privileges_module);
+ CR_CHECK(priv_addset(cfg->priv, PRIV_DTRACE_KERNEL), APLOGNO(03168));
+ CR_CHECK(priv_addset(cfg->priv, PRIV_DTRACE_PROC), APLOGNO(03169));
+ CR_CHECK(priv_addset(cfg->priv, PRIV_DTRACE_USER), APLOGNO(03170));
+ CR_CHECK(priv_addset(cfg->child_priv, PRIV_DTRACE_KERNEL), APLOGNO(03171));
+ CR_CHECK(priv_addset(cfg->child_priv, PRIV_DTRACE_PROC), APLOGNO(03172));
+ CR_CHECK(priv_addset(cfg->child_priv, PRIV_DTRACE_USER), APLOGNO(03173));
+ }
+ CR_CHECK(priv_addset(priv_default, PRIV_DTRACE_KERNEL), APLOGNO(03174));
+ CR_CHECK(priv_addset(priv_default, PRIV_DTRACE_PROC), APLOGNO(03175));
+ CR_CHECK(priv_addset(priv_default, PRIV_DTRACE_USER), APLOGNO(03176));
+ }
+
+ /* set up priv_setid for per-request use */
+ priv_setid = priv_allocset();
+ apr_pool_cleanup_register(pconf, NULL, privileges_term,
+ apr_pool_cleanup_null);
+ priv_emptyset(priv_setid);
+ if (priv_addset(priv_setid, PRIV_PROC_SETID) == -1) {
+ ap_log_perror(APLOG_MARK, APLOG_CRIT, errno, ptemp, APLOGNO(02152)
+ "priv_addset");
+ return !OK;
+ }
+ return OK;
+}
+static int privileges_init(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp)
+{
+ /* refuse to work if the MPM is threaded */
+ int threaded;
+ int rv = ap_mpm_query(AP_MPMQ_IS_THREADED, &threaded);
+ if (rv != APR_SUCCESS) {
+ ap_log_perror(APLOG_MARK, APLOG_NOTICE, rv, ptemp, APLOGNO(02153)
+ "mod_privileges: unable to determine MPM characteristics."
+ " Please ensure you are using a non-threaded MPM "
+ "with this module.");
+ }
+ if (threaded) {
+ ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, ptemp, APLOGNO(02154)
+ "mod_privileges is not compatible with a threaded MPM.");
+ return !OK;
+ }
+ return OK;
+}
+static void privileges_hooks(apr_pool_t *pool)
+{
+ ap_hook_post_read_request(privileges_req, NULL, NULL,
+ APR_HOOK_REALLY_FIRST);
+ ap_hook_header_parser(privileges_req, NULL, NULL, APR_HOOK_REALLY_FIRST);
+ ap_hook_drop_privileges(privileges_drop_first, NULL, NULL, APR_HOOK_FIRST);
+ ap_hook_drop_privileges(privileges_drop_last, NULL, NULL, APR_HOOK_LAST);
+ ap_hook_post_config(privileges_postconf, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_pre_config(privileges_init, NULL, NULL, APR_HOOK_FIRST);
+}
+
+static const char *vhost_user(cmd_parms *cmd, void *dir, const char *arg)
+{
+ priv_cfg *cfg = ap_get_module_config(cmd->server->module_config,
+ &privileges_module);
+ cfg->uid = ap_uname2id(arg);
+ if (cfg->uid == 0) {
+ return apr_pstrcat(cmd->pool, "Invalid userid for VHostUser: ",
+ arg, NULL);
+ }
+ return NULL;
+}
+static const char *vhost_group(cmd_parms *cmd, void *dir, const char *arg)
+{
+ priv_cfg *cfg = ap_get_module_config(cmd->server->module_config,
+ &privileges_module);
+ cfg->gid = ap_gname2id(arg);
+ if (cfg->uid == 0) {
+ return apr_pstrcat(cmd->pool, "Invalid groupid for VHostGroup: ",
+ arg, NULL);
+ }
+ return NULL;
+}
+static const char *vhost_secure(cmd_parms *cmd, void *dir, int arg)
+{
+ priv_cfg *cfg = ap_get_module_config(cmd->server->module_config,
+ &privileges_module);
+ if (!arg) {
+ /* add basic privileges, excluding those covered by cgimode */
+ CFG_CHECK(priv_addset(cfg->priv, PRIV_FILE_LINK_ANY));
+ CFG_CHECK(priv_addset(cfg->priv, PRIV_PROC_INFO));
+ CFG_CHECK(priv_addset(cfg->priv, PRIV_PROC_SESSION));
+ }
+ return NULL;
+}
+static const char *vhost_cgimode(cmd_parms *cmd, void *dir, const char *arg)
+{
+ priv_cfg *cfg = ap_get_module_config(cmd->server->module_config,
+ &privileges_module);
+ if (!strcasecmp(arg, "on")) {
+ /* default - nothing to do */
+ }
+ else if (!strcasecmp(arg, "off")) {
+ /* drop fork+exec privs */
+ CFG_CHECK(priv_delset(cfg->priv, PRIV_PROC_FORK));
+ CFG_CHECK(priv_delset(cfg->priv, PRIV_PROC_EXEC));
+ }
+ else if (!strcasecmp(arg, "secure")) {
+ /* deny privileges to CGI procs */
+ CFG_CHECK(priv_delset(cfg->child_priv, PRIV_PROC_FORK));
+ CFG_CHECK(priv_delset(cfg->child_priv, PRIV_PROC_EXEC));
+ CFG_CHECK(priv_delset(cfg->child_priv, PRIV_FILE_LINK_ANY));
+ CFG_CHECK(priv_delset(cfg->child_priv, PRIV_PROC_INFO));
+ CFG_CHECK(priv_delset(cfg->child_priv, PRIV_PROC_SESSION));
+ }
+ else {
+ return "VHostCGIMode must be On, Off or Secure";
+ }
+
+ return NULL;
+}
+static const char *dtraceenable(cmd_parms *cmd, void *dir, int arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+ dtrace_enabled = arg;
+ return NULL;
+}
+
+static const char *privs_mode(cmd_parms *cmd, void *dir, const char *arg)
+{
+ priv_mode mode = PRIV_UNSET;
+ if (!strcasecmp(arg, "FAST")) {
+ mode = PRIV_FAST;
+ }
+ else if (!strcasecmp(arg, "SECURE")) {
+ mode = PRIV_SECURE;
+ }
+ else if (!strcasecmp(arg, "SELECTIVE")) {
+ mode = PRIV_SELECTIVE;
+ }
+
+ if (cmd->path) {
+ /* In a directory context, set the per_dir_config */
+ priv_dir_cfg *cfg = dir;
+ cfg->mode = mode;
+ if ((mode == PRIV_UNSET) || (mode == PRIV_SELECTIVE)) {
+ return "PrivilegesMode in a Directory context must be FAST or SECURE";
+ }
+ }
+ else {
+ /* In a global or vhost context, set the server config */
+ priv_cfg *cfg = ap_get_module_config(cmd->server->module_config,
+ &privileges_module);
+ cfg->mode = mode;
+ if (mode == PRIV_UNSET) {
+ return "PrivilegesMode must be FAST, SECURE or SELECTIVE";
+ }
+ }
+ return NULL;
+}
+
+#ifdef BIG_SECURITY_HOLE
+static const char *vhost_privs(cmd_parms *cmd, void *dir, const char *arg)
+{
+ priv_cfg *cfg = ap_get_module_config(cmd->server->module_config,
+ &privileges_module);
+ const char *priv = arg;
+
+ if (*priv == '-') {
+ CFG_CHECK(priv_delset(cfg->priv, priv+1));
+ }
+ else if (*priv == '+') {
+ CFG_CHECK(priv_addset(cfg->priv, priv+1));
+ }
+ else {
+ priv_emptyset(cfg->priv);
+ CFG_CHECK(priv_addset(cfg->priv, priv));
+ }
+ return NULL;
+}
+static const char *vhost_cgiprivs(cmd_parms *cmd, void *dir, const char *arg)
+{
+ priv_cfg *cfg = ap_get_module_config(cmd->server->module_config,
+ &privileges_module);
+ const char *priv = arg;
+ if (*priv == '-') {
+ CFG_CHECK(priv_delset(cfg->child_priv, priv+1));
+ }
+ else if (*priv == '+') {
+ CFG_CHECK(priv_addset(cfg->child_priv, priv+1));
+ }
+ else {
+ priv_emptyset(cfg->child_priv);
+ CFG_CHECK(priv_addset(cfg->child_priv, priv));
+ }
+ return NULL;
+}
+#endif
+static const command_rec privileges_cmds[] = {
+ AP_INIT_TAKE1("VHostUser", vhost_user, NULL, RSRC_CONF,
+ "Userid under which the virtualhost will run"),
+ AP_INIT_TAKE1("VHostGroup", vhost_group, NULL, RSRC_CONF,
+ "Group under which the virtualhost will run"),
+ AP_INIT_FLAG("VHostSecure", vhost_secure, NULL, RSRC_CONF,
+ "Run in enhanced security mode (default ON)"),
+ AP_INIT_TAKE1("VHostCGIMode", vhost_cgimode, NULL, RSRC_CONF,
+ "Enable fork+exec for this virtualhost (Off|Secure|On)"),
+ AP_INIT_FLAG("DTracePrivileges", dtraceenable, NULL, RSRC_CONF,
+ "Enable DTrace"),
+ AP_INIT_TAKE1("PrivilegesMode", privs_mode, NULL, RSRC_CONF|ACCESS_CONF,
+ "tradeoff performance vs security (fast or secure)"),
+#ifdef BIG_SECURITY_HOLE
+ AP_INIT_ITERATE("VHostPrivs", vhost_privs, NULL, RSRC_CONF,
+ "Privileges available in the (virtual) server"),
+ AP_INIT_ITERATE("VHostCGIPrivs", vhost_cgiprivs, NULL, RSRC_CONF,
+ "Privileges available to external programs"),
+#endif
+ {NULL}
+};
+AP_DECLARE_MODULE(privileges) = {
+ STANDARD20_MODULE_STUFF,
+ privileges_create_dir_cfg,
+ privileges_merge_dir_cfg,
+ privileges_create_cfg,
+ privileges_merge_cfg,
+ privileges_cmds,
+ privileges_hooks
+};
diff --git a/modules/arch/unix/mod_systemd.c b/modules/arch/unix/mod_systemd.c
new file mode 100644
index 0000000..c3e7082
--- /dev/null
+++ b/modules/arch/unix/mod_systemd.c
@@ -0,0 +1,119 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <stdint.h>
+#include <ap_config.h>
+#include "ap_mpm.h"
+#include <http_core.h>
+#include <httpd.h>
+#include <http_log.h>
+#include <apr_version.h>
+#include <apr_pools.h>
+#include <apr_strings.h>
+#include "unixd.h"
+#include "scoreboard.h"
+#include "mpm_common.h"
+
+#include "systemd/sd-daemon.h"
+
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+static int systemd_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp)
+{
+ sd_notify(0,
+ "RELOADING=1\n"
+ "STATUS=Reading configuration...\n");
+ ap_extended_status = 1;
+ return OK;
+}
+
+/* Report the service is ready in post_config, which could be during
+ * startup or after a reload. The server could still hit a fatal
+ * startup error after this point during ap_run_mpm(), so this is
+ * perhaps too early, but by post_config listen() has been called on
+ * the TCP ports so new connections will not be rejected. There will
+ * always be a possible async failure event simultaneous to the
+ * service reporting "ready", so this should be good enough. */
+static int systemd_post_config(apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *main_server)
+{
+ sd_notify(0, "READY=1\n"
+ "STATUS=Configuration loaded.\n");
+ return OK;
+}
+
+static int systemd_pre_mpm(apr_pool_t *p, ap_scoreboard_e sb_type)
+{
+ sd_notifyf(0, "READY=1\n"
+ "STATUS=Processing requests...\n"
+ "MAINPID=%" APR_PID_T_FMT, getpid());
+
+ return OK;
+}
+
+static int systemd_monitor(apr_pool_t *p, server_rec *s)
+{
+ ap_sload_t sload;
+ apr_interval_time_t up_time;
+ char bps[5];
+
+ if (!ap_extended_status) {
+ /* Nothing useful to report with ExtendedStatus disabled. */
+ return DECLINED;
+ }
+
+ ap_get_sload(&sload);
+ /* up_time in seconds */
+ up_time = (apr_uint32_t) apr_time_sec(apr_time_now() -
+ ap_scoreboard_image->global->restart_time);
+
+ apr_strfsize((unsigned long)((float) (sload.bytes_served)
+ / (float) up_time), bps);
+
+ sd_notifyf(0, "READY=1\n"
+ "STATUS=Total requests: %lu; Idle/Busy workers %d/%d;"
+ "Requests/sec: %.3g; Bytes served/sec: %sB/sec\n",
+ sload.access_count, sload.idle, sload.busy,
+ ((float) sload.access_count) / (float) up_time, bps);
+
+ return DECLINED;
+}
+
+static void systemd_register_hooks(apr_pool_t *p)
+{
+ /* Enable ap_extended_status. */
+ ap_hook_pre_config(systemd_pre_config, NULL, NULL, APR_HOOK_LAST);
+ /* Signal service is ready. */
+ ap_hook_post_config(systemd_post_config, NULL, NULL, APR_HOOK_REALLY_LAST);
+ /* We know the PID in this hook ... */
+ ap_hook_pre_mpm(systemd_pre_mpm, NULL, NULL, APR_HOOK_LAST);
+ /* Used to update httpd's status line using sd_notifyf */
+ ap_hook_monitor(systemd_monitor, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(systemd) = {
+ STANDARD20_MODULE_STUFF,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ systemd_register_hooks,
+};
diff --git a/modules/arch/unix/mod_unixd.c b/modules/arch/unix/mod_unixd.c
new file mode 100644
index 0000000..1baa278
--- /dev/null
+++ b/modules/arch/unix/mod_unixd.c
@@ -0,0 +1,433 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_main.h"
+#include "http_log.h"
+#include "http_core.h"
+#include "mpm_common.h"
+#include "os.h"
+#include "ap_mpm.h"
+#include "mod_unixd.h"
+#include "apr_thread_proc.h"
+#include "apr_strings.h"
+#include "apr_portable.h"
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+#ifdef HAVE_SYS_RESOURCE_H
+#include <sys/resource.h>
+#endif
+/* XXX */
+#include <sys/stat.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_GRP_H
+#include <grp.h>
+#endif
+#ifdef HAVE_STRINGS_H
+#include <strings.h>
+#endif
+#ifdef HAVE_SYS_SEM_H
+#include <sys/sem.h>
+#endif
+#ifdef HAVE_SYS_PRCTL_H
+#include <sys/prctl.h>
+#endif
+
+#ifndef DEFAULT_USER
+#define DEFAULT_USER "#-1"
+#endif
+#ifndef DEFAULT_GROUP
+#define DEFAULT_GROUP "#-1"
+#endif
+
+#if 0
+typedef struct {
+ const char *user_name;
+ uid_t user_id;
+ gid_t group_id;
+ const char *chroot_dir;
+} unixd_config_t;
+#else
+/*
+ * TODO: clean up the separation between this code
+ * and its data structures and unixd.c, as shown
+ * by the fact that we include unixd.h. Create
+ * mod_unixd.h which does what we need and
+ * clean up unixd.h for what it no longer needs
+ */
+#include "unixd.h"
+#endif
+
+
+/* Set group privileges.
+ *
+ * Note that we use the username as set in the config files, rather than
+ * the lookup of to uid --- the same uid may have multiple passwd entries,
+ * with different sets of groups for each.
+ */
+
+static int set_group_privs(void)
+{
+ if (!geteuid()) {
+ const char *name;
+
+ /* Get username if passed as a uid */
+
+ if (ap_unixd_config.user_name[0] == '#') {
+ struct passwd *ent;
+ uid_t uid = atol(&ap_unixd_config.user_name[1]);
+
+ if ((ent = getpwuid(uid)) == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02155)
+ "getpwuid: couldn't determine user name from uid %ld, "
+ "you probably need to modify the User directive",
+ (long)uid);
+ return -1;
+ }
+
+ name = ent->pw_name;
+ }
+ else
+ name = ap_unixd_config.user_name;
+
+#if !defined(OS2)
+ /* OS/2 doesn't support groups. */
+ /*
+ * Set the GID before initgroups(), since on some platforms
+ * setgid() is known to zap the group list.
+ */
+ if (setgid(ap_unixd_config.group_id) == -1) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02156)
+ "setgid: unable to set group id to Group %ld",
+ (long)ap_unixd_config.group_id);
+ return -1;
+ }
+
+ /* Reset `groups' attributes. */
+
+ if (initgroups(name, ap_unixd_config.group_id) == -1) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02157)
+ "initgroups: unable to set groups for User %s "
+ "and Group %ld", name, (long)ap_unixd_config.group_id);
+ return -1;
+ }
+#endif /* !defined(OS2) */
+ }
+ return 0;
+}
+
+
+static int
+unixd_drop_privileges(apr_pool_t *pool, server_rec *s)
+{
+ int rv = set_group_privs();
+
+ if (rv) {
+ return rv;
+ }
+
+ if (NULL != ap_unixd_config.chroot_dir) {
+ if (geteuid()) {
+ rv = errno;
+ ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02158)
+ "Cannot chroot when not started as root");
+ return rv;
+ }
+
+ if (chdir(ap_unixd_config.chroot_dir) != 0) {
+ rv = errno;
+ ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02159)
+ "Can't chdir to %s", ap_unixd_config.chroot_dir);
+ return rv;
+ }
+
+ if (chroot(ap_unixd_config.chroot_dir) != 0) {
+ rv = errno;
+ ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02160)
+ "Can't chroot to %s", ap_unixd_config.chroot_dir);
+ return rv;
+ }
+
+ if (chdir("/") != 0) {
+ rv = errno;
+ ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02161)
+ "Can't chdir to new root");
+ return rv;
+ }
+ }
+
+ /* Only try to switch if we're running as root */
+ if (!geteuid() && (
+#ifdef _OSD_POSIX
+ os_init_job_environment(NULL, ap_unixd_config.user_name, ap_exists_config_define("DEBUG")) != 0 ||
+#endif
+ setuid(ap_unixd_config.user_id) == -1)) {
+ rv = errno;
+ ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02162)
+ "setuid: unable to change to uid: %ld",
+ (long) ap_unixd_config.user_id);
+ return rv;
+ }
+#if defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE)
+ /* this applies to Linux 2.4+ */
+ if (ap_coredumpdir_configured) {
+ if (prctl(PR_SET_DUMPABLE, 1)) {
+ rv = errno;
+ ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02163)
+ "set dumpable failed - this child will not coredump"
+ " after software errors");
+ return rv;
+ }
+ }
+#endif
+
+ return OK;
+}
+
+
+static const char *
+unixd_set_user(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ ap_unixd_config.user_name = arg;
+ ap_unixd_config.user_id = ap_uname2id(arg);
+#if !defined (BIG_SECURITY_HOLE) && !defined (OS2)
+ if (ap_unixd_config.user_id == 0) {
+ return "Error:\tApache has not been designed to serve pages while\n"
+ "\trunning as root. There are known race conditions that\n"
+ "\twill allow any local user to read any file on the system.\n"
+ "\tIf you still desire to serve pages as root then\n"
+ "\tadd -DBIG_SECURITY_HOLE to the CFLAGS env variable\n"
+ "\tand then rebuild the server.\n"
+ "\tIt is strongly suggested that you instead modify the User\n"
+ "\tdirective in your httpd.conf file to list a non-root\n"
+ "\tuser.\n";
+ }
+#endif
+
+ return NULL;
+}
+
+static const char*
+unixd_set_group(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ ap_unixd_config.group_name = arg;
+ ap_unixd_config.group_id = ap_gname2id(arg);
+
+ return NULL;
+}
+
+static const char*
+unixd_set_chroot_dir(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+ if (!ap_is_directory(cmd->pool, arg)) {
+ return "ChrootDir must be a valid directory";
+ }
+
+ ap_unixd_config.chroot_dir = arg;
+ return NULL;
+}
+
+static const char *
+unixd_set_suexec(cmd_parms *cmd, void *dummy, int arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ if (!ap_unixd_config.suexec_enabled && arg) {
+ return apr_pstrcat(cmd->pool, "suEXEC isn't supported: ",
+ ap_unixd_config.suexec_disabled_reason, NULL);
+ }
+
+ if (!arg) {
+ ap_unixd_config.suexec_disabled_reason = "Suexec directive is Off";
+ }
+
+ ap_unixd_config.suexec_enabled = arg;
+ return NULL;
+}
+
+#ifdef AP_SUEXEC_CAPABILITIES
+/* If suexec is using capabilities, don't test for the setuid bit. */
+#define SETUID_TEST(finfo) (1)
+#else
+#define SETUID_TEST(finfo) (finfo.protection & APR_USETID)
+#endif
+
+static int
+unixd_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp)
+{
+ apr_finfo_t wrapper;
+ ap_unixd_config.user_name = DEFAULT_USER;
+ ap_unixd_config.user_id = ap_uname2id(DEFAULT_USER);
+ ap_unixd_config.group_name = DEFAULT_GROUP;
+ ap_unixd_config.group_id = ap_gname2id(DEFAULT_GROUP);
+
+ ap_unixd_config.chroot_dir = NULL; /* none */
+
+ /* Check for suexec */
+ ap_unixd_config.suexec_enabled = 0;
+ if ((apr_stat(&wrapper, SUEXEC_BIN, APR_FINFO_NORM, ptemp))
+ == APR_SUCCESS) {
+ if (SETUID_TEST(wrapper) && wrapper.user == 0
+ && (access(SUEXEC_BIN, R_OK|X_OK) == 0)) {
+ ap_unixd_config.suexec_enabled = 1;
+ ap_unixd_config.suexec_disabled_reason = "";
+ }
+ else {
+ ap_unixd_config.suexec_disabled_reason =
+ "Invalid owner or file mode for " SUEXEC_BIN;
+ }
+ }
+ else {
+ ap_unixd_config.suexec_disabled_reason =
+ "Missing suexec binary " SUEXEC_BIN;
+ }
+
+ ap_sys_privileges_handlers(1);
+ return OK;
+}
+
+AP_DECLARE(int) ap_unixd_setup_child(void)
+{
+ if (set_group_privs()) {
+ return -1;
+ }
+
+ if (NULL != ap_unixd_config.chroot_dir) {
+ if (geteuid()) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02164)
+ "Cannot chroot when not started as root");
+ return -1;
+ }
+ if (chdir(ap_unixd_config.chroot_dir) != 0) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02165)
+ "Can't chdir to %s", ap_unixd_config.chroot_dir);
+ return -1;
+ }
+ if (chroot(ap_unixd_config.chroot_dir) != 0) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02166)
+ "Can't chroot to %s", ap_unixd_config.chroot_dir);
+ return -1;
+ }
+ if (chdir("/") != 0) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02167)
+ "Can't chdir to new root");
+ return -1;
+ }
+ }
+
+ /* Only try to switch if we're running as root */
+ if (!geteuid() && (
+#ifdef _OSD_POSIX
+ os_init_job_environment(NULL, ap_unixd_config.user_name, ap_exists_config_define("DEBUG")) != 0 ||
+#endif
+ setuid(ap_unixd_config.user_id) == -1)) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02168)
+ "setuid: unable to change to uid: %ld",
+ (long) ap_unixd_config.user_id);
+ return -1;
+ }
+#if defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE)
+ /* this applies to Linux 2.4+ */
+ if (ap_coredumpdir_configured) {
+ if (prctl(PR_SET_DUMPABLE, 1)) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02169)
+ "set dumpable failed - this child will not coredump"
+ " after software errors");
+ }
+ }
+#endif
+ return 0;
+}
+
+static void unixd_dump_config(apr_pool_t *p, server_rec *s)
+{
+ apr_file_t *out = NULL;
+ apr_uid_t uid = ap_unixd_config.user_id;
+ apr_gid_t gid = ap_unixd_config.group_id;
+ char *no_root = "";
+ if (!ap_exists_config_define("DUMP_RUN_CFG"))
+ return;
+ if (geteuid() != 0)
+ no_root = " not_used";
+ apr_file_open_stdout(&out, p);
+ apr_file_printf(out, "User: name=\"%s\" id=%lu%s\n",
+ ap_unixd_config.user_name, (unsigned long)uid, no_root);
+ apr_file_printf(out, "Group: name=\"%s\" id=%lu%s\n",
+ ap_unixd_config.group_name, (unsigned long)gid, no_root);
+ if (ap_unixd_config.chroot_dir)
+ apr_file_printf(out, "ChrootDir: \"%s\"%s\n",
+ ap_unixd_config.chroot_dir, no_root);
+}
+
+static void unixd_hooks(apr_pool_t *pool)
+{
+ ap_hook_pre_config(unixd_pre_config,
+ NULL, NULL, APR_HOOK_FIRST);
+ ap_hook_test_config(unixd_dump_config,
+ NULL, NULL, APR_HOOK_FIRST);
+ ap_hook_drop_privileges(unixd_drop_privileges,
+ NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+static const command_rec unixd_cmds[] = {
+ AP_INIT_TAKE1("User", unixd_set_user, NULL, RSRC_CONF,
+ "Effective user id for this server"),
+ AP_INIT_TAKE1("Group", unixd_set_group, NULL, RSRC_CONF,
+ "Effective group id for this server"),
+ AP_INIT_TAKE1("ChrootDir", unixd_set_chroot_dir, NULL, RSRC_CONF,
+ "The directory to chroot(2) into"),
+ AP_INIT_FLAG("Suexec", unixd_set_suexec, NULL, RSRC_CONF,
+ "Enable or disable suEXEC support"),
+ {NULL}
+};
+
+AP_DECLARE_MODULE(unixd) = {
+ STANDARD20_MODULE_STUFF,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ unixd_cmds,
+ unixd_hooks
+};
+
diff --git a/modules/arch/unix/mod_unixd.h b/modules/arch/unix/mod_unixd.h
new file mode 100644
index 0000000..a7f439e
--- /dev/null
+++ b/modules/arch/unix/mod_unixd.h
@@ -0,0 +1,41 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file mod_unixd.h
+ * @brief common stuff that unix MPMs will want
+ *
+ * @addtogroup APACHE_OS_UNIX
+ * @{
+ */
+
+#ifndef MOD_UNIXD_H
+#define MOD_UNIXD_H
+
+#include "ap_config.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+AP_DECLARE(int) ap_unixd_setup_child(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+/** @} */
diff --git a/modules/arch/win32/Makefile.in b/modules/arch/win32/Makefile.in
new file mode 100644
index 0000000..7c5c149
--- /dev/null
+++ b/modules/arch/win32/Makefile.in
@@ -0,0 +1,3 @@
+# a modules Makefile has no explicit targets -- they will be defined by
+# whatever modules are enabled. just grab special.mk to deal with this.
+include $(top_srcdir)/build/special.mk
diff --git a/modules/arch/win32/config.m4 b/modules/arch/win32/config.m4
new file mode 100644
index 0000000..584e76d
--- /dev/null
+++ b/modules/arch/win32/config.m4
@@ -0,0 +1,9 @@
+dnl modules enabled in this directory by default
+
+dnl APACHE_MODULE(name, helptext[, objects[, structname[, default[, config]]]])
+
+APACHE_MODPATH_INIT(arch/win32)
+
+APACHE_MODULE(isapi, isapi extension support, , , no)
+
+APACHE_MODPATH_FINISH
diff --git a/modules/arch/win32/mod_isapi.c b/modules/arch/win32/mod_isapi.c
new file mode 100644
index 0000000..a9816e5
--- /dev/null
+++ b/modules/arch/win32/mod_isapi.c
@@ -0,0 +1,1728 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * mod_isapi.c - Internet Server Application (ISA) module for Apache
+ * by Alexei Kosut <akosut@apache.org>, significant overhauls and
+ * redesign by William Rowe <wrowe@covalent.net>, and hints from many
+ * other developer/users who have hit on specific flaws.
+ *
+ * This module implements the ISAPI Handler architecture, allowing
+ * Apache to load Internet Server Applications (ISAPI extensions),
+ * similar to the support in IIS, Zope, O'Reilly's WebSite and others.
+ *
+ * It is a complete implementation of the ISAPI 2.0 specification,
+ * except for "Microsoft extensions" to the API which provide
+ * asynchronous I/O. It is further extended to include additional
+ * "Microsoft extensions" through IIS 5.0, with some deficiencies
+ * where one-to-one mappings don't exist.
+ *
+ * Refer to /manual/mod/mod_isapi.html for additional details on
+ * configuration and use, but check this source for specific support
+ * of the API,
+ */
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "http_log.h"
+#include "util_script.h"
+#include "mod_core.h"
+#include "apr_lib.h"
+#include "apr_strings.h"
+#include "apr_portable.h"
+#include "apr_buckets.h"
+#include "apr_thread_mutex.h"
+#include "apr_thread_rwlock.h"
+#include "apr_hash.h"
+#include "mod_isapi.h"
+
+/* Retry frequency for a failed-to-load isapi .dll */
+#define ISAPI_RETRY apr_time_from_sec(30)
+
+/**********************************************************
+ *
+ * ISAPI Module Configuration
+ *
+ **********************************************************/
+
+module AP_MODULE_DECLARE_DATA isapi_module;
+
+#define ISAPI_UNDEF -1
+
+/* Our isapi per-dir config structure */
+typedef struct isapi_dir_conf {
+ int read_ahead_buflen;
+ int log_unsupported;
+ int log_to_errlog;
+ int log_to_query;
+ int fake_async;
+} isapi_dir_conf;
+
+typedef struct isapi_loaded isapi_loaded;
+
+apr_status_t isapi_lookup(apr_pool_t *p, server_rec *s, request_rec *r,
+ const char *fpath, isapi_loaded** isa);
+
+static void *create_isapi_dir_config(apr_pool_t *p, char *dummy)
+{
+ isapi_dir_conf *dir = apr_palloc(p, sizeof(isapi_dir_conf));
+
+ dir->read_ahead_buflen = ISAPI_UNDEF;
+ dir->log_unsupported = ISAPI_UNDEF;
+ dir->log_to_errlog = ISAPI_UNDEF;
+ dir->log_to_query = ISAPI_UNDEF;
+ dir->fake_async = ISAPI_UNDEF;
+
+ return dir;
+}
+
+static void *merge_isapi_dir_configs(apr_pool_t *p, void *base_, void *add_)
+{
+ isapi_dir_conf *base = (isapi_dir_conf *) base_;
+ isapi_dir_conf *add = (isapi_dir_conf *) add_;
+ isapi_dir_conf *dir = apr_palloc(p, sizeof(isapi_dir_conf));
+
+ dir->read_ahead_buflen = (add->read_ahead_buflen == ISAPI_UNDEF)
+ ? base->read_ahead_buflen
+ : add->read_ahead_buflen;
+ dir->log_unsupported = (add->log_unsupported == ISAPI_UNDEF)
+ ? base->log_unsupported
+ : add->log_unsupported;
+ dir->log_to_errlog = (add->log_to_errlog == ISAPI_UNDEF)
+ ? base->log_to_errlog
+ : add->log_to_errlog;
+ dir->log_to_query = (add->log_to_query == ISAPI_UNDEF)
+ ? base->log_to_query
+ : add->log_to_query;
+ dir->fake_async = (add->fake_async == ISAPI_UNDEF)
+ ? base->fake_async
+ : add->fake_async;
+
+ return dir;
+}
+
+static const char *isapi_cmd_cachefile(cmd_parms *cmd, void *dummy,
+ const char *filename)
+{
+ isapi_loaded *isa;
+ apr_finfo_t tmp;
+ apr_status_t rv;
+ char *fspec;
+
+ /* ### Just an observation ... it would be terribly cool to be
+ * able to use this per-dir, relative to the directory block being
+ * defined. The hash result remains global, but shorthand of
+ * <Directory "c:/webapps/isapi">
+ * ISAPICacheFile myapp.dll anotherapp.dll thirdapp.dll
+ * </Directory>
+ * would be very convienent.
+ */
+ fspec = ap_server_root_relative(cmd->pool, filename);
+ if (!fspec) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, APR_EBADPATH, cmd->server, APLOGNO(02103)
+ "invalid module path, skipping %s", filename);
+ return NULL;
+ }
+ if ((rv = apr_stat(&tmp, fspec, APR_FINFO_TYPE,
+ cmd->temp_pool)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rv, cmd->server, APLOGNO(02104)
+ "unable to stat, skipping %s", fspec);
+ return NULL;
+ }
+ if (tmp.filetype != APR_REG) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(02105)
+ "not a regular file, skipping %s", fspec);
+ return NULL;
+ }
+
+ /* Load the extension as cached (with null request_rec) */
+ rv = isapi_lookup(cmd->pool, cmd->server, NULL, fspec, &isa);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rv, cmd->server, APLOGNO(02106)
+ "unable to cache, skipping %s", fspec);
+ return NULL;
+ }
+
+ return NULL;
+}
+
+static const command_rec isapi_cmds[] = {
+ AP_INIT_TAKE1("ISAPIReadAheadBuffer", ap_set_int_slot,
+ (void *)APR_OFFSETOF(isapi_dir_conf, read_ahead_buflen),
+ OR_FILEINFO, "Maximum client request body to initially pass to the"
+ " ISAPI handler (default: 49152)"),
+ AP_INIT_FLAG("ISAPILogNotSupported", ap_set_flag_slot,
+ (void *)APR_OFFSETOF(isapi_dir_conf, log_unsupported),
+ OR_FILEINFO, "Log requests not supported by the ISAPI server"
+ " on or off (default: off)"),
+ AP_INIT_FLAG("ISAPIAppendLogToErrors", ap_set_flag_slot,
+ (void *)APR_OFFSETOF(isapi_dir_conf, log_to_errlog),
+ OR_FILEINFO, "Send all Append Log requests to the error log"
+ " on or off (default: off)"),
+ AP_INIT_FLAG("ISAPIAppendLogToQuery", ap_set_flag_slot,
+ (void *)APR_OFFSETOF(isapi_dir_conf, log_to_query),
+ OR_FILEINFO, "Append Log requests are concatenated to the query args"
+ " on or off (default: on)"),
+ AP_INIT_FLAG("ISAPIFakeAsync", ap_set_flag_slot,
+ (void *)APR_OFFSETOF(isapi_dir_conf, fake_async),
+ OR_FILEINFO, "Fake Asynchronous support for isapi callbacks"
+ " on or off [Experimental] (default: off)"),
+ AP_INIT_ITERATE("ISAPICacheFile", isapi_cmd_cachefile, NULL,
+ RSRC_CONF, "Cache the specified ISAPI extension in-process"),
+ {NULL}
+};
+
+/**********************************************************
+ *
+ * ISAPI Module Cache handling section
+ *
+ **********************************************************/
+
+/* Our isapi global config values */
+static struct isapi_global_conf {
+ apr_pool_t *pool;
+ apr_thread_mutex_t *lock;
+ apr_hash_t *hash;
+} loaded;
+
+/* Our loaded isapi module description structure */
+struct isapi_loaded {
+ const char *filename;
+ apr_thread_rwlock_t *in_progress;
+ apr_status_t last_load_rv;
+ apr_time_t last_load_time;
+ apr_dso_handle_t *handle;
+ HSE_VERSION_INFO *isapi_version;
+ apr_uint32_t report_version;
+ apr_uint32_t timeout;
+ PFN_GETEXTENSIONVERSION GetExtensionVersion;
+ PFN_HTTPEXTENSIONPROC HttpExtensionProc;
+ PFN_TERMINATEEXTENSION TerminateExtension;
+};
+
+static apr_status_t isapi_unload(isapi_loaded *isa, int force)
+{
+ /* All done with the DLL... get rid of it...
+ *
+ * If optionally cached, and we weren't asked to force the unload,
+ * pass HSE_TERM_ADVISORY_UNLOAD, and if it returns 1, unload,
+ * otherwise, leave it alone (it didn't choose to cooperate.)
+ */
+ if (!isa->handle) {
+ return APR_SUCCESS;
+ }
+ if (isa->TerminateExtension) {
+ if (force) {
+ (*isa->TerminateExtension)(HSE_TERM_MUST_UNLOAD);
+ }
+ else if (!(*isa->TerminateExtension)(HSE_TERM_ADVISORY_UNLOAD)) {
+ return APR_EGENERAL;
+ }
+ }
+ apr_dso_unload(isa->handle);
+ isa->handle = NULL;
+ return APR_SUCCESS;
+}
+
+static apr_status_t cleanup_isapi(void *isa_)
+{
+ isapi_loaded* isa = (isapi_loaded*) isa_;
+
+ /* We must force the module to unload, we are about
+ * to lose the isapi structure's allocation entirely.
+ */
+ return isapi_unload(isa, 1);
+}
+
+static apr_status_t isapi_load(apr_pool_t *p, server_rec *s, isapi_loaded *isa)
+{
+ apr_status_t rv;
+
+ isa->isapi_version = apr_pcalloc(p, sizeof(HSE_VERSION_INFO));
+
+ /* TODO: These aught to become overridable, so that we
+ * assure a given isapi can be fooled into behaving well.
+ *
+ * The tricky bit, they aren't really a per-dir sort of
+ * config, they will always be constant across every
+ * reference to the .dll no matter what context (vhost,
+ * location, etc) they apply to.
+ */
+ isa->report_version = 0x500; /* Revision 5.0 */
+ isa->timeout = 300 * 1000000; /* microsecs, not used */
+
+ rv = apr_dso_load(&isa->handle, isa->filename, p);
+ if (rv)
+ {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(02107)
+ "failed to load %s", isa->filename);
+ isa->handle = NULL;
+ return rv;
+ }
+
+ rv = apr_dso_sym((void**)&isa->GetExtensionVersion, isa->handle,
+ "GetExtensionVersion");
+ if (rv)
+ {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(02108)
+ "missing GetExtensionVersion() in %s",
+ isa->filename);
+ apr_dso_unload(isa->handle);
+ isa->handle = NULL;
+ return rv;
+ }
+
+ rv = apr_dso_sym((void**)&isa->HttpExtensionProc, isa->handle,
+ "HttpExtensionProc");
+ if (rv)
+ {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(02109)
+ "missing HttpExtensionProc() in %s",
+ isa->filename);
+ apr_dso_unload(isa->handle);
+ isa->handle = NULL;
+ return rv;
+ }
+
+ /* TerminateExtension() is an optional interface */
+ rv = apr_dso_sym((void**)&isa->TerminateExtension, isa->handle,
+ "TerminateExtension");
+ apr_set_os_error(0);
+
+ /* Run GetExtensionVersion() */
+ if (!(isa->GetExtensionVersion)(isa->isapi_version)) {
+ apr_status_t rv = apr_get_os_error();
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(02110)
+ "failed call to GetExtensionVersion() in %s",
+ isa->filename);
+ apr_dso_unload(isa->handle);
+ isa->handle = NULL;
+ return rv;
+ }
+
+ apr_pool_cleanup_register(p, isa, cleanup_isapi,
+ apr_pool_cleanup_null);
+
+ return APR_SUCCESS;
+}
+
+apr_status_t isapi_lookup(apr_pool_t *p, server_rec *s, request_rec *r,
+ const char *fpath, isapi_loaded** isa)
+{
+ apr_status_t rv;
+ const char *key;
+
+ if ((rv = apr_thread_mutex_lock(loaded.lock)) != APR_SUCCESS) {
+ return rv;
+ }
+
+ *isa = apr_hash_get(loaded.hash, fpath, APR_HASH_KEY_STRING);
+
+ if (*isa) {
+
+ /* If we find this lock exists, use a set-aside copy of gainlock
+ * to avoid race conditions on NULLing the in_progress variable
+ * when the load has completed. Release the global isapi hash
+ * lock so other requests can proceed, then rdlock for completion
+ * of loading our desired dll or wrlock if we would like to retry
+ * loading the dll (because last_load_rv failed and retry is up.)
+ */
+ apr_thread_rwlock_t *gainlock = (*isa)->in_progress;
+
+ /* gainlock is NULLed after the module loads successfully.
+ * This free-threaded module can be used without any locking.
+ */
+ if (!gainlock) {
+ rv = (*isa)->last_load_rv;
+ apr_thread_mutex_unlock(loaded.lock);
+ return rv;
+ }
+
+
+ if ((*isa)->last_load_rv == APR_SUCCESS) {
+ apr_thread_mutex_unlock(loaded.lock);
+ if ((rv = apr_thread_rwlock_rdlock(gainlock))
+ != APR_SUCCESS) {
+ return rv;
+ }
+ rv = (*isa)->last_load_rv;
+ apr_thread_rwlock_unlock(gainlock);
+ return rv;
+ }
+
+ if (apr_time_now() > (*isa)->last_load_time + ISAPI_RETRY) {
+
+ /* Remember last_load_time before releasing the global
+ * hash lock to avoid colliding with another thread
+ * that hit this exception at the same time as our
+ * retry attempt, since we unlock the global mutex
+ * before attempting a write lock for this module.
+ */
+ apr_time_t check_time = (*isa)->last_load_time;
+ apr_thread_mutex_unlock(loaded.lock);
+
+ if ((rv = apr_thread_rwlock_wrlock(gainlock))
+ != APR_SUCCESS) {
+ return rv;
+ }
+
+ /* If last_load_time is unchanged, we still own this
+ * retry, otherwise presume another thread provided
+ * our retry (for good or ill). Relock the global
+ * hash for updating last_load_ vars, so their update
+ * is always atomic to the global lock.
+ */
+ if (check_time == (*isa)->last_load_time) {
+
+ rv = isapi_load(loaded.pool, s, *isa);
+
+ apr_thread_mutex_lock(loaded.lock);
+ (*isa)->last_load_rv = rv;
+ (*isa)->last_load_time = apr_time_now();
+ apr_thread_mutex_unlock(loaded.lock);
+ }
+ else {
+ rv = (*isa)->last_load_rv;
+ }
+ apr_thread_rwlock_unlock(gainlock);
+
+ return rv;
+ }
+
+ /* We haven't hit timeup on retry, let's grab the last_rv
+ * within the hash mutex before unlocking.
+ */
+ rv = (*isa)->last_load_rv;
+ apr_thread_mutex_unlock(loaded.lock);
+
+ return rv;
+ }
+
+ /* If the module was not found, it's time to create a hash key entry
+ * before releasing the hash lock to avoid multiple threads from
+ * loading the same module.
+ */
+ key = apr_pstrdup(loaded.pool, fpath);
+ *isa = apr_pcalloc(loaded.pool, sizeof(isapi_loaded));
+ (*isa)->filename = key;
+ if (r) {
+ /* A mutex that exists only long enough to attempt to
+ * load this isapi dll, the release this module to all
+ * other takers that came along during the one-time
+ * load process. Short lifetime for this lock would
+ * be great, however, using r->pool is nasty if those
+ * blocked on the lock haven't all unlocked before we
+ * attempt to destroy. A nastier race condition than
+ * I want to deal with at this moment...
+ */
+ apr_thread_rwlock_create(&(*isa)->in_progress, loaded.pool);
+ apr_thread_rwlock_wrlock((*isa)->in_progress);
+ }
+
+ apr_hash_set(loaded.hash, key, APR_HASH_KEY_STRING, *isa);
+
+ /* Now attempt to load the isapi on our own time,
+ * allow other isapi processing to resume.
+ */
+ apr_thread_mutex_unlock(loaded.lock);
+
+ rv = isapi_load(loaded.pool, s, *isa);
+ (*isa)->last_load_time = apr_time_now();
+ (*isa)->last_load_rv = rv;
+
+ if (r && (rv == APR_SUCCESS)) {
+ /* Let others who are blocked on this particular
+ * module resume their requests, for better or worse.
+ */
+ apr_thread_rwlock_t *unlock = (*isa)->in_progress;
+ (*isa)->in_progress = NULL;
+ apr_thread_rwlock_unlock(unlock);
+ }
+ else if (!r && (rv != APR_SUCCESS)) {
+ /* We must leave a rwlock around for requests to retry
+ * loading this dll after timeup... since we were in
+ * the setup code we had avoided creating this lock.
+ */
+ apr_thread_rwlock_create(&(*isa)->in_progress, loaded.pool);
+ }
+
+ return (*isa)->last_load_rv;
+}
+
+/**********************************************************
+ *
+ * ISAPI Module request callbacks section
+ *
+ **********************************************************/
+
+/* Our "Connection ID" structure */
+struct isapi_cid {
+ EXTENSION_CONTROL_BLOCK *ecb;
+ isapi_dir_conf dconf;
+ isapi_loaded *isa;
+ request_rec *r;
+ int headers_set;
+ int response_sent;
+ PFN_HSE_IO_COMPLETION completion;
+ void *completion_arg;
+ apr_thread_mutex_t *completed;
+};
+
+static int APR_THREAD_FUNC regfnGetServerVariable(isapi_cid *cid,
+ char *variable_name,
+ void *buf_ptr,
+ apr_uint32_t *buf_size)
+{
+ request_rec *r = cid->r;
+ const char *result;
+ char *buf_data = (char*)buf_ptr;
+ apr_uint32_t len;
+
+ if (!strcmp(variable_name, "ALL_HTTP"))
+ {
+ /* crlf delimited, colon split, comma separated and
+ * null terminated list of HTTP_ vars
+ */
+ const apr_array_header_t *arr = apr_table_elts(r->subprocess_env);
+ const apr_table_entry_t *elts = (const apr_table_entry_t *)arr->elts;
+ int i;
+
+ for (len = 0, i = 0; i < arr->nelts; i++) {
+ if (!strncmp(elts[i].key, "HTTP_", 5)) {
+ len += strlen(elts[i].key) + strlen(elts[i].val) + 3;
+ }
+ }
+
+ if (*buf_size < len + 1) {
+ *buf_size = len + 1;
+ apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INSUFFICIENT_BUFFER));
+ return 0;
+ }
+
+ for (i = 0; i < arr->nelts; i++) {
+ if (!strncmp(elts[i].key, "HTTP_", 5)) {
+ strcpy(buf_data, elts[i].key);
+ buf_data += strlen(elts[i].key);
+ *(buf_data++) = ':';
+ strcpy(buf_data, elts[i].val);
+ buf_data += strlen(elts[i].val);
+ *(buf_data++) = '\r';
+ *(buf_data++) = '\n';
+ }
+ }
+
+ *(buf_data++) = '\0';
+ *buf_size = len + 1;
+ return 1;
+ }
+
+ if (!strcmp(variable_name, "ALL_RAW"))
+ {
+ /* crlf delimited, colon split, comma separated and
+ * null terminated list of the raw request header
+ */
+ const apr_array_header_t *arr = apr_table_elts(r->headers_in);
+ const apr_table_entry_t *elts = (const apr_table_entry_t *)arr->elts;
+ int i;
+
+ for (len = 0, i = 0; i < arr->nelts; i++) {
+ len += strlen(elts[i].key) + strlen(elts[i].val) + 4;
+ }
+
+ if (*buf_size < len + 1) {
+ *buf_size = len + 1;
+ apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INSUFFICIENT_BUFFER));
+ return 0;
+ }
+
+ for (i = 0; i < arr->nelts; i++) {
+ strcpy(buf_data, elts[i].key);
+ buf_data += strlen(elts[i].key);
+ *(buf_data++) = ':';
+ *(buf_data++) = ' ';
+ strcpy(buf_data, elts[i].val);
+ buf_data += strlen(elts[i].val);
+ *(buf_data++) = '\r';
+ *(buf_data++) = '\n';
+ }
+ *(buf_data++) = '\0';
+ *buf_size = len + 1;
+ return 1;
+ }
+
+ /* Not a special case */
+ result = apr_table_get(r->subprocess_env, variable_name);
+
+ if (result) {
+ len = strlen(result);
+ if (*buf_size < len + 1) {
+ *buf_size = len + 1;
+ apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INSUFFICIENT_BUFFER));
+ return 0;
+ }
+ strcpy(buf_data, result);
+ *buf_size = len + 1;
+ return 1;
+ }
+
+ /* Not Found */
+ apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_INDEX));
+ return 0;
+}
+
+static int APR_THREAD_FUNC regfnReadClient(isapi_cid *cid,
+ void *buf_data,
+ apr_uint32_t *buf_size)
+{
+ request_rec *r = cid->r;
+ apr_uint32_t read = 0;
+ int res = 0;
+
+ if (r->remaining < *buf_size) {
+ *buf_size = (apr_size_t)r->remaining;
+ }
+
+ while (read < *buf_size &&
+ ((res = ap_get_client_block(r, (char*)buf_data + read,
+ *buf_size - read)) > 0)) {
+ read += res;
+ }
+
+ *buf_size = read;
+ if (res < 0) {
+ apr_set_os_error(APR_FROM_OS_ERROR(ERROR_READ_FAULT));
+ }
+ return (res >= 0);
+}
+
+/* Common code invoked for both HSE_REQ_SEND_RESPONSE_HEADER and
+ * the newer HSE_REQ_SEND_RESPONSE_HEADER_EX ServerSupportFunction(s)
+ * as well as other functions that write responses and presume that
+ * the support functions above are optional.
+ *
+ * Other callers trying to split headers and body bytes should pass
+ * head/headlen alone (leaving stat/statlen NULL/0), so that they
+ * get a proper count of bytes consumed. The argument passed to stat
+ * isn't counted as the head bytes are.
+ */
+static apr_ssize_t send_response_header(isapi_cid *cid,
+ const char *stat,
+ const char *head,
+ apr_size_t statlen,
+ apr_size_t headlen)
+{
+ int head_present = 1;
+ int termarg;
+ int res;
+ int old_status;
+ const char *termch;
+ apr_size_t ate = 0;
+
+ if (!head || headlen == 0 || !*head) {
+ head = stat;
+ stat = NULL;
+ headlen = statlen;
+ statlen = 0;
+ head_present = 0; /* Don't eat the header */
+ }
+
+ if (!stat || statlen == 0 || !*stat) {
+ if (head && headlen && *head && ((stat = memchr(head, '\r', headlen))
+ || (stat = memchr(head, '\n', headlen))
+ || (stat = memchr(head, '\0', headlen))
+ || (stat = head + headlen))) {
+ statlen = stat - head;
+ if (memchr(head, ':', statlen)) {
+ stat = "Status: 200 OK";
+ statlen = strlen(stat);
+ }
+ else {
+ const char *flip = head;
+ head = stat;
+ stat = flip;
+ headlen -= statlen;
+ ate += statlen;
+ if (*head == '\r' && headlen)
+ ++head, --headlen, ++ate;
+ if (*head == '\n' && headlen)
+ ++head, --headlen, ++ate;
+ }
+ }
+ }
+
+ if (stat && (statlen > 0) && *stat) {
+ char *newstat;
+ if (!apr_isdigit(*stat)) {
+ const char *stattok = stat;
+ int toklen = statlen;
+ while (toklen && *stattok && !apr_isspace(*stattok)) {
+ ++stattok; --toklen;
+ }
+ while (toklen && apr_isspace(*stattok)) {
+ ++stattok; --toklen;
+ }
+ /* Now decide if we follow the xxx message
+ * or the http/x.x xxx message format
+ */
+ if (toklen && apr_isdigit(*stattok)) {
+ statlen = toklen;
+ stat = stattok;
+ }
+ }
+ newstat = apr_palloc(cid->r->pool, statlen + 9);
+ strcpy(newstat, "Status: ");
+ apr_cpystrn(newstat + 8, stat, statlen + 1);
+ stat = newstat;
+ statlen += 8;
+ }
+
+ if (!head || headlen == 0 || !*head) {
+ head = "\r\n";
+ headlen = 2;
+ }
+ else
+ {
+ if (head[headlen - 1] && head[headlen]) {
+ /* Whoops... not NULL terminated */
+ head = apr_pstrndup(cid->r->pool, head, headlen);
+ }
+ }
+
+ /* Seems IIS does not enforce the requirement for \r\n termination
+ * on HSE_REQ_SEND_RESPONSE_HEADER, but we won't panic...
+ * ap_scan_script_header_err_strs handles this aspect for us.
+ *
+ * Parse them out, or die trying
+ */
+ old_status = cid->r->status;
+
+ if (stat) {
+ res = ap_scan_script_header_err_strs_ex(cid->r, NULL,
+ APLOG_MODULE_INDEX, &termch, &termarg, stat, head, NULL);
+ }
+ else {
+ res = ap_scan_script_header_err_strs_ex(cid->r, NULL,
+ APLOG_MODULE_INDEX, &termch, &termarg, head, NULL);
+ }
+
+ /* Set our status. */
+ if (res) {
+ /* This is an immediate error result from the parser
+ */
+ cid->r->status = res;
+ cid->r->status_line = ap_get_status_line(cid->r->status);
+ cid->ecb->dwHttpStatusCode = cid->r->status;
+ }
+ else if (cid->r->status) {
+ /* We have a status in r->status, so let's just use it.
+ * This is likely to be the Status: parsed above, and
+ * may also be a delayed error result from the parser.
+ * If it was filled in, status_line should also have
+ * been filled in.
+ */
+ cid->ecb->dwHttpStatusCode = cid->r->status;
+ }
+ else if (cid->ecb->dwHttpStatusCode
+ && cid->ecb->dwHttpStatusCode != HTTP_OK) {
+ /* Now we fall back on dwHttpStatusCode if it appears
+ * ap_scan_script_header fell back on the default code.
+ * Any other results set dwHttpStatusCode to the decoded
+ * status value.
+ */
+ cid->r->status = cid->ecb->dwHttpStatusCode;
+ cid->r->status_line = ap_get_status_line(cid->r->status);
+ }
+ else if (old_status) {
+ /* Well... either there is no dwHttpStatusCode or it's HTTP_OK.
+ * In any case, we don't have a good status to return yet...
+ * Perhaps the one we came in with will be better. Let's use it,
+ * if we were given one (note this is a pedantic case, it would
+ * normally be covered above unless the scan script code unset
+ * the r->status). Should there be a check here as to whether
+ * we are setting a valid response code?
+ */
+ cid->r->status = old_status;
+ cid->r->status_line = ap_get_status_line(cid->r->status);
+ cid->ecb->dwHttpStatusCode = cid->r->status;
+ }
+ else {
+ /* None of dwHttpStatusCode, the parser's r->status nor the
+ * old value of r->status were helpful, and nothing was decoded
+ * from Status: string passed to us. Let's just say HTTP_OK
+ * and get the data out, this was the isapi dev's oversight.
+ */
+ cid->r->status = HTTP_OK;
+ cid->r->status_line = ap_get_status_line(cid->r->status);
+ cid->ecb->dwHttpStatusCode = cid->r->status;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, cid->r, APLOGNO(02111)
+ "Could not determine HTTP response code; using %d",
+ cid->r->status);
+ }
+
+ if (cid->r->status == HTTP_INTERNAL_SERVER_ERROR) {
+ return -1;
+ }
+
+ /* If only Status was passed, we consumed nothing
+ */
+ if (!head_present)
+ return 0;
+
+ cid->headers_set = 1;
+
+ /* If all went well, tell the caller we consumed the headers complete
+ */
+ if (!termch)
+ return(ate + headlen);
+
+ /* Any data left must be sent directly by the caller, all we
+ * give back is the size of the headers we consumed (which only
+ * happens if the parser got to the head arg, which varies based
+ * on whether we passed stat+head to scan, or only head.
+ */
+ if (termch && (termarg == (stat ? 1 : 0))
+ && head_present && head + headlen > termch) {
+ return ate + termch - head;
+ }
+ return ate;
+}
+
+static int APR_THREAD_FUNC regfnWriteClient(isapi_cid *cid,
+ void *buf_ptr,
+ apr_uint32_t *size_arg,
+ apr_uint32_t flags)
+{
+ request_rec *r = cid->r;
+ conn_rec *c = r->connection;
+ apr_uint32_t buf_size = *size_arg;
+ char *buf_data = (char*)buf_ptr;
+ apr_bucket_brigade *bb;
+ apr_bucket *b;
+ apr_status_t rv = APR_SUCCESS;
+
+ if (!cid->headers_set) {
+ /* It appears that the foxisapi module and other clients
+ * presume that WriteClient("headers\n\nbody") will work.
+ * Parse them out, or die trying.
+ */
+ apr_ssize_t ate;
+ ate = send_response_header(cid, NULL, buf_data, 0, buf_size);
+ if (ate < 0) {
+ apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+ return 0;
+ }
+
+ buf_data += ate;
+ buf_size -= ate;
+ }
+
+ if (buf_size) {
+ bb = apr_brigade_create(r->pool, c->bucket_alloc);
+ b = apr_bucket_transient_create(buf_data, buf_size, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ b = apr_bucket_flush_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ rv = ap_pass_brigade(r->output_filters, bb);
+ cid->response_sent = 1;
+ if (rv != APR_SUCCESS)
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(02984)
+ "WriteClient ap_pass_brigade failed: %s",
+ r->filename);
+ }
+
+ if ((flags & HSE_IO_ASYNC) && cid->completion) {
+ if (rv == APR_SUCCESS) {
+ cid->completion(cid->ecb, cid->completion_arg,
+ *size_arg, ERROR_SUCCESS);
+ }
+ else {
+ cid->completion(cid->ecb, cid->completion_arg,
+ *size_arg, ERROR_WRITE_FAULT);
+ }
+ }
+ return (rv == APR_SUCCESS);
+}
+
+static int APR_THREAD_FUNC regfnServerSupportFunction(isapi_cid *cid,
+ apr_uint32_t HSE_code,
+ void *buf_ptr,
+ apr_uint32_t *buf_size,
+ apr_uint32_t *data_type)
+{
+ request_rec *r = cid->r;
+ conn_rec *c = r->connection;
+ char *buf_data = (char*)buf_ptr;
+ request_rec *subreq;
+ apr_status_t rv;
+
+ switch (HSE_code) {
+ case HSE_REQ_SEND_URL_REDIRECT_RESP:
+ /* Set the status to be returned when the HttpExtensionProc()
+ * is done.
+ * WARNING: Microsoft now advertises HSE_REQ_SEND_URL_REDIRECT_RESP
+ * and HSE_REQ_SEND_URL as equivalent per the Jan 2000 SDK.
+ * They most definitely are not, even in their own samples.
+ */
+ apr_table_set (r->headers_out, "Location", buf_data);
+ cid->r->status = cid->ecb->dwHttpStatusCode = HTTP_MOVED_TEMPORARILY;
+ cid->r->status_line = ap_get_status_line(cid->r->status);
+ cid->headers_set = 1;
+ return 1;
+
+ case HSE_REQ_SEND_URL:
+ /* Soak up remaining input */
+ if (r->remaining > 0) {
+ char argsbuffer[HUGE_STRING_LEN];
+ while (ap_get_client_block(r, argsbuffer, HUGE_STRING_LEN));
+ }
+
+ /* Reset the method to GET */
+ r->method = "GET";
+ r->method_number = M_GET;
+
+ /* Don't let anyone think there's still data */
+ apr_table_unset(r->headers_in, "Content-Length");
+
+ /* AV fault per PR3598 - redirected path is lost! */
+ buf_data = apr_pstrdup(r->pool, (char*)buf_data);
+ ap_internal_redirect(buf_data, r);
+ return 1;
+
+ case HSE_REQ_SEND_RESPONSE_HEADER:
+ {
+ /* Parse them out, or die trying */
+ apr_size_t statlen = 0, headlen = 0;
+ apr_ssize_t ate;
+ if (buf_data)
+ statlen = strlen((char*) buf_data);
+ if (data_type)
+ headlen = strlen((char*) data_type);
+ ate = send_response_header(cid, (char*) buf_data,
+ (char*) data_type,
+ statlen, headlen);
+ if (ate < 0) {
+ apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+ return 0;
+ }
+ else if ((apr_size_t)ate < headlen) {
+ apr_bucket_brigade *bb;
+ apr_bucket *b;
+ bb = apr_brigade_create(cid->r->pool, c->bucket_alloc);
+ b = apr_bucket_transient_create((char*) data_type + ate,
+ headlen - ate, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ b = apr_bucket_flush_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ rv = ap_pass_brigade(cid->r->output_filters, bb);
+ cid->response_sent = 1;
+ if (rv != APR_SUCCESS)
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(03177)
+ "ServerSupportFunction "
+ "HSE_REQ_SEND_RESPONSE_HEADER "
+ "ap_pass_brigade failed: %s", r->filename);
+ return (rv == APR_SUCCESS);
+ }
+ /* Deliberately hold off sending 'just the headers' to begin to
+ * accumulate the body and speed up the overall response, or at
+ * least wait for the end the session.
+ */
+ return 1;
+ }
+
+ case HSE_REQ_DONE_WITH_SESSION:
+ /* Signal to resume the thread completing this request,
+ * leave it to the pool cleanup to dispose of our mutex.
+ */
+ if (cid->completed) {
+ (void)apr_thread_mutex_unlock(cid->completed);
+ return 1;
+ }
+ else if (cid->dconf.log_unsupported) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02671)
+ "ServerSupportFunction "
+ "HSE_REQ_DONE_WITH_SESSION is not supported: %s",
+ r->filename);
+ }
+ apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+ return 0;
+
+ case HSE_REQ_MAP_URL_TO_PATH:
+ {
+ /* Map a URL to a filename */
+ char *file = (char *)buf_data;
+ apr_uint32_t len;
+ subreq = ap_sub_req_lookup_uri(
+ apr_pstrndup(cid->r->pool, file, *buf_size), r, NULL);
+
+ if (!subreq->filename) {
+ ap_destroy_sub_req(subreq);
+ return 0;
+ }
+
+ len = (apr_uint32_t)strlen(subreq->filename);
+
+ if ((subreq->finfo.filetype == APR_DIR)
+ && (!subreq->path_info)
+ && (subreq->filename[len - 1] != '/'))
+ file = apr_pstrcat(cid->r->pool, subreq->filename, "/", NULL);
+ else
+ file = apr_pstrcat(cid->r->pool, subreq->filename,
+ subreq->path_info, NULL);
+
+ ap_destroy_sub_req(subreq);
+
+#ifdef WIN32
+ /* We need to make this a real Windows path name */
+ apr_filepath_merge(&file, "", file, APR_FILEPATH_NATIVE, r->pool);
+#endif
+
+ *buf_size = apr_cpystrn(buf_data, file, *buf_size) - buf_data;
+
+ return 1;
+ }
+
+ case HSE_REQ_GET_SSPI_INFO:
+ if (cid->dconf.log_unsupported)
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02672)
+ "ServerSupportFunction HSE_REQ_GET_SSPI_INFO "
+ "is not supported: %s", r->filename);
+ apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+ return 0;
+
+ case HSE_APPEND_LOG_PARAMETER:
+ /* Log buf_data, of buf_size bytes, in the URI Query (cs-uri-query) field
+ */
+ apr_table_set(r->notes, "isapi-parameter", (char*) buf_data);
+ if (cid->dconf.log_to_query) {
+ if (r->args)
+ r->args = apr_pstrcat(r->pool, r->args, (char*) buf_data, NULL);
+ else
+ r->args = apr_pstrdup(r->pool, (char*) buf_data);
+ }
+ if (cid->dconf.log_to_errlog)
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02985)
+ "%s: %s", cid->r->filename,
+ (char*) buf_data);
+ return 1;
+
+ case HSE_REQ_IO_COMPLETION:
+ /* Emulates a completion port... Record callback address and
+ * user defined arg, we will call this after any async request
+ * (e.g. transmitfile) as if the request executed async.
+ * Per MS docs... HSE_REQ_IO_COMPLETION replaces any prior call
+ * to HSE_REQ_IO_COMPLETION, and buf_data may be set to NULL.
+ */
+ if (cid->dconf.fake_async) {
+ cid->completion = (PFN_HSE_IO_COMPLETION) buf_data;
+ cid->completion_arg = (void *) data_type;
+ return 1;
+ }
+ if (cid->dconf.log_unsupported)
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02673)
+ "ServerSupportFunction HSE_REQ_IO_COMPLETION "
+ "is not supported: %s", r->filename);
+ apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+ return 0;
+
+ case HSE_REQ_TRANSMIT_FILE:
+ {
+ /* we do nothing with (tf->dwFlags & HSE_DISCONNECT_AFTER_SEND)
+ */
+ HSE_TF_INFO *tf = (HSE_TF_INFO*)buf_data;
+ apr_uint32_t sent = 0;
+ apr_ssize_t ate = 0;
+ apr_bucket_brigade *bb;
+ apr_bucket *b;
+ apr_file_t *fd;
+ apr_off_t fsize;
+
+ if (!cid->dconf.fake_async && (tf->dwFlags & HSE_IO_ASYNC)) {
+ if (cid->dconf.log_unsupported)
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02674)
+ "ServerSupportFunction HSE_REQ_TRANSMIT_FILE "
+ "as HSE_IO_ASYNC is not supported: %s", r->filename);
+ apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+ return 0;
+ }
+
+ /* Presume the handle was opened with the CORRECT semantics
+ * for TransmitFile
+ */
+ if ((rv = apr_os_file_put(&fd, &tf->hFile,
+ APR_READ | APR_XTHREAD, r->pool))
+ != APR_SUCCESS) {
+ return 0;
+ }
+ if (tf->BytesToWrite) {
+ fsize = tf->BytesToWrite;
+ }
+ else {
+ apr_finfo_t fi;
+ if (apr_file_info_get(&fi, APR_FINFO_SIZE, fd) != APR_SUCCESS) {
+ apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+ return 0;
+ }
+ fsize = fi.size - tf->Offset;
+ }
+
+ /* apr_dupfile_oshandle (&fd, tf->hFile, r->pool); */
+ bb = apr_brigade_create(r->pool, c->bucket_alloc);
+
+ /* According to MS: if calling HSE_REQ_TRANSMIT_FILE with the
+ * HSE_IO_SEND_HEADERS flag, then you can't otherwise call any
+ * HSE_SEND_RESPONSE_HEADERS* fn, but if you don't use the flag,
+ * you must have done so. They document that the pHead headers
+ * option is valid only for HSE_IO_SEND_HEADERS - we are a bit
+ * more flexible and assume with the flag, pHead are the
+ * response headers, and without, pHead simply contains text
+ * (handled after this case).
+ */
+ if ((tf->dwFlags & HSE_IO_SEND_HEADERS) && tf->pszStatusCode) {
+ ate = send_response_header(cid, tf->pszStatusCode,
+ (char*)tf->pHead,
+ strlen(tf->pszStatusCode),
+ tf->HeadLength);
+ }
+ else if (!cid->headers_set && tf->pHead && tf->HeadLength
+ && *(char*)tf->pHead) {
+ ate = send_response_header(cid, NULL, (char*)tf->pHead,
+ 0, tf->HeadLength);
+ if (ate < 0)
+ {
+ apr_brigade_destroy(bb);
+ apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+ return 0;
+ }
+ }
+
+ if (tf->pHead && (apr_size_t)ate < tf->HeadLength) {
+ b = apr_bucket_transient_create((char*)tf->pHead + ate,
+ tf->HeadLength - ate,
+ c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ sent = tf->HeadLength;
+ }
+
+ sent += (apr_uint32_t)fsize;
+ apr_brigade_insert_file(bb, fd, tf->Offset, fsize, r->pool);
+
+ if (tf->pTail && tf->TailLength) {
+ sent += tf->TailLength;
+ b = apr_bucket_transient_create((char*)tf->pTail,
+ tf->TailLength, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ }
+
+ b = apr_bucket_flush_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ rv = ap_pass_brigade(r->output_filters, bb);
+ cid->response_sent = 1;
+ if (rv != APR_SUCCESS)
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(03178)
+ "ServerSupportFunction "
+ "HSE_REQ_TRANSMIT_FILE "
+ "ap_pass_brigade failed: %s", r->filename);
+
+ /* Use tf->pfnHseIO + tf->pContext, or if NULL, then use cid->fnIOComplete
+ * pass pContect to the HseIO callback.
+ */
+ if (tf->dwFlags & HSE_IO_ASYNC) {
+ if (tf->pfnHseIO) {
+ if (rv == APR_SUCCESS) {
+ tf->pfnHseIO(cid->ecb, tf->pContext,
+ ERROR_SUCCESS, sent);
+ }
+ else {
+ tf->pfnHseIO(cid->ecb, tf->pContext,
+ ERROR_WRITE_FAULT, sent);
+ }
+ }
+ else if (cid->completion) {
+ if (rv == APR_SUCCESS) {
+ cid->completion(cid->ecb, cid->completion_arg,
+ sent, ERROR_SUCCESS);
+ }
+ else {
+ cid->completion(cid->ecb, cid->completion_arg,
+ sent, ERROR_WRITE_FAULT);
+ }
+ }
+ }
+ return (rv == APR_SUCCESS);
+ }
+
+ case HSE_REQ_REFRESH_ISAPI_ACL:
+ if (cid->dconf.log_unsupported)
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02675)
+ "ServerSupportFunction "
+ "HSE_REQ_REFRESH_ISAPI_ACL "
+ "is not supported: %s", r->filename);
+ apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+ return 0;
+
+ case HSE_REQ_IS_KEEP_CONN:
+ *((int *)buf_data) = (r->connection->keepalive == AP_CONN_KEEPALIVE);
+ return 1;
+
+ case HSE_REQ_ASYNC_READ_CLIENT:
+ {
+ apr_uint32_t read = 0;
+ int res = 0;
+ if (!cid->dconf.fake_async) {
+ if (cid->dconf.log_unsupported)
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02986)
+ "asynchronous I/O not supported: %s",
+ r->filename);
+ apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+ return 0;
+ }
+
+ if (r->remaining < *buf_size) {
+ *buf_size = (apr_size_t)r->remaining;
+ }
+
+ while (read < *buf_size &&
+ ((res = ap_get_client_block(r, (char*)buf_data + read,
+ *buf_size - read)) > 0)) {
+ read += res;
+ }
+
+ if ((*data_type & HSE_IO_ASYNC) && cid->completion) {
+ /* XXX: Many authors issue their next HSE_REQ_ASYNC_READ_CLIENT
+ * within the completion logic. An example is MS's own PSDK
+ * sample web/iis/extensions/io/ASyncRead. This potentially
+ * leads to stack exhaustion. To refactor, the notification
+ * logic needs to move to isapi_handler() - differentiating
+ * the cid->completed event with a new flag to indicate
+ * an async-notice versus the async request completed.
+ */
+ if (res >= 0) {
+ cid->completion(cid->ecb, cid->completion_arg,
+ read, ERROR_SUCCESS);
+ }
+ else {
+ cid->completion(cid->ecb, cid->completion_arg,
+ read, ERROR_READ_FAULT);
+ }
+ }
+ return (res >= 0);
+ }
+
+ case HSE_REQ_GET_IMPERSONATION_TOKEN: /* Added in ISAPI 4.0 */
+ if (cid->dconf.log_unsupported)
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02676)
+ "ServerSupportFunction "
+ "HSE_REQ_GET_IMPERSONATION_TOKEN "
+ "is not supported: %s", r->filename);
+ apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+ return 0;
+
+ case HSE_REQ_MAP_URL_TO_PATH_EX:
+ {
+ /* Map a URL to a filename */
+ HSE_URL_MAPEX_INFO *info = (HSE_URL_MAPEX_INFO*)data_type;
+ char* test_uri = apr_pstrndup(r->pool, (char *)buf_data, *buf_size);
+
+ subreq = ap_sub_req_lookup_uri(test_uri, r, NULL);
+ info->cchMatchingURL = strlen(test_uri);
+ info->cchMatchingPath = apr_cpystrn(info->lpszPath, subreq->filename,
+ sizeof(info->lpszPath)) - info->lpszPath;
+
+ /* Mapping started with assuming both strings matched.
+ * Now roll on the path_info as a mismatch and handle
+ * terminating slashes for directory matches.
+ */
+ if (subreq->path_info && *subreq->path_info) {
+ apr_cpystrn(info->lpszPath + info->cchMatchingPath,
+ subreq->path_info,
+ sizeof(info->lpszPath) - info->cchMatchingPath);
+ info->cchMatchingURL -= strlen(subreq->path_info);
+ if (subreq->finfo.filetype == APR_DIR
+ && info->cchMatchingPath < sizeof(info->lpszPath) - 1) {
+ /* roll forward over path_info's first slash */
+ ++info->cchMatchingPath;
+ ++info->cchMatchingURL;
+ }
+ }
+ else if (subreq->finfo.filetype == APR_DIR
+ && info->cchMatchingPath < sizeof(info->lpszPath) - 1) {
+ /* Add a trailing slash for directory */
+ info->lpszPath[info->cchMatchingPath++] = '/';
+ info->lpszPath[info->cchMatchingPath] = '\0';
+ }
+
+ /* If the matched isn't a file, roll match back to the prior slash */
+ if (subreq->finfo.filetype == APR_NOFILE) {
+ while (info->cchMatchingPath && info->cchMatchingURL) {
+ if (info->lpszPath[info->cchMatchingPath - 1] == '/')
+ break;
+ --info->cchMatchingPath;
+ --info->cchMatchingURL;
+ }
+ }
+
+ /* Paths returned with back slashes */
+ for (test_uri = info->lpszPath; *test_uri; ++test_uri)
+ if (*test_uri == '/')
+ *test_uri = '\\';
+
+ /* is a combination of:
+ * HSE_URL_FLAGS_READ 0x001 Allow read
+ * HSE_URL_FLAGS_WRITE 0x002 Allow write
+ * HSE_URL_FLAGS_EXECUTE 0x004 Allow execute
+ * HSE_URL_FLAGS_SSL 0x008 Require SSL
+ * HSE_URL_FLAGS_DONT_CACHE 0x010 Don't cache (VRoot only)
+ * HSE_URL_FLAGS_NEGO_CERT 0x020 Allow client SSL cert
+ * HSE_URL_FLAGS_REQUIRE_CERT 0x040 Require client SSL cert
+ * HSE_URL_FLAGS_MAP_CERT 0x080 Map client SSL cert to account
+ * HSE_URL_FLAGS_SSL128 0x100 Require 128-bit SSL cert
+ * HSE_URL_FLAGS_SCRIPT 0x200 Allow script execution
+ *
+ * XxX: As everywhere, EXEC flags could use some work...
+ * and this could go further with more flags, as desired.
+ */
+ info->dwFlags = (subreq->finfo.protection & APR_UREAD ? 0x001 : 0)
+ | (subreq->finfo.protection & APR_UWRITE ? 0x002 : 0)
+ | (subreq->finfo.protection & APR_UEXECUTE ? 0x204 : 0);
+ return 1;
+ }
+
+ case HSE_REQ_ABORTIVE_CLOSE:
+ if (cid->dconf.log_unsupported)
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02677)
+ "ServerSupportFunction HSE_REQ_ABORTIVE_CLOSE"
+ " is not supported: %s", r->filename);
+ apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+ return 0;
+
+ case HSE_REQ_GET_CERT_INFO_EX: /* Added in ISAPI 4.0 */
+ if (cid->dconf.log_unsupported)
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02678)
+ "ServerSupportFunction "
+ "HSE_REQ_GET_CERT_INFO_EX "
+ "is not supported: %s", r->filename);
+ apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+ return 0;
+
+ case HSE_REQ_SEND_RESPONSE_HEADER_EX: /* Added in ISAPI 4.0 */
+ {
+ HSE_SEND_HEADER_EX_INFO *shi = (HSE_SEND_HEADER_EX_INFO*)buf_data;
+
+ /* Ignore shi->fKeepConn - we don't want the advise
+ */
+ apr_ssize_t ate = send_response_header(cid, shi->pszStatus,
+ shi->pszHeader,
+ shi->cchStatus,
+ shi->cchHeader);
+ if (ate < 0) {
+ apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+ return 0;
+ }
+ else if ((apr_size_t)ate < shi->cchHeader) {
+ apr_bucket_brigade *bb;
+ apr_bucket *b;
+ bb = apr_brigade_create(cid->r->pool, c->bucket_alloc);
+ b = apr_bucket_transient_create(shi->pszHeader + ate,
+ shi->cchHeader - ate,
+ c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ b = apr_bucket_flush_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ rv = ap_pass_brigade(cid->r->output_filters, bb);
+ cid->response_sent = 1;
+ if (rv != APR_SUCCESS)
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(03179)
+ "ServerSupportFunction "
+ "HSE_REQ_SEND_RESPONSE_HEADER_EX "
+ "ap_pass_brigade failed: %s", r->filename);
+ return (rv == APR_SUCCESS);
+ }
+ /* Deliberately hold off sending 'just the headers' to begin to
+ * accumulate the body and speed up the overall response, or at
+ * least wait for the end the session.
+ */
+ return 1;
+ }
+
+ case HSE_REQ_CLOSE_CONNECTION: /* Added after ISAPI 4.0 */
+ if (cid->dconf.log_unsupported)
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02679)
+ "ServerSupportFunction "
+ "HSE_REQ_CLOSE_CONNECTION "
+ "is not supported: %s", r->filename);
+ apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+ return 0;
+
+ case HSE_REQ_IS_CONNECTED: /* Added after ISAPI 4.0 */
+ /* Returns True if client is connected c.f. MSKB Q188346
+ * assuming the identical return mechanism as HSE_REQ_IS_KEEP_CONN
+ */
+ *((int *)buf_data) = (r->connection->aborted == 0);
+ return 1;
+
+ case HSE_REQ_EXTENSION_TRIGGER: /* Added after ISAPI 4.0 */
+ /* Undocumented - defined by the Microsoft Jan '00 Platform SDK
+ */
+ if (cid->dconf.log_unsupported)
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02680)
+ "ServerSupportFunction "
+ "HSE_REQ_EXTENSION_TRIGGER "
+ "is not supported: %s", r->filename);
+ apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+ return 0;
+
+ default:
+ if (cid->dconf.log_unsupported)
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02681)
+ "ServerSupportFunction (%d) not supported: "
+ "%s", HSE_code, r->filename);
+ apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+ return 0;
+ }
+}
+
+/**********************************************************
+ *
+ * ISAPI Module request invocation section
+ *
+ **********************************************************/
+
+static apr_status_t isapi_handler (request_rec *r)
+{
+ isapi_dir_conf *dconf;
+ apr_table_t *e;
+ apr_status_t rv;
+ isapi_loaded *isa;
+ isapi_cid *cid;
+ const char *val;
+ apr_uint32_t read;
+ int res;
+
+ if (strcmp(r->handler, "isapi-isa")
+ && strcmp(r->handler, "isapi-handler")) {
+ /* Hang on to the isapi-isa for compatibility with older docs
+ * (wtf did '-isa' mean in the first place?) but introduce
+ * a newer and clearer "isapi-handler" name.
+ */
+ return DECLINED;
+ }
+ dconf = ap_get_module_config(r->per_dir_config, &isapi_module);
+ e = r->subprocess_env;
+
+ /* Use similar restrictions as CGIs
+ *
+ * If this fails, it's pointless to load the isapi dll.
+ */
+ if (!(ap_allow_options(r) & OPT_EXECCGI)) {
+ return HTTP_FORBIDDEN;
+ }
+ if (r->finfo.filetype == APR_NOFILE) {
+ return HTTP_NOT_FOUND;
+ }
+ if (r->finfo.filetype != APR_REG) {
+ return HTTP_FORBIDDEN;
+ }
+ if ((r->used_path_info == AP_REQ_REJECT_PATH_INFO) &&
+ r->path_info && *r->path_info) {
+ /* default to accept */
+ return HTTP_NOT_FOUND;
+ }
+
+ if (isapi_lookup(r->pool, r->server, r, r->filename, &isa)
+ != APR_SUCCESS) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ /* Set up variables */
+ ap_add_common_vars(r);
+ ap_add_cgi_vars(r);
+ apr_table_setn(e, "UNMAPPED_REMOTE_USER", "REMOTE_USER");
+ if ((val = apr_table_get(e, "HTTPS")) && (strcmp(val, "on") == 0))
+ apr_table_setn(e, "SERVER_PORT_SECURE", "1");
+ else
+ apr_table_setn(e, "SERVER_PORT_SECURE", "0");
+ apr_table_setn(e, "URL", r->uri);
+
+ /* Set up connection structure and ecb,
+ * NULL or zero out most fields.
+ */
+ cid = apr_pcalloc(r->pool, sizeof(isapi_cid));
+
+ /* Fixup defaults for dconf */
+ cid->dconf.read_ahead_buflen = (dconf->read_ahead_buflen == ISAPI_UNDEF)
+ ? 49152 : dconf->read_ahead_buflen;
+ cid->dconf.log_unsupported = (dconf->log_unsupported == ISAPI_UNDEF)
+ ? 0 : dconf->log_unsupported;
+ cid->dconf.log_to_errlog = (dconf->log_to_errlog == ISAPI_UNDEF)
+ ? 0 : dconf->log_to_errlog;
+ cid->dconf.log_to_query = (dconf->log_to_query == ISAPI_UNDEF)
+ ? 1 : dconf->log_to_query;
+ cid->dconf.fake_async = (dconf->fake_async == ISAPI_UNDEF)
+ ? 0 : dconf->fake_async;
+
+ cid->ecb = apr_pcalloc(r->pool, sizeof(EXTENSION_CONTROL_BLOCK));
+ cid->ecb->ConnID = cid;
+ cid->isa = isa;
+ cid->r = r;
+ r->status = 0;
+
+ cid->ecb->cbSize = sizeof(EXTENSION_CONTROL_BLOCK);
+ cid->ecb->dwVersion = isa->report_version;
+ cid->ecb->dwHttpStatusCode = 0;
+ strcpy(cid->ecb->lpszLogData, "");
+ /* TODO: are copies really needed here?
+ */
+ cid->ecb->lpszMethod = (char*) r->method;
+ cid->ecb->lpszQueryString = (char*) apr_table_get(e, "QUERY_STRING");
+ cid->ecb->lpszPathInfo = (char*) apr_table_get(e, "PATH_INFO");
+ cid->ecb->lpszPathTranslated = (char*) apr_table_get(e, "PATH_TRANSLATED");
+ cid->ecb->lpszContentType = (char*) apr_table_get(e, "CONTENT_TYPE");
+
+ /* Set up the callbacks */
+ cid->ecb->GetServerVariable = regfnGetServerVariable;
+ cid->ecb->WriteClient = regfnWriteClient;
+ cid->ecb->ReadClient = regfnReadClient;
+ cid->ecb->ServerSupportFunction = regfnServerSupportFunction;
+
+ /* Set up client input */
+ res = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR);
+ if (res) {
+ return res;
+ }
+
+ if (ap_should_client_block(r)) {
+ /* Time to start reading the appropriate amount of data,
+ * and allow the administrator to tweak the number
+ */
+ if (r->remaining) {
+ cid->ecb->cbTotalBytes = (apr_size_t)r->remaining;
+ if (cid->ecb->cbTotalBytes > (apr_uint32_t)cid->dconf.read_ahead_buflen)
+ cid->ecb->cbAvailable = cid->dconf.read_ahead_buflen;
+ else
+ cid->ecb->cbAvailable = cid->ecb->cbTotalBytes;
+ }
+ else
+ {
+ cid->ecb->cbTotalBytes = 0xffffffff;
+ cid->ecb->cbAvailable = cid->dconf.read_ahead_buflen;
+ }
+
+ cid->ecb->lpbData = apr_pcalloc(r->pool, cid->ecb->cbAvailable + 1);
+
+ read = 0;
+ while (read < cid->ecb->cbAvailable &&
+ ((res = ap_get_client_block(r, (char*)cid->ecb->lpbData + read,
+ cid->ecb->cbAvailable - read)) > 0)) {
+ read += res;
+ }
+
+ if (res < 0) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* Although it's not to spec, IIS seems to null-terminate
+ * its lpdData string. So we will too.
+ */
+ if (res == 0)
+ cid->ecb->cbAvailable = cid->ecb->cbTotalBytes = read;
+ else
+ cid->ecb->cbAvailable = read;
+ cid->ecb->lpbData[read] = '\0';
+ }
+ else {
+ cid->ecb->cbTotalBytes = 0;
+ cid->ecb->cbAvailable = 0;
+ cid->ecb->lpbData = NULL;
+ }
+
+ /* To emulate async behavior...
+ *
+ * We create a cid->completed mutex and lock on it so that the
+ * app can believe is it running async.
+ *
+ * This request completes upon a notification through
+ * ServerSupportFunction(HSE_REQ_DONE_WITH_SESSION), which
+ * unlocks this mutex. If the HttpExtensionProc() returns
+ * HSE_STATUS_PENDING, we will attempt to gain this lock again
+ * which may *only* happen once HSE_REQ_DONE_WITH_SESSION has
+ * unlocked the mutex.
+ */
+ if (cid->dconf.fake_async) {
+ rv = apr_thread_mutex_create(&cid->completed,
+ APR_THREAD_MUTEX_UNNESTED,
+ r->pool);
+ if (cid->completed && (rv == APR_SUCCESS)) {
+ rv = apr_thread_mutex_lock(cid->completed);
+ }
+
+ if (!cid->completed || (rv != APR_SUCCESS)) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02112)
+ "Failed to create completion mutex");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ /* All right... try and run the sucker */
+ rv = (*isa->HttpExtensionProc)(cid->ecb);
+
+ /* Check for a log message - and log it */
+ if (*cid->ecb->lpszLogData) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02113)
+ "%s: %s", r->filename, cid->ecb->lpszLogData);
+ }
+
+ switch(rv) {
+ case 0: /* Strange, but MS isapi accepts this as success */
+ case HSE_STATUS_SUCCESS:
+ case HSE_STATUS_SUCCESS_AND_KEEP_CONN:
+ /* Ignore the keepalive stuff; Apache handles it just fine without
+ * the ISAPI Handler's "advice".
+ * Per Microsoft: "In IIS versions 4.0 and later, the return
+ * values HSE_STATUS_SUCCESS and HSE_STATUS_SUCCESS_AND_KEEP_CONN
+ * are functionally identical: Keep-Alive connections are
+ * maintained, if supported by the client."
+ * ... so we were pat all this time
+ */
+ break;
+
+ case HSE_STATUS_PENDING:
+ /* emulating async behavior...
+ */
+ if (cid->completed) {
+ /* The completion port was locked prior to invoking
+ * HttpExtensionProc(). Once we can regain the lock,
+ * when ServerSupportFunction(HSE_REQ_DONE_WITH_SESSION)
+ * is called by the extension to release the lock,
+ * we may finally destroy the request.
+ */
+ (void)apr_thread_mutex_lock(cid->completed);
+ break;
+ }
+ else if (cid->dconf.log_unsupported) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02114)
+ "asynch I/O result HSE_STATUS_PENDING "
+ "from HttpExtensionProc() is not supported: %s",
+ r->filename);
+ r->status = HTTP_INTERNAL_SERVER_ERROR;
+ }
+ break;
+
+ case HSE_STATUS_ERROR:
+ /* end response if we have yet to do so.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), r, APLOGNO(02115)
+ "HSE_STATUS_ERROR result from "
+ "HttpExtensionProc(): %s", r->filename);
+ r->status = HTTP_INTERNAL_SERVER_ERROR;
+ break;
+
+ default:
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), r, APLOGNO(02116)
+ "unrecognized result code %d "
+ "from HttpExtensionProc(): %s ",
+ rv, r->filename);
+ r->status = HTTP_INTERNAL_SERVER_ERROR;
+ break;
+ }
+
+ /* Flush the response now, including headers-only responses */
+ if (cid->headers_set || cid->response_sent) {
+ conn_rec *c = r->connection;
+ apr_bucket_brigade *bb;
+ apr_bucket *b;
+ apr_status_t rv;
+
+ bb = apr_brigade_create(r->pool, c->bucket_alloc);
+ b = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ rv = ap_pass_brigade(r->output_filters, bb);
+ cid->response_sent = 1;
+
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(02117)
+ "ap_pass_brigade failed to "
+ "complete the response: %s ", r->filename);
+ }
+
+ return OK; /* NOT r->status, even if it has changed. */
+ }
+
+ /* As the client returned no error, and if we did not error out
+ * ourselves, trust dwHttpStatusCode to say something relevant.
+ */
+ if (!ap_is_HTTP_SERVER_ERROR(r->status) && cid->ecb->dwHttpStatusCode) {
+ r->status = cid->ecb->dwHttpStatusCode;
+ }
+
+ /* For all missing-response situations simply return the status,
+ * and let the core respond to the client.
+ */
+ return r->status;
+}
+
+/**********************************************************
+ *
+ * ISAPI Module Setup Hooks
+ *
+ **********************************************************/
+
+static int isapi_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp)
+{
+ apr_status_t rv;
+
+ apr_pool_create_ex(&loaded.pool, pconf, NULL, NULL);
+ if (!loaded.pool) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, APR_EGENERAL, NULL, APLOGNO(02118)
+ "could not create the isapi cache pool");
+ return APR_EGENERAL;
+ }
+ apr_pool_tag(loaded.pool, "mod_isapi_load");
+
+ loaded.hash = apr_hash_make(loaded.pool);
+ if (!loaded.hash) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(02119)
+ "Failed to create module cache");
+ return APR_EGENERAL;
+ }
+
+ rv = apr_thread_mutex_create(&loaded.lock, APR_THREAD_MUTEX_DEFAULT,
+ loaded.pool);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL, APLOGNO(02682)
+ "Failed to create module cache lock");
+ return rv;
+ }
+ return OK;
+}
+
+static void isapi_hooks(apr_pool_t *cont)
+{
+ ap_hook_pre_config(isapi_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_handler(isapi_handler, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(isapi) = {
+ STANDARD20_MODULE_STUFF,
+ create_isapi_dir_config, /* create per-dir config */
+ merge_isapi_dir_configs, /* merge per-dir config */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ isapi_cmds, /* command apr_table_t */
+ isapi_hooks /* register hooks */
+};
diff --git a/modules/arch/win32/mod_isapi.dep b/modules/arch/win32/mod_isapi.dep
new file mode 100644
index 0000000..b36dbcf
--- /dev/null
+++ b/modules/arch/win32/mod_isapi.dep
@@ -0,0 +1,61 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_isapi.mak
+
+..\..\..\build\win32\httpd.rc : \
+ "..\..\..\include\ap_release.h"\
+
+
+.\mod_isapi.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_expr.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\http_config.h"\
+ "..\..\..\include\http_core.h"\
+ "..\..\..\include\http_log.h"\
+ "..\..\..\include\http_protocol.h"\
+ "..\..\..\include\http_request.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\mod_core.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\util_cfgtree.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_script.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_thread_rwlock.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_isapi.h"\
+
diff --git a/modules/arch/win32/mod_isapi.dsp b/modules/arch/win32/mod_isapi.dsp
new file mode 100644
index 0000000..39df3e4
--- /dev/null
+++ b/modules/arch/win32/mod_isapi.dsp
@@ -0,0 +1,115 @@
+# Microsoft Developer Studio Project File - Name="mod_isapi" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_isapi - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_isapi.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_isapi.mak" CFG="mod_isapi - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_isapi - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_isapi - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_isapi - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_isapi_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_isapi.res" /i "../../../include" /i "../../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_isapi.so" /d LONG_NAME="isapi_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:".\Release\mod_isapi.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_isapi.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_isapi.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_isapi.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_isapi.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_isapi - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_isapi_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_isapi.res" /i "../../../include" /i "../../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_isapi.so" /d LONG_NAME="isapi_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_isapi.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_isapi.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_isapi.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_isapi.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_isapi.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_isapi - Win32 Release"
+# Name "mod_isapi - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_isapi.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\mod_isapi.h
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/arch/win32/mod_isapi.h b/modules/arch/win32/mod_isapi.h
new file mode 100644
index 0000000..5284386
--- /dev/null
+++ b/modules/arch/win32/mod_isapi.h
@@ -0,0 +1,271 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file mod_isapi.h
+ * @brief ISAPI module extension to Apache
+ *
+ * @defgroup MOD_ISAPI mod_isapi
+ * @ingroup APACHE_MODS
+ * @{
+ */
+
+#ifndef MOD_ISAPI_H
+#define MOD_ISAPI_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* The Version Information storage passed to a module on startup
+ * via the GetExtensionVersion() entry point.
+ */
+typedef struct HSE_VERSION_INFO {
+ apr_uint32_t dwExtensionVersion;
+ char lpszExtensionDesc[256];
+} HSE_VERSION_INFO;
+
+/* The startup entry point that must be exported by every ISAPI handler
+ */
+int APR_THREAD_FUNC GetExtensionVersion(HSE_VERSION_INFO *ver_info);
+typedef int (APR_THREAD_FUNC *PFN_GETEXTENSIONVERSION)(HSE_VERSION_INFO *ver_info);
+
+/* Our internal 'HCONN' representation, always opaque to the user.
+ */
+typedef struct isapi_cid isapi_cid;
+typedef struct isapi_cid *HCONN;
+
+/* Prototypes of the essential functions exposed by mod_isapi
+ * for the module to communicate with Apache.
+ */
+typedef int (APR_THREAD_FUNC
+ *PFN_GETSERVERVARIABLE)(HCONN cid,
+ char *variable_name,
+ void *buf_data,
+ apr_uint32_t *buf_size);
+typedef int (APR_THREAD_FUNC
+ *PFN_WRITECLIENT)(HCONN cid,
+ void *buf_data,
+ apr_uint32_t *buf_size,
+ apr_uint32_t flags);
+typedef int (APR_THREAD_FUNC
+ *PFN_READCLIENT)(HCONN cid,
+ void *buf_data,
+ apr_uint32_t *buf_size);
+typedef int (APR_THREAD_FUNC
+ *PFN_SERVERSUPPORTFUNCTION)(HCONN cid,
+ apr_uint32_t HSE_code,
+ void *buf_data,
+ apr_uint32_t *buf_size,
+ apr_uint32_t *flags);
+
+/* The ecb structure is passed on each invocation of the module
+ */
+typedef struct EXTENSION_CONTROL_BLOCK {
+ apr_uint32_t cbSize;
+ apr_uint32_t dwVersion;
+ HCONN ConnID;
+ apr_uint32_t dwHttpStatusCode;
+ char lpszLogData[80];
+ char *lpszMethod;
+ char *lpszQueryString;
+ char *lpszPathInfo;
+ char *lpszPathTranslated;
+ apr_uint32_t cbTotalBytes;
+ apr_uint32_t cbAvailable;
+ unsigned char *lpbData;
+ char *lpszContentType;
+
+ PFN_GETSERVERVARIABLE GetServerVariable;
+ PFN_WRITECLIENT WriteClient;
+ PFN_READCLIENT ReadClient;
+ PFN_SERVERSUPPORTFUNCTION ServerSupportFunction;
+} EXTENSION_CONTROL_BLOCK;
+
+/* Status/Headers structure to pass to HSE_SEND_HEADER_EX,
+ * an MS extension to ServerSupportFunction
+ */
+typedef struct HSE_SEND_HEADER_EX_INFO {
+ const char * pszStatus; /* HTTP status text, such as "200 OK" */
+ const char * pszHeader; /* HTTP header lines text, such as
+ * "Content-type: text/plain\r\n"
+ * "Content-Language: en\r\n"
+ * Note that (in spite of cchFoo lengths below)
+ * NULL characters will interfere in headers.
+ */
+ apr_uint32_t cchStatus; /* length of pszStatus text */
+ apr_uint32_t cchHeader; /* length of pszHeader text */
+ int fKeepConn; /* Ignored: used to set keep-alive status,
+ * but Apache follows the client's negotiated
+ * HTTP contract to decide.
+ */
+} HSE_SEND_HEADER_EX_INFO;
+
+/* Our only 'supported' MS extended flag bit for TransmitFile,
+ * HSE_IO_SEND_HEADERS indicates that Status+Headers are present
+ * in the pszStatusCode member of the HSE_TF_INFO structure.
+ */
+#define HSE_IO_SEND_HEADERS 8
+
+/* The remaining flags are MS extended flag bits that bear little
+ * relation to Apache; the rules that the Apache server obeys follow
+ * its own design and HTTP protocol filter rules.
+ *
+ * We do not support async, however, we fake it. If HSE_IO_SYNC is
+ * not passed, and a completion context was defined, we will invoke the
+ * completion function immediately following the transfer, and then
+ * return to the caller. If HSE_IO_SYNC is passed, there is no call
+ * necessary to the completion context.
+ */
+#define HSE_IO_SYNC 1
+#define HSE_IO_ASYNC 2
+#define HSE_IO_DISCONNECT_AFTER_SEND 4
+#define HSE_IO_NODELAY 4096
+
+/* The Completion function prototype. This callback may be fixed with
+ * the HSE_REQ_IO_COMPLETION ServerSupportFunction call, or overridden
+ * for the HSE_REQ_TRANSMIT_FILE call.
+ */
+typedef void (APR_THREAD_FUNC *PFN_HSE_IO_COMPLETION)
+ (EXTENSION_CONTROL_BLOCK *ecb,
+ void *ctxt,
+ apr_uint32_t cbIO,
+ apr_uint32_t dwError);
+
+/* TransmitFile structure to pass to HSE_REQ_TRANSMIT_FILE, an MS extension
+ */
+typedef struct HSE_TF_INFO {
+ PFN_HSE_IO_COMPLETION pfnHseIO; /* Overrides the default setting of
+ * HSE_REQ_IO_COMPLETION if not NULL
+ */
+ void *pContext;
+ apr_os_file_t hFile; /* HANDLE/fd to transmit */
+ const char *pszStatusCode; /* Ignored if HSE_IO_SEND_HEADERS is
+ * not set. Includes HTTP status text
+ * plus header text lines, such as
+ * "200 OK\r\n"
+ * "Content-type: text/plain\r\n"
+ */
+ apr_uint32_t BytesToWrite; /* 0 is write-all */
+ apr_uint32_t Offset; /* File Offset */
+ void *pHead; /* Prefix with *pHead body text */
+ apr_uint32_t HeadLength; /* Length of *pHead body text */
+ void *pTail; /* Prefix with *pTail body text */
+ apr_uint32_t TailLength; /* Length of *pTail body text */
+ apr_uint32_t dwFlags; /* bit flags described above */
+} HSE_TF_INFO;
+
+typedef struct HSE_URL_MAPEX_INFO {
+ char lpszPath[260];
+ apr_uint32_t dwFlags;
+ apr_uint32_t cchMatchingPath;
+ apr_uint32_t cchMatchingURL;
+ apr_uint32_t dwReserved1;
+ apr_uint32_t dwReserved2;
+} HSE_URL_MAPEX_INFO;
+
+/* Original ISAPI ServerSupportFunction() HSE_code methods */
+#define HSE_REQ_SEND_URL_REDIRECT_RESP 1
+#define HSE_REQ_SEND_URL 2
+#define HSE_REQ_SEND_RESPONSE_HEADER 3
+#define HSE_REQ_DONE_WITH_SESSION 4
+
+/* MS Extended methods to ISAPI ServerSupportFunction() HSE_code */
+#define HSE_REQ_MAP_URL_TO_PATH 1001 /* Emulated */
+#define HSE_REQ_GET_SSPI_INFO 1002 /* Not Supported */
+#define HSE_APPEND_LOG_PARAMETER 1003 /* Supported */
+#define HSE_REQ_IO_COMPLETION 1005 /* Emulated */
+#define HSE_REQ_TRANSMIT_FILE 1006 /* Async Emulated */
+#define HSE_REQ_REFRESH_ISAPI_ACL 1007 /* Not Supported */
+#define HSE_REQ_IS_KEEP_CONN 1008 /* Supported */
+#define HSE_REQ_ASYNC_READ_CLIENT 1010 /* Emulated */
+/* Added with ISAPI 4.0 */
+#define HSE_REQ_GET_IMPERSONATION_TOKEN 1011 /* Not Supported */
+#define HSE_REQ_MAP_URL_TO_PATH_EX 1012 /* Emulated */
+#define HSE_REQ_ABORTIVE_CLOSE 1014 /* Ignored */
+/* Added after ISAPI 4.0 in IIS 5.0 */
+#define HSE_REQ_GET_CERT_INFO_EX 1015 /* Not Supported */
+#define HSE_REQ_SEND_RESPONSE_HEADER_EX 1016 /* Supported (no nulls!) */
+#define HSE_REQ_CLOSE_CONNECTION 1017 /* Ignored */
+#define HSE_REQ_IS_CONNECTED 1018 /* Supported */
+#define HSE_REQ_EXTENSION_TRIGGER 1020 /* Not Supported */
+
+/* The request entry point that must be exported by every ISAPI handler
+ */
+apr_uint32_t APR_THREAD_FUNC HttpExtensionProc(EXTENSION_CONTROL_BLOCK *ecb);
+typedef apr_uint32_t (APR_THREAD_FUNC
+ *PFN_HTTPEXTENSIONPROC)(EXTENSION_CONTROL_BLOCK *ecb);
+
+/* Allowable return values from HttpExtensionProc (apparently 0 is also
+ * accepted by MS IIS, and we will respect it as Success.)
+ * If the HttpExtensionProc returns HSE_STATUS_PENDING, we will create
+ * a wait mutex and lock on it, until HSE_REQ_DONE_WITH_SESSION is called.
+ */
+#define HSE_STATUS_SUCCESS 1
+#define HSE_STATUS_SUCCESS_AND_KEEP_CONN 2 /* 1 vs 2 Ignored, we choose */
+#define HSE_STATUS_PENDING 3 /* Emulated (thread lock) */
+#define HSE_STATUS_ERROR 4
+
+/* Anticipated error code for common faults within mod_isapi itself
+ */
+#ifndef ERROR_INSUFFICIENT_BUFFER
+#define ERROR_INSUFFICIENT_BUFFER ENOBUFS
+#endif
+#ifndef ERROR_INVALID_INDEX
+#define ERROR_INVALID_INDEX EINVAL
+#endif
+#ifndef ERROR_INVALID_PARAMETER
+#define ERROR_INVALID_PARAMETER EINVAL
+#endif
+#ifndef ERROR_READ_FAULT
+#define ERROR_READ_FAULT EIO
+#endif
+#ifndef ERROR_WRITE_FAULT
+#define ERROR_WRITE_FAULT EIO
+#endif
+#ifndef ERROR_SUCCESS
+#define ERROR_SUCCESS 0
+#endif
+
+/* Valid flags passed with TerminateExtension()
+ */
+#define HSE_TERM_MUST_UNLOAD 1
+#define HSE_TERM_ADVISORY_UNLOAD 2
+
+/* The shutdown entry point optionally exported by an ISAPI handler, passed
+ * HSE_TERM_MUST_UNLOAD or HSE_TERM_ADVISORY_UNLOAD. The module may return
+ * if passed HSE_TERM_ADVISORY_UNLOAD, and the module will remain loaded.
+ * If the module returns 1 to HSE_TERM_ADVISORY_UNLOAD it is immediately
+ * unloaded. If the module is passed HSE_TERM_MUST_UNLOAD, its return value
+ * is ignored.
+ */
+int APR_THREAD_FUNC TerminateExtension(apr_uint32_t flags);
+typedef int (APR_THREAD_FUNC *PFN_TERMINATEEXTENSION)(apr_uint32_t flags);
+
+/* Module may return 0 if passed HSE_TERM_ADVISORY_UNLOAD, and the module
+ * will remain loaded, or 1 if it consents to being unloaded. If the module
+ * is passed HSE_TERM_MUST_UNLOAD, its return value is ignored.
+ */
+#define HSE_TERM_MUST_UNLOAD 1
+#define HSE_TERM_ADVISORY_UNLOAD 2
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !MOD_ISAPI_H */
+/** @} */
+
diff --git a/modules/arch/win32/mod_isapi.mak b/modules/arch/win32/mod_isapi.mak
new file mode 100644
index 0000000..cee9047
--- /dev/null
+++ b/modules/arch/win32/mod_isapi.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_isapi.dsp
+!IF "$(CFG)" == ""
+CFG=mod_isapi - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_isapi - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_isapi - Win32 Release" && "$(CFG)" != "mod_isapi - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_isapi.mak" CFG="mod_isapi - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_isapi - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_isapi - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_isapi - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_isapi.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_isapi.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_isapi.obj"
+ -@erase "$(INTDIR)\mod_isapi.res"
+ -@erase "$(INTDIR)\mod_isapi_src.idb"
+ -@erase "$(INTDIR)\mod_isapi_src.pdb"
+ -@erase "$(OUTDIR)\mod_isapi.exp"
+ -@erase "$(OUTDIR)\mod_isapi.lib"
+ -@erase "$(OUTDIR)\mod_isapi.pdb"
+ -@erase "$(OUTDIR)\mod_isapi.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_isapi_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_isapi.res" /i "../../../include" /i "../../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_isapi.so" /d LONG_NAME="isapi_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_isapi.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_isapi.pdb" /debug /out:"$(OUTDIR)\mod_isapi.so" /implib:"$(OUTDIR)\mod_isapi.lib" /base:@..\..\..\os\win32\BaseAddr.ref,mod_isapi.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_isapi.obj" \
+ "$(INTDIR)\mod_isapi.res" \
+ "..\..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_isapi.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_isapi.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_isapi.so"
+ if exist .\Release\mod_isapi.so.manifest mt.exe -manifest .\Release\mod_isapi.so.manifest -outputresource:.\Release\mod_isapi.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_isapi - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_isapi.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_isapi.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_isapi.obj"
+ -@erase "$(INTDIR)\mod_isapi.res"
+ -@erase "$(INTDIR)\mod_isapi_src.idb"
+ -@erase "$(INTDIR)\mod_isapi_src.pdb"
+ -@erase "$(OUTDIR)\mod_isapi.exp"
+ -@erase "$(OUTDIR)\mod_isapi.lib"
+ -@erase "$(OUTDIR)\mod_isapi.pdb"
+ -@erase "$(OUTDIR)\mod_isapi.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_isapi_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_isapi.res" /i "../../../include" /i "../../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_isapi.so" /d LONG_NAME="isapi_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_isapi.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_isapi.pdb" /debug /out:"$(OUTDIR)\mod_isapi.so" /implib:"$(OUTDIR)\mod_isapi.lib" /base:@..\..\..\os\win32\BaseAddr.ref,mod_isapi.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_isapi.obj" \
+ "$(INTDIR)\mod_isapi.res" \
+ "..\..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_isapi.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_isapi.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_isapi.so"
+ if exist .\Debug\mod_isapi.so.manifest mt.exe -manifest .\Debug\mod_isapi.so.manifest -outputresource:.\Debug\mod_isapi.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_isapi.dep")
+!INCLUDE "mod_isapi.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_isapi.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_isapi - Win32 Release" || "$(CFG)" == "mod_isapi - Win32 Debug"
+
+!IF "$(CFG)" == "mod_isapi - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\arch\win32"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\arch\win32"
+
+!ELSEIF "$(CFG)" == "mod_isapi - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\arch\win32"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\arch\win32"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_isapi - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\arch\win32"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\arch\win32"
+
+!ELSEIF "$(CFG)" == "mod_isapi - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\arch\win32"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\arch\win32"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_isapi - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\arch\win32"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\arch\win32"
+
+!ELSEIF "$(CFG)" == "mod_isapi - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\arch\win32"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\arch\win32"
+
+!ENDIF
+
+SOURCE=..\..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_isapi - Win32 Release"
+
+
+"$(INTDIR)\mod_isapi.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_isapi.res" /i "../../../include" /i "../../../srclib/apr/include" /i "../../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_isapi.so" /d LONG_NAME="isapi_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_isapi - Win32 Debug"
+
+
+"$(INTDIR)\mod_isapi.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_isapi.res" /i "../../../include" /i "../../../srclib/apr/include" /i "../../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_isapi.so" /d LONG_NAME="isapi_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_isapi.c
+
+"$(INTDIR)\mod_isapi.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/arch/win32/mod_win32.c b/modules/arch/win32/mod_win32.c
new file mode 100644
index 0000000..bddc60d
--- /dev/null
+++ b/modules/arch/win32/mod_win32.c
@@ -0,0 +1,563 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifdef WIN32
+
+#include "apr_strings.h"
+#include "apr_portable.h"
+#include "apr_buckets.h"
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "http_log.h"
+#include "util_script.h"
+#include "mod_core.h"
+#include "mod_cgi.h"
+#include "apr_lib.h"
+#include "ap_regkey.h"
+
+/*
+ * CGI Script stuff for Win32...
+ */
+typedef enum { eFileTypeUNKNOWN, eFileTypeBIN, eFileTypeEXE16, eFileTypeEXE32,
+ eFileTypeSCRIPT } file_type_e;
+typedef enum { INTERPRETER_SOURCE_UNSET, INTERPRETER_SOURCE_REGISTRY_STRICT,
+ INTERPRETER_SOURCE_REGISTRY, INTERPRETER_SOURCE_SHEBANG
+ } interpreter_source_e;
+AP_DECLARE(file_type_e) ap_get_win32_interpreter(const request_rec *,
+ char **interpreter,
+ char **arguments);
+
+module AP_MODULE_DECLARE_DATA win32_module;
+
+typedef struct {
+ /* Where to find interpreter to run scripts */
+ interpreter_source_e script_interpreter_source;
+} win32_dir_conf;
+
+static void *create_win32_dir_config(apr_pool_t *p, char *dir)
+{
+ win32_dir_conf *conf;
+ conf = (win32_dir_conf*)apr_palloc(p, sizeof(win32_dir_conf));
+ conf->script_interpreter_source = INTERPRETER_SOURCE_UNSET;
+ return conf;
+}
+
+static void *merge_win32_dir_configs(apr_pool_t *p, void *basev, void *addv)
+{
+ win32_dir_conf *new;
+ win32_dir_conf *base = (win32_dir_conf *) basev;
+ win32_dir_conf *add = (win32_dir_conf *) addv;
+
+ new = (win32_dir_conf *) apr_pcalloc(p, sizeof(win32_dir_conf));
+ new->script_interpreter_source = (add->script_interpreter_source
+ != INTERPRETER_SOURCE_UNSET)
+ ? add->script_interpreter_source
+ : base->script_interpreter_source;
+ return new;
+}
+
+static const char *set_interpreter_source(cmd_parms *cmd, void *dv,
+ char *arg)
+{
+ win32_dir_conf *d = (win32_dir_conf *)dv;
+ if (!strcasecmp(arg, "registry")) {
+ d->script_interpreter_source = INTERPRETER_SOURCE_REGISTRY;
+ }
+ else if (!strcasecmp(arg, "registry-strict")) {
+ d->script_interpreter_source = INTERPRETER_SOURCE_REGISTRY_STRICT;
+ }
+ else if (!strcasecmp(arg, "script")) {
+ d->script_interpreter_source = INTERPRETER_SOURCE_SHEBANG;
+ }
+ else {
+ return apr_pstrcat(cmd->temp_pool, "ScriptInterpreterSource \"", arg,
+ "\" must be \"registry\", \"registry-strict\" or "
+ "\"script\"", NULL);
+ }
+ return NULL;
+}
+
+/* XXX: prep_string should translate the string into unicode,
+ * such that it is compatible with whatever codepage the client
+ * will read characters 80-ff. For the moment, use the unicode
+ * values 0080-00ff. This isn't trivial, since the code page
+ * varies between msdos and Windows applications.
+ * For subsystem 2 [GUI] the default is the system Ansi CP.
+ * For subsystem 3 [CLI] the default is the system OEM CP.
+ */
+static void prep_string(const char ** str, apr_pool_t *p)
+{
+ const char *ch = *str;
+ char *ch2;
+ apr_size_t widen = 0;
+
+ if (!ch) {
+ return;
+ }
+ while (*ch) {
+ if (*(ch++) & 0x80) {
+ ++widen;
+ }
+ }
+ if (!widen) {
+ return;
+ }
+ widen += (ch - *str) + 1;
+ ch = *str;
+ *str = ch2 = apr_palloc(p, widen);
+ while (*ch) {
+ if (*ch & 0x80) {
+ /* sign extension won't hurt us here */
+ *(ch2++) = 0xC0 | ((*ch >> 6) & 0x03);
+ *(ch2++) = 0x80 | (*(ch++) & 0x3f);
+ }
+ else {
+ *(ch2++) = *(ch++);
+ }
+ }
+ *(ch2++) = '\0';
+}
+
+/* Somewhat more exciting ... figure out where the registry has stashed the
+ * ExecCGI or Open command - it may be nested one level deep (or more???)
+ */
+static char* get_interpreter_from_win32_registry(apr_pool_t *p,
+ const char* ext,
+ int strict)
+{
+ apr_status_t rv;
+ ap_regkey_t *name_key = NULL;
+ ap_regkey_t *type_key;
+ ap_regkey_t *key;
+ char execcgi_path[] = "SHELL\\EXECCGI\\COMMAND";
+ char execopen_path[] = "SHELL\\OPEN\\COMMAND";
+ char *type_name;
+ char *buffer;
+
+ if (!ext) {
+ return NULL;
+ }
+ /*
+ * Future optimization:
+ * When the registry is successfully searched, store the strings for
+ * interpreter and arguments in an ext hash to speed up subsequent look-ups
+ */
+
+ /* Open the key associated with the script filetype extension */
+ rv = ap_regkey_open(&type_key, AP_REGKEY_CLASSES_ROOT, ext, APR_READ, p);
+
+ if (rv != APR_SUCCESS) {
+ return NULL;
+ }
+
+ /* Retrieve the name of the script filetype extension */
+ rv = ap_regkey_value_get(&type_name, type_key, "", p);
+
+ if (rv == APR_SUCCESS && type_name[0]) {
+ /* Open the key associated with the script filetype extension */
+ rv = ap_regkey_open(&name_key, AP_REGKEY_CLASSES_ROOT, type_name,
+ APR_READ, p);
+ }
+
+ /* Open the key for the script command path by:
+ *
+ * 1) the 'named' filetype key for ExecCGI/Command
+ * 2) the extension's type key for ExecCGI/Command
+ *
+ * and if the strict arg is false, then continue trying:
+ *
+ * 3) the 'named' filetype key for Open/Command
+ * 4) the extension's type key for Open/Command
+ */
+
+ if (name_key) {
+ if ((rv = ap_regkey_open(&key, name_key, execcgi_path, APR_READ, p))
+ == APR_SUCCESS) {
+ rv = ap_regkey_value_get(&buffer, key, "", p);
+ ap_regkey_close(name_key);
+ }
+ }
+
+ if (!name_key || (rv != APR_SUCCESS)) {
+ if ((rv = ap_regkey_open(&key, type_key, execcgi_path, APR_READ, p))
+ == APR_SUCCESS) {
+ rv = ap_regkey_value_get(&buffer, key, "", p);
+ ap_regkey_close(type_key);
+ }
+ }
+
+ if (!strict && name_key && (rv != APR_SUCCESS)) {
+ if ((rv = ap_regkey_open(&key, name_key, execopen_path, APR_READ, p))
+ == APR_SUCCESS) {
+ rv = ap_regkey_value_get(&buffer, key, "", p);
+ ap_regkey_close(name_key);
+ }
+ }
+
+ if (!strict && (rv != APR_SUCCESS)) {
+ if ((rv = ap_regkey_open(&key, type_key, execopen_path, APR_READ, p))
+ == APR_SUCCESS) {
+ rv = ap_regkey_value_get(&buffer, key, "", p);
+ ap_regkey_close(type_key);
+ }
+ }
+
+ if (name_key) {
+ ap_regkey_close(name_key);
+ }
+
+ ap_regkey_close(type_key);
+
+ if (rv != APR_SUCCESS || !buffer[0]) {
+ return NULL;
+ }
+
+ return buffer;
+}
+
+
+static apr_array_header_t *split_argv(apr_pool_t *p, const char *interp,
+ const char *cgiprg, const char *cgiargs)
+{
+ apr_array_header_t *args = apr_array_make(p, 8, sizeof(char*));
+ char *d = apr_palloc(p, strlen(interp)+1);
+ const char *ch = interp;
+ const char **arg;
+ int prgtaken = 0;
+ int argtaken = 0;
+ int inquo;
+ int sl;
+
+ while (*ch) {
+ /* Skip on through Deep Space */
+ if (apr_isspace(*ch)) {
+ ++ch; continue;
+ }
+ /* One Arg */
+ if (((*ch == '$') || (*ch == '%')) && (*(ch + 1) == '*')) {
+ const char *cgiarg = cgiargs;
+ argtaken = 1;
+ for (;;) {
+ char *w = ap_getword_nulls(p, &cgiarg, '+');
+ if (!*w) {
+ break;
+ }
+ ap_unescape_url(w);
+ prep_string((const char**)&w, p);
+ arg = (const char**)apr_array_push(args);
+ *arg = ap_escape_shell_cmd(p, w);
+ }
+ ch += 2;
+ continue;
+ }
+ if (((*ch == '$') || (*ch == '%')) && (*(ch + 1) == '1')) {
+ /* Todo: Make short name!!! */
+ prgtaken = 1;
+ arg = (const char**)apr_array_push(args);
+ if (*ch == '%') {
+ char *repl = apr_pstrdup(p, cgiprg);
+ *arg = repl;
+ while ((repl = strchr(repl, '/'))) {
+ *repl++ = '\\';
+ }
+ }
+ else {
+ *arg = cgiprg;
+ }
+ ch += 2;
+ continue;
+ }
+ if ((*ch == '\"') && ((*(ch + 1) == '$')
+ || (*(ch + 1) == '%')) && (*(ch + 2) == '1')
+ && (*(ch + 3) == '\"')) {
+ prgtaken = 1;
+ arg = (const char**)apr_array_push(args);
+ if (*(ch + 1) == '%') {
+ char *repl = apr_pstrdup(p, cgiprg);
+ *arg = repl;
+ while ((repl = strchr(repl, '/'))) {
+ *repl++ = '\\';
+ }
+ }
+ else {
+ *arg = cgiprg;
+ }
+ ch += 4;
+ continue;
+ }
+ arg = (const char**)apr_array_push(args);
+ *arg = d;
+ inquo = 0;
+ while (*ch) {
+ if (apr_isspace(*ch) && !inquo) {
+ ++ch; break;
+ }
+ /* Get 'em backslashes */
+ for (sl = 0; *ch == '\\'; ++sl) {
+ *d++ = *ch++;
+ }
+ if (sl & 1) {
+ /* last unmatched '\' + '"' sequence is a '"' */
+ if (*ch == '\"') {
+ *(d - 1) = *ch++;
+ }
+ continue;
+ }
+ if (*ch == '\"') {
+ /* '""' sequence within quotes is a '"' */
+ if (*++ch == '\"' && inquo) {
+ *d++ = *ch++; continue;
+ }
+ /* Flip quote state */
+ inquo = !inquo;
+ if (apr_isspace(*ch) && !inquo) {
+ ++ch; break;
+ }
+ /* All other '"'s are Munched */
+ continue;
+ }
+ /* Anything else is, well, something else */
+ *d++ = *ch++;
+ }
+ /* Term that arg, already pushed on args */
+ *d++ = '\0';
+ }
+
+ if (!prgtaken) {
+ arg = (const char**)apr_array_push(args);
+ *arg = cgiprg;
+ }
+
+ if (!argtaken) {
+ const char *cgiarg = cgiargs;
+ for (;;) {
+ char *w = ap_getword_nulls(p, &cgiarg, '+');
+ if (!*w) {
+ break;
+ }
+ ap_unescape_url(w);
+ prep_string((const char**)&w, p);
+ arg = (const char**)apr_array_push(args);
+ *arg = ap_escape_shell_cmd(p, w);
+ }
+ }
+
+ arg = (const char**)apr_array_push(args);
+ *arg = NULL;
+
+ return args;
+}
+
+
+static apr_status_t ap_cgi_build_command(const char **cmd, const char ***argv,
+ request_rec *r, apr_pool_t *p,
+ cgi_exec_info_t *e_info)
+{
+ const apr_array_header_t *elts_arr = apr_table_elts(r->subprocess_env);
+ const apr_table_entry_t *elts = (apr_table_entry_t *) elts_arr->elts;
+ const char *ext = NULL;
+ const char *interpreter = NULL;
+ win32_dir_conf *d;
+ apr_file_t *fh;
+ const char *args = "";
+ int i;
+
+ d = (win32_dir_conf *)ap_get_module_config(r->per_dir_config,
+ &win32_module);
+
+ if (e_info->cmd_type) {
+ /* We have to consider that the client gets any QUERY_ARGS
+ * without any charset interpretation, use prep_string to
+ * create a string of the literal QUERY_ARGS bytes.
+ */
+ *cmd = r->filename;
+ if (r->args && r->args[0] && !ap_strchr_c(r->args, '=')) {
+ args = r->args;
+ }
+ }
+ /* Handle the complete file name, we DON'T want to follow suexec, since
+ * an unrooted command is as predictable as shooting craps in Win32.
+ * Notice that unlike most mime extension parsing, we have to use the
+ * win32 parsing here, therefore the final extension is the only one
+ * we will consider.
+ */
+ ext = strrchr(apr_filepath_name_get(*cmd), '.');
+
+ /* If the file has an extension and it is not .com and not .exe and
+ * we've been instructed to search the registry, then do so.
+ * Let apr_proc_create do all of the .bat/.cmd dirty work.
+ */
+ if (ext && (!strcasecmp(ext,".exe") || !strcasecmp(ext,".com")
+ || !strcasecmp(ext,".bat") || !strcasecmp(ext,".cmd"))) {
+ interpreter = "";
+ }
+ if (!interpreter && ext
+ && (d->script_interpreter_source
+ == INTERPRETER_SOURCE_REGISTRY
+ || d->script_interpreter_source
+ == INTERPRETER_SOURCE_REGISTRY_STRICT)) {
+ /* Check the registry */
+ int strict = (d->script_interpreter_source
+ == INTERPRETER_SOURCE_REGISTRY_STRICT);
+ interpreter = get_interpreter_from_win32_registry(r->pool, ext,
+ strict);
+ if (interpreter && e_info->cmd_type != APR_SHELLCMD) {
+ e_info->cmd_type = APR_PROGRAM_PATH;
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server,
+ strict ? APLOGNO(03180) "No ExecCGI verb found for files of type '%s'."
+ : APLOGNO(03181) "No ExecCGI or Open verb found for files of type '%s'.",
+ ext);
+ }
+ }
+ if (!interpreter) {
+ apr_status_t rv;
+ char buffer[1024];
+ apr_size_t bytes = sizeof(buffer);
+ apr_size_t i;
+
+ /* Need to peek into the file figure out what it really is...
+ * ### aught to go back and build a cache for this one of these days.
+ */
+ if ((rv = apr_file_open(&fh, *cmd, APR_READ | APR_BUFFERED,
+ APR_OS_DEFAULT, r->pool)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02100)
+ "Failed to open cgi file %s for testing", *cmd);
+ return rv;
+ }
+ if ((rv = apr_file_read(fh, buffer, &bytes)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02101)
+ "Failed to read cgi file %s for testing", *cmd);
+ return rv;
+ }
+ apr_file_close(fh);
+
+ /* Some twisted character [no pun intended] at MS decided that a
+ * zero width joiner as the lead wide character would be ideal for
+ * describing Unicode text files. This was further convoluted to
+ * another MSism that the same character mapped into utf-8, EF BB BF
+ * would signify utf-8 text files.
+ *
+ * Since MS configuration files are all protecting utf-8 encoded
+ * Unicode path, file and resource names, we already have the correct
+ * WinNT encoding. But at least eat the stupid three bytes up front.
+ *
+ * ### A more thorough check would also allow UNICODE text in buf, and
+ * convert it to UTF-8 for invoking unicode scripts. Those are few
+ * and far between, so leave that code an enterprising soul with a need.
+ */
+ if ((bytes >= 3) && memcmp(buffer, "\xEF\xBB\xBF", 3) == 0) {
+ memmove(buffer, buffer + 3, bytes -= 3);
+ }
+
+ /* Script or executable, that is the question...
+ * we check here also for '! so that .vbs scripts can work as CGI.
+ */
+ if ((bytes >= 2) && ((buffer[0] == '#') || (buffer[0] == '\''))
+ && (buffer[1] == '!')) {
+ /* Assuming file is a script since it starts with a shebang */
+ for (i = 2; i < bytes; i++) {
+ if ((buffer[i] == '\r') || (buffer[i] == '\n')) {
+ buffer[i] = '\0';
+ break;
+ }
+ }
+ if (i < bytes) {
+ interpreter = buffer + 2;
+ while (apr_isspace(*interpreter)) {
+ ++interpreter;
+ }
+ if (e_info->cmd_type != APR_SHELLCMD) {
+ e_info->cmd_type = APR_PROGRAM_PATH;
+ }
+ }
+ }
+ else if (bytes >= sizeof(IMAGE_DOS_HEADER)) {
+ /* Not a script, is it an executable? */
+ IMAGE_DOS_HEADER *hdr = (IMAGE_DOS_HEADER*)buffer;
+ if (hdr->e_magic == IMAGE_DOS_SIGNATURE) {
+ if (hdr->e_lfarlc < 0x40) {
+ /* Ought to invoke this 16 bit exe by a stub, (cmd /c?) */
+ interpreter = "";
+ }
+ else {
+ interpreter = "";
+ }
+ }
+ }
+ }
+ if (!interpreter) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02102)
+ "%s is not executable; ensure interpreted scripts have "
+ "\"#!\" or \"'!\" first line", *cmd);
+ return APR_EBADF;
+ }
+
+ *argv = (const char **)(split_argv(p, interpreter, *cmd,
+ args)->elts);
+ *cmd = (*argv)[0];
+
+ e_info->detached = 1;
+
+ /* XXX: Must fix r->subprocess_env to follow utf-8 conventions from
+ * the client's octets so that win32 apr_proc_create is happy.
+ * The -best- way is to determine if the .exe is unicode aware
+ * (using 0x0080-0x00ff) or is linked as a command or windows
+ * application (following the OEM or Ansi code page in effect.)
+ */
+ for (i = 0; i < elts_arr->nelts; ++i) {
+ if (elts[i].key && *elts[i].key && *elts[i].val
+ && !(strncmp(elts[i].key, "REMOTE_", 7) == 0
+ || strcmp(elts[i].key, "GATEWAY_INTERFACE") == 0
+ || strcmp(elts[i].key, "REQUEST_METHOD") == 0
+ || strcmp(elts[i].key, "SERVER_ADDR") == 0
+ || strcmp(elts[i].key, "SERVER_PORT") == 0
+ || strcmp(elts[i].key, "SERVER_PROTOCOL") == 0)) {
+ prep_string((const char**) &elts[i].val, r->pool);
+ }
+ }
+ return APR_SUCCESS;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ APR_REGISTER_OPTIONAL_FN(ap_cgi_build_command);
+}
+
+static const command_rec win32_cmds[] = {
+AP_INIT_TAKE1("ScriptInterpreterSource", set_interpreter_source, NULL,
+ OR_FILEINFO,
+ "Where to find interpreter to run Win32 scripts "
+ "(Registry or script shebang line)"),
+{ NULL }
+};
+
+AP_DECLARE_MODULE(win32) = {
+ STANDARD20_MODULE_STUFF,
+ create_win32_dir_config, /* create per-dir config */
+ merge_win32_dir_configs, /* merge per-dir config */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ win32_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
+
+#endif /* defined WIN32 */
diff --git a/modules/cache/.indent.pro b/modules/cache/.indent.pro
new file mode 100644
index 0000000..a9fbe9f
--- /dev/null
+++ b/modules/cache/.indent.pro
@@ -0,0 +1,54 @@
+-i4 -npsl -di0 -br -nce -d0 -cli0 -npcs -nfc1
+-TBUFF
+-TFILE
+-TTRANS
+-TUINT4
+-T_trans
+-Tallow_options_t
+-Tapache_sfio
+-Tarray_header
+-Tbool_int
+-Tbuf_area
+-Tbuff_struct
+-Tbuffy
+-Tcmd_how
+-Tcmd_parms
+-Tcommand_rec
+-Tcommand_struct
+-Tconn_rec
+-Tcore_dir_config
+-Tcore_server_config
+-Tdir_maker_func
+-Tevent
+-Tglobals_s
+-Thandler_func
+-Thandler_rec
+-Tjoblist_s
+-Tlisten_rec
+-Tmerger_func
+-Tmode_t
+-Tmodule
+-Tmodule_struct
+-Tmutex
+-Tn_long
+-Tother_child_rec
+-Toverrides_t
+-Tparent_score
+-Tpid_t
+-Tpiped_log
+-Tpool
+-Trequest_rec
+-Trequire_line
+-Trlim_t
+-Tscoreboard
+-Tsemaphore
+-Tserver_addr_rec
+-Tserver_rec
+-Tserver_rec_chain
+-Tshort_score
+-Ttable
+-Ttable_entry
+-Tthread
+-Tu_wide_int
+-Tvtime_t
+-Twide_int
diff --git a/modules/cache/Makefile.in b/modules/cache/Makefile.in
new file mode 100644
index 0000000..167b343
--- /dev/null
+++ b/modules/cache/Makefile.in
@@ -0,0 +1,3 @@
+
+include $(top_srcdir)/build/special.mk
+
diff --git a/modules/cache/NWGNUcach_dsk b/modules/cache/NWGNUcach_dsk
new file mode 100644
index 0000000..1c45f34
--- /dev/null
+++ b/modules/cache/NWGNUcach_dsk
@@ -0,0 +1,262 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/server/mpm/netware \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = cach_dsk
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Memory Cache Sub-Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = $(NLM_NAME)
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 65536
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If this is specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# Declare all target files (you must add your files here)
+#
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_cache_disk.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ Apache2 \
+ Libc \
+ mod_cach \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @libc.imp \
+ @aprlib.imp \
+ @httpd.imp \
+ @mod_cache.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ cache_disk_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/cache/NWGNUcach_socache b/modules/cache/NWGNUcach_socache
new file mode 100644
index 0000000..68b3cd9
--- /dev/null
+++ b/modules/cache/NWGNUcach_socache
@@ -0,0 +1,263 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(SRC)/include \
+ $(STDMOD)/generators \
+ $(SERVER)/mpm/netware \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = cach_socache
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Cache Socache Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = cach_socache
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 65536
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If this is specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# Declare all target files (you must add your files here)
+#
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_cache_socache.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ Apache2 \
+ Libc \
+ mod_cach \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @libc.imp \
+ @aprlib.imp \
+ @httpd.imp \
+ @mod_cache.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ cache_socache_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/cache/NWGNUmakefile b/modules/cache/NWGNUmakefile
new file mode 100644
index 0000000..e544df6
--- /dev/null
+++ b/modules/cache/NWGNUmakefile
@@ -0,0 +1,250 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME =
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION =
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME =
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE =
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/mod_cach.nlm \
+ $(OBJDIR)/cach_dsk.nlm \
+ $(OBJDIR)/cach_socache.nlm \
+ $(OBJDIR)/socachdbm.nlm \
+ $(OBJDIR)/socachmem.nlm \
+ $(OBJDIR)/socachshmcb.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/cache/NWGNUmod_cach b/modules/cache/NWGNUmod_cach
new file mode 100644
index 0000000..b17b6b6
--- /dev/null
+++ b/modules/cache/NWGNUmod_cach
@@ -0,0 +1,265 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/server/mpm/netware \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ -DDEBUG \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = mod_cach
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Cache module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = mod_cach
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 65536
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If this is specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# Declare all target files (you must add your files here)
+#
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/mod_cach.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/cache_util.o \
+ $(OBJDIR)/cache_storage.o \
+ $(OBJDIR)/mod_cache.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ Apache2 \
+ Libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @libc.imp \
+ @aprlib.imp \
+ @httpd.imp \
+ @netware.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ @mod_cache.imp \
+ cache_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/cache/NWGNUsocachdbm b/modules/cache/NWGNUsocachdbm
new file mode 100644
index 0000000..f673924
--- /dev/null
+++ b/modules/cache/NWGNUsocachdbm
@@ -0,0 +1,261 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/server/mpm/netware \
+ $(NWOS) \
+ $(STDMOD)/generators \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = socachedbm
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Socache DBM Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = socachedbm
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 65536
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If this is specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# Declare all target files (you must add your files here)
+#
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_socache_dbm.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ Apache2 \
+ Libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @libc.imp \
+ @aprlib.imp \
+ @httpd.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ socache_dbm_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/cache/NWGNUsocachmem b/modules/cache/NWGNUsocachmem
new file mode 100644
index 0000000..cfbf815
--- /dev/null
+++ b/modules/cache/NWGNUsocachmem
@@ -0,0 +1,261 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(STDMOD)/generators \
+ $(AP_WORK)/server/mpm/netware \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = socachemem
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Socache Memory Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = socachemem
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 65536
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If this is specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# Declare all target files (you must add your files here)
+#
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_socache_memcache.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ Apache2 \
+ Libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @libc.imp \
+ @aprlib.imp \
+ @httpd.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ socache_memcache_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/cache/NWGNUsocachshmcb b/modules/cache/NWGNUsocachshmcb
new file mode 100644
index 0000000..bc1850e
--- /dev/null
+++ b/modules/cache/NWGNUsocachshmcb
@@ -0,0 +1,261 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/server/mpm/netware \
+ $(NWOS) \
+ $(STDMOD)/generators \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = socacheshmcb
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Socache DC Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = socacheshmcb
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 65536
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If this is specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# Declare all target files (you must add your files here)
+#
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_socache_shmcb.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ Apache2 \
+ Libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @libc.imp \
+ @aprlib.imp \
+ @httpd.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ socache_shmcb_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/cache/cache_common.h b/modules/cache/cache_common.h
new file mode 100644
index 0000000..9d56d28
--- /dev/null
+++ b/modules/cache/cache_common.h
@@ -0,0 +1,56 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file cache_common.h
+ * @brief Common Cache structs
+ *
+ * @defgroup Cache_cache Cache Functions
+ * @ingroup MOD_CACHE
+ * @{
+ */
+
+#ifndef CACHE_COMMON_H
+#define CACHE_COMMON_H
+
+/* a cache control header breakdown */
+typedef struct cache_control {
+ unsigned int parsed:1;
+ unsigned int cache_control:1;
+ unsigned int pragma:1;
+ unsigned int no_cache:1;
+ unsigned int no_cache_header:1; /* no cache by header match */
+ unsigned int no_store:1;
+ unsigned int max_age:1;
+ unsigned int max_stale:1;
+ unsigned int min_fresh:1;
+ unsigned int no_transform:1;
+ unsigned int only_if_cached:1;
+ unsigned int public:1;
+ unsigned int private:1;
+ unsigned int private_header:1; /* private by header match */
+ unsigned int must_revalidate:1;
+ unsigned int proxy_revalidate:1;
+ unsigned int s_maxage:1;
+ unsigned int invalidated:1; /* has this entity been invalidated? */
+ apr_int64_t max_age_value; /* if positive, then set */
+ apr_int64_t max_stale_value; /* if positive, then set */
+ apr_int64_t min_fresh_value; /* if positive, then set */
+ apr_int64_t s_maxage_value; /* if positive, then set */
+} cache_control_t;
+
+#endif /* CACHE_COMMON_H */
+/** @} */
diff --git a/modules/cache/cache_disk_common.h b/modules/cache/cache_disk_common.h
new file mode 100644
index 0000000..ace569e
--- /dev/null
+++ b/modules/cache/cache_disk_common.h
@@ -0,0 +1,68 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file cache_disk_common.h
+ * @brief Common Disk Cache vars/structs
+ *
+ * @defgroup Cache_cache Cache Functions
+ * @ingroup MOD_DISK_CACHE
+ * @{
+ */
+
+#ifndef CACHE_DIST_COMMON_H
+#define CACHE_DIST_COMMON_H
+
+#define VARY_FORMAT_VERSION 5
+#define DISK_FORMAT_VERSION 6
+
+#define CACHE_HEADER_SUFFIX ".header"
+#define CACHE_DATA_SUFFIX ".data"
+#define CACHE_VDIR_SUFFIX ".vary"
+
+#define AP_TEMPFILE_PREFIX "/"
+#define AP_TEMPFILE_BASE "aptmp"
+#define AP_TEMPFILE_SUFFIX "XXXXXX"
+#define AP_TEMPFILE_BASELEN strlen(AP_TEMPFILE_BASE)
+#define AP_TEMPFILE_NAMELEN strlen(AP_TEMPFILE_BASE AP_TEMPFILE_SUFFIX)
+#define AP_TEMPFILE AP_TEMPFILE_PREFIX AP_TEMPFILE_BASE AP_TEMPFILE_SUFFIX
+
+typedef struct {
+ /* Indicates the format of the header struct stored on-disk. */
+ apr_uint32_t format;
+ /* The HTTP status code returned for this response. */
+ int status;
+ /* The size of the entity name that follows. */
+ apr_size_t name_len;
+ /* The number of times we've cached this entity. */
+ apr_size_t entity_version;
+ /* Miscellaneous time values. */
+ apr_time_t date;
+ apr_time_t expire;
+ apr_time_t request_time;
+ apr_time_t response_time;
+ /* The ident of the body file, so we can test the body matches the header */
+ apr_ino_t inode;
+ apr_dev_t device;
+ /* Does this cached request have a body? */
+ unsigned int has_body:1;
+ unsigned int header_only:1;
+ /* The parsed cache control header */
+ cache_control_t control;
+} disk_cache_info_t;
+
+#endif /* CACHE_DIST_COMMON_H */
+/** @} */
diff --git a/modules/cache/cache_socache_common.h b/modules/cache/cache_socache_common.h
new file mode 100644
index 0000000..3ee3d0d
--- /dev/null
+++ b/modules/cache/cache_socache_common.h
@@ -0,0 +1,57 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file cache_socache_common.h
+ * @brief Common Shared Object Cache vars/structs
+ *
+ * @defgroup Cache_cache Cache Functions
+ * @ingroup MOD_SOCACHE_CACHE
+ * @{
+ */
+
+#ifndef CACHE_SOCACHE_COMMON_H
+#define CACHE_SOCACHE_COMMON_H
+
+#include "apr_time.h"
+
+#include "cache_common.h"
+
+#define CACHE_SOCACHE_VARY_FORMAT_VERSION 1
+#define CACHE_SOCACHE_DISK_FORMAT_VERSION 2
+
+typedef struct {
+ /* Indicates the format of the header struct stored on-disk. */
+ apr_uint32_t format;
+ /* The HTTP status code returned for this response. */
+ int status;
+ /* The size of the entity name that follows. */
+ apr_size_t name_len;
+ /* The number of times we've cached this entity. */
+ apr_size_t entity_version;
+ /* Miscellaneous time values. */
+ apr_time_t date;
+ apr_time_t expire;
+ apr_time_t request_time;
+ apr_time_t response_time;
+ /* Does this cached request have a body? */
+ unsigned int header_only:1;
+ /* The parsed cache control header */
+ cache_control_t control;
+} cache_socache_info_t;
+
+#endif /* CACHE_SOCACHE_COMMON_H */
+/** @} */
diff --git a/modules/cache/cache_storage.c b/modules/cache/cache_storage.c
new file mode 100644
index 0000000..dfda34b
--- /dev/null
+++ b/modules/cache/cache_storage.c
@@ -0,0 +1,790 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mod_cache.h"
+
+#include "cache_storage.h"
+#include "cache_util.h"
+
+APLOG_USE_MODULE(cache);
+
+extern APR_OPTIONAL_FN_TYPE(ap_cache_generate_key) *cache_generate_key;
+
+extern module AP_MODULE_DECLARE_DATA cache_module;
+
+/* -------------------------------------------------------------- */
+
+/*
+ * delete all URL entities from the cache
+ *
+ */
+int cache_remove_url(cache_request_rec *cache, request_rec *r)
+{
+ cache_provider_list *list;
+ cache_handle_t *h;
+
+ list = cache->providers;
+
+ /* Remove the stale cache entry if present. If not, we're
+ * being called from outside of a request; remove the
+ * non-stale handle.
+ */
+ h = cache->stale_handle ? cache->stale_handle : cache->handle;
+ if (!h) {
+ return OK;
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00691)
+ "cache: Removing url %s from the cache", h->cache_obj->key);
+
+ /* for each specified cache type, delete the URL */
+ while (list) {
+ list->provider->remove_url(h, r);
+ list = list->next;
+ }
+ return OK;
+}
+
+
+/*
+ * create a new URL entity in the cache
+ *
+ * It is possible to store more than once entity per URL. This
+ * function will always create a new entity, regardless of whether
+ * other entities already exist for the same URL.
+ *
+ * The size of the entity is provided so that a cache module can
+ * decide whether or not it wants to cache this particular entity.
+ * If the size is unknown, a size of -1 should be set.
+ */
+int cache_create_entity(cache_request_rec *cache, request_rec *r,
+ apr_off_t size, apr_bucket_brigade *in)
+{
+ cache_provider_list *list;
+ cache_handle_t *h = apr_pcalloc(r->pool, sizeof(cache_handle_t));
+ apr_status_t rv;
+
+ if (!cache) {
+ /* This should never happen */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, r, APLOGNO(00692)
+ "cache: No cache request information available for key"
+ " generation");
+ return APR_EGENERAL;
+ }
+
+ if (!cache->key) {
+ rv = cache_generate_key(r, r->pool, &cache->key);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+
+ list = cache->providers;
+ /* for each specified cache type, delete the URL */
+ while (list) {
+ switch (rv = list->provider->create_entity(h, r, cache->key, size, in)) {
+ case OK: {
+ cache->handle = h;
+ cache->provider = list->provider;
+ cache->provider_name = list->provider_name;
+ return OK;
+ }
+ case DECLINED: {
+ list = list->next;
+ continue;
+ }
+ default: {
+ return rv;
+ }
+ }
+ }
+ return DECLINED;
+}
+
+static int filter_header_do(void *v, const char *key, const char *val)
+{
+ if ((*key == 'W' || *key == 'w') && !ap_cstr_casecmp(key, "Warning")
+ && *val == '1') {
+ /* any stored Warning headers with warn-code 1xx (see section
+ * 14.46) MUST be deleted from the cache entry and the forwarded
+ * response.
+ */
+ }
+ else {
+ apr_table_addn(v, key, val);
+ }
+ return 1;
+}
+static int remove_header_do(void *v, const char *key, const char *val)
+{
+ if ((*key == 'W' || *key == 'w') && !ap_cstr_casecmp(key, "Warning")) {
+ /* any stored Warning headers with warn-code 2xx MUST be retained
+ * in the cache entry and the forwarded response.
+ */
+ }
+ else {
+ apr_table_unset(v, key);
+ }
+ return 1;
+}
+static int add_header_do(void *v, const char *key, const char *val)
+{
+ apr_table_addn(v, key, val);
+ return 1;
+}
+
+/**
+ * Take two sets of headers, sandwich them together, and apply the result to
+ * r->headers_out.
+ *
+ * To complicate this, a header may be duplicated in either table. Should a
+ * header exist in the top table, all matching headers will be removed from
+ * the bottom table before the headers are combined. The Warning headers are
+ * handled specially. Warnings are added rather than being replaced, while
+ * in the case of revalidation 1xx Warnings are stripped.
+ *
+ * The Content-Type and Last-Modified headers are then re-parsed and inserted
+ * into the request.
+ */
+void cache_accept_headers(cache_handle_t *h, request_rec *r, apr_table_t *top,
+ apr_table_t *bottom, int revalidation)
+{
+ const char *v;
+
+ if (revalidation) {
+ r->headers_out = apr_table_make(r->pool, 10);
+ apr_table_do(filter_header_do, r->headers_out, bottom, NULL);
+ }
+ else if (r->headers_out != bottom) {
+ r->headers_out = apr_table_copy(r->pool, bottom);
+ }
+ apr_table_do(remove_header_do, r->headers_out, top, NULL);
+ apr_table_do(add_header_do, r->headers_out, top, NULL);
+
+ v = apr_table_get(r->headers_out, "Content-Type");
+ if (v) {
+ ap_set_content_type(r, v);
+ /*
+ * Also unset possible Content-Type headers in r->headers_out and
+ * r->err_headers_out as they may be different to what we have received
+ * from the cache.
+ * Actually they are not needed as r->content_type set by
+ * ap_set_content_type above will be used in the store_headers functions
+ * of the storage providers as a fallback and the HTTP_HEADER filter
+ * does overwrite the Content-Type header with r->content_type anyway.
+ */
+ apr_table_unset(r->headers_out, "Content-Type");
+ apr_table_unset(r->err_headers_out, "Content-Type");
+ }
+
+ /* If the cache gave us a Last-Modified header, we can't just
+ * pass it on blindly because of restrictions on future values.
+ */
+ v = apr_table_get(r->headers_out, "Last-Modified");
+ if (v) {
+ ap_update_mtime(r, apr_date_parse_http(v));
+ ap_set_last_modified(r);
+ }
+
+}
+
+/*
+ * select a specific URL entity in the cache
+ *
+ * It is possible to store more than one entity per URL. Content
+ * negotiation is used to select an entity. Once an entity is
+ * selected, details of it are stored in the per request
+ * config to save time when serving the request later.
+ *
+ * This function returns OK if successful, DECLINED if no
+ * cached entity fits the bill.
+ */
+int cache_select(cache_request_rec *cache, request_rec *r)
+{
+ cache_provider_list *list;
+ apr_status_t rv;
+ cache_handle_t *h;
+
+ if (!cache) {
+ /* This should never happen */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, r, APLOGNO(00693)
+ "cache: No cache request information available for key"
+ " generation");
+ return DECLINED;
+ }
+
+ /* if no-cache, we can't serve from the cache, but we may store to the
+ * cache.
+ */
+ if (!ap_cache_check_no_cache(cache, r)) {
+ return DECLINED;
+ }
+
+ if (!cache->key) {
+ rv = cache_generate_key(r, r->pool, &cache->key);
+ if (rv != APR_SUCCESS) {
+ return DECLINED;
+ }
+ }
+
+ /* go through the cache types till we get a match */
+ h = apr_palloc(r->pool, sizeof(cache_handle_t));
+
+ list = cache->providers;
+
+ while (list) {
+ switch ((rv = list->provider->open_entity(h, r, cache->key))) {
+ case OK: {
+ char *vary = NULL;
+ int mismatch = 0;
+ char *last = NULL;
+
+ if (list->provider->recall_headers(h, r) != APR_SUCCESS) {
+ /* try again with next cache type */
+ list = list->next;
+ continue;
+ }
+
+ /*
+ * Check Content-Negotiation - Vary
+ *
+ * At this point we need to make sure that the object we found in
+ * the cache is the same object that would be delivered to the
+ * client, when the effects of content negotiation are taken into
+ * effect.
+ *
+ * In plain english, we want to make sure that a language-negotiated
+ * document in one language is not given to a client asking for a
+ * language negotiated document in a different language by mistake.
+ *
+ * This code makes the assumption that the storage manager will
+ * cache the req_hdrs if the response contains a Vary header.
+ *
+ * RFC2616 13.6 and 14.44 describe the Vary mechanism.
+ */
+ vary = cache_strqtok(
+ apr_pstrdup(r->pool,
+ cache_table_getm(r->pool, h->resp_hdrs, "Vary")),
+ CACHE_SEPARATOR, &last);
+ while (vary) {
+ const char *h1, *h2;
+
+ /*
+ * is this header in the request and the header in the cached
+ * request identical? If not, we give up and do a straight get
+ */
+ h1 = cache_table_getm(r->pool, r->headers_in, vary);
+ h2 = cache_table_getm(r->pool, h->req_hdrs, vary);
+ if (h1 == h2) {
+ /* both headers NULL, so a match - do nothing */
+ }
+ else if (h1 && h2 && !strcmp(h1, h2)) {
+ /* both headers exist and are equal - do nothing */
+ }
+ else {
+ /* headers do not match, so Vary failed */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS,
+ r, APLOGNO(00694) "cache_select(): Vary header mismatch.");
+ mismatch = 1;
+ break;
+ }
+ vary = cache_strqtok(NULL, CACHE_SEPARATOR, &last);
+ }
+
+ /* no vary match, try next provider */
+ if (mismatch) {
+ /* try again with next cache type */
+ list = list->next;
+ continue;
+ }
+
+ cache->provider = list->provider;
+ cache->provider_name = list->provider_name;
+
+ /*
+ * RFC2616 13.3.4 Rules for When to Use Entity Tags and Last-Modified
+ * Dates: An HTTP/1.1 caching proxy, upon receiving a conditional request
+ * that includes both a Last-Modified date and one or more entity tags as
+ * cache validators, MUST NOT return a locally cached response to the
+ * client unless that cached response is consistent with all of the
+ * conditional header fields in the request.
+ */
+ if (ap_condition_if_match(r, h->resp_hdrs) == AP_CONDITION_NOMATCH
+ || ap_condition_if_unmodified_since(r, h->resp_hdrs)
+ == AP_CONDITION_NOMATCH
+ || ap_condition_if_none_match(r, h->resp_hdrs)
+ == AP_CONDITION_NOMATCH
+ || ap_condition_if_modified_since(r, h->resp_hdrs)
+ == AP_CONDITION_NOMATCH
+ || ap_condition_if_range(r, h->resp_hdrs) == AP_CONDITION_NOMATCH) {
+ mismatch = 1;
+ }
+
+ /* Is our cached response fresh enough? */
+ if (mismatch || !cache_check_freshness(h, cache, r)) {
+ const char *etag, *lastmod;
+
+ /* Cache-Control: only-if-cached and revalidation required, try
+ * the next provider
+ */
+ if (cache->control_in.only_if_cached) {
+ /* try again with next cache type */
+ list = list->next;
+ continue;
+ }
+
+ /* set aside the stale entry for accessing later */
+ cache->stale_headers = apr_table_copy(r->pool,
+ r->headers_in);
+ cache->stale_handle = h;
+
+ /* if no existing conditionals, use conditionals of our own */
+ if (!mismatch) {
+
+ ap_log_rerror(
+ APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00695) "Cached response for %s isn't fresh. Adding "
+ "conditional request headers.", r->uri);
+
+ /* Remove existing conditionals that might conflict with ours */
+ apr_table_unset(r->headers_in, "If-Match");
+ apr_table_unset(r->headers_in, "If-Modified-Since");
+ apr_table_unset(r->headers_in, "If-None-Match");
+ apr_table_unset(r->headers_in, "If-Range");
+ apr_table_unset(r->headers_in, "If-Unmodified-Since");
+
+ etag = apr_table_get(h->resp_hdrs, "ETag");
+ lastmod = apr_table_get(h->resp_hdrs, "Last-Modified");
+
+ if (etag || lastmod) {
+ /* If we have a cached etag and/or Last-Modified add in
+ * our own conditionals.
+ */
+
+ if (etag) {
+ apr_table_set(r->headers_in, "If-None-Match", etag);
+ }
+
+ if (lastmod) {
+ apr_table_set(r->headers_in, "If-Modified-Since",
+ lastmod);
+ }
+
+ /*
+ * Do not do Range requests with our own conditionals: If
+ * we get 304 the Range does not matter and otherwise the
+ * entity changed and we want to have the complete entity
+ */
+ apr_table_unset(r->headers_in, "Range");
+
+ }
+
+ }
+
+ /* ready to revalidate, pretend we were never here */
+ return DECLINED;
+ }
+
+ /* Okay, this response looks okay. Merge in our stuff and go. */
+ cache_accept_headers(h, r, h->resp_hdrs, r->headers_out, 0);
+
+ cache->handle = h;
+ return OK;
+ }
+ case DECLINED: {
+ /* try again with next cache type */
+ list = list->next;
+ continue;
+ }
+ default: {
+ /* oo-er! an error */
+ return rv;
+ }
+ }
+ }
+
+ /* if Cache-Control: only-if-cached, and not cached, return 504 */
+ if (cache->control_in.only_if_cached) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00696)
+ "cache: 'only-if-cached' requested and no cached entity, "
+ "returning 504 Gateway Timeout for: %s", r->uri);
+ return HTTP_GATEWAY_TIME_OUT;
+ }
+
+ return DECLINED;
+}
+
+static apr_status_t cache_canonicalise_key(request_rec *r, apr_pool_t* p,
+ const char *path, const char *query,
+ apr_uri_t *parsed_uri,
+ const char **key)
+{
+ cache_server_conf *conf;
+ char *port_str, *hn, *lcs;
+ const char *hostname, *scheme;
+ int i;
+ const char *kpath;
+ const char *kquery;
+
+ if (*key) {
+ /*
+ * We have been here before during the processing of this request.
+ */
+ return APR_SUCCESS;
+ }
+
+ /*
+ * Get the module configuration. We need this for the CacheIgnoreQueryString
+ * option below.
+ */
+ conf = (cache_server_conf *) ap_get_module_config(r->server->module_config,
+ &cache_module);
+
+ /*
+ * Use the canonical name to improve cache hit rate, but only if this is
+ * not a proxy request or if this is a reverse proxy request.
+ * We need to handle both cases in the same manner as for the reverse proxy
+ * case we have the following situation:
+ *
+ * If a cached entry is looked up by mod_cache's quick handler r->proxyreq
+ * is still unset in the reverse proxy case as it only gets set in the
+ * translate name hook (either by ProxyPass or mod_rewrite) which is run
+ * after the quick handler hook. This is different to the forward proxy
+ * case where it gets set before the quick handler is run (in the
+ * post_read_request hook).
+ * If a cache entry is created by the CACHE_SAVE filter we always have
+ * r->proxyreq set correctly.
+ * So we must ensure that in the reverse proxy case we use the same code
+ * path and using the canonical name seems to be the right thing to do
+ * in the reverse proxy case.
+ */
+ if (!r->proxyreq || (r->proxyreq == PROXYREQ_REVERSE)) {
+ if (conf->base_uri && conf->base_uri->hostname) {
+ hostname = conf->base_uri->hostname;
+ }
+ else {
+ /* Use _default_ as the hostname if none present, as in mod_vhost */
+ hostname = ap_get_server_name(r);
+ if (!hostname) {
+ hostname = "_default_";
+ }
+ }
+ }
+ else if (parsed_uri->hostname) {
+ /* Copy the parsed uri hostname */
+ hn = apr_pstrdup(p, parsed_uri->hostname);
+ ap_str_tolower(hn);
+ /* const work-around */
+ hostname = hn;
+ }
+ else {
+ /* We are a proxied request, with no hostname. Unlikely
+ * to get very far - but just in case */
+ hostname = "_default_";
+ }
+
+ /*
+ * Copy the scheme, ensuring that it is lower case. If the parsed uri
+ * contains no string or if this is not a proxy request get the http
+ * scheme for this request. As r->parsed_uri.scheme is not set if this
+ * is a reverse proxy request, it is ensured that the cases
+ * "no proxy request" and "reverse proxy request" are handled in the same
+ * manner (see above why this is needed).
+ */
+ if (r->proxyreq && parsed_uri->scheme) {
+ /* Copy the scheme and lower-case it */
+ lcs = apr_pstrdup(p, parsed_uri->scheme);
+ ap_str_tolower(lcs);
+ /* const work-around */
+ scheme = lcs;
+ }
+ else {
+ if (conf->base_uri && conf->base_uri->scheme) {
+ scheme = conf->base_uri->scheme;
+ }
+ else {
+ scheme = ap_http_scheme(r);
+ }
+ }
+
+ /*
+ * If this is a proxy request, but not a reverse proxy request (see comment
+ * above why these cases must be handled in the same manner), copy the
+ * URI's port-string (which may be a service name). If the URI contains
+ * no port-string, use apr-util's notion of the default port for that
+ * scheme - if available. Otherwise use the port-number of the current
+ * server.
+ */
+ if (r->proxyreq && (r->proxyreq != PROXYREQ_REVERSE)) {
+ if (parsed_uri->port_str) {
+ port_str = apr_pcalloc(p, strlen(parsed_uri->port_str) + 2);
+ port_str[0] = ':';
+ for (i = 0; parsed_uri->port_str[i]; i++) {
+ port_str[i + 1] = apr_tolower(parsed_uri->port_str[i]);
+ }
+ }
+ else if (apr_uri_port_of_scheme(scheme)) {
+ port_str = apr_psprintf(p, ":%u", apr_uri_port_of_scheme(scheme));
+ }
+ else {
+ /* No port string given in the AbsoluteUri, and we have no
+ * idea what the default port for the scheme is. Leave it
+ * blank and live with the inefficiency of some extra cached
+ * entities.
+ */
+ port_str = "";
+ }
+ }
+ else {
+ if (conf->base_uri && conf->base_uri->port_str) {
+ port_str = apr_pstrcat(p, ":", conf->base_uri->port_str, NULL);
+ }
+ else if (conf->base_uri && conf->base_uri->hostname) {
+ port_str = "";
+ }
+ else {
+ /* Use the server port */
+ port_str = apr_psprintf(p, ":%u", ap_get_server_port(r));
+ }
+ }
+
+ /*
+ * Check if we need to ignore session identifiers in the URL and do so
+ * if needed.
+ */
+ kpath = path;
+ kquery = conf->ignorequerystring ? NULL : query;
+ if (conf->ignore_session_id->nelts) {
+ int i;
+ char **identifier;
+
+ identifier = (char **) conf->ignore_session_id->elts;
+ for (i = 0; i < conf->ignore_session_id->nelts; i++, identifier++) {
+ int len;
+ const char *param;
+
+ len = strlen(*identifier);
+ /*
+ * Check that we have a parameter separator in the last segment
+ * of the path and that the parameter matches our identifier
+ */
+ if ((param = ap_strrchr_c(kpath, ';'))
+ && !strncmp(param + 1, *identifier, len)
+ && (*(param + len + 1) == '=')
+ && !ap_strchr_c(param + len + 2, '/')) {
+ kpath = apr_pstrmemdup(p, kpath, param - kpath);
+ continue;
+ }
+ /*
+ * Check if the identifier is in the query string and cut it out.
+ */
+ if (kquery && *kquery) {
+ /*
+ * First check if the identifier is at the beginning of the
+ * query string and followed by a '='
+ */
+ if (!strncmp(kquery, *identifier, len) && kquery[len] == '=') {
+ param = kquery;
+ }
+ else {
+ char *complete;
+
+ /*
+ * In order to avoid subkey matching (PR 48401) prepend
+ * identifier with a '&' and append a '='
+ */
+ complete = apr_pstrcat(p, "&", *identifier, "=", NULL);
+ param = ap_strstr_c(kquery, complete);
+ /* If we found something we are sitting on the '&' */
+ if (param) {
+ param++;
+ }
+ }
+ if (param) {
+ const char *amp;
+ char *dup = NULL;
+
+ if (kquery != param) {
+ dup = apr_pstrmemdup(p, kquery, param - kquery);
+ kquery = dup;
+ }
+ else {
+ kquery = "";
+ }
+
+ if ((amp = ap_strchr_c(param + len + 1, '&'))) {
+ kquery = apr_pstrcat(p, kquery, amp + 1, NULL);
+ }
+ else {
+ /*
+ * If query string is not "", then we have the case
+ * that the identifier parameter we removed was the
+ * last one in the original query string. Hence we have
+ * a trailing '&' which needs to be removed.
+ */
+ if (dup) {
+ dup[strlen(dup) - 1] = '\0';
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /* Key format is a URI, optionally without the query-string (NULL
+ * per above if conf->ignorequerystring)
+ */
+ *key = apr_pstrcat(p, scheme, "://", hostname, port_str,
+ kpath, "?", kquery, NULL);
+
+ /*
+ * Store the key in the request_config for the cache as r->parsed_uri
+ * might have changed in the time from our first visit here triggered by the
+ * quick handler and our possible second visit triggered by the CACHE_SAVE
+ * filter (e.g. r->parsed_uri got unescaped). In this case we would save the
+ * resource in the cache under a key where it is never found by the quick
+ * handler during following requests.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00698)
+ "cache: Key for entity %s?%s is %s", path, query, *key);
+
+ return APR_SUCCESS;
+}
+
+apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p,
+ const char **key)
+{
+ /* In early processing (quick-handler, forward proxy), we want the initial
+ * query-string from r->parsed_uri, since any change before CACHE_SAVE
+ * shouldn't modify the key. Otherwise we want the actual query-string.
+ */
+ const char *path = r->uri;
+ const char *query = r->args;
+ if (cache_use_early_url(r)) {
+ path = r->parsed_uri.path;
+ query = r->parsed_uri.query;
+ }
+ return cache_canonicalise_key(r, p, path, query, &r->parsed_uri, key);
+}
+
+/*
+ * Invalidate a specific URL entity in all caches
+ *
+ * All cached entities for this URL are removed, usually in
+ * response to a POST/PUT or DELETE.
+ *
+ * This function returns OK if at least one entity was found and
+ * removed, and DECLINED if no cached entities were removed.
+ */
+int cache_invalidate(cache_request_rec *cache, request_rec *r)
+{
+ cache_provider_list *list;
+ apr_status_t rv, status = DECLINED;
+ cache_handle_t *h;
+ apr_uri_t location_uri;
+ apr_uri_t content_location_uri;
+
+ const char *location, *location_key = NULL;
+ const char *content_location, *content_location_key = NULL;
+
+ if (!cache) {
+ /* This should never happen */
+ ap_log_rerror(
+ APLOG_MARK, APLOG_ERR, APR_EGENERAL, r, APLOGNO(00697) "cache: No cache request information available for key"
+ " generation");
+ return DECLINED;
+ }
+
+ if (!cache->key) {
+ rv = cache_generate_key(r, r->pool, &cache->key);
+ if (rv != APR_SUCCESS) {
+ return DECLINED;
+ }
+ }
+
+ location = apr_table_get(r->headers_out, "Location");
+ if (location) {
+ if (apr_uri_parse(r->pool, location, &location_uri)
+ || cache_canonicalise_key(r, r->pool,
+ location_uri.path,
+ location_uri.query,
+ &location_uri, &location_key)
+ || !(r->parsed_uri.hostname
+ && location_uri.hostname
+ && !strcmp(r->parsed_uri.hostname,
+ location_uri.hostname))) {
+ location_key = NULL;
+ }
+ }
+
+ content_location = apr_table_get(r->headers_out, "Content-Location");
+ if (content_location) {
+ if (apr_uri_parse(r->pool, content_location,
+ &content_location_uri)
+ || cache_canonicalise_key(r, r->pool,
+ content_location_uri.path,
+ content_location_uri.query,
+ &content_location_uri,
+ &content_location_key)
+ || !(r->parsed_uri.hostname
+ && content_location_uri.hostname
+ && !strcmp(r->parsed_uri.hostname,
+ content_location_uri.hostname))) {
+ content_location_key = NULL;
+ }
+ }
+
+ /* go through the cache types */
+ h = apr_palloc(r->pool, sizeof(cache_handle_t));
+
+ list = cache->providers;
+
+ while (list) {
+
+ /* invalidate the request uri */
+ rv = list->provider->open_entity(h, r, cache->key);
+ if (OK == rv) {
+ rv = list->provider->invalidate_entity(h, r);
+ status = OK;
+ }
+ ap_log_rerror(
+ APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(02468) "cache: Attempted to invalidate cached entity with key: %s", cache->key);
+
+ /* invalidate the Location */
+ if (location_key) {
+ rv = list->provider->open_entity(h, r, location_key);
+ if (OK == rv) {
+ rv = list->provider->invalidate_entity(h, r);
+ status = OK;
+ }
+ ap_log_rerror(
+ APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(02469) "cache: Attempted to invalidate cached entity with key: %s", location_key);
+ }
+
+ /* invalidate the Content-Location */
+ if (content_location_key) {
+ rv = list->provider->open_entity(h, r, content_location_key);
+ if (OK == rv) {
+ rv = list->provider->invalidate_entity(h, r);
+ status = OK;
+ }
+ ap_log_rerror(
+ APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(02470) "cache: Attempted to invalidate cached entity with key: %s", content_location_key);
+ }
+
+ list = list->next;
+ }
+
+ return status;
+}
diff --git a/modules/cache/cache_storage.h b/modules/cache/cache_storage.h
new file mode 100644
index 0000000..83f2946
--- /dev/null
+++ b/modules/cache/cache_storage.h
@@ -0,0 +1,76 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file cache_storage.h
+ * @brief Cache Storage Functions
+ *
+ * @defgroup Cache_storage Cache Storage Functions
+ * @ingroup MOD_CACHE
+ * @{
+ */
+
+#ifndef CACHE_STORAGE_H
+#define CACHE_STORAGE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "mod_cache.h"
+#include "cache_util.h"
+
+/**
+ * cache_storage.c
+ */
+int cache_remove_url(cache_request_rec *cache, request_rec *r);
+int cache_create_entity(cache_request_rec *cache, request_rec *r,
+ apr_off_t size, apr_bucket_brigade *in);
+int cache_select(cache_request_rec *cache, request_rec *r);
+
+/**
+ * invalidate a specific URL entity in all caches
+ *
+ * All cached entities for this URL are removed, usually in
+ * response to a POST/PUT or DELETE.
+ *
+ * This function returns OK if at least one entity was found and
+ * removed, and DECLINED if no cached entities were removed.
+ * @param cache cache_request_rec
+ * @param r request_rec
+ */
+int cache_invalidate(cache_request_rec *cache, request_rec *r);
+
+apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p,
+ const char **key);
+
+/**
+ * Merge in cached headers into the response
+ * @param h cache_handle_t
+ * @param r request_rec
+ * @param top headers to be applied
+ * @param bottom headers to be overwritten
+ * @param revalidation true if revalidation is taking place
+ */
+void cache_accept_headers(cache_handle_t *h, request_rec *r, apr_table_t *top,
+ apr_table_t *bottom, int revalidation);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !CACHE_STORAGE_H */
+/** @} */
diff --git a/modules/cache/cache_util.c b/modules/cache/cache_util.c
new file mode 100644
index 0000000..fc36431
--- /dev/null
+++ b/modules/cache/cache_util.c
@@ -0,0 +1,1344 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mod_cache.h"
+
+#include "cache_util.h"
+#include <ap_provider.h>
+
+APLOG_USE_MODULE(cache);
+
+/* -------------------------------------------------------------- */
+
+extern APR_OPTIONAL_FN_TYPE(ap_cache_generate_key) *cache_generate_key;
+
+extern module AP_MODULE_DECLARE_DATA cache_module;
+
+/* Determine if "url" matches the hostname, scheme and port and path
+ * in "filter". All but the path comparisons are case-insensitive.
+ */
+static int uri_meets_conditions(const apr_uri_t *filter, const apr_size_t pathlen,
+ const apr_uri_t *url, const char *path)
+{
+ /* Scheme, hostname port and local part. The filter URI and the
+ * URI we test may have the following shapes:
+ * /<path>
+ * <scheme>[:://<hostname>[:<port>][/<path>]]
+ * That is, if there is no scheme then there must be only the path,
+ * and we check only the path; if there is a scheme, we check the
+ * scheme for equality, and then if present we match the hostname,
+ * and then if present match the port, and finally the path if any.
+ *
+ * Note that this means that "/<path>" only matches local paths,
+ * and to match proxied paths one *must* specify the scheme.
+ */
+
+ /* Is the filter is just for a local path or a proxy URI? */
+ if (!filter->scheme) {
+ if (url->scheme || url->hostname) {
+ return 0;
+ }
+ }
+ else {
+ /* The URI scheme must be present and identical except for case. */
+ if (!url->scheme || ap_cstr_casecmp(filter->scheme, url->scheme)) {
+ return 0;
+ }
+
+ /* If the filter hostname is null or empty it matches any hostname,
+ * if it begins with a "*" it matches the _end_ of the URI hostname
+ * excluding the "*", if it begins with a "." it matches the _end_
+ * of the URI * hostname including the ".", otherwise it must match
+ * the URI hostname exactly. */
+
+ if (filter->hostname && filter->hostname[0]) {
+ if (filter->hostname[0] == '.') {
+ const size_t fhostlen = strlen(filter->hostname);
+ const size_t uhostlen = url->hostname ? strlen(url->hostname) : 0;
+
+ if (fhostlen > uhostlen
+ || (url->hostname
+ && strcasecmp(filter->hostname,
+ url->hostname + uhostlen - fhostlen))) {
+ return 0;
+ }
+ }
+ else if (filter->hostname[0] == '*') {
+ const size_t fhostlen = strlen(filter->hostname + 1);
+ const size_t uhostlen = url->hostname ? strlen(url->hostname) : 0;
+
+ if (fhostlen > uhostlen
+ || (url->hostname
+ && strcasecmp(filter->hostname + 1,
+ url->hostname + uhostlen - fhostlen))) {
+ return 0;
+ }
+ }
+ else if (!url->hostname || strcasecmp(filter->hostname, url->hostname)) {
+ return 0;
+ }
+ }
+
+ /* If the filter port is empty it matches any URL port.
+ * If the filter or URL port are missing, or the URL port is
+ * empty, they default to the port for their scheme. */
+
+ if (!(filter->port_str && !filter->port_str[0])) {
+ /* NOTE: ap_port_of_scheme will return 0 if given NULL input */
+ const unsigned fport = filter->port_str ? filter->port
+ : apr_uri_port_of_scheme(filter->scheme);
+ const unsigned uport = (url->port_str && url->port_str[0])
+ ? url->port : apr_uri_port_of_scheme(url->scheme);
+
+ if (fport != uport) {
+ return 0;
+ }
+ }
+ }
+
+ /* For HTTP caching purposes, an empty (NULL) path is equivalent to
+ * a single "/" path. RFCs 3986/2396
+ */
+ if (!path) {
+ if (*filter->path == '/' && pathlen == 1) {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ /* Url has met all of the filter conditions so far, determine
+ * if the paths match.
+ */
+ return !strncmp(filter->path, path, pathlen);
+}
+
+int cache_use_early_url(request_rec *r)
+{
+ cache_server_conf *conf;
+
+ if (r->proxyreq == PROXYREQ_PROXY) {
+ return 1;
+ }
+
+ conf = ap_get_module_config(r->server->module_config, &cache_module);
+ if (conf->quick) {
+ return 1;
+ }
+
+ return 0;
+}
+
+static cache_provider_list *get_provider(request_rec *r, struct cache_enable *ent,
+ cache_provider_list *providers)
+{
+ /* Fetch from global config and add to the list. */
+ cache_provider *provider;
+ provider = ap_lookup_provider(CACHE_PROVIDER_GROUP, ent->type,
+ "0");
+ if (!provider) {
+ /* Log an error! */
+ }
+ else {
+ cache_provider_list *newp;
+ newp = apr_pcalloc(r->pool, sizeof(cache_provider_list));
+ newp->provider_name = ent->type;
+ newp->provider = provider;
+
+ if (!providers) {
+ providers = newp;
+ }
+ else {
+ cache_provider_list *last = providers;
+
+ while (last->next) {
+ if (last->provider == provider) {
+ return providers;
+ }
+ last = last->next;
+ }
+ if (last->provider == provider) {
+ return providers;
+ }
+ last->next = newp;
+ }
+ }
+
+ return providers;
+}
+
+cache_provider_list *cache_get_providers(request_rec *r,
+ cache_server_conf *conf)
+{
+ cache_dir_conf *dconf = ap_get_module_config(r->per_dir_config, &cache_module);
+ cache_provider_list *providers = NULL;
+ const char *path;
+ int i;
+
+ /* per directory cache disable */
+ if (dconf->disable) {
+ return NULL;
+ }
+
+ path = cache_use_early_url(r) ? r->parsed_uri.path : r->uri;
+
+ /* global cache disable */
+ for (i = 0; i < conf->cachedisable->nelts; i++) {
+ struct cache_disable *ent =
+ (struct cache_disable *)conf->cachedisable->elts;
+ if (uri_meets_conditions(&ent[i].url, ent[i].pathlen,
+ &r->parsed_uri, path)) {
+ /* Stop searching now. */
+ return NULL;
+ }
+ }
+
+ /* loop through all the per directory cacheenable entries */
+ for (i = 0; i < dconf->cacheenable->nelts; i++) {
+ struct cache_enable *ent =
+ (struct cache_enable *)dconf->cacheenable->elts;
+ providers = get_provider(r, &ent[i], providers);
+ }
+
+ /* loop through all the global cacheenable entries */
+ for (i = 0; i < conf->cacheenable->nelts; i++) {
+ struct cache_enable *ent =
+ (struct cache_enable *)conf->cacheenable->elts;
+ if (uri_meets_conditions(&ent[i].url, ent[i].pathlen,
+ &r->parsed_uri, path)) {
+ providers = get_provider(r, &ent[i], providers);
+ }
+ }
+
+ return providers;
+}
+
+
+/* do a HTTP/1.1 age calculation */
+CACHE_DECLARE(apr_int64_t) ap_cache_current_age(cache_info *info,
+ const apr_time_t age_value,
+ apr_time_t now)
+{
+ apr_time_t apparent_age, corrected_received_age, response_delay,
+ corrected_initial_age, resident_time, current_age,
+ age_value_usec;
+
+ age_value_usec = apr_time_from_sec(age_value);
+
+ /* Perform an HTTP/1.1 age calculation. (RFC2616 13.2.3) */
+
+ apparent_age = MAX(0, info->response_time - info->date);
+ corrected_received_age = MAX(apparent_age, age_value_usec);
+ response_delay = info->response_time - info->request_time;
+ corrected_initial_age = corrected_received_age + response_delay;
+ resident_time = now - info->response_time;
+ current_age = corrected_initial_age + resident_time;
+
+ if (current_age < 0) {
+ current_age = 0;
+ }
+
+ return apr_time_sec(current_age);
+}
+
+/**
+ * Try obtain a cache wide lock on the given cache key.
+ *
+ * If we return APR_SUCCESS, we obtained the lock, and we are clear to
+ * proceed to the backend. If we return APR_EEXIST, then the lock is
+ * already locked, someone else has gone to refresh the backend data
+ * already, so we must return stale data with a warning in the mean
+ * time. If we return anything else, then something has gone pear
+ * shaped, and we allow the request through to the backend regardless.
+ *
+ * This lock is created from the request pool, meaning that should
+ * something go wrong and the lock isn't deleted on return of the
+ * request headers from the backend for whatever reason, at worst the
+ * lock will be cleaned up when the request dies or finishes.
+ *
+ * If something goes truly bananas and the lock isn't deleted when the
+ * request dies, the lock will be trashed when its max-age is reached,
+ * or when a request arrives containing a Cache-Control: no-cache. At
+ * no point is it possible for this lock to permanently deny access to
+ * the backend.
+ */
+apr_status_t cache_try_lock(cache_server_conf *conf, cache_request_rec *cache,
+ request_rec *r)
+{
+ apr_status_t status;
+ const char *lockname;
+ const char *path;
+ char dir[5];
+ apr_time_t now = apr_time_now();
+ apr_finfo_t finfo;
+ apr_file_t *lockfile;
+ void *dummy;
+
+ finfo.mtime = 0;
+
+ if (!conf || !conf->lock || !conf->lockpath) {
+ /* no locks configured, leave */
+ return APR_SUCCESS;
+ }
+
+ /* lock already obtained earlier? if so, success */
+ apr_pool_userdata_get(&dummy, CACHE_LOCKFILE_KEY, r->pool);
+ if (dummy) {
+ return APR_SUCCESS;
+ }
+
+ /* create the key if it doesn't exist */
+ if (!cache->key) {
+ cache_handle_t *h;
+ /*
+ * Try to use the key of a possible open but stale cache
+ * entry if we have one.
+ */
+ if (cache->handle != NULL) {
+ h = cache->handle;
+ }
+ else {
+ h = cache->stale_handle;
+ }
+ if ((h != NULL) &&
+ (h->cache_obj != NULL) &&
+ (h->cache_obj->key != NULL)) {
+ cache->key = apr_pstrdup(r->pool, h->cache_obj->key);
+ }
+ else {
+ cache_generate_key(r, r->pool, &cache->key);
+ }
+ }
+
+ /* create a hashed filename from the key, and save it for later */
+ lockname = ap_cache_generate_name(r->pool, 0, 0, cache->key);
+
+ /* lock files represent discrete just-went-stale URLs "in flight", so
+ * we support a simple two level directory structure, more is overkill.
+ */
+ dir[0] = '/';
+ dir[1] = lockname[0];
+ dir[2] = '/';
+ dir[3] = lockname[1];
+ dir[4] = 0;
+
+ /* make the directories */
+ path = apr_pstrcat(r->pool, conf->lockpath, dir, NULL);
+ if (APR_SUCCESS != (status = apr_dir_make_recursive(path,
+ APR_UREAD|APR_UWRITE|APR_UEXECUTE, r->pool))) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(00778)
+ "Could not create a cache lock directory: %s",
+ path);
+ return status;
+ }
+ lockname = apr_pstrcat(r->pool, path, "/", lockname, NULL);
+ apr_pool_userdata_set(lockname, CACHE_LOCKNAME_KEY, NULL, r->pool);
+
+ /* is an existing lock file too old? */
+ status = apr_stat(&finfo, lockname,
+ APR_FINFO_MTIME | APR_FINFO_NLINK, r->pool);
+ if (!(APR_STATUS_IS_ENOENT(status)) && APR_SUCCESS != status) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(00779)
+ "Could not stat a cache lock file: %s",
+ lockname);
+ return status;
+ }
+ if ((status == APR_SUCCESS) && (((now - finfo.mtime) > conf->lockmaxage)
+ || (now < finfo.mtime))) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, status, r, APLOGNO(00780)
+ "Cache lock file for '%s' too old, removing: %s",
+ r->uri, lockname);
+ apr_file_remove(lockname, r->pool);
+ }
+
+ /* try obtain a lock on the file */
+ if (APR_SUCCESS == (status = apr_file_open(&lockfile, lockname,
+ APR_WRITE | APR_CREATE | APR_EXCL | APR_DELONCLOSE,
+ APR_UREAD | APR_UWRITE, r->pool))) {
+ apr_pool_userdata_set(lockfile, CACHE_LOCKFILE_KEY, NULL, r->pool);
+ }
+ return status;
+
+}
+
+/**
+ * Remove the cache lock, if present.
+ *
+ * First, try to close the file handle, whose delete-on-close should
+ * kill the file. Otherwise, just delete the file by name.
+ *
+ * If no lock name has yet been calculated, do the calculation of the
+ * lock name first before trying to delete the file.
+ *
+ * If an optional bucket brigade is passed, the lock will only be
+ * removed if the bucket brigade contains an EOS bucket.
+ */
+apr_status_t cache_remove_lock(cache_server_conf *conf,
+ cache_request_rec *cache, request_rec *r, apr_bucket_brigade *bb)
+{
+ void *dummy;
+ const char *lockname;
+
+ if (!conf || !conf->lock || !conf->lockpath) {
+ /* no locks configured, leave */
+ return APR_SUCCESS;
+ }
+ if (bb) {
+ apr_bucket *e;
+ int eos_found = 0;
+ for (e = APR_BRIGADE_FIRST(bb);
+ e != APR_BRIGADE_SENTINEL(bb);
+ e = APR_BUCKET_NEXT(e))
+ {
+ if (APR_BUCKET_IS_EOS(e)) {
+ eos_found = 1;
+ break;
+ }
+ }
+ if (!eos_found) {
+ /* no eos found in brigade, don't delete anything just yet,
+ * we are not done.
+ */
+ return APR_SUCCESS;
+ }
+ }
+ apr_pool_userdata_get(&dummy, CACHE_LOCKFILE_KEY, r->pool);
+ if (dummy) {
+ return apr_file_close((apr_file_t *)dummy);
+ }
+ apr_pool_userdata_get(&dummy, CACHE_LOCKNAME_KEY, r->pool);
+ lockname = (const char *)dummy;
+ if (!lockname) {
+ char dir[5];
+
+ /* create the key if it doesn't exist */
+ if (!cache->key) {
+ cache_generate_key(r, r->pool, &cache->key);
+ }
+
+ /* create a hashed filename from the key, and save it for later */
+ lockname = ap_cache_generate_name(r->pool, 0, 0, cache->key);
+
+ /* lock files represent discrete just-went-stale URLs "in flight", so
+ * we support a simple two level directory structure, more is overkill.
+ */
+ dir[0] = '/';
+ dir[1] = lockname[0];
+ dir[2] = '/';
+ dir[3] = lockname[1];
+ dir[4] = 0;
+
+ lockname = apr_pstrcat(r->pool, conf->lockpath, dir, "/", lockname, NULL);
+ }
+ return apr_file_remove(lockname, r->pool);
+}
+
+int ap_cache_check_no_cache(cache_request_rec *cache, request_rec *r)
+{
+
+ cache_server_conf *conf =
+ (cache_server_conf *)ap_get_module_config(r->server->module_config,
+ &cache_module);
+
+ /*
+ * At this point, we may have data cached, but the request may have
+ * specified that cached data may not be used in a response.
+ *
+ * This is covered under RFC2616 section 14.9.4 (Cache Revalidation and
+ * Reload Controls).
+ *
+ * - RFC2616 14.9.4 End to end reload, Cache-Control: no-cache, or Pragma:
+ * no-cache. The server MUST NOT use a cached copy when responding to such
+ * a request.
+ */
+
+ /* This value comes from the client's initial request. */
+ if (!cache->control_in.parsed) {
+ const char *cc_req = cache_table_getm(r->pool, r->headers_in,
+ "Cache-Control");
+ const char *pragma = cache_table_getm(r->pool, r->headers_in, "Pragma");
+ ap_cache_control(r, &cache->control_in, cc_req, pragma, r->headers_in);
+ }
+
+ if (cache->control_in.no_cache) {
+
+ if (!conf->ignorecachecontrol) {
+ return 0;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02657)
+ "Incoming request is asking for an uncached version of "
+ "%s, but we have been configured to ignore it and serve "
+ "cached content anyway", r->unparsed_uri);
+ }
+ }
+
+ return 1;
+}
+
+int ap_cache_check_no_store(cache_request_rec *cache, request_rec *r)
+{
+
+ cache_server_conf *conf =
+ (cache_server_conf *)ap_get_module_config(r->server->module_config,
+ &cache_module);
+
+ /*
+ * At this point, we may have data cached, but the request may have
+ * specified that cached data may not be used in a response.
+ *
+ * - RFC2616 14.9.2 What May be Stored by Caches. If Cache-Control:
+ * no-store arrives, do not serve from or store to the cache.
+ */
+
+ /* This value comes from the client's initial request. */
+ if (!cache->control_in.parsed) {
+ const char *cc_req = cache_table_getm(r->pool, r->headers_in,
+ "Cache-Control");
+ const char *pragma = cache_table_getm(r->pool, r->headers_in, "Pragma");
+ ap_cache_control(r, &cache->control_in, cc_req, pragma, r->headers_in);
+ }
+
+ if (cache->control_in.no_store) {
+
+ if (!conf->ignorecachecontrol) {
+ /* We're not allowed to serve a cached copy */
+ return 0;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02658)
+ "Incoming request is asking for a no-store version of "
+ "%s, but we have been configured to ignore it and serve "
+ "cached content anyway", r->unparsed_uri);
+ }
+ }
+
+ return 1;
+}
+
+int cache_check_freshness(cache_handle_t *h, cache_request_rec *cache,
+ request_rec *r)
+{
+ apr_status_t status;
+ apr_int64_t age, maxage_req, maxage_cresp, maxage, smaxage, maxstale;
+ apr_int64_t minfresh;
+ const char *cc_req;
+ const char *pragma;
+ const char *agestr = NULL;
+ apr_time_t age_c = 0;
+ cache_info *info = &(h->cache_obj->info);
+ const char *warn_head;
+ cache_server_conf *conf =
+ (cache_server_conf *)ap_get_module_config(r->server->module_config,
+ &cache_module);
+
+ /*
+ * We now want to check if our cached data is still fresh. This depends
+ * on a few things, in this order:
+ *
+ * - RFC2616 14.9.4 End to end reload, Cache-Control: no-cache. no-cache
+ * in either the request or the cached response means that we must
+ * perform the request unconditionally, and ignore cached content. We
+ * should never reach here, but if we do, mark the content as stale,
+ * as this is the best we can do.
+ *
+ * - RFC2616 14.32 Pragma: no-cache This is treated the same as
+ * Cache-Control: no-cache.
+ *
+ * - RFC2616 14.9.3 Cache-Control: max-stale, must-revalidate,
+ * proxy-revalidate if the max-stale request header exists, modify the
+ * stale calculations below so that an object can be at most <max-stale>
+ * seconds stale before we request a revalidation, _UNLESS_ a
+ * must-revalidate or proxy-revalidate cached response header exists to
+ * stop us doing this.
+ *
+ * - RFC2616 14.9.3 Cache-Control: s-maxage the origin server specifies the
+ * maximum age an object can be before it is considered stale. This
+ * directive has the effect of proxy|must revalidate, which in turn means
+ * simple ignore any max-stale setting.
+ *
+ * - RFC2616 14.9.4 Cache-Control: max-age this header can appear in both
+ * requests and responses. If both are specified, the smaller of the two
+ * takes priority.
+ *
+ * - RFC2616 14.21 Expires: if this request header exists in the cached
+ * entity, and it's value is in the past, it has expired.
+ *
+ */
+
+ /* This value comes from the client's initial request. */
+ cc_req = apr_table_get(r->headers_in, "Cache-Control");
+ pragma = apr_table_get(r->headers_in, "Pragma");
+
+ ap_cache_control(r, &cache->control_in, cc_req, pragma, r->headers_in);
+
+ if (cache->control_in.no_cache) {
+
+ if (!conf->ignorecachecontrol) {
+ /* Treat as stale, causing revalidation */
+ return 0;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00781)
+ "Incoming request is asking for a uncached version of "
+ "%s, but we have been configured to ignore it and "
+ "serve a cached response anyway",
+ r->unparsed_uri);
+ }
+
+ /* These come from the cached entity. */
+ if (h->cache_obj->info.control.no_cache
+ || h->cache_obj->info.control.invalidated) {
+ /*
+ * The cached entity contained Cache-Control: no-cache, or a
+ * no-cache with a header present, or a private with a header
+ * present, or the cached entity has been invalidated in the
+ * past, so treat as stale causing revalidation.
+ */
+ return 0;
+ }
+
+ if ((agestr = apr_table_get(h->resp_hdrs, "Age"))) {
+ char *endp;
+ apr_off_t offt;
+ if (!apr_strtoff(&offt, agestr, &endp, 10)
+ && endp > agestr && !*endp) {
+ age_c = offt;
+ }
+ }
+
+ /* calculate age of object */
+ age = ap_cache_current_age(info, age_c, r->request_time);
+
+ /* extract s-maxage */
+ smaxage = h->cache_obj->info.control.s_maxage_value;
+
+ /* extract max-age from request */
+ maxage_req = -1;
+ if (!conf->ignorecachecontrol) {
+ maxage_req = cache->control_in.max_age_value;
+ }
+
+ /*
+ * extract max-age from response, if both s-maxage and max-age, s-maxage
+ * takes priority
+ */
+ if (smaxage != -1) {
+ maxage_cresp = smaxage;
+ }
+ else {
+ maxage_cresp = h->cache_obj->info.control.max_age_value;
+ }
+
+ /*
+ * if both maxage request and response, the smaller one takes priority
+ */
+ if (maxage_req == -1) {
+ maxage = maxage_cresp;
+ }
+ else if (maxage_cresp == -1) {
+ maxage = maxage_req;
+ }
+ else {
+ maxage = MIN(maxage_req, maxage_cresp);
+ }
+
+ /* extract max-stale */
+ if (cache->control_in.max_stale) {
+ if (cache->control_in.max_stale_value != -1) {
+ maxstale = cache->control_in.max_stale_value;
+ }
+ else {
+ /*
+ * If no value is assigned to max-stale, then the client is willing
+ * to accept a stale response of any age (RFC2616 14.9.3). We will
+ * set it to one year in this case as this situation is somewhat
+ * similar to a "never expires" Expires header (RFC2616 14.21)
+ * which is set to a date one year from the time the response is
+ * sent in this case.
+ */
+ maxstale = APR_INT64_C(86400*365);
+ }
+ }
+ else {
+ maxstale = 0;
+ }
+
+ /* extract min-fresh */
+ if (!conf->ignorecachecontrol && cache->control_in.min_fresh) {
+ minfresh = cache->control_in.min_fresh_value;
+ }
+ else {
+ minfresh = 0;
+ }
+
+ /* override maxstale if must-revalidate, proxy-revalidate or s-maxage */
+ if (maxstale && (h->cache_obj->info.control.must_revalidate
+ || h->cache_obj->info.control.proxy_revalidate || smaxage != -1)) {
+ maxstale = 0;
+ }
+
+ /* handle expiration */
+ if (((maxage != -1) && (age < (maxage + maxstale - minfresh))) ||
+ ((smaxage == -1) && (maxage == -1) &&
+ (info->expire != APR_DATE_BAD) &&
+ (age < (apr_time_sec(info->expire - info->date) + maxstale - minfresh)))) {
+
+ warn_head = apr_table_get(h->resp_hdrs, "Warning");
+
+ /* it's fresh darlings... */
+ /* set age header on response */
+ apr_table_set(h->resp_hdrs, "Age",
+ apr_psprintf(r->pool, "%lu", (unsigned long)age));
+
+ /* add warning if maxstale overrode freshness calculation */
+ if (!(((maxage != -1) && age < maxage) ||
+ (info->expire != APR_DATE_BAD &&
+ (apr_time_sec(info->expire - info->date)) > age))) {
+ /* make sure we don't stomp on a previous warning */
+ if ((warn_head == NULL) ||
+ ((warn_head != NULL) && (ap_strstr_c(warn_head, "110") == NULL))) {
+ apr_table_mergen(h->resp_hdrs, "Warning",
+ "110 Response is stale");
+ }
+ }
+
+ /*
+ * If none of Expires, Cache-Control: max-age, or Cache-Control:
+ * s-maxage appears in the response, and the response header age
+ * calculated is more than 24 hours add the warning 113
+ */
+ if ((maxage_cresp == -1) && (smaxage == -1) && (apr_table_get(
+ h->resp_hdrs, "Expires") == NULL) && (age > 86400)) {
+
+ /* Make sure we don't stomp on a previous warning, and don't dup
+ * a 113 marning that is already present. Also, make sure to add
+ * the new warning to the correct *headers_out location.
+ */
+ if ((warn_head == NULL) ||
+ ((warn_head != NULL) && (ap_strstr_c(warn_head, "113") == NULL))) {
+ apr_table_mergen(h->resp_hdrs, "Warning",
+ "113 Heuristic expiration");
+ }
+ }
+ return 1; /* Cache object is fresh (enough) */
+ }
+
+ /*
+ * At this point we are stale, but: if we are under load, we may let
+ * a significant number of stale requests through before the first
+ * stale request successfully revalidates itself, causing a sudden
+ * unexpected thundering herd which in turn brings angst and drama.
+ *
+ * So.
+ *
+ * We want the first stale request to go through as normal. But the
+ * second and subsequent request, we must pretend to be fresh until
+ * the first request comes back with either new content or confirmation
+ * that the stale content is still fresh.
+ *
+ * To achieve this, we create a very simple file based lock based on
+ * the key of the cached object. We attempt to open the lock file with
+ * exclusive write access. If we succeed, woohoo! we're first, and we
+ * follow the stale path to the backend server. If we fail, oh well,
+ * we follow the fresh path, and avoid being a thundering herd.
+ *
+ * The lock lives only as long as the stale request that went on ahead.
+ * If the request succeeds, the lock is deleted. If the request fails,
+ * the lock is deleted, and another request gets to make a new lock
+ * and try again.
+ *
+ * At any time, a request marked "no-cache" will force a refresh,
+ * ignoring the lock, ensuring an extended lockout is impossible.
+ *
+ * A lock that exceeds a maximum age will be deleted, and another
+ * request gets to make a new lock and try again.
+ */
+ status = cache_try_lock(conf, cache, r);
+ if (APR_SUCCESS == status) {
+ /* we obtained a lock, follow the stale path */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00782)
+ "Cache lock obtained for stale cached URL, "
+ "revalidating entry: %s",
+ r->unparsed_uri);
+ return 0;
+ }
+ else if (APR_STATUS_IS_EEXIST(status)) {
+ /* lock already exists, return stale data anyway, with a warning */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO(00783)
+ "Cache already locked for stale cached URL, "
+ "pretend it is fresh: %s",
+ r->unparsed_uri);
+
+ /* make sure we don't stomp on a previous warning */
+ warn_head = apr_table_get(h->resp_hdrs, "Warning");
+ if ((warn_head == NULL) ||
+ ((warn_head != NULL) && (ap_strstr_c(warn_head, "110") == NULL))) {
+ apr_table_mergen(h->resp_hdrs, "Warning",
+ "110 Response is stale");
+ }
+
+ return 1;
+ }
+ else {
+ /* some other error occurred, just treat the object as stale */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO(00784)
+ "Attempt to obtain a cache lock for stale "
+ "cached URL failed, revalidating entry anyway: %s",
+ r->unparsed_uri);
+ return 0;
+ }
+
+}
+
+/* return each comma separated token, one at a time */
+CACHE_DECLARE(const char *)ap_cache_tokstr(apr_pool_t *p, const char *list,
+ const char **str)
+{
+ apr_size_t i;
+ const char *s;
+
+ s = ap_strchr_c(list, ',');
+ if (s != NULL) {
+ i = s - list;
+ do
+ s++;
+ while (apr_isspace(*s))
+ ; /* noop */
+ }
+ else
+ i = strlen(list);
+
+ while (i > 0 && apr_isspace(list[i - 1]))
+ i--;
+
+ *str = s;
+ if (i)
+ return apr_pstrmemdup(p, list, i);
+ else
+ return NULL;
+}
+
+/*
+ * Converts apr_time_t expressed as hex digits to
+ * a true apr_time_t.
+ */
+CACHE_DECLARE(apr_time_t) ap_cache_hex2usec(const char *x)
+{
+ int i, ch;
+ apr_time_t j;
+ for (i = 0, j = 0; i < sizeof(j) * 2; i++) {
+ ch = x[i];
+ j <<= 4;
+ if (apr_isdigit(ch))
+ j |= ch - '0';
+ else if (apr_isupper(ch))
+ j |= ch - ('A' - 10);
+ else
+ j |= ch - ('a' - 10);
+ }
+ return j;
+}
+
+/*
+ * Converts apr_time_t to apr_time_t expressed as hex digits.
+ */
+CACHE_DECLARE(void) ap_cache_usec2hex(apr_time_t j, char *y)
+{
+ int i, ch;
+
+ for (i = (sizeof(j) * 2)-1; i >= 0; i--) {
+ ch = (int)(j & 0xF);
+ j >>= 4;
+ if (ch >= 10)
+ y[i] = ch + ('A' - 10);
+ else
+ y[i] = ch + '0';
+ }
+ y[sizeof(j) * 2] = '\0';
+}
+
+static void cache_hash(const char *it, char *val, int ndepth, int nlength)
+{
+ apr_md5_ctx_t context;
+ unsigned char digest[16];
+ char tmp[22];
+ int i, k, d;
+ unsigned int x;
+ static const char enc_table[64] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_@";
+
+ apr_md5_init(&context);
+ apr_md5_update(&context, (const unsigned char *) it, strlen(it));
+ apr_md5_final(digest, &context);
+
+ /* encode 128 bits as 22 characters, using a modified uuencoding
+ * the encoding is 3 bytes -> 4 characters* i.e. 128 bits is
+ * 5 x 3 bytes + 1 byte -> 5 * 4 characters + 2 characters
+ */
+ for (i = 0, k = 0; i < 15; i += 3) {
+ x = (digest[i] << 16) | (digest[i + 1] << 8) | digest[i + 2];
+ tmp[k++] = enc_table[x >> 18];
+ tmp[k++] = enc_table[(x >> 12) & 0x3f];
+ tmp[k++] = enc_table[(x >> 6) & 0x3f];
+ tmp[k++] = enc_table[x & 0x3f];
+ }
+
+ /* one byte left */
+ x = digest[15];
+ tmp[k++] = enc_table[x >> 2]; /* use up 6 bits */
+ tmp[k++] = enc_table[(x << 4) & 0x3f];
+
+ /* now split into directory levels */
+ for (i = k = d = 0; d < ndepth; ++d) {
+ memcpy(&val[i], &tmp[k], nlength);
+ k += nlength;
+ val[i + nlength] = '/';
+ i += nlength + 1;
+ }
+ memcpy(&val[i], &tmp[k], 22 - k);
+ val[i + 22 - k] = '\0';
+}
+
+CACHE_DECLARE(char *)ap_cache_generate_name(apr_pool_t *p, int dirlevels,
+ int dirlength, const char *name)
+{
+ char hashfile[66];
+ cache_hash(name, hashfile, dirlevels, dirlength);
+ return apr_pstrdup(p, hashfile);
+}
+
+/**
+ * String tokenizer that ignores separator characters within quoted strings
+ * and escaped characters, as per RFC2616 section 2.2.
+ */
+char *cache_strqtok(char *str, const char *sep, char **last)
+{
+ char *token;
+ int quoted = 0;
+
+ if (!str) { /* subsequent call */
+ str = *last; /* start where we left off */
+ }
+
+ if (!str) { /* no more tokens */
+ return NULL;
+ }
+
+ /* skip characters in sep (will terminate at '\0') */
+ while (*str && ap_strchr_c(sep, *str)) {
+ ++str;
+ }
+
+ if (!*str) { /* no more tokens */
+ return NULL;
+ }
+
+ token = str;
+
+ /* skip valid token characters to terminate token and
+ * prepare for the next call (will terminate at '\0)
+ * on the way, ignore all quoted strings, and within
+ * quoted strings, escaped characters.
+ */
+ *last = token;
+ while (**last) {
+ if (!quoted) {
+ if (**last == '\"' && !ap_strchr_c(sep, '\"')) {
+ quoted = 1;
+ ++*last;
+ }
+ else if (!ap_strchr_c(sep, **last)) {
+ ++*last;
+ }
+ else {
+ break;
+ }
+ }
+ else {
+ if (**last == '\"') {
+ quoted = 0;
+ ++*last;
+ }
+ else if (**last == '\\') {
+ ++*last;
+ if (**last) {
+ ++*last;
+ }
+ }
+ else {
+ ++*last;
+ }
+ }
+ }
+
+ if (**last) {
+ **last = '\0';
+ ++*last;
+ }
+
+ return token;
+}
+
+/**
+ * Parse the Cache-Control and Pragma headers in one go, marking
+ * which tokens appear within the header. Populate the structure
+ * passed in.
+ */
+int ap_cache_control(request_rec *r, cache_control_t *cc,
+ const char *cc_header, const char *pragma_header, apr_table_t *headers)
+{
+ char *last;
+
+ if (cc->parsed) {
+ return cc->cache_control || cc->pragma;
+ }
+
+ cc->parsed = 1;
+ cc->max_age_value = -1;
+ cc->max_stale_value = -1;
+ cc->min_fresh_value = -1;
+ cc->s_maxage_value = -1;
+
+ if (pragma_header) {
+ char *header = apr_pstrdup(r->pool, pragma_header);
+ const char *token = cache_strqtok(header, CACHE_SEPARATOR, &last);
+ while (token) {
+ if (!ap_cstr_casecmp(token, "no-cache")) {
+ cc->no_cache = 1;
+ }
+ token = cache_strqtok(NULL, CACHE_SEPARATOR, &last);
+ }
+ cc->pragma = 1;
+ }
+
+ if (cc_header) {
+ char *endp;
+ apr_off_t offt;
+ char *header = apr_pstrdup(r->pool, cc_header);
+ const char *token = cache_strqtok(header, CACHE_SEPARATOR, &last);
+ while (token) {
+ switch (token[0]) {
+ case 'n':
+ case 'N': {
+ if (!ap_cstr_casecmpn(token, "no-cache", 8)) {
+ if (token[8] == '=') {
+ cc->no_cache_header = 1;
+ }
+ else if (!token[8]) {
+ cc->no_cache = 1;
+ }
+ }
+ else if (!ap_cstr_casecmp(token, "no-store")) {
+ cc->no_store = 1;
+ }
+ else if (!ap_cstr_casecmp(token, "no-transform")) {
+ cc->no_transform = 1;
+ }
+ break;
+ }
+ case 'm':
+ case 'M': {
+ if (!ap_cstr_casecmpn(token, "max-age", 7)) {
+ if (token[7] == '='
+ && !apr_strtoff(&offt, token + 8, &endp, 10)
+ && endp > token + 8 && !*endp) {
+ cc->max_age = 1;
+ cc->max_age_value = offt;
+ }
+ }
+ else if (!ap_cstr_casecmp(token, "must-revalidate")) {
+ cc->must_revalidate = 1;
+ }
+ else if (!ap_cstr_casecmpn(token, "max-stale", 9)) {
+ if (token[9] == '='
+ && !apr_strtoff(&offt, token + 10, &endp, 10)
+ && endp > token + 10 && !*endp) {
+ cc->max_stale = 1;
+ cc->max_stale_value = offt;
+ }
+ else if (!token[9]) {
+ cc->max_stale = 1;
+ cc->max_stale_value = -1;
+ }
+ }
+ else if (!ap_cstr_casecmpn(token, "min-fresh", 9)) {
+ if (token[9] == '='
+ && !apr_strtoff(&offt, token + 10, &endp, 10)
+ && endp > token + 10 && !*endp) {
+ cc->min_fresh = 1;
+ cc->min_fresh_value = offt;
+ }
+ }
+ break;
+ }
+ case 'o':
+ case 'O': {
+ if (!ap_cstr_casecmp(token, "only-if-cached")) {
+ cc->only_if_cached = 1;
+ }
+ break;
+ }
+ case 'p':
+ case 'P': {
+ if (!ap_cstr_casecmp(token, "public")) {
+ cc->public = 1;
+ }
+ else if (!ap_cstr_casecmpn(token, "private", 7)) {
+ if (token[7] == '=') {
+ cc->private_header = 1;
+ }
+ else if (!token[7]) {
+ cc->private = 1;
+ }
+ }
+ else if (!ap_cstr_casecmp(token, "proxy-revalidate")) {
+ cc->proxy_revalidate = 1;
+ }
+ break;
+ }
+ case 's':
+ case 'S': {
+ if (!ap_cstr_casecmpn(token, "s-maxage", 8)) {
+ if (token[8] == '='
+ && !apr_strtoff(&offt, token + 9, &endp, 10)
+ && endp > token + 9 && !*endp) {
+ cc->s_maxage = 1;
+ cc->s_maxage_value = offt;
+ }
+ }
+ break;
+ }
+ }
+ token = cache_strqtok(NULL, CACHE_SEPARATOR, &last);
+ }
+ cc->cache_control = 1;
+ }
+
+ return (cc_header != NULL || pragma_header != NULL);
+}
+
+/**
+ * Parse the Cache-Control, identifying and removing headers that
+ * exist as tokens after the no-cache and private tokens.
+ */
+static int cache_control_remove(request_rec *r, const char *cc_header,
+ apr_table_t *headers)
+{
+ char *last, *slast;
+ int found = 0;
+
+ if (cc_header) {
+ char *header = apr_pstrdup(r->pool, cc_header);
+ char *token = cache_strqtok(header, CACHE_SEPARATOR, &last);
+ while (token) {
+ switch (token[0]) {
+ case 'n':
+ case 'N': {
+ if (!ap_cstr_casecmpn(token, "no-cache", 8)) {
+ if (token[8] == '=') {
+ const char *header = cache_strqtok(token + 9,
+ CACHE_SEPARATOR "\"", &slast);
+ while (header) {
+ apr_table_unset(headers, header);
+ header = cache_strqtok(NULL, CACHE_SEPARATOR "\"",
+ &slast);
+ }
+ found = 1;
+ }
+ }
+ break;
+ }
+ case 'p':
+ case 'P': {
+ if (!ap_cstr_casecmpn(token, "private", 7)) {
+ if (token[7] == '=') {
+ const char *header = cache_strqtok(token + 8,
+ CACHE_SEPARATOR "\"", &slast);
+ while (header) {
+ apr_table_unset(headers, header);
+ header = cache_strqtok(NULL, CACHE_SEPARATOR "\"",
+ &slast);
+ }
+ found = 1;
+ }
+ }
+ break;
+ }
+ }
+ token = cache_strqtok(NULL, CACHE_SEPARATOR, &last);
+ }
+ }
+
+ return found;
+}
+
+/*
+ * Create a new table consisting of those elements from an
+ * headers table that are allowed to be stored in a cache.
+ */
+CACHE_DECLARE(apr_table_t *)ap_cache_cacheable_headers(apr_pool_t *pool,
+ apr_table_t *t,
+ server_rec *s)
+{
+ cache_server_conf *conf;
+ char **header;
+ int i;
+ apr_table_t *headers_out;
+
+ /* Short circuit the common case that there are not
+ * (yet) any headers populated.
+ */
+ if (t == NULL) {
+ return apr_table_make(pool, 10);
+ };
+
+ /* Make a copy of the headers, and remove from
+ * the copy any hop-by-hop headers, as defined in Section
+ * 13.5.1 of RFC 2616
+ */
+ headers_out = apr_table_copy(pool, t);
+
+ apr_table_unset(headers_out, "Connection");
+ apr_table_unset(headers_out, "Keep-Alive");
+ apr_table_unset(headers_out, "Proxy-Authenticate");
+ apr_table_unset(headers_out, "Proxy-Authorization");
+ apr_table_unset(headers_out, "TE");
+ apr_table_unset(headers_out, "Trailers");
+ apr_table_unset(headers_out, "Transfer-Encoding");
+ apr_table_unset(headers_out, "Upgrade");
+
+ conf = (cache_server_conf *)ap_get_module_config(s->module_config,
+ &cache_module);
+
+ /* Remove the user defined headers set with CacheIgnoreHeaders.
+ * This may break RFC 2616 compliance on behalf of the administrator.
+ */
+ header = (char **)conf->ignore_headers->elts;
+ for (i = 0; i < conf->ignore_headers->nelts; i++) {
+ apr_table_unset(headers_out, header[i]);
+ }
+ return headers_out;
+}
+
+/*
+ * Create a new table consisting of those elements from an input
+ * headers table that are allowed to be stored in a cache.
+ */
+CACHE_DECLARE(apr_table_t *)ap_cache_cacheable_headers_in(request_rec *r)
+{
+ return ap_cache_cacheable_headers(r->pool, r->headers_in, r->server);
+}
+
+/*
+ * Create a new table consisting of those elements from an output
+ * headers table that are allowed to be stored in a cache;
+ * ensure there is a content type and capture any errors.
+ */
+CACHE_DECLARE(apr_table_t *)ap_cache_cacheable_headers_out(request_rec *r)
+{
+ apr_table_t *headers_out;
+
+ headers_out = ap_cache_cacheable_headers(r->pool,
+ cache_merge_headers_out(r),
+ r->server);
+
+ cache_control_remove(r,
+ cache_table_getm(r->pool, headers_out, "Cache-Control"),
+ headers_out);
+
+ return headers_out;
+}
+
+apr_table_t *cache_merge_headers_out(request_rec *r)
+{
+ apr_table_t *headers_out;
+
+ headers_out = apr_table_overlay(r->pool, r->headers_out,
+ r->err_headers_out);
+
+ if (r->content_type
+ && !apr_table_get(headers_out, "Content-Type")) {
+ const char *ctype = ap_make_content_type(r, r->content_type);
+ if (ctype) {
+ apr_table_setn(headers_out, "Content-Type", ctype);
+ }
+ }
+
+ if (r->content_encoding
+ && !apr_table_get(headers_out, "Content-Encoding")) {
+ apr_table_setn(headers_out, "Content-Encoding",
+ r->content_encoding);
+ }
+
+ return headers_out;
+}
+
+typedef struct
+{
+ apr_pool_t *p;
+ const char *first;
+ apr_array_header_t *merged;
+} cache_table_getm_t;
+
+static int cache_table_getm_do(void *v, const char *key, const char *val)
+{
+ cache_table_getm_t *state = (cache_table_getm_t *) v;
+
+ if (!state->first) {
+ /**
+ * The most common case is a single header, and this is covered by
+ * a fast path that doesn't allocate any memory. On the second and
+ * subsequent header, an array is created and the array concatenated
+ * together to form the final value.
+ */
+ state->first = val;
+ }
+ else {
+ const char **elt;
+ if (!state->merged) {
+ state->merged = apr_array_make(state->p, 10, sizeof(const char *));
+ elt = apr_array_push(state->merged);
+ *elt = state->first;
+ }
+ elt = apr_array_push(state->merged);
+ *elt = val;
+ }
+ return 1;
+}
+
+const char *cache_table_getm(apr_pool_t *p, const apr_table_t *t,
+ const char *key)
+{
+ cache_table_getm_t state;
+
+ state.p = p;
+ state.first = NULL;
+ state.merged = NULL;
+
+ apr_table_do(cache_table_getm_do, &state, t, key, NULL);
+
+ if (!state.first) {
+ return NULL;
+ }
+ else if (!state.merged) {
+ return state.first;
+ }
+ else {
+ return apr_array_pstrcat(p, state.merged, ',');
+ }
+}
diff --git a/modules/cache/cache_util.h b/modules/cache/cache_util.h
new file mode 100644
index 0000000..6b92151
--- /dev/null
+++ b/modules/cache/cache_util.h
@@ -0,0 +1,341 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file cache_util.h
+ * @brief Cache Storage Functions
+ *
+ * @defgroup Cache_util Cache Utility Functions
+ * @ingroup MOD_CACHE
+ * @{
+ */
+
+#ifndef CACHE_UTIL_H
+#define CACHE_UTIL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "mod_cache.h"
+
+#include "apr_hooks.h"
+#include "apr.h"
+#include "apr_lib.h"
+#include "apr_strings.h"
+#include "apr_buckets.h"
+#include "apr_md5.h"
+#include "apr_pools.h"
+#include "apr_strings.h"
+#include "apr_optional.h"
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "httpd.h"
+#include "http_config.h"
+#include "ap_config.h"
+#include "http_core.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "http_vhost.h"
+#include "http_main.h"
+#include "http_log.h"
+#include "http_connection.h"
+#include "util_filter.h"
+#include "apr_uri.h"
+
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+
+#include "apr_atomic.h"
+
+#ifndef MAX
+#define MAX(a,b) ((a) > (b) ? (a) : (b))
+#endif
+#ifndef MIN
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+
+#define MSEC_ONE_DAY ((apr_time_t)(86400*APR_USEC_PER_SEC)) /* one day, in microseconds */
+#define MSEC_ONE_HR ((apr_time_t)(3600*APR_USEC_PER_SEC)) /* one hour, in microseconds */
+#define MSEC_ONE_MIN ((apr_time_t)(60*APR_USEC_PER_SEC)) /* one minute, in microseconds */
+#define MSEC_ONE_SEC ((apr_time_t)(APR_USEC_PER_SEC)) /* one second, in microseconds */
+
+#define DEFAULT_CACHE_MAXEXPIRE MSEC_ONE_DAY
+#define DEFAULT_CACHE_MINEXPIRE 0
+#define DEFAULT_CACHE_EXPIRE MSEC_ONE_HR
+#define DEFAULT_CACHE_LMFACTOR (0.1)
+#define DEFAULT_CACHE_MAXAGE 5
+#define DEFAULT_X_CACHE 0
+#define DEFAULT_X_CACHE_DETAIL 0
+#define DEFAULT_CACHE_STALE_ON_ERROR 1
+#define DEFAULT_CACHE_LOCKPATH "/mod_cache-lock"
+#define CACHE_LOCKNAME_KEY "mod_cache-lockname"
+#define CACHE_LOCKFILE_KEY "mod_cache-lockfile"
+#define CACHE_CTX_KEY "mod_cache-ctx"
+#define CACHE_SEPARATOR ", \t"
+
+/**
+ * cache_util.c
+ */
+
+struct cache_enable {
+ apr_uri_t url;
+ const char *type;
+ apr_size_t pathlen;
+};
+
+struct cache_disable {
+ apr_uri_t url;
+ apr_size_t pathlen;
+};
+
+/* static information about the local cache */
+typedef struct {
+ apr_array_header_t *cacheenable; /* URLs to cache */
+ apr_array_header_t *cachedisable; /* URLs not to cache */
+ /** store the headers that should not be stored in the cache */
+ apr_array_header_t *ignore_headers;
+ /** store the identifiers that should not be used for key calculation */
+ apr_array_header_t *ignore_session_id;
+ const char *lockpath;
+ apr_time_t lockmaxage;
+ apr_uri_t *base_uri;
+ /** ignore client's requests for uncached responses */
+ unsigned int ignorecachecontrol:1;
+ /** ignore query-string when caching */
+ unsigned int ignorequerystring:1;
+ /** run within the quick handler */
+ unsigned int quick:1;
+ /* thundering herd lock */
+ unsigned int lock:1;
+ unsigned int x_cache:1;
+ unsigned int x_cache_detail:1;
+ /* flag if CacheIgnoreHeader has been set */
+ #define CACHE_IGNORE_HEADERS_SET 1
+ #define CACHE_IGNORE_HEADERS_UNSET 0
+ unsigned int ignore_headers_set:1;
+ /* flag if CacheIgnoreURLSessionIdentifiers has been set */
+ #define CACHE_IGNORE_SESSION_ID_SET 1
+ #define CACHE_IGNORE_SESSION_ID_UNSET 0
+ unsigned int ignore_session_id_set:1;
+ unsigned int base_uri_set:1;
+ unsigned int ignorecachecontrol_set:1;
+ unsigned int ignorequerystring_set:1;
+ unsigned int quick_set:1;
+ unsigned int lock_set:1;
+ unsigned int lockpath_set:1;
+ unsigned int lockmaxage_set:1;
+ unsigned int x_cache_set:1;
+ unsigned int x_cache_detail_set:1;
+} cache_server_conf;
+
+typedef struct {
+ /* Minimum time to keep cached files in msecs */
+ apr_time_t minex;
+ /* Maximum time to keep cached files in msecs */
+ apr_time_t maxex;
+ /* default time to keep cached file in msecs */
+ apr_time_t defex;
+ /* factor for estimating expires date */
+ double factor;
+ /* cache enabled for this location */
+ apr_array_header_t *cacheenable;
+ /* cache disabled for this location */
+ unsigned int disable:1;
+ /* set X-Cache headers */
+ unsigned int x_cache:1;
+ unsigned int x_cache_detail:1;
+ /* serve stale on error */
+ unsigned int stale_on_error:1;
+ /** ignore the last-modified header when deciding to cache this request */
+ unsigned int no_last_mod_ignore:1;
+ /** ignore expiration date from server */
+ unsigned int store_expired:1;
+ /** ignore Cache-Control: private header from server */
+ unsigned int store_private:1;
+ /** ignore Cache-Control: no-store header from client or server */
+ unsigned int store_nostore:1;
+ unsigned int minex_set:1;
+ unsigned int maxex_set:1;
+ unsigned int defex_set:1;
+ unsigned int factor_set:1;
+ unsigned int x_cache_set:1;
+ unsigned int x_cache_detail_set:1;
+ unsigned int stale_on_error_set:1;
+ unsigned int no_last_mod_ignore_set:1;
+ unsigned int store_expired_set:1;
+ unsigned int store_private_set:1;
+ unsigned int store_nostore_set:1;
+ unsigned int enable_set:1;
+ unsigned int disable_set:1;
+} cache_dir_conf;
+
+/* A linked-list of authn providers. */
+typedef struct cache_provider_list cache_provider_list;
+
+struct cache_provider_list {
+ const char *provider_name;
+ const cache_provider *provider;
+ cache_provider_list *next;
+};
+
+/* per request cache information */
+typedef struct {
+ cache_provider_list *providers; /* possible cache providers */
+ const cache_provider *provider; /* current cache provider */
+ const char *provider_name; /* current cache provider name */
+ int fresh; /* is the entity fresh? */
+ cache_handle_t *handle; /* current cache handle */
+ cache_handle_t *stale_handle; /* stale cache handle */
+ apr_table_t *stale_headers; /* original request headers. */
+ int in_checked; /* CACHE_SAVE must cache the entity */
+ int block_response; /* CACHE_SAVE must block response. */
+ apr_bucket_brigade *saved_brigade; /* copy of partial response */
+ apr_off_t saved_size; /* length of saved_brigade */
+ apr_time_t exp; /* expiration */
+ apr_time_t lastmod; /* last-modified time */
+ cache_info *info; /* current cache info */
+ ap_filter_t *save_filter; /* Enable us to restore the filter on error */
+ ap_filter_t *remove_url_filter; /* Enable us to remove the filter */
+ const char *key; /* The cache key created for this
+ * request
+ */
+ apr_off_t size; /* the content length from the headers, or -1 */
+ apr_bucket_brigade *out; /* brigade to reuse for upstream responses */
+ cache_control_t control_in; /* cache control incoming */
+} cache_request_rec;
+
+/**
+ * Check the whether the request allows a cached object to be served as per RFC2616
+ * section 14.9.4 (Cache Revalidation and Reload Controls)
+ * @param cache cache_request_rec
+ * @param r request_rec
+ * @return 0 ==> cache object may not be served, 1 ==> cache object may be served
+ */
+int ap_cache_check_no_cache(cache_request_rec *cache, request_rec *r);
+
+/**
+ * Check the whether the request allows a cached object to be stored as per RFC2616
+ * section 14.9.2 (What May be Stored by Caches)
+ * @param cache cache_request_rec
+ * @param r request_rec
+ * @return 0 ==> cache object may not be served, 1 ==> cache object may be served
+ */
+int ap_cache_check_no_store(cache_request_rec *cache, request_rec *r);
+
+/**
+ * Check the freshness of the cache object per RFC2616 section 13.2 (Expiration Model)
+ * @param h cache_handle_t
+ * @param cache cache_request_rec
+ * @param r request_rec
+ * @return 0 ==> cache object is stale, 1 ==> cache object is fresh
+ */
+int cache_check_freshness(cache_handle_t *h, cache_request_rec *cache,
+ request_rec *r);
+
+/**
+ * Try obtain a cache wide lock on the given cache key.
+ *
+ * If we return APR_SUCCESS, we obtained the lock, and we are clear to
+ * proceed to the backend. If we return APR_EEXISTS, then the lock is
+ * already locked, someone else has gone to refresh the backend data
+ * already, so we must return stale data with a warning in the mean
+ * time. If we return anything else, then something has gone pear
+ * shaped, and we allow the request through to the backend regardless.
+ *
+ * This lock is created from the request pool, meaning that should
+ * something go wrong and the lock isn't deleted on return of the
+ * request headers from the backend for whatever reason, at worst the
+ * lock will be cleaned up when the request is dies or finishes.
+ *
+ * If something goes truly bananas and the lock isn't deleted when the
+ * request dies, the lock will be trashed when its max-age is reached,
+ * or when a request arrives containing a Cache-Control: no-cache. At
+ * no point is it possible for this lock to permanently deny access to
+ * the backend.
+ */
+apr_status_t cache_try_lock(cache_server_conf *conf, cache_request_rec *cache,
+ request_rec *r);
+
+/**
+ * Remove the cache lock, if present.
+ *
+ * First, try to close the file handle, whose delete-on-close should
+ * kill the file. Otherwise, just delete the file by name.
+ *
+ * If no lock name has yet been calculated, do the calculation of the
+ * lock name first before trying to delete the file.
+ *
+ * If an optional bucket brigade is passed, the lock will only be
+ * removed if the bucket brigade contains an EOS bucket.
+ */
+apr_status_t cache_remove_lock(cache_server_conf *conf,
+ cache_request_rec *cache, request_rec *r, apr_bucket_brigade *bb);
+
+cache_provider_list *cache_get_providers(request_rec *r,
+ cache_server_conf *conf);
+
+/**
+ * Get a value from a table, where the table may contain multiple
+ * values for a given key.
+ *
+ * When the table contains a single value, that value is returned
+ * unchanged.
+ *
+ * When the table contains two or more values for a key, all values
+ * for the key are returned, separated by commas.
+ */
+const char *cache_table_getm(apr_pool_t *p, const apr_table_t *t,
+ const char *key);
+
+/**
+ * String tokenizer that ignores separator characters within quoted strings
+ * and escaped characters, as per RFC2616 section 2.2.
+ */
+char *cache_strqtok(char *str, const char *sep, char **last);
+
+/**
+ * Merge err_headers_out into headers_out and add request's Content-Type and
+ * Content-Encoding if available.
+ */
+apr_table_t *cache_merge_headers_out(request_rec *r);
+
+/**
+ * Return whether to use request's path/query from early stage (r->parsed_uri)
+ * or the current/rewritable ones (r->uri/r->args).
+ */
+int cache_use_early_url(request_rec *r);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !CACHE_UTIL_H */
+/** @} */
diff --git a/modules/cache/config.m4 b/modules/cache/config.m4
new file mode 100644
index 0000000..4fa414b
--- /dev/null
+++ b/modules/cache/config.m4
@@ -0,0 +1,143 @@
+dnl modules enabled in this directory by default
+
+dnl APACHE_MODULE(name, helptext[, objects[, structname[, default[, config]]]])
+
+APACHE_MODPATH_INIT(cache)
+
+APACHE_MODULE(file_cache, File cache, , , most)
+
+dnl # list of object files for mod_cache
+cache_objs="dnl
+mod_cache.lo dnl
+cache_storage.lo dnl
+cache_util.lo dnl
+"
+cache_disk_objs="mod_cache_disk.lo"
+cache_socache_objs="mod_cache_socache.lo"
+
+case "$host" in
+ *os2*)
+ # OS/2 DLLs must resolve all symbols at build time
+ # and we need some from main cache module
+ cache_disk_objs="$cache_disk_objs mod_cache.la"
+ cache_socache_objs="$cache_socache_objs mod_cache.la"
+ ;;
+esac
+
+APACHE_MODULE(cache, dynamic file caching. At least one storage management module (e.g. mod_cache_disk) is also necessary., $cache_objs, , most)
+APACHE_MODULE(cache_disk, disk caching module, $cache_disk_objs, , most, , cache)
+APACHE_MODULE(cache_socache, shared object caching module, $cache_socache_objs, , most)
+
+dnl
+dnl APACHE_CHECK_DISTCACHE
+dnl
+dnl Configure for the detected distcache installation, giving
+dnl preference to "--with-distcache=<path>" if it was specified.
+dnl
+AC_DEFUN([APACHE_CHECK_DISTCACHE],[
+if test "x$ap_distcache_configured" = "x"; then
+ dnl initialise the variables we use
+ ap_distcache_found=""
+ ap_distcache_base=""
+ ap_distcache_libs=""
+ ap_distcache_ldflags=""
+ ap_distcache_with=""
+
+ dnl Determine the distcache base directory, if any
+ AC_MSG_CHECKING([for user-provided distcache base])
+ AC_ARG_WITH(distcache, APACHE_HELP_STRING(--with-distcache=PATH, Distcache installation directory), [
+ dnl If --with-distcache specifies a directory, we use that directory or fail
+ if test "x$withval" != "xyes" -a "x$withval" != "x"; then
+ dnl This ensures $withval is actually a directory and that it is absolute
+ ap_distcache_with="yes"
+ ap_distcache_base="`cd $withval ; pwd`"
+ fi
+ ])
+ if test "x$ap_distcache_base" = "x"; then
+ AC_MSG_RESULT(none)
+ else
+ AC_MSG_RESULT($ap_distcache_base)
+ fi
+
+ dnl Run header and version checks
+ saved_CPPFLAGS="$CPPFLAGS"
+ saved_LIBS="$LIBS"
+ saved_LDFLAGS="$LDFLAGS"
+
+ if test "x$ap_distcache_base" != "x"; then
+ APR_ADDTO(CPPFLAGS, [-I$ap_distcache_base/include])
+ APR_ADDTO(MOD_INCLUDES, [-I$ap_distcache_base/include])
+ APR_ADDTO(LDFLAGS, [-L$ap_distcache_base/lib])
+ APR_ADDTO(ap_distcache_ldflags, [-L$ap_distcache_base/lib])
+ if test "x$ap_platform_runtime_link_flag" != "x"; then
+ APR_ADDTO(LDFLAGS, [$ap_platform_runtime_link_flag$ap_distcache_base/lib])
+ APR_ADDTO(ap_distcache_ldflags, [$ap_platform_runtime_link_flag$ap_distcache_base/lib])
+ fi
+ fi
+ dnl First check for mandatory headers
+ AC_CHECK_HEADERS([distcache/dc_client.h], [ap_distcache_found="yes"], [])
+ if test "$ap_distcache_found" = "yes"; then
+ dnl test for a good version
+ AC_MSG_CHECKING(for distcache version)
+ AC_TRY_COMPILE([#include <distcache/dc_client.h>],[
+#if DISTCACHE_CLIENT_API != 0x0001
+#error "distcache API version is unrecognised"
+#endif],
+ [],
+ [ap_distcache_found="no"])
+ AC_MSG_RESULT($ap_distcache_found)
+ fi
+ if test "$ap_distcache_found" != "yes"; then
+ if test "x$ap_distcache_with" = "x"; then
+ AC_MSG_WARN([...No distcache detected])
+ else
+ AC_MSG_ERROR([...No distcache detected])
+ fi
+ else
+ dnl Run library and function checks
+ AC_MSG_CHECKING(for distcache libraries)
+ ap_distcache_libs="-ldistcache -lnal"
+ APR_ADDTO(LIBS, [$ap_distcache_libs])
+
+ AC_TRY_LINK(
+ [#include <distcache/dc_client.h>],
+ [DC_CTX *foo = DC_CTX_new((const char *)0,0);],
+ [],
+ [ap_distcache_found="no"])
+ AC_MSG_RESULT($ap_distcache_found)
+ if test "$ap_distcache_found" != "yes"; then
+ if test "x$ap_distcache_base" = "x"; then
+ AC_MSG_WARN([... Error, distcache libraries were missing or unusable])
+ else
+ AC_MSG_ERROR([... Error, distcache libraries were missing or unusable])
+ fi
+ fi
+ fi
+
+ dnl restore
+ CPPFLAGS="$saved_CPPFLAGS"
+ LIBS="$saved_LIBS"
+ LDFLAGS="$saved_LDFLAGS"
+
+ dnl Adjust apache's configuration based on what we found above.
+ if test "$ap_distcache_found" = "yes"; then
+ APR_ADDTO(MOD_SOCACHE_DC_LDADD, [$ap_distcache_ldflags $ap_distcache_libs])
+ AC_DEFINE(HAVE_DISTCACHE, 1, [Define if distcache support is enabled])
+ else
+ enable_socache_dc=no
+ fi
+ ap_distcache_configured="yes"
+fi
+])
+
+APACHE_MODULE(socache_shmcb, shmcb small object cache provider, , , most)
+APACHE_MODULE(socache_dbm, dbm small object cache provider, , , most)
+APACHE_MODULE(socache_memcache, memcache small object cache provider, , , most)
+APACHE_MODULE(socache_redis, redis small object cache provider, , , most)
+APACHE_MODULE(socache_dc, distcache small object cache provider, , , no, [
+ APACHE_CHECK_DISTCACHE
+])
+
+APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/$modpath_current])
+
+APACHE_MODPATH_FINISH
diff --git a/modules/cache/mod_cache.c b/modules/cache/mod_cache.c
new file mode 100644
index 0000000..3b4469e
--- /dev/null
+++ b/modules/cache/mod_cache.c
@@ -0,0 +1,2717 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mod_cache.h"
+
+#include "cache_storage.h"
+#include "cache_util.h"
+
+module AP_MODULE_DECLARE_DATA cache_module;
+APR_OPTIONAL_FN_TYPE(ap_cache_generate_key) *cache_generate_key;
+
+/* -------------------------------------------------------------- */
+
+
+/* Handles for cache filters, resolved at startup to eliminate
+ * a name-to-function mapping on each request
+ */
+static ap_filter_rec_t *cache_filter_handle;
+static ap_filter_rec_t *cache_save_filter_handle;
+static ap_filter_rec_t *cache_save_subreq_filter_handle;
+static ap_filter_rec_t *cache_out_filter_handle;
+static ap_filter_rec_t *cache_out_subreq_filter_handle;
+static ap_filter_rec_t *cache_remove_url_filter_handle;
+static ap_filter_rec_t *cache_invalidate_filter_handle;
+
+/**
+ * Entity headers' names
+ */
+static const char *MOD_CACHE_ENTITY_HEADERS[] = {
+ "Allow",
+ "Content-Encoding",
+ "Content-Language",
+ "Content-Length",
+ "Content-Location",
+ "Content-MD5",
+ "Content-Range",
+ "Content-Type",
+ "Last-Modified",
+ NULL
+};
+
+/*
+ * CACHE handler
+ * -------------
+ *
+ * Can we deliver this request from the cache?
+ * If yes:
+ * deliver the content by installing the CACHE_OUT filter.
+ * If no:
+ * check whether we're allowed to try cache it
+ * If yes:
+ * add CACHE_SAVE filter
+ * If No:
+ * oh well.
+ *
+ * By default, the cache handler runs in the quick handler, bypassing
+ * virtually all server processing and offering the cache its optimal
+ * performance. In this mode, the cache bolts onto the front of the
+ * server, and behaves as a discrete RFC2616 caching proxy
+ * implementation.
+ *
+ * Under certain circumstances, an admin might want to run the cache as
+ * a normal handler instead of a quick handler, allowing the cache to
+ * run after the authorisation hooks, or by allowing fine control over
+ * the placement of the cache in the filter chain. This option comes at
+ * a performance penalty, and should only be used to achieve specific
+ * caching goals where the admin understands what they are doing.
+ */
+
+static int cache_quick_handler(request_rec *r, int lookup)
+{
+ apr_status_t rv;
+ const char *auth;
+ cache_provider_list *providers;
+ cache_request_rec *cache;
+ apr_bucket_brigade *out;
+ apr_bucket *e;
+ ap_filter_t *next;
+ ap_filter_rec_t *cache_out_handle;
+ cache_server_conf *conf;
+
+ conf = (cache_server_conf *) ap_get_module_config(r->server->module_config,
+ &cache_module);
+
+ /* only run if the quick handler is enabled */
+ if (!conf->quick) {
+ return DECLINED;
+ }
+
+ /*
+ * Which cache module (if any) should handle this request?
+ */
+ if (!(providers = cache_get_providers(r, conf))) {
+ return DECLINED;
+ }
+
+ /* make space for the per request config */
+ cache = apr_pcalloc(r->pool, sizeof(cache_request_rec));
+ cache->size = -1;
+ cache->out = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+
+ /* save away the possible providers */
+ cache->providers = providers;
+
+ /*
+ * Are we allowed to serve cached info at all?
+ */
+ if (!ap_cache_check_no_store(cache, r)) {
+ return DECLINED;
+ }
+
+ /* find certain cache controlling headers */
+ auth = apr_table_get(r->headers_in, "Authorization");
+
+ /* First things first - does the request allow us to return
+ * cached information at all? If not, just decline the request.
+ */
+ if (auth) {
+ return DECLINED;
+ }
+
+ /* Are we PUT/POST/DELETE? If so, prepare to invalidate the cached entities.
+ */
+ switch (r->method_number) {
+ case M_PUT:
+ case M_POST:
+ case M_DELETE:
+ {
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(02461)
+ "PUT/POST/DELETE: Adding CACHE_INVALIDATE filter for %s",
+ r->uri);
+
+ /* Add cache_invalidate filter to this request to force a
+ * cache entry to be invalidated if the response is
+ * ultimately successful (2xx).
+ */
+ ap_add_output_filter_handle(
+ cache_invalidate_filter_handle, cache, r,
+ r->connection);
+
+ return DECLINED;
+ }
+ case M_GET: {
+ break;
+ }
+ default : {
+
+ ap_log_rerror(
+ APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(02462) "cache: Method '%s' not cacheable by mod_cache, ignoring: %s", r->method, r->uri);
+
+ return DECLINED;
+ }
+ }
+
+ /*
+ * Try to serve this request from the cache.
+ *
+ * If no existing cache file (DECLINED)
+ * add cache_save filter
+ * If cached file (OK)
+ * clear filter stack
+ * add cache_out filter
+ * return OK
+ */
+ rv = cache_select(cache, r);
+ if (rv != OK) {
+ if (rv == DECLINED) {
+ if (!lookup) {
+
+ /* try to obtain a cache lock at this point. if we succeed,
+ * we are the first to try and cache this url. if we fail,
+ * it means someone else is already trying to cache this
+ * url, and we should just let the request through to the
+ * backend without any attempt to cache. this stops
+ * duplicated simultaneous attempts to cache an entity.
+ */
+ rv = cache_try_lock(conf, cache, r);
+ if (APR_SUCCESS == rv) {
+
+ /*
+ * Add cache_save filter to cache this request. Choose
+ * the correct filter by checking if we are a subrequest
+ * or not.
+ */
+ if (r->main) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS,
+ r, APLOGNO(00749) "Adding CACHE_SAVE_SUBREQ filter for %s",
+ r->uri);
+ cache->save_filter = ap_add_output_filter_handle(
+ cache_save_subreq_filter_handle, cache, r,
+ r->connection);
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS,
+ r, APLOGNO(00750) "Adding CACHE_SAVE filter for %s",
+ r->uri);
+ cache->save_filter = ap_add_output_filter_handle(
+ cache_save_filter_handle, cache, r,
+ r->connection);
+ }
+
+ apr_pool_userdata_setn(cache, CACHE_CTX_KEY, NULL, r->pool);
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00751)
+ "Adding CACHE_REMOVE_URL filter for %s",
+ r->uri);
+
+ /* Add cache_remove_url filter to this request to remove a
+ * stale cache entry if needed. Also put the current cache
+ * request rec in the filter context, as the request that
+ * is available later during running the filter may be
+ * different due to an internal redirect.
+ */
+ cache->remove_url_filter = ap_add_output_filter_handle(
+ cache_remove_url_filter_handle, cache, r,
+ r->connection);
+
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv,
+ r, APLOGNO(00752) "Cache locked for url, not caching "
+ "response: %s", r->uri);
+ /* cache_select() may have added conditional headers */
+ if (cache->stale_headers) {
+ r->headers_in = cache->stale_headers;
+ }
+
+ }
+ }
+ else {
+ if (cache->stale_headers) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS,
+ r, APLOGNO(00753) "Restoring request headers for %s",
+ r->uri);
+
+ r->headers_in = cache->stale_headers;
+ }
+ }
+ }
+ else {
+ /* error */
+ return rv;
+ }
+ return DECLINED;
+ }
+
+ /* we've got a cache hit! tell everyone who cares */
+ cache_run_cache_status(cache->handle, r, r->headers_out, AP_CACHE_HIT,
+ "cache hit");
+
+ /* if we are a lookup, we are exiting soon one way or another; Restore
+ * the headers. */
+ if (lookup) {
+ if (cache->stale_headers) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00754)
+ "Restoring request headers.");
+ r->headers_in = cache->stale_headers;
+ }
+ }
+
+ rv = ap_meets_conditions(r);
+ if (rv != OK) {
+ /* If we are a lookup, we have to return DECLINED as we have no
+ * way of knowing if we will be able to serve the content.
+ */
+ if (lookup) {
+ return DECLINED;
+ }
+
+ /* Return cached status. */
+ return rv;
+ }
+
+ /* If we're a lookup, we can exit now instead of serving the content. */
+ if (lookup) {
+ return OK;
+ }
+
+ /* Serve up the content */
+
+ /* We are in the quick handler hook, which means that no output
+ * filters have been set. So lets run the insert_filter hook.
+ */
+ ap_run_insert_filter(r);
+
+ /*
+ * Add cache_out filter to serve this request. Choose
+ * the correct filter by checking if we are a subrequest
+ * or not.
+ */
+ if (r->main) {
+ cache_out_handle = cache_out_subreq_filter_handle;
+ }
+ else {
+ cache_out_handle = cache_out_filter_handle;
+ }
+ ap_add_output_filter_handle(cache_out_handle, cache, r, r->connection);
+
+ /*
+ * Remove all filters that are before the cache_out filter. This ensures
+ * that we kick off the filter stack with our cache_out filter being the
+ * first in the chain. This make sense because we want to restore things
+ * in the same manner as we saved them.
+ * There may be filters before our cache_out filter, because
+ *
+ * 1. We call ap_set_content_type during cache_select. This causes
+ * Content-Type specific filters to be added.
+ * 2. We call the insert_filter hook. This causes filters e.g. like
+ * the ones set with SetOutputFilter to be added.
+ */
+ next = r->output_filters;
+ while (next && (next->frec != cache_out_handle)) {
+ ap_remove_output_filter(next);
+ next = next->next;
+ }
+
+ /* kick off the filter stack */
+ out = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+ e = apr_bucket_eos_create(out->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(out, e);
+
+ return ap_pass_brigade_fchk(r, out,
+ "cache_quick_handler(%s): ap_pass_brigade returned",
+ cache->provider_name);
+}
+
+/**
+ * If the two filter handles are present within the filter chain, replace
+ * the last instance of the first filter with the last instance of the
+ * second filter, and return true. If the second filter is not present at
+ * all, the first filter is removed, and false is returned. If neither
+ * filter is present, false is returned and this function does nothing.
+ * If a stop filter is specified, processing will stop once this filter is
+ * reached.
+ */
+static int cache_replace_filter(ap_filter_t *next, ap_filter_rec_t *from,
+ ap_filter_rec_t *to, ap_filter_rec_t *stop) {
+ ap_filter_t *ffrom = NULL, *fto = NULL;
+ while (next && next->frec != stop) {
+ if (next->frec == from) {
+ ffrom = next;
+ }
+ if (next->frec == to) {
+ fto = next;
+ }
+ next = next->next;
+ }
+ if (ffrom && fto) {
+ ffrom->frec = fto->frec;
+ ffrom->ctx = fto->ctx;
+ ap_remove_output_filter(fto);
+ return 1;
+ }
+ if (ffrom) {
+ ap_remove_output_filter(ffrom);
+ }
+ return 0;
+}
+
+/**
+ * Find the given filter, and return it if found, or NULL otherwise.
+ */
+static ap_filter_t *cache_get_filter(ap_filter_t *next, ap_filter_rec_t *rec) {
+ while (next) {
+ if (next->frec == rec && next->ctx) {
+ break;
+ }
+ next = next->next;
+ }
+ return next;
+}
+
+/**
+ * The cache handler is functionally similar to the cache_quick_hander,
+ * however a number of steps that are required by the quick handler are
+ * not required here, as the normal httpd processing has already handled
+ * these steps.
+ */
+static int cache_handler(request_rec *r)
+{
+ apr_status_t rv;
+ cache_provider_list *providers;
+ cache_request_rec *cache;
+ apr_bucket_brigade *out;
+ apr_bucket *e;
+ ap_filter_t *next;
+ ap_filter_rec_t *cache_out_handle;
+ ap_filter_rec_t *cache_save_handle;
+ cache_server_conf *conf;
+
+ conf = (cache_server_conf *) ap_get_module_config(r->server->module_config,
+ &cache_module);
+
+ /* only run if the quick handler is disabled */
+ if (conf->quick) {
+ return DECLINED;
+ }
+
+ /*
+ * Which cache module (if any) should handle this request?
+ */
+ if (!(providers = cache_get_providers(r, conf))) {
+ return DECLINED;
+ }
+
+ /* make space for the per request config */
+ cache = apr_pcalloc(r->pool, sizeof(cache_request_rec));
+ cache->size = -1;
+ cache->out = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+
+ /* save away the possible providers */
+ cache->providers = providers;
+
+ /*
+ * Are we allowed to serve cached info at all?
+ */
+ if (!ap_cache_check_no_store(cache, r)) {
+ return DECLINED;
+ }
+
+ /* Are we PUT/POST/DELETE? If so, prepare to invalidate the cached entities.
+ */
+ switch (r->method_number) {
+ case M_PUT:
+ case M_POST:
+ case M_DELETE:
+ {
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(02463)
+ "PUT/POST/DELETE: Adding CACHE_INVALIDATE filter for %s",
+ r->uri);
+
+ /* Add cache_invalidate filter to this request to force a
+ * cache entry to be invalidated if the response is
+ * ultimately successful (2xx).
+ */
+ ap_add_output_filter_handle(
+ cache_invalidate_filter_handle, cache, r,
+ r->connection);
+
+ return DECLINED;
+ }
+ case M_GET: {
+ break;
+ }
+ default : {
+
+ ap_log_rerror(
+ APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(02464) "cache: Method '%s' not cacheable by mod_cache, ignoring: %s", r->method, r->uri);
+
+ return DECLINED;
+ }
+ }
+
+ /*
+ * Try to serve this request from the cache.
+ *
+ * If no existing cache file (DECLINED)
+ * add cache_save filter
+ * If cached file (OK)
+ * clear filter stack
+ * add cache_out filter
+ * return OK
+ */
+ rv = cache_select(cache, r);
+ if (rv != OK) {
+ if (rv == DECLINED) {
+
+ /* try to obtain a cache lock at this point. if we succeed,
+ * we are the first to try and cache this url. if we fail,
+ * it means someone else is already trying to cache this
+ * url, and we should just let the request through to the
+ * backend without any attempt to cache. this stops
+ * duplicated simultaneous attempts to cache an entity.
+ */
+ rv = cache_try_lock(conf, cache, r);
+ if (APR_SUCCESS == rv) {
+
+ /*
+ * Add cache_save filter to cache this request. Choose
+ * the correct filter by checking if we are a subrequest
+ * or not.
+ */
+ if (r->main) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS,
+ r, APLOGNO(00756) "Adding CACHE_SAVE_SUBREQ filter for %s",
+ r->uri);
+ cache_save_handle = cache_save_subreq_filter_handle;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS,
+ r, APLOGNO(00757) "Adding CACHE_SAVE filter for %s",
+ r->uri);
+ cache_save_handle = cache_save_filter_handle;
+ }
+ ap_add_output_filter_handle(cache_save_handle, cache, r,
+ r->connection);
+
+ /*
+ * Did the user indicate the precise location of the
+ * CACHE_SAVE filter by inserting the CACHE filter as a
+ * marker?
+ *
+ * If so, we get cunning and replace CACHE with the
+ * CACHE_SAVE filter. This has the effect of inserting
+ * the CACHE_SAVE filter at the precise location where
+ * the admin wants to cache the content. All filters that
+ * lie before and after the original location of the CACHE
+ * filter will remain in place.
+ */
+ if (cache_replace_filter(r->output_filters,
+ cache_filter_handle, cache_save_handle,
+ ap_get_input_filter_handle("SUBREQ_CORE"))) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS,
+ r, APLOGNO(00758) "Replacing CACHE with CACHE_SAVE "
+ "filter for %s", r->uri);
+ }
+
+ /* save away the save filter stack */
+ cache->save_filter = cache_get_filter(r->output_filters,
+ cache_save_filter_handle);
+
+ apr_pool_userdata_setn(cache, CACHE_CTX_KEY, NULL, r->pool);
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00759)
+ "Adding CACHE_REMOVE_URL filter for %s",
+ r->uri);
+
+ /* Add cache_remove_url filter to this request to remove a
+ * stale cache entry if needed. Also put the current cache
+ * request rec in the filter context, as the request that
+ * is available later during running the filter may be
+ * different due to an internal redirect.
+ */
+ cache->remove_url_filter
+ = ap_add_output_filter_handle(
+ cache_remove_url_filter_handle, cache, r,
+ r->connection);
+
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv,
+ r, APLOGNO(00760) "Cache locked for url, not caching "
+ "response: %s", r->uri);
+ }
+ }
+ else {
+ /* error */
+ return rv;
+ }
+ return DECLINED;
+ }
+
+ /* we've got a cache hit! tell everyone who cares */
+ cache_run_cache_status(cache->handle, r, r->headers_out, AP_CACHE_HIT,
+ "cache hit");
+
+ rv = ap_meets_conditions(r);
+ if (rv != OK) {
+ return rv;
+ }
+
+ /* Serve up the content */
+
+ /*
+ * Add cache_out filter to serve this request. Choose
+ * the correct filter by checking if we are a subrequest
+ * or not.
+ */
+ if (r->main) {
+ cache_out_handle = cache_out_subreq_filter_handle;
+ }
+ else {
+ cache_out_handle = cache_out_filter_handle;
+ }
+ ap_add_output_filter_handle(cache_out_handle, cache, r, r->connection);
+
+ /*
+ * Did the user indicate the precise location of the CACHE_OUT filter by
+ * inserting the CACHE filter as a marker?
+ *
+ * If so, we get cunning and replace CACHE with the CACHE_OUT filters.
+ * This has the effect of inserting the CACHE_OUT filter at the precise
+ * location where the admin wants to cache the content. All filters that
+ * lie *after* the original location of the CACHE filter will remain in
+ * place.
+ */
+ if (cache_replace_filter(r->output_filters, cache_filter_handle,
+ cache_out_handle, ap_get_input_filter_handle("SUBREQ_CORE"))) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS,
+ r, APLOGNO(00761) "Replacing CACHE with CACHE_OUT filter for %s",
+ r->uri);
+ }
+
+ /*
+ * Remove all filters that are before the cache_out filter. This ensures
+ * that we kick off the filter stack with our cache_out filter being the
+ * first in the chain. This make sense because we want to restore things
+ * in the same manner as we saved them.
+ * There may be filters before our cache_out filter, because
+ *
+ * 1. We call ap_set_content_type during cache_select. This causes
+ * Content-Type specific filters to be added.
+ * 2. We call the insert_filter hook. This causes filters e.g. like
+ * the ones set with SetOutputFilter to be added.
+ */
+ next = r->output_filters;
+ while (next && (next->frec != cache_out_handle)) {
+ ap_remove_output_filter(next);
+ next = next->next;
+ }
+
+ /* kick off the filter stack */
+ out = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+ e = apr_bucket_eos_create(out->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(out, e);
+ return ap_pass_brigade_fchk(r, out, "cache(%s): ap_pass_brigade returned",
+ cache->provider_name);
+}
+
+/*
+ * CACHE_OUT filter
+ * ----------------
+ *
+ * Deliver cached content (headers and body) up the stack.
+ */
+static apr_status_t cache_out_filter(ap_filter_t *f, apr_bucket_brigade *in)
+{
+ request_rec *r = f->r;
+ cache_request_rec *cache = (cache_request_rec *)f->ctx;
+
+ if (!cache) {
+ /* user likely configured CACHE_OUT manually; they should use mod_cache
+ * configuration to do that */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00762)
+ "CACHE/CACHE_OUT filter enabled while caching is disabled, ignoring");
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, in);
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00763)
+ "cache: running CACHE_OUT filter");
+
+ /* clean out any previous response up to EOS, if any */
+ while (!APR_BRIGADE_EMPTY(in)) {
+ apr_bucket *e = APR_BRIGADE_FIRST(in);
+ if (APR_BUCKET_IS_EOS(e)) {
+ apr_bucket_brigade *bb = apr_brigade_create(r->pool,
+ r->connection->bucket_alloc);
+
+ /* restore content type of cached response if available */
+ /* Needed especially when stale content gets served. */
+ const char *ct = apr_table_get(cache->handle->resp_hdrs, "Content-Type");
+ if (ct) {
+ ap_set_content_type(r, ct);
+ }
+
+ /* restore status of cached response */
+ r->status = cache->handle->cache_obj->info.status;
+
+ /* recall_headers() was called in cache_select() */
+ cache->provider->recall_body(cache->handle, r->pool, bb);
+ APR_BRIGADE_PREPEND(in, bb);
+
+ /* This filter is done once it has served up its content */
+ ap_remove_output_filter(f);
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00764)
+ "cache: serving %s", r->uri);
+ return ap_pass_brigade(f->next, in);
+
+ }
+ apr_bucket_delete(e);
+ }
+
+ return APR_SUCCESS;
+}
+
+/*
+ * Having jumped through all the hoops and decided to cache the
+ * response, call store_body() for each brigade, handling the
+ * case where the provider can't swallow the full brigade. In this
+ * case, we write the brigade we were passed out downstream, and
+ * loop around to try and cache some more until the in brigade is
+ * completely empty. As soon as the out brigade contains eos, call
+ * commit_entity() to finalise the cached element.
+ */
+static int cache_save_store(ap_filter_t *f, apr_bucket_brigade *in,
+ cache_server_conf *conf, cache_request_rec *cache)
+{
+ int rv = APR_SUCCESS;
+ apr_bucket *e;
+
+ /* pass the brigade in into the cache provider, which is then
+ * expected to move cached buckets to the out brigade, for us
+ * to pass up the filter stack. repeat until in is empty, or
+ * we fail.
+ */
+ while (APR_SUCCESS == rv && !APR_BRIGADE_EMPTY(in)) {
+
+ rv = cache->provider->store_body(cache->handle, f->r, in, cache->out);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, f->r, APLOGNO(00765)
+ "cache: Cache provider's store_body failed for URI %s", f->r->uri);
+ ap_remove_output_filter(f);
+
+ /* give someone else the chance to cache the file */
+ cache_remove_lock(conf, cache, f->r, NULL);
+
+ /* give up trying to cache, just step out the way */
+ APR_BRIGADE_PREPEND(in, cache->out);
+ return ap_pass_brigade(f->next, in);
+
+ }
+
+ /* does the out brigade contain eos? if so, we're done, commit! */
+ for (e = APR_BRIGADE_FIRST(cache->out);
+ e != APR_BRIGADE_SENTINEL(cache->out);
+ e = APR_BUCKET_NEXT(e))
+ {
+ if (APR_BUCKET_IS_EOS(e)) {
+ rv = cache->provider->commit_entity(cache->handle, f->r);
+ break;
+ }
+ }
+
+ /* conditionally remove the lock as soon as we see the eos bucket */
+ cache_remove_lock(conf, cache, f->r, cache->out);
+
+ if (APR_BRIGADE_EMPTY(cache->out)) {
+ if (APR_BRIGADE_EMPTY(in)) {
+ /* cache provider wants more data before passing the brigade
+ * upstream, oblige the provider by leaving to fetch more.
+ */
+ break;
+ }
+ else {
+ /* oops, no data out, but not all data read in either, be
+ * safe and stand down to prevent a spin.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, f->r, APLOGNO(00766)
+ "cache: Cache provider's store_body returned an "
+ "empty brigade, but didn't consume all of the "
+ "input brigade, standing down to prevent a spin");
+ ap_remove_output_filter(f);
+
+ /* give someone else the chance to cache the file */
+ cache_remove_lock(conf, cache, f->r, NULL);
+
+ return ap_pass_brigade(f->next, in);
+ }
+ }
+
+ rv = ap_pass_brigade(f->next, cache->out);
+ }
+
+ return rv;
+}
+
+/**
+ * Sanity check for 304 Not Modified responses, as per RFC2616 Section 10.3.5.
+ */
+static int cache_header_cmp(apr_pool_t *pool, apr_table_t *left,
+ apr_table_t *right, const char *key)
+{
+ const char *h1, *h2;
+
+ if ((h1 = cache_table_getm(pool, left, key))
+ && (h2 = cache_table_getm(pool, right, key)) && (strcmp(h1, h2))) {
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * CACHE_SAVE filter
+ * ---------------
+ *
+ * Decide whether or not this content should be cached.
+ * If we decide no it should not:
+ * remove the filter from the chain
+ * If we decide yes it should:
+ * Have we already started saving the response?
+ * If we have started, pass the data to the storage manager via store_body
+ * Otherwise:
+ * Check to see if we *can* save this particular response.
+ * If we can, call cache_create_entity() and save the headers and body
+ * Finally, pass the data to the next filter (the network or whatever)
+ *
+ * After the various failure cases, the cache lock is proactively removed, so
+ * that another request is given the opportunity to attempt to cache without
+ * waiting for a potentially slow client to acknowledge the failure.
+ */
+
+static apr_status_t cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
+{
+ int rv = !OK;
+ request_rec *r = f->r;
+ cache_request_rec *cache = (cache_request_rec *)f->ctx;
+ cache_server_conf *conf;
+ cache_dir_conf *dconf;
+ cache_control_t control;
+ const char *cc_out, *cl, *pragma;
+ const char *exps, *lastmods, *dates, *etag;
+ apr_time_t exp, date, lastmod, now;
+ apr_off_t size = -1;
+ cache_info *info = NULL;
+ const char *reason, **eh;
+ apr_pool_t *p;
+ apr_bucket *e;
+ apr_table_t *headers;
+ const char *query;
+
+ conf = (cache_server_conf *) ap_get_module_config(r->server->module_config,
+ &cache_module);
+
+ /* Setup cache_request_rec */
+ if (!cache) {
+ /* user likely configured CACHE_SAVE manually; they should really use
+ * mod_cache configuration to do that
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00767)
+ "CACHE/CACHE_SAVE filter enabled while caching is disabled, ignoring");
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, in);
+ }
+
+ reason = NULL;
+ p = r->pool;
+ /*
+ * Pass Data to Cache
+ * ------------------
+ * This section passes the brigades into the cache modules, but only
+ * if the setup section (see below) is complete.
+ */
+ if (cache->block_response) {
+ /* We've already sent down the response and EOS. So, ignore
+ * whatever comes now.
+ */
+ return APR_SUCCESS;
+ }
+
+ /* have we already run the cacheability check and set up the
+ * cached file handle?
+ */
+ if (cache->in_checked) {
+ return cache_save_store(f, in, conf, cache);
+ }
+
+ /*
+ * Setup Data in Cache
+ * -------------------
+ * This section opens the cache entity and sets various caching
+ * parameters, and decides whether this URL should be cached at
+ * all. This section is* run before the above section.
+ */
+
+ dconf = ap_get_module_config(r->per_dir_config, &cache_module);
+
+ /* RFC2616 13.8 Errors or Incomplete Response Cache Behavior:
+ * If a cache receives a 5xx response while attempting to revalidate an
+ * entry, it MAY either forward this response to the requesting client,
+ * or act as if the server failed to respond. In the latter case, it MAY
+ * return a previously received response unless the cached entry
+ * includes the "must-revalidate" cache-control directive (see section
+ * 14.9).
+ *
+ * This covers the case where an error was generated behind us, for example
+ * by a backend server via mod_proxy.
+ */
+ if (dconf->stale_on_error && r->status >= HTTP_INTERNAL_SERVER_ERROR) {
+
+ ap_remove_output_filter(cache->remove_url_filter);
+
+ if (cache->stale_handle
+ && !cache->stale_handle->cache_obj->info.control.must_revalidate
+ && !cache->stale_handle->cache_obj->info.control.proxy_revalidate) {
+ const char *warn_head;
+
+ /* morph the current save filter into the out filter, and serve from
+ * cache.
+ */
+ cache->handle = cache->stale_handle;
+ if (r->main) {
+ f->frec = cache_out_subreq_filter_handle;
+ }
+ else {
+ f->frec = cache_out_filter_handle;
+ }
+
+ r->headers_out = cache->stale_handle->resp_hdrs;
+
+ ap_set_content_type(r, apr_table_get(
+ cache->stale_handle->resp_hdrs, "Content-Type"));
+
+ /* add a revalidation warning */
+ warn_head = apr_table_get(r->err_headers_out, "Warning");
+ if ((warn_head == NULL) || ((warn_head != NULL)
+ && (ap_strstr_c(warn_head, "111") == NULL))) {
+ apr_table_mergen(r->err_headers_out, "Warning",
+ "111 Revalidation failed");
+ }
+
+ cache_run_cache_status(cache->handle, r, r->headers_out, AP_CACHE_HIT,
+ apr_psprintf(r->pool,
+ "cache hit: %d status; stale content returned",
+ r->status));
+
+ /* give someone else the chance to cache the file */
+ cache_remove_lock(conf, cache, f->r, NULL);
+
+ /* pass brigade to our morphed out filter */
+ return ap_pass_brigade(f, in);
+ }
+ }
+
+ query = cache_use_early_url(r) ? r->parsed_uri.query : r->args;
+
+ /* read expiry date; if a bad date, then leave it so the client can
+ * read it
+ */
+ exps = apr_table_get(r->err_headers_out, "Expires");
+ if (exps == NULL) {
+ exps = apr_table_get(r->headers_out, "Expires");
+ }
+ if (exps != NULL) {
+ exp = apr_date_parse_http(exps);
+ }
+ else {
+ exp = APR_DATE_BAD;
+ }
+
+ /* read the last-modified date; if the date is bad, then delete it */
+ lastmods = apr_table_get(r->err_headers_out, "Last-Modified");
+ if (lastmods == NULL) {
+ lastmods = apr_table_get(r->headers_out, "Last-Modified");
+ }
+ if (lastmods != NULL) {
+ lastmod = apr_date_parse_http(lastmods);
+ if (lastmod == APR_DATE_BAD) {
+ lastmods = NULL;
+ }
+ }
+ else {
+ lastmod = APR_DATE_BAD;
+ }
+
+ /* read the etag and cache-control from the entity */
+ etag = apr_table_get(r->err_headers_out, "Etag");
+ if (etag == NULL) {
+ etag = apr_table_get(r->headers_out, "Etag");
+ }
+ cc_out = cache_table_getm(r->pool, r->err_headers_out, "Cache-Control");
+ pragma = cache_table_getm(r->pool, r->err_headers_out, "Pragma");
+ headers = r->err_headers_out;
+ if (!cc_out && !pragma) {
+ cc_out = cache_table_getm(r->pool, r->headers_out, "Cache-Control");
+ pragma = cache_table_getm(r->pool, r->headers_out, "Pragma");
+ headers = r->headers_out;
+ }
+
+ /* Have we received a 304 response without any headers at all? Fall back to
+ * the original headers in the original cached request.
+ */
+ if (r->status == HTTP_NOT_MODIFIED && cache->stale_handle) {
+ if (!cc_out && !pragma) {
+ cc_out = cache_table_getm(r->pool, cache->stale_handle->resp_hdrs,
+ "Cache-Control");
+ pragma = cache_table_getm(r->pool, cache->stale_handle->resp_hdrs,
+ "Pragma");
+ }
+
+ /* 304 does not contain Content-Type and mod_mime regenerates the
+ * Content-Type based on the r->filename. This would lead to original
+ * Content-Type to be lost (overwritten by whatever mod_mime generates).
+ * We preserves the original Content-Type here. */
+ ap_set_content_type(r, apr_table_get(
+ cache->stale_handle->resp_hdrs, "Content-Type"));
+ }
+
+ /* Parse the cache control header */
+ memset(&control, 0, sizeof(cache_control_t));
+ ap_cache_control(r, &control, cc_out, pragma, headers);
+
+ /*
+ * what responses should we not cache?
+ *
+ * At this point we decide based on the response headers whether it
+ * is appropriate _NOT_ to cache the data from the server. There are
+ * a whole lot of conditions that prevent us from caching this data.
+ * They are tested here one by one to be clear and unambiguous.
+ */
+ if (r->status != HTTP_OK && r->status != HTTP_NON_AUTHORITATIVE
+ && r->status != HTTP_PARTIAL_CONTENT
+ && r->status != HTTP_MULTIPLE_CHOICES
+ && r->status != HTTP_MOVED_PERMANENTLY
+ && r->status != HTTP_NOT_MODIFIED) {
+ /* RFC2616 13.4 we are allowed to cache 200, 203, 206, 300, 301 or 410
+ * We allow the caching of 206, but a cache implementation might choose
+ * to decline to cache a 206 if it doesn't know how to.
+ * We include 304 Not Modified here too as this is the origin server
+ * telling us to serve the cached copy.
+ */
+ if (exps != NULL || cc_out != NULL) {
+ /* We are also allowed to cache any response given that it has a
+ * valid Expires or Cache Control header. If we find a either of
+ * those here, we pass request through the rest of the tests. From
+ * the RFC:
+ *
+ * A response received with any other status code (e.g. status
+ * codes 302 and 307) MUST NOT be returned in a reply to a
+ * subsequent request unless there are cache-control directives or
+ * another header(s) that explicitly allow it. For example, these
+ * include the following: an Expires header (section 14.21); a
+ * "max-age", "s-maxage", "must-revalidate", "proxy-revalidate",
+ * "public" or "private" cache-control directive (section 14.9).
+ *
+ * FIXME: Wrong if cc_out has just an extension we don't know about
+ */
+ }
+ else {
+ reason = apr_psprintf(p, "Response status %d", r->status);
+ }
+ }
+
+ if (reason) {
+ /* noop */
+ }
+ else if (!control.s_maxage && !control.max_age && !dconf->store_expired
+ && exps != NULL && exp == APR_DATE_BAD) {
+ /* if a broken Expires header is present, don't cache it
+ * Unless CC: s-maxage or max-age is present
+ */
+ reason = apr_pstrcat(p, "Broken expires header: ", exps, NULL);
+ }
+ else if (!control.s_maxage && !control.max_age
+ && !dconf->store_expired && exp != APR_DATE_BAD
+ && exp < r->request_time) {
+ /* if a Expires header is in the past, don't cache it
+ * Unless CC: s-maxage or max-age is present
+ */
+ reason = "Expires header already expired; not cacheable";
+ }
+ else if (!dconf->store_expired && (control.must_revalidate
+ || control.proxy_revalidate) && (!control.s_maxage_value
+ || (!control.s_maxage && !control.max_age_value)) && lastmods
+ == NULL && etag == NULL) {
+ /* if we're already stale, but can never revalidate, don't cache it */
+ reason
+ = "s-maxage or max-age zero and no Last-Modified or Etag; not cacheable";
+ }
+ else if (!conf->ignorequerystring && query && exps == NULL
+ && !control.max_age && !control.s_maxage) {
+ /* if a query string is present but no explicit expiration time,
+ * don't cache it (RFC 2616/13.9 & 13.2.1)
+ */
+ reason = "Query string present but no explicit expiration time";
+ }
+ else if (r->status == HTTP_NOT_MODIFIED &&
+ !cache->handle && !cache->stale_handle) {
+ /* if the server said 304 Not Modified but we have no cache
+ * file - pass this untouched to the user agent, it's not for us.
+ */
+ reason = "HTTP Status 304 Not Modified";
+ }
+ else if (r->status == HTTP_OK && lastmods == NULL && etag == NULL && (exps
+ == NULL) && (dconf->no_last_mod_ignore == 0) && !control.max_age
+ && !control.s_maxage) {
+ /* 200 OK response from HTTP/1.0 and up without Last-Modified,
+ * Etag, Expires, Cache-Control:max-age, or Cache-Control:s-maxage
+ * headers.
+ */
+ /* Note: mod-include clears last_modified/expires/etags - this
+ * is why we have an optional function for a key-gen ;-)
+ */
+ reason = "No Last-Modified; Etag; Expires; Cache-Control:max-age or Cache-Control:s-maxage headers";
+ }
+ else if (!dconf->store_nostore && control.no_store) {
+ /* RFC2616 14.9.2 Cache-Control: no-store response
+ * indicating do not cache, or stop now if you are
+ * trying to cache it.
+ */
+ reason = "Cache-Control: no-store present";
+ }
+ else if (!dconf->store_private && control.private) {
+ /* RFC2616 14.9.1 Cache-Control: private response
+ * this object is marked for this user's eyes only. Behave
+ * as a tunnel.
+ */
+ reason = "Cache-Control: private present";
+ }
+ else if (apr_table_get(r->headers_in, "Authorization")
+ && !(control.s_maxage || control.must_revalidate
+ || control.proxy_revalidate || control.public)) {
+ /* RFC2616 14.8 Authorisation:
+ * if authorisation is included in the request, we don't cache,
+ * but we can cache if the following exceptions are true:
+ * 1) If Cache-Control: s-maxage is included
+ * 2) If Cache-Control: must-revalidate is included
+ * 3) If Cache-Control: public is included
+ */
+ reason = "Authorization required";
+ }
+ else if (ap_find_token(NULL, apr_table_get(r->headers_out, "Vary"), "*")) {
+ reason = "Vary header contains '*'";
+ }
+ else if (apr_table_get(r->subprocess_env, "no-cache") != NULL) {
+ reason = "environment variable 'no-cache' is set";
+ }
+ else if (r->no_cache) {
+ /* or we've been asked not to cache it above */
+ reason = "r->no_cache present";
+ }
+ else if (cache->stale_handle
+ && APR_DATE_BAD
+ != (date = apr_date_parse_http(
+ apr_table_get(r->headers_out, "Date")))
+ && date < cache->stale_handle->cache_obj->info.date) {
+
+ /**
+ * 13.12 Cache Replacement:
+ *
+ * Note: a new response that has an older Date header value than
+ * existing cached responses is not cacheable.
+ */
+ reason = "updated entity is older than cached entity";
+
+ /* while this response is not cacheable, the previous response still is */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02474)
+ "cache: Removing CACHE_REMOVE_URL filter.");
+ ap_remove_output_filter(cache->remove_url_filter);
+ }
+ else if (r->status == HTTP_NOT_MODIFIED && cache->stale_handle) {
+ apr_table_t *left = cache->stale_handle->resp_hdrs;
+ apr_table_t *right = r->headers_out;
+ const char *ehs = NULL;
+
+ /* and lastly, contradiction checks for revalidated responses
+ * as per RFC2616 Section 10.3.5
+ */
+ if (cache_header_cmp(r->pool, left, right, "ETag")) {
+ ehs = "ETag";
+ }
+ for (eh = MOD_CACHE_ENTITY_HEADERS; *eh; ++eh) {
+ if (cache_header_cmp(r->pool, left, right, *eh)) {
+ ehs = (ehs) ? apr_pstrcat(r->pool, ehs, ", ", *eh, NULL) : *eh;
+ }
+ }
+ if (ehs) {
+ reason = apr_pstrcat(r->pool, "contradiction: 304 Not Modified; "
+ "but ", ehs, " modified", NULL);
+ }
+ }
+
+ /**
+ * Enforce RFC2616 Section 10.3.5, just in case. We caught any
+ * inconsistencies above.
+ *
+ * If the conditional GET used a strong cache validator (see section
+ * 13.3.3), the response SHOULD NOT include other entity-headers.
+ * Otherwise (i.e., the conditional GET used a weak validator), the
+ * response MUST NOT include other entity-headers; this prevents
+ * inconsistencies between cached entity-bodies and updated headers.
+ */
+ if (r->status == HTTP_NOT_MODIFIED) {
+ for (eh = MOD_CACHE_ENTITY_HEADERS; *eh; ++eh) {
+ apr_table_unset(r->headers_out, *eh);
+ }
+ }
+
+ /* Hold the phone. Some servers might allow us to cache a 2xx, but
+ * then make their 304 responses non cacheable. RFC2616 says this:
+ *
+ * If a 304 response indicates an entity not currently cached, then
+ * the cache MUST disregard the response and repeat the request
+ * without the conditional.
+ *
+ * A 304 response with contradictory headers is technically a
+ * different entity, to be safe, we remove the entity from the cache.
+ */
+ if (reason && r->status == HTTP_NOT_MODIFIED && cache->stale_handle) {
+
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02473)
+ "cache: %s responded with an uncacheable 304, "
+ "retrying the request %s. Reason: %s",
+ cache->key, r->unparsed_uri, reason);
+
+ /* we've got a cache conditional miss! tell anyone who cares */
+ cache_run_cache_status(cache->handle, r, r->headers_out, AP_CACHE_MISS,
+ apr_psprintf(r->pool,
+ "conditional cache miss: 304 was uncacheable, entity removed: %s",
+ reason));
+
+ /* remove the cached entity immediately, we might cache it again */
+ ap_remove_output_filter(cache->remove_url_filter);
+ cache_remove_url(cache, r);
+
+ /* let someone else attempt to cache */
+ cache_remove_lock(conf, cache, r, NULL);
+
+ /* remove this filter from the chain */
+ ap_remove_output_filter(f);
+
+ /* retry without the conditionals */
+ apr_table_unset(r->headers_in, "If-Match");
+ apr_table_unset(r->headers_in, "If-Modified-Since");
+ apr_table_unset(r->headers_in, "If-None-Match");
+ apr_table_unset(r->headers_in, "If-Range");
+ apr_table_unset(r->headers_in, "If-Unmodified-Since");
+
+ /* Currently HTTP_NOT_MODIFIED, and after the redirect, handlers won't think to set status to HTTP_OK */
+ r->status = HTTP_OK;
+ ap_internal_redirect(r->unparsed_uri, r);
+
+ return APR_SUCCESS;
+ }
+
+ /* Set the content length if known.
+ */
+ cl = apr_table_get(r->err_headers_out, "Content-Length");
+ if (cl == NULL) {
+ cl = apr_table_get(r->headers_out, "Content-Length");
+ }
+ if (cl && !ap_parse_strict_length(&size, cl)) {
+ reason = "invalid content length";
+ }
+
+ if (reason) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00768)
+ "cache: %s not cached for request %s. Reason: %s",
+ cache->key, r->unparsed_uri, reason);
+
+ /* we've got a cache miss! tell anyone who cares */
+ cache_run_cache_status(cache->handle, r, r->headers_out, AP_CACHE_MISS,
+ reason);
+
+ /* remove this filter from the chain */
+ ap_remove_output_filter(f);
+
+ /* remove the lock file unconditionally */
+ cache_remove_lock(conf, cache, r, NULL);
+
+ /* ship the data up the stack */
+ return ap_pass_brigade(f->next, in);
+ }
+
+ /* Make it so that we don't execute this path again. */
+ cache->in_checked = 1;
+
+ if (!cl) {
+ /* if we don't get the content-length, see if we have all the
+ * buckets and use their length to calculate the size
+ */
+ int all_buckets_here=0;
+ size=0;
+ for (e = APR_BRIGADE_FIRST(in);
+ e != APR_BRIGADE_SENTINEL(in);
+ e = APR_BUCKET_NEXT(e))
+ {
+ if (APR_BUCKET_IS_EOS(e)) {
+ all_buckets_here=1;
+ break;
+ }
+ if (APR_BUCKET_IS_FLUSH(e)) {
+ continue;
+ }
+ if (e->length == (apr_size_t)-1) {
+ break;
+ }
+ size += e->length;
+ }
+ if (!all_buckets_here) {
+ size = -1;
+ }
+ }
+
+ /* remember content length to check response size against later */
+ cache->size = size;
+
+ /* It's safe to cache the response.
+ *
+ * There are two possibilities at this point:
+ * - cache->handle == NULL. In this case there is no previously
+ * cached entity anywhere on the system. We must create a brand
+ * new entity and store the response in it.
+ * - cache->stale_handle != NULL. In this case there is a stale
+ * entity in the system which needs to be replaced by new
+ * content (unless the result was 304 Not Modified, which means
+ * the cached entity is actually fresh, and we should update
+ * the headers).
+ */
+
+ /* Did we have a stale cache entry that really is stale?
+ */
+ if (cache->stale_handle) {
+ if (r->status == HTTP_NOT_MODIFIED) {
+ /* Oh, hey. It isn't that stale! Yay! */
+ cache->handle = cache->stale_handle;
+ info = &cache->handle->cache_obj->info;
+ rv = OK;
+ }
+ else {
+ /* Oh, well. Toss it. */
+ cache->provider->remove_entity(cache->stale_handle);
+ /* Treat the request as if it wasn't conditional. */
+ cache->stale_handle = NULL;
+ /*
+ * Restore the original request headers as they may be needed
+ * by further output filters like the byterange filter to make
+ * the correct decisions.
+ */
+ r->headers_in = cache->stale_headers;
+ }
+ }
+
+ /* no cache handle, create a new entity */
+ if (!cache->handle) {
+ rv = cache_create_entity(cache, r, size, in);
+ info = apr_pcalloc(r->pool, sizeof(cache_info));
+ /* We only set info->status upon the initial creation. */
+ info->status = r->status;
+ }
+
+ if (rv != OK) {
+ /* we've got a cache miss! tell anyone who cares */
+ cache_run_cache_status(cache->handle, r, r->headers_out, AP_CACHE_MISS,
+ "cache miss: cache unwilling to store response");
+
+ /* Caching layer declined the opportunity to cache the response */
+ ap_remove_output_filter(f);
+ cache_remove_lock(conf, cache, r, NULL);
+ return ap_pass_brigade(f->next, in);
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00769)
+ "cache: Caching url %s for request %s",
+ cache->key, r->unparsed_uri);
+
+ /* We are actually caching this response. So it does not
+ * make sense to remove this entity any more.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00770)
+ "cache: Removing CACHE_REMOVE_URL filter.");
+ ap_remove_output_filter(cache->remove_url_filter);
+
+ /*
+ * We now want to update the cache file header information with
+ * the new date, last modified, expire and content length and write
+ * it away to our cache file. First, we determine these values from
+ * the response, using heuristics if appropriate.
+ *
+ * In addition, we make HTTP/1.1 age calculations and write them away
+ * too.
+ */
+
+ /* store away the previously parsed cache control headers */
+ memcpy(&info->control, &control, sizeof(cache_control_t));
+
+ /* Read the date. Generate one if one is not supplied */
+ dates = apr_table_get(r->err_headers_out, "Date");
+ if (dates == NULL) {
+ dates = apr_table_get(r->headers_out, "Date");
+ }
+ if (dates != NULL) {
+ info->date = apr_date_parse_http(dates);
+ }
+ else {
+ info->date = APR_DATE_BAD;
+ }
+
+ now = apr_time_now();
+ if (info->date == APR_DATE_BAD) { /* No, or bad date */
+ /* no date header (or bad header)! */
+ info->date = now;
+ }
+ date = info->date;
+
+ /* set response_time for HTTP/1.1 age calculations */
+ info->response_time = now;
+
+ /* get the request time */
+ info->request_time = r->request_time;
+
+ /* check last-modified date */
+ if (lastmod != APR_DATE_BAD && lastmod > date) {
+ /* if it's in the future, then replace by date */
+ lastmod = date;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0,
+ r, APLOGNO(00771) "cache: Last modified is in the future, "
+ "replacing with now");
+ }
+
+
+ /* CC has priority over Expires. */
+ if (control.s_maxage || control.max_age) {
+ apr_int64_t x;
+
+ x = control.s_maxage ? control.s_maxage_value : control.max_age_value;
+ x = x * MSEC_ONE_SEC;
+
+ if (x < dconf->minex) {
+ x = dconf->minex;
+ }
+ if (x > dconf->maxex) {
+ x = dconf->maxex;
+ }
+ exp = date + x;
+ }
+
+ /* if no expiry date then
+ * if Cache-Control: s-maxage
+ * expiry date = date + smaxage
+ * if Cache-Control: max-age
+ * expiry date = date + max-age
+ * else if lastmod
+ * expiry date = date + min((date - lastmod) * factor, maxexpire)
+ * else
+ * expire date = date + defaultexpire
+ */
+
+ if (exp == APR_DATE_BAD) {
+ if ((lastmod != APR_DATE_BAD) && (lastmod < date)) {
+ /* if lastmod == date then you get 0*conf->factor which results in
+ * an expiration time of now. This causes some problems with
+ * freshness calculations, so we choose the else path...
+ */
+ apr_time_t x = (apr_time_t) ((date - lastmod) * dconf->factor);
+
+ if (x < dconf->minex) {
+ x = dconf->minex;
+ }
+ if (x > dconf->maxex) {
+ x = dconf->maxex;
+ }
+ exp = date + x;
+ }
+ else {
+ exp = date + dconf->defex;
+ }
+ }
+ info->expire = exp;
+
+ /* We found a stale entry which wasn't really stale. */
+ if (cache->stale_handle) {
+
+ /* RFC 2616 10.3.5 states that entity headers are not supposed
+ * to be in the 304 response. Therefore, we need to combine the
+ * response headers with the cached headers *before* we update
+ * the cached headers.
+ *
+ * However, before doing that, we need to first merge in
+ * err_headers_out (note that store_headers() below already selects
+ * the cacheable only headers using ap_cache_cacheable_headers_out(),
+ * here we want to keep the original headers in r->headers_out and
+ * forward all of them to the client, including non-cacheable ones).
+ */
+ r->headers_out = cache_merge_headers_out(r);
+ apr_table_clear(r->err_headers_out);
+
+ /* Merge in our cached headers. However, keep any updated values. */
+ /* take output, overlay on top of cached */
+ cache_accept_headers(cache->handle, r, r->headers_out,
+ cache->handle->resp_hdrs, 1);
+ }
+
+ /* Write away header information to cache. It is possible that we are
+ * trying to update headers for an entity which has already been cached.
+ *
+ * This may fail, due to an unwritable cache area. E.g. filesystem full,
+ * permissions problems or a read-only (re)mount. This must be handled
+ * later.
+ */
+ rv = cache->provider->store_headers(cache->handle, r, info);
+
+ /* Did we just update the cached headers on a revalidated response?
+ *
+ * If so, we can now decide what to serve to the client. This is done in
+ * the same way as with a regular response, but conditions are now checked
+ * against the cached or merged response headers.
+ */
+ if (cache->stale_handle) {
+ apr_bucket_brigade *bb;
+ apr_bucket *bkt;
+ int status;
+
+ /* Load in the saved status and clear the status line. */
+ r->status = info->status;
+ r->status_line = NULL;
+
+ /* We're just saving response headers, so we are done. Commit
+ * the response at this point, unless there was a previous error.
+ */
+ if (rv == APR_SUCCESS) {
+ rv = cache->provider->commit_entity(cache->handle, r);
+ }
+
+ bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+
+ /* Restore the original request headers and see if we need to
+ * return anything else than the cached response (ie. the original
+ * request was conditional).
+ */
+ r->headers_in = cache->stale_headers;
+ status = ap_meets_conditions(r);
+ if (status != OK) {
+ r->status = status;
+
+ /* Strip the entity headers merged from the cached headers before
+ * updating the entry (see cache_accept_headers() above).
+ */
+ for (eh = MOD_CACHE_ENTITY_HEADERS; *eh; ++eh) {
+ apr_table_unset(r->headers_out, *eh);
+ }
+
+ bkt = apr_bucket_flush_create(bb->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, bkt);
+ }
+ else {
+ cache->provider->recall_body(cache->handle, r->pool, bb);
+
+ bkt = apr_bucket_eos_create(bb->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, bkt);
+ }
+
+ cache->block_response = 1;
+
+ /* Before returning we need to handle the possible case of an
+ * unwritable cache. Rather than leaving the entity in the cache
+ * and having it constantly re-validated, now that we have recalled
+ * the body it is safe to try and remove the url from the cache.
+ */
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(00772)
+ "cache: updating headers with store_headers failed. "
+ "Removing cached url.");
+
+ rv = cache->provider->remove_url(cache->stale_handle, r);
+ if (rv != OK) {
+ /* Probably a mod_cache_disk cache area has been (re)mounted
+ * read-only, or that there is a permissions problem.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(00773)
+ "cache: attempt to remove url from cache unsuccessful.");
+ }
+
+ /* we've got a cache conditional hit! tell anyone who cares */
+ cache_run_cache_status(cache->handle, r, r->headers_out,
+ AP_CACHE_REVALIDATE,
+ "conditional cache hit: entity refresh failed");
+
+ }
+ else {
+
+ /* we've got a cache conditional hit! tell anyone who cares */
+ cache_run_cache_status(cache->handle, r, r->headers_out,
+ AP_CACHE_REVALIDATE,
+ "conditional cache hit: entity refreshed");
+
+ }
+
+ /* let someone else attempt to cache */
+ cache_remove_lock(conf, cache, r, NULL);
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(02971)
+ "cache: serving %s (revalidated)", r->uri);
+
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(00774)
+ "cache: store_headers failed");
+
+ /* we've got a cache miss! tell anyone who cares */
+ cache_run_cache_status(cache->handle, r, r->headers_out, AP_CACHE_MISS,
+ "cache miss: store_headers failed");
+
+ ap_remove_output_filter(f);
+ cache_remove_lock(conf, cache, r, NULL);
+ return ap_pass_brigade(f->next, in);
+ }
+
+ /* we've got a cache miss! tell anyone who cares */
+ cache_run_cache_status(cache->handle, r, r->headers_out, AP_CACHE_MISS,
+ "cache miss: attempting entity save");
+
+ return cache_save_store(f, in, conf, cache);
+}
+
+/*
+ * CACHE_REMOVE_URL filter
+ * -----------------------
+ *
+ * This filter gets added in the quick handler every time the CACHE_SAVE filter
+ * gets inserted. Its purpose is to remove a confirmed stale cache entry from
+ * the cache.
+ *
+ * CACHE_REMOVE_URL has to be a protocol filter to ensure that is run even if
+ * the response is a canned error message, which removes the content filters
+ * and thus the CACHE_SAVE filter from the chain.
+ *
+ * CACHE_REMOVE_URL expects cache request rec within its context because the
+ * request this filter runs on can be different from the one whose cache entry
+ * should be removed, due to internal redirects.
+ *
+ * Note that CACHE_SAVE_URL (as a content-set filter, hence run before the
+ * protocol filters) will remove this filter if it decides to cache the file.
+ * Therefore, if this filter is left in, it must mean we need to toss any
+ * existing files.
+ */
+static apr_status_t cache_remove_url_filter(ap_filter_t *f,
+ apr_bucket_brigade *in)
+{
+ request_rec *r = f->r;
+ cache_request_rec *cache;
+
+ /* Setup cache_request_rec */
+ cache = (cache_request_rec *) f->ctx;
+
+ if (!cache) {
+ /* user likely configured CACHE_REMOVE_URL manually; they should really
+ * use mod_cache configuration to do that. So:
+ * 1. Remove ourselves
+ * 2. Do nothing and bail out
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00775)
+ "cache: CACHE_REMOVE_URL enabled unexpectedly");
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, in);
+ }
+
+ /* Now remove this cache entry from the cache */
+ cache_remove_url(cache, r);
+
+ /* remove ourselves */
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, in);
+}
+
+/*
+ * CACHE_INVALIDATE filter
+ * -----------------------
+ *
+ * This filter gets added in the quick handler should a PUT, POST or DELETE
+ * method be detected. If the response is successful, we must invalidate any
+ * cached entity as per RFC2616 section 13.10.
+ *
+ * CACHE_INVALIDATE has to be a protocol filter to ensure that is run even if
+ * the response is a canned error message, which removes the content filters
+ * from the chain.
+ *
+ * CACHE_INVALIDATE expects cache request rec within its context because the
+ * request this filter runs on can be different from the one whose cache entry
+ * should be removed, due to internal redirects.
+ */
+static apr_status_t cache_invalidate_filter(ap_filter_t *f,
+ apr_bucket_brigade *in)
+{
+ request_rec *r = f->r;
+ cache_request_rec *cache;
+
+ /* Setup cache_request_rec */
+ cache = (cache_request_rec *) f->ctx;
+
+ if (!cache) {
+ /* user likely configured CACHE_INVALIDATE manually; they should really
+ * use mod_cache configuration to do that. So:
+ * 1. Remove ourselves
+ * 2. Do nothing and bail out
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02465)
+ "cache: CACHE_INVALIDATE enabled unexpectedly: %s", r->uri);
+ }
+ else {
+
+ if (r->status > 299) {
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02466)
+ "cache: response status to '%s' method is %d (>299), not invalidating cached entity: %s", r->method, r->status, r->uri);
+
+ }
+ else {
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(02467)
+ "cache: Invalidating all cached entities in response to '%s' request for %s",
+ r->method, r->uri);
+
+ cache_invalidate(cache, r);
+
+ /* we've got a cache invalidate! tell everyone who cares */
+ cache_run_cache_status(cache->handle, r, r->headers_out,
+ AP_CACHE_INVALIDATE, apr_psprintf(r->pool,
+ "cache invalidated by %s", r->method));
+
+ }
+
+ }
+
+ /* remove ourselves */
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, in);
+}
+
+/*
+ * CACHE filter
+ * ------------
+ *
+ * This filter can be optionally inserted into the filter chain by the admin as
+ * a marker representing the precise location within the filter chain where
+ * caching is to be performed.
+ *
+ * When the filter chain is set up in the non-quick version of the URL handler,
+ * the CACHE filter is replaced by the CACHE_OUT or CACHE_SAVE filter,
+ * effectively inserting the caching filters at the point indicated by the
+ * admin. The CACHE filter is then removed.
+ *
+ * This allows caching to be performed before the content is passed to the
+ * INCLUDES filter, or to a filter that might perform transformations unique
+ * to the specific request and that would otherwise be non-cacheable.
+ */
+static apr_status_t cache_filter(ap_filter_t *f, apr_bucket_brigade *in)
+{
+
+ cache_server_conf
+ *conf =
+ (cache_server_conf *) ap_get_module_config(f->r->server->module_config,
+ &cache_module);
+
+ /* was the quick handler enabled */
+ if (conf->quick) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, f->r, APLOGNO(00776)
+ "cache: CACHE filter was added in quick handler mode and "
+ "will be ignored: %s", f->r->unparsed_uri);
+ }
+ /* otherwise we may have been bypassed, nothing to see here */
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, APLOGNO(00777)
+ "cache: CACHE filter was added twice, or was added where "
+ "the cache has been bypassed and will be ignored: %s",
+ f->r->unparsed_uri);
+ }
+
+ /* we are just a marker, so let's just remove ourselves */
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, in);
+}
+
+/**
+ * If configured, add the status of the caching attempt to the subprocess
+ * environment, and if configured, to headers in the response.
+ *
+ * The status is saved below the broad category of the status (hit, miss,
+ * revalidate), as well as a single cache-status key. This can be used for
+ * conditional logging.
+ *
+ * The status is optionally saved to an X-Cache header, and the detail of
+ * why a particular cache entry was cached (or not cached) is optionally
+ * saved to an X-Cache-Detail header. This extra detail is useful for
+ * service developers who may need to know whether their Cache-Control headers
+ * are working correctly.
+ */
+static int cache_status(cache_handle_t *h, request_rec *r,
+ apr_table_t *headers, ap_cache_status_e status, const char *reason)
+{
+ cache_server_conf
+ *conf =
+ (cache_server_conf *) ap_get_module_config(r->server->module_config,
+ &cache_module);
+
+ cache_dir_conf *dconf = ap_get_module_config(r->per_dir_config, &cache_module);
+ int x_cache = 0, x_cache_detail = 0;
+
+ switch (status) {
+ case AP_CACHE_HIT: {
+ apr_table_setn(r->subprocess_env, AP_CACHE_HIT_ENV, reason);
+ break;
+ }
+ case AP_CACHE_REVALIDATE: {
+ apr_table_setn(r->subprocess_env, AP_CACHE_REVALIDATE_ENV, reason);
+ break;
+ }
+ case AP_CACHE_MISS: {
+ apr_table_setn(r->subprocess_env, AP_CACHE_MISS_ENV, reason);
+ break;
+ }
+ case AP_CACHE_INVALIDATE: {
+ apr_table_setn(r->subprocess_env, AP_CACHE_INVALIDATE_ENV, reason);
+ break;
+ }
+ }
+
+ apr_table_setn(r->subprocess_env, AP_CACHE_STATUS_ENV, reason);
+
+ if (dconf && dconf->x_cache_set) {
+ x_cache = dconf->x_cache;
+ }
+ else {
+ x_cache = conf->x_cache;
+ }
+ if (x_cache) {
+ apr_table_setn(headers, "X-Cache", apr_psprintf(r->pool, "%s from %s",
+ status == AP_CACHE_HIT ? "HIT"
+ : status == AP_CACHE_REVALIDATE ? "REVALIDATE" : status
+ == AP_CACHE_INVALIDATE ? "INVALIDATE" : "MISS",
+ r->server->server_hostname));
+ }
+
+ if (dconf && dconf->x_cache_detail_set) {
+ x_cache_detail = dconf->x_cache_detail;
+ }
+ else {
+ x_cache_detail = conf->x_cache_detail;
+ }
+ if (x_cache_detail) {
+ apr_table_setn(headers, "X-Cache-Detail", apr_psprintf(r->pool,
+ "\"%s\" from %s", reason, r->server->server_hostname));
+ }
+
+ return OK;
+}
+
+/**
+ * If an error has occurred, but we have a stale cached entry, restore the
+ * filter stack from the save filter onwards. The canned error message will
+ * be discarded in the process, and replaced with the cached response.
+ */
+static void cache_insert_error_filter(request_rec *r)
+{
+ void *dummy;
+ cache_dir_conf *dconf;
+
+ /* ignore everything except for 5xx errors */
+ if (r->status < HTTP_INTERNAL_SERVER_ERROR) {
+ return;
+ }
+
+ dconf = ap_get_module_config(r->per_dir_config, &cache_module);
+
+ if (!dconf->stale_on_error) {
+ return;
+ }
+
+ /* RFC2616 13.8 Errors or Incomplete Response Cache Behavior:
+ * If a cache receives a 5xx response while attempting to revalidate an
+ * entry, it MAY either forward this response to the requesting client,
+ * or act as if the server failed to respond. In the latter case, it MAY
+ * return a previously received response unless the cached entry
+ * includes the "must-revalidate" cache-control directive (see section
+ * 14.9).
+ *
+ * This covers the case where the error was generated by our server via
+ * ap_die().
+ */
+ apr_pool_userdata_get(&dummy, CACHE_CTX_KEY, r->pool);
+ if (dummy) {
+ cache_request_rec *cache = (cache_request_rec *) dummy;
+
+ ap_remove_output_filter(cache->remove_url_filter);
+
+ if (cache->stale_handle && cache->save_filter
+ && !cache->stale_handle->cache_obj->info.control.must_revalidate
+ && !cache->stale_handle->cache_obj->info.control.proxy_revalidate
+ && !cache->stale_handle->cache_obj->info.control.s_maxage) {
+ const char *warn_head;
+ cache_server_conf
+ *conf =
+ (cache_server_conf *) ap_get_module_config(r->server->module_config,
+ &cache_module);
+
+ /* morph the current save filter into the out filter, and serve from
+ * cache.
+ */
+ cache->handle = cache->stale_handle;
+ if (r->main) {
+ cache->save_filter->frec = cache_out_subreq_filter_handle;
+ }
+ else {
+ cache->save_filter->frec = cache_out_filter_handle;
+ }
+
+ r->output_filters = cache->save_filter;
+
+ r->err_headers_out = cache->stale_handle->resp_hdrs;
+
+ /* add a revalidation warning */
+ warn_head = apr_table_get(r->err_headers_out, "Warning");
+ if ((warn_head == NULL) || ((warn_head != NULL)
+ && (ap_strstr_c(warn_head, "111") == NULL))) {
+ apr_table_mergen(r->err_headers_out, "Warning",
+ "111 Revalidation failed");
+ }
+
+ cache_run_cache_status(
+ cache->handle,
+ r,
+ r->err_headers_out,
+ AP_CACHE_HIT,
+ apr_psprintf(
+ r->pool,
+ "cache hit: %d status; stale content returned",
+ r->status));
+
+ /* give someone else the chance to cache the file */
+ cache_remove_lock(conf, cache, r, NULL);
+
+ }
+ }
+
+ return;
+}
+
+/* -------------------------------------------------------------- */
+/* Setup configurable data */
+
+static void *create_dir_config(apr_pool_t *p, char *dummy)
+{
+ cache_dir_conf *dconf = apr_pcalloc(p, sizeof(cache_dir_conf));
+
+ dconf->no_last_mod_ignore = 0;
+ dconf->store_expired = 0;
+ dconf->store_private = 0;
+ dconf->store_nostore = 0;
+
+ /* maximum time to cache a document */
+ dconf->maxex = DEFAULT_CACHE_MAXEXPIRE;
+ dconf->minex = DEFAULT_CACHE_MINEXPIRE;
+ /* default time to cache a document */
+ dconf->defex = DEFAULT_CACHE_EXPIRE;
+
+ /* factor used to estimate Expires date from LastModified date */
+ dconf->factor = DEFAULT_CACHE_LMFACTOR;
+
+ dconf->x_cache = DEFAULT_X_CACHE;
+ dconf->x_cache_detail = DEFAULT_X_CACHE_DETAIL;
+
+ dconf->stale_on_error = DEFAULT_CACHE_STALE_ON_ERROR;
+
+ /* array of providers for this URL space */
+ dconf->cacheenable = apr_array_make(p, 10, sizeof(struct cache_enable));
+
+ return dconf;
+}
+
+static void *merge_dir_config(apr_pool_t *p, void *basev, void *addv) {
+ cache_dir_conf *new = (cache_dir_conf *) apr_pcalloc(p, sizeof(cache_dir_conf));
+ cache_dir_conf *add = (cache_dir_conf *) addv;
+ cache_dir_conf *base = (cache_dir_conf *) basev;
+
+ new->no_last_mod_ignore = (add->no_last_mod_ignore_set == 0) ? base->no_last_mod_ignore : add->no_last_mod_ignore;
+ new->no_last_mod_ignore_set = add->no_last_mod_ignore_set || base->no_last_mod_ignore_set;
+
+ new->store_expired = (add->store_expired_set == 0) ? base->store_expired : add->store_expired;
+ new->store_expired_set = add->store_expired_set || base->store_expired_set;
+ new->store_private = (add->store_private_set == 0) ? base->store_private : add->store_private;
+ new->store_private_set = add->store_private_set || base->store_private_set;
+ new->store_nostore = (add->store_nostore_set == 0) ? base->store_nostore : add->store_nostore;
+ new->store_nostore_set = add->store_nostore_set || base->store_nostore_set;
+
+ /* maximum time to cache a document */
+ new->maxex = (add->maxex_set == 0) ? base->maxex : add->maxex;
+ new->maxex_set = add->maxex_set || base->maxex_set;
+ new->minex = (add->minex_set == 0) ? base->minex : add->minex;
+ new->minex_set = add->minex_set || base->minex_set;
+
+ /* default time to cache a document */
+ new->defex = (add->defex_set == 0) ? base->defex : add->defex;
+ new->defex_set = add->defex_set || base->defex_set;
+
+ /* factor used to estimate Expires date from LastModified date */
+ new->factor = (add->factor_set == 0) ? base->factor : add->factor;
+ new->factor_set = add->factor_set || base->factor_set;
+
+ new->x_cache = (add->x_cache_set == 0) ? base->x_cache : add->x_cache;
+ new->x_cache_set = add->x_cache_set || base->x_cache_set;
+ new->x_cache_detail = (add->x_cache_detail_set == 0) ? base->x_cache_detail
+ : add->x_cache_detail;
+ new->x_cache_detail_set = add->x_cache_detail_set
+ || base->x_cache_detail_set;
+
+ new->stale_on_error = (add->stale_on_error_set == 0) ? base->stale_on_error
+ : add->stale_on_error;
+ new->stale_on_error_set = add->stale_on_error_set
+ || base->stale_on_error_set;
+
+ new->cacheenable = add->enable_set ? apr_array_append(p, base->cacheenable,
+ add->cacheenable) : base->cacheenable;
+ new->enable_set = add->enable_set || base->enable_set;
+ new->disable = (add->disable_set == 0) ? base->disable : add->disable;
+ new->disable_set = add->disable_set || base->disable_set;
+
+ return new;
+}
+
+static void * create_cache_config(apr_pool_t *p, server_rec *s)
+{
+ const char *tmppath = NULL;
+ cache_server_conf *ps = apr_pcalloc(p, sizeof(cache_server_conf));
+
+ /* array of URL prefixes for which caching is enabled */
+ ps->cacheenable = apr_array_make(p, 10, sizeof(struct cache_enable));
+ /* array of URL prefixes for which caching is disabled */
+ ps->cachedisable = apr_array_make(p, 10, sizeof(struct cache_disable));
+ ps->ignorecachecontrol = 0;
+ ps->ignorecachecontrol_set = 0;
+ /* array of headers that should not be stored in cache */
+ ps->ignore_headers = apr_array_make(p, 10, sizeof(char *));
+ ps->ignore_headers_set = CACHE_IGNORE_HEADERS_UNSET;
+ /* flag indicating that query-string should be ignored when caching */
+ ps->ignorequerystring = 0;
+ ps->ignorequerystring_set = 0;
+ /* by default, run in the quick handler */
+ ps->quick = 1;
+ ps->quick_set = 0;
+ /* array of identifiers that should not be used for key calculation */
+ ps->ignore_session_id = apr_array_make(p, 10, sizeof(char *));
+ ps->ignore_session_id_set = CACHE_IGNORE_SESSION_ID_UNSET;
+ ps->lock = 0; /* thundering herd lock defaults to off */
+ ps->lock_set = 0;
+ apr_temp_dir_get(&tmppath, p);
+ if (tmppath) {
+ ps->lockpath = apr_pstrcat(p, tmppath, DEFAULT_CACHE_LOCKPATH, NULL);
+ }
+ ps->lockmaxage = apr_time_from_sec(DEFAULT_CACHE_MAXAGE);
+ ps->x_cache = DEFAULT_X_CACHE;
+ ps->x_cache_detail = DEFAULT_X_CACHE_DETAIL;
+ return ps;
+}
+
+static void * merge_cache_config(apr_pool_t *p, void *basev, void *overridesv)
+{
+ cache_server_conf *ps = apr_pcalloc(p, sizeof(cache_server_conf));
+ cache_server_conf *base = (cache_server_conf *) basev;
+ cache_server_conf *overrides = (cache_server_conf *) overridesv;
+
+ /* array of URL prefixes for which caching is disabled */
+ ps->cachedisable = apr_array_append(p,
+ base->cachedisable,
+ overrides->cachedisable);
+ /* array of URL prefixes for which caching is enabled */
+ ps->cacheenable = apr_array_append(p,
+ base->cacheenable,
+ overrides->cacheenable);
+
+ ps->ignorecachecontrol =
+ (overrides->ignorecachecontrol_set == 0)
+ ? base->ignorecachecontrol
+ : overrides->ignorecachecontrol;
+ ps->ignore_headers =
+ (overrides->ignore_headers_set == CACHE_IGNORE_HEADERS_UNSET)
+ ? base->ignore_headers
+ : overrides->ignore_headers;
+ ps->ignorequerystring =
+ (overrides->ignorequerystring_set == 0)
+ ? base->ignorequerystring
+ : overrides->ignorequerystring;
+ ps->ignore_session_id =
+ (overrides->ignore_session_id_set == CACHE_IGNORE_SESSION_ID_UNSET)
+ ? base->ignore_session_id
+ : overrides->ignore_session_id;
+ ps->lock =
+ (overrides->lock_set == 0)
+ ? base->lock
+ : overrides->lock;
+ ps->lockpath =
+ (overrides->lockpath_set == 0)
+ ? base->lockpath
+ : overrides->lockpath;
+ ps->lockmaxage =
+ (overrides->lockmaxage_set == 0)
+ ? base->lockmaxage
+ : overrides->lockmaxage;
+ ps->quick =
+ (overrides->quick_set == 0)
+ ? base->quick
+ : overrides->quick;
+ ps->x_cache =
+ (overrides->x_cache_set == 0)
+ ? base->x_cache
+ : overrides->x_cache;
+ ps->x_cache_detail =
+ (overrides->x_cache_detail_set == 0)
+ ? base->x_cache_detail
+ : overrides->x_cache_detail;
+ ps->base_uri =
+ (overrides->base_uri_set == 0)
+ ? base->base_uri
+ : overrides->base_uri;
+ return ps;
+}
+
+static const char *set_cache_quick_handler(cmd_parms *parms, void *dummy,
+ int flag)
+{
+ cache_server_conf *conf;
+
+ conf =
+ (cache_server_conf *)ap_get_module_config(parms->server->module_config,
+ &cache_module);
+ conf->quick = flag;
+ conf->quick_set = 1;
+ return NULL;
+
+}
+
+static const char *set_cache_ignore_no_last_mod(cmd_parms *parms, void *dummy,
+ int flag)
+{
+ cache_dir_conf *dconf = (cache_dir_conf *)dummy;
+
+ dconf->no_last_mod_ignore = flag;
+ dconf->no_last_mod_ignore_set = 1;
+ return NULL;
+
+}
+
+static const char *set_cache_ignore_cachecontrol(cmd_parms *parms,
+ void *dummy, int flag)
+{
+ cache_server_conf *conf;
+
+ conf =
+ (cache_server_conf *)ap_get_module_config(parms->server->module_config,
+ &cache_module);
+ conf->ignorecachecontrol = flag;
+ conf->ignorecachecontrol_set = 1;
+ return NULL;
+}
+
+static const char *set_cache_store_expired(cmd_parms *parms, void *dummy,
+ int flag)
+{
+ cache_dir_conf *dconf = (cache_dir_conf *)dummy;
+
+ dconf->store_expired = flag;
+ dconf->store_expired_set = 1;
+ return NULL;
+}
+
+static const char *set_cache_store_private(cmd_parms *parms, void *dummy,
+ int flag)
+{
+ cache_dir_conf *dconf = (cache_dir_conf *)dummy;
+
+ dconf->store_private = flag;
+ dconf->store_private_set = 1;
+ return NULL;
+}
+
+static const char *set_cache_store_nostore(cmd_parms *parms, void *dummy,
+ int flag)
+{
+ cache_dir_conf *dconf = (cache_dir_conf *)dummy;
+
+ dconf->store_nostore = flag;
+ dconf->store_nostore_set = 1;
+ return NULL;
+}
+
+static const char *add_ignore_header(cmd_parms *parms, void *dummy,
+ const char *header)
+{
+ cache_server_conf *conf;
+ char **new;
+
+ conf =
+ (cache_server_conf *)ap_get_module_config(parms->server->module_config,
+ &cache_module);
+ if (!strcasecmp(header, "None")) {
+ /* if header None is listed clear array */
+ conf->ignore_headers->nelts = 0;
+ }
+ else {
+ if ((conf->ignore_headers_set == CACHE_IGNORE_HEADERS_UNSET) ||
+ (conf->ignore_headers->nelts)) {
+ /* Only add header if no "None" has been found in header list
+ * so far.
+ * (When 'None' is passed, IGNORE_HEADERS_SET && nelts == 0.)
+ */
+ new = (char **)apr_array_push(conf->ignore_headers);
+ (*new) = (char *)header;
+ }
+ }
+ conf->ignore_headers_set = CACHE_IGNORE_HEADERS_SET;
+ return NULL;
+}
+
+static const char *add_ignore_session_id(cmd_parms *parms, void *dummy,
+ const char *identifier)
+{
+ cache_server_conf *conf;
+ char **new;
+
+ conf =
+ (cache_server_conf *)ap_get_module_config(parms->server->module_config,
+ &cache_module);
+ if (!strcasecmp(identifier, "None")) {
+ /* if identifier None is listed clear array */
+ conf->ignore_session_id->nelts = 0;
+ }
+ else {
+ if ((conf->ignore_session_id_set == CACHE_IGNORE_SESSION_ID_UNSET) ||
+ (conf->ignore_session_id->nelts)) {
+ /*
+ * Only add identifier if no "None" has been found in identifier
+ * list so far.
+ */
+ new = (char **)apr_array_push(conf->ignore_session_id);
+ (*new) = (char *)identifier;
+ }
+ }
+ conf->ignore_session_id_set = CACHE_IGNORE_SESSION_ID_SET;
+ return NULL;
+}
+
+static const char *add_cache_enable(cmd_parms *parms, void *dummy,
+ const char *type,
+ const char *url)
+{
+ cache_dir_conf *dconf = (cache_dir_conf *)dummy;
+ cache_server_conf *conf;
+ struct cache_enable *new;
+
+ const char *err = ap_check_cmd_context(parms,
+ NOT_IN_DIRECTORY|NOT_IN_LIMIT|NOT_IN_FILES);
+ if (err != NULL) {
+ return err;
+ }
+
+ if (*type == '/') {
+ return apr_psprintf(parms->pool,
+ "provider (%s) starts with a '/'. Are url and provider switched?",
+ type);
+ }
+
+ if (!url) {
+ url = parms->path;
+ }
+ if (!url) {
+ return apr_psprintf(parms->pool,
+ "CacheEnable provider (%s) is missing an URL.", type);
+ }
+ if (parms->path && strncmp(parms->path, url, strlen(parms->path))) {
+ return "When in a Location, CacheEnable must specify a path or an URL below "
+ "that location.";
+ }
+
+ conf =
+ (cache_server_conf *)ap_get_module_config(parms->server->module_config,
+ &cache_module);
+
+ if (parms->path) {
+ new = apr_array_push(dconf->cacheenable);
+ dconf->enable_set = 1;
+ }
+ else {
+ new = apr_array_push(conf->cacheenable);
+ }
+
+ new->type = type;
+ if (apr_uri_parse(parms->pool, url, &(new->url))) {
+ return NULL;
+ }
+ if (new->url.path) {
+ new->pathlen = strlen(new->url.path);
+ } else {
+ new->pathlen = 1;
+ new->url.path = "/";
+ }
+ return NULL;
+}
+
+static const char *add_cache_disable(cmd_parms *parms, void *dummy,
+ const char *url)
+{
+ cache_dir_conf *dconf = (cache_dir_conf *)dummy;
+ cache_server_conf *conf;
+ struct cache_disable *new;
+
+ const char *err = ap_check_cmd_context(parms,
+ NOT_IN_DIRECTORY|NOT_IN_LIMIT|NOT_IN_FILES);
+ if (err != NULL) {
+ return err;
+ }
+
+ conf =
+ (cache_server_conf *)ap_get_module_config(parms->server->module_config,
+ &cache_module);
+
+ if (parms->path) {
+ if (!strcasecmp(url, "on")) {
+ dconf->disable = 1;
+ dconf->disable_set = 1;
+ return NULL;
+ }
+ else {
+ return "CacheDisable must be followed by the word 'on' when in a Location.";
+ }
+ }
+
+ if (!url || (url[0] != '/' && !ap_strchr_c(url, ':'))) {
+ return "CacheDisable must specify a path or an URL.";
+ }
+
+ new = apr_array_push(conf->cachedisable);
+ if (apr_uri_parse(parms->pool, url, &(new->url))) {
+ return NULL;
+ }
+ if (new->url.path) {
+ new->pathlen = strlen(new->url.path);
+ } else {
+ new->pathlen = 1;
+ new->url.path = "/";
+ }
+ return NULL;
+}
+
+static const char *set_cache_maxex(cmd_parms *parms, void *dummy,
+ const char *arg)
+{
+ cache_dir_conf *dconf = (cache_dir_conf *)dummy;
+
+ dconf->maxex = (apr_time_t) (atol(arg) * MSEC_ONE_SEC);
+ dconf->maxex_set = 1;
+ return NULL;
+}
+
+static const char *set_cache_minex(cmd_parms *parms, void *dummy,
+ const char *arg)
+{
+ cache_dir_conf *dconf = (cache_dir_conf *)dummy;
+
+ dconf->minex = (apr_time_t) (atol(arg) * MSEC_ONE_SEC);
+ dconf->minex_set = 1;
+ return NULL;
+}
+
+static const char *set_cache_defex(cmd_parms *parms, void *dummy,
+ const char *arg)
+{
+ cache_dir_conf *dconf = (cache_dir_conf *)dummy;
+
+ dconf->defex = (apr_time_t) (atol(arg) * MSEC_ONE_SEC);
+ dconf->defex_set = 1;
+ return NULL;
+}
+
+static const char *set_cache_factor(cmd_parms *parms, void *dummy,
+ const char *arg)
+{
+ cache_dir_conf *dconf = (cache_dir_conf *)dummy;
+ double val;
+
+ if (sscanf(arg, "%lg", &val) != 1) {
+ return "CacheLastModifiedFactor value must be a float";
+ }
+ dconf->factor = val;
+ dconf->factor_set = 1;
+ return NULL;
+}
+
+static const char *set_cache_ignore_querystring(cmd_parms *parms, void *dummy,
+ int flag)
+{
+ cache_server_conf *conf;
+
+ conf =
+ (cache_server_conf *)ap_get_module_config(parms->server->module_config,
+ &cache_module);
+ conf->ignorequerystring = flag;
+ conf->ignorequerystring_set = 1;
+ return NULL;
+}
+
+static const char *set_cache_lock(cmd_parms *parms, void *dummy,
+ int flag)
+{
+ cache_server_conf *conf;
+
+ conf =
+ (cache_server_conf *)ap_get_module_config(parms->server->module_config,
+ &cache_module);
+ conf->lock = flag;
+ conf->lock_set = 1;
+ return NULL;
+}
+
+static const char *set_cache_lock_path(cmd_parms *parms, void *dummy,
+ const char *arg)
+{
+ cache_server_conf *conf;
+
+ conf =
+ (cache_server_conf *)ap_get_module_config(parms->server->module_config,
+ &cache_module);
+
+ conf->lockpath = ap_server_root_relative(parms->pool, arg);
+ if (!conf->lockpath) {
+ return apr_pstrcat(parms->pool, "Invalid CacheLockPath path ",
+ arg, NULL);
+ }
+ conf->lockpath_set = 1;
+ return NULL;
+}
+
+static const char *set_cache_lock_maxage(cmd_parms *parms, void *dummy,
+ const char *arg)
+{
+ cache_server_conf *conf;
+ apr_int64_t seconds;
+
+ conf =
+ (cache_server_conf *)ap_get_module_config(parms->server->module_config,
+ &cache_module);
+ seconds = apr_atoi64(arg);
+ if (seconds <= 0) {
+ return "CacheLockMaxAge value must be a non-zero positive integer";
+ }
+ conf->lockmaxage = apr_time_from_sec(seconds);
+ conf->lockmaxage_set = 1;
+ return NULL;
+}
+
+static const char *set_cache_x_cache(cmd_parms *parms, void *dummy, int flag)
+{
+
+ if (parms->path) {
+ cache_dir_conf *dconf = (cache_dir_conf *)dummy;
+
+ dconf->x_cache = flag;
+ dconf->x_cache_set = 1;
+
+ }
+ else {
+ cache_server_conf *conf =
+ (cache_server_conf *)ap_get_module_config(parms->server->module_config,
+ &cache_module);
+
+ conf->x_cache = flag;
+ conf->x_cache_set = 1;
+
+ }
+
+ return NULL;
+}
+
+static const char *set_cache_x_cache_detail(cmd_parms *parms, void *dummy, int flag)
+{
+
+ if (parms->path) {
+ cache_dir_conf *dconf = (cache_dir_conf *)dummy;
+
+ dconf->x_cache_detail = flag;
+ dconf->x_cache_detail_set = 1;
+
+ }
+ else {
+ cache_server_conf *conf =
+ (cache_server_conf *)ap_get_module_config(parms->server->module_config,
+ &cache_module);
+
+ conf->x_cache_detail = flag;
+ conf->x_cache_detail_set = 1;
+
+ }
+
+ return NULL;
+}
+
+static const char *set_cache_key_base_url(cmd_parms *parms, void *dummy,
+ const char *arg)
+{
+ cache_server_conf *conf;
+ apr_status_t rv;
+
+ conf =
+ (cache_server_conf *)ap_get_module_config(parms->server->module_config,
+ &cache_module);
+ conf->base_uri = apr_pcalloc(parms->pool, sizeof(apr_uri_t));
+ rv = apr_uri_parse(parms->pool, arg, conf->base_uri);
+ if (rv != APR_SUCCESS) {
+ return apr_psprintf(parms->pool, "Could not parse '%s' as an URL.", arg);
+ }
+ else if (!conf->base_uri->scheme && !conf->base_uri->hostname &&
+ !conf->base_uri->port_str) {
+ return apr_psprintf(parms->pool, "URL '%s' must contain at least one of a scheme, a hostname or a port.", arg);
+ }
+ conf->base_uri_set = 1;
+ return NULL;
+}
+
+static const char *set_cache_stale_on_error(cmd_parms *parms, void *dummy,
+ int flag)
+{
+ cache_dir_conf *dconf = (cache_dir_conf *)dummy;
+
+ dconf->stale_on_error = flag;
+ dconf->stale_on_error_set = 1;
+ return NULL;
+}
+
+static int cache_post_config(apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ /* This is the means by which unusual (non-unix) os's may find alternate
+ * means to run a given command (e.g. shebang/registry parsing on Win32)
+ */
+ cache_generate_key = APR_RETRIEVE_OPTIONAL_FN(ap_cache_generate_key);
+ if (!cache_generate_key) {
+ cache_generate_key = cache_generate_key_default;
+ }
+ return OK;
+}
+
+
+static const command_rec cache_cmds[] =
+{
+ /* XXX
+ * Consider a new config directive that enables loading specific cache
+ * implementations (like mod_cache_mem, mod_cache_file, etc.).
+ * Rather than using a LoadModule directive, admin would use something
+ * like CacheModule mem_cache_module | file_cache_module, etc,
+ * which would cause the approprpriate cache module to be loaded.
+ * This is more intuitive that requiring a LoadModule directive.
+ */
+
+ AP_INIT_TAKE12("CacheEnable", add_cache_enable, NULL, RSRC_CONF|ACCESS_CONF,
+ "A cache type and partial URL prefix below which "
+ "caching is enabled"),
+ AP_INIT_TAKE1("CacheDisable", add_cache_disable, NULL, RSRC_CONF|ACCESS_CONF,
+ "A partial URL prefix below which caching is disabled"),
+ AP_INIT_TAKE1("CacheMaxExpire", set_cache_maxex, NULL, RSRC_CONF|ACCESS_CONF,
+ "The maximum time in seconds to cache a document"),
+ AP_INIT_TAKE1("CacheMinExpire", set_cache_minex, NULL, RSRC_CONF|ACCESS_CONF,
+ "The minimum time in seconds to cache a document"),
+ AP_INIT_TAKE1("CacheDefaultExpire", set_cache_defex, NULL, RSRC_CONF|ACCESS_CONF,
+ "The default time in seconds to cache a document"),
+ AP_INIT_FLAG("CacheQuickHandler", set_cache_quick_handler, NULL,
+ RSRC_CONF,
+ "Run the cache in the quick handler, default on"),
+ AP_INIT_FLAG("CacheIgnoreNoLastMod", set_cache_ignore_no_last_mod, NULL,
+ RSRC_CONF|ACCESS_CONF,
+ "Ignore Responses where there is no Last Modified Header"),
+ AP_INIT_FLAG("CacheIgnoreCacheControl", set_cache_ignore_cachecontrol,
+ NULL, RSRC_CONF,
+ "Ignore requests from the client for uncached content"),
+ AP_INIT_FLAG("CacheStoreExpired", set_cache_store_expired,
+ NULL, RSRC_CONF|ACCESS_CONF,
+ "Ignore expiration dates when populating cache, resulting in "
+ "an If-Modified-Since request to the backend on retrieval"),
+ AP_INIT_FLAG("CacheStorePrivate", set_cache_store_private,
+ NULL, RSRC_CONF|ACCESS_CONF,
+ "Ignore 'Cache-Control: private' and store private content"),
+ AP_INIT_FLAG("CacheStoreNoStore", set_cache_store_nostore,
+ NULL, RSRC_CONF|ACCESS_CONF,
+ "Ignore 'Cache-Control: no-store' and store sensitive content"),
+ AP_INIT_ITERATE("CacheIgnoreHeaders", add_ignore_header, NULL, RSRC_CONF,
+ "A space separated list of headers that should not be "
+ "stored by the cache"),
+ AP_INIT_FLAG("CacheIgnoreQueryString", set_cache_ignore_querystring,
+ NULL, RSRC_CONF,
+ "Ignore query-string when caching"),
+ AP_INIT_ITERATE("CacheIgnoreURLSessionIdentifiers", add_ignore_session_id,
+ NULL, RSRC_CONF, "A space separated list of session "
+ "identifiers that should be ignored for creating the key "
+ "of the cached entity."),
+ AP_INIT_TAKE1("CacheLastModifiedFactor", set_cache_factor, NULL, RSRC_CONF|ACCESS_CONF,
+ "The factor used to estimate Expires date from "
+ "LastModified date"),
+ AP_INIT_FLAG("CacheLock", set_cache_lock,
+ NULL, RSRC_CONF,
+ "Enable or disable the thundering herd lock."),
+ AP_INIT_TAKE1("CacheLockPath", set_cache_lock_path, NULL, RSRC_CONF,
+ "The thundering herd lock path. Defaults to the '"
+ DEFAULT_CACHE_LOCKPATH "' directory in the system "
+ "temp directory."),
+ AP_INIT_TAKE1("CacheLockMaxAge", set_cache_lock_maxage, NULL, RSRC_CONF,
+ "Maximum age of any thundering herd lock."),
+ AP_INIT_FLAG("CacheHeader", set_cache_x_cache, NULL, RSRC_CONF | ACCESS_CONF,
+ "Add a X-Cache header to responses. Default is off."),
+ AP_INIT_FLAG("CacheDetailHeader", set_cache_x_cache_detail, NULL,
+ RSRC_CONF | ACCESS_CONF,
+ "Add a X-Cache-Detail header to responses. Default is off."),
+ AP_INIT_TAKE1("CacheKeyBaseURL", set_cache_key_base_url, NULL, RSRC_CONF,
+ "Override the base URL of reverse proxied cache keys."),
+ AP_INIT_FLAG("CacheStaleOnError", set_cache_stale_on_error,
+ NULL, RSRC_CONF|ACCESS_CONF,
+ "Serve stale content on 5xx errors if present. Defaults to on."),
+ {NULL}
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+ /* cache initializer */
+ /* cache quick handler */
+ ap_hook_quick_handler(cache_quick_handler, NULL, NULL, APR_HOOK_FIRST);
+ /* cache handler */
+ ap_hook_handler(cache_handler, NULL, NULL, APR_HOOK_REALLY_FIRST);
+ /* cache status */
+ cache_hook_cache_status(cache_status, NULL, NULL, APR_HOOK_MIDDLE);
+ /* cache error handler */
+ ap_hook_insert_error_filter(cache_insert_error_filter, NULL, NULL, APR_HOOK_MIDDLE);
+ /* cache filters
+ * XXX The cache filters need to run right after the handlers and before
+ * any other filters. Consider creating AP_FTYPE_CACHE for this purpose.
+ *
+ * Depending on the type of request (subrequest / main request) they
+ * need to be run before AP_FTYPE_CONTENT_SET / after AP_FTYPE_CONTENT_SET
+ * filters. Thus create two filter handles for each type:
+ * cache_save_filter_handle / cache_out_filter_handle to be used by
+ * main requests and
+ * cache_save_subreq_filter_handle / cache_out_subreq_filter_handle
+ * to be run by subrequest
+ */
+ /*
+ * CACHE is placed into the filter chain at an admin specified location,
+ * and when the cache_handler is run, the CACHE filter is swapped with
+ * the CACHE_OUT filter, or CACHE_SAVE filter as appropriate. This has
+ * the effect of offering optional fine control of where the cache is
+ * inserted into the filter chain.
+ */
+ cache_filter_handle =
+ ap_register_output_filter("CACHE",
+ cache_filter,
+ NULL,
+ AP_FTYPE_RESOURCE);
+ /*
+ * CACHE_SAVE must go into the filter chain after a possible DEFLATE
+ * filter to ensure that the compressed content is stored.
+ * Incrementing filter type by 1 ensures this happens.
+ */
+ cache_save_filter_handle =
+ ap_register_output_filter("CACHE_SAVE",
+ cache_save_filter,
+ NULL,
+ AP_FTYPE_CONTENT_SET+1);
+ /*
+ * CACHE_SAVE_SUBREQ must go into the filter chain before SUBREQ_CORE to
+ * handle subrequsts. Decrementing filter type by 1 ensures this
+ * happens.
+ */
+ cache_save_subreq_filter_handle =
+ ap_register_output_filter("CACHE_SAVE_SUBREQ",
+ cache_save_filter,
+ NULL,
+ AP_FTYPE_CONTENT_SET-1);
+ /*
+ * CACHE_OUT must go into the filter chain after a possible DEFLATE
+ * filter to ensure that already compressed cache objects do not
+ * get compressed again. Incrementing filter type by 1 ensures
+ * this happens.
+ */
+ cache_out_filter_handle =
+ ap_register_output_filter("CACHE_OUT",
+ cache_out_filter,
+ NULL,
+ AP_FTYPE_CONTENT_SET+1);
+ /*
+ * CACHE_OUT_SUBREQ must go into the filter chain before SUBREQ_CORE to
+ * handle subrequsts. Decrementing filter type by 1 ensures this
+ * happens.
+ */
+ cache_out_subreq_filter_handle =
+ ap_register_output_filter("CACHE_OUT_SUBREQ",
+ cache_out_filter,
+ NULL,
+ AP_FTYPE_CONTENT_SET-1);
+ /* CACHE_REMOVE_URL has to be a protocol filter to ensure that is
+ * run even if the response is a canned error message, which
+ * removes the content filters.
+ */
+ cache_remove_url_filter_handle =
+ ap_register_output_filter("CACHE_REMOVE_URL",
+ cache_remove_url_filter,
+ NULL,
+ AP_FTYPE_PROTOCOL);
+ cache_invalidate_filter_handle =
+ ap_register_output_filter("CACHE_INVALIDATE",
+ cache_invalidate_filter,
+ NULL,
+ AP_FTYPE_PROTOCOL);
+ ap_hook_post_config(cache_post_config, NULL, NULL, APR_HOOK_REALLY_FIRST);
+}
+
+AP_DECLARE_MODULE(cache) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_dir_config, /* create per-directory config structure */
+ merge_dir_config, /* merge per-directory config structures */
+ create_cache_config, /* create per-server config structure */
+ merge_cache_config, /* merge per-server config structures */
+ cache_cmds, /* command apr_table_t */
+ register_hooks
+};
+
+APR_HOOK_STRUCT(
+ APR_HOOK_LINK(cache_status)
+)
+
+APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(cache, CACHE, int, cache_status,
+ (cache_handle_t *h, request_rec *r,
+ apr_table_t *headers, ap_cache_status_e status,
+ const char *reason), (h, r, headers, status, reason),
+ OK, DECLINED)
diff --git a/modules/cache/mod_cache.dep b/modules/cache/mod_cache.dep
new file mode 100644
index 0000000..79094ac
--- /dev/null
+++ b/modules/cache/mod_cache.dep
@@ -0,0 +1,194 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_cache.mak
+
+.\cache_storage.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_date.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_atomic.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\cache_common.h"\
+ ".\cache_storage.h"\
+ ".\cache_util.h"\
+ ".\mod_cache.h"\
+
+
+.\cache_util.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_date.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_atomic.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\cache_common.h"\
+ ".\cache_util.h"\
+ ".\mod_cache.h"\
+
+
+.\mod_cache.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_date.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_atomic.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\cache_common.h"\
+ ".\cache_storage.h"\
+ ".\cache_util.h"\
+ ".\mod_cache.h"\
+
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
diff --git a/modules/cache/mod_cache.dsp b/modules/cache/mod_cache.dsp
new file mode 100644
index 0000000..40a1b34
--- /dev/null
+++ b/modules/cache/mod_cache.dsp
@@ -0,0 +1,131 @@
+# Microsoft Developer Studio Project File - Name="mod_cache" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_cache - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_cache.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_cache.mak" CFG="mod_cache - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_cache - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_cache - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_cache - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "MOD_CACHE_EXPORTS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "CACHE_DECLARE_EXPORT" /D "MOD_CACHE_EXPORTS" /Fd"Release\mod_cache_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_cache.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_cache.so" /d LONG_NAME="cache_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_cache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_cache.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_cache.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_cache - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "CACHE_DECLARE_EXPORT" /Fd"Debug\mod_cache_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_cache.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_cache.so" /d LONG_NAME="cache_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_cache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_cache.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_cache.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_cache - Win32 Release"
+# Name "mod_cache - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\cache_storage.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\cache_util.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\mod_cache.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# Begin Source File
+
+SOURCE=.\mod_cache.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/cache/mod_cache.h b/modules/cache/mod_cache.h
new file mode 100644
index 0000000..53e80c0
--- /dev/null
+++ b/modules/cache/mod_cache.h
@@ -0,0 +1,192 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file mod_cache.h
+ * @brief Main include file for the Apache Transparent Cache
+ *
+ * @defgroup MOD_CACHE mod_cache
+ * @ingroup APACHE_MODS
+ * @{
+ */
+
+#ifndef MOD_CACHE_H
+#define MOD_CACHE_H
+
+#include "httpd.h"
+#include "apr_date.h"
+#include "apr_optional.h"
+#include "apr_hooks.h"
+
+#include "cache_common.h"
+
+/* Create a set of CACHE_DECLARE(type), CACHE_DECLARE_NONSTD(type) and
+ * CACHE_DECLARE_DATA with appropriate export and import tags for the platform
+ */
+#if !defined(WIN32)
+#define CACHE_DECLARE(type) type
+#define CACHE_DECLARE_NONSTD(type) type
+#define CACHE_DECLARE_DATA
+#elif defined(CACHE_DECLARE_STATIC)
+#define CACHE_DECLARE(type) type __stdcall
+#define CACHE_DECLARE_NONSTD(type) type
+#define CACHE_DECLARE_DATA
+#elif defined(CACHE_DECLARE_EXPORT)
+#define CACHE_DECLARE(type) __declspec(dllexport) type __stdcall
+#define CACHE_DECLARE_NONSTD(type) __declspec(dllexport) type
+#define CACHE_DECLARE_DATA __declspec(dllexport)
+#else
+#define CACHE_DECLARE(type) __declspec(dllimport) type __stdcall
+#define CACHE_DECLARE_NONSTD(type) __declspec(dllimport) type
+#define CACHE_DECLARE_DATA __declspec(dllimport)
+#endif
+
+/* cache info information */
+typedef struct cache_info cache_info;
+struct cache_info {
+ /**
+ * the original time corresponding to the 'Date:' header of the request
+ * served
+ */
+ apr_time_t date;
+ /** a time when the cached entity is due to expire */
+ apr_time_t expire;
+ /** r->request_time from the same request */
+ apr_time_t request_time;
+ /** apr_time_now() at the time the entity was actually cached */
+ apr_time_t response_time;
+ /**
+ * HTTP status code of the cached entity. Though not necessarily the
+ * status code finally issued to the request.
+ */
+ int status;
+ /* cached cache-control */
+ cache_control_t control;
+};
+
+/* cache handle information */
+typedef struct cache_object cache_object_t;
+struct cache_object {
+ const char *key;
+ cache_object_t *next;
+ cache_info info;
+ /* Opaque portion (specific to the implementation) of the cache object */
+ void *vobj;
+};
+
+typedef struct cache_handle cache_handle_t;
+struct cache_handle {
+ cache_object_t *cache_obj;
+ apr_table_t *req_hdrs; /* cached request headers */
+ apr_table_t *resp_hdrs; /* cached response headers */
+};
+
+#define CACHE_PROVIDER_GROUP "cache"
+
+typedef struct {
+ int (*remove_entity) (cache_handle_t *h);
+ apr_status_t (*store_headers)(cache_handle_t *h, request_rec *r, cache_info *i);
+ apr_status_t (*store_body)(cache_handle_t *h, request_rec *r, apr_bucket_brigade *in,
+ apr_bucket_brigade *out);
+ apr_status_t (*recall_headers) (cache_handle_t *h, request_rec *r);
+ apr_status_t (*recall_body) (cache_handle_t *h, apr_pool_t *p, apr_bucket_brigade *bb);
+ int (*create_entity) (cache_handle_t *h, request_rec *r,
+ const char *urlkey, apr_off_t len, apr_bucket_brigade *bb);
+ int (*open_entity) (cache_handle_t *h, request_rec *r,
+ const char *urlkey);
+ int (*remove_url) (cache_handle_t *h, request_rec *r);
+ apr_status_t (*commit_entity)(cache_handle_t *h, request_rec *r);
+ apr_status_t (*invalidate_entity)(cache_handle_t *h, request_rec *r);
+} cache_provider;
+
+typedef enum {
+ AP_CACHE_HIT,
+ AP_CACHE_REVALIDATE,
+ AP_CACHE_MISS,
+ AP_CACHE_INVALIDATE
+} ap_cache_status_e;
+
+#define AP_CACHE_HIT_ENV "cache-hit"
+#define AP_CACHE_REVALIDATE_ENV "cache-revalidate"
+#define AP_CACHE_MISS_ENV "cache-miss"
+#define AP_CACHE_INVALIDATE_ENV "cache-invalidate"
+#define AP_CACHE_STATUS_ENV "cache-status"
+
+
+/* cache_util.c */
+/* do a HTTP/1.1 age calculation */
+CACHE_DECLARE(apr_time_t) ap_cache_current_age(cache_info *info, const apr_time_t age_value,
+ apr_time_t now);
+
+CACHE_DECLARE(apr_time_t) ap_cache_hex2usec(const char *x);
+CACHE_DECLARE(void) ap_cache_usec2hex(apr_time_t j, char *y);
+CACHE_DECLARE(char *) ap_cache_generate_name(apr_pool_t *p, int dirlevels,
+ int dirlength,
+ const char *name);
+CACHE_DECLARE(const char *)ap_cache_tokstr(apr_pool_t *p, const char *list, const char **str);
+
+/* Create a new table consisting of those elements from an
+ * headers table that are allowed to be stored in a cache.
+ */
+CACHE_DECLARE(apr_table_t *)ap_cache_cacheable_headers(apr_pool_t *pool,
+ apr_table_t *t,
+ server_rec *s);
+
+/* Create a new table consisting of those elements from an input
+ * headers table that are allowed to be stored in a cache.
+ */
+CACHE_DECLARE(apr_table_t *)ap_cache_cacheable_headers_in(request_rec *r);
+
+/* Create a new table consisting of those elements from an output
+ * headers table that are allowed to be stored in a cache;
+ * ensure there is a content type and capture any errors.
+ */
+CACHE_DECLARE(apr_table_t *)ap_cache_cacheable_headers_out(request_rec *r);
+
+/**
+ * Parse the Cache-Control and Pragma headers in one go, marking
+ * which tokens appear within the header. Populate the structure
+ * passed in.
+ */
+int ap_cache_control(request_rec *r, cache_control_t *cc, const char *cc_header,
+ const char *pragma_header, apr_table_t *headers);
+
+
+/* hooks */
+
+/**
+ * Cache status hook.
+ * This hook is called as soon as the cache has made a decision as to whether
+ * an entity should be served from cache (hit), should be served from cache
+ * after a successful validation (revalidate), or served from the backend
+ * and potentially cached (miss).
+ *
+ * A basic implementation of this hook exists in mod_cache which writes this
+ * information to the subprocess environment, and optionally to request
+ * headers. Further implementations may add hooks as appropriate to perform
+ * more advanced processing, or to store statistics about the cache behaviour.
+ */
+APR_DECLARE_EXTERNAL_HOOK(cache, CACHE, int, cache_status, (cache_handle_t *h,
+ request_rec *r, apr_table_t *headers, ap_cache_status_e status,
+ const char *reason))
+
+APR_DECLARE_OPTIONAL_FN(apr_status_t,
+ ap_cache_generate_key,
+ (request_rec *r, apr_pool_t*p, const char **key));
+
+
+#endif /*MOD_CACHE_H*/
+/** @} */
diff --git a/modules/cache/mod_cache.mak b/modules/cache/mod_cache.mak
new file mode 100644
index 0000000..a89b1bc
--- /dev/null
+++ b/modules/cache/mod_cache.mak
@@ -0,0 +1,370 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_cache.dsp
+!IF "$(CFG)" == ""
+CFG=mod_cache - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_cache - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_cache - Win32 Release" && "$(CFG)" != "mod_cache - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_cache.mak" CFG="mod_cache - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_cache - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_cache - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_cache - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_cache.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_cache.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\cache_storage.obj"
+ -@erase "$(INTDIR)\cache_util.obj"
+ -@erase "$(INTDIR)\mod_cache.obj"
+ -@erase "$(INTDIR)\mod_cache.res"
+ -@erase "$(INTDIR)\mod_cache_src.idb"
+ -@erase "$(INTDIR)\mod_cache_src.pdb"
+ -@erase "$(OUTDIR)\mod_cache.exp"
+ -@erase "$(OUTDIR)\mod_cache.lib"
+ -@erase "$(OUTDIR)\mod_cache.pdb"
+ -@erase "$(OUTDIR)\mod_cache.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "CACHE_DECLARE_EXPORT" /D "MOD_CACHE_EXPORTS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_cache_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_cache.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_cache.so" /d LONG_NAME="cache_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_cache.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_cache.pdb" /debug /out:"$(OUTDIR)\mod_cache.so" /implib:"$(OUTDIR)\mod_cache.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_cache.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\cache_storage.obj" \
+ "$(INTDIR)\cache_util.obj" \
+ "$(INTDIR)\mod_cache.obj" \
+ "$(INTDIR)\mod_cache.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_cache.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_cache.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_cache.so"
+ if exist .\Release\mod_cache.so.manifest mt.exe -manifest .\Release\mod_cache.so.manifest -outputresource:.\Release\mod_cache.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_cache - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_cache.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_cache.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\cache_storage.obj"
+ -@erase "$(INTDIR)\cache_util.obj"
+ -@erase "$(INTDIR)\mod_cache.obj"
+ -@erase "$(INTDIR)\mod_cache.res"
+ -@erase "$(INTDIR)\mod_cache_src.idb"
+ -@erase "$(INTDIR)\mod_cache_src.pdb"
+ -@erase "$(OUTDIR)\mod_cache.exp"
+ -@erase "$(OUTDIR)\mod_cache.lib"
+ -@erase "$(OUTDIR)\mod_cache.pdb"
+ -@erase "$(OUTDIR)\mod_cache.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "CACHE_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_cache_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_cache.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_cache.so" /d LONG_NAME="cache_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_cache.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_cache.pdb" /debug /out:"$(OUTDIR)\mod_cache.so" /implib:"$(OUTDIR)\mod_cache.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_cache.so
+LINK32_OBJS= \
+ "$(INTDIR)\cache_storage.obj" \
+ "$(INTDIR)\cache_util.obj" \
+ "$(INTDIR)\mod_cache.obj" \
+ "$(INTDIR)\mod_cache.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_cache.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_cache.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_cache.so"
+ if exist .\Debug\mod_cache.so.manifest mt.exe -manifest .\Debug\mod_cache.so.manifest -outputresource:.\Debug\mod_cache.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_cache.dep")
+!INCLUDE "mod_cache.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_cache.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_cache - Win32 Release" || "$(CFG)" == "mod_cache - Win32 Debug"
+SOURCE=.\cache_storage.c
+
+"$(INTDIR)\cache_storage.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\cache_util.c
+
+"$(INTDIR)\cache_util.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\mod_cache.c
+
+"$(INTDIR)\mod_cache.obj" : $(SOURCE) "$(INTDIR)"
+
+
+!IF "$(CFG)" == "mod_cache - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\cache"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ELSEIF "$(CFG)" == "mod_cache - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\cache"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_cache - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\cache"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ELSEIF "$(CFG)" == "mod_cache - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\cache"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_cache - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\cache"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\cache"
+
+!ELSEIF "$(CFG)" == "mod_cache - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\cache"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\cache"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_cache - Win32 Release"
+
+
+"$(INTDIR)\mod_cache.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_cache.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_cache.so" /d LONG_NAME="cache_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_cache - Win32 Debug"
+
+
+"$(INTDIR)\mod_cache.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_cache.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_cache.so" /d LONG_NAME="cache_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+
+!ENDIF
+
diff --git a/modules/cache/mod_cache_disk.c b/modules/cache/mod_cache_disk.c
new file mode 100644
index 0000000..8d17a19
--- /dev/null
+++ b/modules/cache/mod_cache_disk.c
@@ -0,0 +1,1585 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr_lib.h"
+#include "apr_file_io.h"
+#include "apr_strings.h"
+#include "mod_cache.h"
+#include "mod_cache_disk.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "http_core.h"
+#include "ap_provider.h"
+#include "util_filter.h"
+#include "util_script.h"
+#include "util_charset.h"
+
+/*
+ * mod_cache_disk: Disk Based HTTP 1.1 Cache.
+ *
+ * Flow to Find the .data file:
+ * Incoming client requests URI /foo/bar/baz
+ * Generate <hash> off of /foo/bar/baz
+ * Open <hash>.header
+ * Read in <hash>.header file (may contain Format #1 or Format #2)
+ * If format #1 (Contains a list of Vary Headers):
+ * Use each header name (from .header) with our request values (headers_in) to
+ * regenerate <hash> using HeaderName+HeaderValue+.../foo/bar/baz
+ * re-read in <hash>.header (must be format #2)
+ * read in <hash>.data
+ *
+ * Format #1:
+ * apr_uint32_t format;
+ * apr_time_t expire;
+ * apr_array_t vary_headers (delimited by CRLF)
+ *
+ * Format #2:
+ * disk_cache_info_t (first sizeof(apr_uint32_t) bytes is the format)
+ * entity name (dobj->name) [length is in disk_cache_info_t->name_len]
+ * r->headers_out (delimited by CRLF)
+ * CRLF
+ * r->headers_in (delimited by CRLF)
+ * CRLF
+ */
+
+module AP_MODULE_DECLARE_DATA cache_disk_module;
+
+/* Forward declarations */
+static int remove_entity(cache_handle_t *h);
+static apr_status_t store_headers(cache_handle_t *h, request_rec *r, cache_info *i);
+static apr_status_t store_body(cache_handle_t *h, request_rec *r, apr_bucket_brigade *in,
+ apr_bucket_brigade *out);
+static apr_status_t recall_headers(cache_handle_t *h, request_rec *r);
+static apr_status_t recall_body(cache_handle_t *h, apr_pool_t *p, apr_bucket_brigade *bb);
+static apr_status_t read_array(request_rec *r, apr_array_header_t* arr,
+ apr_file_t *file);
+
+/*
+ * Local static functions
+ */
+
+static char *header_file(apr_pool_t *p, disk_cache_conf *conf,
+ disk_cache_object_t *dobj, const char *name)
+{
+ if (!dobj->hashfile) {
+ dobj->hashfile = ap_cache_generate_name(p, conf->dirlevels,
+ conf->dirlength, name);
+ }
+
+ if (dobj->prefix) {
+ return apr_pstrcat(p, dobj->prefix, CACHE_VDIR_SUFFIX "/",
+ dobj->hashfile, CACHE_HEADER_SUFFIX, NULL);
+ }
+ else {
+ return apr_pstrcat(p, conf->cache_root, "/", dobj->hashfile,
+ CACHE_HEADER_SUFFIX, NULL);
+ }
+}
+
+static char *data_file(apr_pool_t *p, disk_cache_conf *conf,
+ disk_cache_object_t *dobj, const char *name)
+{
+ if (!dobj->hashfile) {
+ dobj->hashfile = ap_cache_generate_name(p, conf->dirlevels,
+ conf->dirlength, name);
+ }
+
+ if (dobj->prefix) {
+ return apr_pstrcat(p, dobj->prefix, CACHE_VDIR_SUFFIX "/",
+ dobj->hashfile, CACHE_DATA_SUFFIX, NULL);
+ }
+ else {
+ return apr_pstrcat(p, conf->cache_root, "/", dobj->hashfile,
+ CACHE_DATA_SUFFIX, NULL);
+ }
+}
+
+static apr_status_t mkdir_structure(disk_cache_conf *conf, const char *file, apr_pool_t *pool)
+{
+ apr_status_t rv;
+ char *p;
+
+ for (p = (char*)file + conf->cache_root_len + 1;;) {
+ p = strchr(p, '/');
+ if (!p)
+ break;
+ *p = '\0';
+
+ rv = apr_dir_make(file,
+ APR_UREAD|APR_UWRITE|APR_UEXECUTE, pool);
+ if (rv != APR_SUCCESS && !APR_STATUS_IS_EEXIST(rv)) {
+ return rv;
+ }
+ *p = '/';
+ ++p;
+ }
+ return APR_SUCCESS;
+}
+
+/* htcacheclean may remove directories underneath us.
+ * So, we'll try renaming three times at a cost of 0.002 seconds.
+ */
+static apr_status_t safe_file_rename(disk_cache_conf *conf,
+ const char *src, const char *dest,
+ apr_pool_t *pool)
+{
+ apr_status_t rv;
+
+ rv = apr_file_rename(src, dest, pool);
+
+ if (rv != APR_SUCCESS) {
+ int i;
+
+ for (i = 0; i < 2 && rv != APR_SUCCESS; i++) {
+ /* 1000 micro-seconds aka 0.001 seconds. */
+ apr_sleep(1000);
+
+ rv = mkdir_structure(conf, dest, pool);
+ if (rv != APR_SUCCESS)
+ continue;
+
+ rv = apr_file_rename(src, dest, pool);
+ }
+ }
+
+ return rv;
+}
+
+static apr_status_t file_cache_el_final(disk_cache_conf *conf, disk_cache_file_t *file,
+ request_rec *r)
+{
+ apr_status_t rv = APR_SUCCESS;
+
+ /* This assumes that the tempfiles are on the same file system
+ * as the cache_root. If not, then we need a file copy/move
+ * rather than a rename.
+ */
+
+ /* move the file over */
+ if (file->tempfd) {
+
+ rv = safe_file_rename(conf, file->tempfile, file->file, file->pool);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(00699)
+ "rename tempfile to file failed:"
+ " %s -> %s", file->tempfile, file->file);
+ apr_file_remove(file->tempfile, file->pool);
+ }
+
+ file->tempfd = NULL;
+ }
+
+ return rv;
+}
+
+static apr_status_t file_cache_temp_cleanup(void *dummy)
+{
+ disk_cache_file_t *file = (disk_cache_file_t *)dummy;
+
+ /* clean up the temporary file */
+ if (file->tempfd) {
+ apr_file_remove(file->tempfile, file->pool);
+ file->tempfd = NULL;
+ }
+ file->tempfile = NULL;
+ file->pool = NULL;
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t file_cache_create(disk_cache_conf *conf, disk_cache_file_t *file,
+ apr_pool_t *pool)
+{
+ file->pool = pool;
+ file->tempfile = apr_pstrcat(pool, conf->cache_root, AP_TEMPFILE, NULL);
+
+ apr_pool_cleanup_register(pool, file, file_cache_temp_cleanup, apr_pool_cleanup_null);
+
+ return APR_SUCCESS;
+}
+
+/* These two functions get and put state information into the data
+ * file for an ap_cache_el, this state information will be read
+ * and written transparent to clients of this module
+ */
+static int file_cache_recall_mydata(apr_file_t *fd, cache_info *info,
+ disk_cache_object_t *dobj, request_rec *r)
+{
+ apr_status_t rv;
+ char *urlbuff;
+ apr_size_t len;
+
+ /* read the data from the cache file */
+ len = sizeof(disk_cache_info_t);
+ rv = apr_file_read_full(fd, &dobj->disk_info, len, &len);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ /* Store it away so we can get it later. */
+ info->status = dobj->disk_info.status;
+ info->date = dobj->disk_info.date;
+ info->expire = dobj->disk_info.expire;
+ info->request_time = dobj->disk_info.request_time;
+ info->response_time = dobj->disk_info.response_time;
+
+ memcpy(&info->control, &dobj->disk_info.control, sizeof(cache_control_t));
+
+ /* Note that we could optimize this by conditionally doing the palloc
+ * depending upon the size. */
+ urlbuff = apr_palloc(r->pool, dobj->disk_info.name_len + 1);
+ len = dobj->disk_info.name_len;
+ rv = apr_file_read_full(fd, urlbuff, len, &len);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ urlbuff[dobj->disk_info.name_len] = '\0';
+
+ /* check that we have the same URL */
+ /* Would strncmp be correct? */
+ if (strcmp(urlbuff, dobj->name) != 0) {
+ return APR_EGENERAL;
+ }
+
+ return APR_SUCCESS;
+}
+
+static const char* regen_key(apr_pool_t *p, apr_table_t *headers,
+ apr_array_header_t *varray, const char *oldkey)
+{
+ struct iovec *iov;
+ int i, k;
+ int nvec;
+ const char *header;
+ const char **elts;
+
+ nvec = (varray->nelts * 2) + 1;
+ iov = apr_palloc(p, sizeof(struct iovec) * nvec);
+ elts = (const char **) varray->elts;
+
+ /* TODO:
+ * - Handle multiple-value headers better. (sort them?)
+ * - Handle Case in-sensitive Values better.
+ * This isn't the end of the world, since it just lowers the cache
+ * hit rate, but it would be nice to fix.
+ *
+ * The majority are case insenstive if they are values (encoding etc).
+ * Most of rfc2616 is case insensitive on header contents.
+ *
+ * So the better solution may be to identify headers which should be
+ * treated case-sensitive?
+ * HTTP URI's (3.2.3) [host and scheme are insensitive]
+ * HTTP method (5.1.1)
+ * HTTP-date values (3.3.1)
+ * 3.7 Media Types [excerpt]
+ * The type, subtype, and parameter attribute names are case-
+ * insensitive. Parameter values might or might not be case-sensitive,
+ * depending on the semantics of the parameter name.
+ * 4.20 Except [excerpt]
+ * Comparison of expectation values is case-insensitive for unquoted
+ * tokens (including the 100-continue token), and is case-sensitive for
+ * quoted-string expectation-extensions.
+ */
+
+ for (i=0, k=0; i < varray->nelts; i++) {
+ header = apr_table_get(headers, elts[i]);
+ if (!header) {
+ header = "";
+ }
+ iov[k].iov_base = (char*) elts[i];
+ iov[k].iov_len = strlen(elts[i]);
+ k++;
+ iov[k].iov_base = (char*) header;
+ iov[k].iov_len = strlen(header);
+ k++;
+ }
+ iov[k].iov_base = (char*) oldkey;
+ iov[k].iov_len = strlen(oldkey);
+ k++;
+
+ return apr_pstrcatv(p, iov, k, NULL);
+}
+
+static int array_alphasort(const void *fn1, const void *fn2)
+{
+ return strcmp(*(char**)fn1, *(char**)fn2);
+}
+
+static void tokens_to_array(apr_pool_t *p, const char *data,
+ apr_array_header_t *arr)
+{
+ char *token;
+
+ while ((token = ap_get_list_item(p, &data)) != NULL) {
+ *((const char **) apr_array_push(arr)) = token;
+ }
+
+ /* Sort it so that "Vary: A, B" and "Vary: B, A" are stored the same. */
+ qsort((void *) arr->elts, arr->nelts,
+ sizeof(char *), array_alphasort);
+}
+
+/*
+ * Hook and mod_cache callback functions
+ */
+static int create_entity(cache_handle_t *h, request_rec *r, const char *key, apr_off_t len,
+ apr_bucket_brigade *bb)
+{
+ disk_cache_dir_conf *dconf = ap_get_module_config(r->per_dir_config, &cache_disk_module);
+ disk_cache_conf *conf = ap_get_module_config(r->server->module_config,
+ &cache_disk_module);
+ cache_object_t *obj;
+ disk_cache_object_t *dobj;
+ apr_pool_t *pool;
+
+ if (conf->cache_root == NULL) {
+ return DECLINED;
+ }
+
+ /* we don't support caching of range requests (yet) */
+ if (r->status == HTTP_PARTIAL_CONTENT) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00700)
+ "URL %s partial content response not cached",
+ key);
+ return DECLINED;
+ }
+
+ /* Note, len is -1 if unknown so don't trust it too hard */
+ if (len > dconf->maxfs) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00701)
+ "URL %s failed the size check "
+ "(%" APR_OFF_T_FMT " > %" APR_OFF_T_FMT ")",
+ key, len, dconf->maxfs);
+ return DECLINED;
+ }
+ if (len >= 0 && len < dconf->minfs) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00702)
+ "URL %s failed the size check "
+ "(%" APR_OFF_T_FMT " < %" APR_OFF_T_FMT ")",
+ key, len, dconf->minfs);
+ return DECLINED;
+ }
+
+ /* Allocate and initialize cache_object_t and disk_cache_object_t */
+ h->cache_obj = obj = apr_pcalloc(r->pool, sizeof(*obj));
+ obj->vobj = dobj = apr_pcalloc(r->pool, sizeof(*dobj));
+
+ obj->key = apr_pstrdup(r->pool, key);
+
+ dobj->name = obj->key;
+ dobj->prefix = NULL;
+ /* Save the cache root */
+ dobj->root = apr_pstrmemdup(r->pool, conf->cache_root, conf->cache_root_len);
+ dobj->root_len = conf->cache_root_len;
+
+ apr_pool_create(&pool, r->pool);
+ apr_pool_tag(pool, "mod_cache (create_entity)");
+
+ file_cache_create(conf, &dobj->hdrs, pool);
+ file_cache_create(conf, &dobj->vary, pool);
+ file_cache_create(conf, &dobj->data, pool);
+
+ dobj->data.file = data_file(r->pool, conf, dobj, key);
+ dobj->hdrs.file = header_file(r->pool, conf, dobj, key);
+ dobj->vary.file = header_file(r->pool, conf, dobj, key);
+
+ dobj->disk_info.header_only = r->header_only;
+
+ return OK;
+}
+
+static int open_entity(cache_handle_t *h, request_rec *r, const char *key)
+{
+ apr_uint32_t format;
+ apr_size_t len;
+ const char *nkey;
+ apr_status_t rc;
+ static int error_logged = 0;
+ disk_cache_conf *conf = ap_get_module_config(r->server->module_config,
+ &cache_disk_module);
+#ifdef APR_SENDFILE_ENABLED
+ core_dir_config *coreconf = ap_get_core_module_config(r->per_dir_config);
+#endif
+ apr_finfo_t finfo;
+ cache_object_t *obj;
+ cache_info *info;
+ disk_cache_object_t *dobj;
+ int flags;
+ apr_pool_t *pool;
+
+ h->cache_obj = NULL;
+
+ /* Look up entity keyed to 'url' */
+ if (conf->cache_root == NULL) {
+ if (!error_logged) {
+ error_logged = 1;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00703)
+ "Cannot cache files to disk without a CacheRoot specified.");
+ }
+ return DECLINED;
+ }
+
+ /* Create and init the cache object */
+ obj = apr_pcalloc(r->pool, sizeof(cache_object_t));
+ dobj = apr_pcalloc(r->pool, sizeof(disk_cache_object_t));
+
+ info = &(obj->info);
+
+ /* Open the headers file */
+ dobj->prefix = NULL;
+
+ /* Save the cache root */
+ dobj->root = apr_pstrmemdup(r->pool, conf->cache_root, conf->cache_root_len);
+ dobj->root_len = conf->cache_root_len;
+
+ dobj->vary.file = header_file(r->pool, conf, dobj, key);
+ flags = APR_READ|APR_BINARY|APR_BUFFERED;
+ rc = apr_file_open(&dobj->vary.fd, dobj->vary.file, flags, 0, r->pool);
+ if (rc != APR_SUCCESS) {
+ return DECLINED;
+ }
+
+ /* read the format from the cache file */
+ len = sizeof(format);
+ apr_file_read_full(dobj->vary.fd, &format, len, &len);
+
+ if (format == VARY_FORMAT_VERSION) {
+ apr_array_header_t* varray;
+ apr_time_t expire;
+
+ len = sizeof(expire);
+ apr_file_read_full(dobj->vary.fd, &expire, len, &len);
+
+ varray = apr_array_make(r->pool, 5, sizeof(char*));
+ rc = read_array(r, varray, dobj->vary.fd);
+ if (rc != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(00704)
+ "Cannot parse vary header file: %s",
+ dobj->vary.file);
+ apr_file_close(dobj->vary.fd);
+ return DECLINED;
+ }
+ apr_file_close(dobj->vary.fd);
+
+ nkey = regen_key(r->pool, r->headers_in, varray, key);
+
+ dobj->hashfile = NULL;
+ dobj->prefix = dobj->vary.file;
+ dobj->hdrs.file = header_file(r->pool, conf, dobj, nkey);
+
+ flags = APR_READ|APR_BINARY|APR_BUFFERED;
+ rc = apr_file_open(&dobj->hdrs.fd, dobj->hdrs.file, flags, 0, r->pool);
+ if (rc != APR_SUCCESS) {
+ return DECLINED;
+ }
+ }
+ else if (format != DISK_FORMAT_VERSION) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00705)
+ "File '%s' has a version mismatch. File had version: %d.",
+ dobj->vary.file, format);
+ apr_file_close(dobj->vary.fd);
+ return DECLINED;
+ }
+ else {
+ apr_off_t offset = 0;
+
+ /* oops, not vary as it turns out */
+ dobj->hdrs.fd = dobj->vary.fd;
+ dobj->vary.fd = NULL;
+ dobj->hdrs.file = dobj->vary.file;
+
+ /* This wasn't a Vary Format file, so we must seek to the
+ * start of the file again, so that later reads work.
+ */
+ apr_file_seek(dobj->hdrs.fd, APR_SET, &offset);
+ nkey = key;
+ }
+
+ obj->key = nkey;
+ dobj->key = nkey;
+ dobj->name = key;
+
+ apr_pool_create(&pool, r->pool);
+ apr_pool_tag(pool, "mod_cache (open_entity)");
+
+ file_cache_create(conf, &dobj->hdrs, pool);
+ file_cache_create(conf, &dobj->vary, pool);
+ file_cache_create(conf, &dobj->data, pool);
+
+ dobj->data.file = data_file(r->pool, conf, dobj, nkey);
+
+ /* Read the bytes to setup the cache_info fields */
+ rc = file_cache_recall_mydata(dobj->hdrs.fd, info, dobj, r);
+ if (rc != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(00706)
+ "Cannot read header file %s", dobj->hdrs.file);
+ apr_file_close(dobj->hdrs.fd);
+ return DECLINED;
+ }
+
+
+ /* Is this a cached HEAD request? */
+ if (dobj->disk_info.header_only && !r->header_only) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00707)
+ "HEAD request cached, non-HEAD requested, ignoring: %s",
+ dobj->hdrs.file);
+ apr_file_close(dobj->hdrs.fd);
+ return DECLINED;
+ }
+
+ /* Open the data file */
+ if (dobj->disk_info.has_body) {
+ flags = APR_READ | APR_BINARY;
+#ifdef APR_SENDFILE_ENABLED
+ /* When we are in the quick handler we don't have the per-directory
+ * configuration, so this check only takes the global setting of
+ * the EnableSendFile directive into account.
+ */
+ flags |= AP_SENDFILE_ENABLED(coreconf->enable_sendfile);
+#endif
+ rc = apr_file_open(&dobj->data.fd, dobj->data.file, flags, 0, r->pool);
+ if (rc != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(00708)
+ "Cannot open data file %s", dobj->data.file);
+ apr_file_close(dobj->hdrs.fd);
+ return DECLINED;
+ }
+
+ rc = apr_file_info_get(&finfo, APR_FINFO_SIZE | APR_FINFO_IDENT,
+ dobj->data.fd);
+ if (rc == APR_SUCCESS) {
+ dobj->file_size = finfo.size;
+ }
+
+ /* Atomic check - does the body file belong to the header file? */
+ if (dobj->disk_info.inode == finfo.inode &&
+ dobj->disk_info.device == finfo.device) {
+
+ /* Initialize the cache_handle callback functions */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00709)
+ "Recalled cached URL info header %s", dobj->name);
+
+ /* make the configuration stick */
+ h->cache_obj = obj;
+ obj->vobj = dobj;
+
+ return OK;
+ }
+
+ }
+ else {
+
+ /* make the configuration stick */
+ h->cache_obj = obj;
+ obj->vobj = dobj;
+
+ return OK;
+ }
+
+ /* Oh dear, no luck matching header to the body */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00710)
+ "Cached URL info header '%s' didn't match body, ignoring this entry",
+ dobj->name);
+
+ apr_file_close(dobj->hdrs.fd);
+ return DECLINED;
+}
+
+static void close_disk_cache_fd(disk_cache_file_t *file)
+{
+ if (file->fd != NULL) {
+ apr_file_close(file->fd);
+ file->fd = NULL;
+ }
+ if (file->tempfd != NULL) {
+ apr_file_close(file->tempfd);
+ file->tempfd = NULL;
+ }
+}
+
+static int remove_entity(cache_handle_t *h)
+{
+ disk_cache_object_t *dobj = (disk_cache_object_t *) h->cache_obj->vobj;
+
+ close_disk_cache_fd(&(dobj->hdrs));
+ close_disk_cache_fd(&(dobj->vary));
+ close_disk_cache_fd(&(dobj->data));
+
+ /* Null out the cache object pointer so next time we start from scratch */
+ h->cache_obj = NULL;
+ return OK;
+}
+
+static int remove_url(cache_handle_t *h, request_rec *r)
+{
+ apr_status_t rc;
+ disk_cache_object_t *dobj;
+
+ /* Get disk cache object from cache handle */
+ dobj = (disk_cache_object_t *) h->cache_obj->vobj;
+ if (!dobj) {
+ return DECLINED;
+ }
+
+ /* Delete headers file */
+ if (dobj->hdrs.file) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00711)
+ "Deleting %s from cache.", dobj->hdrs.file);
+
+ rc = apr_file_remove(dobj->hdrs.file, r->pool);
+ if ((rc != APR_SUCCESS) && !APR_STATUS_IS_ENOENT(rc)) {
+ /* Will only result in an output if httpd is started with -e debug.
+ * For reason see log_error_core for the case s == NULL.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rc, r, APLOGNO(00712)
+ "Failed to delete headers file %s from cache.",
+ dobj->hdrs.file);
+ return DECLINED;
+ }
+ }
+
+ /* Delete data file */
+ if (dobj->data.file) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00713)
+ "Deleting %s from cache.", dobj->data.file);
+
+ rc = apr_file_remove(dobj->data.file, r->pool);
+ if ((rc != APR_SUCCESS) && !APR_STATUS_IS_ENOENT(rc)) {
+ /* Will only result in an output if httpd is started with -e debug.
+ * For reason see log_error_core for the case s == NULL.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rc, r, APLOGNO(00714)
+ "Failed to delete data file %s from cache.",
+ dobj->data.file);
+ return DECLINED;
+ }
+ }
+
+ /* now delete directories as far as possible up to our cache root */
+ if (dobj->root) {
+ const char *str_to_copy;
+
+ str_to_copy = dobj->hdrs.file ? dobj->hdrs.file : dobj->data.file;
+ if (str_to_copy) {
+ char *dir, *slash, *q;
+
+ dir = apr_pstrdup(r->pool, str_to_copy);
+
+ /* remove filename */
+ slash = strrchr(dir, '/');
+ *slash = '\0';
+
+ /*
+ * now walk our way back to the cache root, delete everything
+ * in the way as far as possible
+ *
+ * Note: due to the way we constructed the file names in
+ * header_file and data_file, we are guaranteed that the
+ * cache_root is suffixed by at least one '/' which will be
+ * turned into a terminating null by this loop. Therefore,
+ * we won't either delete or go above our cache root.
+ */
+ for (q = dir + dobj->root_len; *q ; ) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00715)
+ "Deleting directory %s from cache", dir);
+
+ rc = apr_dir_remove(dir, r->pool);
+ if (rc != APR_SUCCESS && !APR_STATUS_IS_ENOENT(rc)) {
+ break;
+ }
+ slash = strrchr(q, '/');
+ *slash = '\0';
+ }
+ }
+ }
+
+ return OK;
+}
+
+static apr_status_t read_array(request_rec *r, apr_array_header_t* arr,
+ apr_file_t *file)
+{
+ char w[MAX_STRING_LEN];
+ apr_size_t p;
+ apr_status_t rv;
+
+ while (1) {
+ rv = apr_file_gets(w, MAX_STRING_LEN - 1, file);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00716)
+ "Premature end of vary array.");
+ return rv;
+ }
+
+ p = strlen(w);
+ if (p > 0 && w[p - 1] == '\n') {
+ if (p > 1 && w[p - 2] == CR) {
+ w[p - 2] = '\0';
+ }
+ else {
+ w[p - 1] = '\0';
+ }
+ }
+
+ /* If we've finished reading the array, break out of the loop. */
+ if (w[0] == '\0') {
+ break;
+ }
+
+ *((const char **) apr_array_push(arr)) = apr_pstrdup(r->pool, w);
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t store_array(apr_file_t *fd, apr_array_header_t* arr)
+{
+ int i;
+ apr_status_t rv;
+ struct iovec iov[2];
+ apr_size_t amt;
+ const char **elts;
+
+ elts = (const char **) arr->elts;
+
+ for (i = 0; i < arr->nelts; i++) {
+ iov[0].iov_base = (char*) elts[i];
+ iov[0].iov_len = strlen(elts[i]);
+ iov[1].iov_base = CRLF;
+ iov[1].iov_len = sizeof(CRLF) - 1;
+
+ rv = apr_file_writev_full(fd, (const struct iovec *) &iov, 2, &amt);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+
+ iov[0].iov_base = CRLF;
+ iov[0].iov_len = sizeof(CRLF) - 1;
+
+ return apr_file_writev_full(fd, (const struct iovec *) &iov, 1, &amt);
+}
+
+static apr_status_t read_table(cache_handle_t *handle, request_rec *r,
+ apr_table_t *table, apr_file_t *file)
+{
+ char w[MAX_STRING_LEN];
+ char *l;
+ apr_size_t p;
+ apr_status_t rv;
+
+ while (1) {
+
+ /* ### What about APR_EOF? */
+ rv = apr_file_gets(w, MAX_STRING_LEN - 1, file);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00717)
+ "Premature end of cache headers.");
+ return rv;
+ }
+
+ /* Delete terminal (CR?)LF */
+
+ p = strlen(w);
+ /* Indeed, the host's '\n':
+ '\012' for UNIX; '\015' for MacOS; '\025' for OS/390
+ -- whatever the script generates.
+ */
+ if (p > 0 && w[p - 1] == '\n') {
+ if (p > 1 && w[p - 2] == CR) {
+ w[p - 2] = '\0';
+ }
+ else {
+ w[p - 1] = '\0';
+ }
+ }
+
+ /* If we've finished reading the headers, break out of the loop. */
+ if (w[0] == '\0') {
+ break;
+ }
+
+#if APR_CHARSET_EBCDIC
+ /* Chances are that we received an ASCII header text instead of
+ * the expected EBCDIC header lines. Try to auto-detect:
+ */
+ if (!(l = strchr(w, ':'))) {
+ int maybeASCII = 0, maybeEBCDIC = 0;
+ unsigned char *cp, native;
+ apr_size_t inbytes_left, outbytes_left;
+
+ for (cp = w; *cp != '\0'; ++cp) {
+ native = apr_xlate_conv_byte(ap_hdrs_from_ascii, *cp);
+ if (apr_isprint(*cp) && !apr_isprint(native))
+ ++maybeEBCDIC;
+ if (!apr_isprint(*cp) && apr_isprint(native))
+ ++maybeASCII;
+ }
+ if (maybeASCII > maybeEBCDIC) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00718)
+ "CGI Interface Error: Script headers apparently ASCII: (CGI = %s)",
+ r->filename);
+ inbytes_left = outbytes_left = cp - w;
+ apr_xlate_conv_buffer(ap_hdrs_from_ascii,
+ w, &inbytes_left, w, &outbytes_left);
+ }
+ }
+#endif /*APR_CHARSET_EBCDIC*/
+
+ /* if we see a bogus header don't ignore it. Shout and scream */
+ if (!(l = strchr(w, ':'))) {
+ return APR_EGENERAL;
+ }
+
+ *l++ = '\0';
+ while (apr_isspace(*l)) {
+ ++l;
+ }
+
+ apr_table_add(table, w, l);
+ }
+
+ return APR_SUCCESS;
+}
+
+/*
+ * Reads headers from a buffer and returns an array of headers.
+ * Returns NULL on file error
+ * This routine tries to deal with too long lines and continuation lines.
+ * @@@: XXX: FIXME: currently the headers are passed thru un-merged.
+ * Is that okay, or should they be collapsed where possible?
+ */
+static apr_status_t recall_headers(cache_handle_t *h, request_rec *r)
+{
+ disk_cache_object_t *dobj = (disk_cache_object_t *) h->cache_obj->vobj;
+ apr_status_t rv;
+
+ /* This case should not happen... */
+ if (!dobj->hdrs.fd) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00719)
+ "recalling headers; but no header fd for %s", dobj->name);
+ return APR_NOTFOUND;
+ }
+
+ h->req_hdrs = apr_table_make(r->pool, 20);
+ h->resp_hdrs = apr_table_make(r->pool, 20);
+
+ /* Call routine to read the header lines/status line */
+ rv = read_table(h, r, h->resp_hdrs, dobj->hdrs.fd);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02987)
+ "Error reading response headers from %s for %s",
+ dobj->hdrs.file, dobj->name);
+ }
+ rv = read_table(h, r, h->req_hdrs, dobj->hdrs.fd);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02988)
+ "Error reading request headers from %s for %s",
+ dobj->hdrs.file, dobj->name);
+ }
+
+ apr_file_close(dobj->hdrs.fd);
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00720)
+ "Recalled headers for URL %s", dobj->name);
+ return APR_SUCCESS;
+}
+
+static apr_status_t recall_body(cache_handle_t *h, apr_pool_t *p, apr_bucket_brigade *bb)
+{
+ disk_cache_object_t *dobj = (disk_cache_object_t*) h->cache_obj->vobj;
+
+ if (dobj->data.fd) {
+ apr_brigade_insert_file(bb, dobj->data.fd, 0, dobj->file_size, p);
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t store_table(apr_file_t *fd, apr_table_t *table)
+{
+ int i;
+ apr_status_t rv;
+ struct iovec iov[4];
+ apr_size_t amt;
+ apr_table_entry_t *elts;
+
+ elts = (apr_table_entry_t *) apr_table_elts(table)->elts;
+ for (i = 0; i < apr_table_elts(table)->nelts; ++i) {
+ if (elts[i].key != NULL) {
+ iov[0].iov_base = elts[i].key;
+ iov[0].iov_len = strlen(elts[i].key);
+ iov[1].iov_base = ": ";
+ iov[1].iov_len = sizeof(": ") - 1;
+ iov[2].iov_base = elts[i].val;
+ iov[2].iov_len = strlen(elts[i].val);
+ iov[3].iov_base = CRLF;
+ iov[3].iov_len = sizeof(CRLF) - 1;
+
+ rv = apr_file_writev_full(fd, (const struct iovec *) &iov, 4, &amt);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+ }
+ iov[0].iov_base = CRLF;
+ iov[0].iov_len = sizeof(CRLF) - 1;
+ rv = apr_file_writev_full(fd, (const struct iovec *) &iov, 1, &amt);
+ return rv;
+}
+
+static apr_status_t store_headers(cache_handle_t *h, request_rec *r, cache_info *info)
+{
+ disk_cache_object_t *dobj = (disk_cache_object_t*) h->cache_obj->vobj;
+
+ memcpy(&h->cache_obj->info, info, sizeof(cache_info));
+
+ if (r->headers_out) {
+ dobj->headers_out = ap_cache_cacheable_headers_out(r);
+ }
+
+ if (r->headers_in) {
+ dobj->headers_in = ap_cache_cacheable_headers_in(r);
+ }
+
+ if (r->header_only && r->status != HTTP_NOT_MODIFIED) {
+ dobj->disk_info.header_only = 1;
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t write_headers(cache_handle_t *h, request_rec *r)
+{
+ disk_cache_conf *conf = ap_get_module_config(r->server->module_config,
+ &cache_disk_module);
+ apr_status_t rv;
+ apr_size_t amt;
+ disk_cache_object_t *dobj = (disk_cache_object_t*) h->cache_obj->vobj;
+
+ disk_cache_info_t disk_info;
+ struct iovec iov[2];
+
+ memset(&disk_info, 0, sizeof(disk_cache_info_t));
+
+ if (dobj->headers_out) {
+ const char *tmp;
+
+ tmp = apr_table_get(dobj->headers_out, "Vary");
+
+ if (tmp) {
+ apr_array_header_t* varray;
+ apr_uint32_t format = VARY_FORMAT_VERSION;
+
+ /* If we were initially opened as a vary format, rollback
+ * that internal state for the moment so we can recreate the
+ * vary format hints in the appropriate directory.
+ */
+ if (dobj->prefix) {
+ dobj->hdrs.file = dobj->prefix;
+ dobj->prefix = NULL;
+ }
+
+ rv = mkdir_structure(conf, dobj->hdrs.file, r->pool);
+ if (rv == APR_SUCCESS) {
+ rv = apr_file_mktemp(&dobj->vary.tempfd, dobj->vary.tempfile,
+ APR_CREATE | APR_WRITE | APR_BINARY | APR_EXCL,
+ dobj->vary.pool);
+ }
+
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(00721)
+ "could not create vary file %s",
+ dobj->vary.tempfile);
+ return rv;
+ }
+
+ amt = sizeof(format);
+ rv = apr_file_write_full(dobj->vary.tempfd, &format, amt, NULL);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(00722)
+ "could not write to vary file %s",
+ dobj->vary.tempfile);
+ apr_file_close(dobj->vary.tempfd);
+ apr_pool_destroy(dobj->vary.pool);
+ return rv;
+ }
+
+ amt = sizeof(h->cache_obj->info.expire);
+ rv = apr_file_write_full(dobj->vary.tempfd,
+ &h->cache_obj->info.expire, amt, NULL);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(00723)
+ "could not write to vary file %s",
+ dobj->vary.tempfile);
+ apr_file_close(dobj->vary.tempfd);
+ apr_pool_destroy(dobj->vary.pool);
+ return rv;
+ }
+
+ varray = apr_array_make(r->pool, 6, sizeof(char*));
+ tokens_to_array(r->pool, tmp, varray);
+
+ store_array(dobj->vary.tempfd, varray);
+
+ rv = apr_file_close(dobj->vary.tempfd);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(00724)
+ "could not close vary file %s",
+ dobj->vary.tempfile);
+ apr_pool_destroy(dobj->vary.pool);
+ return rv;
+ }
+
+ tmp = regen_key(r->pool, dobj->headers_in, varray, dobj->name);
+ dobj->prefix = dobj->hdrs.file;
+ dobj->hashfile = NULL;
+ dobj->data.file = data_file(r->pool, conf, dobj, tmp);
+ dobj->hdrs.file = header_file(r->pool, conf, dobj, tmp);
+ }
+ }
+
+
+ rv = apr_file_mktemp(&dobj->hdrs.tempfd, dobj->hdrs.tempfile,
+ APR_CREATE | APR_WRITE | APR_BINARY |
+ APR_BUFFERED | APR_EXCL, dobj->hdrs.pool);
+
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(00725)
+ "could not create header file %s",
+ dobj->hdrs.tempfile);
+ return rv;
+ }
+
+ disk_info.format = DISK_FORMAT_VERSION;
+ disk_info.date = h->cache_obj->info.date;
+ disk_info.expire = h->cache_obj->info.expire;
+ disk_info.entity_version = dobj->disk_info.entity_version++;
+ disk_info.request_time = h->cache_obj->info.request_time;
+ disk_info.response_time = h->cache_obj->info.response_time;
+ disk_info.status = h->cache_obj->info.status;
+ disk_info.inode = dobj->disk_info.inode;
+ disk_info.device = dobj->disk_info.device;
+ disk_info.has_body = dobj->disk_info.has_body;
+ disk_info.header_only = dobj->disk_info.header_only;
+
+ disk_info.name_len = strlen(dobj->name);
+
+ memcpy(&disk_info.control, &h->cache_obj->info.control, sizeof(cache_control_t));
+
+ iov[0].iov_base = (void*)&disk_info;
+ iov[0].iov_len = sizeof(disk_cache_info_t);
+ iov[1].iov_base = (void*)dobj->name;
+ iov[1].iov_len = disk_info.name_len;
+
+ rv = apr_file_writev_full(dobj->hdrs.tempfd, (const struct iovec *) &iov,
+ 2, &amt);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(00726)
+ "could not write info to header file %s",
+ dobj->hdrs.tempfile);
+ apr_file_close(dobj->hdrs.tempfd);
+ apr_pool_destroy(dobj->hdrs.pool);
+ return rv;
+ }
+
+ if (dobj->headers_out) {
+ rv = store_table(dobj->hdrs.tempfd, dobj->headers_out);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(00727)
+ "could not write out-headers to header file %s",
+ dobj->hdrs.tempfile);
+ apr_file_close(dobj->hdrs.tempfd);
+ apr_pool_destroy(dobj->hdrs.pool);
+ return rv;
+ }
+ }
+
+ /* Parse the vary header and dump those fields from the headers_in. */
+ /* FIXME: Make call to the same thing cache_select calls to crack Vary. */
+ if (dobj->headers_in) {
+ rv = store_table(dobj->hdrs.tempfd, dobj->headers_in);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(00728)
+ "could not write in-headers to header file %s",
+ dobj->hdrs.tempfile);
+ apr_file_close(dobj->hdrs.tempfd);
+ apr_pool_destroy(dobj->hdrs.pool);
+ return rv;
+ }
+ }
+
+ rv = apr_file_close(dobj->hdrs.tempfd); /* flush and close */
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(00729)
+ "could not close header file %s",
+ dobj->hdrs.tempfile);
+ apr_pool_destroy(dobj->hdrs.pool);
+ return rv;
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t store_body(cache_handle_t *h, request_rec *r,
+ apr_bucket_brigade *in, apr_bucket_brigade *out)
+{
+ apr_bucket *e;
+ apr_status_t rv = APR_SUCCESS;
+ disk_cache_object_t *dobj = (disk_cache_object_t *) h->cache_obj->vobj;
+ disk_cache_dir_conf *dconf = ap_get_module_config(r->per_dir_config, &cache_disk_module);
+ int seen_eos = 0;
+
+ if (!dobj->offset) {
+ dobj->offset = dconf->readsize;
+ }
+ if (!dobj->timeout && dconf->readtime) {
+ dobj->timeout = apr_time_now() + dconf->readtime;
+ }
+
+ if (dobj->offset) {
+ apr_brigade_partition(in, dobj->offset, &e);
+ }
+
+ while (APR_SUCCESS == rv && !APR_BRIGADE_EMPTY(in)) {
+ const char *str;
+ apr_size_t length, written;
+
+ e = APR_BRIGADE_FIRST(in);
+
+ /* are we done completely? if so, pass any trailing buckets right through */
+ if (dobj->done || !dobj->data.pool) {
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(out, e);
+ continue;
+ }
+
+ /* have we seen eos yet? */
+ if (APR_BUCKET_IS_EOS(e)) {
+ seen_eos = 1;
+ dobj->done = 1;
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(out, e);
+ break;
+ }
+
+ /* honour flush buckets, we'll get called again */
+ if (APR_BUCKET_IS_FLUSH(e)) {
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(out, e);
+ break;
+ }
+
+ /* metadata buckets are preserved as is */
+ if (APR_BUCKET_IS_METADATA(e)) {
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(out, e);
+ continue;
+ }
+
+ /* read the bucket, write to the cache */
+ rv = apr_bucket_read(e, &str, &length, APR_BLOCK_READ);
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(out, e);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00730)
+ "Error when reading bucket for URL %s",
+ h->cache_obj->key);
+ /* Remove the intermediate cache file and return non-APR_SUCCESS */
+ apr_pool_destroy(dobj->data.pool);
+ return rv;
+ }
+
+ /* don't write empty buckets to the cache */
+ if (!length) {
+ continue;
+ }
+
+ if (!dobj->disk_info.header_only) {
+
+ /* Attempt to create the data file at the last possible moment, if
+ * the body is empty, we don't write a file at all, and save an inode.
+ */
+ if (!dobj->data.tempfd) {
+ apr_finfo_t finfo;
+ rv = apr_file_mktemp(&dobj->data.tempfd, dobj->data.tempfile,
+ APR_CREATE | APR_WRITE | APR_BINARY | APR_BUFFERED
+ | APR_EXCL, dobj->data.pool);
+ if (rv != APR_SUCCESS) {
+ apr_pool_destroy(dobj->data.pool);
+ return rv;
+ }
+ dobj->file_size = 0;
+ rv = apr_file_info_get(&finfo, APR_FINFO_IDENT,
+ dobj->data.tempfd);
+ if (rv != APR_SUCCESS) {
+ apr_pool_destroy(dobj->data.pool);
+ return rv;
+ }
+ dobj->disk_info.device = finfo.device;
+ dobj->disk_info.inode = finfo.inode;
+ dobj->disk_info.has_body = 1;
+ }
+
+ /* write to the cache, leave if we fail */
+ rv = apr_file_write_full(dobj->data.tempfd, str, length, &written);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(
+ APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00731) "Error when writing cache file for URL %s", h->cache_obj->key);
+ /* Remove the intermediate cache file and return non-APR_SUCCESS */
+ apr_pool_destroy(dobj->data.pool);
+ return rv;
+ }
+ dobj->file_size += written;
+ if (dobj->file_size > dconf->maxfs) {
+ ap_log_rerror(
+ APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00732) "URL %s failed the size check "
+ "(%" APR_OFF_T_FMT ">%" APR_OFF_T_FMT ")", h->cache_obj->key, dobj->file_size, dconf->maxfs);
+ /* Remove the intermediate cache file and return non-APR_SUCCESS */
+ apr_pool_destroy(dobj->data.pool);
+ return APR_EGENERAL;
+ }
+
+ }
+
+ /* have we reached the limit of how much we're prepared to write in one
+ * go? If so, leave, we'll get called again. This prevents us from trying
+ * to swallow too much data at once, or taking so long to write the data
+ * the client times out.
+ */
+ dobj->offset -= length;
+ if (dobj->offset <= 0) {
+ dobj->offset = 0;
+ break;
+ }
+ if ((dconf->readtime && apr_time_now() > dobj->timeout)) {
+ dobj->timeout = 0;
+ break;
+ }
+
+ }
+
+ /* Was this the final bucket? If yes, close the temp file and perform
+ * sanity checks.
+ */
+ if (seen_eos) {
+ if (!dobj->disk_info.header_only) {
+ const char *cl_header;
+ apr_off_t cl;
+
+ if (dobj->data.tempfd) {
+ rv = apr_file_close(dobj->data.tempfd);
+ if (rv != APR_SUCCESS) {
+ /* Buffered write failed, abandon attempt to write */
+ apr_pool_destroy(dobj->data.pool);
+ return rv;
+ }
+ }
+
+ if (r->connection->aborted || r->no_cache) {
+ ap_log_rerror(
+ APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00733) "Discarding body for URL %s "
+ "because connection has been aborted.", h->cache_obj->key);
+ /* Remove the intermediate cache file and return non-APR_SUCCESS */
+ apr_pool_destroy(dobj->data.pool);
+ return APR_EGENERAL;
+ }
+
+ if (dobj->file_size < dconf->minfs) {
+ ap_log_rerror(
+ APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00734) "URL %s failed the size check "
+ "(%" APR_OFF_T_FMT "<%" APR_OFF_T_FMT ")", h->cache_obj->key, dobj->file_size, dconf->minfs);
+ /* Remove the intermediate cache file and return non-APR_SUCCESS */
+ apr_pool_destroy(dobj->data.pool);
+ return APR_EGENERAL;
+ }
+
+ cl_header = apr_table_get(r->headers_out, "Content-Length");
+ if (cl_header && (!ap_parse_strict_length(&cl, cl_header)
+ || cl != dobj->file_size)) {
+ ap_log_rerror(
+ APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00735) "URL %s didn't receive complete response, not caching", h->cache_obj->key);
+ /* Remove the intermediate cache file and return non-APR_SUCCESS */
+ apr_pool_destroy(dobj->data.pool);
+ return APR_EGENERAL;
+ }
+ }
+
+ /* All checks were fine, we're good to go when the commit comes */
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t commit_entity(cache_handle_t *h, request_rec *r)
+{
+ disk_cache_conf *conf = ap_get_module_config(r->server->module_config,
+ &cache_disk_module);
+ disk_cache_object_t *dobj = (disk_cache_object_t *) h->cache_obj->vobj;
+ apr_status_t rv;
+
+ /* write the headers to disk at the last possible moment */
+ rv = write_headers(h, r);
+
+ /* move header and data tempfiles to the final destination */
+ if (APR_SUCCESS == rv) {
+ rv = file_cache_el_final(conf, &dobj->hdrs, r);
+ }
+ if (APR_SUCCESS == rv) {
+ rv = file_cache_el_final(conf, &dobj->vary, r);
+ }
+ if (APR_SUCCESS == rv) {
+ if (!dobj->disk_info.header_only) {
+ rv = file_cache_el_final(conf, &dobj->data, r);
+ }
+ else if (dobj->data.file) {
+ rv = apr_file_remove(dobj->data.file, dobj->data.pool);
+ }
+ }
+
+ /* remove the cached items completely on any failure */
+ if (APR_SUCCESS != rv) {
+ remove_url(h, r);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00736)
+ "commit_entity: URL '%s' not cached due to earlier disk error.",
+ dobj->name);
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00737)
+ "commit_entity: Headers and body for URL %s cached.",
+ dobj->name);
+ }
+
+ apr_pool_destroy(dobj->data.pool);
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t invalidate_entity(cache_handle_t *h, request_rec *r)
+{
+ apr_status_t rv;
+
+ rv = recall_headers(h, r);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ /* mark the entity as invalidated */
+ h->cache_obj->info.control.invalidated = 1;
+
+ return commit_entity(h, r);
+}
+
+static void *create_dir_config(apr_pool_t *p, char *dummy)
+{
+ disk_cache_dir_conf *dconf = apr_pcalloc(p, sizeof(disk_cache_dir_conf));
+
+ dconf->maxfs = DEFAULT_MAX_FILE_SIZE;
+ dconf->minfs = DEFAULT_MIN_FILE_SIZE;
+ dconf->readsize = DEFAULT_READSIZE;
+ dconf->readtime = DEFAULT_READTIME;
+
+ return dconf;
+}
+
+static void *merge_dir_config(apr_pool_t *p, void *basev, void *addv)
+{
+ disk_cache_dir_conf *new = (disk_cache_dir_conf *) apr_pcalloc(p, sizeof(disk_cache_dir_conf));
+ disk_cache_dir_conf *add = (disk_cache_dir_conf *) addv;
+ disk_cache_dir_conf *base = (disk_cache_dir_conf *) basev;
+
+ new->maxfs = (add->maxfs_set == 0) ? base->maxfs : add->maxfs;
+ new->maxfs_set = add->maxfs_set || base->maxfs_set;
+ new->minfs = (add->minfs_set == 0) ? base->minfs : add->minfs;
+ new->minfs_set = add->minfs_set || base->minfs_set;
+ new->readsize = (add->readsize_set == 0) ? base->readsize : add->readsize;
+ new->readsize_set = add->readsize_set || base->readsize_set;
+ new->readtime = (add->readtime_set == 0) ? base->readtime : add->readtime;
+ new->readtime_set = add->readtime_set || base->readtime_set;
+
+ return new;
+}
+
+static void *create_config(apr_pool_t *p, server_rec *s)
+{
+ disk_cache_conf *conf = apr_pcalloc(p, sizeof(disk_cache_conf));
+
+ /* XXX: Set default values */
+ conf->dirlevels = DEFAULT_DIRLEVELS;
+ conf->dirlength = DEFAULT_DIRLENGTH;
+
+ conf->cache_root = NULL;
+ conf->cache_root_len = 0;
+
+ return conf;
+}
+
+/*
+ * mod_cache_disk configuration directives handlers.
+ */
+static const char
+*set_cache_root(cmd_parms *parms, void *in_struct_ptr, const char *arg)
+{
+ disk_cache_conf *conf = ap_get_module_config(parms->server->module_config,
+ &cache_disk_module);
+ conf->cache_root = arg;
+ conf->cache_root_len = strlen(arg);
+ /* TODO: canonicalize cache_root and strip off any trailing slashes */
+
+ return NULL;
+}
+
+/*
+ * Consider eliminating the next two directives in favor of
+ * Ian's prime number hash...
+ * key = hash_fn( r->uri)
+ * filename = "/key % prime1 /key %prime2/key %prime3"
+ */
+static const char
+*set_cache_dirlevels(cmd_parms *parms, void *in_struct_ptr, const char *arg)
+{
+ disk_cache_conf *conf = ap_get_module_config(parms->server->module_config,
+ &cache_disk_module);
+ int val = atoi(arg);
+ if (val < 1)
+ return "CacheDirLevels value must be an integer greater than 0";
+ if (val * conf->dirlength > CACHEFILE_LEN)
+ return "CacheDirLevels*CacheDirLength value must not be higher than 20";
+ conf->dirlevels = val;
+ return NULL;
+}
+static const char
+*set_cache_dirlength(cmd_parms *parms, void *in_struct_ptr, const char *arg)
+{
+ disk_cache_conf *conf = ap_get_module_config(parms->server->module_config,
+ &cache_disk_module);
+ int val = atoi(arg);
+ if (val < 1)
+ return "CacheDirLength value must be an integer greater than 0";
+ if (val * conf->dirlevels > CACHEFILE_LEN)
+ return "CacheDirLevels*CacheDirLength value must not be higher than 20";
+
+ conf->dirlength = val;
+ return NULL;
+}
+
+static const char
+*set_cache_minfs(cmd_parms *parms, void *in_struct_ptr, const char *arg)
+{
+ disk_cache_dir_conf *dconf = (disk_cache_dir_conf *)in_struct_ptr;
+
+ if (apr_strtoff(&dconf->minfs, arg, NULL, 10) != APR_SUCCESS ||
+ dconf->minfs < 0)
+ {
+ return "CacheMinFileSize argument must be a non-negative integer representing the min size of a file to cache in bytes.";
+ }
+ dconf->minfs_set = 1;
+ return NULL;
+}
+
+static const char
+*set_cache_maxfs(cmd_parms *parms, void *in_struct_ptr, const char *arg)
+{
+ disk_cache_dir_conf *dconf = (disk_cache_dir_conf *)in_struct_ptr;
+
+ if (apr_strtoff(&dconf->maxfs, arg, NULL, 10) != APR_SUCCESS ||
+ dconf->maxfs < 0)
+ {
+ return "CacheMaxFileSize argument must be a non-negative integer representing the max size of a file to cache in bytes.";
+ }
+ dconf->maxfs_set = 1;
+ return NULL;
+}
+
+static const char
+*set_cache_readsize(cmd_parms *parms, void *in_struct_ptr, const char *arg)
+{
+ disk_cache_dir_conf *dconf = (disk_cache_dir_conf *)in_struct_ptr;
+
+ if (apr_strtoff(&dconf->readsize, arg, NULL, 10) != APR_SUCCESS ||
+ dconf->readsize < 0)
+ {
+ return "CacheReadSize argument must be a non-negative integer representing the max amount of data to cache in go.";
+ }
+ dconf->readsize_set = 1;
+ return NULL;
+}
+
+static const char
+*set_cache_readtime(cmd_parms *parms, void *in_struct_ptr, const char *arg)
+{
+ disk_cache_dir_conf *dconf = (disk_cache_dir_conf *)in_struct_ptr;
+ apr_off_t milliseconds;
+
+ if (apr_strtoff(&milliseconds, arg, NULL, 10) != APR_SUCCESS ||
+ milliseconds < 0)
+ {
+ return "CacheReadTime argument must be a non-negative integer representing the max amount of time taken to cache in go.";
+ }
+ dconf->readtime = apr_time_from_msec(milliseconds);
+ dconf->readtime_set = 1;
+ return NULL;
+}
+
+static const command_rec disk_cache_cmds[] =
+{
+ AP_INIT_TAKE1("CacheRoot", set_cache_root, NULL, RSRC_CONF,
+ "The directory to store cache files"),
+ AP_INIT_TAKE1("CacheDirLevels", set_cache_dirlevels, NULL, RSRC_CONF,
+ "The number of levels of subdirectories in the cache"),
+ AP_INIT_TAKE1("CacheDirLength", set_cache_dirlength, NULL, RSRC_CONF,
+ "The number of characters in subdirectory names"),
+ AP_INIT_TAKE1("CacheMinFileSize", set_cache_minfs, NULL, RSRC_CONF | ACCESS_CONF,
+ "The minimum file size to cache a document"),
+ AP_INIT_TAKE1("CacheMaxFileSize", set_cache_maxfs, NULL, RSRC_CONF | ACCESS_CONF,
+ "The maximum file size to cache a document"),
+ AP_INIT_TAKE1("CacheReadSize", set_cache_readsize, NULL, RSRC_CONF | ACCESS_CONF,
+ "The maximum quantity of data to attempt to read and cache in one go"),
+ AP_INIT_TAKE1("CacheReadTime", set_cache_readtime, NULL, RSRC_CONF | ACCESS_CONF,
+ "The maximum time taken to attempt to read and cache in go"),
+ {NULL}
+};
+
+static const cache_provider cache_disk_provider =
+{
+ &remove_entity,
+ &store_headers,
+ &store_body,
+ &recall_headers,
+ &recall_body,
+ &create_entity,
+ &open_entity,
+ &remove_url,
+ &commit_entity,
+ &invalidate_entity
+};
+
+static void disk_cache_register_hook(apr_pool_t *p)
+{
+ /* cache initializer */
+ ap_register_provider(p, CACHE_PROVIDER_GROUP, "disk", "0",
+ &cache_disk_provider);
+}
+
+AP_DECLARE_MODULE(cache_disk) = {
+ STANDARD20_MODULE_STUFF,
+ create_dir_config, /* create per-directory config structure */
+ merge_dir_config, /* merge per-directory config structures */
+ create_config, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ disk_cache_cmds, /* command apr_table_t */
+ disk_cache_register_hook /* register hooks */
+};
diff --git a/modules/cache/mod_cache_disk.dep b/modules/cache/mod_cache_disk.dep
new file mode 100644
index 0000000..c757a8d
--- /dev/null
+++ b/modules/cache/mod_cache_disk.dep
@@ -0,0 +1,59 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_cache_disk.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_cache_disk.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_date.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\cache_common.h"\
+ ".\cache_disk_common.h"\
+ ".\mod_cache.h"\
+ ".\mod_cache_disk.h"\
+
diff --git a/modules/cache/mod_cache_disk.dsp b/modules/cache/mod_cache_disk.dsp
new file mode 100644
index 0000000..19ca39e
--- /dev/null
+++ b/modules/cache/mod_cache_disk.dsp
@@ -0,0 +1,115 @@
+# Microsoft Developer Studio Project File - Name="mod_cache_disk" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_cache_disk - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_cache_disk.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_cache_disk.mak" CFG="mod_cache_disk - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_cache_disk - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_cache_disk - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_cache_disk - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /Fd"Release\mod_cache_disk_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_cache_disk.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_cache_disk.so" /d LONG_NAME="cache_disk_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_cache_disk.so" /base:@..\..\os\win32\BaseAddr.ref,mod_cache_disk.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_cache_disk.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_cache_disk - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /Fd"Debug\mod_cache_disk_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_cache_disk.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_cache_disk.so" /d LONG_NAME="cache_disk_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_cache_disk.so" /base:@..\..\os\win32\BaseAddr.ref,mod_cache_disk.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_cache_disk.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_cache_disk - Win32 Release"
+# Name "mod_cache_disk - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_cache.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\mod_cache_disk.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/cache/mod_cache_disk.h b/modules/cache/mod_cache_disk.h
new file mode 100644
index 0000000..561ee3b
--- /dev/null
+++ b/modules/cache/mod_cache_disk.h
@@ -0,0 +1,91 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MOD_CACHE_DISK_H
+#define MOD_CACHE_DISK_H
+
+#include "apr_file_io.h"
+
+#include "cache_disk_common.h"
+
+/*
+ * include for mod_cache_disk: Disk Based HTTP 1.1 Cache.
+ */
+
+typedef struct {
+ apr_pool_t *pool;
+ const char *file;
+ apr_file_t *fd;
+ char *tempfile;
+ apr_file_t *tempfd;
+} disk_cache_file_t;
+
+/*
+ * disk_cache_object_t
+ * Pointed to by cache_object_t::vobj
+ */
+typedef struct disk_cache_object {
+ const char *root; /* the location of the cache directory */
+ apr_size_t root_len;
+ const char *prefix;
+ disk_cache_file_t data; /* data file structure */
+ disk_cache_file_t hdrs; /* headers file structure */
+ disk_cache_file_t vary; /* vary file structure */
+ const char *hashfile; /* Computed hash key for this URI */
+ const char *name; /* Requested URI without vary bits - suitable for mortals. */
+ const char *key; /* On-disk prefix; URI with Vary bits (if present) */
+ apr_off_t file_size; /* File size of the cached data file */
+ disk_cache_info_t disk_info; /* Header information. */
+ apr_table_t *headers_in; /* Input headers to save */
+ apr_table_t *headers_out; /* Output headers to save */
+ apr_off_t offset; /* Max size to set aside */
+ apr_time_t timeout; /* Max time to set aside */
+ unsigned int done:1; /* Is the attempt to cache complete? */
+} disk_cache_object_t;
+
+
+/*
+ * mod_cache_disk configuration
+ */
+/* TODO: Make defaults OS specific */
+#define CACHEFILE_LEN 20 /* must be less than HASH_LEN/2 */
+#define DEFAULT_DIRLEVELS 2
+#define DEFAULT_DIRLENGTH 2
+#define DEFAULT_MIN_FILE_SIZE 1
+#define DEFAULT_MAX_FILE_SIZE 1000000
+#define DEFAULT_READSIZE 0
+#define DEFAULT_READTIME 0
+
+typedef struct {
+ const char* cache_root;
+ apr_size_t cache_root_len;
+ int dirlevels; /* Number of levels of subdirectories */
+ int dirlength; /* Length of subdirectory names */
+} disk_cache_conf;
+
+typedef struct {
+ apr_off_t minfs; /* minimum file size for cached files */
+ apr_off_t maxfs; /* maximum file size for cached files */
+ apr_off_t readsize; /* maximum data to attempt to cache in one go */
+ apr_time_t readtime; /* maximum time taken to cache in one go */
+ unsigned int minfs_set:1;
+ unsigned int maxfs_set:1;
+ unsigned int readsize_set:1;
+ unsigned int readtime_set:1;
+} disk_cache_dir_conf;
+
+#endif /*MOD_CACHE_DISK_H*/
+
diff --git a/modules/cache/mod_cache_disk.mak b/modules/cache/mod_cache_disk.mak
new file mode 100644
index 0000000..5b4dd7a
--- /dev/null
+++ b/modules/cache/mod_cache_disk.mak
@@ -0,0 +1,381 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_cache_disk.dsp
+!IF "$(CFG)" == ""
+CFG=mod_cache_disk - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_cache_disk - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_cache_disk - Win32 Release" && "$(CFG)" != "mod_cache_disk - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_cache_disk.mak" CFG="mod_cache_disk - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_cache_disk - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_cache_disk - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_cache_disk - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_cache_disk.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_cache - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_cache_disk.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_cache - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_cache_disk.obj"
+ -@erase "$(INTDIR)\mod_cache_disk.res"
+ -@erase "$(INTDIR)\mod_cache_disk_src.idb"
+ -@erase "$(INTDIR)\mod_cache_disk_src.pdb"
+ -@erase "$(OUTDIR)\mod_cache_disk.exp"
+ -@erase "$(OUTDIR)\mod_cache_disk.lib"
+ -@erase "$(OUTDIR)\mod_cache_disk.pdb"
+ -@erase "$(OUTDIR)\mod_cache_disk.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_cache_disk_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_cache_disk.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_cache_disk.so" /d LONG_NAME="cache_disk_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_cache_disk.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_cache_disk.pdb" /debug /out:"$(OUTDIR)\mod_cache_disk.so" /implib:"$(OUTDIR)\mod_cache_disk.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_cache_disk.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_cache_disk.obj" \
+ "$(INTDIR)\mod_cache_disk.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib" \
+ "$(OUTDIR)\mod_cache.lib"
+
+"$(OUTDIR)\mod_cache_disk.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_cache_disk.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_cache_disk.so"
+ if exist .\Release\mod_cache_disk.so.manifest mt.exe -manifest .\Release\mod_cache_disk.so.manifest -outputresource:.\Release\mod_cache_disk.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_cache_disk - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_cache_disk.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_cache - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_cache_disk.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_cache - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_cache_disk.obj"
+ -@erase "$(INTDIR)\mod_cache_disk.res"
+ -@erase "$(INTDIR)\mod_cache_disk_src.idb"
+ -@erase "$(INTDIR)\mod_cache_disk_src.pdb"
+ -@erase "$(OUTDIR)\mod_cache_disk.exp"
+ -@erase "$(OUTDIR)\mod_cache_disk.lib"
+ -@erase "$(OUTDIR)\mod_cache_disk.pdb"
+ -@erase "$(OUTDIR)\mod_cache_disk.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_cache_disk_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_cache_disk.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_cache_disk.so" /d LONG_NAME="cache_disk_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_cache_disk.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_cache_disk.pdb" /debug /out:"$(OUTDIR)\mod_cache_disk.so" /implib:"$(OUTDIR)\mod_cache_disk.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_cache_disk.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_cache_disk.obj" \
+ "$(INTDIR)\mod_cache_disk.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib" \
+ "$(OUTDIR)\mod_cache.lib"
+
+"$(OUTDIR)\mod_cache_disk.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_cache_disk.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_cache_disk.so"
+ if exist .\Debug\mod_cache_disk.so.manifest mt.exe -manifest .\Debug\mod_cache_disk.so.manifest -outputresource:.\Debug\mod_cache_disk.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_cache_disk.dep")
+!INCLUDE "mod_cache_disk.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_cache_disk.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_cache_disk - Win32 Release" || "$(CFG)" == "mod_cache_disk - Win32 Debug"
+
+!IF "$(CFG)" == "mod_cache_disk - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\cache"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ELSEIF "$(CFG)" == "mod_cache_disk - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\cache"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_cache_disk - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\cache"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ELSEIF "$(CFG)" == "mod_cache_disk - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\cache"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_cache_disk - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\cache"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\cache"
+
+!ELSEIF "$(CFG)" == "mod_cache_disk - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\cache"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\cache"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_cache_disk - Win32 Release"
+
+"mod_cache - Win32 Release" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_cache.mak" CFG="mod_cache - Win32 Release"
+ cd "."
+
+"mod_cache - Win32 ReleaseCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_cache.mak" CFG="mod_cache - Win32 Release" RECURSE=1 CLEAN
+ cd "."
+
+!ELSEIF "$(CFG)" == "mod_cache_disk - Win32 Debug"
+
+"mod_cache - Win32 Debug" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_cache.mak" CFG="mod_cache - Win32 Debug"
+ cd "."
+
+"mod_cache - Win32 DebugCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_cache.mak" CFG="mod_cache - Win32 Debug" RECURSE=1 CLEAN
+ cd "."
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_cache_disk - Win32 Release"
+
+
+"$(INTDIR)\mod_cache_disk.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_cache_disk.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_cache_disk.so" /d LONG_NAME="cache_disk_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_cache_disk - Win32 Debug"
+
+
+"$(INTDIR)\mod_cache_disk.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_cache_disk.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_cache_disk.so" /d LONG_NAME="cache_disk_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_cache_disk.c
+
+"$(INTDIR)\mod_cache_disk.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/cache/mod_cache_socache.c b/modules/cache/mod_cache_socache.c
new file mode 100644
index 0000000..5f9e1d6
--- /dev/null
+++ b/modules/cache/mod_cache_socache.c
@@ -0,0 +1,1543 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr_lib.h"
+#include "apr_file_io.h"
+#include "apr_strings.h"
+#include "apr_buckets.h"
+
+#include "apr_version.h"
+#if !APR_VERSION_AT_LEAST(2,0,0)
+#include "apu_version.h"
+#endif
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "http_core.h"
+#include "http_protocol.h"
+#include "ap_provider.h"
+#include "ap_socache.h"
+#include "util_filter.h"
+#include "util_script.h"
+#include "util_charset.h"
+#include "util_mutex.h"
+
+#include "mod_cache.h"
+#include "mod_status.h"
+
+#include "cache_socache_common.h"
+
+/*
+ * mod_cache_socache: Shared Object Cache Based HTTP 1.1 Cache.
+ *
+ * Flow to Find the entry:
+ * Incoming client requests URI /foo/bar/baz
+ * Fetch URI key (may contain Format #1 or Format #2)
+ * If format #1 (Contains a list of Vary Headers):
+ * Use each header name (from .header) with our request values (headers_in) to
+ * regenerate key using HeaderName+HeaderValue+.../foo/bar/baz
+ * re-read in key (must be format #2)
+ *
+ * Format #1:
+ * apr_uint32_t format;
+ * apr_time_t expire;
+ * apr_array_t vary_headers (delimited by CRLF)
+ *
+ * Format #2:
+ * cache_socache_info_t (first sizeof(apr_uint32_t) bytes is the format)
+ * entity name (sobj->name) [length is in cache_socache_info_t->name_len]
+ * r->headers_out (delimited by CRLF)
+ * CRLF
+ * r->headers_in (delimited by CRLF)
+ * CRLF
+ */
+
+module AP_MODULE_DECLARE_DATA cache_socache_module;
+
+/*
+ * cache_socache_object_t
+ * Pointed to by cache_object_t::vobj
+ */
+typedef struct cache_socache_object_t
+{
+ apr_pool_t *pool; /* pool */
+ unsigned char *buffer; /* the cache buffer */
+ apr_size_t buffer_len; /* size of the buffer */
+ apr_bucket_brigade *body; /* brigade containing the body, if any */
+ apr_table_t *headers_in; /* Input headers to save */
+ apr_table_t *headers_out; /* Output headers to save */
+ cache_socache_info_t socache_info; /* Header information. */
+ apr_size_t body_offset; /* offset to the start of the body */
+ apr_off_t body_length; /* length of the cached entity body */
+ apr_time_t expire; /* when to expire the entry */
+
+ const char *name; /* Requested URI without vary bits - suitable for mortals. */
+ const char *key; /* On-disk prefix; URI with Vary bits (if present) */
+ apr_off_t offset; /* Max size to set aside */
+ apr_time_t timeout; /* Max time to set aside */
+ unsigned int newbody :1; /* whether a new body is present */
+ unsigned int done :1; /* Is the attempt to cache complete? */
+} cache_socache_object_t;
+
+/*
+ * mod_cache_socache configuration
+ */
+#define DEFAULT_MAX_FILE_SIZE 100*1024
+#define DEFAULT_MAXTIME 86400
+#define DEFAULT_MINTIME 600
+#define DEFAULT_READSIZE 0
+#define DEFAULT_READTIME 0
+
+typedef struct cache_socache_provider_conf
+{
+ const char *args;
+ ap_socache_provider_t *socache_provider;
+ ap_socache_instance_t *socache_instance;
+} cache_socache_provider_conf;
+
+typedef struct cache_socache_conf
+{
+ cache_socache_provider_conf *provider;
+} cache_socache_conf;
+
+typedef struct cache_socache_dir_conf
+{
+ apr_off_t max; /* maximum file size for cached files */
+ apr_time_t maxtime; /* maximum expiry time */
+ apr_time_t mintime; /* minimum expiry time */
+ apr_off_t readsize; /* maximum data to attempt to cache in one go */
+ apr_time_t readtime; /* maximum time taken to cache in one go */
+ unsigned int max_set :1;
+ unsigned int maxtime_set :1;
+ unsigned int mintime_set :1;
+ unsigned int readsize_set :1;
+ unsigned int readtime_set :1;
+} cache_socache_dir_conf;
+
+/* Shared object cache and mutex */
+static const char * const cache_socache_id = "cache-socache";
+static apr_global_mutex_t *socache_mutex = NULL;
+
+/*
+ * Local static functions
+ */
+
+static apr_status_t read_array(request_rec *r, apr_array_header_t *arr,
+ unsigned char *buffer, apr_size_t buffer_len, apr_size_t *slider)
+{
+ apr_size_t val = *slider;
+
+ while (*slider < buffer_len) {
+ if (buffer[*slider] == '\r') {
+ if (val == *slider) {
+ (*slider)++;
+ return APR_SUCCESS;
+ }
+ *((const char **) apr_array_push(arr)) = apr_pstrndup(r->pool,
+ (const char *) buffer + val, *slider - val);
+ (*slider)++;
+ if (buffer[*slider] == '\n') {
+ (*slider)++;
+ }
+ val = *slider;
+ }
+ else if (buffer[*slider] == '\0') {
+ (*slider)++;
+ return APR_SUCCESS;
+ }
+ else {
+ (*slider)++;
+ }
+ }
+
+ return APR_EOF;
+}
+
+static apr_status_t store_array(apr_array_header_t *arr, unsigned char *buffer,
+ apr_size_t buffer_len, apr_size_t *slider)
+{
+ int i, len;
+ const char **elts;
+
+ elts = (const char **) arr->elts;
+
+ for (i = 0; i < arr->nelts; i++) {
+ apr_size_t e_len = strlen(elts[i]);
+ if (e_len + 3 >= buffer_len - *slider) {
+ return APR_EOF;
+ }
+ len = apr_snprintf(buffer ? (char *) buffer + *slider : NULL,
+ buffer ? buffer_len - *slider : 0, "%s" CRLF, elts[i]);
+ *slider += len;
+ }
+ if (buffer) {
+ memcpy(buffer + *slider, CRLF, sizeof(CRLF) - 1);
+ }
+ *slider += sizeof(CRLF) - 1;
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t read_table(cache_handle_t *handle, request_rec *r,
+ apr_table_t *table, unsigned char *buffer, apr_size_t buffer_len,
+ apr_size_t *slider)
+{
+ apr_size_t key = *slider, colon = 0, len = 0;
+
+ while (*slider < buffer_len) {
+ if (buffer[*slider] == ':') {
+ if (!colon) {
+ colon = *slider;
+ }
+ (*slider)++;
+ }
+ else if (buffer[*slider] == '\r') {
+ len = colon;
+ if (key == *slider) {
+ (*slider)++;
+ if (buffer[*slider] == '\n') {
+ (*slider)++;
+ }
+ return APR_SUCCESS;
+ }
+ if (!colon || buffer[colon++] != ':') {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02344)
+ "Premature end of cache headers.");
+ return APR_EGENERAL;
+ }
+ /* Do not go past the \r from above as apr_isspace('\r') is true */
+ while (apr_isspace(buffer[colon]) && (colon < *slider)) {
+ colon++;
+ }
+ apr_table_addn(table, apr_pstrmemdup(r->pool, (const char *) buffer
+ + key, len - key), apr_pstrmemdup(r->pool,
+ (const char *) buffer + colon, *slider - colon));
+ (*slider)++;
+ if (buffer[*slider] == '\n') {
+ (*slider)++;
+ }
+ key = *slider;
+ colon = 0;
+ }
+ else if (buffer[*slider] == '\0') {
+ (*slider)++;
+ return APR_SUCCESS;
+ }
+ else {
+ (*slider)++;
+ }
+ }
+
+ return APR_EOF;
+}
+
+static apr_status_t store_table(apr_table_t *table, unsigned char *buffer,
+ apr_size_t buffer_len, apr_size_t *slider)
+{
+ int i, len;
+ apr_table_entry_t *elts;
+
+ elts = (apr_table_entry_t *) apr_table_elts(table)->elts;
+ for (i = 0; i < apr_table_elts(table)->nelts; ++i) {
+ if (elts[i].key != NULL) {
+ apr_size_t key_len = strlen(elts[i].key);
+ apr_size_t val_len = strlen(elts[i].val);
+ if (key_len + val_len + 5 >= buffer_len - *slider) {
+ return APR_EOF;
+ }
+ len = apr_snprintf(buffer ? (char *) buffer + *slider : NULL,
+ buffer ? buffer_len - *slider : 0, "%s: %s" CRLF,
+ elts[i].key, elts[i].val);
+ *slider += len;
+ }
+ }
+ if (3 >= buffer_len - *slider) {
+ return APR_EOF;
+ }
+ if (buffer) {
+ memcpy(buffer + *slider, CRLF, sizeof(CRLF) - 1);
+ }
+ *slider += sizeof(CRLF) - 1;
+
+ return APR_SUCCESS;
+}
+
+static const char* regen_key(apr_pool_t *p, apr_table_t *headers,
+ apr_array_header_t *varray, const char *oldkey,
+ apr_size_t *newkeylen)
+{
+ struct iovec *iov;
+ int i, k;
+ int nvec;
+ const char *header;
+ const char **elts;
+
+ nvec = (varray->nelts * 2) + 1;
+ iov = apr_palloc(p, sizeof(struct iovec) * nvec);
+ elts = (const char **) varray->elts;
+
+ /* TODO:
+ * - Handle multiple-value headers better. (sort them?)
+ * - Handle Case in-sensitive Values better.
+ * This isn't the end of the world, since it just lowers the cache
+ * hit rate, but it would be nice to fix.
+ *
+ * The majority are case insenstive if they are values (encoding etc).
+ * Most of rfc2616 is case insensitive on header contents.
+ *
+ * So the better solution may be to identify headers which should be
+ * treated case-sensitive?
+ * HTTP URI's (3.2.3) [host and scheme are insensitive]
+ * HTTP method (5.1.1)
+ * HTTP-date values (3.3.1)
+ * 3.7 Media Types [excerpt]
+ * The type, subtype, and parameter attribute names are case-
+ * insensitive. Parameter values might or might not be case-sensitive,
+ * depending on the semantics of the parameter name.
+ * 4.20 Except [excerpt]
+ * Comparison of expectation values is case-insensitive for unquoted
+ * tokens (including the 100-continue token), and is case-sensitive for
+ * quoted-string expectation-extensions.
+ */
+
+ for (i = 0, k = 0; i < varray->nelts; i++) {
+ header = apr_table_get(headers, elts[i]);
+ if (!header) {
+ header = "";
+ }
+ iov[k].iov_base = (char*) elts[i];
+ iov[k].iov_len = strlen(elts[i]);
+ k++;
+ iov[k].iov_base = (char*) header;
+ iov[k].iov_len = strlen(header);
+ k++;
+ }
+ iov[k].iov_base = (char*) oldkey;
+ iov[k].iov_len = strlen(oldkey);
+ k++;
+
+ return apr_pstrcatv(p, iov, k, newkeylen);
+}
+
+static int array_alphasort(const void *fn1, const void *fn2)
+{
+ return strcmp(*(char**) fn1, *(char**) fn2);
+}
+
+static void tokens_to_array(apr_pool_t *p, const char *data,
+ apr_array_header_t *arr)
+{
+ char *token;
+
+ while ((token = ap_get_list_item(p, &data)) != NULL) {
+ *((const char **) apr_array_push(arr)) = token;
+ }
+
+ /* Sort it so that "Vary: A, B" and "Vary: B, A" are stored the same. */
+ qsort((void *) arr->elts, arr->nelts, sizeof(char *), array_alphasort);
+}
+
+/*
+ * Hook and mod_cache callback functions
+ */
+static int create_entity(cache_handle_t *h, request_rec *r, const char *key,
+ apr_off_t len, apr_bucket_brigade *bb)
+{
+ cache_socache_dir_conf *dconf =
+ ap_get_module_config(r->per_dir_config, &cache_socache_module);
+ cache_socache_conf *conf = ap_get_module_config(r->server->module_config,
+ &cache_socache_module);
+ cache_object_t *obj;
+ cache_socache_object_t *sobj;
+ apr_size_t total;
+
+ if (conf->provider == NULL) {
+ return DECLINED;
+ }
+
+ /* we don't support caching of range requests (yet) */
+ /* TODO: but we could */
+ if (r->status == HTTP_PARTIAL_CONTENT) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02345)
+ "URL %s partial content response not cached",
+ key);
+ return DECLINED;
+ }
+
+ /*
+ * We have a chicken and egg problem. We don't know until we
+ * attempt to store_headers just how big the response will be
+ * and whether it will fit in the cache limits set. But we
+ * need to make a decision now as to whether we plan to try.
+ * If we make the wrong decision, we could prevent another
+ * cache implementation, such as cache_disk, from getting the
+ * opportunity to cache, and that would be unfortunate.
+ *
+ * In a series of tests, from cheapest to most expensive,
+ * decide whether or not to ignore this attempt to cache,
+ * with a small margin just to be sure.
+ */
+ if (len < 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02346)
+ "URL '%s' had no explicit size, ignoring", key);
+ return DECLINED;
+ }
+ if (len > dconf->max) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02347)
+ "URL '%s' body larger than limit, ignoring "
+ "(%" APR_OFF_T_FMT " > %" APR_OFF_T_FMT ")",
+ key, len, dconf->max);
+ return DECLINED;
+ }
+
+ /* estimate the total cached size, given current headers */
+ total = len + sizeof(cache_socache_info_t) + strlen(key);
+ if (APR_SUCCESS != store_table(r->headers_out, NULL, dconf->max, &total)
+ || APR_SUCCESS != store_table(r->headers_in, NULL, dconf->max,
+ &total)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02348)
+ "URL '%s' estimated headers size larger than limit, ignoring "
+ "(%" APR_SIZE_T_FMT " > %" APR_OFF_T_FMT ")",
+ key, total, dconf->max);
+ return DECLINED;
+ }
+
+ if (total >= dconf->max) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02349)
+ "URL '%s' body and headers larger than limit, ignoring "
+ "(%" APR_OFF_T_FMT " > %" APR_OFF_T_FMT ")",
+ key, len, dconf->max);
+ return DECLINED;
+ }
+
+ /* Allocate and initialize cache_object_t and cache_socache_object_t */
+ h->cache_obj = obj = apr_pcalloc(r->pool, sizeof(*obj));
+ obj->vobj = sobj = apr_pcalloc(r->pool, sizeof(*sobj));
+
+ obj->key = apr_pstrdup(r->pool, key);
+ sobj->key = obj->key;
+ sobj->name = obj->key;
+
+ return OK;
+}
+
+static apr_status_t sobj_body_pre_cleanup(void *baton)
+{
+ cache_socache_object_t *sobj = baton;
+ apr_brigade_cleanup(sobj->body);
+ sobj->body = NULL;
+ return APR_SUCCESS;
+}
+
+static int open_entity(cache_handle_t *h, request_rec *r, const char *key)
+{
+ cache_socache_dir_conf *dconf =
+ ap_get_module_config(r->per_dir_config, &cache_socache_module);
+ cache_socache_conf *conf = ap_get_module_config(r->server->module_config,
+ &cache_socache_module);
+ apr_uint32_t format;
+ apr_size_t slider;
+ unsigned int buffer_len;
+ const char *nkey;
+ apr_status_t rc;
+ cache_object_t *obj;
+ cache_info *info;
+ cache_socache_object_t *sobj;
+ apr_size_t len;
+
+ nkey = NULL;
+ h->cache_obj = NULL;
+
+ if (!conf->provider || !conf->provider->socache_instance) {
+ return DECLINED;
+ }
+
+ /* Create and init the cache object */
+ obj = apr_pcalloc(r->pool, sizeof(cache_object_t));
+ sobj = apr_pcalloc(r->pool, sizeof(cache_socache_object_t));
+
+ info = &(obj->info);
+
+ /* Create a temporary pool for the buffer, and destroy it if something
+ * goes wrong so we don't have large buffers of unused memory hanging
+ * about for the lifetime of the response.
+ */
+ apr_pool_create(&sobj->pool, r->pool);
+ apr_pool_tag(sobj->pool, "mod_cache_socache (open_entity)");
+
+ sobj->buffer = apr_palloc(sobj->pool, dconf->max);
+ sobj->buffer_len = dconf->max;
+
+ /* attempt to retrieve the cached entry */
+ if (socache_mutex) {
+ apr_status_t status = apr_global_mutex_lock(socache_mutex);
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02350)
+ "could not acquire lock, ignoring: %s", obj->key);
+ apr_pool_destroy(sobj->pool);
+ sobj->pool = NULL;
+ return DECLINED;
+ }
+ }
+ buffer_len = sobj->buffer_len;
+ rc = conf->provider->socache_provider->retrieve(
+ conf->provider->socache_instance, r->server, (unsigned char *) key,
+ strlen(key), sobj->buffer, &buffer_len, r->pool);
+ if (socache_mutex) {
+ apr_status_t status = apr_global_mutex_unlock(socache_mutex);
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02351)
+ "could not release lock, ignoring: %s", obj->key);
+ apr_pool_destroy(sobj->pool);
+ sobj->pool = NULL;
+ return DECLINED;
+ }
+ }
+ if (rc != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rc, r, APLOGNO(02352)
+ "Key not found in cache: %s", key);
+ apr_pool_destroy(sobj->pool);
+ sobj->pool = NULL;
+ return DECLINED;
+ }
+ if (buffer_len >= sobj->buffer_len) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rc, r, APLOGNO(02353)
+ "Key found in cache but too big, ignoring: %s", key);
+ apr_pool_destroy(sobj->pool);
+ sobj->pool = NULL;
+ return DECLINED;
+ }
+
+ /* read the format from the cache file */
+ memcpy(&format, sobj->buffer, sizeof(format));
+ slider = sizeof(format);
+
+ if (format == CACHE_SOCACHE_VARY_FORMAT_VERSION) {
+ apr_array_header_t* varray;
+ apr_time_t expire;
+
+ memcpy(&expire, sobj->buffer + slider, sizeof(expire));
+ slider += sizeof(expire);
+
+ varray = apr_array_make(r->pool, 5, sizeof(char*));
+ rc = read_array(r, varray, sobj->buffer, buffer_len, &slider);
+ if (rc != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(02354)
+ "Cannot parse vary entry for key: %s", key);
+ apr_pool_destroy(sobj->pool);
+ sobj->pool = NULL;
+ return DECLINED;
+ }
+
+ nkey = regen_key(r->pool, r->headers_in, varray, key, &len);
+
+ /* attempt to retrieve the cached entry */
+ if (socache_mutex) {
+ apr_status_t status = apr_global_mutex_lock(socache_mutex);
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02355)
+ "could not acquire lock, ignoring: %s", obj->key);
+ apr_pool_destroy(sobj->pool);
+ sobj->pool = NULL;
+ return DECLINED;
+ }
+ }
+ buffer_len = sobj->buffer_len;
+ rc = conf->provider->socache_provider->retrieve(
+ conf->provider->socache_instance, r->server,
+ (unsigned char *) nkey, len, sobj->buffer,
+ &buffer_len, r->pool);
+ if (socache_mutex) {
+ apr_status_t status = apr_global_mutex_unlock(socache_mutex);
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02356)
+ "could not release lock, ignoring: %s", obj->key);
+ apr_pool_destroy(sobj->pool);
+ sobj->pool = NULL;
+ return DECLINED;
+ }
+ }
+ if (rc != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rc, r, APLOGNO(02357)
+ "Key not found in cache: %s", key);
+ apr_pool_destroy(sobj->pool);
+ sobj->pool = NULL;
+ return DECLINED;
+ }
+ if (buffer_len >= sobj->buffer_len) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rc, r, APLOGNO(02358)
+ "Key found in cache but too big, ignoring: %s", key);
+ goto fail;
+ }
+
+ }
+ else if (format != CACHE_SOCACHE_DISK_FORMAT_VERSION) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02359)
+ "Key '%s' found in cache has version %d, expected %d, ignoring",
+ key, format, CACHE_SOCACHE_DISK_FORMAT_VERSION);
+ goto fail;
+ }
+ else {
+ nkey = key;
+ }
+
+ obj->key = nkey;
+ sobj->key = nkey;
+ sobj->name = key;
+
+ if (buffer_len >= sizeof(cache_socache_info_t)) {
+ memcpy(&sobj->socache_info, sobj->buffer, sizeof(cache_socache_info_t));
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(02360)
+ "Cache entry for key '%s' too short, removing", nkey);
+ goto fail;
+ }
+ slider = sizeof(cache_socache_info_t);
+
+ /* Store it away so we can get it later. */
+ info->status = sobj->socache_info.status;
+ info->date = sobj->socache_info.date;
+ info->expire = sobj->socache_info.expire;
+ info->request_time = sobj->socache_info.request_time;
+ info->response_time = sobj->socache_info.response_time;
+
+ memcpy(&info->control, &sobj->socache_info.control, sizeof(cache_control_t));
+
+ if (sobj->socache_info.name_len <= buffer_len - slider) {
+ if (strncmp((const char *) sobj->buffer + slider, sobj->name,
+ sobj->socache_info.name_len)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(02361)
+ "Cache entry for key '%s' URL mismatch, ignoring", nkey);
+ apr_pool_destroy(sobj->pool);
+ sobj->pool = NULL;
+ return DECLINED;
+ }
+ slider += sobj->socache_info.name_len;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(02362)
+ "Cache entry for key '%s' too short, removing", nkey);
+ goto fail;
+ }
+
+ /* Is this a cached HEAD request? */
+ if (sobj->socache_info.header_only && !r->header_only) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(02363)
+ "HEAD request cached, non-HEAD requested, ignoring: %s",
+ sobj->key);
+ apr_pool_destroy(sobj->pool);
+ sobj->pool = NULL;
+ return DECLINED;
+ }
+
+ h->req_hdrs = apr_table_make(r->pool, 20);
+ h->resp_hdrs = apr_table_make(r->pool, 20);
+
+ /* Call routine to read the header lines/status line */
+ if (APR_SUCCESS != read_table(h, r, h->resp_hdrs, sobj->buffer, buffer_len,
+ &slider)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(02364)
+ "Cache entry for key '%s' response headers unreadable, removing", nkey);
+ goto fail;
+ }
+ if (APR_SUCCESS != read_table(h, r, h->req_hdrs, sobj->buffer, buffer_len,
+ &slider)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(02365)
+ "Cache entry for key '%s' request headers unreadable, removing", nkey);
+ goto fail;
+ }
+
+ /* Retrieve the body if we have one */
+ len = buffer_len - slider;
+ if (len > 0) {
+ apr_bucket *e;
+ /* Create the body brigade later concatenated to the output filters'
+ * brigade by recall_body(). Since sobj->buffer (the data) point to
+ * sobj->pool (a subpool of r->pool), be safe by using a pool bucket
+ * which can morph to heap if sobj->pool is destroyed while the bucket
+ * is still alive. But if sobj->pool gets destroyed while the bucket is
+ * still in sobj->body (i.e. recall_body() was never called), we don't
+ * need to morph to something just about to be freed, so a pre_cleanup
+ * will take care of cleaning up sobj->body before this happens (and is
+ * a noop otherwise).
+ */
+ sobj->body = apr_brigade_create(sobj->pool, r->connection->bucket_alloc);
+ apr_pool_pre_cleanup_register(sobj->pool, sobj, sobj_body_pre_cleanup);
+ e = apr_bucket_pool_create((const char *) sobj->buffer + slider, len,
+ sobj->pool, r->connection->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(sobj->body, e);
+ }
+
+ /* make the configuration stick */
+ h->cache_obj = obj;
+ obj->vobj = sobj;
+
+ return OK;
+
+fail:
+ if (socache_mutex) {
+ apr_status_t status = apr_global_mutex_lock(socache_mutex);
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02366)
+ "could not acquire lock, ignoring: %s", obj->key);
+ apr_pool_destroy(sobj->pool);
+ sobj->pool = NULL;
+ return DECLINED;
+ }
+ }
+ conf->provider->socache_provider->remove(
+ conf->provider->socache_instance, r->server,
+ (unsigned char *) nkey, strlen(nkey), r->pool);
+ if (socache_mutex) {
+ apr_status_t status = apr_global_mutex_unlock(socache_mutex);
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02367)
+ "could not release lock, ignoring: %s", obj->key);
+ }
+ }
+ apr_pool_destroy(sobj->pool);
+ sobj->pool = NULL;
+ return DECLINED;
+}
+
+static int remove_entity(cache_handle_t *h)
+{
+ /* Null out the cache object pointer so next time we start from scratch */
+ h->cache_obj = NULL;
+ return OK;
+}
+
+static int remove_url(cache_handle_t *h, request_rec *r)
+{
+ cache_socache_conf *conf = ap_get_module_config(r->server->module_config,
+ &cache_socache_module);
+ cache_socache_object_t *sobj;
+
+ sobj = (cache_socache_object_t *) h->cache_obj->vobj;
+ if (!sobj) {
+ return DECLINED;
+ }
+
+ /* Remove the key from the cache */
+ if (socache_mutex) {
+ apr_status_t status = apr_global_mutex_lock(socache_mutex);
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02368)
+ "could not acquire lock, ignoring: %s", sobj->key);
+ apr_pool_destroy(sobj->pool);
+ sobj->pool = NULL;
+ return DECLINED;
+ }
+ }
+ conf->provider->socache_provider->remove(conf->provider->socache_instance,
+ r->server, (unsigned char *) sobj->key, strlen(sobj->key), r->pool);
+ if (socache_mutex) {
+ apr_status_t status = apr_global_mutex_unlock(socache_mutex);
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02369)
+ "could not release lock, ignoring: %s", sobj->key);
+ apr_pool_destroy(sobj->pool);
+ sobj->pool = NULL;
+ return DECLINED;
+ }
+ }
+
+ return OK;
+}
+
+static apr_status_t recall_headers(cache_handle_t *h, request_rec *r)
+{
+ /* we recalled the headers during open_entity, so do nothing */
+ return APR_SUCCESS;
+}
+
+static apr_status_t recall_body(cache_handle_t *h, apr_pool_t *p,
+ apr_bucket_brigade *bb)
+{
+ cache_socache_object_t *sobj = (cache_socache_object_t*) h->cache_obj->vobj;
+
+ if (sobj->body) {
+ APR_BRIGADE_CONCAT(bb, sobj->body);
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t store_headers(cache_handle_t *h, request_rec *r,
+ cache_info *info)
+{
+ cache_socache_dir_conf *dconf =
+ ap_get_module_config(r->per_dir_config, &cache_socache_module);
+ cache_socache_conf *conf = ap_get_module_config(r->server->module_config,
+ &cache_socache_module);
+ apr_size_t slider;
+ apr_status_t rv;
+ cache_object_t *obj = h->cache_obj;
+ cache_socache_object_t *sobj = (cache_socache_object_t*) obj->vobj;
+ cache_socache_info_t *socache_info;
+
+ memcpy(&h->cache_obj->info, info, sizeof(cache_info));
+
+ if (r->headers_out) {
+ sobj->headers_out = ap_cache_cacheable_headers_out(r);
+ }
+
+ if (r->headers_in) {
+ sobj->headers_in = ap_cache_cacheable_headers_in(r);
+ }
+
+ sobj->expire
+ = obj->info.expire > r->request_time + dconf->maxtime ? r->request_time
+ + dconf->maxtime
+ : obj->info.expire + dconf->mintime;
+
+ apr_pool_create(&sobj->pool, r->pool);
+ apr_pool_tag(sobj->pool, "mod_cache_socache (store_headers)");
+
+ sobj->buffer = apr_palloc(sobj->pool, dconf->max);
+ sobj->buffer_len = dconf->max;
+ socache_info = (cache_socache_info_t *) sobj->buffer;
+
+ if (sobj->headers_out) {
+ const char *vary;
+
+ vary = apr_table_get(sobj->headers_out, "Vary");
+
+ if (vary) {
+ apr_array_header_t* varray;
+ apr_uint32_t format = CACHE_SOCACHE_VARY_FORMAT_VERSION;
+
+ memcpy(sobj->buffer, &format, sizeof(format));
+ slider = sizeof(format);
+
+ memcpy(sobj->buffer + slider, &obj->info.expire,
+ sizeof(obj->info.expire));
+ slider += sizeof(obj->info.expire);
+
+ varray = apr_array_make(r->pool, 6, sizeof(char*));
+ tokens_to_array(r->pool, vary, varray);
+
+ if (APR_SUCCESS != (rv = store_array(varray, sobj->buffer,
+ sobj->buffer_len, &slider))) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02370)
+ "buffer too small for Vary array, caching aborted: %s",
+ obj->key);
+ apr_pool_destroy(sobj->pool);
+ sobj->pool = NULL;
+ return rv;
+ }
+ if (socache_mutex) {
+ apr_status_t status = apr_global_mutex_lock(socache_mutex);
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02371)
+ "could not acquire lock, ignoring: %s", obj->key);
+ apr_pool_destroy(sobj->pool);
+ sobj->pool = NULL;
+ return status;
+ }
+ }
+ rv = conf->provider->socache_provider->store(
+ conf->provider->socache_instance, r->server,
+ (unsigned char *) obj->key, strlen(obj->key), sobj->expire,
+ (unsigned char *) sobj->buffer, (unsigned int) slider,
+ sobj->pool);
+ if (socache_mutex) {
+ apr_status_t status = apr_global_mutex_unlock(socache_mutex);
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02372)
+ "could not release lock, ignoring: %s", obj->key);
+ }
+ }
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(02373)
+ "Vary not written to cache, ignoring: %s", obj->key);
+ apr_pool_destroy(sobj->pool);
+ sobj->pool = NULL;
+ return rv;
+ }
+
+ obj->key = sobj->key = regen_key(r->pool, sobj->headers_in, varray,
+ sobj->name, NULL);
+ }
+ }
+
+ socache_info->format = CACHE_SOCACHE_DISK_FORMAT_VERSION;
+ socache_info->date = obj->info.date;
+ socache_info->expire = obj->info.expire;
+ socache_info->entity_version = sobj->socache_info.entity_version++;
+ socache_info->request_time = obj->info.request_time;
+ socache_info->response_time = obj->info.response_time;
+ socache_info->status = obj->info.status;
+
+ if (r->header_only && r->status != HTTP_NOT_MODIFIED) {
+ socache_info->header_only = 1;
+ }
+ else {
+ socache_info->header_only = sobj->socache_info.header_only;
+ }
+
+ socache_info->name_len = strlen(sobj->name);
+
+ memcpy(&socache_info->control, &obj->info.control, sizeof(cache_control_t));
+ slider = sizeof(cache_socache_info_t);
+
+ if (slider + socache_info->name_len >= sobj->buffer_len) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02374)
+ "cache buffer too small for name: %s",
+ sobj->name);
+ apr_pool_destroy(sobj->pool);
+ sobj->pool = NULL;
+ return APR_EGENERAL;
+ }
+ memcpy(sobj->buffer + slider, sobj->name, socache_info->name_len);
+ slider += socache_info->name_len;
+
+ if (sobj->headers_out) {
+ if (APR_SUCCESS != store_table(sobj->headers_out, sobj->buffer,
+ sobj->buffer_len, &slider)) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02375)
+ "out-headers didn't fit in buffer: %s", sobj->name);
+ apr_pool_destroy(sobj->pool);
+ sobj->pool = NULL;
+ return APR_EGENERAL;
+ }
+ }
+
+ /* Parse the vary header and dump those fields from the headers_in. */
+ /* TODO: Make call to the same thing cache_select calls to crack Vary. */
+ if (sobj->headers_in) {
+ if (APR_SUCCESS != store_table(sobj->headers_in, sobj->buffer,
+ sobj->buffer_len, &slider)) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02376)
+ "in-headers didn't fit in buffer %s",
+ sobj->key);
+ apr_pool_destroy(sobj->pool);
+ sobj->pool = NULL;
+ return APR_EGENERAL;
+ }
+ }
+
+ sobj->body_offset = slider;
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t store_body(cache_handle_t *h, request_rec *r,
+ apr_bucket_brigade *in, apr_bucket_brigade *out)
+{
+ apr_bucket *e;
+ apr_status_t rv = APR_SUCCESS;
+ cache_socache_object_t *sobj =
+ (cache_socache_object_t *) h->cache_obj->vobj;
+ cache_socache_dir_conf *dconf =
+ ap_get_module_config(r->per_dir_config, &cache_socache_module);
+ int seen_eos = 0;
+
+ if (!sobj->offset) {
+ sobj->offset = dconf->readsize;
+ }
+ if (!sobj->timeout && dconf->readtime) {
+ sobj->timeout = apr_time_now() + dconf->readtime;
+ }
+
+ if (!sobj->newbody) {
+ sobj->body_length = 0;
+ sobj->newbody = 1;
+ }
+ if (sobj->offset) {
+ apr_brigade_partition(in, sobj->offset, &e);
+ }
+
+ while (APR_SUCCESS == rv && !APR_BRIGADE_EMPTY(in)) {
+ const char *str;
+ apr_size_t length;
+
+ e = APR_BRIGADE_FIRST(in);
+
+ /* are we done completely? if so, pass any trailing buckets right through */
+ if (sobj->done || !sobj->pool) {
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(out, e);
+ continue;
+ }
+
+ /* have we seen eos yet? */
+ if (APR_BUCKET_IS_EOS(e)) {
+ seen_eos = 1;
+ sobj->done = 1;
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(out, e);
+ break;
+ }
+
+ /* honour flush buckets, we'll get called again */
+ if (APR_BUCKET_IS_FLUSH(e)) {
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(out, e);
+ break;
+ }
+
+ /* metadata buckets are preserved as is */
+ if (APR_BUCKET_IS_METADATA(e)) {
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(out, e);
+ continue;
+ }
+
+ /* read the bucket, write to the cache */
+ rv = apr_bucket_read(e, &str, &length, APR_BLOCK_READ);
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(out, e);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02377)
+ "Error when reading bucket for URL %s",
+ h->cache_obj->key);
+ /* Remove the intermediate cache file and return non-APR_SUCCESS */
+ apr_pool_destroy(sobj->pool);
+ sobj->pool = NULL;
+ return rv;
+ }
+
+ /* don't write empty buckets to the cache */
+ if (!length) {
+ continue;
+ }
+
+ sobj->body_length += length;
+ if (sobj->body_length >= sobj->buffer_len - sobj->body_offset) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02378)
+ "URL %s failed the buffer size check "
+ "(%" APR_OFF_T_FMT ">=%" APR_SIZE_T_FMT ")",
+ h->cache_obj->key, sobj->body_length,
+ sobj->buffer_len - sobj->body_offset);
+ apr_pool_destroy(sobj->pool);
+ sobj->pool = NULL;
+ return APR_EGENERAL;
+ }
+ memcpy(sobj->buffer + sobj->body_offset + sobj->body_length - length,
+ str, length);
+
+ /* have we reached the limit of how much we're prepared to write in one
+ * go? If so, leave, we'll get called again. This prevents us from trying
+ * to swallow too much data at once, or taking so long to write the data
+ * the client times out.
+ */
+ sobj->offset -= length;
+ if (sobj->offset <= 0) {
+ sobj->offset = 0;
+ break;
+ }
+ if ((dconf->readtime && apr_time_now() > sobj->timeout)) {
+ sobj->timeout = 0;
+ break;
+ }
+
+ }
+
+ /* Was this the final bucket? If yes, perform sanity checks.
+ */
+ if (seen_eos) {
+ const char *cl_header;
+ apr_off_t cl;
+
+ if (r->connection->aborted || r->no_cache) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02380)
+ "Discarding body for URL %s "
+ "because connection has been aborted.",
+ h->cache_obj->key);
+ apr_pool_destroy(sobj->pool);
+ sobj->pool = NULL;
+ return APR_EGENERAL;
+ }
+
+ cl_header = apr_table_get(r->headers_out, "Content-Length");
+ if (cl_header && (!ap_parse_strict_length(&cl, cl_header)
+ || cl != sobj->body_length)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02381)
+ "URL %s didn't receive complete response, not caching",
+ h->cache_obj->key);
+ apr_pool_destroy(sobj->pool);
+ sobj->pool = NULL;
+ return APR_EGENERAL;
+ }
+
+ /* All checks were fine, we're good to go when the commit comes */
+
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t commit_entity(cache_handle_t *h, request_rec *r)
+{
+ cache_socache_conf *conf = ap_get_module_config(r->server->module_config,
+ &cache_socache_module);
+ cache_object_t *obj = h->cache_obj;
+ cache_socache_object_t *sobj = (cache_socache_object_t *) obj->vobj;
+ apr_status_t rv;
+
+ if (socache_mutex) {
+ apr_status_t status = apr_global_mutex_lock(socache_mutex);
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02384)
+ "could not acquire lock, ignoring: %s", obj->key);
+ apr_pool_destroy(sobj->pool);
+ sobj->pool = NULL;
+ return status;
+ }
+ }
+ rv = conf->provider->socache_provider->store(
+ conf->provider->socache_instance, r->server,
+ (unsigned char *) sobj->key, strlen(sobj->key), sobj->expire,
+ sobj->buffer, sobj->body_offset + sobj->body_length, sobj->pool);
+ if (socache_mutex) {
+ apr_status_t status = apr_global_mutex_unlock(socache_mutex);
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02385)
+ "could not release lock, ignoring: %s", obj->key);
+ apr_pool_destroy(sobj->pool);
+ sobj->pool = NULL;
+ return status;
+ }
+ }
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(02386)
+ "could not write to cache, ignoring: %s", sobj->key);
+ goto fail;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02387)
+ "commit_entity: Headers and body for URL %s cached for maximum of %d seconds.",
+ sobj->name, (apr_uint32_t)apr_time_sec(sobj->expire - r->request_time));
+
+ apr_pool_destroy(sobj->pool);
+ sobj->pool = NULL;
+
+ return APR_SUCCESS;
+
+fail:
+ /* For safety, remove any existing entry on failure, just in case it could not
+ * be revalidated successfully.
+ */
+ if (socache_mutex) {
+ apr_status_t status = apr_global_mutex_lock(socache_mutex);
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02388)
+ "could not acquire lock, ignoring: %s", obj->key);
+ apr_pool_destroy(sobj->pool);
+ sobj->pool = NULL;
+ return rv;
+ }
+ }
+ conf->provider->socache_provider->remove(conf->provider->socache_instance,
+ r->server, (unsigned char *) sobj->key, strlen(sobj->key), r->pool);
+ if (socache_mutex) {
+ apr_status_t status = apr_global_mutex_unlock(socache_mutex);
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02389)
+ "could not release lock, ignoring: %s", obj->key);
+ }
+ }
+
+ apr_pool_destroy(sobj->pool);
+ sobj->pool = NULL;
+ return rv;
+}
+
+static apr_status_t invalidate_entity(cache_handle_t *h, request_rec *r)
+{
+ /* mark the entity as invalidated */
+ h->cache_obj->info.control.invalidated = 1;
+
+ return commit_entity(h, r);
+}
+
+static void *create_dir_config(apr_pool_t *p, char *dummy)
+{
+ cache_socache_dir_conf *dconf =
+ apr_pcalloc(p, sizeof(cache_socache_dir_conf));
+
+ dconf->max = DEFAULT_MAX_FILE_SIZE;
+ dconf->maxtime = apr_time_from_sec(DEFAULT_MAXTIME);
+ dconf->mintime = apr_time_from_sec(DEFAULT_MINTIME);
+ dconf->readsize = DEFAULT_READSIZE;
+ dconf->readtime = DEFAULT_READTIME;
+
+ return dconf;
+}
+
+static void *merge_dir_config(apr_pool_t *p, void *basev, void *addv)
+{
+ cache_socache_dir_conf
+ *new =
+ (cache_socache_dir_conf *) apr_pcalloc(p, sizeof(cache_socache_dir_conf));
+ cache_socache_dir_conf *add = (cache_socache_dir_conf *) addv;
+ cache_socache_dir_conf *base = (cache_socache_dir_conf *) basev;
+
+ new->max = (add->max_set == 0) ? base->max : add->max;
+ new->max_set = add->max_set || base->max_set;
+ new->maxtime = (add->maxtime_set == 0) ? base->maxtime : add->maxtime;
+ new->maxtime_set = add->maxtime_set || base->maxtime_set;
+ new->mintime = (add->mintime_set == 0) ? base->mintime : add->mintime;
+ new->mintime_set = add->mintime_set || base->mintime_set;
+ new->readsize = (add->readsize_set == 0) ? base->readsize : add->readsize;
+ new->readsize_set = add->readsize_set || base->readsize_set;
+ new->readtime = (add->readtime_set == 0) ? base->readtime : add->readtime;
+ new->readtime_set = add->readtime_set || base->readtime_set;
+
+ return new;
+}
+
+static void *create_config(apr_pool_t *p, server_rec *s)
+{
+ cache_socache_conf *conf = apr_pcalloc(p, sizeof(cache_socache_conf));
+
+ return conf;
+}
+
+static void *merge_config(apr_pool_t *p, void *basev, void *overridesv)
+{
+ cache_socache_conf *ps;
+ cache_socache_conf *base = (cache_socache_conf *) basev;
+ cache_socache_conf *overrides = (cache_socache_conf *) overridesv;
+
+ /* socache server config only has one field */
+ ps = overrides ? overrides : base;
+
+ return ps;
+}
+
+/*
+ * mod_cache_socache configuration directives handlers.
+ */
+static const char *set_cache_socache(cmd_parms *cmd, void *in_struct_ptr,
+ const char *arg)
+{
+ cache_socache_conf *conf = ap_get_module_config(cmd->server->module_config,
+ &cache_socache_module);
+ cache_socache_provider_conf *provider = conf->provider
+ = apr_pcalloc(cmd->pool, sizeof(cache_socache_provider_conf));
+
+ const char *err = NULL, *sep, *name;
+
+ /* Argument is of form 'name:args' or just 'name'. */
+ sep = ap_strchr_c(arg, ':');
+ if (sep) {
+ name = apr_pstrmemdup(cmd->pool, arg, sep - arg);
+ sep++;
+ provider->args = sep;
+ }
+ else {
+ name = arg;
+ }
+
+ provider->socache_provider = ap_lookup_provider(AP_SOCACHE_PROVIDER_GROUP,
+ name, AP_SOCACHE_PROVIDER_VERSION);
+ if (provider->socache_provider == NULL) {
+ err = apr_psprintf(cmd->pool,
+ "Unknown socache provider '%s'. Maybe you need "
+ "to load the appropriate socache module "
+ "(mod_socache_%s?)", name, name);
+ }
+ return err;
+}
+
+static const char *set_cache_max(cmd_parms *parms, void *in_struct_ptr,
+ const char *arg)
+{
+ cache_socache_dir_conf *dconf = (cache_socache_dir_conf *) in_struct_ptr;
+
+ if (apr_strtoff(&dconf->max, arg, NULL, 10) != APR_SUCCESS
+ || dconf->max < 1024 || dconf->max > APR_UINT32_MAX) {
+ return "CacheSocacheMaxSize argument must be a integer representing "
+ "the max size of a cached entry (headers and body), at least 1024 "
+ "and at most " APR_STRINGIFY(APR_UINT32_MAX);
+ }
+ dconf->max_set = 1;
+ return NULL;
+}
+
+static const char *set_cache_maxtime(cmd_parms *parms, void *in_struct_ptr,
+ const char *arg)
+{
+ cache_socache_dir_conf *dconf = (cache_socache_dir_conf *) in_struct_ptr;
+ apr_off_t seconds;
+
+ if (apr_strtoff(&seconds, arg, NULL, 10) != APR_SUCCESS || seconds < 0) {
+ return "CacheSocacheMaxTime argument must be the maximum amount of time in seconds to cache an entry.";
+ }
+ dconf->maxtime = apr_time_from_sec(seconds);
+ dconf->maxtime_set = 1;
+ return NULL;
+}
+
+static const char *set_cache_mintime(cmd_parms *parms, void *in_struct_ptr,
+ const char *arg)
+{
+ cache_socache_dir_conf *dconf = (cache_socache_dir_conf *) in_struct_ptr;
+ apr_off_t seconds;
+
+ if (apr_strtoff(&seconds, arg, NULL, 10) != APR_SUCCESS || seconds < 0) {
+ return "CacheSocacheMinTime argument must be the minimum amount of time in seconds to cache an entry.";
+ }
+ dconf->mintime = apr_time_from_sec(seconds);
+ dconf->mintime_set = 1;
+ return NULL;
+}
+
+static const char *set_cache_readsize(cmd_parms *parms, void *in_struct_ptr,
+ const char *arg)
+{
+ cache_socache_dir_conf *dconf = (cache_socache_dir_conf *) in_struct_ptr;
+
+ if (apr_strtoff(&dconf->readsize, arg, NULL, 10) != APR_SUCCESS
+ || dconf->readsize < 0) {
+ return "CacheSocacheReadSize argument must be a non-negative integer representing the max amount of data to cache in go.";
+ }
+ dconf->readsize_set = 1;
+ return NULL;
+}
+
+static const char *set_cache_readtime(cmd_parms *parms, void *in_struct_ptr,
+ const char *arg)
+{
+ cache_socache_dir_conf *dconf = (cache_socache_dir_conf *) in_struct_ptr;
+ apr_off_t milliseconds;
+
+ if (apr_strtoff(&milliseconds, arg, NULL, 10) != APR_SUCCESS
+ || milliseconds < 0) {
+ return "CacheSocacheReadTime argument must be a non-negative integer representing the max amount of time taken to cache in go.";
+ }
+ dconf->readtime = apr_time_from_msec(milliseconds);
+ dconf->readtime_set = 1;
+ return NULL;
+}
+
+static apr_status_t remove_lock(void *data)
+{
+ if (socache_mutex) {
+ apr_global_mutex_destroy(socache_mutex);
+ socache_mutex = NULL;
+ }
+ return APR_SUCCESS;
+}
+
+static apr_status_t destroy_cache(void *data)
+{
+ server_rec *s = data;
+ cache_socache_conf *conf =
+ ap_get_module_config(s->module_config, &cache_socache_module);
+ if (conf->provider && conf->provider->socache_instance) {
+ conf->provider->socache_provider->destroy(
+ conf->provider->socache_instance, s);
+ conf->provider->socache_instance = NULL;
+ }
+ return APR_SUCCESS;
+}
+
+static int socache_status_hook(request_rec *r, int flags)
+{
+ apr_status_t status = APR_SUCCESS;
+ cache_socache_conf *conf = ap_get_module_config(r->server->module_config,
+ &cache_socache_module);
+ if (!conf->provider || !conf->provider->socache_provider ||
+ !conf->provider->socache_instance) {
+ return DECLINED;
+ }
+
+ if (!(flags & AP_STATUS_SHORT)) {
+ ap_rputs("<hr>\n"
+ "<table cellspacing=0 cellpadding=0>\n"
+ "<tr><td bgcolor=\"#000000\">\n"
+ "<b><font color=\"#ffffff\" face=\"Arial,Helvetica\">"
+ "mod_cache_socache Status:</font></b>\n"
+ "</td></tr>\n"
+ "<tr><td bgcolor=\"#ffffff\">\n", r);
+ }
+ else {
+ ap_rputs("ModCacheSocacheStatus\n", r);
+ }
+
+ if (socache_mutex) {
+ status = apr_global_mutex_lock(socache_mutex);
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02816)
+ "could not acquire lock for cache status");
+ }
+ }
+
+ if (status != APR_SUCCESS) {
+ if (!(flags & AP_STATUS_SHORT)) {
+ ap_rputs("No cache status data available\n", r);
+ }
+ else {
+ ap_rputs("NotAvailable\n", r);
+ }
+ } else {
+ conf->provider->socache_provider->status(conf->provider->socache_instance,
+ r, flags);
+ }
+
+ if (socache_mutex && status == APR_SUCCESS) {
+ status = apr_global_mutex_unlock(socache_mutex);
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02817)
+ "could not release lock for cache status");
+ }
+ }
+
+ if (!(flags & AP_STATUS_SHORT)) {
+ ap_rputs("</td></tr>\n</table>\n", r);
+ }
+ return OK;
+}
+
+static void socache_status_register(apr_pool_t *p)
+{
+ APR_OPTIONAL_HOOK(ap, status_hook, socache_status_hook, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+static int socache_precfg(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptmp)
+{
+ apr_status_t rv = ap_mutex_register(pconf, cache_socache_id, NULL,
+ APR_LOCK_DEFAULT, 0);
+ if (rv != APR_SUCCESS) {
+ ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog, APLOGNO(02390)
+ "failed to register %s mutex", cache_socache_id);
+ return 500; /* An HTTP status would be a misnomer! */
+ }
+
+ /* Register to handle mod_status status page generation */
+ socache_status_register(pconf);
+
+ return OK;
+}
+
+static int socache_post_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptmp, server_rec *base_server)
+{
+ server_rec *s;
+ apr_status_t rv;
+ const char *errmsg;
+ static struct ap_socache_hints socache_hints =
+ { 64, 2048, 60000000 };
+
+ for (s = base_server; s; s = s->next) {
+ cache_socache_conf *conf =
+ ap_get_module_config(s->module_config, &cache_socache_module);
+
+ if (!conf->provider) {
+ continue;
+ }
+
+ if (!socache_mutex && conf->provider->socache_provider->flags
+ & AP_SOCACHE_FLAG_NOTMPSAFE) {
+
+ rv = ap_global_mutex_create(&socache_mutex, NULL, cache_socache_id,
+ NULL, s, pconf, 0);
+ if (rv != APR_SUCCESS) {
+ ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog, APLOGNO(02391)
+ "failed to create %s mutex", cache_socache_id);
+ return 500; /* An HTTP status would be a misnomer! */
+ }
+ apr_pool_cleanup_register(pconf, NULL, remove_lock,
+ apr_pool_cleanup_null);
+ }
+
+ errmsg = conf->provider->socache_provider->create(
+ &conf->provider->socache_instance, conf->provider->args, ptmp,
+ pconf);
+ if (errmsg) {
+ ap_log_perror(APLOG_MARK, APLOG_CRIT, 0, plog,
+ APLOGNO(02392) "%s", errmsg);
+ return 500; /* An HTTP status would be a misnomer! */
+ }
+
+ rv = conf->provider->socache_provider->init(
+ conf->provider->socache_instance, cache_socache_id,
+ &socache_hints, s, pconf);
+ if (rv != APR_SUCCESS) {
+ ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog, APLOGNO(02393)
+ "failed to initialise %s cache", cache_socache_id);
+ return 500; /* An HTTP status would be a misnomer! */
+ }
+ apr_pool_cleanup_register(pconf, (void *) s, destroy_cache,
+ apr_pool_cleanup_null);
+
+ }
+
+ return OK;
+}
+
+static void socache_child_init(apr_pool_t *p, server_rec *s)
+{
+ const char *lock;
+ apr_status_t rv;
+ if (!socache_mutex) {
+ return; /* don't waste the overhead of creating mutex & cache */
+ }
+ lock = apr_global_mutex_lockfile(socache_mutex);
+ rv = apr_global_mutex_child_init(&socache_mutex, lock, p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(02394)
+ "failed to initialise mutex in child_init");
+ }
+}
+
+static const command_rec cache_socache_cmds[] =
+{
+ AP_INIT_TAKE1("CacheSocache", set_cache_socache, NULL, RSRC_CONF,
+ "The shared object cache to store cache files"),
+ AP_INIT_TAKE1("CacheSocacheMaxTime", set_cache_maxtime, NULL, RSRC_CONF | ACCESS_CONF,
+ "The maximum cache expiry age to cache a document in seconds"),
+ AP_INIT_TAKE1("CacheSocacheMinTime", set_cache_mintime, NULL, RSRC_CONF | ACCESS_CONF,
+ "The minimum cache expiry age to cache a document in seconds"),
+ AP_INIT_TAKE1("CacheSocacheMaxSize", set_cache_max, NULL, RSRC_CONF | ACCESS_CONF,
+ "The maximum cache entry size (headers and body) to cache a document"),
+ AP_INIT_TAKE1("CacheSocacheReadSize", set_cache_readsize, NULL, RSRC_CONF | ACCESS_CONF,
+ "The maximum quantity of data to attempt to read and cache in one go"),
+ AP_INIT_TAKE1("CacheSocacheReadTime", set_cache_readtime, NULL, RSRC_CONF | ACCESS_CONF,
+ "The maximum time taken to attempt to read and cache in go"),
+ { NULL }
+};
+
+static const cache_provider cache_socache_provider =
+{
+ &remove_entity, &store_headers, &store_body, &recall_headers, &recall_body,
+ &create_entity, &open_entity, &remove_url, &commit_entity,
+ &invalidate_entity
+};
+
+static void cache_socache_register_hook(apr_pool_t *p)
+{
+ /* cache initializer */
+ ap_register_provider(p, CACHE_PROVIDER_GROUP, "socache", "0",
+ &cache_socache_provider);
+ ap_hook_pre_config(socache_precfg, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_post_config(socache_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_child_init(socache_child_init, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(cache_socache) = { STANDARD20_MODULE_STUFF,
+ create_dir_config, /* create per-directory config structure */
+ merge_dir_config, /* merge per-directory config structures */
+ create_config, /* create per-server config structure */
+ merge_config, /* merge per-server config structures */
+ cache_socache_cmds, /* command apr_table_t */
+ cache_socache_register_hook /* register hooks */
+};
diff --git a/modules/cache/mod_cache_socache.dep b/modules/cache/mod_cache_socache.dep
new file mode 100644
index 0000000..d202b40
--- /dev/null
+++ b/modules/cache/mod_cache_socache.dep
@@ -0,0 +1,67 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_cache_socache.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_cache_socache.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_socache.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_date.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ "..\generators\mod_status.h"\
+ ".\cache_common.h"\
+ ".\cache_socache_common.h"\
+ ".\mod_cache.h"\
+
diff --git a/modules/cache/mod_cache_socache.dsp b/modules/cache/mod_cache_socache.dsp
new file mode 100644
index 0000000..1dd8214
--- /dev/null
+++ b/modules/cache/mod_cache_socache.dsp
@@ -0,0 +1,115 @@
+# Microsoft Developer Studio Project File - Name="mod_cache_socache" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_cache_socache - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_cache_socache.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_cache_socache.mak" CFG="mod_cache_socache - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_cache_socache - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_cache_socache - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_cache_socache - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /I "../generators" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /Fd"Release\mod_cache_socache_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_cache_socache.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_cache_socache.so" /d LONG_NAME="cache_socache_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_cache_socache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_cache_socache.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_cache_socache.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_cache_socache - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /I "../generators" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /Fd"Debug\mod_cache_socache_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_cache_socache.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_cache_socache.so" /d LONG_NAME="cache_socache_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_cache_socache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_cache_socache.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_cache_socache.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_cache_socache - Win32 Release"
+# Name "mod_cache_socache - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_cache.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\mod_cache_socache.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/cache/mod_cache_socache.mak b/modules/cache/mod_cache_socache.mak
new file mode 100644
index 0000000..7857e7f
--- /dev/null
+++ b/modules/cache/mod_cache_socache.mak
@@ -0,0 +1,381 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_cache_socache.dsp
+!IF "$(CFG)" == ""
+CFG=mod_cache_socache - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_cache_socache - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_cache_socache - Win32 Release" && "$(CFG)" != "mod_cache_socache - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_cache_socache.mak" CFG="mod_cache_socache - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_cache_socache - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_cache_socache - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_cache_socache - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_cache_socache.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_cache - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_cache_socache.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_cache - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_cache_socache.obj"
+ -@erase "$(INTDIR)\mod_cache_socache.res"
+ -@erase "$(INTDIR)\mod_cache_socache_src.idb"
+ -@erase "$(INTDIR)\mod_cache_socache_src.pdb"
+ -@erase "$(OUTDIR)\mod_cache_socache.exp"
+ -@erase "$(OUTDIR)\mod_cache_socache.lib"
+ -@erase "$(OUTDIR)\mod_cache_socache.pdb"
+ -@erase "$(OUTDIR)\mod_cache_socache.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /I "../generators" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_cache_socache_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_cache_socache.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_cache_socache.so" /d LONG_NAME="cache_socache_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_cache_socache.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_cache_socache.pdb" /debug /out:"$(OUTDIR)\mod_cache_socache.so" /implib:"$(OUTDIR)\mod_cache_socache.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_cache_socache.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_cache_socache.obj" \
+ "$(INTDIR)\mod_cache_socache.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib" \
+ "$(OUTDIR)\mod_cache.lib"
+
+"$(OUTDIR)\mod_cache_socache.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_cache_socache.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_cache_socache.so"
+ if exist .\Release\mod_cache_socache.so.manifest mt.exe -manifest .\Release\mod_cache_socache.so.manifest -outputresource:.\Release\mod_cache_socache.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_cache_socache - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_cache_socache.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_cache - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_cache_socache.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_cache - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_cache_socache.obj"
+ -@erase "$(INTDIR)\mod_cache_socache.res"
+ -@erase "$(INTDIR)\mod_cache_socache_src.idb"
+ -@erase "$(INTDIR)\mod_cache_socache_src.pdb"
+ -@erase "$(OUTDIR)\mod_cache_socache.exp"
+ -@erase "$(OUTDIR)\mod_cache_socache.lib"
+ -@erase "$(OUTDIR)\mod_cache_socache.pdb"
+ -@erase "$(OUTDIR)\mod_cache_socache.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /I "../generators" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_cache_socache_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_cache_socache.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_cache_socache.so" /d LONG_NAME="cache_socache_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_cache_socache.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_cache_socache.pdb" /debug /out:"$(OUTDIR)\mod_cache_socache.so" /implib:"$(OUTDIR)\mod_cache_socache.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_cache_socache.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_cache_socache.obj" \
+ "$(INTDIR)\mod_cache_socache.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib" \
+ "$(OUTDIR)\mod_cache.lib"
+
+"$(OUTDIR)\mod_cache_socache.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_cache_socache.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_cache_socache.so"
+ if exist .\Debug\mod_cache_socache.so.manifest mt.exe -manifest .\Debug\mod_cache_socache.so.manifest -outputresource:.\Debug\mod_cache_socache.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_cache_socache.dep")
+!INCLUDE "mod_cache_socache.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_cache_socache.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_cache_socache - Win32 Release" || "$(CFG)" == "mod_cache_socache - Win32 Debug"
+
+!IF "$(CFG)" == "mod_cache_socache - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\cache"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ELSEIF "$(CFG)" == "mod_cache_socache - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\cache"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_cache_socache - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\cache"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ELSEIF "$(CFG)" == "mod_cache_socache - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\cache"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_cache_socache - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\cache"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\cache"
+
+!ELSEIF "$(CFG)" == "mod_cache_socache - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\cache"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\cache"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_cache_socache - Win32 Release"
+
+"mod_cache - Win32 Release" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_cache.mak" CFG="mod_cache - Win32 Release"
+ cd "."
+
+"mod_cache - Win32 ReleaseCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_cache.mak" CFG="mod_cache - Win32 Release" RECURSE=1 CLEAN
+ cd "."
+
+!ELSEIF "$(CFG)" == "mod_cache_socache - Win32 Debug"
+
+"mod_cache - Win32 Debug" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_cache.mak" CFG="mod_cache - Win32 Debug"
+ cd "."
+
+"mod_cache - Win32 DebugCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_cache.mak" CFG="mod_cache - Win32 Debug" RECURSE=1 CLEAN
+ cd "."
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_cache_socache - Win32 Release"
+
+
+"$(INTDIR)\mod_cache_socache.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_cache_socache.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_cache_socache.so" /d LONG_NAME="cache_socache_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_cache_socache - Win32 Debug"
+
+
+"$(INTDIR)\mod_cache_socache.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_cache_socache.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_cache_socache.so" /d LONG_NAME="cache_socache_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_cache_socache.c
+
+"$(INTDIR)\mod_cache_socache.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/cache/mod_file_cache.c b/modules/cache/mod_file_cache.c
new file mode 100644
index 0000000..ce1db2d
--- /dev/null
+++ b/modules/cache/mod_file_cache.c
@@ -0,0 +1,414 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Author: mod_file_cache by Bill Stoddard <stoddard apache.org>
+ * Based on mod_mmap_static by Dean Gaudet <dgaudet arctic.org>
+ *
+ * v0.01: initial implementation
+ */
+
+/*
+ Documentation:
+
+ Some sites have a set of static files that are really busy, and
+ change infrequently (or even on a regular schedule). Save time
+ by caching open handles to these files. This module, unlike
+ mod_mmap_static, caches open file handles, not file content.
+ On systems (like Windows) with heavy system call overhead and
+ that have an efficient sendfile implementation, caching file handles
+ offers several advantages over caching content. First, the file system
+ can manage the memory, allowing infrequently hit cached files to
+ be paged out. Second, since caching open handles does not consume
+ significant resources, it will be possible to enable an AutoLoadCache
+ feature where static files are dynamically loaded in the cache
+ as the server runs. On systems that have file change notification,
+ this module can be enhanced to automatically garbage collect
+ cached files that change on disk.
+
+ This module should work on Unix systems that have sendfile. Place
+ cachefile directives into your configuration to direct files to
+ be cached.
+
+ cachefile /path/to/file1
+ cachefile /path/to/file2
+ ...
+
+ These files are only cached when the server is restarted, so if you
+ change the list, or if the files are changed, then you'll need to
+ restart the server.
+
+ To reiterate that point: if the files are modified *in place*
+ without restarting the server you may end up serving requests that
+ are completely bogus. You should update files by unlinking the old
+ copy and putting a new copy in place.
+
+ There's no such thing as inheriting these files across vhosts or
+ whatever... place the directives in the main server only.
+
+ Known problems:
+
+ Don't use Alias or RewriteRule to move these files around... unless
+ you feel like paying for an extra stat() on each request. This is
+ a deficiency in the Apache API that will hopefully be solved some day.
+ The file will be served out of the file handle cache, but there will be
+ an extra stat() that's a waste.
+*/
+
+#include "apr.h"
+
+#if !(APR_HAS_SENDFILE || APR_HAS_MMAP)
+#error mod_file_cache only works on systems with APR_HAS_SENDFILE or APR_HAS_MMAP
+#endif
+
+#include "apr_mmap.h"
+#include "apr_strings.h"
+#include "apr_hash.h"
+#include "apr_buckets.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#if APR_HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "http_core.h"
+
+module AP_MODULE_DECLARE_DATA file_cache_module;
+
+typedef struct {
+#if APR_HAS_SENDFILE
+ apr_file_t *file;
+#endif
+ const char *filename;
+ apr_finfo_t finfo;
+ int is_mmapped;
+#if APR_HAS_MMAP
+ apr_mmap_t *mm;
+#endif
+ char mtimestr[APR_RFC822_DATE_LEN];
+ char sizestr[21]; /* big enough to hold any 64-bit file size + null */
+} a_file;
+
+typedef struct {
+ apr_hash_t *fileht;
+} a_server_config;
+
+
+static void *create_server_config(apr_pool_t *p, server_rec *s)
+{
+ a_server_config *sconf = apr_palloc(p, sizeof(*sconf));
+
+ sconf->fileht = apr_hash_make(p);
+ return sconf;
+}
+
+static void cache_the_file(cmd_parms *cmd, const char *filename, int mmap)
+{
+ a_server_config *sconf;
+ a_file *new_file;
+ a_file tmp;
+ apr_file_t *fd = NULL;
+ apr_status_t rc;
+ const char *fspec;
+
+ fspec = ap_server_root_relative(cmd->pool, filename);
+ if (!fspec) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, APR_EBADPATH, cmd->server, APLOGNO(00794)
+ "invalid file path "
+ "%s, skipping", filename);
+ return;
+ }
+ if ((rc = apr_stat(&tmp.finfo, fspec, APR_FINFO_MIN,
+ cmd->temp_pool)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server, APLOGNO(00795)
+ "unable to stat(%s), skipping", fspec);
+ return;
+ }
+ if (tmp.finfo.filetype != APR_REG) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00796)
+ "%s isn't a regular file, skipping", fspec);
+ return;
+ }
+ if (tmp.finfo.size > AP_MAX_SENDFILE) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00797)
+ "%s is too large to cache, skipping", fspec);
+ return;
+ }
+
+ rc = apr_file_open(&fd, fspec, APR_READ | APR_BINARY | APR_XTHREAD,
+ APR_OS_DEFAULT, cmd->pool);
+ if (rc != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server, APLOGNO(00798)
+ "unable to open(%s, O_RDONLY), skipping", fspec);
+ return;
+ }
+ apr_file_inherit_set(fd);
+
+ /* WooHoo, we have a file to put in the cache */
+ new_file = apr_pcalloc(cmd->pool, sizeof(a_file));
+ new_file->finfo = tmp.finfo;
+
+#if APR_HAS_MMAP
+ if (mmap) {
+ /* MMAPFile directive. MMAP'ing the file
+ * XXX: APR_HAS_LARGE_FILES issue; need to reject this request if
+ * size is greater than MAX(apr_size_t) (perhaps greater than 1M?).
+ */
+ if ((rc = apr_mmap_create(&new_file->mm, fd, 0,
+ (apr_size_t)new_file->finfo.size,
+ APR_MMAP_READ, cmd->pool)) != APR_SUCCESS) {
+ apr_file_close(fd);
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server, APLOGNO(00799)
+ "unable to mmap %s, skipping", filename);
+ return;
+ }
+ apr_file_close(fd);
+ new_file->is_mmapped = TRUE;
+ }
+#endif
+#if APR_HAS_SENDFILE
+ if (!mmap) {
+ /* CacheFile directive. Caching the file handle */
+ new_file->is_mmapped = FALSE;
+ new_file->file = fd;
+ }
+#endif
+
+ new_file->filename = fspec;
+ apr_rfc822_date(new_file->mtimestr, new_file->finfo.mtime);
+ apr_snprintf(new_file->sizestr, sizeof new_file->sizestr, "%" APR_OFF_T_FMT, new_file->finfo.size);
+
+ sconf = ap_get_module_config(cmd->server->module_config, &file_cache_module);
+ apr_hash_set(sconf->fileht, new_file->filename, strlen(new_file->filename), new_file);
+
+}
+
+static const char *cachefilehandle(cmd_parms *cmd, void *dummy, const char *filename)
+{
+#if APR_HAS_SENDFILE
+ cache_the_file(cmd, filename, 0);
+#else
+ /* Sendfile not supported by this OS */
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00800)
+ "unable to cache file: %s. Sendfile is not supported on this OS", filename);
+#endif
+ return NULL;
+}
+static const char *cachefilemmap(cmd_parms *cmd, void *dummy, const char *filename)
+{
+#if APR_HAS_MMAP
+ cache_the_file(cmd, filename, 1);
+#else
+ /* MMAP not supported by this OS */
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00801)
+ "unable to cache file: %s. MMAP is not supported by this OS", filename);
+#endif
+ return NULL;
+}
+
+static int file_cache_post_config(apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ /* Hummm, anything to do here? */
+ return OK;
+}
+
+/* If it's one of ours, fill in r->finfo now to avoid extra stat()... this is a
+ * bit of a kludge, because we really want to run after core_translate runs.
+ */
+static int file_cache_xlat(request_rec *r)
+{
+ a_server_config *sconf;
+ a_file *match;
+ int res;
+
+ sconf = ap_get_module_config(r->server->module_config, &file_cache_module);
+
+ /* we only operate when at least one cachefile directive was used */
+ if (!apr_hash_count(sconf->fileht)) {
+ return DECLINED;
+ }
+
+ res = ap_core_translate(r);
+ if (res != OK || !r->filename) {
+ return res;
+ }
+
+ /* search the cache */
+ match = (a_file *) apr_hash_get(sconf->fileht, r->filename, APR_HASH_KEY_STRING);
+ if (match == NULL)
+ return DECLINED;
+
+ /* pass search results to handler */
+ ap_set_module_config(r->request_config, &file_cache_module, match);
+
+ /* shortcircuit the get_path_info() stat() calls and stuff */
+ r->finfo = match->finfo;
+ return OK;
+}
+
+static int mmap_handler(request_rec *r, a_file *file)
+{
+#if APR_HAS_MMAP
+ conn_rec *c = r->connection;
+ apr_bucket *b;
+ apr_mmap_t *mm;
+ apr_bucket_brigade *bb = apr_brigade_create(r->pool, c->bucket_alloc);
+
+ apr_mmap_dup(&mm, file->mm, r->pool);
+ b = apr_bucket_mmap_create(mm, 0, (apr_size_t)file->finfo.size,
+ c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ b = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+
+ if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS)
+ return AP_FILTER_ERROR;
+#endif
+ return OK;
+}
+
+static int sendfile_handler(request_rec *r, a_file *file)
+{
+#if APR_HAS_SENDFILE
+ conn_rec *c = r->connection;
+ apr_bucket *b;
+ apr_bucket_brigade *bb = apr_brigade_create(r->pool, c->bucket_alloc);
+
+ apr_brigade_insert_file(bb, file->file, 0, file->finfo.size, r->pool);
+
+ b = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+
+ if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS)
+ return AP_FILTER_ERROR;
+#endif
+ return OK;
+}
+
+static int file_cache_handler(request_rec *r)
+{
+ a_file *match;
+ int errstatus;
+ int rc = OK;
+
+ /* Bail out if r->handler isn't the default value, and doesn't look like a Content-Type
+ * XXX: Even though we made the user explicitly list each path to cache?
+ */
+ if (ap_strcmp_match(r->handler, "*/*") && !AP_IS_DEFAULT_HANDLER_NAME(r->handler)) {
+ return DECLINED;
+ }
+
+ /* we don't handle anything but GET */
+ if (r->method_number != M_GET) return DECLINED;
+
+ /* did xlat phase find the file? */
+ match = ap_get_module_config(r->request_config, &file_cache_module);
+
+ if (match == NULL) {
+ return DECLINED;
+ }
+
+ /* note that we would handle GET on this resource */
+ r->allowed |= (AP_METHOD_BIT << M_GET);
+
+ /* This handler has no use for a request body (yet), but we still
+ * need to read and discard it if the client sent one.
+ */
+ if ((errstatus = ap_discard_request_body(r)) != OK)
+ return errstatus;
+
+ ap_update_mtime(r, match->finfo.mtime);
+
+ /* ap_set_last_modified() always converts the file mtime to a string
+ * which is slow. Accelerate the common case.
+ * ap_set_last_modified(r);
+ */
+ {
+ apr_time_t mod_time;
+ char *datestr;
+
+ mod_time = ap_rationalize_mtime(r, r->mtime);
+ if (mod_time == match->finfo.mtime)
+ datestr = match->mtimestr;
+ else {
+ datestr = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
+ apr_rfc822_date(datestr, mod_time);
+ }
+ apr_table_setn(r->headers_out, "Last-Modified", datestr);
+ }
+
+ /* ap_set_content_length() always converts the same number and never
+ * returns an error. Accelerate it.
+ */
+ r->clength = match->finfo.size;
+ apr_table_setn(r->headers_out, "Content-Length", match->sizestr);
+
+ ap_set_etag(r);
+ if ((errstatus = ap_meets_conditions(r)) != OK) {
+ return errstatus;
+ }
+
+ /* Call appropriate handler */
+ if (!r->header_only) {
+ if (match->is_mmapped == TRUE)
+ rc = mmap_handler(r, match);
+ else
+ rc = sendfile_handler(r, match);
+ }
+
+ return rc;
+}
+
+static const command_rec file_cache_cmds[] =
+{
+AP_INIT_ITERATE("cachefile", cachefilehandle, NULL, RSRC_CONF,
+ "A space separated list of files to add to the file handle cache at config time"),
+AP_INIT_ITERATE("mmapfile", cachefilemmap, NULL, RSRC_CONF,
+ "A space separated list of files to mmap at config time"),
+ {NULL}
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_hook_handler(file_cache_handler, NULL, NULL, APR_HOOK_LAST);
+ ap_hook_post_config(file_cache_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_translate_name(file_cache_xlat, NULL, NULL, APR_HOOK_MIDDLE);
+ /* This trick doesn't work apparently because the translate hooks
+ are single shot. If the core_hook returns OK, then our hook is
+ not called.
+ ap_hook_translate_name(file_cache_xlat, aszPre, NULL, APR_HOOK_MIDDLE);
+ */
+
+}
+
+AP_DECLARE_MODULE(file_cache) =
+{
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ create_server_config, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ file_cache_cmds, /* command handlers */
+ register_hooks /* register hooks */
+};
diff --git a/modules/cache/mod_file_cache.dep b/modules/cache/mod_file_cache.dep
new file mode 100644
index 0000000..3d47099
--- /dev/null
+++ b/modules/cache/mod_file_cache.dep
@@ -0,0 +1,56 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_file_cache.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_file_cache.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/cache/mod_file_cache.dsp b/modules/cache/mod_file_cache.dsp
new file mode 100644
index 0000000..3c6afa4
--- /dev/null
+++ b/modules/cache/mod_file_cache.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_file_cache" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_file_cache - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_file_cache.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_file_cache.mak" CFG="mod_file_cache - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_file_cache - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_file_cache - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_file_cache - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_file_cache_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_file_cache.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_file_cache.so" /d LONG_NAME="file_cache_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 /nologo /subsystem:windows /dll /out:".\Release\mod_file_cache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_file_cache.so
+# ADD LINK32 /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_file_cache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_file_cache.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_file_cache.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_file_cache - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_file_cache_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_file_cache.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_file_cache.so" /d LONG_NAME="file_cache_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_file_cache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_file_cache.so
+# ADD LINK32 /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_file_cache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_file_cache.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_file_cache.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_file_cache - Win32 Release"
+# Name "mod_file_cache - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_file_cache.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/cache/mod_file_cache.exp b/modules/cache/mod_file_cache.exp
new file mode 100644
index 0000000..23b092a
--- /dev/null
+++ b/modules/cache/mod_file_cache.exp
@@ -0,0 +1 @@
+file_cache_module
diff --git a/modules/cache/mod_file_cache.mak b/modules/cache/mod_file_cache.mak
new file mode 100644
index 0000000..0f54dc2
--- /dev/null
+++ b/modules/cache/mod_file_cache.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_file_cache.dsp
+!IF "$(CFG)" == ""
+CFG=mod_file_cache - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_file_cache - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_file_cache - Win32 Release" && "$(CFG)" != "mod_file_cache - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_file_cache.mak" CFG="mod_file_cache - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_file_cache - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_file_cache - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_file_cache - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_file_cache.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_file_cache.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_file_cache.obj"
+ -@erase "$(INTDIR)\mod_file_cache.res"
+ -@erase "$(INTDIR)\mod_file_cache_src.idb"
+ -@erase "$(INTDIR)\mod_file_cache_src.pdb"
+ -@erase "$(OUTDIR)\mod_file_cache.exp"
+ -@erase "$(OUTDIR)\mod_file_cache.lib"
+ -@erase "$(OUTDIR)\mod_file_cache.pdb"
+ -@erase "$(OUTDIR)\mod_file_cache.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_file_cache_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_file_cache.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_file_cache.so" /d LONG_NAME="file_cache_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_file_cache.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=/nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_file_cache.pdb" /debug /out:"$(OUTDIR)\mod_file_cache.so" /implib:"$(OUTDIR)\mod_file_cache.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_file_cache.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_file_cache.obj" \
+ "$(INTDIR)\mod_file_cache.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_file_cache.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_file_cache.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_file_cache.so"
+ if exist .\Release\mod_file_cache.so.manifest mt.exe -manifest .\Release\mod_file_cache.so.manifest -outputresource:.\Release\mod_file_cache.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_file_cache - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_file_cache.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_file_cache.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_file_cache.obj"
+ -@erase "$(INTDIR)\mod_file_cache.res"
+ -@erase "$(INTDIR)\mod_file_cache_src.idb"
+ -@erase "$(INTDIR)\mod_file_cache_src.pdb"
+ -@erase "$(OUTDIR)\mod_file_cache.exp"
+ -@erase "$(OUTDIR)\mod_file_cache.lib"
+ -@erase "$(OUTDIR)\mod_file_cache.pdb"
+ -@erase "$(OUTDIR)\mod_file_cache.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_file_cache_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_file_cache.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_file_cache.so" /d LONG_NAME="file_cache_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_file_cache.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=/nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_file_cache.pdb" /debug /out:"$(OUTDIR)\mod_file_cache.so" /implib:"$(OUTDIR)\mod_file_cache.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_file_cache.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_file_cache.obj" \
+ "$(INTDIR)\mod_file_cache.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_file_cache.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_file_cache.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_file_cache.so"
+ if exist .\Debug\mod_file_cache.so.manifest mt.exe -manifest .\Debug\mod_file_cache.so.manifest -outputresource:.\Debug\mod_file_cache.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_file_cache.dep")
+!INCLUDE "mod_file_cache.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_file_cache.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_file_cache - Win32 Release" || "$(CFG)" == "mod_file_cache - Win32 Debug"
+
+!IF "$(CFG)" == "mod_file_cache - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\cache"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ELSEIF "$(CFG)" == "mod_file_cache - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\cache"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_file_cache - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\cache"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ELSEIF "$(CFG)" == "mod_file_cache - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\cache"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_file_cache - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\cache"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\cache"
+
+!ELSEIF "$(CFG)" == "mod_file_cache - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\cache"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\cache"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_file_cache - Win32 Release"
+
+
+"$(INTDIR)\mod_file_cache.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_file_cache.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_file_cache.so" /d LONG_NAME="file_cache_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_file_cache - Win32 Debug"
+
+
+"$(INTDIR)\mod_file_cache.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_file_cache.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_file_cache.so" /d LONG_NAME="file_cache_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_file_cache.c
+
+"$(INTDIR)\mod_file_cache.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/cache/mod_socache_dbm.c b/modules/cache/mod_socache_dbm.c
new file mode 100644
index 0000000..df97573
--- /dev/null
+++ b/modules/cache/mod_socache_dbm.c
@@ -0,0 +1,764 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "httpd.h"
+#include "http_log.h"
+#include "http_request.h"
+#include "http_protocol.h"
+#include "http_config.h"
+#include "mpm_common.h"
+#include "mod_status.h"
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_time.h"
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+#include "apr_dbm.h"
+
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "ap_socache.h"
+
+#if AP_NEED_SET_MUTEX_PERMS
+#include "unixd.h"
+#endif
+
+/* Use of the context structure must be thread-safe after the initial
+ * create/init; callers must hold the mutex. */
+struct ap_socache_instance_t {
+ const char *data_file;
+ /* Pool must only be used with the mutex held. */
+ apr_pool_t *pool;
+ apr_time_t last_expiry;
+ apr_interval_time_t expiry_interval;
+};
+
+/**
+ * Support for DBM library
+ */
+#define DBM_FILE_MODE ( APR_UREAD | APR_UWRITE | APR_GREAD | APR_WREAD )
+
+#define DEFAULT_DBM_PREFIX "socache-dbm-"
+
+/* ### this should use apr_dbm_usednames. */
+#if !defined(DBM_FILE_SUFFIX_DIR) && !defined(DBM_FILE_SUFFIX_PAG)
+#if defined(DBM_SUFFIX)
+#define DBM_FILE_SUFFIX_DIR DBM_SUFFIX
+#define DBM_FILE_SUFFIX_PAG DBM_SUFFIX
+#elif defined(__FreeBSD__) || (defined(DB_LOCK) && defined(DB_SHMEM))
+#define DBM_FILE_SUFFIX_DIR ".db"
+#define DBM_FILE_SUFFIX_PAG ".db"
+#else
+#define DBM_FILE_SUFFIX_DIR ".dir"
+#define DBM_FILE_SUFFIX_PAG ".pag"
+#endif
+#endif
+
+static void socache_dbm_expire(ap_socache_instance_t *ctx, server_rec *s);
+
+static apr_status_t socache_dbm_remove(ap_socache_instance_t *ctx,
+ server_rec *s, const unsigned char *id,
+ unsigned int idlen, apr_pool_t *p);
+
+static const char *socache_dbm_create(ap_socache_instance_t **context,
+ const char *arg,
+ apr_pool_t *tmp, apr_pool_t *p)
+{
+ ap_socache_instance_t *ctx;
+
+ *context = ctx = apr_pcalloc(p, sizeof *ctx);
+
+ if (arg && *arg) {
+ ctx->data_file = ap_server_root_relative(p, arg);
+ if (!ctx->data_file) {
+ return apr_psprintf(tmp, "Invalid cache file path %s", arg);
+ }
+ }
+
+ apr_pool_create(&ctx->pool, p);
+ apr_pool_tag(ctx->pool, "socache_dbm_instance");
+
+ return NULL;
+}
+
+#if AP_NEED_SET_MUTEX_PERMS
+static int try_chown(apr_pool_t *p, server_rec *s,
+ const char *name, const char *suffix)
+{
+ if (suffix)
+ name = apr_pstrcat(p, name, suffix, NULL);
+ if (-1 == chown(name, ap_unixd_config.user_id,
+ (gid_t)-1 /* no gid change */ ))
+ {
+ if (errno != ENOENT)
+ ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(errno), s, APLOGNO(00802)
+ "Can't change owner of %s", name);
+ return -1;
+ }
+ return 0;
+}
+#endif
+
+
+static apr_status_t socache_dbm_init(ap_socache_instance_t *ctx,
+ const char *namespace,
+ const struct ap_socache_hints *hints,
+ server_rec *s, apr_pool_t *p)
+{
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ const apr_dbm_driver_t *driver;
+ const apu_err_t *err;
+#endif
+ apr_dbm_t *dbm;
+ apr_status_t rv;
+
+ /* for the DBM we need the data file */
+ if (ctx->data_file == NULL) {
+ const char *path = apr_pstrcat(p, DEFAULT_DBM_PREFIX, namespace,
+ NULL);
+
+ ctx->data_file = ap_runtime_dir_relative(p, path);
+
+ if (ctx->data_file == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00803)
+ "could not use default path '%s' for DBM socache",
+ path);
+ return APR_EINVAL;
+ }
+ }
+
+ /* open it once to create it and to make sure it _can_ be created */
+ apr_pool_clear(ctx->pool);
+
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ if ((rv = apr_dbm_get_driver(&driver, NULL, &err,
+ ctx->pool) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10277)
+ "Cannot load socache DBM library '%s': %s",
+ err->reason, err->msg);
+ return rv;
+ }
+ if ((rv = apr_dbm_open2(&dbm, driver, ctx->data_file,
+ APR_DBM_RWCREATE, DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00804)
+ "Cannot create socache DBM file `%s'",
+ ctx->data_file);
+ return DECLINED;
+ }
+#else
+ if ((rv = apr_dbm_open(&dbm, ctx->data_file,
+ APR_DBM_RWCREATE, DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00804)
+ "Cannot create socache DBM file `%s'",
+ ctx->data_file);
+ return rv;
+ }
+#endif
+ apr_dbm_close(dbm);
+
+ ctx->expiry_interval = (hints && hints->expiry_interval
+ ? hints->expiry_interval : apr_time_from_sec(30));
+
+#if AP_NEED_SET_MUTEX_PERMS
+ /*
+ * We have to make sure the Apache child processes have access to
+ * the DBM file. But because there are brain-dead platforms where we
+ * cannot exactly determine the suffixes we try all possibilities.
+ */
+ if (geteuid() == 0 /* is superuser */) {
+ try_chown(p, s, ctx->data_file, NULL);
+ if (try_chown(p, s, ctx->data_file, DBM_FILE_SUFFIX_DIR))
+ if (try_chown(p, s, ctx->data_file, ".db"))
+ try_chown(p, s, ctx->data_file, ".dir");
+ if (try_chown(p, s, ctx->data_file, DBM_FILE_SUFFIX_PAG))
+ if (try_chown(p, s, ctx->data_file, ".db"))
+ try_chown(p, s, ctx->data_file, ".pag");
+ }
+#endif
+ socache_dbm_expire(ctx, s);
+
+ return APR_SUCCESS;
+}
+
+static void socache_dbm_destroy(ap_socache_instance_t *ctx, server_rec *s)
+{
+ /* the correct way */
+ unlink(apr_pstrcat(ctx->pool, ctx->data_file, DBM_FILE_SUFFIX_DIR, NULL));
+ unlink(apr_pstrcat(ctx->pool, ctx->data_file, DBM_FILE_SUFFIX_PAG, NULL));
+ /* the additional ways to be sure */
+ unlink(apr_pstrcat(ctx->pool, ctx->data_file, ".dir", NULL));
+ unlink(apr_pstrcat(ctx->pool, ctx->data_file, ".pag", NULL));
+ unlink(apr_pstrcat(ctx->pool, ctx->data_file, ".db", NULL));
+ unlink(ctx->data_file);
+}
+
+static apr_status_t socache_dbm_store(ap_socache_instance_t *ctx,
+ server_rec *s, const unsigned char *id,
+ unsigned int idlen, apr_time_t expiry,
+ unsigned char *ucaData,
+ unsigned int nData, apr_pool_t *pool)
+{
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ const apr_dbm_driver_t *driver;
+ const apu_err_t *err;
+#endif
+ apr_dbm_t *dbm;
+ apr_datum_t dbmkey;
+ apr_datum_t dbmval;
+ apr_status_t rv;
+
+ /* be careful: do not try to store too much bytes in a DBM file! */
+#ifdef PAIRMAX
+ if ((idlen + nData) >= PAIRMAX) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00805)
+ "data size too large for DBM socache: %d >= %d",
+ (idlen + nData), PAIRMAX);
+ return APR_ENOSPC;
+ }
+#else
+ if ((idlen + nData) >= 950 /* at least less than approx. 1KB */) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00806)
+ "data size too large for DBM socache: %d >= %d",
+ (idlen + nData), 950);
+ return APR_ENOSPC;
+ }
+#endif
+
+ /* create DBM key */
+ dbmkey.dptr = (char *)id;
+ dbmkey.dsize = idlen;
+
+ /* create DBM value */
+ dbmval.dsize = sizeof(apr_time_t) + nData;
+ dbmval.dptr = (char *)ap_malloc(dbmval.dsize);
+ memcpy((char *)dbmval.dptr, &expiry, sizeof(apr_time_t));
+ memcpy((char *)dbmval.dptr+sizeof(apr_time_t), ucaData, nData);
+
+ /* and store it to the DBM file */
+ apr_pool_clear(ctx->pool);
+
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ if ((rv = apr_dbm_get_driver(&driver, NULL, &err,
+ ctx->pool) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10278)
+ "Cannot load socache DBM library '%s' (store): %s",
+ err->reason, err->msg);
+ free(dbmval.dptr);
+ return rv;
+ }
+ if ((rv = apr_dbm_open2(&dbm, driver, ctx->data_file,
+ APR_DBM_RWCREATE, DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00807)
+ "Cannot open socache DBM file `%s' for writing "
+ "(store)",
+ ctx->data_file);
+ free(dbmval.dptr);
+ return rv;
+ }
+#else
+ if ((rv = apr_dbm_open(&dbm, ctx->data_file,
+ APR_DBM_RWCREATE, DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00807)
+ "Cannot open socache DBM file `%s' for writing "
+ "(store)",
+ ctx->data_file);
+ free(dbmval.dptr);
+ return rv;
+ }
+#endif
+ if ((rv = apr_dbm_store(dbm, dbmkey, dbmval)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00808)
+ "Cannot store socache object to DBM file `%s'",
+ ctx->data_file);
+ apr_dbm_close(dbm);
+ free(dbmval.dptr);
+ return rv;
+ }
+ apr_dbm_close(dbm);
+
+ /* free temporary buffers */
+ free(dbmval.dptr);
+
+ /* allow the regular expiring to occur */
+ socache_dbm_expire(ctx, s);
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t socache_dbm_retrieve(ap_socache_instance_t *ctx, server_rec *s,
+ const unsigned char *id, unsigned int idlen,
+ unsigned char *dest, unsigned int *destlen,
+ apr_pool_t *p)
+{
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ const apr_dbm_driver_t *driver;
+ const apu_err_t *err;
+#endif
+ apr_dbm_t *dbm;
+ apr_datum_t dbmkey;
+ apr_datum_t dbmval;
+ unsigned int nData;
+ apr_time_t expiry;
+ apr_time_t now;
+ apr_status_t rc;
+
+ /* allow the regular expiring to occur */
+ socache_dbm_expire(ctx, s);
+
+ /* create DBM key and values */
+ dbmkey.dptr = (char *)id;
+ dbmkey.dsize = idlen;
+
+ /* and fetch it from the DBM file
+ * XXX: Should we open the dbm against r->pool so the cleanup will
+ * do the apr_dbm_close? This would make the code a bit cleaner.
+ */
+ apr_pool_clear(ctx->pool);
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ if ((rv = apr_dbm_get_driver(&driver, NULL, &err,
+ ctx->pool) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10279)
+ "Cannot load socache DBM library '%s' (fetch): %s",
+ err->reason, err->msg);
+ return rc;
+ }
+ if ((rv = apr_dbm_open2(&dbm, driver, ctx->data_file,
+ APR_DBM_RWCREATE, DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rc, s, APLOGNO(00809)
+ "Cannot open socache DBM file `%s' for reading "
+ "(fetch)",
+ ctx->data_file);
+ return rc;
+ }
+#else
+ if ((rc = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE,
+ DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rc, s, APLOGNO(00809)
+ "Cannot open socache DBM file `%s' for reading "
+ "(fetch)",
+ ctx->data_file);
+ return rc;
+ }
+#endif
+ rc = apr_dbm_fetch(dbm, dbmkey, &dbmval);
+ if (rc != APR_SUCCESS) {
+ apr_dbm_close(dbm);
+ return APR_NOTFOUND;
+ }
+ if (dbmval.dptr == NULL || dbmval.dsize <= sizeof(apr_time_t)) {
+ apr_dbm_close(dbm);
+ return APR_EGENERAL;
+ }
+
+ /* parse resulting data */
+ nData = dbmval.dsize-sizeof(apr_time_t);
+ if (nData > *destlen) {
+ apr_dbm_close(dbm);
+ return APR_ENOSPC;
+ }
+
+ *destlen = nData;
+ memcpy(&expiry, dbmval.dptr, sizeof(apr_time_t));
+ memcpy(dest, (char *)dbmval.dptr + sizeof(apr_time_t), nData);
+
+ apr_dbm_close(dbm);
+
+ /* make sure the stuff is still not expired */
+ now = apr_time_now();
+ if (expiry <= now) {
+ socache_dbm_remove(ctx, s, id, idlen, p);
+ return APR_NOTFOUND;
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t socache_dbm_remove(ap_socache_instance_t *ctx,
+ server_rec *s, const unsigned char *id,
+ unsigned int idlen, apr_pool_t *p)
+{
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ const apr_dbm_driver_t *driver;
+ const apu_err_t *err;
+#endif
+ apr_dbm_t *dbm;
+ apr_datum_t dbmkey;
+ apr_status_t rv;
+
+ /* create DBM key and values */
+ dbmkey.dptr = (char *)id;
+ dbmkey.dsize = idlen;
+
+ /* and delete it from the DBM file */
+ apr_pool_clear(ctx->pool);
+
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ if ((rv = apr_dbm_get_driver(&driver, NULL, &err,
+ ctx->pool) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10280)
+ "Cannot load socache DBM library '%s' (delete): %s",
+ err->reason, err->msg);
+ return rv;
+ }
+ if ((rv = apr_dbm_open2(&dbm, driver, ctx->data_file,
+ APR_DBM_RWCREATE, DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00810)
+ "Cannot open socache DBM file `%s' for writing "
+ "(delete)",
+ ctx->data_file);
+ return rv;
+ }
+#else
+ if ((rv = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE,
+ DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00810)
+ "Cannot open socache DBM file `%s' for writing "
+ "(delete)",
+ ctx->data_file);
+ return rv;
+ }
+#endif
+ apr_dbm_delete(dbm, dbmkey);
+ apr_dbm_close(dbm);
+
+ return APR_SUCCESS;
+}
+
+static void socache_dbm_expire(ap_socache_instance_t *ctx, server_rec *s)
+{
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ const apr_dbm_driver_t *driver;
+ const apu_err_t *err;
+#endif
+ apr_dbm_t *dbm;
+ apr_datum_t dbmkey;
+ apr_datum_t dbmval;
+ apr_time_t expiry;
+ int elts = 0;
+ int deleted = 0;
+ int expired;
+ apr_datum_t *keylist;
+ int keyidx;
+ int i;
+ apr_time_t now;
+ apr_status_t rv;
+
+ /*
+ * make sure the expiration for still not-accessed
+ * socache entries is done only from time to time
+ */
+ now = apr_time_now();
+
+ if (now < ctx->last_expiry + ctx->expiry_interval) {
+ return;
+ }
+
+ ctx->last_expiry = now;
+
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ if ((rv = apr_dbm_get_driver(&driver, NULL, &err,
+ ctx->pool) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10281)
+ "Cannot load socache DBM library '%s' (expire): %s",
+ err->reason, err->msg);
+ return rv;
+ }
+#endif
+
+ /*
+ * Here we have to be very carefully: Not all DBM libraries are
+ * smart enough to allow one to iterate over the elements and at the
+ * same time delete expired ones. Some of them get totally crazy
+ * while others have no problems. So we have to do it the slower but
+ * more safe way: we first iterate over all elements and remember
+ * those which have to be expired. Then in a second pass we delete
+ * all those expired elements. Additionally we reopen the DBM file
+ * to be really safe in state.
+ */
+
+#define KEYMAX 1024
+
+ for (;;) {
+ /* allocate the key array in a memory sub pool */
+ apr_pool_clear(ctx->pool);
+
+ if ((keylist = apr_palloc(ctx->pool, sizeof(dbmkey)*KEYMAX)) == NULL) {
+ break;
+ }
+
+ /* pass 1: scan DBM database */
+ keyidx = 0;
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ if ((rv = apr_dbm_open2(&dbm, driver, ctx->data_file, APR_DBM_RWCREATE,
+ DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00811)
+ "Cannot open socache DBM file `%s' for "
+ "scanning",
+ ctx->data_file);
+ break;
+ }
+#else
+ if ((rv = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE,
+ DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00811)
+ "Cannot open socache DBM file `%s' for "
+ "scanning",
+ ctx->data_file);
+ break;
+ }
+#endif
+ apr_dbm_firstkey(dbm, &dbmkey);
+ while (dbmkey.dptr != NULL) {
+ elts++;
+ expired = FALSE;
+ apr_dbm_fetch(dbm, dbmkey, &dbmval);
+ if (dbmval.dsize <= sizeof(apr_time_t) || dbmval.dptr == NULL)
+ expired = TRUE;
+ else {
+ memcpy(&expiry, dbmval.dptr, sizeof(apr_time_t));
+ if (expiry <= now)
+ expired = TRUE;
+ }
+ if (expired) {
+ if ((keylist[keyidx].dptr = apr_pmemdup(ctx->pool, dbmkey.dptr, dbmkey.dsize)) != NULL) {
+ keylist[keyidx].dsize = dbmkey.dsize;
+ keyidx++;
+ if (keyidx == KEYMAX)
+ break;
+ }
+ }
+ apr_dbm_nextkey(dbm, &dbmkey);
+ }
+ apr_dbm_close(dbm);
+
+ /* pass 2: delete expired elements */
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ if (apr_dbm_open2(&dbm, driver, ctx->data_file, APR_DBM_RWCREATE,
+ DBM_FILE_MODE, ctx->pool) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00812)
+ "Cannot re-open socache DBM file `%s' for "
+ "expiring",
+ ctx->data_file);
+ break;
+ }
+#else
+ if (apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE,
+ DBM_FILE_MODE, ctx->pool) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00812)
+ "Cannot re-open socache DBM file `%s' for "
+ "expiring",
+ ctx->data_file);
+ break;
+ }
+#endif
+ for (i = 0; i < keyidx; i++) {
+ apr_dbm_delete(dbm, keylist[i]);
+ deleted++;
+ }
+ apr_dbm_close(dbm);
+
+ if (keyidx < KEYMAX)
+ break;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00813)
+ "DBM socache expiry: "
+ "old: %d, new: %d, removed: %d",
+ elts, elts-deleted, deleted);
+}
+
+static void socache_dbm_status(ap_socache_instance_t *ctx, request_rec *r,
+ int flags)
+{
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ const apr_dbm_driver_t *driver;
+ const apu_err_t *err;
+#endif
+ apr_dbm_t *dbm;
+ apr_datum_t dbmkey;
+ apr_datum_t dbmval;
+ int elts;
+ long size;
+ int avg;
+ apr_status_t rv;
+
+ elts = 0;
+ size = 0;
+
+ apr_pool_clear(ctx->pool);
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ if ((rv = apr_dbm_get_driver(&driver, NULL, &err,
+ ctx->pool) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10282)
+ "Cannot load socache DBM library '%s' (status retrieval): %s",
+ err->reason, err->msg);
+ return;
+ }
+ if ((rv = apr_dbm_open2(&dbm, driver, ctx->data_file, APR_DBM_RWCREATE,
+ DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00814)
+ "Cannot open socache DBM file `%s' for status "
+ "retrieval",
+ ctx->data_file);
+ return;
+ }
+#else
+ if ((rv = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE,
+ DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00814)
+ "Cannot open socache DBM file `%s' for status "
+ "retrieval",
+ ctx->data_file);
+ return;
+ }
+#endif
+ /*
+ * XXX - Check the return value of apr_dbm_firstkey, apr_dbm_fetch - TBD
+ */
+ apr_dbm_firstkey(dbm, &dbmkey);
+ for ( ; dbmkey.dptr != NULL; apr_dbm_nextkey(dbm, &dbmkey)) {
+ apr_dbm_fetch(dbm, dbmkey, &dbmval);
+ if (dbmval.dptr == NULL)
+ continue;
+ elts += 1;
+ size += dbmval.dsize;
+ }
+ apr_dbm_close(dbm);
+ if (size > 0 && elts > 0)
+ avg = (int)(size / (long)elts);
+ else
+ avg = 0;
+ if (!(flags & AP_STATUS_SHORT)) {
+ ap_rprintf(r, "cache type: <b>DBM</b>, maximum size: <b>unlimited</b><br>");
+ ap_rprintf(r, "current entries: <b>%d</b>, current size: <b>%ld</b> bytes<br>", elts, size);
+ ap_rprintf(r, "average entry size: <b>%d</b> bytes<br>", avg);
+ }
+ else {
+ ap_rputs("CacheType: DBM\n", r);
+ ap_rputs("CacheMaximumSize: unlimited\n", r);
+ ap_rprintf(r, "CacheCurrentEntries: %d\n", elts);
+ ap_rprintf(r, "CacheCurrentSize: %ld\n", size);
+ ap_rprintf(r, "CacheAvgEntrySize: %d\n", avg);
+ }
+}
+
+static apr_status_t socache_dbm_iterate(ap_socache_instance_t *ctx,
+ server_rec *s, void *userctx,
+ ap_socache_iterator_t *iterator,
+ apr_pool_t *pool)
+{
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ const apr_dbm_driver_t *driver;
+ const apu_err_t *err;
+#endif
+ apr_dbm_t *dbm;
+ apr_datum_t dbmkey;
+ apr_datum_t dbmval;
+ apr_time_t expiry;
+ int expired;
+ apr_time_t now;
+ apr_status_t rv;
+
+ /*
+ * make sure the expired records are omitted
+ */
+ now = apr_time_now();
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ if ((rv = apr_dbm_get_driver(&driver, NULL, &err,
+ ctx->pool) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10283)
+ "Cannot load socache DBM library '%s' (iterating): %s",
+ err->reason, err->msg);
+ return rv;
+ }
+ if ((rv = apr_dbm_open2(&dbm, driver, ctx->data_file, APR_DBM_RWCREATE,
+ DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00815)
+ "Cannot open socache DBM file `%s' for "
+ "iterating", ctx->data_file);
+ return rv;
+ }
+#else
+ if ((rv = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE,
+ DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00815)
+ "Cannot open socache DBM file `%s' for "
+ "iterating", ctx->data_file);
+ return rv;
+ }
+#endif
+ rv = apr_dbm_firstkey(dbm, &dbmkey);
+ while (rv == APR_SUCCESS && dbmkey.dptr != NULL) {
+ expired = FALSE;
+ apr_dbm_fetch(dbm, dbmkey, &dbmval);
+ if (dbmval.dsize <= sizeof(apr_time_t) || dbmval.dptr == NULL)
+ expired = TRUE;
+ else {
+ memcpy(&expiry, dbmval.dptr, sizeof(apr_time_t));
+ if (expiry <= now)
+ expired = TRUE;
+ }
+ if (!expired) {
+ rv = iterator(ctx, s, userctx,
+ (unsigned char *)dbmkey.dptr, dbmkey.dsize,
+ (unsigned char *)dbmval.dptr + sizeof(apr_time_t),
+ dbmval.dsize - sizeof(apr_time_t), pool);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(00816)
+ "dbm `%s' entry iterated", ctx->data_file);
+ if (rv != APR_SUCCESS)
+ return rv;
+ }
+ rv = apr_dbm_nextkey(dbm, &dbmkey);
+ }
+ apr_dbm_close(dbm);
+
+ if (rv != APR_SUCCESS && rv != APR_EOF) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00817)
+ "Failure reading first/next socache DBM file `%s' record",
+ ctx->data_file);
+ return rv;
+ }
+ return APR_SUCCESS;
+}
+
+static const ap_socache_provider_t socache_dbm = {
+ "dbm",
+ AP_SOCACHE_FLAG_NOTMPSAFE,
+ socache_dbm_create,
+ socache_dbm_init,
+ socache_dbm_destroy,
+ socache_dbm_store,
+ socache_dbm_retrieve,
+ socache_dbm_remove,
+ socache_dbm_status,
+ socache_dbm_iterate
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_register_provider(p, AP_SOCACHE_PROVIDER_GROUP, "dbm",
+ AP_SOCACHE_PROVIDER_VERSION,
+ &socache_dbm);
+}
+
+AP_DECLARE_MODULE(socache_dbm) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, NULL, NULL, NULL, NULL,
+ register_hooks
+};
diff --git a/modules/cache/mod_socache_dbm.dep b/modules/cache/mod_socache_dbm.dep
new file mode 100644
index 0000000..8288f2e
--- /dev/null
+++ b/modules/cache/mod_socache_dbm.dep
@@ -0,0 +1,60 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_socache_dbm.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_socache_dbm.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_mpm.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_socache.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mpm_common.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ "..\generators\mod_status.h"\
+
diff --git a/modules/cache/mod_socache_dbm.dsp b/modules/cache/mod_socache_dbm.dsp
new file mode 100644
index 0000000..4de39aa
--- /dev/null
+++ b/modules/cache/mod_socache_dbm.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_socache_dbm" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_socache_dbm - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_socache_dbm.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_socache_dbm.mak" CFG="mod_socache_dbm - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_socache_dbm - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_socache_dbm - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_socache_dbm - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "mod_socache_dbm_EXPORTS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /I "../generators" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_socache_dbm_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_socache_dbm.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_socache_dbm.so" /d LONG_NAME="socache_dbm_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_socache_dbm.so" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_dbm.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_socache_dbm.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_socache_dbm - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /I "../generators" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_socache_dbm_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_socache_dbm.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_socache_dbm.so" /d LONG_NAME="socache_dbm_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_socache_dbm.so" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_dbm.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_socache_dbm.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_socache_dbm - Win32 Release"
+# Name "mod_socache_dbm - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_socache_dbm.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/cache/mod_socache_dbm.mak b/modules/cache/mod_socache_dbm.mak
new file mode 100644
index 0000000..93453f8
--- /dev/null
+++ b/modules/cache/mod_socache_dbm.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_socache_dbm.dsp
+!IF "$(CFG)" == ""
+CFG=mod_socache_dbm - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_socache_dbm - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_socache_dbm - Win32 Release" && "$(CFG)" != "mod_socache_dbm - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_socache_dbm.mak" CFG="mod_socache_dbm - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_socache_dbm - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_socache_dbm - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_socache_dbm - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_socache_dbm.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_socache_dbm.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_socache_dbm.obj"
+ -@erase "$(INTDIR)\mod_socache_dbm.res"
+ -@erase "$(INTDIR)\mod_socache_dbm_src.idb"
+ -@erase "$(INTDIR)\mod_socache_dbm_src.pdb"
+ -@erase "$(OUTDIR)\mod_socache_dbm.exp"
+ -@erase "$(OUTDIR)\mod_socache_dbm.lib"
+ -@erase "$(OUTDIR)\mod_socache_dbm.pdb"
+ -@erase "$(OUTDIR)\mod_socache_dbm.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /I "../generators" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_socache_dbm_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_socache_dbm.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_socache_dbm.so" /d LONG_NAME="socache_dbm_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_socache_dbm.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_socache_dbm.pdb" /debug /out:"$(OUTDIR)\mod_socache_dbm.so" /implib:"$(OUTDIR)\mod_socache_dbm.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_dbm.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_socache_dbm.obj" \
+ "$(INTDIR)\mod_socache_dbm.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_socache_dbm.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_socache_dbm.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_socache_dbm.so"
+ if exist .\Release\mod_socache_dbm.so.manifest mt.exe -manifest .\Release\mod_socache_dbm.so.manifest -outputresource:.\Release\mod_socache_dbm.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_socache_dbm - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_socache_dbm.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_socache_dbm.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_socache_dbm.obj"
+ -@erase "$(INTDIR)\mod_socache_dbm.res"
+ -@erase "$(INTDIR)\mod_socache_dbm_src.idb"
+ -@erase "$(INTDIR)\mod_socache_dbm_src.pdb"
+ -@erase "$(OUTDIR)\mod_socache_dbm.exp"
+ -@erase "$(OUTDIR)\mod_socache_dbm.lib"
+ -@erase "$(OUTDIR)\mod_socache_dbm.pdb"
+ -@erase "$(OUTDIR)\mod_socache_dbm.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /I "../generators" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_socache_dbm_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_socache_dbm.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_socache_dbm.so" /d LONG_NAME="socache_dbm_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_socache_dbm.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_socache_dbm.pdb" /debug /out:"$(OUTDIR)\mod_socache_dbm.so" /implib:"$(OUTDIR)\mod_socache_dbm.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_dbm.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_socache_dbm.obj" \
+ "$(INTDIR)\mod_socache_dbm.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_socache_dbm.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_socache_dbm.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_socache_dbm.so"
+ if exist .\Debug\mod_socache_dbm.so.manifest mt.exe -manifest .\Debug\mod_socache_dbm.so.manifest -outputresource:.\Debug\mod_socache_dbm.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_socache_dbm.dep")
+!INCLUDE "mod_socache_dbm.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_socache_dbm.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_socache_dbm - Win32 Release" || "$(CFG)" == "mod_socache_dbm - Win32 Debug"
+
+!IF "$(CFG)" == "mod_socache_dbm - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\cache"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ELSEIF "$(CFG)" == "mod_socache_dbm - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\cache"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_socache_dbm - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\cache"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ELSEIF "$(CFG)" == "mod_socache_dbm - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\cache"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_socache_dbm - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\cache"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\cache"
+
+!ELSEIF "$(CFG)" == "mod_socache_dbm - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\cache"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\cache"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_socache_dbm - Win32 Release"
+
+
+"$(INTDIR)\mod_socache_dbm.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_socache_dbm.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_socache_dbm.so" /d LONG_NAME="socache_dbm_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_socache_dbm - Win32 Debug"
+
+
+"$(INTDIR)\mod_socache_dbm.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_socache_dbm.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_socache_dbm.so" /d LONG_NAME="socache_dbm_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_socache_dbm.c
+
+"$(INTDIR)\mod_socache_dbm.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/cache/mod_socache_dc.c b/modules/cache/mod_socache_dc.c
new file mode 100644
index 0000000..1fc52a7
--- /dev/null
+++ b/modules/cache/mod_socache_dc.c
@@ -0,0 +1,198 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "httpd.h"
+#include "http_log.h"
+#include "http_request.h"
+#include "http_config.h"
+#include "http_protocol.h"
+#include "mod_status.h"
+
+#include "apr_strings.h"
+#include "apr_time.h"
+
+#include "ap_socache.h"
+
+#include "distcache/dc_client.h"
+
+#if !defined(DISTCACHE_CLIENT_API) || (DISTCACHE_CLIENT_API < 0x0001)
+#error "You must compile with a more recent version of the distcache-base package"
+#endif
+
+struct ap_socache_instance_t {
+ /* Configured target server: */
+ const char *target;
+ /* distcache client context: */
+ DC_CTX *dc;
+};
+
+static const char *socache_dc_create(ap_socache_instance_t **context,
+ const char *arg,
+ apr_pool_t *tmp, apr_pool_t *p)
+{
+ struct ap_socache_instance_t *ctx;
+
+ ctx = *context = apr_palloc(p, sizeof *ctx);
+
+ ctx->target = apr_pstrdup(p, arg);
+
+ return NULL;
+}
+
+static apr_status_t socache_dc_init(ap_socache_instance_t *ctx,
+ const char *namespace,
+ const struct ap_socache_hints *hints,
+ server_rec *s, apr_pool_t *p)
+{
+#if 0
+ /* If a "persistent connection" mode of operation is preferred, you *must*
+ * also use the PIDCHECK flag to ensure fork()'d processes don't interlace
+ * comms on the same connection as each other. */
+#define SESSION_CTX_FLAGS SESSION_CTX_FLAG_PERSISTENT | \
+ SESSION_CTX_FLAG_PERSISTENT_PIDCHECK | \
+ SESSION_CTX_FLAG_PERSISTENT_RETRY | \
+ SESSION_CTX_FLAG_PERSISTENT_LATE
+#else
+ /* This mode of operation will open a temporary connection to the 'target'
+ * for each cache operation - this makes it safe against fork()
+ * automatically. This mode is preferred when running a local proxy (over
+ * unix domain sockets) because overhead is negligible and it reduces the
+ * performance/stability danger of file-descriptor bloatage. */
+#define SESSION_CTX_FLAGS 0
+#endif
+ ctx->dc = DC_CTX_new(ctx->target, SESSION_CTX_FLAGS);
+ if (!ctx->dc) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00738) "distributed scache failed to obtain context");
+ return APR_EGENERAL;
+ }
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(00739) "distributed scache context initialised");
+
+ return APR_SUCCESS;
+}
+
+static void socache_dc_destroy(ap_socache_instance_t *ctx, server_rec *s)
+{
+ if (ctx && ctx->dc) {
+ DC_CTX_free(ctx->dc);
+ ctx->dc = NULL;
+ }
+}
+
+static apr_status_t socache_dc_store(ap_socache_instance_t *ctx, server_rec *s,
+ const unsigned char *id, unsigned int idlen,
+ apr_time_t expiry,
+ unsigned char *der, unsigned int der_len,
+ apr_pool_t *p)
+{
+ /* !@#$%^ - why do we deal with *absolute* time anyway???
+ * Uhm - because most things expire things at a specific time?
+ * Were the API were thought out expiry - r->request_time is a good approximation
+ */
+ expiry -= apr_time_now();
+ /* Send the serialised session to the distributed cache context */
+ if (!DC_CTX_add_session(ctx->dc, id, idlen, der, der_len,
+ apr_time_msec(expiry))) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00740) "distributed scache 'store' failed");
+ return APR_EGENERAL;
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00741) "distributed scache 'store' successful");
+ return APR_SUCCESS;
+}
+
+static apr_status_t socache_dc_retrieve(ap_socache_instance_t *ctx, server_rec *s,
+ const unsigned char *id, unsigned int idlen,
+ unsigned char *dest, unsigned int *destlen,
+ apr_pool_t *p)
+{
+ unsigned int data_len;
+
+ /* Retrieve any corresponding session from the distributed cache context */
+ if (!DC_CTX_get_session(ctx->dc, id, idlen, dest, *destlen, &data_len)) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00742) "distributed scache 'retrieve' MISS");
+ return APR_NOTFOUND;
+ }
+ if (data_len > *destlen) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00743) "distributed scache 'retrieve' OVERFLOW");
+ return APR_ENOSPC;
+ }
+ *destlen = data_len;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00744) "distributed scache 'retrieve' HIT");
+ return APR_SUCCESS;
+}
+
+static apr_status_t socache_dc_remove(ap_socache_instance_t *ctx,
+ server_rec *s, const unsigned char *id,
+ unsigned int idlen, apr_pool_t *p)
+{
+ /* Remove any corresponding session from the distributed cache context */
+ if (!DC_CTX_remove_session(ctx->dc, id, idlen)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00745) "distributed scache 'remove' MISS");
+ return APR_NOTFOUND;
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00746) "distributed scache 'remove' HIT");
+ return APR_SUCCESS;
+ }
+}
+
+static void socache_dc_status(ap_socache_instance_t *ctx, request_rec *r, int flags)
+{
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00747)
+ "distributed scache 'socache_dc_status'");
+ if (!(flags & AP_STATUS_SHORT)) {
+ ap_rprintf(r, "cache type: <b>DC (Distributed Cache)</b>, "
+ " target: <b>%s</b><br>", ctx->target);
+ }
+ else {
+ ap_rputs("CacheType: DC\n", r);
+ ap_rvputs(r, "CacheTarget: ", ctx->target, "\n", NULL);
+ }
+}
+
+static apr_status_t socache_dc_iterate(ap_socache_instance_t *instance,
+ server_rec *s, void *userctx,
+ ap_socache_iterator_t *iterator,
+ apr_pool_t *pool)
+{
+ return APR_ENOTIMPL;
+}
+
+static const ap_socache_provider_t socache_dc = {
+ "distcache",
+ 0,
+ socache_dc_create,
+ socache_dc_init,
+ socache_dc_destroy,
+ socache_dc_store,
+ socache_dc_retrieve,
+ socache_dc_remove,
+ socache_dc_status,
+ socache_dc_iterate
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_register_provider(p, AP_SOCACHE_PROVIDER_GROUP, "dc",
+ AP_SOCACHE_PROVIDER_VERSION,
+ &socache_dc);
+}
+
+AP_DECLARE_MODULE(socache_dc) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, NULL, NULL, NULL, NULL,
+ register_hooks
+};
+
diff --git a/modules/cache/mod_socache_dc.dep b/modules/cache/mod_socache_dc.dep
new file mode 100644
index 0000000..ef969f7
--- /dev/null
+++ b/modules/cache/mod_socache_dc.dep
@@ -0,0 +1,55 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_socache_dc.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_socache_dc.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_socache.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/cache/mod_socache_dc.dsp b/modules/cache/mod_socache_dc.dsp
new file mode 100644
index 0000000..0ff315c
--- /dev/null
+++ b/modules/cache/mod_socache_dc.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_socache_dc" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_socache_dc - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_socache_dc.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_socache_dc.mak" CFG="mod_socache_dc - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_socache_dc - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_socache_dc - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_socache_dc - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "mod_socache_dc_EXPORTS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_socache_dc_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_socache_dc.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_socache_dc.so" /d LONG_NAME="socache_dc_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_socache_dc.so" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_dc.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_socache_dc.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_socache_dc - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_socache_dc_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_socache_dc.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_socache_dc.so" /d LONG_NAME="socache_dc_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_socache_dc.so" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_dc.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_socache_dc.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_socache_dc - Win32 Release"
+# Name "mod_socache_dc - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_socache_dc.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/cache/mod_socache_dc.mak b/modules/cache/mod_socache_dc.mak
new file mode 100644
index 0000000..0c4c3c0
--- /dev/null
+++ b/modules/cache/mod_socache_dc.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_socache_dc.dsp
+!IF "$(CFG)" == ""
+CFG=mod_socache_dc - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_socache_dc - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_socache_dc - Win32 Release" && "$(CFG)" != "mod_socache_dc - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_socache_dc.mak" CFG="mod_socache_dc - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_socache_dc - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_socache_dc - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_socache_dc - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_socache_dc.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_socache_dc.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_socache_dc.obj"
+ -@erase "$(INTDIR)\mod_socache_dc.res"
+ -@erase "$(INTDIR)\mod_socache_dc_src.idb"
+ -@erase "$(INTDIR)\mod_socache_dc_src.pdb"
+ -@erase "$(OUTDIR)\mod_socache_dc.exp"
+ -@erase "$(OUTDIR)\mod_socache_dc.lib"
+ -@erase "$(OUTDIR)\mod_socache_dc.pdb"
+ -@erase "$(OUTDIR)\mod_socache_dc.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_socache_dc_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_socache_dc.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_socache_dc.so" /d LONG_NAME="socache_dc_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_socache_dc.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_socache_dc.pdb" /debug /out:"$(OUTDIR)\mod_socache_dc.so" /implib:"$(OUTDIR)\mod_socache_dc.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_dc.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_socache_dc.obj" \
+ "$(INTDIR)\mod_socache_dc.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_socache_dc.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_socache_dc.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_socache_dc.so"
+ if exist .\Release\mod_socache_dc.so.manifest mt.exe -manifest .\Release\mod_socache_dc.so.manifest -outputresource:.\Release\mod_socache_dc.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_socache_dc - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_socache_dc.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_socache_dc.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_socache_dc.obj"
+ -@erase "$(INTDIR)\mod_socache_dc.res"
+ -@erase "$(INTDIR)\mod_socache_dc_src.idb"
+ -@erase "$(INTDIR)\mod_socache_dc_src.pdb"
+ -@erase "$(OUTDIR)\mod_socache_dc.exp"
+ -@erase "$(OUTDIR)\mod_socache_dc.lib"
+ -@erase "$(OUTDIR)\mod_socache_dc.pdb"
+ -@erase "$(OUTDIR)\mod_socache_dc.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_socache_dc_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_socache_dc.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_socache_dc.so" /d LONG_NAME="socache_dc_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_socache_dc.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_socache_dc.pdb" /debug /out:"$(OUTDIR)\mod_socache_dc.so" /implib:"$(OUTDIR)\mod_socache_dc.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_dc.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_socache_dc.obj" \
+ "$(INTDIR)\mod_socache_dc.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_socache_dc.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_socache_dc.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_socache_dc.so"
+ if exist .\Debug\mod_socache_dc.so.manifest mt.exe -manifest .\Debug\mod_socache_dc.so.manifest -outputresource:.\Debug\mod_socache_dc.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_socache_dc.dep")
+!INCLUDE "mod_socache_dc.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_socache_dc.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_socache_dc - Win32 Release" || "$(CFG)" == "mod_socache_dc - Win32 Debug"
+
+!IF "$(CFG)" == "mod_socache_dc - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\cache"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ELSEIF "$(CFG)" == "mod_socache_dc - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\cache"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_socache_dc - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\cache"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ELSEIF "$(CFG)" == "mod_socache_dc - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\cache"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_socache_dc - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\cache"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\cache"
+
+!ELSEIF "$(CFG)" == "mod_socache_dc - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\cache"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\cache"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_socache_dc - Win32 Release"
+
+
+"$(INTDIR)\mod_socache_dc.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_socache_dc.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_socache_dc.so" /d LONG_NAME="socache_dc_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_socache_dc - Win32 Debug"
+
+
+"$(INTDIR)\mod_socache_dc.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_socache_dc.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_socache_dc.so" /d LONG_NAME="socache_dc_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_socache_dc.c
+
+"$(INTDIR)\mod_socache_dc.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/cache/mod_socache_memcache.c b/modules/cache/mod_socache_memcache.c
new file mode 100644
index 0000000..f122ba4
--- /dev/null
+++ b/modules/cache/mod_socache_memcache.c
@@ -0,0 +1,418 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements. See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_protocol.h"
+
+#include "apr.h"
+
+#include "ap_socache.h"
+#include "ap_mpm.h"
+#include "http_log.h"
+#include "apr_memcache.h"
+#include "apr_strings.h"
+#include "mod_status.h"
+
+/* The underlying apr_memcache system is thread safe.. */
+#define MC_KEY_LEN 254
+
+#ifndef MC_DEFAULT_SERVER_PORT
+#define MC_DEFAULT_SERVER_PORT 11211
+#endif
+
+
+#ifndef MC_DEFAULT_SERVER_MIN
+#define MC_DEFAULT_SERVER_MIN 0
+#endif
+
+#ifndef MC_DEFAULT_SERVER_SMAX
+#define MC_DEFAULT_SERVER_SMAX 1
+#endif
+
+#ifndef MC_DEFAULT_SERVER_TTL
+#define MC_DEFAULT_SERVER_TTL apr_time_from_sec(15)
+#endif
+
+module AP_MODULE_DECLARE_DATA socache_memcache_module;
+
+typedef struct {
+ apr_uint32_t ttl;
+} socache_mc_svr_cfg;
+
+struct ap_socache_instance_t {
+ const char *servers;
+ apr_memcache_t *mc;
+ const char *tag;
+ apr_size_t taglen; /* strlen(tag) + 1 */
+};
+
+static const char *socache_mc_create(ap_socache_instance_t **context,
+ const char *arg,
+ apr_pool_t *tmp, apr_pool_t *p)
+{
+ ap_socache_instance_t *ctx;
+
+ *context = ctx = apr_palloc(p, sizeof *ctx);
+
+ if (!arg || !*arg) {
+ return "List of server names required to create memcache socache.";
+ }
+
+ ctx->servers = apr_pstrdup(p, arg);
+
+ return NULL;
+}
+
+static apr_status_t socache_mc_init(ap_socache_instance_t *ctx,
+ const char *namespace,
+ const struct ap_socache_hints *hints,
+ server_rec *s, apr_pool_t *p)
+{
+ apr_status_t rv;
+ int thread_limit = 0;
+ apr_uint16_t nservers = 0;
+ char *cache_config;
+ char *split;
+ char *tok;
+
+ socache_mc_svr_cfg *sconf = ap_get_module_config(s->module_config,
+ &socache_memcache_module);
+
+ ap_mpm_query(AP_MPMQ_HARD_LIMIT_THREADS, &thread_limit);
+
+ /* Find all the servers in the first run to get a total count */
+ cache_config = apr_pstrdup(p, ctx->servers);
+ split = apr_strtok(cache_config, ",", &tok);
+ while (split) {
+ nservers++;
+ split = apr_strtok(NULL,",", &tok);
+ }
+
+ rv = apr_memcache_create(p, nservers, 0, &ctx->mc);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00785)
+ "Failed to create Memcache Object of '%d' size.",
+ nservers);
+ return rv;
+ }
+
+ /* Now add each server to the memcache */
+ cache_config = apr_pstrdup(p, ctx->servers);
+ split = apr_strtok(cache_config, ",", &tok);
+ while (split) {
+ apr_memcache_server_t *st;
+ char *host_str;
+ char *scope_id;
+ apr_port_t port;
+
+ rv = apr_parse_addr_port(&host_str, &scope_id, &port, split, p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00786)
+ "Failed to Parse memcache Server: '%s'", split);
+ return rv;
+ }
+
+ if (host_str == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00787)
+ "Failed to Parse Server, "
+ "no hostname specified: '%s'", split);
+ return APR_EINVAL;
+ }
+
+ if (port == 0) {
+ port = MC_DEFAULT_SERVER_PORT;
+ }
+
+ rv = apr_memcache_server_create(p,
+ host_str, port,
+ MC_DEFAULT_SERVER_MIN,
+ MC_DEFAULT_SERVER_SMAX,
+ thread_limit,
+ sconf->ttl,
+ &st);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00788)
+ "Failed to Create memcache Server: %s:%d",
+ host_str, port);
+ return rv;
+ }
+
+ rv = apr_memcache_add_server(ctx->mc, st);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00789)
+ "Failed to Add memcache Server: %s:%d",
+ host_str, port);
+ return rv;
+ }
+
+ split = apr_strtok(NULL,",", &tok);
+ }
+
+ ctx->tag = apr_pstrcat(p, namespace, ":", NULL);
+ ctx->taglen = strlen(ctx->tag) + 1;
+
+ /* socache API constraint: */
+ AP_DEBUG_ASSERT(ctx->taglen <= 16);
+
+ return APR_SUCCESS;
+}
+
+static void socache_mc_destroy(ap_socache_instance_t *context, server_rec *s)
+{
+ /* noop. */
+}
+
+/* Converts (binary) id into a key prefixed by the predetermined
+ * namespace tag; writes output to key buffer. Returns non-zero if
+ * the id won't fit in the key buffer. */
+static int socache_mc_id2key(ap_socache_instance_t *ctx,
+ const unsigned char *id, unsigned int idlen,
+ char *key, apr_size_t keylen)
+{
+ char *cp;
+
+ if (idlen * 2 + ctx->taglen >= keylen)
+ return 1;
+
+ cp = apr_cpystrn(key, ctx->tag, ctx->taglen);
+ ap_bin2hex(id, idlen, cp);
+
+ return 0;
+}
+
+static apr_status_t socache_mc_store(ap_socache_instance_t *ctx, server_rec *s,
+ const unsigned char *id, unsigned int idlen,
+ apr_time_t expiry,
+ unsigned char *ucaData, unsigned int nData,
+ apr_pool_t *p)
+{
+ char buf[MC_KEY_LEN];
+ apr_status_t rv;
+
+ if (socache_mc_id2key(ctx, id, idlen, buf, sizeof buf)) {
+ return APR_EINVAL;
+ }
+
+ /* memcache needs time in seconds till expiry; fail if this is not
+ * positive *before* casting to unsigned (apr_uint32_t). */
+ expiry -= apr_time_now();
+ if (apr_time_sec(expiry) <= 0) {
+ return APR_EINVAL;
+ }
+ rv = apr_memcache_set(ctx->mc, buf, (char*)ucaData, nData,
+ apr_time_sec(expiry), 0);
+
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00790)
+ "scache_mc: error setting key '%s' "
+ "with %d bytes of data", buf, nData);
+ return rv;
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t socache_mc_retrieve(ap_socache_instance_t *ctx, server_rec *s,
+ const unsigned char *id, unsigned int idlen,
+ unsigned char *dest, unsigned int *destlen,
+ apr_pool_t *p)
+{
+ apr_size_t data_len;
+ char buf[MC_KEY_LEN], *data;
+ apr_status_t rv;
+
+ if (socache_mc_id2key(ctx, id, idlen, buf, sizeof buf)) {
+ return APR_EINVAL;
+ }
+
+ /* ### this could do with a subpool, but _getp looks like it will
+ * eat memory like it's going out of fashion anyway. */
+
+ rv = apr_memcache_getp(ctx->mc, p, buf, &data, &data_len, NULL);
+ if (rv) {
+ if (rv != APR_NOTFOUND) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00791)
+ "scache_mc: 'retrieve' FAIL");
+ }
+ return rv;
+ }
+ else if (data_len > *destlen) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00792)
+ "scache_mc: 'retrieve' OVERFLOW");
+ return APR_ENOMEM;
+ }
+
+ memcpy(dest, data, data_len);
+ *destlen = data_len;
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t socache_mc_remove(ap_socache_instance_t *ctx, server_rec *s,
+ const unsigned char *id,
+ unsigned int idlen, apr_pool_t *p)
+{
+ char buf[MC_KEY_LEN];
+ apr_status_t rv;
+
+ if (socache_mc_id2key(ctx, id, idlen, buf, sizeof buf)) {
+ return APR_EINVAL;
+ }
+
+ rv = apr_memcache_delete(ctx->mc, buf, 0);
+
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(00793)
+ "scache_mc: error deleting key '%s' ",
+ buf);
+ }
+
+ return rv;
+}
+
+static void socache_mc_status(ap_socache_instance_t *ctx, request_rec *r, int flags)
+{
+ apr_memcache_t *rc = ctx->mc;
+ int i;
+
+ for (i = 0; i < rc->ntotal; i++) {
+ apr_memcache_server_t *ms;
+ apr_memcache_stats_t *stats;
+ apr_status_t rv;
+ char *br = (!(flags & AP_STATUS_SHORT) ? "<br />" : "");
+
+ ms = rc->live_servers[i];
+
+ ap_rprintf(r, "Memcached server: %s:%d [%s]%s\n", ms->host, (int)ms->port,
+ (ms->status == APR_MC_SERVER_LIVE) ? "Up" : "Down",
+ br);
+ rv = apr_memcache_stats(ms, r->pool, &stats);
+ if (rv != APR_SUCCESS)
+ continue;
+ if (!(flags & AP_STATUS_SHORT)) {
+ ap_rprintf(r, "<b>Version:</b> <i>%s</i> [%u bits], PID: <i>%u</i>, Uptime: <i>%u hrs</i> <br />\n",
+ stats->version , stats->pointer_size, stats->pid, stats->uptime/3600);
+ ap_rprintf(r, "<b>Clients::</b> Structures: <i>%u</i>, Total: <i>%u</i>, Current: <i>%u</i> <br />\n",
+ stats->connection_structures, stats->total_connections, stats->curr_connections);
+ ap_rprintf(r, "<b>Storage::</b> Total Items: <i>%u</i>, Current Items: <i>%u</i>, Bytes: <i>%" APR_UINT64_T_FMT "</i> <br />\n",
+ stats->total_items, stats->curr_items, stats->bytes);
+ ap_rprintf(r, "<b>CPU::</b> System: <i>%u</i>, User: <i>%u</i> <br />\n",
+ (unsigned)stats->rusage_system, (unsigned)stats->rusage_user );
+ ap_rprintf(r, "<b>Cache::</b> Gets: <i>%u</i>, Sets: <i>%u</i>, Hits: <i>%u</i>, Misses: <i>%u</i> <br />\n",
+ stats->cmd_get, stats->cmd_set, stats->get_hits, stats->get_misses);
+ ap_rprintf(r, "<b>Net::</b> Input bytes: <i>%" APR_UINT64_T_FMT "</i>, Output bytes: <i>%" APR_UINT64_T_FMT "</i> <br />\n",
+ stats->bytes_read, stats->bytes_written);
+ ap_rprintf(r, "<b>Misc::</b> Evictions: <i>%" APR_UINT64_T_FMT "</i>, MaxMem: <i>%u</i>, Threads: <i>%u</i> <br />\n",
+ stats->evictions, stats->limit_maxbytes, stats->threads);
+ ap_rputs("<hr><br />\n", r);
+ }
+ else {
+ ap_rprintf(r, "Version: %s [%u bits], PID: %u, Uptime: %u hrs %s\n",
+ stats->version , stats->pointer_size, stats->pid, stats->uptime/3600, br);
+ ap_rprintf(r, "Clients:: Structures: %d, Total: %d, Current: %u %s\n",
+ stats->connection_structures, stats->total_connections, stats->curr_connections, br);
+ ap_rprintf(r, "Storage:: Total Items: %u, Current Items: %u, Bytes: %" APR_UINT64_T_FMT " %s\n",
+ stats->total_items, stats->curr_items, stats->bytes, br);
+ ap_rprintf(r, "CPU:: System: %u, User: %u %s\n",
+ (unsigned)stats->rusage_system, (unsigned)stats->rusage_user , br);
+ ap_rprintf(r, "Cache:: Gets: %u, Sets: %u, Hits: %u, Misses: %u %s\n",
+ stats->cmd_get, stats->cmd_set, stats->get_hits, stats->get_misses, br);
+ ap_rprintf(r, "Net:: Input bytes: %" APR_UINT64_T_FMT ", Output bytes: %" APR_UINT64_T_FMT " %s\n",
+ stats->bytes_read, stats->bytes_written, br);
+ ap_rprintf(r, "Misc:: Evictions: %" APR_UINT64_T_FMT ", MaxMem: %u, Threads: %u %s\n",
+ stats->evictions, stats->limit_maxbytes, stats->threads, br);
+ }
+ }
+
+}
+
+static apr_status_t socache_mc_iterate(ap_socache_instance_t *instance,
+ server_rec *s, void *userctx,
+ ap_socache_iterator_t *iterator,
+ apr_pool_t *pool)
+{
+ return APR_ENOTIMPL;
+}
+
+static const ap_socache_provider_t socache_mc = {
+ "memcache",
+ 0,
+ socache_mc_create,
+ socache_mc_init,
+ socache_mc_destroy,
+ socache_mc_store,
+ socache_mc_retrieve,
+ socache_mc_remove,
+ socache_mc_status,
+ socache_mc_iterate
+};
+
+static void *create_server_config(apr_pool_t *p, server_rec *s)
+{
+ socache_mc_svr_cfg *sconf = apr_pcalloc(p, sizeof(socache_mc_svr_cfg));
+
+ sconf->ttl = MC_DEFAULT_SERVER_TTL;
+
+ return sconf;
+}
+
+static const char *socache_mc_set_ttl(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ apr_interval_time_t ttl;
+ socache_mc_svr_cfg *sconf = ap_get_module_config(cmd->server->module_config,
+ &socache_memcache_module);
+
+ if (ap_timeout_parameter_parse(arg, &ttl, "s") != APR_SUCCESS) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ " has wrong format", NULL);
+ }
+
+ if ((ttl < apr_time_from_sec(0)) || (ttl > apr_time_from_sec(3600))) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ " can only be 0 or up to one hour.", NULL);
+ }
+
+ /* apr_memcache_server_create needs a ttl in usec. */
+ sconf->ttl = ttl;
+
+ return NULL;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_register_provider(p, AP_SOCACHE_PROVIDER_GROUP, "memcache",
+ AP_SOCACHE_PROVIDER_VERSION,
+ &socache_mc);
+}
+
+static const command_rec socache_memcache_cmds[] = {
+ AP_INIT_TAKE1("MemcacheConnTTL", socache_mc_set_ttl, NULL, RSRC_CONF,
+ "TTL used for the connection with the memcache server(s)"),
+ { NULL }
+};
+
+AP_DECLARE_MODULE(socache_memcache) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-dir config structures */
+ NULL, /* merge per-dir config structures */
+ create_server_config, /* create per-server config structures */
+ NULL, /* merge per-server config structures */
+ socache_memcache_cmds, /* table of config file commands */
+ register_hooks /* register hooks */
+};
diff --git a/modules/cache/mod_socache_memcache.dep b/modules/cache/mod_socache_memcache.dep
new file mode 100644
index 0000000..2929c5e
--- /dev/null
+++ b/modules/cache/mod_socache_memcache.dep
@@ -0,0 +1,59 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_socache_memcache.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_socache_memcache.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_mpm.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_socache.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_memcache.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_reslist.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr-util\include\apu_version.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_version.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/cache/mod_socache_memcache.dsp b/modules/cache/mod_socache_memcache.dsp
new file mode 100644
index 0000000..d530826
--- /dev/null
+++ b/modules/cache/mod_socache_memcache.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_socache_memcache" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_socache_memcache - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_socache_memcache.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_socache_memcache.mak" CFG="mod_socache_memcache - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_socache_memcache - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_socache_memcache - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_socache_memcache - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "mod_socache_memcache_EXPORTS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /I "../generators" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_socache_memcache_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_socache_memcache.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_socache_memcache.so" /d LONG_NAME="socache_memcache_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_socache_memcache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_memcache.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_socache_memcache.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_socache_memcache - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /I "../generators" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_socache_memcache_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_socache_memcache.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_socache_memcache.so" /d LONG_NAME="socache_memcache_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_socache_memcache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_memcache.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_socache_memcache.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_socache_memcache - Win32 Release"
+# Name "mod_socache_memcache - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_socache_memcache.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/cache/mod_socache_memcache.mak b/modules/cache/mod_socache_memcache.mak
new file mode 100644
index 0000000..b75a9a1
--- /dev/null
+++ b/modules/cache/mod_socache_memcache.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_socache_memcache.dsp
+!IF "$(CFG)" == ""
+CFG=mod_socache_memcache - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_socache_memcache - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_socache_memcache - Win32 Release" && "$(CFG)" != "mod_socache_memcache - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_socache_memcache.mak" CFG="mod_socache_memcache - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_socache_memcache - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_socache_memcache - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_socache_memcache - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_socache_memcache.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_socache_memcache.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_socache_memcache.obj"
+ -@erase "$(INTDIR)\mod_socache_memcache.res"
+ -@erase "$(INTDIR)\mod_socache_memcache_src.idb"
+ -@erase "$(INTDIR)\mod_socache_memcache_src.pdb"
+ -@erase "$(OUTDIR)\mod_socache_memcache.exp"
+ -@erase "$(OUTDIR)\mod_socache_memcache.lib"
+ -@erase "$(OUTDIR)\mod_socache_memcache.pdb"
+ -@erase "$(OUTDIR)\mod_socache_memcache.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /I "../generators" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_socache_memcache_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_socache_memcache.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_socache_memcache.so" /d LONG_NAME="socache_memcache_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_socache_memcache.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_socache_memcache.pdb" /debug /out:"$(OUTDIR)\mod_socache_memcache.so" /implib:"$(OUTDIR)\mod_socache_memcache.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_memcache.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_socache_memcache.obj" \
+ "$(INTDIR)\mod_socache_memcache.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_socache_memcache.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_socache_memcache.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_socache_memcache.so"
+ if exist .\Release\mod_socache_memcache.so.manifest mt.exe -manifest .\Release\mod_socache_memcache.so.manifest -outputresource:.\Release\mod_socache_memcache.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_socache_memcache - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_socache_memcache.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_socache_memcache.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_socache_memcache.obj"
+ -@erase "$(INTDIR)\mod_socache_memcache.res"
+ -@erase "$(INTDIR)\mod_socache_memcache_src.idb"
+ -@erase "$(INTDIR)\mod_socache_memcache_src.pdb"
+ -@erase "$(OUTDIR)\mod_socache_memcache.exp"
+ -@erase "$(OUTDIR)\mod_socache_memcache.lib"
+ -@erase "$(OUTDIR)\mod_socache_memcache.pdb"
+ -@erase "$(OUTDIR)\mod_socache_memcache.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /I "../generators" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_socache_memcache_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_socache_memcache.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_socache_memcache.so" /d LONG_NAME="socache_memcache_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_socache_memcache.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_socache_memcache.pdb" /debug /out:"$(OUTDIR)\mod_socache_memcache.so" /implib:"$(OUTDIR)\mod_socache_memcache.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_memcache.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_socache_memcache.obj" \
+ "$(INTDIR)\mod_socache_memcache.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_socache_memcache.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_socache_memcache.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_socache_memcache.so"
+ if exist .\Debug\mod_socache_memcache.so.manifest mt.exe -manifest .\Debug\mod_socache_memcache.so.manifest -outputresource:.\Debug\mod_socache_memcache.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_socache_memcache.dep")
+!INCLUDE "mod_socache_memcache.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_socache_memcache.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_socache_memcache - Win32 Release" || "$(CFG)" == "mod_socache_memcache - Win32 Debug"
+
+!IF "$(CFG)" == "mod_socache_memcache - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\cache"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ELSEIF "$(CFG)" == "mod_socache_memcache - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\cache"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_socache_memcache - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\cache"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ELSEIF "$(CFG)" == "mod_socache_memcache - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\cache"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_socache_memcache - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\cache"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\cache"
+
+!ELSEIF "$(CFG)" == "mod_socache_memcache - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\cache"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\cache"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_socache_memcache - Win32 Release"
+
+
+"$(INTDIR)\mod_socache_memcache.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_socache_memcache.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_socache_memcache.so" /d LONG_NAME="socache_memcache_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_socache_memcache - Win32 Debug"
+
+
+"$(INTDIR)\mod_socache_memcache.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_socache_memcache.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_socache_memcache.so" /d LONG_NAME="socache_memcache_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_socache_memcache.c
+
+"$(INTDIR)\mod_socache_memcache.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/cache/mod_socache_redis.c b/modules/cache/mod_socache_redis.c
new file mode 100644
index 0000000..450f406
--- /dev/null
+++ b/modules/cache/mod_socache_redis.c
@@ -0,0 +1,486 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements. See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_protocol.h"
+
+#include "apr.h"
+#include "apu_version.h"
+
+#include "ap_socache.h"
+#include "ap_mpm.h"
+#include "http_log.h"
+#include "apr_strings.h"
+#include "mod_status.h"
+
+typedef struct {
+ apr_uint32_t ttl;
+ apr_uint32_t rwto;
+} socache_rd_svr_cfg;
+
+/* apr_redis support requires >= 1.6 */
+#if APU_MAJOR_VERSION > 1 || \
+ (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION > 5)
+#define HAVE_APU_REDIS 1
+#endif
+
+/* The underlying apr_redis system is thread safe.. */
+#define RD_KEY_LEN 254
+
+#ifndef RD_DEFAULT_SERVER_PORT
+#define RD_DEFAULT_SERVER_PORT 6379
+#endif
+
+
+#ifndef RD_DEFAULT_SERVER_MIN
+#define RD_DEFAULT_SERVER_MIN 0
+#endif
+
+#ifndef RD_DEFAULT_SERVER_SMAX
+#define RD_DEFAULT_SERVER_SMAX 1
+#endif
+
+#ifndef RD_DEFAULT_SERVER_TTL
+#define RD_DEFAULT_SERVER_TTL apr_time_from_sec(15)
+#endif
+
+#ifndef RD_DEFAULT_SERVER_RWTO
+#define RD_DEFAULT_SERVER_RWTO apr_time_from_sec(5)
+#endif
+
+module AP_MODULE_DECLARE_DATA socache_redis_module;
+
+#ifdef HAVE_APU_REDIS
+#include "apr_redis.h"
+struct ap_socache_instance_t {
+ const char *servers;
+ apr_redis_t *rc;
+ const char *tag;
+ apr_size_t taglen; /* strlen(tag) + 1 */
+};
+
+static const char *socache_rd_create(ap_socache_instance_t **context,
+ const char *arg,
+ apr_pool_t *tmp, apr_pool_t *p)
+{
+ ap_socache_instance_t *ctx;
+
+ *context = ctx = apr_pcalloc(p, sizeof *ctx);
+
+ if (!arg || !*arg) {
+ return "List of server names required to create redis socache.";
+ }
+
+ ctx->servers = apr_pstrdup(p, arg);
+
+ return NULL;
+}
+
+static apr_status_t socache_rd_init(ap_socache_instance_t *ctx,
+ const char *namespace,
+ const struct ap_socache_hints *hints,
+ server_rec *s, apr_pool_t *p)
+{
+ apr_status_t rv;
+ int thread_limit = 0;
+ apr_uint16_t nservers = 0;
+ char *cache_config;
+ char *split;
+ char *tok;
+
+ socache_rd_svr_cfg *sconf = ap_get_module_config(s->module_config,
+ &socache_redis_module);
+
+ ap_mpm_query(AP_MPMQ_HARD_LIMIT_THREADS, &thread_limit);
+
+ /* Find all the servers in the first run to get a total count */
+ cache_config = apr_pstrdup(p, ctx->servers);
+ split = apr_strtok(cache_config, ",", &tok);
+ while (split) {
+ nservers++;
+ split = apr_strtok(NULL,",", &tok);
+ }
+
+ rv = apr_redis_create(p, nservers, 0, &ctx->rc);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(03473)
+ "Failed to create Redis Object of '%d' size.",
+ nservers);
+ return rv;
+ }
+
+ /* Now add each server to the redis */
+ cache_config = apr_pstrdup(p, ctx->servers);
+ split = apr_strtok(cache_config, ",", &tok);
+ while (split) {
+ apr_redis_server_t *st;
+ char *host_str;
+ char *scope_id;
+ apr_port_t port;
+
+ rv = apr_parse_addr_port(&host_str, &scope_id, &port, split, p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(03474)
+ "Failed to Parse redis Server: '%s'", split);
+ return rv;
+ }
+
+ if (host_str == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(03475)
+ "Failed to Parse Server, "
+ "no hostname specified: '%s'", split);
+ return APR_EINVAL;
+ }
+
+ if (port == 0) {
+ port = RD_DEFAULT_SERVER_PORT;
+ }
+
+ rv = apr_redis_server_create(p,
+ host_str, port,
+ RD_DEFAULT_SERVER_MIN,
+ RD_DEFAULT_SERVER_SMAX,
+ thread_limit,
+ sconf->ttl,
+ sconf->rwto,
+ &st);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(03476)
+ "Failed to Create redis Server: %s:%d",
+ host_str, port);
+ return rv;
+ }
+
+ rv = apr_redis_add_server(ctx->rc, st);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(03477)
+ "Failed to Add redis Server: %s:%d",
+ host_str, port);
+ return rv;
+ }
+
+ split = apr_strtok(NULL,",", &tok);
+ }
+
+ ctx->tag = apr_pstrcat(p, namespace, ":", NULL);
+ ctx->taglen = strlen(ctx->tag) + 1;
+
+ /* socache API constraint: */
+ AP_DEBUG_ASSERT(ctx->taglen <= 16);
+
+ return APR_SUCCESS;
+}
+
+static void socache_rd_destroy(ap_socache_instance_t *context, server_rec *s)
+{
+ /* noop. */
+}
+
+/* Converts (binary) id into a key prefixed by the predetermined
+ * namespace tag; writes output to key buffer. Returns non-zero if
+ * the id won't fit in the key buffer. */
+static int socache_rd_id2key(ap_socache_instance_t *ctx,
+ const unsigned char *id, unsigned int idlen,
+ char *key, apr_size_t keylen)
+{
+ char *cp;
+
+ if (idlen * 2 + ctx->taglen >= keylen)
+ return 1;
+
+ cp = apr_cpystrn(key, ctx->tag, ctx->taglen);
+ ap_bin2hex(id, idlen, cp);
+
+ return 0;
+}
+
+static apr_status_t socache_rd_store(ap_socache_instance_t *ctx, server_rec *s,
+ const unsigned char *id, unsigned int idlen,
+ apr_time_t expiry,
+ unsigned char *ucaData, unsigned int nData,
+ apr_pool_t *p)
+{
+ char buf[RD_KEY_LEN];
+ apr_status_t rv;
+ apr_uint32_t timeout;
+
+ if (socache_rd_id2key(ctx, id, idlen, buf, sizeof(buf))) {
+ return APR_EINVAL;
+ }
+ timeout = apr_time_sec(expiry - apr_time_now());
+ if (timeout <= 0) {
+ return APR_EINVAL;
+ }
+
+ rv = apr_redis_setex(ctx->rc, buf, (char*)ucaData, nData, timeout, 0);
+
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(03478)
+ "scache_rd: error setting key '%s' "
+ "with %d bytes of data", buf, nData);
+ return rv;
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t socache_rd_retrieve(ap_socache_instance_t *ctx, server_rec *s,
+ const unsigned char *id, unsigned int idlen,
+ unsigned char *dest, unsigned int *destlen,
+ apr_pool_t *p)
+{
+ apr_size_t data_len;
+ char buf[RD_KEY_LEN], *data;
+ apr_status_t rv;
+
+ if (socache_rd_id2key(ctx, id, idlen, buf, sizeof buf)) {
+ return APR_EINVAL;
+ }
+
+ /* ### this could do with a subpool, but _getp looks like it will
+ * eat memory like it's going out of fashion anyway. */
+
+ rv = apr_redis_getp(ctx->rc, p, buf, &data, &data_len, NULL);
+ if (rv) {
+ if (rv != APR_NOTFOUND) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(03479)
+ "scache_rd: 'retrieve' FAIL");
+ }
+ return rv;
+ }
+ else if (data_len > *destlen) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(03480)
+ "scache_rd: 'retrieve' OVERFLOW");
+ return APR_ENOMEM;
+ }
+
+ memcpy(dest, data, data_len);
+ *destlen = data_len;
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t socache_rd_remove(ap_socache_instance_t *ctx, server_rec *s,
+ const unsigned char *id,
+ unsigned int idlen, apr_pool_t *p)
+{
+ char buf[RD_KEY_LEN];
+ apr_status_t rv;
+
+ if (socache_rd_id2key(ctx, id, idlen, buf, sizeof buf)) {
+ return APR_EINVAL;
+ }
+
+ rv = apr_redis_delete(ctx->rc, buf, 0);
+
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(03481)
+ "scache_rd: error deleting key '%s' ",
+ buf);
+ }
+
+ return rv;
+}
+
+static void socache_rd_status(ap_socache_instance_t *ctx, request_rec *r, int flags)
+{
+ apr_redis_t *rc = ctx->rc;
+ int i;
+
+ for (i = 0; i < rc->ntotal; i++) {
+ apr_redis_server_t *rs;
+ apr_redis_stats_t *stats;
+ char *role;
+ apr_status_t rv;
+ char *br = (!(flags & AP_STATUS_SHORT) ? "<br />" : "");
+
+ rs = rc->live_servers[i];
+
+ ap_rprintf(r, "Redis server: %s:%d [%s]%s\n", rs->host, (int)rs->port,
+ (rs->status == APR_RC_SERVER_LIVE) ? "Up" : "Down",
+ br);
+ rv = apr_redis_stats(rs, r->pool, &stats);
+ if (rv != APR_SUCCESS)
+ continue;
+ if (!(flags & AP_STATUS_SHORT)) {
+ ap_rprintf(r, "<b>General::</b> Version: <i>%u.%u.%u</i> [%u bits], PID: <i>%u</i>, Uptime: <i>%u hrs</i> <br />\n",
+ stats->major, stats->minor, stats->patch, stats->arch_bits,
+ stats->process_id, stats->uptime_in_seconds/3600);
+ ap_rprintf(r, "<b>Clients::</b> Connected: <i>%d</i>, Blocked: <i>%d</i> <br />\n",
+ stats->connected_clients, stats->blocked_clients);
+ ap_rprintf(r, "<b>Memory::</b> Total: <i>%" APR_UINT64_T_FMT "</i>, Max: <i>%" APR_UINT64_T_FMT "</i>, Used: <i>%" APR_UINT64_T_FMT "</i> <br />\n",
+ stats->total_system_memory, stats->maxmemory, stats->used_memory);
+ ap_rprintf(r, "<b>CPU::</b> System: <i>%u</i>, User: <i>%u</i><br />\n",
+ stats->used_cpu_sys, stats->used_cpu_user );
+ ap_rprintf(r, "<b>Connections::</b> Recd: <i>%" APR_UINT64_T_FMT "</i>, Processed: <i>%" APR_UINT64_T_FMT "</i>, Rejected: <i>%" APR_UINT64_T_FMT "</i> <br />\n",
+ stats->total_connections_received, stats->total_commands_processed,
+ stats->rejected_connections);
+ ap_rprintf(r, "<b>Cache::</b> Hits: <i>%" APR_UINT64_T_FMT "</i>, Misses: <i>%" APR_UINT64_T_FMT "</i> <br />\n",
+ stats->keyspace_hits, stats->keyspace_misses);
+ ap_rprintf(r, "<b>Net::</b> Input bytes: <i>%" APR_UINT64_T_FMT "</i>, Output bytes: <i>%" APR_UINT64_T_FMT "</i> <br />\n",
+ stats->total_net_input_bytes, stats->total_net_output_bytes);
+ if (stats->role == APR_RS_SERVER_MASTER)
+ role = "master";
+ else if (stats->role == APR_RS_SERVER_SLAVE)
+ role = "slave";
+ else
+ role = "unknown";
+ ap_rprintf(r, "<b>Misc::</b> Role: <i>%s</i>, Connected Slaves: <i>%u</i>, Is Cluster?: <i>%s</i> \n",
+ role, stats->connected_clients,
+ (stats->cluster_enabled ? "yes" : "no"));
+ ap_rputs("<hr><br />\n", r);
+ }
+ else {
+ ap_rprintf(r, "Version: %u.%u.%u [%u bits], PID: %u, Uptime: %u hrs %s\n",
+ stats->major, stats->minor, stats->patch, stats->arch_bits,
+ stats->process_id, stats->uptime_in_seconds/3600, br);
+ ap_rprintf(r, "Clients:: Connected: %d, Blocked: %d %s\n",
+ stats->connected_clients, stats->blocked_clients, br);
+ ap_rprintf(r, "Memory:: Total: %" APR_UINT64_T_FMT ", Max: %" APR_UINT64_T_FMT ", Used: %" APR_UINT64_T_FMT " %s\n",
+ stats->total_system_memory, stats->maxmemory, stats->used_memory,
+ br);
+ ap_rprintf(r, "CPU:: System: %u, User: %u %s\n",
+ stats->used_cpu_sys, stats->used_cpu_user , br);
+ ap_rprintf(r, "Connections:: Recd: %" APR_UINT64_T_FMT ", Processed: %" APR_UINT64_T_FMT ", Rejected: %" APR_UINT64_T_FMT " %s\n",
+ stats->total_connections_received, stats->total_commands_processed,
+ stats->rejected_connections, br);
+ ap_rprintf(r, "Cache:: Hits: %" APR_UINT64_T_FMT ", Misses: %" APR_UINT64_T_FMT " %s\n",
+ stats->keyspace_hits, stats->keyspace_misses, br);
+ ap_rprintf(r, "Net:: Input bytes: %" APR_UINT64_T_FMT ", Output bytes: %" APR_UINT64_T_FMT " %s\n",
+ stats->total_net_input_bytes, stats->total_net_output_bytes, br);
+ if (stats->role == APR_RS_SERVER_MASTER)
+ role = "master";
+ else if (stats->role == APR_RS_SERVER_SLAVE)
+ role = "slave";
+ else
+ role = "unknown";
+ ap_rprintf(r, "Misc:: Role: %s, Connected Slaves: %u, Is Cluster?: %s %s\n",
+ role, stats->connected_clients,
+ (stats->cluster_enabled ? "yes" : "no"), br);
+ }
+ }
+
+}
+
+static apr_status_t socache_rd_iterate(ap_socache_instance_t *instance,
+ server_rec *s, void *userctx,
+ ap_socache_iterator_t *iterator,
+ apr_pool_t *pool)
+{
+ return APR_ENOTIMPL;
+}
+
+static const ap_socache_provider_t socache_mc = {
+ "redis",
+ 0,
+ socache_rd_create,
+ socache_rd_init,
+ socache_rd_destroy,
+ socache_rd_store,
+ socache_rd_retrieve,
+ socache_rd_remove,
+ socache_rd_status,
+ socache_rd_iterate,
+};
+
+#endif /* HAVE_APU_REDIS */
+
+static void* create_server_config(apr_pool_t* p, server_rec* s)
+{
+ socache_rd_svr_cfg *sconf = apr_palloc(p, sizeof(socache_rd_svr_cfg));
+
+ sconf->ttl = RD_DEFAULT_SERVER_TTL;
+ sconf->rwto = RD_DEFAULT_SERVER_RWTO;
+
+ return sconf;
+}
+
+static const char *socache_rd_set_ttl(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ apr_interval_time_t ttl;
+ socache_rd_svr_cfg *sconf = ap_get_module_config(cmd->server->module_config,
+ &socache_redis_module);
+
+ if (ap_timeout_parameter_parse(arg, &ttl, "s") != APR_SUCCESS) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ " has wrong format", NULL);
+ }
+
+ if ((ttl < apr_time_from_sec(0)) || (ttl > apr_time_from_sec(3600))) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ " can only be 0 or up to one hour.", NULL);
+ }
+
+ /* apr_redis_server_create needs a ttl in usec. */
+ sconf->ttl = ttl;
+
+ return NULL;
+}
+
+static const char *socache_rd_set_rwto(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ apr_interval_time_t rwto;
+ socache_rd_svr_cfg *sconf = ap_get_module_config(cmd->server->module_config,
+ &socache_redis_module);
+
+ if (ap_timeout_parameter_parse(arg, &rwto, "s") != APR_SUCCESS) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ " has wrong format", NULL);
+ }
+
+ if ((rwto < apr_time_from_sec(0)) || (rwto > apr_time_from_sec(3600))) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ " can only be 0 or up to one hour.", NULL);
+ }
+
+ /* apr_redis_server_create needs a ttl in usec. */
+ sconf->rwto = rwto;
+
+ return NULL;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+#ifdef HAVE_APU_REDIS
+
+ ap_register_provider(p, AP_SOCACHE_PROVIDER_GROUP, "redis",
+ AP_SOCACHE_PROVIDER_VERSION,
+ &socache_mc);
+#endif
+}
+
+static const command_rec socache_redis_cmds[] =
+{
+ AP_INIT_TAKE1("RedisConnPoolTTL", socache_rd_set_ttl, NULL, RSRC_CONF,
+ "TTL used for the connection pool with the Redis server(s)"),
+ AP_INIT_TAKE1("RedisTimeout", socache_rd_set_rwto, NULL, RSRC_CONF,
+ "R/W timeout used for the connection with the Redis server(s)"),
+ {NULL}
+};
+
+AP_DECLARE_MODULE(socache_redis) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-dir config structures */
+ NULL, /* merge per-dir config structures */
+ create_server_config, /* create per-server config structures */
+ NULL, /* merge per-server config structures */
+ socache_redis_cmds, /* table of config file commands */
+ register_hooks /* register hooks */
+};
+
diff --git a/modules/cache/mod_socache_redis.dep b/modules/cache/mod_socache_redis.dep
new file mode 100644
index 0000000..a450a54
--- /dev/null
+++ b/modules/cache/mod_socache_redis.dep
@@ -0,0 +1,5 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_socache_shmcb.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
diff --git a/modules/cache/mod_socache_redis.dsp b/modules/cache/mod_socache_redis.dsp
new file mode 100644
index 0000000..18f5962
--- /dev/null
+++ b/modules/cache/mod_socache_redis.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_socache_redis" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_socache_redis - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_socache_redis.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_socache_redis.mak" CFG="mod_socache_redis - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_socache_redis - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_socache_redis - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_socache_redis - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../generators" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "AAA_DECLARE_EXPORT" /Fd"Release\mod_socache_redis_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_socache_redis.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_socache_redis.so" /d LONG_NAME="socache_redis module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_socache_redis.so" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_redis.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_socache_redis.so" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_redis.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_socache_redis.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_socache_redis - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../generators" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "AAA_DECLARE_EXPORT" /Fd"Debug\mod_socache_redis_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_socache_redis.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_socache_redis.so" /d LONG_NAME="socache_redis module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_socache_redis.so" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_redis.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_socache_redis.so" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_redis.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_socache_redis.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_socache_redis - Win32 Release"
+# Name "mod_socache_redis - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_socache_redis.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/cache/mod_socache_redis.mak b/modules/cache/mod_socache_redis.mak
new file mode 100644
index 0000000..e4aab37
--- /dev/null
+++ b/modules/cache/mod_socache_redis.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_socache_redis.dsp
+!IF "$(CFG)" == ""
+CFG=mod_socache_redis - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_socache_redis - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_socache_redis - Win32 Release" && "$(CFG)" != "mod_socache_redis - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_socache_redis.mak" CFG="mod_socache_redis - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_socache_redis - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_socache_redis - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_socache_redis - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_socache_redis.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_socache_redis.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_socache_redis.obj"
+ -@erase "$(INTDIR)\mod_socache_redis.res"
+ -@erase "$(INTDIR)\mod_socache_redis_src.idb"
+ -@erase "$(INTDIR)\mod_socache_redis_src.pdb"
+ -@erase "$(OUTDIR)\mod_socache_redis.exp"
+ -@erase "$(OUTDIR)\mod_socache_redis.lib"
+ -@erase "$(OUTDIR)\mod_socache_redis.pdb"
+ -@erase "$(OUTDIR)\mod_socache_redis.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /I "../generators" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_socache_redis_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_socache_redis.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_socache_redis.so" /d LONG_NAME="socache_redis_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_socache_redis.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_socache_redis.pdb" /debug /out:"$(OUTDIR)\mod_socache_redis.so" /implib:"$(OUTDIR)\mod_socache_redis.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_redis.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_socache_redis.obj" \
+ "$(INTDIR)\mod_socache_redis.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_socache_redis.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_socache_redis.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_socache_redis.so"
+ if exist .\Release\mod_socache_redis.so.manifest mt.exe -manifest .\Release\mod_socache_redis.so.manifest -outputresource:.\Release\mod_socache_redis.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_socache_redis - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_socache_redis.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_socache_redis.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_socache_redis.obj"
+ -@erase "$(INTDIR)\mod_socache_redis.res"
+ -@erase "$(INTDIR)\mod_socache_redis_src.idb"
+ -@erase "$(INTDIR)\mod_socache_redis_src.pdb"
+ -@erase "$(OUTDIR)\mod_socache_redis.exp"
+ -@erase "$(OUTDIR)\mod_socache_redis.lib"
+ -@erase "$(OUTDIR)\mod_socache_redis.pdb"
+ -@erase "$(OUTDIR)\mod_socache_redis.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /I "../generators" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_socache_redis_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_socache_redis.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_socache_redis.so" /d LONG_NAME="socache_redis_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_socache_redis.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_socache_redis.pdb" /debug /out:"$(OUTDIR)\mod_socache_redis.so" /implib:"$(OUTDIR)\mod_socache_redis.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_redis.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_socache_redis.obj" \
+ "$(INTDIR)\mod_socache_redis.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_socache_redis.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_socache_redis.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_socache_redis.so"
+ if exist .\Debug\mod_socache_redis.so.manifest mt.exe -manifest .\Debug\mod_socache_redis.so.manifest -outputresource:.\Debug\mod_socache_redis.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_socache_redis.dep")
+!INCLUDE "mod_socache_redis.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_socache_redis.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_socache_redis - Win32 Release" || "$(CFG)" == "mod_socache_redis - Win32 Debug"
+
+!IF "$(CFG)" == "mod_socache_redis - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\cache"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ELSEIF "$(CFG)" == "mod_socache_redis - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\cache"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_socache_redis - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\cache"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ELSEIF "$(CFG)" == "mod_socache_redis - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\cache"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_socache_redis - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\cache"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\cache"
+
+!ELSEIF "$(CFG)" == "mod_socache_redis - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\cache"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\cache"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_socache_redis - Win32 Release"
+
+
+"$(INTDIR)\mod_socache_redis.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_socache_redis.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_socache_redis.so" /d LONG_NAME="socache_redis_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_socache_redis - Win32 Debug"
+
+
+"$(INTDIR)\mod_socache_redis.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_socache_redis.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_socache_redis.so" /d LONG_NAME="socache_redis_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_socache_redis.c
+
+"$(INTDIR)\mod_socache_redis.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/cache/mod_socache_shmcb.c b/modules/cache/mod_socache_shmcb.c
new file mode 100644
index 0000000..4727961
--- /dev/null
+++ b/modules/cache/mod_socache_shmcb.c
@@ -0,0 +1,1087 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "httpd.h"
+#include "http_log.h"
+#include "http_request.h"
+#include "http_protocol.h"
+#include "http_config.h"
+#include "mod_status.h"
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_time.h"
+#include "apr_shm.h"
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+#include "apr_general.h"
+
+#if APR_HAVE_LIMITS_H
+#include <limits.h>
+#endif
+
+#include "ap_socache.h"
+
+/* XXX Unfortunately, there are still many unsigned ints in use here, so we
+ * XXX cannot allow more than UINT_MAX. Since some of the ints are exposed in
+ * XXX public interfaces, a simple search and replace is not enough.
+ * XXX It should be possible to extend that so that the total cache size can
+ * XXX be APR_SIZE_MAX and only the object size needs to be smaller than
+ * XXX UINT_MAX.
+ */
+#define SHMCB_MAX_SIZE (UINT_MAX<APR_SIZE_MAX ? UINT_MAX : APR_SIZE_MAX)
+
+#define DEFAULT_SHMCB_PREFIX "socache-shmcb-"
+
+#define DEFAULT_SHMCB_SUFFIX ".cache"
+
+#define ALIGNED_HEADER_SIZE APR_ALIGN_DEFAULT(sizeof(SHMCBHeader))
+#define ALIGNED_SUBCACHE_SIZE APR_ALIGN_DEFAULT(sizeof(SHMCBSubcache))
+#define ALIGNED_INDEX_SIZE APR_ALIGN_DEFAULT(sizeof(SHMCBIndex))
+
+/*
+ * Header structure - the start of the shared-mem segment
+ */
+typedef struct {
+ /* Stats for cache operations */
+ unsigned long stat_stores;
+ unsigned long stat_replaced;
+ unsigned long stat_expiries;
+ unsigned long stat_scrolled;
+ unsigned long stat_retrieves_hit;
+ unsigned long stat_retrieves_miss;
+ unsigned long stat_removes_hit;
+ unsigned long stat_removes_miss;
+ /* Number of subcaches */
+ unsigned int subcache_num;
+ /* How many indexes each subcache's queue has */
+ unsigned int index_num;
+ /* How large each subcache is, including the queue and data */
+ unsigned int subcache_size;
+ /* How far into each subcache the data area is (optimisation) */
+ unsigned int subcache_data_offset;
+ /* How large the data area in each subcache is (optimisation) */
+ unsigned int subcache_data_size;
+} SHMCBHeader;
+
+/*
+ * Subcache structure - the start of each subcache, followed by
+ * indexes then data
+ */
+typedef struct {
+ /* The start position and length of the cyclic buffer of indexes */
+ unsigned int idx_pos, idx_used;
+ /* Same for the data area */
+ unsigned int data_pos, data_used;
+} SHMCBSubcache;
+
+/*
+ * Index structure - each subcache has an array of these
+ */
+typedef struct {
+ /* absolute time this entry expires */
+ apr_time_t expires;
+ /* location within the subcache's data area */
+ unsigned int data_pos;
+ /* size (most logic ignores this, we keep it only to minimise memcpy) */
+ unsigned int data_used;
+ /* length of the used data which contains the id */
+ unsigned int id_len;
+ /* Used to mark explicitly-removed socache entries */
+ unsigned char removed;
+} SHMCBIndex;
+
+struct ap_socache_instance_t {
+ apr_pool_t *pool;
+ const char *data_file;
+ apr_size_t shm_size;
+ apr_shm_t *shm;
+ SHMCBHeader *header;
+};
+
+/* The SHM data segment is of fixed size and stores data as follows.
+ *
+ * [ SHMCBHeader | Subcaches ]
+ *
+ * The SHMCBHeader header structure stores metadata concerning the
+ * cache and the contained subcaches.
+ *
+ * Subcaches is a hash table of header->subcache_num SHMCBSubcache
+ * structures. The hash table is indexed by SHMCB_MASK(id). Each
+ * SHMCBSubcache structure has a fixed size (header->subcache_size),
+ * which is determined at creation time, and looks like the following:
+ *
+ * [ SHMCBSubcache | Indexes | Data ]
+ *
+ * Each subcache is prefixed by the SHMCBSubcache structure.
+ *
+ * The subcache's "Data" segment is a single cyclic data buffer, of
+ * total size header->subcache_data_size; data inside is referenced
+ * using byte offsets. The offset marking the beginning of the cyclic
+ * buffer is subcache->data_pos; the buffer's length is
+ * subcache->data_used.
+ *
+ * "Indexes" is an array of header->index_num SHMCBIndex structures,
+ * which is used as a cyclic queue; subcache->idx_pos gives the array
+ * index of the first in use, subcache->idx_used gives the number in
+ * use. Both ->idx_* values have a range of [0, header->index_num)
+ *
+ * Each in-use SHMCBIndex structure represents a single cached object.
+ * The ID and data segment are stored consecutively in the subcache's
+ * cyclic data buffer. The "Data" segment can thus be seen to
+ * look like this, for example
+ *
+ * offset: [ 0 1 2 3 4 5 6 ...
+ * contents:[ ID1 Data1 ID2 Data2 ID3 ...
+ *
+ * where the corresponding indices would look like:
+ *
+ * idx1 = { data_pos = 0, data_used = 3, id_len = 1, ...}
+ * idx2 = { data_pos = 3, data_used = 3, id_len = 1, ...}
+ * ...
+ */
+
+/* This macro takes a pointer to the header and a zero-based index and returns
+ * a pointer to the corresponding subcache. */
+#define SHMCB_SUBCACHE(pHeader, num) \
+ (SHMCBSubcache *)(((unsigned char *)(pHeader)) + \
+ ALIGNED_HEADER_SIZE + \
+ (num) * ((pHeader)->subcache_size))
+
+/* This macro takes a pointer to the header and an id and returns a
+ * pointer to the corresponding subcache. */
+#define SHMCB_MASK(pHeader, id) \
+ SHMCB_SUBCACHE((pHeader), *(id) & ((pHeader)->subcache_num - 1))
+
+/* This macro takes the same params as the last, generating two outputs for use
+ * in ap_log_error(...). */
+#define SHMCB_MASK_DBG(pHeader, id) \
+ *(id), (*(id) & ((pHeader)->subcache_num - 1))
+
+/* This macro takes a pointer to a subcache and a zero-based index and returns
+ * a pointer to the corresponding SHMCBIndex. */
+#define SHMCB_INDEX(pSubcache, num) \
+ (SHMCBIndex *)(((unsigned char *)pSubcache) + \
+ ALIGNED_SUBCACHE_SIZE + \
+ (num) * ALIGNED_INDEX_SIZE)
+
+/* This macro takes a pointer to the header and a subcache and returns a
+ * pointer to the corresponding data area. */
+#define SHMCB_DATA(pHeader, pSubcache) \
+ ((unsigned char *)(pSubcache) + (pHeader)->subcache_data_offset)
+
+/*
+ * Cyclic functions - assists in "wrap-around"/modulo logic
+ */
+
+/* Addition modulo 'mod' */
+#define SHMCB_CYCLIC_INCREMENT(val,inc,mod) \
+ (((val) + (inc)) % (mod))
+
+/* Subtraction (or "distance between") modulo 'mod' */
+#define SHMCB_CYCLIC_SPACE(val1,val2,mod) \
+ ((val2) >= (val1) ? ((val2) - (val1)) : \
+ ((val2) + (mod) - (val1)))
+
+/* A "normal-to-cyclic" memcpy. */
+static void shmcb_cyclic_ntoc_memcpy(unsigned int buf_size, unsigned char *data,
+ unsigned int dest_offset, const unsigned char *src,
+ unsigned int src_len)
+{
+ if (dest_offset + src_len < buf_size)
+ /* It be copied all in one go */
+ memcpy(data + dest_offset, src, src_len);
+ else {
+ /* Copy the two splits */
+ memcpy(data + dest_offset, src, buf_size - dest_offset);
+ memcpy(data, src + buf_size - dest_offset,
+ src_len + dest_offset - buf_size);
+ }
+}
+
+/* A "cyclic-to-normal" memcpy. */
+static void shmcb_cyclic_cton_memcpy(unsigned int buf_size, unsigned char *dest,
+ const unsigned char *data, unsigned int src_offset,
+ unsigned int src_len)
+{
+ if (src_offset + src_len < buf_size)
+ /* It be copied all in one go */
+ memcpy(dest, data + src_offset, src_len);
+ else {
+ /* Copy the two splits */
+ memcpy(dest, data + src_offset, buf_size - src_offset);
+ memcpy(dest + buf_size - src_offset, data,
+ src_len + src_offset - buf_size);
+ }
+}
+
+/* A memcmp against a cyclic data buffer. Compares SRC of length
+ * SRC_LEN against the contents of cyclic buffer DATA (which is of
+ * size BUF_SIZE), starting at offset DEST_OFFSET. Got that? Good. */
+static int shmcb_cyclic_memcmp(unsigned int buf_size, unsigned char *data,
+ unsigned int dest_offset,
+ const unsigned char *src,
+ unsigned int src_len)
+{
+ if (dest_offset + src_len < buf_size)
+ /* It be compared all in one go */
+ return memcmp(data + dest_offset, src, src_len);
+ else {
+ /* Compare the two splits */
+ int diff;
+
+ diff = memcmp(data + dest_offset, src, buf_size - dest_offset);
+ if (diff) {
+ return diff;
+ }
+ return memcmp(data, src + buf_size - dest_offset,
+ src_len + dest_offset - buf_size);
+ }
+}
+
+
+/* Prototypes for low-level subcache operations */
+static void shmcb_subcache_expire(server_rec *, SHMCBHeader *, SHMCBSubcache *,
+ apr_time_t);
+/* Returns zero on success, non-zero on failure. */
+static int shmcb_subcache_store(server_rec *s, SHMCBHeader *header,
+ SHMCBSubcache *subcache,
+ unsigned char *data, unsigned int data_len,
+ const unsigned char *id, unsigned int id_len,
+ apr_time_t expiry);
+/* Returns zero on success, non-zero on failure. */
+static int shmcb_subcache_retrieve(server_rec *, SHMCBHeader *, SHMCBSubcache *,
+ const unsigned char *id, unsigned int idlen,
+ unsigned char *data, unsigned int *datalen);
+/* Returns zero on success, non-zero on failure. */
+static int shmcb_subcache_remove(server_rec *, SHMCBHeader *, SHMCBSubcache *,
+ const unsigned char *, unsigned int);
+
+/* Returns result of the (iterator)() call, zero is success (continue) */
+static apr_status_t shmcb_subcache_iterate(ap_socache_instance_t *instance,
+ server_rec *s,
+ void *userctx,
+ SHMCBHeader *header,
+ SHMCBSubcache *subcache,
+ ap_socache_iterator_t *iterator,
+ unsigned char **buf,
+ apr_size_t *buf_len,
+ apr_pool_t *pool,
+ apr_time_t now);
+
+/*
+ * High-Level "handlers" as per ssl_scache.c
+ * subcache internals are deferred to shmcb_subcache_*** functions lower down
+ */
+
+static const char *socache_shmcb_create(ap_socache_instance_t **context,
+ const char *arg,
+ apr_pool_t *tmp, apr_pool_t *p)
+{
+ ap_socache_instance_t *ctx;
+ char *path, *cp, *cp2;
+
+ /* Allocate the context. */
+ *context = ctx = apr_pcalloc(p, sizeof *ctx);
+ ctx->pool = p;
+
+ ctx->shm_size = 1024*512; /* 512KB */
+
+ if (!arg || *arg == '\0') {
+ /* Use defaults. */
+ return NULL;
+ }
+
+ ctx->data_file = path = ap_server_root_relative(p, arg);
+
+ cp = strrchr(path, '(');
+ cp2 = path + strlen(path) - 1;
+ if (cp) {
+ char *endptr;
+ if (*cp2 != ')') {
+ return "Invalid argument: no closing parenthesis or cache size "
+ "missing after pathname with parenthesis";
+ }
+ *cp++ = '\0';
+ *cp2 = '\0';
+
+
+ ctx->shm_size = strtol(cp, &endptr, 10);
+ if (endptr != cp2) {
+ return "Invalid argument: cache size not numerical";
+ }
+
+ if (ctx->shm_size < 8192) {
+ return "Invalid argument: size has to be >= 8192 bytes";
+
+ }
+
+ if (ctx->shm_size >= SHMCB_MAX_SIZE) {
+ return apr_psprintf(tmp, "Invalid argument: size has "
+ "to be < %" APR_SIZE_T_FMT " bytes on this platform",
+ SHMCB_MAX_SIZE);
+ }
+ }
+ else if (cp2 >= path && *cp2 == ')') {
+ return "Invalid argument: no opening parenthesis";
+ }
+
+ return NULL;
+}
+
+static apr_status_t socache_shmcb_cleanup(void *arg)
+{
+ ap_socache_instance_t *ctx = arg;
+ if (ctx->shm) {
+ apr_shm_destroy(ctx->shm);
+ ctx->shm = NULL;
+ }
+ return APR_SUCCESS;
+}
+
+static apr_status_t socache_shmcb_init(ap_socache_instance_t *ctx,
+ const char *namespace,
+ const struct ap_socache_hints *hints,
+ server_rec *s, apr_pool_t *p)
+{
+ void *shm_segment;
+ apr_size_t shm_segsize;
+ apr_status_t rv;
+ SHMCBHeader *header;
+ unsigned int num_subcache, num_idx, loop;
+ apr_size_t avg_obj_size, avg_id_len;
+
+ /* Create shared memory segment */
+ if (ctx->data_file == NULL) {
+ const char *path = apr_pstrcat(p, DEFAULT_SHMCB_PREFIX, namespace,
+ DEFAULT_SHMCB_SUFFIX, NULL);
+
+ ctx->data_file = ap_runtime_dir_relative(p, path);
+ }
+
+ /* Use anonymous shm by default, fall back on name-based. */
+ rv = apr_shm_create(&ctx->shm, ctx->shm_size, NULL, p);
+ if (APR_STATUS_IS_ENOTIMPL(rv)) {
+ /* If anon shm isn't supported, fail if no named file was
+ * configured successfully; the ap_server_root_relative call
+ * above will return NULL for invalid paths. */
+ if (ctx->data_file == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00818)
+ "Could not use anonymous shm for '%s' cache",
+ namespace);
+ ctx->shm = NULL;
+ return APR_EINVAL;
+ }
+
+ /* For a name-based segment, remove it first in case of a
+ * previous unclean shutdown. */
+ apr_shm_remove(ctx->data_file, p);
+
+ rv = apr_shm_create(&ctx->shm, ctx->shm_size, ctx->data_file, p);
+ }
+
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00819)
+ "Could not allocate shared memory segment for shmcb "
+ "socache");
+ ctx->shm = NULL;
+ return rv;
+ }
+ apr_pool_cleanup_register(ctx->pool, ctx, socache_shmcb_cleanup,
+ apr_pool_cleanup_null);
+
+ shm_segment = apr_shm_baseaddr_get(ctx->shm);
+ shm_segsize = apr_shm_size_get(ctx->shm);
+ if (shm_segsize < (5 * ALIGNED_HEADER_SIZE)) {
+ /* the segment is ridiculously small, bail out */
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00820)
+ "shared memory segment too small");
+ return APR_ENOSPC;
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00821)
+ "shmcb_init allocated %" APR_SIZE_T_FMT
+ " bytes of shared memory",
+ shm_segsize);
+ /* Discount the header */
+ shm_segsize -= ALIGNED_HEADER_SIZE;
+ /* Select index size based on average object size hints, if given. */
+ avg_obj_size = hints && hints->avg_obj_size ? hints->avg_obj_size : 150;
+ avg_id_len = hints && hints->avg_id_len ? hints->avg_id_len : 30;
+ num_idx = (shm_segsize) / (avg_obj_size + avg_id_len);
+ num_subcache = 256;
+ while ((num_idx / num_subcache) < (2 * num_subcache))
+ num_subcache /= 2;
+ num_idx /= num_subcache;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00822)
+ "for %" APR_SIZE_T_FMT " bytes (%" APR_SIZE_T_FMT
+ " including header), recommending %u subcaches, "
+ "%u indexes each", shm_segsize,
+ shm_segsize + ALIGNED_HEADER_SIZE,
+ num_subcache, num_idx);
+ if (num_idx < 5) {
+ /* we're still too small, bail out */
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00823)
+ "shared memory segment too small");
+ return APR_ENOSPC;
+ }
+ /* OK, we're sorted */
+ ctx->header = header = shm_segment;
+ header->stat_stores = 0;
+ header->stat_replaced = 0;
+ header->stat_expiries = 0;
+ header->stat_scrolled = 0;
+ header->stat_retrieves_hit = 0;
+ header->stat_retrieves_miss = 0;
+ header->stat_removes_hit = 0;
+ header->stat_removes_miss = 0;
+ header->subcache_num = num_subcache;
+ /* Convert the subcache size (in bytes) to a value that is suitable for
+ * structure alignment on the host platform, by rounding down if necessary. */
+ header->subcache_size = (size_t)(shm_segsize / num_subcache);
+ if (header->subcache_size != APR_ALIGN_DEFAULT(header->subcache_size)) {
+ header->subcache_size = APR_ALIGN_DEFAULT(header->subcache_size) -
+ APR_ALIGN_DEFAULT(1);
+ }
+ header->subcache_data_offset = ALIGNED_SUBCACHE_SIZE +
+ num_idx * ALIGNED_INDEX_SIZE;
+ header->subcache_data_size = header->subcache_size -
+ header->subcache_data_offset;
+ header->index_num = num_idx;
+
+ /* Output trace info */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00824)
+ "shmcb_init_memory choices follow");
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00825)
+ "subcache_num = %u", header->subcache_num);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00826)
+ "subcache_size = %u", header->subcache_size);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00827)
+ "subcache_data_offset = %u", header->subcache_data_offset);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00828)
+ "subcache_data_size = %u", header->subcache_data_size);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00829)
+ "index_num = %u", header->index_num);
+ /* The header is done, make the caches empty */
+ for (loop = 0; loop < header->subcache_num; loop++) {
+ SHMCBSubcache *subcache = SHMCB_SUBCACHE(header, loop);
+ subcache->idx_pos = subcache->idx_used = 0;
+ subcache->data_pos = subcache->data_used = 0;
+ }
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(00830)
+ "Shared memory socache initialised");
+ /* Success ... */
+
+ return APR_SUCCESS;
+}
+
+static void socache_shmcb_destroy(ap_socache_instance_t *ctx, server_rec *s)
+{
+ if (ctx) {
+ apr_pool_cleanup_run(ctx->pool, ctx, socache_shmcb_cleanup);
+ }
+}
+
+static apr_status_t socache_shmcb_store(ap_socache_instance_t *ctx,
+ server_rec *s, const unsigned char *id,
+ unsigned int idlen, apr_time_t expiry,
+ unsigned char *encoded,
+ unsigned int len_encoded,
+ apr_pool_t *p)
+{
+ SHMCBHeader *header = ctx->header;
+ SHMCBSubcache *subcache = SHMCB_MASK(header, id);
+ int tryreplace;
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00831)
+ "socache_shmcb_store (0x%02x -> subcache %d)",
+ SHMCB_MASK_DBG(header, id));
+ /* XXX: Says who? Why shouldn't this be acceptable, or padded if not? */
+ if (idlen < 4) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00832) "unusably short id provided "
+ "(%u bytes)", idlen);
+ return APR_EINVAL;
+ }
+ tryreplace = shmcb_subcache_remove(s, header, subcache, id, idlen);
+ if (shmcb_subcache_store(s, header, subcache, encoded,
+ len_encoded, id, idlen, expiry)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00833)
+ "can't store an socache entry!");
+ return APR_ENOSPC;
+ }
+ if (tryreplace == 0) {
+ header->stat_replaced++;
+ }
+ else {
+ header->stat_stores++;
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00834)
+ "leaving socache_shmcb_store successfully");
+ return APR_SUCCESS;
+}
+
+static apr_status_t socache_shmcb_retrieve(ap_socache_instance_t *ctx,
+ server_rec *s,
+ const unsigned char *id, unsigned int idlen,
+ unsigned char *dest, unsigned int *destlen,
+ apr_pool_t *p)
+{
+ SHMCBHeader *header = ctx->header;
+ SHMCBSubcache *subcache = SHMCB_MASK(header, id);
+ int rv;
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00835)
+ "socache_shmcb_retrieve (0x%02x -> subcache %d)",
+ SHMCB_MASK_DBG(header, id));
+
+ /* Get the entry corresponding to the id, if it exists. */
+ rv = shmcb_subcache_retrieve(s, header, subcache, id, idlen,
+ dest, destlen);
+ if (rv == 0)
+ header->stat_retrieves_hit++;
+ else
+ header->stat_retrieves_miss++;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00836)
+ "leaving socache_shmcb_retrieve successfully");
+
+ return rv == 0 ? APR_SUCCESS : APR_NOTFOUND;
+}
+
+static apr_status_t socache_shmcb_remove(ap_socache_instance_t *ctx,
+ server_rec *s, const unsigned char *id,
+ unsigned int idlen, apr_pool_t *p)
+{
+ SHMCBHeader *header = ctx->header;
+ SHMCBSubcache *subcache = SHMCB_MASK(header, id);
+ apr_status_t rv;
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00837)
+ "socache_shmcb_remove (0x%02x -> subcache %d)",
+ SHMCB_MASK_DBG(header, id));
+ if (idlen < 4) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00838) "unusably short id provided "
+ "(%u bytes)", idlen);
+ return APR_EINVAL;
+ }
+ if (shmcb_subcache_remove(s, header, subcache, id, idlen) == 0) {
+ header->stat_removes_hit++;
+ rv = APR_SUCCESS;
+ } else {
+ header->stat_removes_miss++;
+ rv = APR_NOTFOUND;
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00839)
+ "leaving socache_shmcb_remove successfully");
+
+ return rv;
+}
+
+static void socache_shmcb_status(ap_socache_instance_t *ctx,
+ request_rec *r, int flags)
+{
+ server_rec *s = r->server;
+ SHMCBHeader *header = ctx->header;
+ unsigned int loop, total = 0, cache_total = 0, non_empty_subcaches = 0;
+ apr_time_t idx_expiry, min_expiry = 0, max_expiry = 0;
+ apr_time_t now = apr_time_now();
+ double expiry_total = 0;
+ int index_pct, cache_pct;
+
+ AP_DEBUG_ASSERT(header->subcache_num > 0);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00840) "inside shmcb_status");
+ /* Perform the iteration inside the mutex to avoid corruption or invalid
+ * pointer arithmetic. The rest of our logic uses read-only header data so
+ * doesn't need the lock. */
+ /* Iterate over the subcaches */
+ for (loop = 0; loop < header->subcache_num; loop++) {
+ SHMCBSubcache *subcache = SHMCB_SUBCACHE(header, loop);
+ shmcb_subcache_expire(s, header, subcache, now);
+ total += subcache->idx_used;
+ cache_total += subcache->data_used;
+ if (subcache->idx_used) {
+ SHMCBIndex *idx = SHMCB_INDEX(subcache, subcache->idx_pos);
+ non_empty_subcaches++;
+ idx_expiry = idx->expires;
+ expiry_total += (double)idx_expiry;
+ max_expiry = ((idx_expiry > max_expiry) ? idx_expiry : max_expiry);
+ if (!min_expiry)
+ min_expiry = idx_expiry;
+ else
+ min_expiry = ((idx_expiry < min_expiry) ? idx_expiry : min_expiry);
+ }
+ }
+ index_pct = (100 * total) / (header->index_num *
+ header->subcache_num);
+ cache_pct = (100 * cache_total) / (header->subcache_data_size *
+ header->subcache_num);
+ /* Generate Output */
+ if (!(flags & AP_STATUS_SHORT)) {
+ ap_rprintf(r, "cache type: <b>SHMCB</b>, shared memory: <b>%" APR_SIZE_T_FMT "</b> "
+ "bytes, current entries: <b>%d</b><br>",
+ ctx->shm_size, total);
+ ap_rprintf(r, "subcaches: <b>%d</b>, indexes per subcache: <b>%d</b><br>",
+ header->subcache_num, header->index_num);
+ if (non_empty_subcaches) {
+ apr_time_t average_expiry = (apr_time_t)(expiry_total / (double)non_empty_subcaches);
+ ap_rprintf(r, "time left on oldest entries' objects: ");
+ if (now < average_expiry)
+ ap_rprintf(r, "avg: <b>%d</b> seconds, (range: %d...%d)<br>",
+ (int)apr_time_sec(average_expiry - now),
+ (int)apr_time_sec(min_expiry - now),
+ (int)apr_time_sec(max_expiry - now));
+ else
+ ap_rprintf(r, "expiry_threshold: <b>Calculation error!</b><br>");
+ }
+
+ ap_rprintf(r, "index usage: <b>%d%%</b>, cache usage: <b>%d%%</b><br>",
+ index_pct, cache_pct);
+ ap_rprintf(r, "total entries stored since starting: <b>%lu</b><br>",
+ header->stat_stores);
+ ap_rprintf(r, "total entries replaced since starting: <b>%lu</b><br>",
+ header->stat_replaced);
+ ap_rprintf(r, "total entries expired since starting: <b>%lu</b><br>",
+ header->stat_expiries);
+ ap_rprintf(r, "total (pre-expiry) entries scrolled out of the cache: "
+ "<b>%lu</b><br>", header->stat_scrolled);
+ ap_rprintf(r, "total retrieves since starting: <b>%lu</b> hit, "
+ "<b>%lu</b> miss<br>", header->stat_retrieves_hit,
+ header->stat_retrieves_miss);
+ ap_rprintf(r, "total removes since starting: <b>%lu</b> hit, "
+ "<b>%lu</b> miss<br>", header->stat_removes_hit,
+ header->stat_removes_miss);
+ }
+ else {
+ ap_rputs("CacheType: SHMCB\n", r);
+ ap_rprintf(r, "CacheSharedMemory: %" APR_SIZE_T_FMT "\n",
+ ctx->shm_size);
+ ap_rprintf(r, "CacheCurrentEntries: %d\n", total);
+ ap_rprintf(r, "CacheSubcaches: %d\n", header->subcache_num);
+ ap_rprintf(r, "CacheIndexesPerSubcaches: %d\n", header->index_num);
+ if (non_empty_subcaches) {
+ apr_time_t average_expiry = (apr_time_t)(expiry_total / (double)non_empty_subcaches);
+ if (now < average_expiry) {
+ ap_rprintf(r, "CacheTimeLeftOldestAvg: %d\n", (int)apr_time_sec(average_expiry - now));
+ ap_rprintf(r, "CacheTimeLeftOldestMin: %d\n", (int)apr_time_sec(min_expiry - now));
+ ap_rprintf(r, "CacheTimeLeftOldestMax: %d\n", (int)apr_time_sec(max_expiry - now));
+ }
+ }
+
+ ap_rprintf(r, "CacheIndexUsage: %d%%\n", index_pct);
+ ap_rprintf(r, "CacheUsage: %d%%\n", cache_pct);
+ ap_rprintf(r, "CacheStoreCount: %lu\n", header->stat_stores);
+ ap_rprintf(r, "CacheReplaceCount: %lu\n", header->stat_replaced);
+ ap_rprintf(r, "CacheExpireCount: %lu\n", header->stat_expiries);
+ ap_rprintf(r, "CacheDiscardCount: %lu\n", header->stat_scrolled);
+ ap_rprintf(r, "CacheRetrieveHitCount: %lu\n", header->stat_retrieves_hit);
+ ap_rprintf(r, "CacheRetrieveMissCount: %lu\n", header->stat_retrieves_miss);
+ ap_rprintf(r, "CacheRemoveHitCount: %lu\n", header->stat_removes_hit);
+ ap_rprintf(r, "CacheRemoveMissCount: %lu\n", header->stat_removes_miss);
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00841) "leaving shmcb_status");
+}
+
+static apr_status_t socache_shmcb_iterate(ap_socache_instance_t *instance,
+ server_rec *s, void *userctx,
+ ap_socache_iterator_t *iterator,
+ apr_pool_t *pool)
+{
+ SHMCBHeader *header = instance->header;
+ unsigned int loop;
+ apr_time_t now = apr_time_now();
+ apr_status_t rv = APR_SUCCESS;
+ apr_size_t buflen = 0;
+ unsigned char *buf = NULL;
+
+ /* Perform the iteration inside the mutex to avoid corruption or invalid
+ * pointer arithmetic. The rest of our logic uses read-only header data so
+ * doesn't need the lock. */
+ /* Iterate over the subcaches */
+ for (loop = 0; loop < header->subcache_num && rv == APR_SUCCESS; loop++) {
+ SHMCBSubcache *subcache = SHMCB_SUBCACHE(header, loop);
+ rv = shmcb_subcache_iterate(instance, s, userctx, header, subcache,
+ iterator, &buf, &buflen, pool, now);
+ }
+ return rv;
+}
+
+/*
+ * Subcache-level cache operations
+ */
+
+static void shmcb_subcache_expire(server_rec *s, SHMCBHeader *header,
+ SHMCBSubcache *subcache, apr_time_t now)
+{
+ unsigned int loop = 0, freed = 0, expired = 0;
+ unsigned int new_idx_pos = subcache->idx_pos;
+ SHMCBIndex *idx = NULL;
+
+ while (loop < subcache->idx_used) {
+ idx = SHMCB_INDEX(subcache, new_idx_pos);
+ if (idx->removed)
+ freed++;
+ else if (idx->expires <= now)
+ expired++;
+ else
+ /* not removed and not expired yet, we're done iterating */
+ break;
+ loop++;
+ new_idx_pos = SHMCB_CYCLIC_INCREMENT(new_idx_pos, 1, header->index_num);
+ }
+ if (!loop)
+ /* Nothing to do */
+ return;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00842)
+ "expiring %u and reclaiming %u removed socache entries",
+ expired, freed);
+ if (loop == subcache->idx_used) {
+ /* We're expiring everything, piece of cake */
+ subcache->idx_used = 0;
+ subcache->data_used = 0;
+ } else {
+ /* There remain other indexes, so we can use idx to adjust 'data' */
+ unsigned int diff = SHMCB_CYCLIC_SPACE(subcache->data_pos,
+ idx->data_pos,
+ header->subcache_data_size);
+ /* Adjust the indexes */
+ subcache->idx_used -= loop;
+ subcache->idx_pos = new_idx_pos;
+ /* Adjust the data area */
+ subcache->data_used -= diff;
+ subcache->data_pos = idx->data_pos;
+ }
+ header->stat_expiries += expired;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00843)
+ "we now have %u socache entries", subcache->idx_used);
+}
+
+static int shmcb_subcache_store(server_rec *s, SHMCBHeader *header,
+ SHMCBSubcache *subcache,
+ unsigned char *data, unsigned int data_len,
+ const unsigned char *id, unsigned int id_len,
+ apr_time_t expiry)
+{
+ unsigned int data_offset, new_idx, id_offset;
+ SHMCBIndex *idx;
+ unsigned int total_len = id_len + data_len;
+
+ /* Sanity check the input */
+ if (total_len > header->subcache_data_size) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00844)
+ "inserting socache entry larger (%d) than subcache data area (%d)",
+ total_len, header->subcache_data_size);
+ return -1;
+ }
+
+ /* First reclaim space from removed and expired records. */
+ shmcb_subcache_expire(s, header, subcache, apr_time_now());
+
+ /* Loop until there is enough space to insert
+ * XXX: This should first compress out-of-order expiries and
+ * removed records, and then force-remove oldest-first
+ */
+ if (header->subcache_data_size - subcache->data_used < total_len
+ || subcache->idx_used == header->index_num) {
+ unsigned int loop = 0;
+
+ idx = SHMCB_INDEX(subcache, subcache->idx_pos);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00845)
+ "about to force-expire, subcache: idx_used=%d, "
+ "data_used=%d", subcache->idx_used, subcache->data_used);
+ do {
+ SHMCBIndex *idx2;
+
+ /* Adjust the indexes by one */
+ subcache->idx_pos = SHMCB_CYCLIC_INCREMENT(subcache->idx_pos, 1,
+ header->index_num);
+ subcache->idx_used--;
+ if (!subcache->idx_used) {
+ /* There's nothing left */
+ subcache->data_used = 0;
+ break;
+ }
+ /* Adjust the data */
+ idx2 = SHMCB_INDEX(subcache, subcache->idx_pos);
+ subcache->data_used -= SHMCB_CYCLIC_SPACE(idx->data_pos, idx2->data_pos,
+ header->subcache_data_size);
+ subcache->data_pos = idx2->data_pos;
+ /* Stats */
+ header->stat_scrolled++;
+ /* Loop admin */
+ idx = idx2;
+ loop++;
+ } while (header->subcache_data_size - subcache->data_used < total_len);
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00846)
+ "finished force-expire, subcache: idx_used=%d, "
+ "data_used=%d", subcache->idx_used, subcache->data_used);
+ }
+
+ /* HERE WE ASSUME THAT THE NEW ENTRY SHOULD GO ON THE END! I'M NOT
+ * CHECKING WHETHER IT SHOULD BE GENUINELY "INSERTED" SOMEWHERE.
+ *
+ * We aught to fix that. httpd (never mind third party modules)
+ * does not promise to perform any processing in date order
+ * (c.f. FAQ "My log entries are not in date order!")
+ */
+ /* Insert the id */
+ id_offset = SHMCB_CYCLIC_INCREMENT(subcache->data_pos, subcache->data_used,
+ header->subcache_data_size);
+ shmcb_cyclic_ntoc_memcpy(header->subcache_data_size,
+ SHMCB_DATA(header, subcache), id_offset,
+ id, id_len);
+ subcache->data_used += id_len;
+ /* Insert the data */
+ data_offset = SHMCB_CYCLIC_INCREMENT(subcache->data_pos, subcache->data_used,
+ header->subcache_data_size);
+ shmcb_cyclic_ntoc_memcpy(header->subcache_data_size,
+ SHMCB_DATA(header, subcache), data_offset,
+ data, data_len);
+ subcache->data_used += data_len;
+ /* Insert the index */
+ new_idx = SHMCB_CYCLIC_INCREMENT(subcache->idx_pos, subcache->idx_used,
+ header->index_num);
+ idx = SHMCB_INDEX(subcache, new_idx);
+ idx->expires = expiry;
+ idx->data_pos = id_offset;
+ idx->data_used = total_len;
+ idx->id_len = id_len;
+ idx->removed = 0;
+ subcache->idx_used++;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00847)
+ "insert happened at idx=%d, data=(%u:%u)", new_idx,
+ id_offset, data_offset);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00848)
+ "finished insert, subcache: idx_pos/idx_used=%d/%d, "
+ "data_pos/data_used=%d/%d",
+ subcache->idx_pos, subcache->idx_used,
+ subcache->data_pos, subcache->data_used);
+ return 0;
+}
+
+static int shmcb_subcache_retrieve(server_rec *s, SHMCBHeader *header,
+ SHMCBSubcache *subcache,
+ const unsigned char *id, unsigned int idlen,
+ unsigned char *dest, unsigned int *destlen)
+{
+ unsigned int pos;
+ unsigned int loop = 0;
+ apr_time_t now = apr_time_now();
+
+ pos = subcache->idx_pos;
+
+ while (loop < subcache->idx_used) {
+ SHMCBIndex *idx = SHMCB_INDEX(subcache, pos);
+
+ /* Only consider 'idx' if the id matches, and the "removed"
+ * flag isn't set, and the record is not expired.
+ * Check the data length too to avoid a buffer overflow
+ * in case of corruption, which should be impossible,
+ * but it's cheap to be safe. */
+ if (!idx->removed
+ && idx->id_len == idlen
+ && (idx->data_used - idx->id_len) <= *destlen
+ && shmcb_cyclic_memcmp(header->subcache_data_size,
+ SHMCB_DATA(header, subcache),
+ idx->data_pos, id, idx->id_len) == 0) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00849)
+ "match at idx=%d, data=%d", pos, idx->data_pos);
+ if (idx->expires > now) {
+ unsigned int data_offset;
+
+ /* Find the offset of the data segment, after the id */
+ data_offset = SHMCB_CYCLIC_INCREMENT(idx->data_pos,
+ idx->id_len,
+ header->subcache_data_size);
+
+ *destlen = idx->data_used - idx->id_len;
+
+ /* Copy out the data */
+ shmcb_cyclic_cton_memcpy(header->subcache_data_size,
+ dest, SHMCB_DATA(header, subcache),
+ data_offset, *destlen);
+
+ return 0;
+ }
+ else {
+ /* Already stale, quietly remove and treat as not-found */
+ idx->removed = 1;
+ header->stat_expiries++;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00850)
+ "shmcb_subcache_retrieve discarding expired entry");
+ return -1;
+ }
+ }
+ /* Increment */
+ loop++;
+ pos = SHMCB_CYCLIC_INCREMENT(pos, 1, header->index_num);
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00851)
+ "shmcb_subcache_retrieve found no match");
+ return -1;
+}
+
+static int shmcb_subcache_remove(server_rec *s, SHMCBHeader *header,
+ SHMCBSubcache *subcache,
+ const unsigned char *id,
+ unsigned int idlen)
+{
+ unsigned int pos;
+ unsigned int loop = 0;
+
+ pos = subcache->idx_pos;
+ while (loop < subcache->idx_used) {
+ SHMCBIndex *idx = SHMCB_INDEX(subcache, pos);
+
+ /* Only consider 'idx' if the id matches, and the "removed"
+ * flag isn't set. */
+ if (!idx->removed && idx->id_len == idlen
+ && shmcb_cyclic_memcmp(header->subcache_data_size,
+ SHMCB_DATA(header, subcache),
+ idx->data_pos, id, idx->id_len) == 0) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00852)
+ "possible match at idx=%d, data=%d", pos, idx->data_pos);
+
+ /* Found the matching entry, remove it quietly. */
+ idx->removed = 1;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00853)
+ "shmcb_subcache_remove removing matching entry");
+ return 0;
+ }
+ /* Increment */
+ loop++;
+ pos = SHMCB_CYCLIC_INCREMENT(pos, 1, header->index_num);
+ }
+
+ return -1; /* failure */
+}
+
+
+static apr_status_t shmcb_subcache_iterate(ap_socache_instance_t *instance,
+ server_rec *s,
+ void *userctx,
+ SHMCBHeader *header,
+ SHMCBSubcache *subcache,
+ ap_socache_iterator_t *iterator,
+ unsigned char **buf,
+ apr_size_t *buf_len,
+ apr_pool_t *pool,
+ apr_time_t now)
+{
+ unsigned int pos;
+ unsigned int loop = 0;
+ apr_status_t rv;
+
+ pos = subcache->idx_pos;
+ while (loop < subcache->idx_used) {
+ SHMCBIndex *idx = SHMCB_INDEX(subcache, pos);
+
+ /* Only consider 'idx' if the "removed" flag isn't set. */
+ if (!idx->removed) {
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00854)
+ "iterating idx=%d, data=%d", pos, idx->data_pos);
+ if (idx->expires > now) {
+ unsigned char *id = *buf;
+ unsigned char *dest;
+ unsigned int data_offset, dest_len;
+ apr_size_t buf_req;
+
+ /* Find the offset of the data segment, after the id */
+ data_offset = SHMCB_CYCLIC_INCREMENT(idx->data_pos,
+ idx->id_len,
+ header->subcache_data_size);
+
+ dest_len = idx->data_used - idx->id_len;
+
+ buf_req = APR_ALIGN_DEFAULT(idx->id_len + 1)
+ + APR_ALIGN_DEFAULT(dest_len + 1);
+
+ if (buf_req > *buf_len) {
+ /* Grow to ~150% of this buffer requirement on resize
+ * always using APR_ALIGN_DEFAULT sized pages
+ */
+ *buf_len = buf_req + APR_ALIGN_DEFAULT(buf_req / 2);
+ *buf = apr_palloc(pool, *buf_len);
+ id = *buf;
+ }
+
+ dest = *buf + APR_ALIGN_DEFAULT(idx->id_len + 1);
+
+ /* Copy out the data, because it's potentially cyclic */
+ shmcb_cyclic_cton_memcpy(header->subcache_data_size, id,
+ SHMCB_DATA(header, subcache),
+ idx->data_pos, idx->id_len);
+ id[idx->id_len] = '\0';
+
+ shmcb_cyclic_cton_memcpy(header->subcache_data_size, dest,
+ SHMCB_DATA(header, subcache),
+ data_offset, dest_len);
+ dest[dest_len] = '\0';
+
+ rv = iterator(instance, s, userctx, id, idx->id_len,
+ dest, dest_len, pool);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(00855)
+ "shmcb entry iterated");
+ if (rv != APR_SUCCESS)
+ return rv;
+ }
+ else {
+ /* Already stale, quietly remove and treat as not-found */
+ idx->removed = 1;
+ header->stat_expiries++;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00856)
+ "shmcb_subcache_iterate discarding expired entry");
+ }
+ }
+ /* Increment */
+ loop++;
+ pos = SHMCB_CYCLIC_INCREMENT(pos, 1, header->index_num);
+ }
+
+ return APR_SUCCESS;
+}
+
+static const ap_socache_provider_t socache_shmcb = {
+ "shmcb",
+ AP_SOCACHE_FLAG_NOTMPSAFE,
+ socache_shmcb_create,
+ socache_shmcb_init,
+ socache_shmcb_destroy,
+ socache_shmcb_store,
+ socache_shmcb_retrieve,
+ socache_shmcb_remove,
+ socache_shmcb_status,
+ socache_shmcb_iterate
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_register_provider(p, AP_SOCACHE_PROVIDER_GROUP, "shmcb",
+ AP_SOCACHE_PROVIDER_VERSION,
+ &socache_shmcb);
+
+ /* Also register shmcb under the default provider name. */
+ ap_register_provider(p, AP_SOCACHE_PROVIDER_GROUP,
+ AP_SOCACHE_DEFAULT_PROVIDER,
+ AP_SOCACHE_PROVIDER_VERSION,
+ &socache_shmcb);
+}
+
+AP_DECLARE_MODULE(socache_shmcb) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, NULL, NULL, NULL, NULL,
+ register_hooks
+};
diff --git a/modules/cache/mod_socache_shmcb.dep b/modules/cache/mod_socache_shmcb.dep
new file mode 100644
index 0000000..a5802b2
--- /dev/null
+++ b/modules/cache/mod_socache_shmcb.dep
@@ -0,0 +1,56 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_socache_shmcb.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_socache_shmcb.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_socache.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ "..\generators\mod_status.h"\
+
diff --git a/modules/cache/mod_socache_shmcb.dsp b/modules/cache/mod_socache_shmcb.dsp
new file mode 100644
index 0000000..17c1008
--- /dev/null
+++ b/modules/cache/mod_socache_shmcb.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_socache_shmcb" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_socache_shmcb - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_socache_shmcb.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_socache_shmcb.mak" CFG="mod_socache_shmcb - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_socache_shmcb - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_socache_shmcb - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_socache_shmcb - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "mod_socache_shmcb_EXPORTS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /I "../generators" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_socache_shmcb_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_socache_shmcb.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_socache_shmcb.so" /d LONG_NAME="socache_shmcb_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_socache_shmcb.so" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_shmcb.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_socache_shmcb.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_socache_shmcb - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /I "../generators" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_socache_shmcb_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_socache_shmcb.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_socache_shmcb.so" /d LONG_NAME="socache_shmcb_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_socache_shmcb.so" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_shmcb.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_socache_shmcb.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_socache_shmcb - Win32 Release"
+# Name "mod_socache_shmcb - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_socache_shmcb.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/cache/mod_socache_shmcb.mak b/modules/cache/mod_socache_shmcb.mak
new file mode 100644
index 0000000..7081784
--- /dev/null
+++ b/modules/cache/mod_socache_shmcb.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_socache_shmcb.dsp
+!IF "$(CFG)" == ""
+CFG=mod_socache_shmcb - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_socache_shmcb - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_socache_shmcb - Win32 Release" && "$(CFG)" != "mod_socache_shmcb - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_socache_shmcb.mak" CFG="mod_socache_shmcb - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_socache_shmcb - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_socache_shmcb - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_socache_shmcb - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_socache_shmcb.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_socache_shmcb.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_socache_shmcb.obj"
+ -@erase "$(INTDIR)\mod_socache_shmcb.res"
+ -@erase "$(INTDIR)\mod_socache_shmcb_src.idb"
+ -@erase "$(INTDIR)\mod_socache_shmcb_src.pdb"
+ -@erase "$(OUTDIR)\mod_socache_shmcb.exp"
+ -@erase "$(OUTDIR)\mod_socache_shmcb.lib"
+ -@erase "$(OUTDIR)\mod_socache_shmcb.pdb"
+ -@erase "$(OUTDIR)\mod_socache_shmcb.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /I "../generators" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_socache_shmcb_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_socache_shmcb.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_socache_shmcb.so" /d LONG_NAME="socache_shmcb_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_socache_shmcb.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_socache_shmcb.pdb" /debug /out:"$(OUTDIR)\mod_socache_shmcb.so" /implib:"$(OUTDIR)\mod_socache_shmcb.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_shmcb.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_socache_shmcb.obj" \
+ "$(INTDIR)\mod_socache_shmcb.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_socache_shmcb.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_socache_shmcb.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_socache_shmcb.so"
+ if exist .\Release\mod_socache_shmcb.so.manifest mt.exe -manifest .\Release\mod_socache_shmcb.so.manifest -outputresource:.\Release\mod_socache_shmcb.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_socache_shmcb - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_socache_shmcb.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_socache_shmcb.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_socache_shmcb.obj"
+ -@erase "$(INTDIR)\mod_socache_shmcb.res"
+ -@erase "$(INTDIR)\mod_socache_shmcb_src.idb"
+ -@erase "$(INTDIR)\mod_socache_shmcb_src.pdb"
+ -@erase "$(OUTDIR)\mod_socache_shmcb.exp"
+ -@erase "$(OUTDIR)\mod_socache_shmcb.lib"
+ -@erase "$(OUTDIR)\mod_socache_shmcb.pdb"
+ -@erase "$(OUTDIR)\mod_socache_shmcb.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /I "../generators" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_socache_shmcb_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_socache_shmcb.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_socache_shmcb.so" /d LONG_NAME="socache_shmcb_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_socache_shmcb.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_socache_shmcb.pdb" /debug /out:"$(OUTDIR)\mod_socache_shmcb.so" /implib:"$(OUTDIR)\mod_socache_shmcb.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_shmcb.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_socache_shmcb.obj" \
+ "$(INTDIR)\mod_socache_shmcb.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_socache_shmcb.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_socache_shmcb.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_socache_shmcb.so"
+ if exist .\Debug\mod_socache_shmcb.so.manifest mt.exe -manifest .\Debug\mod_socache_shmcb.so.manifest -outputresource:.\Debug\mod_socache_shmcb.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_socache_shmcb.dep")
+!INCLUDE "mod_socache_shmcb.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_socache_shmcb.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_socache_shmcb - Win32 Release" || "$(CFG)" == "mod_socache_shmcb - Win32 Debug"
+
+!IF "$(CFG)" == "mod_socache_shmcb - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\cache"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ELSEIF "$(CFG)" == "mod_socache_shmcb - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\cache"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_socache_shmcb - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\cache"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ELSEIF "$(CFG)" == "mod_socache_shmcb - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\cache"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\cache"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_socache_shmcb - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\cache"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\cache"
+
+!ELSEIF "$(CFG)" == "mod_socache_shmcb - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\cache"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\cache"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_socache_shmcb - Win32 Release"
+
+
+"$(INTDIR)\mod_socache_shmcb.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_socache_shmcb.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_socache_shmcb.so" /d LONG_NAME="socache_shmcb_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_socache_shmcb - Win32 Debug"
+
+
+"$(INTDIR)\mod_socache_shmcb.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_socache_shmcb.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_socache_shmcb.so" /d LONG_NAME="socache_shmcb_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_socache_shmcb.c
+
+"$(INTDIR)\mod_socache_shmcb.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/cluster/Makefile.in b/modules/cluster/Makefile.in
new file mode 100644
index 0000000..7c5c149
--- /dev/null
+++ b/modules/cluster/Makefile.in
@@ -0,0 +1,3 @@
+# a modules Makefile has no explicit targets -- they will be defined by
+# whatever modules are enabled. just grab special.mk to deal with this.
+include $(top_srcdir)/build/special.mk
diff --git a/modules/cluster/NWGNUmakefile b/modules/cluster/NWGNUmakefile
new file mode 100644
index 0000000..eda07b1
--- /dev/null
+++ b/modules/cluster/NWGNUmakefile
@@ -0,0 +1,246 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME =
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION =
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME =
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE =
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/modheartbeat.nlm \
+ $(OBJDIR)/modheartmonitor.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/cluster/NWGNUmodheartbeat b/modules/cluster/NWGNUmodheartbeat
new file mode 100644
index 0000000..b2c3ece
--- /dev/null
+++ b/modules/cluster/NWGNUmodheartbeat
@@ -0,0 +1,257 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/modules/core \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = modheartbeat
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Heart Beat Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = modheartbeat
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/modheartbeat.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_heartbeat.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ heartbeat_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/cluster/NWGNUmodheartmonitor b/modules/cluster/NWGNUmodheartmonitor
new file mode 100644
index 0000000..2436b27
--- /dev/null
+++ b/modules/cluster/NWGNUmodheartmonitor
@@ -0,0 +1,257 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/modules/core \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = modheartmonitor
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Heart Beat Monitor Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = modheartmonitor
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/modheartmonitor.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_heartmonitor.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ heartmonitor_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/cluster/README.heartbeat b/modules/cluster/README.heartbeat
new file mode 100644
index 0000000..5eec549
--- /dev/null
+++ b/modules/cluster/README.heartbeat
@@ -0,0 +1,33 @@
+mod_heartbeat
+
+Broadcasts the current Apache Connection status over multicast.
+
+Example Configuration:
+ HeartbeatAddress 239.0.0.1:27999
+
+Dependencies:
+ mod_status must be either a static module, or if a dynamic module, it must be
+ loaded before mod_heartbeat.
+
+
+Consuming:
+ Every 1 second, this module generates a single multicast UDP packet,
+ containing the number of busy and idle workers.
+
+ The packet is a simple ASCII format, similar to GET query parameters in UDP.
+
+ An Example packet:
+ v=1&ready=75&busy=0
+
+ Consumers should handle new variables besides busy and ready, separated by '&'
+ being added in the future.
+
+Misc:
+ The interval of 1 seconds is controlled by the HEARTBEAT_INTERVAL
+ compile time define. This is not currently tunable at run time. To make this
+ module send the status packet more often, you must add to the CFLAGS used to
+ compile the module to include:
+ -DHEARTBEAT_INTERVAL=3
+ Would cause the broadcasts to be sent every 3 seconds.
+
+
diff --git a/modules/cluster/README.heartmonitor b/modules/cluster/README.heartmonitor
new file mode 100644
index 0000000..0cb70bc
--- /dev/null
+++ b/modules/cluster/README.heartmonitor
@@ -0,0 +1,30 @@
+mod_heartmonitor
+
+Collects the Apache Connection status data over multicast.
+
+Example Configuration:
+ # First parameter is the interface to listen on
+ HeartbeatListen 239.0.0.1:27999
+ # Absolute path, or relative path to ServerRoot
+ HeartbeatStorage logs/hb.dat
+
+Dependencies:
+ Due to a bug in APR's apr_socket_recvfrom, version 1.2.12 or newer must be
+ used:
+ <http://svn.apache.org/viewvc?view=rev&revision=467600>
+
+Consuming:
+ This module atomically writes to the configured path, a list of servers,
+ along with metadata about them.
+
+ Included data about each server:
+ - IP Address
+ - Busy Slots
+ - Open Slots
+ - Last Seen
+
+ Every 5 seconds, this file will be updated with the current status of the
+ cluster.
+
+
+
diff --git a/modules/cluster/config5.m4 b/modules/cluster/config5.m4
new file mode 100644
index 0000000..8667316
--- /dev/null
+++ b/modules/cluster/config5.m4
@@ -0,0 +1,17 @@
+
+APACHE_MODPATH_INIT(cluster)
+
+heartbeat_objects='mod_heartbeat.lo'
+
+case "$host" in
+ *os2*)
+ # OS/2 DLLs must resolve all symbols at build time
+ # and we need some from the watchdog module
+ heartbeat_objects="$heartbeat_objects ../core/mod_watchdog.la"
+ ;;
+esac
+
+APACHE_MODULE(heartbeat, Generates Heartbeats, $heartbeat_objects, , , , watchdog)
+APACHE_MODULE(heartmonitor, Collects Heartbeats, , , )
+
+APACHE_MODPATH_FINISH
diff --git a/modules/cluster/mod_heartbeat.c b/modules/cluster/mod_heartbeat.c
new file mode 100644
index 0000000..1dc4a91
--- /dev/null
+++ b/modules/cluster/mod_heartbeat.c
@@ -0,0 +1,228 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "apr_strings.h"
+
+#include "ap_mpm.h"
+#include "scoreboard.h"
+#include "mod_watchdog.h"
+
+#ifndef HEARTBEAT_INTERVAL
+#define HEARTBEAT_INTERVAL (1)
+#endif
+
+module AP_MODULE_DECLARE_DATA heartbeat_module;
+
+typedef struct hb_ctx_t
+{
+ int active;
+ apr_sockaddr_t *mcast_addr;
+ int server_limit;
+ int thread_limit;
+ apr_status_t status;
+} hb_ctx_t;
+
+static const char *msg_format = "v=%u&ready=%u&busy=%u";
+
+#define MSG_VERSION (1)
+
+static int hb_monitor(hb_ctx_t *ctx, apr_pool_t *p)
+{
+ apr_size_t len;
+ apr_socket_t *sock = NULL;
+ char buf[256];
+ int i, j;
+ apr_uint32_t ready = 0;
+ apr_uint32_t busy = 0;
+ ap_generation_t mpm_generation;
+
+ ap_mpm_query(AP_MPMQ_GENERATION, &mpm_generation);
+
+ for (i = 0; i < ctx->server_limit; i++) {
+ process_score *ps;
+ ps = ap_get_scoreboard_process(i);
+
+ for (j = 0; j < ctx->thread_limit; j++) {
+ int res;
+
+ worker_score *ws = NULL;
+
+ ws = &ap_scoreboard_image->servers[i][j];
+
+ res = ws->status;
+
+ if (res == SERVER_READY && ps->generation == mpm_generation) {
+ ready++;
+ }
+ else if (res != SERVER_DEAD &&
+ res != SERVER_STARTING && res != SERVER_IDLE_KILL &&
+ ps->generation == mpm_generation) {
+ busy++;
+ }
+ }
+ }
+
+ len = apr_snprintf(buf, sizeof(buf), msg_format, MSG_VERSION, ready, busy);
+
+ do {
+ apr_status_t rv;
+ rv = apr_socket_create(&sock, ctx->mcast_addr->family,
+ SOCK_DGRAM, APR_PROTO_UDP, p);
+ if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rv,
+ NULL, APLOGNO(02097) "Heartbeat: apr_socket_create failed");
+ break;
+ }
+
+ rv = apr_mcast_loopback(sock, 1);
+ if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rv,
+ NULL, APLOGNO(02098) "Heartbeat: apr_mcast_loopback failed");
+ break;
+ }
+
+ rv = apr_socket_sendto(sock, ctx->mcast_addr, 0, buf, &len);
+ if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rv,
+ NULL, APLOGNO(02099) "Heartbeat: apr_socket_sendto failed");
+ break;
+ }
+ } while (0);
+
+ if (sock) {
+ apr_socket_close(sock);
+ }
+
+ return OK;
+}
+
+static int hb_watchdog_init(server_rec *s, const char *name, apr_pool_t *pool)
+{
+ hb_ctx_t *ctx = ap_get_module_config(s->module_config, &heartbeat_module);
+
+ ap_mpm_query(AP_MPMQ_HARD_LIMIT_THREADS, &ctx->thread_limit);
+ ap_mpm_query(AP_MPMQ_HARD_LIMIT_DAEMONS, &ctx->server_limit);
+
+ return OK;
+}
+
+static int hb_watchdog_exit(server_rec *s, const char *name, apr_pool_t *pool)
+{
+ return OK;
+}
+
+static int hb_watchdog_step(server_rec *s, const char *name, apr_pool_t *pool)
+{
+ hb_ctx_t *ctx = ap_get_module_config(s->module_config, &heartbeat_module);
+
+ if (!ctx->active || strcmp(name, AP_WATCHDOG_SINGLETON)) {
+ return OK;
+ }
+ return hb_monitor(ctx, pool);
+}
+
+static int hb_watchdog_need(server_rec *s, const char *name,
+ int parent, int singleton)
+{
+ hb_ctx_t *ctx = ap_get_module_config(s->module_config, &heartbeat_module);
+
+ if (ctx->active && singleton && !strcmp(name, AP_WATCHDOG_SINGLETON))
+ return OK;
+ else
+ return DECLINED;
+}
+
+static void hb_register_hooks(apr_pool_t *p)
+{
+ ap_hook_watchdog_need(hb_watchdog_need, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_watchdog_init(hb_watchdog_init, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_watchdog_step(hb_watchdog_step, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_watchdog_exit(hb_watchdog_exit, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+static void *hb_create_config(apr_pool_t *p, server_rec *s)
+{
+ hb_ctx_t *cfg = (hb_ctx_t *) apr_pcalloc(p, sizeof(hb_ctx_t));
+
+ return cfg;
+}
+
+static const char *cmd_hb_address(cmd_parms *cmd,
+ void *dconf, const char *addr)
+{
+ apr_status_t rv;
+ char *host_str;
+ char *scope_id;
+ apr_port_t port = 0;
+ apr_pool_t *p = cmd->pool;
+ hb_ctx_t *ctx =
+ (hb_ctx_t *) ap_get_module_config(cmd->server->module_config,
+ &heartbeat_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ if (!ctx->active) {
+ ctx->active = 1;
+ }
+ else {
+ return "HeartbeatAddress: May only be specified once.";
+ }
+
+ rv = apr_parse_addr_port(&host_str, &scope_id, &port, addr, cmd->temp_pool);
+
+ if (rv) {
+ return "HeartbeatAddress: Unable to parse address.";
+ }
+
+ if (host_str == NULL) {
+ return "HeartbeatAddress: No host provided in address";
+ }
+
+ if (port == 0) {
+ return "HeartbeatAddress: No port provided in address";
+ }
+
+ rv = apr_sockaddr_info_get(&ctx->mcast_addr, host_str, APR_INET, port, 0,
+ p);
+
+ if (rv) {
+ return "HeartbeatAddress: apr_sockaddr_info_get failed.";
+ }
+
+ return NULL;
+}
+
+static const command_rec hb_cmds[] = {
+ AP_INIT_TAKE1("HeartbeatAddress", cmd_hb_address, NULL, RSRC_CONF,
+ "Address to send heartbeat requests"),
+ {NULL}
+};
+
+AP_DECLARE_MODULE(heartbeat) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ hb_create_config, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ hb_cmds, /* command apr_table_t */
+ hb_register_hooks
+};
diff --git a/modules/cluster/mod_heartbeat.dep b/modules/cluster/mod_heartbeat.dep
new file mode 100644
index 0000000..d95401f
--- /dev/null
+++ b/modules/cluster/mod_heartbeat.dep
@@ -0,0 +1,55 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_heartbeat.mak
+
+.\mod_heartbeat.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_mpm.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ "..\core\mod_watchdog.h"\
+
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
diff --git a/modules/cluster/mod_heartbeat.dsp b/modules/cluster/mod_heartbeat.dsp
new file mode 100644
index 0000000..a2014dc
--- /dev/null
+++ b/modules/cluster/mod_heartbeat.dsp
@@ -0,0 +1,123 @@
+# Microsoft Developer Studio Project File - Name="mod_heartbeat" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_heartbeat - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_heartbeat.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_heartbeat.mak" CFG="mod_heartbeat - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_heartbeat - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_heartbeat - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_heartbeat - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../core" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_heartbeat_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_heartbeat.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_heartbeat.so" /d LONG_NAME="heartbeat_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:".\Release\mod_heartbeat.so" /base:@..\..\os\win32\BaseAddr.ref,mod_heartbeat.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_heartbeat.so" /base:@..\..\os\win32\BaseAddr.ref,mod_heartbeat.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_heartbeat.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_heartbeat - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../core" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_heartbeat_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_heartbeat.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_heartbeat.so" /d LONG_NAME="heartbeat_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_heartbeat.so" /base:@..\..\os\win32\BaseAddr.ref,mod_heartbeat.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_heartbeat.so" /base:@..\..\os\win32\BaseAddr.ref,mod_heartbeat.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_heartbeat.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_heartbeat - Win32 Release"
+# Name "mod_heartbeat - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\mod_heartbeat.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter ".h"
+# Begin Source File
+
+SOURCE=.\mod_proxy.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/cluster/mod_heartbeat.mak b/modules/cluster/mod_heartbeat.mak
new file mode 100644
index 0000000..7ce22c7
--- /dev/null
+++ b/modules/cluster/mod_heartbeat.mak
@@ -0,0 +1,380 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_heartbeat.dsp
+!IF "$(CFG)" == ""
+CFG=mod_heartbeat - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_heartbeat - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_heartbeat - Win32 Release" && "$(CFG)" != "mod_heartbeat - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_heartbeat.mak" CFG="mod_heartbeat - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_heartbeat - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_heartbeat - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_heartbeat - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_heartbeat.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_watchdog - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_heartbeat.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_watchdog - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_heartbeat.obj"
+ -@erase "$(INTDIR)\mod_heartbeat.res"
+ -@erase "$(INTDIR)\mod_heartbeat_src.idb"
+ -@erase "$(INTDIR)\mod_heartbeat_src.pdb"
+ -@erase "$(OUTDIR)\mod_heartbeat.exp"
+ -@erase "$(OUTDIR)\mod_heartbeat.lib"
+ -@erase "$(OUTDIR)\mod_heartbeat.pdb"
+ -@erase "$(OUTDIR)\mod_heartbeat.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../core" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_heartbeat_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_heartbeat.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_heartbeat.so" /d LONG_NAME="heartbeat_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_heartbeat.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_heartbeat.pdb" /debug /out:"$(OUTDIR)\mod_heartbeat.so" /implib:"$(OUTDIR)\mod_heartbeat.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_heartbeat.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_heartbeat.obj" \
+ "$(INTDIR)\mod_heartbeat.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib" \
+ "..\core\Release\mod_watchdog.lib"
+
+"$(OUTDIR)\mod_heartbeat.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_heartbeat.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_heartbeat.so"
+ if exist .\Release\mod_heartbeat.so.manifest mt.exe -manifest .\Release\mod_heartbeat.so.manifest -outputresource:.\Release\mod_heartbeat.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_heartbeat - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_heartbeat.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_watchdog - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_heartbeat.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_watchdog - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_heartbeat.obj"
+ -@erase "$(INTDIR)\mod_heartbeat.res"
+ -@erase "$(INTDIR)\mod_heartbeat_src.idb"
+ -@erase "$(INTDIR)\mod_heartbeat_src.pdb"
+ -@erase "$(OUTDIR)\mod_heartbeat.exp"
+ -@erase "$(OUTDIR)\mod_heartbeat.lib"
+ -@erase "$(OUTDIR)\mod_heartbeat.pdb"
+ -@erase "$(OUTDIR)\mod_heartbeat.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../core" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_heartbeat_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_heartbeat.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_heartbeat.so" /d LONG_NAME="heartbeat_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_heartbeat.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_heartbeat.pdb" /debug /out:"$(OUTDIR)\mod_heartbeat.so" /implib:"$(OUTDIR)\mod_heartbeat.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_heartbeat.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_heartbeat.obj" \
+ "$(INTDIR)\mod_heartbeat.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib" \
+ "..\core\Debug\mod_watchdog.lib"
+
+"$(OUTDIR)\mod_heartbeat.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_heartbeat.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_heartbeat.so"
+ if exist .\Debug\mod_heartbeat.so.manifest mt.exe -manifest .\Debug\mod_heartbeat.so.manifest -outputresource:.\Debug\mod_heartbeat.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_heartbeat.dep")
+!INCLUDE "mod_heartbeat.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_heartbeat.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_heartbeat - Win32 Release" || "$(CFG)" == "mod_heartbeat - Win32 Debug"
+SOURCE=.\mod_heartbeat.c
+
+"$(INTDIR)\mod_heartbeat.obj" : $(SOURCE) "$(INTDIR)"
+
+
+!IF "$(CFG)" == "mod_heartbeat - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\cluster"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\cluster"
+
+!ELSEIF "$(CFG)" == "mod_heartbeat - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\cluster"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\cluster"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_heartbeat - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\cluster"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\cluster"
+
+!ELSEIF "$(CFG)" == "mod_heartbeat - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\cluster"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\cluster"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_heartbeat - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\cluster"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\cluster"
+
+!ELSEIF "$(CFG)" == "mod_heartbeat - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\cluster"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\cluster"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_heartbeat - Win32 Release"
+
+"mod_watchdog - Win32 Release" :
+ cd ".\..\core"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_watchdog.mak" CFG="mod_watchdog - Win32 Release"
+ cd "..\cluster"
+
+"mod_watchdog - Win32 ReleaseCLEAN" :
+ cd ".\..\core"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_watchdog.mak" CFG="mod_watchdog - Win32 Release" RECURSE=1 CLEAN
+ cd "..\cluster"
+
+!ELSEIF "$(CFG)" == "mod_heartbeat - Win32 Debug"
+
+"mod_watchdog - Win32 Debug" :
+ cd ".\..\core"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_watchdog.mak" CFG="mod_watchdog - Win32 Debug"
+ cd "..\cluster"
+
+"mod_watchdog - Win32 DebugCLEAN" :
+ cd ".\..\core"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_watchdog.mak" CFG="mod_watchdog - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\cluster"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_heartbeat - Win32 Release"
+
+
+"$(INTDIR)\mod_heartbeat.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_heartbeat.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_heartbeat.so" /d LONG_NAME="heartbeat_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_heartbeat - Win32 Debug"
+
+
+"$(INTDIR)\mod_heartbeat.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_heartbeat.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_heartbeat.so" /d LONG_NAME="heartbeat_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+
+!ENDIF
+
diff --git a/modules/cluster/mod_heartmonitor.c b/modules/cluster/mod_heartmonitor.c
new file mode 100644
index 0000000..53b6504
--- /dev/null
+++ b/modules/cluster/mod_heartmonitor.c
@@ -0,0 +1,920 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "http_core.h"
+#include "http_protocol.h"
+#include "apr_strings.h"
+#include "apr_hash.h"
+#include "apr_time.h"
+#include "ap_mpm.h"
+#include "scoreboard.h"
+#include "mod_watchdog.h"
+#include "ap_slotmem.h"
+#include "heartbeat.h"
+
+
+#ifndef HM_UPDATE_SEC
+/* How often we update the stats file */
+/* TODO: Make a runtime config */
+#define HM_UPDATE_SEC (5)
+#endif
+
+#define HM_WATHCHDOG_NAME ("_heartmonitor_")
+
+static const ap_slotmem_provider_t *storage = NULL;
+static ap_slotmem_instance_t *slotmem = NULL;
+static int maxworkers = 10;
+
+module AP_MODULE_DECLARE_DATA heartmonitor_module;
+
+typedef struct hm_server_t
+{
+ const char *ip;
+ int busy;
+ int ready;
+ unsigned int port;
+ apr_time_t seen;
+} hm_server_t;
+
+typedef struct hm_ctx_t
+{
+ int active;
+ const char *storage_path;
+ ap_watchdog_t *watchdog;
+ apr_interval_time_t interval;
+ apr_sockaddr_t *mcast_addr;
+ apr_status_t status;
+ volatile int keep_running;
+ apr_socket_t *sock;
+ apr_pool_t *p;
+ apr_hash_t *servers;
+ server_rec *s;
+} hm_ctx_t;
+
+typedef struct hm_slot_server_ctx_t {
+ hm_server_t *s;
+ int found;
+ unsigned int item_id;
+} hm_slot_server_ctx_t;
+
+static apr_status_t hm_listen(hm_ctx_t *ctx)
+{
+ apr_status_t rv;
+
+ rv = apr_socket_create(&ctx->sock, ctx->mcast_addr->family,
+ SOCK_DGRAM, APR_PROTO_UDP, ctx->p);
+
+ if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ctx->s, APLOGNO(02068)
+ "Failed to create listening socket.");
+ return rv;
+ }
+
+ rv = apr_socket_opt_set(ctx->sock, APR_SO_REUSEADDR, 1);
+ if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ctx->s, APLOGNO(02069)
+ "Failed to set APR_SO_REUSEADDR to 1 on socket.");
+ return rv;
+ }
+
+
+ rv = apr_socket_opt_set(ctx->sock, APR_SO_NONBLOCK, 1);
+ if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ctx->s, APLOGNO(02070)
+ "Failed to set APR_SO_NONBLOCK to 1 on socket.");
+ return rv;
+ }
+
+ rv = apr_socket_bind(ctx->sock, ctx->mcast_addr);
+ if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ctx->s, APLOGNO(02071)
+ "Failed to bind on socket.");
+ return rv;
+ }
+
+ rv = apr_mcast_join(ctx->sock, ctx->mcast_addr, NULL, NULL);
+
+ if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ctx->s, APLOGNO(02072)
+ "Failed to join multicast group");
+ return rv;
+ }
+
+ rv = apr_mcast_loopback(ctx->sock, 1);
+ if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ctx->s, APLOGNO(02073)
+ "Failed to accept localhost mulitcast on socket.");
+ return rv;
+ }
+
+ return APR_SUCCESS;
+}
+
+/* XXX: The same exists in mod_lbmethod_heartbeat.c where it is named argstr_to_table */
+static void qs_to_table(const char *input, apr_table_t *parms,
+ apr_pool_t *p)
+{
+ char *key;
+ char *value;
+ char *query_string;
+ char *strtok_state;
+
+ if (input == NULL) {
+ return;
+ }
+
+ query_string = apr_pstrdup(p, input);
+
+ key = apr_strtok(query_string, "&", &strtok_state);
+ while (key) {
+ value = strchr(key, '=');
+ if (value) {
+ *value = '\0'; /* Split the string in two */
+ value++; /* Skip passed the = */
+ }
+ else {
+ value = "1";
+ }
+ ap_unescape_url(key);
+ ap_unescape_url(value);
+ apr_table_set(parms, key, value);
+ /*
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03182)
+ "Found query arg: %s = %s", key, value);
+ */
+ key = apr_strtok(NULL, "&", &strtok_state);
+ }
+}
+
+
+#define SEEN_TIMEOUT (30)
+
+/* Store in the slotmem */
+static apr_status_t hm_update(void* mem, void *data, apr_pool_t *p)
+{
+ hm_slot_server_t *old = (hm_slot_server_t *) mem;
+ hm_slot_server_ctx_t *s = (hm_slot_server_ctx_t *) data;
+ hm_server_t *new = s->s;
+ if (strcmp(old->ip, new->ip)==0) {
+ s->found = 1;
+ old->busy = new->busy;
+ old->ready = new->ready;
+ old->seen = new->seen;
+ }
+ return APR_SUCCESS;
+}
+/* Read the id corresponding to the entry in the slotmem */
+static apr_status_t hm_readid(void* mem, void *data, apr_pool_t *p)
+{
+ hm_slot_server_t *old = (hm_slot_server_t *) mem;
+ hm_slot_server_ctx_t *s = (hm_slot_server_ctx_t *) data;
+ hm_server_t *new = s->s;
+ if (strcmp(old->ip, new->ip)==0) {
+ s->found = 1;
+ s->item_id = old->id;
+ }
+ return APR_SUCCESS;
+}
+/* update the entry or create it if not existing */
+static apr_status_t hm_slotmem_update_stat(hm_server_t *s, apr_pool_t *pool)
+{
+ /* We call do_all (to try to update) otherwise grab + put */
+ hm_slot_server_ctx_t ctx;
+ ctx.s = s;
+ ctx.found = 0;
+ storage->doall(slotmem, hm_update, &ctx, pool);
+ if (!ctx.found) {
+ unsigned int i;
+ hm_slot_server_t hmserver;
+ memset(&hmserver, 0, sizeof(hmserver));
+ apr_cpystrn(hmserver.ip, s->ip, sizeof(hmserver.ip));
+ hmserver.busy = s->busy;
+ hmserver.ready = s->ready;
+ hmserver.seen = s->seen;
+ /* XXX locking for grab() / put() */
+ storage->grab(slotmem, &i);
+ hmserver.id = i;
+ storage->put(slotmem, i, (unsigned char *)&hmserver, sizeof(hmserver));
+ }
+ return APR_SUCCESS;
+}
+static apr_status_t hm_slotmem_remove_stat(hm_server_t *s, apr_pool_t *pool)
+{
+ hm_slot_server_ctx_t ctx;
+ ctx.s = s;
+ ctx.found = 0;
+ storage->doall(slotmem, hm_readid, &ctx, pool);
+ if (ctx.found) {
+ storage->release(slotmem, ctx.item_id);
+ }
+ return APR_SUCCESS;
+}
+static apr_status_t hm_file_update_stat(hm_ctx_t *ctx, hm_server_t *s, apr_pool_t *pool)
+{
+ apr_status_t rv;
+ apr_file_t *fp;
+ apr_file_t *fpin;
+ apr_time_t now;
+ apr_time_t fage;
+ apr_finfo_t fi;
+ int updated = 0;
+ char *path = apr_pstrcat(pool, ctx->storage_path, ".tmp.XXXXXX", NULL);
+
+
+ /* TODO: Update stats file (!) */
+ rv = apr_file_mktemp(&fp, path, APR_CREATE | APR_WRITE, pool);
+
+ if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ctx->s, APLOGNO(02074)
+ "Unable to open tmp file: %s", path);
+ return rv;
+ }
+ rv = apr_file_open(&fpin, ctx->storage_path, APR_READ|APR_BINARY|APR_BUFFERED,
+ APR_OS_DEFAULT, pool);
+
+ now = apr_time_now();
+ if (rv == APR_SUCCESS) {
+ char *t;
+ apr_table_t *hbt = apr_table_make(pool, 10);
+ apr_bucket_alloc_t *ba;
+ apr_bucket_brigade *bb;
+ apr_bucket_brigade *tmpbb;
+
+ rv = apr_file_info_get(&fi, APR_FINFO_SIZE | APR_FINFO_MTIME, fpin);
+ if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ctx->s, APLOGNO(02075)
+ "Unable to read file: %s", ctx->storage_path);
+ return rv;
+ }
+
+ /* Read the file and update the line corresponding to the node */
+ ba = apr_bucket_alloc_create(pool);
+ bb = apr_brigade_create(pool, ba);
+ apr_brigade_insert_file(bb, fpin, 0, fi.size, pool);
+ tmpbb = apr_brigade_create(pool, ba);
+ fage = apr_time_sec(now - fi.mtime);
+ do {
+ char buf[4096];
+ const char *ip;
+ apr_size_t bsize = sizeof(buf);
+
+ apr_brigade_cleanup(tmpbb);
+ if (APR_BRIGADE_EMPTY(bb)) {
+ break;
+ }
+ rv = apr_brigade_split_line(tmpbb, bb,
+ APR_BLOCK_READ, sizeof(buf));
+
+ if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ctx->s, APLOGNO(02076)
+ "Unable to read from file: %s", ctx->storage_path);
+ return rv;
+ }
+
+ apr_brigade_flatten(tmpbb, buf, &bsize);
+ if (bsize == 0) {
+ break;
+ }
+ buf[bsize - 1] = 0;
+ t = strchr(buf, ' ');
+ if (t) {
+ ip = apr_pstrmemdup(pool, buf, t - buf);
+ }
+ else {
+ ip = NULL;
+ }
+
+ if (!ip || buf[0] == '#') {
+ /* copy things we can't process */
+ apr_file_printf(fp, "%s\n", buf);
+ }
+ else if (strcmp(ip, s->ip) != 0 ) {
+ hm_server_t node;
+ apr_time_t seen;
+ const char *val;
+
+ /* Update seen time according to the last file modification */
+ apr_table_clear(hbt);
+ qs_to_table(apr_pstrdup(pool, t), hbt, pool);
+ if ((val = apr_table_get(hbt, "busy"))) {
+ node.busy = atoi(val);
+ }
+ else {
+ node.busy = 0;
+ }
+
+ if ((val = apr_table_get(hbt, "ready"))) {
+ node.ready = atoi(val);
+ }
+ else {
+ node.ready = 0;
+ }
+
+ if ((val = apr_table_get(hbt, "lastseen"))) {
+ node.seen = atoi(val);
+ }
+ else {
+ node.seen = SEEN_TIMEOUT;
+ }
+ seen = fage + node.seen;
+
+ if ((val = apr_table_get(hbt, "port"))) {
+ node.port = atoi(val);
+ }
+ else {
+ node.port = 80;
+ }
+ apr_file_printf(fp, "%s &ready=%u&busy=%u&lastseen=%u&port=%u\n",
+ ip, node.ready, node.busy, (unsigned int) seen, node.port);
+ }
+ else {
+ apr_time_t seen;
+ seen = apr_time_sec(now - s->seen);
+ apr_file_printf(fp, "%s &ready=%u&busy=%u&lastseen=%u&port=%u\n",
+ s->ip, s->ready, s->busy, (unsigned int) seen, s->port);
+ updated = 1;
+ }
+ } while (1);
+ }
+
+ if (!updated) {
+ apr_time_t seen;
+ seen = apr_time_sec(now - s->seen);
+ apr_file_printf(fp, "%s &ready=%u&busy=%u&lastseen=%u&port=%u\n",
+ s->ip, s->ready, s->busy, (unsigned int) seen, s->port);
+ }
+
+ rv = apr_file_flush(fp);
+ if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ctx->s, APLOGNO(02077)
+ "Unable to flush file: %s", path);
+ return rv;
+ }
+
+ rv = apr_file_close(fp);
+ if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ctx->s, APLOGNO(02078)
+ "Unable to close file: %s", path);
+ return rv;
+ }
+
+ rv = apr_file_perms_set(path,
+ APR_FPROT_UREAD | APR_FPROT_GREAD |
+ APR_FPROT_WREAD);
+ if (rv && rv != APR_INCOMPLETE && rv != APR_ENOTIMPL) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ctx->s, APLOGNO(02079)
+ "Unable to set file permissions on %s",
+ path);
+ return rv;
+ }
+
+ rv = apr_file_rename(path, ctx->storage_path, pool);
+
+ if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ctx->s, APLOGNO(02080)
+ "Unable to move file: %s -> %s", path,
+ ctx->storage_path);
+ return rv;
+ }
+
+ return APR_SUCCESS;
+}
+static apr_status_t hm_update_stat(hm_ctx_t *ctx, hm_server_t *s, apr_pool_t *pool)
+{
+ if (slotmem)
+ return hm_slotmem_update_stat(s, pool);
+ else
+ return hm_file_update_stat(ctx, s, pool);
+}
+
+/* Store in a file */
+static apr_status_t hm_file_update_stats(hm_ctx_t *ctx, apr_pool_t *p)
+{
+ apr_status_t rv;
+ apr_file_t *fp;
+ apr_hash_index_t *hi;
+ apr_time_t now;
+ char *path = apr_pstrcat(p, ctx->storage_path, ".tmp.XXXXXX", NULL);
+ /* TODO: Update stats file (!) */
+ rv = apr_file_mktemp(&fp, path, APR_CREATE | APR_WRITE, p);
+
+ if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ctx->s, APLOGNO(02081)
+ "Unable to open tmp file: %s", path);
+ return rv;
+ }
+
+ now = apr_time_now();
+ for (hi = apr_hash_first(p, ctx->servers);
+ hi != NULL; hi = apr_hash_next(hi)) {
+ hm_server_t *s = NULL;
+ apr_time_t seen;
+ apr_hash_this(hi, NULL, NULL, (void **) &s);
+ seen = apr_time_sec(now - s->seen);
+ if (seen > SEEN_TIMEOUT) {
+ /*
+ * Skip this entry from the heartbeat file -- when it comes back,
+ * we will reuse the memory...
+ */
+ }
+ else {
+ apr_file_printf(fp, "%s &ready=%u&busy=%u&lastseen=%u&port=%u\n",
+ s->ip, s->ready, s->busy, (unsigned int) seen, s->port);
+ }
+ }
+
+ rv = apr_file_flush(fp);
+ if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ctx->s, APLOGNO(02082)
+ "Unable to flush file: %s", path);
+ return rv;
+ }
+
+ rv = apr_file_close(fp);
+ if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ctx->s, APLOGNO(02083)
+ "Unable to close file: %s", path);
+ return rv;
+ }
+
+ rv = apr_file_perms_set(path,
+ APR_FPROT_UREAD | APR_FPROT_GREAD |
+ APR_FPROT_WREAD);
+ if (rv && rv != APR_INCOMPLETE && rv != APR_ENOTIMPL) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ctx->s, APLOGNO(02084)
+ "Unable to set file permissions on %s",
+ path);
+ return rv;
+ }
+
+ rv = apr_file_rename(path, ctx->storage_path, p);
+
+ if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ctx->s, APLOGNO(02085)
+ "Unable to move file: %s -> %s", path,
+ ctx->storage_path);
+ return rv;
+ }
+
+ return APR_SUCCESS;
+}
+/* Store in a slotmem */
+static apr_status_t hm_slotmem_update_stats(hm_ctx_t *ctx, apr_pool_t *p)
+{
+ apr_status_t rv;
+ apr_time_t now;
+ apr_hash_index_t *hi;
+ now = apr_time_now();
+ for (hi = apr_hash_first(p, ctx->servers);
+ hi != NULL; hi = apr_hash_next(hi)) {
+ hm_server_t *s = NULL;
+ apr_time_t seen;
+ apr_hash_this(hi, NULL, NULL, (void **) &s);
+ seen = apr_time_sec(now - s->seen);
+ if (seen > SEEN_TIMEOUT) {
+ /* remove it */
+ rv = hm_slotmem_remove_stat(s, p);
+ } else {
+ /* update it */
+ rv = hm_slotmem_update_stat(s, p);
+ }
+ if (rv !=APR_SUCCESS)
+ return rv;
+ }
+ return APR_SUCCESS;
+}
+/* Store/update the stats */
+static apr_status_t hm_update_stats(hm_ctx_t *ctx, apr_pool_t *p)
+{
+ if (slotmem)
+ return hm_slotmem_update_stats(ctx, p);
+ else
+ return hm_file_update_stats(ctx, p);
+}
+
+static hm_server_t *hm_get_server(hm_ctx_t *ctx, const char *ip, const int port)
+{
+ hm_server_t *s;
+
+ s = apr_hash_get(ctx->servers, ip, APR_HASH_KEY_STRING);
+
+ if (s == NULL) {
+ s = apr_palloc(ctx->p, sizeof(hm_server_t));
+ s->ip = apr_pstrdup(ctx->p, ip);
+ s->port = port;
+ s->ready = 0;
+ s->busy = 0;
+ s->seen = 0;
+ apr_hash_set(ctx->servers, s->ip, APR_HASH_KEY_STRING, s);
+ }
+
+ return s;
+}
+
+/* Process a message received from a backend node */
+static void hm_processmsg(hm_ctx_t *ctx, apr_pool_t *p,
+ apr_sockaddr_t *from, char *buf, apr_size_t len)
+{
+ apr_table_t *tbl;
+
+ buf[len] = '\0';
+
+ tbl = apr_table_make(p, 10);
+
+ qs_to_table(buf, tbl, p);
+
+ if (apr_table_get(tbl, "v") != NULL &&
+ apr_table_get(tbl, "busy") != NULL &&
+ apr_table_get(tbl, "ready") != NULL) {
+ char *ip;
+ int port = 80;
+ hm_server_t *s;
+ /* TODO: REMOVE ME BEFORE PRODUCTION (????) */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ctx->s, APLOGNO(02086)
+ "%pI busy=%s ready=%s", from,
+ apr_table_get(tbl, "busy"), apr_table_get(tbl, "ready"));
+
+ apr_sockaddr_ip_get(&ip, from);
+
+ if (apr_table_get(tbl, "port") != NULL)
+ port = atoi(apr_table_get(tbl, "port"));
+
+ s = hm_get_server(ctx, ip, port);
+
+ s->busy = atoi(apr_table_get(tbl, "busy"));
+ s->ready = atoi(apr_table_get(tbl, "ready"));
+ s->seen = apr_time_now();
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, ctx->s, APLOGNO(02087)
+ "malformed message from %pI",
+ from);
+ }
+
+}
+/* Read message from multicast socket */
+#define MAX_MSG_LEN (1000)
+static apr_status_t hm_recv(hm_ctx_t *ctx, apr_pool_t *p)
+{
+ char buf[MAX_MSG_LEN + 1];
+ apr_sockaddr_t from;
+ apr_size_t len = MAX_MSG_LEN;
+ apr_status_t rv;
+
+ from.pool = p;
+
+ rv = apr_socket_recvfrom(&from, ctx->sock, 0, buf, &len);
+
+ if (APR_STATUS_IS_EAGAIN(rv)) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ctx->s, APLOGNO(02088) "would block");
+ return APR_SUCCESS;
+ }
+ else if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ctx->s, APLOGNO(02089) "recvfrom failed");
+ return rv;
+ }
+
+ hm_processmsg(ctx, p, &from, buf, len);
+
+ return rv;
+}
+
+static apr_status_t hm_watchdog_callback(int state, void *data,
+ apr_pool_t *pool)
+{
+ apr_status_t rv = APR_SUCCESS;
+ apr_time_t cur, now;
+ hm_ctx_t *ctx = (hm_ctx_t *)data;
+
+ if (!ctx->active) {
+ return rv;
+ }
+
+ switch (state) {
+ case AP_WATCHDOG_STATE_STARTING:
+ rv = hm_listen(ctx);
+ if (rv) {
+ ctx->status = rv;
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ctx->s, APLOGNO(02090)
+ "Unable to listen for connections!");
+ }
+ else {
+ ctx->keep_running = 1;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ctx->s, APLOGNO(02091)
+ "%s listener started.",
+ HM_WATHCHDOG_NAME);
+ }
+ break;
+ case AP_WATCHDOG_STATE_RUNNING:
+ /* store in the slotmem or in the file depending on configuration */
+ hm_update_stats(ctx, pool);
+ cur = now = apr_time_sec(apr_time_now());
+
+ while ((now - cur) < apr_time_sec(ctx->interval)) {
+ int n;
+ apr_status_t rc;
+ apr_pool_t *p;
+ apr_pollfd_t pfd;
+ apr_interval_time_t timeout;
+
+ apr_pool_create(&p, pool);
+ apr_pool_tag(p, "hm_running");
+
+ pfd.desc_type = APR_POLL_SOCKET;
+ pfd.desc.s = ctx->sock;
+ pfd.p = p;
+ pfd.reqevents = APR_POLLIN;
+
+ timeout = apr_time_from_sec(1);
+
+ rc = apr_poll(&pfd, 1, &n, timeout);
+
+ if (!ctx->keep_running) {
+ apr_pool_destroy(p);
+ break;
+ }
+ if (rc == APR_SUCCESS && (pfd.rtnevents & APR_POLLIN)) {
+ hm_recv(ctx, p);
+ }
+ now = apr_time_sec(apr_time_now());
+ apr_pool_destroy(p);
+ }
+ break;
+ case AP_WATCHDOG_STATE_STOPPING:
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ctx->s, APLOGNO(02092)
+ "stopping %s listener.",
+ HM_WATHCHDOG_NAME);
+
+ ctx->keep_running = 0;
+ if (ctx->sock) {
+ apr_socket_close(ctx->sock);
+ ctx->sock = NULL;
+ }
+ break;
+ }
+ return rv;
+}
+
+static int hm_post_config(apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ apr_status_t rv;
+ hm_ctx_t *ctx = ap_get_module_config(s->module_config,
+ &heartmonitor_module);
+ APR_OPTIONAL_FN_TYPE(ap_watchdog_get_instance) *hm_watchdog_get_instance;
+ APR_OPTIONAL_FN_TYPE(ap_watchdog_register_callback) *hm_watchdog_register_callback;
+
+ hm_watchdog_get_instance = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_get_instance);
+ hm_watchdog_register_callback = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_register_callback);
+ if (!hm_watchdog_get_instance || !hm_watchdog_register_callback) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s, APLOGNO(02093)
+ "mod_watchdog is required");
+ return !OK;
+ }
+
+ /* Create the slotmem */
+ if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_CONFIG) {
+ /* this is the real thing */
+ if (maxworkers) {
+ storage = ap_lookup_provider(AP_SLOTMEM_PROVIDER_GROUP, "shm",
+ AP_SLOTMEM_PROVIDER_VERSION);
+ if (!storage) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02284)
+ "failed to lookup provider 'shm' for '%s', "
+ "maybe you need to load mod_slotmem_shm?",
+ AP_SLOTMEM_PROVIDER_GROUP);
+ return !OK;
+ }
+ storage->create(&slotmem, "mod_heartmonitor", sizeof(hm_slot_server_t), maxworkers, AP_SLOTMEM_TYPE_PREGRAB, p);
+ if (!slotmem) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02285)
+ "slotmem_create for status failed");
+ return !OK;
+ }
+ }
+ }
+
+ if (!ctx->active) {
+ return OK;
+ }
+ rv = hm_watchdog_get_instance(&ctx->watchdog,
+ HM_WATHCHDOG_NAME,
+ 0, 1, p);
+ if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(02094)
+ "Failed to create watchdog instance (%s)",
+ HM_WATHCHDOG_NAME);
+ return !OK;
+ }
+ /* Register a callback with zero interval. */
+ rv = hm_watchdog_register_callback(ctx->watchdog,
+ 0,
+ ctx,
+ hm_watchdog_callback);
+ if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(02095)
+ "Failed to register watchdog callback (%s)",
+ HM_WATHCHDOG_NAME);
+ return !OK;
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02096)
+ "wd callback %s", HM_WATHCHDOG_NAME);
+ return OK;
+}
+
+static int hm_handler(request_rec *r)
+{
+ apr_bucket_brigade *input_brigade;
+ apr_size_t len;
+ char *buf;
+ apr_status_t status;
+ apr_table_t *tbl;
+ hm_server_t hmserver;
+ char *ip;
+ hm_ctx_t *ctx;
+
+ if (strcmp(r->handler, "heartbeat")) {
+ return DECLINED;
+ }
+ if (r->method_number != M_POST) {
+ return HTTP_METHOD_NOT_ALLOWED;
+ }
+
+ len = MAX_MSG_LEN;
+ ctx = ap_get_module_config(r->server->module_config,
+ &heartmonitor_module);
+
+ buf = apr_pcalloc(r->pool, MAX_MSG_LEN);
+ input_brigade = apr_brigade_create(r->connection->pool, r->connection->bucket_alloc);
+ status = ap_get_brigade(r->input_filters, input_brigade, AP_MODE_READBYTES, APR_BLOCK_READ, MAX_MSG_LEN);
+ if (status != APR_SUCCESS) {
+ return ap_map_http_request_error(status, HTTP_BAD_REQUEST);
+ }
+ apr_brigade_flatten(input_brigade, buf, &len);
+
+ /* we can't use hm_processmsg because it uses hm_get_server() */
+ buf[len] = '\0';
+ tbl = apr_table_make(r->pool, 10);
+ qs_to_table(buf, tbl, r->pool);
+ apr_sockaddr_ip_get(&ip, r->connection->client_addr);
+ hmserver.ip = ip;
+ hmserver.port = 80;
+ if (apr_table_get(tbl, "port") != NULL)
+ hmserver.port = atoi(apr_table_get(tbl, "port"));
+ hmserver.busy = atoi(apr_table_get(tbl, "busy"));
+ hmserver.ready = atoi(apr_table_get(tbl, "ready"));
+ hmserver.seen = apr_time_now();
+ hm_update_stat(ctx, &hmserver, r->pool);
+
+ ap_set_content_type(r, "text/plain");
+ ap_set_content_length(r, 2);
+ ap_rputs("OK", r);
+ ap_rflush(r);
+
+ return OK;
+}
+
+static void hm_register_hooks(apr_pool_t *p)
+{
+ static const char * const aszSucc[]={ "mod_proxy.c", NULL };
+ ap_hook_post_config(hm_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+
+ ap_hook_handler(hm_handler, NULL, aszSucc, APR_HOOK_FIRST);
+}
+
+static void *hm_create_config(apr_pool_t *p, server_rec *s)
+{
+ hm_ctx_t *ctx = (hm_ctx_t *) apr_palloc(p, sizeof(hm_ctx_t));
+
+ ctx->active = 0;
+ ctx->storage_path = ap_runtime_dir_relative(p, DEFAULT_HEARTBEAT_STORAGE);
+ /* TODO: Add directive for tuning the update interval
+ */
+ ctx->interval = apr_time_from_sec(HM_UPDATE_SEC);
+ ctx->s = s;
+ apr_pool_create(&ctx->p, p);
+ apr_pool_tag(ctx->p, "hm_ctx");
+ ctx->servers = apr_hash_make(ctx->p);
+
+ return ctx;
+}
+
+static const char *cmd_hm_storage(cmd_parms *cmd,
+ void *dconf, const char *path)
+{
+ apr_pool_t *p = cmd->pool;
+ hm_ctx_t *ctx =
+ (hm_ctx_t *) ap_get_module_config(cmd->server->module_config,
+ &heartmonitor_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ ctx->storage_path = ap_runtime_dir_relative(p, path);
+
+ return NULL;
+}
+
+static const char *cmd_hm_listen(cmd_parms *cmd,
+ void *dconf, const char *mcast_addr)
+{
+ apr_status_t rv;
+ char *host_str;
+ char *scope_id;
+ apr_port_t port = 0;
+ apr_pool_t *p = cmd->pool;
+ hm_ctx_t *ctx =
+ (hm_ctx_t *) ap_get_module_config(cmd->server->module_config,
+ &heartmonitor_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ if (!ctx->active) {
+ ctx->active = 1;
+ }
+ else {
+ return "HeartbeatListen: May only be specified once.";
+ }
+
+ rv = apr_parse_addr_port(&host_str, &scope_id, &port, mcast_addr, cmd->temp_pool);
+
+ if (rv) {
+ return "HeartbeatListen: Unable to parse multicast address.";
+ }
+
+ if (host_str == NULL) {
+ return "HeartbeatListen: No host provided in multicast address";
+ }
+
+ if (port == 0) {
+ return "HeartbeatListen: No port provided in multicast address";
+ }
+
+ rv = apr_sockaddr_info_get(&ctx->mcast_addr, host_str, APR_INET, port, 0,
+ p);
+
+ if (rv) {
+ return
+ "HeartbeatListen: apr_sockaddr_info_get failed on multicast address";
+ }
+
+ return NULL;
+}
+
+static const char *cmd_hm_maxworkers(cmd_parms *cmd,
+ void *dconf, const char *data)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ maxworkers = atoi(data);
+ if (maxworkers != 0 && maxworkers < 10)
+ return "HeartbeatMaxServers: Should be 0 for file storage, "
+ "or greater or equal than 10 for slotmem";
+
+ return NULL;
+}
+
+static const command_rec hm_cmds[] = {
+ AP_INIT_TAKE1("HeartbeatListen", cmd_hm_listen, NULL, RSRC_CONF,
+ "Address to listen for heartbeat requests"),
+ AP_INIT_TAKE1("HeartbeatStorage", cmd_hm_storage, NULL, RSRC_CONF,
+ "Path to store heartbeat data."),
+ AP_INIT_TAKE1("HeartbeatMaxServers", cmd_hm_maxworkers, NULL, RSRC_CONF,
+ "Max number of servers when using slotmem (instead file) to store heartbeat data."),
+ {NULL}
+};
+
+AP_DECLARE_MODULE(heartmonitor) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ hm_create_config, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ hm_cmds, /* command apr_table_t */
+ hm_register_hooks
+};
diff --git a/modules/cluster/mod_heartmonitor.dep b/modules/cluster/mod_heartmonitor.dep
new file mode 100644
index 0000000..266efbf
--- /dev/null
+++ b/modules/cluster/mod_heartmonitor.dep
@@ -0,0 +1,63 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_heartmonitor.mak
+
+.\mod_heartmonitor.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_mpm.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_slotmem.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\heartbeat.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ "..\core\mod_watchdog.h"\
+
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
diff --git a/modules/cluster/mod_heartmonitor.dsp b/modules/cluster/mod_heartmonitor.dsp
new file mode 100644
index 0000000..8eb716b
--- /dev/null
+++ b/modules/cluster/mod_heartmonitor.dsp
@@ -0,0 +1,123 @@
+# Microsoft Developer Studio Project File - Name="mod_heartmonitor" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_heartmonitor - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_heartmonitor.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_heartmonitor.mak" CFG="mod_heartmonitor - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_heartmonitor - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_heartmonitor - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_heartmonitor - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../core" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_heartmonitor_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_heartmonitor.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_heartmonitor.so" /d LONG_NAME="heartmonitor_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:".\Release\mod_heartmonitor.so" /base:@..\..\os\win32\BaseAddr.ref,mod_heartmonitor.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_heartmonitor.so" /base:@..\..\os\win32\BaseAddr.ref,mod_heartmonitor.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_heartmonitor.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_heartmonitor - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../core" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_heartmonitor_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_heartmonitor.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_heartmonitor.so" /d LONG_NAME="heartmonitor_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_heartmonitor.so" /base:@..\..\os\win32\BaseAddr.ref,mod_heartmonitor.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_heartmonitor.so" /base:@..\..\os\win32\BaseAddr.ref,mod_heartmonitor.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_heartmonitor.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_heartmonitor - Win32 Release"
+# Name "mod_heartmonitor - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\mod_heartmonitor.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter ".h"
+# Begin Source File
+
+SOURCE=.\mod_proxy.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/cluster/mod_heartmonitor.mak b/modules/cluster/mod_heartmonitor.mak
new file mode 100644
index 0000000..2d935d5
--- /dev/null
+++ b/modules/cluster/mod_heartmonitor.mak
@@ -0,0 +1,380 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_heartmonitor.dsp
+!IF "$(CFG)" == ""
+CFG=mod_heartmonitor - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_heartmonitor - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_heartmonitor - Win32 Release" && "$(CFG)" != "mod_heartmonitor - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_heartmonitor.mak" CFG="mod_heartmonitor - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_heartmonitor - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_heartmonitor - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_heartmonitor - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_heartmonitor.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_watchdog - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_heartmonitor.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_watchdog - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_heartmonitor.obj"
+ -@erase "$(INTDIR)\mod_heartmonitor.res"
+ -@erase "$(INTDIR)\mod_heartmonitor_src.idb"
+ -@erase "$(INTDIR)\mod_heartmonitor_src.pdb"
+ -@erase "$(OUTDIR)\mod_heartmonitor.exp"
+ -@erase "$(OUTDIR)\mod_heartmonitor.lib"
+ -@erase "$(OUTDIR)\mod_heartmonitor.pdb"
+ -@erase "$(OUTDIR)\mod_heartmonitor.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../core" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_heartmonitor_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_heartmonitor.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_heartmonitor.so" /d LONG_NAME="heartmonitor_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_heartmonitor.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_heartmonitor.pdb" /debug /out:"$(OUTDIR)\mod_heartmonitor.so" /implib:"$(OUTDIR)\mod_heartmonitor.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_heartmonitor.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_heartmonitor.obj" \
+ "$(INTDIR)\mod_heartmonitor.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib" \
+ "..\core\Release\mod_watchdog.lib"
+
+"$(OUTDIR)\mod_heartmonitor.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_heartmonitor.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_heartmonitor.so"
+ if exist .\Release\mod_heartmonitor.so.manifest mt.exe -manifest .\Release\mod_heartmonitor.so.manifest -outputresource:.\Release\mod_heartmonitor.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_heartmonitor - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_heartmonitor.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_watchdog - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_heartmonitor.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_watchdog - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_heartmonitor.obj"
+ -@erase "$(INTDIR)\mod_heartmonitor.res"
+ -@erase "$(INTDIR)\mod_heartmonitor_src.idb"
+ -@erase "$(INTDIR)\mod_heartmonitor_src.pdb"
+ -@erase "$(OUTDIR)\mod_heartmonitor.exp"
+ -@erase "$(OUTDIR)\mod_heartmonitor.lib"
+ -@erase "$(OUTDIR)\mod_heartmonitor.pdb"
+ -@erase "$(OUTDIR)\mod_heartmonitor.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../core" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_heartmonitor_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_heartmonitor.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_heartmonitor.so" /d LONG_NAME="heartmonitor_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_heartmonitor.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_heartmonitor.pdb" /debug /out:"$(OUTDIR)\mod_heartmonitor.so" /implib:"$(OUTDIR)\mod_heartmonitor.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_heartmonitor.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_heartmonitor.obj" \
+ "$(INTDIR)\mod_heartmonitor.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib" \
+ "..\core\Debug\mod_watchdog.lib"
+
+"$(OUTDIR)\mod_heartmonitor.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_heartmonitor.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_heartmonitor.so"
+ if exist .\Debug\mod_heartmonitor.so.manifest mt.exe -manifest .\Debug\mod_heartmonitor.so.manifest -outputresource:.\Debug\mod_heartmonitor.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_heartmonitor.dep")
+!INCLUDE "mod_heartmonitor.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_heartmonitor.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_heartmonitor - Win32 Release" || "$(CFG)" == "mod_heartmonitor - Win32 Debug"
+SOURCE=.\mod_heartmonitor.c
+
+"$(INTDIR)\mod_heartmonitor.obj" : $(SOURCE) "$(INTDIR)"
+
+
+!IF "$(CFG)" == "mod_heartmonitor - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\cluster"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\cluster"
+
+!ELSEIF "$(CFG)" == "mod_heartmonitor - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\cluster"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\cluster"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_heartmonitor - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\cluster"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\cluster"
+
+!ELSEIF "$(CFG)" == "mod_heartmonitor - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\cluster"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\cluster"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_heartmonitor - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\cluster"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\cluster"
+
+!ELSEIF "$(CFG)" == "mod_heartmonitor - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\cluster"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\cluster"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_heartmonitor - Win32 Release"
+
+"mod_watchdog - Win32 Release" :
+ cd ".\..\core"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_watchdog.mak" CFG="mod_watchdog - Win32 Release"
+ cd "..\cluster"
+
+"mod_watchdog - Win32 ReleaseCLEAN" :
+ cd ".\..\core"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_watchdog.mak" CFG="mod_watchdog - Win32 Release" RECURSE=1 CLEAN
+ cd "..\cluster"
+
+!ELSEIF "$(CFG)" == "mod_heartmonitor - Win32 Debug"
+
+"mod_watchdog - Win32 Debug" :
+ cd ".\..\core"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_watchdog.mak" CFG="mod_watchdog - Win32 Debug"
+ cd "..\cluster"
+
+"mod_watchdog - Win32 DebugCLEAN" :
+ cd ".\..\core"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_watchdog.mak" CFG="mod_watchdog - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\cluster"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_heartmonitor - Win32 Release"
+
+
+"$(INTDIR)\mod_heartmonitor.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_heartmonitor.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_heartmonitor.so" /d LONG_NAME="heartmonitor_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_heartmonitor - Win32 Debug"
+
+
+"$(INTDIR)\mod_heartmonitor.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_heartmonitor.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_heartmonitor.so" /d LONG_NAME="heartmonitor_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+
+!ENDIF
+
diff --git a/modules/config7.m4 b/modules/config7.m4
new file mode 100644
index 0000000..16f2ff3
--- /dev/null
+++ b/modules/config7.m4
@@ -0,0 +1,56 @@
+AC_MSG_CHECKING(for extra modules)
+AC_ARG_WITH(module,
+ APACHE_HELP_STRING(--with-module=module-type:module-file,
+ Enable module-file in the modules/<module-type> directory.),
+ [
+ withval=`echo $withval | sed -e 's/,/ /g'`
+ for mod in $withval
+ do
+ modtype=`echo $mod | sed -e's/\(.*\):.*/\1/'`
+ pkg=`echo $mod | sed -e's/.*:\(.*\)/\1/'`
+ modfilec=`echo $pkg | sed -e 's;^.*/;;'`
+ modfileo=`echo $pkg | sed -e 's;^.*/;;' -e 's;\.c$;.o;'`
+ modpath_current="modules/$modtype"
+ if test "x$mod" != "x$modpath_current/$modfilec"; then
+ if test ! -d "$modpath_current"; then
+ mkdir $modpath_current
+ echo 'include $(top_srcdir)/build/special.mk' > $modpath_current/Makefile.in
+ fi
+ cp $pkg $modpath_current/$modfilec
+ fi
+ module=`echo $pkg | sed -e 's;\(.*/\).*mod_\(.*\).c;\2;'`
+ objects="mod_$module.lo"
+ # The filename of a convenience library must have a "lib" prefix:
+ libname="libmod_$module.la"
+ BUILTIN_LIBS="$BUILTIN_LIBS $modpath_current/$libname"
+ if test ! -s "$modpath_current/modules.mk"; then
+ cat >>$modpath_current/modules.mk<<EOF
+$libname: $objects
+ \$(MOD_LINK) $objects
+DISTCLEAN_TARGETS = modules.mk
+static = $libname
+shared =
+EOF
+ else
+ cat >>$modpath_current/modules.mk.tmp<<EOF
+$libname: $objects
+ \$(MOD_LINK) $objects
+EOF
+ cat $modpath_current/modules.mk >> $modpath_current/modules.mk.tmp
+ rm $modpath_current/modules.mk
+ mv $modpath_current/modules.mk.tmp $modpath_current/modules.mk
+ sed -e "s/\(static =.*\)/\1 $libname/" $modpath_current/modules.mk > $modpath_current/modules.mk.tmp
+ rm $modpath_current/modules.mk
+ mv $modpath_current/modules.mk.tmp $modpath_current/modules.mk
+ fi
+ MODLIST="$MODLIST $module"
+ EXTRA_MODLIST="$EXTRA_MODLIST $modtype:$modfilec"
+ MODULE_DIRS="$MODULE_DIRS $modtype"
+ APACHE_FAST_OUTPUT($modpath_current/Makefile)
+ done
+ if test ! -z "$EXTRA_MODLIST"; then
+ AC_MSG_RESULT(added:$EXTRA_MODLIST)
+ fi
+ ],
+ [ AC_MSG_RESULT(none)
+ ])
diff --git a/modules/core/Makefile.in b/modules/core/Makefile.in
new file mode 100644
index 0000000..167b343
--- /dev/null
+++ b/modules/core/Makefile.in
@@ -0,0 +1,3 @@
+
+include $(top_srcdir)/build/special.mk
+
diff --git a/modules/core/NWGNUmakefile b/modules/core/NWGNUmakefile
new file mode 100644
index 0000000..a4dd8d3
--- /dev/null
+++ b/modules/core/NWGNUmakefile
@@ -0,0 +1,257 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(SRC)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = macro
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Macro Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Echo Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/macro.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_macro.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ macro_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/core/config.m4 b/modules/core/config.m4
new file mode 100644
index 0000000..94fb4a1
--- /dev/null
+++ b/modules/core/config.m4
@@ -0,0 +1,60 @@
+dnl modules enabled in this directory by default
+
+dnl APACHE_MODULE(name, helptext[, objects[, structname[, default[, config]]]])
+
+APACHE_MODPATH_INIT(core)
+
+APR_CHECK_APR_DEFINE(APR_HAS_DSO)
+
+case "x$enable_so" in
+ "xyes")
+ if test $ac_cv_define_APR_HAS_DSO = "no"; then
+ AC_MSG_ERROR([mod_so has been requested but cannot be built on your system])
+ fi
+ ;;
+ "xshared")
+ AC_MSG_ERROR([mod_so can not be built as a shared DSO])
+ ;;
+ "xno")
+ ;;
+ "x")
+ enable_so=$ac_cv_define_APR_HAS_DSO
+ ;;
+esac
+
+dnl mod_so can only be built statically. Override the default here.
+if test "x$enable_so" = "xyes"; then
+ enable_so="static"
+fi
+
+if test "x$enable_so" = "xstatic"; then
+ APR_ADDTO(HTTPD_LDFLAGS, [-export-dynamic])
+ INSTALL_DSO=yes
+else
+ INSTALL_DSO=no
+fi
+APACHE_SUBST(INSTALL_DSO)
+
+if test "$sharedobjs" = "yes"; then
+ if test $ac_cv_define_APR_HAS_DSO = "no"; then
+ AC_MSG_ERROR([shared objects have been requested but cannot be built since mod_so cannot be built])
+ elif test $enable_so = "no"; then
+ AC_MSG_ERROR([shared objects have been requested but cannot be built since mod_so was disabled])
+ fi
+fi
+
+APACHE_MODULE(so, DSO capability. This module will be automatically enabled unless you build all modules statically., , , $enable_so)
+
+APACHE_MODULE(watchdog, Watchdog module, , , most, [
+ APR_CHECK_APR_DEFINE(APR_HAS_THREADS)
+ if test $ac_cv_define_APR_HAS_THREADS = "no"; then
+ AC_MSG_WARN([mod_watchdog requires apr to be built with --enable-threads])
+ enable_watchdog=no
+ fi
+])
+
+APACHE_MODULE(macro, Define and use macros in configuration files, , , most)
+
+APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/$modpath_current])
+
+APACHE_MODPATH_FINISH
diff --git a/modules/core/mod_macro.c b/modules/core/mod_macro.c
new file mode 100644
index 0000000..04af43b
--- /dev/null
+++ b/modules/core/mod_macro.c
@@ -0,0 +1,950 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_hash.h"
+
+/************************************************ COMPILE TIME DEBUG CONTROL */
+/*
+ debug:
+ #define MOD_MACRO_DEBUG 1
+
+ gdb:
+ run -f ./test/conf/test??.conf
+*/
+/* #define MOD_MACRO_DEBUG 1 */
+#undef MOD_MACRO_DEBUG
+
+#if defined(debug)
+#undef debug
+#endif /* debug */
+
+#if defined(MOD_MACRO_DEBUG)
+#define debug(stmt) stmt
+#else
+#define debug(stmt)
+#endif /* MOD_MACRO_DEBUG */
+
+/******************************************************** MODULE DECLARATION */
+
+module AP_MODULE_DECLARE_DATA macro_module;
+
+/********************************************************** MACRO MANAGEMENT */
+
+/*
+ this is a macro: name, arguments, contents, location.
+*/
+typedef struct
+{
+ char *name; /* lower case name of the macro */
+ apr_array_header_t *arguments; /* of char*, macro parameter names */
+ apr_array_header_t *contents; /* of char*, macro body */
+ char *location; /* of macro definition, for error messages */
+} ap_macro_t;
+
+/* configuration tokens.
+ */
+#define BEGIN_MACRO "<Macro"
+#define END_MACRO "</Macro>"
+#define USE_MACRO "Use"
+#define UNDEF_MACRO "UndefMacro"
+
+/*
+ Macros are kept globally...
+ They are not per-server or per-directory entities.
+
+ note: they are in a temp_pool, and there is a lazy initialization.
+ ap_macros is reset to NULL in pre_config hook to not depend
+ on static vs dynamic configuration.
+
+ hash type: (char *) name -> (ap_macro_t *) macro
+*/
+static apr_hash_t *ap_macros = NULL;
+
+/*************************************************************** PARSE UTILS */
+
+#define empty_string_p(p) (!(p) || *(p) == '\0')
+#define trim(line) while (*(line) == ' ' || *(line) == '\t') (line)++
+
+/*
+ return configuration-parsed arguments from line as an array.
+ the line is expected not to contain any '\n'?
+*/
+static apr_array_header_t *get_arguments(apr_pool_t * pool, const char *line)
+{
+ apr_array_header_t *args = apr_array_make(pool, 1, sizeof(char *));
+
+ trim(line);
+ while (*line) {
+ char *arg = ap_getword_conf(pool, &line);
+ char **new = apr_array_push(args);
+ *new = arg;
+ trim(line);
+ }
+
+ return args;
+}
+
+/*
+ warn if anything non blank appears, but ignore comments...
+*/
+static void warn_if_non_blank(const char * what,
+ char * ptr,
+ ap_configfile_t * cfg)
+{
+ char * p;
+ for (p=ptr; *p; p++) {
+ if (*p == '#')
+ break;
+ if (*p != ' ' && *p != '\t') {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, APLOGNO(02989)
+ "%s on line %d of %s: %s",
+ what, cfg->line_number, cfg->name, ptr);
+ break;
+ }
+ }
+}
+
+/*
+ get read lines as an array till end_token.
+ counts nesting for begin_token/end_token.
+ it assumes a line-per-line configuration (thru getline).
+ this function could be exported.
+ begin_token may be NULL.
+*/
+static char *get_lines_till_end_token(apr_pool_t * pool,
+ ap_configfile_t * config_file,
+ const char *end_token,
+ const char *begin_token,
+ const char *where,
+ apr_array_header_t ** plines)
+{
+ apr_array_header_t *lines = apr_array_make(pool, 1, sizeof(char *));
+ char line[MAX_STRING_LEN]; /* sorry, but this is expected by getline:-( */
+ int macro_nesting = 1, any_nesting = 1;
+ int line_number_start = config_file->line_number;
+
+ while (!ap_cfg_getline(line, MAX_STRING_LEN, config_file)) {
+ char *ptr = line;
+ char *first, **new;
+ /* skip comments */
+ if (*line == '#')
+ continue;
+ first = ap_getword_conf_nc(pool, &ptr);
+ if (first) {
+ /* detect nesting... */
+ if (!strncmp(first, "</", 2)) {
+ any_nesting--;
+ if (any_nesting < 0) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING,
+ 0, NULL, APLOGNO(02793)
+ "bad (negative) nesting on line %d of %s",
+ config_file->line_number - line_number_start,
+ where);
+ }
+ }
+ else if (!strncmp(first, "<", 1)) {
+ any_nesting++;
+ }
+
+ if (!strcasecmp(first, end_token)) {
+ /* check for proper closing */
+ char * endp = (char *) ap_strrchr_c(line, '>');
+
+ /* this cannot happen if end_token contains '>' */
+ if (endp == NULL) {
+ return "end directive missing closing '>'";
+ }
+
+ warn_if_non_blank(
+ APLOGNO(02794) "non blank chars found after directive closing",
+ endp+1, config_file);
+
+ macro_nesting--;
+ if (!macro_nesting) {
+ if (any_nesting) {
+ ap_log_error(APLOG_MARK,
+ APLOG_WARNING, 0, NULL, APLOGNO(02795)
+ "bad cumulated nesting (%+d) in %s",
+ any_nesting, where);
+ }
+ *plines = lines;
+ return NULL;
+ }
+ }
+ else if (begin_token && !strcasecmp(first, begin_token)) {
+ macro_nesting++;
+ }
+ }
+ new = apr_array_push(lines);
+ *new = apr_psprintf(pool, "%s" APR_EOL_STR, line); /* put EOL back? */
+ }
+
+ return apr_psprintf(pool, "expected token not found: %s", end_token);
+}
+
+/* the @* arguments are double-quote escaped when substituted */
+#define ESCAPE_ARG '@'
+
+/* other $* and %* arguments are simply replaced without escaping */
+#define ARG_PREFIX "$%@"
+
+/*
+ characters allowed in an argument?
+ not used yet, because that would trigger some backward compatibility.
+*/
+#define ARG_CONTENT \
+ "abcdefghijklmnopqrstuvwxyz" \
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
+ "0123456789_" ARG_PREFIX
+
+/*
+ returns whether it looks like an argument, i.e. prefixed by ARG_PREFIX.
+*/
+static int looks_like_an_argument(const char *word)
+{
+ return ap_strchr(ARG_PREFIX, *word) != 0;
+}
+
+/*
+ generates an error on macro with two arguments of the same name.
+ generates an error if a macro argument name is empty.
+ generates a warning if arguments name prefixes conflict.
+ generates a warning if the first char of an argument is not in ARG_PREFIX
+*/
+static const char *check_macro_arguments(apr_pool_t * pool,
+ const ap_macro_t * macro)
+{
+ char **tab = (char **) macro->arguments->elts;
+ int nelts = macro->arguments->nelts;
+ int i;
+
+ for (i = 0; i < nelts; i++) {
+ size_t ltabi = strlen(tab[i]);
+ int j;
+
+ if (ltabi == 0) {
+ return apr_psprintf(pool,
+ "macro '%s' (%s): empty argument #%d name",
+ macro->name, macro->location, i + 1);
+ }
+ else if (!looks_like_an_argument(tab[i])) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, APLOGNO(02796)
+ "macro '%s' (%s) "
+ "argument name '%s' (#%d) without expected prefix, "
+ "better prefix argument names with one of '%s'.",
+ macro->name, macro->location,
+ tab[i], i + 1, ARG_PREFIX);
+ }
+
+ for (j = i + 1; j < nelts; j++) {
+ size_t ltabj = strlen(tab[j]);
+
+ /* must not use the same argument name twice */
+ if (!strcmp(tab[i], tab[j])) {
+ return apr_psprintf(pool,
+ "argument name conflict in macro '%s' (%s): "
+ "argument '%s': #%d and #%d, "
+ "change argument names!",
+ macro->name, macro->location,
+ tab[i], i + 1, j + 1);
+ }
+
+ /* warn about common prefix, but only if non empty names */
+ if (ltabi && ltabj &&
+ !strncmp(tab[i], tab[j], ltabi < ltabj ? ltabi : ltabj)) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING,
+ 0, NULL, APLOGNO(02797)
+ "macro '%s' (%s): "
+ "argument name prefix conflict (%s #%d and %s #%d), "
+ "be careful about your macro definition!",
+ macro->name, macro->location,
+ tab[i], i + 1, tab[j], j + 1);
+ }
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ warn about empty strings in array. could be legitimate.
+*/
+static void check_macro_use_arguments(const char *where,
+ const apr_array_header_t * array)
+{
+ char **tab = (char **) array->elts;
+ int i;
+ for (i = 0; i < array->nelts; i++) {
+ if (empty_string_p(tab[i])) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, APLOGNO(02798)
+ "%s: empty argument #%d", where, i + 1);
+ }
+ }
+}
+
+/******************************************************** SUBSTITUTION UTILS */
+
+/* could be switched to '\'' */
+#define DELIM '"'
+#define ESCAPE '\\'
+
+/*
+ returns the number of needed escapes for the string
+*/
+static int number_of_escapes(const char delim, const char *str)
+{
+ int nesc = 0;
+ const char *s = str;
+ while (*s) {
+ if (*s == ESCAPE || *s == delim)
+ nesc++;
+ s++;
+ }
+ debug(fprintf(stderr, "escapes: %d ---%s---\n", nesc, str));
+ return nesc;
+}
+
+/*
+ replace name by replacement at the beginning of buf of bufsize.
+ returns an error message or NULL.
+ C is not really a nice language for processing strings.
+*/
+static char *substitute(char *buf,
+ const int bufsize,
+ const char *name,
+ const char *replacement, const int do_esc)
+{
+ int lbuf = strlen(buf),
+ lname = strlen(name),
+ lrepl = strlen(replacement),
+ lsubs = lrepl +
+ (do_esc ? (2 + number_of_escapes(DELIM, replacement)) : 0),
+ shift = lsubs - lname, size = lbuf + shift, i, j;
+
+ /* buf must starts with name */
+ ap_assert(!strncmp(buf, name, lname));
+
+ /* hmmm??? */
+ if (!strcmp(name, replacement))
+ return NULL;
+
+ debug(fprintf(stderr,
+ "substitute(%s,%s,%s,%d,sh=%d,lbuf=%d,lrepl=%d,lsubs=%d)\n",
+ buf, name, replacement, do_esc, shift, lbuf, lrepl, lsubs));
+
+ if (size >= bufsize) {
+ /* could/should I reallocate? */
+ return "cannot substitute, buffer size too small";
+ }
+
+ /* cannot use strcpy as strings may overlap */
+ if (shift != 0) {
+ memmove(buf + lname + shift, buf + lname, lbuf - lname + 1);
+ }
+
+ /* insert the replacement with escapes */
+ j = 0;
+ if (do_esc)
+ buf[j++] = DELIM;
+ for (i = 0; i < lrepl; i++, j++) {
+ if (do_esc && (replacement[i] == DELIM || replacement[i] == ESCAPE))
+ buf[j++] = ESCAPE;
+ buf[j] = replacement[i];
+ }
+ if (do_esc)
+ buf[j++] = DELIM;
+
+ return NULL;
+}
+
+/*
+ find first occurrence of args in buf.
+ in case of conflict, the LONGEST argument is kept. (could be the FIRST?).
+ returns the pointer and the whichone found, or NULL.
+*/
+static char *next_substitution(const char *buf,
+ const apr_array_header_t * args, int *whichone)
+{
+ char *chosen = NULL, **tab = (char **) args->elts;
+ size_t lchosen = 0;
+ int i;
+
+ for (i = 0; i < args->nelts; i++) {
+ char *found = ap_strstr((char *) buf, tab[i]);
+ size_t lfound = strlen(tab[i]);
+ if (found && (!chosen || found < chosen ||
+ (found == chosen && lchosen < lfound))) {
+ chosen = found;
+ lchosen = lfound;
+ *whichone = i;
+ }
+ }
+
+ return chosen;
+}
+
+/*
+ substitute macro arguments by replacements in buf of bufsize.
+ returns an error message or NULL.
+ if used is defined, returns the used macro arguments.
+*/
+static const char *substitute_macro_args(
+ char *buf,
+ int bufsize,
+ const ap_macro_t * macro,
+ const apr_array_header_t * replacements,
+ apr_array_header_t * used)
+{
+ char *ptr = buf,
+ **atab = (char **) macro->arguments->elts,
+ **rtab = (char **) replacements->elts;
+ int whichone = -1;
+
+ if (used) {
+ ap_assert(used->nalloc >= replacements->nelts);
+ }
+ debug(fprintf(stderr, "1# %s", buf));
+
+ while ((ptr = next_substitution(ptr, macro->arguments, &whichone))) {
+ const char *errmsg = substitute(ptr, buf - ptr + bufsize,
+ atab[whichone], rtab[whichone],
+ atab[whichone][0] == ESCAPE_ARG);
+ if (errmsg) {
+ return errmsg;
+ }
+ ptr += strlen(rtab[whichone]);
+ if (used) {
+ used->elts[whichone] = 1;
+ }
+ }
+ debug(fprintf(stderr, "2# %s", buf));
+
+ return NULL;
+}
+
+/*
+ perform substitutions in a macro contents and
+ return the result as a newly allocated array, if result is defined.
+ may also return an error message.
+ passes used down to substitute_macro_args.
+*/
+static const char *process_content(apr_pool_t * pool,
+ const ap_macro_t * macro,
+ const apr_array_header_t * replacements,
+ apr_array_header_t * used,
+ apr_array_header_t ** result)
+{
+ apr_array_header_t *contents = macro->contents;
+ char line[MAX_STRING_LEN];
+ int i;
+
+ if (result) {
+ *result = apr_array_make(pool, contents->nelts, sizeof(char *));
+ }
+
+ /* for each line of the macro body */
+ for (i = 0; i < contents->nelts; i++) {
+ const char *errmsg;
+ /* copy the line and substitute macro parameters */
+ strncpy(line, ((char **) contents->elts)[i], MAX_STRING_LEN - 1);
+ errmsg = substitute_macro_args(line, MAX_STRING_LEN,
+ macro, replacements, used);
+ if (errmsg) {
+ return apr_psprintf(pool,
+ "while processing line %d of macro '%s' (%s) %s",
+ i + 1, macro->name, macro->location, errmsg);
+ }
+ /* append substituted line to result array */
+ if (result) {
+ char **new = apr_array_push(*result);
+ *new = apr_pstrdup(pool, line);
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ warn if some macro arguments are not used.
+*/
+static const char *check_macro_contents(apr_pool_t * pool,
+ const ap_macro_t * macro)
+{
+ int nelts = macro->arguments->nelts;
+ char **names = (char **) macro->arguments->elts;
+ apr_array_header_t *used;
+ int i;
+ const char *errmsg;
+
+ if (macro->contents->nelts == 0) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, APLOGNO(02799)
+ "macro '%s' (%s): empty contents!",
+ macro->name, macro->location);
+ return NULL; /* no need to further warnings... */
+ }
+
+ used = apr_array_make(pool, nelts, sizeof(char));
+
+ for (i = 0; i < nelts; i++) {
+ used->elts[i] = 0;
+ }
+
+ errmsg = process_content(pool, macro, macro->arguments, used, NULL);
+
+ if (errmsg) {
+ return errmsg;
+ }
+
+ for (i = 0; i < nelts; i++) {
+ if (!used->elts[i]) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, APLOGNO(02800)
+ "macro '%s' (%s): argument '%s' (#%d) never used",
+ macro->name, macro->location, names[i], i + 1);
+ }
+ }
+
+ return NULL;
+}
+
+
+/************************************************** MACRO PSEUDO CONFIG FILE */
+
+/*
+ The expanded content of the macro is to be parsed as a ap_configfile_t.
+ This is used to have some kind of old fashionned C object oriented inherited
+ data structure for configs.
+
+ The following struct stores the contents.
+
+ This structure holds pointers (next, upper) to the current "file" which was
+ being processed and is interrupted by the macro expansion. At the end
+ of processing the macro, the initial data structure will be put back
+ in place (see function next_one) and the reading will go on from there.
+
+ If macros are used within macros, there may be a cascade of such temporary
+ arrays used to insert the expanded macro contents before resuming the real
+ file processing.
+
+ There is some hopus-pocus to deal with line_number when transiting from
+ one config to the other.
+*/
+typedef struct
+{
+ int index; /* current element */
+ int char_index; /* current char in element */
+ int length; /* cached length of the current line */
+ apr_array_header_t *contents; /* array of char * */
+ ap_configfile_t *next; /* next config once this one is processed */
+ ap_configfile_t **upper; /* hack: where to update it if needed */
+} array_contents_t;
+
+/*
+ Get next config if any.
+ this may be called several times if there are continuations.
+*/
+static int next_one(array_contents_t * ml)
+{
+ if (ml->next) {
+ ap_assert(ml->upper);
+ *(ml->upper) = ml->next;
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ returns next char if possible
+ this may involve switching to enclosing config.
+*/
+static apr_status_t array_getch(char *ch, void *param)
+{
+ array_contents_t *ml = (array_contents_t *) param;
+ char **tab = (char **) ml->contents->elts;
+
+ while (ml->char_index >= ml->length) {
+ if (ml->index >= ml->contents->nelts) {
+ /* maybe update */
+ if (ml->next && ml->next->getch && next_one(ml)) {
+ apr_status_t rc = ml->next->getch(ch, ml->next->param);
+ if (*ch==LF)
+ ml->next->line_number++;
+ return rc;
+ }
+ return APR_EOF;
+ }
+ ml->index++;
+ ml->char_index = 0;
+ ml->length = ml->index >= ml->contents->nelts ?
+ 0 : strlen(tab[ml->index]);
+ }
+
+ *ch = tab[ml->index][ml->char_index++];
+ return APR_SUCCESS;
+}
+
+/*
+ returns a buf a la fgets.
+ no more than a line at a time, otherwise the parsing is too much ahead...
+ NULL at EOF.
+*/
+static apr_status_t array_getstr(void *buf, size_t bufsize, void *param)
+{
+ array_contents_t *ml = (array_contents_t *) param;
+ char *buffer = (char *) buf;
+ char next = '\0';
+ size_t i = 0;
+ apr_status_t rc = APR_SUCCESS;
+
+ /* read chars from stream, stop on newline */
+ while (i < bufsize - 1 && next != LF &&
+ ((rc = array_getch(&next, param)) == APR_SUCCESS)) {
+ buffer[i++] = next;
+ }
+
+ if (rc == APR_EOF) {
+ /* maybe update to next, possibly a recursion */
+ if (next_one(ml)) {
+ ap_assert(ml->next->getstr);
+ /* keep next line count in sync! the caller will update
+ the current line_number, we need to forward to the next */
+ ml->next->line_number++;
+ return ml->next->getstr(buf, bufsize, ml->next->param);
+ }
+ /* else that is really all we can do */
+ return APR_EOF;
+ }
+
+ buffer[i] = '\0';
+
+ return APR_SUCCESS;
+}
+
+/*
+ close the array stream?
+*/
+static apr_status_t array_close(void *param)
+{
+ array_contents_t *ml = (array_contents_t *) param;
+ /* move index at end of stream... */
+ ml->index = ml->contents->nelts;
+ ml->char_index = ml->length;
+ return APR_SUCCESS;
+}
+
+/*
+ create an array config stream insertion "object".
+ could be exported.
+*/
+static ap_configfile_t *make_array_config(apr_pool_t * pool,
+ apr_array_header_t * contents,
+ const char *where,
+ ap_configfile_t * cfg,
+ ap_configfile_t ** upper)
+{
+ array_contents_t *ls =
+ (array_contents_t *) apr_palloc(pool, sizeof(array_contents_t));
+ ap_assert(ls!=NULL);
+
+ ls->index = 0;
+ ls->char_index = 0;
+ ls->contents = contents;
+ ls->length = ls->contents->nelts < 1 ?
+ 0 : strlen(((char **) ls->contents->elts)[0]);
+ ls->next = cfg;
+ ls->upper = upper;
+
+ return ap_pcfg_open_custom(pool, where, (void *) ls,
+ array_getch, array_getstr, array_close);
+}
+
+
+/********************************************************** KEYWORD HANDLING */
+
+/*
+ handles: <Macro macroname arg1 arg2 ...> any trash there is ignored...
+*/
+static const char *macro_section(cmd_parms * cmd,
+ void *dummy, const char *arg)
+{
+ apr_pool_t *pool;
+ char *endp, *name, *where;
+ const char *errmsg;
+ ap_macro_t *macro;
+
+ debug(fprintf(stderr, "macro_section: arg='%s'\n", arg));
+
+ /* lazy initialization */
+ if (ap_macros == NULL) {
+ pool = cmd->pool;
+ ap_macros = apr_hash_make(pool);
+ ap_assert(ap_macros != NULL);
+ apr_pool_cleanup_register(pool, &ap_macros,
+ ap_pool_cleanup_set_null,
+ apr_pool_cleanup_null);
+ }
+ else {
+ pool = apr_hash_pool_get(ap_macros);
+ }
+
+ endp = (char *) ap_strrchr_c(arg, '>');
+
+ if (endp == NULL) {
+ return BEGIN_MACRO "> directive missing closing '>'";
+ }
+
+ if (endp == arg) {
+ return BEGIN_MACRO " macro definition: empty name";
+ }
+
+ warn_if_non_blank(APLOGNO(02801) "non blank chars found after "
+ BEGIN_MACRO " closing '>'",
+ endp+1, cmd->config_file);
+
+ /* coldly drop '>[^>]*$' out */
+ *endp = '\0';
+
+ /* get lowercase macro name */
+ name = ap_getword_conf(pool, &arg);
+ if (empty_string_p(name)) {
+ return BEGIN_MACRO " macro definition: name not found";
+ }
+
+ ap_str_tolower(name);
+ macro = apr_hash_get(ap_macros, name, APR_HASH_KEY_STRING);
+
+ if (macro != NULL) {
+ /* already defined: warn about the redefinition */
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, APLOGNO(02802)
+ "macro '%s' multiply defined: "
+ "%s, redefined on line %d of \"%s\"",
+ macro->name, macro->location,
+ cmd->config_file->line_number, cmd->config_file->name);
+ }
+ else {
+ /* allocate a new macro */
+ macro = (ap_macro_t *) apr_palloc(pool, sizeof(ap_macro_t));
+ macro->name = name;
+ }
+
+ debug(fprintf(stderr, "macro_section: name=%s\n", name));
+
+ /* get macro arguments */
+ macro->location = apr_psprintf(pool,
+ "defined on line %d of \"%s\"",
+ cmd->config_file->line_number,
+ cmd->config_file->name);
+ debug(fprintf(stderr, "macro_section: location=%s\n", macro->location));
+
+ where =
+ apr_psprintf(pool, "macro '%s' (%s)", macro->name, macro->location);
+
+ if (looks_like_an_argument(name)) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, APLOGNO(02803)
+ "%s better prefix a macro name with any of '%s'",
+ where, ARG_PREFIX);
+ }
+
+ /* get macro parameters */
+ macro->arguments = get_arguments(pool, arg);
+
+ errmsg = check_macro_arguments(cmd->temp_pool, macro);
+
+ if (errmsg) {
+ return errmsg;
+ }
+
+ errmsg = get_lines_till_end_token(pool, cmd->config_file,
+ END_MACRO, BEGIN_MACRO,
+ where, &macro->contents);
+
+ if (errmsg) {
+ return apr_psprintf(cmd->temp_pool,
+ "%s" APR_EOL_STR "\tcontents error: %s",
+ where, errmsg);
+ }
+
+ errmsg = check_macro_contents(cmd->temp_pool, macro);
+
+ if (errmsg) {
+ return apr_psprintf(cmd->temp_pool,
+ "%s" APR_EOL_STR "\tcontents checking error: %s",
+ where, errmsg);
+ }
+
+ /* store the new macro */
+ apr_hash_set(ap_macros, name, APR_HASH_KEY_STRING, macro);
+
+ return NULL;
+}
+
+/*
+ handles: Use name value1 value2 ...
+*/
+static const char *use_macro(cmd_parms * cmd, void *dummy, const char *arg)
+{
+ char *name, *recursion, *where;
+ const char *errmsg;
+ ap_macro_t *macro;
+ apr_array_header_t *replacements;
+ apr_array_header_t *contents;
+
+ debug(fprintf(stderr, "use_macro -%s-\n", arg));
+
+ /* must be initialized, or no macros has been defined */
+ if (ap_macros == NULL) {
+ return "no macro defined before " USE_MACRO;
+ }
+
+ /* get lowercase macro name */
+ name = ap_getword_conf(cmd->temp_pool, &arg);
+ ap_str_tolower(name);
+
+ if (empty_string_p(name)) {
+ return "no macro name specified with " USE_MACRO;
+ }
+
+ /* get macro definition */
+ macro = apr_hash_get(ap_macros, name, APR_HASH_KEY_STRING);
+
+ if (!macro) {
+ return apr_psprintf(cmd->temp_pool, "macro '%s' undefined", name);
+ }
+
+ /* recursion is detected here by looking at the config file name,
+ * which may already contains "macro 'foo'". Ok, it looks like a hack,
+ * but otherwise it is uneasy to keep this data available somewhere...
+ * the name has just the needed visibility and liveness.
+ */
+ recursion =
+ apr_pstrcat(cmd->temp_pool, "macro '", macro->name, "'", NULL);
+
+ if (ap_strstr((char *) cmd->config_file->name, recursion)) {
+ return apr_psprintf(cmd->temp_pool,
+ "recursive use of macro '%s' is invalid",
+ macro->name);
+ }
+
+ /* get macro arguments */
+ replacements = get_arguments(cmd->temp_pool, arg);
+
+ if (macro->arguments->nelts != replacements->nelts) {
+ return apr_psprintf(cmd->temp_pool,
+ "macro '%s' (%s) used "
+ "with %d arguments instead of %d",
+ macro->name, macro->location,
+ replacements->nelts, macro->arguments->nelts);
+ }
+
+ where = apr_psprintf(cmd->temp_pool,
+ "macro '%s' (%s) used on line %d of \"%s\"",
+ macro->name, macro->location,
+ cmd->config_file->line_number,
+ cmd->config_file->name);
+
+ check_macro_use_arguments(where, replacements);
+
+ errmsg = process_content(cmd->temp_pool, macro, replacements,
+ NULL, &contents);
+
+ if (errmsg) {
+ return apr_psprintf(cmd->temp_pool,
+ "%s error while substituting: %s",
+ where, errmsg);
+ }
+
+ /* the current "config file" is replaced by a string array...
+ at the end of processing the array, the initial config file
+ will be returned there (see next_one) so as to go on. */
+ cmd->config_file = make_array_config(cmd->temp_pool, contents, where,
+ cmd->config_file, &cmd->config_file);
+
+ return NULL;
+}
+
+static const char *undef_macro(cmd_parms * cmd, void *dummy, const char *arg)
+{
+ char *name;
+ ap_macro_t *macro;
+
+ /* must be initialized, or no macros has been defined */
+ if (ap_macros == NULL) {
+ return "no macro defined before " UNDEF_MACRO;
+ }
+
+ if (empty_string_p(arg)) {
+ return "no macro name specified with " UNDEF_MACRO;
+ }
+
+ /* check that the macro is defined */
+ name = apr_pstrdup(cmd->temp_pool, arg);
+ ap_str_tolower(name);
+ macro = apr_hash_get(ap_macros, name, APR_HASH_KEY_STRING);
+ if (macro == NULL) {
+ /* could be a warning? */
+ return apr_psprintf(cmd->temp_pool,
+ "cannot remove undefined macro '%s'", name);
+ }
+
+ /* free macro: cannot do that */
+ /* remove macro from hash table */
+ apr_hash_set(ap_macros, name, APR_HASH_KEY_STRING, NULL);
+
+ return NULL;
+}
+
+/************************************************************* EXPORT MODULE */
+
+/*
+ macro module commands.
+ configuration file macro stuff
+ they are processed immediately when found, hence the EXEC_ON_READ.
+*/
+static const command_rec macro_cmds[] = {
+ AP_INIT_RAW_ARGS(BEGIN_MACRO, macro_section, NULL, EXEC_ON_READ | OR_ALL,
+ "Beginning of a macro definition section."),
+ AP_INIT_RAW_ARGS(USE_MACRO, use_macro, NULL, EXEC_ON_READ | OR_ALL,
+ "Use of a macro."),
+ AP_INIT_TAKE1(UNDEF_MACRO, undef_macro, NULL, EXEC_ON_READ | OR_ALL,
+ "Remove a macro definition."),
+
+ {NULL}
+};
+
+/*
+ Module hooks are request-oriented thus it does not suit configuration
+ file utils a lot. I haven't found any clean hook to apply something
+ before then after configuration file processing. Also what about
+ .htaccess files?
+
+ Thus I think that server/util.c or server/config.c
+ would be a better place for this stuff.
+*/
+
+AP_DECLARE_MODULE(macro) = {
+ STANDARD20_MODULE_STUFF, /* common stuff */
+ NULL, /* create per-directory config */
+ NULL, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ macro_cmds, /* configuration commands */
+ NULL /* register hooks */
+};
diff --git a/modules/core/mod_macro.dep b/modules/core/mod_macro.dep
new file mode 100644
index 0000000..74ea1a5
--- /dev/null
+++ b/modules/core/mod_macro.dep
@@ -0,0 +1,45 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_macro.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_macro.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/core/mod_macro.dsp b/modules/core/mod_macro.dsp
new file mode 100644
index 0000000..61914cc
--- /dev/null
+++ b/modules/core/mod_macro.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_macro" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_macro - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_macro.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_macro.mak" CFG="mod_macro - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_macro - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_macro - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_macro - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_macro_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG" /i "../../include" /i "../../srclib/apr/include" /d BIN_NAME="mod_macro.so" /d LONG_NAME="macro_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_macro.so" /base:@..\..\os\win32\BaseAddr.ref,mod_macro.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_macro.so" /base:@..\..\os\win32\BaseAddr.ref,mod_macro.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_macro.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_macro - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_macro_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG" /i "../../include" /i "../../srclib/apr/include" /d BIN_NAME="mod_macro.so" /d LONG_NAME="macro_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_macro.so" /base:@..\..\os\win32\BaseAddr.ref,mod_macro.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_macro.so" /base:@..\..\os\win32\BaseAddr.ref,mod_macro.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_macro.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_macro - Win32 Release"
+# Name "mod_macro - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_macro.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/core/mod_macro.mak b/modules/core/mod_macro.mak
new file mode 100644
index 0000000..656d96a
--- /dev/null
+++ b/modules/core/mod_macro.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_macro.dsp
+!IF "$(CFG)" == ""
+CFG=mod_macro - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_macro - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_macro - Win32 Release" && "$(CFG)" != "mod_macro - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_macro.mak" CFG="mod_macro - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_macro - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_macro - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_macro - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_macro.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_macro.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\httpd.res"
+ -@erase "$(INTDIR)\mod_macro.obj"
+ -@erase "$(INTDIR)\mod_macro_src.idb"
+ -@erase "$(INTDIR)\mod_macro_src.pdb"
+ -@erase "$(OUTDIR)\mod_macro.exp"
+ -@erase "$(OUTDIR)\mod_macro.lib"
+ -@erase "$(OUTDIR)\mod_macro.pdb"
+ -@erase "$(OUTDIR)\mod_macro.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_macro_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\httpd.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_macro.so" /d LONG_NAME="macro_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_macro.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_macro.pdb" /debug /out:"$(OUTDIR)\mod_macro.so" /implib:"$(OUTDIR)\mod_macro.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_macro.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_macro.obj" \
+ "$(INTDIR)\httpd.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_macro.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_macro.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_macro.so"
+ if exist .\Release\mod_macro.so.manifest mt.exe -manifest .\Release\mod_macro.so.manifest -outputresource:.\Release\mod_macro.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_macro - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_macro.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_macro.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\httpd.res"
+ -@erase "$(INTDIR)\mod_macro.obj"
+ -@erase "$(INTDIR)\mod_macro_src.idb"
+ -@erase "$(INTDIR)\mod_macro_src.pdb"
+ -@erase "$(OUTDIR)\mod_macro.exp"
+ -@erase "$(OUTDIR)\mod_macro.lib"
+ -@erase "$(OUTDIR)\mod_macro.pdb"
+ -@erase "$(OUTDIR)\mod_macro.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_macro_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\httpd.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_macro.so" /d LONG_NAME="macro_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_macro.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_macro.pdb" /debug /out:"$(OUTDIR)\mod_macro.so" /implib:"$(OUTDIR)\mod_macro.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_macro.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_macro.obj" \
+ "$(INTDIR)\httpd.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_macro.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_macro.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_macro.so"
+ if exist .\Debug\mod_macro.so.manifest mt.exe -manifest .\Debug\mod_macro.so.manifest -outputresource:.\Debug\mod_macro.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_macro.dep")
+!INCLUDE "mod_macro.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_macro.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_macro - Win32 Release" || "$(CFG)" == "mod_macro - Win32 Debug"
+
+!IF "$(CFG)" == "mod_macro - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\core"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\core"
+
+!ELSEIF "$(CFG)" == "mod_macro - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\core"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\core"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_macro - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\core"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\core"
+
+!ELSEIF "$(CFG)" == "mod_macro - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\core"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\core"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_macro - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\core"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\core"
+
+!ELSEIF "$(CFG)" == "mod_macro - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\core"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\core"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_macro - Win32 Release"
+
+
+"$(INTDIR)\httpd.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\httpd.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_macro.so" /d LONG_NAME="macro_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_macro - Win32 Debug"
+
+
+"$(INTDIR)\httpd.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\httpd.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_macro.so" /d LONG_NAME="macro_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_macro.c
+
+"$(INTDIR)\mod_macro.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/core/mod_so.c b/modules/core/mod_so.c
new file mode 100644
index 0000000..f5d18c1
--- /dev/null
+++ b/modules/core/mod_so.c
@@ -0,0 +1,442 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * This module is used to load Apache modules at runtime. This means that the
+ * server functionality can be extended without recompiling and even without
+ * taking the server down at all. Only a HUP or AP_SIG_GRACEFUL signal
+ * needs to be sent to the server to reload the dynamically loaded modules.
+ *
+ * To use, you'll first need to build your module as a shared library, then
+ * update your configuration (httpd.conf) to get the Apache core to load the
+ * module at start-up.
+ *
+ * The easiest way to build a module as a shared library is to use the
+ * `SharedModule' command in the Configuration file, instead of `AddModule'.
+ * You should also change the file extension from `.o' to `.so'. So, for
+ * example, to build the status module as a shared library edit Configuration
+ * and change
+ * AddModule modules/standard/mod_status.o
+ * to
+ * SharedModule modules/standard/mod_status.so
+ *
+ * Run Configure and make. Now Apache's httpd binary will _not_ include
+ * mod_status. Instead a shared object called mod_status.so will be build, in
+ * the modules/standard directory. You can build most of the modules as shared
+ * libraries like this.
+ *
+ * To use the shared module, move the .so file(s) into an appropriate
+ * directory. You might like to create a directory called "modules" under you
+ * server root for this (e.g. /usr/local/httpd/modules).
+ *
+ * Then edit your conf/httpd.conf file, and add LoadModule lines. For
+ * example
+ * LoadModule status_module modules/mod_status.so
+ *
+ * The first argument is the module's structure name (look at the end of the
+ * module source to find this). The second option is the path to the module
+ * file, relative to the server root. Put these directives right at the top
+ * of your httpd.conf file.
+ *
+ * Now you can start Apache. A message will be logged at "debug" level to your
+ * error_log to confirm that the module(s) are loaded (use "LogLevel debug"
+ * directive to get these log messages).
+ *
+ * If you edit the LoadModule directives while the server is live you can get
+ * Apache to re-load the modules by sending it a HUP or AP_SIG_GRACEFUL
+ * signal as normal. You can use this to dynamically change the capability
+ * of your server without bringing it down.
+ *
+ * Because currently there is only limited builtin support in the Configure
+ * script for creating the shared library files (`.so'), please consult your
+ * vendors cc(1), ld(1) and dlopen(3) manpages to find out the appropriate
+ * compiler and linker flags and insert them manually into the Configuration
+ * file under CFLAGS_SHLIB, LDFLAGS_SHLIB and LDFLAGS_SHLIB_EXPORT.
+ *
+ * If you still have problems figuring out the flags both try the paper
+ * http://developer.netscape.com/library/documentation/enterprise
+ * /unix/svrplug.htm#1013807
+ * or install a Perl 5 interpreter on your platform and then run the command
+ *
+ * $ perl -V:usedl -V:ccdlflags -V:cccdlflags -V:lddlflags
+ *
+ * This gives you what type of dynamic loading Perl 5 uses on your platform
+ * and which compiler and linker flags Perl 5 uses to create the shared object
+ * files.
+ *
+ * Another location where you can find useful hints is the `ltconfig' script
+ * of the GNU libtool 1.2 package. Search for your platform name inside the
+ * various "case" constructs.
+ *
+ */
+
+#include "apr.h"
+#include "apr_dso.h"
+#include "apr_strings.h"
+#include "apr_errno.h"
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "http_core.h"
+
+#include "mod_so.h"
+
+module AP_MODULE_DECLARE_DATA so_module;
+
+
+/*
+ * Server configuration to keep track of actually
+ * loaded modules and the corresponding module name.
+ */
+
+typedef struct so_server_conf {
+ apr_array_header_t *loaded_modules;
+} so_server_conf;
+
+static void *so_sconf_create(apr_pool_t *p, server_rec *s)
+{
+ so_server_conf *soc;
+
+ soc = (so_server_conf *)apr_pcalloc(p, sizeof(so_server_conf));
+ soc->loaded_modules = apr_array_make(p, DYNAMIC_MODULE_LIMIT,
+ sizeof(ap_module_symbol_t));
+
+ return (void *)soc;
+}
+
+#ifndef NO_DLOPEN
+
+/*
+ * This is the cleanup for a loaded shared object. It unloads the module.
+ * This is called as a cleanup function from the core.
+ */
+
+static apr_status_t unload_module(void *data)
+{
+ ap_module_symbol_t *modi = (ap_module_symbol_t*)data;
+
+ /* only unload if module information is still existing */
+ if (modi->modp == NULL)
+ return APR_SUCCESS;
+
+ /* remove the module pointer from the core structure */
+ ap_remove_loaded_module(modi->modp);
+
+ /* destroy the module information */
+ modi->modp = NULL;
+ modi->name = NULL;
+ return APR_SUCCESS;
+}
+
+static const char *dso_load(cmd_parms *cmd, apr_dso_handle_t **modhandlep,
+ const char *filename, const char **used_filename)
+{
+ int retry = 0;
+ const char *fullname = ap_server_root_relative(cmd->temp_pool, filename);
+ char my_error[256];
+ if (filename != NULL && ap_strchr_c(filename, '/') == NULL) {
+ /* retry on error without path to use dlopen()'s search path */
+ retry = 1;
+ }
+
+ if (fullname == NULL && !retry) {
+ return apr_psprintf(cmd->temp_pool, "Invalid %s path %s",
+ cmd->cmd->name, filename);
+ }
+ *used_filename = fullname;
+ if (fullname && apr_dso_load(modhandlep, fullname, cmd->pool) == APR_SUCCESS) {
+ return NULL;
+ }
+ if (retry) {
+ *used_filename = filename;
+ if (apr_dso_load(modhandlep, filename, cmd->pool) == APR_SUCCESS)
+ return NULL;
+ }
+
+ return apr_pstrcat(cmd->temp_pool, "Cannot load ", filename,
+ " into server: ",
+ apr_dso_error(*modhandlep, my_error, sizeof(my_error)),
+ NULL);
+}
+
+/*
+ * This is called for the directive LoadModule and actually loads
+ * a shared object file into the address space of the server process.
+ */
+
+static const char *load_module(cmd_parms *cmd, void *dummy,
+ const char *modname, const char *filename)
+{
+ apr_dso_handle_t *modhandle;
+ apr_dso_handle_sym_t modsym;
+ module *modp;
+ const char *module_file;
+ so_server_conf *sconf;
+ ap_module_symbol_t *modi;
+ ap_module_symbol_t *modie;
+ int i;
+ const char *error;
+
+ /* we need to setup this value for dummy to make sure that we don't try
+ * to add a non-existent tree into the build when we return to
+ * execute_now.
+ */
+ *(ap_directive_t **)dummy = NULL;
+
+ /*
+ * check for already existing module
+ * If it already exists, we have nothing to do
+ * Check both dynamically-loaded modules and statically-linked modules.
+ */
+ sconf = (so_server_conf *)ap_get_module_config(cmd->server->module_config,
+ &so_module);
+ modie = (ap_module_symbol_t *)sconf->loaded_modules->elts;
+ for (i = 0; i < sconf->loaded_modules->nelts; i++) {
+ modi = &modie[i];
+ if (modi->name != NULL && strcmp(modi->name, modname) == 0) {
+ ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, cmd->pool, APLOGNO(01574)
+ "module %s is already loaded, skipping",
+ modname);
+ return NULL;
+ }
+ }
+
+ for (i = 0; ap_preloaded_modules[i]; i++) {
+ const char *preload_name;
+ apr_size_t preload_len;
+ apr_size_t thismod_len;
+
+ modp = ap_preloaded_modules[i];
+
+ /* make sure we're comparing apples with apples
+ * make sure name of preloaded module is mod_FOO.c
+ * make sure name of structure being loaded is FOO_module
+ */
+
+ if (memcmp(modp->name, "mod_", 4)) {
+ continue;
+ }
+
+ preload_name = modp->name + strlen("mod_");
+ preload_len = strlen(preload_name) - 2;
+
+ if (strlen(modname) <= strlen("_module")) {
+ continue;
+ }
+ thismod_len = strlen(modname) - strlen("_module");
+ if (strcmp(modname + thismod_len, "_module")) {
+ continue;
+ }
+
+ if (thismod_len != preload_len) {
+ continue;
+ }
+
+ if (!memcmp(modname, preload_name, preload_len)) {
+ return apr_pstrcat(cmd->pool, "module ", modname,
+ " is built-in and can't be loaded",
+ NULL);
+ }
+ }
+
+ modi = apr_array_push(sconf->loaded_modules);
+ modi->name = modname;
+
+ /*
+ * Load the file into the Apache address space
+ */
+ error = dso_load(cmd, &modhandle, filename, &module_file);
+ if (error)
+ return error;
+ ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, cmd->pool, APLOGNO(01575)
+ "loaded module %s from %s", modname, module_file);
+
+ /*
+ * Retrieve the pointer to the module structure through the module name:
+ * First with the hidden variant (prefix `AP_') and then with the plain
+ * symbol name.
+ */
+ if (apr_dso_sym(&modsym, modhandle, modname) != APR_SUCCESS) {
+ char my_error[256];
+
+ return apr_pstrcat(cmd->pool, "Can't locate API module structure `",
+ modname, "' in file ", module_file, ": ",
+ apr_dso_error(modhandle, my_error, sizeof(my_error)),
+ NULL);
+ }
+ modp = (module*) modsym;
+ modp->dynamic_load_handle = (apr_dso_handle_t *)modhandle;
+ modi->modp = modp;
+
+ /*
+ * Make sure the found module structure is really a module structure
+ *
+ */
+ if (modp->magic != MODULE_MAGIC_COOKIE) {
+ return apr_psprintf(cmd->pool, "API module structure '%s' in file %s "
+ "is garbled - expected signature %08lx but saw "
+ "%08lx - perhaps this is not an Apache module DSO, "
+ "or was compiled for a different Apache version?",
+ modname, module_file,
+ MODULE_MAGIC_COOKIE, modp->magic);
+ }
+
+ /*
+ * Add this module to the Apache core structures
+ */
+ error = ap_add_loaded_module(modp, cmd->pool, modname);
+ if (error) {
+ return error;
+ }
+
+ /*
+ * Register a cleanup in the config apr_pool_t (normally pconf). When
+ * we do a restart (or shutdown) this cleanup will cause the
+ * shared object to be unloaded.
+ */
+ apr_pool_cleanup_register(cmd->pool, modi, unload_module, apr_pool_cleanup_null);
+
+ /*
+ * Finally we need to run the configuration process for the module
+ */
+ ap_single_module_configure(cmd->pool, cmd->server, modp);
+
+ return NULL;
+}
+
+/*
+ * This implements the LoadFile directive and loads an arbitrary
+ * shared object file into the address space of the server process.
+ */
+
+static const char *load_file(cmd_parms *cmd, void *dummy, const char *filename)
+{
+ apr_dso_handle_t *handle;
+ const char *used_file, *error;
+
+ error = dso_load(cmd, &handle, filename, &used_file);
+ if (error)
+ return error;
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, APLOGNO(01576)
+ "loaded file %s", used_file);
+
+ return NULL;
+}
+
+static module *ap_find_loaded_module_symbol(server_rec *s, const char *modname)
+{
+ so_server_conf *sconf;
+ ap_module_symbol_t *modi;
+ ap_module_symbol_t *modie;
+ int i;
+
+ sconf = (so_server_conf *)ap_get_module_config(s->module_config,
+ &so_module);
+ modie = (ap_module_symbol_t *)sconf->loaded_modules->elts;
+
+ for (i = 0; i < sconf->loaded_modules->nelts; i++) {
+ modi = &modie[i];
+ if (modi->name != NULL && strcmp(modi->name, modname) == 0) {
+ return modi->modp;
+ }
+ }
+ return NULL;
+}
+
+static void dump_loaded_modules(apr_pool_t *p, server_rec *s)
+{
+ ap_module_symbol_t *modie;
+ ap_module_symbol_t *modi;
+ so_server_conf *sconf;
+ int i;
+ apr_file_t *out = NULL;
+
+ if (!ap_exists_config_define("DUMP_MODULES")) {
+ return;
+ }
+
+ apr_file_open_stdout(&out, p);
+
+ apr_file_printf(out, "Loaded Modules:\n");
+
+ sconf = (so_server_conf *)ap_get_module_config(s->module_config,
+ &so_module);
+ for (i = 0; ; i++) {
+ modi = &ap_prelinked_module_symbols[i];
+ if (modi->name != NULL) {
+ apr_file_printf(out, " %s (static)\n", modi->name);
+ }
+ else {
+ break;
+ }
+ }
+
+ modie = (ap_module_symbol_t *)sconf->loaded_modules->elts;
+ for (i = 0; i < sconf->loaded_modules->nelts; i++) {
+ modi = &modie[i];
+ if (modi->name != NULL) {
+ apr_file_printf(out, " %s (shared)\n", modi->name);
+ }
+ }
+}
+
+#else /* not NO_DLOPEN */
+
+static const char *load_file(cmd_parms *cmd, void *dummy, const char *filename)
+{
+ ap_log_perror(APLOG_MARK, APLOG_STARTUP, 0, cmd->pool, APLOGNO(01577)
+ "WARNING: LoadFile not supported on this platform");
+ return NULL;
+}
+
+static const char *load_module(cmd_parms *cmd, void *dummy,
+ const char *modname, const char *filename)
+{
+ ap_log_perror(APLOG_MARK, APLOG_STARTUP, 0, cmd->pool, APLOGNO(01578)
+ "WARNING: LoadModule not supported on this platform");
+ return NULL;
+}
+
+#endif /* NO_DLOPEN */
+
+static void register_hooks(apr_pool_t *p)
+{
+#ifndef NO_DLOPEN
+ APR_REGISTER_OPTIONAL_FN(ap_find_loaded_module_symbol);
+ ap_hook_test_config(dump_loaded_modules, NULL, NULL, APR_HOOK_MIDDLE);
+#endif
+}
+
+static const command_rec so_cmds[] = {
+ AP_INIT_TAKE2("LoadModule", load_module, NULL, RSRC_CONF | EXEC_ON_READ,
+ "a module name and the name of a shared object file to load it from"),
+ AP_INIT_ITERATE("LoadFile", load_file, NULL, RSRC_CONF | EXEC_ON_READ,
+ "shared object file or library to load into the server at runtime"),
+ { NULL }
+};
+
+AP_DECLARE_MODULE(so) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-dir config */
+ NULL, /* merge per-dir config */
+ so_sconf_create, /* server config */
+ NULL, /* merge server config */
+ so_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/core/mod_so.h b/modules/core/mod_so.h
new file mode 100644
index 0000000..2b26999
--- /dev/null
+++ b/modules/core/mod_so.h
@@ -0,0 +1,38 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file mod_so.h
+ * @brief Shared Object Loader Extension Module for Apache
+ *
+ * @defgroup MOD_SO mod_so
+ * @ingroup APACHE_MODS
+ * @{
+ */
+
+#ifndef MOD_SO_H
+#define MOD_SO_H 1
+
+#include "apr_optional.h"
+#include "httpd.h"
+
+/* optional function declaration */
+APR_DECLARE_OPTIONAL_FN(module *, ap_find_loaded_module_symbol,
+ (server_rec *s, const char *modname));
+
+#endif /* MOD_SO_H */
+/** @} */
+
diff --git a/modules/core/mod_watchdog.c b/modules/core/mod_watchdog.c
new file mode 100644
index 0000000..99ec7cf
--- /dev/null
+++ b/modules/core/mod_watchdog.c
@@ -0,0 +1,718 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Watchdog module.
+ */
+
+#include "apr.h"
+#include "mod_watchdog.h"
+#include "ap_provider.h"
+#include "ap_mpm.h"
+#include "http_core.h"
+#include "util_mutex.h"
+
+#include "apr_atomic.h"
+
+#define AP_WATCHDOG_PGROUP "watchdog"
+#define AP_WATCHDOG_PVERSION "parent"
+#define AP_WATCHDOG_CVERSION "child"
+
+typedef struct watchdog_list_t watchdog_list_t;
+
+struct watchdog_list_t
+{
+ struct watchdog_list_t *next;
+ ap_watchdog_t *wd;
+ apr_status_t status;
+ apr_interval_time_t interval;
+ apr_interval_time_t step;
+ const void *data;
+ ap_watchdog_callback_fn_t *callback_fn;
+};
+
+struct ap_watchdog_t
+{
+ apr_uint32_t thread_started; /* set to 1 once thread running */
+ apr_proc_mutex_t *mutex;
+ const char *name;
+ watchdog_list_t *callbacks;
+ int is_running;
+ int singleton;
+ int active;
+ apr_interval_time_t step;
+ apr_thread_t *thread;
+ apr_pool_t *pool;
+};
+
+typedef struct wd_server_conf_t wd_server_conf_t;
+struct wd_server_conf_t
+{
+ int child_workers;
+ int parent_workers;
+ apr_pool_t *pool;
+ server_rec *s;
+};
+
+static wd_server_conf_t *wd_server_conf = NULL;
+static apr_interval_time_t wd_interval = AP_WD_TM_INTERVAL;
+static int mpm_is_forked = AP_MPMQ_NOT_SUPPORTED;
+static const char *wd_proc_mutex_type = "watchdog-callback";
+
+static apr_status_t wd_worker_cleanup(void *data)
+{
+ apr_status_t rv;
+ ap_watchdog_t *w = (ap_watchdog_t *)data;
+
+ /* Do nothing if the thread wasn't started. */
+ if (apr_atomic_read32(&w->thread_started) != 1)
+ return APR_SUCCESS;
+
+ if (w->is_running) {
+ watchdog_list_t *wl = w->callbacks;
+ while (wl) {
+ if (wl->status == APR_SUCCESS) {
+ /* Execute watchdog callback with STOPPING state */
+ (*wl->callback_fn)(AP_WATCHDOG_STATE_STOPPING,
+ (void *)wl->data, w->pool);
+ wl->status = APR_EOF;
+ }
+ wl = wl->next;
+ }
+ }
+ w->is_running = 0;
+ apr_thread_join(&rv, w->thread);
+ return rv;
+}
+
+/*--------------------------------------------------------------------------*/
+/* */
+/* Main watchdog worker thread. */
+/* For singleton workers child thread that first obtains the process */
+/* mutex is running. Threads in other child's are locked on mutex. */
+/* */
+/*--------------------------------------------------------------------------*/
+static void* APR_THREAD_FUNC wd_worker(apr_thread_t *thread, void *data)
+{
+ ap_watchdog_t *w = (ap_watchdog_t *)data;
+ apr_status_t rv;
+ int locked = 0;
+ int probed = 0;
+ int inited = 0;
+ int mpmq_s = 0;
+ apr_pool_t *temp_pool = NULL;
+
+ w->pool = apr_thread_pool_get(thread);
+ w->is_running = 1;
+
+ apr_atomic_set32(&w->thread_started, 1); /* thread started */
+
+ if (w->mutex) {
+ while (w->is_running) {
+ if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpmq_s) != APR_SUCCESS) {
+ w->is_running = 0;
+ break;
+ }
+ if (mpmq_s == AP_MPMQ_STOPPING) {
+ w->is_running = 0;
+ break;
+ }
+ rv = apr_proc_mutex_trylock(w->mutex);
+ if (rv == APR_SUCCESS) {
+ if (probed) {
+ /* Sleep after we were locked
+ * up to 1 second. Httpd can be
+ * in the middle of shutdown, and
+ * our child didn't yet received
+ * the shutdown signal.
+ */
+ probed = 10;
+ while (w->is_running && probed > 0) {
+ apr_sleep(AP_WD_TM_INTERVAL);
+ probed--;
+ if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpmq_s) != APR_SUCCESS) {
+ w->is_running = 0;
+ break;
+ }
+ if (mpmq_s == AP_MPMQ_STOPPING) {
+ w->is_running = 0;
+ break;
+ }
+ }
+ }
+ locked = 1;
+ break;
+ }
+ probed = 1;
+ apr_sleep(AP_WD_TM_SLICE);
+ }
+ }
+
+ apr_pool_create(&temp_pool, w->pool);
+ apr_pool_tag(temp_pool, "wd_running");
+
+ if (w->is_running) {
+ watchdog_list_t *wl = w->callbacks;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wd_server_conf->s,
+ APLOGNO(02972) "%sWatchdog (%s) running",
+ w->singleton ? "Singleton " : "", w->name);
+ apr_time_clock_hires(w->pool);
+ if (wl) {
+ while (wl && w->is_running) {
+ /* Execute watchdog callback */
+ wl->status = (*wl->callback_fn)(AP_WATCHDOG_STATE_STARTING,
+ (void *)wl->data, temp_pool);
+ wl = wl->next;
+ }
+ apr_pool_clear(temp_pool);
+ }
+ else {
+ ap_run_watchdog_init(wd_server_conf->s, w->name, w->pool);
+ inited = 1;
+ }
+ }
+
+ /* Main execution loop */
+ while (w->is_running) {
+ apr_time_t curr;
+ watchdog_list_t *wl = w->callbacks;
+
+ apr_sleep(AP_WD_TM_SLICE);
+ if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpmq_s) != APR_SUCCESS) {
+ w->is_running = 0;
+ }
+ if (mpmq_s == AP_MPMQ_STOPPING) {
+ w->is_running = 0;
+ }
+ if (!w->is_running) {
+ break;
+ }
+ curr = apr_time_now() - AP_WD_TM_SLICE;
+ while (wl && w->is_running) {
+ if (wl->status == APR_SUCCESS) {
+ wl->step += (apr_time_now() - curr);
+ if (wl->step >= wl->interval) {
+ wl->step = 0;
+ /* Execute watchdog callback */
+ wl->status = (*wl->callback_fn)(AP_WATCHDOG_STATE_RUNNING,
+ (void *)wl->data, temp_pool);
+ if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpmq_s) != APR_SUCCESS) {
+ w->is_running = 0;
+ }
+ if (mpmq_s == AP_MPMQ_STOPPING) {
+ w->is_running = 0;
+ }
+ }
+ }
+ wl = wl->next;
+ }
+ if (w->is_running && w->callbacks == NULL) {
+ /* This is hook mode watchdog
+ * running on WatchogInterval
+ */
+ w->step += (apr_time_now() - curr);
+ if (w->step >= wd_interval) {
+ w->step = 0;
+ /* Run watchdog step hook */
+ ap_run_watchdog_step(wd_server_conf->s, w->name, temp_pool);
+ }
+ }
+
+ apr_pool_clear(temp_pool);
+ }
+
+ apr_pool_destroy(temp_pool);
+
+ if (inited) {
+ /* Run the watchdog exit hooks.
+ * If this was singleton watchdog the init hook
+ * might never been called, so skip the exit hook
+ * in that case as well.
+ */
+ ap_run_watchdog_exit(wd_server_conf->s, w->name, w->pool);
+ }
+ else {
+ watchdog_list_t *wl = w->callbacks;
+ while (wl) {
+ if (wl->status == APR_SUCCESS) {
+ /* Execute watchdog callback with STOPPING state */
+ (*wl->callback_fn)(AP_WATCHDOG_STATE_STOPPING,
+ (void *)wl->data, w->pool);
+ }
+ wl = wl->next;
+ }
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wd_server_conf->s,
+ APLOGNO(02973) "%sWatchdog (%s) stopping",
+ w->singleton ? "Singleton " : "", w->name);
+
+ if (locked)
+ apr_proc_mutex_unlock(w->mutex);
+ apr_thread_exit(w->thread, APR_SUCCESS);
+
+ return NULL;
+}
+
+static apr_status_t wd_startup(ap_watchdog_t *w, apr_pool_t *p)
+{
+ apr_status_t rc;
+
+ apr_atomic_set32(&w->thread_started, 0);
+
+ if (w->singleton) {
+ /* Initialize singleton mutex in child */
+ rc = apr_proc_mutex_child_init(&w->mutex,
+ apr_proc_mutex_lockfile(w->mutex), p);
+ if (rc != APR_SUCCESS)
+ return rc;
+ }
+
+ /* Start the newly created watchdog */
+ rc = ap_thread_create(&w->thread, NULL, wd_worker, w, p);
+ if (rc == APR_SUCCESS) {
+ apr_pool_pre_cleanup_register(p, w, wd_worker_cleanup);
+ }
+
+ return rc;
+}
+
+static apr_status_t ap_watchdog_get_instance(ap_watchdog_t **watchdog,
+ const char *name,
+ int parent,
+ int singleton,
+ apr_pool_t *p)
+{
+ ap_watchdog_t *w;
+ const char *pver = parent ? AP_WATCHDOG_PVERSION : AP_WATCHDOG_CVERSION;
+
+ if (parent && mpm_is_forked != AP_MPMQ_NOT_SUPPORTED) {
+ /* Parent threads are not supported for
+ * forked mpm's
+ */
+ *watchdog = NULL;
+ return APR_ENOTIMPL;
+ }
+ w = ap_lookup_provider(AP_WATCHDOG_PGROUP, name, pver);
+ if (w) {
+ *watchdog = w;
+ return APR_SUCCESS;
+ }
+ w = apr_pcalloc(p, sizeof(ap_watchdog_t));
+ w->name = name;
+ w->pool = p;
+ w->singleton = parent ? 0 : singleton;
+ *watchdog = w;
+ return ap_register_provider(p, AP_WATCHDOG_PGROUP, name,
+ pver, *watchdog);
+}
+
+static apr_status_t ap_watchdog_set_callback_interval(ap_watchdog_t *w,
+ apr_interval_time_t interval,
+ const void *data,
+ ap_watchdog_callback_fn_t *callback)
+{
+ watchdog_list_t *c = w->callbacks;
+ apr_status_t rv = APR_EOF;
+
+ while (c) {
+ if (c->data == data && c->callback_fn == callback) {
+ /* We have existing callback.
+ * Update the interval and reset status, so the
+ * callback and continue execution if stopped earlier.
+ */
+ c->interval = interval;
+ c->step = 0;
+ c->status = APR_SUCCESS;
+ rv = APR_SUCCESS;
+ break;
+ }
+ c = c->next;
+ }
+ return rv;
+}
+
+static apr_status_t ap_watchdog_register_callback(ap_watchdog_t *w,
+ apr_interval_time_t interval,
+ const void *data,
+ ap_watchdog_callback_fn_t *callback)
+{
+ watchdog_list_t *c = w->callbacks;
+
+ while (c) {
+ if (c->data == data && c->callback_fn == callback) {
+ /* We have already registered callback.
+ * Do not allow callbacks that have the same
+ * function and data pointers.
+ */
+ return APR_EEXIST;
+ }
+ c = c->next;
+ }
+ c = apr_palloc(w->pool, sizeof(watchdog_list_t));
+ c->data = data;
+ c->callback_fn = callback;
+ c->interval = interval;
+ c->step = 0;
+ c->status = APR_EINIT;
+
+ c->wd = w;
+ c->next = w->callbacks;
+ w->callbacks = c;
+ w->active++;
+
+ return APR_SUCCESS;
+}
+
+/*--------------------------------------------------------------------------*/
+/* */
+/* Pre config hook. */
+/* Create default watchdogs for parent and child */
+/* Parent watchdog executes inside parent process so it doesn't need the */
+/* singleton mutex */
+/* */
+/*--------------------------------------------------------------------------*/
+static int wd_pre_config_hook(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp)
+{
+ apr_status_t rv;
+ ap_watchdog_t *w;
+
+ ap_mpm_query(AP_MPMQ_IS_FORKED, &mpm_is_forked);
+ if ((rv = ap_watchdog_get_instance(&w,
+ AP_WATCHDOG_SINGLETON, 0, 1, pconf)) != APR_SUCCESS) {
+ return rv;
+ }
+ if ((rv = ap_watchdog_get_instance(&w,
+ AP_WATCHDOG_DEFAULT, 0, 0, pconf)) != APR_SUCCESS) {
+ return rv;
+ }
+ if (mpm_is_forked == AP_MPMQ_NOT_SUPPORTED) {
+ /* Create parent process watchdog for
+ * non forked mpm's only.
+ */
+ if ((rv = ap_watchdog_get_instance(&w,
+ AP_WATCHDOG_DEFAULT, 1, 0, pconf)) != APR_SUCCESS) {
+ return rv;
+ }
+ }
+
+ if ((rv = ap_mutex_register(pconf, wd_proc_mutex_type, NULL,
+ APR_LOCK_DEFAULT, 0)) != APR_SUCCESS) {
+ return rv;
+ }
+
+ return OK;
+}
+
+/*--------------------------------------------------------------------------*/
+/* */
+/* Post config hook. */
+/* Create watchdog thread in parent and initializes Watchdog module */
+/* */
+/*--------------------------------------------------------------------------*/
+static int wd_post_config_hook(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ apr_status_t rv;
+ const char *pk = "watchdog_init_module_tag";
+ apr_pool_t *ppconf = pconf;
+ const apr_array_header_t *wl;
+
+ if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG)
+ /* First time config phase -- skip. */
+ return OK;
+
+ apr_pool_userdata_get((void *)&wd_server_conf, pk, ppconf);
+ if (!wd_server_conf) {
+ if (!(wd_server_conf = apr_pcalloc(ppconf, sizeof(wd_server_conf_t))))
+ return APR_ENOMEM;
+ apr_pool_create(&wd_server_conf->pool, ppconf);
+ apr_pool_tag(wd_server_conf->pool, "wd_server_conf");
+ apr_pool_userdata_set(wd_server_conf, pk, apr_pool_cleanup_null, ppconf);
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(010033)
+ "Watchdog: Running with WatchdogInterval %"
+ APR_TIME_T_FMT "ms", apr_time_as_msec(wd_interval));
+ wd_server_conf->s = s;
+ if ((wl = ap_list_provider_names(pconf, AP_WATCHDOG_PGROUP,
+ AP_WATCHDOG_PVERSION))) {
+ const ap_list_provider_names_t *wn;
+ int i;
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02974)
+ "Watchdog: found parent providers.");
+
+ wn = (ap_list_provider_names_t *)wl->elts;
+ for (i = 0; i < wl->nelts; i++) {
+ ap_watchdog_t *w = ap_lookup_provider(AP_WATCHDOG_PGROUP,
+ wn[i].provider_name,
+ AP_WATCHDOG_PVERSION);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02975)
+ "Watchdog: Looking for parent (%s).", wn[i].provider_name);
+ if (w) {
+ if (!w->active) {
+ int status = ap_run_watchdog_need(s, w->name, 1,
+ w->singleton);
+ if (status == OK) {
+ /* One of the modules returned OK to this watchdog.
+ * Mark it as active
+ */
+ w->active = 1;
+ }
+ }
+ if (w->active) {
+ /* We have active watchdog.
+ * Create the watchdog thread
+ */
+ if ((rv = wd_startup(w, wd_server_conf->pool)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(01571)
+ "Watchdog: Failed to create parent worker thread.");
+ return rv;
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(02976)
+ "Watchdog: Created parent worker thread (%s).", w->name);
+ wd_server_conf->parent_workers++;
+ }
+ }
+ }
+ }
+ if (wd_server_conf->parent_workers) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01572)
+ "Spawned %d parent worker threads.",
+ wd_server_conf->parent_workers);
+ }
+ if ((wl = ap_list_provider_names(pconf, AP_WATCHDOG_PGROUP,
+ AP_WATCHDOG_CVERSION))) {
+ const ap_list_provider_names_t *wn;
+ int i;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02977)
+ "Watchdog: found child providers.");
+
+ wn = (ap_list_provider_names_t *)wl->elts;
+ for (i = 0; i < wl->nelts; i++) {
+ ap_watchdog_t *w = ap_lookup_provider(AP_WATCHDOG_PGROUP,
+ wn[i].provider_name,
+ AP_WATCHDOG_CVERSION);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02978)
+ "Watchdog: Looking for child (%s).", wn[i].provider_name);
+ if (w) {
+ if (!w->active) {
+ int status = ap_run_watchdog_need(s, w->name, 0,
+ w->singleton);
+ if (status == OK) {
+ /* One of the modules returned OK to this watchdog.
+ * Mark it as active
+ */
+ w->active = 1;
+ }
+ }
+ if (w->active) {
+ /* We have some callbacks registered.
+ * Create mutexes for singleton watchdogs
+ */
+ if (w->singleton) {
+ rv = ap_proc_mutex_create(&w->mutex, NULL, wd_proc_mutex_type,
+ w->name, s,
+ wd_server_conf->pool, 0);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(10095)
+ "Watchdog: Failed to create singleton mutex.");
+ return rv;
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(02979)
+ "Watchdog: Created singleton mutex (%s).", w->name);
+ }
+ wd_server_conf->child_workers++;
+ }
+ }
+ }
+ }
+ return OK;
+}
+
+/*--------------------------------------------------------------------------*/
+/* */
+/* Child init hook. */
+/* Create watchdog threads and initializes Mutexes in child */
+/* */
+/*--------------------------------------------------------------------------*/
+static void wd_child_init_hook(apr_pool_t *p, server_rec *s)
+{
+ apr_status_t rv = OK;
+ const apr_array_header_t *wl;
+
+ if (!wd_server_conf->child_workers) {
+ /* We don't have anything configured, bail out.
+ */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(02980)
+ "Watchdog: nothing configured?");
+ return;
+ }
+ if ((wl = ap_list_provider_names(p, AP_WATCHDOG_PGROUP,
+ AP_WATCHDOG_CVERSION))) {
+ const ap_list_provider_names_t *wn;
+ int i;
+ wn = (ap_list_provider_names_t *)wl->elts;
+ for (i = 0; i < wl->nelts; i++) {
+ ap_watchdog_t *w = ap_lookup_provider(AP_WATCHDOG_PGROUP,
+ wn[i].provider_name,
+ AP_WATCHDOG_CVERSION);
+ if (w && w->active) {
+ /* We have some callbacks registered.
+ * Kick of the watchdog
+ */
+ if ((rv = wd_startup(w, wd_server_conf->pool)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(01573)
+ "Watchdog: Failed to create child worker thread.");
+ /* No point to continue */
+ return;
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(02981)
+ "Watchdog: Created child worker thread (%s).", wn[i].provider_name);
+ }
+ }
+ }
+}
+
+/*--------------------------------------------------------------------------*/
+/* */
+/* WatchdogInterval directive */
+/* */
+/*--------------------------------------------------------------------------*/
+static const char *wd_cmd_watchdog_int(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ apr_status_t rv;
+ const char *errs = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (errs != NULL)
+ return errs;
+ rv = ap_timeout_parameter_parse(arg, &wd_interval, "s");
+
+ if (rv != APR_SUCCESS)
+ return "Unparse-able WatchdogInterval setting";
+ if (wd_interval < AP_WD_TM_SLICE) {
+ return apr_psprintf(cmd->pool, "Invalid WatchdogInterval: minimal value %"
+ APR_TIME_T_FMT "ms", apr_time_as_msec(AP_WD_TM_SLICE));
+ }
+
+ return NULL;
+}
+
+/*--------------------------------------------------------------------------*/
+/* */
+/* List of directives specific to our module. */
+/* */
+/*--------------------------------------------------------------------------*/
+static const command_rec wd_directives[] =
+{
+ AP_INIT_TAKE1(
+ "WatchdogInterval", /* directive name */
+ wd_cmd_watchdog_int, /* config action routine */
+ NULL, /* argument to include in call */
+ RSRC_CONF, /* where available */
+ "Watchdog interval in seconds"
+ ),
+ {NULL}
+};
+
+/*--------------------------------------------------------------------------*/
+/* */
+/* Which functions are responsible for which hooks in the server. */
+/* */
+/*--------------------------------------------------------------------------*/
+static void wd_register_hooks(apr_pool_t *p)
+{
+
+ /* Only the mpm_winnt has child init hook handler.
+ * Make sure that we are called after the mpm child init handler
+ * initializes.
+ */
+ static const char *const after_mpm[] = { "mpm_winnt.c", NULL};
+
+ /* Pre config handling
+ */
+ ap_hook_pre_config(wd_pre_config_hook,
+ NULL,
+ NULL,
+ APR_HOOK_FIRST);
+
+ /* Post config handling
+ */
+ ap_hook_post_config(wd_post_config_hook,
+ NULL,
+ NULL,
+ APR_HOOK_LAST);
+
+ /* Child init hook
+ */
+ ap_hook_child_init(wd_child_init_hook,
+ after_mpm,
+ NULL,
+ APR_HOOK_MIDDLE);
+
+ APR_REGISTER_OPTIONAL_FN(ap_watchdog_get_instance);
+ APR_REGISTER_OPTIONAL_FN(ap_watchdog_register_callback);
+ APR_REGISTER_OPTIONAL_FN(ap_watchdog_set_callback_interval);
+}
+
+/*--------------------------------------------------------------------------*/
+/* */
+/* The list of callback routines and data structures that provide */
+/* the static hooks into our module from the other parts of the server. */
+/* */
+/*--------------------------------------------------------------------------*/
+AP_DECLARE_MODULE(watchdog) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ wd_directives, /* command apr_table_t */
+ wd_register_hooks /* register hooks */
+};
+
+/*--------------------------------------------------------------------------*/
+/* */
+/* The list of optional hooks that we provide */
+/* */
+/*--------------------------------------------------------------------------*/
+APR_HOOK_STRUCT(
+ APR_HOOK_LINK(watchdog_need)
+ APR_HOOK_LINK(watchdog_init)
+ APR_HOOK_LINK(watchdog_exit)
+ APR_HOOK_LINK(watchdog_step)
+)
+
+APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(ap, AP_WD, int, watchdog_need,
+ (server_rec *s, const char *name,
+ int parent, int singleton),
+ (s, name, parent, singleton),
+ DECLINED)
+APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ap, AP_WD, int, watchdog_init,
+ (server_rec *s, const char *name,
+ apr_pool_t *pool),
+ (s, name, pool),
+ OK, DECLINED)
+APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ap, AP_WD, int, watchdog_exit,
+ (server_rec *s, const char *name,
+ apr_pool_t *pool),
+ (s, name, pool),
+ OK, DECLINED)
+APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ap, AP_WD, int, watchdog_step,
+ (server_rec *s, const char *name,
+ apr_pool_t *pool),
+ (s, name, pool),
+ OK, DECLINED)
diff --git a/modules/core/mod_watchdog.dep b/modules/core/mod_watchdog.dep
new file mode 100644
index 0000000..147475b
--- /dev/null
+++ b/modules/core/mod_watchdog.dep
@@ -0,0 +1,59 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_watchdog.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_watchdog.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_mpm.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_watchdog.h"\
+
diff --git a/modules/core/mod_watchdog.dsp b/modules/core/mod_watchdog.dsp
new file mode 100644
index 0000000..fcb1f28
--- /dev/null
+++ b/modules/core/mod_watchdog.dsp
@@ -0,0 +1,115 @@
+# Microsoft Developer Studio Project File - Name="mod_watchdog" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_watchdog - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_watchdog.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_watchdog.mak" CFG="mod_watchdog - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_watchdog - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_watchdog - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_watchdog - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "AP_WD_DECLARE_EXPORT" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_watchdog_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_watchdog.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_watchdog.so" /d LONG_NAME="watchdog_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_watchdog.so" /base:@..\..\os\win32\BaseAddr.ref,mod_watchdog.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_watchdog.so" /base:@..\..\os\win32\BaseAddr.ref,mod_watchdog.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_watchdog.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_watchdog - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "AP_WD_DECLARE_EXPORT" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_watchdog_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_watchdog.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_watchdog.so" /d LONG_NAME="watchdog_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_watchdog.so" /base:@..\..\os\win32\BaseAddr.ref,mod_watchdog.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_watchdog.so" /base:@..\..\os\win32\BaseAddr.ref,mod_watchdog.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_watchdog.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_watchdog - Win32 Release"
+# Name "mod_watchdog - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_watchdog.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\mod_watchdog.h
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/core/mod_watchdog.h b/modules/core/mod_watchdog.h
new file mode 100644
index 0000000..8e7112c
--- /dev/null
+++ b/modules/core/mod_watchdog.h
@@ -0,0 +1,213 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MOD_WATCHDOG_H
+#define MOD_WATCHDOG_H
+
+/**
+ * @file mod_watchdog.h
+ * @brief Watchdog module for Apache
+ *
+ * @defgroup MOD_WATCHDOG mod_watchdog
+ * @ingroup APACHE_MODS
+ * @{
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "ap_provider.h"
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_pools.h"
+#include "apr_shm.h"
+#include "apr_hash.h"
+#include "apr_hooks.h"
+#include "apr_optional.h"
+#include "apr_file_io.h"
+#include "apr_time.h"
+#include "apr_thread_proc.h"
+#include "apr_global_mutex.h"
+#include "apr_thread_mutex.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Default singleton watchdog instance name.
+ * Singleton watchdog is protected by mutex and
+ * guaranteed to be run inside a single child process
+ * at any time.
+ */
+#define AP_WATCHDOG_SINGLETON "_singleton_"
+
+/**
+ * Default watchdog instance name
+ */
+#define AP_WATCHDOG_DEFAULT "_default_"
+
+/**
+ * Default Watchdog interval
+ */
+#define AP_WD_TM_INTERVAL APR_TIME_C(1000000) /* 1 second */
+
+/**
+ * Watchdog thread timer resolution
+ */
+#define AP_WD_TM_SLICE APR_TIME_C(100000) /* 100 ms */
+
+/* State values for callback */
+#define AP_WATCHDOG_STATE_STARTING 1
+#define AP_WATCHDOG_STATE_RUNNING 2
+#define AP_WATCHDOG_STATE_STOPPING 3
+
+typedef struct ap_watchdog_t ap_watchdog_t;
+
+/* Create a set of AP_WD_DECLARE(type), AP_WD_DECLARE_NONSTD(type) and
+ * AP_WD_DECLARE_DATA with appropriate export and import tags for the platform
+ */
+#if !defined(AP_WD_DECLARE)
+#if !defined(WIN32)
+#define AP_WD_DECLARE(type) type
+#define AP_WD_DECLARE_NONSTD(type) type
+#define AP_WD_DECLARE_DATA
+#elif defined(AP_WD_DECLARE_STATIC)
+#define AP_WD_DECLARE(type) type __stdcall
+#define AP_WD_DECLARE_NONSTD(type) type
+#define AP_WD_DECLARE_DATA
+#elif defined(AP_WD_DECLARE_EXPORT)
+#define AP_WD_DECLARE(type) __declspec(dllexport) type __stdcall
+#define AP_WD_DECLARE_NONSTD(type) __declspec(dllexport) type
+#define AP_WD_DECLARE_DATA __declspec(dllexport)
+#else
+#define AP_WD_DECLARE(type) __declspec(dllimport) type __stdcall
+#define AP_WD_DECLARE_NONSTD(type) __declspec(dllimport) type
+#define AP_WD_DECLARE_DATA __declspec(dllimport)
+#endif
+#endif
+
+/**
+ * Callback function used for watchdog.
+ * @param state Watchdog state function. See @p AP_WATCHDOG_STATE_ .
+ * @param data is what was passed to @p ap_watchdog_register_callback function.
+ * @param pool Temporary callback pool destroyed after the call.
+ * @return APR_SUCCESS to continue calling this callback.
+ */
+typedef apr_status_t ap_watchdog_callback_fn_t(int state, void *data,
+ apr_pool_t *pool);
+
+/**
+ * Get watchdog instance.
+ * @param watchdog Storage for watchdog instance.
+ * @param name Watchdog name.
+ * @param parent Non-zero to get the parent process watchdog instance.
+ * @param singleton Non-zero to get the singleton watchdog instance.
+ * @param p The pool to use.
+ * @return APR_SUCCESS if all went well
+ * @remark Use @p AP_WATCHDOG_DEFAULT to get default watchdog instance.
+ * If separate watchdog thread is needed provide unique name
+ * and function will create a new watchdog instance.
+ * Note that default client process watchdog works in singleton mode.
+ */
+APR_DECLARE_OPTIONAL_FN(apr_status_t, ap_watchdog_get_instance,
+ (ap_watchdog_t **watchdog, const char *name, int parent,
+ int singleton, apr_pool_t *p));
+
+/**
+ * Register watchdog callback.
+ * @param watchdog Watchdog to use
+ * @param interval Interval on which the callback function will execute.
+ * @param callback The function to call on watchdog event.
+ * @param data The data to pass to the callback function.
+ * @return APR_SUCCESS if all went well. APR_EEXIST if already registered.
+ */
+APR_DECLARE_OPTIONAL_FN(apr_status_t, ap_watchdog_register_callback,
+ (ap_watchdog_t *watchdog, apr_interval_time_t interval,
+ const void *data, ap_watchdog_callback_fn_t *callback));
+
+/**
+ * Update registered watchdog callback interval.
+ * @param w Watchdog to use
+ * @param interval New interval on which the callback function will execute.
+ * @param callback The function to call on watchdog event.
+ * @param data The data to pass to the callback function.
+ * @return APR_SUCCESS if all went well. APR_EOF if callback was not found.
+ */
+APR_DECLARE_OPTIONAL_FN(apr_status_t, ap_watchdog_set_callback_interval,
+ (ap_watchdog_t *w, apr_interval_time_t interval,
+ const void *data, ap_watchdog_callback_fn_t *callback));
+
+/**
+ * Watchdog require hook.
+ * @param s The server record
+ * @param name Watchdog name.
+ * @param parent Non-zero to indicate the parent process watchdog mode.
+ * @param singleton Non-zero to indicate the singleton watchdog mode.
+ * @return OK to enable notifications from this watchdog, DECLINED otherwise.
+ * @remark This is called in post config phase for all watchdog instances
+ * that have no callbacks registered. Modules using this hook
+ * should ensure that their post_config hook is called after watchdog
+ * post_config.
+ */
+APR_DECLARE_EXTERNAL_HOOK(ap, AP_WD, int, watchdog_need, (server_rec *s,
+ const char *name,
+ int parent, int singleton))
+
+
+/**
+ * Watchdog initialize hook.
+ * It is called after the watchdog thread is initialized.
+ * @param s The server record
+ * @param name Watchdog name.
+ * @param pool The pool used to create the watchdog.
+ */
+APR_DECLARE_EXTERNAL_HOOK(ap, AP_WD, int, watchdog_init, (
+ server_rec *s,
+ const char *name,
+ apr_pool_t *pool))
+
+/**
+ * Watchdog terminate hook.
+ * It is called when the watchdog thread is terminated.
+ * @param s The server record
+ * @param name Watchdog name.
+ * @param pool The pool used to create the watchdog.
+ */
+APR_DECLARE_EXTERNAL_HOOK(ap, AP_WD, int, watchdog_exit, (
+ server_rec *s,
+ const char *name,
+ apr_pool_t *pool))
+
+/**
+ * Fixed interval watchdog hook.
+ * It is called regularly on @p AP_WD_TM_INTERVAL interval.
+ * @param s The server record
+ * @param name Watchdog name.
+ * @param pool Temporary pool destroyed after the call.
+ */
+APR_DECLARE_EXTERNAL_HOOK(ap, AP_WD, int, watchdog_step, (
+ server_rec *s,
+ const char *name,
+ apr_pool_t *pool))
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MOD_WATCHDOG_H */
+/** @} */
diff --git a/modules/core/mod_watchdog.mak b/modules/core/mod_watchdog.mak
new file mode 100644
index 0000000..6b58c6d
--- /dev/null
+++ b/modules/core/mod_watchdog.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_watchdog.dsp
+!IF "$(CFG)" == ""
+CFG=mod_watchdog - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_watchdog - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_watchdog - Win32 Release" && "$(CFG)" != "mod_watchdog - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_watchdog.mak" CFG="mod_watchdog - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_watchdog - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_watchdog - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_watchdog - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_watchdog.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_watchdog.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_watchdog.obj"
+ -@erase "$(INTDIR)\mod_watchdog.res"
+ -@erase "$(INTDIR)\mod_watchdog_src.idb"
+ -@erase "$(INTDIR)\mod_watchdog_src.pdb"
+ -@erase "$(OUTDIR)\mod_watchdog.exp"
+ -@erase "$(OUTDIR)\mod_watchdog.lib"
+ -@erase "$(OUTDIR)\mod_watchdog.pdb"
+ -@erase "$(OUTDIR)\mod_watchdog.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "AP_WD_DECLARE_EXPORT" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_watchdog_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_watchdog.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_watchdog.so" /d LONG_NAME="watchdog_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_watchdog.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_watchdog.pdb" /debug /out:"$(OUTDIR)\mod_watchdog.so" /implib:"$(OUTDIR)\mod_watchdog.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_watchdog.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_watchdog.obj" \
+ "$(INTDIR)\mod_watchdog.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_watchdog.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_watchdog.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_watchdog.so"
+ if exist .\Release\mod_watchdog.so.manifest mt.exe -manifest .\Release\mod_watchdog.so.manifest -outputresource:.\Release\mod_watchdog.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_watchdog - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_watchdog.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_watchdog.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_watchdog.obj"
+ -@erase "$(INTDIR)\mod_watchdog.res"
+ -@erase "$(INTDIR)\mod_watchdog_src.idb"
+ -@erase "$(INTDIR)\mod_watchdog_src.pdb"
+ -@erase "$(OUTDIR)\mod_watchdog.exp"
+ -@erase "$(OUTDIR)\mod_watchdog.lib"
+ -@erase "$(OUTDIR)\mod_watchdog.pdb"
+ -@erase "$(OUTDIR)\mod_watchdog.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "AP_WD_DECLARE_EXPORT" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_watchdog_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_watchdog.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_watchdog.so" /d LONG_NAME="watchdog_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_watchdog.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_watchdog.pdb" /debug /out:"$(OUTDIR)\mod_watchdog.so" /implib:"$(OUTDIR)\mod_watchdog.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_watchdog.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_watchdog.obj" \
+ "$(INTDIR)\mod_watchdog.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_watchdog.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_watchdog.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_watchdog.so"
+ if exist .\Debug\mod_watchdog.so.manifest mt.exe -manifest .\Debug\mod_watchdog.so.manifest -outputresource:.\Debug\mod_watchdog.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_watchdog.dep")
+!INCLUDE "mod_watchdog.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_watchdog.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_watchdog - Win32 Release" || "$(CFG)" == "mod_watchdog - Win32 Debug"
+
+!IF "$(CFG)" == "mod_watchdog - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\core"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\core"
+
+!ELSEIF "$(CFG)" == "mod_watchdog - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\core"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\core"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_watchdog - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\core"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\core"
+
+!ELSEIF "$(CFG)" == "mod_watchdog - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\core"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\core"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_watchdog - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\core"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\core"
+
+!ELSEIF "$(CFG)" == "mod_watchdog - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\core"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\core"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_watchdog - Win32 Release"
+
+
+"$(INTDIR)\mod_watchdog.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_watchdog.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_watchdog.so" /d LONG_NAME="watchdog_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_watchdog - Win32 Debug"
+
+
+"$(INTDIR)\mod_watchdog.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_watchdog.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_watchdog.so" /d LONG_NAME="watchdog_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_watchdog.c
+
+"$(INTDIR)\mod_watchdog.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/core/test/Makefile b/modules/core/test/Makefile
new file mode 100644
index 0000000..bd2a6cb
--- /dev/null
+++ b/modules/core/test/Makefile
@@ -0,0 +1,67 @@
+#
+# mod_macro non regression tests
+
+# where is apache
+APA.dir = /tmp/apache
+
+# apache executable with mod macro loaded
+HTTPD = \
+ $(APA.dir)/bin/httpd \
+ -C 'LoadModule macro_module modules/mod_macro.so' \
+ -C "Define PWD $$PWD/conf"
+
+# default target
+.PHONY: default
+default: clean
+
+# run all non regression tests
+.PHONY: check
+check: check-out
+
+# result directory
+OUT = out
+out:
+ mkdir $@
+
+# test cases & results
+F.conf = $(wildcard conf/test*.conf)
+F.out = $(F.conf:conf/%.conf=$(OUT)/%.out)
+
+# run all tests
+.PHONY: run-test
+run-test: $(F.out)
+
+# generate & compare in a separate directory
+.PHONY: check-out
+check-out: out
+ $(RM) out/*.out
+ $(MAKE) OUT=out run-test
+ diff -r out/ ref/
+
+# generate & compare in the same directory
+.PHONY: check-ref
+check-ref:
+ $(RM) ref/*.out
+ $(MAKE) OUT=ref run-test
+ svn diff ref/
+
+# run one test case
+# filter output so that it is portable
+# use '|' sed separator because $PWD will contain plenty '/'
+$(OUT)/%.out: conf/%.conf
+ { \
+ echo "# testing with $<" ; \
+ $(HTTPD) -f $$PWD/$< 2>&1 ; \
+ echo "# exit: $$?" ; \
+ } > $@.tmp ; \
+ sed -e "s|$$PWD|.|g" \
+ -e "s|^\[[\.a-zA-Z0-9 :]*\] ||" \
+ -e "s|\[pid [0-9]*:tid [0-9]*] ||" \
+ $@.tmp > $@ ; \
+ $(RM) $@.tmp
+
+# cleanup
+.PHONY: clean
+clean:
+ $(RM) *~
+ $(RM) -r out
diff --git a/modules/core/test/conf/inc63_1.conf b/modules/core/test/conf/inc63_1.conf
new file mode 100644
index 0000000..6a436f9
--- /dev/null
+++ b/modules/core/test/conf/inc63_1.conf
@@ -0,0 +1,5 @@
+# macro for include
+<Macro Foo $where>
+ Warning "Foo macro at $where"
+</Macro>
+Use Foo "inc63_.conf:5"
diff --git a/modules/core/test/conf/inc63_2.conf b/modules/core/test/conf/inc63_2.conf
new file mode 100644
index 0000000..3a0da9e
--- /dev/null
+++ b/modules/core/test/conf/inc63_2.conf
@@ -0,0 +1,3 @@
+# use macro defined elsewhere
+Use Foo "inc63_2.conf:2"
+Use Bla "inc63_2.conf:3"
diff --git a/modules/core/test/conf/test01.conf b/modules/core/test/conf/test01.conf
new file mode 100644
index 0000000..9a3d9ab
--- /dev/null
+++ b/modules/core/test/conf/test01.conf
@@ -0,0 +1,3 @@
+# no macro name
+<Macro>
+</Macro>
diff --git a/modules/core/test/conf/test02.conf b/modules/core/test/conf/test02.conf
new file mode 100644
index 0000000..1fe4b41
--- /dev/null
+++ b/modules/core/test/conf/test02.conf
@@ -0,0 +1,3 @@
+# no macro name and spaces
+<Macro >
+</Macro>
diff --git a/modules/core/test/conf/test03.conf b/modules/core/test/conf/test03.conf
new file mode 100644
index 0000000..fdcf4c8
--- /dev/null
+++ b/modules/core/test/conf/test03.conf
@@ -0,0 +1,5 @@
+# use undefined macro
+<Macro foo>
+ Warning "macro foo"
+</Macro>
+Use bla
diff --git a/modules/core/test/conf/test04.conf b/modules/core/test/conf/test04.conf
new file mode 100644
index 0000000..8228775
--- /dev/null
+++ b/modules/core/test/conf/test04.conf
@@ -0,0 +1,5 @@
+# wrong args
+<Macro foo>
+ Warning "macro foo"
+</Macro>
+Use foo hello
diff --git a/modules/core/test/conf/test05.conf b/modules/core/test/conf/test05.conf
new file mode 100644
index 0000000..2166aca
--- /dev/null
+++ b/modules/core/test/conf/test05.conf
@@ -0,0 +1,5 @@
+# wrong args
+<Macro foo $premier>
+ Warning "macro foo $premier"
+</Macro>
+Use foo
diff --git a/modules/core/test/conf/test06.conf b/modules/core/test/conf/test06.conf
new file mode 100644
index 0000000..35e6b68
--- /dev/null
+++ b/modules/core/test/conf/test06.conf
@@ -0,0 +1,6 @@
+# wrong args
+<Macro foo $premier>
+ Warning "macro foo $premier"
+</Macro>
+Use foo one two
+
diff --git a/modules/core/test/conf/test07.conf b/modules/core/test/conf/test07.conf
new file mode 100644
index 0000000..06f050e
--- /dev/null
+++ b/modules/core/test/conf/test07.conf
@@ -0,0 +1,3 @@
+# missing end macro
+<Macro foo $premier>
+hello
diff --git a/modules/core/test/conf/test08.conf b/modules/core/test/conf/test08.conf
new file mode 100644
index 0000000..333dbd9
--- /dev/null
+++ b/modules/core/test/conf/test08.conf
@@ -0,0 +1,3 @@
+# missing begin macro
+ServerName hello
+</Macro>
diff --git a/modules/core/test/conf/test09.conf b/modules/core/test/conf/test09.conf
new file mode 100644
index 0000000..2513b6e
--- /dev/null
+++ b/modules/core/test/conf/test09.conf
@@ -0,0 +1,6 @@
+# recursion is bad
+<Macro foo>
+Use foo
+</Macro>
+
+Use foo
diff --git a/modules/core/test/conf/test10.conf b/modules/core/test/conf/test10.conf
new file mode 100644
index 0000000..157129d
--- /dev/null
+++ b/modules/core/test/conf/test10.conf
@@ -0,0 +1,10 @@
+# indirect recursion is bad
+<Macro foo>
+Use bla
+</Macro>
+
+<Macro bla>
+Use foo
+</Macro>
+
+Use foo
diff --git a/modules/core/test/conf/test11.conf b/modules/core/test/conf/test11.conf
new file mode 100644
index 0000000..f397ec6
--- /dev/null
+++ b/modules/core/test/conf/test11.conf
@@ -0,0 +1,15 @@
+# inner macros...
+<Macro foo $arg>
+<Macro $arg.in>
+Warning "macro $arg.in line 1"
+</Macro>
+</Macro>
+
+# generate a one.in macro
+Use foo one
+
+# use it!
+Use one.in
+
+# end processing
+Error "done line 15."
diff --git a/modules/core/test/conf/test12.conf b/modules/core/test/conf/test12.conf
new file mode 100644
index 0000000..84403c6
--- /dev/null
+++ b/modules/core/test/conf/test12.conf
@@ -0,0 +1,12 @@
+# multiply defined generates a warning
+<Macro foo>
+ Warning "macro foo 1, line 1"
+</Macro>
+
+<Macro foo>
+ Warning "macro foo 2, line 1"
+</Macro>
+
+Use foo
+
+Error "done line 12."
diff --git a/modules/core/test/conf/test13.conf b/modules/core/test/conf/test13.conf
new file mode 100644
index 0000000..244470d
--- /dev/null
+++ b/modules/core/test/conf/test13.conf
@@ -0,0 +1,18 @@
+# case insensitive
+<Macro FOO>
+ Warning "macro FOO line 1"
+</MACRO>
+
+<MACRO bla>
+ Warning "macro bla line 1"
+</macro>
+
+use foo
+
+<macro foo>
+ Warning "redefined macro foo line 1"
+</macro>
+
+use FOO
+
+Error "done line 18."
diff --git a/modules/core/test/conf/test14.conf b/modules/core/test/conf/test14.conf
new file mode 100644
index 0000000..48d8888
--- /dev/null
+++ b/modules/core/test/conf/test14.conf
@@ -0,0 +1,23 @@
+# VirtualHost example
+
+<Macro MyVirtualHost $host $port $dir>
+ Listen $port
+ <VirtualHost $host:$port>
+ DocumentRoot $dir
+ <Directory $dir>
+ Warning "directory $dir"
+ </Directory>
+ # limit access to intranet subdir.
+ <Directory $dir/intranet>
+ Warning "directory $dir/intranet"
+ </Directory>
+ </VirtualHost>
+</Macro>
+
+Use MyVirtualHost www.apache.org 80 /projects/apache/web
+
+Use MyVirtualHost www.perl.com 8080 /projects/perl/web
+
+Use MyVirtualHost www.ensmp.fr 1234 /projects/mines/web
+
+Error "done line 23."
diff --git a/modules/core/test/conf/test15.conf b/modules/core/test/conf/test15.conf
new file mode 100644
index 0000000..7990e15
--- /dev/null
+++ b/modules/core/test/conf/test15.conf
@@ -0,0 +1,9 @@
+# non nested...
+<macro test>
+<directory /tmp>
+</macro>
+
+use test
+</directory>
+
+Error should not reach this point.
diff --git a/modules/core/test/conf/test16.conf b/modules/core/test/conf/test16.conf
new file mode 100644
index 0000000..471f66e
--- /dev/null
+++ b/modules/core/test/conf/test16.conf
@@ -0,0 +1,11 @@
+# bad nesting
+
+<Macro foo>
+</Limit>
+</Macro>
+
+<Limit GET>
+Use foo
+</Limit>
+
+stop
diff --git a/modules/core/test/conf/test17.conf b/modules/core/test/conf/test17.conf
new file mode 100644
index 0000000..f6294bb
--- /dev/null
+++ b/modules/core/test/conf/test17.conf
@@ -0,0 +1,10 @@
+# bad but good nesting
+
+<Macro foo>
+</Directory>
+</Macro>
+
+<Directory /tmp>
+Use foo
+
+Error "done on line 10."
diff --git a/modules/core/test/conf/test18.conf b/modules/core/test/conf/test18.conf
new file mode 100644
index 0000000..118617d
--- /dev/null
+++ b/modules/core/test/conf/test18.conf
@@ -0,0 +1,10 @@
+# bad but good nesting
+
+<Macro foo>
+</Location>
+</Macro>
+
+<Location /intranet>
+Use foo
+
+Error "done on line 10."
diff --git a/modules/core/test/conf/test19.conf b/modules/core/test/conf/test19.conf
new file mode 100644
index 0000000..6568e96
--- /dev/null
+++ b/modules/core/test/conf/test19.conf
@@ -0,0 +1,26 @@
+# okay till done
+
+<Macro foo $where>
+ # something
+ Warning "macro foo line 2 in $where"
+</Macro>
+
+<Directory /tmp>
+ Use foo Directory
+</Directory>
+
+<Location /intra>
+ Use foo Location
+</Location>
+
+<VirtualHost www.apache.org>
+ Use foo VirtualHost
+</VirtualHost>
+
+<VirtualHost www.perl.com>
+ <Directory /tmp>
+ Use foo "VirtualHost & Directory"
+ </Directory>
+</VirtualHost>
+
+Error "done line 26."
diff --git a/modules/core/test/conf/test20.conf b/modules/core/test/conf/test20.conf
new file mode 100644
index 0000000..ccbae0d
--- /dev/null
+++ b/modules/core/test/conf/test20.conf
@@ -0,0 +1,11 @@
+# directory in directory through a macro
+
+<Macro foo $dir>
+ <Directory $dir>
+ Warning "macro foo $dir"
+ </Directory>
+</Macro>
+
+<Directory /tmp>
+ Use foo /tmp
+</Directory>
diff --git a/modules/core/test/conf/test21.conf b/modules/core/test/conf/test21.conf
new file mode 100644
index 0000000..7a8c4c9
--- /dev/null
+++ b/modules/core/test/conf/test21.conf
@@ -0,0 +1,11 @@
+# raise an error
+
+<Macro foo>
+ <Directory /tmp>
+ Error "macro foo dir /tmp"
+ </Directory>
+</Macro>
+
+<VirtualHost *>
+ Use foo
+</VirtualHost>
diff --git a/modules/core/test/conf/test22.conf b/modules/core/test/conf/test22.conf
new file mode 100644
index 0000000..5a89f83
--- /dev/null
+++ b/modules/core/test/conf/test22.conf
@@ -0,0 +1,11 @@
+# simple nesting
+
+<Macro foo>
+ <Directory /tmp>
+ Warning "macro foo"
+ </Directory>
+</Macro>
+
+Use foo
+
+Error "done on line 11."
diff --git a/modules/core/test/conf/test23.conf b/modules/core/test/conf/test23.conf
new file mode 100644
index 0000000..e21e2ee
--- /dev/null
+++ b/modules/core/test/conf/test23.conf
@@ -0,0 +1,15 @@
+# macro defined in a directory
+
+<Directory /tmp>
+ <Macro foo>
+ Warning "macro foo in /tmp"
+ </Macro>
+</Directory>
+
+Use foo
+
+<Directory /tmp>
+ Use foo
+</Directory>
+
+Error "done!"
diff --git a/modules/core/test/conf/test24.conf b/modules/core/test/conf/test24.conf
new file mode 100644
index 0000000..d35070e
--- /dev/null
+++ b/modules/core/test/conf/test24.conf
@@ -0,0 +1,23 @@
+# nesting...
+
+<Macro bla>
+ <Location /intra>
+ Warning "macro bla intra"
+ </Location>
+ <Location /private>
+ Warning "macro bla private"
+ </Location>
+</Macro>
+
+# ok location in config
+Use bla
+
+# ok, location in VH
+<VirtualHost foo.com>
+ Use bla
+</VirtualHost>
+
+<Directory /tmp>
+ # fails: Location within an Directory
+ Use bla
+</Directory>
diff --git a/modules/core/test/conf/test25.conf b/modules/core/test/conf/test25.conf
new file mode 100644
index 0000000..724cf94
--- /dev/null
+++ b/modules/core/test/conf/test25.conf
@@ -0,0 +1,27 @@
+# ok till stop.
+
+<Macro RestrictedAccessPolicy $ips>
+ Warning "restricted access policy $ips"
+</Macro>
+
+<Directory /unexpected/1>
+ Use RestrictedAccessPolicy 10.0.0.0/8
+</Directory>
+
+<Macro LocalAccessOnly>
+ Use RestrictedAccessPolicy 10.0.0.0/8
+</Macro>
+
+<Directory /unexpected/2>
+ Use RestrictedAccessPolicy "192.54.172.0/24 192.54.148.0/24 10.0.0.0/8"
+</Directory>
+
+<Location /intra>
+ Use LocalAccessOnly
+</Location>
+
+<Location /admin>
+ Use LocalAccessOnly
+</Location>
+
+Error "done line 27."
diff --git a/modules/core/test/conf/test26.conf b/modules/core/test/conf/test26.conf
new file mode 100644
index 0000000..bb4b5ad
--- /dev/null
+++ b/modules/core/test/conf/test26.conf
@@ -0,0 +1,19 @@
+# ok till stop.
+# test quotes...
+
+<Macro funny "first arg" 'second ... arg'>
+ <Directory first arg>
+ Warning "funny directory"
+ </Directory>
+ <Location second ... arg>
+ Warning "funny location"
+ </Location>
+</Macro>
+
+Use funny /unexpected/1 /intra
+
+<VirtualHost www.apache.org>
+ Use funny /unexpected/2 /intranet
+</VirtualHost>
+
+Error "done!"
diff --git a/modules/core/test/conf/test27.conf b/modules/core/test/conf/test27.conf
new file mode 100644
index 0000000..2f3e83c
--- /dev/null
+++ b/modules/core/test/conf/test27.conf
@@ -0,0 +1,22 @@
+# define a macro in a macro.
+
+<Macro foo $dir $name>
+ <Macro foo.$name>
+ <Directory $dir>
+ Warning "foo.$name $dir"
+ </Directory>
+ </Macro>
+</Macro>
+
+Use foo /unexpected/1 one
+Use foo /unexpected/2 two
+
+Use foo.one
+Use foo.two
+Use foo.one
+
+UndefMacro foo.one
+UndefMacro foo.two
+UndefMacro foo
+
+Error "done!"
diff --git a/modules/core/test/conf/test28.conf b/modules/core/test/conf/test28.conf
new file mode 100644
index 0000000..69c1c9b
--- /dev/null
+++ b/modules/core/test/conf/test28.conf
@@ -0,0 +1,13 @@
+# interaction with IfModule
+
+<IfModule mod_macro.c>
+ <Macro foo>
+ Warning "macro foo"
+ </Macro>
+
+ Use foo
+
+ Error "done!"
+</IfModule>
+
+Error "should not get there"
diff --git a/modules/core/test/conf/test29.conf b/modules/core/test/conf/test29.conf
new file mode 100644
index 0000000..7d1f380
--- /dev/null
+++ b/modules/core/test/conf/test29.conf
@@ -0,0 +1,10 @@
+# trigger line overflow during expansion
+
+<Macro toobigaline a>
+ Warning aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+</Macro>
+
+Use toobigaline "cette ligne va etre vraiment trop longue ya pas de doute"
+
+Error "should not get there!"
diff --git a/modules/core/test/conf/test30.conf b/modules/core/test/conf/test30.conf
new file mode 100644
index 0000000..72b2bb2
--- /dev/null
+++ b/modules/core/test/conf/test30.conf
@@ -0,0 +1,12 @@
+# name conficts: the longest is chosen
+# also test a parametric section
+
+<Macro foo $dir $directive>
+ <$directive $dir>
+ Warning "section $directive $dir"
+ </$directive>
+</Macro>
+
+Use foo /unexpected/1 Directory
+
+Error "done!"
diff --git a/modules/core/test/conf/test31.conf b/modules/core/test/conf/test31.conf
new file mode 100644
index 0000000..14964ba
--- /dev/null
+++ b/modules/core/test/conf/test31.conf
@@ -0,0 +1,16 @@
+# parameter name conflicts
+
+<Macro bla $dir $di $dd $d>
+ Warning "argument name conflicts"
+ $d $di $dir $dd
+</Macro>
+
+Use bla '' '' 8080 Listen
+
+<Macro foo $d $di $dir $dd>
+ Warning "conflicts, but arguments are not used"
+</Macro>
+
+Use foo '' '' 8080 Listen
+
+Error "done on line 16."
diff --git a/modules/core/test/conf/test32.conf b/modules/core/test/conf/test32.conf
new file mode 100644
index 0000000..0b116ec
--- /dev/null
+++ b/modules/core/test/conf/test32.conf
@@ -0,0 +1,7 @@
+# error if same argument name.
+
+<Macro foo $arg1 $arg2 $arg3 $arg2>
+# bad
+</Macro>
+
+
diff --git a/modules/core/test/conf/test33.conf b/modules/core/test/conf/test33.conf
new file mode 100644
index 0000000..2a8a6db
--- /dev/null
+++ b/modules/core/test/conf/test33.conf
@@ -0,0 +1,3 @@
+# empty name.
+
+Use ''
diff --git a/modules/core/test/conf/test34.conf b/modules/core/test/conf/test34.conf
new file mode 100644
index 0000000..1f4671f
--- /dev/null
+++ b/modules/core/test/conf/test34.conf
@@ -0,0 +1,14 @@
+# macro parameter prefix conflicts in two orders
+
+<Macro foo $d $dd>
+ Warning "macro foo conflict one"
+</Macro>
+
+<Macro bla $dd $d>
+ Warning "macro bla conflict two"
+</Macro>
+
+Use foo 1 2
+Use bla 1 2
+
+Error "done on line 14."
diff --git a/modules/core/test/conf/test35.conf b/modules/core/test/conf/test35.conf
new file mode 100644
index 0000000..37a623e
--- /dev/null
+++ b/modules/core/test/conf/test35.conf
@@ -0,0 +1,10 @@
+# unused arguments
+
+<Macro warnings u1 u2 n1 n2 u3>
+ Warning "macro cannot be used just within a comment u1 u2 u3"
+ # n1 n2
+</Macro>
+
+Use warnings 1 2 3 4 5
+
+Error "done on line 10."
diff --git a/modules/core/test/conf/test36.conf b/modules/core/test/conf/test36.conf
new file mode 100644
index 0000000..a68667e
--- /dev/null
+++ b/modules/core/test/conf/test36.conf
@@ -0,0 +1,12 @@
+<Macro warnings $u $n $u1 $n1 $u2 $n2>
+ Warning "many warnings! $u $u1 $u2"
+ # $n $n1 $n2
+</Macro>
+
+# warn about unused arguments
+Use warnings 1 2 3 4 5 6
+
+# may warn about empty arguments?
+Use warnings '' '' '' '' '' ''
+
+Error "done!"
diff --git a/modules/core/test/conf/test37.conf b/modules/core/test/conf/test37.conf
new file mode 100644
index 0000000..296dde7
--- /dev/null
+++ b/modules/core/test/conf/test37.conf
@@ -0,0 +1,7 @@
+# empty argument name
+
+<Macro stupid ''>
+ Warn "macro stupid"
+</Macro>
+
+Use stupid hello
diff --git a/modules/core/test/conf/test38.conf b/modules/core/test/conf/test38.conf
new file mode 100644
index 0000000..184763f
--- /dev/null
+++ b/modules/core/test/conf/test38.conf
@@ -0,0 +1,10 @@
+# ifmodule
+
+<IfModule mod_macro.c>
+Warning it is really a good idea to have mod_macro.c installed.
+</IfModule>
+
+<IfModule !mod_perl.c>
+Error it seems you do not have mod perl installed.
+</IfModule>
+
diff --git a/modules/core/test/conf/test39.conf b/modules/core/test/conf/test39.conf
new file mode 100644
index 0000000..792232f
--- /dev/null
+++ b/modules/core/test/conf/test39.conf
@@ -0,0 +1,23 @@
+# okay till stop.
+
+<IfModule mod_macro.c>
+ <Macro ModMacro>
+ Warning Thanks for using mod_macro!
+ </Macro>
+</IfModule>
+
+<IfModule !mod_macro.c>
+ <Macro ModMacro>
+ Error Sorry, mod_macro must be installed to run this configuration file.
+ </Macro>
+</IfModule>
+
+Use ModMacro
+
+<Macro foo>
+ Warning "macro foo"
+</Macro>
+
+Use foo
+
+Error "done!"
diff --git a/modules/core/test/conf/test40.conf b/modules/core/test/conf/test40.conf
new file mode 100644
index 0000000..e6b81f7
--- /dev/null
+++ b/modules/core/test/conf/test40.conf
@@ -0,0 +1,33 @@
+# configuration example with mod_macro
+#
+
+<VirtualHost www.foo.com>
+ DocumentRoot /foo/document/root/directory
+
+ <Macro SubDirAccessControl $subdir>
+ # access control to subdirs...
+ <Location /$subdir>
+ Warning "location /$subdir"
+ </Location>
+ </Macro>
+
+ # repeat uses
+ Use SubDirAccessControl A
+ Use SubDirAccessControl B
+ Use SubDirAccessControl C
+ Use SubDirAccessControl D
+ Use SubDirAccessControl E
+ Use SubDirAccessControl G
+ Use SubDirAccessControl H
+ Use SubDirAccessControl J
+ Use SubDirAccessControl K
+ Use SubDirAccessControl L
+ Use SubDirAccessControl M
+ Use SubDirAccessControl N
+
+ # cleanup
+ UndefMacro SubDirAccessControl
+
+</VirtualHost>
+
+Error Stop configuration file processing.
diff --git a/modules/core/test/conf/test41.conf b/modules/core/test/conf/test41.conf
new file mode 100644
index 0000000..c4e6bdb
--- /dev/null
+++ b/modules/core/test/conf/test41.conf
@@ -0,0 +1,20 @@
+# another configuration example without mod_macro
+
+<VirtualHost www.foo.com>
+ DocumentRoot /foo/document/root/directory
+
+ <Location /A>
+ Warning "location /A"
+ </Location>
+
+ <Location /B>
+ Warning "location /B"
+ </Location>
+
+ <Location /C>
+ Warning "location /C"
+ </Location>
+
+</VirtualHost>
+
+Error Stop configuration file processing.
diff --git a/modules/core/test/conf/test42.conf b/modules/core/test/conf/test42.conf
new file mode 100644
index 0000000..a142604
--- /dev/null
+++ b/modules/core/test/conf/test42.conf
@@ -0,0 +1,13 @@
+# multiple macro uses
+
+<Macro foo $p>
+ Warning "macro foo $p"
+</Macro>
+
+Use foo ''
+Use foo ''
+Use foo ''
+Use foo ''
+Use foo ''
+
+Error "done on line 13."
diff --git a/modules/core/test/conf/test43.conf b/modules/core/test/conf/test43.conf
new file mode 100644
index 0000000..264b916
--- /dev/null
+++ b/modules/core/test/conf/test43.conf
@@ -0,0 +1,29 @@
+# non necessarily nested.
+
+<Macro begindir $dir>
+<Directory $dir>
+# hello
+</Macro>
+
+<Macro enddir>
+</Directory>
+</Macro>
+
+Use begindir /unexpected/1
+Use enddir
+
+
+Use begindir /unexpected/2
+Use enddir
+
+Use begindir /unexpected/3
+<Limit GET>
+</Limit>
+Use enddir
+
+<VirtualHost foo.com>
+Use begindir /unexpected/4
+Use enddir
+</VirtualHost>
+
+Error ok!
diff --git a/modules/core/test/conf/test44.conf b/modules/core/test/conf/test44.conf
new file mode 100644
index 0000000..ef4883e
--- /dev/null
+++ b/modules/core/test/conf/test44.conf
@@ -0,0 +1,19 @@
+# working recursion...
+
+<Macro foo>
+use bla
+</Macro>
+
+<Macro bla>
+<IfDefine NoFoo>
+use foo
+</IfDefine>
+</Macro>
+
+
+<IfDefine !NoFoo>
+# foo gonna call bla, bla wont call foo back...
+use foo
+</IfDefine>
+
+Error okay.
diff --git a/modules/core/test/conf/test45.conf b/modules/core/test/conf/test45.conf
new file mode 100644
index 0000000..0394935
--- /dev/null
+++ b/modules/core/test/conf/test45.conf
@@ -0,0 +1,7 @@
+# strange chars
+
+<Macro warnings $1 %2 &3 @4 #5 ~6 *7 .8 ,9 !a -b +c =d :e ;f ?g>
+# hello $1 %2 &3 @4 #5 ~6 *7 .8 ,9 !a -b +c =d :e ;f ?g
+</Macro>
+
+Error "done on line 7."
diff --git a/modules/core/test/conf/test46.conf b/modules/core/test/conf/test46.conf
new file mode 100644
index 0000000..50520ed
--- /dev/null
+++ b/modules/core/test/conf/test46.conf
@@ -0,0 +1,11 @@
+# various working prefixes
+
+<Macro $i %j @k>
+# hello %j @k
+</Macro>
+
+<Macro warnings $i second>
+# not used.
+</Macro>
+
+Error okay.
diff --git a/modules/core/test/conf/test47.conf b/modules/core/test/conf/test47.conf
new file mode 100644
index 0000000..6e73664
--- /dev/null
+++ b/modules/core/test/conf/test47.conf
@@ -0,0 +1,15 @@
+# empty macro contents...
+
+<Macro foo>
+</Macro>
+
+Use foo
+
+<Macro bla $i>
+</Macro>
+
+<Macro bof $i>
+# some contents...
+</Macro>
+
+Error okay.
diff --git a/modules/core/test/conf/test48.conf b/modules/core/test/conf/test48.conf
new file mode 100644
index 0000000..96bf461
--- /dev/null
+++ b/modules/core/test/conf/test48.conf
@@ -0,0 +1,23 @@
+# test substitution...
+
+<Macro M %premier>
+Warning %premier
+</Macro>
+
+Use M 1
+Use M 12
+Use M 123
+Use M 1234
+Use M 12345
+Use M 123456
+Use M 1234567
+Use M 12345678
+Use M 123456789
+Use M 1234567890
+Use M 1234567890a
+Use M 1234567890ab
+Use M 1234567890abc
+Use M 1234567890abcd
+Use M 1234567890abcde
+
+Error "done line 23."
diff --git a/modules/core/test/conf/test49.conf b/modules/core/test/conf/test49.conf
new file mode 100644
index 0000000..7a21c82
--- /dev/null
+++ b/modules/core/test/conf/test49.conf
@@ -0,0 +1,2 @@
+# undef macro before anything
+UndefMacro foo
diff --git a/modules/core/test/conf/test50.conf b/modules/core/test/conf/test50.conf
new file mode 100644
index 0000000..33dd359
--- /dev/null
+++ b/modules/core/test/conf/test50.conf
@@ -0,0 +1,5 @@
+# undef non existing macro
+<Macro foo>
+ Warning "foo macro"
+</Macro>
+UndefMacro bla
diff --git a/modules/core/test/conf/test51.conf b/modules/core/test/conf/test51.conf
new file mode 100644
index 0000000..50214fa
--- /dev/null
+++ b/modules/core/test/conf/test51.conf
@@ -0,0 +1,9 @@
+# undef existing macro, and try to use it
+<Macro foo>
+ Warning "foo macro contents"
+</Macro>
+# expanded, but will not be processed because of error
+Use foo
+UndefMacro foo
+# error, does not exist anymore
+Use foo
diff --git a/modules/core/test/conf/test52.conf b/modules/core/test/conf/test52.conf
new file mode 100644
index 0000000..bb77c73
--- /dev/null
+++ b/modules/core/test/conf/test52.conf
@@ -0,0 +1,8 @@
+# undef existing macro, and try to use it
+<Macro foo>
+ Warning "foo macro contents line 1"
+</Macro>
+Use foo
+UndefMacro foo
+
+Error "done line 8."
diff --git a/modules/core/test/conf/test53.conf b/modules/core/test/conf/test53.conf
new file mode 100644
index 0000000..08e8c98
--- /dev/null
+++ b/modules/core/test/conf/test53.conf
@@ -0,0 +1,2 @@
+# use undefined macro without prior definition
+Use bla
diff --git a/modules/core/test/conf/test54.conf b/modules/core/test/conf/test54.conf
new file mode 100644
index 0000000..7dd30ac
--- /dev/null
+++ b/modules/core/test/conf/test54.conf
@@ -0,0 +1,6 @@
+# empty macro
+<Macro foo>
+</Macro>
+Use foo
+
+Error "done line 6."
diff --git a/modules/core/test/conf/test55.conf b/modules/core/test/conf/test55.conf
new file mode 100644
index 0000000..bd978e9
--- /dev/null
+++ b/modules/core/test/conf/test55.conf
@@ -0,0 +1,11 @@
+# line numbers...
+<Macro foo $where>
+ Warning "macro foo(:2) line 1 ($where)"
+</Macro>
+<Macro bla $where>
+ Warning "macro bla(:5) line 1 ($where)"
+ Use foo "bla line 2"
+</Macro>
+Use foo "file line 9"
+Use bla "file line 10"
+Error "done line 11."
diff --git a/modules/core/test/conf/test56.conf b/modules/core/test/conf/test56.conf
new file mode 100644
index 0000000..b7366a2
--- /dev/null
+++ b/modules/core/test/conf/test56.conf
@@ -0,0 +1,18 @@
+# nesting warnings
+<Macro Open $dir>
+ <Directory $dir>
+ Warning "Open:2 $dir"
+</Macro>
+<Macro Close>
+ Warning "Close:1"
+ </Directory>
+</Macro>
+
+# some uses
+Use Open /tmp
+Use Close
+
+Use Open /etc
+Use Close
+
+Error "done line 18."
diff --git a/modules/core/test/conf/test57.conf b/modules/core/test/conf/test57.conf
new file mode 100644
index 0000000..7c36868
--- /dev/null
+++ b/modules/core/test/conf/test57.conf
@@ -0,0 +1,4 @@
+# empty argument name
+<Macro foo $x ''>
+ Warning "macro foo line 1"
+</Macro>
diff --git a/modules/core/test/conf/test58.conf b/modules/core/test/conf/test58.conf
new file mode 100644
index 0000000..6c8a2eb
--- /dev/null
+++ b/modules/core/test/conf/test58.conf
@@ -0,0 +1,4 @@
+# bad directive closing
+<Macro foo
+ Warning "macro foo line 1"
+</Macro>
diff --git a/modules/core/test/conf/test59.conf b/modules/core/test/conf/test59.conf
new file mode 100644
index 0000000..9f43d7d
--- /dev/null
+++ b/modules/core/test/conf/test59.conf
@@ -0,0 +1,4 @@
+# empty name
+<Macro ''>
+ Warning "empty quoted name macro"
+</Macro>
diff --git a/modules/core/test/conf/test60.conf b/modules/core/test/conf/test60.conf
new file mode 100644
index 0000000..969a4eb
--- /dev/null
+++ b/modules/core/test/conf/test60.conf
@@ -0,0 +1,17 @@
+# @ escaping
+<Macro Foo $one $two>
+ Warning "macro Foo arg 1: $one"
+ Warning "macro Foo arg 2: $two"
+</Macro>
+<Macro Bla @first @second>
+ Warning Macro Bla arg 1: @first
+ Warning Macro Bla arg 2: @second
+ Use Foo @first 'second'
+ Use Foo 'first' @second
+ Use Foo @first @second
+</Macro>
+
+Use Foo hello world
+Use Bla "hello world" "thank you"
+
+Error "done on line 17."
diff --git a/modules/core/test/conf/test61.conf b/modules/core/test/conf/test61.conf
new file mode 100644
index 0000000..cd28597
--- /dev/null
+++ b/modules/core/test/conf/test61.conf
@@ -0,0 +1,18 @@
+# deep expansion
+<Macro F1 $x>
+ Warning "F1:1 x=$x"
+</Macro>
+<Macro F2 $x>
+ Warning "F2:1 x=$x"
+ Use F1 $x
+</Macro>
+<Macro F3 $x>
+ Warning "F3:1 x=$x"
+ Use F2 $x
+</Macro>
+<Macro F4 $x>
+ Warning "F4:1 x=$x"
+ Use F3 $x
+</Macro>
+Use F4 "line=17"
+Error "done line 18."
diff --git a/modules/core/test/conf/test62.conf b/modules/core/test/conf/test62.conf
new file mode 100644
index 0000000..9d611de
--- /dev/null
+++ b/modules/core/test/conf/test62.conf
@@ -0,0 +1,25 @@
+# test continuations
+<Macro Line \
+ $start \
+ $stop>
+ Warning \
+ "Line:1-2 start at $start"
+ Warning \
+ "Line:3-4 stop at $stop"
+</Macro>
+
+Use Line 11 11
+Use Line \
+ 12 13
+Use Line \
+ 14 \
+ 16
+Use Line 17 \
+ 18
+Use Line \
+ \
+ 19 \
+ \
+ 23
+
+Error "done line 25."
diff --git a/modules/core/test/conf/test63.conf b/modules/core/test/conf/test63.conf
new file mode 100644
index 0000000..7988ae4
--- /dev/null
+++ b/modules/core/test/conf/test63.conf
@@ -0,0 +1,9 @@
+# include
+include ${PWD}/inc63_1.conf
+Use Foo "test63.conf:3"
+<Macro Bla $where>
+ Warning "Bla at $where"
+</Macro>
+include ${PWD}/inc63_2.conf
+Use Bla "test63.conf:8"
+Error "done at line 9."
diff --git a/modules/core/test/conf/test64.conf b/modules/core/test/conf/test64.conf
new file mode 100644
index 0000000..6c12328
--- /dev/null
+++ b/modules/core/test/conf/test64.conf
@@ -0,0 +1,5 @@
+# just continuations
+Warning "on line 2"
+Warning \
+ "from line 3 to line 4"
+Error "done on line 5."
diff --git a/modules/core/test/conf/test65.conf b/modules/core/test/conf/test65.conf
new file mode 100644
index 0000000..df9adc3
--- /dev/null
+++ b/modules/core/test/conf/test65.conf
@@ -0,0 +1,11 @@
+# simple use continuation
+<Macro Line $line>
+ # first macro line is a comment
+ Warning "Line: $line"
+</Macro>
+Use Line \
+ "on line 6-7"
+Use \
+ Line \
+ "on line 8-10"
+Error "done on line 11."
diff --git a/modules/core/test/conf/test66.conf b/modules/core/test/conf/test66.conf
new file mode 100644
index 0000000..a14e587
--- /dev/null
+++ b/modules/core/test/conf/test66.conf
@@ -0,0 +1,7 @@
+# no double substitution
+<Macro Foo $x $y>
+ Warning "Foo: x=$x y=$y"
+</Macro>
+Use Foo X Y
+Use Foo "$y" "$x"
+Error "done on line 7."
diff --git a/modules/core/test/conf/test67.conf b/modules/core/test/conf/test67.conf
new file mode 100644
index 0000000..04a5d3d
--- /dev/null
+++ b/modules/core/test/conf/test67.conf
@@ -0,0 +1 @@
+Error "done at line 1 without LF." \ No newline at end of file
diff --git a/modules/core/test/conf/test68.conf b/modules/core/test/conf/test68.conf
new file mode 100644
index 0000000..2a7b85b
--- /dev/null
+++ b/modules/core/test/conf/test68.conf
@@ -0,0 +1,5 @@
+# two directives with continuations & no eol at eof
+Warning \
+ "line 2-3"
+Error \
+ "done on line 4-5." \ No newline at end of file
diff --git a/modules/core/test/conf/test69.conf b/modules/core/test/conf/test69.conf
new file mode 100644
index 0000000..11a0830
--- /dev/null
+++ b/modules/core/test/conf/test69.conf
@@ -0,0 +1,14 @@
+# warn if ignored non-blank stuff after closing '>'
+<Macro Foo> this stuff is ignored...
+ Warning "Foo"
+</Macro> this stuff is ignored as well...
+Use Foo
+<Macro Bla>
+ Warning "Bla"
+</Macro>
+Use Bla
+<Macro Comments> # comments are fine
+ Warning "Comments"
+</Macro> # comments are fine
+Use Comments
+Error "done on line 14."
diff --git a/modules/core/test/ref/test01.out b/modules/core/test/ref/test01.out
new file mode 100644
index 0000000..9ea6665
--- /dev/null
+++ b/modules/core/test/ref/test01.out
@@ -0,0 +1,3 @@
+# testing with conf/test01.conf
+httpd: Syntax error on line 2 of ./conf/test01.conf: <Macro macro definition: empty name
+# exit: 1
diff --git a/modules/core/test/ref/test02.out b/modules/core/test/ref/test02.out
new file mode 100644
index 0000000..6b49191
--- /dev/null
+++ b/modules/core/test/ref/test02.out
@@ -0,0 +1,3 @@
+# testing with conf/test02.conf
+httpd: Syntax error on line 2 of ./conf/test02.conf: <Macro macro definition: empty name
+# exit: 1
diff --git a/modules/core/test/ref/test03.out b/modules/core/test/ref/test03.out
new file mode 100644
index 0000000..f857901
--- /dev/null
+++ b/modules/core/test/ref/test03.out
@@ -0,0 +1,3 @@
+# testing with conf/test03.conf
+httpd: Syntax error on line 5 of ./conf/test03.conf: macro 'bla' undefined
+# exit: 1
diff --git a/modules/core/test/ref/test04.out b/modules/core/test/ref/test04.out
new file mode 100644
index 0000000..aaa2e6b
--- /dev/null
+++ b/modules/core/test/ref/test04.out
@@ -0,0 +1,3 @@
+# testing with conf/test04.conf
+httpd: Syntax error on line 5 of ./conf/test04.conf: macro 'foo' (defined on line 2 of "./conf/test04.conf") used with 1 arguments instead of 0
+# exit: 1
diff --git a/modules/core/test/ref/test05.out b/modules/core/test/ref/test05.out
new file mode 100644
index 0000000..184c40c
--- /dev/null
+++ b/modules/core/test/ref/test05.out
@@ -0,0 +1,3 @@
+# testing with conf/test05.conf
+httpd: Syntax error on line 5 of ./conf/test05.conf: macro 'foo' (defined on line 2 of "./conf/test05.conf") used with 0 arguments instead of 1
+# exit: 1
diff --git a/modules/core/test/ref/test06.out b/modules/core/test/ref/test06.out
new file mode 100644
index 0000000..221dd05
--- /dev/null
+++ b/modules/core/test/ref/test06.out
@@ -0,0 +1,3 @@
+# testing with conf/test06.conf
+httpd: Syntax error on line 5 of ./conf/test06.conf: macro 'foo' (defined on line 2 of "./conf/test06.conf") used with 2 arguments instead of 1
+# exit: 1
diff --git a/modules/core/test/ref/test07.out b/modules/core/test/ref/test07.out
new file mode 100644
index 0000000..60003ec
--- /dev/null
+++ b/modules/core/test/ref/test07.out
@@ -0,0 +1,3 @@
+# testing with conf/test07.conf
+httpd: Syntax error on line 2 of ./conf/test07.conf: macro 'foo' (defined on line 2 of "./conf/test07.conf")\n\tcontents error: expected token not found: </Macro>
+# exit: 1
diff --git a/modules/core/test/ref/test08.out b/modules/core/test/ref/test08.out
new file mode 100644
index 0000000..124c7a0
--- /dev/null
+++ b/modules/core/test/ref/test08.out
@@ -0,0 +1,3 @@
+# testing with conf/test08.conf
+httpd: Syntax error on line 3 of ./conf/test08.conf: </Macro> without matching <Macro> section
+# exit: 1
diff --git a/modules/core/test/ref/test09.out b/modules/core/test/ref/test09.out
new file mode 100644
index 0000000..9af1225
--- /dev/null
+++ b/modules/core/test/ref/test09.out
@@ -0,0 +1,3 @@
+# testing with conf/test09.conf
+httpd: Syntax error on line 1 of macro 'foo' (defined on line 2 of "./conf/test09.conf") used on line 6 of "./conf/test09.conf": recursive use of macro 'foo' is invalid
+# exit: 1
diff --git a/modules/core/test/ref/test10.out b/modules/core/test/ref/test10.out
new file mode 100644
index 0000000..4d81abc
--- /dev/null
+++ b/modules/core/test/ref/test10.out
@@ -0,0 +1,3 @@
+# testing with conf/test10.conf
+httpd: Syntax error on line 1 of macro 'bla' (defined on line 6 of "./conf/test10.conf") used on line 1 of "macro 'foo' (defined on line 2 of "./conf/test10.conf") used on line 10 of "./conf/test10.conf"": recursive use of macro 'foo' is invalid
+# exit: 1
diff --git a/modules/core/test/ref/test11.out b/modules/core/test/ref/test11.out
new file mode 100644
index 0000000..d18c95e
--- /dev/null
+++ b/modules/core/test/ref/test11.out
@@ -0,0 +1,6 @@
+# testing with conf/test11.conf
+[core:warn] macro one.in line 1 on line 1 of macro 'one.in' (defined on line 1 of "macro 'foo' (defined on line 2 of "./conf/test11.conf") used on line 9 of "./conf/test11.conf"") used on line 12 of "./conf/test11.conf"
+[core:error] done line 15. on line 15 of ./conf/test11.conf
+AH00526: Syntax error on line 15 of ./conf/test11.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test12.out b/modules/core/test/ref/test12.out
new file mode 100644
index 0000000..b1ab234
--- /dev/null
+++ b/modules/core/test/ref/test12.out
@@ -0,0 +1,7 @@
+# testing with conf/test12.conf
+[macro:warn] macro 'foo' multiply defined: defined on line 2 of "./conf/test12.conf", redefined on line 6 of "./conf/test12.conf"
+[core:warn] macro foo 2, line 1 on line 1 of macro 'foo' (defined on line 6 of "./conf/test12.conf") used on line 10 of "./conf/test12.conf"
+[core:error] done line 12. on line 12 of ./conf/test12.conf
+AH00526: Syntax error on line 12 of ./conf/test12.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test13.out b/modules/core/test/ref/test13.out
new file mode 100644
index 0000000..13d501e
--- /dev/null
+++ b/modules/core/test/ref/test13.out
@@ -0,0 +1,8 @@
+# testing with conf/test13.conf
+[macro:warn] macro 'foo' multiply defined: defined on line 2 of "./conf/test13.conf", redefined on line 12 of "./conf/test13.conf"
+[core:warn] macro FOO line 1 on line 1 of macro 'foo' (defined on line 2 of "./conf/test13.conf") used on line 10 of "./conf/test13.conf"
+[core:warn] redefined macro foo line 1 on line 1 of macro 'foo' (defined on line 12 of "./conf/test13.conf") used on line 16 of "./conf/test13.conf"
+[core:error] done line 18. on line 18 of ./conf/test13.conf
+AH00526: Syntax error on line 18 of ./conf/test13.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test14.out b/modules/core/test/ref/test14.out
new file mode 100644
index 0000000..1650715
--- /dev/null
+++ b/modules/core/test/ref/test14.out
@@ -0,0 +1,14 @@
+# testing with conf/test14.conf
+AH00112: Warning: DocumentRoot [/projects/apache/web] does not exist
+[core:warn] directory /projects/apache/web on line 5 of macro 'myvirtualhost' (defined on line 3 of "./conf/test14.conf") used on line 17 of "./conf/test14.conf"
+[core:warn] directory /projects/apache/web/intranet on line 8 of macro 'myvirtualhost' (defined on line 3 of "./conf/test14.conf") used on line 17 of "./conf/test14.conf"
+AH00112: Warning: DocumentRoot [/projects/perl/web] does not exist
+[core:warn] directory /projects/perl/web on line 5 of macro 'myvirtualhost' (defined on line 3 of "./conf/test14.conf") used on line 19 of "./conf/test14.conf"
+[core:warn] directory /projects/perl/web/intranet on line 8 of macro 'myvirtualhost' (defined on line 3 of "./conf/test14.conf") used on line 19 of "./conf/test14.conf"
+AH00112: Warning: DocumentRoot [/projects/mines/web] does not exist
+[core:warn] directory /projects/mines/web on line 5 of macro 'myvirtualhost' (defined on line 3 of "./conf/test14.conf") used on line 21 of "./conf/test14.conf"
+[core:warn] directory /projects/mines/web/intranet on line 8 of macro 'myvirtualhost' (defined on line 3 of "./conf/test14.conf") used on line 21 of "./conf/test14.conf"
+[core:error] done line 23. on line 23 of ./conf/test14.conf
+AH00526: Syntax error on line 23 of ./conf/test14.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test15.out b/modules/core/test/ref/test15.out
new file mode 100644
index 0000000..b0b82b7
--- /dev/null
+++ b/modules/core/test/ref/test15.out
@@ -0,0 +1,6 @@
+# testing with conf/test15.conf
+[macro:warn] bad cumulated nesting (+1) in macro 'test' (defined on line 2 of "./conf/test15.conf")
+[core:error] should not reach this point. on line 9 of ./conf/test15.conf
+AH00526: Syntax error on line 9 of ./conf/test15.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test16.out b/modules/core/test/ref/test16.out
new file mode 100644
index 0000000..6e0f9ca
--- /dev/null
+++ b/modules/core/test/ref/test16.out
@@ -0,0 +1,5 @@
+# testing with conf/test16.conf
+[macro:warn] bad (negative) nesting on line 2 of macro 'foo' (defined on line 3 of "./conf/test16.conf")
+[macro:warn] bad cumulated nesting (-1) in macro 'foo' (defined on line 3 of "./conf/test16.conf")
+httpd: Syntax error on line 9 of ./conf/test16.conf: </Limit> without matching <Limit> section
+# exit: 1
diff --git a/modules/core/test/ref/test17.out b/modules/core/test/ref/test17.out
new file mode 100644
index 0000000..c6ca16d
--- /dev/null
+++ b/modules/core/test/ref/test17.out
@@ -0,0 +1,7 @@
+# testing with conf/test17.conf
+[macro:warn] bad (negative) nesting on line 2 of macro 'foo' (defined on line 3 of "./conf/test17.conf")
+[macro:warn] bad cumulated nesting (-1) in macro 'foo' (defined on line 3 of "./conf/test17.conf")
+[core:error] done on line 10. on line 10 of ./conf/test17.conf
+AH00526: Syntax error on line 10 of ./conf/test17.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test18.out b/modules/core/test/ref/test18.out
new file mode 100644
index 0000000..c5cee81
--- /dev/null
+++ b/modules/core/test/ref/test18.out
@@ -0,0 +1,7 @@
+# testing with conf/test18.conf
+[macro:warn] bad (negative) nesting on line 2 of macro 'foo' (defined on line 3 of "./conf/test18.conf")
+[macro:warn] bad cumulated nesting (-1) in macro 'foo' (defined on line 3 of "./conf/test18.conf")
+[core:error] done on line 10. on line 10 of ./conf/test18.conf
+AH00526: Syntax error on line 10 of ./conf/test18.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test19.out b/modules/core/test/ref/test19.out
new file mode 100644
index 0000000..411e569
--- /dev/null
+++ b/modules/core/test/ref/test19.out
@@ -0,0 +1,9 @@
+# testing with conf/test19.conf
+[core:warn] macro foo line 2 in Directory on line 1 of macro 'foo' (defined on line 3 of "./conf/test19.conf") used on line 9 of "./conf/test19.conf"
+[core:warn] macro foo line 2 in Location on line 1 of macro 'foo' (defined on line 3 of "./conf/test19.conf") used on line 13 of "./conf/test19.conf"
+[core:warn] macro foo line 2 in VirtualHost on line 1 of macro 'foo' (defined on line 3 of "./conf/test19.conf") used on line 17 of "./conf/test19.conf"
+[core:warn] macro foo line 2 in VirtualHost & Directory on line 1 of macro 'foo' (defined on line 3 of "./conf/test19.conf") used on line 22 of "./conf/test19.conf"
+[core:error] done line 26. on line 26 of ./conf/test19.conf
+AH00526: Syntax error on line 26 of ./conf/test19.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test20.out b/modules/core/test/ref/test20.out
new file mode 100644
index 0000000..3ce2b60
--- /dev/null
+++ b/modules/core/test/ref/test20.out
@@ -0,0 +1,4 @@
+# testing with conf/test20.conf
+AH00526: Syntax error on line 1 of macro 'foo' (defined on line 3 of "./conf/test20.conf") used on line 10 of "./conf/test20.conf":
+<Directory not allowed here
+# exit: 1
diff --git a/modules/core/test/ref/test21.out b/modules/core/test/ref/test21.out
new file mode 100644
index 0000000..ac8d843
--- /dev/null
+++ b/modules/core/test/ref/test21.out
@@ -0,0 +1,5 @@
+# testing with conf/test21.conf
+[core:error] macro foo dir /tmp on line 2 of macro 'foo' (defined on line 3 of "./conf/test21.conf") used on line 10 of "./conf/test21.conf"
+AH00526: Syntax error on line 2 of macro 'foo' (defined on line 3 of "./conf/test21.conf") used on line 10 of "./conf/test21.conf":
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test22.out b/modules/core/test/ref/test22.out
new file mode 100644
index 0000000..def17dd
--- /dev/null
+++ b/modules/core/test/ref/test22.out
@@ -0,0 +1,6 @@
+# testing with conf/test22.conf
+[core:warn] macro foo on line 2 of macro 'foo' (defined on line 3 of "./conf/test22.conf") used on line 9 of "./conf/test22.conf"
+[core:error] done on line 11. on line 11 of ./conf/test22.conf
+AH00526: Syntax error on line 11 of ./conf/test22.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test23.out b/modules/core/test/ref/test23.out
new file mode 100644
index 0000000..827c861
--- /dev/null
+++ b/modules/core/test/ref/test23.out
@@ -0,0 +1,7 @@
+# testing with conf/test23.conf
+[core:warn] macro foo in /tmp on line 1 of macro 'foo' (defined on line 4 of "./conf/test23.conf") used on line 9 of "./conf/test23.conf"
+[core:warn] macro foo in /tmp on line 1 of macro 'foo' (defined on line 4 of "./conf/test23.conf") used on line 12 of "./conf/test23.conf"
+[core:error] done! on line 15 of ./conf/test23.conf
+AH00526: Syntax error on line 15 of ./conf/test23.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test24.out b/modules/core/test/ref/test24.out
new file mode 100644
index 0000000..e5d370d
--- /dev/null
+++ b/modules/core/test/ref/test24.out
@@ -0,0 +1,8 @@
+# testing with conf/test24.conf
+[core:warn] macro bla intra on line 2 of macro 'bla' (defined on line 3 of "./conf/test24.conf") used on line 13 of "./conf/test24.conf"
+[core:warn] macro bla private on line 5 of macro 'bla' (defined on line 3 of "./conf/test24.conf") used on line 13 of "./conf/test24.conf"
+[core:warn] macro bla intra on line 2 of macro 'bla' (defined on line 3 of "./conf/test24.conf") used on line 17 of "./conf/test24.conf"
+[core:warn] macro bla private on line 5 of macro 'bla' (defined on line 3 of "./conf/test24.conf") used on line 17 of "./conf/test24.conf"
+AH00526: Syntax error on line 1 of macro 'bla' (defined on line 3 of "./conf/test24.conf") used on line 22 of "./conf/test24.conf":
+<Location not allowed here
+# exit: 1
diff --git a/modules/core/test/ref/test25.out b/modules/core/test/ref/test25.out
new file mode 100644
index 0000000..56bb0bd
--- /dev/null
+++ b/modules/core/test/ref/test25.out
@@ -0,0 +1,9 @@
+# testing with conf/test25.conf
+[core:warn] restricted access policy 10.0.0.0/8 on line 1 of macro 'restrictedaccesspolicy' (defined on line 3 of "./conf/test25.conf") used on line 8 of "./conf/test25.conf"
+[core:warn] restricted access policy 192.54.172.0/24 192.54.148.0/24 10.0.0.0/8 on line 1 of macro 'restrictedaccesspolicy' (defined on line 3 of "./conf/test25.conf") used on line 16 of "./conf/test25.conf"
+[core:warn] restricted access policy 10.0.0.0/8 on line 1 of macro 'restrictedaccesspolicy' (defined on line 3 of "./conf/test25.conf") used on line 1 of "macro 'localaccessonly' (defined on line 11 of "./conf/test25.conf") used on line 20 of "./conf/test25.conf""
+[core:warn] restricted access policy 10.0.0.0/8 on line 1 of macro 'restrictedaccesspolicy' (defined on line 3 of "./conf/test25.conf") used on line 1 of "macro 'localaccessonly' (defined on line 11 of "./conf/test25.conf") used on line 24 of "./conf/test25.conf""
+[core:error] done line 27. on line 27 of ./conf/test25.conf
+AH00526: Syntax error on line 27 of ./conf/test25.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test26.out b/modules/core/test/ref/test26.out
new file mode 100644
index 0000000..28cba28
--- /dev/null
+++ b/modules/core/test/ref/test26.out
@@ -0,0 +1,11 @@
+# testing with conf/test26.conf
+[macro:warn] macro 'funny' (defined on line 4 of "./conf/test26.conf") argument name 'first arg' (#1) without expected prefix, better prefix argument names with one of '$%@'.
+[macro:warn] macro 'funny' (defined on line 4 of "./conf/test26.conf") argument name 'second ... arg' (#2) without expected prefix, better prefix argument names with one of '$%@'.
+[core:warn] funny directory on line 2 of macro 'funny' (defined on line 4 of "./conf/test26.conf") used on line 13 of "./conf/test26.conf"
+[core:warn] funny location on line 5 of macro 'funny' (defined on line 4 of "./conf/test26.conf") used on line 13 of "./conf/test26.conf"
+[core:warn] funny directory on line 2 of macro 'funny' (defined on line 4 of "./conf/test26.conf") used on line 16 of "./conf/test26.conf"
+[core:warn] funny location on line 5 of macro 'funny' (defined on line 4 of "./conf/test26.conf") used on line 16 of "./conf/test26.conf"
+[core:error] done! on line 19 of ./conf/test26.conf
+AH00526: Syntax error on line 19 of ./conf/test26.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test27.out b/modules/core/test/ref/test27.out
new file mode 100644
index 0000000..46aa8ea
--- /dev/null
+++ b/modules/core/test/ref/test27.out
@@ -0,0 +1,8 @@
+# testing with conf/test27.conf
+[core:warn] foo.one /unexpected/1 on line 2 of macro 'foo.one' (defined on line 1 of "macro 'foo' (defined on line 3 of "./conf/test27.conf") used on line 11 of "./conf/test27.conf"") used on line 14 of "./conf/test27.conf"
+[core:warn] foo.two /unexpected/2 on line 2 of macro 'foo.two' (defined on line 1 of "macro 'foo' (defined on line 3 of "./conf/test27.conf") used on line 12 of "./conf/test27.conf"") used on line 15 of "./conf/test27.conf"
+[core:warn] foo.one /unexpected/1 on line 2 of macro 'foo.one' (defined on line 1 of "macro 'foo' (defined on line 3 of "./conf/test27.conf") used on line 11 of "./conf/test27.conf"") used on line 16 of "./conf/test27.conf"
+[core:error] done! on line 22 of ./conf/test27.conf
+AH00526: Syntax error on line 22 of ./conf/test27.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test28.out b/modules/core/test/ref/test28.out
new file mode 100644
index 0000000..eb0a23f
--- /dev/null
+++ b/modules/core/test/ref/test28.out
@@ -0,0 +1,6 @@
+# testing with conf/test28.conf
+[core:warn] macro foo on line 1 of macro 'foo' (defined on line 4 of "./conf/test28.conf") used on line 8 of "./conf/test28.conf"
+[core:error] done! on line 10 of ./conf/test28.conf
+AH00526: Syntax error on line 10 of ./conf/test28.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test29.out b/modules/core/test/ref/test29.out
new file mode 100644
index 0000000..48488f9
--- /dev/null
+++ b/modules/core/test/ref/test29.out
@@ -0,0 +1,4 @@
+# testing with conf/test29.conf
+[macro:warn] macro 'toobigaline' (defined on line 3 of "./conf/test29.conf") argument name 'a' (#1) without expected prefix, better prefix argument names with one of '$%@'.
+httpd: Syntax error on line 8 of ./conf/test29.conf: macro 'toobigaline' (defined on line 3 of "./conf/test29.conf") used on line 8 of "./conf/test29.conf" error while substituting: while processing line 1 of macro 'toobigaline' (defined on line 3 of "./conf/test29.conf") cannot substitute, buffer size too small
+# exit: 1
diff --git a/modules/core/test/ref/test30.out b/modules/core/test/ref/test30.out
new file mode 100644
index 0000000..a84c69f
--- /dev/null
+++ b/modules/core/test/ref/test30.out
@@ -0,0 +1,7 @@
+# testing with conf/test30.conf
+[macro:warn] macro 'foo' (defined on line 4 of "./conf/test30.conf"): argument name prefix conflict ($dir #1 and $directive #2), be careful about your macro definition!
+[core:warn] section Directory /unexpected/1 on line 2 of macro 'foo' (defined on line 4 of "./conf/test30.conf") used on line 10 of "./conf/test30.conf"
+[core:error] done! on line 12 of ./conf/test30.conf
+AH00526: Syntax error on line 12 of ./conf/test30.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test31.out b/modules/core/test/ref/test31.out
new file mode 100644
index 0000000..831a784
--- /dev/null
+++ b/modules/core/test/ref/test31.out
@@ -0,0 +1,23 @@
+# testing with conf/test31.conf
+[macro:warn] macro 'bla' (defined on line 3 of "./conf/test31.conf"): argument name prefix conflict ($dir #1 and $di #2), be careful about your macro definition!
+[macro:warn] macro 'bla' (defined on line 3 of "./conf/test31.conf"): argument name prefix conflict ($dir #1 and $d #4), be careful about your macro definition!
+[macro:warn] macro 'bla' (defined on line 3 of "./conf/test31.conf"): argument name prefix conflict ($di #2 and $d #4), be careful about your macro definition!
+[macro:warn] macro 'bla' (defined on line 3 of "./conf/test31.conf"): argument name prefix conflict ($dd #3 and $d #4), be careful about your macro definition!
+[macro:warn] macro 'bla' (defined on line 3 of "./conf/test31.conf") used on line 8 of "./conf/test31.conf": empty argument #1
+[macro:warn] macro 'bla' (defined on line 3 of "./conf/test31.conf") used on line 8 of "./conf/test31.conf": empty argument #2
+[macro:warn] macro 'foo' (defined on line 10 of "./conf/test31.conf"): argument name prefix conflict ($d #1 and $di #2), be careful about your macro definition!
+[macro:warn] macro 'foo' (defined on line 10 of "./conf/test31.conf"): argument name prefix conflict ($d #1 and $dir #3), be careful about your macro definition!
+[macro:warn] macro 'foo' (defined on line 10 of "./conf/test31.conf"): argument name prefix conflict ($d #1 and $dd #4), be careful about your macro definition!
+[macro:warn] macro 'foo' (defined on line 10 of "./conf/test31.conf"): argument name prefix conflict ($di #2 and $dir #3), be careful about your macro definition!
+[macro:warn] macro 'foo' (defined on line 10 of "./conf/test31.conf"): argument '$d' (#1) never used
+[macro:warn] macro 'foo' (defined on line 10 of "./conf/test31.conf"): argument '$di' (#2) never used
+[macro:warn] macro 'foo' (defined on line 10 of "./conf/test31.conf"): argument '$dir' (#3) never used
+[macro:warn] macro 'foo' (defined on line 10 of "./conf/test31.conf"): argument '$dd' (#4) never used
+[macro:warn] macro 'foo' (defined on line 10 of "./conf/test31.conf") used on line 14 of "./conf/test31.conf": empty argument #1
+[macro:warn] macro 'foo' (defined on line 10 of "./conf/test31.conf") used on line 14 of "./conf/test31.conf": empty argument #2
+[core:warn] argument name conflicts on line 1 of macro 'bla' (defined on line 3 of "./conf/test31.conf") used on line 8 of "./conf/test31.conf"
+[core:warn] conflicts, but arguments are not used on line 1 of macro 'foo' (defined on line 10 of "./conf/test31.conf") used on line 14 of "./conf/test31.conf"
+[core:error] done on line 16. on line 16 of ./conf/test31.conf
+AH00526: Syntax error on line 16 of ./conf/test31.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test32.out b/modules/core/test/ref/test32.out
new file mode 100644
index 0000000..b1620af
--- /dev/null
+++ b/modules/core/test/ref/test32.out
@@ -0,0 +1,3 @@
+# testing with conf/test32.conf
+httpd: Syntax error on line 3 of ./conf/test32.conf: argument name conflict in macro 'foo' (defined on line 3 of "./conf/test32.conf"): argument '$arg2': #2 and #4, change argument names!
+# exit: 1
diff --git a/modules/core/test/ref/test33.out b/modules/core/test/ref/test33.out
new file mode 100644
index 0000000..cc5b3d0
--- /dev/null
+++ b/modules/core/test/ref/test33.out
@@ -0,0 +1,3 @@
+# testing with conf/test33.conf
+httpd: Syntax error on line 3 of ./conf/test33.conf: no macro defined before Use
+# exit: 1
diff --git a/modules/core/test/ref/test34.out b/modules/core/test/ref/test34.out
new file mode 100644
index 0000000..1264a54
--- /dev/null
+++ b/modules/core/test/ref/test34.out
@@ -0,0 +1,13 @@
+# testing with conf/test34.conf
+[macro:warn] macro 'foo' (defined on line 3 of "./conf/test34.conf"): argument name prefix conflict ($d #1 and $dd #2), be careful about your macro definition!
+[macro:warn] macro 'foo' (defined on line 3 of "./conf/test34.conf"): argument '$d' (#1) never used
+[macro:warn] macro 'foo' (defined on line 3 of "./conf/test34.conf"): argument '$dd' (#2) never used
+[macro:warn] macro 'bla' (defined on line 7 of "./conf/test34.conf"): argument name prefix conflict ($dd #1 and $d #2), be careful about your macro definition!
+[macro:warn] macro 'bla' (defined on line 7 of "./conf/test34.conf"): argument '$dd' (#1) never used
+[macro:warn] macro 'bla' (defined on line 7 of "./conf/test34.conf"): argument '$d' (#2) never used
+[core:warn] macro foo conflict one on line 1 of macro 'foo' (defined on line 3 of "./conf/test34.conf") used on line 11 of "./conf/test34.conf"
+[core:warn] macro bla conflict two on line 1 of macro 'bla' (defined on line 7 of "./conf/test34.conf") used on line 12 of "./conf/test34.conf"
+[core:error] done on line 14. on line 14 of ./conf/test34.conf
+AH00526: Syntax error on line 14 of ./conf/test34.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test35.out b/modules/core/test/ref/test35.out
new file mode 100644
index 0000000..fb56238
--- /dev/null
+++ b/modules/core/test/ref/test35.out
@@ -0,0 +1,13 @@
+# testing with conf/test35.conf
+[macro:warn] macro 'warnings' (defined on line 3 of "./conf/test35.conf") argument name 'u1' (#1) without expected prefix, better prefix argument names with one of '$%@'.
+[macro:warn] macro 'warnings' (defined on line 3 of "./conf/test35.conf") argument name 'u2' (#2) without expected prefix, better prefix argument names with one of '$%@'.
+[macro:warn] macro 'warnings' (defined on line 3 of "./conf/test35.conf") argument name 'n1' (#3) without expected prefix, better prefix argument names with one of '$%@'.
+[macro:warn] macro 'warnings' (defined on line 3 of "./conf/test35.conf") argument name 'n2' (#4) without expected prefix, better prefix argument names with one of '$%@'.
+[macro:warn] macro 'warnings' (defined on line 3 of "./conf/test35.conf") argument name 'u3' (#5) without expected prefix, better prefix argument names with one of '$%@'.
+[macro:warn] macro 'warnings' (defined on line 3 of "./conf/test35.conf"): argument 'n1' (#3) never used
+[macro:warn] macro 'warnings' (defined on line 3 of "./conf/test35.conf"): argument 'n2' (#4) never used
+[core:warn] macro cannot be used just within a comment 1 2 5 on line 1 of macro 'warnings' (defined on line 3 of "./conf/test35.conf") used on line 8 of "./conf/test35.conf"
+[core:error] done on line 10. on line 10 of ./conf/test35.conf
+AH00526: Syntax error on line 10 of ./conf/test35.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test36.out b/modules/core/test/ref/test36.out
new file mode 100644
index 0000000..4c627d7
--- /dev/null
+++ b/modules/core/test/ref/test36.out
@@ -0,0 +1,20 @@
+# testing with conf/test36.conf
+[macro:warn] macro 'warnings' (defined on line 1 of "./conf/test36.conf"): argument name prefix conflict ($u #1 and $u1 #3), be careful about your macro definition!
+[macro:warn] macro 'warnings' (defined on line 1 of "./conf/test36.conf"): argument name prefix conflict ($u #1 and $u2 #5), be careful about your macro definition!
+[macro:warn] macro 'warnings' (defined on line 1 of "./conf/test36.conf"): argument name prefix conflict ($n #2 and $n1 #4), be careful about your macro definition!
+[macro:warn] macro 'warnings' (defined on line 1 of "./conf/test36.conf"): argument name prefix conflict ($n #2 and $n2 #6), be careful about your macro definition!
+[macro:warn] macro 'warnings' (defined on line 1 of "./conf/test36.conf"): argument '$n' (#2) never used
+[macro:warn] macro 'warnings' (defined on line 1 of "./conf/test36.conf"): argument '$n1' (#4) never used
+[macro:warn] macro 'warnings' (defined on line 1 of "./conf/test36.conf"): argument '$n2' (#6) never used
+[macro:warn] macro 'warnings' (defined on line 1 of "./conf/test36.conf") used on line 10 of "./conf/test36.conf": empty argument #1
+[macro:warn] macro 'warnings' (defined on line 1 of "./conf/test36.conf") used on line 10 of "./conf/test36.conf": empty argument #2
+[macro:warn] macro 'warnings' (defined on line 1 of "./conf/test36.conf") used on line 10 of "./conf/test36.conf": empty argument #3
+[macro:warn] macro 'warnings' (defined on line 1 of "./conf/test36.conf") used on line 10 of "./conf/test36.conf": empty argument #4
+[macro:warn] macro 'warnings' (defined on line 1 of "./conf/test36.conf") used on line 10 of "./conf/test36.conf": empty argument #5
+[macro:warn] macro 'warnings' (defined on line 1 of "./conf/test36.conf") used on line 10 of "./conf/test36.conf": empty argument #6
+[core:warn] many warnings! 1 3 5 on line 1 of macro 'warnings' (defined on line 1 of "./conf/test36.conf") used on line 7 of "./conf/test36.conf"
+[core:warn] many warnings! on line 1 of macro 'warnings' (defined on line 1 of "./conf/test36.conf") used on line 10 of "./conf/test36.conf"
+[core:error] done! on line 12 of ./conf/test36.conf
+AH00526: Syntax error on line 12 of ./conf/test36.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test37.out b/modules/core/test/ref/test37.out
new file mode 100644
index 0000000..fc119c8
--- /dev/null
+++ b/modules/core/test/ref/test37.out
@@ -0,0 +1,3 @@
+# testing with conf/test37.conf
+httpd: Syntax error on line 3 of ./conf/test37.conf: macro 'stupid' (defined on line 3 of "./conf/test37.conf"): empty argument #1 name
+# exit: 1
diff --git a/modules/core/test/ref/test38.out b/modules/core/test/ref/test38.out
new file mode 100644
index 0000000..ed3f1bb
--- /dev/null
+++ b/modules/core/test/ref/test38.out
@@ -0,0 +1,6 @@
+# testing with conf/test38.conf
+[core:warn] it is really a good idea to have mod_macro.c installed. on line 4 of ./conf/test38.conf
+[core:error] it seems you do not have mod perl installed. on line 8 of ./conf/test38.conf
+AH00526: Syntax error on line 8 of ./conf/test38.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test39.out b/modules/core/test/ref/test39.out
new file mode 100644
index 0000000..ea2cede
--- /dev/null
+++ b/modules/core/test/ref/test39.out
@@ -0,0 +1,7 @@
+# testing with conf/test39.conf
+[core:warn] Thanks for using mod_macro! on line 1 of macro 'modmacro' (defined on line 4 of "./conf/test39.conf") used on line 15 of "./conf/test39.conf"
+[core:warn] macro foo on line 1 of macro 'foo' (defined on line 17 of "./conf/test39.conf") used on line 21 of "./conf/test39.conf"
+[core:error] done! on line 23 of ./conf/test39.conf
+AH00526: Syntax error on line 23 of ./conf/test39.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test40.out b/modules/core/test/ref/test40.out
new file mode 100644
index 0000000..e3c5971
--- /dev/null
+++ b/modules/core/test/ref/test40.out
@@ -0,0 +1,18 @@
+# testing with conf/test40.conf
+AH00112: Warning: DocumentRoot [/foo/document/root/directory] does not exist
+[core:warn] location /A on line 2 of macro 'subdiraccesscontrol' (defined on line 7 of "./conf/test40.conf") used on line 15 of "./conf/test40.conf"
+[core:warn] location /B on line 2 of macro 'subdiraccesscontrol' (defined on line 7 of "./conf/test40.conf") used on line 16 of "./conf/test40.conf"
+[core:warn] location /C on line 2 of macro 'subdiraccesscontrol' (defined on line 7 of "./conf/test40.conf") used on line 17 of "./conf/test40.conf"
+[core:warn] location /D on line 2 of macro 'subdiraccesscontrol' (defined on line 7 of "./conf/test40.conf") used on line 18 of "./conf/test40.conf"
+[core:warn] location /E on line 2 of macro 'subdiraccesscontrol' (defined on line 7 of "./conf/test40.conf") used on line 19 of "./conf/test40.conf"
+[core:warn] location /G on line 2 of macro 'subdiraccesscontrol' (defined on line 7 of "./conf/test40.conf") used on line 20 of "./conf/test40.conf"
+[core:warn] location /H on line 2 of macro 'subdiraccesscontrol' (defined on line 7 of "./conf/test40.conf") used on line 21 of "./conf/test40.conf"
+[core:warn] location /J on line 2 of macro 'subdiraccesscontrol' (defined on line 7 of "./conf/test40.conf") used on line 22 of "./conf/test40.conf"
+[core:warn] location /K on line 2 of macro 'subdiraccesscontrol' (defined on line 7 of "./conf/test40.conf") used on line 23 of "./conf/test40.conf"
+[core:warn] location /L on line 2 of macro 'subdiraccesscontrol' (defined on line 7 of "./conf/test40.conf") used on line 24 of "./conf/test40.conf"
+[core:warn] location /M on line 2 of macro 'subdiraccesscontrol' (defined on line 7 of "./conf/test40.conf") used on line 25 of "./conf/test40.conf"
+[core:warn] location /N on line 2 of macro 'subdiraccesscontrol' (defined on line 7 of "./conf/test40.conf") used on line 26 of "./conf/test40.conf"
+[core:error] Stop configuration file processing. on line 33 of ./conf/test40.conf
+AH00526: Syntax error on line 33 of ./conf/test40.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test41.out b/modules/core/test/ref/test41.out
new file mode 100644
index 0000000..3bef285
--- /dev/null
+++ b/modules/core/test/ref/test41.out
@@ -0,0 +1,9 @@
+# testing with conf/test41.conf
+AH00112: Warning: DocumentRoot [/foo/document/root/directory] does not exist
+[core:warn] location /A on line 7 of ./conf/test41.conf
+[core:warn] location /B on line 11 of ./conf/test41.conf
+[core:warn] location /C on line 15 of ./conf/test41.conf
+[core:error] Stop configuration file processing. on line 20 of ./conf/test41.conf
+AH00526: Syntax error on line 20 of ./conf/test41.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test42.out b/modules/core/test/ref/test42.out
new file mode 100644
index 0000000..bab1510
--- /dev/null
+++ b/modules/core/test/ref/test42.out
@@ -0,0 +1,15 @@
+# testing with conf/test42.conf
+[macro:warn] macro 'foo' (defined on line 3 of "./conf/test42.conf") used on line 7 of "./conf/test42.conf": empty argument #1
+[macro:warn] macro 'foo' (defined on line 3 of "./conf/test42.conf") used on line 8 of "./conf/test42.conf": empty argument #1
+[macro:warn] macro 'foo' (defined on line 3 of "./conf/test42.conf") used on line 9 of "./conf/test42.conf": empty argument #1
+[macro:warn] macro 'foo' (defined on line 3 of "./conf/test42.conf") used on line 10 of "./conf/test42.conf": empty argument #1
+[macro:warn] macro 'foo' (defined on line 3 of "./conf/test42.conf") used on line 11 of "./conf/test42.conf": empty argument #1
+[core:warn] macro foo on line 1 of macro 'foo' (defined on line 3 of "./conf/test42.conf") used on line 7 of "./conf/test42.conf"
+[core:warn] macro foo on line 1 of macro 'foo' (defined on line 3 of "./conf/test42.conf") used on line 8 of "./conf/test42.conf"
+[core:warn] macro foo on line 1 of macro 'foo' (defined on line 3 of "./conf/test42.conf") used on line 9 of "./conf/test42.conf"
+[core:warn] macro foo on line 1 of macro 'foo' (defined on line 3 of "./conf/test42.conf") used on line 10 of "./conf/test42.conf"
+[core:warn] macro foo on line 1 of macro 'foo' (defined on line 3 of "./conf/test42.conf") used on line 11 of "./conf/test42.conf"
+[core:error] done on line 13. on line 13 of ./conf/test42.conf
+AH00526: Syntax error on line 13 of ./conf/test42.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test43.out b/modules/core/test/ref/test43.out
new file mode 100644
index 0000000..04a72d8
--- /dev/null
+++ b/modules/core/test/ref/test43.out
@@ -0,0 +1,8 @@
+# testing with conf/test43.conf
+[macro:warn] bad cumulated nesting (+1) in macro 'begindir' (defined on line 3 of "./conf/test43.conf")
+[macro:warn] bad (negative) nesting on line 2 of macro 'enddir' (defined on line 8 of "./conf/test43.conf")
+[macro:warn] bad cumulated nesting (-1) in macro 'enddir' (defined on line 8 of "./conf/test43.conf")
+[core:error] ok! on line 29 of ./conf/test43.conf
+AH00526: Syntax error on line 29 of ./conf/test43.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test44.out b/modules/core/test/ref/test44.out
new file mode 100644
index 0000000..e744cda
--- /dev/null
+++ b/modules/core/test/ref/test44.out
@@ -0,0 +1,5 @@
+# testing with conf/test44.conf
+[core:error] okay. on line 19 of ./conf/test44.conf
+AH00526: Syntax error on line 19 of ./conf/test44.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test45.out b/modules/core/test/ref/test45.out
new file mode 100644
index 0000000..daadaac
--- /dev/null
+++ b/modules/core/test/ref/test45.out
@@ -0,0 +1,19 @@
+# testing with conf/test45.conf
+[macro:warn] macro 'warnings' (defined on line 3 of "./conf/test45.conf") argument name '&3' (#3) without expected prefix, better prefix argument names with one of '$%@'.
+[macro:warn] macro 'warnings' (defined on line 3 of "./conf/test45.conf") argument name '#5' (#5) without expected prefix, better prefix argument names with one of '$%@'.
+[macro:warn] macro 'warnings' (defined on line 3 of "./conf/test45.conf") argument name '~6' (#6) without expected prefix, better prefix argument names with one of '$%@'.
+[macro:warn] macro 'warnings' (defined on line 3 of "./conf/test45.conf") argument name '*7' (#7) without expected prefix, better prefix argument names with one of '$%@'.
+[macro:warn] macro 'warnings' (defined on line 3 of "./conf/test45.conf") argument name '.8' (#8) without expected prefix, better prefix argument names with one of '$%@'.
+[macro:warn] macro 'warnings' (defined on line 3 of "./conf/test45.conf") argument name ',9' (#9) without expected prefix, better prefix argument names with one of '$%@'.
+[macro:warn] macro 'warnings' (defined on line 3 of "./conf/test45.conf") argument name '!a' (#10) without expected prefix, better prefix argument names with one of '$%@'.
+[macro:warn] macro 'warnings' (defined on line 3 of "./conf/test45.conf") argument name '-b' (#11) without expected prefix, better prefix argument names with one of '$%@'.
+[macro:warn] macro 'warnings' (defined on line 3 of "./conf/test45.conf") argument name '+c' (#12) without expected prefix, better prefix argument names with one of '$%@'.
+[macro:warn] macro 'warnings' (defined on line 3 of "./conf/test45.conf") argument name '=d' (#13) without expected prefix, better prefix argument names with one of '$%@'.
+[macro:warn] macro 'warnings' (defined on line 3 of "./conf/test45.conf") argument name ':e' (#14) without expected prefix, better prefix argument names with one of '$%@'.
+[macro:warn] macro 'warnings' (defined on line 3 of "./conf/test45.conf") argument name ';f' (#15) without expected prefix, better prefix argument names with one of '$%@'.
+[macro:warn] macro 'warnings' (defined on line 3 of "./conf/test45.conf") argument name '?g' (#16) without expected prefix, better prefix argument names with one of '$%@'.
+[macro:warn] macro 'warnings' (defined on line 3 of "./conf/test45.conf"): empty contents!
+[core:error] done on line 7. on line 7 of ./conf/test45.conf
+AH00526: Syntax error on line 7 of ./conf/test45.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test46.out b/modules/core/test/ref/test46.out
new file mode 100644
index 0000000..8059744
--- /dev/null
+++ b/modules/core/test/ref/test46.out
@@ -0,0 +1,9 @@
+# testing with conf/test46.conf
+[macro:warn] macro '$i' (defined on line 3 of "./conf/test46.conf") better prefix a macro name with any of '$%@'
+[macro:warn] macro '$i' (defined on line 3 of "./conf/test46.conf"): empty contents!
+[macro:warn] macro 'warnings' (defined on line 7 of "./conf/test46.conf") argument name 'second' (#2) without expected prefix, better prefix argument names with one of '$%@'.
+[macro:warn] macro 'warnings' (defined on line 7 of "./conf/test46.conf"): empty contents!
+[core:error] okay. on line 11 of ./conf/test46.conf
+AH00526: Syntax error on line 11 of ./conf/test46.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test47.out b/modules/core/test/ref/test47.out
new file mode 100644
index 0000000..58fb3cc
--- /dev/null
+++ b/modules/core/test/ref/test47.out
@@ -0,0 +1,8 @@
+# testing with conf/test47.conf
+[macro:warn] macro 'foo' (defined on line 3 of "./conf/test47.conf"): empty contents!
+[macro:warn] macro 'bla' (defined on line 8 of "./conf/test47.conf"): empty contents!
+[macro:warn] macro 'bof' (defined on line 11 of "./conf/test47.conf"): empty contents!
+[core:error] okay. on line 15 of ./conf/test47.conf
+AH00526: Syntax error on line 15 of ./conf/test47.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test48.out b/modules/core/test/ref/test48.out
new file mode 100644
index 0000000..6ac5e99
--- /dev/null
+++ b/modules/core/test/ref/test48.out
@@ -0,0 +1,20 @@
+# testing with conf/test48.conf
+[core:warn] 1 on line 1 of macro 'm' (defined on line 3 of "./conf/test48.conf") used on line 7 of "./conf/test48.conf"
+[core:warn] 12 on line 1 of macro 'm' (defined on line 3 of "./conf/test48.conf") used on line 8 of "./conf/test48.conf"
+[core:warn] 123 on line 1 of macro 'm' (defined on line 3 of "./conf/test48.conf") used on line 9 of "./conf/test48.conf"
+[core:warn] 1234 on line 1 of macro 'm' (defined on line 3 of "./conf/test48.conf") used on line 10 of "./conf/test48.conf"
+[core:warn] 12345 on line 1 of macro 'm' (defined on line 3 of "./conf/test48.conf") used on line 11 of "./conf/test48.conf"
+[core:warn] 123456 on line 1 of macro 'm' (defined on line 3 of "./conf/test48.conf") used on line 12 of "./conf/test48.conf"
+[core:warn] 1234567 on line 1 of macro 'm' (defined on line 3 of "./conf/test48.conf") used on line 13 of "./conf/test48.conf"
+[core:warn] 12345678 on line 1 of macro 'm' (defined on line 3 of "./conf/test48.conf") used on line 14 of "./conf/test48.conf"
+[core:warn] 123456789 on line 1 of macro 'm' (defined on line 3 of "./conf/test48.conf") used on line 15 of "./conf/test48.conf"
+[core:warn] 1234567890 on line 1 of macro 'm' (defined on line 3 of "./conf/test48.conf") used on line 16 of "./conf/test48.conf"
+[core:warn] 1234567890a on line 1 of macro 'm' (defined on line 3 of "./conf/test48.conf") used on line 17 of "./conf/test48.conf"
+[core:warn] 1234567890ab on line 1 of macro 'm' (defined on line 3 of "./conf/test48.conf") used on line 18 of "./conf/test48.conf"
+[core:warn] 1234567890abc on line 1 of macro 'm' (defined on line 3 of "./conf/test48.conf") used on line 19 of "./conf/test48.conf"
+[core:warn] 1234567890abcd on line 1 of macro 'm' (defined on line 3 of "./conf/test48.conf") used on line 20 of "./conf/test48.conf"
+[core:warn] 1234567890abcde on line 1 of macro 'm' (defined on line 3 of "./conf/test48.conf") used on line 21 of "./conf/test48.conf"
+[core:error] done line 23. on line 23 of ./conf/test48.conf
+AH00526: Syntax error on line 23 of ./conf/test48.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test49.out b/modules/core/test/ref/test49.out
new file mode 100644
index 0000000..5e83e02
--- /dev/null
+++ b/modules/core/test/ref/test49.out
@@ -0,0 +1,3 @@
+# testing with conf/test49.conf
+httpd: Syntax error on line 2 of ./conf/test49.conf: no macro defined before UndefMacro
+# exit: 1
diff --git a/modules/core/test/ref/test50.out b/modules/core/test/ref/test50.out
new file mode 100644
index 0000000..477e854
--- /dev/null
+++ b/modules/core/test/ref/test50.out
@@ -0,0 +1,3 @@
+# testing with conf/test50.conf
+httpd: Syntax error on line 5 of ./conf/test50.conf: cannot remove undefined macro 'bla'
+# exit: 1
diff --git a/modules/core/test/ref/test51.out b/modules/core/test/ref/test51.out
new file mode 100644
index 0000000..be9cc17
--- /dev/null
+++ b/modules/core/test/ref/test51.out
@@ -0,0 +1,3 @@
+# testing with conf/test51.conf
+httpd: Syntax error on line 9 of ./conf/test51.conf: macro 'foo' undefined
+# exit: 1
diff --git a/modules/core/test/ref/test52.out b/modules/core/test/ref/test52.out
new file mode 100644
index 0000000..f41b7d6
--- /dev/null
+++ b/modules/core/test/ref/test52.out
@@ -0,0 +1,6 @@
+# testing with conf/test52.conf
+[core:warn] foo macro contents line 1 on line 1 of macro 'foo' (defined on line 2 of "./conf/test52.conf") used on line 5 of "./conf/test52.conf"
+[core:error] done line 8. on line 8 of ./conf/test52.conf
+AH00526: Syntax error on line 8 of ./conf/test52.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test53.out b/modules/core/test/ref/test53.out
new file mode 100644
index 0000000..2fb3852
--- /dev/null
+++ b/modules/core/test/ref/test53.out
@@ -0,0 +1,3 @@
+# testing with conf/test53.conf
+httpd: Syntax error on line 2 of ./conf/test53.conf: no macro defined before Use
+# exit: 1
diff --git a/modules/core/test/ref/test54.out b/modules/core/test/ref/test54.out
new file mode 100644
index 0000000..814b491
--- /dev/null
+++ b/modules/core/test/ref/test54.out
@@ -0,0 +1,6 @@
+# testing with conf/test54.conf
+[macro:warn] macro 'foo' (defined on line 2 of "./conf/test54.conf"): empty contents!
+[core:error] done line 6. on line 6 of ./conf/test54.conf
+AH00526: Syntax error on line 6 of ./conf/test54.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test55.out b/modules/core/test/ref/test55.out
new file mode 100644
index 0000000..c3590f9
--- /dev/null
+++ b/modules/core/test/ref/test55.out
@@ -0,0 +1,8 @@
+# testing with conf/test55.conf
+[core:warn] macro foo(:2) line 1 (file line 9) on line 1 of macro 'foo' (defined on line 2 of "./conf/test55.conf") used on line 9 of "./conf/test55.conf"
+[core:warn] macro bla(:5) line 1 (file line 10) on line 1 of macro 'bla' (defined on line 5 of "./conf/test55.conf") used on line 10 of "./conf/test55.conf"
+[core:warn] macro foo(:2) line 1 (bla line 2) on line 1 of macro 'foo' (defined on line 2 of "./conf/test55.conf") used on line 2 of "macro 'bla' (defined on line 5 of "./conf/test55.conf") used on line 10 of "./conf/test55.conf""
+[core:error] done line 11. on line 11 of ./conf/test55.conf
+AH00526: Syntax error on line 11 of ./conf/test55.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test56.out b/modules/core/test/ref/test56.out
new file mode 100644
index 0000000..f2a0b6e
--- /dev/null
+++ b/modules/core/test/ref/test56.out
@@ -0,0 +1,12 @@
+# testing with conf/test56.conf
+[macro:warn] bad cumulated nesting (+1) in macro 'open' (defined on line 2 of "./conf/test56.conf")
+[macro:warn] bad (negative) nesting on line 3 of macro 'close' (defined on line 6 of "./conf/test56.conf")
+[macro:warn] bad cumulated nesting (-1) in macro 'close' (defined on line 6 of "./conf/test56.conf")
+[core:warn] Open:2 /tmp on line 2 of macro 'open' (defined on line 2 of "./conf/test56.conf") used on line 12 of "./conf/test56.conf"
+[core:warn] Close:1 on line 1 of macro 'close' (defined on line 6 of "./conf/test56.conf") used on line 13 of "./conf/test56.conf"
+[core:warn] Open:2 /etc on line 2 of macro 'open' (defined on line 2 of "./conf/test56.conf") used on line 15 of "./conf/test56.conf"
+[core:warn] Close:1 on line 1 of macro 'close' (defined on line 6 of "./conf/test56.conf") used on line 16 of "./conf/test56.conf"
+[core:error] done line 18. on line 18 of ./conf/test56.conf
+AH00526: Syntax error on line 18 of ./conf/test56.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test57.out b/modules/core/test/ref/test57.out
new file mode 100644
index 0000000..77a1901
--- /dev/null
+++ b/modules/core/test/ref/test57.out
@@ -0,0 +1,3 @@
+# testing with conf/test57.conf
+httpd: Syntax error on line 2 of ./conf/test57.conf: macro 'foo' (defined on line 2 of "./conf/test57.conf"): empty argument #2 name
+# exit: 1
diff --git a/modules/core/test/ref/test58.out b/modules/core/test/ref/test58.out
new file mode 100644
index 0000000..0ece028
--- /dev/null
+++ b/modules/core/test/ref/test58.out
@@ -0,0 +1,3 @@
+# testing with conf/test58.conf
+httpd: Syntax error on line 2 of ./conf/test58.conf: <Macro> directive missing closing '>'
+# exit: 1
diff --git a/modules/core/test/ref/test59.out b/modules/core/test/ref/test59.out
new file mode 100644
index 0000000..7895917
--- /dev/null
+++ b/modules/core/test/ref/test59.out
@@ -0,0 +1,3 @@
+# testing with conf/test59.conf
+httpd: Syntax error on line 2 of ./conf/test59.conf: <Macro macro definition: name not found
+# exit: 1
diff --git a/modules/core/test/ref/test60.out b/modules/core/test/ref/test60.out
new file mode 100644
index 0000000..cac7002
--- /dev/null
+++ b/modules/core/test/ref/test60.out
@@ -0,0 +1,15 @@
+# testing with conf/test60.conf
+[core:warn] macro Foo arg 1: hello on line 1 of macro 'foo' (defined on line 2 of "./conf/test60.conf") used on line 14 of "./conf/test60.conf"
+[core:warn] macro Foo arg 2: world on line 2 of macro 'foo' (defined on line 2 of "./conf/test60.conf") used on line 14 of "./conf/test60.conf"
+[core:warn] Macro Bla arg 1: "hello world" on line 1 of macro 'bla' (defined on line 6 of "./conf/test60.conf") used on line 15 of "./conf/test60.conf"
+[core:warn] Macro Bla arg 2: "thank you" on line 2 of macro 'bla' (defined on line 6 of "./conf/test60.conf") used on line 15 of "./conf/test60.conf"
+[core:warn] macro Foo arg 1: hello world on line 1 of macro 'foo' (defined on line 2 of "./conf/test60.conf") used on line 3 of "macro 'bla' (defined on line 6 of "./conf/test60.conf") used on line 15 of "./conf/test60.conf""
+[core:warn] macro Foo arg 2: second on line 2 of macro 'foo' (defined on line 2 of "./conf/test60.conf") used on line 3 of "macro 'bla' (defined on line 6 of "./conf/test60.conf") used on line 15 of "./conf/test60.conf""
+[core:warn] macro Foo arg 1: first on line 1 of macro 'foo' (defined on line 2 of "./conf/test60.conf") used on line 4 of "macro 'bla' (defined on line 6 of "./conf/test60.conf") used on line 15 of "./conf/test60.conf""
+[core:warn] macro Foo arg 2: thank you on line 2 of macro 'foo' (defined on line 2 of "./conf/test60.conf") used on line 4 of "macro 'bla' (defined on line 6 of "./conf/test60.conf") used on line 15 of "./conf/test60.conf""
+[core:warn] macro Foo arg 1: hello world on line 1 of macro 'foo' (defined on line 2 of "./conf/test60.conf") used on line 5 of "macro 'bla' (defined on line 6 of "./conf/test60.conf") used on line 15 of "./conf/test60.conf""
+[core:warn] macro Foo arg 2: thank you on line 2 of macro 'foo' (defined on line 2 of "./conf/test60.conf") used on line 5 of "macro 'bla' (defined on line 6 of "./conf/test60.conf") used on line 15 of "./conf/test60.conf""
+[core:error] done on line 17. on line 17 of ./conf/test60.conf
+AH00526: Syntax error on line 17 of ./conf/test60.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test61.out b/modules/core/test/ref/test61.out
new file mode 100644
index 0000000..59639c9
--- /dev/null
+++ b/modules/core/test/ref/test61.out
@@ -0,0 +1,9 @@
+# testing with conf/test61.conf
+[core:warn] F4:1 x=line=17 on line 1 of macro 'f4' (defined on line 13 of "./conf/test61.conf") used on line 17 of "./conf/test61.conf"
+[core:warn] F3:1 x=line=17 on line 1 of macro 'f3' (defined on line 9 of "./conf/test61.conf") used on line 2 of "macro 'f4' (defined on line 13 of "./conf/test61.conf") used on line 17 of "./conf/test61.conf""
+[core:warn] F2:1 x=line=17 on line 1 of macro 'f2' (defined on line 5 of "./conf/test61.conf") used on line 2 of "macro 'f3' (defined on line 9 of "./conf/test61.conf") used on line 2 of "macro 'f4' (defined on line 13 of "./conf/test61.conf") used on line 17 of "./conf/test61.conf"""
+[core:warn] F1:1 x=line=17 on line 1 of macro 'f1' (defined on line 2 of "./conf/test61.conf") used on line 2 of "macro 'f2' (defined on line 5 of "./conf/test61.conf") used on line 2 of "macro 'f3' (defined on line 9 of "./conf/test61.conf") used on line 2 of "macro 'f4' (defined on line 13 of "./conf/test61.conf") used on line 17 of "./conf/test61.conf""""
+[core:error] done line 18. on line 18 of ./conf/test61.conf
+AH00526: Syntax error on line 18 of ./conf/test61.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test62.out b/modules/core/test/ref/test62.out
new file mode 100644
index 0000000..a956e7e
--- /dev/null
+++ b/modules/core/test/ref/test62.out
@@ -0,0 +1,15 @@
+# testing with conf/test62.conf
+[core:warn] Line:1-2 start at 11 on line 1 of macro 'line' (defined on line 4 of "./conf/test62.conf") used on line 11 of "./conf/test62.conf"
+[core:warn] Line:3-4 stop at 11 on line 2 of macro 'line' (defined on line 4 of "./conf/test62.conf") used on line 11 of "./conf/test62.conf"
+[core:warn] Line:1-2 start at 12 on line 1 of macro 'line' (defined on line 4 of "./conf/test62.conf") used on line 13 of "./conf/test62.conf"
+[core:warn] Line:3-4 stop at 13 on line 2 of macro 'line' (defined on line 4 of "./conf/test62.conf") used on line 13 of "./conf/test62.conf"
+[core:warn] Line:1-2 start at 14 on line 1 of macro 'line' (defined on line 4 of "./conf/test62.conf") used on line 16 of "./conf/test62.conf"
+[core:warn] Line:3-4 stop at 16 on line 2 of macro 'line' (defined on line 4 of "./conf/test62.conf") used on line 16 of "./conf/test62.conf"
+[core:warn] Line:1-2 start at 17 on line 1 of macro 'line' (defined on line 4 of "./conf/test62.conf") used on line 18 of "./conf/test62.conf"
+[core:warn] Line:3-4 stop at 18 on line 2 of macro 'line' (defined on line 4 of "./conf/test62.conf") used on line 18 of "./conf/test62.conf"
+[core:warn] Line:1-2 start at 19 on line 1 of macro 'line' (defined on line 4 of "./conf/test62.conf") used on line 23 of "./conf/test62.conf"
+[core:warn] Line:3-4 stop at 23 on line 2 of macro 'line' (defined on line 4 of "./conf/test62.conf") used on line 23 of "./conf/test62.conf"
+[core:error] done line 25. on line 25 of ./conf/test62.conf
+AH00526: Syntax error on line 25 of ./conf/test62.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test63.out b/modules/core/test/ref/test63.out
new file mode 100644
index 0000000..985710d
--- /dev/null
+++ b/modules/core/test/ref/test63.out
@@ -0,0 +1,10 @@
+# testing with conf/test63.conf
+[core:warn] Foo macro at inc63_.conf:5 on line 1 of macro 'foo' (defined on line 2 of "./conf/inc63_1.conf") used on line 5 of "./conf/inc63_1.conf"
+[core:warn] Foo macro at test63.conf:3 on line 1 of macro 'foo' (defined on line 2 of "./conf/inc63_1.conf") used on line 3 of "./conf/test63.conf"
+[core:warn] Foo macro at inc63_2.conf:2 on line 1 of macro 'foo' (defined on line 2 of "./conf/inc63_1.conf") used on line 2 of "./conf/inc63_2.conf"
+[core:warn] Bla at inc63_2.conf:3 on line 1 of macro 'bla' (defined on line 4 of "./conf/test63.conf") used on line 3 of "./conf/inc63_2.conf"
+[core:warn] Bla at test63.conf:8 on line 1 of macro 'bla' (defined on line 4 of "./conf/test63.conf") used on line 8 of "./conf/test63.conf"
+[core:error] done at line 9. on line 9 of ./conf/test63.conf
+AH00526: Syntax error on line 9 of ./conf/test63.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test64.out b/modules/core/test/ref/test64.out
new file mode 100644
index 0000000..99fec92
--- /dev/null
+++ b/modules/core/test/ref/test64.out
@@ -0,0 +1,7 @@
+# testing with conf/test64.conf
+[core:warn] on line 2 on line 2 of ./conf/test64.conf
+[core:warn] from line 3 to line 4 on line 4 of ./conf/test64.conf
+[core:error] done on line 5. on line 5 of ./conf/test64.conf
+AH00526: Syntax error on line 5 of ./conf/test64.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test65.out b/modules/core/test/ref/test65.out
new file mode 100644
index 0000000..62882e2
--- /dev/null
+++ b/modules/core/test/ref/test65.out
@@ -0,0 +1,7 @@
+# testing with conf/test65.conf
+[core:warn] Line: on line 6-7 on line 1 of macro 'line' (defined on line 2 of "./conf/test65.conf") used on line 7 of "./conf/test65.conf"
+[core:warn] Line: on line 8-10 on line 1 of macro 'line' (defined on line 2 of "./conf/test65.conf") used on line 10 of "./conf/test65.conf"
+[core:error] done on line 11. on line 11 of ./conf/test65.conf
+AH00526: Syntax error on line 11 of ./conf/test65.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test66.out b/modules/core/test/ref/test66.out
new file mode 100644
index 0000000..db8616a
--- /dev/null
+++ b/modules/core/test/ref/test66.out
@@ -0,0 +1,7 @@
+# testing with conf/test66.conf
+[core:warn] Foo: x=X y=Y on line 1 of macro 'foo' (defined on line 2 of "./conf/test66.conf") used on line 5 of "./conf/test66.conf"
+[core:warn] Foo: x=$y y=$x on line 1 of macro 'foo' (defined on line 2 of "./conf/test66.conf") used on line 6 of "./conf/test66.conf"
+[core:error] done on line 7. on line 7 of ./conf/test66.conf
+AH00526: Syntax error on line 7 of ./conf/test66.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test67.out b/modules/core/test/ref/test67.out
new file mode 100644
index 0000000..b83f074
--- /dev/null
+++ b/modules/core/test/ref/test67.out
@@ -0,0 +1,5 @@
+# testing with conf/test67.conf
+[core:error] done at line 1 without LF. on line 1 of ./conf/test67.conf
+AH00526: Syntax error on line 1 of ./conf/test67.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test68.out b/modules/core/test/ref/test68.out
new file mode 100644
index 0000000..0289127
--- /dev/null
+++ b/modules/core/test/ref/test68.out
@@ -0,0 +1,6 @@
+# testing with conf/test68.conf
+[core:warn] line 2-3 on line 3 of ./conf/test68.conf
+[core:error] done on line 4-5. on line 5 of ./conf/test68.conf
+AH00526: Syntax error on line 5 of ./conf/test68.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/core/test/ref/test69.out b/modules/core/test/ref/test69.out
new file mode 100644
index 0000000..ac0c1db
--- /dev/null
+++ b/modules/core/test/ref/test69.out
@@ -0,0 +1,10 @@
+# testing with conf/test69.conf
+[macro:warn] non blank chars found after <Macro closing '>' on line 2 of ./conf/test69.conf: this stuff is ignored...
+[macro:warn] non blank chars found after directive closing on line 4 of ./conf/test69.conf: this stuff is ignored as well...
+[core:warn] Foo on line 1 of macro 'foo' (defined on line 2 of "./conf/test69.conf") used on line 5 of "./conf/test69.conf"
+[core:warn] Bla on line 1 of macro 'bla' (defined on line 6 of "./conf/test69.conf") used on line 9 of "./conf/test69.conf"
+[core:warn] Comments on line 1 of macro 'comments' (defined on line 10 of "./conf/test69.conf") used on line 13 of "./conf/test69.conf"
+[core:error] done on line 14. on line 14 of ./conf/test69.conf
+AH00526: Syntax error on line 14 of ./conf/test69.conf:
+Configuration processing stopped by Error directive
+# exit: 1
diff --git a/modules/database/Makefile.in b/modules/database/Makefile.in
new file mode 100644
index 0000000..7c5c149
--- /dev/null
+++ b/modules/database/Makefile.in
@@ -0,0 +1,3 @@
+# a modules Makefile has no explicit targets -- they will be defined by
+# whatever modules are enabled. just grab special.mk to deal with this.
+include $(top_srcdir)/build/special.mk
diff --git a/modules/database/NWGNUmakefile b/modules/database/NWGNUmakefile
new file mode 100644
index 0000000..7a96c04
--- /dev/null
+++ b/modules/database/NWGNUmakefile
@@ -0,0 +1,262 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = moddbd
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) DBD Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = DBD Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/moddbd.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_dbd.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ dbd_module \
+ ap_dbd_open \
+ ap_dbd_close \
+ ap_dbd_acquire \
+ ap_dbd_cacquire \
+ ap_dbd_prepare \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/database/config.m4 b/modules/database/config.m4
new file mode 100644
index 0000000..809270b
--- /dev/null
+++ b/modules/database/config.m4
@@ -0,0 +1,8 @@
+
+APACHE_MODPATH_INIT(database)
+
+APACHE_MODULE(dbd, Apache DBD Framework, , , most)
+
+APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/$modpath_current])
+
+APACHE_MODPATH_FINISH
diff --git a/modules/database/mod_dbd.c b/modules/database/mod_dbd.c
new file mode 100644
index 0000000..aa6b764
--- /dev/null
+++ b/modules/database/mod_dbd.c
@@ -0,0 +1,995 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Overview of what this is and does:
+ * http://www.apache.org/~niq/dbd.html
+ * or
+ * http://apache.webthing.com/database/
+ */
+
+#include "apr_reslist.h"
+#include "apr_strings.h"
+#include "apr_hash.h"
+#include "apr_tables.h"
+#include "apr_lib.h"
+#include "apr_dbd.h"
+
+#define APR_WANT_MEMFUNC
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "http_protocol.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "http_request.h"
+#include "mod_dbd.h"
+
+extern module AP_MODULE_DECLARE_DATA dbd_module;
+
+APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(dbd, DBD, apr_status_t, post_connect,
+ (apr_pool_t *pool, dbd_cfg_t *cfg,
+ ap_dbd_t *dbd),
+ (pool, cfg, dbd), OK, DECLINED)
+
+/************ svr cfg: manage db connection pool ****************/
+
+#define NMIN_SET 0x1
+#define NKEEP_SET 0x2
+#define NMAX_SET 0x4
+#define EXPTIME_SET 0x8
+
+typedef struct dbd_group_t dbd_group_t;
+
+struct dbd_group_t {
+ dbd_cfg_t *cfg;
+ dbd_group_t *next;
+ apr_pool_t *pool;
+#if APR_HAS_THREADS
+ apr_thread_mutex_t *mutex;
+ apr_reslist_t *reslist;
+ int destroyed;
+#else
+ ap_dbd_t *rec;
+#endif
+};
+
+typedef struct {
+ dbd_cfg_t *cfg;
+ dbd_group_t *group;
+} svr_cfg;
+
+typedef enum { cmd_name, cmd_params, cmd_persist,
+ cmd_min, cmd_keep, cmd_max, cmd_exp
+} cmd_parts;
+
+static apr_pool_t *config_pool;
+static dbd_group_t *group_list;
+
+/* a default DBDriver value that'll generate meaningful error messages */
+static const char *const no_dbdriver = "[DBDriver unset]";
+
+/* A default nmin of >0 will help with generating meaningful
+ * startup error messages if the database is down.
+ */
+#define DEFAULT_NMIN 1
+#define DEFAULT_NKEEP 2
+#define DEFAULT_NMAX 10
+#define DEFAULT_EXPTIME 300
+
+#define DEFAULT_SQL_INIT_ARRAY_SIZE 5
+
+static void *create_dbd_config(apr_pool_t *pool, server_rec *s)
+{
+ svr_cfg *svr = apr_pcalloc(pool, sizeof(svr_cfg));
+ dbd_cfg_t *cfg = svr->cfg = apr_pcalloc(pool, sizeof(dbd_cfg_t));
+
+ cfg->server = s;
+ cfg->name = no_dbdriver; /* to generate meaningful error messages */
+ cfg->params = ""; /* don't risk segfault on misconfiguration */
+ cfg->persist = -1;
+#if APR_HAS_THREADS
+ cfg->nmin = DEFAULT_NMIN;
+ cfg->nkeep = DEFAULT_NKEEP;
+ cfg->nmax = DEFAULT_NMAX;
+ cfg->exptime = DEFAULT_EXPTIME;
+#endif
+ cfg->queries = apr_hash_make(pool);
+ cfg->init_queries = apr_array_make(pool, DEFAULT_SQL_INIT_ARRAY_SIZE,
+ sizeof(const char *));
+
+ return svr;
+}
+
+static void *merge_dbd_config(apr_pool_t *pool, void *basev, void *addv)
+{
+ dbd_cfg_t *base = ((svr_cfg*) basev)->cfg;
+ dbd_cfg_t *add = ((svr_cfg*) addv)->cfg;
+ svr_cfg *svr = apr_pcalloc(pool, sizeof(svr_cfg));
+ dbd_cfg_t *new = svr->cfg = apr_pcalloc(pool, sizeof(dbd_cfg_t));
+
+ new->server = add->server;
+ new->name = (add->name != no_dbdriver) ? add->name : base->name;
+ new->params = strcmp(add->params, "") ? add->params : base->params;
+ new->persist = (add->persist != -1) ? add->persist : base->persist;
+#if APR_HAS_THREADS
+ new->nmin = (add->set&NMIN_SET) ? add->nmin : base->nmin;
+ new->nkeep = (add->set&NKEEP_SET) ? add->nkeep : base->nkeep;
+ new->nmax = (add->set&NMAX_SET) ? add->nmax : base->nmax;
+ new->exptime = (add->set&EXPTIME_SET) ? add->exptime : base->exptime;
+#endif
+ new->queries = apr_hash_overlay(pool, add->queries, base->queries);
+ new->init_queries = apr_array_append(pool, add->init_queries,
+ base->init_queries);
+
+ return svr;
+}
+
+static void ap_dbd_sql_init(server_rec *s, const char *query)
+{
+ svr_cfg *svr;
+ const char **arr_item;
+
+ svr = ap_get_module_config(s->module_config, &dbd_module);
+ if (!svr) {
+ /* some modules may call from within config directive handlers, and
+ * if these are called in a server context that contains no mod_dbd
+ * config directives, then we have to create our own server config
+ */
+ svr = create_dbd_config(config_pool, s);
+ ap_set_module_config(s->module_config, &dbd_module, svr);
+ }
+
+ if (query) {
+ arr_item = apr_array_push(svr->cfg->init_queries);
+ *arr_item = query;
+ }
+}
+
+static const char *dbd_param(cmd_parms *cmd, void *dconf, const char *val)
+{
+ apr_status_t rv;
+ const apr_dbd_driver_t *driver = NULL;
+ svr_cfg *svr = ap_get_module_config(cmd->server->module_config,
+ &dbd_module);
+ dbd_cfg_t *cfg = svr->cfg;
+
+ switch ((long) cmd->info) {
+ case cmd_name:
+ cfg->name = val;
+ /* loading the driver involves once-only dlloading that is
+ * best done at server startup. This also guarantees that
+ * we won't return an error later.
+ */
+ rv = apr_dbd_get_driver(cmd->pool, cfg->name, &driver);
+ if (APR_STATUS_IS_ENOTIMPL(rv)) {
+ return apr_psprintf(cmd->pool, "No driver for %s", cfg->name);
+ }
+ else if (APR_STATUS_IS_EDSOOPEN(rv)) {
+ return apr_psprintf(cmd->pool,
+#ifdef NETWARE
+ "Can't load driver file dbd%s.nlm",
+#else
+ "Can't load driver file apr_dbd_%s.so",
+#endif
+ cfg->name);
+ }
+ else if (APR_STATUS_IS_ESYMNOTFOUND(rv)) {
+ return apr_psprintf(cmd->pool,
+ "Failed to load driver apr_dbd_%s_driver",
+ cfg->name);
+ }
+ break;
+ case cmd_params:
+ cfg->params = val;
+ break;
+ }
+
+ return NULL;
+}
+
+#if APR_HAS_THREADS
+static const char *dbd_param_int(cmd_parms *cmd, void *dconf, const char *val)
+{
+ svr_cfg *svr = ap_get_module_config(cmd->server->module_config,
+ &dbd_module);
+ dbd_cfg_t *cfg = svr->cfg;
+ const char *p;
+
+ for (p = val; *p; ++p) {
+ if (!apr_isdigit(*p)) {
+ return "Argument must be numeric!";
+ }
+ }
+
+ switch ((long) cmd->info) {
+ case cmd_min:
+ cfg->nmin = atoi(val);
+ cfg->set |= NMIN_SET;
+ break;
+ case cmd_keep:
+ cfg->nkeep = atoi(val);
+ cfg->set |= NKEEP_SET;
+ break;
+ case cmd_max:
+ cfg->nmax = atoi(val);
+ cfg->set |= NMAX_SET;
+ break;
+ case cmd_exp:
+ cfg->exptime = atoi(val);
+ cfg->set |= EXPTIME_SET;
+ break;
+ }
+
+ return NULL;
+}
+#endif
+
+static const char *dbd_param_flag(cmd_parms *cmd, void *dconf, int flag)
+{
+ svr_cfg *svr = ap_get_module_config(cmd->server->module_config,
+ &dbd_module);
+
+ switch ((long) cmd->info) {
+ case cmd_persist:
+ svr->cfg->persist = flag;
+ break;
+ }
+
+ return NULL;
+}
+
+static const char *dbd_prepare(cmd_parms *cmd, void *dconf, const char *query,
+ const char *label)
+{
+ if (!label) {
+ label = query;
+ query = "";
+ }
+
+ ap_dbd_prepare(cmd->server, query, label);
+
+ return NULL;
+}
+
+static const char *dbd_init_sql(cmd_parms *cmd, void *dconf, const char *query)
+{
+ if (!query || *query == '\n') {
+ return "You should specify SQL statement";
+ }
+
+ ap_dbd_sql_init(cmd->server, query);
+
+ return NULL;
+}
+
+static const command_rec dbd_cmds[] = {
+ AP_INIT_TAKE1("DBDriver", dbd_param, (void*)cmd_name, RSRC_CONF,
+ "SQL Driver"),
+ AP_INIT_TAKE1("DBDParams", dbd_param, (void*)cmd_params, RSRC_CONF,
+ "SQL Driver Params"),
+ AP_INIT_FLAG("DBDPersist", dbd_param_flag, (void*)cmd_persist, RSRC_CONF,
+ "Use persistent connection/pool"),
+ AP_INIT_TAKE12("DBDPrepareSQL", dbd_prepare, NULL, RSRC_CONF,
+ "SQL statement to prepare (or nothing, to override "
+ "statement inherited from main server) and label"),
+ AP_INIT_TAKE1("DBDInitSQL", dbd_init_sql, NULL, RSRC_CONF,
+ "SQL statement to be executed after connection is created"),
+#if APR_HAS_THREADS
+ AP_INIT_TAKE1("DBDMin", dbd_param_int, (void*)cmd_min, RSRC_CONF,
+ "Minimum number of connections"),
+ /* XXX: note that mod_proxy calls this "smax" */
+ AP_INIT_TAKE1("DBDKeep", dbd_param_int, (void*)cmd_keep, RSRC_CONF,
+ "Maximum number of sustained connections"),
+ AP_INIT_TAKE1("DBDMax", dbd_param_int, (void*)cmd_max, RSRC_CONF,
+ "Maximum number of connections"),
+ /* XXX: note that mod_proxy calls this "ttl" (time to live) */
+ AP_INIT_TAKE1("DBDExptime", dbd_param_int, (void*)cmd_exp, RSRC_CONF,
+ "Keepalive time for idle connections"),
+#endif
+ {NULL}
+};
+
+static int dbd_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp)
+{
+ config_pool = pconf;
+ group_list = NULL;
+ return OK;
+}
+
+DBD_DECLARE_NONSTD(void) ap_dbd_prepare(server_rec *s, const char *query,
+ const char *label)
+{
+ svr_cfg *svr;
+
+ svr = ap_get_module_config(s->module_config, &dbd_module);
+ if (!svr) {
+ /* some modules may call from within config directive handlers, and
+ * if these are called in a server context that contains no mod_dbd
+ * config directives, then we have to create our own server config
+ */
+ svr = create_dbd_config(config_pool, s);
+ ap_set_module_config(s->module_config, &dbd_module, svr);
+ }
+
+ if (apr_hash_get(svr->cfg->queries, label, APR_HASH_KEY_STRING)
+ && strcmp(query, "")) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(02653)
+ "conflicting SQL statements with label %s", label);
+ }
+
+ apr_hash_set(svr->cfg->queries, label, APR_HASH_KEY_STRING, query);
+}
+
+typedef struct {
+ const char *label, *query;
+} dbd_query_t;
+
+static int dbd_post_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ server_rec *sp;
+ apr_array_header_t *add_queries = apr_array_make(ptemp, 10,
+ sizeof(dbd_query_t));
+
+ for (sp = s; sp; sp = sp->next) {
+ svr_cfg *svr = ap_get_module_config(sp->module_config, &dbd_module);
+ dbd_cfg_t *cfg = svr->cfg;
+ apr_hash_index_t *hi_first = apr_hash_first(ptemp, cfg->queries);
+ dbd_group_t *group;
+
+ /* dbd_setup in 2.2.3 and under was causing spurious error messages
+ * when dbd isn't configured. We can stop that with a quick check here
+ * together with a similar check in ap_dbd_open (where being
+ * unconfigured is a genuine error that must be reported).
+ */
+ if (cfg->name == no_dbdriver || !cfg->persist) {
+ continue;
+ }
+
+ for (group = group_list; group; group = group->next) {
+ dbd_cfg_t *group_cfg = group->cfg;
+ apr_hash_index_t *hi;
+ int group_ok = 1;
+
+ if (strcmp(cfg->name, group_cfg->name)
+ || strcmp(cfg->params, group_cfg->params)) {
+ continue;
+ }
+
+#if APR_HAS_THREADS
+ if (cfg->nmin != group_cfg->nmin
+ || cfg->nkeep != group_cfg->nkeep
+ || cfg->nmax != group_cfg->nmax
+ || cfg->exptime != group_cfg->exptime) {
+ continue;
+ }
+#endif
+
+ add_queries->nelts = 0;
+
+ for (hi = hi_first; hi; hi = apr_hash_next(hi)) {
+ const char *label, *query;
+ const char *group_query;
+
+ apr_hash_this(hi, (void*) &label, NULL, (void*) &query);
+
+ group_query = apr_hash_get(group_cfg->queries, label,
+ APR_HASH_KEY_STRING);
+
+ if (!group_query) {
+ dbd_query_t *add_query = apr_array_push(add_queries);
+
+ add_query->label = label;
+ add_query->query = query;
+ }
+ else if (strcmp(query, group_query)) {
+ group_ok = 0;
+ break;
+ }
+ }
+
+ if (group_ok) {
+ int i;
+
+ for (i = 0; i < add_queries->nelts; ++i) {
+ dbd_query_t *add_query = ((dbd_query_t*) add_queries->elts)
+ + i;
+
+ apr_hash_set(group_cfg->queries, add_query->label,
+ APR_HASH_KEY_STRING, add_query->query);
+ }
+
+ svr->group = group;
+ break;
+ }
+ }
+
+ if (!svr->group) {
+ svr->group = group = apr_pcalloc(pconf, sizeof(dbd_group_t));
+
+ group->cfg = cfg;
+
+ group->next = group_list;
+ group_list = group;
+ }
+ }
+
+ return OK;
+}
+
+static apr_status_t dbd_prepared_init(apr_pool_t *pool, dbd_cfg_t *cfg,
+ ap_dbd_t *rec)
+{
+ apr_hash_index_t *hi;
+
+ rec->prepared = apr_hash_make(pool);
+
+ for (hi = apr_hash_first(pool, cfg->queries); hi;
+ hi = apr_hash_next(hi)) {
+ const char *label, *query;
+ apr_dbd_prepared_t *stmt;
+
+ apr_hash_this(hi, (void*) &label, NULL, (void*) &query);
+
+ if (!strcmp(query, "")) {
+ continue;
+ }
+
+ stmt = NULL;
+ if (apr_dbd_prepare(rec->driver, pool, rec->handle, query,
+ label, &stmt)) {
+ return APR_EGENERAL;
+ }
+ else {
+ apr_hash_set(rec->prepared, label, APR_HASH_KEY_STRING, stmt);
+ }
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t dbd_init_sql_init(apr_pool_t *pool, dbd_cfg_t *cfg,
+ ap_dbd_t *rec)
+{
+ int i;
+ apr_status_t rv = APR_SUCCESS;
+
+ for (i = 0; i < cfg->init_queries->nelts; i++) {
+ int nrows;
+ char **query_p;
+
+ query_p = (char **)cfg->init_queries->elts + i;
+
+ if (apr_dbd_query(rec->driver, rec->handle, &nrows, *query_p)) {
+ rv = APR_EGENERAL;
+ break;
+ }
+ }
+
+ return rv;
+}
+
+static apr_status_t dbd_close(void *data)
+{
+ ap_dbd_t *rec = data;
+
+ return apr_dbd_close(rec->driver, rec->handle);
+}
+
+#if APR_HAS_THREADS
+static apr_status_t dbd_destruct(void *data, void *params, apr_pool_t *pool)
+{
+ dbd_group_t *group = params;
+
+ if (!group->destroyed) {
+ ap_dbd_t *rec = data;
+
+ apr_pool_destroy(rec->pool);
+ }
+
+ return APR_SUCCESS;
+}
+#endif
+
+/* an apr_reslist_constructor for SQL connections
+ * Also use this for opening in non-reslist modes, since it gives
+ * us all the error-handling in one place.
+ */
+static apr_status_t dbd_construct(void **data_ptr,
+ void *params, apr_pool_t *pool)
+{
+ dbd_group_t *group = params;
+ dbd_cfg_t *cfg = group->cfg;
+ apr_pool_t *rec_pool, *prepared_pool;
+ ap_dbd_t *rec;
+ apr_status_t rv;
+ const char *err = "";
+
+ rv = apr_pool_create(&rec_pool, pool);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, cfg->server, APLOGNO(00624)
+ "Failed to create memory pool");
+ return rv;
+ }
+ apr_pool_tag(rec_pool, "dbd_rec_pool");
+
+ rec = apr_pcalloc(rec_pool, sizeof(ap_dbd_t));
+
+ rec->pool = rec_pool;
+
+ /* The driver is loaded at config time now, so this just checks a hash.
+ * If that changes, the driver DSO could be registered to unload against
+ * our pool, which is probably not what we want. Error checking isn't
+ * necessary now, but in case that changes in the future ...
+ */
+ rv = apr_dbd_get_driver(rec->pool, cfg->name, &rec->driver);
+ if (rv != APR_SUCCESS) {
+ if (APR_STATUS_IS_ENOTIMPL(rv)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, cfg->server, APLOGNO(00625)
+ "driver for %s not available", cfg->name);
+ }
+ else if (APR_STATUS_IS_EDSOOPEN(rv)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, cfg->server, APLOGNO(00626)
+ "can't find driver for %s", cfg->name);
+ }
+ else if (APR_STATUS_IS_ESYMNOTFOUND(rv)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, cfg->server, APLOGNO(00627)
+ "driver for %s is invalid or corrupted",
+ cfg->name);
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, cfg->server, APLOGNO(00628)
+ "mod_dbd not compatible with APR in get_driver");
+ }
+ apr_pool_destroy(rec->pool);
+ return rv;
+ }
+
+ rv = apr_dbd_open_ex(rec->driver, rec->pool, cfg->params, &rec->handle, &err);
+ if (rv != APR_SUCCESS) {
+ switch (rv) {
+ case APR_EGENERAL:
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, cfg->server, APLOGNO(00629)
+ "Can't connect to %s: %s", cfg->name, err);
+ break;
+ default:
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, cfg->server, APLOGNO(00630)
+ "mod_dbd not compatible with APR in open");
+ break;
+ }
+
+ apr_pool_destroy(rec->pool);
+ return rv;
+ }
+
+ apr_pool_cleanup_register(rec->pool, rec, dbd_close,
+ apr_pool_cleanup_null);
+
+ /* we use a sub-pool for the prepared statements for each connection so
+ * that they will be cleaned up first, before the connection is closed
+ */
+ rv = apr_pool_create(&prepared_pool, rec->pool);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, cfg->server, APLOGNO(00631)
+ "Failed to create memory pool");
+
+ apr_pool_destroy(rec->pool);
+ return rv;
+ }
+ apr_pool_tag(prepared_pool, "dbd_prepared_pool");
+
+ rv = dbd_prepared_init(prepared_pool, cfg, rec);
+ if (rv != APR_SUCCESS) {
+ const char *errmsg = apr_dbd_error(rec->driver, rec->handle, rv);
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, cfg->server, APLOGNO(00632)
+ "failed to prepare SQL statements: %s",
+ (errmsg ? errmsg : "[???]"));
+
+ apr_pool_destroy(rec->pool);
+ return rv;
+ }
+
+ dbd_run_post_connect(prepared_pool, cfg, rec);
+
+ *data_ptr = rec;
+
+ return APR_SUCCESS;
+}
+
+#if APR_HAS_THREADS
+static apr_status_t dbd_destroy(void *data)
+{
+ dbd_group_t *group = data;
+
+ group->destroyed = 1;
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t dbd_setup(server_rec *s, dbd_group_t *group)
+{
+ dbd_cfg_t *cfg = group->cfg;
+ apr_status_t rv;
+
+ /* We create the reslist using a sub-pool of the pool passed to our
+ * child_init hook. No other threads can be here because we're
+ * either in the child_init phase or dbd_setup_lock() acquired our mutex.
+ * No other threads will use this sub-pool after this, except via
+ * reslist calls, which have an internal mutex.
+ *
+ * We need to short-circuit the cleanup registered internally by
+ * apr_reslist_create(). We do this by registering dbd_destroy()
+ * as a cleanup afterwards, so that it will run before the reslist's
+ * internal cleanup.
+ *
+ * If we didn't do this, then we could free memory twice when the pool
+ * was destroyed. When apr_pool_destroy() runs, it first destroys all
+ * all the per-connection sub-pools created in dbd_construct(), and
+ * then it runs the reslist's cleanup. The cleanup calls dbd_destruct()
+ * on each resource, which would then attempt to destroy the sub-pools
+ * a second time.
+ */
+ rv = apr_reslist_create(&group->reslist,
+ cfg->nmin, cfg->nkeep, cfg->nmax,
+ apr_time_from_sec(cfg->exptime),
+ dbd_construct, dbd_destruct, group,
+ group->pool);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00633)
+ "failed to initialise");
+ return rv;
+ }
+
+ apr_pool_cleanup_register(group->pool, group, dbd_destroy,
+ apr_pool_cleanup_null);
+
+ return APR_SUCCESS;
+}
+#endif
+
+static apr_status_t dbd_setup_init(apr_pool_t *pool, server_rec *s)
+{
+ dbd_group_t *group;
+ apr_status_t rv = APR_SUCCESS;
+
+ for (group = group_list; group; group = group->next) {
+ apr_status_t rv2;
+
+ rv2 = apr_pool_create(&group->pool, pool);
+ if (rv2 != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv2, s, APLOGNO(00634)
+ "Failed to create reslist cleanup memory pool");
+ return rv2;
+ }
+ apr_pool_tag(group->pool, "dbd_group");
+
+#if APR_HAS_THREADS
+ rv2 = dbd_setup(s, group);
+ if (rv2 == APR_SUCCESS) {
+ continue;
+ }
+ else if (rv == APR_SUCCESS) {
+ rv = rv2;
+ }
+
+ /* we failed, so create a mutex so that subsequent competing callers
+ * to ap_dbd_open can serialize themselves while they retry
+ */
+ rv2 = apr_thread_mutex_create(&group->mutex,
+ APR_THREAD_MUTEX_DEFAULT, pool);
+ if (rv2 != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv2, s, APLOGNO(00635)
+ "Failed to create thread mutex");
+ return rv2;
+ }
+#endif
+ }
+
+ return rv;
+}
+
+static void dbd_child_init(apr_pool_t *p, server_rec *s)
+{
+ apr_status_t rv = dbd_setup_init(p, s);
+ if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00636)
+ "child init failed!");
+ }
+}
+
+#if APR_HAS_THREADS
+static apr_status_t dbd_setup_lock(server_rec *s, dbd_group_t *group)
+{
+ apr_status_t rv = APR_SUCCESS, rv2;
+
+ /* several threads could be here at the same time, all trying to
+ * initialize the reslist because dbd_setup_init failed to do so
+ */
+ if (!group->mutex) {
+ /* we already logged an error when the mutex couldn't be created */
+ return APR_EGENERAL;
+ }
+
+ rv2 = apr_thread_mutex_lock(group->mutex);
+ if (rv2 != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv2, s, APLOGNO(00637)
+ "Failed to acquire thread mutex");
+ return rv2;
+ }
+
+ if (!group->reslist) {
+ rv = dbd_setup(s, group);
+ }
+
+ rv2 = apr_thread_mutex_unlock(group->mutex);
+ if (rv2 != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv2, s, APLOGNO(00638)
+ "Failed to release thread mutex");
+ if (rv == APR_SUCCESS) {
+ rv = rv2;
+ }
+ }
+
+ return rv;
+}
+#endif
+
+/* Functions we export for modules to use:
+ - open acquires a connection from the pool (opens one if necessary)
+ - close releases it back in to the pool
+*/
+DBD_DECLARE_NONSTD(void) ap_dbd_close(server_rec *s, ap_dbd_t *rec)
+{
+ svr_cfg *svr = ap_get_module_config(s->module_config, &dbd_module);
+
+ if (!svr->cfg->persist) {
+ apr_pool_destroy(rec->pool);
+ }
+#if APR_HAS_THREADS
+ else {
+ apr_reslist_release(svr->group->reslist, rec);
+ }
+#endif
+}
+
+static apr_status_t dbd_check(apr_pool_t *pool, server_rec *s, ap_dbd_t *rec)
+{
+ svr_cfg *svr;
+ apr_status_t rv = apr_dbd_check_conn(rec->driver, pool, rec->handle);
+ const char *errmsg;
+
+ if ((rv == APR_SUCCESS) || (rv == APR_ENOTIMPL)) {
+ return APR_SUCCESS;
+ }
+
+ /* we don't have a driver-specific error code, so we'll just pass
+ * a "success" value and rely on the driver to ignore it
+ */
+ errmsg = apr_dbd_error(rec->driver, rec->handle, 0);
+ if (!errmsg) {
+ errmsg = "(unknown)";
+ }
+
+ svr = ap_get_module_config(s->module_config, &dbd_module);
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00639)
+ "DBD [%s] Error: %s", svr->cfg->name, errmsg);
+ return rv;
+}
+
+DBD_DECLARE_NONSTD(ap_dbd_t*) ap_dbd_open(apr_pool_t *pool, server_rec *s)
+{
+ svr_cfg *svr = ap_get_module_config(s->module_config, &dbd_module);
+ dbd_group_t *group = svr->group;
+ dbd_cfg_t *cfg = svr->cfg;
+ ap_dbd_t *rec = NULL;
+#if APR_HAS_THREADS
+ apr_status_t rv;
+#endif
+
+ /* If nothing is configured, we shouldn't be here */
+ if (cfg->name == no_dbdriver) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(02654)
+ "not configured");
+ return NULL;
+ }
+
+ if (!cfg->persist) {
+ /* Return a once-only connection */
+ group = apr_pcalloc(pool, sizeof(dbd_group_t));
+
+ group->cfg = cfg;
+
+ dbd_construct((void*) &rec, group, pool);
+ return rec;
+ }
+
+#if APR_HAS_THREADS
+ if (!group->reslist) {
+ if (dbd_setup_lock(s, group) != APR_SUCCESS) {
+ return NULL;
+ }
+ }
+
+ rv = apr_reslist_acquire(group->reslist, (void*) &rec);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(02655)
+ "Failed to acquire DBD connection from pool!");
+ return NULL;
+ }
+
+ if (dbd_check(pool, s, rec) != APR_SUCCESS) {
+ apr_reslist_invalidate(group->reslist, rec);
+ return NULL;
+ }
+#else
+ /* If we have a persistent connection and it's good, we'll use it;
+ * since this is non-threaded, we can update without a mutex
+ */
+ rec = group->rec;
+ if (rec) {
+ if (dbd_check(pool, s, rec) != APR_SUCCESS) {
+ apr_pool_destroy(rec->pool);
+ rec = NULL;
+ }
+ }
+
+ /* We don't have a connection right now, so we'll open one */
+ if (!rec) {
+ dbd_construct((void*) &rec, group, group->pool);
+ group->rec = rec;
+ }
+#endif
+
+ return rec;
+}
+
+#if APR_HAS_THREADS
+typedef struct {
+ ap_dbd_t *rec;
+ apr_reslist_t *reslist;
+} dbd_acquire_t;
+
+static apr_status_t dbd_release(void *data)
+{
+ dbd_acquire_t *acq = data;
+ apr_reslist_release(acq->reslist, acq->rec);
+ return APR_SUCCESS;
+}
+
+DBD_DECLARE_NONSTD(ap_dbd_t *) ap_dbd_acquire(request_rec *r)
+{
+ dbd_acquire_t *acq;
+
+ while (!ap_is_initial_req(r)) {
+ if (r->prev) {
+ r = r->prev;
+ }
+ else if (r->main) {
+ r = r->main;
+ }
+ }
+
+ acq = ap_get_module_config(r->request_config, &dbd_module);
+ if (!acq) {
+ acq = apr_palloc(r->pool, sizeof(dbd_acquire_t));
+ acq->rec = ap_dbd_open(r->pool, r->server);
+ if (acq->rec) {
+ svr_cfg *svr = ap_get_module_config(r->server->module_config,
+ &dbd_module);
+
+ ap_set_module_config(r->request_config, &dbd_module, acq);
+ if (svr->cfg->persist) {
+ acq->reslist = svr->group->reslist;
+ apr_pool_cleanup_register(r->pool, acq, dbd_release,
+ apr_pool_cleanup_null);
+ }
+ }
+ }
+
+ return acq->rec;
+}
+
+DBD_DECLARE_NONSTD(ap_dbd_t *) ap_dbd_cacquire(conn_rec *c)
+{
+ dbd_acquire_t *acq = ap_get_module_config(c->conn_config, &dbd_module);
+
+ if (!acq) {
+ acq = apr_palloc(c->pool, sizeof(dbd_acquire_t));
+ acq->rec = ap_dbd_open(c->pool, c->base_server);
+ if (acq->rec) {
+ svr_cfg *svr = ap_get_module_config(c->base_server->module_config,
+ &dbd_module);
+
+ ap_set_module_config(c->conn_config, &dbd_module, acq);
+ if (svr->cfg->persist) {
+ acq->reslist = svr->group->reslist;
+ apr_pool_cleanup_register(c->pool, acq, dbd_release,
+ apr_pool_cleanup_null);
+ }
+ }
+ }
+
+ return acq->rec;
+}
+#else
+DBD_DECLARE_NONSTD(ap_dbd_t *) ap_dbd_acquire(request_rec *r)
+{
+ ap_dbd_t *rec;
+
+ while (!ap_is_initial_req(r)) {
+ if (r->prev) {
+ r = r->prev;
+ }
+ else if (r->main) {
+ r = r->main;
+ }
+ }
+
+ rec = ap_get_module_config(r->request_config, &dbd_module);
+ if (!rec) {
+ rec = ap_dbd_open(r->pool, r->server);
+ if (rec) {
+ ap_set_module_config(r->request_config, &dbd_module, rec);
+ }
+ }
+
+ return rec;
+}
+
+DBD_DECLARE_NONSTD(ap_dbd_t *) ap_dbd_cacquire(conn_rec *c)
+{
+ ap_dbd_t *rec = ap_get_module_config(c->conn_config, &dbd_module);
+
+ if (!rec) {
+ rec = ap_dbd_open(c->pool, c->base_server);
+ if (rec) {
+ ap_set_module_config(c->conn_config, &dbd_module, rec);
+ }
+ }
+
+ return rec;
+}
+#endif
+
+static void dbd_hooks(apr_pool_t *pool)
+{
+ ap_hook_pre_config(dbd_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_post_config(dbd_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_child_init(dbd_child_init, NULL, NULL, APR_HOOK_MIDDLE);
+
+ APR_REGISTER_OPTIONAL_FN(ap_dbd_prepare);
+ APR_REGISTER_OPTIONAL_FN(ap_dbd_open);
+ APR_REGISTER_OPTIONAL_FN(ap_dbd_close);
+ APR_REGISTER_OPTIONAL_FN(ap_dbd_acquire);
+ APR_REGISTER_OPTIONAL_FN(ap_dbd_cacquire);
+
+ APR_OPTIONAL_HOOK(dbd, post_connect, dbd_init_sql_init,
+ NULL, NULL, APR_HOOK_MIDDLE);
+
+ apr_dbd_init(pool);
+}
+
+AP_DECLARE_MODULE(dbd) = {
+ STANDARD20_MODULE_STUFF,
+ NULL,
+ NULL,
+ create_dbd_config,
+ merge_dbd_config,
+ dbd_cmds,
+ dbd_hooks
+};
+
diff --git a/modules/database/mod_dbd.dep b/modules/database/mod_dbd.dep
new file mode 100644
index 0000000..ba15c8f
--- /dev/null
+++ b/modules/database/mod_dbd.dep
@@ -0,0 +1,58 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_dbd.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_dbd.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_dbd.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_reslist.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_dbd.h"\
+
diff --git a/modules/database/mod_dbd.dsp b/modules/database/mod_dbd.dsp
new file mode 100644
index 0000000..4fd3c8a
--- /dev/null
+++ b/modules/database/mod_dbd.dsp
@@ -0,0 +1,115 @@
+# Microsoft Developer Studio Project File - Name="mod_dbd" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_dbd - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_dbd.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_dbd.mak" CFG="mod_dbd - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_dbd - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_dbd - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_dbd - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "DBD_DECLARE_EXPORT" /Fd"Release\mod_dbd_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_dbd.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_dbd.so" /d LONG_NAME="dbd_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_dbd.so" /base:@..\..\os\win32\BaseAddr.ref,mod_dbd.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_dbd.so" /base:@..\..\os\win32\BaseAddr.ref,mod_dbd.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_dbd.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_dbd - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "DBD_DECLARE_EXPORT" /Fd"Debug\mod_dbd_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_dbd.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_dbd.so" /d LONG_NAME="dbd_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_dbd.so" /base:@..\..\os\win32\BaseAddr.ref,mod_dbd.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_dbd.so" /base:@..\..\os\win32\BaseAddr.ref,mod_dbd.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_dbd.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_dbd - Win32 Release"
+# Name "mod_dbd - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_dbd.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\mod_dbd.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/database/mod_dbd.h b/modules/database/mod_dbd.h
new file mode 100644
index 0000000..e4d2153
--- /dev/null
+++ b/modules/database/mod_dbd.h
@@ -0,0 +1,123 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file mod_dbd.h
+ * @brief Database Access Extension Module for Apache
+ *
+ * Overview of what this is and does:
+ * http://www.apache.org/~niq/dbd.html
+ * or
+ * http://apache.webthing.com/database/
+ *
+ * @defgroup MOD_DBD mod_dbd
+ * @ingroup APACHE_MODS
+ * @{
+ */
+
+#ifndef DBD_H
+#define DBD_H
+
+/* Create a set of DBD_DECLARE(type), DBD_DECLARE_NONSTD(type) and
+ * DBD_DECLARE_DATA with appropriate export and import tags for the platform
+ */
+#if !defined(WIN32)
+#define DBD_DECLARE(type) type
+#define DBD_DECLARE_NONSTD(type) type
+#define DBD_DECLARE_DATA
+#elif defined(DBD_DECLARE_STATIC)
+#define DBD_DECLARE(type) type __stdcall
+#define DBD_DECLARE_NONSTD(type) type
+#define DBD_DECLARE_DATA
+#elif defined(DBD_DECLARE_EXPORT)
+#define DBD_DECLARE(type) __declspec(dllexport) type __stdcall
+#define DBD_DECLARE_NONSTD(type) __declspec(dllexport) type
+#define DBD_DECLARE_DATA __declspec(dllexport)
+#else
+#define DBD_DECLARE(type) __declspec(dllimport) type __stdcall
+#define DBD_DECLARE_NONSTD(type) __declspec(dllimport) type
+#define DBD_DECLARE_DATA __declspec(dllimport)
+#endif
+
+#include <httpd.h>
+#include <apr_optional.h>
+#include <apr_hash.h>
+#include <apr_hooks.h>
+
+typedef struct {
+ server_rec *server;
+ const char *name;
+ const char *params;
+ int persist;
+#if APR_HAS_THREADS
+ int nmin;
+ int nkeep;
+ int nmax;
+ int exptime;
+ int set;
+#endif
+ apr_hash_t *queries;
+ apr_array_header_t *init_queries;
+} dbd_cfg_t;
+
+typedef struct {
+ apr_dbd_t *handle;
+ const apr_dbd_driver_t *driver;
+ apr_hash_t *prepared;
+ apr_pool_t *pool;
+} ap_dbd_t;
+
+/* Export functions to access the database */
+
+/* acquire a connection that MUST be explicitly closed.
+ * Returns NULL on error
+ */
+DBD_DECLARE_NONSTD(ap_dbd_t*) ap_dbd_open(apr_pool_t*, server_rec*);
+
+/* release a connection acquired with ap_dbd_open */
+DBD_DECLARE_NONSTD(void) ap_dbd_close(server_rec*, ap_dbd_t*);
+
+/* acquire a connection that will have the lifetime of a request
+ * and MUST NOT be explicitly closed. Return NULL on error.
+ * This is the preferred function for most applications.
+ */
+DBD_DECLARE_NONSTD(ap_dbd_t*) ap_dbd_acquire(request_rec*);
+
+/* acquire a connection that will have the lifetime of a connection
+ * and MUST NOT be explicitly closed. Return NULL on error.
+ * This is the preferred function for most applications.
+ */
+DBD_DECLARE_NONSTD(ap_dbd_t*) ap_dbd_cacquire(conn_rec*);
+
+/* Prepare a statement for use by a client module during
+ * the server startup/configuration phase. Can't be called
+ * after the server has created its children (use apr_dbd_*).
+ */
+DBD_DECLARE_NONSTD(void) ap_dbd_prepare(server_rec*, const char*, const char*);
+
+/* Also export them as optional functions for modules that prefer it */
+APR_DECLARE_OPTIONAL_FN(ap_dbd_t*, ap_dbd_open, (apr_pool_t*, server_rec*));
+APR_DECLARE_OPTIONAL_FN(void, ap_dbd_close, (server_rec*, ap_dbd_t*));
+APR_DECLARE_OPTIONAL_FN(ap_dbd_t*, ap_dbd_acquire, (request_rec*));
+APR_DECLARE_OPTIONAL_FN(ap_dbd_t*, ap_dbd_cacquire, (conn_rec*));
+APR_DECLARE_OPTIONAL_FN(void, ap_dbd_prepare, (server_rec*, const char*, const char*));
+
+APR_DECLARE_EXTERNAL_HOOK(dbd, DBD, apr_status_t, post_connect,
+ (apr_pool_t *, dbd_cfg_t *, ap_dbd_t *))
+
+#endif
+/** @} */
+
diff --git a/modules/database/mod_dbd.mak b/modules/database/mod_dbd.mak
new file mode 100644
index 0000000..5cf2265
--- /dev/null
+++ b/modules/database/mod_dbd.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_dbd.dsp
+!IF "$(CFG)" == ""
+CFG=mod_dbd - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_dbd - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_dbd - Win32 Release" && "$(CFG)" != "mod_dbd - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_dbd.mak" CFG="mod_dbd - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_dbd - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_dbd - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_dbd - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_dbd.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_dbd.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_dbd.obj"
+ -@erase "$(INTDIR)\mod_dbd.res"
+ -@erase "$(INTDIR)\mod_dbd_src.idb"
+ -@erase "$(INTDIR)\mod_dbd_src.pdb"
+ -@erase "$(OUTDIR)\mod_dbd.exp"
+ -@erase "$(OUTDIR)\mod_dbd.lib"
+ -@erase "$(OUTDIR)\mod_dbd.pdb"
+ -@erase "$(OUTDIR)\mod_dbd.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "DBD_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_dbd_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_dbd.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_dbd.so" /d LONG_NAME="dbd_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_dbd.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_dbd.pdb" /debug /out:"$(OUTDIR)\mod_dbd.so" /implib:"$(OUTDIR)\mod_dbd.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_dbd.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_dbd.obj" \
+ "$(INTDIR)\mod_dbd.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_dbd.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_dbd.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_dbd.so"
+ if exist .\Release\mod_dbd.so.manifest mt.exe -manifest .\Release\mod_dbd.so.manifest -outputresource:.\Release\mod_dbd.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_dbd - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_dbd.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_dbd.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_dbd.obj"
+ -@erase "$(INTDIR)\mod_dbd.res"
+ -@erase "$(INTDIR)\mod_dbd_src.idb"
+ -@erase "$(INTDIR)\mod_dbd_src.pdb"
+ -@erase "$(OUTDIR)\mod_dbd.exp"
+ -@erase "$(OUTDIR)\mod_dbd.lib"
+ -@erase "$(OUTDIR)\mod_dbd.pdb"
+ -@erase "$(OUTDIR)\mod_dbd.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "DBD_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_dbd_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_dbd.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_dbd.so" /d LONG_NAME="dbd_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_dbd.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_dbd.pdb" /debug /out:"$(OUTDIR)\mod_dbd.so" /implib:"$(OUTDIR)\mod_dbd.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_dbd.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_dbd.obj" \
+ "$(INTDIR)\mod_dbd.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_dbd.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_dbd.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_dbd.so"
+ if exist .\Debug\mod_dbd.so.manifest mt.exe -manifest .\Debug\mod_dbd.so.manifest -outputresource:.\Debug\mod_dbd.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_dbd.dep")
+!INCLUDE "mod_dbd.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_dbd.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_dbd - Win32 Release" || "$(CFG)" == "mod_dbd - Win32 Debug"
+
+!IF "$(CFG)" == "mod_dbd - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\database"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\database"
+
+!ELSEIF "$(CFG)" == "mod_dbd - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\database"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\database"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_dbd - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\database"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\database"
+
+!ELSEIF "$(CFG)" == "mod_dbd - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\database"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\database"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_dbd - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\database"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\database"
+
+!ELSEIF "$(CFG)" == "mod_dbd - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\database"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\database"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_dbd - Win32 Release"
+
+
+"$(INTDIR)\mod_dbd.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_dbd.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_dbd.so" /d LONG_NAME="dbd_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_dbd - Win32 Debug"
+
+
+"$(INTDIR)\mod_dbd.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_dbd.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_dbd.so" /d LONG_NAME="dbd_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_dbd.c
+
+"$(INTDIR)\mod_dbd.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/dav/fs/Makefile.in b/modules/dav/fs/Makefile.in
new file mode 100644
index 0000000..7c5c149
--- /dev/null
+++ b/modules/dav/fs/Makefile.in
@@ -0,0 +1,3 @@
+# a modules Makefile has no explicit targets -- they will be defined by
+# whatever modules are enabled. just grab special.mk to deal with this.
+include $(top_srcdir)/build/special.mk
diff --git a/modules/dav/fs/NWGNUmakefile b/modules/dav/fs/NWGNUmakefile
new file mode 100644
index 0000000..e402428
--- /dev/null
+++ b/modules/dav/fs/NWGNUmakefile
@@ -0,0 +1,269 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/server/mpm/netware \
+ $(AP_WORK)/modules/dav/main \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = moddavfs
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) DAV FileSystem Sub-Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+NLM_THREAD_NAME = $(NLM_NAME) Thread
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 65536
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If this is specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# Declare all target files (you must add your files here)
+#
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_dav_fs.o \
+ $(OBJDIR)/dbm.o \
+ $(OBJDIR)/lock.o \
+ $(OBJDIR)/repos.o \
+ $(OBJDIR)/libprews.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ Apache2 \
+ Libc \
+ mod_dav \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @libc.imp \
+ @aprlib.imp \
+ @httpd.imp \
+ @../main/dav.imp \
+ $(EOLIST)
+
+# Don't link with Winsock if standard sockets are being used
+ifndef USE_STDSOCKETS
+FILES_nlm_Ximports += @ws2nlm.imp \
+ $(EOLIST)
+endif
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ dav_fs_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+#
+# Any specialized rules here
+#
+
+vpath %.c ../../arch/netware
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/dav/fs/config6.m4 b/modules/dav/fs/config6.m4
new file mode 100644
index 0000000..dd26ec8
--- /dev/null
+++ b/modules/dav/fs/config6.m4
@@ -0,0 +1,23 @@
+dnl modules enabled in this directory by default
+
+APACHE_MODPATH_INIT(dav/fs)
+
+dav_fs_objects="mod_dav_fs.lo dbm.lo lock.lo repos.lo"
+
+if test "x$enable_dav" != "x"; then
+ dav_fs_enable=$enable_dav
+else
+ dav_fs_enable=$dav_enable
+fi
+
+case "$host" in
+ *os2*)
+ # OS/2 DLLs must resolve all symbols at build time
+ # and we need some from main DAV module
+ dav_fs_objects="$dav_fs_objects ../main/mod_dav.la"
+ ;;
+esac
+
+APACHE_MODULE(dav_fs, DAV provider for the filesystem. --enable-dav also enables mod_dav_fs., $dav_fs_objects, , $dav_fs_enable,,dav)
+
+APACHE_MODPATH_FINISH
diff --git a/modules/dav/fs/dbm.c b/modules/dav/fs/dbm.c
new file mode 100644
index 0000000..347d75d
--- /dev/null
+++ b/modules/dav/fs/dbm.c
@@ -0,0 +1,800 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+** DAV extension module for Apache 2.0.*
+** - Database support using DBM-style databases,
+** part of the filesystem repository implementation
+*/
+
+/*
+** This implementation uses a SDBM database per file and directory to
+** record the properties. These databases are kept in a subdirectory (of
+** the directory in question or the directory that holds the file in
+** question) named by the macro DAV_FS_STATE_DIR (.DAV). The filename of the
+** database is equivalent to the target filename, and is
+** DAV_FS_STATE_FILE_FOR_DIR (.state_for_dir) for the directory itself.
+*/
+
+#include "apr_strings.h"
+#include "apr_file_io.h"
+
+#include "apr_dbm.h"
+
+#define APR_WANT_BYTEFUNC
+#include "apr_want.h" /* for ntohs and htons */
+
+#include "apr_version.h"
+#if !APR_VERSION_AT_LEAST(2,0,0)
+#include "apu_version.h"
+#endif
+
+#include "mod_dav.h"
+#include "repos.h"
+#include "http_log.h"
+#include "http_main.h" /* for ap_server_conf */
+
+APLOG_USE_MODULE(dav_fs);
+
+struct dav_db {
+ apr_pool_t *pool;
+ apr_dbm_t *file;
+
+ /* when used as a property database: */
+
+ int version; /* *minor* version of this db */
+
+ dav_buffer ns_table; /* table of namespace URIs */
+ short ns_count; /* number of entries in table */
+ int ns_table_dirty; /* ns_table was modified */
+ apr_hash_t *uri_index; /* map URIs to (1-based) table indices */
+
+ dav_buffer wb_key; /* work buffer for dav_gdbm_key */
+
+ apr_datum_t iter; /* iteration key */
+};
+
+/* -------------------------------------------------------------------------
+ *
+ * GENERIC DBM ACCESS
+ *
+ * For the most part, this just uses the APR DBM functions. They are wrapped
+ * a bit with some error handling (using the mod_dav error functions).
+ */
+
+void dav_dbm_get_statefiles(apr_pool_t *p, const char *fname,
+ const char **state1, const char **state2)
+{
+ if (fname == NULL)
+ fname = DAV_FS_STATE_FILE_FOR_DIR;
+
+ apr_dbm_get_usednames(p, fname, state1, state2);
+}
+
+static dav_error * dav_fs_dbm_error(dav_db *db, apr_pool_t *p,
+ apr_status_t status)
+{
+ int errcode;
+ const char *errstr;
+ dav_error *err;
+ char errbuf[200];
+
+ if (status == APR_SUCCESS)
+ return NULL;
+
+ p = db ? db->pool : p;
+
+ /* There might not be a <db> if we had problems creating it. */
+ if (db == NULL) {
+ errcode = 1;
+ errstr = "Could not open property database.";
+ if (APR_STATUS_IS_EDSOOPEN(status))
+ ap_log_error(APLOG_MARK, APLOG_CRIT, status, ap_server_conf, APLOGNO(00576)
+ "The DBM driver could not be loaded");
+ }
+ else {
+ (void) apr_dbm_geterror(db->file, &errcode, errbuf, sizeof(errbuf));
+ errstr = apr_pstrdup(p, errbuf);
+ }
+
+ err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, errcode, status, errstr);
+ return err;
+}
+
+/* ensure that our state subdirectory is present */
+/* ### does this belong here or in dav_fs_repos.c ?? */
+void dav_fs_ensure_state_dir(apr_pool_t * p, const char *dirname)
+{
+ const char *pathname = apr_pstrcat(p, dirname, "/" DAV_FS_STATE_DIR, NULL);
+
+ /* ### do we need to deal with the umask? */
+
+ /* just try to make it, ignoring any resulting errors */
+ (void) apr_dir_make(pathname, APR_OS_DEFAULT, p);
+}
+
+/* dav_dbm_open_direct: Opens a *dbm database specified by path.
+ * ro = boolean read-only flag.
+ */
+dav_error * dav_dbm_open_direct(apr_pool_t *p, const char *pathname, int ro,
+ dav_db **pdb)
+{
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ const apr_dbm_driver_t *driver;
+ const apu_err_t *err;
+#endif
+ apr_dbm_t *file = NULL;
+ apr_status_t status;
+
+ *pdb = NULL;
+
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ if ((status = apr_dbm_get_driver(&driver, NULL, &err, p)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, status, ap_server_conf, APLOGNO(10289)
+ "mod_dav_fs: The DBM library '%s' could not be loaded: %s",
+ err->reason, err->msg);
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 1, status,
+ "Could not load library for property database.");
+ }
+ if ((status = apr_dbm_open2(&file, driver, pathname,
+ ro ? APR_DBM_READONLY : APR_DBM_RWCREATE,
+ APR_OS_DEFAULT, p))
+ != APR_SUCCESS && !ro) {
+ return dav_fs_dbm_error(NULL, p, status);
+ }
+#else
+ if ((status = apr_dbm_open(&file, pathname,
+ ro ? APR_DBM_READONLY : APR_DBM_RWCREATE,
+ APR_OS_DEFAULT, p))
+ != APR_SUCCESS
+ && !ro) {
+ /* ### do something with 'status' */
+
+ /* we can't continue if we couldn't open the file
+ and we need to write */
+ return dav_fs_dbm_error(NULL, p, status);
+ }
+#endif
+
+ /* may be NULL if we tried to open a non-existent db as read-only */
+ if (file != NULL) {
+ /* we have an open database... return it */
+ *pdb = apr_pcalloc(p, sizeof(**pdb));
+ (*pdb)->pool = p;
+ (*pdb)->file = file;
+ }
+
+ return NULL;
+}
+
+static dav_error * dav_dbm_open(apr_pool_t * p, const dav_resource *resource,
+ int ro, dav_db **pdb)
+{
+ const char *dirpath;
+ const char *fname;
+ const char *pathname;
+
+ /* Get directory and filename for resource */
+ /* ### should test this result value... */
+ (void) dav_fs_dir_file_name(resource, &dirpath, &fname);
+
+ /* If not opening read-only, ensure the state dir exists */
+ if (!ro) {
+ /* ### what are the perf implications of always checking this? */
+ dav_fs_ensure_state_dir(p, dirpath);
+ }
+
+ pathname = apr_pstrcat(p, dirpath, "/" DAV_FS_STATE_DIR "/",
+ fname ? fname : DAV_FS_STATE_FILE_FOR_DIR,
+ NULL);
+
+ /* ### readers cannot open while a writer has this open; we should
+ ### perform a few retries with random pauses. */
+
+ /* ### do we need to deal with the umask? */
+
+ return dav_dbm_open_direct(p, pathname, ro, pdb);
+}
+
+void dav_dbm_close(dav_db *db)
+{
+ apr_dbm_close(db->file);
+}
+
+dav_error * dav_dbm_fetch(dav_db *db, apr_datum_t key, apr_datum_t *pvalue)
+{
+ apr_status_t status;
+
+ if (!key.dptr) {
+ /* no key could be created (namespace not known) => no value */
+ memset(pvalue, 0, sizeof(*pvalue));
+ status = APR_SUCCESS;
+ } else {
+ status = apr_dbm_fetch(db->file, key, pvalue);
+ }
+
+ return dav_fs_dbm_error(db, NULL, status);
+}
+
+dav_error * dav_dbm_store(dav_db *db, apr_datum_t key, apr_datum_t value)
+{
+ apr_status_t status = apr_dbm_store(db->file, key, value);
+
+ return dav_fs_dbm_error(db, NULL, status);
+}
+
+dav_error * dav_dbm_delete(dav_db *db, apr_datum_t key)
+{
+ apr_status_t status = apr_dbm_delete(db->file, key);
+
+ return dav_fs_dbm_error(db, NULL, status);
+}
+
+int dav_dbm_exists(dav_db *db, apr_datum_t key)
+{
+ return apr_dbm_exists(db->file, key);
+}
+
+static dav_error * dav_dbm_firstkey(dav_db *db, apr_datum_t *pkey)
+{
+ apr_status_t status = apr_dbm_firstkey(db->file, pkey);
+
+ return dav_fs_dbm_error(db, NULL, status);
+}
+
+static dav_error * dav_dbm_nextkey(dav_db *db, apr_datum_t *pkey)
+{
+ apr_status_t status = apr_dbm_nextkey(db->file, pkey);
+
+ return dav_fs_dbm_error(db, NULL, status);
+}
+
+void dav_dbm_freedatum(dav_db *db, apr_datum_t data)
+{
+ apr_dbm_freedatum(db->file, data);
+}
+
+/* -------------------------------------------------------------------------
+ *
+ * PROPERTY DATABASE FUNCTIONS
+ */
+
+
+#define DAV_GDBM_NS_KEY "METADATA"
+#define DAV_GDBM_NS_KEY_LEN 8
+
+typedef struct {
+ unsigned char major;
+#define DAV_DBVSN_MAJOR 4
+ /*
+ ** V4 -- 0.9.9 ..
+ ** Prior versions could have keys or values with invalid
+ ** namespace prefixes as a result of the xmlns="" form not
+ ** resetting the default namespace to be "no namespace". The
+ ** namespace would be set to "" which is invalid; it should
+ ** be set to "no namespace".
+ **
+ ** V3 -- 0.9.8
+ ** Prior versions could have values with invalid namespace
+ ** prefixes due to an incorrect mapping of input to propdb
+ ** namespace indices. Version bumped to obsolete the old
+ ** values.
+ **
+ ** V2 -- 0.9.7
+ ** This introduced the xml:lang value into the property value's
+ ** record in the propdb.
+ **
+ ** V1 -- .. 0.9.6
+ ** Initial version.
+ */
+
+
+ unsigned char minor;
+#define DAV_DBVSN_MINOR 0
+
+ short ns_count;
+
+} dav_propdb_metadata;
+
+struct dav_deadprop_rollback {
+ apr_datum_t key;
+ apr_datum_t value;
+};
+
+struct dav_namespace_map {
+ int *ns_map;
+};
+
+/*
+** Internal function to build a key
+**
+** WARNING: returns a pointer to a "static" buffer holding the key. The
+** value must be copied or no longer used if this function is
+** called again.
+*/
+static apr_datum_t dav_build_key(dav_db *db, const dav_prop_name *name)
+{
+ char nsbuf[20];
+ apr_size_t l_ns, l_name = strlen(name->name);
+ apr_datum_t key = { 0 };
+
+ /*
+ * Convert namespace ID to a string. "no namespace" is an empty string,
+ * so the keys will have the form ":name". Otherwise, the keys will
+ * have the form "#:name".
+ */
+ if (*name->ns == '\0') {
+ nsbuf[0] = '\0';
+ l_ns = 0;
+ }
+ else {
+ long ns_id = (long)apr_hash_get(db->uri_index, name->ns,
+ APR_HASH_KEY_STRING);
+
+
+ if (ns_id == 0) {
+ /* the namespace was not found(!) */
+ return key; /* zeroed */
+ }
+
+ l_ns = apr_snprintf(nsbuf, sizeof(nsbuf), "%ld", ns_id - 1);
+ }
+
+ /* assemble: #:name */
+ dav_set_bufsize(db->pool, &db->wb_key, l_ns + 1 + l_name + 1);
+ memcpy(db->wb_key.buf, nsbuf, l_ns);
+ db->wb_key.buf[l_ns] = ':';
+ memcpy(&db->wb_key.buf[l_ns + 1], name->name, l_name + 1);
+
+ /* build the database key */
+ key.dsize = l_ns + 1 + l_name + 1;
+ key.dptr = db->wb_key.buf;
+
+ return key;
+}
+
+static void dav_append_prop(apr_pool_t *pool,
+ const char *name, const char *value,
+ apr_text_header *phdr)
+{
+ const char *s;
+ const char *lang = value;
+
+ /* skip past the xml:lang value */
+ value += strlen(lang) + 1;
+
+ if (*value == '\0') {
+ /* the property is an empty value */
+ if (*name == ':') {
+ /* "no namespace" case */
+ s = apr_pstrcat(pool, "<", name+1, "/>" DEBUG_CR, NULL);
+ }
+ else {
+ s = apr_pstrcat(pool, "<ns", name, "/>" DEBUG_CR, NULL);
+ }
+ }
+ else if (*lang != '\0') {
+ if (*name == ':') {
+ /* "no namespace" case */
+ s = apr_pstrcat(pool, "<", name+1, " xml:lang=\"",
+ lang, "\">", value, "</", name+1, ">" DEBUG_CR,
+ NULL);
+ }
+ else {
+ s = apr_pstrcat(pool, "<ns", name, " xml:lang=\"",
+ lang, "\">", value, "</ns", name, ">" DEBUG_CR,
+ NULL);
+ }
+ }
+ else if (*name == ':') {
+ /* "no namespace" case */
+ s = apr_pstrcat(pool, "<", name+1, ">", value, "</", name+1, ">"
+ DEBUG_CR, NULL);
+ }
+ else {
+ s = apr_pstrcat(pool, "<ns", name, ">", value, "</ns", name, ">"
+ DEBUG_CR, NULL);
+ }
+
+ apr_text_append(pool, phdr, s);
+}
+
+static dav_error * dav_propdb_open(apr_pool_t *pool,
+ const dav_resource *resource, int ro,
+ dav_db **pdb)
+{
+ dav_db *db;
+ dav_error *err;
+ apr_datum_t key;
+ apr_datum_t value = { 0 };
+
+ *pdb = NULL;
+
+ /*
+ ** Return if an error occurred, or there is no database.
+ **
+ ** NOTE: db could be NULL if we attempted to open a readonly
+ ** database that doesn't exist. If we require read/write
+ ** access, then a database was created and opened.
+ */
+ if ((err = dav_dbm_open(pool, resource, ro, &db)) != NULL
+ || db == NULL)
+ return err;
+
+ db->uri_index = apr_hash_make(pool);
+
+ key.dptr = DAV_GDBM_NS_KEY;
+ key.dsize = DAV_GDBM_NS_KEY_LEN;
+ if ((err = dav_dbm_fetch(db, key, &value)) != NULL) {
+ /* ### push a higher-level description? */
+ return err;
+ }
+
+ if (value.dptr == NULL) {
+ dav_propdb_metadata m = {
+ DAV_DBVSN_MAJOR, DAV_DBVSN_MINOR, 0
+ };
+
+ /*
+ ** If there is no METADATA key, then the database may be
+ ** from versions 0.9.0 .. 0.9.4 (which would be incompatible).
+ ** These can be identified by the presence of an NS_TABLE entry.
+ */
+ key.dptr = "NS_TABLE";
+ key.dsize = 8;
+ if (dav_dbm_exists(db, key)) {
+ dav_dbm_close(db);
+
+ /* call it a major version error */
+ return dav_new_error(pool, HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_PROP_BAD_MAJOR, 0,
+ "Prop database has the wrong major "
+ "version number and cannot be used.");
+ }
+
+ /* initialize a new metadata structure */
+ dav_set_bufsize(pool, &db->ns_table, sizeof(m));
+ memcpy(db->ns_table.buf, &m, sizeof(m));
+ }
+ else {
+ dav_propdb_metadata m;
+ long ns;
+ const char *uri;
+
+ dav_set_bufsize(pool, &db->ns_table, value.dsize);
+ memcpy(db->ns_table.buf, value.dptr, value.dsize);
+
+ memcpy(&m, value.dptr, sizeof(m));
+ if (m.major != DAV_DBVSN_MAJOR) {
+ dav_dbm_close(db);
+
+ return dav_new_error(pool, HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_PROP_BAD_MAJOR, 0,
+ "Prop database has the wrong major "
+ "version number and cannot be used.");
+ }
+ db->version = m.minor;
+ db->ns_count = ntohs(m.ns_count);
+
+ dav_dbm_freedatum(db, value);
+
+ /* create db->uri_index */
+ for (ns = 0, uri = db->ns_table.buf + sizeof(dav_propdb_metadata);
+ ns++ < db->ns_count;
+ uri += strlen(uri) + 1) {
+
+ /* we must copy the key, in case ns_table.buf moves */
+ apr_hash_set(db->uri_index,
+ apr_pstrdup(pool, uri), APR_HASH_KEY_STRING,
+ (void *)ns);
+ }
+ }
+
+ *pdb = db;
+ return NULL;
+}
+
+static void dav_propdb_close(dav_db *db)
+{
+
+ if (db->ns_table_dirty) {
+ dav_propdb_metadata m;
+ apr_datum_t key;
+ apr_datum_t value;
+ dav_error *err;
+
+ key.dptr = DAV_GDBM_NS_KEY;
+ key.dsize = DAV_GDBM_NS_KEY_LEN;
+
+ value.dptr = db->ns_table.buf;
+ value.dsize = db->ns_table.cur_len;
+
+ /* fill in the metadata that we store into the prop db. */
+ m.major = DAV_DBVSN_MAJOR;
+ m.minor = db->version; /* ### keep current minor version? */
+ m.ns_count = htons(db->ns_count);
+
+ memcpy(db->ns_table.buf, &m, sizeof(m));
+
+ err = dav_dbm_store(db, key, value);
+ if (err != NULL)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, err->aprerr, ap_server_conf,
+ APLOGNO(00577) "Error writing propdb: %s", err->desc);
+ }
+
+ dav_dbm_close(db);
+}
+
+static dav_error * dav_propdb_define_namespaces(dav_db *db, dav_xmlns_info *xi)
+{
+ int ns;
+ const char *uri = db->ns_table.buf + sizeof(dav_propdb_metadata);
+
+ /* within the prop values, we use "ns%d" for prefixes... register them */
+ for (ns = 0; ns < db->ns_count; ++ns, uri += strlen(uri) + 1) {
+
+ /* Empty URIs signify the empty namespace. These do not get a
+ namespace prefix. when we generate the value, we will simply
+ leave off the prefix, which is defined by mod_dav to be the
+ empty namespace. */
+ if (*uri == '\0')
+ continue;
+
+ /* ns_table.buf can move, so copy its value (we want the values to
+ last as long as the provided dav_xmlns_info). */
+ dav_xmlns_add(xi,
+ apr_psprintf(xi->pool, "ns%d", ns),
+ apr_pstrdup(xi->pool, uri));
+ }
+
+ return NULL;
+}
+
+static dav_error * dav_propdb_output_value(dav_db *db,
+ const dav_prop_name *name,
+ dav_xmlns_info *xi,
+ apr_text_header *phdr,
+ int *found)
+{
+ apr_datum_t key = dav_build_key(db, name);
+ apr_datum_t value;
+ dav_error *err;
+
+ if ((err = dav_dbm_fetch(db, key, &value)) != NULL)
+ return err;
+ if (value.dptr == NULL) {
+ *found = 0;
+ return NULL;
+ }
+ *found = 1;
+
+ dav_append_prop(db->pool, key.dptr, value.dptr, phdr);
+
+ dav_dbm_freedatum(db, value);
+
+ return NULL;
+}
+
+static dav_error * dav_propdb_map_namespaces(
+ dav_db *db,
+ const apr_array_header_t *namespaces,
+ dav_namespace_map **mapping)
+{
+ dav_namespace_map *m = apr_palloc(db->pool, sizeof(*m));
+ int i;
+ int *pmap;
+ const char **puri;
+
+ /*
+ ** Iterate over the provided namespaces. If a namespace already appears
+ ** in our internal map of URI -> ns_id, then store that in the map. If
+ ** we don't know the namespace yet, then add it to the map and to our
+ ** table of known namespaces.
+ */
+ m->ns_map = pmap = apr_palloc(db->pool, namespaces->nelts * sizeof(*pmap));
+ for (i = namespaces->nelts, puri = (const char **)namespaces->elts;
+ i-- > 0;
+ ++puri, ++pmap) {
+
+ const char *uri = *puri;
+ apr_size_t uri_len = strlen(uri);
+ long ns_id = (long)apr_hash_get(db->uri_index, uri, uri_len);
+
+ if (ns_id == 0) {
+ dav_check_bufsize(db->pool, &db->ns_table, uri_len + 1);
+ memcpy(db->ns_table.buf + db->ns_table.cur_len, uri, uri_len + 1);
+ db->ns_table.cur_len += uri_len + 1;
+
+ /* copy the uri in case the passed-in namespaces changes in
+ some way. */
+ apr_hash_set(db->uri_index, apr_pstrdup(db->pool, uri), uri_len,
+ (void *)((long)(db->ns_count + 1)));
+
+ db->ns_table_dirty = 1;
+
+ *pmap = db->ns_count++;
+ }
+ else {
+ *pmap = ns_id - 1;
+ }
+ }
+
+ *mapping = m;
+ return NULL;
+}
+
+static dav_error * dav_propdb_store(dav_db *db, const dav_prop_name *name,
+ const apr_xml_elem *elem,
+ dav_namespace_map *mapping)
+{
+ apr_datum_t key = dav_build_key(db, name);
+ apr_datum_t value;
+
+ /* Note: mapping->ns_map was set up in dav_propdb_map_namespaces() */
+
+ /* ### use a db- subpool for these values? clear on exit? */
+
+ /* quote all the values in the element */
+ /* ### be nice to do this without affecting the element itself */
+ /* ### of course, the cast indicates Badness is occurring here */
+ apr_xml_quote_elem(db->pool, (apr_xml_elem *)elem);
+
+ /* generate a text blob for the xml:lang plus the contents */
+ apr_xml_to_text(db->pool, elem, APR_XML_X2T_LANG_INNER, NULL,
+ mapping->ns_map,
+ (const char **)&value.dptr, &value.dsize);
+
+ return dav_dbm_store(db, key, value);
+}
+
+static dav_error * dav_propdb_remove(dav_db *db, const dav_prop_name *name)
+{
+ apr_datum_t key = dav_build_key(db, name);
+ return dav_dbm_delete(db, key);
+}
+
+static int dav_propdb_exists(dav_db *db, const dav_prop_name *name)
+{
+ apr_datum_t key = dav_build_key(db, name);
+ return dav_dbm_exists(db, key);
+}
+
+static const char *dav_get_ns_table_uri(dav_db *db, int ns_id)
+{
+ const char *p = db->ns_table.buf + sizeof(dav_propdb_metadata);
+
+ while (ns_id--)
+ p += strlen(p) + 1;
+
+ return p;
+}
+
+static void dav_set_name(dav_db *db, dav_prop_name *pname)
+{
+ const char *s = db->iter.dptr;
+
+ if (s == NULL) {
+ pname->ns = pname->name = NULL;
+ }
+ else if (*s == ':') {
+ pname->ns = "";
+ pname->name = s + 1;
+ }
+ else {
+ int id = atoi(s);
+
+ pname->ns = dav_get_ns_table_uri(db, id);
+ if (s[1] == ':') {
+ pname->name = s + 2;
+ }
+ else {
+ pname->name = ap_strchr_c(s + 2, ':') + 1;
+ }
+ }
+}
+
+static dav_error * dav_propdb_next_name(dav_db *db, dav_prop_name *pname)
+{
+ dav_error *err;
+
+ /* free the previous key. note: if the loop is aborted, then the DBM
+ will toss the key (via pool cleanup) */
+ if (db->iter.dptr != NULL)
+ dav_dbm_freedatum(db, db->iter);
+
+ if ((err = dav_dbm_nextkey(db, &db->iter)) != NULL)
+ return err;
+
+ /* skip past the METADATA key */
+ if (db->iter.dptr != NULL && *db->iter.dptr == 'M')
+ return dav_propdb_next_name(db, pname);
+
+ dav_set_name(db, pname);
+ return NULL;
+}
+
+static dav_error * dav_propdb_first_name(dav_db *db, dav_prop_name *pname)
+{
+ dav_error *err;
+
+ if ((err = dav_dbm_firstkey(db, &db->iter)) != NULL)
+ return err;
+
+ /* skip past the METADATA key */
+ if (db->iter.dptr != NULL && *db->iter.dptr == 'M')
+ return dav_propdb_next_name(db, pname);
+
+ dav_set_name(db, pname);
+ return NULL;
+}
+
+static dav_error * dav_propdb_get_rollback(dav_db *db,
+ const dav_prop_name *name,
+ dav_deadprop_rollback **prollback)
+{
+ dav_deadprop_rollback *rb = apr_pcalloc(db->pool, sizeof(*rb));
+ apr_datum_t key;
+ apr_datum_t value;
+ dav_error *err;
+
+ key = dav_build_key(db, name);
+ rb->key.dptr = apr_pstrdup(db->pool, key.dptr);
+ rb->key.dsize = key.dsize;
+
+ if ((err = dav_dbm_fetch(db, key, &value)) != NULL)
+ return err;
+ if (value.dptr != NULL) {
+ rb->value.dptr = apr_pmemdup(db->pool, value.dptr, value.dsize);
+ rb->value.dsize = value.dsize;
+ }
+
+ *prollback = rb;
+ return NULL;
+}
+
+static dav_error * dav_propdb_apply_rollback(dav_db *db,
+ dav_deadprop_rollback *rollback)
+{
+ if (!rollback) {
+ return NULL; /* no rollback, nothing to do */
+ }
+
+ if (rollback->value.dptr == NULL) {
+ /* don't fail if the thing isn't really there. */
+ (void) dav_dbm_delete(db, rollback->key);
+ return NULL;
+ }
+
+ return dav_dbm_store(db, rollback->key, rollback->value);
+}
+
+const dav_hooks_db dav_hooks_db_dbm =
+{
+ dav_propdb_open,
+ dav_propdb_close,
+ dav_propdb_define_namespaces,
+ dav_propdb_output_value,
+ dav_propdb_map_namespaces,
+ dav_propdb_store,
+ dav_propdb_remove,
+ dav_propdb_exists,
+ dav_propdb_first_name,
+ dav_propdb_next_name,
+ dav_propdb_get_rollback,
+ dav_propdb_apply_rollback,
+
+ NULL /* ctx */
+};
diff --git a/modules/dav/fs/lock.c b/modules/dav/fs/lock.c
new file mode 100644
index 0000000..ef18c4a
--- /dev/null
+++ b/modules/dav/fs/lock.c
@@ -0,0 +1,1445 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+** DAV filesystem lock implementation
+*/
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_file_io.h"
+#include "apr_uuid.h"
+
+#define APR_WANT_MEMFUNC
+#include "apr_want.h"
+
+#include "httpd.h"
+#include "http_log.h"
+
+#include "mod_dav.h"
+#include "repos.h"
+
+
+/* ---------------------------------------------------------------
+**
+** Lock database primitives
+**
+*/
+
+/*
+** LOCK DATABASES
+**
+** Lockdiscovery information is stored in the single lock database specified
+** by the DAVLockDB directive. Information about this db is stored in the
+** global server configuration.
+**
+** KEY
+**
+** The database is keyed by a key_type unsigned char (DAV_TYPE_FNAME)
+** followed by the full path. The key_type DAV_TYPE_INODE is not used anymore.
+**
+** VALUE
+**
+** The value consists of a list of elements.
+** DIRECT LOCK: [char (DAV_LOCK_DIRECT),
+** char (dav_lock_scope),
+** char (dav_lock_type),
+** int depth,
+** time_t expires,
+** apr_uuid_t locktoken,
+** char[] owner,
+** char[] auth_user]
+**
+** INDIRECT LOCK: [char (DAV_LOCK_INDIRECT),
+** apr_uuid_t locktoken,
+** time_t expires,
+** apr_size_t key_size,
+** char[] key]
+** The key is to the collection lock that resulted in this indirect lock
+*/
+
+#define DAV_TRUE 1
+#define DAV_FALSE 0
+
+#define DAV_CREATE_LIST 23
+#define DAV_APPEND_LIST 24
+
+/* Stored lock_discovery prefix */
+#define DAV_LOCK_DIRECT 1
+#define DAV_LOCK_INDIRECT 2
+
+/*
+ * not used anymore
+ * #define DAV_TYPE_INODE 10
+ */
+#define DAV_TYPE_FNAME 11
+
+
+/* ack. forward declare. */
+static dav_error * dav_fs_remove_locknull_member(apr_pool_t *p,
+ const char *filename,
+ dav_buffer *pbuf);
+
+/*
+** Use the opaquelock scheme for locktokens
+*/
+struct dav_locktoken {
+ apr_uuid_t uuid;
+};
+#define dav_compare_locktoken(plt1, plt2) \
+ memcmp(&(plt1)->uuid, &(plt2)->uuid, sizeof((plt1)->uuid))
+
+
+/* #################################################################
+** ### keep these structures (internal) or move fully to dav_lock?
+*/
+
+/*
+** We need to reliably size the fixed-length portion of
+** dav_lock_discovery; best to separate it into another
+** struct for a convenient sizeof, unless we pack lock_discovery.
+*/
+typedef struct dav_lock_discovery_fixed
+{
+ char scope;
+ char type;
+ int depth;
+ time_t timeout;
+} dav_lock_discovery_fixed;
+
+typedef struct dav_lock_discovery
+{
+ struct dav_lock_discovery_fixed f;
+
+ dav_locktoken *locktoken;
+ const char *owner; /* owner field from activelock */
+ const char *auth_user; /* authenticated user who created the lock */
+ struct dav_lock_discovery *next;
+} dav_lock_discovery;
+
+/* Indirect locks represent locks inherited from containing collections.
+ * They reference the lock token for the collection the lock is
+ * inherited from. A lock provider may also define a key to the
+ * inherited lock, for fast datbase lookup. The key is opaque outside
+ * the lock provider.
+ */
+typedef struct dav_lock_indirect
+{
+ dav_locktoken *locktoken;
+ apr_datum_t key;
+ struct dav_lock_indirect *next;
+ time_t timeout;
+} dav_lock_indirect;
+
+/* ################################################################# */
+
+
+/*
+** Stored direct lock info - full lock_discovery length:
+** prefix + Fixed length + lock token + 2 strings + 2 nulls (one for each string)
+*/
+#define dav_size_direct(a) ( 1 + sizeof(dav_lock_discovery_fixed) \
+ + sizeof(apr_uuid_t) \
+ + ((a)->owner ? strlen((a)->owner) : 0) \
+ + ((a)->auth_user ? strlen((a)->auth_user) : 0) \
+ + 2)
+
+/* Stored indirect lock info - lock token and apr_datum_t */
+#define dav_size_indirect(a) (1 + sizeof(apr_uuid_t) \
+ + sizeof(time_t) \
+ + sizeof((a)->key.dsize) + (a)->key.dsize)
+
+/*
+** The lockdb structure.
+**
+** The <db> field may be NULL, meaning one of two things:
+** 1) That we have not actually opened the underlying database (yet). The
+** <opened> field should be false.
+** 2) We opened it readonly and it wasn't present.
+**
+** The delayed opening (determined by <opened>) makes creating a lockdb
+** quick, while deferring the underlying I/O until it is actually required.
+**
+** We export the notion of a lockdb, but hide the details of it. Most
+** implementations will use a database of some kind, but it is certainly
+** possible that alternatives could be used.
+*/
+struct dav_lockdb_private
+{
+ request_rec *r; /* for accessing the uuid state */
+ apr_pool_t *pool; /* a pool to use */
+ const char *lockdb_path; /* where is the lock database? */
+
+ int opened; /* we opened the database */
+ dav_db *db; /* if non-NULL, the lock database */
+};
+typedef struct
+{
+ dav_lockdb pub;
+ dav_lockdb_private priv;
+} dav_lockdb_combined;
+
+/*
+** The private part of the lock structure.
+*/
+struct dav_lock_private
+{
+ apr_datum_t key; /* key into the lock database */
+};
+typedef struct
+{
+ dav_lock pub;
+ dav_lock_private priv;
+ dav_locktoken token;
+} dav_lock_combined;
+
+/*
+** This must be forward-declared so the open_lockdb function can use it.
+*/
+extern const dav_hooks_locks dav_hooks_locks_fs;
+
+
+/* internal function for creating locks */
+static dav_lock *dav_fs_alloc_lock(dav_lockdb *lockdb, apr_datum_t key,
+ const dav_locktoken *locktoken)
+{
+ dav_lock_combined *comb;
+
+ comb = apr_pcalloc(lockdb->info->pool, sizeof(*comb));
+ comb->pub.rectype = DAV_LOCKREC_DIRECT;
+ comb->pub.info = &comb->priv;
+ comb->priv.key = key;
+
+ if (locktoken == NULL) {
+ comb->pub.locktoken = &comb->token;
+ apr_uuid_get(&comb->token.uuid);
+ }
+ else {
+ comb->pub.locktoken = locktoken;
+ }
+
+ return &comb->pub;
+}
+
+/*
+** dav_fs_parse_locktoken
+**
+** Parse an opaquelocktoken URI into a locktoken.
+*/
+static dav_error * dav_fs_parse_locktoken(
+ apr_pool_t *p,
+ const char *char_token,
+ dav_locktoken **locktoken_p)
+{
+ dav_locktoken *locktoken;
+
+ if (ap_strstr_c(char_token, "opaquelocktoken:") != char_token) {
+ return dav_new_error(p,
+ HTTP_BAD_REQUEST, DAV_ERR_LOCK_UNK_STATE_TOKEN, 0,
+ "The lock token uses an unknown State-token "
+ "format and could not be parsed.");
+ }
+ char_token += 16;
+
+ locktoken = apr_pcalloc(p, sizeof(*locktoken));
+ if (apr_uuid_parse(&locktoken->uuid, char_token)) {
+ return dav_new_error(p, HTTP_BAD_REQUEST, DAV_ERR_LOCK_PARSE_TOKEN, 0,
+ "The opaquelocktoken has an incorrect format "
+ "and could not be parsed.");
+ }
+
+ *locktoken_p = locktoken;
+ return NULL;
+}
+
+/*
+** dav_fs_format_locktoken
+**
+** Generate the URI for a locktoken
+*/
+static const char *dav_fs_format_locktoken(
+ apr_pool_t *p,
+ const dav_locktoken *locktoken)
+{
+ char buf[APR_UUID_FORMATTED_LENGTH + 1];
+
+ apr_uuid_format(buf, &locktoken->uuid);
+ return apr_pstrcat(p, "opaquelocktoken:", buf, NULL);
+}
+
+/*
+** dav_fs_compare_locktoken
+**
+** Determine whether two locktokens are the same
+*/
+static int dav_fs_compare_locktoken(
+ const dav_locktoken *lt1,
+ const dav_locktoken *lt2)
+{
+ return dav_compare_locktoken(lt1, lt2);
+}
+
+/*
+** dav_fs_really_open_lockdb:
+**
+** If the database hasn't been opened yet, then open the thing.
+*/
+static dav_error * dav_fs_really_open_lockdb(dav_lockdb *lockdb)
+{
+ dav_error *err;
+
+ if (lockdb->info->opened)
+ return NULL;
+
+ err = dav_dbm_open_direct(lockdb->info->pool,
+ lockdb->info->lockdb_path,
+ lockdb->ro,
+ &lockdb->info->db);
+ if (err != NULL) {
+ return dav_push_error(lockdb->info->pool,
+ HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_LOCK_OPENDB,
+ "Could not open the lock database.",
+ err);
+ }
+
+ /* all right. it is opened now. */
+ lockdb->info->opened = 1;
+
+ return NULL;
+}
+
+/*
+** dav_fs_open_lockdb:
+**
+** "open" the lock database, as specified in the global server configuration.
+** If force is TRUE, then the database is opened now, rather than lazily.
+**
+** Note that only one can be open read/write.
+*/
+static dav_error * dav_fs_open_lockdb(request_rec *r, int ro, int force,
+ dav_lockdb **lockdb)
+{
+ dav_lockdb_combined *comb;
+
+ comb = apr_pcalloc(r->pool, sizeof(*comb));
+ comb->pub.hooks = &dav_hooks_locks_fs;
+ comb->pub.ro = ro;
+ comb->pub.info = &comb->priv;
+ comb->priv.r = r;
+ comb->priv.pool = r->pool;
+
+ comb->priv.lockdb_path = dav_get_lockdb_path(r);
+ if (comb->priv.lockdb_path == NULL) {
+ return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_LOCK_NO_DB, 0,
+ "A lock database was not specified with the "
+ "DAVLockDB directive. One must be specified "
+ "to use the locking functionality.");
+ }
+
+ /* done initializing. return it. */
+ *lockdb = &comb->pub;
+
+ if (force) {
+ /* ### add a higher-level comment? */
+ return dav_fs_really_open_lockdb(*lockdb);
+ }
+
+ return NULL;
+}
+
+/*
+** dav_fs_close_lockdb:
+**
+** Close it. Duh.
+*/
+static void dav_fs_close_lockdb(dav_lockdb *lockdb)
+{
+ if (lockdb->info->db != NULL)
+ dav_dbm_close(lockdb->info->db);
+}
+
+/*
+** dav_fs_build_key: Given a resource, return a apr_datum_t key
+** to look up lock information for this file.
+*/
+static apr_datum_t dav_fs_build_key(apr_pool_t *p,
+ const dav_resource *resource)
+{
+ const char *pathname = dav_fs_pathname(resource);
+ apr_datum_t key;
+
+ /* ### does this allocation have a proper lifetime? need to check */
+ /* ### can we use a buffer for this? */
+
+ /* size is TYPE + pathname + null */
+ key.dsize = strlen(pathname) + 2;
+ key.dptr = apr_palloc(p, key.dsize);
+ *key.dptr = DAV_TYPE_FNAME;
+ memcpy(key.dptr + 1, pathname, key.dsize - 1);
+ if (key.dptr[key.dsize - 2] == '/')
+ key.dptr[--key.dsize - 1] = '\0';
+ return key;
+}
+
+/*
+** dav_fs_lock_expired: return 1 (true) if the given timeout is in the past
+** or present (the lock has expired), or 0 (false) if in the future
+** (the lock has not yet expired).
+*/
+static int dav_fs_lock_expired(time_t expires)
+{
+ return expires != DAV_TIMEOUT_INFINITE && time(NULL) >= expires;
+}
+
+/*
+** dav_fs_save_lock_record: Saves the lock information specified in the
+** direct and indirect lock lists about path into the lock database.
+** If direct and indirect == NULL, the key is removed.
+*/
+static dav_error * dav_fs_save_lock_record(dav_lockdb *lockdb, apr_datum_t key,
+ dav_lock_discovery *direct,
+ dav_lock_indirect *indirect)
+{
+ dav_error *err;
+ apr_datum_t val = { 0 };
+ char *ptr;
+ dav_lock_discovery *dp = direct;
+ dav_lock_indirect *ip = indirect;
+
+#if DAV_DEBUG
+ if (lockdb->ro) {
+ return dav_new_error(lockdb->info->pool,
+ HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "INTERNAL DESIGN ERROR: the lockdb was opened "
+ "readonly, but an attempt to save locks was "
+ "performed.");
+ }
+#endif
+
+ if ((err = dav_fs_really_open_lockdb(lockdb)) != NULL) {
+ /* ### add a higher-level error? */
+ return err;
+ }
+
+ /* If nothing to save, delete key */
+ if (dp == NULL && ip == NULL) {
+ /* don't fail if the key is not present */
+ /* ### but what about other errors? */
+ (void) dav_dbm_delete(lockdb->info->db, key);
+ return NULL;
+ }
+
+ while(dp) {
+ val.dsize += dav_size_direct(dp);
+ dp = dp->next;
+ }
+ while(ip) {
+ val.dsize += dav_size_indirect(ip);
+ ip = ip->next;
+ }
+
+ /* ### can this be apr_palloc() ? */
+ /* ### hmmm.... investigate the use of a buffer here */
+ ptr = val.dptr = apr_pcalloc(lockdb->info->pool, val.dsize);
+ dp = direct;
+ ip = indirect;
+
+ while(dp) {
+ *ptr++ = DAV_LOCK_DIRECT; /* Direct lock - lock_discovery struct follows */
+ memcpy(ptr, dp, sizeof(dp->f)); /* Fixed portion of struct */
+ ptr += sizeof(dp->f);
+ memcpy(ptr, dp->locktoken, sizeof(*dp->locktoken));
+ ptr += sizeof(*dp->locktoken);
+ if (dp->owner == NULL) {
+ *ptr++ = '\0';
+ }
+ else {
+ memcpy(ptr, dp->owner, strlen(dp->owner) + 1);
+ ptr += strlen(dp->owner) + 1;
+ }
+ if (dp->auth_user == NULL) {
+ *ptr++ = '\0';
+ }
+ else {
+ memcpy(ptr, dp->auth_user, strlen(dp->auth_user) + 1);
+ ptr += strlen(dp->auth_user) + 1;
+ }
+
+ dp = dp->next;
+ }
+
+ while(ip) {
+ *ptr++ = DAV_LOCK_INDIRECT; /* Indirect lock prefix */
+ memcpy(ptr, ip->locktoken, sizeof(*ip->locktoken)); /* Locktoken */
+ ptr += sizeof(*ip->locktoken);
+ memcpy(ptr, &ip->timeout, sizeof(ip->timeout)); /* Expire time */
+ ptr += sizeof(ip->timeout);
+ memcpy(ptr, &ip->key.dsize, sizeof(ip->key.dsize)); /* Size of key */
+ ptr += sizeof(ip->key.dsize);
+ memcpy(ptr, ip->key.dptr, ip->key.dsize); /* Key data */
+ ptr += ip->key.dsize;
+ ip = ip->next;
+ }
+
+ if ((err = dav_dbm_store(lockdb->info->db, key, val)) != NULL) {
+ /* ### more details? add an error_id? */
+ return dav_push_error(lockdb->info->pool,
+ HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_LOCK_SAVE_LOCK,
+ "Could not save lock information.",
+ err);
+ }
+
+ return NULL;
+}
+
+/*
+** dav_load_lock_record: Reads lock information about key from lock db;
+** creates linked lists of the direct and indirect locks.
+**
+** If add_method = DAV_APPEND_LIST, the result will be appended to the
+** head of the direct and indirect lists supplied.
+**
+** Passive lock removal: If lock has timed out, it will not be returned.
+** ### How much "logging" does RFC 2518 require?
+*/
+static dav_error * dav_fs_load_lock_record(dav_lockdb *lockdb, apr_datum_t key,
+ int add_method,
+ dav_lock_discovery **direct,
+ dav_lock_indirect **indirect)
+{
+ apr_pool_t *p = lockdb->info->pool;
+ dav_error *err;
+ apr_size_t offset = 0;
+ int need_save = DAV_FALSE;
+ apr_datum_t val = { 0 };
+ dav_lock_discovery *dp;
+ dav_lock_indirect *ip;
+ dav_buffer buf = { 0 };
+
+ if (add_method != DAV_APPEND_LIST) {
+ *direct = NULL;
+ *indirect = NULL;
+ }
+
+ if ((err = dav_fs_really_open_lockdb(lockdb)) != NULL) {
+ /* ### add a higher-level error? */
+ return err;
+ }
+
+ /*
+ ** If we opened readonly and the db wasn't there, then there are no
+ ** locks for this resource. Just exit.
+ */
+ if (lockdb->info->db == NULL)
+ return NULL;
+
+ if ((err = dav_dbm_fetch(lockdb->info->db, key, &val)) != NULL)
+ return err;
+
+ if (!val.dsize)
+ return NULL;
+
+ while (offset < val.dsize) {
+ switch (*(val.dptr + offset++)) {
+ case DAV_LOCK_DIRECT:
+ /* Create and fill a dav_lock_discovery structure */
+
+ dp = apr_pcalloc(p, sizeof(*dp));
+ memcpy(dp, val.dptr + offset, sizeof(dp->f));
+ offset += sizeof(dp->f);
+ dp->locktoken = apr_pmemdup(p, val.dptr + offset, sizeof(*dp->locktoken));
+ offset += sizeof(*dp->locktoken);
+ if (*(val.dptr + offset) == '\0') {
+ ++offset;
+ }
+ else {
+ dp->owner = apr_pstrdup(p, val.dptr + offset);
+ offset += strlen(dp->owner) + 1;
+ }
+
+ if (*(val.dptr + offset) == '\0') {
+ ++offset;
+ }
+ else {
+ dp->auth_user = apr_pstrdup(p, val.dptr + offset);
+ offset += strlen(dp->auth_user) + 1;
+ }
+
+ if (!dav_fs_lock_expired(dp->f.timeout)) {
+ dp->next = *direct;
+ *direct = dp;
+ }
+ else {
+ need_save = DAV_TRUE;
+
+ /* Remove timed-out locknull fm .locknull list */
+ if (*key.dptr == DAV_TYPE_FNAME) {
+ const char *fname = key.dptr + 1;
+ apr_finfo_t finfo;
+ apr_status_t rv;
+
+ /* if we don't see the file, then it's a locknull */
+ rv = apr_stat(&finfo, fname, APR_FINFO_MIN | APR_FINFO_LINK, p);
+ if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) {
+ if ((err = dav_fs_remove_locknull_member(p, fname, &buf)) != NULL) {
+ /* ### push a higher-level description? */
+ return err;
+ }
+ }
+ }
+ }
+ break;
+
+ case DAV_LOCK_INDIRECT:
+ /* Create and fill a dav_lock_indirect structure */
+
+ ip = apr_pcalloc(p, sizeof(*ip));
+ ip->locktoken = apr_pmemdup(p, val.dptr + offset, sizeof(*ip->locktoken));
+ offset += sizeof(*ip->locktoken);
+ memcpy(&ip->timeout, val.dptr + offset, sizeof(ip->timeout));
+ offset += sizeof(ip->timeout);
+ memcpy(&ip->key.dsize, val.dptr + offset, sizeof(ip->key.dsize)); /* length of datum */
+ offset += sizeof(ip->key.dsize);
+ ip->key.dptr = apr_pmemdup(p, val.dptr + offset, ip->key.dsize);
+ offset += ip->key.dsize;
+
+ if (!dav_fs_lock_expired(ip->timeout)) {
+ ip->next = *indirect;
+ *indirect = ip;
+ }
+ else {
+ need_save = DAV_TRUE;
+ /* A locknull resource will never be locked indirectly */
+ }
+
+ break;
+
+ default:
+ dav_dbm_freedatum(lockdb->info->db, val);
+
+ /* ### should use a computed_desc and insert corrupt token data */
+ --offset;
+ return dav_new_error(p,
+ HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_LOCK_CORRUPT_DB, 0,
+ apr_psprintf(p,
+ "The lock database was found to "
+ "be corrupt. offset %"
+ APR_SIZE_T_FMT ", c=%02x",
+ offset, val.dptr[offset]));
+ }
+ }
+
+ dav_dbm_freedatum(lockdb->info->db, val);
+
+ /* Clean up this record if we found expired locks */
+ /*
+ ** ### shouldn't do this if we've been opened READONLY. elide the
+ ** ### timed-out locks from the response, but don't save that info back
+ */
+ if (need_save == DAV_TRUE) {
+ return dav_fs_save_lock_record(lockdb, key, *direct, *indirect);
+ }
+
+ return NULL;
+}
+
+/* resolve <indirect>, returning <*direct> */
+static dav_error * dav_fs_resolve(dav_lockdb *lockdb,
+ dav_lock_indirect *indirect,
+ dav_lock_discovery **direct,
+ dav_lock_discovery **ref_dp,
+ dav_lock_indirect **ref_ip)
+{
+ dav_error *err;
+ dav_lock_discovery *dir;
+ dav_lock_indirect *ind;
+
+ if ((err = dav_fs_load_lock_record(lockdb, indirect->key,
+ DAV_CREATE_LIST,
+ &dir, &ind)) != NULL) {
+ /* ### insert a higher-level description? */
+ return err;
+ }
+ if (ref_dp != NULL) {
+ *ref_dp = dir;
+ *ref_ip = ind;
+ }
+
+ for (; dir != NULL; dir = dir->next) {
+ if (!dav_compare_locktoken(indirect->locktoken, dir->locktoken)) {
+ *direct = dir;
+ return NULL;
+ }
+ }
+
+ /* No match found (but we should have found one!) */
+
+ /* ### use a different description and/or error ID? */
+ return dav_new_error(lockdb->info->pool,
+ HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_LOCK_CORRUPT_DB, 0,
+ "The lock database was found to be corrupt. "
+ "An indirect lock's direct lock could not "
+ "be found.");
+}
+
+/* ---------------------------------------------------------------
+**
+** Property-related lock functions
+**
+*/
+
+/*
+** dav_fs_get_supportedlock: Returns a static string for all supportedlock
+** properties. I think we save more returning a static string than
+** constructing it every time, though it might look cleaner.
+*/
+static const char *dav_fs_get_supportedlock(const dav_resource *resource)
+{
+ static const char supported[] = DEBUG_CR
+ "<D:lockentry>" DEBUG_CR
+ "<D:lockscope><D:exclusive/></D:lockscope>" DEBUG_CR
+ "<D:locktype><D:write/></D:locktype>" DEBUG_CR
+ "</D:lockentry>" DEBUG_CR
+ "<D:lockentry>" DEBUG_CR
+ "<D:lockscope><D:shared/></D:lockscope>" DEBUG_CR
+ "<D:locktype><D:write/></D:locktype>" DEBUG_CR
+ "</D:lockentry>" DEBUG_CR;
+
+ return supported;
+}
+
+/* ---------------------------------------------------------------
+**
+** General lock functions
+**
+*/
+
+/* ---------------------------------------------------------------
+**
+** Functions dealing with lock-null resources
+**
+*/
+
+/*
+** dav_fs_load_locknull_list: Returns a dav_buffer dump of the locknull file
+** for the given directory.
+*/
+static dav_error * dav_fs_load_locknull_list(apr_pool_t *p, const char *dirpath,
+ dav_buffer *pbuf)
+{
+ apr_finfo_t finfo;
+ apr_file_t *file = NULL;
+ dav_error *err = NULL;
+ apr_size_t amt;
+ apr_status_t rv;
+
+ dav_buffer_init(p, pbuf, dirpath);
+
+ if (pbuf->buf[pbuf->cur_len - 1] == '/')
+ pbuf->buf[--pbuf->cur_len] = '\0';
+
+ dav_buffer_place(p, pbuf, "/" DAV_FS_STATE_DIR "/" DAV_FS_LOCK_NULL_FILE);
+
+ /* reset this in case we leave w/o reading into the buffer */
+ pbuf->cur_len = 0;
+
+ if (apr_file_open(&file, pbuf->buf, APR_READ | APR_BINARY, APR_OS_DEFAULT,
+ p) != APR_SUCCESS) {
+ return NULL;
+ }
+
+ rv = apr_file_info_get(&finfo, APR_FINFO_SIZE, file);
+ if (rv != APR_SUCCESS) {
+ err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ apr_psprintf(p,
+ "Opened but could not stat file %s",
+ pbuf->buf));
+ goto loaderror;
+ }
+
+ if (finfo.size != (apr_size_t)finfo.size) {
+ err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ apr_psprintf(p,
+ "Opened but rejected huge file %s",
+ pbuf->buf));
+ goto loaderror;
+ }
+
+ amt = (apr_size_t)finfo.size;
+ dav_set_bufsize(p, pbuf, amt);
+ if ((rv = apr_file_read(file, pbuf->buf, &amt)) != APR_SUCCESS
+ || amt != finfo.size) {
+ err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ apr_psprintf(p,
+ "Failure reading locknull file "
+ "for %s", dirpath));
+
+ /* just in case the caller disregards the returned error */
+ pbuf->cur_len = 0;
+ goto loaderror;
+ }
+
+ loaderror:
+ apr_file_close(file);
+ return err;
+}
+
+/*
+** dav_fs_save_locknull_list: Saves contents of pbuf into the
+** locknull file for dirpath.
+*/
+static dav_error * dav_fs_save_locknull_list(apr_pool_t *p, const char *dirpath,
+ dav_buffer *pbuf)
+{
+ const char *pathname;
+ apr_file_t *file = NULL;
+ dav_error *err = NULL;
+ apr_size_t amt;
+ apr_status_t rv;
+
+ if (pbuf->buf == NULL)
+ return NULL;
+
+ dav_fs_ensure_state_dir(p, dirpath);
+ pathname = apr_pstrcat(p,
+ dirpath,
+ dirpath[strlen(dirpath) - 1] == '/' ? "" : "/",
+ DAV_FS_STATE_DIR "/" DAV_FS_LOCK_NULL_FILE,
+ NULL);
+
+ if (pbuf->cur_len == 0) {
+ /* delete the file if cur_len == 0 */
+ if ((rv = apr_file_remove(pathname, p)) != APR_SUCCESS) {
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ apr_psprintf(p,
+ "Error removing %s", pathname));
+ }
+ return NULL;
+ }
+
+ if ((rv = apr_file_open(&file, pathname,
+ APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BINARY,
+ APR_OS_DEFAULT, p)) != APR_SUCCESS) {
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ apr_psprintf(p,
+ "Error opening %s for writing",
+ pathname));
+ }
+
+ amt = pbuf->cur_len;
+ if ((rv = apr_file_write_full(file, pbuf->buf, amt, &amt)) != APR_SUCCESS
+ || amt != pbuf->cur_len) {
+ err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ apr_psprintf(p,
+ "Error writing %" APR_SIZE_T_FMT
+ " bytes to %s",
+ pbuf->cur_len, pathname));
+ }
+
+ apr_file_close(file);
+ return err;
+}
+
+/*
+** dav_fs_remove_locknull_member: Removes filename from the locknull list
+** for directory path.
+*/
+static dav_error * dav_fs_remove_locknull_member(apr_pool_t *p,
+ const char *filename,
+ dav_buffer *pbuf)
+{
+ dav_error *err;
+ apr_size_t len;
+ apr_size_t scanlen;
+ char *scan;
+ const char *scanend;
+ char *dirpath = apr_pstrdup(p, filename);
+ char *fname = strrchr(dirpath, '/');
+ int dirty = 0;
+
+ if (fname != NULL)
+ *fname++ = '\0';
+ else
+ fname = dirpath;
+ len = strlen(fname) + 1;
+
+ if ((err = dav_fs_load_locknull_list(p, dirpath, pbuf)) != NULL) {
+ /* ### add a higher level description? */
+ return err;
+ }
+
+ for (scan = pbuf->buf, scanend = scan + pbuf->cur_len;
+ scan < scanend;
+ scan += scanlen) {
+ scanlen = strlen(scan) + 1;
+ if (len == scanlen && memcmp(fname, scan, scanlen) == 0) {
+ pbuf->cur_len -= scanlen;
+ memmove(scan, scan + scanlen, scanend - (scan + scanlen));
+ dirty = 1;
+ break;
+ }
+ }
+
+ if (dirty) {
+ if ((err = dav_fs_save_locknull_list(p, dirpath, pbuf)) != NULL) {
+ /* ### add a higher level description? */
+ return err;
+ }
+ }
+
+ return NULL;
+}
+
+/* Note: used by dav_fs_repos.c */
+dav_error * dav_fs_get_locknull_members(
+ const dav_resource *resource,
+ dav_buffer *pbuf)
+{
+ const char *dirpath;
+
+ /* ### should test this result value... */
+ (void) dav_fs_dir_file_name(resource, &dirpath, NULL);
+ return dav_fs_load_locknull_list(dav_fs_pool(resource), dirpath, pbuf);
+}
+
+/* ### fold into append_lock? */
+/* ### take an optional buf parameter? */
+static dav_error * dav_fs_add_locknull_state(
+ dav_lockdb *lockdb,
+ const dav_resource *resource)
+{
+ dav_buffer buf = { 0 };
+ apr_pool_t *p = lockdb->info->pool;
+ const char *dirpath;
+ const char *fname;
+ dav_error *err;
+
+ /* ### should test this result value... */
+ (void) dav_fs_dir_file_name(resource, &dirpath, &fname);
+
+ if ((err = dav_fs_load_locknull_list(p, dirpath, &buf)) != NULL) {
+ return dav_push_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
+ "Could not load .locknull file.", err);
+ }
+
+ dav_buffer_append(p, &buf, fname);
+ buf.cur_len++; /* we want the null-term here */
+
+ if ((err = dav_fs_save_locknull_list(p, dirpath, &buf)) != NULL) {
+ return dav_push_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
+ "Could not save .locknull file.", err);
+ }
+
+ return NULL;
+}
+
+/*
+** dav_fs_remove_locknull_state: Given a request, check to see if r->filename
+** is/was a lock-null resource. If so, return it to an existent state, i.e.
+** remove it from the list in the appropriate .DAV/locknull file.
+*/
+static dav_error * dav_fs_remove_locknull_state(
+ dav_lockdb *lockdb,
+ const dav_resource *resource)
+{
+ dav_buffer buf = { 0 };
+ dav_error *err;
+ apr_pool_t *p = lockdb->info->pool;
+ const char *pathname = dav_fs_pathname(resource);
+
+ if ((err = dav_fs_remove_locknull_member(p, pathname, &buf)) != NULL) {
+ /* ### add a higher-level description? */
+ return err;
+ }
+
+ return NULL;
+}
+
+static dav_error * dav_fs_create_lock(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ dav_lock **lock)
+{
+ apr_datum_t key;
+
+ key = dav_fs_build_key(lockdb->info->pool, resource);
+
+ *lock = dav_fs_alloc_lock(lockdb,
+ key,
+ NULL);
+
+ (*lock)->is_locknull = !resource->exists;
+
+ return NULL;
+}
+
+static dav_error * dav_fs_get_locks(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int calltype,
+ dav_lock **locks)
+{
+ apr_pool_t *p = lockdb->info->pool;
+ apr_datum_t key;
+ dav_error *err;
+ dav_lock *lock = NULL;
+ dav_lock *newlock;
+ dav_lock_discovery *dp;
+ dav_lock_indirect *ip;
+
+#if DAV_DEBUG
+ if (calltype == DAV_GETLOCKS_COMPLETE) {
+ return dav_new_error(lockdb->info->pool,
+ HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "INTERNAL DESIGN ERROR: DAV_GETLOCKS_COMPLETE "
+ "is not yet supported");
+ }
+#endif
+
+ key = dav_fs_build_key(p, resource);
+ if ((err = dav_fs_load_lock_record(lockdb, key, DAV_CREATE_LIST,
+ &dp, &ip)) != NULL) {
+ /* ### push a higher-level desc? */
+ return err;
+ }
+
+ /* copy all direct locks to the result list */
+ for (; dp != NULL; dp = dp->next) {
+ newlock = dav_fs_alloc_lock(lockdb, key, dp->locktoken);
+ newlock->is_locknull = !resource->exists;
+ newlock->scope = dp->f.scope;
+ newlock->type = dp->f.type;
+ newlock->depth = dp->f.depth;
+ newlock->timeout = dp->f.timeout;
+ newlock->owner = dp->owner;
+ newlock->auth_user = dp->auth_user;
+
+ /* hook into the result list */
+ newlock->next = lock;
+ lock = newlock;
+ }
+
+ /* copy all the indirect locks to the result list. resolve as needed. */
+ for (; ip != NULL; ip = ip->next) {
+ newlock = dav_fs_alloc_lock(lockdb, ip->key, ip->locktoken);
+ newlock->is_locknull = !resource->exists;
+
+ if (calltype == DAV_GETLOCKS_RESOLVED) {
+ if ((err = dav_fs_resolve(lockdb, ip, &dp, NULL, NULL)) != NULL) {
+ /* ### push a higher-level desc? */
+ return err;
+ }
+
+ newlock->scope = dp->f.scope;
+ newlock->type = dp->f.type;
+ newlock->depth = dp->f.depth;
+ newlock->timeout = dp->f.timeout;
+ newlock->owner = dp->owner;
+ newlock->auth_user = dp->auth_user;
+ }
+ else {
+ /* DAV_GETLOCKS_PARTIAL */
+ newlock->rectype = DAV_LOCKREC_INDIRECT_PARTIAL;
+ }
+
+ /* hook into the result list */
+ newlock->next = lock;
+ lock = newlock;
+ }
+
+ *locks = lock;
+ return NULL;
+}
+
+static dav_error * dav_fs_find_lock(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ const dav_locktoken *locktoken,
+ int partial_ok,
+ dav_lock **lock)
+{
+ dav_error *err;
+ apr_datum_t key;
+ dav_lock_discovery *dp;
+ dav_lock_indirect *ip;
+
+ *lock = NULL;
+
+ key = dav_fs_build_key(lockdb->info->pool, resource);
+ if ((err = dav_fs_load_lock_record(lockdb, key, DAV_CREATE_LIST,
+ &dp, &ip)) != NULL) {
+ /* ### push a higher-level desc? */
+ return err;
+ }
+
+ for (; dp != NULL; dp = dp->next) {
+ if (!dav_compare_locktoken(locktoken, dp->locktoken)) {
+ *lock = dav_fs_alloc_lock(lockdb, key, locktoken);
+ (*lock)->is_locknull = !resource->exists;
+ (*lock)->scope = dp->f.scope;
+ (*lock)->type = dp->f.type;
+ (*lock)->depth = dp->f.depth;
+ (*lock)->timeout = dp->f.timeout;
+ (*lock)->owner = dp->owner;
+ (*lock)->auth_user = dp->auth_user;
+ return NULL;
+ }
+ }
+
+ for (; ip != NULL; ip = ip->next) {
+ if (!dav_compare_locktoken(locktoken, ip->locktoken)) {
+ *lock = dav_fs_alloc_lock(lockdb, ip->key, locktoken);
+ (*lock)->is_locknull = !resource->exists;
+
+ /* ### nobody uses the resolving right now! */
+ if (partial_ok) {
+ (*lock)->rectype = DAV_LOCKREC_INDIRECT_PARTIAL;
+ }
+ else {
+ (*lock)->rectype = DAV_LOCKREC_INDIRECT;
+ if ((err = dav_fs_resolve(lockdb, ip, &dp,
+ NULL, NULL)) != NULL) {
+ /* ### push a higher-level desc? */
+ return err;
+ }
+ (*lock)->scope = dp->f.scope;
+ (*lock)->type = dp->f.type;
+ (*lock)->depth = dp->f.depth;
+ (*lock)->timeout = dp->f.timeout;
+ (*lock)->owner = dp->owner;
+ (*lock)->auth_user = dp->auth_user;
+ }
+ return NULL;
+ }
+ }
+
+ return NULL;
+}
+
+static dav_error * dav_fs_has_locks(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int *locks_present)
+{
+ dav_error *err;
+ apr_datum_t key;
+
+ *locks_present = 0;
+
+ if ((err = dav_fs_really_open_lockdb(lockdb)) != NULL) {
+ /* ### insert a higher-level error description */
+ return err;
+ }
+
+ /*
+ ** If we opened readonly and the db wasn't there, then there are no
+ ** locks for this resource. Just exit.
+ */
+ if (lockdb->info->db == NULL)
+ return NULL;
+
+ key = dav_fs_build_key(lockdb->info->pool, resource);
+
+ *locks_present = dav_dbm_exists(lockdb->info->db, key);
+
+ return NULL;
+}
+
+static dav_error * dav_fs_append_locks(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int make_indirect,
+ const dav_lock *lock)
+{
+ apr_pool_t *p = lockdb->info->pool;
+ dav_error *err;
+ dav_lock_indirect *ip;
+ dav_lock_discovery *dp;
+ apr_datum_t key;
+
+ key = dav_fs_build_key(lockdb->info->pool, resource);
+ if ((err = dav_fs_load_lock_record(lockdb, key, 0, &dp, &ip)) != NULL) {
+ /* ### maybe add in a higher-level description */
+ return err;
+ }
+
+ /*
+ ** ### when we store the lock more directly, we need to update
+ ** ### lock->rectype and lock->is_locknull
+ */
+
+ if (make_indirect) {
+ for (; lock != NULL; lock = lock->next) {
+
+ /* ### this works for any <lock> rectype */
+ dav_lock_indirect *newi = apr_pcalloc(p, sizeof(*newi));
+
+ /* ### shut off the const warning for now */
+ newi->locktoken = (dav_locktoken *)lock->locktoken;
+ newi->timeout = lock->timeout;
+ newi->key = lock->info->key;
+ newi->next = ip;
+ ip = newi;
+ }
+ }
+ else {
+ for (; lock != NULL; lock = lock->next) {
+ /* create and link in the right kind of lock */
+
+ if (lock->rectype == DAV_LOCKREC_DIRECT) {
+ dav_lock_discovery *newd = apr_pcalloc(p, sizeof(*newd));
+
+ newd->f.scope = lock->scope;
+ newd->f.type = lock->type;
+ newd->f.depth = lock->depth;
+ newd->f.timeout = lock->timeout;
+ /* ### shut off the const warning for now */
+ newd->locktoken = (dav_locktoken *)lock->locktoken;
+ newd->owner = lock->owner;
+ newd->auth_user = lock->auth_user;
+ newd->next = dp;
+ dp = newd;
+ }
+ else {
+ /* DAV_LOCKREC_INDIRECT(_PARTIAL) */
+
+ dav_lock_indirect *newi = apr_pcalloc(p, sizeof(*newi));
+
+ /* ### shut off the const warning for now */
+ newi->locktoken = (dav_locktoken *)lock->locktoken;
+ newi->key = lock->info->key;
+ newi->next = ip;
+ ip = newi;
+ }
+ }
+ }
+
+ if ((err = dav_fs_save_lock_record(lockdb, key, dp, ip)) != NULL) {
+ /* ### maybe add a higher-level description */
+ return err;
+ }
+
+ /* we have a special list for recording locknull resources */
+ /* ### ack! this can add two copies to the locknull list */
+ if (!resource->exists
+ && (err = dav_fs_add_locknull_state(lockdb, resource)) != NULL) {
+ /* ### maybe add a higher-level description */
+ return err;
+ }
+
+ return NULL;
+}
+
+static dav_error * dav_fs_remove_lock(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ const dav_locktoken *locktoken)
+{
+ dav_error *err;
+ dav_buffer buf = { 0 };
+ dav_lock_discovery *dh = NULL;
+ dav_lock_indirect *ih = NULL;
+ apr_datum_t key;
+
+ key = dav_fs_build_key(lockdb->info->pool, resource);
+
+ if (locktoken != NULL) {
+ dav_lock_discovery *dp;
+ dav_lock_discovery *dprev = NULL;
+ dav_lock_indirect *ip;
+ dav_lock_indirect *iprev = NULL;
+
+ if ((err = dav_fs_load_lock_record(lockdb, key, DAV_CREATE_LIST,
+ &dh, &ih)) != NULL) {
+ /* ### maybe add a higher-level description */
+ return err;
+ }
+
+ for (dp = dh; dp != NULL; dp = dp->next) {
+ if (dav_compare_locktoken(locktoken, dp->locktoken) == 0) {
+ if (dprev)
+ dprev->next = dp->next;
+ else
+ dh = dh->next;
+ }
+ dprev = dp;
+ }
+
+ for (ip = ih; ip != NULL; ip = ip->next) {
+ if (dav_compare_locktoken(locktoken, ip->locktoken) == 0) {
+ if (iprev)
+ iprev->next = ip->next;
+ else
+ ih = ih->next;
+ }
+ iprev = ip;
+ }
+
+ }
+
+ /* save the modified locks, or remove all locks (dh=ih=NULL). */
+ if ((err = dav_fs_save_lock_record(lockdb, key, dh, ih)) != NULL) {
+ /* ### maybe add a higher-level description */
+ return err;
+ }
+
+ /*
+ ** If this resource is a locknull resource AND no more locks exist,
+ ** then remove the locknull member.
+ **
+ ** Note: remove_locknull_state() attempts to convert a locknull member
+ ** to a real member. In this case, all locks are gone, so the
+ ** locknull resource returns to the null state (ie. doesn't exist),
+ ** so there is no need to update the lockdb (and it won't find
+ ** any because a precondition is that none exist).
+ */
+ if (!resource->exists && dh == NULL && ih == NULL
+ && (err = dav_fs_remove_locknull_member(lockdb->info->pool,
+ dav_fs_pathname(resource),
+ &buf)) != NULL) {
+ /* ### maybe add a higher-level description */
+ return err;
+ }
+
+ return NULL;
+}
+
+static int dav_fs_do_refresh(dav_lock_discovery *dp,
+ const dav_locktoken_list *ltl,
+ time_t new_time)
+{
+ int dirty = 0;
+
+ for (; ltl != NULL; ltl = ltl->next) {
+ if (dav_compare_locktoken(dp->locktoken, ltl->locktoken) == 0)
+ {
+ dp->f.timeout = new_time;
+ dirty = 1;
+ break;
+ }
+ }
+
+ return dirty;
+}
+
+static dav_error * dav_fs_refresh_locks(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ const dav_locktoken_list *ltl,
+ time_t new_time,
+ dav_lock **locks)
+{
+ dav_error *err;
+ apr_datum_t key;
+ dav_lock_discovery *dp;
+ dav_lock_discovery *dp_scan;
+ dav_lock_indirect *ip;
+ int dirty = 0;
+ dav_lock *newlock;
+
+ *locks = NULL;
+
+ key = dav_fs_build_key(lockdb->info->pool, resource);
+ if ((err = dav_fs_load_lock_record(lockdb, key, DAV_CREATE_LIST,
+ &dp, &ip)) != NULL) {
+ /* ### maybe add in a higher-level description */
+ return err;
+ }
+
+ /* ### we should be refreshing direct AND (resolved) indirect locks! */
+
+ /* refresh all of the direct locks on this resource */
+ for (dp_scan = dp; dp_scan != NULL; dp_scan = dp_scan->next) {
+ if (dav_fs_do_refresh(dp_scan, ltl, new_time)) {
+ /* the lock was refreshed. return the lock. */
+ newlock = dav_fs_alloc_lock(lockdb, key, dp_scan->locktoken);
+ newlock->is_locknull = !resource->exists;
+ newlock->scope = dp_scan->f.scope;
+ newlock->type = dp_scan->f.type;
+ newlock->depth = dp_scan->f.depth;
+ newlock->timeout = dp_scan->f.timeout;
+ newlock->owner = dp_scan->owner;
+ newlock->auth_user = dp_scan->auth_user;
+
+ newlock->next = *locks;
+ *locks = newlock;
+
+ dirty = 1;
+ }
+ }
+
+ /* if we refreshed any locks, then save them back. */
+ if (dirty
+ && (err = dav_fs_save_lock_record(lockdb, key, dp, ip)) != NULL) {
+ /* ### maybe add in a higher-level description */
+ return err;
+ }
+
+ /* for each indirect lock, find its direct lock and refresh it. */
+ for (; ip != NULL; ip = ip->next) {
+ dav_lock_discovery *ref_dp;
+ dav_lock_indirect *ref_ip;
+
+ if ((err = dav_fs_resolve(lockdb, ip, &dp_scan,
+ &ref_dp, &ref_ip)) != NULL) {
+ /* ### push a higher-level desc? */
+ return err;
+ }
+ if (dav_fs_do_refresh(dp_scan, ltl, new_time)) {
+ /* the lock was refreshed. return the lock. */
+ newlock = dav_fs_alloc_lock(lockdb, ip->key, dp_scan->locktoken);
+ newlock->is_locknull = !resource->exists;
+ newlock->scope = dp_scan->f.scope;
+ newlock->type = dp_scan->f.type;
+ newlock->depth = dp_scan->f.depth;
+ newlock->timeout = dp_scan->f.timeout;
+ newlock->owner = dp_scan->owner;
+ newlock->auth_user = dp_scan->auth_user;
+
+ newlock->next = *locks;
+ *locks = newlock;
+
+ /* save the (resolved) direct lock back */
+ if ((err = dav_fs_save_lock_record(lockdb, ip->key, ref_dp,
+ ref_ip)) != NULL) {
+ /* ### push a higher-level desc? */
+ return err;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+
+const dav_hooks_locks dav_hooks_locks_fs =
+{
+ dav_fs_get_supportedlock,
+ dav_fs_parse_locktoken,
+ dav_fs_format_locktoken,
+ dav_fs_compare_locktoken,
+ dav_fs_open_lockdb,
+ dav_fs_close_lockdb,
+ dav_fs_remove_locknull_state,
+ dav_fs_create_lock,
+ dav_fs_get_locks,
+ dav_fs_find_lock,
+ dav_fs_has_locks,
+ dav_fs_append_locks,
+ dav_fs_remove_lock,
+ dav_fs_refresh_locks,
+ NULL, /* lookup_resource */
+
+ NULL /* ctx */
+};
diff --git a/modules/dav/fs/mod_dav_fs.c b/modules/dav/fs/mod_dav_fs.c
new file mode 100644
index 0000000..addfd7e
--- /dev/null
+++ b/modules/dav/fs/mod_dav_fs.c
@@ -0,0 +1,108 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "apr_strings.h"
+
+#include "mod_dav.h"
+#include "repos.h"
+
+/* per-server configuration */
+typedef struct {
+ const char *lockdb_path;
+
+} dav_fs_server_conf;
+
+extern module AP_MODULE_DECLARE_DATA dav_fs_module;
+
+const char *dav_get_lockdb_path(const request_rec *r)
+{
+ dav_fs_server_conf *conf;
+
+ conf = ap_get_module_config(r->server->module_config, &dav_fs_module);
+ return conf->lockdb_path;
+}
+
+static void *dav_fs_create_server_config(apr_pool_t *p, server_rec *s)
+{
+ return apr_pcalloc(p, sizeof(dav_fs_server_conf));
+}
+
+static void *dav_fs_merge_server_config(apr_pool_t *p,
+ void *base, void *overrides)
+{
+ dav_fs_server_conf *parent = base;
+ dav_fs_server_conf *child = overrides;
+ dav_fs_server_conf *newconf;
+
+ newconf = apr_pcalloc(p, sizeof(*newconf));
+
+ newconf->lockdb_path =
+ child->lockdb_path ? child->lockdb_path : parent->lockdb_path;
+
+ return newconf;
+}
+
+/*
+ * Command handler for the DAVLockDB directive, which is TAKE1
+ */
+static const char *dav_fs_cmd_davlockdb(cmd_parms *cmd, void *config,
+ const char *arg1)
+{
+ dav_fs_server_conf *conf;
+ conf = ap_get_module_config(cmd->server->module_config,
+ &dav_fs_module);
+ conf->lockdb_path = ap_server_root_relative(cmd->pool, arg1);
+
+ if (!conf->lockdb_path) {
+ return apr_pstrcat(cmd->pool, "Invalid DAVLockDB path ",
+ arg1, NULL);
+ }
+
+ return NULL;
+}
+
+static const command_rec dav_fs_cmds[] =
+{
+ /* per server */
+ AP_INIT_TAKE1("DAVLockDB", dav_fs_cmd_davlockdb, NULL, RSRC_CONF,
+ "specify a lock database"),
+
+ { NULL }
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+ dav_hook_gather_propsets(dav_fs_gather_propsets, NULL, NULL,
+ APR_HOOK_MIDDLE);
+ dav_hook_find_liveprop(dav_fs_find_liveprop, NULL, NULL, APR_HOOK_MIDDLE);
+ dav_hook_insert_all_liveprops(dav_fs_insert_all_liveprops, NULL, NULL,
+ APR_HOOK_MIDDLE);
+
+ dav_fs_register(p);
+}
+
+AP_DECLARE_MODULE(dav_fs) =
+{
+ STANDARD20_MODULE_STUFF,
+ NULL, /* dir config creater */
+ NULL, /* dir merger --- default is to override */
+ dav_fs_create_server_config, /* server config */
+ dav_fs_merge_server_config, /* merge server config */
+ dav_fs_cmds, /* command table */
+ register_hooks, /* register hooks */
+};
diff --git a/modules/dav/fs/mod_dav_fs.dep b/modules/dav/fs/mod_dav_fs.dep
new file mode 100644
index 0000000..0d67f04
--- /dev/null
+++ b/modules/dav/fs/mod_dav_fs.dep
@@ -0,0 +1,203 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_dav_fs.mak
+
+.\dbm.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\http_config.h"\
+ "..\..\..\include\http_log.h"\
+ "..\..\..\include\http_main.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\mod_dav.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\util_cfgtree.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_xml.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apr_xml.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ ".\repos.h"\
+
+
+.\lock.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\http_config.h"\
+ "..\..\..\include\http_log.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\mod_dav.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\util_cfgtree.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_xml.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apr_xml.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ ".\repos.h"\
+
+
+.\mod_dav_fs.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\http_config.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\mod_dav.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\util_cfgtree.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_xml.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apr_xml.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ ".\repos.h"\
+
+
+.\repos.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\http_config.h"\
+ "..\..\..\include\http_log.h"\
+ "..\..\..\include\http_protocol.h"\
+ "..\..\..\include\http_request.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\mod_dav.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\util_cfgtree.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_xml.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apr_xml.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ ".\repos.h"\
+
+
+..\..\..\build\win32\httpd.rc : \
+ "..\..\..\include\ap_release.h"\
+
diff --git a/modules/dav/fs/mod_dav_fs.dsp b/modules/dav/fs/mod_dav_fs.dsp
new file mode 100644
index 0000000..fbfc1e4
--- /dev/null
+++ b/modules/dav/fs/mod_dav_fs.dsp
@@ -0,0 +1,135 @@
+# Microsoft Developer Studio Project File - Name="mod_dav_fs" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_dav_fs - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_dav_fs.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_dav_fs.mak" CFG="mod_dav_fs - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_dav_fs - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_dav_fs - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_dav_fs - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_dav_fs_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_dav_fs.res" /i "../../../include" /i "../../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_dav_fs.so" /d LONG_NAME="dav_fs_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:".\Release\mod_dav_fs.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav_fs.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_dav_fs.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav_fs.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_dav_fs.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_dav_fs - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_dav_fs_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_dav_fs.res" /i "../../../include" /i "../../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_dav_fs.so" /d LONG_NAME="dav_fs_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_dav_fs.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav_fs.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_dav_fs.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav_fs.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_dav_fs.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_dav_fs - Win32 Release"
+# Name "mod_dav_fs - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\dbm.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\lock.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\mod_dav_fs.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\repos.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd"
+# Begin Source File
+
+SOURCE=.\repos.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/dav/fs/mod_dav_fs.mak b/modules/dav/fs/mod_dav_fs.mak
new file mode 100644
index 0000000..5baff67
--- /dev/null
+++ b/modules/dav/fs/mod_dav_fs.mak
@@ -0,0 +1,407 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_dav_fs.dsp
+!IF "$(CFG)" == ""
+CFG=mod_dav_fs - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_dav_fs - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_dav_fs - Win32 Release" && "$(CFG)" != "mod_dav_fs - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_dav_fs.mak" CFG="mod_dav_fs - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_dav_fs - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_dav_fs - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_dav_fs - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_dav_fs.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_dav - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_dav_fs.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_dav - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\dbm.obj"
+ -@erase "$(INTDIR)\lock.obj"
+ -@erase "$(INTDIR)\mod_dav_fs.obj"
+ -@erase "$(INTDIR)\mod_dav_fs.res"
+ -@erase "$(INTDIR)\mod_dav_fs_src.idb"
+ -@erase "$(INTDIR)\mod_dav_fs_src.pdb"
+ -@erase "$(INTDIR)\repos.obj"
+ -@erase "$(OUTDIR)\mod_dav_fs.exp"
+ -@erase "$(OUTDIR)\mod_dav_fs.lib"
+ -@erase "$(OUTDIR)\mod_dav_fs.pdb"
+ -@erase "$(OUTDIR)\mod_dav_fs.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_dav_fs_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_dav_fs.res" /i "../../../include" /i "../../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_dav_fs.so" /d LONG_NAME="dav_fs_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_dav_fs.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_dav_fs.pdb" /debug /out:"$(OUTDIR)\mod_dav_fs.so" /implib:"$(OUTDIR)\mod_dav_fs.lib" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav_fs.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\dbm.obj" \
+ "$(INTDIR)\lock.obj" \
+ "$(INTDIR)\mod_dav_fs.obj" \
+ "$(INTDIR)\repos.obj" \
+ "$(INTDIR)\mod_dav_fs.res" \
+ "..\..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\..\Release\libhttpd.lib" \
+ "..\main\Release\mod_dav.lib"
+
+"$(OUTDIR)\mod_dav_fs.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_dav_fs.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_dav_fs.so"
+ if exist .\Release\mod_dav_fs.so.manifest mt.exe -manifest .\Release\mod_dav_fs.so.manifest -outputresource:.\Release\mod_dav_fs.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_dav_fs - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_dav_fs.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_dav - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_dav_fs.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_dav - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\dbm.obj"
+ -@erase "$(INTDIR)\lock.obj"
+ -@erase "$(INTDIR)\mod_dav_fs.obj"
+ -@erase "$(INTDIR)\mod_dav_fs.res"
+ -@erase "$(INTDIR)\mod_dav_fs_src.idb"
+ -@erase "$(INTDIR)\mod_dav_fs_src.pdb"
+ -@erase "$(INTDIR)\repos.obj"
+ -@erase "$(OUTDIR)\mod_dav_fs.exp"
+ -@erase "$(OUTDIR)\mod_dav_fs.lib"
+ -@erase "$(OUTDIR)\mod_dav_fs.pdb"
+ -@erase "$(OUTDIR)\mod_dav_fs.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_dav_fs_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_dav_fs.res" /i "../../../include" /i "../../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_dav_fs.so" /d LONG_NAME="dav_fs_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_dav_fs.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_dav_fs.pdb" /debug /out:"$(OUTDIR)\mod_dav_fs.so" /implib:"$(OUTDIR)\mod_dav_fs.lib" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav_fs.so
+LINK32_OBJS= \
+ "$(INTDIR)\dbm.obj" \
+ "$(INTDIR)\lock.obj" \
+ "$(INTDIR)\mod_dav_fs.obj" \
+ "$(INTDIR)\repos.obj" \
+ "$(INTDIR)\mod_dav_fs.res" \
+ "..\..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\..\Debug\libhttpd.lib" \
+ "..\main\Debug\mod_dav.lib"
+
+"$(OUTDIR)\mod_dav_fs.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_dav_fs.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_dav_fs.so"
+ if exist .\Debug\mod_dav_fs.so.manifest mt.exe -manifest .\Debug\mod_dav_fs.so.manifest -outputresource:.\Debug\mod_dav_fs.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_dav_fs.dep")
+!INCLUDE "mod_dav_fs.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_dav_fs.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_dav_fs - Win32 Release" || "$(CFG)" == "mod_dav_fs - Win32 Debug"
+SOURCE=.\dbm.c
+
+"$(INTDIR)\dbm.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\lock.c
+
+"$(INTDIR)\lock.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\mod_dav_fs.c
+
+"$(INTDIR)\mod_dav_fs.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\repos.c
+
+"$(INTDIR)\repos.obj" : $(SOURCE) "$(INTDIR)"
+
+
+!IF "$(CFG)" == "mod_dav_fs - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\dav\fs"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\dav\fs"
+
+!ELSEIF "$(CFG)" == "mod_dav_fs - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\dav\fs"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\dav\fs"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_dav_fs - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\dav\fs"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\dav\fs"
+
+!ELSEIF "$(CFG)" == "mod_dav_fs - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\dav\fs"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\dav\fs"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_dav_fs - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\dav\fs"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\dav\fs"
+
+!ELSEIF "$(CFG)" == "mod_dav_fs - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\dav\fs"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\dav\fs"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_dav_fs - Win32 Release"
+
+"mod_dav - Win32 Release" :
+ cd ".\..\main"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_dav.mak" CFG="mod_dav - Win32 Release"
+ cd "..\fs"
+
+"mod_dav - Win32 ReleaseCLEAN" :
+ cd ".\..\main"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_dav.mak" CFG="mod_dav - Win32 Release" RECURSE=1 CLEAN
+ cd "..\fs"
+
+!ELSEIF "$(CFG)" == "mod_dav_fs - Win32 Debug"
+
+"mod_dav - Win32 Debug" :
+ cd ".\..\main"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_dav.mak" CFG="mod_dav - Win32 Debug"
+ cd "..\fs"
+
+"mod_dav - Win32 DebugCLEAN" :
+ cd ".\..\main"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_dav.mak" CFG="mod_dav - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\fs"
+
+!ENDIF
+
+SOURCE=..\..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_dav_fs - Win32 Release"
+
+
+"$(INTDIR)\mod_dav_fs.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_dav_fs.res" /i "../../../include" /i "../../../srclib/apr/include" /i "../../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_dav_fs.so" /d LONG_NAME="dav_fs_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_dav_fs - Win32 Debug"
+
+
+"$(INTDIR)\mod_dav_fs.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_dav_fs.res" /i "../../../include" /i "../../../srclib/apr/include" /i "../../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_dav_fs.so" /d LONG_NAME="dav_fs_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+
+!ENDIF
+
diff --git a/modules/dav/fs/repos.c b/modules/dav/fs/repos.c
new file mode 100644
index 0000000..d38868c
--- /dev/null
+++ b/modules/dav/fs/repos.c
@@ -0,0 +1,2269 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+** DAV filesystem-based repository provider
+*/
+
+#include "apr.h"
+#include "apr_file_io.h"
+#include "apr_strings.h"
+#include "apr_buckets.h"
+
+#if APR_HAVE_UNISTD_H
+#include <unistd.h> /* for getpid() */
+#endif
+
+#include "httpd.h"
+#include "http_log.h"
+#include "http_protocol.h" /* for ap_set_* (in dav_fs_set_headers) */
+#include "http_request.h" /* for ap_update_mtime() */
+
+#include "mod_dav.h"
+#include "repos.h"
+
+
+/* to assist in debugging mod_dav's GET handling */
+#define DEBUG_GET_HANDLER 0
+
+#define DAV_FS_COPY_BLOCKSIZE 16384 /* copy 16k at a time */
+
+/* context needed to identify a resource */
+struct dav_resource_private {
+ apr_pool_t *pool; /* memory storage pool associated with request */
+ const char *pathname; /* full pathname to resource */
+ apr_finfo_t finfo; /* filesystem info */
+ request_rec *r;
+};
+
+/* private context for doing a filesystem walk */
+typedef struct {
+ /* the input walk parameters */
+ const dav_walk_params *params;
+
+ /* reused as we walk */
+ dav_walk_resource wres;
+
+ dav_resource res1;
+ dav_resource_private info1;
+ dav_buffer path1;
+ dav_buffer uri_buf;
+
+ /* MOVE/COPY need a secondary path */
+ dav_resource res2;
+ dav_resource_private info2;
+ dav_buffer path2;
+
+ dav_buffer locknull_buf;
+
+} dav_fs_walker_context;
+
+typedef struct {
+ int is_move; /* is this a MOVE? */
+ dav_buffer work_buf; /* handy buffer for copymove_file() */
+
+ /* CALLBACK: this is a secondary resource managed specially for us */
+ const dav_resource *res_dst;
+
+ /* copied from dav_walk_params (they are invariant across the walk) */
+ const dav_resource *root;
+ apr_pool_t *pool;
+
+} dav_fs_copymove_walk_ctx;
+
+/* an internal WALKTYPE to walk hidden files (the .DAV directory) */
+#define DAV_WALKTYPE_HIDDEN 0x4000
+
+/* an internal WALKTYPE to call collections (again) after their contents */
+#define DAV_WALKTYPE_POSTFIX 0x8000
+
+#define DAV_CALLTYPE_POSTFIX 1000 /* a private call type */
+
+
+/* pull this in from the other source file */
+extern const dav_hooks_locks dav_hooks_locks_fs;
+
+/* forward-declare the hook structures */
+static const dav_hooks_repository dav_hooks_repository_fs;
+static const dav_hooks_liveprop dav_hooks_liveprop_fs;
+
+/*
+** The namespace URIs that we use. This list and the enumeration must
+** stay in sync.
+*/
+static const char * const dav_fs_namespace_uris[] =
+{
+ "DAV:",
+ "http://apache.org/dav/props/",
+
+ NULL /* sentinel */
+};
+enum {
+ DAV_FS_URI_DAV, /* the DAV: namespace URI */
+ DAV_FS_URI_MYPROPS /* the namespace URI for our custom props */
+};
+
+/*
+** Does this platform support an executable flag?
+**
+** ### need a way to portably abstract this query
+**
+** DAV_FINFO_MASK gives the appropriate mask to use for the stat call
+** used to get file attributes.
+*/
+#ifndef WIN32
+#define DAV_FS_HAS_EXECUTABLE
+#define DAV_FINFO_MASK (APR_FINFO_LINK | APR_FINFO_TYPE | APR_FINFO_INODE | \
+ APR_FINFO_SIZE | APR_FINFO_CTIME | APR_FINFO_MTIME | \
+ APR_FINFO_PROT)
+#else
+/* as above, but without APR_FINFO_PROT */
+#define DAV_FINFO_MASK (APR_FINFO_LINK | APR_FINFO_TYPE | APR_FINFO_INODE | \
+ APR_FINFO_SIZE | APR_FINFO_CTIME | APR_FINFO_MTIME)
+#endif
+
+/*
+** The single property that we define (in the DAV_FS_URI_MYPROPS namespace)
+*/
+#define DAV_PROPID_FS_executable 1
+
+/*
+ * prefix for temporary files
+ */
+#define DAV_FS_TMP_PREFIX ".davfs.tmp"
+
+static const dav_liveprop_spec dav_fs_props[] =
+{
+ /* standard DAV properties */
+ {
+ DAV_FS_URI_DAV,
+ "creationdate",
+ DAV_PROPID_creationdate,
+ 0
+ },
+ {
+ DAV_FS_URI_DAV,
+ "getcontentlength",
+ DAV_PROPID_getcontentlength,
+ 0
+ },
+ {
+ DAV_FS_URI_DAV,
+ "getetag",
+ DAV_PROPID_getetag,
+ 0
+ },
+ {
+ DAV_FS_URI_DAV,
+ "getlastmodified",
+ DAV_PROPID_getlastmodified,
+ 0
+ },
+
+ /* our custom properties */
+ {
+ DAV_FS_URI_MYPROPS,
+ "executable",
+ DAV_PROPID_FS_executable,
+ 0 /* handled special in dav_fs_is_writable */
+ },
+
+ { 0 } /* sentinel */
+};
+
+static const dav_liveprop_group dav_fs_liveprop_group =
+{
+ dav_fs_props,
+ dav_fs_namespace_uris,
+ &dav_hooks_liveprop_fs
+};
+
+
+/* define the dav_stream structure for our use */
+struct dav_stream {
+ apr_pool_t *p;
+ apr_file_t *f;
+ const char *pathname; /* we may need to remove it at close time */
+ char *temppath;
+ int unlink_on_error;
+};
+
+/* returns an appropriate HTTP status code given an APR status code for a
+ * failed I/O operation. ### use something besides 500? */
+#define MAP_IO2HTTP(e) (APR_STATUS_IS_ENOSPC(e) ? HTTP_INSUFFICIENT_STORAGE : \
+ APR_STATUS_IS_ENOENT(e) ? HTTP_CONFLICT : \
+ HTTP_INTERNAL_SERVER_ERROR)
+
+/* forward declaration for internal treewalkers */
+static dav_error * dav_fs_walk(const dav_walk_params *params, int depth,
+ dav_response **response);
+static dav_error * dav_fs_internal_walk(const dav_walk_params *params,
+ int depth, int is_move,
+ const dav_resource *root_dst,
+ dav_response **response);
+
+/* --------------------------------------------------------------------
+**
+** PRIVATE REPOSITORY FUNCTIONS
+*/
+static request_rec *dav_fs_get_request_rec(const dav_resource *resource)
+{
+ return resource->info->r;
+}
+
+apr_pool_t *dav_fs_pool(const dav_resource *resource)
+{
+ return resource->info->pool;
+}
+
+const char *dav_fs_pathname(const dav_resource *resource)
+{
+ return resource->info->pathname;
+}
+
+dav_error * dav_fs_dir_file_name(
+ const dav_resource *resource,
+ const char **dirpath_p,
+ const char **fname_p)
+{
+ dav_resource_private *ctx = resource->info;
+
+ if (resource->collection) {
+ *dirpath_p = ctx->pathname;
+ if (fname_p != NULL)
+ *fname_p = NULL;
+ }
+ else {
+ const char *testpath, *rootpath;
+ char *dirpath = ap_make_dirstr_parent(ctx->pool, ctx->pathname);
+ apr_size_t dirlen = strlen(dirpath);
+ apr_status_t rv = APR_SUCCESS;
+
+ testpath = dirpath;
+ if (dirlen > 0) {
+ rv = apr_filepath_root(&rootpath, &testpath, 0, ctx->pool);
+ }
+
+ /* remove trailing slash from dirpath, unless it's a root path
+ */
+ if ((rv == APR_SUCCESS && testpath && *testpath)
+ || rv == APR_ERELATIVE) {
+ if (dirpath[dirlen - 1] == '/') {
+ dirpath[dirlen - 1] = '\0';
+ }
+ }
+
+ /* ###: Looks like a response could be appropriate
+ *
+ * APR_SUCCESS here tells us the dir is a root
+ * APR_ERELATIVE told us we had no root (ok)
+ * APR_EINCOMPLETE an incomplete testpath told us
+ * there was no -file- name here!
+ * APR_EBADPATH or other errors tell us this file
+ * path is undecipherable
+ */
+
+ if (rv == APR_SUCCESS || rv == APR_ERELATIVE) {
+ *dirpath_p = dirpath;
+ if (fname_p != NULL)
+ *fname_p = ctx->pathname + dirlen;
+ }
+ else {
+ return dav_new_error(ctx->pool, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ "An incomplete/bad path was found in "
+ "dav_fs_dir_file_name.");
+ }
+ }
+
+ return NULL;
+}
+
+/* Note: picked up from ap_gm_timestr_822() */
+/* NOTE: buf must be at least DAV_TIMEBUF_SIZE chars in size */
+static void dav_format_time(int style, apr_time_t sec, char *buf, apr_size_t buflen)
+{
+ apr_time_exp_t tms;
+
+ /* ### what to do if fails? */
+ (void) apr_time_exp_gmt(&tms, sec);
+
+ if (style == DAV_STYLE_ISO8601) {
+ /* ### should we use "-00:00" instead of "Z" ?? */
+
+ /* 20 chars plus null term */
+ apr_snprintf(buf, buflen, "%.4d-%.2d-%.2dT%.2d:%.2d:%.2dZ",
+ tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday,
+ tms.tm_hour, tms.tm_min, tms.tm_sec);
+ return;
+ }
+
+ /* RFC 822 date format; as strftime '%a, %d %b %Y %T GMT' */
+
+ /* 29 chars plus null term */
+ apr_snprintf(buf, buflen, "%s, %.2d %s %d %.2d:%.2d:%.2d GMT",
+ apr_day_snames[tms.tm_wday],
+ tms.tm_mday, apr_month_snames[tms.tm_mon],
+ tms.tm_year + 1900,
+ tms.tm_hour, tms.tm_min, tms.tm_sec);
+}
+
+/* Copy or move src to dst; src_finfo is used to propagate permissions
+ * bits across if non-NULL; dst_finfo must be non-NULL iff dst already
+ * exists. */
+static dav_error * dav_fs_copymove_file(
+ int is_move,
+ apr_pool_t * p,
+ const char *src,
+ const char *dst,
+ const apr_finfo_t *src_finfo,
+ const apr_finfo_t *dst_finfo,
+ dav_buffer *pbuf)
+{
+ dav_buffer work_buf = { 0 };
+ apr_file_t *inf = NULL;
+ apr_file_t *outf = NULL;
+ apr_status_t status;
+ apr_fileperms_t perms;
+
+ if (pbuf == NULL)
+ pbuf = &work_buf;
+
+ /* Determine permissions to use for destination */
+ if (src_finfo && src_finfo->valid & APR_FINFO_PROT
+ && src_finfo->protection & APR_UEXECUTE) {
+ perms = src_finfo->protection;
+
+ if (dst_finfo != NULL) {
+ /* chmod it if it already exist */
+ if ((status = apr_file_perms_set(dst, perms)) != APR_SUCCESS) {
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ "Could not set permissions on destination");
+ }
+ }
+ }
+ else {
+ perms = APR_OS_DEFAULT;
+ }
+
+ dav_set_bufsize(p, pbuf, DAV_FS_COPY_BLOCKSIZE);
+
+ if ((status = apr_file_open(&inf, src, APR_READ | APR_BINARY,
+ APR_OS_DEFAULT, p)) != APR_SUCCESS) {
+ /* ### use something besides 500? */
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ "Could not open file for reading");
+ }
+
+ /* ### do we need to deal with the umask? */
+ status = apr_file_open(&outf, dst, APR_WRITE | APR_CREATE | APR_TRUNCATE
+ | APR_BINARY, perms, p);
+ if (status != APR_SUCCESS) {
+ apr_file_close(inf);
+
+ return dav_new_error(p, MAP_IO2HTTP(status), 0, status,
+ "Could not open file for writing");
+ }
+
+ while (1) {
+ apr_size_t len = DAV_FS_COPY_BLOCKSIZE;
+
+ status = apr_file_read(inf, pbuf->buf, &len);
+ if (status != APR_SUCCESS && status != APR_EOF) {
+ apr_status_t lcl_status;
+
+ apr_file_close(inf);
+ apr_file_close(outf);
+
+ if ((lcl_status = apr_file_remove(dst, p)) != APR_SUCCESS) {
+ /* ### ACK! Inconsistent state... */
+
+ /* ### use something besides 500? */
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
+ lcl_status,
+ "Could not delete output after read "
+ "failure. Server is now in an "
+ "inconsistent state.");
+ }
+
+ /* ### use something besides 500? */
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ "Could not read input file");
+ }
+
+ if (status == APR_EOF)
+ break;
+
+ /* write any bytes that were read */
+ status = apr_file_write_full(outf, pbuf->buf, len, NULL);
+ if (status != APR_SUCCESS) {
+ apr_status_t lcl_status;
+
+ apr_file_close(inf);
+ apr_file_close(outf);
+
+ if ((lcl_status = apr_file_remove(dst, p)) != APR_SUCCESS) {
+ /* ### ACK! Inconsistent state... */
+
+ /* ### use something besides 500? */
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
+ lcl_status,
+ "Could not delete output after write "
+ "failure. Server is now in an "
+ "inconsistent state.");
+ }
+
+ return dav_new_error(p, MAP_IO2HTTP(status), 0, status,
+ "Could not write output file");
+ }
+ }
+
+ apr_file_close(inf);
+ apr_file_close(outf);
+
+ if (is_move && (status = apr_file_remove(src, p)) != APR_SUCCESS) {
+ dav_error *err;
+ apr_status_t lcl_status;
+
+ if (APR_STATUS_IS_ENOENT(status)) {
+ /*
+ * Something is wrong here but the result is what we wanted.
+ * We definitely should not remove the destination file.
+ */
+ err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ apr_psprintf(p, "Could not remove source "
+ "file %s after move to %s. The "
+ "server may be in an "
+ "inconsistent state.", src, dst));
+ return err;
+ }
+ else if ((lcl_status = apr_file_remove(dst, p)) != APR_SUCCESS) {
+ /* ### ACK. this creates an inconsistency. do more!? */
+
+ /* ### use something besides 500? */
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, lcl_status,
+ "Could not remove source or destination "
+ "file. Server is now in an inconsistent "
+ "state.");
+ }
+
+ /* ### use something besides 500? */
+ err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ "Could not remove source file after move. "
+ "Destination was removed to ensure consistency.");
+ return err;
+ }
+
+ return NULL;
+}
+
+/* copy/move a file from within a state dir to another state dir */
+/* ### need more buffers to replace the pool argument */
+static dav_error * dav_fs_copymove_state(
+ int is_move,
+ apr_pool_t * p,
+ const char *src_dir, const char *src_file,
+ const char *dst_dir, const char *dst_file,
+ dav_buffer *pbuf)
+{
+ apr_finfo_t src_finfo; /* finfo for source file */
+ apr_finfo_t dst_state_finfo; /* finfo for STATE directory */
+ apr_status_t rv;
+ const char *src;
+ const char *dst;
+
+ /* build the propset pathname for the source file */
+ src = apr_pstrcat(p, src_dir, "/" DAV_FS_STATE_DIR "/", src_file, NULL);
+
+ /* the source file doesn't exist */
+ rv = apr_stat(&src_finfo, src, APR_FINFO_NORM, p);
+ if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) {
+ return NULL;
+ }
+
+ /* build the pathname for the destination state dir */
+ dst = apr_pstrcat(p, dst_dir, "/" DAV_FS_STATE_DIR, NULL);
+
+ /* ### do we need to deal with the umask? */
+
+ /* ensure that it exists */
+ rv = apr_dir_make(dst, APR_OS_DEFAULT, p);
+ if (rv != APR_SUCCESS) {
+ if (!APR_STATUS_IS_EEXIST(rv)) {
+ /* ### use something besides 500? */
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ "Could not create internal state directory");
+ }
+ }
+
+ /* get info about the state directory */
+ rv = apr_stat(&dst_state_finfo, dst, APR_FINFO_NORM, p);
+ if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) {
+ /* Ack! Where'd it go? */
+ /* ### use something besides 500? */
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ "State directory disappeared");
+ }
+
+ /* The mkdir() may have failed because a *file* exists there already */
+ if (dst_state_finfo.filetype != APR_DIR) {
+ /* ### try to recover by deleting this file? (and mkdir again) */
+ /* ### use something besides 500? */
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "State directory is actually a file");
+ }
+
+ /* append the target file to the state directory pathname */
+ dst = apr_pstrcat(p, dst, "/", dst_file, NULL);
+
+ /* copy/move the file now */
+ if (is_move) {
+ /* try simple rename first */
+ rv = apr_file_rename(src, dst, p);
+ if (APR_STATUS_IS_EXDEV(rv)) {
+ return dav_fs_copymove_file(is_move, p, src, dst, NULL, NULL, pbuf);
+ }
+ if (rv != APR_SUCCESS) {
+ /* ### use something besides 500? */
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ "Could not move state file.");
+ }
+ }
+ else
+ {
+ /* gotta copy (and delete) */
+ return dav_fs_copymove_file(is_move, p, src, dst, NULL, NULL, pbuf);
+ }
+
+ return NULL;
+}
+
+static dav_error *dav_fs_copymoveset(int is_move, apr_pool_t *p,
+ const dav_resource *src,
+ const dav_resource *dst,
+ dav_buffer *pbuf)
+{
+ const char *src_dir;
+ const char *src_file;
+ const char *src_state1;
+ const char *src_state2;
+ const char *dst_dir;
+ const char *dst_file;
+ const char *dst_state1;
+ const char *dst_state2;
+ dav_error *err;
+
+ /* Get directory and filename for resources */
+ /* ### should test these result values... */
+ (void) dav_fs_dir_file_name(src, &src_dir, &src_file);
+ (void) dav_fs_dir_file_name(dst, &dst_dir, &dst_file);
+
+ /* Get the corresponding state files for each resource */
+ dav_dbm_get_statefiles(p, src_file, &src_state1, &src_state2);
+ dav_dbm_get_statefiles(p, dst_file, &dst_state1, &dst_state2);
+#if DAV_DEBUG
+ if ((src_state2 != NULL && dst_state2 == NULL) ||
+ (src_state2 == NULL && dst_state2 != NULL)) {
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "DESIGN ERROR: dav_dbm_get_statefiles() "
+ "returned inconsistent results.");
+ }
+#endif
+
+ err = dav_fs_copymove_state(is_move, p,
+ src_dir, src_state1,
+ dst_dir, dst_state1,
+ pbuf);
+
+ if (err == NULL && src_state2 != NULL) {
+ err = dav_fs_copymove_state(is_move, p,
+ src_dir, src_state2,
+ dst_dir, dst_state2,
+ pbuf);
+
+ if (err != NULL) {
+ /* ### CRAP. inconsistency. */
+ /* ### should perform some cleanup at the target if we still
+ ### have the original files */
+
+ /* Change the error to reflect the bad server state. */
+ err->status = HTTP_INTERNAL_SERVER_ERROR;
+ err->desc =
+ "Could not fully copy/move the properties. "
+ "The server is now in an inconsistent state.";
+ }
+ }
+
+ return err;
+}
+
+static dav_error *dav_fs_deleteset(apr_pool_t *p, const dav_resource *resource)
+{
+ const char *dirpath;
+ const char *fname;
+ const char *state1;
+ const char *state2;
+ const char *pathname;
+ apr_status_t status;
+
+ /* Get directory, filename, and state-file names for the resource */
+ /* ### should test this result value... */
+ (void) dav_fs_dir_file_name(resource, &dirpath, &fname);
+ dav_dbm_get_statefiles(p, fname, &state1, &state2);
+
+ /* build the propset pathname for the file */
+ pathname = apr_pstrcat(p,
+ dirpath,
+ "/" DAV_FS_STATE_DIR "/",
+ state1,
+ NULL);
+
+ /* note: we may get ENOENT if the state dir is not present */
+ if ((status = apr_file_remove(pathname, p)) != APR_SUCCESS
+ && !APR_STATUS_IS_ENOENT(status)) {
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ "Could not remove properties.");
+ }
+
+ if (state2 != NULL) {
+ /* build the propset pathname for the file */
+ pathname = apr_pstrcat(p,
+ dirpath,
+ "/" DAV_FS_STATE_DIR "/",
+ state2,
+ NULL);
+
+ if ((status = apr_file_remove(pathname, p)) != APR_SUCCESS
+ && !APR_STATUS_IS_ENOENT(status)) {
+ /* ### CRAP. only removed half. */
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ "Could not fully remove properties. "
+ "The server is now in an inconsistent "
+ "state.");
+ }
+ }
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------
+**
+** REPOSITORY HOOK FUNCTIONS
+*/
+
+static dav_error * dav_fs_get_resource(
+ request_rec *r,
+ const char *root_dir,
+ const char *label,
+ int use_checked_in,
+ dav_resource **result_resource)
+{
+ dav_resource_private *ctx;
+ dav_resource *resource;
+ char *s;
+ char *filename;
+ apr_size_t len;
+
+ /* ### optimize this into a single allocation! */
+
+ /* Create private resource context descriptor */
+ ctx = apr_pcalloc(r->pool, sizeof(*ctx));
+ ctx->finfo = r->finfo;
+ ctx->r = r;
+
+ /* ### this should go away */
+ ctx->pool = r->pool;
+
+ /* Preserve case on OSes which fold canonical filenames */
+#if 0
+ /* ### not available in Apache 2.0 yet */
+ filename = r->case_preserved_filename;
+#else
+ filename = r->filename;
+#endif
+
+ /*
+ ** If there is anything in the path_info, then this indicates that the
+ ** entire path was not used to specify the file/dir. We want to append
+ ** it onto the filename so that we get a "valid" pathname for null
+ ** resources.
+ */
+ s = apr_pstrcat(r->pool, filename, r->path_info, NULL);
+
+ /* make sure the pathname does not have a trailing "/" */
+ len = strlen(s);
+ if (len > 1 && s[len - 1] == '/') {
+ s[len - 1] = '\0';
+ }
+ ctx->pathname = s;
+
+ /* Create resource descriptor */
+ resource = apr_pcalloc(r->pool, sizeof(*resource));
+ resource->type = DAV_RESOURCE_TYPE_REGULAR;
+ resource->info = ctx;
+ resource->hooks = &dav_hooks_repository_fs;
+ resource->pool = r->pool;
+
+ /* make sure the URI does not have a trailing "/" */
+ len = strlen(r->uri);
+ if (len > 1 && r->uri[len - 1] == '/') {
+ s = apr_pstrmemdup(r->pool, r->uri, len-1);
+ resource->uri = s;
+ }
+ else {
+ resource->uri = r->uri;
+ }
+
+ if (r->finfo.filetype != APR_NOFILE) {
+ resource->exists = 1;
+ resource->collection = r->finfo.filetype == APR_DIR;
+
+ /* unused info in the URL will indicate a null resource */
+
+ if (r->path_info != NULL && *r->path_info != '\0') {
+ if (resource->collection) {
+ /* only a trailing "/" is allowed */
+ if (*r->path_info != '/' || r->path_info[1] != '\0') {
+
+ /*
+ ** This URL/filename represents a locknull resource or
+ ** possibly a destination of a MOVE/COPY
+ */
+ resource->exists = 0;
+ resource->collection = 0;
+ }
+ }
+ else
+ {
+ /*
+ ** The base of the path refers to a file -- nothing should
+ ** be in path_info. The resource is simply an error: it
+ ** can't be a null or a locknull resource.
+ */
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0, 0,
+ "The URL contains extraneous path "
+ "components. The resource could not "
+ "be identified.");
+ }
+
+ /* retain proper integrity across the structures */
+ if (!resource->exists) {
+ ctx->finfo.filetype = APR_NOFILE;
+ }
+ }
+ }
+
+ *result_resource = resource;
+ return NULL;
+}
+
+static dav_error * dav_fs_get_parent_resource(const dav_resource *resource,
+ dav_resource **result_parent)
+{
+ dav_resource_private *ctx = resource->info;
+ dav_resource_private *parent_ctx;
+ dav_resource *parent_resource;
+ apr_status_t rv;
+ char *dirpath;
+ const char *testroot;
+ const char *testpath;
+
+ /* If we're at the root of the URL space, then there is no parent. */
+ if (strcmp(resource->uri, "/") == 0) {
+ *result_parent = NULL;
+ return NULL;
+ }
+
+ /* If given resource is root, then there is no parent.
+ * Unless we can retrieve the filepath root, this is
+ * intendend to fail. If we split the root and
+ * no path info remains, then we also fail.
+ */
+ testpath = ctx->pathname;
+ rv = apr_filepath_root(&testroot, &testpath, 0, ctx->pool);
+ if ((rv != APR_SUCCESS && rv != APR_ERELATIVE)
+ || !testpath || !*testpath) {
+ *result_parent = NULL;
+ return NULL;
+ }
+
+ /* ### optimize this into a single allocation! */
+
+ /* Create private resource context descriptor */
+ parent_ctx = apr_pcalloc(ctx->pool, sizeof(*parent_ctx));
+
+ /* ### this should go away */
+ parent_ctx->pool = ctx->pool;
+
+ dirpath = ap_make_dirstr_parent(ctx->pool, ctx->pathname);
+ if (strlen(dirpath) > 1 && dirpath[strlen(dirpath) - 1] == '/')
+ dirpath[strlen(dirpath) - 1] = '\0';
+ parent_ctx->pathname = dirpath;
+
+ parent_resource = apr_pcalloc(ctx->pool, sizeof(*parent_resource));
+ parent_resource->info = parent_ctx;
+ parent_resource->collection = 1;
+ parent_resource->hooks = &dav_hooks_repository_fs;
+ parent_resource->pool = resource->pool;
+
+ if (resource->uri != NULL) {
+ char *uri = ap_make_dirstr_parent(ctx->pool, resource->uri);
+ if (strlen(uri) > 1 && uri[strlen(uri) - 1] == '/')
+ uri[strlen(uri) - 1] = '\0';
+ parent_resource->uri = uri;
+ }
+
+ rv = apr_stat(&parent_ctx->finfo, parent_ctx->pathname,
+ APR_FINFO_NORM, ctx->pool);
+ if (rv == APR_SUCCESS || rv == APR_INCOMPLETE) {
+ parent_resource->exists = 1;
+ }
+
+ *result_parent = parent_resource;
+ return NULL;
+}
+
+static int dav_fs_is_same_resource(
+ const dav_resource *res1,
+ const dav_resource *res2)
+{
+ dav_resource_private *ctx1 = res1->info;
+ dav_resource_private *ctx2 = res2->info;
+
+ if (res1->hooks != res2->hooks)
+ return 0;
+
+ if ((ctx1->finfo.filetype != APR_NOFILE) && (ctx2->finfo.filetype != APR_NOFILE)
+ && (ctx1->finfo.valid & ctx2->finfo.valid & APR_FINFO_INODE)) {
+ return ctx1->finfo.inode == ctx2->finfo.inode;
+ }
+ else {
+ return strcmp(ctx1->pathname, ctx2->pathname) == 0;
+ }
+}
+
+static int dav_fs_is_parent_resource(
+ const dav_resource *res1,
+ const dav_resource *res2)
+{
+ dav_resource_private *ctx1 = res1->info;
+ dav_resource_private *ctx2 = res2->info;
+ apr_size_t len1 = strlen(ctx1->pathname);
+ apr_size_t len2;
+
+ if (res1->hooks != res2->hooks)
+ return 0;
+
+ /* it is safe to use ctx2 now */
+ len2 = strlen(ctx2->pathname);
+
+ return (len2 > len1
+ && memcmp(ctx1->pathname, ctx2->pathname, len1) == 0
+ && ctx2->pathname[len1] == '/');
+}
+
+static apr_status_t tmpfile_cleanup(void *data)
+{
+ dav_stream *ds = data;
+ if (ds->temppath) {
+ apr_file_remove(ds->temppath, ds->p);
+ }
+ return APR_SUCCESS;
+}
+
+/* custom mktemp that creates the file with APR_OS_DEFAULT permissions */
+static apr_status_t dav_fs_mktemp(apr_file_t **fp, char *templ, apr_pool_t *p)
+{
+ apr_status_t rv;
+ int num = ((getpid() << 7) + (apr_uintptr_t)templ % (1 << 16) ) %
+ ( 1 << 23 ) ;
+ char *numstr = templ + strlen(templ) - 6;
+
+ ap_assert(numstr >= templ);
+
+ do {
+ num = (num + 1) % ( 1 << 23 );
+ apr_snprintf(numstr, 7, "%06x", num);
+ rv = apr_file_open(fp, templ,
+ APR_WRITE | APR_CREATE | APR_BINARY | APR_EXCL,
+ APR_OS_DEFAULT, p);
+ } while (APR_STATUS_IS_EEXIST(rv));
+
+ return rv;
+}
+
+static dav_error * dav_fs_open_stream(const dav_resource *resource,
+ dav_stream_mode mode,
+ dav_stream **stream)
+{
+ apr_pool_t *p = resource->info->pool;
+ dav_stream *ds = apr_pcalloc(p, sizeof(*ds));
+ apr_int32_t flags;
+ apr_status_t rv;
+
+ switch (mode) {
+ default:
+ flags = APR_READ | APR_BINARY;
+ break;
+
+ case DAV_MODE_WRITE_TRUNC:
+ flags = APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BINARY;
+ break;
+ case DAV_MODE_WRITE_SEEKABLE:
+ flags = APR_WRITE | APR_CREATE | APR_BINARY;
+ break;
+ }
+
+ ds->p = p;
+ ds->pathname = resource->info->pathname;
+ ds->temppath = NULL;
+ ds->unlink_on_error = 0;
+
+ if (mode == DAV_MODE_WRITE_TRUNC) {
+ ds->temppath = apr_pstrcat(p, ap_make_dirstr_parent(p, ds->pathname),
+ DAV_FS_TMP_PREFIX "XXXXXX", NULL);
+ rv = dav_fs_mktemp(&ds->f, ds->temppath, ds->p);
+ apr_pool_cleanup_register(p, ds, tmpfile_cleanup,
+ apr_pool_cleanup_null);
+ }
+ else if (mode == DAV_MODE_WRITE_SEEKABLE) {
+ rv = apr_file_open(&ds->f, ds->pathname, flags | APR_FOPEN_EXCL,
+ APR_OS_DEFAULT, ds->p);
+ if (rv == APR_SUCCESS) {
+ /* we have created a new file */
+ ds->unlink_on_error = 1;
+ }
+ else if (APR_STATUS_IS_EEXIST(rv)) {
+ rv = apr_file_open(&ds->f, ds->pathname, flags, APR_OS_DEFAULT,
+ ds->p);
+ if (rv != APR_SUCCESS) {
+ return dav_new_error(p, MAP_IO2HTTP(rv), 0, rv,
+ apr_psprintf(p, "Could not open an existing "
+ "resource for writing: %s.",
+ ds->pathname));
+ }
+ }
+ }
+ else {
+ rv = apr_file_open(&ds->f, ds->pathname, flags, APR_OS_DEFAULT, ds->p);
+ if (rv != APR_SUCCESS) {
+ return dav_new_error(p, MAP_IO2HTTP(rv), 0, rv,
+ apr_psprintf(p, "Could not open an existing "
+ "resource for reading: %s.",
+ ds->pathname));
+ }
+ }
+
+ if (rv != APR_SUCCESS) {
+ return dav_new_error(p, MAP_IO2HTTP(rv), 0, rv,
+ apr_psprintf(p, "An error occurred while opening "
+ "a resource for writing: %s.",
+ ds->pathname));
+ }
+
+ /* (APR registers cleanups for the fd with the pool) */
+
+ *stream = ds;
+ return NULL;
+}
+
+static dav_error * dav_fs_close_stream(dav_stream *stream, int commit)
+{
+ apr_status_t rv;
+
+ apr_file_close(stream->f);
+
+ if (!commit) {
+ if (stream->temppath) {
+ apr_pool_cleanup_run(stream->p, stream, tmpfile_cleanup);
+ }
+ else if (stream->unlink_on_error) {
+ if ((rv = apr_file_remove(stream->pathname, stream->p))
+ != APR_SUCCESS) {
+ /* ### use a better description? */
+ return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0,
+ rv,
+ "There was a problem removing (rolling "
+ "back) the resource "
+ "when it was being closed.");
+ }
+ }
+ }
+ else if (stream->temppath) {
+ rv = apr_file_rename(stream->temppath, stream->pathname, stream->p);
+ if (rv) {
+ return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ "There was a problem writing the file "
+ "atomically after writes.");
+ }
+ apr_pool_cleanup_kill(stream->p, stream, tmpfile_cleanup);
+ }
+
+ return NULL;
+}
+
+static dav_error * dav_fs_write_stream(dav_stream *stream,
+ const void *buf, apr_size_t bufsize)
+{
+ apr_status_t status;
+
+ status = apr_file_write_full(stream->f, buf, bufsize, NULL);
+ if (APR_STATUS_IS_ENOSPC(status)) {
+ return dav_new_error(stream->p, HTTP_INSUFFICIENT_STORAGE, 0, status,
+ "There is not enough storage to write to "
+ "this resource.");
+ }
+ else if (status != APR_SUCCESS) {
+ /* ### use something besides 500? */
+ return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ "An error occurred while writing to a "
+ "resource.");
+ }
+ return NULL;
+}
+
+static dav_error * dav_fs_seek_stream(dav_stream *stream, apr_off_t abs_pos)
+{
+ apr_status_t status;
+
+ if ((status = apr_file_seek(stream->f, APR_SET, &abs_pos))
+ != APR_SUCCESS) {
+ /* ### should check whether apr_file_seek set abs_pos was set to the
+ * correct position? */
+ /* ### use something besides 500? */
+ return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ "Could not seek to specified position in the "
+ "resource.");
+ }
+ return NULL;
+}
+
+
+#if DEBUG_GET_HANDLER
+
+/* only define set_headers() and deliver() for debug purposes */
+
+
+static dav_error * dav_fs_set_headers(request_rec *r,
+ const dav_resource *resource)
+{
+ /* ### this function isn't really used since we have a get_pathname */
+ if (!resource->exists)
+ return NULL;
+
+ /* make sure the proper mtime is in the request record */
+ ap_update_mtime(r, resource->info->finfo.mtime);
+
+ /* ### note that these use r->filename rather than <resource> */
+ ap_set_last_modified(r);
+ ap_set_etag(r);
+
+ /* we accept byte-ranges */
+ ap_set_accept_ranges(r);
+
+ /* set up the Content-Length header */
+ ap_set_content_length(r, resource->info->finfo.size);
+
+ /* ### how to set the content type? */
+ /* ### until this is resolved, the Content-Type header is busted */
+
+ return NULL;
+}
+
+static dav_error * dav_fs_deliver(const dav_resource *resource,
+ ap_filter_t *output)
+{
+ apr_pool_t *pool = resource->pool;
+ apr_bucket_brigade *bb;
+ apr_file_t *fd;
+ apr_status_t status;
+ apr_bucket *bkt;
+
+ /* Check resource type */
+ if (resource->type != DAV_RESOURCE_TYPE_REGULAR
+ && resource->type != DAV_RESOURCE_TYPE_VERSION
+ && resource->type != DAV_RESOURCE_TYPE_WORKING) {
+ return dav_new_error(pool, HTTP_CONFLICT, 0, 0,
+ "Cannot GET this type of resource.");
+ }
+ if (resource->collection) {
+ return dav_new_error(pool, HTTP_CONFLICT, 0, 0,
+ "There is no default response to GET for a "
+ "collection.");
+ }
+
+ if ((status = apr_file_open(&fd, resource->info->pathname,
+ APR_READ | APR_BINARY, 0,
+ pool)) != APR_SUCCESS) {
+ return dav_new_error(pool, HTTP_FORBIDDEN, 0, status,
+ "File permissions deny server access.");
+ }
+
+ bb = apr_brigade_create(pool, output->c->bucket_alloc);
+
+ apr_brigade_insert_file(bb, fd, 0, resource->info->finfo.size, pool);
+
+ bkt = apr_bucket_eos_create(output->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, bkt);
+
+ if ((status = ap_pass_brigade(output, bb)) != APR_SUCCESS) {
+ return dav_new_error(pool, AP_FILTER_ERROR, 0, status,
+ "Could not write contents to filter.");
+ }
+
+ return NULL;
+}
+
+#endif /* DEBUG_GET_HANDLER */
+
+
+static dav_error * dav_fs_create_collection(dav_resource *resource)
+{
+ dav_resource_private *ctx = resource->info;
+ apr_status_t status;
+
+ status = apr_dir_make(ctx->pathname, APR_OS_DEFAULT, ctx->pool);
+ if (APR_STATUS_IS_ENOSPC(status)) {
+ return dav_new_error(ctx->pool, HTTP_INSUFFICIENT_STORAGE, 0, status,
+ "There is not enough storage to create "
+ "this collection.");
+ }
+ else if (APR_STATUS_IS_ENOENT(status)) {
+ return dav_new_error(ctx->pool, HTTP_CONFLICT, 0, status,
+ "Cannot create collection; intermediate "
+ "collection does not exist.");
+ }
+ else if (status != APR_SUCCESS) {
+ /* ### refine this error message? */
+ return dav_new_error(ctx->pool, HTTP_FORBIDDEN, 0, status,
+ "Unable to create collection.");
+ }
+
+ /* update resource state to show it exists as a collection */
+ resource->exists = 1;
+ resource->collection = 1;
+
+ return NULL;
+}
+
+static dav_error * dav_fs_copymove_walker(dav_walk_resource *wres,
+ int calltype)
+{
+ apr_status_t status;
+ dav_fs_copymove_walk_ctx *ctx = wres->walk_ctx;
+ dav_resource_private *srcinfo = wres->resource->info;
+ dav_resource_private *dstinfo = ctx->res_dst->info;
+ dav_error *err = NULL;
+
+ if (wres->resource->collection) {
+ if (calltype == DAV_CALLTYPE_POSTFIX) {
+ /* Postfix call for MOVE. delete the source dir.
+ * Note: when copying, we do not enable the postfix-traversal.
+ */
+ /* ### we are ignoring any error here; what should we do? */
+ (void) apr_dir_remove(srcinfo->pathname, ctx->pool);
+ }
+ else {
+ /* copy/move of a collection. Create the new, target collection */
+ if ((status = apr_dir_make(dstinfo->pathname, APR_OS_DEFAULT,
+ ctx->pool)) != APR_SUCCESS) {
+ /* ### assume it was a permissions problem */
+ /* ### need a description here */
+ err = dav_new_error(ctx->pool, HTTP_FORBIDDEN, 0, status, NULL);
+ }
+ }
+ }
+ else {
+ err = dav_fs_copymove_file(ctx->is_move, ctx->pool,
+ srcinfo->pathname, dstinfo->pathname,
+ &srcinfo->finfo,
+ ctx->res_dst->exists ? &dstinfo->finfo : NULL,
+ &ctx->work_buf);
+ /* ### push a higher-level description? */
+ }
+
+ /*
+ ** If we have a "not so bad" error, then it might need to go into a
+ ** multistatus response.
+ **
+ ** For a MOVE, it will always go into the multistatus. It could be
+ ** that everything has been moved *except* for the root. Using a
+ ** multistatus (with no errors for the other resources) will signify
+ ** this condition.
+ **
+ ** For a COPY, we are traversing in a prefix fashion. If the root fails,
+ ** then we can just bail out now.
+ */
+ if (err != NULL
+ && !ap_is_HTTP_SERVER_ERROR(err->status)
+ && (ctx->is_move
+ || !dav_fs_is_same_resource(wres->resource, ctx->root))) {
+ /* ### use errno to generate DAV:responsedescription? */
+ dav_add_response(wres, err->status, NULL);
+
+ /* the error is in the multistatus now. do not stop the traversal. */
+ return NULL;
+ }
+
+ return err;
+}
+
+static dav_error *dav_fs_copymove_resource(
+ int is_move,
+ const dav_resource *src,
+ const dav_resource *dst,
+ int depth,
+ dav_response **response)
+{
+ dav_error *err = NULL;
+ dav_buffer work_buf = { 0 };
+
+ *response = NULL;
+
+ /* if a collection, recursively copy/move it and its children,
+ * including the state dirs
+ */
+ if (src->collection) {
+ dav_walk_params params = { 0 };
+ dav_response *multi_status;
+
+ params.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_HIDDEN;
+ params.func = dav_fs_copymove_walker;
+ params.pool = src->info->pool;
+ params.root = src;
+
+ /* params.walk_ctx is managed by dav_fs_internal_walk() */
+
+ /* postfix is needed for MOVE to delete source dirs */
+ if (is_move)
+ params.walk_type |= DAV_WALKTYPE_POSTFIX;
+
+ /* note that we return the error OR the multistatus. never both */
+
+ if ((err = dav_fs_internal_walk(&params, depth, is_move, dst,
+ &multi_status)) != NULL) {
+ /* on a "real" error, then just punt. nothing else to do. */
+ return err;
+ }
+
+ if ((*response = multi_status) != NULL) {
+ /* some multistatus responses exist. wrap them in a 207 */
+ return dav_new_error(src->info->pool, HTTP_MULTI_STATUS, 0, 0,
+ "Error(s) occurred on some resources during "
+ "the COPY/MOVE process.");
+ }
+
+ return NULL;
+ }
+
+ /* not a collection */
+ if ((err = dav_fs_copymove_file(is_move, src->info->pool,
+ src->info->pathname, dst->info->pathname,
+ &src->info->finfo,
+ dst->exists ? &dst->info->finfo : NULL,
+ &work_buf)) != NULL) {
+ /* ### push a higher-level description? */
+ return err;
+ }
+
+ /* copy/move properties as well */
+ return dav_fs_copymoveset(is_move, src->info->pool, src, dst, &work_buf);
+}
+
+static dav_error * dav_fs_copy_resource(
+ const dav_resource *src,
+ dav_resource *dst,
+ int depth,
+ dav_response **response)
+{
+ dav_error *err;
+
+#if DAV_DEBUG
+ if (src->hooks != dst->hooks) {
+ /*
+ ** ### strictly speaking, this is a design error; we should not
+ ** ### have reached this point.
+ */
+ return dav_new_error(src->info->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "DESIGN ERROR: a mix of repositories "
+ "was passed to copy_resource.");
+ }
+#endif
+
+ if ((err = dav_fs_copymove_resource(0, src, dst, depth,
+ response)) == NULL) {
+
+ /* update state of destination resource to show it exists */
+ dst->exists = 1;
+ dst->collection = src->collection;
+ }
+
+ return err;
+}
+
+static dav_error * dav_fs_move_resource(
+ dav_resource *src,
+ dav_resource *dst,
+ dav_response **response)
+{
+ dav_resource_private *srcinfo = src->info;
+ dav_resource_private *dstinfo = dst->info;
+ dav_error *err;
+ apr_status_t rv;
+
+#if DAV_DEBUG
+ if (src->hooks != dst->hooks) {
+ /*
+ ** ### strictly speaking, this is a design error; we should not
+ ** ### have reached this point.
+ */
+ return dav_new_error(src->info->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "DESIGN ERROR: a mix of repositories "
+ "was passed to move_resource.");
+ }
+#endif
+
+
+ /* try rename first */
+ rv = apr_file_rename(srcinfo->pathname, dstinfo->pathname, srcinfo->pool);
+
+ /* if we can't simply rename, then do it the hard way... */
+ if (APR_STATUS_IS_EXDEV(rv)) {
+ if ((err = dav_fs_copymove_resource(1, src, dst, DAV_INFINITY,
+ response)) == NULL) {
+ /* update resource states */
+ dst->exists = 1;
+ dst->collection = src->collection;
+ src->exists = 0;
+ src->collection = 0;
+ }
+
+ return err;
+ }
+
+ /* no multistatus response */
+ *response = NULL;
+
+ if (rv != APR_SUCCESS) {
+ /* ### should have a better error than this. */
+ return dav_new_error(srcinfo->pool, HTTP_INTERNAL_SERVER_ERROR, 0, rv,
+ "Could not rename resource.");
+ }
+
+ /* Rename did work. Update resource states and move properties as well */
+ dst->exists = 1;
+ dst->collection = src->collection;
+ src->exists = 0;
+ src->collection = 0;
+
+ if ((err = dav_fs_copymoveset(1, src->info->pool,
+ src, dst, NULL)) == NULL) {
+ /* no error. we're done. go ahead and return now. */
+ return NULL;
+ }
+
+ /* error occurred during properties move; try to put resource back */
+ if (apr_file_rename(dstinfo->pathname, srcinfo->pathname,
+ srcinfo->pool) != APR_SUCCESS) {
+ /* couldn't put it back! */
+ return dav_push_error(srcinfo->pool,
+ HTTP_INTERNAL_SERVER_ERROR, 0,
+ "The resource was moved, but a failure "
+ "occurred during the move of its "
+ "properties. The resource could not be "
+ "restored to its original location. The "
+ "server is now in an inconsistent state.",
+ err);
+ }
+
+ /* update resource states again */
+ src->exists = 1;
+ src->collection = dst->collection;
+ dst->exists = 0;
+ dst->collection = 0;
+
+ /* resource moved back, but properties may be inconsistent */
+ return dav_push_error(srcinfo->pool,
+ HTTP_INTERNAL_SERVER_ERROR, 0,
+ "The resource was moved, but a failure "
+ "occurred during the move of its properties. "
+ "The resource was moved back to its original "
+ "location, but its properties may have been "
+ "partially moved. The server may be in an "
+ "inconsistent state.",
+ err);
+}
+
+static dav_error * dav_fs_delete_walker(dav_walk_resource *wres, int calltype)
+{
+ dav_resource_private *info = wres->resource->info;
+
+ /* do not attempt to remove a null resource,
+ * or a collection with children
+ */
+ if (wres->resource->exists &&
+ (!wres->resource->collection || calltype == DAV_CALLTYPE_POSTFIX)) {
+ /* try to remove the resource */
+ apr_status_t result;
+
+ result = wres->resource->collection
+ ? apr_dir_remove(info->pathname, wres->pool)
+ : apr_file_remove(info->pathname, wres->pool);
+
+ /*
+ ** If an error occurred, then add it to multistatus response.
+ ** Note that we add it for the root resource, too. It is quite
+ ** possible to delete the whole darn tree, yet fail on the root.
+ **
+ ** (also: remember we are deleting via a postfix traversal)
+ */
+ if (result != APR_SUCCESS) {
+ /* ### assume there is a permissions problem */
+
+ /* ### use errno to generate DAV:responsedescription? */
+ dav_add_response(wres, HTTP_FORBIDDEN, NULL);
+ }
+ }
+
+ return NULL;
+}
+
+static dav_error * dav_fs_remove_resource(dav_resource *resource,
+ dav_response **response)
+{
+ apr_status_t status;
+ dav_resource_private *info = resource->info;
+
+ *response = NULL;
+
+ /* if a collection, recursively remove it and its children,
+ * including the state dirs
+ */
+ if (resource->collection) {
+ dav_walk_params params = { 0 };
+ dav_error *err = NULL;
+ dav_response *multi_status;
+
+ params.walk_type = (DAV_WALKTYPE_NORMAL
+ | DAV_WALKTYPE_HIDDEN
+ | DAV_WALKTYPE_POSTFIX);
+ params.func = dav_fs_delete_walker;
+ params.pool = info->pool;
+ params.root = resource;
+
+ if ((err = dav_fs_walk(&params, DAV_INFINITY,
+ &multi_status)) != NULL) {
+ /* on a "real" error, then just punt. nothing else to do. */
+ return err;
+ }
+
+ if ((*response = multi_status) != NULL) {
+ /* some multistatus responses exist. wrap them in a 207 */
+ return dav_new_error(info->pool, HTTP_MULTI_STATUS, 0, 0,
+ "Error(s) occurred on some resources during "
+ "the deletion process.");
+ }
+
+ /* no errors... update resource state */
+ resource->exists = 0;
+ resource->collection = 0;
+
+ return NULL;
+ }
+
+ /* not a collection; remove the file and its properties */
+ if ((status = apr_file_remove(info->pathname, info->pool)) != APR_SUCCESS) {
+ /* ### put a description in here */
+ return dav_new_error(info->pool, HTTP_FORBIDDEN, 0, status, NULL);
+ }
+
+ /* update resource state */
+ resource->exists = 0;
+ resource->collection = 0;
+
+ /* remove properties and return its result */
+ return dav_fs_deleteset(info->pool, resource);
+}
+
+/* ### move this to dav_util? */
+/* Walk recursively down through directories, *
+ * including lock-null resources as we go. */
+static dav_error * dav_fs_walker(dav_fs_walker_context *fsctx, int depth)
+{
+ const dav_walk_params *params = fsctx->params;
+ apr_pool_t *pool = params->pool;
+ apr_status_t status;
+ dav_error *err = NULL;
+ int isdir = fsctx->res1.collection;
+ apr_finfo_t dirent;
+ apr_dir_t *dirp;
+
+ /* ensure the context is prepared properly, then call the func */
+ err = (*params->func)(&fsctx->wres,
+ isdir
+ ? DAV_CALLTYPE_COLLECTION
+ : DAV_CALLTYPE_MEMBER);
+ if (err != NULL) {
+ return err;
+ }
+
+ if (depth == 0 || !isdir) {
+ return NULL;
+ }
+
+ /* put a trailing slash onto the directory, in preparation for appending
+ * files to it as we discovery them within the directory */
+ dav_check_bufsize(pool, &fsctx->path1, DAV_BUFFER_PAD);
+ fsctx->path1.buf[fsctx->path1.cur_len++] = '/';
+ fsctx->path1.buf[fsctx->path1.cur_len] = '\0'; /* in pad area */
+
+ /* if a secondary path is present, then do that, too */
+ if (fsctx->path2.buf != NULL) {
+ dav_check_bufsize(pool, &fsctx->path2, DAV_BUFFER_PAD);
+ fsctx->path2.buf[fsctx->path2.cur_len++] = '/';
+ fsctx->path2.buf[fsctx->path2.cur_len] = '\0'; /* in pad area */
+ }
+
+ /* Note: the URI should ALREADY have a trailing "/" */
+
+ /* for this first pass of files, all resources exist */
+ fsctx->res1.exists = 1;
+
+ /* a file is the default; we'll adjust if we hit a directory */
+ fsctx->res1.collection = 0;
+ fsctx->res2.collection = 0;
+
+ /* open and scan the directory */
+ if ((status = apr_dir_open(&dirp, fsctx->path1.buf, pool)) != APR_SUCCESS) {
+ /* ### need a better error */
+ return dav_new_error(pool, HTTP_NOT_FOUND, 0, status, NULL);
+ }
+ while ((apr_dir_read(&dirent, APR_FINFO_DIRENT, dirp)) == APR_SUCCESS) {
+ apr_size_t len;
+
+ len = strlen(dirent.name);
+
+ /* avoid recursing into our current, parent, or state directories */
+ if (dirent.name[0] == '.'
+ && (len == 1 || (dirent.name[1] == '.' && len == 2))) {
+ continue;
+ }
+
+ if (params->walk_type & DAV_WALKTYPE_AUTH) {
+ /* ### need to authorize each file */
+ /* ### example: .htaccess is normally configured to fail auth */
+
+ /* stuff in the state directory and temp files are never authorized! */
+ if (!strcmp(dirent.name, DAV_FS_STATE_DIR) ||
+ !strncmp(dirent.name, DAV_FS_TMP_PREFIX,
+ strlen(DAV_FS_TMP_PREFIX))) {
+ continue;
+ }
+ }
+ /* skip the state dir and temp files unless a HIDDEN is performed */
+ if (!(params->walk_type & DAV_WALKTYPE_HIDDEN)
+ && (!strcmp(dirent.name, DAV_FS_STATE_DIR) ||
+ !strncmp(dirent.name, DAV_FS_TMP_PREFIX,
+ strlen(DAV_FS_TMP_PREFIX)))) {
+ continue;
+ }
+
+ /* append this file onto the path buffer (copy null term) */
+ dav_buffer_place_mem(pool, &fsctx->path1, dirent.name, len + 1, 0);
+
+ status = apr_stat(&fsctx->info1.finfo, fsctx->path1.buf,
+ DAV_FINFO_MASK, pool);
+ if (status != APR_SUCCESS && status != APR_INCOMPLETE) {
+ /* woah! where'd it go? */
+ /* ### should have a better error here */
+ err = dav_new_error(pool, HTTP_NOT_FOUND, 0, status, NULL);
+ break;
+ }
+
+ /* copy the file to the URI, too. NOTE: we will pad an extra byte
+ for the trailing slash later. */
+ dav_buffer_place_mem(pool, &fsctx->uri_buf, dirent.name, len + 1, 1);
+
+ /* if there is a secondary path, then do that, too */
+ if (fsctx->path2.buf != NULL) {
+ dav_buffer_place_mem(pool, &fsctx->path2, dirent.name, len + 1, 0);
+ }
+
+ /* set up the (internal) pathnames for the two resources */
+ fsctx->info1.pathname = fsctx->path1.buf;
+ fsctx->info2.pathname = fsctx->path2.buf;
+
+ /* set up the URI for the current resource */
+ fsctx->res1.uri = fsctx->uri_buf.buf;
+
+ /* ### for now, only process regular files (e.g. skip symlinks) */
+ if (fsctx->info1.finfo.filetype == APR_REG) {
+ /* call the function for the specified dir + file */
+ if ((err = (*params->func)(&fsctx->wres,
+ DAV_CALLTYPE_MEMBER)) != NULL) {
+ /* ### maybe add a higher-level description? */
+ break;
+ }
+ }
+ else if (fsctx->info1.finfo.filetype == APR_DIR) {
+ apr_size_t save_path_len = fsctx->path1.cur_len;
+ apr_size_t save_uri_len = fsctx->uri_buf.cur_len;
+ apr_size_t save_path2_len = fsctx->path2.cur_len;
+
+ /* adjust length to incorporate the subdir name */
+ fsctx->path1.cur_len += len;
+ fsctx->path2.cur_len += len;
+
+ /* adjust URI length to incorporate subdir and a slash */
+ fsctx->uri_buf.cur_len += len + 1;
+ fsctx->uri_buf.buf[fsctx->uri_buf.cur_len - 1] = '/';
+ fsctx->uri_buf.buf[fsctx->uri_buf.cur_len] = '\0';
+
+ /* switch over to a collection */
+ fsctx->res1.collection = 1;
+ fsctx->res2.collection = 1;
+
+ /* recurse on the subdir */
+ /* ### don't always want to quit on error from single child */
+ if ((err = dav_fs_walker(fsctx, depth - 1)) != NULL) {
+ /* ### maybe add a higher-level description? */
+ break;
+ }
+
+ /* put the various information back */
+ fsctx->path1.cur_len = save_path_len;
+ fsctx->path2.cur_len = save_path2_len;
+ fsctx->uri_buf.cur_len = save_uri_len;
+
+ fsctx->res1.collection = 0;
+ fsctx->res2.collection = 0;
+
+ /* assert: res1.exists == 1 */
+ }
+ }
+
+ /* ### check the return value of this? */
+ apr_dir_close(dirp);
+
+ if (err != NULL)
+ return err;
+
+ if (params->walk_type & DAV_WALKTYPE_LOCKNULL) {
+ apr_size_t offset = 0;
+
+ /* null terminate the directory name */
+ fsctx->path1.buf[fsctx->path1.cur_len - 1] = '\0';
+
+ /* Include any lock null resources found in this collection */
+ fsctx->res1.collection = 1;
+ if ((err = dav_fs_get_locknull_members(&fsctx->res1,
+ &fsctx->locknull_buf)) != NULL) {
+ /* ### maybe add a higher-level description? */
+ return err;
+ }
+
+ /* put a slash back on the end of the directory */
+ fsctx->path1.buf[fsctx->path1.cur_len - 1] = '/';
+
+ /* these are all non-existent (files) */
+ fsctx->res1.exists = 0;
+ fsctx->res1.collection = 0;
+ memset(&fsctx->info1.finfo, 0, sizeof(fsctx->info1.finfo));
+
+ while (offset < fsctx->locknull_buf.cur_len) {
+ apr_size_t len = strlen(fsctx->locknull_buf.buf + offset);
+ dav_lock *locks = NULL;
+
+ /*
+ ** Append the locknull file to the paths and the URI. Note that
+ ** we don't have to pad the URI for a slash since a locknull
+ ** resource is not a collection.
+ */
+ dav_buffer_place_mem(pool, &fsctx->path1,
+ fsctx->locknull_buf.buf + offset, len + 1, 0);
+ dav_buffer_place_mem(pool, &fsctx->uri_buf,
+ fsctx->locknull_buf.buf + offset, len + 1, 0);
+ if (fsctx->path2.buf != NULL) {
+ dav_buffer_place_mem(pool, &fsctx->path2,
+ fsctx->locknull_buf.buf + offset,
+ len + 1, 0);
+ }
+
+ /* set up the (internal) pathnames for the two resources */
+ fsctx->info1.pathname = fsctx->path1.buf;
+ fsctx->info2.pathname = fsctx->path2.buf;
+
+ /* set up the URI for the current resource */
+ fsctx->res1.uri = fsctx->uri_buf.buf;
+
+ /*
+ ** To prevent a PROPFIND showing an expired locknull
+ ** resource, query the lock database to force removal
+ ** of both the lock entry and .locknull, if necessary..
+ ** Sure, the query in PROPFIND would do this.. after
+ ** the locknull resource was already included in the
+ ** return.
+ **
+ ** NOTE: we assume the caller has opened the lock database
+ ** if they have provided DAV_WALKTYPE_LOCKNULL.
+ */
+ /* ### we should also look into opening it read-only and
+ ### eliding timed-out items from the walk, yet leaving
+ ### them in the locknull database until somebody opens
+ ### the thing writable.
+ */
+ /* ### probably ought to use has_locks. note the problem
+ ### mentioned above, though... we would traverse this as
+ ### a locknull, but then a PROPFIND would load the lock
+ ### info, causing a timeout and the locks would not be
+ ### reported. Therefore, a null resource would be returned
+ ### in the PROPFIND.
+ ###
+ ### alternative: just load unresolved locks. any direct
+ ### locks will be timed out (correct). any indirect will
+ ### not (correct; consider if a parent timed out -- the
+ ### timeout routines do not walk and remove indirects;
+ ### even the resolve func would probably fail when it
+ ### tried to find a timed-out direct lock).
+ */
+ if ((err = dav_lock_query(params->lockdb, &fsctx->res1,
+ &locks)) != NULL) {
+ /* ### maybe add a higher-level description? */
+ return err;
+ }
+
+ /* call the function for the specified dir + file */
+ if (locks != NULL &&
+ (err = (*params->func)(&fsctx->wres,
+ DAV_CALLTYPE_LOCKNULL)) != NULL) {
+ /* ### maybe add a higher-level description? */
+ return err;
+ }
+
+ offset += len + 1;
+ }
+
+ /* reset the exists flag */
+ fsctx->res1.exists = 1;
+ }
+
+ if (params->walk_type & DAV_WALKTYPE_POSTFIX) {
+ /* replace the dirs' trailing slashes with null terms */
+ fsctx->path1.buf[--fsctx->path1.cur_len] = '\0';
+ fsctx->uri_buf.buf[--fsctx->uri_buf.cur_len] = '\0';
+ if (fsctx->path2.buf != NULL) {
+ fsctx->path2.buf[--fsctx->path2.cur_len] = '\0';
+ }
+
+ /* this is a collection which exists */
+ fsctx->res1.collection = 1;
+
+ return (*params->func)(&fsctx->wres, DAV_CALLTYPE_POSTFIX);
+ }
+
+ return NULL;
+}
+
+static dav_error * dav_fs_internal_walk(const dav_walk_params *params,
+ int depth, int is_move,
+ const dav_resource *root_dst,
+ dav_response **response)
+{
+ dav_fs_walker_context fsctx = { 0 };
+ dav_error *err;
+ dav_fs_copymove_walk_ctx cm_ctx = { 0 };
+
+#if DAV_DEBUG
+ if ((params->walk_type & DAV_WALKTYPE_LOCKNULL) != 0
+ && params->lockdb == NULL) {
+ return dav_new_error(params->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "DESIGN ERROR: walker called to walk locknull "
+ "resources, but a lockdb was not provided.");
+ }
+#endif
+
+ fsctx.params = params;
+ fsctx.wres.walk_ctx = params->walk_ctx;
+ fsctx.wres.pool = params->pool;
+
+ /* ### zero out versioned, working, baselined? */
+
+ fsctx.res1 = *params->root;
+ fsctx.res1.pool = params->pool;
+
+ fsctx.res1.info = &fsctx.info1;
+ fsctx.info1 = *params->root->info;
+
+ /* the pathname is stored in the path1 buffer */
+ dav_buffer_init(params->pool, &fsctx.path1, fsctx.info1.pathname);
+ fsctx.info1.pathname = fsctx.path1.buf;
+
+ if (root_dst != NULL) {
+ /* internal call from the COPY/MOVE code. set it up. */
+
+ fsctx.wres.walk_ctx = &cm_ctx;
+ cm_ctx.is_move = is_move;
+ cm_ctx.res_dst = &fsctx.res2;
+ cm_ctx.root = params->root;
+ cm_ctx.pool = params->pool;
+
+ fsctx.res2 = *root_dst;
+ fsctx.res2.exists = 0;
+ fsctx.res2.collection = 0;
+ fsctx.res2.uri = NULL; /* we don't track this */
+ fsctx.res2.pool = params->pool;
+
+ fsctx.res2.info = &fsctx.info2;
+ fsctx.info2 = *root_dst->info;
+
+ /* res2 does not exist -- clear its finfo structure */
+ memset(&fsctx.info2.finfo, 0, sizeof(fsctx.info2.finfo));
+
+ /* the pathname is stored in the path2 buffer */
+ dav_buffer_init(params->pool, &fsctx.path2, fsctx.info2.pathname);
+ fsctx.info2.pathname = fsctx.path2.buf;
+ }
+
+ /* prep the URI buffer */
+ dav_buffer_init(params->pool, &fsctx.uri_buf, params->root->uri);
+
+ /* if we have a directory, then ensure the URI has a trailing "/" */
+ if (fsctx.res1.collection
+ && fsctx.uri_buf.buf[fsctx.uri_buf.cur_len - 1] != '/') {
+
+ /* this will fall into the pad area */
+ fsctx.uri_buf.buf[fsctx.uri_buf.cur_len++] = '/';
+ fsctx.uri_buf.buf[fsctx.uri_buf.cur_len] = '\0';
+ }
+
+ /* the current resource's URI is stored in the uri_buf buffer */
+ fsctx.res1.uri = fsctx.uri_buf.buf;
+
+ /* point the callback's resource at our structure */
+ fsctx.wres.resource = &fsctx.res1;
+
+ /* always return the error, and any/all multistatus responses */
+ err = dav_fs_walker(&fsctx, depth);
+ *response = fsctx.wres.response;
+ return err;
+}
+
+static dav_error * dav_fs_walk(const dav_walk_params *params, int depth,
+ dav_response **response)
+{
+ /* always return the error, and any/all multistatus responses */
+ return dav_fs_internal_walk(params, depth, 0, NULL, response);
+}
+
+/* dav_fs_etag: Creates an etag for the file path.
+ */
+static const char *dav_fs_getetag(const dav_resource *resource)
+{
+ etag_rec er;
+
+ dav_resource_private *ctx = resource->info;
+
+ if (!resource->exists || !ctx->r) {
+ return "";
+ }
+
+ er.vlist_validator = NULL;
+ er.request_time = ctx->r->request_time;
+ er.finfo = &ctx->finfo;
+ er.pathname = ctx->pathname;
+ er.fd = NULL;
+ er.force_weak = 0;
+
+ return ap_make_etag_ex(ctx->r, &er);
+}
+
+static const dav_hooks_repository dav_hooks_repository_fs =
+{
+ DEBUG_GET_HANDLER, /* normally: special GET handling not required */
+ dav_fs_get_resource,
+ dav_fs_get_parent_resource,
+ dav_fs_is_same_resource,
+ dav_fs_is_parent_resource,
+ dav_fs_open_stream,
+ dav_fs_close_stream,
+ dav_fs_write_stream,
+ dav_fs_seek_stream,
+#if DEBUG_GET_HANDLER
+ dav_fs_set_headers,
+ dav_fs_deliver,
+#else
+ NULL,
+ NULL,
+#endif
+ dav_fs_create_collection,
+ dav_fs_copy_resource,
+ dav_fs_move_resource,
+ dav_fs_remove_resource,
+ dav_fs_walk,
+ dav_fs_getetag,
+ NULL,
+ dav_fs_get_request_rec,
+ dav_fs_pathname
+};
+
+static dav_prop_insert dav_fs_insert_prop(const dav_resource *resource,
+ int propid, dav_prop_insert what,
+ apr_text_header *phdr)
+{
+ const char *value;
+ const char *s;
+ apr_pool_t *p = resource->info->pool;
+ const dav_liveprop_spec *info;
+ long global_ns;
+
+ /* an HTTP-date can be 29 chars plus a null term */
+ /* a 64-bit size can be 20 chars plus a null term */
+ char buf[DAV_TIMEBUF_SIZE];
+
+ /*
+ ** None of FS provider properties are defined if the resource does not
+ ** exist. Just bail for this case.
+ **
+ ** Even though we state that the FS properties are not defined, the
+ ** client cannot store dead values -- we deny that thru the is_writable
+ ** hook function.
+ */
+ if (!resource->exists)
+ return DAV_PROP_INSERT_NOTDEF;
+
+ switch (propid) {
+ case DAV_PROPID_creationdate:
+ /*
+ ** Closest thing to a creation date. since we don't actually
+ ** perform the operations that would modify ctime (after we
+ ** create the file), then we should be pretty safe here.
+ */
+ dav_format_time(DAV_STYLE_ISO8601,
+ resource->info->finfo.ctime,
+ buf, sizeof(buf));
+ value = buf;
+ break;
+
+ case DAV_PROPID_getcontentlength:
+ /* our property, but not defined on collection resources */
+ if (resource->collection)
+ return DAV_PROP_INSERT_NOTDEF;
+
+ apr_snprintf(buf, sizeof(buf), "%" APR_OFF_T_FMT, resource->info->finfo.size);
+ value = buf;
+ break;
+
+ case DAV_PROPID_getetag:
+ value = dav_fs_getetag(resource);
+ break;
+
+ case DAV_PROPID_getlastmodified:
+ dav_format_time(DAV_STYLE_RFC822,
+ resource->info->finfo.mtime,
+ buf, sizeof(buf));
+ value = buf;
+ break;
+
+ case DAV_PROPID_FS_executable:
+ /* our property, but not defined on collection resources */
+ if (resource->collection)
+ return DAV_PROP_INSERT_NOTDEF;
+
+ /* our property, but not defined on this platform */
+ if (!(resource->info->finfo.valid & APR_FINFO_UPROT))
+ return DAV_PROP_INSERT_NOTDEF;
+
+ /* the files are "ours" so we only need to check owner exec privs */
+ if (resource->info->finfo.protection & APR_UEXECUTE)
+ value = "T";
+ else
+ value = "F";
+ break;
+
+ default:
+ /* ### what the heck was this property? */
+ return DAV_PROP_INSERT_NOTDEF;
+ }
+
+ /* assert: value != NULL */
+
+ /* get the information and global NS index for the property */
+ global_ns = dav_get_liveprop_info(propid, &dav_fs_liveprop_group, &info);
+
+ /* assert: info != NULL && info->name != NULL */
+
+ /* DBG3("FS: inserting lp%d:%s (local %d)", ns, scan->name, scan->ns); */
+
+ if (what == DAV_PROP_INSERT_VALUE) {
+ s = apr_psprintf(p, "<lp%ld:%s>%s</lp%ld:%s>" DEBUG_CR,
+ global_ns, info->name, value, global_ns, info->name);
+ }
+ else if (what == DAV_PROP_INSERT_NAME) {
+ s = apr_psprintf(p, "<lp%ld:%s/>" DEBUG_CR, global_ns, info->name);
+ }
+ else {
+ /* assert: what == DAV_PROP_INSERT_SUPPORTED */
+ s = apr_pstrcat(p,
+ "<D:supported-live-property D:name=\"",
+ info->name,
+ "\" D:namespace=\"",
+ dav_fs_namespace_uris[info->ns],
+ "\"/>" DEBUG_CR, NULL);
+ }
+ apr_text_append(p, phdr, s);
+
+ /* we inserted what was asked for */
+ return what;
+}
+
+static int dav_fs_is_writable(const dav_resource *resource, int propid)
+{
+ const dav_liveprop_spec *info;
+
+#ifdef DAV_FS_HAS_EXECUTABLE
+ /* if we have the executable property, and this isn't a collection,
+ then the property is writable. */
+ if (propid == DAV_PROPID_FS_executable && !resource->collection)
+ return 1;
+#endif
+
+ (void) dav_get_liveprop_info(propid, &dav_fs_liveprop_group, &info);
+ return info->is_writable;
+}
+
+static dav_error *dav_fs_patch_validate(const dav_resource *resource,
+ const apr_xml_elem *elem,
+ int operation,
+ void **context,
+ int *defer_to_dead)
+{
+ const apr_text *cdata;
+ const apr_text *f_cdata;
+ char value;
+ dav_elem_private *priv = elem->priv;
+
+ if (priv->propid != DAV_PROPID_FS_executable) {
+ *defer_to_dead = 1;
+ return NULL;
+ }
+
+ if (operation == DAV_PROP_OP_DELETE) {
+ return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0, 0,
+ "The 'executable' property cannot be removed.");
+ }
+
+ cdata = elem->first_cdata.first;
+
+ /* ### hmm. this isn't actually looking at all the possible text items */
+ f_cdata = elem->first_child == NULL
+ ? NULL
+ : elem->first_child->following_cdata.first;
+
+ /* DBG3("name=%s cdata=%s f_cdata=%s",elem->name,cdata ? cdata->text : "[null]",f_cdata ? f_cdata->text : "[null]"); */
+
+ if (cdata == NULL) {
+ if (f_cdata == NULL) {
+ return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0, 0,
+ "The 'executable' property expects a single "
+ "character, valued 'T' or 'F'. There was no "
+ "value submitted.");
+ }
+ cdata = f_cdata;
+ }
+ else if (f_cdata != NULL)
+ goto too_long;
+
+ if (cdata->next != NULL || strlen(cdata->text) != 1)
+ goto too_long;
+
+ value = cdata->text[0];
+ if (value != 'T' && value != 'F') {
+ return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0, 0,
+ "The 'executable' property expects a single "
+ "character, valued 'T' or 'F'. The value "
+ "submitted is invalid.");
+ }
+
+ *context = (void *)((long)(value == 'T'));
+
+ return NULL;
+
+ too_long:
+ return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0, 0,
+ "The 'executable' property expects a single "
+ "character, valued 'T' or 'F'. The value submitted "
+ "has too many characters.");
+
+}
+
+static dav_error *dav_fs_patch_exec(const dav_resource *resource,
+ const apr_xml_elem *elem,
+ int operation,
+ void *context,
+ dav_liveprop_rollback **rollback_ctx)
+{
+ long value = context != NULL;
+ apr_fileperms_t perms = resource->info->finfo.protection;
+ apr_status_t status;
+ long old_value = (perms & APR_UEXECUTE) != 0;
+
+ /* assert: prop == executable. operation == SET. */
+
+ /* don't do anything if there is no change. no rollback info either. */
+ /* DBG2("new value=%d (old=%d)", value, old_value); */
+ if (value == old_value)
+ return NULL;
+
+ perms &= ~APR_UEXECUTE;
+ if (value)
+ perms |= APR_UEXECUTE;
+
+ if ((status = apr_file_perms_set(resource->info->pathname, perms))
+ != APR_SUCCESS) {
+ return dav_new_error(resource->info->pool,
+ HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ "Could not set the executable flag of the "
+ "target resource.");
+ }
+
+ /* update the resource and set up the rollback context */
+ resource->info->finfo.protection = perms;
+ *rollback_ctx = (dav_liveprop_rollback *)old_value;
+
+ return NULL;
+}
+
+static void dav_fs_patch_commit(const dav_resource *resource,
+ int operation,
+ void *context,
+ dav_liveprop_rollback *rollback_ctx)
+{
+ /* nothing to do */
+}
+
+static dav_error *dav_fs_patch_rollback(const dav_resource *resource,
+ int operation,
+ void *context,
+ dav_liveprop_rollback *rollback_ctx)
+{
+ apr_fileperms_t perms = resource->info->finfo.protection & ~APR_UEXECUTE;
+ apr_status_t status;
+ int value = rollback_ctx != NULL;
+
+ /* assert: prop == executable. operation == SET. */
+
+ /* restore the executable bit */
+ if (value)
+ perms |= APR_UEXECUTE;
+
+ if ((status = apr_file_perms_set(resource->info->pathname, perms))
+ != APR_SUCCESS) {
+ return dav_new_error(resource->info->pool,
+ HTTP_INTERNAL_SERVER_ERROR, 0, status,
+ "After a failure occurred, the resource's "
+ "executable flag could not be restored.");
+ }
+
+ /* restore the resource's state */
+ resource->info->finfo.protection = perms;
+
+ return NULL;
+}
+
+
+static const dav_hooks_liveprop dav_hooks_liveprop_fs =
+{
+ dav_fs_insert_prop,
+ dav_fs_is_writable,
+ dav_fs_namespace_uris,
+ dav_fs_patch_validate,
+ dav_fs_patch_exec,
+ dav_fs_patch_commit,
+ dav_fs_patch_rollback
+};
+
+static const dav_provider dav_fs_provider =
+{
+ &dav_hooks_repository_fs,
+ &dav_hooks_db_dbm,
+ &dav_hooks_locks_fs,
+ NULL, /* vsn */
+ NULL, /* binding */
+ NULL, /* search */
+
+ NULL /* ctx */
+};
+
+void dav_fs_gather_propsets(apr_array_header_t *uris)
+{
+#ifdef DAV_FS_HAS_EXECUTABLE
+ *(const char **)apr_array_push(uris) =
+ "<http://apache.org/dav/propset/fs/1>";
+#endif
+}
+
+int dav_fs_find_liveprop(const dav_resource *resource,
+ const char *ns_uri, const char *name,
+ const dav_hooks_liveprop **hooks)
+{
+ /* don't try to find any liveprops if this isn't "our" resource */
+ if (resource->hooks != &dav_hooks_repository_fs)
+ return 0;
+ return dav_do_find_liveprop(ns_uri, name, &dav_fs_liveprop_group, hooks);
+}
+
+void dav_fs_insert_all_liveprops(request_rec *r, const dav_resource *resource,
+ dav_prop_insert what, apr_text_header *phdr)
+{
+ /* don't insert any liveprops if this isn't "our" resource */
+ if (resource->hooks != &dav_hooks_repository_fs)
+ return;
+
+ if (!resource->exists) {
+ /* a lock-null resource */
+ /*
+ ** ### technically, we should insert empty properties. dunno offhand
+ ** ### what part of the spec said this, but it was essentially thus:
+ ** ### "the properties should be defined, but may have no value".
+ */
+ return;
+ }
+
+ (void) dav_fs_insert_prop(resource, DAV_PROPID_creationdate,
+ what, phdr);
+ (void) dav_fs_insert_prop(resource, DAV_PROPID_getcontentlength,
+ what, phdr);
+ (void) dav_fs_insert_prop(resource, DAV_PROPID_getlastmodified,
+ what, phdr);
+ (void) dav_fs_insert_prop(resource, DAV_PROPID_getetag,
+ what, phdr);
+
+#ifdef DAV_FS_HAS_EXECUTABLE
+ /* Only insert this property if it is defined for this platform. */
+ (void) dav_fs_insert_prop(resource, DAV_PROPID_FS_executable,
+ what, phdr);
+#endif
+
+ /* ### we know the others aren't defined as liveprops */
+}
+
+void dav_fs_register(apr_pool_t *p)
+{
+ /* register the namespace URIs */
+ dav_register_liveprop_group(p, &dav_fs_liveprop_group);
+
+ /* register the repository provider */
+ dav_register_provider(p, "filesystem", &dav_fs_provider);
+}
diff --git a/modules/dav/fs/repos.h b/modules/dav/fs/repos.h
new file mode 100644
index 0000000..b164611
--- /dev/null
+++ b/modules/dav/fs/repos.h
@@ -0,0 +1,84 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file repos.h
+ * @brief Declarations for the filesystem repository implementation
+ *
+ * @addtogroup MOD_DAV
+ * @{
+ */
+
+#ifndef _DAV_FS_REPOS_H_
+#define _DAV_FS_REPOS_H_
+
+/* the subdirectory to hold all DAV-related information for a directory */
+#define DAV_FS_STATE_DIR ".DAV"
+#define DAV_FS_STATE_FILE_FOR_DIR ".state_for_dir"
+#define DAV_FS_LOCK_NULL_FILE ".locknull"
+
+
+/* ensure that our state subdirectory is present */
+void dav_fs_ensure_state_dir(apr_pool_t *p, const char *dirname);
+
+/* return the storage pool associated with a resource */
+apr_pool_t *dav_fs_pool(const dav_resource *resource);
+
+/* return the full pathname for a resource */
+const char *dav_fs_pathname(const dav_resource *resource);
+
+/* return the directory and filename for a resource */
+dav_error * dav_fs_dir_file_name(const dav_resource *resource,
+ const char **dirpath,
+ const char **fname);
+
+/* return the list of locknull members in this resource's directory */
+dav_error * dav_fs_get_locknull_members(const dav_resource *resource,
+ dav_buffer *pbuf);
+
+
+/* DBM functions used by the repository and locking providers */
+extern const dav_hooks_db dav_hooks_db_dbm;
+
+dav_error * dav_dbm_open_direct(apr_pool_t *p, const char *pathname, int ro,
+ dav_db **pdb);
+void dav_dbm_get_statefiles(apr_pool_t *p, const char *fname,
+ const char **state1, const char **state2);
+dav_error * dav_dbm_delete(dav_db *db, apr_datum_t key);
+dav_error * dav_dbm_store(dav_db *db, apr_datum_t key, apr_datum_t value);
+dav_error * dav_dbm_fetch(dav_db *db, apr_datum_t key, apr_datum_t *pvalue);
+void dav_dbm_freedatum(dav_db *db, apr_datum_t data);
+int dav_dbm_exists(dav_db *db, apr_datum_t key);
+void dav_dbm_close(dav_db *db);
+
+/* where is the lock database located? */
+const char *dav_get_lockdb_path(const request_rec *r);
+
+const dav_hooks_locks *dav_fs_get_lock_hooks(request_rec *r);
+const dav_hooks_propdb *dav_fs_get_propdb_hooks(request_rec *r);
+
+void dav_fs_gather_propsets(apr_array_header_t *uris);
+int dav_fs_find_liveprop(const dav_resource *resource,
+ const char *ns_uri, const char *name,
+ const dav_hooks_liveprop **hooks);
+void dav_fs_insert_all_liveprops(request_rec *r, const dav_resource *resource,
+ dav_prop_insert what, apr_text_header *phdr);
+
+void dav_fs_register(apr_pool_t *p);
+
+#endif /* _DAV_FS_REPOS_H_ */
+/** @} */
+
diff --git a/modules/dav/lock/Makefile.in b/modules/dav/lock/Makefile.in
new file mode 100644
index 0000000..7c5c149
--- /dev/null
+++ b/modules/dav/lock/Makefile.in
@@ -0,0 +1,3 @@
+# a modules Makefile has no explicit targets -- they will be defined by
+# whatever modules are enabled. just grab special.mk to deal with this.
+include $(top_srcdir)/build/special.mk
diff --git a/modules/dav/lock/NWGNUmakefile b/modules/dav/lock/NWGNUmakefile
new file mode 100644
index 0000000..b515d27
--- /dev/null
+++ b/modules/dav/lock/NWGNUmakefile
@@ -0,0 +1,259 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/server/mpm/netware \
+ $(AP_WORK)/modules/dav/main \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = moddavlk
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) DAV Database Lock Sub-Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = $(NLM_NAME) Thread
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 65536
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If this is specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# Declare all target files (you must add your files here)
+#
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_dav_lock.o \
+ $(OBJDIR)/locks.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ Apache2 \
+ Libc \
+ mod_dav \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @libc.imp \
+ @aprlib.imp \
+ @httpd.imp \
+ @../main/dav.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ dav_lock_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/dav/lock/config6.m4 b/modules/dav/lock/config6.m4
new file mode 100644
index 0000000..02a05e5
--- /dev/null
+++ b/modules/dav/lock/config6.m4
@@ -0,0 +1,17 @@
+dnl modules enabled in this directory by default
+
+APACHE_MODPATH_INIT(dav/lock)
+
+dav_lock_objects="mod_dav_lock.lo locks.lo"
+
+case "$host" in
+ *os2*)
+ # OS/2 DLLs must resolve all symbols at build time
+ # and we need some from main DAV module
+ dav_lock_objects="$dav_lock_objects ../main/mod_dav.la"
+ ;;
+esac
+
+APACHE_MODULE(dav_lock, DAV provider for generic locking, $dav_lock_objects, ,,,dav)
+
+APACHE_MODPATH_FINISH
diff --git a/modules/dav/lock/locks.c b/modules/dav/lock/locks.c
new file mode 100644
index 0000000..0f072ec
--- /dev/null
+++ b/modules/dav/lock/locks.c
@@ -0,0 +1,1237 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Generic DAV lock implementation that a DAV provider can use.
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_file_io.h"
+#include "apr_uuid.h"
+
+#define APR_WANT_MEMFUNC
+#include "apr_want.h"
+
+#include "apr_version.h"
+#if !APR_VERSION_AT_LEAST(2,0,0)
+#include "apu_version.h"
+#endif
+
+#include "httpd.h"
+#include "http_log.h"
+#include "http_main.h" /* for ap_server_conf */
+
+#include "mod_dav.h"
+
+#include "locks.h"
+
+
+/* ---------------------------------------------------------------
+ *
+ * Lock database primitives
+ *
+ */
+
+/*
+ * LOCK DATABASES
+ *
+ * Lockdiscovery information is stored in the single lock database specified
+ * by the DAVGenericLockDB directive. Information about this db is stored in
+ * the per-dir configuration.
+ *
+ * KEY
+ *
+ * The database is keyed by a key_type unsigned char (DAV_TYPE_FNAME)
+ * followed by full path.
+ *
+ * VALUE
+ *
+ * The value consists of a list of elements.
+ * DIRECT LOCK: [char (DAV_LOCK_DIRECT),
+ * char (dav_lock_scope),
+ * char (dav_lock_type),
+ * int depth,
+ * time_t expires,
+ * apr_uuid_t locktoken,
+ * char[] owner,
+ * char[] auth_user]
+ *
+ * INDIRECT LOCK: [char (DAV_LOCK_INDIRECT),
+ * apr_uuid_t locktoken,
+ * time_t expires,
+ * int key_size,
+ * char[] key]
+ * The key is to the collection lock that resulted in this indirect lock
+ */
+
+#define DAV_TRUE 1
+#define DAV_FALSE 0
+
+#define DAV_CREATE_LIST 23
+#define DAV_APPEND_LIST 24
+
+/* Stored lock_discovery prefix */
+#define DAV_LOCK_DIRECT 1
+#define DAV_LOCK_INDIRECT 2
+
+#define DAV_TYPE_FNAME 11
+
+/* Use the opaquelock scheme for locktokens */
+struct dav_locktoken {
+ apr_uuid_t uuid;
+};
+#define dav_compare_locktoken(plt1, plt2) \
+ memcmp(&(plt1)->uuid, &(plt2)->uuid, sizeof((plt1)->uuid))
+
+
+/* #################################################################
+ * ### keep these structures (internal) or move fully to dav_lock?
+ */
+
+/*
+ * We need to reliably size the fixed-length portion of
+ * dav_lock_discovery; best to separate it into another
+ * struct for a convenient sizeof, unless we pack lock_discovery.
+ */
+typedef struct dav_lock_discovery_fixed
+{
+ char scope;
+ char type;
+ int depth;
+ time_t timeout;
+} dav_lock_discovery_fixed;
+
+typedef struct dav_lock_discovery
+{
+ struct dav_lock_discovery_fixed f;
+
+ dav_locktoken *locktoken;
+ const char *owner; /* owner field from activelock */
+ const char *auth_user; /* authenticated user who created the lock */
+ struct dav_lock_discovery *next;
+} dav_lock_discovery;
+
+/* Indirect locks represent locks inherited from containing collections.
+ * They reference the lock token for the collection the lock is
+ * inherited from. A lock provider may also define a key to the
+ * inherited lock, for fast datbase lookup. The key is opaque outside
+ * the lock provider.
+ */
+typedef struct dav_lock_indirect
+{
+ dav_locktoken *locktoken;
+ apr_datum_t key;
+ struct dav_lock_indirect *next;
+ time_t timeout;
+} dav_lock_indirect;
+
+/* ################################################################# */
+
+/*
+ * Stored direct lock info - full lock_discovery length:
+ * prefix + Fixed length + lock token + 2 strings + 2 nulls (one for each
+ * string)
+ */
+#define dav_size_direct(a) (1 + sizeof(dav_lock_discovery_fixed) \
+ + sizeof(apr_uuid_t) \
+ + ((a)->owner ? strlen((a)->owner) : 0) \
+ + ((a)->auth_user ? strlen((a)->auth_user) : 0) \
+ + 2)
+
+/* Stored indirect lock info - lock token and apr_datum_t */
+#define dav_size_indirect(a) (1 + sizeof(apr_uuid_t) \
+ + sizeof(time_t) \
+ + sizeof(int) + (a)->key.dsize)
+
+/*
+ * The lockdb structure.
+ *
+ * The <db> field may be NULL, meaning one of two things:
+ * 1) That we have not actually opened the underlying database (yet). The
+ * <opened> field should be false.
+ * 2) We opened it readonly and it wasn't present.
+ *
+ * The delayed opening (determined by <opened>) makes creating a lockdb
+ * quick, while deferring the underlying I/O until it is actually required.
+ *
+ * We export the notion of a lockdb, but hide the details of it. Most
+ * implementations will use a database of some kind, but it is certainly
+ * possible that alternatives could be used.
+ */
+struct dav_lockdb_private
+{
+ request_rec *r; /* for accessing the uuid state */
+ apr_pool_t *pool; /* a pool to use */
+ const char *lockdb_path; /* where is the lock database? */
+
+ int opened; /* we opened the database */
+ apr_dbm_t *db; /* if non-NULL, the lock database */
+};
+
+typedef struct
+{
+ dav_lockdb pub;
+ dav_lockdb_private priv;
+} dav_lockdb_combined;
+
+/*
+ * The private part of the lock structure.
+ */
+struct dav_lock_private
+{
+ apr_datum_t key; /* key into the lock database */
+};
+typedef struct
+{
+ dav_lock pub;
+ dav_lock_private priv;
+ dav_locktoken token;
+} dav_lock_combined;
+
+/*
+ * This must be forward-declared so the open_lockdb function can use it.
+ */
+extern const dav_hooks_locks dav_hooks_locks_generic;
+
+static dav_error * dav_generic_dbm_new_error(apr_dbm_t *db, apr_pool_t *p,
+ apr_status_t status)
+{
+ int errcode;
+ const char *errstr;
+ dav_error *err;
+ char errbuf[200];
+
+ if (status == APR_SUCCESS) {
+ return NULL;
+ }
+
+ /* There might not be a <db> if we had problems creating it. */
+ if (db == NULL) {
+ errcode = 1;
+ errstr = "Could not open property database.";
+ }
+ else {
+ (void) apr_dbm_geterror(db, &errcode, errbuf, sizeof(errbuf));
+ errstr = apr_pstrdup(p, errbuf);
+ }
+
+ err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, errcode, status, errstr);
+ return err;
+}
+
+/* internal function for creating locks */
+static dav_lock *dav_generic_alloc_lock(dav_lockdb *lockdb, apr_datum_t key,
+ const dav_locktoken *locktoken)
+{
+ dav_lock_combined *comb;
+
+ comb = apr_pcalloc(lockdb->info->pool, sizeof(*comb));
+ comb->pub.rectype = DAV_LOCKREC_DIRECT;
+ comb->pub.info = &comb->priv;
+ comb->priv.key = key;
+
+ if (locktoken == NULL) {
+ comb->pub.locktoken = &comb->token;
+ apr_uuid_get(&comb->token.uuid);
+ }
+ else {
+ comb->pub.locktoken = locktoken;
+ }
+
+ return &comb->pub;
+}
+
+/*
+ * dav_generic_parse_locktoken
+ *
+ * Parse an opaquelocktoken URI into a locktoken.
+ */
+static dav_error * dav_generic_parse_locktoken(apr_pool_t *p,
+ const char *char_token,
+ dav_locktoken **locktoken_p)
+{
+ dav_locktoken *locktoken;
+
+ if (ap_strstr_c(char_token, "opaquelocktoken:") != char_token) {
+ return dav_new_error(p,
+ HTTP_BAD_REQUEST, DAV_ERR_LOCK_UNK_STATE_TOKEN, 0,
+ "The lock token uses an unknown State-token "
+ "format and could not be parsed.");
+ }
+ char_token += 16;
+
+ locktoken = apr_pcalloc(p, sizeof(*locktoken));
+ if (apr_uuid_parse(&locktoken->uuid, char_token)) {
+ return dav_new_error(p, HTTP_BAD_REQUEST, DAV_ERR_LOCK_PARSE_TOKEN, 0,
+ "The opaquelocktoken has an incorrect format "
+ "and could not be parsed.");
+ }
+
+ *locktoken_p = locktoken;
+ return NULL;
+}
+
+/*
+ * dav_generic_format_locktoken
+ *
+ * Generate the URI for a locktoken
+ */
+static const char *dav_generic_format_locktoken(apr_pool_t *p,
+ const dav_locktoken *locktoken)
+{
+ char buf[APR_UUID_FORMATTED_LENGTH + 1];
+
+ apr_uuid_format(buf, &locktoken->uuid);
+ return apr_pstrcat(p, "opaquelocktoken:", buf, NULL);
+}
+
+/*
+ * dav_generic_compare_locktoken
+ *
+ * Determine whether two locktokens are the same
+ */
+static int dav_generic_compare_locktoken(const dav_locktoken *lt1,
+ const dav_locktoken *lt2)
+{
+ return dav_compare_locktoken(lt1, lt2);
+}
+
+/*
+ * dav_generic_really_open_lockdb:
+ *
+ * If the database hasn't been opened yet, then open the thing.
+ */
+static dav_error * dav_generic_really_open_lockdb(dav_lockdb *lockdb)
+{
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ const apr_dbm_driver_t *driver;
+ const apu_err_t *er;
+#endif
+ dav_error *err;
+ apr_status_t status = APR_SUCCESS;
+
+ if (lockdb->info->opened) {
+ return NULL;
+ }
+
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ status = apr_dbm_get_driver(&driver, NULL, &er, lockdb->info->pool);
+
+ if (status) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, status, ap_server_conf, APLOGNO(10288)
+ "mod_dav_lock: The DBM library '%s' could not be loaded: %s",
+ er->reason, er->msg);
+ return dav_new_error(lockdb->info->pool, HTTP_INTERNAL_SERVER_ERROR, 1,
+ status, "Could not load library for property database.");
+ }
+
+ status = apr_dbm_open2(&lockdb->info->db, driver, lockdb->info->lockdb_path,
+ lockdb->ro ? APR_DBM_READONLY : APR_DBM_RWCREATE,
+ APR_OS_DEFAULT, lockdb->info->pool);
+#else
+ status = apr_dbm_open(&lockdb->info->db, lockdb->info->lockdb_path,
+ lockdb->ro ? APR_DBM_READONLY : APR_DBM_RWCREATE,
+ APR_OS_DEFAULT, lockdb->info->pool);
+#endif
+
+ if (status) {
+ err = dav_generic_dbm_new_error(lockdb->info->db, lockdb->info->pool,
+ status);
+ return dav_push_error(lockdb->info->pool,
+ HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_LOCK_OPENDB,
+ "Could not open the lock database.",
+ err);
+ }
+
+ /* all right. it is opened now. */
+ lockdb->info->opened = 1;
+
+ return NULL;
+}
+
+/*
+ * dav_generic_open_lockdb:
+ *
+ * "open" the lock database, as specified in the global server configuration.
+ * If force is TRUE, then the database is opened now, rather than lazily.
+ *
+ * Note that only one can be open read/write.
+ */
+static dav_error * dav_generic_open_lockdb(request_rec *r, int ro, int force,
+ dav_lockdb **lockdb)
+{
+ dav_lockdb_combined *comb;
+
+ comb = apr_pcalloc(r->pool, sizeof(*comb));
+ comb->pub.hooks = &dav_hooks_locks_generic;
+ comb->pub.ro = ro;
+ comb->pub.info = &comb->priv;
+ comb->priv.r = r;
+ comb->priv.pool = r->pool;
+
+ comb->priv.lockdb_path = dav_generic_get_lockdb_path(r);
+ if (comb->priv.lockdb_path == NULL) {
+ return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_LOCK_NO_DB, 0,
+ "A lock database was not specified with the "
+ "DAVGenericLockDB directive. One must be "
+ "specified to use the locking functionality.");
+ }
+
+ /* done initializing. return it. */
+ *lockdb = &comb->pub;
+
+ if (force) {
+ /* ### add a higher-level comment? */
+ return dav_generic_really_open_lockdb(*lockdb);
+ }
+
+ return NULL;
+}
+
+/*
+ * dav_generic_close_lockdb:
+ *
+ * Close it. Duh.
+ */
+static void dav_generic_close_lockdb(dav_lockdb *lockdb)
+{
+ if (lockdb->info->db != NULL) {
+ apr_dbm_close(lockdb->info->db);
+ }
+ lockdb->info->opened = 0;
+}
+
+/*
+ * dav_generic_build_key
+ *
+ * Given a pathname, build a DAV_TYPE_FNAME lock database key.
+ */
+static apr_datum_t dav_generic_build_key(apr_pool_t *p,
+ const dav_resource *resource)
+{
+ apr_datum_t key;
+ const char *pathname = resource->uri;
+
+ /* ### does this allocation have a proper lifetime? need to check */
+ /* ### can we use a buffer for this? */
+
+ /* size is TYPE + pathname + null */
+ key.dsize = strlen(pathname) + 2;
+ key.dptr = apr_palloc(p, key.dsize);
+ *key.dptr = DAV_TYPE_FNAME;
+ memcpy(key.dptr + 1, pathname, key.dsize - 1);
+ if (key.dptr[key.dsize - 2] == '/')
+ key.dptr[--key.dsize - 1] = '\0';
+ return key;
+}
+
+/*
+ * dav_generic_lock_expired: return 1 (true) if the given timeout is in the
+ * past or present (the lock has expired), or 0 (false) if in the future
+ * (the lock has not yet expired).
+ */
+static int dav_generic_lock_expired(time_t expires)
+{
+ return expires != DAV_TIMEOUT_INFINITE && time(NULL) >= expires;
+}
+
+/*
+ * dav_generic_save_lock_record: Saves the lock information specified in the
+ * direct and indirect lock lists about path into the lock database.
+ * If direct and indirect == NULL, the key is removed.
+ */
+static dav_error * dav_generic_save_lock_record(dav_lockdb *lockdb,
+ apr_datum_t key,
+ dav_lock_discovery *direct,
+ dav_lock_indirect *indirect)
+{
+ dav_error *err;
+ apr_status_t status;
+ apr_datum_t val = { 0 };
+ char *ptr;
+ dav_lock_discovery *dp = direct;
+ dav_lock_indirect *ip = indirect;
+
+#if DAV_DEBUG
+ if (lockdb->ro) {
+ return dav_new_error(lockdb->info->pool,
+ HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "INTERNAL DESIGN ERROR: the lockdb was opened "
+ "readonly, but an attempt to save locks was "
+ "performed.");
+ }
+#endif
+
+ if ((err = dav_generic_really_open_lockdb(lockdb)) != NULL) {
+ /* ### add a higher-level error? */
+ return err;
+ }
+
+ /* If nothing to save, delete key */
+ if (dp == NULL && ip == NULL) {
+ /* don't fail if the key is not present */
+ /* ### but what about other errors? */
+ apr_dbm_delete(lockdb->info->db, key);
+ return NULL;
+ }
+
+ while (dp) {
+ val.dsize += dav_size_direct(dp);
+ dp = dp->next;
+ }
+ while (ip) {
+ val.dsize += dav_size_indirect(ip);
+ ip = ip->next;
+ }
+
+ /* ### can this be apr_palloc() ? */
+ /* ### hmmm.... investigate the use of a buffer here */
+ ptr = val.dptr = apr_pcalloc(lockdb->info->pool, val.dsize);
+ dp = direct;
+ ip = indirect;
+
+ while (dp) {
+ /* Direct lock - lock_discovery struct follows */
+ *ptr++ = DAV_LOCK_DIRECT;
+ memcpy(ptr, dp, sizeof(dp->f)); /* Fixed portion of struct */
+ ptr += sizeof(dp->f);
+ memcpy(ptr, dp->locktoken, sizeof(*dp->locktoken));
+ ptr += sizeof(*dp->locktoken);
+ if (dp->owner == NULL) {
+ *ptr++ = '\0';
+ }
+ else {
+ memcpy(ptr, dp->owner, strlen(dp->owner) + 1);
+ ptr += strlen(dp->owner) + 1;
+ }
+ if (dp->auth_user == NULL) {
+ *ptr++ = '\0';
+ }
+ else {
+ memcpy(ptr, dp->auth_user, strlen(dp->auth_user) + 1);
+ ptr += strlen(dp->auth_user) + 1;
+ }
+
+ dp = dp->next;
+ }
+
+ while (ip) {
+ /* Indirect lock prefix */
+ *ptr++ = DAV_LOCK_INDIRECT;
+
+ memcpy(ptr, ip->locktoken, sizeof(*ip->locktoken));
+ ptr += sizeof(*ip->locktoken);
+
+ memcpy(ptr, &ip->timeout, sizeof(ip->timeout));
+ ptr += sizeof(ip->timeout);
+
+ memcpy(ptr, &ip->key.dsize, sizeof(ip->key.dsize));
+ ptr += sizeof(ip->key.dsize);
+
+ memcpy(ptr, ip->key.dptr, ip->key.dsize);
+ ptr += ip->key.dsize;
+
+ ip = ip->next;
+ }
+
+ if ((status = apr_dbm_store(lockdb->info->db, key, val)) != APR_SUCCESS) {
+ /* ### more details? add an error_id? */
+ err = dav_generic_dbm_new_error(lockdb->info->db, lockdb->info->pool,
+ status);
+ return dav_push_error(lockdb->info->pool,
+ HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_LOCK_SAVE_LOCK,
+ "Could not save lock information.",
+ err);
+ }
+
+ return NULL;
+}
+
+/*
+ * dav_load_lock_record: Reads lock information about key from lock db;
+ * creates linked lists of the direct and indirect locks.
+ *
+ * If add_method = DAV_APPEND_LIST, the result will be appended to the
+ * head of the direct and indirect lists supplied.
+ *
+ * Passive lock removal: If lock has timed out, it will not be returned.
+ * ### How much "logging" does RFC 2518 require?
+ */
+static dav_error * dav_generic_load_lock_record(dav_lockdb *lockdb,
+ apr_datum_t key,
+ int add_method,
+ dav_lock_discovery **direct,
+ dav_lock_indirect **indirect)
+{
+ apr_pool_t *p = lockdb->info->pool;
+ dav_error *err;
+ apr_status_t status;
+ apr_size_t offset = 0;
+ int need_save = DAV_FALSE;
+ apr_datum_t val = { 0 };
+ dav_lock_discovery *dp;
+ dav_lock_indirect *ip;
+
+ if (add_method != DAV_APPEND_LIST) {
+ *direct = NULL;
+ *indirect = NULL;
+ }
+
+ if ((err = dav_generic_really_open_lockdb(lockdb)) != NULL) {
+ /* ### add a higher-level error? */
+ return err;
+ }
+
+ /*
+ * If we opened readonly and the db wasn't there, then there are no
+ * locks for this resource. Just exit.
+ */
+ if (lockdb->info->db == NULL) {
+ return NULL;
+ }
+
+ if ((status = apr_dbm_fetch(lockdb->info->db, key, &val)) != APR_SUCCESS) {
+ return dav_generic_dbm_new_error(lockdb->info->db, p, status);
+ }
+
+ if (!val.dsize) {
+ return NULL;
+ }
+
+ while (offset < val.dsize) {
+ switch (*(val.dptr + offset++)) {
+ case DAV_LOCK_DIRECT:
+ /* Create and fill a dav_lock_discovery structure */
+
+ dp = apr_pcalloc(p, sizeof(*dp));
+
+ /* Copy the dav_lock_discovery_fixed portion */
+ memcpy(dp, val.dptr + offset, sizeof(dp->f));
+ offset += sizeof(dp->f);
+
+ /* Copy the lock token. */
+ dp->locktoken = apr_pmemdup(p, val.dptr + offset, sizeof(*dp->locktoken));
+ offset += sizeof(*dp->locktoken);
+
+ /* Do we have an owner field? */
+ if (*(val.dptr + offset) == '\0') {
+ ++offset;
+ }
+ else {
+ apr_size_t len = strlen(val.dptr + offset);
+ dp->owner = apr_pstrmemdup(p, val.dptr + offset, len);
+ offset += len + 1;
+ }
+
+ if (*(val.dptr + offset) == '\0') {
+ ++offset;
+ }
+ else {
+ apr_size_t len = strlen(val.dptr + offset);
+ dp->auth_user = apr_pstrmemdup(p, val.dptr + offset, len);
+ offset += len + 1;
+ }
+
+ if (!dav_generic_lock_expired(dp->f.timeout)) {
+ dp->next = *direct;
+ *direct = dp;
+ }
+ else {
+ need_save = DAV_TRUE;
+ }
+ break;
+
+ case DAV_LOCK_INDIRECT:
+ /* Create and fill a dav_lock_indirect structure */
+
+ ip = apr_pcalloc(p, sizeof(*ip));
+ ip->locktoken = apr_pmemdup(p, val.dptr + offset, sizeof(*ip->locktoken));
+ offset += sizeof(*ip->locktoken);
+ memcpy(&ip->timeout, val.dptr + offset, sizeof(ip->timeout));
+ offset += sizeof(ip->timeout);
+ /* length of datum */
+ ip->key.dsize = *((int *) (val.dptr + offset));
+ offset += sizeof(ip->key.dsize);
+ ip->key.dptr = apr_pmemdup(p, val.dptr + offset, ip->key.dsize);
+ offset += ip->key.dsize;
+
+ if (!dav_generic_lock_expired(ip->timeout)) {
+ ip->next = *indirect;
+ *indirect = ip;
+ }
+ else {
+ need_save = DAV_TRUE;
+ }
+
+ break;
+
+ default:
+ apr_dbm_freedatum(lockdb->info->db, val);
+
+ /* ### should use a computed_desc and insert corrupt token data */
+ --offset;
+ return dav_new_error(p,
+ HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_LOCK_CORRUPT_DB, 0,
+ apr_psprintf(p,
+ "The lock database was found to "
+ "be corrupt. offset %"
+ APR_SIZE_T_FMT ", c=%02x",
+ offset, val.dptr[offset]));
+ }
+ }
+
+ apr_dbm_freedatum(lockdb->info->db, val);
+
+ /* Clean up this record if we found expired locks */
+ /*
+ * ### shouldn't do this if we've been opened READONLY. elide the
+ * ### timed-out locks from the response, but don't save that info back
+ */
+ if (need_save == DAV_TRUE) {
+ return dav_generic_save_lock_record(lockdb, key, *direct, *indirect);
+ }
+
+ return NULL;
+}
+
+/* resolve <indirect>, returning <*direct> */
+static dav_error * dav_generic_resolve(dav_lockdb *lockdb,
+ dav_lock_indirect *indirect,
+ dav_lock_discovery **direct,
+ dav_lock_discovery **ref_dp,
+ dav_lock_indirect **ref_ip)
+{
+ dav_error *err;
+ dav_lock_discovery *dir;
+ dav_lock_indirect *ind;
+
+ if ((err = dav_generic_load_lock_record(lockdb, indirect->key,
+ DAV_CREATE_LIST,
+ &dir, &ind)) != NULL) {
+ /* ### insert a higher-level description? */
+ return err;
+ }
+ if (ref_dp != NULL) {
+ *ref_dp = dir;
+ *ref_ip = ind;
+ }
+
+ for (; dir != NULL; dir = dir->next) {
+ if (!dav_compare_locktoken(indirect->locktoken, dir->locktoken)) {
+ *direct = dir;
+ return NULL;
+ }
+ }
+
+ /* No match found (but we should have found one!) */
+
+ /* ### use a different description and/or error ID? */
+ return dav_new_error(lockdb->info->pool,
+ HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_LOCK_CORRUPT_DB, 0,
+ "The lock database was found to be corrupt. "
+ "An indirect lock's direct lock could not "
+ "be found.");
+}
+
+/* ---------------------------------------------------------------
+ *
+ * Property-related lock functions
+ *
+ */
+
+/*
+ * dav_generic_get_supportedlock: Returns a static string for all
+ * supportedlock properties. I think we save more returning a static string
+ * than constructing it every time, though it might look cleaner.
+ */
+static const char *dav_generic_get_supportedlock(const dav_resource *resource)
+{
+ static const char supported[] = DEBUG_CR
+ "<D:lockentry>" DEBUG_CR
+ "<D:lockscope><D:exclusive/></D:lockscope>" DEBUG_CR
+ "<D:locktype><D:write/></D:locktype>" DEBUG_CR
+ "</D:lockentry>" DEBUG_CR
+ "<D:lockentry>" DEBUG_CR
+ "<D:lockscope><D:shared/></D:lockscope>" DEBUG_CR
+ "<D:locktype><D:write/></D:locktype>" DEBUG_CR
+ "</D:lockentry>" DEBUG_CR;
+
+ return supported;
+}
+
+/* ---------------------------------------------------------------
+ *
+ * General lock functions
+ *
+ */
+
+static dav_error * dav_generic_remove_locknull_state(dav_lockdb *lockdb,
+ const dav_resource *resource)
+{
+ /* We don't need to do anything. */
+ return NULL;
+}
+
+static dav_error * dav_generic_create_lock(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ dav_lock **lock)
+{
+ apr_datum_t key;
+
+ key = dav_generic_build_key(lockdb->info->pool, resource);
+
+ *lock = dav_generic_alloc_lock(lockdb, key, NULL);
+
+ (*lock)->is_locknull = !resource->exists;
+
+ return NULL;
+}
+
+static dav_error * dav_generic_get_locks(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int calltype,
+ dav_lock **locks)
+{
+ apr_pool_t *p = lockdb->info->pool;
+ apr_datum_t key;
+ dav_error *err;
+ dav_lock *lock = NULL;
+ dav_lock *newlock;
+ dav_lock_discovery *dp;
+ dav_lock_indirect *ip;
+
+#if DAV_DEBUG
+ if (calltype == DAV_GETLOCKS_COMPLETE) {
+ return dav_new_error(lockdb->info->pool,
+ HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "INTERNAL DESIGN ERROR: DAV_GETLOCKS_COMPLETE "
+ "is not yet supported");
+ }
+#endif
+
+ key = dav_generic_build_key(p, resource);
+ if ((err = dav_generic_load_lock_record(lockdb, key, DAV_CREATE_LIST,
+ &dp, &ip)) != NULL) {
+ /* ### push a higher-level desc? */
+ return err;
+ }
+
+ /* copy all direct locks to the result list */
+ for (; dp != NULL; dp = dp->next) {
+ newlock = dav_generic_alloc_lock(lockdb, key, dp->locktoken);
+ newlock->is_locknull = !resource->exists;
+ newlock->scope = dp->f.scope;
+ newlock->type = dp->f.type;
+ newlock->depth = dp->f.depth;
+ newlock->timeout = dp->f.timeout;
+ newlock->owner = dp->owner;
+ newlock->auth_user = dp->auth_user;
+
+ /* hook into the result list */
+ newlock->next = lock;
+ lock = newlock;
+ }
+
+ /* copy all the indirect locks to the result list. resolve as needed. */
+ for (; ip != NULL; ip = ip->next) {
+ newlock = dav_generic_alloc_lock(lockdb, ip->key, ip->locktoken);
+ newlock->is_locknull = !resource->exists;
+
+ if (calltype == DAV_GETLOCKS_RESOLVED) {
+ err = dav_generic_resolve(lockdb, ip, &dp, NULL, NULL);
+ if (err != NULL) {
+ /* ### push a higher-level desc? */
+ return err;
+ }
+
+ newlock->scope = dp->f.scope;
+ newlock->type = dp->f.type;
+ newlock->depth = dp->f.depth;
+ newlock->timeout = dp->f.timeout;
+ newlock->owner = dp->owner;
+ newlock->auth_user = dp->auth_user;
+ }
+ else {
+ /* DAV_GETLOCKS_PARTIAL */
+ newlock->rectype = DAV_LOCKREC_INDIRECT_PARTIAL;
+ }
+
+ /* hook into the result list */
+ newlock->next = lock;
+ lock = newlock;
+ }
+
+ *locks = lock;
+ return NULL;
+}
+
+static dav_error * dav_generic_find_lock(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ const dav_locktoken *locktoken,
+ int partial_ok,
+ dav_lock **lock)
+{
+ dav_error *err;
+ apr_datum_t key;
+ dav_lock_discovery *dp;
+ dav_lock_indirect *ip;
+
+ *lock = NULL;
+
+ key = dav_generic_build_key(lockdb->info->pool, resource);
+ if ((err = dav_generic_load_lock_record(lockdb, key, DAV_CREATE_LIST,
+ &dp, &ip)) != NULL) {
+ /* ### push a higher-level desc? */
+ return err;
+ }
+
+ for (; dp != NULL; dp = dp->next) {
+ if (!dav_compare_locktoken(locktoken, dp->locktoken)) {
+ *lock = dav_generic_alloc_lock(lockdb, key, locktoken);
+ (*lock)->is_locknull = !resource->exists;
+ (*lock)->scope = dp->f.scope;
+ (*lock)->type = dp->f.type;
+ (*lock)->depth = dp->f.depth;
+ (*lock)->timeout = dp->f.timeout;
+ (*lock)->owner = dp->owner;
+ (*lock)->auth_user = dp->auth_user;
+ return NULL;
+ }
+ }
+
+ for (; ip != NULL; ip = ip->next) {
+ if (!dav_compare_locktoken(locktoken, ip->locktoken)) {
+ *lock = dav_generic_alloc_lock(lockdb, ip->key, locktoken);
+ (*lock)->is_locknull = !resource->exists;
+
+ /* ### nobody uses the resolving right now! */
+ if (partial_ok) {
+ (*lock)->rectype = DAV_LOCKREC_INDIRECT_PARTIAL;
+ }
+ else {
+ (*lock)->rectype = DAV_LOCKREC_INDIRECT;
+ if ((err = dav_generic_resolve(lockdb, ip, &dp,
+ NULL, NULL)) != NULL) {
+ /* ### push a higher-level desc? */
+ return err;
+ }
+ (*lock)->scope = dp->f.scope;
+ (*lock)->type = dp->f.type;
+ (*lock)->depth = dp->f.depth;
+ (*lock)->timeout = dp->f.timeout;
+ (*lock)->owner = dp->owner;
+ (*lock)->auth_user = dp->auth_user;
+ }
+ return NULL;
+ }
+ }
+
+ return NULL;
+}
+
+static dav_error * dav_generic_has_locks(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int *locks_present)
+{
+ dav_error *err;
+ apr_datum_t key;
+
+ *locks_present = 0;
+
+ if ((err = dav_generic_really_open_lockdb(lockdb)) != NULL) {
+ /* ### insert a higher-level error description */
+ return err;
+ }
+
+ /*
+ * If we opened readonly and the db wasn't there, then there are no
+ * locks for this resource. Just exit.
+ */
+ if (lockdb->info->db == NULL)
+ return NULL;
+
+ key = dav_generic_build_key(lockdb->info->pool, resource);
+
+ *locks_present = apr_dbm_exists(lockdb->info->db, key);
+
+ return NULL;
+}
+
+static dav_error * dav_generic_append_locks(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int make_indirect,
+ const dav_lock *lock)
+{
+ apr_pool_t *p = lockdb->info->pool;
+ dav_error *err;
+ dav_lock_indirect *ip;
+ dav_lock_discovery *dp;
+ apr_datum_t key;
+
+ key = dav_generic_build_key(lockdb->info->pool, resource);
+
+ err = dav_generic_load_lock_record(lockdb, key, 0, &dp, &ip);
+ if (err != NULL) {
+ /* ### maybe add in a higher-level description */
+ return err;
+ }
+
+ /*
+ * ### when we store the lock more directly, we need to update
+ * ### lock->rectype and lock->is_locknull
+ */
+
+ if (make_indirect) {
+ for (; lock != NULL; lock = lock->next) {
+
+ /* ### this works for any <lock> rectype */
+ dav_lock_indirect *newi = apr_pcalloc(p, sizeof(*newi));
+
+ /* ### shut off the const warning for now */
+ newi->locktoken = (dav_locktoken *)lock->locktoken;
+ newi->timeout = lock->timeout;
+ newi->key = lock->info->key;
+ newi->next = ip;
+ ip = newi;
+ }
+ }
+ else {
+ for (; lock != NULL; lock = lock->next) {
+ /* create and link in the right kind of lock */
+
+ if (lock->rectype == DAV_LOCKREC_DIRECT) {
+ dav_lock_discovery *newd = apr_pcalloc(p, sizeof(*newd));
+
+ newd->f.scope = lock->scope;
+ newd->f.type = lock->type;
+ newd->f.depth = lock->depth;
+ newd->f.timeout = lock->timeout;
+ /* ### shut off the const warning for now */
+ newd->locktoken = (dav_locktoken *)lock->locktoken;
+ newd->owner = lock->owner;
+ newd->auth_user = lock->auth_user;
+ newd->next = dp;
+ dp = newd;
+ }
+ else {
+ /* DAV_LOCKREC_INDIRECT(_PARTIAL) */
+
+ dav_lock_indirect *newi = apr_pcalloc(p, sizeof(*newi));
+
+ /* ### shut off the const warning for now */
+ newi->locktoken = (dav_locktoken *)lock->locktoken;
+ newi->key = lock->info->key;
+ newi->next = ip;
+ ip = newi;
+ }
+ }
+ }
+
+ if ((err = dav_generic_save_lock_record(lockdb, key, dp, ip)) != NULL) {
+ /* ### maybe add a higher-level description */
+ return err;
+ }
+
+ return NULL;
+}
+
+static dav_error * dav_generic_remove_lock(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ const dav_locktoken *locktoken)
+{
+ dav_error *err;
+ dav_lock_discovery *dh = NULL;
+ dav_lock_indirect *ih = NULL;
+ apr_datum_t key;
+
+ key = dav_generic_build_key(lockdb->info->pool, resource);
+
+ if (locktoken != NULL) {
+ dav_lock_discovery *dp;
+ dav_lock_discovery *dprev = NULL;
+ dav_lock_indirect *ip;
+ dav_lock_indirect *iprev = NULL;
+
+ if ((err = dav_generic_load_lock_record(lockdb, key, DAV_CREATE_LIST,
+ &dh, &ih)) != NULL) {
+ /* ### maybe add a higher-level description */
+ return err;
+ }
+
+ for (dp = dh; dp != NULL; dp = dp->next) {
+ if (dav_compare_locktoken(locktoken, dp->locktoken) == 0) {
+ if (dprev)
+ dprev->next = dp->next;
+ else
+ dh = dh->next;
+ }
+ dprev = dp;
+ }
+
+ for (ip = ih; ip != NULL; ip = ip->next) {
+ if (dav_compare_locktoken(locktoken, ip->locktoken) == 0) {
+ if (iprev)
+ iprev->next = ip->next;
+ else
+ ih = ih->next;
+ }
+ iprev = ip;
+ }
+
+ }
+
+ /* save the modified locks, or remove all locks (dh=ih=NULL). */
+ if ((err = dav_generic_save_lock_record(lockdb, key, dh, ih)) != NULL) {
+ /* ### maybe add a higher-level description */
+ return err;
+ }
+
+ return NULL;
+}
+
+static int dav_generic_do_refresh(dav_lock_discovery *dp,
+ const dav_locktoken_list *ltl,
+ time_t new_time)
+{
+ int dirty = 0;
+
+ for (; ltl != NULL; ltl = ltl->next) {
+ if (dav_compare_locktoken(dp->locktoken, ltl->locktoken) == 0)
+ {
+ dp->f.timeout = new_time;
+ dirty = 1;
+ break;
+ }
+ }
+
+ return dirty;
+}
+
+static dav_error * dav_generic_refresh_locks(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ const dav_locktoken_list *ltl,
+ time_t new_time,
+ dav_lock **locks)
+{
+ dav_error *err;
+ apr_datum_t key;
+ dav_lock_discovery *dp;
+ dav_lock_discovery *dp_scan;
+ dav_lock_indirect *ip;
+ int dirty = 0;
+ dav_lock *newlock;
+
+ *locks = NULL;
+
+ key = dav_generic_build_key(lockdb->info->pool, resource);
+ if ((err = dav_generic_load_lock_record(lockdb, key, DAV_CREATE_LIST,
+ &dp, &ip)) != NULL) {
+ /* ### maybe add in a higher-level description */
+ return err;
+ }
+
+ /* ### we should be refreshing direct AND (resolved) indirect locks! */
+
+ /* refresh all of the direct locks on this resource */
+ for (dp_scan = dp; dp_scan != NULL; dp_scan = dp_scan->next) {
+ if (dav_generic_do_refresh(dp_scan, ltl, new_time)) {
+ /* the lock was refreshed. return the lock. */
+ newlock = dav_generic_alloc_lock(lockdb, key, dp_scan->locktoken);
+ newlock->is_locknull = !resource->exists;
+ newlock->scope = dp_scan->f.scope;
+ newlock->type = dp_scan->f.type;
+ newlock->depth = dp_scan->f.depth;
+ newlock->timeout = dp_scan->f.timeout;
+ newlock->owner = dp_scan->owner;
+ newlock->auth_user = dp_scan->auth_user;
+
+ newlock->next = *locks;
+ *locks = newlock;
+
+ dirty = 1;
+ }
+ }
+
+ /* if we refreshed any locks, then save them back. */
+ if (dirty
+ && (err = dav_generic_save_lock_record(lockdb, key, dp, ip)) != NULL) {
+ /* ### maybe add in a higher-level description */
+ return err;
+ }
+
+ /* for each indirect lock, find its direct lock and refresh it. */
+ for (; ip != NULL; ip = ip->next) {
+ dav_lock_discovery *ref_dp;
+ dav_lock_indirect *ref_ip;
+
+ if ((err = dav_generic_resolve(lockdb, ip, &dp_scan,
+ &ref_dp, &ref_ip)) != NULL) {
+ /* ### push a higher-level desc? */
+ return err;
+ }
+ if (dav_generic_do_refresh(dp_scan, ltl, new_time)) {
+ /* the lock was refreshed. return the lock. */
+ newlock = dav_generic_alloc_lock(lockdb, ip->key, dp->locktoken);
+ newlock->is_locknull = !resource->exists;
+ newlock->scope = dp->f.scope;
+ newlock->type = dp->f.type;
+ newlock->depth = dp->f.depth;
+ newlock->timeout = dp->f.timeout;
+ newlock->owner = dp->owner;
+ newlock->auth_user = dp_scan->auth_user;
+
+ newlock->next = *locks;
+ *locks = newlock;
+
+ /* save the (resolved) direct lock back */
+ if ((err = dav_generic_save_lock_record(lockdb, ip->key, ref_dp,
+ ref_ip)) != NULL) {
+ /* ### push a higher-level desc? */
+ return err;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+
+const dav_hooks_locks dav_hooks_locks_generic =
+{
+ dav_generic_get_supportedlock,
+ dav_generic_parse_locktoken,
+ dav_generic_format_locktoken,
+ dav_generic_compare_locktoken,
+ dav_generic_open_lockdb,
+ dav_generic_close_lockdb,
+ dav_generic_remove_locknull_state,
+ dav_generic_create_lock,
+ dav_generic_get_locks,
+ dav_generic_find_lock,
+ dav_generic_has_locks,
+ dav_generic_append_locks,
+ dav_generic_remove_lock,
+ dav_generic_refresh_locks,
+ NULL, /* lookup_resource */
+
+ NULL /* ctx */
+};
diff --git a/modules/dav/lock/locks.h b/modules/dav/lock/locks.h
new file mode 100644
index 0000000..8a78a53
--- /dev/null
+++ b/modules/dav/lock/locks.h
@@ -0,0 +1,33 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file locks.h
+ * @brief Declarations for the generic lock implementation
+ *
+ * @addtogroup MOD_DAV
+ * @{
+ */
+
+#ifndef _DAV_LOCK_LOCKS_H_
+#define _DAV_LOCK_LOCKS_H_
+
+/* where is the lock database located? */
+const char *dav_generic_get_lockdb_path(const request_rec *r);
+
+#endif /* _DAV_LOCK_LOCKS_H_ */
+/** @} */
+
diff --git a/modules/dav/lock/mod_dav_lock.c b/modules/dav/lock/mod_dav_lock.c
new file mode 100644
index 0000000..56a3202
--- /dev/null
+++ b/modules/dav/lock/mod_dav_lock.c
@@ -0,0 +1,104 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "apr_strings.h"
+#include "ap_provider.h"
+
+#include "mod_dav.h"
+#include "locks.h"
+
+/* per-dir configuration */
+typedef struct {
+ const char *lockdb_path;
+} dav_lock_dir_conf;
+
+extern const dav_hooks_locks dav_hooks_locks_generic;
+
+extern module AP_MODULE_DECLARE_DATA dav_lock_module;
+
+const char *dav_generic_get_lockdb_path(const request_rec *r)
+{
+ dav_lock_dir_conf *conf;
+
+ conf = ap_get_module_config(r->per_dir_config, &dav_lock_module);
+ return conf->lockdb_path;
+}
+
+static void *dav_lock_create_dir_config(apr_pool_t *p, char *dir)
+{
+ return apr_pcalloc(p, sizeof(dav_lock_dir_conf));
+}
+
+static void *dav_lock_merge_dir_config(apr_pool_t *p,
+ void *base, void *overrides)
+{
+ dav_lock_dir_conf *parent = base;
+ dav_lock_dir_conf *child = overrides;
+ dav_lock_dir_conf *newconf;
+
+ newconf = apr_pcalloc(p, sizeof(*newconf));
+
+ newconf->lockdb_path =
+ child->lockdb_path ? child->lockdb_path : parent->lockdb_path;
+
+ return newconf;
+}
+
+/*
+ * Command handler for the DAVGenericLockDB directive, which is TAKE1
+ */
+static const char *dav_lock_cmd_davlockdb(cmd_parms *cmd, void *config,
+ const char *arg1)
+{
+ dav_lock_dir_conf *conf = config;
+
+ conf->lockdb_path = ap_server_root_relative(cmd->pool, arg1);
+
+ if (!conf->lockdb_path) {
+ return apr_pstrcat(cmd->pool, "Invalid DAVGenericLockDB path ",
+ arg1, NULL);
+ }
+
+ return NULL;
+}
+
+static const command_rec dav_lock_cmds[] =
+{
+ /* per server */
+ AP_INIT_TAKE1("DAVGenericLockDB", dav_lock_cmd_davlockdb, NULL, ACCESS_CONF,
+ "specify a lock database"),
+
+ { NULL }
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_register_provider(p, "dav-lock", "generic", "0",
+ &dav_hooks_locks_generic);
+}
+
+AP_DECLARE_MODULE(dav_lock) =
+{
+ STANDARD20_MODULE_STUFF,
+ dav_lock_create_dir_config, /* dir config creater */
+ dav_lock_merge_dir_config, /* dir merger --- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ dav_lock_cmds, /* command table */
+ register_hooks, /* register hooks */
+};
diff --git a/modules/dav/lock/mod_dav_lock.dep b/modules/dav/lock/mod_dav_lock.dep
new file mode 100644
index 0000000..e21d71b
--- /dev/null
+++ b/modules/dav/lock/mod_dav_lock.dep
@@ -0,0 +1,100 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_dav_lock.mak
+
+.\locks.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\http_config.h"\
+ "..\..\..\include\http_log.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\mod_dav.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\util_cfgtree.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_xml.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apr_xml.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ ".\locks.h"\
+
+
+.\mod_dav_lock.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_provider.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\http_config.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\mod_dav.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\util_cfgtree.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_xml.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apr_xml.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ ".\locks.h"\
+
+
+..\..\..\build\win32\httpd.rc : \
+ "..\..\..\include\ap_release.h"\
+
diff --git a/modules/dav/lock/mod_dav_lock.dsp b/modules/dav/lock/mod_dav_lock.dsp
new file mode 100644
index 0000000..e1c1a24
--- /dev/null
+++ b/modules/dav/lock/mod_dav_lock.dsp
@@ -0,0 +1,127 @@
+# Microsoft Developer Studio Project File - Name="mod_dav_lock" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_dav_lock - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_dav_lock.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_dav_lock.mak" CFG="mod_dav_lock - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_dav_lock - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_dav_lock - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_dav_lock - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_dav_lock_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_dav_lock.res" /i "../../../include" /i "../../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_dav_lock.so" /d LONG_NAME="dav_lock_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:".\Release\mod_dav_lock.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav_lock.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_dav_lock.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav_lock.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_dav_lock.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_dav_lock - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_dav_lock_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_dav_lock.res" /i "../../../include" /i "../../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_dav_lock.so" /d LONG_NAME="dav_lock_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_dav_lock.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav_lock.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_dav_lock.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav_lock.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_dav_lock.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_dav_lock - Win32 Release"
+# Name "mod_dav_lock - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\locks.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\mod_dav_lock.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd"
+# Begin Source File
+
+SOURCE=.\locks.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/dav/lock/mod_dav_lock.mak b/modules/dav/lock/mod_dav_lock.mak
new file mode 100644
index 0000000..0ae2b6a
--- /dev/null
+++ b/modules/dav/lock/mod_dav_lock.mak
@@ -0,0 +1,389 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_dav_lock.dsp
+!IF "$(CFG)" == ""
+CFG=mod_dav_lock - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_dav_lock - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_dav_lock - Win32 Release" && "$(CFG)" != "mod_dav_lock - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_dav_lock.mak" CFG="mod_dav_lock - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_dav_lock - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_dav_lock - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_dav_lock - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_dav_lock.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_dav - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_dav_lock.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_dav - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\locks.obj"
+ -@erase "$(INTDIR)\mod_dav_lock.obj"
+ -@erase "$(INTDIR)\mod_dav_lock.res"
+ -@erase "$(INTDIR)\mod_dav_lock_src.idb"
+ -@erase "$(INTDIR)\mod_dav_lock_src.pdb"
+ -@erase "$(OUTDIR)\mod_dav_lock.exp"
+ -@erase "$(OUTDIR)\mod_dav_lock.lib"
+ -@erase "$(OUTDIR)\mod_dav_lock.pdb"
+ -@erase "$(OUTDIR)\mod_dav_lock.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_dav_lock_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_dav_lock.res" /i "../../../include" /i "../../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_dav_lock.so" /d LONG_NAME="dav_lock_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_dav_lock.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_dav_lock.pdb" /debug /out:"$(OUTDIR)\mod_dav_lock.so" /implib:"$(OUTDIR)\mod_dav_lock.lib" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav_lock.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\locks.obj" \
+ "$(INTDIR)\mod_dav_lock.obj" \
+ "$(INTDIR)\mod_dav_lock.res" \
+ "..\..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\..\Release\libhttpd.lib" \
+ "..\main\Release\mod_dav.lib"
+
+"$(OUTDIR)\mod_dav_lock.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_dav_lock.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_dav_lock.so"
+ if exist .\Release\mod_dav_lock.so.manifest mt.exe -manifest .\Release\mod_dav_lock.so.manifest -outputresource:.\Release\mod_dav_lock.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_dav_lock - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_dav_lock.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_dav - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_dav_lock.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_dav - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\locks.obj"
+ -@erase "$(INTDIR)\mod_dav_lock.obj"
+ -@erase "$(INTDIR)\mod_dav_lock.res"
+ -@erase "$(INTDIR)\mod_dav_lock_src.idb"
+ -@erase "$(INTDIR)\mod_dav_lock_src.pdb"
+ -@erase "$(OUTDIR)\mod_dav_lock.exp"
+ -@erase "$(OUTDIR)\mod_dav_lock.lib"
+ -@erase "$(OUTDIR)\mod_dav_lock.pdb"
+ -@erase "$(OUTDIR)\mod_dav_lock.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_dav_lock_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_dav_lock.res" /i "../../../include" /i "../../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_dav_lock.so" /d LONG_NAME="dav_lock_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_dav_lock.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_dav_lock.pdb" /debug /out:"$(OUTDIR)\mod_dav_lock.so" /implib:"$(OUTDIR)\mod_dav_lock.lib" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav_lock.so
+LINK32_OBJS= \
+ "$(INTDIR)\locks.obj" \
+ "$(INTDIR)\mod_dav_lock.obj" \
+ "$(INTDIR)\mod_dav_lock.res" \
+ "..\..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\..\Debug\libhttpd.lib" \
+ "..\main\Debug\mod_dav.lib"
+
+"$(OUTDIR)\mod_dav_lock.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_dav_lock.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_dav_lock.so"
+ if exist .\Debug\mod_dav_lock.so.manifest mt.exe -manifest .\Debug\mod_dav_lock.so.manifest -outputresource:.\Debug\mod_dav_lock.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_dav_lock.dep")
+!INCLUDE "mod_dav_lock.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_dav_lock.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_dav_lock - Win32 Release" || "$(CFG)" == "mod_dav_lock - Win32 Debug"
+SOURCE=.\locks.c
+
+"$(INTDIR)\locks.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\mod_dav_lock.c
+
+"$(INTDIR)\mod_dav_lock.obj" : $(SOURCE) "$(INTDIR)"
+
+
+!IF "$(CFG)" == "mod_dav_lock - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\dav\lock"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\dav\lock"
+
+!ELSEIF "$(CFG)" == "mod_dav_lock - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\dav\lock"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\dav\lock"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_dav_lock - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\dav\lock"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\dav\lock"
+
+!ELSEIF "$(CFG)" == "mod_dav_lock - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\dav\lock"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\dav\lock"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_dav_lock - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\dav\lock"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\dav\lock"
+
+!ELSEIF "$(CFG)" == "mod_dav_lock - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\dav\lock"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\dav\lock"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_dav_lock - Win32 Release"
+
+"mod_dav - Win32 Release" :
+ cd ".\..\main"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_dav.mak" CFG="mod_dav - Win32 Release"
+ cd "..\lock"
+
+"mod_dav - Win32 ReleaseCLEAN" :
+ cd ".\..\main"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_dav.mak" CFG="mod_dav - Win32 Release" RECURSE=1 CLEAN
+ cd "..\lock"
+
+!ELSEIF "$(CFG)" == "mod_dav_lock - Win32 Debug"
+
+"mod_dav - Win32 Debug" :
+ cd ".\..\main"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_dav.mak" CFG="mod_dav - Win32 Debug"
+ cd "..\lock"
+
+"mod_dav - Win32 DebugCLEAN" :
+ cd ".\..\main"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_dav.mak" CFG="mod_dav - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\lock"
+
+!ENDIF
+
+SOURCE=..\..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_dav_lock - Win32 Release"
+
+
+"$(INTDIR)\mod_dav_lock.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_dav_lock.res" /i "../../../include" /i "../../../srclib/apr/include" /i "../../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_dav_lock.so" /d LONG_NAME="dav_lock_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_dav_lock - Win32 Debug"
+
+
+"$(INTDIR)\mod_dav_lock.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_dav_lock.res" /i "../../../include" /i "../../../srclib/apr/include" /i "../../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_dav_lock.so" /d LONG_NAME="dav_lock_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+
+!ENDIF
+
diff --git a/modules/dav/main/Makefile.in b/modules/dav/main/Makefile.in
new file mode 100644
index 0000000..7c5c149
--- /dev/null
+++ b/modules/dav/main/Makefile.in
@@ -0,0 +1,3 @@
+# a modules Makefile has no explicit targets -- they will be defined by
+# whatever modules are enabled. just grab special.mk to deal with this.
+include $(top_srcdir)/build/special.mk
diff --git a/modules/dav/main/NWGNUmakefile b/modules/dav/main/NWGNUmakefile
new file mode 100644
index 0000000..bc94a22
--- /dev/null
+++ b/modules/dav/main/NWGNUmakefile
@@ -0,0 +1,268 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/server/mpm/netware \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = mod_dav
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) DAV module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = $(NLM_NAME) Thread
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 65536
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If this is specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# Declare all target files (you must add your files here)
+#
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_dav.o \
+ $(OBJDIR)/liveprop.o \
+ $(OBJDIR)/props.o \
+ $(OBJDIR)/providers.o \
+ $(OBJDIR)/std_liveprop.o \
+ $(OBJDIR)/util.o \
+ $(OBJDIR)/util_lock.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ Apache2 \
+ Libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @libc.imp \
+ @aprlib.imp \
+ @httpd.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ dav_module \
+ @dav.imp \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/dav/main/config5.m4 b/modules/dav/main/config5.m4
new file mode 100644
index 0000000..28823c7
--- /dev/null
+++ b/modules/dav/main/config5.m4
@@ -0,0 +1,21 @@
+dnl modules enabled in this directory by default
+
+APACHE_MODPATH_INIT(dav/main)
+
+dav_objects="mod_dav.lo props.lo util.lo util_lock.lo liveprop.lo providers.lo std_liveprop.lo"
+
+if test "$enable_http" = "no"; then
+ dav_enable=no
+else
+ dav_enable=most
+fi
+
+APACHE_MODULE(dav, WebDAV protocol handling. --enable-dav also enables mod_dav_fs, $dav_objects, , $dav_enable)
+
+if test "$dav_enable" != "no" -o "$enable_dav" != "no"; then
+ apache_need_expat=yes
+fi
+
+APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/$modpath_current])
+
+APACHE_MODPATH_FINISH
diff --git a/modules/dav/main/liveprop.c b/modules/dav/main/liveprop.c
new file mode 100644
index 0000000..d170d75
--- /dev/null
+++ b/modules/dav/main/liveprop.c
@@ -0,0 +1,140 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr_pools.h"
+#include "apr_hash.h"
+#include "apr_errno.h"
+#include "apr_strings.h"
+#include "util_xml.h" /* for apr_text_header */
+#include "mod_dav.h"
+
+
+static apr_hash_t *dav_liveprop_uris = NULL;
+static long dav_liveprop_count = 0;
+
+
+static apr_status_t dav_cleanup_liveprops(void *ctx)
+{
+ dav_liveprop_uris = NULL;
+ dav_liveprop_count = 0;
+ return APR_SUCCESS;
+}
+
+static void dav_register_liveprop_namespace(apr_pool_t *p, const char *uri)
+{
+ long value;
+
+ if (dav_liveprop_uris == NULL) {
+ dav_liveprop_uris = apr_hash_make(p);
+ apr_pool_cleanup_register(p, NULL, dav_cleanup_liveprops, apr_pool_cleanup_null);
+ }
+
+ value = (long)apr_hash_get(dav_liveprop_uris, uri, APR_HASH_KEY_STRING);
+ if (value != 0) {
+ /* already registered */
+ return;
+ }
+
+ /* start at 1, and count up */
+ apr_hash_set(dav_liveprop_uris, uri, APR_HASH_KEY_STRING,
+ (void *)++dav_liveprop_count);
+}
+
+DAV_DECLARE(long) dav_get_liveprop_ns_index(const char *uri)
+{
+ return (long)apr_hash_get(dav_liveprop_uris, uri, APR_HASH_KEY_STRING);
+}
+
+DAV_DECLARE(long) dav_get_liveprop_ns_count(void)
+{
+ return dav_liveprop_count;
+}
+
+DAV_DECLARE(void) dav_add_all_liveprop_xmlns(apr_pool_t *p,
+ apr_text_header *phdr)
+{
+ apr_hash_index_t *idx = apr_hash_first(p, dav_liveprop_uris);
+
+ for ( ; idx != NULL; idx = apr_hash_next(idx) ) {
+ const void *key;
+ void *val;
+ const char *s;
+
+ apr_hash_this(idx, &key, NULL, &val);
+
+ s = apr_psprintf(p, " xmlns:lp%ld=\"%s\"", (long)val, (const char *)key);
+ apr_text_append(p, phdr, s);
+ }
+}
+
+DAV_DECLARE(int) dav_do_find_liveprop(const char *ns_uri, const char *name,
+ const dav_liveprop_group *group,
+ const dav_hooks_liveprop **hooks)
+{
+ const char * const *uris = group->namespace_uris;
+ const dav_liveprop_spec *scan;
+ int ns;
+
+ /* first: locate the namespace in the namespace table */
+ for (ns = 0; uris[ns] != NULL; ++ns)
+ if (strcmp(ns_uri, uris[ns]) == 0)
+ break;
+ if (uris[ns] == NULL) {
+ /* not our property (the namespace matched none of ours) */
+ return 0;
+ }
+
+ /* second: look for the property in the liveprop specs */
+ for (scan = group->specs; scan->name != NULL; ++scan)
+ if (ns == scan->ns && strcmp(name, scan->name) == 0) {
+ *hooks = group->hooks;
+ return scan->propid;
+ }
+
+ /* not our property (same namespace, but no matching prop name) */
+ return 0;
+}
+
+DAV_DECLARE(long) dav_get_liveprop_info(int propid,
+ const dav_liveprop_group *group,
+ const dav_liveprop_spec **info)
+{
+ const dav_liveprop_spec *scan;
+
+ for (scan = group->specs; scan->name != NULL; ++scan) {
+ if (scan->propid == propid) {
+ *info = scan;
+
+ /* map the provider-local NS into a global NS index */
+ return dav_get_liveprop_ns_index(group->namespace_uris[scan->ns]);
+ }
+ }
+
+ /* assert: should not reach this point */
+ *info = NULL;
+ return 0;
+}
+
+DAV_DECLARE(void) dav_register_liveprop_group(apr_pool_t *p,
+ const dav_liveprop_group *group)
+{
+ /* register the namespace URIs */
+ const char * const * uris = group->namespace_uris;
+
+ for ( ; *uris != NULL; ++uris) {
+ dav_register_liveprop_namespace(p, *uris);
+ }
+}
diff --git a/modules/dav/main/mod_dav.c b/modules/dav/main/mod_dav.c
new file mode 100644
index 0000000..d16ab4a
--- /dev/null
+++ b/modules/dav/main/mod_dav.c
@@ -0,0 +1,5237 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * DAV extension module for Apache 2.0.*
+ *
+ * This module is repository-independent. It depends on hooks provided by a
+ * repository implementation.
+ *
+ * APACHE ISSUES:
+ * - within a DAV hierarchy, if an unknown method is used and we default
+ * to Apache's implementation, it sends back an OPTIONS with the wrong
+ * set of methods -- there is NO HOOK for us.
+ * therefore: we need to manually handle the HTTP_METHOD_NOT_ALLOWED
+ * and HTTP_NOT_IMPLEMENTED responses (not ap_send_error_response).
+ * - process_mkcol_body() had to dup code from ap_setup_client_block().
+ * - it would be nice to get status lines from Apache for arbitrary
+ * status codes
+ * - it would be nice to be able to extend Apache's set of response
+ * codes so that it doesn't return 500 when an unknown code is placed
+ * into r->status.
+ * - http_vhost functions should apply "const" to their params
+ *
+ * DESIGN NOTES:
+ * - For PROPFIND, we batch up the entire response in memory before
+ * sending it. We may want to reorganize around sending the information
+ * as we suck it in from the propdb. Alternatively, we should at least
+ * generate a total Content-Length if we're going to buffer in memory
+ * so that we can keep the connection open.
+ */
+
+#include "apr_strings.h"
+#include "apr_lib.h" /* for apr_is* */
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_main.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "util_script.h"
+
+#include "mod_dav.h"
+
+#include "ap_provider.h"
+
+
+/* ### what is the best way to set this? */
+#define DAV_DEFAULT_PROVIDER "filesystem"
+
+/* used to denote that mod_dav will be handling this request */
+#define DAV_HANDLER_NAME "dav-handler"
+
+APLOG_USE_MODULE(dav);
+
+enum {
+ DAV_ENABLED_UNSET = 0,
+ DAV_ENABLED_OFF,
+ DAV_ENABLED_ON
+};
+
+/* per-dir configuration */
+typedef struct {
+ const char *provider_name;
+ const dav_provider *provider;
+ const char *dir;
+ int locktimeout;
+ int allow_depthinfinity;
+ int allow_lockdiscovery;
+
+} dav_dir_conf;
+
+/* per-server configuration */
+typedef struct {
+ int unused;
+
+} dav_server_conf;
+
+#define DAV_INHERIT_VALUE(parent, child, field) \
+ ((child)->field ? (child)->field : (parent)->field)
+
+
+/* forward-declare for use in configuration lookup */
+extern module DAV_DECLARE_DATA dav_module;
+
+/* DAV methods */
+enum {
+ DAV_M_BIND = 0,
+ DAV_M_SEARCH,
+ DAV_M_LAST
+};
+static int dav_methods[DAV_M_LAST];
+
+
+static int dav_init_handler(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp,
+ server_rec *s)
+{
+ /* DBG0("dav_init_handler"); */
+
+ /* Register DAV methods */
+ dav_methods[DAV_M_BIND] = ap_method_register(p, "BIND");
+ dav_methods[DAV_M_SEARCH] = ap_method_register(p, "SEARCH");
+
+ return OK;
+}
+
+static void *dav_create_server_config(apr_pool_t *p, server_rec *s)
+{
+ dav_server_conf *newconf;
+
+ newconf = (dav_server_conf *)apr_pcalloc(p, sizeof(*newconf));
+
+ /* ### this isn't used at the moment... */
+
+ return newconf;
+}
+
+static void *dav_merge_server_config(apr_pool_t *p, void *base, void *overrides)
+{
+#if 0
+ dav_server_conf *child = overrides;
+#endif
+ dav_server_conf *newconf;
+
+ newconf = (dav_server_conf *)apr_pcalloc(p, sizeof(*newconf));
+
+ /* ### nothing to merge right now... */
+
+ return newconf;
+}
+
+static void *dav_create_dir_config(apr_pool_t *p, char *dir)
+{
+ /* NOTE: dir==NULL creates the default per-dir config */
+
+ dav_dir_conf *conf;
+
+ conf = (dav_dir_conf *)apr_pcalloc(p, sizeof(*conf));
+
+ /* clean up the directory to remove any trailing slash */
+ if (dir != NULL) {
+ char *d;
+ apr_size_t l;
+
+ l = strlen(dir);
+ d = apr_pstrmemdup(p, dir, l);
+ if (l > 1 && d[l - 1] == '/')
+ d[l - 1] = '\0';
+ conf->dir = d;
+ }
+
+ return conf;
+}
+
+static void *dav_merge_dir_config(apr_pool_t *p, void *base, void *overrides)
+{
+ dav_dir_conf *parent = base;
+ dav_dir_conf *child = overrides;
+ dav_dir_conf *newconf = (dav_dir_conf *)apr_pcalloc(p, sizeof(*newconf));
+
+ /* DBG3("dav_merge_dir_config: new=%08lx base=%08lx overrides=%08lx",
+ (long)newconf, (long)base, (long)overrides); */
+
+ newconf->provider_name = DAV_INHERIT_VALUE(parent, child, provider_name);
+ newconf->provider = DAV_INHERIT_VALUE(parent, child, provider);
+ if (parent->provider_name != NULL) {
+ if (child->provider_name == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(00578)
+ "\"DAV Off\" cannot be used to turn off a subtree "
+ "of a DAV-enabled location.");
+ }
+ else if (strcasecmp(child->provider_name,
+ parent->provider_name) != 0) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(00579)
+ "A subtree cannot specify a different DAV provider "
+ "than its parent.");
+ }
+ }
+
+ newconf->locktimeout = DAV_INHERIT_VALUE(parent, child, locktimeout);
+ newconf->dir = DAV_INHERIT_VALUE(parent, child, dir);
+ newconf->allow_depthinfinity = DAV_INHERIT_VALUE(parent, child,
+ allow_depthinfinity);
+ newconf->allow_lockdiscovery = DAV_INHERIT_VALUE(parent, child,
+ allow_lockdiscovery);
+
+ return newconf;
+}
+
+DAV_DECLARE(const char *) dav_get_provider_name(request_rec *r)
+{
+ dav_dir_conf *conf = ap_get_module_config(r->per_dir_config, &dav_module);
+ return conf ? conf->provider_name : NULL;
+}
+
+DAV_DECLARE(const dav_provider *) dav_get_provider(request_rec *r)
+{
+ dav_dir_conf *conf;
+
+ conf = ap_get_module_config(r->per_dir_config, &dav_module);
+ /* assert: conf->provider_name != NULL
+ (otherwise, DAV is disabled, and we wouldn't be here) */
+
+ /* assert: conf->provider != NULL
+ (checked when conf->provider_name is set) */
+ return conf->provider;
+}
+
+DAV_DECLARE(const dav_hooks_locks *) dav_get_lock_hooks(request_rec *r)
+{
+ return dav_get_provider(r)->locks;
+}
+
+DAV_DECLARE(const dav_hooks_propdb *) dav_get_propdb_hooks(request_rec *r)
+{
+ return dav_get_provider(r)->propdb;
+}
+
+DAV_DECLARE(const dav_hooks_vsn *) dav_get_vsn_hooks(request_rec *r)
+{
+ return dav_get_provider(r)->vsn;
+}
+
+DAV_DECLARE(const dav_hooks_binding *) dav_get_binding_hooks(request_rec *r)
+{
+ return dav_get_provider(r)->binding;
+}
+
+DAV_DECLARE(const dav_hooks_search *) dav_get_search_hooks(request_rec *r)
+{
+ return dav_get_provider(r)->search;
+}
+
+/*
+ * Command handler for the DAV directive, which is TAKE1.
+ */
+static const char *dav_cmd_dav(cmd_parms *cmd, void *config, const char *arg1)
+{
+ dav_dir_conf *conf = (dav_dir_conf *)config;
+
+ if (strcasecmp(arg1, "on") == 0) {
+ conf->provider_name = DAV_DEFAULT_PROVIDER;
+ }
+ else if (strcasecmp(arg1, "off") == 0) {
+ conf->provider_name = NULL;
+ conf->provider = NULL;
+ }
+ else {
+ conf->provider_name = arg1;
+ }
+
+ if (conf->provider_name != NULL) {
+ /* lookup and cache the actual provider now */
+ conf->provider = dav_lookup_provider(conf->provider_name);
+
+ if (conf->provider == NULL) {
+ /* by the time they use it, the provider should be loaded and
+ registered with us. */
+ return apr_psprintf(cmd->pool,
+ "Unknown DAV provider: %s",
+ conf->provider_name);
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Command handler for the DAVDepthInfinity directive, which is FLAG.
+ */
+static const char *dav_cmd_davdepthinfinity(cmd_parms *cmd, void *config,
+ int arg)
+{
+ dav_dir_conf *conf = (dav_dir_conf *)config;
+
+ if (arg)
+ conf->allow_depthinfinity = DAV_ENABLED_ON;
+ else
+ conf->allow_depthinfinity = DAV_ENABLED_OFF;
+ return NULL;
+}
+
+/*
+ * Command handler for the DAVLockDiscovery directive, which is FLAG.
+ */
+static const char *dav_cmd_davlockdiscovery(cmd_parms *cmd, void *config,
+ int arg)
+{
+ dav_dir_conf *conf = (dav_dir_conf *)config;
+
+ if (arg)
+ conf->allow_lockdiscovery = DAV_ENABLED_ON;
+ else
+ conf->allow_lockdiscovery = DAV_ENABLED_OFF;
+ return NULL;
+}
+
+/*
+ * Command handler for DAVMinTimeout directive, which is TAKE1
+ */
+static const char *dav_cmd_davmintimeout(cmd_parms *cmd, void *config,
+ const char *arg1)
+{
+ dav_dir_conf *conf = (dav_dir_conf *)config;
+
+ conf->locktimeout = atoi(arg1);
+ if (conf->locktimeout < 0)
+ return "DAVMinTimeout requires a non-negative integer.";
+
+ return NULL;
+}
+
+/*
+** dav_error_response()
+**
+** Send a nice response back to the user. In most cases, Apache doesn't
+** allow us to provide details in the body about what happened. This
+** function allows us to completely specify the response body.
+**
+** ### this function is not logging any errors! (e.g. the body)
+*/
+static int dav_error_response(request_rec *r, int status, const char *body)
+{
+ r->status = status;
+ r->status_line = ap_get_status_line(status);
+
+ ap_set_content_type(r, "text/html; charset=ISO-8859-1");
+
+ /* begin the response now... */
+ ap_rvputs(r,
+ DAV_RESPONSE_BODY_1,
+ r->status_line,
+ DAV_RESPONSE_BODY_2,
+ &r->status_line[4],
+ DAV_RESPONSE_BODY_3,
+ body,
+ DAV_RESPONSE_BODY_4,
+ ap_psignature("<hr />\n", r),
+ DAV_RESPONSE_BODY_5,
+ NULL);
+
+ /* the response has been sent. */
+ /*
+ * ### Use of DONE obviates logging..!
+ */
+ return DONE;
+}
+
+
+/*
+ * Send a "standardized" error response based on the error's namespace & tag
+ */
+static int dav_error_response_tag(request_rec *r,
+ dav_error *err)
+{
+ r->status = err->status;
+
+ ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
+
+ ap_rputs(DAV_XML_HEADER DEBUG_CR
+ "<D:error xmlns:D=\"DAV:\"", r);
+
+ if (err->desc != NULL) {
+ /* ### should move this namespace somewhere (with the others!) */
+ ap_rputs(" xmlns:m=\"http://apache.org/dav/xmlns\"", r);
+ }
+
+ if (err->childtags) {
+ if (err->namespace != NULL) {
+ ap_rprintf(r,
+ " xmlns:C=\"%s\">" DEBUG_CR
+ "<C:%s>%s</C:%s>" DEBUG_CR,
+ err->namespace,
+ err->tagname, err->childtags, err->tagname);
+ }
+ else {
+ ap_rprintf(r,
+ ">" DEBUG_CR
+ "<D:%s>%s</D:%s>" DEBUG_CR,
+ err->tagname, err->childtags, err->tagname);
+ }
+ }
+ else {
+ if (err->namespace != NULL) {
+ ap_rprintf(r,
+ " xmlns:C=\"%s\">" DEBUG_CR
+ "<C:%s/>" DEBUG_CR,
+ err->namespace, err->tagname);
+ }
+ else {
+ ap_rprintf(r,
+ ">" DEBUG_CR
+ "<D:%s/>" DEBUG_CR, err->tagname);
+ }
+ }
+
+ /* here's our mod_dav specific tag: */
+ if (err->desc != NULL) {
+ ap_rprintf(r,
+ "<m:human-readable errcode=\"%d\">" DEBUG_CR
+ "%s" DEBUG_CR
+ "</m:human-readable>" DEBUG_CR,
+ err->error_id,
+ apr_xml_quote_string(r->pool, err->desc, 0));
+ }
+
+ ap_rputs("</D:error>" DEBUG_CR, r);
+
+ /* the response has been sent. */
+ /*
+ * ### Use of DONE obviates logging..!
+ */
+ return DONE;
+}
+
+
+/*
+ * Apache's URI escaping does not replace '&' since that is a valid character
+ * in a URI (to form a query section). We must explicitly handle it so that
+ * we can embed the URI into an XML document.
+ */
+static const char *dav_xml_escape_uri(apr_pool_t *p, const char *uri)
+{
+ const char *e_uri = ap_escape_uri(p, uri);
+
+ /* check the easy case... */
+ if (ap_strchr_c(e_uri, '&') == NULL)
+ return e_uri;
+
+ /* there was a '&', so more work is needed... sigh. */
+
+ /*
+ * Note: this is a teeny bit of overkill since we know there are no
+ * '<' or '>' characters, but who cares.
+ */
+ return apr_xml_quote_string(p, e_uri, 0);
+}
+
+
+/* Write a complete RESPONSE object out as a <DAV:response> xml
+ element. Data is sent into brigade BB, which is auto-flushed into
+ the output filter stack for request R. Use POOL for any temporary
+ allocations.
+
+ [Presumably the <multistatus> tag has already been written; this
+ routine is shared by dav_send_multistatus and dav_stream_response.]
+*/
+DAV_DECLARE(void) dav_send_one_response(dav_response *response,
+ apr_bucket_brigade *bb,
+ request_rec *r,
+ apr_pool_t *pool)
+{
+ apr_text *t = NULL;
+
+ if (response->propresult.xmlns == NULL) {
+ ap_fputs(r->output_filters, bb, "<D:response>");
+ }
+ else {
+ ap_fputs(r->output_filters, bb, "<D:response");
+ for (t = response->propresult.xmlns; t; t = t->next) {
+ ap_fputs(r->output_filters, bb, t->text);
+ }
+ ap_fputc(r->output_filters, bb, '>');
+ }
+
+ ap_fputstrs(r->output_filters, bb,
+ DEBUG_CR "<D:href>",
+ dav_xml_escape_uri(pool, response->href),
+ "</D:href>" DEBUG_CR,
+ NULL);
+
+ if (response->propresult.propstats == NULL) {
+ /* use the Status-Line text from Apache. Note, this will
+ * default to 500 Internal Server Error if first->status
+ * is not a known (or valid) status code.
+ */
+ ap_fputstrs(r->output_filters, bb,
+ "<D:status>HTTP/1.1 ",
+ ap_get_status_line(response->status),
+ "</D:status>" DEBUG_CR,
+ NULL);
+ }
+ else {
+ /* assume this includes <propstat> and is quoted properly */
+ for (t = response->propresult.propstats; t; t = t->next) {
+ ap_fputs(r->output_filters, bb, t->text);
+ }
+ }
+
+ if (response->desc != NULL) {
+ /*
+ * We supply the description, so we know it doesn't have to
+ * have any escaping/encoding applied to it.
+ */
+ ap_fputstrs(r->output_filters, bb,
+ "<D:responsedescription>",
+ response->desc,
+ "</D:responsedescription>" DEBUG_CR,
+ NULL);
+ }
+
+ ap_fputs(r->output_filters, bb, "</D:response>" DEBUG_CR);
+}
+
+
+/* Factorized helper function: prep request_rec R for a multistatus
+ response and write <multistatus> tag into BB, destined for
+ R->output_filters. Use xml NAMESPACES in initial tag, if
+ non-NULL. */
+DAV_DECLARE(void) dav_begin_multistatus(apr_bucket_brigade *bb,
+ request_rec *r, int status,
+ apr_array_header_t *namespaces)
+{
+ /* Set the correct status and Content-Type */
+ r->status = status;
+ ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
+
+ /* Send the headers and actual multistatus response now... */
+ ap_fputs(r->output_filters, bb, DAV_XML_HEADER DEBUG_CR
+ "<D:multistatus xmlns:D=\"DAV:\"");
+
+ if (namespaces != NULL) {
+ int i;
+
+ for (i = namespaces->nelts; i--; ) {
+ ap_fprintf(r->output_filters, bb, " xmlns:ns%d=\"%s\"", i,
+ APR_XML_GET_URI_ITEM(namespaces, i));
+ }
+ }
+
+ ap_fputs(r->output_filters, bb, ">" DEBUG_CR);
+}
+
+/* Finish a multistatus response started by dav_begin_multistatus: */
+DAV_DECLARE(apr_status_t) dav_finish_multistatus(request_rec *r,
+ apr_bucket_brigade *bb)
+{
+ apr_bucket *b;
+
+ ap_fputs(r->output_filters, bb, "</D:multistatus>" DEBUG_CR);
+
+ /* indicate the end of the response body */
+ b = apr_bucket_eos_create(r->connection->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+
+ /* deliver whatever might be remaining in the brigade */
+ return ap_pass_brigade(r->output_filters, bb);
+}
+
+DAV_DECLARE(void) dav_send_multistatus(request_rec *r, int status,
+ dav_response *first,
+ apr_array_header_t *namespaces)
+{
+ apr_pool_t *subpool;
+ apr_bucket_brigade *bb = apr_brigade_create(r->pool,
+ r->connection->bucket_alloc);
+
+ dav_begin_multistatus(bb, r, status, namespaces);
+
+ apr_pool_create(&subpool, r->pool);
+ apr_pool_tag(subpool, "mod_dav-multistatus");
+
+ for (; first != NULL; first = first->next) {
+ apr_pool_clear(subpool);
+ dav_send_one_response(first, bb, r, subpool);
+ }
+ apr_pool_destroy(subpool);
+
+ dav_finish_multistatus(r, bb);
+}
+
+/*
+ * dav_log_err()
+ *
+ * Write error information to the log.
+ */
+static void dav_log_err(request_rec *r, dav_error *err, int level)
+{
+ dav_error *errscan;
+
+ /* Log the errors */
+ /* ### should have a directive to log the first or all */
+ for (errscan = err; errscan != NULL; errscan = errscan->prev) {
+ if (errscan->desc == NULL)
+ continue;
+
+ /* Intentional no APLOGNO */
+ ap_log_rerror(APLOG_MARK, level, errscan->aprerr, r, "%s [%d, #%d]",
+ errscan->desc, errscan->status, errscan->error_id);
+ }
+}
+
+/*
+ * dav_handle_err()
+ *
+ * Handle the standard error processing. <err> must be non-NULL.
+ *
+ * <response> is set by the following:
+ * - dav_validate_request()
+ * - dav_add_lock()
+ * - repos_hooks->remove_resource
+ * - repos_hooks->move_resource
+ * - repos_hooks->copy_resource
+ * - vsn_hooks->update
+ */
+DAV_DECLARE(int) dav_handle_err(request_rec *r, dav_error *err,
+ dav_response *response)
+{
+ /* log the errors */
+ dav_log_err(r, err, APLOG_ERR);
+
+ if (!ap_is_HTTP_VALID_RESPONSE(err->status)) {
+ /* we have responded already */
+ return AP_FILTER_ERROR;
+ }
+
+ if (response == NULL) {
+ dav_error *stackerr = err;
+
+ /* our error messages are safe; tell Apache this */
+ apr_table_setn(r->notes, "verbose-error-to", "*");
+
+ /* Didn't get a multistatus response passed in, but we still
+ might be able to generate a standard <D:error> response.
+ Search the error stack for an errortag. */
+ while (stackerr != NULL && stackerr->tagname == NULL)
+ stackerr = stackerr->prev;
+
+ if (stackerr != NULL && stackerr->tagname != NULL)
+ return dav_error_response_tag(r, stackerr);
+
+ return err->status;
+ }
+
+ /* send the multistatus and tell Apache the request/response is DONE. */
+ dav_send_multistatus(r, err->status, response, NULL);
+ return DONE;
+}
+
+/* handy function for return values of methods that (may) create things.
+ * locn if provided is assumed to be escaped. */
+static int dav_created(request_rec *r, const char *locn, const char *what,
+ int replaced)
+{
+ const char *body;
+
+ if (locn == NULL) {
+ locn = ap_escape_uri(r->pool, r->uri);
+ }
+
+ /* did the target resource already exist? */
+ if (replaced) {
+ /* Apache will supply a default message */
+ return HTTP_NO_CONTENT;
+ }
+
+ /* Per HTTP/1.1, S10.2.2: add a Location header to contain the
+ * URI that was created. */
+
+ /* Convert locn to an absolute URI, and return in Location header */
+ apr_table_setn(r->headers_out, "Location", ap_construct_url(r->pool, locn, r));
+
+ /* ### insert an ETag header? see HTTP/1.1 S10.2.2 */
+
+ /* Apache doesn't allow us to set a variable body for HTTP_CREATED, so
+ * we must manufacture the entire response. */
+ body = apr_pstrcat(r->pool, what, " ", ap_escape_html(r->pool, locn),
+ " has been created.", NULL);
+ return dav_error_response(r, HTTP_CREATED, body);
+}
+
+/* ### move to dav_util? */
+DAV_DECLARE(int) dav_get_depth(request_rec *r, int def_depth)
+{
+ const char *depth = apr_table_get(r->headers_in, "Depth");
+
+ if (depth == NULL) {
+ return def_depth;
+ }
+
+ if (ap_cstr_casecmp(depth, "infinity") == 0) {
+ return DAV_INFINITY;
+ }
+ else if (strcmp(depth, "0") == 0) {
+ return 0;
+ }
+ else if (strcmp(depth, "1") == 0) {
+ return 1;
+ }
+
+ /* The caller will return an HTTP_BAD_REQUEST. This will augment the
+ * default message that Apache provides. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00580)
+ "An invalid Depth header was specified.");
+ return -1;
+}
+
+static int dav_get_overwrite(request_rec *r)
+{
+ const char *overwrite = apr_table_get(r->headers_in, "Overwrite");
+
+ if (overwrite == NULL) {
+ return 1; /* default is "T" */
+ }
+
+ if ((*overwrite == 'F' || *overwrite == 'f') && overwrite[1] == '\0') {
+ return 0;
+ }
+
+ if ((*overwrite == 'T' || *overwrite == 't') && overwrite[1] == '\0') {
+ return 1;
+ }
+
+ /* The caller will return an HTTP_BAD_REQUEST. This will augment the
+ * default message that Apache provides. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00581)
+ "An invalid Overwrite header was specified.");
+ return -1;
+}
+
+/* resolve a request URI to a resource descriptor.
+ *
+ * If label_allowed != 0, then allow the request target to be altered by
+ * a Label: header.
+ *
+ * If use_checked_in is true, then the repository provider should return
+ * the resource identified by the DAV:checked-in property of the resource
+ * identified by the Request-URI.
+ */
+DAV_DECLARE(dav_error *) dav_get_resource(request_rec *r, int label_allowed,
+ int use_checked_in, dav_resource **res_p)
+{
+ dav_dir_conf *conf;
+ const char *label = NULL;
+ dav_error *err;
+
+ /* if the request target can be overridden, get any target selector */
+ if (label_allowed) {
+ label = apr_table_get(r->headers_in, "label");
+ }
+
+ conf = ap_get_module_config(r->per_dir_config, &dav_module);
+ /* assert: conf->provider != NULL */
+ if (conf->provider == NULL) {
+ return dav_new_error(r->pool, HTTP_METHOD_NOT_ALLOWED, 0, 0,
+ apr_psprintf(r->pool,
+ "DAV not enabled for %s",
+ ap_escape_html(r->pool, r->uri)));
+ }
+
+ /* resolve the resource */
+ err = (*conf->provider->repos->get_resource)(r, conf->dir,
+ label, use_checked_in,
+ res_p);
+ if (err != NULL) {
+ err = dav_push_error(r->pool, err->status, 0,
+ "Could not fetch resource information.", err);
+ return err;
+ }
+
+ /* Note: this shouldn't happen, but just be sure... */
+ if (*res_p == NULL) {
+ /* ### maybe use HTTP_INTERNAL_SERVER_ERROR */
+ return dav_new_error(r->pool, HTTP_NOT_FOUND, 0, 0,
+ apr_psprintf(r->pool,
+ "The provider did not define a "
+ "resource for %s.",
+ ap_escape_html(r->pool, r->uri)));
+ }
+
+ /* ### hmm. this doesn't feel like the right place or thing to do */
+ /* if there were any input headers requiring a Vary header in the response,
+ * add it now */
+ dav_add_vary_header(r, r, *res_p);
+
+ return NULL;
+}
+
+DAV_DECLARE(dav_error *) dav_open_lockdb(request_rec *r,
+ int ro,
+ dav_lockdb **lockdb)
+{
+ const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
+
+ if (hooks == NULL) {
+ *lockdb = NULL;
+ return NULL;
+ }
+
+ /* open the thing lazily */
+ return (*hooks->open_lockdb)(r, ro, 0, lockdb);
+}
+
+DAV_DECLARE(void) dav_close_lockdb(dav_lockdb *lockdb)
+{
+ (lockdb->hooks->close_lockdb)(lockdb);
+}
+
+/**
+ * @return 1 if valid content-range,
+ * 0 if no content-range,
+ * -1 if malformed content-range
+ */
+static int dav_parse_range(request_rec *r,
+ apr_off_t *range_start, apr_off_t *range_end)
+{
+ const char *range_c;
+ char *range;
+ char *dash;
+ char *slash;
+
+ range_c = apr_table_get(r->headers_in, "content-range");
+ if (range_c == NULL)
+ return 0;
+
+ range = apr_pstrdup(r->pool, range_c);
+ if (ap_cstr_casecmpn(range, "bytes ", 6) != 0
+ || (dash = ap_strchr(range + 6, '-')) == NULL
+ || (slash = ap_strchr(range + 6, '/')) == NULL) {
+ /* malformed header */
+ return -1;
+ }
+
+ *dash++ = *slash++ = '\0';
+
+ /* detect invalid ranges */
+ if (!ap_parse_strict_length(range_start, range + 6)) {
+ return -1;
+ }
+ if (!ap_parse_strict_length(range_end, dash)
+ || *range_end < *range_start) {
+ return -1;
+ }
+
+ if (*slash != '*') {
+ apr_off_t dummy;
+
+ if (!ap_parse_strict_length(&dummy, slash)
+ || dummy <= *range_end) {
+ return -1;
+ }
+ }
+
+ /* we now have a valid range */
+ return 1;
+}
+
+/* handle the GET method */
+static int dav_method_get(request_rec *r)
+{
+ dav_resource *resource;
+ dav_error *err;
+ int status;
+
+ /* This method should only be called when the resource is not
+ * visible to Apache. We will fetch the resource from the repository,
+ * then create a subrequest for Apache to handle.
+ */
+ err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ if (!resource->exists) {
+ /* Apache will supply a default error for this. */
+ return HTTP_NOT_FOUND;
+ }
+
+ /* set up the HTTP headers for the response */
+ if ((err = (*resource->hooks->set_headers)(r, resource)) != NULL) {
+ err = dav_push_error(r->pool, err->status, 0,
+ "Unable to set up HTTP headers.",
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* Handle conditional requests */
+ status = ap_meets_conditions(r);
+ if (status) {
+ return status;
+ }
+
+ if (r->header_only) {
+ return DONE;
+ }
+
+ /* okay... time to deliver the content */
+ if ((err = (*resource->hooks->deliver)(resource,
+ r->output_filters)) != NULL) {
+ err = dav_push_error(r->pool, err->status, 0,
+ "Unable to deliver content.",
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+
+ return DONE;
+}
+
+/* validate resource/locks on POST, then pass to the default handler */
+static int dav_method_post(request_rec *r)
+{
+ dav_resource *resource;
+ dav_error *err;
+
+ /* Ask repository module to resolve the resource */
+ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* Note: depth == 0. Implies no need for a multistatus response. */
+ if ((err = dav_validate_request(r, resource, 0, NULL, NULL,
+ DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
+ /* ### add a higher-level description? */
+ return dav_handle_err(r, err, NULL);
+ }
+
+ return DECLINED;
+}
+
+/* handle the PUT method */
+static int dav_method_put(request_rec *r)
+{
+ dav_resource *resource;
+ int resource_state;
+ dav_auto_version_info av_info;
+ const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
+ const char *body;
+ dav_error *err;
+ dav_error *err2;
+ dav_stream_mode mode;
+ dav_stream *stream;
+ dav_response *multi_response;
+ int has_range;
+ apr_off_t range_start;
+ apr_off_t range_end;
+
+ /* Ask repository module to resolve the resource */
+ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* If not a file or collection resource, PUT not allowed */
+ if (resource->type != DAV_RESOURCE_TYPE_REGULAR
+ && resource->type != DAV_RESOURCE_TYPE_WORKING) {
+ body = apr_psprintf(r->pool,
+ "Cannot create resource %s with PUT.",
+ ap_escape_html(r->pool, r->uri));
+ return dav_error_response(r, HTTP_CONFLICT, body);
+ }
+
+ /* Cannot PUT a collection */
+ if (resource->collection) {
+ return dav_error_response(r, HTTP_CONFLICT,
+ "Cannot PUT to a collection.");
+
+ }
+
+ resource_state = dav_get_resource_state(r, resource);
+
+ /*
+ * Note: depth == 0 normally requires no multistatus response. However,
+ * if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI
+ * other than the Request-URI, thereby requiring a multistatus.
+ *
+ * If the resource does not exist (DAV_RESOURCE_NULL), then we must
+ * check the resource *and* its parent. If the resource exists or is
+ * a locknull resource, then we check only the resource.
+ */
+ if ((err = dav_validate_request(r, resource, 0, NULL, &multi_response,
+ resource_state == DAV_RESOURCE_NULL ?
+ DAV_VALIDATE_PARENT :
+ DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
+ /* ### add a higher-level description? */
+ return dav_handle_err(r, err, multi_response);
+ }
+
+ has_range = dav_parse_range(r, &range_start, &range_end);
+ if (has_range < 0) {
+ /* RFC 2616 14.16: If we receive an invalid Content-Range we must
+ * not use the content.
+ */
+ body = apr_psprintf(r->pool,
+ "Malformed Content-Range header for PUT %s.",
+ ap_escape_html(r->pool, r->uri));
+ return dav_error_response(r, HTTP_BAD_REQUEST, body);
+ } else if (has_range) {
+ mode = DAV_MODE_WRITE_SEEKABLE;
+ }
+ else {
+ mode = DAV_MODE_WRITE_TRUNC;
+ }
+
+ /* make sure the resource can be modified (if versioning repository) */
+ if ((err = dav_auto_checkout(r, resource,
+ 0 /* not parent_only */,
+ &av_info)) != NULL) {
+ /* ### add a higher-level description? */
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* Create the new file in the repository */
+ if ((err = (*resource->hooks->open_stream)(resource, mode,
+ &stream)) != NULL) {
+ int status = err->status ? err->status : HTTP_FORBIDDEN;
+ if (status > 299) {
+ err = dav_push_error(r->pool, status, 0,
+ apr_psprintf(r->pool,
+ "Unable to PUT new contents for %s.",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ }
+ else {
+ err = NULL;
+ }
+ }
+
+ if (err == NULL && has_range) {
+ /* a range was provided. seek to the start */
+ err = (*resource->hooks->seek_stream)(stream, range_start);
+ }
+
+ if (err == NULL) {
+ apr_bucket_brigade *bb;
+ apr_bucket *b;
+ int seen_eos = 0;
+
+ bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+
+ do {
+ apr_status_t rc;
+
+ rc = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES,
+ APR_BLOCK_READ, DAV_READ_BLOCKSIZE);
+
+ if (rc != APR_SUCCESS) {
+ int http_err;
+ char *msg = ap_escape_html(r->pool, r->uri);
+ http_err = ap_map_http_request_error(rc, HTTP_BAD_REQUEST);
+ msg = apr_psprintf(r->pool, "An error occurred while reading "
+ "the request body (URI: %s)",
+ msg);
+ err = dav_new_error(r->pool, http_err, 0, rc, msg);
+ break;
+ }
+
+ for (b = APR_BRIGADE_FIRST(bb);
+ b != APR_BRIGADE_SENTINEL(bb);
+ b = APR_BUCKET_NEXT(b))
+ {
+ const char *data;
+ apr_size_t len;
+
+ if (APR_BUCKET_IS_EOS(b)) {
+ seen_eos = 1;
+ break;
+ }
+
+ if (APR_BUCKET_IS_METADATA(b)) {
+ continue;
+ }
+
+ if (err == NULL) {
+ /* write whatever we read, until we see an error */
+ rc = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
+ if (rc != APR_SUCCESS) {
+ err = dav_new_error(r->pool, HTTP_BAD_REQUEST, 0, rc,
+ apr_psprintf(r->pool,
+ "An error occurred while"
+ " reading the request body"
+ " from the bucket (URI: %s)",
+ ap_escape_html(r->pool, r->uri)));
+ break;
+ }
+
+ err = (*resource->hooks->write_stream)(stream, data, len);
+ }
+ }
+
+ apr_brigade_cleanup(bb);
+ } while (!seen_eos);
+
+ apr_brigade_destroy(bb);
+
+ err2 = (*resource->hooks->close_stream)(stream,
+ err == NULL /* commit */);
+ err = dav_join_error(err, err2);
+ }
+
+ /*
+ * Ensure that we think the resource exists now.
+ * ### eek. if an error occurred during the write and we did not commit,
+ * ### then the resource might NOT exist (e.g. dav_fs_repos.c)
+ */
+ if (err == NULL) {
+ resource->exists = 1;
+ }
+
+ /* restore modifiability of resources back to what they were */
+ err2 = dav_auto_checkin(r, resource, err != NULL /* undo if error */,
+ 0 /*unlock*/, &av_info);
+
+ /* check for errors now */
+ if (err != NULL) {
+ err = dav_join_error(err, err2); /* don't forget err2 */
+ return dav_handle_err(r, err, NULL);
+ }
+
+ if (err2 != NULL) {
+ /* just log a warning */
+ err2 = dav_push_error(r->pool, err2->status, 0,
+ "The PUT was successful, but there "
+ "was a problem automatically checking in "
+ "the resource or its parent collection.",
+ err2);
+ dav_log_err(r, err2, APLOG_WARNING);
+ }
+
+ /* ### place the Content-Type and Content-Language into the propdb */
+
+ if (locks_hooks != NULL) {
+ dav_lockdb *lockdb;
+
+ if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
+ /* The file creation was successful, but the locking failed. */
+ err = dav_push_error(r->pool, err->status, 0,
+ "The file was PUT successfully, but there "
+ "was a problem opening the lock database "
+ "which prevents inheriting locks from the "
+ "parent resources.",
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* notify lock system that we have created/replaced a resource */
+ err = dav_notify_created(r, lockdb, resource, resource_state, 0);
+
+ (*locks_hooks->close_lockdb)(lockdb);
+
+ if (err != NULL) {
+ /* The file creation was successful, but the locking failed. */
+ err = dav_push_error(r->pool, err->status, 0,
+ "The file was PUT successfully, but there "
+ "was a problem updating its lock "
+ "information.",
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+ }
+
+ /* NOTE: WebDAV spec, S8.7.1 states properties should be unaffected */
+
+ /* return an appropriate response (HTTP_CREATED or HTTP_NO_CONTENT) */
+ return dav_created(r, NULL, "Resource", resource_state == DAV_RESOURCE_EXISTS);
+}
+
+
+/* Use POOL to temporarily construct a dav_response object (from WRES
+ STATUS, and PROPSTATS) and stream it via WRES's ctx->brigade. */
+static void dav_stream_response(dav_walk_resource *wres,
+ int status,
+ dav_get_props_result *propstats,
+ apr_pool_t *pool)
+{
+ dav_response resp = { 0 };
+ dav_walker_ctx *ctx = wres->walk_ctx;
+
+ resp.href = wres->resource->uri;
+ resp.status = status;
+ if (propstats) {
+ resp.propresult = *propstats;
+ }
+
+ dav_send_one_response(&resp, ctx->bb, ctx->r, pool);
+}
+
+
+/* ### move this to dav_util? */
+DAV_DECLARE(void) dav_add_response(dav_walk_resource *wres,
+ int status, dav_get_props_result *propstats)
+{
+ dav_response *resp;
+
+ /* just drop some data into an dav_response */
+ resp = apr_pcalloc(wres->pool, sizeof(*resp));
+ resp->href = apr_pstrdup(wres->pool, wres->resource->uri);
+ resp->status = status;
+ if (propstats) {
+ resp->propresult = *propstats;
+ }
+
+ resp->next = wres->response;
+ wres->response = resp;
+}
+
+
+/* handle the DELETE method */
+static int dav_method_delete(request_rec *r)
+{
+ dav_resource *resource;
+ dav_auto_version_info av_info;
+ dav_error *err;
+ dav_error *err2;
+ dav_response *multi_response;
+ int result;
+ int depth;
+
+ /* We don't use the request body right now, so torch it. */
+ if ((result = ap_discard_request_body(r)) != OK) {
+ return result;
+ }
+
+ /* Ask repository module to resolve the resource */
+ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ if (!resource->exists) {
+ /* Apache will supply a default error for this. */
+ return HTTP_NOT_FOUND;
+ }
+
+ /* 2518 says that depth must be infinity only for collections.
+ * For non-collections, depth is ignored, unless it is an illegal value (1).
+ */
+ depth = dav_get_depth(r, DAV_INFINITY);
+
+ if (resource->collection && depth != DAV_INFINITY) {
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00582)
+ "Depth must be \"infinity\" for DELETE of a collection.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ if (!resource->collection && depth == 1) {
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00583)
+ "Depth of \"1\" is not allowed for DELETE.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /*
+ ** If any resources fail the lock/If: conditions, then we must fail
+ ** the delete. Each of the failing resources will be listed within
+ ** a DAV:multistatus body, wrapped into a 424 response.
+ **
+ ** Note that a failure on the resource itself does not generate a
+ ** multistatus response -- only internal members/collections.
+ */
+ if ((err = dav_validate_request(r, resource, depth, NULL,
+ &multi_response,
+ DAV_VALIDATE_PARENT
+ | DAV_VALIDATE_USE_424, NULL)) != NULL) {
+ err = dav_push_error(r->pool, err->status, 0,
+ apr_psprintf(r->pool,
+ "Could not DELETE %s due to a failed "
+ "precondition (e.g. locks).",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, multi_response);
+ }
+
+ /* ### RFC 2518 s. 8.10.5 says to remove _all_ locks, not just those
+ * locked by the token(s) in the if_header.
+ */
+ if ((result = dav_unlock(r, resource, NULL)) != OK) {
+ return result;
+ }
+
+ /* if versioned resource, make sure parent is checked out */
+ if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
+ &av_info)) != NULL) {
+ /* ### add a higher-level description? */
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* try to remove the resource */
+ err = (*resource->hooks->remove_resource)(resource, &multi_response);
+
+ /* restore writability of parent back to what it was */
+ err2 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */,
+ 0 /*unlock*/, &av_info);
+
+ /* check for errors now */
+ if (err != NULL) {
+ err = dav_push_error(r->pool, err->status, 0,
+ apr_psprintf(r->pool,
+ "Could not DELETE %s.",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, multi_response);
+ }
+ if (err2 != NULL) {
+ /* just log a warning */
+ err = dav_push_error(r->pool, err2->status, 0,
+ "The DELETE was successful, but there "
+ "was a problem automatically checking in "
+ "the parent collection.",
+ err2);
+ dav_log_err(r, err, APLOG_WARNING);
+ }
+
+ /* ### HTTP_NO_CONTENT if no body, HTTP_OK if there is a body (some day) */
+
+ /* Apache will supply a default error for this. */
+ return HTTP_NO_CONTENT;
+}
+
+/* generate DAV:supported-method-set OPTIONS response */
+static dav_error *dav_gen_supported_methods(request_rec *r,
+ const apr_xml_elem *elem,
+ const apr_table_t *methods,
+ apr_text_header *body)
+{
+ const apr_array_header_t *arr;
+ const apr_table_entry_t *elts;
+ apr_xml_elem *child;
+ apr_xml_attr *attr;
+ char *s;
+ int i;
+
+ apr_text_append(r->pool, body, "<D:supported-method-set>" DEBUG_CR);
+
+ if (elem->first_child == NULL) {
+ /* show all supported methods */
+ arr = apr_table_elts(methods);
+ elts = (const apr_table_entry_t *)arr->elts;
+
+ for (i = 0; i < arr->nelts; ++i) {
+ if (elts[i].key == NULL)
+ continue;
+
+ s = apr_pstrcat(r->pool,
+ "<D:supported-method D:name=\"",
+ elts[i].key,
+ "\"/>" DEBUG_CR, NULL);
+ apr_text_append(r->pool, body, s);
+ }
+ }
+ else {
+ /* check for support of specific methods */
+ for (child = elem->first_child; child != NULL; child = child->next) {
+ if (child->ns == APR_XML_NS_DAV_ID
+ && strcmp(child->name, "supported-method") == 0) {
+ const char *name = NULL;
+
+ /* go through attributes to find method name */
+ for (attr = child->attr; attr != NULL; attr = attr->next) {
+ if (attr->ns == APR_XML_NS_DAV_ID
+ && strcmp(attr->name, "name") == 0)
+ name = attr->value;
+ }
+
+ if (name == NULL) {
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0, 0,
+ "A DAV:supported-method element "
+ "does not have a \"name\" attribute");
+ }
+
+ /* see if method is supported */
+ if (apr_table_get(methods, name) != NULL) {
+ s = apr_pstrcat(r->pool,
+ "<D:supported-method D:name=\"",
+ name, "\"/>" DEBUG_CR, NULL);
+ apr_text_append(r->pool, body, s);
+ }
+ }
+ }
+ }
+
+ apr_text_append(r->pool, body, "</D:supported-method-set>" DEBUG_CR);
+ return NULL;
+}
+
+/* generate DAV:supported-live-property-set OPTIONS response */
+static dav_error *dav_gen_supported_live_props(request_rec *r,
+ const dav_resource *resource,
+ const apr_xml_elem *elem,
+ apr_text_header *body)
+{
+ dav_lockdb *lockdb;
+ dav_propdb *propdb;
+ apr_xml_elem *child;
+ apr_xml_attr *attr;
+ dav_error *err;
+
+ /* open lock database, to report on supported lock properties */
+ if ((err = dav_open_lockdb(r, 1, &lockdb)) != NULL) {
+ return dav_push_error(r->pool, err->status, 0,
+ "The lock database could not be opened, "
+ "preventing the reporting of supported lock "
+ "properties.",
+ err);
+ }
+
+ /* open the property database (readonly) for the resource */
+ if ((err = dav_open_propdb(r, lockdb, resource, DAV_PROPDB_RO, NULL,
+ &propdb)) != NULL) {
+ if (lockdb != NULL)
+ (*lockdb->hooks->close_lockdb)(lockdb);
+
+ return dav_push_error(r->pool, err->status, 0,
+ "The property database could not be opened, "
+ "preventing report of supported properties.",
+ err);
+ }
+
+ apr_text_append(r->pool, body, "<D:supported-live-property-set>" DEBUG_CR);
+
+ if (elem->first_child == NULL) {
+ /* show all supported live properties */
+ dav_get_props_result props = dav_get_allprops(propdb, DAV_PROP_INSERT_SUPPORTED);
+ body->last->next = props.propstats;
+ while (body->last->next != NULL)
+ body->last = body->last->next;
+ }
+ else {
+ /* check for support of specific live property */
+ for (child = elem->first_child; child != NULL; child = child->next) {
+ if (child->ns == APR_XML_NS_DAV_ID
+ && strcmp(child->name, "supported-live-property") == 0) {
+ const char *name = NULL;
+ const char *nmspace = NULL;
+
+ /* go through attributes to find name and namespace */
+ for (attr = child->attr; attr != NULL; attr = attr->next) {
+ if (attr->ns == APR_XML_NS_DAV_ID) {
+ if (strcmp(attr->name, "name") == 0)
+ name = attr->value;
+ else if (strcmp(attr->name, "namespace") == 0)
+ nmspace = attr->value;
+ }
+ }
+
+ if (name == NULL) {
+ err = dav_new_error(r->pool, HTTP_BAD_REQUEST, 0, 0,
+ "A DAV:supported-live-property "
+ "element does not have a \"name\" "
+ "attribute");
+ break;
+ }
+
+ /* default namespace to DAV: */
+ if (nmspace == NULL)
+ nmspace = "DAV:";
+
+ /* check for support of property */
+ dav_get_liveprop_supported(propdb, nmspace, name, body);
+ }
+ }
+ }
+
+ apr_text_append(r->pool, body, "</D:supported-live-property-set>" DEBUG_CR);
+
+ dav_close_propdb(propdb);
+
+ if (lockdb != NULL)
+ (*lockdb->hooks->close_lockdb)(lockdb);
+
+ return err;
+}
+
+
+/* generate DAV:supported-report-set OPTIONS response */
+static dav_error *dav_gen_supported_reports(request_rec *r,
+ const dav_resource *resource,
+ const apr_xml_elem *elem,
+ apr_text_header *body)
+{
+ apr_xml_elem *child;
+ apr_xml_attr *attr;
+ dav_error *err = NULL;
+ char *s;
+ apr_array_header_t *reports;
+ const dav_report_elem *rp;
+
+ apr_text_append(r->pool, body, "<D:supported-report-set>" DEBUG_CR);
+
+ reports = apr_array_make(r->pool, 5, sizeof(const char *));
+ dav_run_gather_reports(r, resource, reports, &err);
+ if (err != NULL) {
+ return dav_push_error(r->pool, err->status, 0,
+ "DAV:supported-report-set could not be "
+ "determined due to a problem fetching the "
+ "available reports for this resource.",
+ err);
+ }
+
+ if (elem->first_child == NULL) {
+ int i;
+
+ /* show all supported reports */
+ rp = (const dav_report_elem *)reports->elts;
+ for (i = 0; i < reports->nelts; i++, rp++) {
+ /* Note: we presume reports->namespace is
+ * properly XML/URL quoted */
+ s = apr_pstrcat(r->pool,
+ "<D:supported-report D:name=\"",
+ rp->name,
+ "\" D:namespace=\"",
+ rp->nmspace,
+ "\"/>" DEBUG_CR, NULL);
+ apr_text_append(r->pool, body, s);
+ }
+ }
+ else {
+ /* check for support of specific report */
+ for (child = elem->first_child; child != NULL; child = child->next) {
+ if (child->ns == APR_XML_NS_DAV_ID
+ && strcmp(child->name, "supported-report") == 0) {
+ const char *name = NULL;
+ const char *nmspace = NULL;
+ int i;
+
+ /* go through attributes to find name and namespace */
+ for (attr = child->attr; attr != NULL; attr = attr->next) {
+ if (attr->ns == APR_XML_NS_DAV_ID) {
+ if (strcmp(attr->name, "name") == 0)
+ name = attr->value;
+ else if (strcmp(attr->name, "namespace") == 0)
+ nmspace = attr->value;
+ }
+ }
+
+ if (name == NULL) {
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0, 0,
+ "A DAV:supported-report element "
+ "does not have a \"name\" attribute");
+ }
+
+ /* default namespace to DAV: */
+ if (nmspace == NULL) {
+ nmspace = "DAV:";
+ }
+
+ rp = (const dav_report_elem *)reports->elts;
+ for (i = 0; i < reports->nelts; i++, rp++) {
+ if (strcmp(name, rp->name) == 0
+ && strcmp(nmspace, rp->nmspace) == 0) {
+ /* Note: we presume reports->nmspace is
+ * properly XML/URL quoted
+ */
+ s = apr_pstrcat(r->pool,
+ "<D:supported-report "
+ "D:name=\"",
+ rp->name,
+ "\" D:namespace=\"",
+ rp->nmspace,
+ "\"/>" DEBUG_CR, NULL);
+ apr_text_append(r->pool, body, s);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ apr_text_append(r->pool, body, "</D:supported-report-set>" DEBUG_CR);
+ return NULL;
+}
+
+
+/* handle the SEARCH method */
+static int dav_method_search(request_rec *r)
+{
+ const dav_hooks_search *search_hooks = DAV_GET_HOOKS_SEARCH(r);
+ dav_resource *resource;
+ dav_error *err;
+ dav_response *multi_status;
+
+ /* If no search provider, decline the request */
+ if (search_hooks == NULL)
+ return DECLINED;
+
+ /* This method should only be called when the resource is not
+ * visible to Apache. We will fetch the resource from the repository,
+ * then create a subrequest for Apache to handle.
+ */
+ err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ if (!resource->exists) {
+ /* Apache will supply a default error for this. */
+ return HTTP_NOT_FOUND;
+ }
+
+ /* set up the HTTP headers for the response */
+ if ((err = (*resource->hooks->set_headers)(r, resource)) != NULL) {
+ err = dav_push_error(r->pool, err->status, 0,
+ "Unable to set up HTTP headers.",
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+
+ if (r->header_only) {
+ return DONE;
+ }
+
+ /* okay... time to search the content */
+ /* Let's validate XML and process walk function
+ * in the hook function
+ */
+ if ((err = (*search_hooks->search_resource)(r, &multi_status)) != NULL) {
+ /* ### add a higher-level description? */
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* We have results in multi_status */
+ /* Should I pass namespace?? */
+ dav_send_multistatus(r, HTTP_MULTI_STATUS, multi_status, NULL);
+
+ return DONE;
+}
+
+
+/* handle the OPTIONS method */
+static int dav_method_options(request_rec *r)
+{
+ const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+ const dav_hooks_binding *binding_hooks = DAV_GET_HOOKS_BINDING(r);
+ const dav_hooks_search *search_hooks = DAV_GET_HOOKS_SEARCH(r);
+ dav_resource *resource;
+ const char *dav_level;
+ char *allow;
+ char *s;
+ const apr_array_header_t *arr;
+ const apr_table_entry_t *elts;
+ apr_table_t *methods = apr_table_make(r->pool, 12);
+ apr_text_header vsn_options = { 0 };
+ apr_text_header body = { 0 };
+ apr_text *t;
+ int text_size;
+ int result;
+ int i;
+ apr_array_header_t *uri_ary;
+ apr_xml_doc *doc;
+ const apr_xml_elem *elem;
+ dav_error *err;
+
+ apr_array_header_t *extensions;
+ ap_list_provider_names_t *entry;
+
+ /* resolve the resource */
+ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* parse any request body */
+ if ((result = ap_xml_parse_input(r, &doc)) != OK) {
+ return result;
+ }
+ /* note: doc == NULL if no request body */
+
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ if (doc && !dav_validate_root(doc, "options")) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00584)
+ "The \"options\" element was not found.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* determine which providers are available */
+ dav_level = "1";
+
+ if (locks_hooks != NULL) {
+ dav_level = "1,2";
+ }
+
+ if (binding_hooks != NULL)
+ dav_level = apr_pstrcat(r->pool, dav_level, ",bindings", NULL);
+
+ /* DAV header additions registered by external modules */
+ extensions = ap_list_provider_names(r->pool, DAV_OPTIONS_EXTENSION_GROUP, "0");
+ entry = (ap_list_provider_names_t *)extensions->elts;
+
+ for (i = 0; i < extensions->nelts; i++, entry++) {
+ const dav_options_provider *options =
+ dav_get_options_providers(entry->provider_name);
+
+ if (options && options->dav_header) {
+ apr_text_header hoptions = { 0 };
+
+ options->dav_header(r, resource, &hoptions);
+ for (t = hoptions.first; t && t->text; t = t->next)
+ dav_level = apr_pstrcat(r->pool, dav_level, ",", t->text, NULL);
+ }
+ }
+
+ /* ###
+ * MSFT Web Folders chokes if length of DAV header value > 63 characters!
+ * To workaround that, we use separate DAV headers for versioning and
+ * live prop provider namespace URIs.
+ * ###
+ */
+ apr_table_setn(r->headers_out, "DAV", dav_level);
+
+ /*
+ * If there is a versioning provider, generate DAV headers
+ * for versioning options.
+ */
+ if (vsn_hooks != NULL) {
+ (*vsn_hooks->get_vsn_options)(r->pool, &vsn_options);
+
+ for (t = vsn_options.first; t != NULL; t = t->next)
+ apr_table_addn(r->headers_out, "DAV", t->text);
+ }
+
+ /*
+ * Gather property set URIs from all the liveprop providers,
+ * and generate a separate DAV header for each URI, to avoid
+ * problems with long header lengths.
+ */
+ uri_ary = apr_array_make(r->pool, 5, sizeof(const char *));
+ dav_run_gather_propsets(uri_ary);
+ for (i = 0; i < uri_ary->nelts; ++i) {
+ if (((char **)uri_ary->elts)[i] != NULL)
+ apr_table_addn(r->headers_out, "DAV", ((char **)uri_ary->elts)[i]);
+ }
+
+ /* this tells MSFT products to skip looking for FrontPage extensions */
+ apr_table_setn(r->headers_out, "MS-Author-Via", "DAV");
+
+ /*
+ * Determine which methods are allowed on the resource.
+ * Three cases: resource is null (3), is lock-null (7.4), or exists.
+ *
+ * All cases support OPTIONS, and if there is a lock provider, LOCK.
+ * (Lock-) null resources also support MKCOL and PUT.
+ * Lock-null supports PROPFIND and UNLOCK.
+ * Existing resources support lots of stuff.
+ */
+
+ apr_table_addn(methods, "OPTIONS", "");
+
+ /* ### take into account resource type */
+ switch (dav_get_resource_state(r, resource))
+ {
+ case DAV_RESOURCE_EXISTS:
+ /* resource exists */
+ apr_table_addn(methods, "GET", "");
+ apr_table_addn(methods, "HEAD", "");
+ apr_table_addn(methods, "POST", "");
+ apr_table_addn(methods, "DELETE", "");
+ apr_table_addn(methods, "TRACE", "");
+ apr_table_addn(methods, "PROPFIND", "");
+ apr_table_addn(methods, "PROPPATCH", "");
+ apr_table_addn(methods, "COPY", "");
+ apr_table_addn(methods, "MOVE", "");
+
+ if (!resource->collection)
+ apr_table_addn(methods, "PUT", "");
+
+ if (locks_hooks != NULL) {
+ apr_table_addn(methods, "LOCK", "");
+ apr_table_addn(methods, "UNLOCK", "");
+ }
+
+ break;
+
+ case DAV_RESOURCE_LOCK_NULL:
+ /* resource is lock-null. */
+ apr_table_addn(methods, "MKCOL", "");
+ apr_table_addn(methods, "PROPFIND", "");
+ apr_table_addn(methods, "PUT", "");
+
+ if (locks_hooks != NULL) {
+ apr_table_addn(methods, "LOCK", "");
+ apr_table_addn(methods, "UNLOCK", "");
+ }
+
+ break;
+
+ case DAV_RESOURCE_NULL:
+ /* resource is null. */
+ apr_table_addn(methods, "MKCOL", "");
+ apr_table_addn(methods, "PUT", "");
+
+ if (locks_hooks != NULL)
+ apr_table_addn(methods, "LOCK", "");
+
+ break;
+
+ default:
+ /* ### internal error! */
+ break;
+ }
+
+ /* If there is a versioning provider, add versioning methods */
+ if (vsn_hooks != NULL) {
+ if (!resource->exists) {
+ if ((*vsn_hooks->versionable)(resource))
+ apr_table_addn(methods, "VERSION-CONTROL", "");
+
+ if (vsn_hooks->can_be_workspace != NULL
+ && (*vsn_hooks->can_be_workspace)(resource))
+ apr_table_addn(methods, "MKWORKSPACE", "");
+
+ if (vsn_hooks->can_be_activity != NULL
+ && (*vsn_hooks->can_be_activity)(resource))
+ apr_table_addn(methods, "MKACTIVITY", "");
+ }
+ else if (!resource->versioned) {
+ if ((*vsn_hooks->versionable)(resource))
+ apr_table_addn(methods, "VERSION-CONTROL", "");
+ }
+ else if (resource->working) {
+ apr_table_addn(methods, "CHECKIN", "");
+
+ /* ### we might not support this DeltaV option */
+ apr_table_addn(methods, "UNCHECKOUT", "");
+ }
+ else if (vsn_hooks->add_label != NULL) {
+ apr_table_addn(methods, "CHECKOUT", "");
+ apr_table_addn(methods, "LABEL", "");
+ }
+ else {
+ apr_table_addn(methods, "CHECKOUT", "");
+ }
+ }
+
+ /* If there is a bindings provider, see if resource is bindable */
+ if (binding_hooks != NULL
+ && (*binding_hooks->is_bindable)(resource)) {
+ apr_table_addn(methods, "BIND", "");
+ }
+
+ /* If there is a search provider, set SEARCH in option */
+ if (search_hooks != NULL) {
+ apr_table_addn(methods, "SEARCH", "");
+ }
+
+ /* additional methods registered by external modules */
+ extensions = ap_list_provider_names(r->pool, DAV_OPTIONS_EXTENSION_GROUP, "0");
+ entry = (ap_list_provider_names_t *)extensions->elts;
+
+ for (i = 0; i < extensions->nelts; i++, entry++) {
+ const dav_options_provider *options =
+ dav_get_options_providers(entry->provider_name);
+
+ if (options && options->dav_method) {
+ apr_text_header hoptions = { 0 };
+
+ options->dav_method(r, resource, &hoptions);
+ for (t = hoptions.first; t && t->text; t = t->next)
+ apr_table_addn(methods, t->text, "");
+ }
+ }
+
+ /* Generate the Allow header */
+ arr = apr_table_elts(methods);
+ elts = (const apr_table_entry_t *)arr->elts;
+ text_size = 0;
+
+ /* first, compute total length */
+ for (i = 0; i < arr->nelts; ++i) {
+ if (elts[i].key == NULL)
+ continue;
+
+ /* add 1 for comma or null */
+ text_size += strlen(elts[i].key) + 1;
+ }
+
+ s = allow = apr_palloc(r->pool, text_size);
+
+ for (i = 0; i < arr->nelts; ++i) {
+ if (elts[i].key == NULL)
+ continue;
+
+ if (s != allow)
+ *s++ = ',';
+
+ strcpy(s, elts[i].key);
+ s += strlen(s);
+ }
+
+ apr_table_setn(r->headers_out, "Allow", allow);
+
+
+ /* If there is search set_option_head function, set head */
+ /* DASL: <DAV:basicsearch>
+ * DASL: <http://foo.bar.com/syntax1>
+ * DASL: <http://akuma.com/syntax2>
+ */
+ if (search_hooks != NULL
+ && *search_hooks->set_option_head != NULL) {
+ if ((err = (*search_hooks->set_option_head)(r)) != NULL) {
+ return dav_handle_err(r, err, NULL);
+ }
+ }
+
+ /* if there was no request body, then there is no response body */
+ if (doc == NULL) {
+ ap_set_content_length(r, 0);
+
+ /* ### this sends a Content-Type. the default OPTIONS does not. */
+
+ /* ### the default (ap_send_http_options) returns OK, but I believe
+ * ### that is because it is the default handler and nothing else
+ * ### will run after the thing. */
+ return DONE;
+ }
+
+ /* handle each options request */
+ for (elem = doc->root->first_child; elem != NULL; elem = elem->next) {
+ /* check for something we recognize first */
+ int core_option = 0;
+ dav_error *err = NULL;
+
+ if (elem->ns == APR_XML_NS_DAV_ID) {
+ if (strcmp(elem->name, "supported-method-set") == 0) {
+ err = dav_gen_supported_methods(r, elem, methods, &body);
+ core_option = 1;
+ }
+ else if (strcmp(elem->name, "supported-live-property-set") == 0) {
+ err = dav_gen_supported_live_props(r, resource, elem, &body);
+ core_option = 1;
+ }
+ else if (strcmp(elem->name, "supported-report-set") == 0) {
+ err = dav_gen_supported_reports(r, resource, elem, &body);
+ core_option = 1;
+ }
+ }
+
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* if unrecognized option, pass to versioning provider */
+ if (!core_option && vsn_hooks != NULL) {
+ if ((err = (*vsn_hooks->get_option)(resource, elem, &body))
+ != NULL) {
+ return dav_handle_err(r, err, NULL);
+ }
+ }
+ }
+
+ /* send the options response */
+ r->status = HTTP_OK;
+ ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
+
+ /* send the headers and response body */
+ ap_rputs(DAV_XML_HEADER DEBUG_CR
+ "<D:options-response xmlns:D=\"DAV:\">" DEBUG_CR, r);
+
+ for (t = body.first; t != NULL; t = t->next)
+ ap_rputs(t->text, r);
+
+ ap_rputs("</D:options-response>" DEBUG_CR, r);
+
+ /* we've sent everything necessary to the client. */
+ return DONE;
+}
+
+static void dav_cache_badprops(dav_walker_ctx *ctx)
+{
+ const apr_xml_elem *elem;
+ apr_text_header hdr = { 0 };
+
+ /* just return if we built the thing already */
+ if (ctx->propstat_404 != NULL) {
+ return;
+ }
+
+ apr_text_append(ctx->w.pool, &hdr,
+ "<D:propstat>" DEBUG_CR
+ "<D:prop>" DEBUG_CR);
+
+ elem = dav_find_child(ctx->doc->root, "prop");
+ for (elem = elem->first_child; elem; elem = elem->next) {
+ apr_text_append(ctx->w.pool, &hdr,
+ apr_xml_empty_elem(ctx->w.pool, elem));
+ }
+
+ apr_text_append(ctx->w.pool, &hdr,
+ "</D:prop>" DEBUG_CR
+ "<D:status>HTTP/1.1 404 Not Found</D:status>" DEBUG_CR
+ "</D:propstat>" DEBUG_CR);
+
+ ctx->propstat_404 = hdr.first;
+}
+
+static dav_error * dav_propfind_walker(dav_walk_resource *wres, int calltype)
+{
+ dav_walker_ctx *ctx = wres->walk_ctx;
+ dav_dir_conf *conf;
+ int flags = DAV_PROPDB_RO;
+ dav_error *err;
+ dav_propdb *propdb;
+ dav_get_props_result propstats = { 0 };
+
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(ctx->r, NULL, wres->resource, ctx->doc, &err) != DECLINED
+ && err) {
+ apr_pool_clear(ctx->scratchpool);
+ return NULL;
+ }
+
+ conf = ap_get_module_config(ctx->r->per_dir_config, &dav_module);
+ if (conf && conf->allow_lockdiscovery == DAV_ENABLED_OFF)
+ flags |= DAV_PROPDB_DISABLE_LOCKDISCOVERY;
+
+ /*
+ ** Note: ctx->doc can only be NULL for DAV_PROPFIND_IS_ALLPROP. Since
+ ** dav_get_allprops() does not need to do namespace translation,
+ ** we're okay.
+ **
+ ** Note: we cast to lose the "const". The propdb won't try to change
+ ** the resource, however, since we are opening readonly.
+ */
+ err = dav_popen_propdb(ctx->scratchpool,
+ ctx->r, ctx->w.lockdb, wres->resource, flags,
+ ctx->doc ? ctx->doc->namespaces : NULL, &propdb);
+ if (err != NULL) {
+ /* ### do something with err! */
+
+ if (ctx->propfind_type == DAV_PROPFIND_IS_PROP) {
+ dav_get_props_result badprops = { 0 };
+
+ /* some props were expected on this collection/resource */
+ dav_cache_badprops(ctx);
+ badprops.propstats = ctx->propstat_404;
+ dav_stream_response(wres, 0, &badprops, ctx->scratchpool);
+ }
+ else {
+ /* no props on this collection/resource */
+ dav_stream_response(wres, HTTP_OK, NULL, ctx->scratchpool);
+ }
+
+ apr_pool_clear(ctx->scratchpool);
+ return NULL;
+ }
+ /* ### what to do about closing the propdb on server failure? */
+
+ if (ctx->propfind_type == DAV_PROPFIND_IS_PROP) {
+ propstats = dav_get_props(propdb, ctx->doc);
+ }
+ else {
+ dav_prop_insert what = ctx->propfind_type == DAV_PROPFIND_IS_ALLPROP
+ ? DAV_PROP_INSERT_VALUE
+ : DAV_PROP_INSERT_NAME;
+ propstats = dav_get_allprops(propdb, what);
+ }
+ dav_stream_response(wres, 0, &propstats, ctx->scratchpool);
+
+ dav_close_propdb(propdb);
+
+ /* at this point, ctx->scratchpool has been used to stream a
+ single response. this function fully controls the pool, and
+ thus has the right to clear it for the next iteration of this
+ callback. */
+ apr_pool_clear(ctx->scratchpool);
+
+ return NULL;
+}
+
+/* handle the PROPFIND method */
+static int dav_method_propfind(request_rec *r)
+{
+ dav_resource *resource;
+ int depth;
+ dav_error *err;
+ int result;
+ apr_xml_doc *doc;
+ dav_walker_ctx ctx = { { 0 } };
+ dav_response *multi_status;
+
+ /* Ask repository module to resolve the resource */
+ err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ if ((result = ap_xml_parse_input(r, &doc)) != OK) {
+ return result;
+ }
+ /* note: doc == NULL if no request body */
+
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ if (dav_get_resource_state(r, resource) == DAV_RESOURCE_NULL) {
+ /* Apache will supply a default error for this. */
+ return HTTP_NOT_FOUND;
+ }
+
+ if ((depth = dav_get_depth(r, DAV_INFINITY)) < 0) {
+ /* dav_get_depth() supplies additional information for the
+ * default message. */
+ return HTTP_BAD_REQUEST;
+ }
+
+ if (depth == DAV_INFINITY && resource->collection) {
+ dav_dir_conf *conf;
+ conf = (dav_dir_conf *)ap_get_module_config(r->per_dir_config,
+ &dav_module);
+ /* default is to DISALLOW these requests */
+ if (conf->allow_depthinfinity != DAV_ENABLED_ON) {
+ return dav_error_response(r, HTTP_FORBIDDEN,
+ apr_psprintf(r->pool,
+ "PROPFIND requests with a "
+ "Depth of \"infinity\" are "
+ "not allowed for %s.",
+ ap_escape_html(r->pool,
+ r->uri)));
+ }
+ }
+
+ if (doc && !dav_validate_root(doc, "propfind")) {
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00585)
+ "The \"propfind\" element was not found.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* ### validate that only one of these three elements is present */
+
+ if (doc == NULL || dav_find_child(doc->root, "allprop") != NULL) {
+ /* note: no request body implies allprop */
+ ctx.propfind_type = DAV_PROPFIND_IS_ALLPROP;
+ }
+ else if (dav_find_child(doc->root, "propname") != NULL) {
+ ctx.propfind_type = DAV_PROPFIND_IS_PROPNAME;
+ }
+ else if (dav_find_child(doc->root, "prop") != NULL) {
+ ctx.propfind_type = DAV_PROPFIND_IS_PROP;
+ }
+ else {
+ /* "propfind" element must have one of the above three children */
+
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00586)
+ "The \"propfind\" element does not contain one of "
+ "the required child elements (the specific command).");
+ return HTTP_BAD_REQUEST;
+ }
+
+ ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_AUTH;
+ ctx.w.func = dav_propfind_walker;
+ ctx.w.walk_ctx = &ctx;
+ ctx.w.pool = r->pool;
+ ctx.w.root = resource;
+
+ ctx.doc = doc;
+ ctx.r = r;
+ ctx.bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+ apr_pool_create(&ctx.scratchpool, r->pool);
+ apr_pool_tag(ctx.scratchpool, "mod_dav-scratch");
+
+ if ((err = dav_open_lockdb(r, 1, &ctx.w.lockdb)) != NULL) {
+ err = dav_push_error(r->pool, err->status, 0,
+ "The lock database could not be opened, "
+ "preventing access to the various lock "
+ "properties for the PROPFIND.",
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+ if (ctx.w.lockdb != NULL) {
+ /* if we have a lock database, then we can walk locknull resources */
+ ctx.w.walk_type |= DAV_WALKTYPE_LOCKNULL;
+ }
+
+ /* send <multistatus> tag, with all doc->namespaces attached. */
+
+ /* NOTE: we *cannot* leave out the doc's namespaces from the
+ initial <multistatus> tag. if a 404 was generated for an HREF,
+ then we need to spit out the doc's namespaces for use by the
+ 404. Note that <response> elements will override these ns0,
+ ns1, etc, but NOT within the <response> scope for the
+ badprops. */
+ dav_begin_multistatus(ctx.bb, r, HTTP_MULTI_STATUS,
+ doc ? doc->namespaces : NULL);
+
+ /* Have the provider walk the resource. */
+ err = (*resource->hooks->walk)(&ctx.w, depth, &multi_status);
+
+ if (ctx.w.lockdb != NULL) {
+ (*ctx.w.lockdb->hooks->close_lockdb)(ctx.w.lockdb);
+ }
+
+ if (err != NULL) {
+ /* If an error occurred during the resource walk, there's
+ basically nothing we can do but abort the connection and
+ log an error. This is one of the limitations of HTTP; it
+ needs to "know" the entire status of the response before
+ generating it, which is just impossible in these streamy
+ response situations. */
+ err = dav_push_error(r->pool, err->status, 0,
+ "Provider encountered an error while streaming"
+ " a multistatus PROPFIND response.", err);
+ dav_log_err(r, err, APLOG_ERR);
+ r->connection->aborted = 1;
+ return DONE;
+ }
+
+ dav_finish_multistatus(r, ctx.bb);
+
+ /* the response has been sent. */
+ return DONE;
+}
+
+DAV_DECLARE(apr_text *) dav_failed_proppatch(apr_pool_t *p,
+ apr_array_header_t *prop_ctx)
+{
+ apr_text_header hdr = { 0 };
+ int i = prop_ctx->nelts;
+ dav_prop_ctx *ctx = (dav_prop_ctx *)prop_ctx->elts;
+ dav_error *err424_set = NULL;
+ dav_error *err424_delete = NULL;
+ const char *s;
+
+ /* ### might be nice to sort by status code and description */
+
+ for ( ; i-- > 0; ++ctx ) {
+ apr_text_append(p, &hdr,
+ "<D:propstat>" DEBUG_CR
+ "<D:prop>");
+ apr_text_append(p, &hdr, apr_xml_empty_elem(p, ctx->prop));
+ apr_text_append(p, &hdr, "</D:prop>" DEBUG_CR);
+
+ if (ctx->err == NULL) {
+ /* nothing was assigned here yet, so make it a 424 */
+
+ if (ctx->operation == DAV_PROP_OP_SET) {
+ if (err424_set == NULL)
+ err424_set = dav_new_error(p, HTTP_FAILED_DEPENDENCY, 0, 0,
+ "Attempted DAV:set operation "
+ "could not be completed due "
+ "to other errors.");
+ ctx->err = err424_set;
+ }
+ else if (ctx->operation == DAV_PROP_OP_DELETE) {
+ if (err424_delete == NULL)
+ err424_delete = dav_new_error(p, HTTP_FAILED_DEPENDENCY, 0, 0,
+ "Attempted DAV:remove "
+ "operation could not be "
+ "completed due to other "
+ "errors.");
+ ctx->err = err424_delete;
+ }
+ }
+
+ s = apr_psprintf(p,
+ "<D:status>"
+ "HTTP/1.1 %d (status)"
+ "</D:status>" DEBUG_CR,
+ ctx->err->status);
+ apr_text_append(p, &hdr, s);
+
+ /* ### we should use compute_desc if necessary... */
+ if (ctx->err->desc != NULL) {
+ apr_text_append(p, &hdr, "<D:responsedescription>" DEBUG_CR);
+ apr_text_append(p, &hdr, ctx->err->desc);
+ apr_text_append(p, &hdr, "</D:responsedescription>" DEBUG_CR);
+ }
+
+ apr_text_append(p, &hdr, "</D:propstat>" DEBUG_CR);
+ }
+
+ return hdr.first;
+}
+
+DAV_DECLARE(apr_text *) dav_success_proppatch(apr_pool_t *p,
+ apr_array_header_t *prop_ctx)
+{
+ apr_text_header hdr = { 0 };
+ int i = prop_ctx->nelts;
+ dav_prop_ctx *ctx = (dav_prop_ctx *)prop_ctx->elts;
+
+ /*
+ * ### we probably need to revise the way we assemble the response...
+ * ### this code assumes everything will return status==200.
+ */
+
+ apr_text_append(p, &hdr,
+ "<D:propstat>" DEBUG_CR
+ "<D:prop>" DEBUG_CR);
+
+ for ( ; i-- > 0; ++ctx ) {
+ apr_text_append(p, &hdr, apr_xml_empty_elem(p, ctx->prop));
+ }
+
+ apr_text_append(p, &hdr,
+ "</D:prop>" DEBUG_CR
+ "<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR
+ "</D:propstat>" DEBUG_CR);
+
+ return hdr.first;
+}
+
+static void dav_prop_log_errors(dav_prop_ctx *ctx)
+{
+ dav_log_err(ctx->r, ctx->err, APLOG_ERR);
+}
+
+/*
+ * Call <func> for each context. This can stop when an error occurs, or
+ * simply iterate through the whole list.
+ *
+ * Returns 1 if an error occurs (and the iteration is aborted). Returns 0
+ * if all elements are processed.
+ *
+ * If <reverse> is true (non-zero), then the list is traversed in
+ * reverse order.
+ */
+static int dav_process_ctx_list(void (*func)(dav_prop_ctx *ctx),
+ apr_array_header_t *ctx_list, int stop_on_error,
+ int reverse)
+{
+ int i = ctx_list->nelts;
+ dav_prop_ctx *ctx = (dav_prop_ctx *)ctx_list->elts;
+
+ if (reverse)
+ ctx += i;
+
+ while (i--) {
+ if (reverse)
+ --ctx;
+
+ (*func)(ctx);
+ if (stop_on_error && DAV_PROP_CTX_HAS_ERR(*ctx)) {
+ return 1;
+ }
+
+ if (!reverse)
+ ++ctx;
+ }
+
+ return 0;
+}
+
+/* handle the PROPPATCH method */
+static int dav_method_proppatch(request_rec *r)
+{
+ dav_error *err;
+ dav_resource *resource;
+ int result;
+ apr_xml_doc *doc;
+ apr_xml_elem *child;
+ dav_propdb *propdb;
+ int failure = 0;
+ dav_response resp = { 0 };
+ apr_text *propstat_text;
+ apr_array_header_t *ctx_list;
+ dav_prop_ctx *ctx;
+ dav_auto_version_info av_info;
+
+ /* Ask repository module to resolve the resource */
+ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ if ((result = ap_xml_parse_input(r, &doc)) != OK) {
+ return result;
+ }
+ /* note: doc == NULL if no request body */
+
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ if (!resource->exists) {
+ /* Apache will supply a default error for this. */
+ return HTTP_NOT_FOUND;
+ }
+
+ if (doc == NULL || !dav_validate_root(doc, "propertyupdate")) {
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00587)
+ "The request body does not contain "
+ "a \"propertyupdate\" element.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* Check If-Headers and existing locks */
+ /* Note: depth == 0. Implies no need for a multistatus response. */
+ if ((err = dav_validate_request(r, resource, 0, NULL, NULL,
+ DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
+ /* ### add a higher-level description? */
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* make sure the resource can be modified (if versioning repository) */
+ if ((err = dav_auto_checkout(r, resource,
+ 0 /* not parent_only */,
+ &av_info)) != NULL) {
+ /* ### add a higher-level description? */
+ return dav_handle_err(r, err, NULL);
+ }
+
+ if ((err = dav_open_propdb(r, NULL, resource,
+ DAV_PROPDB_NONE, doc->namespaces,
+ &propdb)) != NULL) {
+ /* undo any auto-checkout */
+ dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, &av_info);
+
+ err = dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
+ apr_psprintf(r->pool,
+ "Could not open the property "
+ "database for %s.",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+ /* ### what to do about closing the propdb on server failure? */
+
+ /* ### validate "live" properties */
+
+ /* set up an array to hold property operation contexts */
+ ctx_list = apr_array_make(r->pool, 10, sizeof(dav_prop_ctx));
+
+ /* do a first pass to ensure that all "remove" properties exist */
+ for (child = doc->root->first_child; child; child = child->next) {
+ int is_remove;
+ apr_xml_elem *prop_group;
+ apr_xml_elem *one_prop;
+
+ /* Ignore children that are not set/remove */
+ if (child->ns != APR_XML_NS_DAV_ID
+ || (!(is_remove = (strcmp(child->name, "remove") == 0))
+ && strcmp(child->name, "set") != 0)) {
+ continue;
+ }
+
+ /* make sure that a "prop" child exists for set/remove */
+ if ((prop_group = dav_find_child(child, "prop")) == NULL) {
+ dav_close_propdb(propdb);
+
+ /* undo any auto-checkout */
+ dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, &av_info);
+
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00588)
+ "A \"prop\" element is missing inside "
+ "the propertyupdate command.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ for (one_prop = prop_group->first_child; one_prop;
+ one_prop = one_prop->next) {
+
+ ctx = (dav_prop_ctx *)apr_array_push(ctx_list);
+ ctx->propdb = propdb;
+ ctx->operation = is_remove ? DAV_PROP_OP_DELETE : DAV_PROP_OP_SET;
+ ctx->prop = one_prop;
+
+ ctx->r = r; /* for later use by dav_prop_log_errors() */
+
+ dav_prop_validate(ctx);
+
+ if ( DAV_PROP_CTX_HAS_ERR(*ctx) ) {
+ failure = 1;
+ }
+ }
+ }
+
+ /* ### should test that we found at least one set/remove */
+
+ /* execute all of the operations */
+ if (!failure && dav_process_ctx_list(dav_prop_exec, ctx_list, 1, 0)) {
+ failure = 1;
+ }
+
+ /* generate a failure/success response */
+ if (failure) {
+ (void)dav_process_ctx_list(dav_prop_rollback, ctx_list, 0, 1);
+ propstat_text = dav_failed_proppatch(r->pool, ctx_list);
+ }
+ else {
+ (void)dav_process_ctx_list(dav_prop_commit, ctx_list, 0, 0);
+ propstat_text = dav_success_proppatch(r->pool, ctx_list);
+ }
+
+ /* make sure this gets closed! */
+ dav_close_propdb(propdb);
+
+ /* complete any auto-versioning */
+ dav_auto_checkin(r, resource, failure, 0 /*unlock*/, &av_info);
+
+ /* log any errors that occurred */
+ (void)dav_process_ctx_list(dav_prop_log_errors, ctx_list, 0, 0);
+
+ resp.href = resource->uri;
+
+ /* ### should probably use something new to pass along this text... */
+ resp.propresult.propstats = propstat_text;
+
+ dav_send_multistatus(r, HTTP_MULTI_STATUS, &resp, doc->namespaces);
+
+ /* the response has been sent. */
+ return DONE;
+}
+
+static int process_mkcol_body(request_rec *r)
+{
+ /* This is snarfed from ap_setup_client_block(). We could get pretty
+ * close to this behavior by passing REQUEST_NO_BODY, but we need to
+ * return HTTP_UNSUPPORTED_MEDIA_TYPE (while ap_setup_client_block
+ * returns HTTP_REQUEST_ENTITY_TOO_LARGE). */
+
+ const char *tenc = apr_table_get(r->headers_in, "Transfer-Encoding");
+ const char *lenp = apr_table_get(r->headers_in, "Content-Length");
+
+ /* make sure to set the Apache request fields properly. */
+ r->read_body = REQUEST_NO_BODY;
+ r->read_chunked = 0;
+ r->remaining = 0;
+
+ if (tenc) {
+ if (ap_cstr_casecmp(tenc, "chunked")) {
+ /* Use this instead of Apache's default error string */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00589)
+ "Unknown Transfer-Encoding %s", tenc);
+ return HTTP_NOT_IMPLEMENTED;
+ }
+
+ r->read_chunked = 1;
+ }
+ else if (lenp) {
+ if (!ap_parse_strict_length(&r->remaining, lenp)) {
+ r->remaining = 0;
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00590)
+ "Invalid Content-Length %s", lenp);
+ return HTTP_BAD_REQUEST;
+ }
+ }
+
+ if (r->read_chunked || r->remaining > 0) {
+ /* ### log something? */
+
+ /* Apache will supply a default error for this. */
+ return HTTP_UNSUPPORTED_MEDIA_TYPE;
+ }
+
+ /*
+ * Get rid of the body. this will call ap_setup_client_block(), but
+ * our copy above has already verified its work.
+ */
+ return ap_discard_request_body(r);
+}
+
+/* handle the MKCOL method */
+static int dav_method_mkcol(request_rec *r)
+{
+ dav_resource *resource;
+ int resource_state;
+ dav_auto_version_info av_info;
+ const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
+ dav_error *err;
+ dav_error *err2;
+ int result;
+ dav_response *multi_status;
+
+ /* handle the request body */
+ /* ### this may move lower once we start processing bodies */
+ if ((result = process_mkcol_body(r)) != OK) {
+ return result;
+ }
+
+ /* Ask repository module to resolve the resource */
+ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ if (resource->exists) {
+ /* oops. something was already there! */
+
+ /* Apache will supply a default error for this. */
+ /* ### we should provide a specific error message! */
+ return HTTP_METHOD_NOT_ALLOWED;
+ }
+
+ resource_state = dav_get_resource_state(r, resource);
+
+ /*
+ * Check If-Headers and existing locks.
+ *
+ * Note: depth == 0 normally requires no multistatus response. However,
+ * if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI
+ * other than the Request-URI, thereby requiring a multistatus.
+ *
+ * If the resource does not exist (DAV_RESOURCE_NULL), then we must
+ * check the resource *and* its parent. If the resource exists or is
+ * a locknull resource, then we check only the resource.
+ */
+ if ((err = dav_validate_request(r, resource, 0, NULL, &multi_status,
+ resource_state == DAV_RESOURCE_NULL ?
+ DAV_VALIDATE_PARENT :
+ DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
+ /* ### add a higher-level description? */
+ return dav_handle_err(r, err, multi_status);
+ }
+
+ /* if versioned resource, make sure parent is checked out */
+ if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
+ &av_info)) != NULL) {
+ /* ### add a higher-level description? */
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* try to create the collection */
+ resource->collection = 1;
+ err = (*resource->hooks->create_collection)(resource);
+
+ /* restore modifiability of parent back to what it was */
+ err2 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */,
+ 0 /*unlock*/, &av_info);
+
+ /* check for errors now */
+ if (err != NULL) {
+ return dav_handle_err(r, err, NULL);
+ }
+ if (err2 != NULL) {
+ /* just log a warning */
+ err = dav_push_error(r->pool, err2->status, 0,
+ "The MKCOL was successful, but there "
+ "was a problem automatically checking in "
+ "the parent collection.",
+ err2);
+ dav_log_err(r, err, APLOG_WARNING);
+ }
+
+ if (locks_hooks != NULL) {
+ dav_lockdb *lockdb;
+
+ if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
+ /* The directory creation was successful, but the locking failed. */
+ err = dav_push_error(r->pool, err->status, 0,
+ "The MKCOL was successful, but there "
+ "was a problem opening the lock database "
+ "which prevents inheriting locks from the "
+ "parent resources.",
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* notify lock system that we have created/replaced a resource */
+ err = dav_notify_created(r, lockdb, resource, resource_state, 0);
+
+ (*locks_hooks->close_lockdb)(lockdb);
+
+ if (err != NULL) {
+ /* The dir creation was successful, but the locking failed. */
+ err = dav_push_error(r->pool, err->status, 0,
+ "The MKCOL was successful, but there "
+ "was a problem updating its lock "
+ "information.",
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+ }
+
+ /* return an appropriate response (HTTP_CREATED) */
+ return dav_created(r, NULL, "Collection", 0);
+}
+
+/* handle the COPY and MOVE methods */
+static int dav_method_copymove(request_rec *r, int is_move)
+{
+ dav_resource *resource;
+ dav_resource *resnew;
+ dav_auto_version_info src_av_info = { 0 };
+ dav_auto_version_info dst_av_info = { 0 };
+ const char *body;
+ const char *dest;
+ dav_error *err;
+ dav_error *err2;
+ dav_error *err3;
+ dav_response *multi_response;
+ dav_lookup_result lookup;
+ int is_dir;
+ int overwrite;
+ int depth;
+ int result;
+ dav_lockdb *lockdb;
+ int replace_dest;
+ int resnew_state;
+
+ /* Ask repository module to resolve the resource */
+ err = dav_get_resource(r, !is_move /* label_allowed */,
+ 0 /* use_checked_in */, &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ if (!resource->exists) {
+ /* Apache will supply a default error for this. */
+ return HTTP_NOT_FOUND;
+ }
+
+ /* If not a file or collection resource, COPY/MOVE not allowed */
+ /* ### allow COPY/MOVE of DeltaV resource types */
+ if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
+ body = apr_psprintf(r->pool,
+ "Cannot COPY/MOVE resource %s.",
+ ap_escape_html(r->pool, r->uri));
+ return dav_error_response(r, HTTP_METHOD_NOT_ALLOWED, body);
+ }
+
+ /* get the destination URI */
+ dest = apr_table_get(r->headers_in, "Destination");
+ if (dest == NULL) {
+ /* Look in headers provided by Netscape's Roaming Profiles */
+ const char *nscp_host = apr_table_get(r->headers_in, "Host");
+ const char *nscp_path = apr_table_get(r->headers_in, "New-uri");
+
+ if (nscp_host != NULL && nscp_path != NULL)
+ dest = apr_pstrcat(r->pool, "http://", nscp_host, nscp_path, NULL);
+ }
+ if (dest == NULL) {
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00591)
+ "The request is missing a Destination header.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ lookup = dav_lookup_uri(dest, r, 1 /* must_be_absolute */);
+ if (lookup.rnew == NULL) {
+ if (lookup.err.status == HTTP_BAD_REQUEST) {
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00592)
+ "%s", lookup.err.desc);
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* ### this assumes that dav_lookup_uri() only generates a status
+ * ### that Apache can provide a status line for!! */
+
+ return dav_error_response(r, lookup.err.status, lookup.err.desc);
+ }
+ if (lookup.rnew->status != HTTP_OK) {
+ const char *auth = apr_table_get(lookup.rnew->err_headers_out,
+ "WWW-Authenticate");
+ if (lookup.rnew->status == HTTP_UNAUTHORIZED && auth != NULL) {
+ /* propagate the WWW-Authorization header up from the
+ * subreq so the client sees it. */
+ apr_table_setn(r->err_headers_out, "WWW-Authenticate",
+ apr_pstrdup(r->pool, auth));
+ }
+
+ /* ### how best to report this... */
+ return dav_error_response(r, lookup.rnew->status,
+ "Destination URI had an error.");
+ }
+
+ /* Resolve destination resource */
+ err = dav_get_resource(lookup.rnew, 0 /* label_allowed */,
+ 0 /* use_checked_in */, &resnew);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, resnew, NULL, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* are the two resources handled by the same repository? */
+ if (resource->hooks != resnew->hooks) {
+ /* ### this message exposes some backend config, but screw it... */
+ return dav_error_response(r, HTTP_BAD_GATEWAY,
+ "Destination URI is handled by a "
+ "different repository than the source URI. "
+ "MOVE or COPY between repositories is "
+ "not possible.");
+ }
+
+ /* get and parse the overwrite header value */
+ if ((overwrite = dav_get_overwrite(r)) < 0) {
+ /* dav_get_overwrite() supplies additional information for the
+ * default message. */
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* quick failure test: if dest exists and overwrite is false. */
+ if (resnew->exists && !overwrite) {
+ /* Supply some text for the error response body. */
+ return dav_error_response(r, HTTP_PRECONDITION_FAILED,
+ "Destination is not empty and "
+ "Overwrite is not \"T\"");
+ }
+
+ /* are the source and destination the same? */
+ if ((*resource->hooks->is_same_resource)(resource, resnew)) {
+ /* Supply some text for the error response body. */
+ return dav_error_response(r, HTTP_FORBIDDEN,
+ "Source and Destination URIs are the same.");
+
+ }
+
+ is_dir = resource->collection;
+
+ /* get and parse the Depth header value. "0" and "infinity" are legal. */
+ if ((depth = dav_get_depth(r, DAV_INFINITY)) < 0) {
+ /* dav_get_depth() supplies additional information for the
+ * default message. */
+ return HTTP_BAD_REQUEST;
+ }
+ if (depth == 1) {
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00593)
+ "Depth must be \"0\" or \"infinity\" for COPY or MOVE.");
+ return HTTP_BAD_REQUEST;
+ }
+ if (is_move && is_dir && depth != DAV_INFINITY) {
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00594)
+ "Depth must be \"infinity\" when moving a collection.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /*
+ * Check If-Headers and existing locks for each resource in the source.
+ * We will return a 424 response with a DAV:multistatus body.
+ * The multistatus responses will contain the information about any
+ * resource that fails the validation.
+ *
+ * We check the parent resource, too, if this is a MOVE. Moving the
+ * resource effectively removes it from the parent collection, so we
+ * must ensure that we have met the appropriate conditions.
+ *
+ * If a problem occurs with the Request-URI itself, then a plain error
+ * (rather than a multistatus) will be returned.
+ */
+ if ((err = dav_validate_request(r, resource, depth, NULL,
+ &multi_response,
+ (is_move ? DAV_VALIDATE_PARENT
+ : DAV_VALIDATE_RESOURCE
+ | DAV_VALIDATE_NO_MODIFY)
+ | DAV_VALIDATE_USE_424,
+ NULL)) != NULL) {
+ err = dav_push_error(r->pool, err->status, 0,
+ apr_psprintf(r->pool,
+ "Could not %s %s due to a failed "
+ "precondition on the source "
+ "(e.g. locks).",
+ is_move ? "MOVE" : "COPY",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, multi_response);
+ }
+
+ /*
+ * Check If-Headers and existing locks for destination. Note that we
+ * use depth==infinity since the target (hierarchy) will be deleted
+ * before the move/copy is completed.
+ *
+ * Note that we are overwriting the target, which implies a DELETE, so
+ * we are subject to the error/response rules as a DELETE. Namely, we
+ * will return a 424 error if any of the validations fail.
+ * (see dav_method_delete() for more information)
+ */
+ if ((err = dav_validate_request(lookup.rnew, resnew, DAV_INFINITY, NULL,
+ &multi_response,
+ DAV_VALIDATE_PARENT
+ | DAV_VALIDATE_USE_424, NULL)) != NULL) {
+ err = dav_push_error(r->pool, err->status, 0,
+ apr_psprintf(r->pool,
+ "Could not MOVE/COPY %s due to a "
+ "failed precondition on the "
+ "destination (e.g. locks).",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, multi_response);
+ }
+
+ if (is_dir
+ && depth == DAV_INFINITY
+ && (*resource->hooks->is_parent_resource)(resource, resnew)) {
+ /* Supply some text for the error response body. */
+ return dav_error_response(r, HTTP_FORBIDDEN,
+ "Source collection contains the "
+ "Destination.");
+
+ }
+ if (is_dir
+ && (*resnew->hooks->is_parent_resource)(resnew, resource)) {
+ /* The destination must exist (since it contains the source), and
+ * a condition above implies Overwrite==T. Obviously, we cannot
+ * delete the Destination before the MOVE/COPY, as that would
+ * delete the Source.
+ */
+
+ /* Supply some text for the error response body. */
+ return dav_error_response(r, HTTP_FORBIDDEN,
+ "Destination collection contains the Source "
+ "and Overwrite has been specified.");
+ }
+
+ /* ### for now, we don't need anything in the body */
+ if ((result = ap_discard_request_body(r)) != OK) {
+ return result;
+ }
+
+ if ((err = dav_open_lockdb(r, 0, &lockdb)) != NULL) {
+ /* ### add a higher-level description? */
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* remove any locks from the old resources */
+ /*
+ * ### this is Yet Another Traversal. if we do a rename(), then we
+ * ### really don't have to do this in some cases since the inode
+ * ### values will remain constant across the move. but we can't
+ * ### know that fact from outside the provider :-(
+ *
+ * ### note that we now have a problem atomicity in the move/copy
+ * ### since a failure after this would have removed locks (technically,
+ * ### this is okay to do, but really...)
+ */
+ if (is_move && lockdb != NULL) {
+ /* ### this is wrong! it blasts direct locks on parent resources */
+ /* ### pass lockdb! */
+ (void)dav_unlock(r, resource, NULL);
+ }
+
+ /* if this is a move, then the source parent collection will be modified */
+ if (is_move) {
+ if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
+ &src_av_info)) != NULL) {
+ if (lockdb != NULL)
+ (*lockdb->hooks->close_lockdb)(lockdb);
+
+ /* ### add a higher-level description? */
+ return dav_handle_err(r, err, NULL);
+ }
+ }
+
+ /*
+ * Remember the initial state of the destination, so the lock system
+ * can be notified as to how it changed.
+ */
+ resnew_state = dav_get_resource_state(lookup.rnew, resnew);
+
+ /* In a MOVE operation, the destination is replaced by the source.
+ * In a COPY operation, if the destination exists, is under version
+ * control, and is the same resource type as the source,
+ * then it should not be replaced, but modified to be a copy of
+ * the source.
+ */
+ if (!resnew->exists)
+ replace_dest = 0;
+ else if (is_move || !resource->versioned)
+ replace_dest = 1;
+ else if (resource->type != resnew->type)
+ replace_dest = 1;
+ else if ((resource->collection == 0) != (resnew->collection == 0))
+ replace_dest = 1;
+ else
+ replace_dest = 0;
+
+ /* If the destination must be created or replaced,
+ * make sure the parent collection is writable
+ */
+ if (!resnew->exists || replace_dest) {
+ if ((err = dav_auto_checkout(r, resnew, 1 /*parent_only*/,
+ &dst_av_info)) != NULL) {
+ /* could not make destination writable:
+ * if move, restore state of source parent
+ */
+ if (is_move) {
+ (void)dav_auto_checkin(r, NULL, 1 /* undo */,
+ 0 /*unlock*/, &src_av_info);
+ }
+
+ if (lockdb != NULL)
+ (*lockdb->hooks->close_lockdb)(lockdb);
+
+ /* ### add a higher-level description? */
+ return dav_handle_err(r, err, NULL);
+ }
+ }
+
+ /* If source and destination parents are the same, then
+ * use the same resource object, so status updates to one are reflected
+ * in the other, when doing auto-versioning. Otherwise,
+ * we may try to checkin the parent twice.
+ */
+ if (src_av_info.parent_resource != NULL
+ && dst_av_info.parent_resource != NULL
+ && (*src_av_info.parent_resource->hooks->is_same_resource)
+ (src_av_info.parent_resource, dst_av_info.parent_resource)) {
+
+ dst_av_info.parent_resource = src_av_info.parent_resource;
+ }
+
+ /* If destination is being replaced, remove it first
+ * (we know Ovewrite must be TRUE). Then try to copy/move the resource.
+ */
+ if (replace_dest)
+ err = (*resnew->hooks->remove_resource)(resnew, &multi_response);
+
+ if (err == NULL) {
+ if (is_move)
+ err = (*resource->hooks->move_resource)(resource, resnew,
+ &multi_response);
+ else
+ err = (*resource->hooks->copy_resource)(resource, resnew, depth,
+ &multi_response);
+ }
+
+ /* perform any auto-versioning cleanup */
+ err2 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */,
+ 0 /*unlock*/, &dst_av_info);
+
+ if (is_move) {
+ err3 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */,
+ 0 /*unlock*/, &src_av_info);
+ }
+ else
+ err3 = NULL;
+
+ /* check for error from remove/copy/move operations */
+ if (err != NULL) {
+ if (lockdb != NULL)
+ (*lockdb->hooks->close_lockdb)(lockdb);
+
+ err = dav_push_error(r->pool, err->status, 0,
+ apr_psprintf(r->pool,
+ "Could not MOVE/COPY %s.",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, multi_response);
+ }
+
+ /* check for errors from auto-versioning */
+ if (err2 != NULL) {
+ /* just log a warning */
+ err = dav_push_error(r->pool, err2->status, 0,
+ "The MOVE/COPY was successful, but there was a "
+ "problem automatically checking in the "
+ "source parent collection.",
+ err2);
+ dav_log_err(r, err, APLOG_WARNING);
+ }
+ if (err3 != NULL) {
+ /* just log a warning */
+ err = dav_push_error(r->pool, err3->status, 0,
+ "The MOVE/COPY was successful, but there was a "
+ "problem automatically checking in the "
+ "destination or its parent collection.",
+ err3);
+ dav_log_err(r, err, APLOG_WARNING);
+ }
+
+ /* propagate any indirect locks at the target */
+ if (lockdb != NULL) {
+
+ /* notify lock system that we have created/replaced a resource */
+ err = dav_notify_created(r, lockdb, resnew, resnew_state, depth);
+
+ (*lockdb->hooks->close_lockdb)(lockdb);
+
+ if (err != NULL) {
+ /* The move/copy was successful, but the locking failed. */
+ err = dav_push_error(r->pool, err->status, 0,
+ "The MOVE/COPY was successful, but there "
+ "was a problem updating the lock "
+ "information.",
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+ }
+
+ /* return an appropriate response (HTTP_CREATED or HTTP_NO_CONTENT) */
+ return dav_created(r, lookup.rnew->unparsed_uri, "Destination",
+ resnew_state == DAV_RESOURCE_EXISTS);
+}
+
+/* dav_method_lock: Handler to implement the DAV LOCK method
+ * Returns appropriate HTTP_* response.
+ */
+static int dav_method_lock(request_rec *r)
+{
+ dav_error *err;
+ dav_resource *resource;
+ dav_resource *parent;
+ const dav_hooks_locks *locks_hooks;
+ int result;
+ int depth;
+ int new_lock_request = 0;
+ apr_xml_doc *doc;
+ dav_lock *lock;
+ dav_response *multi_response = NULL;
+ dav_lockdb *lockdb;
+ int resource_state;
+
+ /* If no locks provider, decline the request */
+ locks_hooks = DAV_GET_HOOKS_LOCKS(r);
+ if (locks_hooks == NULL)
+ return DECLINED;
+
+ if ((result = ap_xml_parse_input(r, &doc)) != OK)
+ return result;
+
+ depth = dav_get_depth(r, DAV_INFINITY);
+ if (depth != 0 && depth != DAV_INFINITY) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00595)
+ "Depth must be 0 or \"infinity\" for LOCK.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* Ask repository module to resolve the resource */
+ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* Check if parent collection exists */
+ if ((err = resource->hooks->get_parent_resource(resource, &parent)) != NULL) {
+ /* ### add a higher-level description? */
+ return dav_handle_err(r, err, NULL);
+ }
+ if (parent && (!parent->exists || parent->collection != 1)) {
+ err = dav_new_error(r->pool, HTTP_CONFLICT, 0, 0,
+ apr_psprintf(r->pool,
+ "The parent resource of %s does not "
+ "exist or is not a collection.",
+ ap_escape_html(r->pool, r->uri)));
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /*
+ * Open writable. Unless an error occurs, we'll be
+ * writing into the database.
+ */
+ if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
+ /* ### add a higher-level description? */
+ return dav_handle_err(r, err, NULL);
+ }
+
+ if (doc != NULL) {
+ if ((err = dav_lock_parse_lockinfo(r, resource, lockdb, doc,
+ &lock)) != NULL) {
+ /* ### add a higher-level description to err? */
+ goto error;
+ }
+ new_lock_request = 1;
+
+ lock->auth_user = apr_pstrdup(r->pool, r->user);
+ }
+
+ resource_state = dav_get_resource_state(r, resource);
+
+ /*
+ * Check If-Headers and existing locks.
+ *
+ * If this will create a locknull resource, then the LOCK will affect
+ * the parent collection (much like a PUT/MKCOL). For that case, we must
+ * validate the parent resource's conditions.
+ */
+ if ((err = dav_validate_request(r, resource, depth, NULL, &multi_response,
+ (resource_state == DAV_RESOURCE_NULL
+ ? DAV_VALIDATE_PARENT
+ : DAV_VALIDATE_RESOURCE)
+ | (new_lock_request ? lock->scope : 0)
+ | DAV_VALIDATE_ADD_LD,
+ lockdb)) != OK) {
+ err = dav_push_error(r->pool, err->status, 0,
+ apr_psprintf(r->pool,
+ "Could not LOCK %s due to a failed "
+ "precondition (e.g. other locks).",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ goto error;
+ }
+
+ if (new_lock_request == 0) {
+ dav_locktoken_list *ltl;
+
+ /*
+ * Refresh request
+ * ### Assumption: We can renew multiple locks on the same resource
+ * ### at once. First harvest all the positive lock-tokens given in
+ * ### the If header. Then modify the lock entries for this resource
+ * ### with the new Timeout val.
+ */
+
+ if ((err = dav_get_locktoken_list(r, &ltl)) != NULL) {
+ err = dav_push_error(r->pool, err->status, 0,
+ apr_psprintf(r->pool,
+ "The lock refresh for %s failed "
+ "because no lock tokens were "
+ "specified in an \"If:\" "
+ "header.",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ goto error;
+ }
+
+ if ((err = (*locks_hooks->refresh_locks)(lockdb, resource, ltl,
+ dav_get_timeout(r),
+ &lock)) != NULL) {
+ /* ### add a higher-level description to err? */
+ goto error;
+ }
+ } else {
+ /* New lock request */
+ char *locktoken_txt;
+ dav_dir_conf *conf;
+
+ conf = (dav_dir_conf *)ap_get_module_config(r->per_dir_config,
+ &dav_module);
+
+ /* apply lower bound (if any) from DAVMinTimeout directive */
+ if (lock->timeout != DAV_TIMEOUT_INFINITE
+ && lock->timeout < time(NULL) + conf->locktimeout)
+ lock->timeout = time(NULL) + conf->locktimeout;
+
+ err = dav_add_lock(r, resource, lockdb, lock, &multi_response);
+ if (err != NULL) {
+ /* ### add a higher-level description to err? */
+ goto error;
+ }
+
+ locktoken_txt = apr_pstrcat(r->pool, "<",
+ (*locks_hooks->format_locktoken)(r->pool,
+ lock->locktoken),
+ ">", NULL);
+
+ apr_table_setn(r->headers_out, "Lock-Token", locktoken_txt);
+ }
+
+ (*locks_hooks->close_lockdb)(lockdb);
+
+ r->status = HTTP_OK;
+ ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
+
+ ap_rputs(DAV_XML_HEADER DEBUG_CR "<D:prop xmlns:D=\"DAV:\">" DEBUG_CR, r);
+ if (lock == NULL)
+ ap_rputs("<D:lockdiscovery/>" DEBUG_CR, r);
+ else {
+ ap_rprintf(r,
+ "<D:lockdiscovery>" DEBUG_CR
+ "%s" DEBUG_CR
+ "</D:lockdiscovery>" DEBUG_CR,
+ dav_lock_get_activelock(r, lock, NULL));
+ }
+ ap_rputs("</D:prop>", r);
+
+ /* the response has been sent. */
+ return DONE;
+
+ error:
+ (*locks_hooks->close_lockdb)(lockdb);
+ return dav_handle_err(r, err, multi_response);
+}
+
+/* dav_method_unlock: Handler to implement the DAV UNLOCK method
+ * Returns appropriate HTTP_* response.
+ */
+static int dav_method_unlock(request_rec *r)
+{
+ dav_error *err;
+ dav_resource *resource;
+ const dav_hooks_locks *locks_hooks;
+ int result;
+ const char *const_locktoken_txt;
+ char *locktoken_txt;
+ dav_locktoken *locktoken = NULL;
+ int resource_state;
+ dav_response *multi_response;
+
+ /* If no locks provider, decline the request */
+ locks_hooks = DAV_GET_HOOKS_LOCKS(r);
+ if (locks_hooks == NULL)
+ return DECLINED;
+
+ if ((const_locktoken_txt = apr_table_get(r->headers_in,
+ "Lock-Token")) == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00596)
+ "Unlock failed (%s): "
+ "No Lock-Token specified in header", r->filename);
+ return HTTP_BAD_REQUEST;
+ }
+
+ locktoken_txt = apr_pstrdup(r->pool, const_locktoken_txt);
+ if (locktoken_txt[0] != '<') {
+ /* ### should provide more specifics... */
+ return HTTP_BAD_REQUEST;
+ }
+ locktoken_txt++;
+
+ if (locktoken_txt[strlen(locktoken_txt) - 1] != '>') {
+ /* ### should provide more specifics... */
+ return HTTP_BAD_REQUEST;
+ }
+ locktoken_txt[strlen(locktoken_txt) - 1] = '\0';
+
+ if ((err = (*locks_hooks->parse_locktoken)(r->pool, locktoken_txt,
+ &locktoken)) != NULL) {
+ err = dav_push_error(r->pool, HTTP_BAD_REQUEST, 0,
+ apr_psprintf(r->pool,
+ "The UNLOCK on %s failed -- an "
+ "invalid lock token was specified "
+ "in the \"If:\" header.",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* Ask repository module to resolve the resource */
+ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ resource_state = dav_get_resource_state(r, resource);
+
+ /*
+ * Check If-Headers and existing locks.
+ *
+ * Note: depth == 0 normally requires no multistatus response. However,
+ * if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI
+ * other than the Request-URI, thereby requiring a multistatus.
+ *
+ * If the resource is a locknull resource, then the UNLOCK will affect
+ * the parent collection (much like a delete). For that case, we must
+ * validate the parent resource's conditions.
+ */
+ if ((err = dav_validate_request(r, resource, 0, locktoken,
+ &multi_response,
+ resource_state == DAV_RESOURCE_LOCK_NULL
+ ? DAV_VALIDATE_PARENT
+ : DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
+ /* ### add a higher-level description? */
+ return dav_handle_err(r, err, multi_response);
+ }
+
+ /* ### RFC 2518 s. 8.11: If this resource is locked by locktoken,
+ * _all_ resources locked by locktoken are released. It does not say
+ * resource has to be the root of an infinite lock. Thus, an UNLOCK
+ * on any part of an infinite lock will remove the lock on all resources.
+ *
+ * For us, if r->filename represents an indirect lock (part of an infinity lock),
+ * we must actually perform an UNLOCK on the direct lock for this resource.
+ */
+ if ((result = dav_unlock(r, resource, locktoken)) != OK) {
+ return result;
+ }
+
+ return HTTP_NO_CONTENT;
+}
+
+static int dav_method_vsn_control(request_rec *r)
+{
+ dav_resource *resource;
+ int resource_state;
+ dav_auto_version_info av_info;
+ const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+ dav_error *err;
+ apr_xml_doc *doc;
+ const char *target = NULL;
+ int result;
+
+ /* if no versioning provider, decline the request */
+ if (vsn_hooks == NULL)
+ return DECLINED;
+
+ /* ask repository module to resolve the resource */
+ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* parse the request body (may be a version-control element) */
+ if ((result = ap_xml_parse_input(r, &doc)) != OK) {
+ return result;
+ }
+ /* note: doc == NULL if no request body */
+
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* remember the pre-creation resource state */
+ resource_state = dav_get_resource_state(r, resource);
+
+ if (doc != NULL) {
+ const apr_xml_elem *child;
+ apr_size_t tsize;
+
+ if (!dav_validate_root(doc, "version-control")) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00597)
+ "The request body does not contain "
+ "a \"version-control\" element.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* get the version URI */
+ if ((child = dav_find_child(doc->root, "version")) == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00598)
+ "The \"version-control\" element does not contain "
+ "a \"version\" element.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ if ((child = dav_find_child(child, "href")) == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00599)
+ "The \"version\" element does not contain "
+ "an \"href\" element.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* get version URI */
+ apr_xml_to_text(r->pool, child, APR_XML_X2T_INNER, NULL, NULL,
+ &target, &tsize);
+ if (tsize == 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00600)
+ "An \"href\" element does not contain a URI.");
+ return HTTP_BAD_REQUEST;
+ }
+ }
+
+ /* Check request preconditions */
+
+ /* ### need a general mechanism for reporting precondition violations
+ * ### (should be returning XML document for 403/409 responses)
+ */
+
+ /* if not versioning existing resource, must specify version to select */
+ if (!resource->exists && target == NULL) {
+ err = dav_new_error(r->pool, HTTP_CONFLICT, 0, 0,
+ "<DAV:initial-version-required/>");
+ return dav_handle_err(r, err, NULL);
+ }
+ else if (resource->exists) {
+ /* cannot add resource to existing version history */
+ if (target != NULL) {
+ err = dav_new_error(r->pool, HTTP_CONFLICT, 0, 0,
+ "<DAV:cannot-add-to-existing-history/>");
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* resource must be unversioned and versionable, or version selector */
+ if (resource->type != DAV_RESOURCE_TYPE_REGULAR
+ || (!resource->versioned && !(vsn_hooks->versionable)(resource))) {
+ err = dav_new_error(r->pool, HTTP_CONFLICT, 0, 0,
+ "<DAV:must-be-versionable/>");
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* the DeltaV spec says if resource is a version selector,
+ * then VERSION-CONTROL is a no-op
+ */
+ if (resource->versioned) {
+ /* set the Cache-Control header, per the spec */
+ apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
+
+ /* no body */
+ ap_set_content_length(r, 0);
+
+ return DONE;
+ }
+ }
+
+ /* Check If-Headers and existing locks */
+ /* Note: depth == 0. Implies no need for a multistatus response. */
+ if ((err = dav_validate_request(r, resource, 0, NULL, NULL,
+ resource_state == DAV_RESOURCE_NULL ?
+ DAV_VALIDATE_PARENT :
+ DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* if in versioned collection, make sure parent is checked out */
+ if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
+ &av_info)) != NULL) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* attempt to version-control the resource */
+ if ((err = (*vsn_hooks->vsn_control)(resource, target)) != NULL) {
+ dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, &av_info);
+ err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
+ apr_psprintf(r->pool,
+ "Could not VERSION-CONTROL resource %s.",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* revert writability of parent directory */
+ err = dav_auto_checkin(r, resource, 0 /*undo*/, 0 /*unlock*/, &av_info);
+ if (err != NULL) {
+ /* just log a warning */
+ err = dav_push_error(r->pool, err->status, 0,
+ "The VERSION-CONTROL was successful, but there "
+ "was a problem automatically checking in "
+ "the parent collection.",
+ err);
+ dav_log_err(r, err, APLOG_WARNING);
+ }
+
+ /* if the resource is lockable, let lock system know of new resource */
+ if (locks_hooks != NULL
+ && (*locks_hooks->get_supportedlock)(resource) != NULL) {
+ dav_lockdb *lockdb;
+
+ if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
+ /* The resource creation was successful, but the locking failed. */
+ err = dav_push_error(r->pool, err->status, 0,
+ "The VERSION-CONTROL was successful, but there "
+ "was a problem opening the lock database "
+ "which prevents inheriting locks from the "
+ "parent resources.",
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* notify lock system that we have created/replaced a resource */
+ err = dav_notify_created(r, lockdb, resource, resource_state, 0);
+
+ (*locks_hooks->close_lockdb)(lockdb);
+
+ if (err != NULL) {
+ /* The dir creation was successful, but the locking failed. */
+ err = dav_push_error(r->pool, err->status, 0,
+ "The VERSION-CONTROL was successful, but there "
+ "was a problem updating its lock "
+ "information.",
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+ }
+
+ /* set the Cache-Control header, per the spec */
+ apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
+
+ /* return an appropriate response (HTTP_CREATED) */
+ return dav_created(r, resource->uri, "Version selector", 0 /*replaced*/);
+}
+
+/* handle the CHECKOUT method */
+static int dav_method_checkout(request_rec *r)
+{
+ dav_resource *resource;
+ dav_resource *working_resource;
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+ dav_error *err;
+ int result;
+ apr_xml_doc *doc;
+ int apply_to_vsn = 0;
+ int is_unreserved = 0;
+ int is_fork_ok = 0;
+ int create_activity = 0;
+ apr_array_header_t *activities = NULL;
+
+ /* If no versioning provider, decline the request */
+ if (vsn_hooks == NULL)
+ return DECLINED;
+
+ if ((result = ap_xml_parse_input(r, &doc)) != OK)
+ return result;
+
+ if (doc != NULL) {
+ const apr_xml_elem *aset;
+
+ if (!dav_validate_root(doc, "checkout")) {
+ /* This supplies additional information for the default msg. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00601)
+ "The request body, if present, must be a "
+ "DAV:checkout element.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ if (dav_find_child(doc->root, "apply-to-version") != NULL) {
+ if (apr_table_get(r->headers_in, "label") != NULL) {
+ /* ### we want generic 403/409 XML reporting here */
+ /* ### DAV:must-not-have-label-and-apply-to-version */
+ return dav_error_response(r, HTTP_CONFLICT,
+ "DAV:apply-to-version cannot be "
+ "used in conjunction with a "
+ "Label header.");
+ }
+ apply_to_vsn = 1;
+ }
+
+ is_unreserved = dav_find_child(doc->root, "unreserved") != NULL;
+ is_fork_ok = dav_find_child(doc->root, "fork-ok") != NULL;
+
+ if ((aset = dav_find_child(doc->root, "activity-set")) != NULL) {
+ if (dav_find_child(aset, "new") != NULL) {
+ create_activity = 1;
+ }
+ else {
+ const apr_xml_elem *child = aset->first_child;
+
+ activities = apr_array_make(r->pool, 1, sizeof(const char *));
+
+ for (; child != NULL; child = child->next) {
+ if (child->ns == APR_XML_NS_DAV_ID
+ && strcmp(child->name, "href") == 0) {
+ const char *href;
+
+ href = dav_xml_get_cdata(child, r->pool,
+ 1 /* strip_white */);
+ *(const char **)apr_array_push(activities) = href;
+ }
+ }
+
+ if (activities->nelts == 0) {
+ /* no href's is a DTD violation:
+ <!ELEMENT activity-set (href+ | new)>
+ */
+
+ /* This supplies additional info for the default msg. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00602)
+ "Within the DAV:activity-set element, the "
+ "DAV:new element must be used, or at least "
+ "one DAV:href must be specified.");
+ return HTTP_BAD_REQUEST;
+ }
+ }
+ }
+ }
+
+ /* Ask repository module to resolve the resource */
+ err = dav_get_resource(r, 1 /*label_allowed*/, apply_to_vsn, &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ if (!resource->exists) {
+ /* Apache will supply a default error for this. */
+ return HTTP_NOT_FOUND;
+ }
+
+ /* Check the state of the resource: must be a file or collection,
+ * must be versioned, and must not already be checked out.
+ */
+ if (resource->type != DAV_RESOURCE_TYPE_REGULAR
+ && resource->type != DAV_RESOURCE_TYPE_VERSION) {
+ return dav_error_response(r, HTTP_CONFLICT,
+ "Cannot checkout this type of resource.");
+ }
+
+ if (!resource->versioned) {
+ return dav_error_response(r, HTTP_CONFLICT,
+ "Cannot checkout unversioned resource.");
+ }
+
+ if (resource->working) {
+ return dav_error_response(r, HTTP_CONFLICT,
+ "The resource is already checked out to the workspace.");
+ }
+
+ /* ### do lock checks, once behavior is defined */
+
+ /* Do the checkout */
+ if ((err = (*vsn_hooks->checkout)(resource, 0 /*auto_checkout*/,
+ is_unreserved, is_fork_ok,
+ create_activity, activities,
+ &working_resource)) != NULL) {
+ err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
+ apr_psprintf(r->pool,
+ "Could not CHECKOUT resource %s.",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* set the Cache-Control header, per the spec */
+ apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
+
+ /* if no working resource created, return OK,
+ * else return CREATED with working resource URL in Location header
+ */
+ if (working_resource == NULL) {
+ /* no body */
+ ap_set_content_length(r, 0);
+ return DONE;
+ }
+
+ return dav_created(r, working_resource->uri, "Checked-out resource", 0);
+}
+
+/* handle the UNCHECKOUT method */
+static int dav_method_uncheckout(request_rec *r)
+{
+ dav_resource *resource;
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+ dav_error *err;
+ int result;
+
+ /* If no versioning provider, decline the request */
+ if (vsn_hooks == NULL)
+ return DECLINED;
+
+ if ((result = ap_discard_request_body(r)) != OK) {
+ return result;
+ }
+
+ /* Ask repository module to resolve the resource */
+ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ if (!resource->exists) {
+ /* Apache will supply a default error for this. */
+ return HTTP_NOT_FOUND;
+ }
+
+ /* Check the state of the resource: must be a file or collection,
+ * must be versioned, and must be checked out.
+ */
+ if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
+ return dav_error_response(r, HTTP_CONFLICT,
+ "Cannot uncheckout this type of resource.");
+ }
+
+ if (!resource->versioned) {
+ return dav_error_response(r, HTTP_CONFLICT,
+ "Cannot uncheckout unversioned resource.");
+ }
+
+ if (!resource->working) {
+ return dav_error_response(r, HTTP_CONFLICT,
+ "The resource is not checked out to the workspace.");
+ }
+
+ /* ### do lock checks, once behavior is defined */
+
+ /* Do the uncheckout */
+ if ((err = (*vsn_hooks->uncheckout)(resource)) != NULL) {
+ err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
+ apr_psprintf(r->pool,
+ "Could not UNCHECKOUT resource %s.",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* no body */
+ ap_set_content_length(r, 0);
+
+ return DONE;
+}
+
+/* handle the CHECKIN method */
+static int dav_method_checkin(request_rec *r)
+{
+ dav_resource *resource;
+ dav_resource *new_version;
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+ dav_error *err;
+ int result;
+ apr_xml_doc *doc;
+ int keep_checked_out = 0;
+
+ /* If no versioning provider, decline the request */
+ if (vsn_hooks == NULL)
+ return DECLINED;
+
+ if ((result = ap_xml_parse_input(r, &doc)) != OK)
+ return result;
+
+ if (doc != NULL) {
+ if (!dav_validate_root(doc, "checkin")) {
+ /* This supplies additional information for the default msg. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00603)
+ "The request body, if present, must be a "
+ "DAV:checkin element.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ keep_checked_out = dav_find_child(doc->root, "keep-checked-out") != NULL;
+ }
+
+ /* Ask repository module to resolve the resource */
+ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ if (!resource->exists) {
+ /* Apache will supply a default error for this. */
+ return HTTP_NOT_FOUND;
+ }
+
+ /* Check the state of the resource: must be a file or collection,
+ * must be versioned, and must be checked out.
+ */
+ if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
+ return dav_error_response(r, HTTP_CONFLICT,
+ "Cannot checkin this type of resource.");
+ }
+
+ if (!resource->versioned) {
+ return dav_error_response(r, HTTP_CONFLICT,
+ "Cannot checkin unversioned resource.");
+ }
+
+ if (!resource->working) {
+ return dav_error_response(r, HTTP_CONFLICT,
+ "The resource is not checked out.");
+ }
+
+ /* ### do lock checks, once behavior is defined */
+
+ /* Do the checkin */
+ if ((err = (*vsn_hooks->checkin)(resource, keep_checked_out, &new_version))
+ != NULL) {
+ err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
+ apr_psprintf(r->pool,
+ "Could not CHECKIN resource %s.",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+
+ return dav_created(r, new_version->uri, "Version", 0);
+}
+
+static int dav_method_update(request_rec *r)
+{
+ dav_resource *resource;
+ dav_resource *version = NULL;
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+ apr_xml_doc *doc;
+ apr_xml_elem *child;
+ int is_label = 0;
+ int depth;
+ int result;
+ apr_size_t tsize;
+ const char *target;
+ dav_response *multi_response;
+ dav_error *err;
+ dav_lookup_result lookup;
+
+ /* If no versioning provider, or UPDATE not supported,
+ * decline the request */
+ if (vsn_hooks == NULL || vsn_hooks->update == NULL)
+ return DECLINED;
+
+ if ((depth = dav_get_depth(r, 0)) < 0) {
+ /* dav_get_depth() supplies additional information for the
+ * default message. */
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* parse the request body */
+ if ((result = ap_xml_parse_input(r, &doc)) != OK) {
+ return result;
+ }
+
+ if (doc == NULL || !dav_validate_root(doc, "update")) {
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00604)
+ "The request body does not contain "
+ "an \"update\" element.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* check for label-name or version element, but not both */
+ if ((child = dav_find_child(doc->root, "label-name")) != NULL)
+ is_label = 1;
+ else if ((child = dav_find_child(doc->root, "version")) != NULL) {
+ /* get the href element */
+ if ((child = dav_find_child(child, "href")) == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00605)
+ "The version element does not contain "
+ "an \"href\" element.");
+ return HTTP_BAD_REQUEST;
+ }
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00606)
+ "The \"update\" element does not contain "
+ "a \"label-name\" or \"version\" element.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* a depth greater than zero is only allowed for a label */
+ if (!is_label && depth != 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00607)
+ "Depth must be zero for UPDATE with a version");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* get the target value (a label or a version URI) */
+ apr_xml_to_text(r->pool, child, APR_XML_X2T_INNER, NULL, NULL,
+ &target, &tsize);
+ if (tsize == 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00608)
+ "A \"label-name\" or \"href\" element does not contain "
+ "any content.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* Ask repository module to resolve the resource */
+ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ if (!resource->exists) {
+ /* Apache will supply a default error for this. */
+ return HTTP_NOT_FOUND;
+ }
+
+ /* ### need a general mechanism for reporting precondition violations
+ * ### (should be returning XML document for 403/409 responses)
+ */
+ if (resource->type != DAV_RESOURCE_TYPE_REGULAR
+ || !resource->versioned || resource->working) {
+ return dav_error_response(r, HTTP_CONFLICT,
+ "<DAV:must-be-checked-in-version-controlled-resource>");
+ }
+
+ /* if target is a version, resolve the version resource */
+ /* ### dav_lookup_uri only allows absolute URIs; is that OK? */
+ if (!is_label) {
+ lookup = dav_lookup_uri(target, r, 0 /* must_be_absolute */);
+ if (lookup.rnew == NULL) {
+ if (lookup.err.status == HTTP_BAD_REQUEST) {
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00609)
+ "%s", lookup.err.desc);
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* ### this assumes that dav_lookup_uri() only generates a status
+ * ### that Apache can provide a status line for!! */
+
+ return dav_error_response(r, lookup.err.status, lookup.err.desc);
+ }
+ if (lookup.rnew->status != HTTP_OK) {
+ /* ### how best to report this... */
+ return dav_error_response(r, lookup.rnew->status,
+ "Version URI had an error.");
+ }
+
+ /* resolve version resource */
+ err = dav_get_resource(lookup.rnew, 0 /* label_allowed */,
+ 0 /* use_checked_in */, &version);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* NULL out target, since we're using a version resource */
+ target = NULL;
+ }
+
+ /* do the UPDATE operation */
+ err = (*vsn_hooks->update)(resource, version, target, depth, &multi_response);
+
+ if (err != NULL) {
+ err = dav_push_error(r->pool, err->status, 0,
+ apr_psprintf(r->pool,
+ "Could not UPDATE %s.",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, multi_response);
+ }
+
+ /* set the Cache-Control header, per the spec */
+ apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
+
+ /* no body */
+ ap_set_content_length(r, 0);
+
+ return DONE;
+}
+
+/* context maintained during LABEL treewalk */
+typedef struct dav_label_walker_ctx
+{
+ /* input: */
+ dav_walk_params w;
+
+ /* original request */
+ request_rec *r;
+
+ /* label being manipulated */
+ const char *label;
+
+ /* label operation */
+ int label_op;
+#define DAV_LABEL_ADD 1
+#define DAV_LABEL_SET 2
+#define DAV_LABEL_REMOVE 3
+
+ /* version provider hooks */
+ const dav_hooks_vsn *vsn_hooks;
+
+} dav_label_walker_ctx;
+
+static dav_error * dav_label_walker(dav_walk_resource *wres, int calltype)
+{
+ dav_label_walker_ctx *ctx = wres->walk_ctx;
+ dav_error *err = NULL;
+
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(ctx->r, NULL, wres->resource, NULL, &err) != DECLINED
+ && err) {
+ /* precondition failed, dropping through */
+ }
+
+ /* Check the state of the resource: must be a version or
+ * non-checkedout version selector
+ */
+ /* ### need a general mechanism for reporting precondition violations
+ * ### (should be returning XML document for 403/409 responses)
+ */
+ else if (wres->resource->type != DAV_RESOURCE_TYPE_VERSION &&
+ (wres->resource->type != DAV_RESOURCE_TYPE_REGULAR
+ || !wres->resource->versioned)) {
+ err = dav_new_error(ctx->w.pool, HTTP_CONFLICT, 0, 0,
+ "<DAV:must-be-version-or-version-selector/>");
+ }
+ else if (wres->resource->working) {
+ err = dav_new_error(ctx->w.pool, HTTP_CONFLICT, 0, 0,
+ "<DAV:must-not-be-checked-out/>");
+ }
+ else {
+ /* do the label operation */
+ if (ctx->label_op == DAV_LABEL_REMOVE)
+ err = (*ctx->vsn_hooks->remove_label)(wres->resource, ctx->label);
+ else
+ err = (*ctx->vsn_hooks->add_label)(wres->resource, ctx->label,
+ ctx->label_op == DAV_LABEL_SET);
+ }
+
+ if (err != NULL) {
+ /* ### need utility routine to add response with description? */
+ dav_add_response(wres, err->status, NULL);
+ wres->response->desc = err->desc;
+ }
+
+ return NULL;
+}
+
+static int dav_method_label(request_rec *r)
+{
+ dav_resource *resource;
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+ apr_xml_doc *doc;
+ apr_xml_elem *child;
+ int depth;
+ int result;
+ apr_size_t tsize;
+ dav_error *err;
+ dav_label_walker_ctx ctx = { { 0 } };
+ dav_response *multi_status;
+
+ /* If no versioning provider, or the provider doesn't support
+ * labels, decline the request */
+ if (vsn_hooks == NULL || vsn_hooks->add_label == NULL)
+ return DECLINED;
+
+ /* parse the request body */
+ if ((result = ap_xml_parse_input(r, &doc)) != OK) {
+ return result;
+ }
+
+ /* Ask repository module to resolve the resource */
+ err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ if (!resource->exists) {
+ /* Apache will supply a default error for this. */
+ return HTTP_NOT_FOUND;
+ }
+
+ if ((depth = dav_get_depth(r, 0)) < 0) {
+ /* dav_get_depth() supplies additional information for the
+ * default message. */
+ return HTTP_BAD_REQUEST;
+ }
+
+ if (doc == NULL || !dav_validate_root(doc, "label")) {
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00610)
+ "The request body does not contain "
+ "a \"label\" element.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* check for add, set, or remove element */
+ if ((child = dav_find_child(doc->root, "add")) != NULL) {
+ ctx.label_op = DAV_LABEL_ADD;
+ }
+ else if ((child = dav_find_child(doc->root, "set")) != NULL) {
+ ctx.label_op = DAV_LABEL_SET;
+ }
+ else if ((child = dav_find_child(doc->root, "remove")) != NULL) {
+ ctx.label_op = DAV_LABEL_REMOVE;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00611)
+ "The \"label\" element does not contain "
+ "an \"add\", \"set\", or \"remove\" element.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* get the label string */
+ if ((child = dav_find_child(child, "label-name")) == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00612)
+ "The label command element does not contain "
+ "a \"label-name\" element.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ apr_xml_to_text(r->pool, child, APR_XML_X2T_INNER, NULL, NULL,
+ &ctx.label, &tsize);
+ if (tsize == 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00613)
+ "A \"label-name\" element does not contain "
+ "a label name.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* do the label operation walk */
+ ctx.w.walk_type = DAV_WALKTYPE_NORMAL;
+ ctx.w.func = dav_label_walker;
+ ctx.w.walk_ctx = &ctx;
+ ctx.w.pool = r->pool;
+ ctx.w.root = resource;
+ ctx.r = r;
+ ctx.vsn_hooks = vsn_hooks;
+
+ err = (*resource->hooks->walk)(&ctx.w, depth, &multi_status);
+
+ if (err != NULL) {
+ /* some sort of error occurred which terminated the walk */
+ err = dav_push_error(r->pool, err->status, 0,
+ "The LABEL operation was terminated prematurely.",
+ err);
+ return dav_handle_err(r, err, multi_status);
+ }
+
+ if (multi_status != NULL) {
+ /* One or more resources had errors. If depth was zero, convert
+ * response to simple error, else make sure there is an
+ * overall error to pass to dav_handle_err()
+ */
+ if (depth == 0) {
+ err = dav_new_error(r->pool, multi_status->status, 0, 0,
+ multi_status->desc);
+ multi_status = NULL;
+ }
+ else {
+ err = dav_new_error(r->pool, HTTP_MULTI_STATUS, 0, 0,
+ "Errors occurred during the LABEL operation.");
+ }
+
+ return dav_handle_err(r, err, multi_status);
+ }
+
+ /* set the Cache-Control header, per the spec */
+ apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
+
+ /* no body */
+ ap_set_content_length(r, 0);
+
+ return DONE;
+}
+
+static int dav_core_deliver_report(request_rec *r,
+ const dav_resource *resource,
+ const apr_xml_doc *doc,
+ ap_filter_t *output, dav_error **err)
+{
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+
+ if (vsn_hooks) {
+ *err = (*vsn_hooks->deliver_report)(r, resource, doc,
+ r->output_filters);
+ return OK;
+ }
+
+ return DECLINED;
+}
+
+static void dav_core_gather_reports(
+ request_rec *r,
+ const dav_resource *resource,
+ apr_array_header_t *reports,
+ dav_error **err)
+{
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+
+ if (vsn_hooks) {
+ const dav_report_elem *rp;
+
+ (*err) = (*vsn_hooks->avail_reports)(resource, &rp);
+ while (rp && rp->name) {
+
+ dav_report_elem *report = apr_array_push(reports);
+
+ report->nmspace = rp->nmspace;
+ report->name = rp->name;
+
+ rp++;
+ }
+ }
+
+}
+
+static int dav_method_report(request_rec *r)
+{
+ dav_resource *resource;
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+ apr_xml_doc *doc;
+ dav_error *err = NULL;
+
+ int result;
+ int label_allowed;
+
+ if ((result = ap_xml_parse_input(r, &doc)) != OK) {
+ return result;
+ }
+ if (doc == NULL) {
+ /* This supplies additional information for the default msg. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00614)
+ "The request body must specify a report.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* Ask repository module to resolve the resource.
+ * First determine whether a Target-Selector header is allowed
+ * for this report.
+ */
+ label_allowed = vsn_hooks ? (*vsn_hooks->report_label_header_allowed)(doc) : 0;
+ err = dav_get_resource(r, label_allowed, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ if (!resource->exists) {
+ /* Apache will supply a default error for this. */
+ return HTTP_NOT_FOUND;
+ }
+
+ /* set up defaults for the report response */
+ r->status = HTTP_OK;
+ ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
+ err = NULL;
+
+ /* run report hook */
+ result = dav_run_deliver_report(r, resource, doc,
+ r->output_filters, &err);
+ if (err != NULL) {
+
+ if (! r->sent_bodyct) {
+ /* No data has been sent to client yet; throw normal error. */
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* If an error occurred during the report delivery, there's
+ basically nothing we can do but abort the connection and
+ log an error. This is one of the limitations of HTTP; it
+ needs to "know" the entire status of the response before
+ generating it, which is just impossible in these streamy
+ response situations. */
+ err = dav_push_error(r->pool, err->status, 0,
+ "Provider encountered an error while streaming"
+ " a REPORT response.", err);
+ dav_log_err(r, err, APLOG_ERR);
+ r->connection->aborted = 1;
+
+ return DONE;
+ }
+ switch (result) {
+ case OK:
+ return DONE;
+ case DECLINED:
+ /* No one handled the report */
+ return HTTP_NOT_IMPLEMENTED;
+ default:
+ return DONE;
+ }
+
+ return DONE;
+}
+
+static int dav_method_make_workspace(request_rec *r)
+{
+ dav_resource *resource;
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+ dav_error *err;
+ apr_xml_doc *doc;
+ int result;
+
+ /* if no versioning provider, or the provider does not support workspaces,
+ * decline the request
+ */
+ if (vsn_hooks == NULL || vsn_hooks->make_workspace == NULL)
+ return DECLINED;
+
+ /* ask repository module to resolve the resource */
+ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* parse the request body (must be a mkworkspace element) */
+ if ((result = ap_xml_parse_input(r, &doc)) != OK) {
+ return result;
+ }
+
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ if (doc == NULL
+ || !dav_validate_root(doc, "mkworkspace")) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00615)
+ "The request body does not contain "
+ "a \"mkworkspace\" element.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* Check request preconditions */
+
+ /* ### need a general mechanism for reporting precondition violations
+ * ### (should be returning XML document for 403/409 responses)
+ */
+
+ /* resource must not already exist */
+ if (resource->exists) {
+ err = dav_new_error(r->pool, HTTP_CONFLICT, 0, 0,
+ "<DAV:resource-must-be-null/>");
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* ### what about locking? */
+
+ /* attempt to create the workspace */
+ if ((err = (*vsn_hooks->make_workspace)(resource, doc)) != NULL) {
+ err = dav_push_error(r->pool, err->status, 0,
+ apr_psprintf(r->pool,
+ "Could not create workspace %s.",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* set the Cache-Control header, per the spec */
+ apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
+
+ /* return an appropriate response (HTTP_CREATED) */
+ return dav_created(r, resource->uri, "Workspace", 0 /*replaced*/);
+}
+
+static int dav_method_make_activity(request_rec *r)
+{
+ dav_resource *resource;
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+ dav_error *err;
+ int result;
+
+ /* if no versioning provider, or the provider does not support activities,
+ * decline the request
+ */
+ if (vsn_hooks == NULL || vsn_hooks->make_activity == NULL)
+ return DECLINED;
+
+ /* ask repository module to resolve the resource */
+ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* MKACTIVITY does not have a defined request body. */
+ if ((result = ap_discard_request_body(r)) != OK) {
+ return result;
+ }
+
+ /* Check request preconditions */
+
+ /* ### need a general mechanism for reporting precondition violations
+ * ### (should be returning XML document for 403/409 responses)
+ */
+
+ /* resource must not already exist */
+ if (resource->exists) {
+ err = dav_new_error(r->pool, HTTP_CONFLICT, 0, 0,
+ "<DAV:resource-must-be-null/>");
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* the provider must say whether the resource can be created as
+ an activity, i.e. whether the location is ok. */
+ if (vsn_hooks->can_be_activity != NULL
+ && !(*vsn_hooks->can_be_activity)(resource)) {
+ err = dav_new_error(r->pool, HTTP_FORBIDDEN, 0, 0,
+ "<DAV:activity-location-ok/>");
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* ### what about locking? */
+
+ /* attempt to create the activity */
+ if ((err = (*vsn_hooks->make_activity)(resource)) != NULL) {
+ err = dav_push_error(r->pool, err->status, 0,
+ apr_psprintf(r->pool,
+ "Could not create activity %s.",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* set the Cache-Control header, per the spec */
+ apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
+
+ /* return an appropriate response (HTTP_CREATED) */
+ return dav_created(r, resource->uri, "Activity", 0 /*replaced*/);
+}
+
+static int dav_method_baseline_control(request_rec *r)
+{
+ /* ### */
+ return HTTP_METHOD_NOT_ALLOWED;
+}
+
+static int dav_method_merge(request_rec *r)
+{
+ dav_resource *resource;
+ dav_resource *source_resource;
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+ dav_error *err;
+ int result;
+ apr_xml_doc *doc;
+ apr_xml_elem *source_elem;
+ apr_xml_elem *href_elem;
+ apr_xml_elem *prop_elem;
+ const char *source;
+ int no_auto_merge;
+ int no_checkout;
+ dav_lookup_result lookup;
+
+ /* If no versioning provider, decline the request */
+ if (vsn_hooks == NULL)
+ return DECLINED;
+
+ if ((result = ap_xml_parse_input(r, &doc)) != OK)
+ return result;
+
+ if (doc == NULL || !dav_validate_root(doc, "merge")) {
+ /* This supplies additional information for the default msg. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00616)
+ "The request body must be present and must be a "
+ "DAV:merge element.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ if ((source_elem = dav_find_child(doc->root, "source")) == NULL) {
+ /* This supplies additional information for the default msg. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00617)
+ "The DAV:merge element must contain a DAV:source "
+ "element.");
+ return HTTP_BAD_REQUEST;
+ }
+ if ((href_elem = dav_find_child(source_elem, "href")) == NULL) {
+ /* This supplies additional information for the default msg. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00618)
+ "The DAV:source element must contain a DAV:href "
+ "element.");
+ return HTTP_BAD_REQUEST;
+ }
+ source = dav_xml_get_cdata(href_elem, r->pool, 1 /* strip_white */);
+
+ /* get a subrequest for the source, so that we can get a dav_resource
+ for that source. */
+ lookup = dav_lookup_uri(source, r, 0 /* must_be_absolute */);
+ if (lookup.rnew == NULL) {
+ if (lookup.err.status == HTTP_BAD_REQUEST) {
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00619)
+ "%s", lookup.err.desc);
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* ### this assumes that dav_lookup_uri() only generates a status
+ * ### that Apache can provide a status line for!! */
+
+ return dav_error_response(r, lookup.err.status, lookup.err.desc);
+ }
+ if (lookup.rnew->status != HTTP_OK) {
+ /* ### how best to report this... */
+ return dav_error_response(r, lookup.rnew->status,
+ "Merge source URI had an error.");
+ }
+ err = dav_get_resource(lookup.rnew, 0 /* label_allowed */,
+ 0 /* use_checked_in */, &source_resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, source_resource, NULL, doc, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ no_auto_merge = dav_find_child(doc->root, "no-auto-merge") != NULL;
+ no_checkout = dav_find_child(doc->root, "no-checkout") != NULL;
+
+ prop_elem = dav_find_child(doc->root, "prop");
+
+ /* ### check RFC. I believe the DAV:merge element may contain any
+ ### element also allowed within DAV:checkout. need to extract them
+ ### here, and pass them along.
+ ### if so, then refactor the CHECKOUT method handling so we can reuse
+ ### the code. maybe create a structure to hold CHECKOUT parameters
+ ### which can be passed to the checkout() and merge() hooks. */
+
+ /* Ask repository module to resolve the resource */
+ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, source_resource, resource, doc, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ if (!resource->exists) {
+ /* Apache will supply a default error for this. */
+ return HTTP_NOT_FOUND;
+ }
+
+ /* ### check the source and target resources flags/types */
+
+ /* ### do lock checks, once behavior is defined */
+
+ /* set the Cache-Control header, per the spec */
+ /* ### correct? */
+ apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
+
+ /* Initialize these values for a standard MERGE response. If the MERGE
+ is going to do something different (i.e. an error), then it must
+ return a dav_error, and we'll reset these values properly. */
+ r->status = HTTP_OK;
+ ap_set_content_type(r, "text/xml");
+
+ /* ### should we do any preliminary response generation? probably not,
+ ### because we may have an error, thus demanding something else in
+ ### the response body. */
+
+ /* Do the merge, including any response generation. */
+ if ((err = (*vsn_hooks->merge)(resource, source_resource,
+ no_auto_merge, no_checkout,
+ prop_elem,
+ r->output_filters)) != NULL) {
+ /* ### is err->status the right error here? */
+ err = dav_push_error(r->pool, err->status, 0,
+ apr_psprintf(r->pool,
+ "Could not MERGE resource \"%s\" "
+ "into \"%s\".",
+ ap_escape_html(r->pool, source),
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* the response was fully generated by the merge() hook. */
+ /* ### urk. does this prevent logging? need to check... */
+ return DONE;
+}
+
+static int dav_method_bind(request_rec *r)
+{
+ dav_resource *resource;
+ dav_resource *binding;
+ dav_auto_version_info av_info;
+ const dav_hooks_binding *binding_hooks = DAV_GET_HOOKS_BINDING(r);
+ const char *dest;
+ dav_error *err;
+ dav_error *err2;
+ dav_response *multi_response = NULL;
+ dav_lookup_result lookup;
+ int overwrite;
+
+ /* If no bindings provider, decline the request */
+ if (binding_hooks == NULL)
+ return DECLINED;
+
+ /* Ask repository module to resolve the resource */
+ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+ &resource);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ if (!resource->exists) {
+ /* Apache will supply a default error for this. */
+ return HTTP_NOT_FOUND;
+ }
+
+ /* get the destination URI */
+ dest = apr_table_get(r->headers_in, "Destination");
+ if (dest == NULL) {
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00620)
+ "The request is missing a Destination header.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ lookup = dav_lookup_uri(dest, r, 0 /* must_be_absolute */);
+ if (lookup.rnew == NULL) {
+ if (lookup.err.status == HTTP_BAD_REQUEST) {
+ /* This supplies additional information for the default message. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00621)
+ "%s", lookup.err.desc);
+ return HTTP_BAD_REQUEST;
+ }
+ else if (lookup.err.status == HTTP_BAD_GATEWAY) {
+ /* ### Bindings protocol draft 02 says to return 507
+ * ### (Cross Server Binding Forbidden); Apache already defines 507
+ * ### as HTTP_INSUFFICIENT_STORAGE. So, for now, we'll return
+ * ### HTTP_FORBIDDEN
+ */
+ return dav_error_response(r, HTTP_FORBIDDEN,
+ "Cross server bindings are not "
+ "allowed by this server.");
+ }
+
+ /* ### this assumes that dav_lookup_uri() only generates a status
+ * ### that Apache can provide a status line for!! */
+
+ return dav_error_response(r, lookup.err.status, lookup.err.desc);
+ }
+ if (lookup.rnew->status != HTTP_OK) {
+ /* ### how best to report this... */
+ return dav_error_response(r, lookup.rnew->status,
+ "Destination URI had an error.");
+ }
+
+ /* resolve binding resource */
+ err = dav_get_resource(lookup.rnew, 0 /* label_allowed */,
+ 0 /* use_checked_in */, &binding);
+ if (err != NULL)
+ return dav_handle_err(r, err, NULL);
+
+ /* check for any method preconditions */
+ if (dav_run_method_precondition(r, resource, binding, NULL, &err) != DECLINED
+ && err) {
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* are the two resources handled by the same repository? */
+ if (resource->hooks != binding->hooks) {
+ /* ### this message exposes some backend config, but screw it... */
+ return dav_error_response(r, HTTP_BAD_GATEWAY,
+ "Destination URI is handled by a "
+ "different repository than the source URI. "
+ "BIND between repositories is not possible.");
+ }
+
+ /* get and parse the overwrite header value */
+ if ((overwrite = dav_get_overwrite(r)) < 0) {
+ /* dav_get_overwrite() supplies additional information for the
+ * default message. */
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* quick failure test: if dest exists and overwrite is false. */
+ if (binding->exists && !overwrite) {
+ return dav_error_response(r, HTTP_PRECONDITION_FAILED,
+ "Destination is not empty and "
+ "Overwrite is not \"T\"");
+ }
+
+ /* are the source and destination the same? */
+ if ((*resource->hooks->is_same_resource)(resource, binding)) {
+ return dav_error_response(r, HTTP_FORBIDDEN,
+ "Source and Destination URIs are the same.");
+ }
+
+ /*
+ * Check If-Headers and existing locks for destination. Note that we
+ * use depth==infinity since the target (hierarchy) will be deleted
+ * before the move/copy is completed.
+ *
+ * Note that we are overwriting the target, which implies a DELETE, so
+ * we are subject to the error/response rules as a DELETE. Namely, we
+ * will return a 424 error if any of the validations fail.
+ * (see dav_method_delete() for more information)
+ */
+ if ((err = dav_validate_request(lookup.rnew, binding, DAV_INFINITY, NULL,
+ &multi_response,
+ DAV_VALIDATE_PARENT
+ | DAV_VALIDATE_USE_424, NULL)) != NULL) {
+ err = dav_push_error(r->pool, err->status, 0,
+ apr_psprintf(r->pool,
+ "Could not BIND %s due to a "
+ "failed precondition on the "
+ "destination (e.g. locks).",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, multi_response);
+ }
+
+ /* guard against creating circular bindings */
+ if (resource->collection
+ && (*resource->hooks->is_parent_resource)(resource, binding)) {
+ return dav_error_response(r, HTTP_FORBIDDEN,
+ "Source collection contains the Destination.");
+ }
+ if (resource->collection
+ && (*resource->hooks->is_parent_resource)(binding, resource)) {
+ /* The destination must exist (since it contains the source), and
+ * a condition above implies Overwrite==T. Obviously, we cannot
+ * delete the Destination before the BIND, as that would
+ * delete the Source.
+ */
+
+ return dav_error_response(r, HTTP_FORBIDDEN,
+ "Destination collection contains the Source and "
+ "Overwrite has been specified.");
+ }
+
+ /* prepare the destination collection for modification */
+ if ((err = dav_auto_checkout(r, binding, 1 /* parent_only */,
+ &av_info)) != NULL) {
+ /* could not make destination writable */
+ return dav_handle_err(r, err, NULL);
+ }
+
+ /* If target exists, remove it first (we know Ovewrite must be TRUE).
+ * Then try to bind to the resource.
+ */
+ if (binding->exists)
+ err = (*resource->hooks->remove_resource)(binding, &multi_response);
+
+ if (err == NULL) {
+ err = (*binding_hooks->bind_resource)(resource, binding);
+ }
+
+ /* restore parent collection states */
+ err2 = dav_auto_checkin(r, NULL,
+ err != NULL /* undo if error */,
+ 0 /* unlock */, &av_info);
+
+ /* check for error from remove/bind operations */
+ if (err != NULL) {
+ err = dav_push_error(r->pool, err->status, 0,
+ apr_psprintf(r->pool,
+ "Could not BIND %s.",
+ ap_escape_html(r->pool, r->uri)),
+ err);
+ return dav_handle_err(r, err, multi_response);
+ }
+
+ /* check for errors from reverting writability */
+ if (err2 != NULL) {
+ /* just log a warning */
+ err = dav_push_error(r->pool, err2->status, 0,
+ "The BIND was successful, but there was a "
+ "problem automatically checking in the "
+ "source parent collection.",
+ err2);
+ dav_log_err(r, err, APLOG_WARNING);
+ }
+
+ /* return an appropriate response (HTTP_CREATED) */
+ /* ### spec doesn't say what happens when destination was replaced */
+ return dav_created(r, lookup.rnew->unparsed_uri, "Binding", 0);
+}
+
+
+/*
+ * Response handler for DAV resources
+ */
+static int dav_handler(request_rec *r)
+{
+ if (strcmp(r->handler, DAV_HANDLER_NAME) != 0)
+ return DECLINED;
+
+ /* Reject requests with an unescaped hash character, as these may
+ * be more destructive than the user intended. */
+ if (r->parsed_uri.fragment != NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00622)
+ "buggy client used un-escaped hash in Request-URI");
+ return dav_error_response(r, HTTP_BAD_REQUEST,
+ "The request was invalid: the URI included "
+ "an un-escaped hash character");
+ }
+
+ /* ### do we need to do anything with r->proxyreq ?? */
+
+ /*
+ * ### anything else to do here? could another module and/or
+ * ### config option "take over" the handler here? i.e. how do
+ * ### we lock down this hierarchy so that we are the ultimate
+ * ### arbiter? (or do we simply depend on the administrator
+ * ### to avoid conflicting configurations?)
+ */
+
+ /*
+ * Set up the methods mask, since that's one of the reasons this handler
+ * gets called, and lower-level things may need the info.
+ *
+ * First, set the mask to the methods we handle directly. Since by
+ * definition we own our managed space, we unconditionally set
+ * the r->allowed field rather than ORing our values with anything
+ * any other module may have put in there.
+ *
+ * These are the HTTP-defined methods that we handle directly.
+ */
+ r->allowed = 0
+ | (AP_METHOD_BIT << M_GET)
+ | (AP_METHOD_BIT << M_PUT)
+ | (AP_METHOD_BIT << M_DELETE)
+ | (AP_METHOD_BIT << M_OPTIONS)
+ | (AP_METHOD_BIT << M_INVALID);
+
+ /*
+ * These are the DAV methods we handle.
+ */
+ r->allowed |= 0
+ | (AP_METHOD_BIT << M_COPY)
+ | (AP_METHOD_BIT << M_LOCK)
+ | (AP_METHOD_BIT << M_UNLOCK)
+ | (AP_METHOD_BIT << M_MKCOL)
+ | (AP_METHOD_BIT << M_MOVE)
+ | (AP_METHOD_BIT << M_PROPFIND)
+ | (AP_METHOD_BIT << M_PROPPATCH);
+
+ /*
+ * These are methods that we don't handle directly, but let the
+ * server's default handler do for us as our agent.
+ */
+ r->allowed |= 0
+ | (AP_METHOD_BIT << M_POST);
+
+ /* ### hrm. if we return HTTP_METHOD_NOT_ALLOWED, then an Allow header
+ * ### is sent; it will need the other allowed states; since the default
+ * ### handler is not called on error, then it doesn't add the other
+ * ### allowed states, so we must
+ */
+
+ /* ### we might need to refine this for just where we return the error.
+ * ### also, there is the issue with other methods (see ISSUES)
+ */
+
+ /* dispatch the appropriate method handler */
+ if (r->method_number == M_GET) {
+ return dav_method_get(r);
+ }
+
+ if (r->method_number == M_PUT) {
+ return dav_method_put(r);
+ }
+
+ if (r->method_number == M_POST) {
+ return dav_method_post(r);
+ }
+
+ if (r->method_number == M_DELETE) {
+ return dav_method_delete(r);
+ }
+
+ if (r->method_number == M_OPTIONS) {
+ return dav_method_options(r);
+ }
+
+ if (r->method_number == M_PROPFIND) {
+ return dav_method_propfind(r);
+ }
+
+ if (r->method_number == M_PROPPATCH) {
+ return dav_method_proppatch(r);
+ }
+
+ if (r->method_number == M_MKCOL) {
+ return dav_method_mkcol(r);
+ }
+
+ if (r->method_number == M_COPY) {
+ return dav_method_copymove(r, DAV_DO_COPY);
+ }
+
+ if (r->method_number == M_MOVE) {
+ return dav_method_copymove(r, DAV_DO_MOVE);
+ }
+
+ if (r->method_number == M_LOCK) {
+ return dav_method_lock(r);
+ }
+
+ if (r->method_number == M_UNLOCK) {
+ return dav_method_unlock(r);
+ }
+
+ if (r->method_number == M_VERSION_CONTROL) {
+ return dav_method_vsn_control(r);
+ }
+
+ if (r->method_number == M_CHECKOUT) {
+ return dav_method_checkout(r);
+ }
+
+ if (r->method_number == M_UNCHECKOUT) {
+ return dav_method_uncheckout(r);
+ }
+
+ if (r->method_number == M_CHECKIN) {
+ return dav_method_checkin(r);
+ }
+
+ if (r->method_number == M_UPDATE) {
+ return dav_method_update(r);
+ }
+
+ if (r->method_number == M_LABEL) {
+ return dav_method_label(r);
+ }
+
+ if (r->method_number == M_REPORT) {
+ return dav_method_report(r);
+ }
+
+ if (r->method_number == M_MKWORKSPACE) {
+ return dav_method_make_workspace(r);
+ }
+
+ if (r->method_number == M_MKACTIVITY) {
+ return dav_method_make_activity(r);
+ }
+
+ if (r->method_number == M_BASELINE_CONTROL) {
+ return dav_method_baseline_control(r);
+ }
+
+ if (r->method_number == M_MERGE) {
+ return dav_method_merge(r);
+ }
+
+ /* BIND method */
+ if (r->method_number == dav_methods[DAV_M_BIND]) {
+ return dav_method_bind(r);
+ }
+
+ /* DASL method */
+ if (r->method_number == dav_methods[DAV_M_SEARCH]) {
+ return dav_method_search(r);
+ }
+
+ /* ### add'l methods for Advanced Collections, ACLs */
+
+ return DECLINED;
+}
+
+static int dav_fixups(request_rec *r)
+{
+ dav_dir_conf *conf;
+
+ /* quickly ignore any HTTP/0.9 requests which aren't subreqs. */
+ if (r->assbackwards && !r->main) {
+ return DECLINED;
+ }
+
+ conf = (dav_dir_conf *)ap_get_module_config(r->per_dir_config,
+ &dav_module);
+
+ /* if DAV is not enabled, then we've got nothing to do */
+ if (conf->provider == NULL) {
+ return DECLINED;
+ }
+
+ /* We are going to handle almost every request. In certain cases,
+ the provider maps to the filesystem (thus, handle_get is
+ FALSE), and core Apache will handle it. a For that case, we
+ just return right away. */
+ if (r->method_number == M_GET) {
+ /*
+ * ### need some work to pull Content-Type and Content-Language
+ * ### from the property database.
+ */
+
+ /*
+ * If the repository hasn't indicated that it will handle the
+ * GET method, then just punt.
+ *
+ * ### this isn't quite right... taking over the response can break
+ * ### things like mod_negotiation. need to look into this some more.
+ */
+ if (!conf->provider->repos->handle_get) {
+ return DECLINED;
+ }
+ }
+
+ /* ### this is wrong. We should only be setting the r->handler for the
+ * requests that mod_dav knows about. If we set the handler for M_POST
+ * requests, then CGI scripts that use POST will return the source for the
+ * script. However, mod_dav DOES handle POST, so something else needs
+ * to be fixed.
+ */
+ if (r->method_number != M_POST) {
+
+ /* We are going to be handling the response for this resource. */
+ r->handler = DAV_HANDLER_NAME;
+ return OK;
+ }
+
+ return DECLINED;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_hook_handler(dav_handler, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_post_config(dav_init_handler, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_fixups(dav_fixups, NULL, NULL, APR_HOOK_MIDDLE);
+
+ dav_hook_find_liveprop(dav_core_find_liveprop, NULL, NULL, APR_HOOK_LAST);
+ dav_hook_insert_all_liveprops(dav_core_insert_all_liveprops,
+ NULL, NULL, APR_HOOK_MIDDLE);
+
+ dav_hook_deliver_report(dav_core_deliver_report,
+ NULL, NULL, APR_HOOK_LAST);
+ dav_hook_gather_reports(dav_core_gather_reports,
+ NULL, NULL, APR_HOOK_LAST);
+
+ dav_core_register_uris(p);
+}
+
+/*---------------------------------------------------------------------------
+ *
+ * Configuration info for the module
+ */
+
+static const command_rec dav_cmds[] =
+{
+ /* per directory/location */
+ AP_INIT_TAKE1("DAV", dav_cmd_dav, NULL, ACCESS_CONF,
+ "specify the DAV provider for a directory or location"),
+
+ /* per directory/location, or per server */
+ AP_INIT_TAKE1("DAVMinTimeout", dav_cmd_davmintimeout, NULL,
+ ACCESS_CONF|RSRC_CONF,
+ "specify minimum allowed timeout"),
+
+ /* per directory/location, or per server */
+ AP_INIT_FLAG("DAVDepthInfinity", dav_cmd_davdepthinfinity, NULL,
+ ACCESS_CONF|RSRC_CONF,
+ "allow Depth infinity PROPFIND requests"),
+
+ /* per directory/location, or per server */
+ AP_INIT_FLAG("DAVLockDiscovery", dav_cmd_davlockdiscovery, NULL,
+ ACCESS_CONF|RSRC_CONF,
+ "allow lock discovery by PROPFIND requests"),
+
+ { NULL }
+};
+
+module DAV_DECLARE_DATA dav_module =
+{
+ STANDARD20_MODULE_STUFF,
+ dav_create_dir_config, /* dir config creater */
+ dav_merge_dir_config, /* dir merger --- default is to override */
+ dav_create_server_config, /* server config */
+ dav_merge_server_config, /* merge server config */
+ dav_cmds, /* command table */
+ register_hooks, /* register hooks */
+};
+
+APR_HOOK_STRUCT(
+ APR_HOOK_LINK(gather_propsets)
+ APR_HOOK_LINK(find_liveprop)
+ APR_HOOK_LINK(insert_all_liveprops)
+ APR_HOOK_LINK(deliver_report)
+ APR_HOOK_LINK(gather_reports)
+ APR_HOOK_LINK(method_precondition)
+ )
+
+APR_IMPLEMENT_EXTERNAL_HOOK_VOID(dav, DAV, gather_propsets,
+ (apr_array_header_t *uris),
+ (uris))
+
+APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(dav, DAV, int, find_liveprop,
+ (const dav_resource *resource,
+ const char *ns_uri, const char *name,
+ const dav_hooks_liveprop **hooks),
+ (resource, ns_uri, name, hooks), 0)
+
+APR_IMPLEMENT_EXTERNAL_HOOK_VOID(dav, DAV, insert_all_liveprops,
+ (request_rec *r, const dav_resource *resource,
+ dav_prop_insert what, apr_text_header *phdr),
+ (r, resource, what, phdr))
+
+APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(dav, DAV, int, deliver_report,
+ (request_rec *r,
+ const dav_resource *resource,
+ const apr_xml_doc *doc,
+ ap_filter_t *output, dav_error **err),
+ (r, resource, doc, output, err), DECLINED)
+
+APR_IMPLEMENT_EXTERNAL_HOOK_VOID(dav, DAV, gather_reports,
+ (request_rec *r, const dav_resource *resource,
+ apr_array_header_t *reports, dav_error **err),
+ (r, resource, reports, err))
+
+APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(dav, DAV, int, method_precondition,
+ (request_rec *r,
+ dav_resource *src, const dav_resource *dest,
+ const apr_xml_doc *doc,
+ dav_error **err),
+ (r, src, dest, doc, err), DECLINED)
diff --git a/modules/dav/main/mod_dav.dep b/modules/dav/main/mod_dav.dep
new file mode 100644
index 0000000..5efe2f4
--- /dev/null
+++ b/modules/dav/main/mod_dav.dep
@@ -0,0 +1,354 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_dav.mak
+
+.\liveprop.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_xml.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apr_xml.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_dav.h"\
+
+
+.\mod_dav.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_expr.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_provider.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\http_config.h"\
+ "..\..\..\include\http_core.h"\
+ "..\..\..\include\http_log.h"\
+ "..\..\..\include\http_main.h"\
+ "..\..\..\include\http_protocol.h"\
+ "..\..\..\include\http_request.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\util_cfgtree.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_script.h"\
+ "..\..\..\include\util_xml.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apr_xml.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_dav.h"\
+
+
+.\props.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\http_config.h"\
+ "..\..\..\include\http_log.h"\
+ "..\..\..\include\http_request.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\util_cfgtree.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_xml.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apr_xml.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_dav.h"\
+
+
+.\providers.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_provider.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_xml.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apr_xml.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_dav.h"\
+
+
+.\std_liveprop.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_provider.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_xml.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apr_xml.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_dav.h"\
+
+
+.\util.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\http_config.h"\
+ "..\..\..\include\http_log.h"\
+ "..\..\..\include\http_protocol.h"\
+ "..\..\..\include\http_request.h"\
+ "..\..\..\include\http_vhost.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\util_cfgtree.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_xml.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apr_xml.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_dav.h"\
+
+
+.\util_lock.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_expr.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\http_config.h"\
+ "..\..\..\include\http_core.h"\
+ "..\..\..\include\http_log.h"\
+ "..\..\..\include\http_protocol.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\util_cfgtree.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_xml.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apr_xml.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_dav.h"\
+
+
+..\..\..\build\win32\httpd.rc : \
+ "..\..\..\include\ap_release.h"\
+
diff --git a/modules/dav/main/mod_dav.dsp b/modules/dav/main/mod_dav.dsp
new file mode 100644
index 0000000..752cea8
--- /dev/null
+++ b/modules/dav/main/mod_dav.dsp
@@ -0,0 +1,147 @@
+# Microsoft Developer Studio Project File - Name="mod_dav" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_dav - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_dav.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_dav.mak" CFG="mod_dav - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_dav - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_dav - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_dav - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "DAV_DECLARE_EXPORT" /Fd"Release\mod_dav_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_dav.res" /i "../../../include" /i "../../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_dav.so" /d LONG_NAME="dav_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:".\Release\mod_dav.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_dav.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_dav.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_dav - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "DAV_DECLARE_EXPORT" /Fd"Debug\mod_dav_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_dav.res" /i "../../../include" /i "../../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_dav.so" /d LONG_NAME="dav_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_dav.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_dav.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_dav.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_dav - Win32 Release"
+# Name "mod_dav - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\liveprop.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\mod_dav.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\props.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\providers.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\std_liveprop.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\util.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\util_lock.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd"
+# Begin Source File
+
+SOURCE=.\mod_dav.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/dav/main/mod_dav.h b/modules/dav/main/mod_dav.h
new file mode 100644
index 0000000..eca34a2
--- /dev/null
+++ b/modules/dav/main/mod_dav.h
@@ -0,0 +1,2670 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file mod_dav.h
+ * @brief DAV extension module for Apache 2.0.*
+ *
+ * @defgroup MOD_DAV mod_dav
+ * @ingroup APACHE_MODS
+ * @{
+ */
+
+#ifndef _MOD_DAV_H_
+#define _MOD_DAV_H_
+
+#include "apr_hooks.h"
+#include "apr_hash.h"
+#include "apr_dbm.h"
+#include "apr_tables.h"
+
+#include "httpd.h"
+#include "util_filter.h"
+#include "util_xml.h"
+
+#include <limits.h> /* for INT_MAX */
+#include <time.h> /* for time_t */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#define DAV_VERSION AP_SERVER_BASEREVISION
+
+#define DAV_XML_HEADER "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+#define DAV_XML_CONTENT_TYPE "text/xml; charset=\"utf-8\""
+
+#define DAV_READ_BLOCKSIZE 2048 /* used for reading input blocks */
+
+#define DAV_RESPONSE_BODY_1 "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n<html>\n<head>\n<title>"
+#define DAV_RESPONSE_BODY_2 "</title>\n</head><body>\n<h1>"
+#define DAV_RESPONSE_BODY_3 "</h1>\n<p>"
+#define DAV_RESPONSE_BODY_4 "</p>\n"
+#define DAV_RESPONSE_BODY_5 "</body></html>\n"
+
+#define DAV_DO_COPY 0
+#define DAV_DO_MOVE 1
+
+
+#if 1
+#define DAV_DEBUG 1
+#define DEBUG_CR "\n"
+#define DBG0(f) ap_log_error(APLOG_MARK, \
+ APLOG_ERR, 0, NULL, (f))
+#define DBG1(f,a1) ap_log_error(APLOG_MARK, \
+ APLOG_ERR, 0, NULL, f, a1)
+#define DBG2(f,a1,a2) ap_log_error(APLOG_MARK, \
+ APLOG_ERR, 0, NULL, f, a1, a2)
+#define DBG3(f,a1,a2,a3) ap_log_error(APLOG_MARK, \
+ APLOG_ERR, 0, NULL, f, a1, a2, a3)
+#else
+#undef DAV_DEBUG
+#define DEBUG_CR ""
+#endif
+
+#define DAV_INFINITY INT_MAX /* for the Depth: header */
+
+/* Create a set of DAV_DECLARE(type), DAV_DECLARE_NONSTD(type) and
+ * DAV_DECLARE_DATA with appropriate export and import tags for the platform
+ */
+#if !defined(WIN32)
+#define DAV_DECLARE(type) type
+#define DAV_DECLARE_NONSTD(type) type
+#define DAV_DECLARE_DATA
+#elif defined(DAV_DECLARE_STATIC)
+#define DAV_DECLARE(type) type __stdcall
+#define DAV_DECLARE_NONSTD(type) type
+#define DAV_DECLARE_DATA
+#elif defined(DAV_DECLARE_EXPORT)
+#define DAV_DECLARE(type) __declspec(dllexport) type __stdcall
+#define DAV_DECLARE_NONSTD(type) __declspec(dllexport) type
+#define DAV_DECLARE_DATA __declspec(dllexport)
+#else
+#define DAV_DECLARE(type) __declspec(dllimport) type __stdcall
+#define DAV_DECLARE_NONSTD(type) __declspec(dllimport) type
+#define DAV_DECLARE_DATA __declspec(dllimport)
+#endif
+
+/* --------------------------------------------------------------------
+**
+** ERROR MANAGEMENT
+*/
+
+/*
+** dav_error structure.
+**
+** In most cases, mod_dav uses a pointer to a dav_error structure. If the
+** pointer is NULL, then no error has occurred.
+**
+** In certain cases, a dav_error structure is directly used. In these cases,
+** a status value of 0 means that an error has not occurred.
+**
+** Note: this implies that status != 0 whenever an error occurs.
+**
+** The desc field is optional (it may be NULL). When NULL, it typically
+** implies that Apache has a proper description for the specified status.
+*/
+typedef struct dav_error {
+ int status; /* suggested HTTP status (0 for no error) */
+ int error_id; /* DAV-specific error ID */
+ const char *desc; /* DAV:responsedescription and error log */
+
+ apr_status_t aprerr; /* APR error if any, or 0/APR_SUCCESS */
+
+ const char *namespace; /* [optional] namespace of error */
+ const char *tagname; /* name of error-tag */
+
+ struct dav_error *prev; /* previous error (in stack) */
+
+ const char *childtags; /* error-tag may have children */
+
+} dav_error;
+
+/*
+** Create a new error structure. save_errno will be filled with the current
+** errno value.
+*/
+DAV_DECLARE(dav_error*) dav_new_error(apr_pool_t *p, int status,
+ int error_id, apr_status_t aprerr,
+ const char *desc);
+
+
+/*
+** Create a new error structure with tagname and (optional) namespace;
+** namespace may be NULL, which means "DAV:".
+*/
+DAV_DECLARE(dav_error*) dav_new_error_tag(apr_pool_t *p, int status,
+ int error_id, apr_status_t aprerr,
+ const char *desc,
+ const char *namespace,
+ const char *tagname);
+
+
+/*
+** Push a new error description onto the stack of errors.
+**
+** This function is used to provide an additional description to an existing
+** error.
+**
+** <status> should contain the caller's view of what the current status is,
+** given the underlying error. If it doesn't have a better idea, then the
+** caller should pass prev->status.
+**
+** <error_id> can specify a new error_id since the topmost description has
+** changed.
+*/
+DAV_DECLARE(dav_error*) dav_push_error(apr_pool_t *p, int status, int error_id,
+ const char *desc, dav_error *prev);
+
+
+/*
+** Join two errors together.
+**
+** This function is used to add a new error stack onto an existing error so
+** that subsequent errors can be reported after the first error. It returns
+** the correct error stack to use so that the caller can blindly call it
+** without checking that both dest and src are not NULL.
+**
+** <dest> is the error stack that the error will be added to.
+**
+** <src> is the error stack that will be appended.
+*/
+DAV_DECLARE(dav_error*) dav_join_error(dav_error* dest, dav_error* src);
+
+typedef struct dav_response dav_response;
+
+/*
+** dav_handle_err()
+**
+** Handle the standard error processing. <err> must be non-NULL.
+**
+** <response> is set by the following:
+** - dav_validate_request()
+** - dav_add_lock()
+** - repos_hooks->remove_resource
+** - repos_hooks->move_resource
+** - repos_hooks->copy_resource
+** - vsn_hooks->update
+*/
+DAV_DECLARE(int) dav_handle_err(request_rec *r, dav_error *err,
+ dav_response *response);
+
+/* error ID values... */
+
+/* IF: header errors */
+#define DAV_ERR_IF_PARSE 100 /* general parsing error */
+#define DAV_ERR_IF_MULTIPLE_NOT 101 /* multiple "Not" found */
+#define DAV_ERR_IF_UNK_CHAR 102 /* unknown char in header */
+#define DAV_ERR_IF_ABSENT 103 /* no locktokens given */
+#define DAV_ERR_IF_TAGGED 104 /* in parsing tagged-list */
+#define DAV_ERR_IF_UNCLOSED_PAREN 105 /* in no-tagged-list */
+
+/* Prop DB errors */
+#define DAV_ERR_PROP_BAD_MAJOR 200 /* major version was wrong */
+#define DAV_ERR_PROP_READONLY 201 /* prop is read-only */
+#define DAV_ERR_PROP_NO_DATABASE 202 /* writable db not avail */
+#define DAV_ERR_PROP_NOT_FOUND 203 /* prop not found */
+#define DAV_ERR_PROP_BAD_LOCKDB 204 /* could not open lockdb */
+#define DAV_ERR_PROP_OPENING 205 /* problem opening propdb */
+#define DAV_ERR_PROP_EXEC 206 /* problem exec'ing patch */
+
+/* Predefined DB errors */
+/* ### any to define?? */
+
+/* Predefined locking system errors */
+#define DAV_ERR_LOCK_OPENDB 400 /* could not open lockdb */
+#define DAV_ERR_LOCK_NO_DB 401 /* no database defined */
+#define DAV_ERR_LOCK_CORRUPT_DB 402 /* DB is corrupt */
+#define DAV_ERR_LOCK_UNK_STATE_TOKEN 403 /* unknown State-token */
+#define DAV_ERR_LOCK_PARSE_TOKEN 404 /* bad opaquelocktoken */
+#define DAV_ERR_LOCK_SAVE_LOCK 405 /* err saving locks */
+
+/*
+** Some comments on Error ID values:
+**
+** The numbers do not necessarily need to be unique. Uniqueness simply means
+** that two errors that have not been predefined above can be distinguished
+** from each other. At the moment, mod_dav does not use this distinguishing
+** feature, but it could be used in the future to collapse <response> elements
+** into groups based on the error ID (and associated responsedescription).
+**
+** If a compute_desc is provided, then the error ID should be unique within
+** the context of the compute_desc function (so the function can figure out
+** what to filled into the desc).
+**
+** Basically, subsystems can ignore defining new error ID values if they want
+** to. The subsystems *do* need to return the predefined errors when
+** appropriate, so that mod_dav can figure out what to do. Subsystems can
+** simply leave the error ID field unfilled (zero) if there isn't an error
+** that must be placed there.
+*/
+
+
+/* --------------------------------------------------------------------
+**
+** HOOK STRUCTURES
+**
+** These are here for forward-declaration purposes. For more info, see
+** the section title "HOOK HANDLING" for more information, plus each
+** structure definition.
+*/
+
+/* forward-declare this structure */
+typedef struct dav_hooks_propdb dav_hooks_propdb;
+typedef struct dav_hooks_locks dav_hooks_locks;
+typedef struct dav_hooks_vsn dav_hooks_vsn;
+typedef struct dav_hooks_repository dav_hooks_repository;
+typedef struct dav_hooks_liveprop dav_hooks_liveprop;
+typedef struct dav_hooks_binding dav_hooks_binding;
+typedef struct dav_hooks_search dav_hooks_search;
+
+/* ### deprecated name */
+typedef dav_hooks_propdb dav_hooks_db;
+
+
+/* --------------------------------------------------------------------
+**
+** RESOURCE HANDLING
+*/
+
+/*
+** Resource Types:
+** The base protocol defines only file and collection resources.
+** The versioning protocol defines several additional resource types
+** to represent artifacts of a version control system.
+**
+** This enumeration identifies the type of URL used to identify the
+** resource. Since the same resource may have more than one type of
+** URL which can identify it, dav_resource_type cannot be used
+** alone to determine the type of the resource; attributes of the
+** dav_resource object must also be consulted.
+*/
+typedef enum {
+ DAV_RESOURCE_TYPE_UNKNOWN,
+
+ DAV_RESOURCE_TYPE_REGULAR, /* file or collection; could be
+ * unversioned, or version selector,
+ * or baseline selector */
+
+ DAV_RESOURCE_TYPE_VERSION, /* version or baseline URL */
+
+ DAV_RESOURCE_TYPE_HISTORY, /* version or baseline history URL */
+
+ DAV_RESOURCE_TYPE_WORKING, /* working resource URL */
+
+ DAV_RESOURCE_TYPE_WORKSPACE, /* workspace URL */
+
+ DAV_RESOURCE_TYPE_ACTIVITY, /* activity URL */
+
+ DAV_RESOURCE_TYPE_PRIVATE /* repository-private type */
+
+} dav_resource_type;
+
+/*
+** Opaque, repository-specific information for a resource.
+*/
+typedef struct dav_resource_private dav_resource_private;
+
+/*
+** Resource descriptor, generated by a repository provider.
+**
+** Note: the lock-null state is not explicitly represented here,
+** since it may be expensive to compute. Use dav_get_resource_state()
+** to determine whether a non-existent resource is a lock-null resource.
+**
+** A quick explanation of how the flags can apply to different resources:
+**
+** unversioned file or collection:
+** type = DAV_RESOURCE_TYPE_REGULAR
+** exists = ? (1 if exists)
+** collection = ? (1 if collection)
+** versioned = 0
+** baselined = 0
+** working = 0
+**
+** version-controlled resource or configuration:
+** type = DAV_RESOURCE_TYPE_REGULAR
+** exists = 1
+** collection = ? (1 if collection)
+** versioned = 1
+** baselined = ? (1 if configuration)
+** working = ? (1 if checked out)
+**
+** version/baseline history:
+** type = DAV_RESOURCE_TYPE_HISTORY
+** exists = 1
+** collection = 0
+** versioned = 0
+** baselined = 0
+** working = 0
+**
+** version/baseline:
+** type = DAV_RESOURCE_TYPE_VERSION
+** exists = 1
+** collection = ? (1 if collection)
+** versioned = 1
+** baselined = ? (1 if baseline)
+** working = 0
+**
+** working resource:
+** type = DAV_RESOURCE_TYPE_WORKING
+** exists = 1
+** collection = ? (1 if collection)
+** versioned = 1
+** baselined = 0
+** working = 1
+**
+** workspace:
+** type = DAV_RESOURCE_TYPE_WORKSPACE
+** exists = ? (1 if exists)
+** collection = 1
+** versioned = ? (1 if version-controlled)
+** baselined = ? (1 if baseline-controlled)
+** working = ? (1 if checked out)
+**
+** activity:
+** type = DAV_RESOURCE_TYPE_ACTIVITY
+** exists = ? (1 if exists)
+** collection = 0
+** versioned = 0
+** baselined = 0
+** working = 0
+*/
+typedef struct dav_resource {
+ dav_resource_type type;
+
+ int exists; /* 0 => null resource */
+
+ int collection; /* 0 => file; can be 1 for
+ * REGULAR, VERSION, and WORKING resources,
+ * and is always 1 for WORKSPACE */
+
+ int versioned; /* 0 => unversioned; can be 1 for
+ * REGULAR and WORKSPACE resources,
+ * and is always 1 for VERSION and WORKING */
+
+ int baselined; /* 0 => not baselined; can be 1 for
+ * REGULAR, VERSION, and WORKSPACE resources;
+ * versioned == 1 when baselined == 1 */
+
+ int working; /* 0 => not checked out; can be 1 for
+ * REGULAR and WORKSPACE resources,
+ * and is always 1 for WORKING */
+
+ const char *uri; /* the URI for this resource;
+ * currently has an ABI flaw where sometimes it is
+ * assumed to be encoded and sometimes not */
+
+ dav_resource_private *info; /* the provider's private info */
+
+ const dav_hooks_repository *hooks; /* hooks used for this resource */
+
+ /* When allocating items related specifically to this resource, the
+ following pool should be used. Its lifetime will be at least as
+ long as the dav_resource structure. */
+ apr_pool_t *pool;
+
+} dav_resource;
+
+/*
+** Lock token type. Lock providers define the details of a lock token.
+** However, all providers are expected to at least be able to parse
+** the "opaquelocktoken" scheme, which is represented by a uuid_t.
+*/
+typedef struct dav_locktoken dav_locktoken;
+
+DAV_DECLARE(dav_error *) dav_get_resource(request_rec *r, int label_allowed,
+ int use_checked_in, dav_resource **res_p);
+
+
+/* --------------------------------------------------------------------
+**
+** BUFFER HANDLING
+**
+** These buffers are used as a lightweight buffer reuse mechanism. Apache
+** provides sub-pool creation and destruction to much the same effect, but
+** the sub-pools are a bit more general and heavyweight than these buffers.
+*/
+
+/* buffer for reuse; can grow to accommodate needed size */
+typedef struct
+{
+ apr_size_t alloc_len; /* how much has been allocated */
+ apr_size_t cur_len; /* how much is currently being used */
+ char *buf; /* buffer contents */
+} dav_buffer;
+#define DAV_BUFFER_MINSIZE 256 /* minimum size for buffer */
+#define DAV_BUFFER_PAD 64 /* amount of pad when growing */
+
+/* set the cur_len to the given size and ensure space is available */
+DAV_DECLARE(void) dav_set_bufsize(apr_pool_t *p, dav_buffer *pbuf,
+ apr_size_t size);
+
+/* initialize a buffer and copy the specified (null-term'd) string into it */
+DAV_DECLARE(void) dav_buffer_init(apr_pool_t *p, dav_buffer *pbuf,
+ const char *str);
+
+/* check that the buffer can accommodate <extra_needed> more bytes */
+DAV_DECLARE(void) dav_check_bufsize(apr_pool_t *p, dav_buffer *pbuf,
+ apr_size_t extra_needed);
+
+/* append a string to the end of the buffer, adjust length */
+DAV_DECLARE(void) dav_buffer_append(apr_pool_t *p, dav_buffer *pbuf,
+ const char *str);
+
+/* place a string on the end of the buffer, do NOT adjust length */
+DAV_DECLARE(void) dav_buffer_place(apr_pool_t *p, dav_buffer *pbuf,
+ const char *str);
+
+/* place some memory on the end of a buffer; do NOT adjust length */
+DAV_DECLARE(void) dav_buffer_place_mem(apr_pool_t *p, dav_buffer *pbuf,
+ const void *mem, apr_size_t amt,
+ apr_size_t pad);
+
+
+/* --------------------------------------------------------------------
+**
+** HANDY UTILITIES
+*/
+
+/* contains results from one of the getprop functions */
+typedef struct
+{
+ apr_text * propstats; /* <propstat> element text */
+ apr_text * xmlns; /* namespace decls for <response> elem */
+} dav_get_props_result;
+
+/* holds the contents of a <response> element */
+struct dav_response
+{
+ const char *href; /* always */
+ const char *desc; /* optional description at <response> level */
+
+ /* use status if propresult.propstats is NULL. */
+ dav_get_props_result propresult;
+
+ int status;
+
+ struct dav_response *next;
+};
+
+typedef struct
+{
+ request_rec *rnew; /* new subrequest */
+ dav_error err; /* potential error response */
+} dav_lookup_result;
+
+
+DAV_DECLARE(dav_lookup_result) dav_lookup_uri(const char *uri, request_rec *r,
+ int must_be_absolute);
+
+/* defines type of property info a provider is to return */
+typedef enum {
+ DAV_PROP_INSERT_NOTDEF, /* property is defined by this provider,
+ but nothing was inserted because the
+ (live) property is not defined for this
+ resource (it may be present as a dead
+ property). */
+ DAV_PROP_INSERT_NOTSUPP, /* property is recognized by this provider,
+ but it is not supported, and cannot be
+ treated as a dead property */
+ DAV_PROP_INSERT_NAME, /* a property name (empty elem) was
+ inserted into the text block */
+ DAV_PROP_INSERT_VALUE, /* a property name/value pair was inserted
+ into the text block */
+ DAV_PROP_INSERT_SUPPORTED /* a supported live property was added to
+ the text block as a
+ <DAV:supported-live-property> element */
+} dav_prop_insert;
+
+/* ### this stuff is private to dav/fs/repos.c; move it... */
+/* format a time string (buf must be at least DAV_TIMEBUF_SIZE chars) */
+#define DAV_STYLE_ISO8601 1
+#define DAV_STYLE_RFC822 2
+#define DAV_TIMEBUF_SIZE 30
+
+/* Write a complete RESPONSE object out as a <DAV:response> xml
+ * element. Data is sent into brigade BB, which is auto-flushed into
+ * the output filter stack for request R. Use POOL for any temporary
+ * allocations.
+ *
+ * [Presumably the <multistatus> tag has already been written; this
+ * routine is shared by dav_send_multistatus and dav_stream_response.]
+ */
+DAV_DECLARE(void) dav_send_one_response(dav_response *response,
+ apr_bucket_brigade *bb,
+ request_rec *r,
+ apr_pool_t *pool);
+
+/* Factorized helper function: prep request_rec R for a multistatus
+ * response and write <multistatus> tag into BB, destined for
+ * R->output_filters. Use xml NAMESPACES in initial tag, if
+ * non-NULL.
+ */
+DAV_DECLARE(void) dav_begin_multistatus(apr_bucket_brigade *bb,
+ request_rec *r, int status,
+ apr_array_header_t *namespaces);
+
+/* Finish a multistatus response started by dav_begin_multistatus: */
+DAV_DECLARE(apr_status_t) dav_finish_multistatus(request_rec *r,
+ apr_bucket_brigade *bb);
+
+/* Send a multistatus response */
+DAV_DECLARE(void) dav_send_multistatus(request_rec *r, int status,
+ dav_response *first,
+ apr_array_header_t *namespaces);
+
+DAV_DECLARE(apr_text *) dav_failed_proppatch(apr_pool_t *p,
+ apr_array_header_t *prop_ctx);
+DAV_DECLARE(apr_text *) dav_success_proppatch(apr_pool_t *p,
+ apr_array_header_t *prop_ctx);
+
+DAV_DECLARE(int) dav_get_depth(request_rec *r, int def_depth);
+
+DAV_DECLARE(int) dav_validate_root(const apr_xml_doc *doc,
+ const char *tagname);
+DAV_DECLARE(int) dav_validate_root_ns(const apr_xml_doc *doc,
+ int ns, const char *tagname);
+DAV_DECLARE(apr_xml_elem *) dav_find_child(const apr_xml_elem *elem,
+ const char *tagname);
+DAV_DECLARE(apr_xml_elem *) dav_find_child_ns(const apr_xml_elem *elem,
+ int ns, const char *tagname);
+DAV_DECLARE(apr_xml_elem *) dav_find_next_ns(const apr_xml_elem *elem,
+ int ns, const char *tagname);
+
+/* find and return the attribute with a name in the given namespace */
+DAV_DECLARE(apr_xml_attr *) dav_find_attr_ns(const apr_xml_elem *elem,
+ int ns, const char *attrname);
+
+/* find and return the attribute with a given DAV: tagname */
+DAV_DECLARE(apr_xml_attr *) dav_find_attr(const apr_xml_elem *elem,
+ const char *attrname);
+
+/* gather up all the CDATA into a single string */
+DAV_DECLARE(const char *) dav_xml_get_cdata(const apr_xml_elem *elem, apr_pool_t *pool,
+ int strip_white);
+
+/*
+** XML namespace handling
+**
+** This structure tracks namespace declarations (xmlns:prefix="URI").
+** It maintains a one-to-many relationship of URIs-to-prefixes. In other
+** words, one URI may be defined by many prefixes, but any specific
+** prefix will specify only one URI.
+**
+** Prefixes using the "g###" pattern can be generated automatically if
+** the caller does not have specific prefix requirements.
+*/
+typedef struct {
+ apr_pool_t *pool;
+ apr_hash_t *uri_prefix; /* map URIs to an available prefix */
+ apr_hash_t *prefix_uri; /* map all prefixes to their URIs */
+ int count; /* counter for "g###" prefixes */
+} dav_xmlns_info;
+
+/* create an empty dav_xmlns_info structure */
+DAV_DECLARE(dav_xmlns_info *) dav_xmlns_create(apr_pool_t *pool);
+
+/* add a specific prefix/URI pair. the prefix/uri should have a lifetime
+ at least that of xmlns->pool */
+DAV_DECLARE(void) dav_xmlns_add(dav_xmlns_info *xi,
+ const char *prefix, const char *uri);
+
+/* add a URI (if not present); any prefix is acceptable and is returned.
+ the uri should have a lifetime at least that xmlns->pool */
+DAV_DECLARE(const char *) dav_xmlns_add_uri(dav_xmlns_info *xi,
+ const char *uri);
+
+/* return the URI for a specified prefix (or NULL if the prefix is unknown) */
+DAV_DECLARE(const char *) dav_xmlns_get_uri(dav_xmlns_info *xi,
+ const char *prefix);
+
+/* return an available prefix for a specified URI (or NULL if the URI
+ is unknown) */
+DAV_DECLARE(const char *) dav_xmlns_get_prefix(dav_xmlns_info *xi,
+ const char *uri);
+
+/* generate xmlns declarations (appending into the given text) */
+DAV_DECLARE(void) dav_xmlns_generate(dav_xmlns_info *xi,
+ apr_text_header *phdr);
+
+/* --------------------------------------------------------------------
+**
+** DAV PLUGINS
+*/
+
+/* ### docco ... */
+
+/*
+** dav_provider
+**
+** This structure wraps up all of the hooks that a mod_dav provider can
+** supply. The provider MUST supply <repos> and <propdb>. The rest are
+** optional and should contain NULL if that feature is not supplied.
+**
+** Note that a provider cannot pick and choose portions from various
+** underlying implementations (which was theoretically possible in
+** mod_dav 1.0). There are too many dependencies between a dav_resource
+** (defined by <repos>) and the other functionality.
+**
+** Live properties and report extensions are not part of the dav_provider
+** structure because they are handled through the APR_HOOK interface (to
+** allow for multiple providers). The core always provides some
+** properties, and then a given provider will add more properties.
+**
+** Some providers may need to associate a context with the dav_provider
+** structure -- the ctx field is available for storing this context. Just
+** leave it NULL if it isn't required.
+*/
+typedef struct {
+ const dav_hooks_repository *repos;
+ const dav_hooks_propdb *propdb;
+ const dav_hooks_locks *locks;
+ const dav_hooks_vsn *vsn;
+ const dav_hooks_binding *binding;
+ const dav_hooks_search *search;
+
+ void *ctx;
+} dav_provider;
+
+/*
+** gather_propsets: gather all live property propset-URIs
+**
+** The hook implementor should push one or more URIs into the specified
+** array. These URIs are returned in the DAV: header to let clients know
+** what sets of live properties are supported by the installation. mod_dav
+** will place open/close angle brackets around each value (much like
+** a Coded-URL); quotes and brackets should not be in the value.
+**
+** Example: http://apache.org/dav/props/
+**
+** (of course, use your own domain to ensure a unique value)
+*/
+APR_DECLARE_EXTERNAL_HOOK(dav, DAV, void, gather_propsets,
+ (apr_array_header_t *uris))
+
+/*
+** find_liveprop: find a live property, returning a non-zero, unique,
+** opaque identifier.
+**
+** If the hook implementor determines the specified URI/name refers to
+** one of its properties, then it should fill in HOOKS and return a
+** non-zero value. The returned value is the "property ID" and will
+** be passed to the various liveprop hook functions.
+**
+** Return 0 if the property is not defined by the hook implementor.
+*/
+APR_DECLARE_EXTERNAL_HOOK(dav, DAV, int, find_liveprop,
+ (const dav_resource *resource,
+ const char *ns_uri, const char *name,
+ const dav_hooks_liveprop **hooks))
+
+/*
+** insert_all_liveprops: insert all (known) live property names/values.
+**
+** The hook implementor should append XML text to PHDR, containing liveprop
+** names. If INSVALUE is true, then the property values should also be
+** inserted into the output XML stream.
+**
+** The liveprop provider should insert *all* known and *defined* live
+** properties on the specified resource. If a particular liveprop is
+** not defined for this resource, then it should not be inserted.
+*/
+APR_DECLARE_EXTERNAL_HOOK(dav, DAV, void, insert_all_liveprops,
+ (request_rec *r, const dav_resource *resource,
+ dav_prop_insert what, apr_text_header *phdr))
+
+/*
+** deliver_report: given a parsed report request, process the request
+** an deliver the resulting report.
+**
+** The hook implementer should decide whether it should handle the given
+** report, and if so, write the response to the output filter. If the
+** report is not relevant, return DECLINED.
+*/
+APR_DECLARE_EXTERNAL_HOOK(dav, DAV, int, deliver_report,
+ (request_rec *r,
+ const dav_resource *resource,
+ const apr_xml_doc *doc,
+ ap_filter_t *output, dav_error **err))
+
+/*
+** gather_reports: get all reports.
+**
+** The hook implementor should push one or more dav_report_elem structures
+** containing report names into the specified array. These names are returned
+** in the DAV:supported-reports-set property to let clients know
+** what reports are supported by the installation.
+**
+*/
+APR_DECLARE_EXTERNAL_HOOK(dav, DAV, void, gather_reports,
+ (request_rec *r, const dav_resource *resource,
+ apr_array_header_t *reports, dav_error **err))
+
+/*
+ ** method_precondition: check method preconditions.
+ **
+ ** If a WebDAV extension needs to set any preconditions on a method, this
+ ** hook is where to do it. If the precondition fails, return an error
+ ** response with the tagname set to the value of the failed precondition.
+ **
+ ** If the method requires an XML body, this will be read and provided as
+ ** the doc value. If not, doc is NULL. An extension that needs to verify
+ ** the non-XML body of a request should register an input filter to do so
+ ** within this hook.
+ **
+ ** Methods like PUT will supply a single src resource, and the dst will
+ ** be NULL.
+ **
+ ** Methods like COPY or MOVE will trigger this hook twice. The first
+ ** invocation will supply just the source resource. The second invocation
+ ** will supply a source and destination. This allows preconditions on the
+ ** source resource to be verified before making an attempt to get the
+ ** destination resource.
+ **
+ ** Methods like PROPFIND and LABEL will trigger this hook initially for
+ ** the src resource, and then subsequently for each resource that has
+ ** been walked during processing, with the walked resource passed in dst,
+ ** and NULL passed in src.
+ **
+ ** As a rule, the src resource originates from a request that has passed
+ ** through httpd's authn/authz hooks, while the dst resource has not.
+ */
+APR_DECLARE_EXTERNAL_HOOK(dav, DAV, int, method_precondition,
+ (request_rec *r,
+ dav_resource *src, const dav_resource *dst,
+ const apr_xml_doc *doc, dav_error **err))
+
+
+DAV_DECLARE(const dav_hooks_locks *) dav_get_lock_hooks(request_rec *r);
+DAV_DECLARE(const dav_hooks_propdb *) dav_get_propdb_hooks(request_rec *r);
+DAV_DECLARE(const dav_hooks_vsn *) dav_get_vsn_hooks(request_rec *r);
+DAV_DECLARE(const dav_hooks_binding *) dav_get_binding_hooks(request_rec *r);
+DAV_DECLARE(const dav_hooks_search *) dav_get_search_hooks(request_rec *r);
+
+DAV_DECLARE(void) dav_register_provider(apr_pool_t *p, const char *name,
+ const dav_provider *hooks);
+DAV_DECLARE(const dav_provider *) dav_lookup_provider(const char *name);
+DAV_DECLARE(const char *) dav_get_provider_name(request_rec *r);
+DAV_DECLARE(const dav_provider *) dav_get_provider(request_rec *r);
+
+
+/* ### deprecated */
+#define DAV_GET_HOOKS_PROPDB(r) dav_get_propdb_hooks(r)
+#define DAV_GET_HOOKS_LOCKS(r) dav_get_lock_hooks(r)
+#define DAV_GET_HOOKS_VSN(r) dav_get_vsn_hooks(r)
+#define DAV_GET_HOOKS_BINDING(r) dav_get_binding_hooks(r)
+#define DAV_GET_HOOKS_SEARCH(r) dav_get_search_hooks(r)
+
+
+/* --------------------------------------------------------------------
+**
+** IF HEADER PROCESSING
+**
+** Here is the definition of the If: header from RFC 2518, S9.4:
+**
+** If = "If" ":" (1*No-tag-list | 1*Tagged-list)
+** No-tag-list = List
+** Tagged-list = Resource 1*List
+** Resource = Coded-URL
+** List = "(" 1*(["Not"](State-token | "[" entity-tag "]")) ")"
+** State-token = Coded-URL
+** Coded-URL = "<" absoluteURI ">" ; absoluteURI from RFC 2616
+**
+** List corresponds to dav_if_state_list. No-tag-list corresponds to
+** dav_if_header with uri==NULL. Tagged-list corresponds to a sequence of
+** dav_if_header structures with (duplicate) uri==Resource -- one
+** dav_if_header per state_list. A second Tagged-list will start a new
+** sequence of dav_if_header structures with the new URI.
+**
+** A summary of the semantics, mapped into our structures:
+** - Chained dav_if_headers: OR
+** - Chained dav_if_state_lists: AND
+** - NULL uri matches all resources
+*/
+
+typedef enum
+{
+ dav_if_etag,
+ dav_if_opaquelock,
+ dav_if_unknown /* the "unknown" state type; always matches false. */
+} dav_if_state_type;
+
+typedef struct dav_if_state_list
+{
+ dav_if_state_type type;
+
+ int condition;
+#define DAV_IF_COND_NORMAL 0
+#define DAV_IF_COND_NOT 1 /* "Not" was applied */
+
+ const char *etag;
+ dav_locktoken *locktoken;
+
+ struct dav_if_state_list *next;
+} dav_if_state_list;
+
+typedef struct dav_if_header
+{
+ const char *uri;
+ apr_size_t uri_len;
+ struct dav_if_state_list *state;
+ struct dav_if_header *next;
+
+ int dummy_header; /* used internally by the lock/etag validation */
+} dav_if_header;
+
+typedef struct dav_locktoken_list
+{
+ dav_locktoken *locktoken;
+ struct dav_locktoken_list *next;
+} dav_locktoken_list;
+
+DAV_DECLARE(dav_error *) dav_get_locktoken_list(request_rec *r,
+ dav_locktoken_list **ltl);
+
+
+/* --------------------------------------------------------------------
+**
+** LIVE PROPERTY HANDLING
+*/
+
+/* opaque type for PROPPATCH rollback information */
+typedef struct dav_liveprop_rollback dav_liveprop_rollback;
+
+struct dav_hooks_liveprop
+{
+ /*
+ ** Insert property information into a text block. The property to
+ ** insert is identified by the propid value. The information to insert
+ ** is identified by the "what" argument, as follows:
+ ** DAV_PROP_INSERT_NAME
+ ** property name, as an empty XML element
+ ** DAV_PROP_INSERT_VALUE
+ ** property name/value, as an XML element
+ ** DAV_PROP_INSERT_SUPPORTED
+ ** if the property is defined on the resource, then
+ ** a DAV:supported-live-property element, as defined
+ ** by the DeltaV extensions to RFC2518.
+ **
+ ** Providers should return DAV_PROP_INSERT_NOTDEF if the property is
+ ** known and not defined for this resource, so should be handled as a
+ ** dead property. If a provider recognizes, but does not support, a
+ ** property, and does not want it handled as a dead property, it should
+ ** return DAV_PROP_INSERT_NOTSUPP.
+ **
+ ** Some DAV extensions, like CalDAV, specify both document elements
+ ** and property elements that need to be taken into account when
+ ** generating a property. The document element and property element
+ ** are made available in the dav_liveprop_elem structure under the
+ ** resource, accessible as follows:
+ **
+ ** dav_get_liveprop_element(resource);
+ **
+ ** Returns one of DAV_PROP_INSERT_* based on what happened.
+ **
+ ** ### we may need more context... ie. the lock database
+ */
+ dav_prop_insert (*insert_prop)(const dav_resource *resource,
+ int propid, dav_prop_insert what,
+ apr_text_header *phdr);
+
+ /*
+ ** Determine whether a given property is writable.
+ **
+ ** ### we may want a different semantic. i.e. maybe it should be
+ ** ### "can we write <value> into this property?"
+ **
+ ** Returns 1 if the live property can be written, 0 if read-only.
+ */
+ int (*is_writable)(const dav_resource *resource, int propid);
+
+ /*
+ ** This member defines the set of namespace URIs that the provider
+ ** uses for its properties. When insert_all is called, it will be
+ ** passed a list of integers that map from indices into this list
+ ** to namespace IDs for output generation.
+ **
+ ** The last entry in this list should be a NULL value (sentinel).
+ */
+ const char * const * namespace_uris;
+
+ /*
+ ** ### this is not the final design. we want an open-ended way for
+ ** ### liveprop providers to attach *new* properties. To this end,
+ ** ### we'll have a "give me a list of the props you define", a way
+ ** ### to check for a prop's existence, a way to validate a set/remove
+ ** ### of a prop, and a way to execute/commit/rollback that change.
+ */
+
+ /*
+ ** Validate that the live property can be assigned a value, and that
+ ** the provided value is valid.
+ **
+ ** elem will point to the XML element that names the property. For
+ ** example:
+ ** <lp1:executable>T</lp1:executable>
+ **
+ ** The provider can access the cdata fields and the child elements
+ ** to extract the relevant pieces.
+ **
+ ** operation is one of DAV_PROP_OP_SET or _DELETE.
+ **
+ ** The provider may return a value in *context which will be passed
+ ** to each of the exec/commit/rollback functions. For example, this
+ ** may contain an internal value which has been processed from the
+ ** input element.
+ **
+ ** The provider must set defer_to_dead to true (non-zero) or false.
+ ** If true, then the set/remove is deferred to the dead property
+ ** database. Note: it will be set to zero on entry.
+ */
+ dav_error * (*patch_validate)(const dav_resource *resource,
+ const apr_xml_elem *elem,
+ int operation,
+ void **context,
+ int *defer_to_dead);
+
+ /* ### doc... */
+ dav_error * (*patch_exec)(const dav_resource *resource,
+ const apr_xml_elem *elem,
+ int operation,
+ void *context,
+ dav_liveprop_rollback **rollback_ctx);
+
+ /* ### doc... */
+ void (*patch_commit)(const dav_resource *resource,
+ int operation,
+ void *context,
+ dav_liveprop_rollback *rollback_ctx);
+
+ /* ### doc... */
+ dav_error * (*patch_rollback)(const dav_resource *resource,
+ int operation,
+ void *context,
+ dav_liveprop_rollback *rollback_ctx);
+
+ /*
+ ** If a provider needs a context to associate with this hooks structure,
+ ** then this field may be used. In most cases, it will just be NULL.
+ */
+ void *ctx;
+};
+
+/*
+** dav_liveprop_spec: specify a live property
+**
+** This structure is used as a standard way to determine if a particular
+** property is a live property. Its use is not part of the mandated liveprop
+** interface, but can be used by liveprop providers in conjunction with the
+** utility routines below.
+**
+** spec->name == NULL is the defined end-sentinel for a list of specs.
+*/
+typedef struct {
+ int ns; /* provider-local namespace index */
+ const char *name; /* name of the property */
+
+ int propid; /* provider-local property ID */
+
+ int is_writable; /* is the property writable? */
+
+} dav_liveprop_spec;
+
+/*
+** dav_liveprop_group: specify a group of liveprops
+**
+** This structure specifies a group of live properties, their namespaces,
+** and how to handle them.
+*/
+typedef struct {
+ const dav_liveprop_spec *specs;
+ const char * const *namespace_uris;
+ const dav_hooks_liveprop *hooks;
+
+} dav_liveprop_group;
+
+/* ### docco */
+DAV_DECLARE(int) dav_do_find_liveprop(const char *ns_uri, const char *name,
+ const dav_liveprop_group *group,
+ const dav_hooks_liveprop **hooks);
+
+/* ### docco */
+DAV_DECLARE(long) dav_get_liveprop_info(int propid,
+ const dav_liveprop_group *group,
+ const dav_liveprop_spec **info);
+
+/* ### docco */
+DAV_DECLARE(void) dav_register_liveprop_group(apr_pool_t *pool,
+ const dav_liveprop_group *group);
+
+/* ### docco */
+DAV_DECLARE(long) dav_get_liveprop_ns_index(const char *uri);
+
+/* ### docco */
+DAV_DECLARE(long) dav_get_liveprop_ns_count(void);
+
+/* ### docco */
+DAV_DECLARE(void) dav_add_all_liveprop_xmlns(apr_pool_t *p,
+ apr_text_header *phdr);
+
+typedef struct {
+ const apr_xml_doc *doc;
+ const apr_xml_elem *elem;
+} dav_liveprop_elem;
+
+/*
+ ** When calling insert_prop(), the associated request element and
+ ** document is accessible using the following call.
+ */
+DAV_DECLARE(dav_liveprop_elem *) dav_get_liveprop_element(const dav_resource
+ *resource);
+
+/*
+** The following three functions are part of mod_dav's internal handling
+** for the core WebDAV properties. They are not part of mod_dav's API.
+*/
+DAV_DECLARE_NONSTD(int) dav_core_find_liveprop(
+ const dav_resource *resource,
+ const char *ns_uri,
+ const char *name,
+ const dav_hooks_liveprop **hooks);
+DAV_DECLARE_NONSTD(void) dav_core_insert_all_liveprops(
+ request_rec *r,
+ const dav_resource *resource,
+ dav_prop_insert what,
+ apr_text_header *phdr);
+DAV_DECLARE_NONSTD(void) dav_core_register_uris(apr_pool_t *p);
+
+
+/*
+** Standard WebDAV Property Identifiers
+**
+** A live property provider does not need to use these; they are simply
+** provided for convenience.
+**
+** Property identifiers need to be unique within a given provider, but not
+** *across* providers (note: this uniqueness constraint was different in
+** older versions of mod_dav).
+**
+** The identifiers start at 20000 to make it easier for providers to avoid
+** conflicts with the standard properties. The properties are arranged
+** alphabetically, and may be reordered from time to time (as properties
+** are introduced).
+**
+** NOTE: there is no problem with reordering (e.g. binary compat) since the
+** identifiers are only used within a given provider, which would pick up
+** the entire set of changes upon a recompile.
+*/
+enum {
+ DAV_PROPID_BEGIN = 20000,
+
+ /* Standard WebDAV properties (RFC 2518) */
+ DAV_PROPID_creationdate,
+ DAV_PROPID_displayname,
+ DAV_PROPID_getcontentlanguage,
+ DAV_PROPID_getcontentlength,
+ DAV_PROPID_getcontenttype,
+ DAV_PROPID_getetag,
+ DAV_PROPID_getlastmodified,
+ DAV_PROPID_lockdiscovery,
+ DAV_PROPID_resourcetype,
+ DAV_PROPID_source,
+ DAV_PROPID_supportedlock,
+
+ /* DeltaV properties (from the I-D (#14)) */
+ DAV_PROPID_activity_checkout_set,
+ DAV_PROPID_activity_set,
+ DAV_PROPID_activity_version_set,
+ DAV_PROPID_auto_merge_set,
+ DAV_PROPID_auto_version,
+ DAV_PROPID_baseline_collection,
+ DAV_PROPID_baseline_controlled_collection,
+ DAV_PROPID_baseline_controlled_collection_set,
+ DAV_PROPID_checked_in,
+ DAV_PROPID_checked_out,
+ DAV_PROPID_checkin_fork,
+ DAV_PROPID_checkout_fork,
+ DAV_PROPID_checkout_set,
+ DAV_PROPID_comment,
+ DAV_PROPID_creator_displayname,
+ DAV_PROPID_current_activity_set,
+ DAV_PROPID_current_workspace_set,
+ DAV_PROPID_default_variant,
+ DAV_PROPID_eclipsed_set,
+ DAV_PROPID_label_name_set,
+ DAV_PROPID_merge_set,
+ DAV_PROPID_precursor_set,
+ DAV_PROPID_predecessor_set,
+ DAV_PROPID_root_version,
+ DAV_PROPID_subactivity_set,
+ DAV_PROPID_subbaseline_set,
+ DAV_PROPID_successor_set,
+ DAV_PROPID_supported_method_set,
+ DAV_PROPID_supported_live_property_set,
+ DAV_PROPID_supported_report_set,
+ DAV_PROPID_unreserved,
+ DAV_PROPID_variant_set,
+ DAV_PROPID_version_controlled_binding_set,
+ DAV_PROPID_version_controlled_configuration,
+ DAV_PROPID_version_history,
+ DAV_PROPID_version_name,
+ DAV_PROPID_workspace,
+ DAV_PROPID_workspace_checkout_set,
+
+ DAV_PROPID_END
+};
+
+/*
+** Property Identifier Registration
+**
+** At the moment, mod_dav requires live property providers to ensure that
+** each property returned has a unique value. For now, this is done through
+** central registration (there are no known providers other than the default,
+** so this remains manageable).
+**
+** WARNING: the TEST ranges should never be "shipped".
+*/
+#define DAV_PROPID_CORE 10000 /* ..10099. defined by mod_dav */
+#define DAV_PROPID_FS 10100 /* ..10299.
+ mod_dav filesystem provider. */
+#define DAV_PROPID_TEST1 10300 /* ..10399 */
+#define DAV_PROPID_TEST2 10400 /* ..10499 */
+#define DAV_PROPID_TEST3 10500 /* ..10599 */
+/* Next: 10600 */
+
+
+/* --------------------------------------------------------------------
+**
+** DATABASE FUNCTIONS
+*/
+
+typedef struct dav_db dav_db;
+typedef struct dav_namespace_map dav_namespace_map;
+typedef struct dav_deadprop_rollback dav_deadprop_rollback;
+
+typedef struct {
+ const char *ns; /* "" signals "no namespace" */
+ const char *name;
+} dav_prop_name;
+
+/* hook functions to enable pluggable databases */
+struct dav_hooks_propdb
+{
+ dav_error * (*open)(apr_pool_t *p, const dav_resource *resource, int ro,
+ dav_db **pdb);
+ void (*close)(dav_db *db);
+
+ /*
+ ** In bulk, define any namespaces that the values and their name
+ ** elements may need.
+ **
+ ** Note: sometimes mod_dav will defer calling this until output_value
+ ** returns found==1. If the output process needs the dav_xmlns_info
+ ** filled for its work, then it will need to fill it on demand rather
+ ** than depending upon this hook to fill in the structure.
+ **
+ ** Note: this will *always* be called during an output sequence. Thus,
+ ** the provider may rely solely on using this to fill the xmlns info.
+ */
+ dav_error * (*define_namespaces)(dav_db *db, dav_xmlns_info *xi);
+
+ /*
+ ** Output the value from the database (i.e. add an element name and
+ ** the value into *phdr). Set *found based on whether the name/value
+ ** was found in the propdb.
+ **
+ ** Note: it is NOT an error for the key/value pair to not exist.
+ **
+ ** The dav_xmlns_info passed to define_namespaces() is also passed to
+ ** each output_value() call so that namespaces can be added on-demand.
+ ** It can also be used to look up prefixes or URIs during the output
+ ** process.
+ */
+ dav_error * (*output_value)(dav_db *db, const dav_prop_name *name,
+ dav_xmlns_info *xi,
+ apr_text_header *phdr, int *found);
+
+ /*
+ ** Build a mapping from "global" namespaces (stored in apr_xml_*)
+ ** into provider-local namespace identifiers.
+ **
+ ** This mapping should be done once per set of namespaces, and the
+ ** resulting mapping should be passed into the store() hook function.
+ **
+ ** Note: usually, there is just a single document/namespaces for all
+ ** elements passed. However, the generality of creating multiple
+ ** mappings and passing them to store() is provided here.
+ **
+ ** Note: this is only in preparation for a series of store() calls.
+ ** As a result, the propdb must be open for read/write access when
+ ** this function is called.
+ */
+ dav_error * (*map_namespaces)(dav_db *db,
+ const apr_array_header_t *namespaces,
+ dav_namespace_map **mapping);
+
+ /*
+ ** Store a property value for a given name. The value->combined field
+ ** MUST be set for this call.
+ **
+ ** ### WARNING: current providers will quote the text within ELEM.
+ ** ### this implies you can call this function only once with a given
+ ** ### element structure (a second time will quote it again).
+ */
+ dav_error * (*store)(dav_db *db, const dav_prop_name *name,
+ const apr_xml_elem *elem,
+ dav_namespace_map *mapping);
+
+ /* remove a given property */
+ dav_error * (*remove)(dav_db *db, const dav_prop_name *name);
+
+ /* returns 1 if the record specified by "key" exists; 0 otherwise */
+ int (*exists)(dav_db *db, const dav_prop_name *name);
+
+ /*
+ ** Iterate over the property names in the database.
+ **
+ ** iter->name.ns == iter->name.name == NULL when there are no more names.
+ **
+ ** Note: only one iteration may occur over the propdb at a time.
+ */
+ dav_error * (*first_name)(dav_db *db, dav_prop_name *pname);
+ dav_error * (*next_name)(dav_db *db, dav_prop_name *pname);
+
+ /*
+ ** Rollback support: get rollback context, and apply it.
+ **
+ ** struct dav_deadprop_rollback is a provider-private structure; it
+ ** should remember the name, and the name's old value (or the fact that
+ ** the value was not present, and should be deleted if a rollback occurs).
+ */
+ dav_error * (*get_rollback)(dav_db *db, const dav_prop_name *name,
+ dav_deadprop_rollback **prollback);
+ dav_error * (*apply_rollback)(dav_db *db,
+ dav_deadprop_rollback *rollback);
+
+ /*
+ ** If a provider needs a context to associate with this hooks structure,
+ ** then this field may be used. In most cases, it will just be NULL.
+ */
+ void *ctx;
+};
+
+
+/* --------------------------------------------------------------------
+**
+** LOCK FUNCTIONS
+*/
+
+/* Used to represent a Timeout header of "Infinity" */
+#define DAV_TIMEOUT_INFINITE 0
+
+DAV_DECLARE(time_t) dav_get_timeout(request_rec *r);
+
+/*
+** Opaque, provider-specific information for a lock database.
+*/
+typedef struct dav_lockdb_private dav_lockdb_private;
+
+/*
+** Opaque, provider-specific information for a lock record.
+*/
+typedef struct dav_lock_private dav_lock_private;
+
+/*
+** Lock database type. Lock providers are urged to implement a "lazy" open, so
+** doing an "open" is cheap until something is actually needed from the DB.
+*/
+typedef struct
+{
+ const dav_hooks_locks *hooks; /* the hooks used for this lockdb */
+ int ro; /* was it opened readonly? */
+
+ dav_lockdb_private *info;
+
+} dav_lockdb;
+
+typedef enum {
+ DAV_LOCKSCOPE_UNKNOWN,
+ DAV_LOCKSCOPE_EXCLUSIVE,
+ DAV_LOCKSCOPE_SHARED
+} dav_lock_scope;
+
+typedef enum {
+ DAV_LOCKTYPE_UNKNOWN,
+ DAV_LOCKTYPE_WRITE
+} dav_lock_type;
+
+typedef enum {
+ DAV_LOCKREC_DIRECT, /* lock asserted on this resource */
+ DAV_LOCKREC_INDIRECT, /* lock inherited from a parent */
+ DAV_LOCKREC_INDIRECT_PARTIAL /* most info is not filled in */
+} dav_lock_rectype;
+
+/*
+** dav_lock: hold information about a lock on a resource.
+**
+** This structure is used for both direct and indirect locks. A direct lock
+** is a lock applied to a specific resource by the client. An indirect lock
+** is one that is inherited from a parent resource by virtue of a non-zero
+** Depth: header when the lock was applied.
+**
+** mod_dav records both types of locks in the lock database, managing their
+** addition/removal as resources are moved about the namespace.
+**
+** Note that the lockdb is free to marshal this structure in any form that
+** it likes.
+**
+** For a "partial" lock, the <rectype> and <locktoken> fields must be filled
+** in. All other (user) fields should be zeroed. The lock provider will
+** usually fill in the <info> field, and the <next> field may be used to
+** construct a list of partial locks.
+**
+** The lock provider MUST use the info field to store a value such that a
+** dav_lock structure can locate itself in the underlying lock database.
+** This requirement is needed for refreshing: when an indirect dav_lock is
+** refreshed, its reference to the direct lock does not specify the direct's
+** resource, so the only way to locate the (refreshed, direct) lock in the
+** database is to use the info field.
+**
+** Note that <is_locknull> only refers to the resource where this lock was
+** found.
+** ### hrm. that says the abstraction is wrong. is_locknull may disappear.
+*/
+typedef struct dav_lock
+{
+ dav_lock_rectype rectype; /* type of lock record */
+ int is_locknull; /* lock establishes a locknull resource */
+
+ /* ### put the resource in here? */
+
+ dav_lock_scope scope; /* scope of the lock */
+ dav_lock_type type; /* type of lock */
+ int depth; /* depth of the lock */
+ time_t timeout; /* when the lock will timeout */
+
+ const dav_locktoken *locktoken; /* the token that was issued */
+
+ const char *owner; /* (XML) owner of the lock */
+ const char *auth_user; /* auth'd username owning lock */
+
+ dav_lock_private *info; /* private to the lockdb */
+
+ struct dav_lock *next; /* for managing a list of locks */
+} dav_lock;
+
+/* Property-related public lock functions */
+DAV_DECLARE(const char *)dav_lock_get_activelock(request_rec *r,
+ dav_lock *locks,
+ dav_buffer *pbuf);
+
+/* LockDB-related public lock functions */
+DAV_DECLARE(dav_error *) dav_open_lockdb(request_rec *r,
+ int ro,
+ dav_lockdb **lockdb);
+DAV_DECLARE(void) dav_close_lockdb(dav_lockdb *lockdb);
+DAV_DECLARE(dav_error *) dav_lock_parse_lockinfo(request_rec *r,
+ const dav_resource *resource,
+ dav_lockdb *lockdb,
+ const apr_xml_doc *doc,
+ dav_lock **lock_request);
+DAV_DECLARE(int) dav_unlock(request_rec *r,
+ const dav_resource *resource,
+ const dav_locktoken *locktoken);
+DAV_DECLARE(dav_error *) dav_add_lock(request_rec *r,
+ const dav_resource *resource,
+ dav_lockdb *lockdb, dav_lock *request,
+ dav_response **response);
+DAV_DECLARE(dav_error *) dav_notify_created(request_rec *r,
+ dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int resource_state,
+ int depth);
+
+DAV_DECLARE(dav_error*) dav_lock_query(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ dav_lock **locks);
+
+DAV_DECLARE(dav_error *) dav_validate_request(request_rec *r,
+ dav_resource *resource,
+ int depth,
+ dav_locktoken *locktoken,
+ dav_response **response,
+ int flags,
+ dav_lockdb *lockdb);
+/*
+** flags:
+** 0x0F -- reserved for <dav_lock_scope> values
+**
+** other flags, detailed below
+*/
+#define DAV_VALIDATE_RESOURCE 0x0010 /* validate just the resource */
+#define DAV_VALIDATE_PARENT 0x0020 /* validate resource AND its parent */
+#define DAV_VALIDATE_ADD_LD 0x0040 /* add DAV:lockdiscovery into
+ the 424 DAV:response */
+#define DAV_VALIDATE_USE_424 0x0080 /* return 424 status, not 207 */
+#define DAV_VALIDATE_IS_PARENT 0x0100 /* for internal use */
+#define DAV_VALIDATE_NO_MODIFY 0x0200 /* resource is not being modified
+ so allow even if lock token
+ is not provided */
+
+/* Lock-null related public lock functions */
+DAV_DECLARE(int) dav_get_resource_state(request_rec *r,
+ const dav_resource *resource);
+
+/* Lock provider hooks. Locking is optional, so there may be no
+ * lock provider for a given repository.
+ */
+struct dav_hooks_locks
+{
+ /* Return the supportedlock property for a resource */
+ const char * (*get_supportedlock)(
+ const dav_resource *resource
+ );
+
+ /* Parse a lock token URI, returning a lock token object allocated
+ * in the given pool.
+ */
+ dav_error * (*parse_locktoken)(
+ apr_pool_t *p,
+ const char *char_token,
+ dav_locktoken **locktoken_p
+ );
+
+ /* Format a lock token object into a URI string, allocated in
+ * the given pool.
+ *
+ * Always returns non-NULL.
+ */
+ const char * (*format_locktoken)(
+ apr_pool_t *p,
+ const dav_locktoken *locktoken
+ );
+
+ /* Compare two lock tokens.
+ *
+ * Result < 0 => lt1 < lt2
+ * Result == 0 => lt1 == lt2
+ * Result > 0 => lt1 > lt2
+ */
+ int (*compare_locktoken)(
+ const dav_locktoken *lt1,
+ const dav_locktoken *lt2
+ );
+
+ /* Open the provider's lock database.
+ *
+ * The provider may or may not use a "real" database for locks
+ * (a lock could be an attribute on a resource, for example).
+ *
+ * The provider may choose to use the value of the DAVLockDB directive
+ * (as returned by dav_get_lockdb_path()) to decide where to place
+ * any storage it may need.
+ *
+ * The request storage pool should be associated with the lockdb,
+ * so it can be used in subsequent operations.
+ *
+ * If ro != 0, only readonly operations will be performed.
+ * If force == 0, the open can be "lazy"; no subsequent locking operations
+ * may occur.
+ * If force != 0, locking operations will definitely occur.
+ */
+ dav_error * (*open_lockdb)(
+ request_rec *r,
+ int ro,
+ int force,
+ dav_lockdb **lockdb
+ );
+
+ /* Indicates completion of locking operations */
+ void (*close_lockdb)(
+ dav_lockdb *lockdb
+ );
+
+ /* Take a resource out of the lock-null state. */
+ dav_error * (*remove_locknull_state)(
+ dav_lockdb *lockdb,
+ const dav_resource *resource
+ );
+
+ /*
+ ** Create a (direct) lock structure for the given resource. A locktoken
+ ** will be created.
+ **
+ ** The lock provider may store private information into lock->info.
+ */
+ dav_error * (*create_lock)(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ dav_lock **lock);
+
+ /*
+ ** Get the locks associated with the specified resource.
+ **
+ ** If resolve_locks is true (non-zero), then any indirect locks are
+ ** resolved to their actual, direct lock (i.e. the reference to followed
+ ** to the original lock).
+ **
+ ** The locks, if any, are returned as a linked list in no particular
+ ** order. If no locks are present, then *locks will be NULL.
+ */
+ dav_error * (*get_locks)(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int calltype,
+ dav_lock **locks);
+
+#define DAV_GETLOCKS_RESOLVED 0 /* resolve indirects to directs */
+#define DAV_GETLOCKS_PARTIAL 1 /* leave indirects partially filled */
+#define DAV_GETLOCKS_COMPLETE 2 /* fill out indirect locks */
+
+ /*
+ ** Find a particular lock on a resource (specified by its locktoken).
+ **
+ ** *lock will be set to NULL if the lock is not found.
+ **
+ ** Note that the provider can optimize the unmarshalling -- only one
+ ** lock (or none) must be constructed and returned.
+ **
+ ** If partial_ok is true (non-zero), then an indirect lock can be
+ ** partially filled in. Otherwise, another lookup is done and the
+ ** lock structure will be filled out as a DAV_LOCKREC_INDIRECT.
+ */
+ dav_error * (*find_lock)(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ const dav_locktoken *locktoken,
+ int partial_ok,
+ dav_lock **lock);
+
+ /*
+ ** Quick test to see if the resource has *any* locks on it.
+ **
+ ** This is typically used to determine if a non-existent resource
+ ** has a lock and is (therefore) a locknull resource.
+ **
+ ** WARNING: this function may return TRUE even when timed-out locks
+ ** exist (i.e. it may not perform timeout checks).
+ */
+ dav_error * (*has_locks)(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int *locks_present);
+
+ /*
+ ** Append the specified lock(s) to the set of locks on this resource.
+ **
+ ** If "make_indirect" is true (non-zero), then the specified lock(s)
+ ** should be converted to an indirect lock (if it is a direct lock)
+ ** before appending. Note that the conversion to an indirect lock does
+ ** not alter the passed-in lock -- the change is internal the
+ ** append_locks function.
+ **
+ ** Multiple locks are specified using the lock->next links.
+ */
+ dav_error * (*append_locks)(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int make_indirect,
+ const dav_lock *lock);
+
+ /*
+ ** Remove any lock that has the specified locktoken.
+ **
+ ** If locktoken == NULL, then ALL locks are removed.
+ */
+ dav_error * (*remove_lock)(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ const dav_locktoken *locktoken);
+
+ /*
+ ** Refresh all locks, found on the specified resource, which has a
+ ** locktoken in the provided list.
+ **
+ ** If the lock is indirect, then the direct lock is referenced and
+ ** refreshed.
+ **
+ ** Each lock that is updated is returned in the <locks> argument.
+ ** Note that the locks will be fully resolved.
+ */
+ dav_error * (*refresh_locks)(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ const dav_locktoken_list *ltl,
+ time_t new_time,
+ dav_lock **locks);
+
+ /*
+ ** Look up the resource associated with a particular locktoken.
+ **
+ ** The search begins at the specified <start_resource> and the lock
+ ** specified by <locktoken>.
+ **
+ ** If the resource/token specifies an indirect lock, then the direct
+ ** lock will be looked up, and THAT resource will be returned. In other
+ ** words, this function always returns the resource where a particular
+ ** lock (token) was asserted.
+ **
+ ** NOTE: this function pointer is allowed to be NULL, indicating that
+ ** the provider does not support this type of functionality. The
+ ** caller should then traverse up the repository hierarchy looking
+ ** for the resource defining a lock with this locktoken.
+ */
+ dav_error * (*lookup_resource)(dav_lockdb *lockdb,
+ const dav_locktoken *locktoken,
+ const dav_resource *start_resource,
+ const dav_resource **resource);
+
+ /*
+ ** If a provider needs a context to associate with this hooks structure,
+ ** then this field may be used. In most cases, it will just be NULL.
+ */
+ void *ctx;
+};
+
+/* what types of resources can be discovered by dav_get_resource_state() */
+#define DAV_RESOURCE_LOCK_NULL 10 /* resource lock-null */
+#define DAV_RESOURCE_NULL 11 /* resource null */
+#define DAV_RESOURCE_EXISTS 12 /* resource exists */
+#define DAV_RESOURCE_ERROR 13 /* an error occurred */
+
+
+/* --------------------------------------------------------------------
+**
+** PROPERTY HANDLING
+*/
+
+typedef struct dav_propdb dav_propdb;
+
+#define DAV_PROPDB_NONE 0
+#define DAV_PROPDB_RO 1
+#define DAV_PROPDB_DISABLE_LOCKDISCOVERY 2
+
+DAV_DECLARE(dav_error *) dav_open_propdb(
+ request_rec *r,
+ dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int flags,
+ apr_array_header_t *ns_xlate,
+ dav_propdb **propdb);
+
+DAV_DECLARE(dav_error *) dav_popen_propdb(
+ apr_pool_t *p,
+ request_rec *r,
+ dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int flags,
+ apr_array_header_t *ns_xlate,
+ dav_propdb **propdb);
+
+
+DAV_DECLARE(void) dav_close_propdb(dav_propdb *db);
+
+DAV_DECLARE(dav_get_props_result) dav_get_props(
+ dav_propdb *db,
+ apr_xml_doc *doc);
+
+DAV_DECLARE(dav_get_props_result) dav_get_allprops(
+ dav_propdb *db,
+ dav_prop_insert what);
+
+DAV_DECLARE(void) dav_get_liveprop_supported(
+ dav_propdb *propdb,
+ const char *ns_uri,
+ const char *propname,
+ apr_text_header *body);
+
+/*
+** 3-phase property modification.
+**
+** 1) validate props. readable? unlocked? ACLs allow access?
+** 2) execute operation (set/delete)
+** 3) commit or rollback
+**
+** ### eventually, auth must be available. a ref to the request_rec (which
+** ### contains the auth info) should be in the shared context struct.
+**
+** Each function may alter the error values and information contained within
+** the context record. This should be done as an "increasing" level of
+** error, rather than overwriting any previous error.
+**
+** Note that commit() cannot generate errors. It should simply free the
+** rollback information.
+**
+** rollback() may generate additional errors because the rollback operation
+** can sometimes fail(!).
+**
+** The caller should allocate an array of these, one per operation. It should
+** be zero-initialized, then the db, operation, and prop fields should be
+** filled in before calling dav_prop_validate. Note that the set/delete
+** operations are order-dependent. For a given (logical) context, the same
+** pointer must be passed to each phase.
+**
+** error_type is an internal value, but will have the same numeric value
+** for each possible "desc" value. This allows the caller to group the
+** descriptions via the error_type variable, rather than through string
+** comparisons. Note that "status" does not provide enough granularity to
+** differentiate/group the "desc" values.
+**
+** Note that the propdb will maintain some (global) context across all
+** of the property change contexts. This implies that you can have only
+** one open transaction per propdb.
+*/
+typedef struct dav_prop_ctx
+{
+ dav_propdb *propdb;
+
+ apr_xml_elem *prop; /* property to affect */
+
+ int operation;
+#define DAV_PROP_OP_SET 1 /* set a property value */
+#define DAV_PROP_OP_DELETE 2 /* delete a prop value */
+/* ### add a GET? */
+
+ /* private items to the propdb */
+ int is_liveprop;
+ void *liveprop_ctx;
+ struct dav_rollback_item *rollback; /* optional rollback info */
+
+ dav_error *err; /* error (if any) */
+
+ /* private to mod_dav.c */
+ request_rec *r;
+
+} dav_prop_ctx;
+
+DAV_DECLARE_NONSTD(void) dav_prop_validate(dav_prop_ctx *ctx);
+DAV_DECLARE_NONSTD(void) dav_prop_exec(dav_prop_ctx *ctx);
+DAV_DECLARE_NONSTD(void) dav_prop_commit(dav_prop_ctx *ctx);
+DAV_DECLARE_NONSTD(void) dav_prop_rollback(dav_prop_ctx *ctx);
+
+#define DAV_PROP_CTX_HAS_ERR(dpc) ((dpc).err && (dpc).err->status >= 300)
+
+
+/* --------------------------------------------------------------------
+**
+** WALKER STRUCTURE
+*/
+
+enum {
+ DAV_CALLTYPE_MEMBER = 1, /* called for a member resource */
+ DAV_CALLTYPE_COLLECTION, /* called for a collection */
+ DAV_CALLTYPE_LOCKNULL /* called for a locknull resource */
+};
+
+typedef struct
+{
+ /* the client-provided context */
+ void *walk_ctx;
+
+ /* pool to use for allocations in the callback */
+ apr_pool_t *pool;
+
+ /* the current resource */
+ const dav_resource *resource;
+
+ /* OUTPUT: add responses to this */
+ dav_response *response;
+
+} dav_walk_resource;
+
+typedef struct
+{
+ int walk_type;
+#define DAV_WALKTYPE_AUTH 0x0001 /* limit to authorized files */
+#define DAV_WALKTYPE_NORMAL 0x0002 /* walk normal files */
+#define DAV_WALKTYPE_LOCKNULL 0x0004 /* walk locknull resources */
+
+ /* callback function and a client context for the walk */
+ dav_error * (*func)(dav_walk_resource *wres, int calltype);
+ void *walk_ctx;
+
+ /* what pool to use for allocations needed by walk logic */
+ apr_pool_t *pool;
+
+ /* beginning root of the walk */
+ const dav_resource *root;
+
+ /* lock database to enable walking LOCKNULL resources */
+ dav_lockdb *lockdb;
+
+} dav_walk_params;
+
+/* directory tree walking context */
+typedef struct dav_walker_ctx
+{
+ /* input: */
+ dav_walk_params w;
+
+
+ /* ### client data... phasing out this big glom */
+
+ /* this brigade buffers data being sent to r->output_filters */
+ apr_bucket_brigade *bb;
+
+ /* a scratch pool, used to stream responses and iteratively cleared. */
+ apr_pool_t *scratchpool;
+
+ request_rec *r; /* original request */
+
+ /* for PROPFIND operations */
+ apr_xml_doc *doc;
+ int propfind_type;
+#define DAV_PROPFIND_IS_ALLPROP 1
+#define DAV_PROPFIND_IS_PROPNAME 2
+#define DAV_PROPFIND_IS_PROP 3
+
+ apr_text *propstat_404; /* (cached) propstat giving a 404 error */
+
+ const dav_if_header *if_header; /* for validation */
+ const dav_locktoken *locktoken; /* for UNLOCK */
+ const dav_lock *lock; /* for LOCK */
+ int skip_root; /* for dav_inherit_locks() */
+
+ int flags;
+
+ dav_buffer work_buf; /* for dav_validate_request() */
+
+} dav_walker_ctx;
+
+DAV_DECLARE(void) dav_add_response(dav_walk_resource *wres,
+ int status,
+ dav_get_props_result *propstats);
+
+
+/* --------------------------------------------------------------------
+**
+** "STREAM" STRUCTURE
+**
+** mod_dav uses this abstraction for interacting with the repository
+** while fetching/storing resources. mod_dav views resources as a stream
+** of bytes.
+**
+** Note that the structure is opaque -- it is private to the repository
+** that created the stream in the repository's "open" function.
+**
+** ### THIS STUFF IS GOING AWAY ... GET/read requests are handled by
+** ### having the provider jam stuff straight into the filter stack.
+** ### this is only left for handling PUT/write requests.
+*/
+
+typedef struct dav_stream dav_stream;
+
+typedef enum {
+ DAV_MODE_WRITE_TRUNC, /* truncate and open for writing */
+ DAV_MODE_WRITE_SEEKABLE /* open for writing; random access */
+} dav_stream_mode;
+
+
+/* --------------------------------------------------------------------
+**
+** REPOSITORY FUNCTIONS
+*/
+
+/* Repository provider hooks */
+struct dav_hooks_repository
+{
+ /* Flag for whether repository requires special GET handling.
+ * If resources in the repository are not visible in the
+ * filesystem location which URLs map to, then special handling
+ * is required to first fetch a resource from the repository,
+ * respond to the GET request, then free the resource copy.
+ */
+ int handle_get;
+
+ /* Get a resource descriptor for the URI in a request. A descriptor
+ * should always be returned even if the resource does not exist. This
+ * repository has been identified as handling the resource given by
+ * the URI, so an answer must be given. If there is a problem with the
+ * URI or accessing the resource or whatever, then an error should be
+ * returned.
+ *
+ * root_dir:
+ * the root of the directory for which this repository is configured.
+ *
+ * label:
+ * if a Label: header is present (and allowed), this is the label
+ * to use to identify a version resource from the resource's
+ * corresponding version history. Otherwise, it will be NULL.
+ *
+ * use_checked_in:
+ * use the DAV:checked-in property of the resource identified by the
+ * Request-URI to identify and return a version resource
+ *
+ * The provider may associate the request storage pool with the resource
+ * (in the resource->pool field), to use in other operations on that
+ * resource.
+ */
+ dav_error * (*get_resource)(
+ request_rec *r,
+ const char *root_dir,
+ const char *label,
+ int use_checked_in,
+ dav_resource **resource
+ );
+
+ /* Get a resource descriptor for the parent of the given resource.
+ * The resources need not exist. NULL is returned if the resource
+ * is the root collection.
+ *
+ * An error should be returned only if there is a fatal error in
+ * fetching information about the parent resource.
+ */
+ dav_error * (*get_parent_resource)(
+ const dav_resource *resource,
+ dav_resource **parent_resource
+ );
+
+ /* Determine whether two resource descriptors refer to the same resource.
+ *
+ * Result != 0 => the resources are the same.
+ */
+ int (*is_same_resource)(
+ const dav_resource *res1,
+ const dav_resource *res2
+ );
+
+ /* Determine whether one resource is a parent (immediate or otherwise)
+ * of another.
+ *
+ * Result != 0 => res1 is a parent of res2.
+ */
+ int (*is_parent_resource)(
+ const dav_resource *res1,
+ const dav_resource *res2
+ );
+
+ /*
+ ** Open a stream for this resource, using the specified mode. The
+ ** stream will be returned in *stream.
+ */
+ dav_error * (*open_stream)(const dav_resource *resource,
+ dav_stream_mode mode,
+ dav_stream **stream);
+
+ /*
+ ** Close the specified stream.
+ **
+ ** mod_dav will (ideally) make sure to call this. For safety purposes,
+ ** a provider should (ideally) register a cleanup function with the
+ ** request pool to get this closed and cleaned up.
+ **
+ ** Note the possibility of an error from the close -- it is entirely
+ ** feasible that the close does a "commit" of some kind, which can
+ ** produce an error.
+ **
+ ** commit should be TRUE (non-zero) or FALSE (0) if the stream was
+ ** opened for writing. This flag states whether to retain the file
+ ** or not.
+ ** Note: the commit flag is ignored for streams opened for reading.
+ */
+ dav_error * (*close_stream)(dav_stream *stream, int commit);
+
+ /*
+ ** Write data to the stream.
+ **
+ ** All of the bytes must be written, or an error should be returned.
+ */
+ dav_error * (*write_stream)(dav_stream *stream,
+ const void *buf, apr_size_t bufsize);
+
+ /*
+ ** Seek to an absolute position in the stream. This is used to support
+ ** Content-Range in a GET/PUT.
+ **
+ ** NOTE: if this function is NULL (which is allowed), then any
+ ** operations using Content-Range will be refused.
+ */
+ dav_error * (*seek_stream)(dav_stream *stream, apr_off_t abs_position);
+
+ /*
+ ** If a GET is processed using a stream (open_stream, read_stream)
+ ** rather than via a sub-request (on get_pathname), then this function
+ ** is used to provide the repository with a way to set the headers
+ ** in the response.
+ **
+ ** This function may be called without a following deliver(), to
+ ** handle a HEAD request.
+ **
+ ** This may be NULL if handle_get is FALSE.
+ */
+ dav_error * (*set_headers)(request_rec *r,
+ const dav_resource *resource);
+
+ /*
+ ** The provider should deliver the resource into the specified filter.
+ ** Basically, this is the response to the GET method.
+ **
+ ** Note that this is called for all resources, including collections.
+ ** The provider should determine what has content to deliver or not.
+ **
+ ** set_headers will be called prior to this function, allowing the
+ ** provider to set the appropriate response headers.
+ **
+ ** This may be NULL if handle_get is FALSE.
+ ** ### maybe toss handle_get and just use this function as the marker
+ */
+ dav_error * (*deliver)(const dav_resource *resource,
+ ap_filter_t *output);
+
+ /* Create a collection resource. The resource must not already exist.
+ *
+ * Result == NULL if the collection was created successfully. Also, the
+ * resource object is updated to reflect that the resource exists, and
+ * is a collection.
+ */
+ dav_error * (*create_collection)(
+ dav_resource *resource
+ );
+
+ /* Copy one resource to another. The destination may exist, if it is
+ * versioned.
+ * Handles both files and collections. Properties are copied as well.
+ * If the destination exists and is versioned, the provider must update
+ * the destination to have identical content to the source,
+ * recursively for collections.
+ * The depth argument is ignored for a file, and can be either 0 or
+ * DAV_INFINITY for a collection.
+ * If an error occurs in a child resource, then the return value is
+ * non-NULL, and *response is set to a multistatus response.
+ * If the copy is successful, the dst resource object is
+ * updated to reflect that the resource exists.
+ */
+ dav_error * (*copy_resource)(
+ const dav_resource *src,
+ dav_resource *dst,
+ int depth,
+ dav_response **response
+ );
+
+ /* Move one resource to another. The destination must not exist.
+ * Handles both files and collections. Properties are moved as well.
+ * If an error occurs in a child resource, then the return value is
+ * non-NULL, and *response is set to a multistatus response.
+ * If the move is successful, the src and dst resource objects are
+ * updated to reflect that the source no longer exists, and the
+ * destination does.
+ */
+ dav_error * (*move_resource)(
+ dav_resource *src,
+ dav_resource *dst,
+ dav_response **response
+ );
+
+ /* Remove a resource. Handles both files and collections.
+ * Removes any associated properties as well.
+ * If an error occurs in a child resource, then the return value is
+ * non-NULL, and *response is set to a multistatus response.
+ * If the delete is successful, the resource object is updated to
+ * reflect that the resource no longer exists.
+ */
+ dav_error * (*remove_resource)(
+ dav_resource *resource,
+ dav_response **response
+ );
+
+ /* Walk a resource hierarchy.
+ *
+ * Iterates over the resource hierarchy specified by params->root.
+ * Control of the walk and the callback are specified by 'params'.
+ *
+ * An error may be returned. *response will contain multistatus
+ * responses (if any) suitable for the body of the error. It is also
+ * possible to return NULL, yet still have multistatus responses.
+ * In this case, typically the caller should return a 207 (Multistatus)
+ * and the responses (in the body) as the HTTP response.
+ */
+ dav_error * (*walk)(const dav_walk_params *params, int depth,
+ dav_response **response);
+
+ /* Get the entity tag for a resource */
+ const char * (*getetag)(const dav_resource *resource);
+
+ /*
+ ** If a provider needs a context to associate with this hooks structure,
+ ** then this field may be used. In most cases, it will just be NULL.
+ */
+ void *ctx;
+
+ /* Get the request rec for a resource */
+ request_rec * (*get_request_rec)(const dav_resource *resource);
+
+ /* Get the pathname for a resource */
+ const char * (*get_pathname)(const dav_resource *resource);
+};
+
+
+/* --------------------------------------------------------------------
+**
+** VERSIONING FUNCTIONS
+*/
+
+
+/* dav_add_vary_header
+ *
+ * If there were any headers in the request which require a Vary header
+ * in the response, add it.
+ */
+DAV_DECLARE(void) dav_add_vary_header(request_rec *in_req,
+ request_rec *out_req,
+ const dav_resource *resource);
+
+/*
+** Flags specifying auto-versioning behavior, returned by
+** the auto_versionable hook. The value returned depends
+** on both the state of the resource and the value of the
+** DAV:auto-versioning property for the resource.
+**
+** If the resource does not exist (null or lock-null),
+** DAV_AUTO_VERSION_ALWAYS causes creation of a new version-controlled resource
+**
+** If the resource is checked in,
+** DAV_AUTO_VERSION_ALWAYS causes it to be checked out always,
+** DAV_AUTO_VERSION_LOCKED causes it to be checked out only when locked
+**
+** If the resource is checked out,
+** DAV_AUTO_VERSION_ALWAYS causes it to be checked in always,
+** DAV_AUTO_VERSION_LOCKED causes it to be checked in when unlocked
+** (note: a provider should allow auto-checkin only for resources which
+** were automatically checked out)
+**
+** In all cases, DAV_AUTO_VERSION_NEVER results in no auto-versioning behavior.
+*/
+typedef enum {
+ DAV_AUTO_VERSION_NEVER,
+ DAV_AUTO_VERSION_ALWAYS,
+ DAV_AUTO_VERSION_LOCKED
+} dav_auto_version;
+
+/*
+** This structure is used to record what auto-versioning operations
+** were done to make a resource writable, so that they can be undone
+** at the end of a request.
+*/
+typedef struct {
+ int resource_versioned; /* 1 => resource was auto-version-controlled */
+ int resource_checkedout; /* 1 => resource was auto-checked-out */
+ int parent_checkedout; /* 1 => parent was auto-checked-out */
+ dav_resource *parent_resource; /* parent resource, if it was needed */
+} dav_auto_version_info;
+
+/* Ensure that a resource is writable. If there is no versioning
+ * provider, then this is essentially a no-op. Versioning repositories
+ * require explicit resource creation and checkout before they can
+ * be written to. If a new resource is to be created, or an existing
+ * resource deleted, the parent collection must be checked out as well.
+ *
+ * Set the parent_only flag to only make the parent collection writable.
+ * Otherwise, both parent and child are made writable as needed. If the
+ * child does not exist, then a new versioned resource is created and
+ * checked out.
+ *
+ * If auto-versioning is not enabled for a versioned resource, then an error is
+ * returned, since the resource cannot be modified.
+ *
+ * The dav_auto_version_info structure is filled in with enough information
+ * to restore both parent and child resources to the state they were in
+ * before the auto-versioning operations occurred.
+ */
+DAV_DECLARE(dav_error *) dav_auto_checkout(
+ request_rec *r,
+ dav_resource *resource,
+ int parent_only,
+ dav_auto_version_info *av_info);
+
+/* Revert the writability of resources back to what they were
+ * before they were modified. If undo == 0, then the resource
+ * modifications are maintained (i.e. they are checked in).
+ * If undo != 0, then resource modifications are discarded
+ * (i.e. they are unchecked out).
+ *
+ * Set the unlock flag to indicate that the resource is about
+ * to be unlocked; it will be checked in if the resource
+ * auto-versioning property indicates it should be. In this case,
+ * av_info is ignored, so it can be NULL.
+ *
+ * The resource argument may be NULL if only the parent resource
+ * was checked out (i.e. the parent_only was != 0 in the
+ * dav_auto_checkout call).
+ */
+DAV_DECLARE(dav_error *) dav_auto_checkin(
+ request_rec *r,
+ dav_resource *resource,
+ int undo,
+ int unlock,
+ dav_auto_version_info *av_info);
+
+/*
+** This structure is used to describe available reports
+**
+** "nmspace" should be valid XML and URL-quoted. mod_dav will place
+** double-quotes around it and use it in an xmlns declaration.
+*/
+typedef struct {
+ const char *nmspace; /* namespace of the XML report element */
+ const char *name; /* element name for the XML report */
+} dav_report_elem;
+
+
+/* Versioning provider hooks */
+struct dav_hooks_vsn
+{
+ /*
+ ** MANDATORY HOOKS
+ ** The following hooks are mandatory for all versioning providers;
+ ** they define the functionality needed to implement "core" versioning.
+ */
+
+ /* Return supported versioning options.
+ * Each dav_text item in the list will be returned as a separate
+ * DAV header. Providers are advised to limit the length of an
+ * individual text item to 63 characters, to conform to the limit
+ * used by MS Web Folders.
+ */
+ void (*get_vsn_options)(apr_pool_t *p, apr_text_header *phdr);
+
+ /* Get the value of a specific option for an OPTIONS request.
+ * The option being requested is given by the parsed XML
+ * element object "elem". The value of the option should be
+ * appended to the "option" text object.
+ */
+ dav_error * (*get_option)(const dav_resource *resource,
+ const apr_xml_elem *elem,
+ apr_text_header *option);
+
+ /* Determine whether a non-versioned (or non-existent) resource
+ * is versionable. Returns != 0 if resource can be versioned.
+ */
+ int (*versionable)(const dav_resource *resource);
+
+ /* Determine whether auto-versioning is enabled for a resource
+ * (which may not exist, or may not be versioned). If the resource
+ * is a checked-out resource, the provider must only enable
+ * auto-checkin if the resource was automatically checked out.
+ *
+ * The value returned depends on both the state of the resource
+ * and the value of its DAV:auto-version property. See the description
+ * of the dav_auto_version enumeration above for the details.
+ */
+ dav_auto_version (*auto_versionable)(const dav_resource *resource);
+
+ /* Put a resource under version control. If the resource already
+ * exists unversioned, then it becomes the initial version of the
+ * new version history, and it is replaced by a version selector
+ * which targets the new version.
+ *
+ * If the resource does not exist, then a new version-controlled
+ * resource is created which either targets an existing version (if the
+ * "target" argument is not NULL), or the initial, empty version
+ * in a new history resource (if the "target" argument is NULL).
+ *
+ * If successful, the resource object state is updated appropriately
+ * (that is, changed to refer to the new version-controlled resource).
+ */
+ dav_error * (*vsn_control)(dav_resource *resource,
+ const char *target);
+
+ /* Checkout a resource. If successful, the resource
+ * object state is updated appropriately.
+ *
+ * The auto_checkout flag will be set if this checkout is being
+ * done automatically, as part of some method which modifies
+ * the resource. The provider must remember that the resource
+ * was automatically checked out, so it can determine whether it
+ * can be automatically checked in. (Auto-checkin should only be
+ * enabled for resources which were automatically checked out.)
+ *
+ * If the working resource has a different URL from the
+ * target resource, a dav_resource descriptor is returned
+ * for the new working resource. Otherwise, the original
+ * resource descriptor will refer to the working resource.
+ * The working_resource argument can be NULL if the caller
+ * is not interested in the working resource.
+ *
+ * If the client has specified DAV:unreserved or DAV:fork-ok in the
+ * checkout request, then the corresponding flags are set. If
+ * DAV:activity-set has been specified, then create_activity is set
+ * if DAV:new was specified; otherwise, the DAV:href elements' CDATA
+ * (the actual href text) is passed in the "activities" array (each
+ * element of the array is a const char *). activities will be NULL
+ * no DAV:activity-set was provided or when create_activity is set.
+ */
+ dav_error * (*checkout)(dav_resource *resource,
+ int auto_checkout,
+ int is_unreserved, int is_fork_ok,
+ int create_activity,
+ apr_array_header_t *activities,
+ dav_resource **working_resource);
+
+ /* Uncheckout a checked-out resource. If successful, the resource
+ * object state is updated appropriately.
+ */
+ dav_error * (*uncheckout)(dav_resource *resource);
+
+ /* Checkin a checked-out resource. If successful, the resource
+ * object state is updated appropriately, and the
+ * version_resource descriptor will refer to the new version.
+ * The version_resource argument can be NULL if the caller
+ * is not interested in the new version resource.
+ *
+ * If the client has specified DAV:keep-checked-out in the checkin
+ * request, then the keep_checked_out flag is set. The provider
+ * should create a new version, but keep the resource in the
+ * checked-out state.
+ */
+ dav_error * (*checkin)(dav_resource *resource,
+ int keep_checked_out,
+ dav_resource **version_resource);
+
+ /*
+ ** Return the set of reports available at this resource.
+ **
+ ** An array of report elements should be returned, with an end-marker
+ ** element containing namespace==NULL. The value of the
+ ** DAV:supported-report-set property will be constructed and
+ ** returned.
+ */
+ dav_error * (*avail_reports)(const dav_resource *resource,
+ const dav_report_elem **reports);
+
+ /*
+ ** Determine whether a Label header can be used
+ ** with a particular report. The dav_xml_doc structure
+ ** contains the parsed report request body.
+ ** Returns 0 if the Label header is not allowed.
+ */
+ int (*report_label_header_allowed)(const apr_xml_doc *doc);
+
+ /*
+ ** Generate a report on a resource. Since a provider is free
+ ** to define its own reports, and the value of request headers
+ ** may affect the interpretation of a report, the request record
+ ** must be passed to this routine.
+ **
+ ** The dav_xml_doc structure contains the parsed report request
+ ** body. The report response should be generated into the specified
+ ** output filter.
+ **
+ ** If an error occurs, and a response has not yet been generated,
+ ** then an error can be returned from this function. mod_dav will
+ ** construct an appropriate error response. Once some output has
+ ** been placed into the filter, however, the provider should not
+ ** return an error -- there is no way that mod_dav can deliver it
+ ** properly.
+ **
+ ** ### maybe we need a way to signal an error anyways, and then
+ ** ### apache can abort the connection?
+ */
+ dav_error * (*deliver_report)(request_rec *r,
+ const dav_resource *resource,
+ const apr_xml_doc *doc,
+ ap_filter_t *output);
+
+ /*
+ ** OPTIONAL HOOKS
+ ** The following hooks are optional; if not defined, then the
+ ** corresponding protocol methods will be unsupported.
+ */
+
+ /*
+ ** Set the state of a checked-in version-controlled resource.
+ **
+ ** If the request specified a version, the version resource
+ ** represents that version. If the request specified a label,
+ ** then "version" is NULL, and "label" is the label.
+ **
+ ** The depth argument is ignored for a file, and can be 0, 1, or
+ ** DAV_INFINITY for a collection. The depth argument only applies
+ ** with a label, not a version.
+ **
+ ** If an error occurs in a child resource, then the return value is
+ ** non-NULL, and *response is set to a multistatus response.
+ **
+ ** This hook is optional; if not defined, then the UPDATE method
+ ** will not be supported.
+ */
+ dav_error * (*update)(const dav_resource *resource,
+ const dav_resource *version,
+ const char *label,
+ int depth,
+ dav_response **response);
+
+ /*
+ ** Add a label to a version. The resource is either a specific
+ ** version, or a version selector, in which case the label should
+ ** be added to the current target of the version selector. The
+ ** version selector cannot be checked out.
+ **
+ ** If replace != 0, any existing label by the same name is
+ ** effectively deleted first. Otherwise, it is an error to
+ ** attempt to add a label which already exists on some version
+ ** of the same history resource.
+ **
+ ** This hook is optional; if not defined, then the LABEL method
+ ** will not be supported. If it is defined, then the remove_label
+ ** hook must be defined also.
+ */
+ dav_error * (*add_label)(const dav_resource *resource,
+ const char *label,
+ int replace);
+
+ /*
+ ** Remove a label from a version. The resource is either a specific
+ ** version, or a version selector, in which case the label should
+ ** be added to the current target of the version selector. The
+ ** version selector cannot be checked out.
+ **
+ ** It is an error if no such label exists on the specified version.
+ **
+ ** This hook is optional, but if defined, the add_label hook
+ ** must be defined also.
+ */
+ dav_error * (*remove_label)(const dav_resource *resource,
+ const char *label);
+
+ /*
+ ** Determine whether a null resource can be created as a workspace.
+ ** The provider may restrict workspaces to certain locations.
+ ** Returns 0 if the resource cannot be a workspace.
+ **
+ ** This hook is optional; if the provider does not support workspaces,
+ ** it should be set to NULL.
+ */
+ int (*can_be_workspace)(const dav_resource *resource);
+
+ /*
+ ** Create a workspace resource. The resource must not already
+ ** exist. Any <DAV:mkworkspace> element is passed to the provider
+ ** in the "doc" structure; it may be empty.
+ **
+ ** If workspace creation is successful, the state of the resource
+ ** object is updated appropriately.
+ **
+ ** This hook is optional; if the provider does not support workspaces,
+ ** it should be set to NULL.
+ */
+ dav_error * (*make_workspace)(dav_resource *resource,
+ apr_xml_doc *doc);
+
+ /*
+ ** Determine whether a null resource can be created as an activity.
+ ** The provider may restrict activities to certain locations.
+ ** Returns 0 if the resource cannot be an activity.
+ **
+ ** This hook is optional; if the provider does not support activities,
+ ** it should be set to NULL.
+ */
+ int (*can_be_activity)(const dav_resource *resource);
+
+ /*
+ ** Create an activity resource. The resource must not already
+ ** exist.
+ **
+ ** If activity creation is successful, the state of the resource
+ ** object is updated appropriately.
+ **
+ ** This hook is optional; if the provider does not support activities,
+ ** it should be set to NULL.
+ */
+ dav_error * (*make_activity)(dav_resource *resource);
+
+ /*
+ ** Merge a resource (tree) into target resource (tree).
+ **
+ ** ### more doc...
+ **
+ ** This hook is optional; if the provider does not support merging,
+ ** then this should be set to NULL.
+ */
+ dav_error * (*merge)(dav_resource *target, dav_resource *source,
+ int no_auto_merge, int no_checkout,
+ apr_xml_elem *prop_elem,
+ ap_filter_t *output);
+
+ /*
+ ** If a provider needs a context to associate with this hooks structure,
+ ** then this field may be used. In most cases, it will just be NULL.
+ */
+ void *ctx;
+};
+
+
+/* --------------------------------------------------------------------
+**
+** BINDING FUNCTIONS
+*/
+
+/* binding provider hooks */
+struct dav_hooks_binding {
+
+ /* Determine whether a resource can be the target of a binding.
+ * Returns 0 if the resource cannot be a binding target.
+ */
+ int (*is_bindable)(const dav_resource *resource);
+
+ /* Create a binding to a resource.
+ * The resource argument is the target of the binding;
+ * the binding argument must be a resource which does not already
+ * exist.
+ */
+ dav_error * (*bind_resource)(const dav_resource *resource,
+ dav_resource *binding);
+
+ /*
+ ** If a provider needs a context to associate with this hooks structure,
+ ** then this field may be used. In most cases, it will just be NULL.
+ */
+ void *ctx;
+
+};
+
+
+/* --------------------------------------------------------------------
+**
+** SEARCH(DASL) FUNCTIONS
+*/
+
+/* search provider hooks */
+struct dav_hooks_search {
+ /* Set header for a OPTION method
+ * An error may be returned.
+ * To set a hadder, this function might call
+ * apr_table_setn(r->headers_out, "DASL", dasl_optin1);
+ *
+ * Examples:
+ * DASL: <DAV:basicsearch>
+ * DASL: <http://foo.bar.com/syntax1>
+ * DASL: <http://akuma.com/syntax2>
+ */
+ dav_error * (*set_option_head)(request_rec *r);
+
+ /* Search resources
+ * An error may be returned. *response will contain multistatus
+ * responses (if any) suitable for the body of the error. It is also
+ * possible to return NULL, yet still have multistatus responses.
+ * In this case, typically the caller should return a 207 (Multistatus)
+ * and the responses (in the body) as the HTTP response.
+ */
+ dav_error * (*search_resource)(request_rec *r,
+ dav_response **response);
+
+ /*
+ ** If a provider needs a context to associate with this hooks structure,
+ ** then this field may be used. In most cases, it will just be NULL.
+ */
+ void *ctx;
+
+};
+
+
+/* --------------------------------------------------------------------
+**
+** MISCELLANEOUS STUFF
+*/
+
+typedef struct {
+ int propid; /* live property ID */
+ const dav_hooks_liveprop *provider; /* the provider defining this prop */
+} dav_elem_private;
+
+/* --------------------------------------------------------------------
+**
+** DAV OPTIONS
+*/
+#define DAV_OPTIONS_EXTENSION_GROUP "dav_options"
+
+typedef struct dav_options_provider
+{
+ dav_error* (*dav_header)(request_rec *r,
+ const dav_resource *resource,
+ apr_text_header *phdr);
+
+ dav_error* (*dav_method)(request_rec *r,
+ const dav_resource *resource,
+ apr_text_header *phdr);
+
+ void *ctx;
+} dav_options_provider;
+
+extern DAV_DECLARE(const dav_options_provider *) dav_get_options_providers(const char *name);
+
+extern DAV_DECLARE(void) dav_options_provider_register(apr_pool_t *p,
+ const char *name,
+ const dav_options_provider *provider);
+
+/* --------------------------------------------------------------------
+**
+** DAV RESOURCE TYPE HOOKS
+*/
+
+typedef struct dav_resource_type_provider
+{
+ int (*get_resource_type)(const dav_resource *resource,
+ const char **name,
+ const char **uri);
+} dav_resource_type_provider;
+
+#define DAV_RESOURCE_TYPE_GROUP "dav_resource_type"
+
+DAV_DECLARE(void) dav_resource_type_provider_register(apr_pool_t *p,
+ const char *name,
+ const dav_resource_type_provider *provider);
+
+DAV_DECLARE(const dav_resource_type_provider *) dav_get_resource_type_providers(const char *name);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _MOD_DAV_H_ */
+/** @} */
+
diff --git a/modules/dav/main/mod_dav.mak b/modules/dav/main/mod_dav.mak
new file mode 100644
index 0000000..a107e22
--- /dev/null
+++ b/modules/dav/main/mod_dav.mak
@@ -0,0 +1,406 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_dav.dsp
+!IF "$(CFG)" == ""
+CFG=mod_dav - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_dav - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_dav - Win32 Release" && "$(CFG)" != "mod_dav - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_dav.mak" CFG="mod_dav - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_dav - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_dav - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_dav - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_dav.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_dav.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\liveprop.obj"
+ -@erase "$(INTDIR)\mod_dav.obj"
+ -@erase "$(INTDIR)\mod_dav.res"
+ -@erase "$(INTDIR)\mod_dav_src.idb"
+ -@erase "$(INTDIR)\mod_dav_src.pdb"
+ -@erase "$(INTDIR)\props.obj"
+ -@erase "$(INTDIR)\providers.obj"
+ -@erase "$(INTDIR)\std_liveprop.obj"
+ -@erase "$(INTDIR)\util.obj"
+ -@erase "$(INTDIR)\util_lock.obj"
+ -@erase "$(OUTDIR)\mod_dav.exp"
+ -@erase "$(OUTDIR)\mod_dav.lib"
+ -@erase "$(OUTDIR)\mod_dav.pdb"
+ -@erase "$(OUTDIR)\mod_dav.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "DAV_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_dav_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_dav.res" /i "../../../include" /i "../../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_dav.so" /d LONG_NAME="dav_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_dav.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_dav.pdb" /debug /out:"$(OUTDIR)\mod_dav.so" /implib:"$(OUTDIR)\mod_dav.lib" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\liveprop.obj" \
+ "$(INTDIR)\mod_dav.obj" \
+ "$(INTDIR)\props.obj" \
+ "$(INTDIR)\providers.obj" \
+ "$(INTDIR)\std_liveprop.obj" \
+ "$(INTDIR)\util.obj" \
+ "$(INTDIR)\util_lock.obj" \
+ "$(INTDIR)\mod_dav.res" \
+ "..\..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_dav.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_dav.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_dav.so"
+ if exist .\Release\mod_dav.so.manifest mt.exe -manifest .\Release\mod_dav.so.manifest -outputresource:.\Release\mod_dav.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_dav - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_dav.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_dav.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\liveprop.obj"
+ -@erase "$(INTDIR)\mod_dav.obj"
+ -@erase "$(INTDIR)\mod_dav.res"
+ -@erase "$(INTDIR)\mod_dav_src.idb"
+ -@erase "$(INTDIR)\mod_dav_src.pdb"
+ -@erase "$(INTDIR)\props.obj"
+ -@erase "$(INTDIR)\providers.obj"
+ -@erase "$(INTDIR)\std_liveprop.obj"
+ -@erase "$(INTDIR)\util.obj"
+ -@erase "$(INTDIR)\util_lock.obj"
+ -@erase "$(OUTDIR)\mod_dav.exp"
+ -@erase "$(OUTDIR)\mod_dav.lib"
+ -@erase "$(OUTDIR)\mod_dav.pdb"
+ -@erase "$(OUTDIR)\mod_dav.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "DAV_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_dav_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_dav.res" /i "../../../include" /i "../../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_dav.so" /d LONG_NAME="dav_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_dav.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_dav.pdb" /debug /out:"$(OUTDIR)\mod_dav.so" /implib:"$(OUTDIR)\mod_dav.lib" /base:@..\..\..\os\win32\BaseAddr.ref,mod_dav.so
+LINK32_OBJS= \
+ "$(INTDIR)\liveprop.obj" \
+ "$(INTDIR)\mod_dav.obj" \
+ "$(INTDIR)\props.obj" \
+ "$(INTDIR)\providers.obj" \
+ "$(INTDIR)\std_liveprop.obj" \
+ "$(INTDIR)\util.obj" \
+ "$(INTDIR)\util_lock.obj" \
+ "$(INTDIR)\mod_dav.res" \
+ "..\..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_dav.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_dav.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_dav.so"
+ if exist .\Debug\mod_dav.so.manifest mt.exe -manifest .\Debug\mod_dav.so.manifest -outputresource:.\Debug\mod_dav.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_dav.dep")
+!INCLUDE "mod_dav.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_dav.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_dav - Win32 Release" || "$(CFG)" == "mod_dav - Win32 Debug"
+SOURCE=.\liveprop.c
+
+"$(INTDIR)\liveprop.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\mod_dav.c
+
+"$(INTDIR)\mod_dav.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\props.c
+
+"$(INTDIR)\props.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\providers.c
+
+"$(INTDIR)\providers.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\std_liveprop.c
+
+"$(INTDIR)\std_liveprop.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\util.c
+
+"$(INTDIR)\util.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\util_lock.c
+
+"$(INTDIR)\util_lock.obj" : $(SOURCE) "$(INTDIR)"
+
+
+!IF "$(CFG)" == "mod_dav - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\dav\main"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\dav\main"
+
+!ELSEIF "$(CFG)" == "mod_dav - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\dav\main"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\dav\main"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_dav - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\dav\main"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\dav\main"
+
+!ELSEIF "$(CFG)" == "mod_dav - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\dav\main"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\dav\main"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_dav - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\dav\main"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\dav\main"
+
+!ELSEIF "$(CFG)" == "mod_dav - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\dav\main"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\dav\main"
+
+!ENDIF
+
+SOURCE=..\..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_dav - Win32 Release"
+
+
+"$(INTDIR)\mod_dav.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_dav.res" /i "../../../include" /i "../../../srclib/apr/include" /i "../../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_dav.so" /d LONG_NAME="dav_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_dav - Win32 Debug"
+
+
+"$(INTDIR)\mod_dav.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_dav.res" /i "../../../include" /i "../../../srclib/apr/include" /i "../../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_dav.so" /d LONG_NAME="dav_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+
+!ENDIF
+
diff --git a/modules/dav/main/props.c b/modules/dav/main/props.c
new file mode 100644
index 0000000..c320f8a
--- /dev/null
+++ b/modules/dav/main/props.c
@@ -0,0 +1,1179 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+** DAV extension module for Apache 2.0.*
+** - Property database handling (repository-independent)
+**
+** NOTES:
+**
+** PROPERTY DATABASE
+**
+** This version assumes that there is a per-resource database provider
+** to record properties. The database provider decides how and where to
+** store these databases.
+**
+** The DBM keys for the properties have the following form:
+**
+** namespace ":" propname
+**
+** For example: 5:author
+**
+** The namespace provides an integer index into the namespace table
+** (see below). propname is simply the property name, without a namespace
+** prefix.
+**
+** A special case exists for properties that had a prefix starting with
+** "xml". The XML Specification reserves these for future use. mod_dav
+** stores and retrieves them unchanged. The keys for these properties
+** have the form:
+**
+** ":" propname
+**
+** The propname will contain the prefix and the property name. For
+** example, a key might be ":xmlfoo:name"
+**
+** The ":name" style will also be used for properties that do not
+** exist within a namespace.
+**
+** The DBM values consist of two null-terminated strings, appended
+** together (the null-terms are retained and stored in the database).
+** The first string is the xml:lang value for the property. An empty
+** string signifies that a lang value was not in context for the value.
+** The second string is the property value itself.
+**
+**
+** NAMESPACE TABLE
+**
+** The namespace table is an array that lists each of the namespaces
+** that are in use by the properties in the given propdb. Each entry
+** in the array is a simple URI.
+**
+** For example: http://www.foo.bar/standards/props/
+**
+** The prefix used for the property is stripped and the URI for it
+** is entered into the namespace table. Also, any namespaces used
+** within the property value will be entered into the table (and
+** stripped from the child elements).
+**
+** The namespaces are stored in the DBM database under the "METADATA" key.
+**
+**
+** STRIPPING NAMESPACES
+**
+** Within the property values, the namespace declarations (xmlns...)
+** are stripped. Each element and attribute will have its prefix removed
+** and a new prefix inserted.
+**
+** This must be done so that we can return multiple properties in a
+** PROPFIND which may have (originally) used conflicting prefixes. For
+** that case, we must bind all property value elements to new namespace
+** values.
+**
+** This implies that clients must NOT be sensitive to the namespace
+** prefix used for their properties. It WILL change when the properties
+** are returned (we return them as "ns<index>", e.g. "ns5"). Also, the
+** property value can contain ONLY XML elements and CDATA. PI and comment
+** elements will be stripped. CDATA whitespace will be preserved, but
+** whitespace within element tags will be altered. Attribute ordering
+** may be altered. Element and CDATA ordering will be preserved.
+**
+**
+** ATTRIBUTES ON PROPERTY NAME ELEMENTS
+**
+** When getting/setting properties, the XML used looks like:
+**
+** <prop>
+** <propname1>value</propname1>
+** <propname2>value</propname1>
+** </prop>
+**
+** This implementation (mod_dav) DOES NOT save any attributes that are
+** associated with the <propname1> element. The property value is deemed
+** to be only the contents ("value" in the above example).
+**
+** We do store the xml:lang value (if any) that applies to the context
+** of the <propname1> element. Whether the xml:lang attribute is on
+** <propname1> itself, or from a higher level element, we will store it
+** with the property value.
+**
+**
+** VERSIONING
+**
+** The DBM db contains a key named "METADATA" that holds database-level
+** information, such as the namespace table. The record also contains the
+** db's version number as the very first 16-bit value. This first number
+** is actually stored as two single bytes: the first byte is a "major"
+** version number. The second byte is a "minor" number.
+**
+** If the major number is not what mod_dav expects, then the db is closed
+** immediately and an error is returned. A minor number change is
+** acceptable -- it is presumed that old/new dav_props.c can deal with
+** the database format. For example, a newer dav_props might update the
+** minor value and append information to the end of the metadata record
+** (which would be ignored by previous versions).
+**
+**
+** ISSUES:
+**
+** At the moment, for the dav_get_allprops() and dav_get_props() functions,
+** we must return a set of xmlns: declarations for ALL known namespaces
+** in the file. There isn't a way to filter this because we don't know
+** which are going to be used or not. Examining property names is not
+** sufficient because the property values could use entirely different
+** namespaces.
+**
+** ==> we must devise a scheme where we can "garbage collect" the namespace
+** entries from the property database.
+*/
+
+#include "apr.h"
+#include "apr_strings.h"
+
+#define APR_WANT_STDIO
+#define APR_WANT_BYTEFUNC
+#include "apr_want.h"
+
+#include "mod_dav.h"
+
+#include "http_log.h"
+#include "http_request.h"
+
+/*
+** There is some rough support for writable DAV:getcontenttype and
+** DAV:getcontentlanguage properties. If this #define is (1), then
+** this support is disabled.
+**
+** We are disabling it because of a lack of support in GET and PUT
+** operations. For GET, it would be "expensive" to look for a propdb,
+** open it, and attempt to extract the Content-Type and Content-Language
+** values for the response.
+** (Handling the PUT would not be difficult, though)
+*/
+#define DAV_DISABLE_WRITABLE_PROPS 1
+
+#define DAV_EMPTY_VALUE "\0" /* TWO null terms */
+
+#define DAV_PROP_ELEMENT "mod_dav-element"
+
+struct dav_propdb {
+ apr_pool_t *p; /* the pool we should use */
+ request_rec *r; /* the request record */
+
+ const dav_resource *resource; /* the target resource */
+
+ int deferred; /* open of db has been deferred */
+ dav_db *db; /* underlying database containing props */
+
+ apr_array_header_t *ns_xlate; /* translation of an elem->ns to URI */
+ dav_namespace_map *mapping; /* namespace mapping */
+
+ dav_lockdb *lockdb; /* the lock database */
+
+ dav_buffer wb_lock; /* work buffer for lockdiscovery property */
+
+ int flags; /* ro, disable lock discovery */
+
+ /* if we ever run a GET subreq, it will be stored here */
+ request_rec *subreq;
+
+ /* hooks we should use for processing (based on the target resource) */
+ const dav_hooks_db *db_hooks;
+};
+
+/* NOTE: dav_core_props[] and the following enum must stay in sync. */
+/* ### move these into a "core" liveprop provider? */
+static const char * const dav_core_props[] =
+{
+ "getcontenttype",
+ "getcontentlanguage",
+ "lockdiscovery",
+ "supportedlock",
+
+ NULL /* sentinel */
+};
+enum {
+ DAV_PROPID_CORE_getcontenttype = DAV_PROPID_CORE,
+ DAV_PROPID_CORE_getcontentlanguage,
+ DAV_PROPID_CORE_lockdiscovery,
+ DAV_PROPID_CORE_supportedlock,
+
+ DAV_PROPID_CORE_UNKNOWN
+};
+
+/*
+** This structure is used to track information needed for a rollback.
+*/
+typedef struct dav_rollback_item {
+ /* select one of the two rollback context structures based on the
+ value of dav_prop_ctx.is_liveprop */
+ dav_deadprop_rollback *deadprop;
+ dav_liveprop_rollback *liveprop;
+
+} dav_rollback_item;
+
+
+static int dav_find_liveprop_provider(dav_propdb *propdb,
+ const char *ns_uri,
+ const char *propname,
+ const dav_hooks_liveprop **provider)
+{
+ int propid;
+
+ *provider = NULL;
+
+ if (ns_uri == NULL) {
+ /* policy: liveprop providers cannot define no-namespace properties */
+ return DAV_PROPID_CORE_UNKNOWN;
+ }
+
+ /* check liveprop providers first, so they can define core properties */
+ propid = dav_run_find_liveprop(propdb->resource, ns_uri, propname,
+ provider);
+ if (propid != 0) {
+ return propid;
+ }
+
+ /* check for core property */
+ if (strcmp(ns_uri, "DAV:") == 0) {
+ const char * const *p = dav_core_props;
+
+ for (propid = DAV_PROPID_CORE; *p != NULL; ++p, ++propid)
+ if (strcmp(propname, *p) == 0) {
+ return propid;
+ }
+ }
+
+ /* no provider for this property */
+ return DAV_PROPID_CORE_UNKNOWN;
+}
+
+static void dav_find_liveprop(dav_propdb *propdb, apr_xml_elem *elem)
+{
+ const char *ns_uri;
+ dav_elem_private *priv = elem->priv;
+ const dav_hooks_liveprop *hooks;
+
+
+ if (elem->ns == APR_XML_NS_NONE)
+ ns_uri = NULL;
+ else if (elem->ns == APR_XML_NS_DAV_ID)
+ ns_uri = "DAV:";
+ else
+ ns_uri = APR_XML_GET_URI_ITEM(propdb->ns_xlate, elem->ns);
+
+ priv->propid = dav_find_liveprop_provider(propdb, ns_uri, elem->name,
+ &hooks);
+
+ /* ### this test seems redundant... */
+ if (priv->propid != DAV_PROPID_CORE_UNKNOWN) {
+ priv->provider = hooks;
+ }
+}
+
+/* is the live property read/write? */
+static int dav_rw_liveprop(dav_propdb *propdb, dav_elem_private *priv)
+{
+ int propid = priv->propid;
+
+ /*
+ ** Check the liveprop provider (if this is a provider-defined prop)
+ */
+ if (priv->provider != NULL) {
+ return (*priv->provider->is_writable)(propdb->resource, propid);
+ }
+
+ /* these are defined as read-only */
+ if (propid == DAV_PROPID_CORE_lockdiscovery
+#if DAV_DISABLE_WRITABLE_PROPS
+ || propid == DAV_PROPID_CORE_getcontenttype
+ || propid == DAV_PROPID_CORE_getcontentlanguage
+#endif
+ || propid == DAV_PROPID_CORE_supportedlock
+ ) {
+
+ return 0;
+ }
+
+ /* these are defined as read/write */
+ if (propid == DAV_PROPID_CORE_getcontenttype
+ || propid == DAV_PROPID_CORE_getcontentlanguage
+ || propid == DAV_PROPID_CORE_UNKNOWN) {
+
+ return 1;
+ }
+
+ /*
+ ** We don't recognize the property, so it must be dead (and writable)
+ */
+ return 1;
+}
+
+/* do a sub-request to fetch properties for the target resource's URI. */
+static void dav_do_prop_subreq(dav_propdb *propdb)
+{
+ /* need to escape the uri that's in the resource struct because during
+ * the property walker it's not encoded. */
+ const char *e_uri = ap_escape_uri(propdb->p,
+ propdb->resource->uri);
+
+ /* perform a "GET" on the resource's URI (note that the resource
+ may not correspond to the current request!). */
+ propdb->subreq = ap_sub_req_lookup_uri(e_uri, propdb->r, NULL);
+}
+
+static dav_error * dav_insert_coreprop(dav_propdb *propdb,
+ int propid, const char *name,
+ dav_prop_insert what,
+ apr_text_header *phdr,
+ dav_prop_insert *inserted)
+{
+ const char *value = NULL;
+ dav_error *err;
+
+ *inserted = DAV_PROP_INSERT_NOTDEF;
+
+ /* fast-path the common case */
+ if (propid == DAV_PROPID_CORE_UNKNOWN)
+ return NULL;
+
+ switch (propid) {
+
+ case DAV_PROPID_CORE_lockdiscovery:
+ if (propdb->flags & DAV_PROPDB_DISABLE_LOCKDISCOVERY) {
+ value = "";
+ break;
+ }
+
+ if (propdb->lockdb != NULL) {
+ dav_lock *locks;
+
+ if ((err = dav_lock_query(propdb->lockdb, propdb->resource,
+ &locks)) != NULL) {
+ return dav_push_error(propdb->p, err->status, 0,
+ "DAV:lockdiscovery could not be "
+ "determined due to a problem fetching "
+ "the locks for this resource.",
+ err);
+ }
+
+ /* fast-path the no-locks case */
+ if (locks == NULL) {
+ value = "";
+ }
+ else {
+ /*
+ ** This may modify the buffer. value may point to
+ ** wb_lock.pbuf or a string constant.
+ */
+ value = dav_lock_get_activelock(propdb->r, locks,
+ &propdb->wb_lock);
+
+ /* make a copy to isolate it from changes to wb_lock */
+ value = apr_pstrdup(propdb->p, propdb->wb_lock.buf);
+ }
+ }
+ break;
+
+ case DAV_PROPID_CORE_supportedlock:
+ if (propdb->lockdb != NULL) {
+ value = (*propdb->lockdb->hooks->get_supportedlock)(propdb->resource);
+ }
+ break;
+
+ case DAV_PROPID_CORE_getcontenttype:
+ if (propdb->subreq == NULL) {
+ dav_do_prop_subreq(propdb);
+ }
+ if (propdb->subreq->content_type != NULL) {
+ value = propdb->subreq->content_type;
+ }
+ break;
+
+ case DAV_PROPID_CORE_getcontentlanguage:
+ {
+ const char *lang;
+
+ if (propdb->subreq == NULL) {
+ dav_do_prop_subreq(propdb);
+ }
+ if ((lang = apr_table_get(propdb->subreq->headers_out,
+ "Content-Language")) != NULL) {
+ value = lang;
+ }
+ break;
+ }
+
+ default:
+ /* fall through to interpret as a dead property */
+ break;
+ }
+
+ /* if something was supplied, then insert it */
+ if (value != NULL) {
+ const char *s;
+
+ if (what == DAV_PROP_INSERT_SUPPORTED) {
+ /* use D: prefix to refer to the DAV: namespace URI,
+ * and let the namespace attribute default to "DAV:"
+ */
+ s = apr_pstrcat(propdb->p,
+ "<D:supported-live-property D:name=\"",
+ name, "\"/>" DEBUG_CR, NULL);
+ }
+ else if (what == DAV_PROP_INSERT_VALUE && *value != '\0') {
+ /* use D: prefix to refer to the DAV: namespace URI */
+ s = apr_pstrcat(propdb->p, "<D:", name, ">", value, "</D:", name,
+ ">" DEBUG_CR, NULL);
+ }
+ else {
+ /* use D: prefix to refer to the DAV: namespace URI */
+ s = apr_pstrcat(propdb->p, "<D:", name, "/>" DEBUG_CR, NULL);
+ }
+ apr_text_append(propdb->p, phdr, s);
+
+ *inserted = what;
+ }
+
+ return NULL;
+}
+
+static dav_error * dav_insert_liveprop(dav_propdb *propdb,
+ const apr_xml_elem *elem,
+ dav_prop_insert what,
+ apr_text_header *phdr,
+ dav_prop_insert *inserted)
+{
+ dav_elem_private *priv = elem->priv;
+
+ *inserted = DAV_PROP_INSERT_NOTDEF;
+
+ if (priv->provider == NULL) {
+ /* this is a "core" property that we define */
+ return dav_insert_coreprop(propdb, priv->propid, elem->name,
+ what, phdr, inserted);
+ }
+
+ /* ask the provider (that defined this prop) to insert the prop */
+ *inserted = (*priv->provider->insert_prop)(propdb->resource, priv->propid,
+ what, phdr);
+
+ return NULL;
+}
+
+static void dav_output_prop_name(apr_pool_t *pool,
+ const dav_prop_name *name,
+ dav_xmlns_info *xi,
+ apr_text_header *phdr)
+{
+ const char *s;
+
+ if (*name->ns == '\0')
+ s = apr_pstrcat(pool, "<", name->name, "/>" DEBUG_CR, NULL);
+ else {
+ const char *prefix = dav_xmlns_add_uri(xi, name->ns);
+
+ s = apr_pstrcat(pool, "<", prefix, ":", name->name, "/>" DEBUG_CR, NULL);
+ }
+
+ apr_text_append(pool, phdr, s);
+}
+
+static void dav_insert_xmlns(apr_pool_t *p, const char *pre_prefix, long ns,
+ const char *ns_uri, apr_text_header *phdr)
+{
+ const char *s;
+
+ s = apr_psprintf(p, " xmlns:%s%ld=\"%s\"", pre_prefix, ns, ns_uri);
+ apr_text_append(p, phdr, s);
+}
+
+static dav_error *dav_really_open_db(dav_propdb *propdb, int ro)
+{
+ dav_error *err;
+
+ /* we're trying to open the db; turn off the 'deferred' flag */
+ propdb->deferred = 0;
+
+ /* ask the DB provider to open the thing */
+ err = (*propdb->db_hooks->open)(propdb->p, propdb->resource, ro,
+ &propdb->db);
+ if (err != NULL) {
+ return dav_push_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_PROP_OPENING,
+ "Could not open the property database.",
+ err);
+ }
+
+ /*
+ ** NOTE: propdb->db could be NULL if we attempted to open a readonly
+ ** database that doesn't exist. If we require read/write
+ ** access, then a database was created and opened.
+ */
+
+ return NULL;
+}
+
+DAV_DECLARE(dav_error *)dav_open_propdb(request_rec *r, dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int flags,
+ apr_array_header_t * ns_xlate,
+ dav_propdb **p_propdb)
+{
+ return dav_popen_propdb(r->pool, r, lockdb, resource,
+ flags, ns_xlate, p_propdb);
+}
+
+DAV_DECLARE(dav_error *)dav_popen_propdb(apr_pool_t *p,
+ request_rec *r, dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int flags,
+ apr_array_header_t * ns_xlate,
+ dav_propdb **p_propdb)
+{
+ dav_propdb *propdb = NULL;
+
+ propdb = apr_pcalloc(p, sizeof(*propdb));
+ propdb->p = p;
+
+ *p_propdb = NULL;
+
+#if DAV_DEBUG
+ if (resource->uri == NULL) {
+ return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "INTERNAL DESIGN ERROR: resource must define "
+ "its URI.");
+ }
+#endif
+
+ propdb->r = r;
+ propdb->resource = resource;
+ propdb->ns_xlate = ns_xlate;
+
+ propdb->db_hooks = DAV_GET_HOOKS_PROPDB(r);
+
+ propdb->lockdb = lockdb;
+
+ propdb->flags = flags;
+
+ /* always defer actual open, to avoid expense of accessing db
+ * when only live properties are involved
+ */
+ propdb->deferred = 1;
+
+ /* ### what to do about closing the propdb on server failure? */
+
+ *p_propdb = propdb;
+ return NULL;
+}
+
+DAV_DECLARE(void) dav_close_propdb(dav_propdb *propdb)
+{
+ if (propdb->db != NULL) {
+ (*propdb->db_hooks->close)(propdb->db);
+ }
+
+ if (propdb->subreq) {
+ ap_destroy_sub_req(propdb->subreq);
+ propdb->subreq = NULL;
+ }
+}
+
+DAV_DECLARE(dav_get_props_result) dav_get_allprops(dav_propdb *propdb,
+ dav_prop_insert what)
+{
+ const dav_hooks_db *db_hooks = propdb->db_hooks;
+ apr_text_header hdr = { 0 };
+ apr_text_header hdr_ns = { 0 };
+ dav_get_props_result result = { 0 };
+ int found_contenttype = 0;
+ int found_contentlang = 0;
+ dav_prop_insert unused_inserted;
+
+ /* if not just getting supported live properties,
+ * scan all properties in the dead prop database
+ */
+ if (what != DAV_PROP_INSERT_SUPPORTED) {
+ if (propdb->deferred) {
+ /* ### what to do with db open error? */
+ (void) dav_really_open_db(propdb, 1 /*ro*/);
+ }
+
+ /* initialize the result with some start tags... */
+ apr_text_append(propdb->p, &hdr,
+ "<D:propstat>" DEBUG_CR
+ "<D:prop>" DEBUG_CR);
+
+ /* if there ARE properties, then scan them */
+ if (propdb->db != NULL) {
+ dav_xmlns_info *xi = dav_xmlns_create(propdb->p);
+ dav_prop_name name;
+ dav_error *err;
+
+ /* define (up front) any namespaces the db might need */
+ (void) (*db_hooks->define_namespaces)(propdb->db, xi);
+
+ /* get the first property name, beginning the scan */
+ err = (*db_hooks->first_name)(propdb->db, &name);
+ while (!err && name.ns) {
+
+ /*
+ ** We also look for <DAV:getcontenttype> and
+ ** <DAV:getcontentlanguage>. If they are not stored as dead
+ ** properties, then we need to perform a subrequest to get
+ ** their values (if any).
+ */
+ if (*name.ns == 'D' && strcmp(name.ns, "DAV:") == 0
+ && *name.name == 'g') {
+ if (strcmp(name.name, "getcontenttype") == 0) {
+ found_contenttype = 1;
+ }
+ else if (strcmp(name.name, "getcontentlanguage") == 0) {
+ found_contentlang = 1;
+ }
+ }
+
+ if (what == DAV_PROP_INSERT_VALUE) {
+ int found;
+
+ if ((err = (*db_hooks->output_value)(propdb->db, &name,
+ xi, &hdr,
+ &found)) != NULL) {
+ /* ### anything better to do? */
+ /* ### probably should enter a 500 error */
+ goto next_key;
+ }
+ /* assert: found == 1 */
+ }
+ else {
+ /* the value was not requested, so just add an empty
+ tag specifying the property name. */
+ dav_output_prop_name(propdb->p, &name, xi, &hdr);
+ }
+
+ next_key:
+ err = (*db_hooks->next_name)(propdb->db, &name);
+ }
+
+ /* all namespaces have been entered into xi. generate them into
+ the output now. */
+ dav_xmlns_generate(xi, &hdr_ns);
+
+ } /* propdb->db != NULL */
+
+ /* add namespaces for all the liveprop providers */
+ dav_add_all_liveprop_xmlns(propdb->p, &hdr_ns);
+ }
+
+ /* ask the liveprop providers to insert their properties */
+ dav_run_insert_all_liveprops(propdb->r, propdb->resource, what, &hdr);
+
+ /* insert the standard properties */
+ /* ### should be handling the return errors here */
+ (void)dav_insert_coreprop(propdb,
+ DAV_PROPID_CORE_supportedlock, "supportedlock",
+ what, &hdr, &unused_inserted);
+ (void)dav_insert_coreprop(propdb,
+ DAV_PROPID_CORE_lockdiscovery, "lockdiscovery",
+ what, &hdr, &unused_inserted);
+
+ /* if we didn't find these, then do the whole subreq thing. */
+ if (!found_contenttype) {
+ /* ### should be handling the return error here */
+ (void)dav_insert_coreprop(propdb,
+ DAV_PROPID_CORE_getcontenttype,
+ "getcontenttype",
+ what, &hdr, &unused_inserted);
+ }
+ if (!found_contentlang) {
+ /* ### should be handling the return error here */
+ (void)dav_insert_coreprop(propdb,
+ DAV_PROPID_CORE_getcontentlanguage,
+ "getcontentlanguage",
+ what, &hdr, &unused_inserted);
+ }
+
+ /* if not just reporting on supported live props,
+ * terminate the result */
+ if (what != DAV_PROP_INSERT_SUPPORTED) {
+ apr_text_append(propdb->p, &hdr,
+ "</D:prop>" DEBUG_CR
+ "<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR
+ "</D:propstat>" DEBUG_CR);
+ }
+
+ result.propstats = hdr.first;
+ result.xmlns = hdr_ns.first;
+ return result;
+}
+
+DAV_DECLARE(dav_get_props_result) dav_get_props(dav_propdb *propdb,
+ apr_xml_doc *doc)
+{
+ const dav_hooks_db *db_hooks = propdb->db_hooks;
+ apr_xml_elem *elem = dav_find_child(doc->root, "prop");
+ apr_text_header hdr_good = { 0 };
+ apr_text_header hdr_bad = { 0 };
+ apr_text_header hdr_ns = { 0 };
+ int have_good = 0;
+ dav_get_props_result result = { 0 };
+ dav_liveprop_elem *element;
+ char *marks_liveprop;
+ dav_xmlns_info *xi;
+ int xi_filled = 0;
+
+ /* we lose both the document and the element when calling (insert_prop),
+ * make these available in the pool.
+ */
+ element = dav_get_liveprop_element(propdb->resource);
+ if (!element) {
+ element = apr_pcalloc(propdb->resource->pool, sizeof(dav_liveprop_elem));
+ apr_pool_userdata_setn(element, DAV_PROP_ELEMENT, NULL, propdb->resource->pool);
+ }
+ else {
+ memset(element, 0, sizeof(dav_liveprop_elem));
+ }
+ element->doc = doc;
+
+ /* ### NOTE: we should pass in TWO buffers -- one for keys, one for
+ the marks */
+
+ /* we will ALWAYS provide a "good" result, even if it is EMPTY */
+ apr_text_append(propdb->p, &hdr_good,
+ "<D:propstat>" DEBUG_CR
+ "<D:prop>" DEBUG_CR);
+
+ /* ### the marks should be in a buffer! */
+ /* allocate zeroed-memory for the marks. These marks indicate which
+ liveprop namespaces we've generated into the output xmlns buffer */
+
+ /* same for the liveprops */
+ marks_liveprop = apr_pcalloc(propdb->p, dav_get_liveprop_ns_count() + 1);
+
+ xi = dav_xmlns_create(propdb->p);
+
+ for (elem = elem->first_child; elem; elem = elem->next) {
+ dav_elem_private *priv;
+ dav_error *err;
+ dav_prop_insert inserted;
+ dav_prop_name name;
+
+ element->elem = elem;
+
+ /*
+ ** First try live property providers; if they don't handle
+ ** the property, then try looking it up in the propdb.
+ */
+
+ if (elem->priv == NULL) {
+ /* elem->priv outlives propdb->p. Hence use the request pool */
+ elem->priv = apr_pcalloc(propdb->r->pool, sizeof(*priv));
+ }
+ priv = elem->priv;
+
+ /* cache the propid; dav_get_props() could be called many times */
+ if (priv->propid == 0)
+ dav_find_liveprop(propdb, elem);
+
+ if (priv->propid != DAV_PROPID_CORE_UNKNOWN) {
+
+ /* insert the property. returns 1 if an insertion was done. */
+ if ((err = dav_insert_liveprop(propdb, elem, DAV_PROP_INSERT_VALUE,
+ &hdr_good, &inserted)) != NULL) {
+ /* ### need to propagate the error to the caller... */
+ /* ### skip it for now, as if nothing was inserted */
+ }
+ if (inserted == DAV_PROP_INSERT_VALUE) {
+ have_good = 1;
+
+ /*
+ ** Add the liveprop's namespace URIs. Note that provider==NULL
+ ** for core properties.
+ */
+ if (priv->provider != NULL) {
+ const char * const * scan_ns_uri;
+
+ for (scan_ns_uri = priv->provider->namespace_uris;
+ *scan_ns_uri != NULL;
+ ++scan_ns_uri) {
+ long ns;
+
+ ns = dav_get_liveprop_ns_index(*scan_ns_uri);
+ if (marks_liveprop[ns])
+ continue;
+ marks_liveprop[ns] = 1;
+
+ dav_insert_xmlns(propdb->p, "lp", ns, *scan_ns_uri,
+ &hdr_ns);
+ }
+ }
+
+ /* property added. move on to the next property. */
+ continue;
+ }
+ else if (inserted == DAV_PROP_INSERT_NOTDEF) {
+ /* nothing to do. fall thru to allow property to be handled
+ as a dead property */
+ }
+#if DAV_DEBUG
+ else {
+#if 0
+ /* ### need to change signature to return an error */
+ return dav_new_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR, 0,
+ 0,
+ "INTERNAL DESIGN ERROR: insert_liveprop "
+ "did not insert what was asked for.");
+#endif
+ }
+#endif
+ }
+
+ /* The property wasn't a live property, so look in the dead property
+ database. */
+
+ /* make sure propdb is really open */
+ if (propdb->deferred) {
+ /* ### what to do with db open error? */
+ (void) dav_really_open_db(propdb, 1 /*ro*/);
+ }
+
+ if (elem->ns == APR_XML_NS_NONE)
+ name.ns = "";
+ else
+ name.ns = APR_XML_GET_URI_ITEM(propdb->ns_xlate, elem->ns);
+ name.name = elem->name;
+
+ /* only bother to look if a database exists */
+ if (propdb->db != NULL) {
+ int found;
+
+ if ((err = (*db_hooks->output_value)(propdb->db, &name,
+ xi, &hdr_good,
+ &found)) != NULL) {
+ /* ### what to do? continue doesn't seem right... */
+ continue;
+ }
+
+ if (found) {
+ have_good = 1;
+
+ /* if we haven't added the db's namespaces, then do so... */
+ if (!xi_filled) {
+ (void) (*db_hooks->define_namespaces)(propdb->db, xi);
+ xi_filled = 1;
+ }
+ continue;
+ }
+ }
+
+ /* not found as a live OR dead property. add a record to the "bad"
+ propstats */
+
+ /* make sure we've started our "bad" propstat */
+ if (hdr_bad.first == NULL) {
+ apr_text_append(propdb->p, &hdr_bad,
+ "<D:propstat>" DEBUG_CR
+ "<D:prop>" DEBUG_CR);
+ }
+
+ /* output this property's name (into the bad propstats) */
+ dav_output_prop_name(propdb->p, &name, xi, &hdr_bad);
+ }
+
+ apr_text_append(propdb->p, &hdr_good,
+ "</D:prop>" DEBUG_CR
+ "<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR
+ "</D:propstat>" DEBUG_CR);
+
+ /* default to start with the good */
+ result.propstats = hdr_good.first;
+
+ /* we may not have any "bad" results */
+ if (hdr_bad.first != NULL) {
+ /* "close" the bad propstat */
+ apr_text_append(propdb->p, &hdr_bad,
+ "</D:prop>" DEBUG_CR
+ "<D:status>HTTP/1.1 404 Not Found</D:status>" DEBUG_CR
+ "</D:propstat>" DEBUG_CR);
+
+ /* if there are no good props, then just return the bad */
+ if (!have_good) {
+ result.propstats = hdr_bad.first;
+ }
+ else {
+ /* hook the bad propstat to the end of the good one */
+ hdr_good.last->next = hdr_bad.first;
+ }
+ }
+
+ /* add in all the various namespaces, and return them */
+ dav_xmlns_generate(xi, &hdr_ns);
+ result.xmlns = hdr_ns.first;
+
+ return result;
+}
+
+DAV_DECLARE(void) dav_get_liveprop_supported(dav_propdb *propdb,
+ const char *ns_uri,
+ const char *propname,
+ apr_text_header *body)
+{
+ int propid;
+ const dav_hooks_liveprop *hooks;
+
+ propid = dav_find_liveprop_provider(propdb, ns_uri, propname, &hooks);
+
+ if (propid != DAV_PROPID_CORE_UNKNOWN) {
+ if (hooks == NULL) {
+ /* this is a "core" property that we define */
+ dav_prop_insert unused_inserted;
+ dav_insert_coreprop(propdb, propid, propname,
+ DAV_PROP_INSERT_SUPPORTED, body, &unused_inserted);
+ }
+ else {
+ (*hooks->insert_prop)(propdb->resource, propid,
+ DAV_PROP_INSERT_SUPPORTED, body);
+ }
+ }
+}
+
+DAV_DECLARE(dav_liveprop_elem *) dav_get_liveprop_element(const dav_resource *resource)
+{
+ dav_liveprop_elem *element;
+
+ apr_pool_userdata_get((void **)&element, DAV_PROP_ELEMENT, resource->pool);
+
+ return element;
+}
+
+DAV_DECLARE_NONSTD(void) dav_prop_validate(dav_prop_ctx *ctx)
+{
+ dav_propdb *propdb = ctx->propdb;
+ apr_xml_elem *prop = ctx->prop;
+ dav_elem_private *priv;
+
+ priv = ctx->prop->priv = apr_pcalloc(propdb->p, sizeof(*priv));
+
+ /*
+ ** Check to see if this is a live property, and fill the fields
+ ** in the XML elem, as appropriate.
+ **
+ ** Verify that the property is read/write. If not, then it cannot
+ ** be SET or DELETEd.
+ */
+ if (priv->propid == 0) {
+ dav_find_liveprop(propdb, prop);
+
+ /* it's a liveprop if a provider was found */
+ /* ### actually the "core" props should really be liveprops, but
+ ### there is no "provider" for those and the r/w props are
+ ### treated as dead props anyhow */
+ ctx->is_liveprop = priv->provider != NULL;
+ }
+
+ if (!dav_rw_liveprop(propdb, priv)) {
+ ctx->err = dav_new_error(propdb->p, HTTP_CONFLICT,
+ DAV_ERR_PROP_READONLY, 0,
+ "Property is read-only.");
+ return;
+ }
+
+ if (ctx->is_liveprop) {
+ int defer_to_dead = 0;
+
+ ctx->err = (*priv->provider->patch_validate)(propdb->resource,
+ prop, ctx->operation,
+ &ctx->liveprop_ctx,
+ &defer_to_dead);
+ if (ctx->err != NULL || !defer_to_dead)
+ return;
+
+ /* clear is_liveprop -- act as a dead prop now */
+ ctx->is_liveprop = 0;
+ }
+
+ /*
+ ** The property is supposed to be stored into the dead-property
+ ** database. Make sure the thing is truly open (and writable).
+ */
+ if (propdb->deferred
+ && (ctx->err = dav_really_open_db(propdb, 0 /* ro */)) != NULL) {
+ return;
+ }
+
+ /*
+ ** There should be an open, writable database in here!
+ **
+ ** Note: the database would be NULL if it was opened readonly and it
+ ** did not exist.
+ */
+ if (propdb->db == NULL) {
+ ctx->err = dav_new_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_PROP_NO_DATABASE, 0,
+ "Attempted to set/remove a property "
+ "without a valid, open, read/write "
+ "property database.");
+ return;
+ }
+
+ if (ctx->operation == DAV_PROP_OP_SET) {
+ /*
+ ** Prep the element => propdb namespace index mapping, inserting
+ ** namespace URIs into the propdb that don't exist.
+ */
+ (void) (*propdb->db_hooks->map_namespaces)(propdb->db,
+ propdb->ns_xlate,
+ &propdb->mapping);
+ }
+ else if (ctx->operation == DAV_PROP_OP_DELETE) {
+ /*
+ ** There are no checks to perform here. If a property exists, then
+ ** we will delete it. If it does not exist, then it does not matter
+ ** (see S12.13.1).
+ **
+ ** Note that if a property does not exist, that does not rule out
+ ** that a SET will occur during this PROPPATCH (thusly creating it).
+ */
+ }
+}
+
+DAV_DECLARE_NONSTD(void) dav_prop_exec(dav_prop_ctx *ctx)
+{
+ dav_propdb *propdb = ctx->propdb;
+ dav_error *err = NULL;
+ dav_elem_private *priv = ctx->prop->priv;
+
+ ctx->rollback = apr_pcalloc(propdb->p, sizeof(*ctx->rollback));
+
+ if (ctx->is_liveprop) {
+ err = (*priv->provider->patch_exec)(propdb->resource,
+ ctx->prop, ctx->operation,
+ ctx->liveprop_ctx,
+ &ctx->rollback->liveprop);
+ }
+ else {
+ dav_prop_name name;
+
+ if (ctx->prop->ns == APR_XML_NS_NONE)
+ name.ns = "";
+ else
+ name.ns = APR_XML_GET_URI_ITEM(propdb->ns_xlate, ctx->prop->ns);
+ name.name = ctx->prop->name;
+
+ /* save the old value so that we can do a rollback. */
+ if ((err = (*propdb->db_hooks
+ ->get_rollback)(propdb->db, &name,
+ &ctx->rollback->deadprop)) != NULL)
+ goto error;
+
+ if (ctx->operation == DAV_PROP_OP_SET) {
+
+ /* Note: propdb->mapping was set in dav_prop_validate() */
+ err = (*propdb->db_hooks->store)(propdb->db, &name, ctx->prop,
+ propdb->mapping);
+
+ /*
+ ** If an error occurred, then assume that we didn't change the
+ ** value. Remove the rollback item so that we don't try to set
+ ** its value during the rollback.
+ */
+ /* ### euh... where is the removal? */
+ }
+ else if (ctx->operation == DAV_PROP_OP_DELETE) {
+
+ /*
+ ** Delete the property. Ignore errors -- the property is there, or
+ ** we are deleting it for a second time.
+ **
+ ** http://tools.ietf.org/html/rfc4918#section-14.23 says
+ ** "Specifying the removal of a property that does not exist is
+ ** not an error"
+ */
+ /* ### but what about other errors? */
+ (void) (*propdb->db_hooks->remove)(propdb->db, &name);
+ }
+ }
+
+ error:
+ /* push a more specific error here */
+ if (err != NULL) {
+ /*
+ ** Use HTTP_INTERNAL_SERVER_ERROR because we shouldn't have seen
+ ** any errors at this point.
+ */
+ ctx->err = dav_push_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR,
+ DAV_ERR_PROP_EXEC,
+ "Could not execute PROPPATCH.", err);
+ }
+}
+
+DAV_DECLARE_NONSTD(void) dav_prop_commit(dav_prop_ctx *ctx)
+{
+ dav_elem_private *priv = ctx->prop->priv;
+
+ /*
+ ** Note that a commit implies ctx->err is NULL. The caller should assume
+ ** a status of HTTP_OK for this case.
+ */
+
+ if (ctx->is_liveprop) {
+ (*priv->provider->patch_commit)(ctx->propdb->resource,
+ ctx->operation,
+ ctx->liveprop_ctx,
+ ctx->rollback->liveprop);
+ }
+}
+
+DAV_DECLARE_NONSTD(void) dav_prop_rollback(dav_prop_ctx *ctx)
+{
+ dav_error *err = NULL;
+ dav_elem_private *priv = ctx->prop->priv;
+
+ /* do nothing if there is no rollback information. */
+ if (ctx->rollback == NULL)
+ return;
+
+ /*
+ ** ### if we have an error, and a rollback occurs, then the namespace
+ ** ### mods should not happen at all. Basically, the namespace management
+ ** ### is simply a bitch.
+ */
+
+ if (ctx->is_liveprop) {
+ err = (*priv->provider->patch_rollback)(ctx->propdb->resource,
+ ctx->operation,
+ ctx->liveprop_ctx,
+ ctx->rollback->liveprop);
+ }
+ else {
+ err = (*ctx->propdb->db_hooks
+ ->apply_rollback)(ctx->propdb->db, ctx->rollback->deadprop);
+ }
+
+ if (err != NULL) {
+ if (ctx->err == NULL)
+ ctx->err = err;
+ else {
+ dav_error *scan = err;
+
+ /* hook previous errors at the end of the rollback error */
+ while (scan->prev != NULL)
+ scan = scan->prev;
+ scan->prev = ctx->err;
+ ctx->err = err;
+ }
+ }
+}
diff --git a/modules/dav/main/providers.c b/modules/dav/main/providers.c
new file mode 100644
index 0000000..44870fd
--- /dev/null
+++ b/modules/dav/main/providers.c
@@ -0,0 +1,58 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr_pools.h"
+#include "apr_hash.h"
+#include "ap_provider.h"
+#include "mod_dav.h"
+
+#define DAV_PROVIDER_GROUP "dav"
+
+DAV_DECLARE(void) dav_register_provider(apr_pool_t *p, const char *name,
+ const dav_provider *provider)
+{
+ ap_register_provider(p, DAV_PROVIDER_GROUP, name, "0", provider);
+}
+
+DAV_DECLARE(const dav_provider *) dav_lookup_provider(const char *name)
+{
+ return ap_lookup_provider(DAV_PROVIDER_GROUP, name, "0");
+}
+
+DAV_DECLARE(void) dav_options_provider_register(apr_pool_t *p,
+ const char *name,
+ const dav_options_provider *provider)
+{
+ ap_register_provider(p, DAV_OPTIONS_EXTENSION_GROUP, name, "0", provider);
+}
+
+DAV_DECLARE(const dav_options_provider *) dav_get_options_providers(const char *name)
+{
+ return ap_lookup_provider(DAV_OPTIONS_EXTENSION_GROUP, name, "0");
+}
+
+
+DAV_DECLARE(void) dav_resource_type_provider_register(apr_pool_t *p,
+ const char *name,
+ const dav_resource_type_provider *provider)
+{
+ ap_register_provider(p, DAV_RESOURCE_TYPE_GROUP, name, "0", provider);
+}
+
+DAV_DECLARE(const dav_resource_type_provider *) dav_get_resource_type_providers(const char *name)
+{
+ return ap_lookup_provider(DAV_RESOURCE_TYPE_GROUP, name, "0");
+}
diff --git a/modules/dav/main/std_liveprop.c b/modules/dav/main/std_liveprop.c
new file mode 100644
index 0000000..cb46b65
--- /dev/null
+++ b/modules/dav/main/std_liveprop.c
@@ -0,0 +1,226 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "httpd.h"
+#include "util_xml.h"
+#include "apr_strings.h"
+#include "ap_provider.h"
+
+#include "mod_dav.h"
+
+/* forward-declare */
+static const dav_hooks_liveprop dav_core_hooks_liveprop;
+
+/*
+** The namespace URIs that we use. There will only ever be "DAV:".
+*/
+static const char * const dav_core_namespace_uris[] =
+{
+ "DAV:",
+ NULL /* sentinel */
+};
+
+/*
+** Define each of the core properties that this provider will handle.
+** Note that all of them are in the DAV: namespace, which has a
+** provider-local index of 0.
+*/
+static const dav_liveprop_spec dav_core_props[] =
+{
+ { 0, "comment", DAV_PROPID_comment, 1 },
+ { 0, "creator-displayname", DAV_PROPID_creator_displayname, 1 },
+ { 0, "displayname", DAV_PROPID_displayname, 1 },
+ { 0, "resourcetype", DAV_PROPID_resourcetype, 0 },
+ { 0, "source", DAV_PROPID_source, 1 },
+
+ { 0 } /* sentinel */
+};
+
+static const dav_liveprop_group dav_core_liveprop_group =
+{
+ dav_core_props,
+ dav_core_namespace_uris,
+ &dav_core_hooks_liveprop
+};
+
+static dav_prop_insert dav_core_insert_prop(const dav_resource *resource,
+ int propid, dav_prop_insert what,
+ apr_text_header *phdr)
+{
+ const char *value = NULL;
+ const char *s;
+ apr_pool_t *p = resource->pool;
+ const dav_liveprop_spec *info;
+ long global_ns;
+
+ switch (propid)
+ {
+ case DAV_PROPID_resourcetype:
+ { /* additional type info provided by external modules ? */
+ int i;
+
+ apr_array_header_t *extensions =
+ ap_list_provider_names(p, DAV_RESOURCE_TYPE_GROUP, "0");
+ ap_list_provider_names_t *entry =
+ (ap_list_provider_names_t *)extensions->elts;
+
+ for (i = 0; i < extensions->nelts; i++, entry++) {
+ const dav_resource_type_provider *res_hooks =
+ dav_get_resource_type_providers(entry->provider_name);
+ const char *name = NULL, *uri = NULL;
+
+ if (!res_hooks || !res_hooks->get_resource_type)
+ continue;
+
+ if (!res_hooks->get_resource_type(resource, &name, &uri) &&
+ name) {
+
+ if (!uri || !strcasecmp(uri, "DAV:"))
+ value = apr_pstrcat(p, value ? value : "",
+ "<D:", name, "/>", NULL);
+ else
+ value = apr_pstrcat(p, value ? value : "",
+ "<x:", name,
+ " xmlns:x=\"", uri,
+ "\"/>", NULL);
+ }
+ }
+ }
+ switch (resource->type) {
+ case DAV_RESOURCE_TYPE_VERSION:
+ if (resource->baselined) {
+ value = apr_pstrcat(p, value ? value : "", "<D:baseline/>", NULL);
+ break;
+ }
+ /* fall through */
+ case DAV_RESOURCE_TYPE_REGULAR:
+ case DAV_RESOURCE_TYPE_WORKING:
+ if (resource->collection) {
+ value = apr_pstrcat(p, value ? value : "", "<D:collection/>", NULL);
+ }
+ else {
+ /* ### should we denote lock-null resources? */
+ if (value == NULL) {
+ value = ""; /* becomes: <D:resourcetype/> */
+ }
+ }
+ break;
+ case DAV_RESOURCE_TYPE_HISTORY:
+ value = apr_pstrcat(p, value ? value : "", "<D:version-history/>", NULL);
+ break;
+ case DAV_RESOURCE_TYPE_WORKSPACE:
+ value = apr_pstrcat(p, value ? value : "", "<D:collection/>", NULL);
+ break;
+ case DAV_RESOURCE_TYPE_ACTIVITY:
+ value = apr_pstrcat(p, value ? value : "", "<D:activity/>", NULL);
+ break;
+
+ default:
+ /* ### bad juju */
+ return DAV_PROP_INSERT_NOTDEF;
+ }
+ break;
+
+ case DAV_PROPID_comment:
+ case DAV_PROPID_creator_displayname:
+ case DAV_PROPID_displayname:
+ case DAV_PROPID_source:
+ default:
+ /*
+ ** This property is known, but not defined as a liveprop. However,
+ ** it may be a dead property.
+ */
+ return DAV_PROP_INSERT_NOTDEF;
+ }
+
+ /* assert: value != NULL */
+
+ /* get the information and global NS index for the property */
+ global_ns = dav_get_liveprop_info(propid, &dav_core_liveprop_group, &info);
+
+ /* assert: info != NULL && info->name != NULL */
+
+ if (what == DAV_PROP_INSERT_SUPPORTED) {
+ s = apr_pstrcat(p,
+ "<D:supported-live-property D:name=\"", info->name,
+ "\" D:namespace=\"", dav_core_namespace_uris[info->ns],
+ "\"/>" DEBUG_CR, NULL);
+ }
+ else if (what == DAV_PROP_INSERT_VALUE && *value != '\0') {
+ s = apr_psprintf(p, "<lp%ld:%s>%s</lp%ld:%s>" DEBUG_CR,
+ global_ns, info->name, value, global_ns, info->name);
+ }
+ else {
+ s = apr_psprintf(p, "<lp%ld:%s/>" DEBUG_CR, global_ns, info->name);
+ }
+ apr_text_append(p, phdr, s);
+
+ /* we inserted what was asked for */
+ return what;
+}
+
+static int dav_core_is_writable(const dav_resource *resource, int propid)
+{
+ const dav_liveprop_spec *info;
+
+ (void) dav_get_liveprop_info(propid, &dav_core_liveprop_group, &info);
+ return info->is_writable;
+}
+
+static dav_error * dav_core_patch_validate(const dav_resource *resource,
+ const apr_xml_elem *elem,
+ int operation, void **context,
+ int *defer_to_dead)
+{
+ /* all of our writable props go in the dead prop database */
+ *defer_to_dead = 1;
+
+ return NULL;
+}
+
+static const dav_hooks_liveprop dav_core_hooks_liveprop = {
+ dav_core_insert_prop,
+ dav_core_is_writable,
+ dav_core_namespace_uris,
+ dav_core_patch_validate,
+ NULL, /* patch_exec */
+ NULL, /* patch_commit */
+ NULL, /* patch_rollback */
+};
+
+DAV_DECLARE_NONSTD(int) dav_core_find_liveprop(
+ const dav_resource *resource,
+ const char *ns_uri, const char *name,
+ const dav_hooks_liveprop **hooks)
+{
+ return dav_do_find_liveprop(ns_uri, name, &dav_core_liveprop_group, hooks);
+}
+
+DAV_DECLARE_NONSTD(void) dav_core_insert_all_liveprops(
+ request_rec *r,
+ const dav_resource *resource,
+ dav_prop_insert what,
+ apr_text_header *phdr)
+{
+ (void) dav_core_insert_prop(resource, DAV_PROPID_resourcetype,
+ what, phdr);
+}
+
+DAV_DECLARE_NONSTD(void) dav_core_register_uris(apr_pool_t *p)
+{
+ /* register the namespace URIs */
+ dav_register_liveprop_group(p, &dav_core_liveprop_group);
+}
diff --git a/modules/dav/main/util.c b/modules/dav/main/util.c
new file mode 100644
index 0000000..3f7822f
--- /dev/null
+++ b/modules/dav/main/util.c
@@ -0,0 +1,2213 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+** DAV extension module for Apache 2.0.*
+** - various utilities, repository-independent
+*/
+
+#include "apr_strings.h"
+#include "apr_lib.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "mod_dav.h"
+
+#include "http_request.h"
+#include "http_config.h"
+#include "http_vhost.h"
+#include "http_log.h"
+#include "http_protocol.h"
+
+DAV_DECLARE(dav_error*) dav_new_error(apr_pool_t *p, int status, int error_id,
+ apr_status_t aprerr, const char *desc)
+{
+ dav_error *err = apr_pcalloc(p, sizeof(*err));
+
+ /* DBG3("dav_new_error: %d %d %s", status, error_id, desc ? desc : "(no desc)"); */
+
+ err->status = status;
+ err->error_id = error_id;
+ err->desc = desc;
+ err->aprerr = aprerr;
+
+ return err;
+}
+
+DAV_DECLARE(dav_error*) dav_new_error_tag(apr_pool_t *p, int status,
+ int error_id, apr_status_t aprerr,
+ const char *desc,
+ const char *namespace,
+ const char *tagname)
+{
+ dav_error *err = dav_new_error(p, status, error_id, aprerr, desc);
+
+ err->tagname = tagname;
+ err->namespace = namespace;
+
+ return err;
+}
+
+
+DAV_DECLARE(dav_error*) dav_push_error(apr_pool_t *p, int status,
+ int error_id, const char *desc,
+ dav_error *prev)
+{
+ dav_error *err = apr_pcalloc(p, sizeof(*err));
+
+ err->status = status;
+ err->error_id = error_id;
+ err->desc = desc;
+ err->prev = prev;
+
+ return err;
+}
+
+DAV_DECLARE(dav_error*) dav_join_error(dav_error *dest, dav_error *src)
+{
+ dav_error *curr = dest;
+
+ /* src error doesn't exist so nothing to join just return dest */
+ if (src == NULL) {
+ return dest;
+ }
+
+ /* dest error doesn't exist so nothing to join just return src */
+ if (curr == NULL) {
+ return src;
+ }
+
+ /* find last error in dest stack */
+ while (curr->prev != NULL) {
+ curr = curr->prev;
+ }
+
+ /* add the src error onto end of dest stack and return it */
+ curr->prev = src;
+ return dest;
+}
+
+/* ### Unclear if this was designed to be used with an uninitialized
+ * dav_buffer struct, but is used on by dav_lock_get_activelock().
+ * Hence check for pbuf->buf. */
+DAV_DECLARE(void) dav_check_bufsize(apr_pool_t * p, dav_buffer *pbuf,
+ apr_size_t extra_needed)
+{
+ /* grow the buffer if necessary */
+ if (pbuf->cur_len + extra_needed > pbuf->alloc_len) {
+ char *newbuf;
+
+ pbuf->alloc_len += extra_needed + DAV_BUFFER_PAD;
+ newbuf = apr_palloc(p, pbuf->alloc_len);
+ if (pbuf->buf)
+ memcpy(newbuf, pbuf->buf, pbuf->cur_len);
+ pbuf->buf = newbuf;
+ }
+}
+
+DAV_DECLARE(void) dav_set_bufsize(apr_pool_t * p, dav_buffer *pbuf,
+ apr_size_t size)
+{
+ /* NOTE: this does not retain prior contents */
+
+ /* NOTE: this function is used to init the first pointer, too, since
+ the PAD will be larger than alloc_len (0) for zeroed structures */
+
+ /* grow if we don't have enough for the requested size plus padding */
+ if (size + DAV_BUFFER_PAD > pbuf->alloc_len) {
+ /* set the new length; min of MINSIZE */
+ pbuf->alloc_len = size + DAV_BUFFER_PAD;
+ if (pbuf->alloc_len < DAV_BUFFER_MINSIZE)
+ pbuf->alloc_len = DAV_BUFFER_MINSIZE;
+
+ pbuf->buf = apr_palloc(p, pbuf->alloc_len);
+ }
+ pbuf->cur_len = size;
+}
+
+
+/* initialize a buffer and copy the specified (null-term'd) string into it */
+DAV_DECLARE(void) dav_buffer_init(apr_pool_t *p, dav_buffer *pbuf,
+ const char *str)
+{
+ dav_set_bufsize(p, pbuf, strlen(str));
+ memcpy(pbuf->buf, str, pbuf->cur_len + 1);
+}
+
+/* append a string to the end of the buffer, adjust length */
+DAV_DECLARE(void) dav_buffer_append(apr_pool_t *p, dav_buffer *pbuf,
+ const char *str)
+{
+ apr_size_t len = strlen(str);
+
+ dav_check_bufsize(p, pbuf, len + 1);
+ memcpy(pbuf->buf + pbuf->cur_len, str, len + 1);
+ pbuf->cur_len += len;
+}
+
+/* place a string on the end of the buffer, do NOT adjust length */
+DAV_DECLARE(void) dav_buffer_place(apr_pool_t *p, dav_buffer *pbuf,
+ const char *str)
+{
+ apr_size_t len = strlen(str);
+
+ dav_check_bufsize(p, pbuf, len + 1);
+ memcpy(pbuf->buf + pbuf->cur_len, str, len + 1);
+}
+
+/* place some memory on the end of a buffer; do NOT adjust length */
+DAV_DECLARE(void) dav_buffer_place_mem(apr_pool_t *p, dav_buffer *pbuf,
+ const void *mem, apr_size_t amt,
+ apr_size_t pad)
+{
+ dav_check_bufsize(p, pbuf, amt + pad);
+ memcpy(pbuf->buf + pbuf->cur_len, mem, amt);
+}
+
+/*
+** dav_lookup_uri()
+**
+** Extension for ap_sub_req_lookup_uri() which can't handle absolute
+** URIs properly.
+**
+** If NULL is returned, then an error occurred with parsing the URI or
+** the URI does not match the current server.
+*/
+DAV_DECLARE(dav_lookup_result) dav_lookup_uri(const char *uri,
+ request_rec * r,
+ int must_be_absolute)
+{
+ dav_lookup_result result = { 0 };
+ const char *scheme;
+ apr_port_t port;
+ apr_uri_t comp;
+ char *new_file;
+ const char *domain;
+
+ /* first thing to do is parse the URI into various components */
+ if (apr_uri_parse(r->pool, uri, &comp) != APR_SUCCESS) {
+ result.err.status = HTTP_BAD_REQUEST;
+ result.err.desc = "Invalid syntax in Destination URI.";
+ return result;
+ }
+
+ /* the URI must be an absoluteURI (WEBDAV S9.3) */
+ if (comp.scheme == NULL && must_be_absolute) {
+ result.err.status = HTTP_BAD_REQUEST;
+ result.err.desc = "Destination URI must be an absolute URI.";
+ return result;
+ }
+
+ /* the URI must not have a query (args) or a fragment */
+ if (comp.query != NULL || comp.fragment != NULL) {
+ result.err.status = HTTP_BAD_REQUEST;
+ result.err.desc =
+ "Destination URI contains invalid components "
+ "(a query or a fragment).";
+ return result;
+ }
+
+ /* If the scheme or port was provided, then make sure that it matches
+ the scheme/port of this request. If the request must be absolute,
+ then require the (explicit/implicit) scheme/port be matching.
+
+ ### hmm. if a port wasn't provided (does the parse return port==0?),
+ ### but we're on a non-standard port, then we won't detect that the
+ ### URI's port implies the wrong one.
+ */
+ if (comp.scheme != NULL || comp.port != 0 || must_be_absolute)
+ {
+ /* ### not sure this works if the current request came in via https: */
+ scheme = r->parsed_uri.scheme;
+ if (scheme == NULL)
+ scheme = ap_http_scheme(r);
+
+ /* insert a port if the URI did not contain one */
+ if (comp.port == 0)
+ comp.port = apr_uri_port_of_scheme(comp.scheme);
+
+ /* now, verify that the URI uses the same scheme as the current.
+ request. the port must match our port.
+ */
+ port = r->connection->local_addr->port;
+ if (ap_cstr_casecmp(comp.scheme, scheme) != 0
+#ifdef APACHE_PORT_HANDLING_IS_BUSTED
+ || comp.port != port
+#endif
+ ) {
+ result.err.status = HTTP_BAD_GATEWAY;
+ result.err.desc = apr_psprintf(r->pool,
+ "Destination URI refers to "
+ "different scheme or port "
+ "(%s://hostname:%d)" APR_EOL_STR
+ "(want: %s://hostname:%d)",
+ comp.scheme ? comp.scheme : scheme,
+ comp.port ? comp.port : port,
+ scheme, port);
+ return result;
+ }
+ }
+
+ /* we have verified the scheme, port, and general structure */
+
+ /*
+ ** Hrm. IE5 will pass unqualified hostnames for both the
+ ** Host: and Destination: headers. This breaks the
+ ** http_vhost.c::matches_aliases function.
+ **
+ ** For now, qualify unqualified comp.hostnames with
+ ** r->server->server_hostname.
+ **
+ ** ### this is a big hack. Apache should provide a better way.
+ ** ### maybe the admin should list the unqualified hosts in a
+ ** ### <ServerAlias> block?
+ */
+ if (comp.hostname != NULL
+ && strrchr(comp.hostname, '.') == NULL
+ && (domain = strchr(r->server->server_hostname, '.')) != NULL) {
+ comp.hostname = apr_pstrcat(r->pool, comp.hostname, domain, NULL);
+ }
+
+ /* now, if a hostname was provided, then verify that it represents the
+ same server as the current connection. note that we just use our
+ port, since we've verified the URI matches ours */
+#ifdef APACHE_PORT_HANDLING_IS_BUSTED
+ if (comp.hostname != NULL &&
+ !ap_matches_request_vhost(r, comp.hostname, port)) {
+ result.err.status = HTTP_BAD_GATEWAY;
+ result.err.desc = "Destination URI refers to a different server.";
+ return result;
+ }
+#endif
+
+ /* we have verified that the requested URI denotes the same server as
+ the current request. Therefore, we can use ap_sub_req_lookup_uri() */
+
+ /* reconstruct a URI as just the path */
+ new_file = apr_uri_unparse(r->pool, &comp, APR_URI_UNP_OMITSITEPART);
+
+ /*
+ * Lookup the URI and return the sub-request. Note that we use the
+ * same HTTP method on the destination. This allows the destination
+ * to apply appropriate restrictions (e.g. readonly).
+ */
+ result.rnew = ap_sub_req_method_uri(r->method, new_file, r, NULL);
+
+ return result;
+}
+
+/* ---------------------------------------------------------------
+**
+** XML UTILITY FUNCTIONS
+*/
+
+/* validate that the root element uses a given DAV: tagname (TRUE==valid) */
+DAV_DECLARE(int) dav_validate_root_ns(const apr_xml_doc *doc,
+ int ns, const char *tagname)
+{
+ return doc->root &&
+ doc->root->ns == ns &&
+ strcmp(doc->root->name, tagname) == 0;
+}
+
+/* validate that the root element uses a given DAV: tagname (TRUE==valid) */
+DAV_DECLARE(int) dav_validate_root(const apr_xml_doc *doc,
+ const char *tagname)
+{
+ return dav_validate_root_ns(doc, APR_XML_NS_DAV_ID, tagname);
+}
+
+/* find and return the next child with a tagname in the given namespace */
+DAV_DECLARE(apr_xml_elem *) dav_find_next_ns(const apr_xml_elem *elem,
+ int ns, const char *tagname)
+{
+ apr_xml_elem *child = elem->next;
+
+ for (; child; child = child->next)
+ if (child->ns == ns && !strcmp(child->name, tagname))
+ return child;
+ return NULL;
+}
+
+/* find and return the (unique) child with a tagname in the given namespace */
+DAV_DECLARE(apr_xml_elem *) dav_find_child_ns(const apr_xml_elem *elem,
+ int ns, const char *tagname)
+{
+ apr_xml_elem *child = elem->first_child;
+
+ for (; child; child = child->next)
+ if (child->ns == ns && !strcmp(child->name, tagname))
+ return child;
+ return NULL;
+}
+
+/* find and return the (unique) child with a given DAV: tagname */
+DAV_DECLARE(apr_xml_elem *) dav_find_child(const apr_xml_elem *elem,
+ const char *tagname)
+{
+ return dav_find_child_ns(elem, APR_XML_NS_DAV_ID, tagname);
+}
+
+/* find and return the attribute with a name in the given namespace */
+DAV_DECLARE(apr_xml_attr *) dav_find_attr_ns(const apr_xml_elem *elem,
+ int ns, const char *attrname)
+{
+ apr_xml_attr *attr = elem->attr;
+
+ for (; attr; attr = attr->next)
+ if (attr->ns == ns && !strcmp(attr->name, attrname))
+ return attr;
+ return NULL;
+}
+
+/* find and return the attribute with a given DAV: tagname */
+DAV_DECLARE(apr_xml_attr *) dav_find_attr(const apr_xml_elem *elem,
+ const char *attrname)
+{
+ return dav_find_attr_ns(elem, APR_XML_NS_DAV_ID, attrname);
+}
+
+/* gather up all the CDATA into a single string */
+DAV_DECLARE(const char *) dav_xml_get_cdata(const apr_xml_elem *elem, apr_pool_t *pool,
+ int strip_white)
+{
+ apr_size_t len = 0;
+ apr_text *scan;
+ const apr_xml_elem *child;
+ char *cdata;
+ char *s;
+ apr_size_t tlen;
+ const char *found_text = NULL; /* initialize to avoid gcc warning */
+ int found_count = 0;
+
+ for (scan = elem->first_cdata.first; scan != NULL; scan = scan->next) {
+ found_text = scan->text;
+ ++found_count;
+ len += strlen(found_text);
+ }
+
+ for (child = elem->first_child; child != NULL; child = child->next) {
+ for (scan = child->following_cdata.first;
+ scan != NULL;
+ scan = scan->next) {
+ found_text = scan->text;
+ ++found_count;
+ len += strlen(found_text);
+ }
+ }
+
+ /* some fast-path cases:
+ * 1) zero-length cdata
+ * 2) a single piece of cdata with no whitespace to strip
+ */
+ if (len == 0)
+ return "";
+ if (found_count == 1) {
+ if (!strip_white
+ || (!apr_isspace(*found_text)
+ && !apr_isspace(found_text[len - 1])))
+ return found_text;
+ }
+
+ cdata = s = apr_palloc(pool, len + 1);
+
+ for (scan = elem->first_cdata.first; scan != NULL; scan = scan->next) {
+ tlen = strlen(scan->text);
+ memcpy(s, scan->text, tlen);
+ s += tlen;
+ }
+
+ for (child = elem->first_child; child != NULL; child = child->next) {
+ for (scan = child->following_cdata.first;
+ scan != NULL;
+ scan = scan->next) {
+ tlen = strlen(scan->text);
+ memcpy(s, scan->text, tlen);
+ s += tlen;
+ }
+ }
+
+ *s = '\0';
+
+ if (strip_white) {
+ /* trim leading whitespace */
+ while (apr_isspace(*cdata)) { /* assume: return false for '\0' */
+ ++cdata;
+ --len;
+ }
+
+ /* trim trailing whitespace */
+ while (len-- > 0 && apr_isspace(cdata[len]))
+ continue;
+ cdata[len + 1] = '\0';
+ }
+
+ return cdata;
+}
+
+DAV_DECLARE(dav_xmlns_info *) dav_xmlns_create(apr_pool_t *pool)
+{
+ dav_xmlns_info *xi = apr_pcalloc(pool, sizeof(*xi));
+
+ xi->pool = pool;
+ xi->uri_prefix = apr_hash_make(pool);
+ xi->prefix_uri = apr_hash_make(pool);
+
+ return xi;
+}
+
+DAV_DECLARE(void) dav_xmlns_add(dav_xmlns_info *xi,
+ const char *prefix, const char *uri)
+{
+ /* this "should" not overwrite a prefix mapping */
+ apr_hash_set(xi->prefix_uri, prefix, APR_HASH_KEY_STRING, uri);
+
+ /* note: this may overwrite an existing URI->prefix mapping, but it
+ doesn't matter -- any prefix is usable to specify the URI. */
+ apr_hash_set(xi->uri_prefix, uri, APR_HASH_KEY_STRING, prefix);
+}
+
+DAV_DECLARE(const char *) dav_xmlns_add_uri(dav_xmlns_info *xi,
+ const char *uri)
+{
+ const char *prefix;
+
+ if ((prefix = apr_hash_get(xi->uri_prefix, uri,
+ APR_HASH_KEY_STRING)) != NULL)
+ return prefix;
+
+ prefix = apr_psprintf(xi->pool, "g%d", xi->count++);
+ dav_xmlns_add(xi, prefix, uri);
+ return prefix;
+}
+
+DAV_DECLARE(const char *) dav_xmlns_get_uri(dav_xmlns_info *xi,
+ const char *prefix)
+{
+ return apr_hash_get(xi->prefix_uri, prefix, APR_HASH_KEY_STRING);
+}
+
+DAV_DECLARE(const char *) dav_xmlns_get_prefix(dav_xmlns_info *xi,
+ const char *uri)
+{
+ return apr_hash_get(xi->uri_prefix, uri, APR_HASH_KEY_STRING);
+}
+
+DAV_DECLARE(void) dav_xmlns_generate(dav_xmlns_info *xi,
+ apr_text_header *phdr)
+{
+ apr_hash_index_t *hi = apr_hash_first(xi->pool, xi->prefix_uri);
+
+ for (; hi != NULL; hi = apr_hash_next(hi)) {
+ const void *prefix;
+ void *uri;
+ const char *s;
+
+ apr_hash_this(hi, &prefix, NULL, &uri);
+
+ s = apr_pstrcat(xi->pool, " xmlns:", (const char *)prefix, "=\"",
+ (const char *)uri, "\"", NULL);
+ apr_text_append(xi->pool, phdr, s);
+ }
+}
+
+/* ---------------------------------------------------------------
+**
+** Timeout header processing
+**
+*/
+
+/* dav_get_timeout: If the Timeout: header exists, return a time_t
+ * when this lock is expected to expire. Otherwise, return
+ * a time_t of DAV_TIMEOUT_INFINITE.
+ *
+ * It's unclear if DAV clients are required to understand
+ * Seconds-xxx and Infinity time values. We assume that they do.
+ * In addition, for now, that's all we understand, too.
+ */
+DAV_DECLARE(time_t) dav_get_timeout(request_rec *r)
+{
+ time_t now, expires = DAV_TIMEOUT_INFINITE;
+
+ const char *timeout_const = apr_table_get(r->headers_in, "Timeout");
+ const char *timeout = apr_pstrdup(r->pool, timeout_const), *val;
+
+ if (timeout == NULL)
+ return DAV_TIMEOUT_INFINITE;
+
+ /* Use the first thing we understand, or infinity if
+ * we don't understand anything.
+ */
+
+ while ((val = ap_getword_white(r->pool, &timeout)) && strlen(val)) {
+ if (!strncmp(val, "Infinite", 8)) {
+ return DAV_TIMEOUT_INFINITE;
+ }
+
+ if (!strncmp(val, "Second-", 7)) {
+ val += 7;
+ /* ### We need to handle overflow better:
+ * ### timeout will be <= 2^32 - 1
+ */
+ expires = atol(val);
+ now = time(NULL);
+ return now + expires;
+ }
+ }
+
+ return DAV_TIMEOUT_INFINITE;
+}
+
+/* ---------------------------------------------------------------
+**
+** If Header processing
+**
+*/
+
+/* add_if_resource returns a new if_header, linking it to next_ih.
+ */
+static dav_if_header *dav_add_if_resource(apr_pool_t *p, dav_if_header *next_ih,
+ const char *uri, apr_size_t uri_len)
+{
+ dav_if_header *ih;
+
+ if ((ih = apr_pcalloc(p, sizeof(*ih))) == NULL)
+ return NULL;
+
+ ih->uri = uri;
+ ih->uri_len = uri_len;
+ ih->next = next_ih;
+
+ return ih;
+}
+
+/* add_if_state adds a condition to an if_header.
+ */
+static dav_error * dav_add_if_state(apr_pool_t *p, dav_if_header *ih,
+ const char *state_token,
+ dav_if_state_type t, int condition,
+ const dav_hooks_locks *locks_hooks)
+{
+ dav_if_state_list *new_sl;
+
+ new_sl = apr_pcalloc(p, sizeof(*new_sl));
+
+ new_sl->condition = condition;
+ new_sl->type = t;
+
+ if (t == dav_if_opaquelock) {
+ dav_error *err;
+
+ if ((err = (*locks_hooks->parse_locktoken)(p, state_token,
+ &new_sl->locktoken)) != NULL) {
+ /* If the state token cannot be parsed, treat it as an
+ * unknown state; this will evaluate to "false" later
+ * during If header validation. */
+ if (err->error_id == DAV_ERR_LOCK_UNK_STATE_TOKEN) {
+ new_sl->type = dav_if_unknown;
+ }
+ else {
+ /* ### maybe add a higher-level description */
+ return err;
+ }
+ }
+ }
+ else
+ new_sl->etag = state_token;
+
+ new_sl->next = ih->state;
+ ih->state = new_sl;
+
+ return NULL;
+}
+
+/* fetch_next_token returns the substring from str+1
+ * to the next occurrence of char term, or \0, whichever
+ * occurs first. Leading whitespace is ignored.
+ */
+static char *dav_fetch_next_token(char **str, char term)
+{
+ char *sp;
+ char *token;
+
+ token = *str + 1;
+
+ while (*token && (*token == ' ' || *token == '\t'))
+ token++;
+
+ if ((sp = strchr(token, term)) == NULL)
+ return NULL;
+
+ *sp = '\0';
+ *str = sp;
+ return token;
+}
+
+/* dav_process_if_header:
+ *
+ * If NULL (no error) is returned, then **if_header points to the
+ * "If" productions structure (or NULL if "If" is not present).
+ *
+ * ### this part is bogus:
+ * If an error is encountered, the error is logged. Parent should
+ * return err->status.
+ */
+static dav_error * dav_process_if_header(request_rec *r, dav_if_header **p_ih)
+{
+ dav_error *err;
+ char *str;
+ char *list;
+ const char *state_token;
+ const char *uri = NULL; /* scope of current production; NULL=no-tag */
+ apr_size_t uri_len = 0;
+ apr_status_t rv;
+ dav_if_header *ih = NULL;
+ apr_uri_t parsed_uri;
+ const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
+ enum {no_tagged, tagged, unknown} list_type = unknown;
+ int condition;
+
+ *p_ih = NULL;
+
+ if ((str = apr_pstrdup(r->pool, apr_table_get(r->headers_in, "If"))) == NULL)
+ return NULL;
+
+ while (*str) {
+ switch(*str) {
+ case '<':
+ /* Tagged-list production - following states apply to this uri */
+ if (list_type == no_tagged
+ || ((uri = dav_fetch_next_token(&str, '>')) == NULL)) {
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST,
+ DAV_ERR_IF_TAGGED, 0,
+ "Invalid If-header: unclosed \"<\" or "
+ "unexpected tagged-list production.");
+ }
+
+ /* 2518 specifies this must be an absolute URI; just take the
+ * relative part for later comparison against r->uri */
+ if ((rv = apr_uri_parse(r->pool, uri, &parsed_uri)) != APR_SUCCESS
+ || !parsed_uri.path) {
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST,
+ DAV_ERR_IF_TAGGED, rv,
+ "Invalid URI in tagged If-header.");
+ }
+ /* note that parsed_uri.path is allocated; we can trash it */
+
+ /* clean up the URI a bit */
+ if (!ap_normalize_path(parsed_uri.path,
+ AP_NORMALIZE_NOT_ABOVE_ROOT |
+ AP_NORMALIZE_DECODE_UNRESERVED)) {
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST,
+ DAV_ERR_IF_TAGGED, rv,
+ "Invalid URI path tagged If-header.");
+ }
+
+ /* the resources we will compare to have unencoded paths */
+ if (ap_unescape_url(parsed_uri.path) != OK) {
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST,
+ DAV_ERR_IF_TAGGED, rv,
+ "Invalid percent encoded URI in "
+ "tagged If-header.");
+ }
+
+ uri_len = strlen(parsed_uri.path);
+ if (uri_len > 1 && parsed_uri.path[uri_len - 1] == '/') {
+ parsed_uri.path[--uri_len] = '\0';
+ }
+
+ uri = parsed_uri.path;
+ list_type = tagged;
+ break;
+
+ case '(':
+ /* List production */
+
+ /* If a uri has not been encountered, this is a No-Tagged-List */
+ if (list_type == unknown)
+ list_type = no_tagged;
+
+ if ((list = dav_fetch_next_token(&str, ')')) == NULL) {
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST,
+ DAV_ERR_IF_UNCLOSED_PAREN, 0,
+ "Invalid If-header: unclosed \"(\".");
+ }
+
+ if ((ih = dav_add_if_resource(r->pool, ih, uri, uri_len)) == NULL) {
+ /* ### dav_add_if_resource() should return an error for us! */
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST,
+ DAV_ERR_IF_PARSE, 0,
+ "Internal server error parsing \"If:\" "
+ "header.");
+ }
+
+ condition = DAV_IF_COND_NORMAL;
+
+ while (*list) {
+ /* List is the entire production (in a uri scope) */
+
+ switch (*list) {
+ case '<':
+ if ((state_token = dav_fetch_next_token(&list, '>')) == NULL) {
+ /* ### add a description to this error */
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST,
+ DAV_ERR_IF_PARSE, 0, NULL);
+ }
+
+ if ((err = dav_add_if_state(r->pool, ih, state_token, dav_if_opaquelock,
+ condition, locks_hooks)) != NULL) {
+ /* ### maybe add a higher level description */
+ return err;
+ }
+ condition = DAV_IF_COND_NORMAL;
+ break;
+
+ case '[':
+ if ((state_token = dav_fetch_next_token(&list, ']')) == NULL) {
+ /* ### add a description to this error */
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST,
+ DAV_ERR_IF_PARSE, 0, NULL);
+ }
+
+ if ((err = dav_add_if_state(r->pool, ih, state_token, dav_if_etag,
+ condition, locks_hooks)) != NULL) {
+ /* ### maybe add a higher level description */
+ return err;
+ }
+ condition = DAV_IF_COND_NORMAL;
+ break;
+
+ case 'N':
+ if (list[1] == 'o' && list[2] == 't') {
+ if (condition != DAV_IF_COND_NORMAL) {
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST,
+ DAV_ERR_IF_MULTIPLE_NOT, 0,
+ "Invalid \"If:\" header: "
+ "Multiple \"not\" entries "
+ "for the same state.");
+ }
+ condition = DAV_IF_COND_NOT;
+ list += 2;
+ }
+ else {
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST,
+ DAV_ERR_IF_UNK_CHAR, 0,
+ "Invalid \"If:\" header: "
+ "Unexpected character in List");
+ }
+ break;
+
+ case ' ':
+ case '\t':
+ break;
+
+ default:
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST,
+ DAV_ERR_IF_UNK_CHAR, 0,
+ apr_psprintf(r->pool,
+ "Invalid \"If:\" "
+ "header: Unexpected "
+ "character encountered "
+ "(0x%02x, '%c').",
+ *list, *list));
+ }
+
+ list++;
+ }
+ break;
+
+ case ' ':
+ case '\t':
+ break;
+
+ default:
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST,
+ DAV_ERR_IF_UNK_CHAR, 0,
+ apr_psprintf(r->pool,
+ "Invalid \"If:\" header: "
+ "Unexpected character "
+ "encountered (0x%02x, '%c').",
+ *str, *str));
+ }
+
+ str++;
+ }
+
+ *p_ih = ih;
+ return NULL;
+}
+
+static int dav_find_submitted_locktoken(const dav_if_header *if_header,
+ const dav_lock *lock_list,
+ const dav_hooks_locks *locks_hooks)
+{
+ for (; if_header != NULL; if_header = if_header->next) {
+ const dav_if_state_list *state_list;
+
+ for (state_list = if_header->state;
+ state_list != NULL;
+ state_list = state_list->next) {
+
+ if (state_list->type == dav_if_opaquelock) {
+ const dav_lock *lock;
+
+ /* given state_list->locktoken, match it */
+
+ /*
+ ** The resource will have one or more lock tokens. We only
+ ** need to match one of them against any token in the
+ ** If: header.
+ **
+ ** One token case: It is an exclusive or shared lock. Either
+ ** way, we must find it.
+ **
+ ** N token case: They are shared locks. By policy, we need
+ ** to match only one. The resource's other
+ ** tokens may belong to somebody else (so we
+ ** shouldn't see them in the If: header anyway)
+ */
+ for (lock = lock_list; lock != NULL; lock = lock->next) {
+
+ if (!(*locks_hooks->compare_locktoken)(state_list->locktoken, lock->locktoken)) {
+ return 1;
+ }
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+/* dav_validate_resource_state:
+ * Returns NULL if path/uri meets if-header and lock requirements
+ */
+static dav_error * dav_validate_resource_state(apr_pool_t *p,
+ const dav_resource *resource,
+ dav_lockdb *lockdb,
+ const dav_if_header *if_header,
+ int flags,
+ dav_buffer *pbuf,
+ request_rec *r)
+{
+ dav_error *err;
+ const char *uri;
+ const char *etag;
+ const dav_hooks_locks *locks_hooks = (lockdb ? lockdb->hooks : NULL);
+ const dav_if_header *ifhdr_scan;
+ dav_if_state_list *state_list;
+ dav_lock *lock_list;
+ dav_lock *lock;
+ int num_matched;
+ int num_that_apply;
+ int seen_locktoken;
+ apr_size_t uri_len;
+ const char *reason = NULL;
+
+ /* DBG1("validate: <%s>", resource->uri); */
+
+ /*
+ ** The resource will have one of three states:
+ **
+ ** 1) No locks. We have no special requirements that the user supply
+ ** specific locktokens. One of the state lists must match, and
+ ** we're done.
+ **
+ ** 2) One exclusive lock. The locktoken must appear *anywhere* in the
+ ** If: header. Of course, asserting the token in a "Not" term will
+ ** quickly fail that state list :-). If the locktoken appears in
+ ** one of the state lists *and* one state list matches, then we're
+ ** done.
+ **
+ ** 3) One or more shared locks. One of the locktokens must appear
+ ** *anywhere* in the If: header. If one of the locktokens appears,
+ ** and we match one state list, then we are done.
+ **
+ ** The <seen_locktoken> variable determines whether we have seen one
+ ** of this resource's locktokens in the If: header.
+ */
+
+ /*
+ ** If this is a new lock request, <flags> will contain the requested
+ ** lock scope. Three rules apply:
+ **
+ ** 1) Do not require a (shared) locktoken to be seen (when we are
+ ** applying another shared lock)
+ ** 2) If the scope is exclusive and we see any locks, fail.
+ ** 3) If the scope is shared and we see an exclusive lock, fail.
+ */
+
+ if (lockdb == NULL) {
+ /* we're in State 1. no locks. */
+ lock_list = NULL;
+ }
+ else {
+ /*
+ ** ### hrm... we don't need to have these fully
+ ** ### resolved since we're only looking at the
+ ** ### locktokens...
+ **
+ ** ### use get_locks w/ calltype=PARTIAL
+ */
+ if ((err = dav_lock_query(lockdb, resource, &lock_list)) != NULL) {
+ return dav_push_error(p,
+ HTTP_INTERNAL_SERVER_ERROR, 0,
+ "The locks could not be queried for "
+ "verification against a possible \"If:\" "
+ "header.",
+ err);
+ }
+
+ /* lock_list now determines whether we're in State 1, 2, or 3. */
+ }
+
+ /*
+ ** For a new, exclusive lock: if any locks exist, fail.
+ ** For a new, shared lock: if an exclusive lock exists, fail.
+ ** else, do not require a token to be seen.
+ */
+ if (flags & DAV_LOCKSCOPE_EXCLUSIVE) {
+ if (lock_list != NULL) {
+ return dav_new_error(p, HTTP_LOCKED, 0, 0,
+ "Existing lock(s) on the requested resource "
+ "prevent an exclusive lock.");
+ }
+
+ /*
+ ** There are no locks, so we can pretend that we've already met
+ ** any requirement to find the resource's locks in an If: header.
+ */
+ seen_locktoken = 1;
+ }
+ else if (flags & DAV_LOCKSCOPE_SHARED) {
+ /*
+ ** Strictly speaking, we don't need this loop. Either the first
+ ** (and only) lock will be EXCLUSIVE, or none of them will be.
+ */
+ for (lock = lock_list; lock != NULL; lock = lock->next) {
+ if (lock->scope == DAV_LOCKSCOPE_EXCLUSIVE) {
+ return dav_new_error(p, HTTP_LOCKED, 0, 0,
+ "The requested resource is already "
+ "locked exclusively.");
+ }
+ }
+
+ /*
+ ** The locks on the resource (if any) are all shared. Set the
+ ** <seen_locktoken> flag to indicate that we do not need to find
+ ** the locks in an If: header.
+ */
+ seen_locktoken = 1;
+ }
+ else {
+ /*
+ ** For methods other than LOCK:
+ **
+ ** If we have no locks or if the resource is not being modified
+ ** (per RFC 4918 the lock token is not required on resources
+ ** we are not changing), then <seen_locktoken> can be set to true --
+ ** pretending that we've already met the requirement of seeing one
+ ** of the resource's locks in the If: header.
+ **
+ ** Otherwise, it must be cleared and we'll look for one.
+ */
+ seen_locktoken = (lock_list == NULL
+ || flags & DAV_VALIDATE_NO_MODIFY);
+ }
+
+ /*
+ ** If there is no If: header, then we can shortcut some logic:
+ **
+ ** 1) if we do not need to find a locktoken in the (non-existent) If:
+ ** header, then we are successful.
+ **
+ ** 2) if we must find a locktoken in the (non-existent) If: header, then
+ ** we fail.
+ */
+ if (if_header == NULL) {
+ if (seen_locktoken)
+ return NULL;
+
+ return dav_new_error(p, HTTP_LOCKED, 0, 0,
+ "This resource is locked and an \"If:\" header "
+ "was not supplied to allow access to the "
+ "resource.");
+ }
+ /* the If: header is present */
+
+ /*
+ ** If a dummy header is present (because of a Lock-Token: header), then
+ ** we are required to find that token in this resource's set of locks.
+ ** If we have no locks, then we immediately fail.
+ **
+ ** This is a 400 (Bad Request) since they should only submit a locktoken
+ ** that actually exists.
+ **
+ ** Don't issue this response if we're talking about the parent resource.
+ ** It is okay for that resource to NOT have this locktoken.
+ ** (in fact, it certainly will not: a dummy_header only occurs for the
+ ** UNLOCK method, the parent is checked only for locknull resources,
+ ** and the parent certainly does not have the (locknull's) locktoken)
+ */
+ if (lock_list == NULL && if_header->dummy_header) {
+ if (flags & DAV_VALIDATE_IS_PARENT)
+ return NULL;
+ return dav_new_error(p, HTTP_BAD_REQUEST, 0, 0,
+ "The locktoken specified in the \"Lock-Token:\" "
+ "header is invalid because this resource has no "
+ "outstanding locks.");
+ }
+
+ /*
+ ** Prepare the input URI. We want the URI to never have a trailing slash.
+ **
+ ** When URIs are placed into the dav_if_header structure, they are
+ ** guaranteed to never have a trailing slash. If the URIs are equivalent,
+ ** then it doesn't matter if they both lack a trailing slash -- they're
+ ** still equivalent.
+ **
+ ** Note: we could also ensure that a trailing slash is present on both
+ ** URIs, but the majority of URIs provided to us via a resource walk
+ ** will not contain that trailing slash.
+ */
+ uri = resource->uri;
+ uri_len = strlen(uri);
+ if (uri[uri_len - 1] == '/') {
+ dav_set_bufsize(p, pbuf, uri_len);
+ memcpy(pbuf->buf, uri, uri_len);
+ pbuf->buf[--uri_len] = '\0';
+ uri = pbuf->buf;
+ }
+
+ /* get the resource's etag; we may need it during the checks */
+ etag = (*resource->hooks->getetag)(resource);
+
+ /* how many state_lists apply to this URI? */
+ num_that_apply = 0;
+
+ /* If there are if-headers, fail if this resource
+ * does not match at least one state_list.
+ */
+ for (ifhdr_scan = if_header;
+ ifhdr_scan != NULL;
+ ifhdr_scan = ifhdr_scan->next) {
+
+ /* DBG2("uri=<%s> if_uri=<%s>", uri, ifhdr_scan->uri ? ifhdr_scan->uri : "(no uri)"); */
+
+ if (ifhdr_scan->uri != NULL
+ && (uri_len != ifhdr_scan->uri_len
+ || memcmp(uri, ifhdr_scan->uri, uri_len) != 0)) {
+ /*
+ ** A tagged-list's URI doesn't match this resource's URI.
+ ** Skip to the next state_list to see if it will match.
+ */
+ continue;
+ }
+
+ /* this state_list applies to this resource */
+
+ /*
+ ** ### only one state_list should ever apply! a no-tag, or a tagged
+ ** ### where S9.4.2 states only one can match.
+ **
+ ** ### revamp this code to loop thru ifhdr_scan until we find the
+ ** ### matching state_list. process it. stop.
+ */
+ ++num_that_apply;
+
+ /* To succeed, resource must match *all* of the states
+ * specified in the state_list.
+ */
+ for (state_list = ifhdr_scan->state;
+ state_list != NULL;
+ state_list = state_list->next) {
+
+ switch(state_list->type) {
+ case dav_if_etag:
+ {
+ const char *given_etag, *current_etag;
+ int mismatch;
+
+ /* Do a weak entity comparison function as defined in
+ * RFC 2616 13.3.3.
+ */
+ if (state_list->etag[0] == 'W' &&
+ state_list->etag[1] == '/') {
+ given_etag = state_list->etag + 2;
+ }
+ else {
+ given_etag = state_list->etag;
+ }
+ if (etag[0] == 'W' &&
+ etag[1] == '/') {
+ current_etag = etag + 2;
+ }
+ else {
+ current_etag = etag;
+ }
+
+ mismatch = strcmp(given_etag, current_etag);
+
+ if (state_list->condition == DAV_IF_COND_NORMAL && mismatch) {
+ /*
+ ** The specified entity-tag does not match the
+ ** entity-tag on the resource. This state_list is
+ ** not going to match. Bust outta here.
+ */
+ reason =
+ "an entity-tag was specified, but the resource's "
+ "actual ETag does not match.";
+ goto state_list_failed;
+ }
+ else if (state_list->condition == DAV_IF_COND_NOT
+ && !mismatch) {
+ /*
+ ** The specified entity-tag DOES match the
+ ** entity-tag on the resource. This state_list is
+ ** not going to match. Bust outta here.
+ */
+ reason =
+ "an entity-tag was specified using the \"Not\" form, "
+ "but the resource's actual ETag matches the provided "
+ "entity-tag.";
+ goto state_list_failed;
+ }
+ break;
+ }
+
+ case dav_if_opaquelock:
+ if (lockdb == NULL) {
+ if (state_list->condition == DAV_IF_COND_NOT) {
+ /* the locktoken is definitely not there! (success) */
+ continue;
+ }
+
+ /* condition == DAV_IF_COND_NORMAL */
+
+ /*
+ ** If no lockdb is provided, then validation fails for
+ ** this state_list (NORMAL means we were supposed to
+ ** find the token, which we obviously cannot do without
+ ** a lock database).
+ **
+ ** Go and try the next state list.
+ */
+ reason =
+ "a State-token was supplied, but a lock database "
+ "is not available for to provide the required lock.";
+ goto state_list_failed;
+ }
+
+ /* Resource validation 'fails' if:
+ * ANY of the lock->locktokens match
+ * a NOT state_list->locktoken,
+ * OR
+ * NONE of the lock->locktokens match
+ * a NORMAL state_list->locktoken.
+ */
+ num_matched = 0;
+ for (lock = lock_list; lock != NULL; lock = lock->next) {
+
+ /*
+ DBG2("compare: rsrc=%s ifhdr=%s",
+ (*locks_hooks->format_locktoken)(p, lock->locktoken),
+ (*locks_hooks->format_locktoken)(p, state_list->locktoken));
+ */
+
+ /* nothing to do if the locktokens do not match. */
+ if ((*locks_hooks->compare_locktoken)(state_list->locktoken, lock->locktoken)) {
+ continue;
+ }
+
+ /*
+ ** We have now matched up one of the resource's locktokens
+ ** to a locktoken in a State-token in the If: header.
+ ** Note this fact, so that we can pass the overall
+ ** requirement of seeing at least one of the resource's
+ ** locktokens.
+ */
+ seen_locktoken = 1;
+
+ if (state_list->condition == DAV_IF_COND_NOT) {
+ /*
+ ** This state requires that the specified locktoken
+ ** is NOT present on the resource. But we just found
+ ** it. There is no way this state-list can now
+ ** succeed, so go try another one.
+ */
+ reason =
+ "a State-token was supplied, which used a "
+ "\"Not\" condition. The State-token was found "
+ "in the locks on this resource";
+ goto state_list_failed;
+ }
+
+ /* condition == DAV_IF_COND_NORMAL */
+
+ /* Validate auth_user: If an authenticated user created
+ ** the lock, only the same user may submit that locktoken
+ ** to manipulate a resource.
+ */
+ if (lock->auth_user &&
+ (!r->user ||
+ strcmp(lock->auth_user, r->user))) {
+ const char *errmsg;
+
+ errmsg = apr_pstrcat(p, "User \"",
+ r->user,
+ "\" submitted a locktoken created "
+ "by user \"",
+ lock->auth_user, "\".", NULL);
+ return dav_new_error(p, HTTP_FORBIDDEN, 0, 0, errmsg);
+ }
+
+ /*
+ ** We just matched a specified State-Token to one of the
+ ** resource's locktokens.
+ **
+ ** Break out of the lock scan -- we only needed to find
+ ** one match (actually, there shouldn't be any other
+ ** matches in the lock list).
+ */
+ num_matched = 1;
+ break;
+ }
+
+ if (num_matched == 0
+ && state_list->condition == DAV_IF_COND_NORMAL) {
+ /*
+ ** We had a NORMAL state, meaning that we should have
+ ** found the State-Token within the locks on this
+ ** resource. We didn't, so this state_list must fail.
+ */
+ reason =
+ "a State-token was supplied, but it was not found "
+ "in the locks on this resource.";
+ goto state_list_failed;
+ }
+
+ break;
+
+ case dav_if_unknown:
+ /* Request is predicated on some unknown state token,
+ * which must be presumed to *not* match, so fail
+ * unless this is a Not condition. */
+
+ if (state_list->condition == DAV_IF_COND_NORMAL) {
+ reason =
+ "an unknown state token was supplied";
+ goto state_list_failed;
+ }
+ break;
+
+ } /* switch */
+ } /* foreach ( state_list ) */
+
+ /*
+ ** We've checked every state in this state_list and none of them
+ ** have failed. Since all of them succeeded, then we have a matching
+ ** state list and we may be done.
+ **
+ ** The next requirement is that we have seen one of the resource's
+ ** locktokens (if any). If we have, then we can just exit. If we
+ ** haven't, then we need to keep looking.
+ */
+ if (seen_locktoken) {
+ /* woo hoo! */
+ return NULL;
+ }
+
+ /*
+ ** Haven't seen one. Let's break out of the search and just look
+ ** for a matching locktoken.
+ */
+ break;
+
+ /*
+ ** This label is used when we detect that a state_list is not
+ ** going to match this resource. We bust out and try the next
+ ** state_list.
+ */
+ state_list_failed:
+ ;
+
+ } /* foreach ( ifhdr_scan ) */
+
+ /*
+ ** The above loop exits for one of two reasons:
+ ** 1) a state_list matched and seen_locktoken is false.
+ ** 2) all if_header structures were scanned, without (1) occurring
+ */
+
+ if (ifhdr_scan == NULL) {
+ /*
+ ** We finished the loop without finding any matching state lists.
+ */
+
+ /*
+ ** If none of the state_lists apply to this resource, then we
+ ** may have succeeded. Note that this scenario implies a
+ ** tagged-list with no matching state_lists. If the If: header
+ ** was a no-tag-list, then it would have applied to this resource.
+ **
+ ** S9.4.2 states that when no state_lists apply, then the header
+ ** should be ignored.
+ **
+ ** If we saw one of the resource's locktokens, then we're done.
+ ** If we did not see a locktoken, then we fail.
+ */
+ if (num_that_apply == 0) {
+ if (seen_locktoken)
+ return NULL;
+
+ /*
+ ** We may have aborted the scan before seeing the locktoken.
+ ** Rescan the If: header to see if we can find the locktoken
+ ** somewhere.
+ **
+ ** Note that seen_locktoken == 0 implies lock_list != NULL
+ ** which implies locks_hooks != NULL.
+ */
+ if (dav_find_submitted_locktoken(if_header, lock_list,
+ locks_hooks)) {
+ /*
+ ** We found a match! We're set... none of the If: header
+ ** assertions apply (implicit success), and the If: header
+ ** specified the locktoken somewhere. We're done.
+ */
+ return NULL;
+ }
+
+ return dav_new_error(p, HTTP_LOCKED, 0 /* error_id */, 0,
+ "This resource is locked and the \"If:\" "
+ "header did not specify one of the "
+ "locktokens for this resource's lock(s).");
+ }
+ /* else: one or more state_lists were applicable, but failed. */
+
+ /*
+ ** If the dummy_header did not match, then they specified an
+ ** incorrect token in the Lock-Token header. Forget whether the
+ ** If: statement matched or not... we'll tell them about the
+ ** bad Lock-Token first. That is considered a 400 (Bad Request).
+ */
+ if (if_header->dummy_header) {
+ return dav_new_error(p, HTTP_BAD_REQUEST, 0, 0,
+ "The locktoken specified in the "
+ "\"Lock-Token:\" header did not specify one "
+ "of this resource's locktoken(s).");
+ }
+
+ if (reason == NULL) {
+ return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0, 0,
+ "The preconditions specified by the \"If:\" "
+ "header did not match this resource.");
+ }
+
+ return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0, 0,
+ apr_psprintf(p,
+ "The precondition(s) specified by "
+ "the \"If:\" header did not match "
+ "this resource. At least one "
+ "failure is because: %s", reason));
+ }
+
+ /* assert seen_locktoken == 0 */
+
+ /*
+ ** ifhdr_scan != NULL implies we found a matching state_list.
+ **
+ ** Since we're still here, it also means that we have not yet found
+ ** one the resource's locktokens in the If: header.
+ **
+ ** Scan all the if_headers and states looking for one of this
+ ** resource's locktokens. Note that we need to go back and scan them
+ ** all -- we may have aborted a scan with a failure before we saw a
+ ** matching token.
+ **
+ ** Note that seen_locktoken == 0 implies lock_list != NULL which implies
+ ** locks_hooks != NULL.
+ */
+ if (dav_find_submitted_locktoken(if_header, lock_list, locks_hooks)) {
+ /*
+ ** We found a match! We're set... we have a matching state list,
+ ** and the If: header specified the locktoken somewhere. We're done.
+ */
+ return NULL;
+ }
+
+ /*
+ ** We had a matching state list, but the user agent did not specify one
+ ** of this resource's locktokens. Tell them so.
+ **
+ ** Note that we need to special-case the message on whether a "dummy"
+ ** header exists. If it exists, yet we didn't see a needed locktoken,
+ ** then that implies the dummy header (Lock-Token header) did NOT
+ ** specify one of this resource's locktokens. (this implies something
+ ** in the real If: header matched)
+ **
+ ** We want to note the 400 (Bad Request) in favor of a 423 (Locked).
+ */
+ if (if_header->dummy_header) {
+ return dav_new_error(p, HTTP_BAD_REQUEST, 0, 0,
+ "The locktoken specified in the "
+ "\"Lock-Token:\" header did not specify one "
+ "of this resource's locktoken(s).");
+ }
+
+ return dav_new_error(p, HTTP_LOCKED, 1 /* error_id */, 0,
+ "This resource is locked and the \"If:\" header "
+ "did not specify one of the "
+ "locktokens for this resource's lock(s).");
+}
+
+/* dav_validate_walker: Walker callback function to validate resource state */
+static dav_error * dav_validate_walker(dav_walk_resource *wres, int calltype)
+{
+ dav_walker_ctx *ctx = wres->walk_ctx;
+ dav_error *err;
+
+ if ((err = dav_validate_resource_state(ctx->w.pool, wres->resource,
+ ctx->w.lockdb,
+ ctx->if_header, ctx->flags,
+ &ctx->work_buf, ctx->r)) == NULL) {
+ /* There was no error, so just bug out. */
+ return NULL;
+ }
+
+ /*
+ ** If we have a serious server error, or if the request itself failed,
+ ** then just return error (not a multistatus).
+ */
+ if (ap_is_HTTP_SERVER_ERROR(err->status)
+ || (*wres->resource->hooks->is_same_resource)(wres->resource,
+ ctx->w.root)) {
+ /* ### maybe push a higher-level description? */
+ return err;
+ }
+
+ /* associate the error with the current URI */
+ dav_add_response(wres, err->status, NULL);
+
+ return NULL;
+}
+
+/* If-* header checking */
+static int dav_meets_conditions(request_rec *r, int resource_state)
+{
+ const char *if_match, *if_none_match;
+ int retVal;
+
+ /* If-Match '*' fix. Resource existence not checked by ap_meets_conditions.
+ * If-Match '*' request should succeed only if the resource exists. */
+ if ((if_match = apr_table_get(r->headers_in, "If-Match")) != NULL) {
+ if (if_match[0] == '*' && resource_state != DAV_RESOURCE_EXISTS)
+ return HTTP_PRECONDITION_FAILED;
+ }
+
+ retVal = ap_meets_conditions(r);
+
+ /* If-None-Match '*' fix. If-None-Match '*' request should succeed
+ * if the resource does not exist. */
+ if (retVal == HTTP_PRECONDITION_FAILED) {
+ /* Note. If if_none_match != NULL, if_none_match is the culprit.
+ * Since, in presence of If-None-Match,
+ * other If-* headers are undefined. */
+ if ((if_none_match =
+ apr_table_get(r->headers_in, "If-None-Match")) != NULL) {
+ if (if_none_match[0] == '*'
+ && resource_state != DAV_RESOURCE_EXISTS) {
+ return OK;
+ }
+ }
+ }
+
+ return retVal;
+}
+
+/*
+** dav_validate_request: Validate if-headers (and check for locks) on:
+** (1) r->filename @ depth;
+** (2) Parent of r->filename if check_parent == 1
+**
+** The check of parent should be done when it is necessary to verify that
+** the parent collection will accept a new member (ie current resource
+** state is null).
+**
+** Return OK on successful validation.
+** On error, return appropriate HTTP_* code, and log error. If a multi-stat
+** error is necessary, response will point to it, else NULL.
+*/
+DAV_DECLARE(dav_error *) dav_validate_request(request_rec *r,
+ dav_resource *resource,
+ int depth,
+ dav_locktoken *locktoken,
+ dav_response **response,
+ int flags,
+ dav_lockdb *lockdb)
+{
+ dav_error *err;
+ int result;
+ dav_if_header *if_header;
+ int lock_db_opened_locally = 0;
+ const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
+ const dav_hooks_repository *repos_hooks = resource->hooks;
+ dav_buffer work_buf = { 0 };
+ dav_response *new_response;
+ int resource_state;
+ const char *etag;
+ int set_etag = 0;
+
+#if DAV_DEBUG
+ if (depth && response == NULL) {
+ /*
+ ** ### bleck. we can't return errors for other URIs unless we have
+ ** ### a "response" ptr.
+ */
+ return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "DESIGN ERROR: dav_validate_request called "
+ "with depth>0, but no response ptr.");
+ }
+#endif
+
+ if (response != NULL)
+ *response = NULL;
+
+ /* Set the ETag header required by dav_meets_conditions() */
+ etag = apr_table_get(r->headers_out, "ETag");
+ if (!etag) {
+ etag = (*resource->hooks->getetag)(resource);
+ if (etag && *etag) {
+ apr_table_set(r->headers_out, "ETag", etag);
+ set_etag = 1;
+ }
+ }
+ /* Do the standard checks for conditional requests using
+ * If-..-Since, If-Match etc */
+ resource_state = dav_get_resource_state(r, resource);
+ result = dav_meets_conditions(r, resource_state);
+ if (set_etag) {
+ /*
+ * If we have set an ETag to headers out above for
+ * dav_meets_conditions() revert this here as we do not want to set
+ * the ETag in responses to requests with methods where this might not
+ * be desired.
+ */
+ apr_table_unset(r->headers_out, "ETag");
+ }
+ if (result != OK) {
+ return dav_new_error(r->pool, result, 0, 0, NULL);
+ }
+
+ /* always parse (and later process) the If: header */
+ if ((err = dav_process_if_header(r, &if_header)) != NULL) {
+ /* ### maybe add higher-level description */
+ return err;
+ }
+
+ /* If a locktoken was specified, create a dummy if_header with which
+ * to validate resources. In the interim, figure out why DAV uses
+ * locktokens in an if-header without a Lock-Token header to refresh
+ * locks, but a Lock-Token header without an if-header to remove them.
+ */
+ if (locktoken != NULL) {
+ dav_if_header *ifhdr_new;
+
+ ifhdr_new = apr_pcalloc(r->pool, sizeof(*ifhdr_new));
+ ifhdr_new->uri = resource->uri;
+ ifhdr_new->uri_len = strlen(resource->uri);
+ ifhdr_new->dummy_header = 1;
+
+ ifhdr_new->state = apr_pcalloc(r->pool, sizeof(*ifhdr_new->state));
+ ifhdr_new->state->type = dav_if_opaquelock;
+ ifhdr_new->state->condition = DAV_IF_COND_NORMAL;
+ ifhdr_new->state->locktoken = locktoken;
+
+ ifhdr_new->next = if_header;
+ if_header = ifhdr_new;
+ }
+
+ /*
+ ** If necessary, open the lock database (read-only, lazily);
+ ** the validation process may need to retrieve or update lock info.
+ ** Otherwise, assume provided lockdb is valid and opened rw.
+ */
+ if (lockdb == NULL) {
+ if (locks_hooks != NULL) {
+ if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
+ /* ### maybe insert higher-level comment */
+ return err;
+ }
+ lock_db_opened_locally = 1;
+ }
+ }
+
+ /* (1) Validate the specified resource, at the specified depth.
+ * Avoid the walk there is no if_header and we aren't planning
+ * to modify this resource. */
+ if (resource->exists && depth > 0 && !(!if_header && flags & DAV_VALIDATE_NO_MODIFY)) {
+ dav_walker_ctx ctx = { { 0 } };
+ dav_response *multi_status;
+
+ ctx.w.walk_type = DAV_WALKTYPE_NORMAL;
+ ctx.w.func = dav_validate_walker;
+ ctx.w.walk_ctx = &ctx;
+ ctx.w.pool = r->pool;
+ ctx.w.root = resource;
+
+ ctx.if_header = if_header;
+ ctx.r = r;
+ ctx.flags = flags;
+
+ if (lockdb != NULL) {
+ ctx.w.lockdb = lockdb;
+ ctx.w.walk_type |= DAV_WALKTYPE_LOCKNULL;
+ }
+
+ err = (*repos_hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status);
+ if (err == NULL) {
+ *response = multi_status;
+ }
+ /* else: implies a 5xx status code occurred. */
+ }
+ else {
+ err = dav_validate_resource_state(r->pool, resource, lockdb,
+ if_header, flags, &work_buf, r);
+ }
+
+ /* (2) Validate the parent resource if requested */
+ if (err == NULL && (flags & DAV_VALIDATE_PARENT)) {
+ dav_resource *parent_resource;
+
+ err = (*repos_hooks->get_parent_resource)(resource, &parent_resource);
+
+ if (err == NULL && parent_resource == NULL) {
+ err = dav_new_error(r->pool, HTTP_FORBIDDEN, 0, 0,
+ "Cannot access parent of repository root.");
+ }
+ else if (err == NULL) {
+ err = dav_validate_resource_state(r->pool, parent_resource, lockdb,
+ if_header,
+ flags | DAV_VALIDATE_IS_PARENT,
+ &work_buf, r);
+
+ /*
+ ** This error occurred on the parent resource. This implies that
+ ** we have to create a multistatus response (to report the error
+ ** against a URI other than the Request-URI). "Convert" this error
+ ** into a multistatus response.
+ */
+ if (err != NULL) {
+ new_response = apr_pcalloc(r->pool, sizeof(*new_response));
+
+ new_response->href = parent_resource->uri;
+ new_response->status = err->status;
+ new_response->desc =
+ "A validation error has occurred on the parent resource, "
+ "preventing the operation on the resource specified by "
+ "the Request-URI.";
+ if (err->desc != NULL) {
+ new_response->desc = apr_pstrcat(r->pool,
+ new_response->desc,
+ " The error was: ",
+ err->desc, NULL);
+ }
+
+ /* assert: DAV_VALIDATE_PARENT implies response != NULL */
+ new_response->next = *response;
+ *response = new_response;
+
+ err = NULL;
+ }
+ }
+ }
+
+ if (lock_db_opened_locally)
+ (*locks_hooks->close_lockdb)(lockdb);
+
+ /*
+ ** If we don't have a (serious) error, and we have multistatus responses,
+ ** then we need to construct an "error". This error will be the overall
+ ** status returned, and the multistatus responses will go into its body.
+ **
+ ** For certain methods, the overall error will be a 424. The default is
+ ** to construct a standard 207 response.
+ */
+ if (err == NULL && response != NULL && *response != NULL) {
+ apr_text *propstat = NULL;
+
+ if ((flags & DAV_VALIDATE_USE_424) != 0) {
+ /* manufacture a 424 error to hold the multistatus response(s) */
+ return dav_new_error(r->pool, HTTP_FAILED_DEPENDENCY, 0, 0,
+ "An error occurred on another resource, "
+ "preventing the requested operation on "
+ "this resource.");
+ }
+
+ /*
+ ** Whatever caused the error, the Request-URI should have a 424
+ ** associated with it since we cannot complete the method.
+ **
+ ** For a LOCK operation, insert an empty DAV:lockdiscovery property.
+ ** For other methods, return a simple 424.
+ */
+ if ((flags & DAV_VALIDATE_ADD_LD) != 0) {
+ propstat = apr_pcalloc(r->pool, sizeof(*propstat));
+ propstat->text =
+ "<D:propstat>" DEBUG_CR
+ "<D:prop><D:lockdiscovery/></D:prop>" DEBUG_CR
+ "<D:status>HTTP/1.1 424 Failed Dependency</D:status>" DEBUG_CR
+ "</D:propstat>" DEBUG_CR;
+ }
+
+ /* create the 424 response */
+ new_response = apr_pcalloc(r->pool, sizeof(*new_response));
+ new_response->href = resource->uri;
+ new_response->status = HTTP_FAILED_DEPENDENCY;
+ new_response->propresult.propstats = propstat;
+ new_response->desc =
+ "An error occurred on another resource, preventing the "
+ "requested operation on this resource.";
+
+ new_response->next = *response;
+ *response = new_response;
+
+ /* manufacture a 207 error for the multistatus response(s) */
+ return dav_new_error(r->pool, HTTP_MULTI_STATUS, 0, 0,
+ "Error(s) occurred on resources during the "
+ "validation process.");
+ }
+
+ return err;
+}
+
+/* dav_get_locktoken_list:
+ *
+ * Sets ltl to a locktoken_list of all positive locktokens in header,
+ * else NULL if no If-header, or no positive locktokens.
+ */
+DAV_DECLARE(dav_error *) dav_get_locktoken_list(request_rec *r,
+ dav_locktoken_list **ltl)
+{
+ dav_error *err;
+ dav_if_header *if_header;
+ dav_if_state_list *if_state;
+ dav_locktoken_list *lock_token = NULL;
+
+ *ltl = NULL;
+
+ if ((err = dav_process_if_header(r, &if_header)) != NULL) {
+ /* ### add a higher-level description? */
+ return err;
+ }
+
+ while (if_header != NULL) {
+ if_state = if_header->state; /* Beginning of the if_state linked list */
+ while (if_state != NULL) {
+ if (if_state->condition == DAV_IF_COND_NORMAL
+ && if_state->type == dav_if_opaquelock) {
+ lock_token = apr_pcalloc(r->pool, sizeof(dav_locktoken_list));
+ lock_token->locktoken = if_state->locktoken;
+ lock_token->next = *ltl;
+ *ltl = lock_token;
+ }
+ if_state = if_state->next;
+ }
+ if_header = if_header->next;
+ }
+ if (*ltl == NULL) {
+ /* No nodes added */
+ return dav_new_error(r->pool, HTTP_BAD_REQUEST, DAV_ERR_IF_ABSENT, 0,
+ "No locktokens were specified in the \"If:\" "
+ "header, so the refresh could not be performed.");
+ }
+
+ return NULL;
+}
+
+#if 0 /* not needed right now... */
+
+static const char *strip_white(const char *s, apr_pool_t *pool)
+{
+ apr_size_t idx;
+
+ /* trim leading whitespace */
+ while (apr_isspace(*s)) /* assume: return false for '\0' */
+ ++s;
+
+ /* trim trailing whitespace */
+ idx = strlen(s) - 1;
+ if (apr_isspace(s[idx])) {
+ char *s2 = apr_pstrdup(pool, s);
+
+ while (apr_isspace(s2[idx]) && idx > 0)
+ --idx;
+ s2[idx + 1] = '\0';
+ return s2;
+ }
+
+ return s;
+}
+#endif
+
+#define DAV_LABEL_HDR "Label"
+
+/* dav_add_vary_header
+ *
+ * If there were any headers in the request which require a Vary header
+ * in the response, add it.
+ */
+DAV_DECLARE(void) dav_add_vary_header(request_rec *in_req,
+ request_rec *out_req,
+ const dav_resource *resource)
+{
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(in_req);
+
+ /* ### this is probably all wrong... I think there is a function in
+ ### the Apache API to add things to the Vary header. need to check */
+
+ /* Only versioning headers require a Vary response header,
+ * so only do this check if there is a versioning provider */
+ if (vsn_hooks != NULL) {
+ const char *target = apr_table_get(in_req->headers_in, DAV_LABEL_HDR);
+
+ /* If Target-Selector specified, add it to the Vary header */
+ if (target != NULL) {
+ const char *vary = apr_table_get(out_req->headers_out, "Vary");
+
+ if (vary == NULL)
+ vary = DAV_LABEL_HDR;
+ else
+ vary = apr_pstrcat(out_req->pool, vary, "," DAV_LABEL_HDR,
+ NULL);
+
+ apr_table_setn(out_req->headers_out, "Vary", vary);
+ }
+ }
+}
+
+/* dav_can_auto_checkout
+ *
+ * Determine whether auto-checkout is enabled for a resource.
+ * r - the request_rec
+ * resource - the resource
+ * auto_version - the value of the auto_versionable hook for the resource
+ * lockdb - pointer to lock database (opened if necessary)
+ * auto_checkout - set to 1 if auto-checkout enabled
+ */
+static dav_error * dav_can_auto_checkout(
+ request_rec *r,
+ dav_resource *resource,
+ dav_auto_version auto_version,
+ dav_lockdb **lockdb,
+ int *auto_checkout)
+{
+ dav_error *err;
+ dav_lock *lock_list;
+
+ *auto_checkout = 0;
+
+ if (auto_version == DAV_AUTO_VERSION_ALWAYS) {
+ *auto_checkout = 1;
+ }
+ else if (auto_version == DAV_AUTO_VERSION_LOCKED) {
+ if (*lockdb == NULL) {
+ const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
+
+ if (locks_hooks == NULL) {
+ return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "Auto-checkout is only enabled for locked resources, "
+ "but there is no lock provider.");
+ }
+
+ if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, lockdb)) != NULL) {
+ return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
+ "Cannot open lock database to determine "
+ "auto-versioning behavior.",
+ err);
+ }
+ }
+
+ if ((err = dav_lock_query(*lockdb, resource, &lock_list)) != NULL) {
+ return dav_push_error(r->pool,
+ HTTP_INTERNAL_SERVER_ERROR, 0,
+ "The locks could not be queried for "
+ "determining auto-versioning behavior.",
+ err);
+ }
+
+ if (lock_list != NULL)
+ *auto_checkout = 1;
+ }
+
+ return NULL;
+}
+
+/* see mod_dav.h for docco */
+DAV_DECLARE(dav_error *) dav_auto_checkout(
+ request_rec *r,
+ dav_resource *resource,
+ int parent_only,
+ dav_auto_version_info *av_info)
+{
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+ dav_lockdb *lockdb = NULL;
+ dav_error *err = NULL;
+
+ /* Initialize results */
+ memset(av_info, 0, sizeof(*av_info));
+
+ /* if no versioning provider, just return */
+ if (vsn_hooks == NULL)
+ return NULL;
+
+ /* check parent resource if requested or if resource must be created */
+ if (!resource->exists || parent_only) {
+ dav_resource *parent;
+
+ if ((err = (*resource->hooks->get_parent_resource)(resource,
+ &parent)) != NULL)
+ goto done;
+
+ if (parent == NULL || !parent->exists) {
+ err = dav_new_error(r->pool, HTTP_CONFLICT, 0, 0,
+ apr_psprintf(r->pool,
+ "Missing one or more intermediate "
+ "collections. Cannot create resource %s.",
+ ap_escape_html(r->pool, resource->uri)));
+ goto done;
+ }
+
+ av_info->parent_resource = parent;
+
+ /* if parent versioned and not checked out, see if it can be */
+ if (parent->versioned && !parent->working) {
+ int checkout_parent;
+
+ if ((err = dav_can_auto_checkout(r, parent,
+ (*vsn_hooks->auto_versionable)(parent),
+ &lockdb, &checkout_parent))
+ != NULL) {
+ goto done;
+ }
+
+ if (!checkout_parent) {
+ err = dav_new_error(r->pool, HTTP_CONFLICT, 0, 0,
+ "<DAV:cannot-modify-checked-in-parent>");
+ goto done;
+ }
+
+ /* Try to checkout the parent collection.
+ * Note that auto-versioning can only be applied to a version selector,
+ * so no separate working resource will be created.
+ */
+ if ((err = (*vsn_hooks->checkout)(parent, 1 /*auto_checkout*/,
+ 0, 0, 0, NULL, NULL))
+ != NULL)
+ {
+ err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
+ apr_psprintf(r->pool,
+ "Unable to auto-checkout parent collection. "
+ "Cannot create resource %s.",
+ ap_escape_html(r->pool, resource->uri)),
+ err);
+ goto done;
+ }
+
+ /* remember that parent was checked out */
+ av_info->parent_checkedout = 1;
+ }
+ }
+
+ /* if only checking parent, we're done */
+ if (parent_only)
+ goto done;
+
+ /* if creating a new resource, see if it should be version-controlled */
+ if (!resource->exists
+ && (*vsn_hooks->auto_versionable)(resource) == DAV_AUTO_VERSION_ALWAYS) {
+
+ if ((err = (*vsn_hooks->vsn_control)(resource, NULL)) != NULL) {
+ err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
+ apr_psprintf(r->pool,
+ "Unable to create versioned resource %s.",
+ ap_escape_html(r->pool, resource->uri)),
+ err);
+ goto done;
+ }
+
+ /* remember that resource was created */
+ av_info->resource_versioned = 1;
+ }
+
+ /* if resource is versioned, make sure it is checked out */
+ if (resource->versioned && !resource->working) {
+ int checkout_resource;
+
+ if ((err = dav_can_auto_checkout(r, resource,
+ (*vsn_hooks->auto_versionable)(resource),
+ &lockdb, &checkout_resource)) != NULL) {
+ goto done;
+ }
+
+ if (!checkout_resource) {
+ err = dav_new_error(r->pool, HTTP_CONFLICT, 0, 0,
+ "<DAV:cannot-modify-version-controlled-content>");
+ goto done;
+ }
+
+ /* Auto-versioning can only be applied to version selectors, so
+ * no separate working resource will be created. */
+ if ((err = (*vsn_hooks->checkout)(resource, 1 /*auto_checkout*/,
+ 0, 0, 0, NULL, NULL))
+ != NULL)
+ {
+ err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
+ apr_psprintf(r->pool,
+ "Unable to checkout resource %s.",
+ ap_escape_html(r->pool, resource->uri)),
+ err);
+ goto done;
+ }
+
+ /* remember that resource was checked out */
+ av_info->resource_checkedout = 1;
+ }
+
+done:
+
+ /* make sure lock database is closed */
+ if (lockdb != NULL)
+ (*lockdb->hooks->close_lockdb)(lockdb);
+
+ /* if an error occurred, undo any auto-versioning operations already done */
+ if (err != NULL) {
+ dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, av_info);
+ return err;
+ }
+
+ return NULL;
+}
+
+/* see mod_dav.h for docco */
+DAV_DECLARE(dav_error *) dav_auto_checkin(
+ request_rec *r,
+ dav_resource *resource,
+ int undo,
+ int unlock,
+ dav_auto_version_info *av_info)
+{
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+ dav_error *err = NULL;
+ dav_auto_version auto_version;
+
+ /* If no versioning provider, this is a no-op */
+ if (vsn_hooks == NULL)
+ return NULL;
+
+ /* If undoing auto-checkouts, then do uncheckouts */
+ if (undo) {
+ if (resource != NULL) {
+ if (av_info->resource_checkedout) {
+ if ((err = (*vsn_hooks->uncheckout)(resource)) != NULL) {
+ return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
+ apr_psprintf(r->pool,
+ "Unable to undo auto-checkout "
+ "of resource %s.",
+ ap_escape_html(r->pool, resource->uri)),
+ err);
+ }
+ }
+
+ if (av_info->resource_versioned) {
+ dav_response *response;
+
+ /* ### should we do anything with the response? */
+ if ((err = (*resource->hooks->remove_resource)(resource,
+ &response)) != NULL) {
+ return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
+ apr_psprintf(r->pool,
+ "Unable to undo auto-version-control "
+ "of resource %s.",
+ ap_escape_html(r->pool, resource->uri)),
+ err);
+ }
+ }
+ }
+
+ if (av_info->parent_resource != NULL && av_info->parent_checkedout) {
+ if ((err = (*vsn_hooks->uncheckout)(av_info->parent_resource)) != NULL) {
+ return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
+ apr_psprintf(r->pool,
+ "Unable to undo auto-checkout "
+ "of parent collection %s.",
+ ap_escape_html(r->pool, av_info->parent_resource->uri)),
+ err);
+ }
+ }
+
+ return NULL;
+ }
+
+ /* If the resource was checked out, and auto-checkin is enabled,
+ * then check it in.
+ */
+ if (resource != NULL && resource->working
+ && (unlock || av_info->resource_checkedout)) {
+
+ auto_version = (*vsn_hooks->auto_versionable)(resource);
+
+ if (auto_version == DAV_AUTO_VERSION_ALWAYS ||
+ (unlock && (auto_version == DAV_AUTO_VERSION_LOCKED))) {
+
+ if ((err = (*vsn_hooks->checkin)(resource,
+ 0 /*keep_checked_out*/, NULL))
+ != NULL) {
+ return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
+ apr_psprintf(r->pool,
+ "Unable to auto-checkin resource %s.",
+ ap_escape_html(r->pool, resource->uri)),
+ err);
+ }
+ }
+ }
+
+ /* If parent resource was checked out, and auto-checkin is enabled,
+ * then check it in.
+ */
+ if (!unlock
+ && av_info->parent_checkedout
+ && av_info->parent_resource != NULL
+ && av_info->parent_resource->working) {
+
+ auto_version = (*vsn_hooks->auto_versionable)(av_info->parent_resource);
+
+ if (auto_version == DAV_AUTO_VERSION_ALWAYS) {
+ if ((err = (*vsn_hooks->checkin)(av_info->parent_resource,
+ 0 /*keep_checked_out*/, NULL))
+ != NULL) {
+ return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
+ apr_psprintf(r->pool,
+ "Unable to auto-checkin parent collection %s.",
+ ap_escape_html(r->pool, av_info->parent_resource->uri)),
+ err);
+ }
+ }
+ }
+
+ return NULL;
+}
diff --git a/modules/dav/main/util_lock.c b/modules/dav/main/util_lock.c
new file mode 100644
index 0000000..1b3a647
--- /dev/null
+++ b/modules/dav/main/util_lock.c
@@ -0,0 +1,798 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+** DAV repository-independent lock functions
+*/
+
+#include "apr.h"
+#include "apr_strings.h"
+
+#include "mod_dav.h"
+#include "http_log.h"
+#include "http_config.h"
+#include "http_protocol.h"
+#include "http_core.h"
+
+APLOG_USE_MODULE(dav);
+
+/* ---------------------------------------------------------------
+**
+** Property-related lock functions
+**
+*/
+
+/*
+** dav_lock_get_activelock: Returns a <lockdiscovery> containing
+** an activelock element for every item in the lock_discovery tree
+*/
+DAV_DECLARE(const char *) dav_lock_get_activelock(request_rec *r,
+ dav_lock *lock,
+ dav_buffer *pbuf)
+{
+ dav_lock *lock_scan;
+ const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
+ int count = 0;
+ dav_buffer work_buf = { 0 };
+ apr_pool_t *p = r->pool;
+
+ /* If no locks or no lock provider, there are no locks */
+ if (lock == NULL || hooks == NULL) {
+ /*
+ ** Since resourcediscovery is defined with (activelock)*,
+ ** <D:activelock/> shouldn't be necessary for an empty lock.
+ */
+ return "";
+ }
+
+ /*
+ ** Note: it could be interesting to sum the lengths of the owners
+ ** and locktokens during this loop. However, the buffer
+ ** mechanism provides some rough padding so that we don't
+ ** really need to have an exact size. Further, constructing
+ ** locktoken strings could be relatively expensive.
+ */
+ for (lock_scan = lock; lock_scan != NULL; lock_scan = lock_scan->next)
+ count++;
+
+ /* if a buffer was not provided, then use an internal buffer */
+ if (pbuf == NULL)
+ pbuf = &work_buf;
+
+ /* reset the length before we start appending stuff */
+ pbuf->cur_len = 0;
+
+ /* prep the buffer with a "good" size */
+ dav_check_bufsize(p, pbuf, count * 300);
+
+ for (; lock != NULL; lock = lock->next) {
+ char tmp[100];
+
+#if DAV_DEBUG
+ if (lock->rectype == DAV_LOCKREC_INDIRECT_PARTIAL) {
+ /* ### crap. design error */
+ dav_buffer_append(p, pbuf,
+ "DESIGN ERROR: attempted to product an "
+ "activelock element from a partial, indirect "
+ "lock record. Creating an XML parsing error "
+ "to ease detection of this situation: <");
+ }
+#endif
+
+ dav_buffer_append(p, pbuf, "<D:activelock>" DEBUG_CR "<D:locktype>");
+ switch (lock->type) {
+ case DAV_LOCKTYPE_WRITE:
+ dav_buffer_append(p, pbuf, "<D:write/>");
+ break;
+ default:
+ /* ### internal error. log something? */
+ break;
+ }
+ dav_buffer_append(p, pbuf, "</D:locktype>" DEBUG_CR "<D:lockscope>");
+ switch (lock->scope) {
+ case DAV_LOCKSCOPE_EXCLUSIVE:
+ dav_buffer_append(p, pbuf, "<D:exclusive/>");
+ break;
+ case DAV_LOCKSCOPE_SHARED:
+ dav_buffer_append(p, pbuf, "<D:shared/>");
+ break;
+ default:
+ /* ### internal error. log something? */
+ break;
+ }
+ dav_buffer_append(p, pbuf, "</D:lockscope>" DEBUG_CR);
+ apr_snprintf(tmp, sizeof(tmp), "<D:depth>%s</D:depth>" DEBUG_CR,
+ lock->depth == DAV_INFINITY ? "infinity" : "0");
+ dav_buffer_append(p, pbuf, tmp);
+
+ if (lock->owner) {
+ /*
+ ** This contains a complete, self-contained <DAV:owner> element,
+ ** with namespace declarations and xml:lang handling. Just drop
+ ** it in.
+ */
+ dav_buffer_append(p, pbuf, lock->owner);
+ }
+
+ dav_buffer_append(p, pbuf, "<D:timeout>");
+ if (lock->timeout == DAV_TIMEOUT_INFINITE) {
+ dav_buffer_append(p, pbuf, "Infinite");
+ }
+ else {
+ time_t now = time(NULL);
+
+ /*
+ ** Check if the timeout is not, for any reason, already elapsed.
+ ** (e.g., because of a large collection, or disk under heavy load...)
+ */
+ if (now >= lock->timeout) {
+ dav_buffer_append(p, pbuf, "Second-0");
+ }
+ else {
+ apr_snprintf(tmp, sizeof(tmp), "Second-%lu", (long unsigned int)(lock->timeout - now));
+ dav_buffer_append(p, pbuf, tmp);
+ }
+ }
+
+ dav_buffer_append(p, pbuf,
+ "</D:timeout>" DEBUG_CR
+ "<D:locktoken>" DEBUG_CR
+ "<D:href>");
+ dav_buffer_append(p, pbuf,
+ (*hooks->format_locktoken)(p, lock->locktoken));
+ dav_buffer_append(p, pbuf,
+ "</D:href>" DEBUG_CR
+ "</D:locktoken>" DEBUG_CR
+ "</D:activelock>" DEBUG_CR);
+ }
+
+ return pbuf->buf;
+}
+
+/*
+** dav_lock_parse_lockinfo: Validates the given xml_doc to contain a
+** lockinfo XML element, then populates a dav_lock structure
+** with its contents.
+*/
+DAV_DECLARE(dav_error *) dav_lock_parse_lockinfo(request_rec *r,
+ const dav_resource *resource,
+ dav_lockdb *lockdb,
+ const apr_xml_doc *doc,
+ dav_lock **lock_request)
+{
+ apr_pool_t *p = r->pool;
+ dav_error *err;
+ apr_xml_elem *child;
+ dav_lock *lock;
+
+ if (!dav_validate_root(doc, "lockinfo")) {
+ return dav_new_error(p, HTTP_BAD_REQUEST, 0, 0,
+ "The request body contains an unexpected "
+ "XML root element.");
+ }
+
+ if ((err = (*lockdb->hooks->create_lock)(lockdb, resource,
+ &lock)) != NULL) {
+ return dav_push_error(p, err->status, 0,
+ "Could not parse the lockinfo due to an "
+ "internal problem creating a lock structure.",
+ err);
+ }
+
+ lock->depth = dav_get_depth(r, DAV_INFINITY);
+ if (lock->depth == -1) {
+ return dav_new_error(p, HTTP_BAD_REQUEST, 0, 0,
+ "An invalid Depth header was specified.");
+ }
+ lock->timeout = dav_get_timeout(r);
+
+ /* Parse elements in the XML body */
+ for (child = doc->root->first_child; child; child = child->next) {
+ if (strcmp(child->name, "locktype") == 0
+ && child->first_child
+ && lock->type == DAV_LOCKTYPE_UNKNOWN) {
+ if (strcmp(child->first_child->name, "write") == 0) {
+ lock->type = DAV_LOCKTYPE_WRITE;
+ continue;
+ }
+ }
+ if (strcmp(child->name, "lockscope") == 0
+ && child->first_child
+ && lock->scope == DAV_LOCKSCOPE_UNKNOWN) {
+ if (strcmp(child->first_child->name, "exclusive") == 0)
+ lock->scope = DAV_LOCKSCOPE_EXCLUSIVE;
+ else if (strcmp(child->first_child->name, "shared") == 0)
+ lock->scope = DAV_LOCKSCOPE_SHARED;
+ if (lock->scope != DAV_LOCKSCOPE_UNKNOWN)
+ continue;
+ }
+
+ if (strcmp(child->name, "owner") == 0 && lock->owner == NULL) {
+ const char *text;
+
+ /* quote all the values in the <DAV:owner> element */
+ apr_xml_quote_elem(p, child);
+
+ /*
+ ** Store a full <DAV:owner> element with namespace definitions
+ ** and an xml:lang definition, if applicable.
+ */
+ apr_xml_to_text(p, child, APR_XML_X2T_FULL_NS_LANG, doc->namespaces,
+ NULL, &text, NULL);
+ lock->owner = text;
+
+ continue;
+ }
+
+ return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0, 0,
+ apr_psprintf(p,
+ "The server cannot satisfy the "
+ "LOCK request due to an unknown XML "
+ "element (\"%s\") within the "
+ "DAV:lockinfo element.",
+ child->name));
+ }
+
+ *lock_request = lock;
+ return NULL;
+}
+
+/* ---------------------------------------------------------------
+**
+** General lock functions
+**
+*/
+
+/* dav_lock_walker: Walker callback function to record indirect locks */
+static dav_error * dav_lock_walker(dav_walk_resource *wres, int calltype)
+{
+ dav_walker_ctx *ctx = wres->walk_ctx;
+ dav_error *err;
+
+ /* We don't want to set indirects on the target */
+ if ((*wres->resource->hooks->is_same_resource)(wres->resource,
+ ctx->w.root))
+ return NULL;
+
+ if ((err = (*ctx->w.lockdb->hooks->append_locks)(ctx->w.lockdb,
+ wres->resource, 1,
+ ctx->lock)) != NULL) {
+ if (ap_is_HTTP_SERVER_ERROR(err->status)) {
+ /* ### add a higher-level description? */
+ return err;
+ }
+
+ /* add to the multistatus response */
+ dav_add_response(wres, err->status, NULL);
+
+ /*
+ ** ### actually, this is probably wrong: we want to fail the whole
+ ** ### LOCK process if something goes bad. maybe the caller should
+ ** ### do a dav_unlock() (e.g. a rollback) if any errors occurred.
+ */
+ }
+
+ return NULL;
+}
+
+/*
+** dav_add_lock: Add a direct lock for resource, and indirect locks for
+** all children, bounded by depth.
+** ### assume request only contains one lock
+*/
+DAV_DECLARE(dav_error *) dav_add_lock(request_rec *r,
+ const dav_resource *resource,
+ dav_lockdb *lockdb, dav_lock *lock,
+ dav_response **response)
+{
+ dav_error *err;
+ int depth = lock->depth;
+
+ *response = NULL;
+
+ /* Requested lock can be:
+ * Depth: 0 for null resource, existing resource, or existing collection
+ * Depth: Inf for existing collection
+ */
+
+ /*
+ ** 2518 9.2 says to ignore depth if target is not a collection (it has
+ ** no internal children); pretend the client gave the correct depth.
+ */
+ if (!resource->collection) {
+ depth = 0;
+ }
+
+ /* In all cases, first add direct entry in lockdb */
+
+ /*
+ ** Append the new (direct) lock to the resource's existing locks.
+ **
+ ** Note: this also handles locknull resources
+ */
+ if ((err = (*lockdb->hooks->append_locks)(lockdb, resource, 0,
+ lock)) != NULL) {
+ /* ### maybe add a higher-level description */
+ return err;
+ }
+
+ if (depth > 0) {
+ /* Walk existing collection and set indirect locks */
+ dav_walker_ctx ctx = { { 0 } };
+ dav_response *multi_status;
+
+ ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_AUTH;
+ ctx.w.func = dav_lock_walker;
+ ctx.w.walk_ctx = &ctx;
+ ctx.w.pool = r->pool;
+ ctx.w.root = resource;
+ ctx.w.lockdb = lockdb;
+
+ ctx.r = r;
+ ctx.lock = lock;
+
+ err = (*resource->hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status);
+ if (err != NULL) {
+ /* implies a 5xx status code occurred. screw the multistatus */
+ return err;
+ }
+
+ if (multi_status != NULL) {
+ /* manufacture a 207 error for the multistatus response */
+ *response = multi_status;
+ return dav_new_error(r->pool, HTTP_MULTI_STATUS, 0, 0,
+ "Error(s) occurred on resources during the "
+ "addition of a depth lock.");
+ }
+ }
+
+ return NULL;
+}
+
+/*
+** dav_lock_query: Opens the lock database. Returns a linked list of
+** dav_lock structures for all direct locks on path.
+*/
+DAV_DECLARE(dav_error*) dav_lock_query(dav_lockdb *lockdb,
+ const dav_resource *resource,
+ dav_lock **locks)
+{
+ /* If no lock database, return empty result */
+ if (lockdb == NULL) {
+ *locks = NULL;
+ return NULL;
+ }
+
+ /* ### insert a higher-level description? */
+ return (*lockdb->hooks->get_locks)(lockdb, resource,
+ DAV_GETLOCKS_RESOLVED,
+ locks);
+}
+
+/* dav_unlock_walker: Walker callback function to remove indirect locks */
+static dav_error * dav_unlock_walker(dav_walk_resource *wres, int calltype)
+{
+ dav_walker_ctx *ctx = wres->walk_ctx;
+ dav_error *err;
+
+ /* Before removing the lock, do any auto-checkin required */
+ if (wres->resource->working) {
+ /* ### get rid of this typecast */
+ if ((err = dav_auto_checkin(ctx->r, (dav_resource *) wres->resource,
+ 0 /*undo*/, 1 /*unlock*/, NULL))
+ != NULL) {
+ return err;
+ }
+ }
+
+ if ((err = (*ctx->w.lockdb->hooks->remove_lock)(ctx->w.lockdb,
+ wres->resource,
+ ctx->locktoken)) != NULL) {
+ /* ### should we stop or return a multistatus? looks like STOP */
+ /* ### add a higher-level description? */
+ return err;
+ }
+
+ return NULL;
+}
+
+/*
+** dav_get_direct_resource:
+**
+** Find a lock on the specified resource, then return the resource the
+** lock was applied to (in other words, given a (possibly) indirect lock,
+** return the direct lock's corresponding resource).
+**
+** If the lock is an indirect lock, this usually means traversing up the
+** namespace [repository] hierarchy. Note that some lock providers may be
+** able to return this information with a traversal.
+*/
+static dav_error * dav_get_direct_resource(apr_pool_t *p,
+ dav_lockdb *lockdb,
+ const dav_locktoken *locktoken,
+ const dav_resource *resource,
+ const dav_resource **direct_resource)
+{
+ if (lockdb->hooks->lookup_resource != NULL) {
+ return (*lockdb->hooks->lookup_resource)(lockdb, locktoken,
+ resource, direct_resource);
+ }
+
+ *direct_resource = NULL;
+
+ /* Find the top of this lock-
+ * If r->filename's direct locks include locktoken, use r->filename.
+ * If r->filename's indirect locks include locktoken, retry r->filename/..
+ * Else fail.
+ */
+ while (resource != NULL) {
+ dav_error *err;
+ dav_lock *lock;
+ dav_resource *parent;
+
+ /*
+ ** Find the lock specified by <locktoken> on <resource>. If it is
+ ** an indirect lock, then partial results are okay. We're just
+ ** trying to find the thing and know whether it is a direct or
+ ** an indirect lock.
+ */
+ if ((err = (*lockdb->hooks->find_lock)(lockdb, resource, locktoken,
+ 1, &lock)) != NULL) {
+ /* ### add a higher-level desc? */
+ return err;
+ }
+
+ /* not found! that's an error. */
+ if (lock == NULL) {
+ return dav_new_error(p, HTTP_BAD_REQUEST, 0, 0,
+ "The specified locktoken does not correspond "
+ "to an existing lock on this resource.");
+ }
+
+ if (lock->rectype == DAV_LOCKREC_DIRECT) {
+ /* we found the direct lock. return this resource. */
+
+ *direct_resource = resource;
+ return NULL;
+ }
+
+ /* the lock was indirect. move up a level in the URL namespace */
+ if ((err = (*resource->hooks->get_parent_resource)(resource,
+ &parent)) != NULL) {
+ /* ### add a higher-level desc? */
+ return err;
+ }
+ resource = parent;
+ }
+
+ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "The lock database is corrupt. A direct lock could "
+ "not be found for the corresponding indirect lock "
+ "on this resource.");
+}
+
+/*
+** dav_unlock: Removes all direct and indirect locks for r->filename,
+** with given locktoken. If locktoken == null_locktoken, all locks
+** are removed. If r->filename represents an indirect lock,
+** we must unlock the appropriate direct lock.
+** Returns OK or appropriate HTTP_* response and logs any errors.
+**
+** ### We've already crawled the tree to ensure everything was locked
+** by us; there should be no need to incorporate a rollback.
+*/
+DAV_DECLARE(int) dav_unlock(request_rec *r, const dav_resource *resource,
+ const dav_locktoken *locktoken)
+{
+ int result;
+ dav_lockdb *lockdb;
+ const dav_resource *lock_resource = resource;
+ const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
+ const dav_hooks_repository *repos_hooks = resource->hooks;
+ dav_walker_ctx ctx = { { 0 } };
+ dav_response *multi_status;
+ dav_error *err;
+
+ /* If no locks provider, then there is nothing to unlock. */
+ if (hooks == NULL) {
+ return OK;
+ }
+
+ /* 2518 requires the entire lock to be removed if resource/locktoken
+ * point to an indirect lock. We need resource of the _direct_
+ * lock in order to walk down the tree and remove the locks. So,
+ * If locktoken != null_locktoken,
+ * Walk up the resource hierarchy until we see a direct lock.
+ * Or, we could get the direct lock's db/key, pick out the URL
+ * and do a subrequest. I think walking up is faster and will work
+ * all the time.
+ * Else
+ * Just start removing all locks at and below resource.
+ */
+
+ if ((err = (*hooks->open_lockdb)(r, 0, 1, &lockdb)) != NULL) {
+ /* ### return err! maybe add a higher-level desc */
+ /* ### map result to something nice; log an error */
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (locktoken != NULL
+ && (err = dav_get_direct_resource(r->pool, lockdb,
+ locktoken, resource,
+ &lock_resource)) != NULL) {
+ /* ### add a higher-level desc? */
+ /* ### should return err! */
+ return err->status;
+ }
+
+ /* At this point, lock_resource/locktoken refers to a direct lock (key), ie
+ * the root of a depth > 0 lock, or locktoken is null.
+ */
+ ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_LOCKNULL;
+ ctx.w.func = dav_unlock_walker;
+ ctx.w.walk_ctx = &ctx;
+ ctx.w.pool = r->pool;
+ ctx.w.root = lock_resource;
+ ctx.w.lockdb = lockdb;
+
+ ctx.r = r;
+ ctx.locktoken = locktoken;
+
+ err = (*repos_hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status);
+
+ /* ### fix this! */
+ /* ### do something with multi_status */
+ result = err == NULL ? OK : err->status;
+
+ (*hooks->close_lockdb)(lockdb);
+
+ return result;
+}
+
+/* dav_inherit_walker: Walker callback function to inherit locks */
+static dav_error * dav_inherit_walker(dav_walk_resource *wres, int calltype)
+{
+ dav_walker_ctx *ctx = wres->walk_ctx;
+
+ if (ctx->skip_root
+ && (*wres->resource->hooks->is_same_resource)(wres->resource,
+ ctx->w.root)) {
+ return NULL;
+ }
+
+ /* ### maybe add a higher-level desc */
+ return (*ctx->w.lockdb->hooks->append_locks)(ctx->w.lockdb,
+ wres->resource, 1,
+ ctx->lock);
+}
+
+/*
+** dav_inherit_locks: When a resource or collection is added to a collection,
+** locks on the collection should be inherited to the resource/collection.
+** (MOVE, MKCOL, etc) Here we propagate any direct or indirect locks from
+** parent of resource to resource and below.
+*/
+static dav_error * dav_inherit_locks(request_rec *r, dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int use_parent)
+{
+ dav_error *err;
+ const dav_resource *which_resource;
+ dav_lock *locks;
+ dav_lock *scan;
+ dav_lock *prev;
+ dav_walker_ctx ctx = { { 0 } };
+ const dav_hooks_repository *repos_hooks = resource->hooks;
+ dav_response *multi_status;
+
+ if (use_parent) {
+ dav_resource *parent;
+ if ((err = (*repos_hooks->get_parent_resource)(resource,
+ &parent)) != NULL) {
+ /* ### add a higher-level desc? */
+ return err;
+ }
+ if (parent == NULL) {
+ /* ### map result to something nice; log an error */
+ return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+ "Could not fetch parent resource. Unable to "
+ "inherit locks from the parent and apply "
+ "them to this resource.");
+ }
+ which_resource = parent;
+ }
+ else {
+ which_resource = resource;
+ }
+
+ if ((err = (*lockdb->hooks->get_locks)(lockdb, which_resource,
+ DAV_GETLOCKS_PARTIAL,
+ &locks)) != NULL) {
+ /* ### maybe add a higher-level desc */
+ return err;
+ }
+
+ if (locks == NULL) {
+ /* No locks to propagate, just return */
+ return NULL;
+ }
+
+ /*
+ ** (1) Copy all indirect locks from our parent;
+ ** (2) Create indirect locks for the depth infinity, direct locks
+ ** in our parent.
+ **
+ ** The append_locks call in the walker callback will do the indirect
+ ** conversion, but we need to remove any direct locks that are NOT
+ ** depth "infinity".
+ */
+ for (scan = locks, prev = NULL;
+ scan != NULL;
+ prev = scan, scan = scan->next) {
+
+ if (scan->rectype == DAV_LOCKREC_DIRECT
+ && scan->depth != DAV_INFINITY) {
+
+ if (prev == NULL)
+ locks = scan->next;
+ else
+ prev->next = scan->next;
+ }
+ }
+
+ /* <locks> has all our new locks. Walk down and propagate them. */
+
+ ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_LOCKNULL;
+ ctx.w.func = dav_inherit_walker;
+ ctx.w.walk_ctx = &ctx;
+ ctx.w.pool = r->pool;
+ ctx.w.root = resource;
+ ctx.w.lockdb = lockdb;
+
+ ctx.r = r;
+ ctx.lock = locks;
+ ctx.skip_root = !use_parent;
+
+ /* ### do something with multi_status */
+ return (*repos_hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status);
+}
+
+/* ---------------------------------------------------------------
+**
+** Functions dealing with lock-null resources
+**
+*/
+
+/*
+** dav_get_resource_state: Returns the state of the resource
+** r->filename: DAV_RESOURCE_NULL, DAV_RESOURCE_LOCK_NULL,
+** or DAV_RESOURCE_EXIST.
+**
+** Returns DAV_RESOURCE_ERROR if an error occurs.
+*/
+DAV_DECLARE(int) dav_get_resource_state(request_rec *r,
+ const dav_resource *resource)
+{
+ const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
+
+ if (resource->exists)
+ return DAV_RESOURCE_EXISTS;
+
+ if (hooks != NULL) {
+ dav_error *err;
+ dav_lockdb *lockdb;
+ int locks_present;
+
+ /*
+ ** A locknull resource has the form:
+ **
+ ** known-dir "/" locknull-file
+ **
+ ** It would be nice to look into <resource> to verify this form,
+ ** but it does not have enough information for us. Instead, we
+ ** can look at the path_info. If the form does not match, then
+ ** there is no way we could have a locknull resource -- it must
+ ** be a plain, null resource.
+ **
+ ** Apache sets r->filename to known-dir/unknown-file and r->path_info
+ ** to "" for the "proper" case. If anything is in path_info, then
+ ** it can't be a locknull resource.
+ **
+ ** ### I bet this path_info hack doesn't work for repositories.
+ ** ### Need input from repository implementors! What kind of
+ ** ### restructure do we need? New provider APIs?
+ */
+ if (r->path_info != NULL && *r->path_info != '\0') {
+ return DAV_RESOURCE_NULL;
+ }
+
+ if ((err = (*hooks->open_lockdb)(r, 1, 1, &lockdb)) == NULL) {
+ /* note that we might see some expired locks... *shrug* */
+ err = (*hooks->has_locks)(lockdb, resource, &locks_present);
+ (*hooks->close_lockdb)(lockdb);
+ }
+
+ if (err != NULL) {
+ /* ### don't log an error. return err. add higher-level desc. */
+
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00623)
+ "Failed to query lock-null status for %s",
+ r->filename);
+
+ return DAV_RESOURCE_ERROR;
+ }
+
+ if (locks_present)
+ return DAV_RESOURCE_LOCK_NULL;
+ }
+
+ return DAV_RESOURCE_NULL;
+}
+
+DAV_DECLARE(dav_error *) dav_notify_created(request_rec *r,
+ dav_lockdb *lockdb,
+ const dav_resource *resource,
+ int resource_state,
+ int depth)
+{
+ dav_error *err;
+
+ if (resource_state == DAV_RESOURCE_LOCK_NULL) {
+
+ /*
+ ** The resource is no longer a locknull resource. This will remove
+ ** the special marker.
+ **
+ ** Note that a locknull resource has already inherited all of the
+ ** locks from the parent. We do not need to call dav_inherit_locks.
+ **
+ ** NOTE: some lock providers record locks for locknull resources using
+ ** a different key than for regular resources. this will shift
+ ** the lock information between the two key types.
+ */
+ (void)(*lockdb->hooks->remove_locknull_state)(lockdb, resource);
+
+ /*
+ ** There are resources under this one, which are new. We must
+ ** propagate the locks down to the new resources.
+ */
+ if (depth > 0 &&
+ (err = dav_inherit_locks(r, lockdb, resource, 0)) != NULL) {
+ /* ### add a higher level desc? */
+ return err;
+ }
+ }
+ else if (resource_state == DAV_RESOURCE_NULL) {
+
+ /* ### should pass depth to dav_inherit_locks so that it can
+ ** ### optimize for the depth==0 case.
+ */
+
+ /* this resource should inherit locks from its parent */
+ if ((err = dav_inherit_locks(r, lockdb, resource, 1)) != NULL) {
+
+ err = dav_push_error(r->pool, err->status, 0,
+ "The resource was created successfully, but "
+ "there was a problem inheriting locks from "
+ "the parent resource.",
+ err);
+ return err;
+ }
+ }
+ /* else the resource already exists and its locks are correct. */
+
+ return NULL;
+}
diff --git a/modules/debugging/Makefile.in b/modules/debugging/Makefile.in
new file mode 100644
index 0000000..7c5c149
--- /dev/null
+++ b/modules/debugging/Makefile.in
@@ -0,0 +1,3 @@
+# a modules Makefile has no explicit targets -- they will be defined by
+# whatever modules are enabled. just grab special.mk to deal with this.
+include $(top_srcdir)/build/special.mk
diff --git a/modules/debugging/NWGNUmakefile b/modules/debugging/NWGNUmakefile
new file mode 100644
index 0000000..5bac93c
--- /dev/null
+++ b/modules/debugging/NWGNUmakefile
@@ -0,0 +1,246 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME =
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION =
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME =
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE =
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/modbucketeer.nlm \
+ $(OBJDIR)/moddumpio.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/debugging/NWGNUmodbucketeer b/modules/debugging/NWGNUmodbucketeer
new file mode 100644
index 0000000..22a2d11
--- /dev/null
+++ b/modules/debugging/NWGNUmodbucketeer
@@ -0,0 +1,248 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = modbucketeer
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Bucketeer Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Bucketeer Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_bucketeer.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ bucketeer_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/debugging/NWGNUmoddumpio b/modules/debugging/NWGNUmoddumpio
new file mode 100644
index 0000000..b09fa05
--- /dev/null
+++ b/modules/debugging/NWGNUmoddumpio
@@ -0,0 +1,248 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = moddumpio
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) DumpIO Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = DumpIO Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_dumpio.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ dumpio_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/debugging/README b/modules/debugging/README
new file mode 100644
index 0000000..df71d07
--- /dev/null
+++ b/modules/debugging/README
@@ -0,0 +1 @@
+debugging modules for Apache 2.x
diff --git a/modules/debugging/config.m4 b/modules/debugging/config.m4
new file mode 100644
index 0000000..28441e1
--- /dev/null
+++ b/modules/debugging/config.m4
@@ -0,0 +1,7 @@
+
+APACHE_MODPATH_INIT(debugging)
+
+APACHE_MODULE(bucketeer, buckets manipulation filter. Useful only for developers and testing purposes., , , no)
+APACHE_MODULE(dumpio, I/O dump filter, , , most)
+
+APACHE_MODPATH_FINISH
diff --git a/modules/debugging/mod_bucketeer.c b/modules/debugging/mod_bucketeer.c
new file mode 100644
index 0000000..4142cbe
--- /dev/null
+++ b/modules/debugging/mod_bucketeer.c
@@ -0,0 +1,187 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * mod_bucketeer.c: split buckets whenever we find a control-char
+ *
+ * Written by Ian Holsman
+ *
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "apr_strings.h"
+#include "apr_general.h"
+#include "util_filter.h"
+#include "apr_buckets.h"
+#include "http_request.h"
+#include "http_protocol.h"
+
+static const char bucketeerFilterName[] = "BUCKETEER";
+module AP_MODULE_DECLARE_DATA bucketeer_module;
+
+typedef struct bucketeer_filter_config_t
+{
+ char bucketdelimiter;
+ char passdelimiter;
+ char flushdelimiter;
+} bucketeer_filter_config_t;
+
+
+static void *create_bucketeer_server_config(apr_pool_t *p, server_rec *s)
+{
+ bucketeer_filter_config_t *c = apr_pcalloc(p, sizeof *c);
+
+ c->bucketdelimiter = 0x02; /* ^B */
+ c->passdelimiter = 0x10; /* ^P */
+ c->flushdelimiter = 0x06; /* ^F */
+
+ return c;
+}
+
+typedef struct bucketeer_ctx_t
+{
+ apr_bucket_brigade *bb;
+} bucketeer_ctx_t;
+
+static apr_status_t bucketeer_out_filter(ap_filter_t *f,
+ apr_bucket_brigade *bb)
+{
+ apr_bucket *e;
+ request_rec *r = f->r;
+ bucketeer_ctx_t *ctx = f->ctx;
+ bucketeer_filter_config_t *c;
+
+ c = ap_get_module_config(r->server->module_config, &bucketeer_module);
+
+ /* If have a context, it means we've done this before successfully. */
+ if (!ctx) {
+ if (!r->content_type || strncmp(r->content_type, "text/", 5)) {
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ /* We're cool with filtering this. */
+ ctx = f->ctx = apr_pcalloc(f->r->pool, sizeof(*ctx));
+ ctx->bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
+ apr_table_unset(f->r->headers_out, "Content-Length");
+ }
+
+ for (e = APR_BRIGADE_FIRST(bb);
+ e != APR_BRIGADE_SENTINEL(bb);
+ e = APR_BUCKET_NEXT(e))
+ {
+ const char *data;
+ apr_size_t len, i, lastpos;
+
+ if (APR_BUCKET_IS_EOS(e)) {
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
+
+ /* Okay, we've seen the EOS.
+ * Time to pass it along down the chain.
+ */
+ return ap_pass_brigade(f->next, ctx->bb);
+ }
+
+ if (APR_BUCKET_IS_FLUSH(e)) {
+ /*
+ * Ignore flush buckets for the moment..
+ * we decide what to stream
+ */
+ continue;
+ }
+
+ if (APR_BUCKET_IS_METADATA(e)) {
+ /* metadata bucket */
+ apr_bucket *cpy;
+ apr_bucket_copy(e, &cpy);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, cpy);
+ continue;
+ }
+
+ /* read */
+ apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
+
+ if (len > 0) {
+ lastpos = 0;
+ for (i = 0; i < len; i++) {
+ if (data[i] == c->flushdelimiter ||
+ data[i] == c->bucketdelimiter ||
+ data[i] == c->passdelimiter) {
+ apr_bucket *p;
+ if (i - lastpos > 0) {
+ p = apr_bucket_pool_create(apr_pmemdup(f->r->pool,
+ &data[lastpos],
+ i - lastpos),
+ i - lastpos,
+ f->r->pool,
+ f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, p);
+ }
+ lastpos = i + 1;
+ if (data[i] == c->flushdelimiter) {
+ p = apr_bucket_flush_create(f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, p);
+ }
+ if (data[i] == c->passdelimiter) {
+ apr_status_t rv;
+
+ rv = ap_pass_brigade(f->next, ctx->bb);
+ if (rv) {
+ return rv;
+ }
+ }
+ }
+ }
+ /* XXX: really should append this to the next 'real' bucket */
+ if (lastpos < i) {
+ apr_bucket *p;
+ p = apr_bucket_pool_create(apr_pmemdup(f->r->pool,
+ &data[lastpos],
+ i - lastpos),
+ i - lastpos,
+ f->r->pool,
+ f->c->bucket_alloc);
+ lastpos = i;
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, p);
+ }
+ }
+ }
+
+ return APR_SUCCESS;
+}
+
+static void register_hooks(apr_pool_t * p)
+{
+ ap_register_output_filter(bucketeerFilterName, bucketeer_out_filter,
+ NULL, AP_FTYPE_RESOURCE-1);
+}
+
+static const command_rec bucketeer_filter_cmds[] = {
+ {NULL}
+};
+
+AP_DECLARE_MODULE(bucketeer) = {
+ STANDARD20_MODULE_STUFF,
+ NULL,
+ NULL,
+ create_bucketeer_server_config,
+ NULL,
+ bucketeer_filter_cmds,
+ register_hooks
+};
diff --git a/modules/debugging/mod_bucketeer.dep b/modules/debugging/mod_bucketeer.dep
new file mode 100644
index 0000000..911398d
--- /dev/null
+++ b/modules/debugging/mod_bucketeer.dep
@@ -0,0 +1,53 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_bucketeer.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_bucketeer.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/debugging/mod_bucketeer.dsp b/modules/debugging/mod_bucketeer.dsp
new file mode 100644
index 0000000..58337c2
--- /dev/null
+++ b/modules/debugging/mod_bucketeer.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_bucketeer" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_bucketeer - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_bucketeer.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_bucketeer.mak" CFG="mod_bucketeer - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_bucketeer - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_bucketeer - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_bucketeer - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_bucketeer_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_bucketeer.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_bucketeer.so" /d LONG_NAME="bucketeer_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_bucketeer.so" /base:@..\..\os\win32\BaseAddr.ref,mod_bucketeer.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_bucketeer.so" /base:@..\..\os\win32\BaseAddr.ref,mod_bucketeer.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_bucketeer.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_bucketeer - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_bucketeer_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_bucketeer.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_bucketeer.so" /d LONG_NAME="bucketeer_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_bucketeer.so" /base:@..\..\os\win32\BaseAddr.ref,mod_bucketeer.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_bucketeer.so" /base:@..\..\os\win32\BaseAddr.ref,mod_bucketeer.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_bucketeer.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_bucketeer - Win32 Release"
+# Name "mod_bucketeer - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_bucketeer.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/debugging/mod_bucketeer.mak b/modules/debugging/mod_bucketeer.mak
new file mode 100644
index 0000000..0e31e94
--- /dev/null
+++ b/modules/debugging/mod_bucketeer.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_bucketeer.dsp
+!IF "$(CFG)" == ""
+CFG=mod_bucketeer - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_bucketeer - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_bucketeer - Win32 Release" && "$(CFG)" != "mod_bucketeer - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_bucketeer.mak" CFG="mod_bucketeer - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_bucketeer - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_bucketeer - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_bucketeer - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_bucketeer.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_bucketeer.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_bucketeer.obj"
+ -@erase "$(INTDIR)\mod_bucketeer.res"
+ -@erase "$(INTDIR)\mod_bucketeer_src.idb"
+ -@erase "$(INTDIR)\mod_bucketeer_src.pdb"
+ -@erase "$(OUTDIR)\mod_bucketeer.exp"
+ -@erase "$(OUTDIR)\mod_bucketeer.lib"
+ -@erase "$(OUTDIR)\mod_bucketeer.pdb"
+ -@erase "$(OUTDIR)\mod_bucketeer.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_bucketeer_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_bucketeer.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_bucketeer.so" /d LONG_NAME="bucketeer_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_bucketeer.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_bucketeer.pdb" /debug /out:"$(OUTDIR)\mod_bucketeer.so" /implib:"$(OUTDIR)\mod_bucketeer.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_bucketeer.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_bucketeer.obj" \
+ "$(INTDIR)\mod_bucketeer.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_bucketeer.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_bucketeer.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_bucketeer.so"
+ if exist .\Release\mod_bucketeer.so.manifest mt.exe -manifest .\Release\mod_bucketeer.so.manifest -outputresource:.\Release\mod_bucketeer.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_bucketeer - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_bucketeer.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_bucketeer.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_bucketeer.obj"
+ -@erase "$(INTDIR)\mod_bucketeer.res"
+ -@erase "$(INTDIR)\mod_bucketeer_src.idb"
+ -@erase "$(INTDIR)\mod_bucketeer_src.pdb"
+ -@erase "$(OUTDIR)\mod_bucketeer.exp"
+ -@erase "$(OUTDIR)\mod_bucketeer.lib"
+ -@erase "$(OUTDIR)\mod_bucketeer.pdb"
+ -@erase "$(OUTDIR)\mod_bucketeer.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_bucketeer_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_bucketeer.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_bucketeer.so" /d LONG_NAME="bucketeer_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_bucketeer.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_bucketeer.pdb" /debug /out:"$(OUTDIR)\mod_bucketeer.so" /implib:"$(OUTDIR)\mod_bucketeer.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_bucketeer.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_bucketeer.obj" \
+ "$(INTDIR)\mod_bucketeer.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_bucketeer.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_bucketeer.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_bucketeer.so"
+ if exist .\Debug\mod_bucketeer.so.manifest mt.exe -manifest .\Debug\mod_bucketeer.so.manifest -outputresource:.\Debug\mod_bucketeer.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_bucketeer.dep")
+!INCLUDE "mod_bucketeer.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_bucketeer.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_bucketeer - Win32 Release" || "$(CFG)" == "mod_bucketeer - Win32 Debug"
+
+!IF "$(CFG)" == "mod_bucketeer - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\debugging"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\debugging"
+
+!ELSEIF "$(CFG)" == "mod_bucketeer - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\debugging"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\debugging"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_bucketeer - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\debugging"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\debugging"
+
+!ELSEIF "$(CFG)" == "mod_bucketeer - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\debugging"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\debugging"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_bucketeer - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\debugging"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\debugging"
+
+!ELSEIF "$(CFG)" == "mod_bucketeer - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\debugging"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\debugging"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_bucketeer - Win32 Release"
+
+
+"$(INTDIR)\mod_bucketeer.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_bucketeer.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_bucketeer.so" /d LONG_NAME="bucketeer_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_bucketeer - Win32 Debug"
+
+
+"$(INTDIR)\mod_bucketeer.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_bucketeer.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_bucketeer.so" /d LONG_NAME="bucketeer_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_bucketeer.c
+
+"$(INTDIR)\mod_bucketeer.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/debugging/mod_dumpio.c b/modules/debugging/mod_dumpio.c
new file mode 100644
index 0000000..8a33860
--- /dev/null
+++ b/modules/debugging/mod_dumpio.c
@@ -0,0 +1,255 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Originally written @ Covalent by Jim Jagielski
+ */
+
+/*
+ * mod_dumpio.c:
+ * Think of this as a filter sniffer for Apache 2.x. It logs
+ * all filter data right before and after it goes out on the
+ * wire (BUT right before SSL encoded or after SSL decoded).
+ * It can produce a *huge* amount of data.
+ */
+
+
+#include "httpd.h"
+#include "http_connection.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "apr_strings.h"
+
+module AP_MODULE_DECLARE_DATA dumpio_module ;
+
+typedef struct dumpio_conf_t {
+ int enable_input;
+ int enable_output;
+} dumpio_conf_t;
+
+/* consider up to 80 additional characters, and factor the longest
+ * line length of all \xNN sequences; log_error cannot record more
+ * than MAX_STRING_LEN characters.
+ */
+#define dumpio_MAX_STRING_LEN (MAX_STRING_LEN / 4 - 80)
+
+/*
+ * Workhorse function: simply log to the current error_log
+ * info about the data in the bucket as well as the data itself
+ */
+static void dumpit(ap_filter_t *f, apr_bucket *b, dumpio_conf_t *ptr)
+{
+ conn_rec *c = f->c;
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE7, 0, c,
+ "mod_dumpio: %s (%s-%s): %" APR_SIZE_T_FMT " bytes",
+ f->frec->name,
+ (APR_BUCKET_IS_METADATA(b)) ? "metadata" : "data",
+ b->type->name,
+ b->length) ;
+
+ if (!(APR_BUCKET_IS_METADATA(b)))
+ {
+#if APR_CHARSET_EBCDIC
+ char xlatebuf[dumpio_MAX_STRING_LEN + 1];
+#endif
+ const char *buf;
+ apr_size_t nbytes;
+ apr_status_t rv = apr_bucket_read(b, &buf, &nbytes, APR_BLOCK_READ);
+
+ if (rv == APR_SUCCESS)
+ {
+ while (nbytes)
+ {
+ apr_size_t logbytes = nbytes;
+ if (logbytes > dumpio_MAX_STRING_LEN)
+ logbytes = dumpio_MAX_STRING_LEN;
+ nbytes -= logbytes;
+
+#if APR_CHARSET_EBCDIC
+ memcpy(xlatebuf, buf, logbytes);
+ ap_xlate_proto_from_ascii(xlatebuf, logbytes);
+ xlatebuf[logbytes] = '\0';
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE7, 0, c,
+ "mod_dumpio: %s (%s-%s): %s", f->frec->name,
+ (APR_BUCKET_IS_METADATA(b)) ? "metadata" : "data",
+ b->type->name, xlatebuf);
+#else
+ /* XXX: Seriously flawed; we do not pay attention to embedded
+ * \0's in the request body, these should be escaped; however,
+ * the logging function already performs a significant amount
+ * of escaping, and so any escaping would be double-escaped.
+ * The coding solution is to throw away the current logic
+ * within ap_log_error, and introduce new vformatter %-escapes
+ * for escaping text, and for binary text (fixed len strings).
+ */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE7, 0, c,
+ "mod_dumpio: %s (%s-%s): %.*s", f->frec->name,
+ (APR_BUCKET_IS_METADATA(b)) ? "metadata" : "data",
+ b->type->name, (int)logbytes, buf);
+#endif
+ buf += logbytes;
+ }
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE7, rv, c,
+ "mod_dumpio: %s (%s-%s): %s", f->frec->name,
+ (APR_BUCKET_IS_METADATA(b)) ? "metadata" : "data",
+ b->type->name, "error reading data");
+ }
+ }
+}
+
+#define whichmode( mode ) \
+ ( (( mode ) == AP_MODE_READBYTES) ? "readbytes" : \
+ (( mode ) == AP_MODE_GETLINE) ? "getline" : \
+ (( mode ) == AP_MODE_EATCRLF) ? "eatcrlf" : \
+ (( mode ) == AP_MODE_SPECULATIVE) ? "speculative" : \
+ (( mode ) == AP_MODE_EXHAUSTIVE) ? "exhaustive" : \
+ (( mode ) == AP_MODE_INIT) ? "init" : "unknown" \
+ )
+
+static int dumpio_input_filter (ap_filter_t *f, apr_bucket_brigade *bb,
+ ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes)
+{
+
+ apr_bucket *b;
+ apr_status_t ret;
+ conn_rec *c = f->c;
+ dumpio_conf_t *ptr = f->ctx;
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE7, 0, c,
+ "mod_dumpio: %s [%s-%s] %" APR_OFF_T_FMT " readbytes",
+ f->frec->name,
+ whichmode(mode),
+ ((block) == APR_BLOCK_READ) ? "blocking" : "nonblocking",
+ readbytes);
+
+ ret = ap_get_brigade(f->next, bb, mode, block, readbytes);
+
+ if (ret == APR_SUCCESS) {
+ for (b = APR_BRIGADE_FIRST(bb); b != APR_BRIGADE_SENTINEL(bb); b = APR_BUCKET_NEXT(b)) {
+ dumpit(f, b, ptr);
+ }
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE7, 0, c,
+ "mod_dumpio: %s - %d", f->frec->name, ret) ;
+ return ret;
+ }
+
+ return APR_SUCCESS ;
+}
+
+static int dumpio_output_filter (ap_filter_t *f, apr_bucket_brigade *bb)
+{
+ apr_bucket *b;
+ conn_rec *c = f->c;
+ dumpio_conf_t *ptr = f->ctx;
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE7, 0, c, "mod_dumpio: %s", f->frec->name);
+
+ for (b = APR_BRIGADE_FIRST(bb); b != APR_BRIGADE_SENTINEL(bb); b = APR_BUCKET_NEXT(b)) {
+ /*
+ * If we ever see an EOS, make sure to FLUSH.
+ */
+ if (APR_BUCKET_IS_EOS(b)) {
+ apr_bucket *flush = apr_bucket_flush_create(f->c->bucket_alloc);
+ APR_BUCKET_INSERT_BEFORE(b, flush);
+ }
+ dumpit(f, b, ptr);
+ }
+
+ return ap_pass_brigade(f->next, bb) ;
+}
+
+static int dumpio_pre_conn(conn_rec *c, void *csd)
+{
+ dumpio_conf_t *ptr;
+
+ if (!APLOGctrace7(c)) {
+ /* Nothing to do below TRACE7 */
+ return DECLINED;
+ }
+
+ ptr = (dumpio_conf_t *) ap_get_module_config(c->base_server->module_config,
+ &dumpio_module);
+
+ if (ptr->enable_input)
+ ap_add_input_filter("DUMPIO_IN", ptr, NULL, c);
+ if (ptr->enable_output)
+ ap_add_output_filter("DUMPIO_OUT", ptr, NULL, c);
+ return OK;
+}
+
+static void dumpio_register_hooks(apr_pool_t *p)
+{
+/*
+ * We know that SSL is CONNECTION + 5
+ */
+ ap_register_output_filter("DUMPIO_OUT", dumpio_output_filter,
+ NULL, AP_FTYPE_CONNECTION + 3) ;
+
+ ap_register_input_filter("DUMPIO_IN", dumpio_input_filter,
+ NULL, AP_FTYPE_CONNECTION + 3) ;
+
+ ap_hook_pre_connection(dumpio_pre_conn, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+static void *dumpio_create_sconfig(apr_pool_t *p, server_rec *s)
+{
+ dumpio_conf_t *ptr = apr_pcalloc(p, sizeof *ptr);
+ ptr->enable_input = 0;
+ ptr->enable_output = 0;
+ return ptr;
+}
+
+static const char *dumpio_enable_input(cmd_parms *cmd, void *dummy, int arg)
+{
+ dumpio_conf_t *ptr = ap_get_module_config(cmd->server->module_config,
+ &dumpio_module);
+
+ ptr->enable_input = arg;
+ return NULL;
+}
+
+static const char *dumpio_enable_output(cmd_parms *cmd, void *dummy, int arg)
+{
+ dumpio_conf_t *ptr = ap_get_module_config(cmd->server->module_config,
+ &dumpio_module);
+
+ ptr->enable_output = arg;
+ return NULL;
+}
+
+static const command_rec dumpio_cmds[] = {
+ AP_INIT_FLAG("DumpIOInput", dumpio_enable_input, NULL,
+ RSRC_CONF, "Enable I/O Dump on Input Data"),
+ AP_INIT_FLAG("DumpIOOutput", dumpio_enable_output, NULL,
+ RSRC_CONF, "Enable I/O Dump on Output Data"),
+ { NULL }
+};
+
+AP_DECLARE_MODULE(dumpio) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-dir config structures */
+ NULL, /* merge per-dir config structures */
+ dumpio_create_sconfig, /* create per-server config structures */
+ NULL, /* merge per-server config structures */
+ dumpio_cmds, /* table of config file commands */
+ dumpio_register_hooks /* register hooks */
+};
diff --git a/modules/debugging/mod_dumpio.dep b/modules/debugging/mod_dumpio.dep
new file mode 100644
index 0000000..889febc
--- /dev/null
+++ b/modules/debugging/mod_dumpio.dep
@@ -0,0 +1,50 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_dumpio.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_dumpio.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/debugging/mod_dumpio.dsp b/modules/debugging/mod_dumpio.dsp
new file mode 100644
index 0000000..d15965e
--- /dev/null
+++ b/modules/debugging/mod_dumpio.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_dumpio" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_dumpio - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_dumpio.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_dumpio.mak" CFG="mod_dumpio - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_dumpio - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_dumpio - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_dumpio - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_dumpio_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_dumpio.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_dumpio.so" /d LONG_NAME="dumpio_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_dumpio.so" /base:@..\..\os\win32\BaseAddr.ref,mod_dumpio.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_dumpio.so" /base:@..\..\os\win32\BaseAddr.ref,mod_dumpio.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_dumpio.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_dumpio - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_dumpio_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_dumpio.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_dumpio.so" /d LONG_NAME="dumpio_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_dumpio.so" /base:@..\..\os\win32\BaseAddr.ref,mod_dumpio.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_dumpio.so" /base:@..\..\os\win32\BaseAddr.ref,mod_dumpio.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_dumpio.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_dumpio - Win32 Release"
+# Name "mod_dumpio - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_dumpio.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/debugging/mod_dumpio.mak b/modules/debugging/mod_dumpio.mak
new file mode 100644
index 0000000..d0943c7
--- /dev/null
+++ b/modules/debugging/mod_dumpio.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_dumpio.dsp
+!IF "$(CFG)" == ""
+CFG=mod_dumpio - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_dumpio - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_dumpio - Win32 Release" && "$(CFG)" != "mod_dumpio - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_dumpio.mak" CFG="mod_dumpio - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_dumpio - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_dumpio - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_dumpio - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_dumpio.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_dumpio.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_dumpio.obj"
+ -@erase "$(INTDIR)\mod_dumpio.res"
+ -@erase "$(INTDIR)\mod_dumpio_src.idb"
+ -@erase "$(INTDIR)\mod_dumpio_src.pdb"
+ -@erase "$(OUTDIR)\mod_dumpio.exp"
+ -@erase "$(OUTDIR)\mod_dumpio.lib"
+ -@erase "$(OUTDIR)\mod_dumpio.pdb"
+ -@erase "$(OUTDIR)\mod_dumpio.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_dumpio_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_dumpio.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_dumpio.so" /d LONG_NAME="dumpio_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_dumpio.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_dumpio.pdb" /debug /out:"$(OUTDIR)\mod_dumpio.so" /implib:"$(OUTDIR)\mod_dumpio.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_dumpio.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_dumpio.obj" \
+ "$(INTDIR)\mod_dumpio.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_dumpio.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_dumpio.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_dumpio.so"
+ if exist .\Release\mod_dumpio.so.manifest mt.exe -manifest .\Release\mod_dumpio.so.manifest -outputresource:.\Release\mod_dumpio.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_dumpio - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_dumpio.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_dumpio.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_dumpio.obj"
+ -@erase "$(INTDIR)\mod_dumpio.res"
+ -@erase "$(INTDIR)\mod_dumpio_src.idb"
+ -@erase "$(INTDIR)\mod_dumpio_src.pdb"
+ -@erase "$(OUTDIR)\mod_dumpio.exp"
+ -@erase "$(OUTDIR)\mod_dumpio.lib"
+ -@erase "$(OUTDIR)\mod_dumpio.pdb"
+ -@erase "$(OUTDIR)\mod_dumpio.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_dumpio_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_dumpio.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_dumpio.so" /d LONG_NAME="dumpio_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_dumpio.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_dumpio.pdb" /debug /out:"$(OUTDIR)\mod_dumpio.so" /implib:"$(OUTDIR)\mod_dumpio.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_dumpio.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_dumpio.obj" \
+ "$(INTDIR)\mod_dumpio.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_dumpio.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_dumpio.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_dumpio.so"
+ if exist .\Debug\mod_dumpio.so.manifest mt.exe -manifest .\Debug\mod_dumpio.so.manifest -outputresource:.\Debug\mod_dumpio.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_dumpio.dep")
+!INCLUDE "mod_dumpio.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_dumpio.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_dumpio - Win32 Release" || "$(CFG)" == "mod_dumpio - Win32 Debug"
+
+!IF "$(CFG)" == "mod_dumpio - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\debugging"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\debugging"
+
+!ELSEIF "$(CFG)" == "mod_dumpio - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\debugging"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\debugging"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_dumpio - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\debugging"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\debugging"
+
+!ELSEIF "$(CFG)" == "mod_dumpio - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\debugging"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\debugging"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_dumpio - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\debugging"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\debugging"
+
+!ELSEIF "$(CFG)" == "mod_dumpio - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\debugging"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\debugging"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_dumpio - Win32 Release"
+
+
+"$(INTDIR)\mod_dumpio.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_dumpio.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_dumpio.so" /d LONG_NAME="dumpio_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_dumpio - Win32 Debug"
+
+
+"$(INTDIR)\mod_dumpio.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_dumpio.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_dumpio.so" /d LONG_NAME="dumpio_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_dumpio.c
+
+"$(INTDIR)\mod_dumpio.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/echo/.indent.pro b/modules/echo/.indent.pro
new file mode 100644
index 0000000..a9fbe9f
--- /dev/null
+++ b/modules/echo/.indent.pro
@@ -0,0 +1,54 @@
+-i4 -npsl -di0 -br -nce -d0 -cli0 -npcs -nfc1
+-TBUFF
+-TFILE
+-TTRANS
+-TUINT4
+-T_trans
+-Tallow_options_t
+-Tapache_sfio
+-Tarray_header
+-Tbool_int
+-Tbuf_area
+-Tbuff_struct
+-Tbuffy
+-Tcmd_how
+-Tcmd_parms
+-Tcommand_rec
+-Tcommand_struct
+-Tconn_rec
+-Tcore_dir_config
+-Tcore_server_config
+-Tdir_maker_func
+-Tevent
+-Tglobals_s
+-Thandler_func
+-Thandler_rec
+-Tjoblist_s
+-Tlisten_rec
+-Tmerger_func
+-Tmode_t
+-Tmodule
+-Tmodule_struct
+-Tmutex
+-Tn_long
+-Tother_child_rec
+-Toverrides_t
+-Tparent_score
+-Tpid_t
+-Tpiped_log
+-Tpool
+-Trequest_rec
+-Trequire_line
+-Trlim_t
+-Tscoreboard
+-Tsemaphore
+-Tserver_addr_rec
+-Tserver_rec
+-Tserver_rec_chain
+-Tshort_score
+-Ttable
+-Ttable_entry
+-Tthread
+-Tu_wide_int
+-Tvtime_t
+-Twide_int
diff --git a/modules/echo/Makefile.in b/modules/echo/Makefile.in
new file mode 100644
index 0000000..167b343
--- /dev/null
+++ b/modules/echo/Makefile.in
@@ -0,0 +1,3 @@
+
+include $(top_srcdir)/build/special.mk
+
diff --git a/modules/echo/NWGNUmakefile b/modules/echo/NWGNUmakefile
new file mode 100644
index 0000000..7add53d
--- /dev/null
+++ b/modules/echo/NWGNUmakefile
@@ -0,0 +1,257 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = echo
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Echo Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Echo Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/echo.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_echo.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ echo_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/echo/config.m4 b/modules/echo/config.m4
new file mode 100644
index 0000000..ae65d3d
--- /dev/null
+++ b/modules/echo/config.m4
@@ -0,0 +1,9 @@
+dnl modules enabled in this directory by default
+
+dnl APACHE_MODULE(name, helptext[, objects[, structname[, default[, config]]]])
+
+APACHE_MODPATH_INIT(echo)
+
+APACHE_MODULE(echo, ECHO server, , , )
+
+APACHE_MODPATH_FINISH
diff --git a/modules/echo/mod_echo.c b/modules/echo/mod_echo.c
new file mode 100644
index 0000000..c7f2a19
--- /dev/null
+++ b/modules/echo/mod_echo.c
@@ -0,0 +1,218 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ap_config.h"
+#include "ap_mmn.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_connection.h"
+#include "http_core.h"
+#include "http_log.h"
+
+#include "apr_buckets.h"
+#include "apr_strings.h"
+#include "util_filter.h"
+#include "scoreboard.h"
+
+module AP_MODULE_DECLARE_DATA echo_module;
+
+typedef struct {
+ int bEnabled;
+} EchoConfig;
+
+static void *create_echo_server_config(apr_pool_t *p, server_rec *s)
+{
+ EchoConfig *pConfig = apr_pcalloc(p, sizeof *pConfig);
+
+ pConfig->bEnabled = 0;
+
+ return pConfig;
+}
+
+static const char *echo_on(cmd_parms *cmd, void *dummy, int arg)
+{
+ EchoConfig *pConfig = ap_get_module_config(cmd->server->module_config,
+ &echo_module);
+ pConfig->bEnabled = arg;
+
+ return NULL;
+}
+
+static apr_status_t brigade_peek(apr_bucket_brigade *bbIn,
+ char *buff, apr_size_t bufflen)
+{
+ apr_bucket *b;
+ apr_size_t readbytes = 0;
+
+ if (bufflen--)
+ /* compensate for NULL */
+ *buff = '\0';
+ else
+ return APR_EGENERAL;
+
+ if (APR_BRIGADE_EMPTY(bbIn))
+ return APR_EGENERAL;
+
+ b = APR_BRIGADE_FIRST(bbIn);
+
+ while ((b != APR_BRIGADE_SENTINEL(bbIn)) && (readbytes < bufflen)) {
+ const char *pos;
+ const char *str;
+ apr_size_t len;
+ apr_status_t rv;
+
+ if ((rv = apr_bucket_read(b, &str, &len, APR_NONBLOCK_READ))
+ != APR_SUCCESS)
+ return rv;
+
+ if ((pos = memchr(str, APR_ASCII_LF, len)) != NULL)
+ len = pos - str;
+ if (len > bufflen - readbytes)
+ len = bufflen - readbytes;
+ memcpy (buff + readbytes, str, len);
+ readbytes += len;
+ buff[readbytes] = '\0';
+
+ b = APR_BUCKET_NEXT(b);
+ }
+ return APR_SUCCESS;
+}
+
+
+static int update_echo_child_status(ap_sb_handle_t *sbh,
+ int status, conn_rec *c,
+ apr_bucket_brigade *last_echoed)
+{
+ worker_score *ws = ap_get_scoreboard_worker(sbh);
+ int old_status = ws->status;
+
+ ws->status = status;
+
+ if (!ap_extended_status)
+ return old_status;
+
+ ws->last_used = apr_time_now();
+
+ /* initial pass only, please - in the name of efficiency */
+ if (c) {
+ apr_cpystrn(ws->client64,
+ ap_get_remote_host(c, c->base_server->lookup_defaults,
+ REMOTE_NOLOOKUP, NULL),
+ sizeof(ws->client64));
+ apr_cpystrn(ws->vhost, c->base_server->server_hostname,
+ sizeof(ws->vhost));
+ /* Deliberate trailing space - filling in string on WRITE passes */
+ apr_cpystrn(ws->request, "ECHO ", sizeof(ws->request));
+ }
+
+ /* each subsequent WRITE pass, let's update what we echoed */
+ if (last_echoed) {
+ brigade_peek(last_echoed, ws->request + sizeof("ECHO ") - 1,
+ sizeof(ws->request) - sizeof("ECHO ") + 1);
+ }
+
+ return old_status;
+}
+
+static int process_echo_connection(conn_rec *c)
+{
+ apr_bucket_brigade *bb;
+ apr_bucket *b;
+ apr_socket_t *csd = NULL;
+ EchoConfig *pConfig = ap_get_module_config(c->base_server->module_config,
+ &echo_module);
+
+ if (!pConfig->bEnabled) {
+ return DECLINED;
+ }
+
+ ap_time_process_request(c->sbh, START_PREQUEST);
+ update_echo_child_status(c->sbh, SERVER_BUSY_READ, c, NULL);
+
+ bb = apr_brigade_create(c->pool, c->bucket_alloc);
+
+ for ( ; ; ) {
+ apr_status_t rv;
+
+ /* Get a single line of input from the client */
+ if (((rv = ap_get_brigade(c->input_filters, bb, AP_MODE_GETLINE,
+ APR_BLOCK_READ, 0)) != APR_SUCCESS)) {
+ apr_brigade_cleanup(bb);
+ if (!APR_STATUS_IS_EOF(rv) && ! APR_STATUS_IS_TIMEUP(rv))
+ ap_log_error(APLOG_MARK, APLOG_INFO, rv, c->base_server, APLOGNO(01611)
+ "ProtocolEcho: Failure reading from %s",
+ c->client_ip);
+ break;
+ }
+
+ /* Something horribly wrong happened. Someone didn't block! */
+ if (APR_BRIGADE_EMPTY(bb)) {
+ ap_log_error(APLOG_MARK, APLOG_INFO, rv, c->base_server, APLOGNO(01612)
+ "ProtocolEcho: Error - read empty brigade from %s!",
+ c->client_ip);
+ break;
+ }
+
+ if (!csd) {
+ csd = ap_get_conn_socket(c);
+ apr_socket_timeout_set(csd, c->base_server->keep_alive_timeout);
+ }
+
+ update_echo_child_status(c->sbh, SERVER_BUSY_WRITE, NULL, bb);
+
+ /* Make sure the data is flushed to the client */
+ b = apr_bucket_flush_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ rv = ap_pass_brigade(c->output_filters, bb);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_INFO, rv, c->base_server, APLOGNO(01613)
+ "ProtocolEcho: Failure writing to %s",
+ c->client_ip);
+ break;
+ }
+ apr_brigade_cleanup(bb);
+
+ /* Announce our intent to loop */
+ update_echo_child_status(c->sbh, SERVER_BUSY_KEEPALIVE, NULL, NULL);
+ }
+ apr_brigade_destroy(bb);
+ ap_time_process_request(c->sbh, STOP_PREQUEST);
+ update_echo_child_status(c->sbh, SERVER_CLOSING, c, NULL);
+ return OK;
+}
+
+static const command_rec echo_cmds[] =
+{
+ AP_INIT_FLAG("ProtocolEcho", echo_on, NULL, RSRC_CONF,
+ "Run an echo server on this host"),
+ { NULL }
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_hook_process_connection(process_echo_connection, NULL, NULL,
+ APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(echo) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ create_echo_server_config, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ echo_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/echo/mod_echo.dep b/modules/echo/mod_echo.dep
new file mode 100644
index 0000000..d4789e7
--- /dev/null
+++ b/modules/echo/mod_echo.dep
@@ -0,0 +1,56 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_echo.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_echo.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/echo/mod_echo.dsp b/modules/echo/mod_echo.dsp
new file mode 100644
index 0000000..5d00969
--- /dev/null
+++ b/modules/echo/mod_echo.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_echo" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_echo - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_echo.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_echo.mak" CFG="mod_echo - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_echo - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_echo - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_echo - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_echo_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_echo.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_echo.so" /d LONG_NAME="echo_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_echo.so" /base:@..\..\os\win32\BaseAddr.ref,mod_echo.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_echo.so" /base:@..\..\os\win32\BaseAddr.ref,mod_echo.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_echo.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_echo - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_echo_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_echo.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_echo.so" /d LONG_NAME="echo_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_echo.so" /base:@..\..\os\win32\BaseAddr.ref,mod_echo.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_echo.so" /base:@..\..\os\win32\BaseAddr.ref,mod_echo.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_echo.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_echo - Win32 Release"
+# Name "mod_echo - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_echo.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/echo/mod_echo.mak b/modules/echo/mod_echo.mak
new file mode 100644
index 0000000..6066fa7
--- /dev/null
+++ b/modules/echo/mod_echo.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_echo.dsp
+!IF "$(CFG)" == ""
+CFG=mod_echo - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_echo - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_echo - Win32 Release" && "$(CFG)" != "mod_echo - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_echo.mak" CFG="mod_echo - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_echo - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_echo - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_echo - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_echo.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_echo.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_echo.obj"
+ -@erase "$(INTDIR)\mod_echo.res"
+ -@erase "$(INTDIR)\mod_echo_src.idb"
+ -@erase "$(INTDIR)\mod_echo_src.pdb"
+ -@erase "$(OUTDIR)\mod_echo.exp"
+ -@erase "$(OUTDIR)\mod_echo.lib"
+ -@erase "$(OUTDIR)\mod_echo.pdb"
+ -@erase "$(OUTDIR)\mod_echo.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_echo_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_echo.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_echo.so" /d LONG_NAME="echo_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_echo.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_echo.pdb" /debug /out:"$(OUTDIR)\mod_echo.so" /implib:"$(OUTDIR)\mod_echo.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_echo.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_echo.obj" \
+ "$(INTDIR)\mod_echo.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_echo.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_echo.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_echo.so"
+ if exist .\Release\mod_echo.so.manifest mt.exe -manifest .\Release\mod_echo.so.manifest -outputresource:.\Release\mod_echo.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_echo - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_echo.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_echo.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_echo.obj"
+ -@erase "$(INTDIR)\mod_echo.res"
+ -@erase "$(INTDIR)\mod_echo_src.idb"
+ -@erase "$(INTDIR)\mod_echo_src.pdb"
+ -@erase "$(OUTDIR)\mod_echo.exp"
+ -@erase "$(OUTDIR)\mod_echo.lib"
+ -@erase "$(OUTDIR)\mod_echo.pdb"
+ -@erase "$(OUTDIR)\mod_echo.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_echo_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_echo.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_echo.so" /d LONG_NAME="echo_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_echo.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_echo.pdb" /debug /out:"$(OUTDIR)\mod_echo.so" /implib:"$(OUTDIR)\mod_echo.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_echo.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_echo.obj" \
+ "$(INTDIR)\mod_echo.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_echo.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_echo.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_echo.so"
+ if exist .\Debug\mod_echo.so.manifest mt.exe -manifest .\Debug\mod_echo.so.manifest -outputresource:.\Debug\mod_echo.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_echo.dep")
+!INCLUDE "mod_echo.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_echo.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_echo - Win32 Release" || "$(CFG)" == "mod_echo - Win32 Debug"
+
+!IF "$(CFG)" == "mod_echo - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\echo"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\echo"
+
+!ELSEIF "$(CFG)" == "mod_echo - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\echo"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\echo"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_echo - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\echo"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\echo"
+
+!ELSEIF "$(CFG)" == "mod_echo - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\echo"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\echo"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_echo - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\echo"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\echo"
+
+!ELSEIF "$(CFG)" == "mod_echo - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\echo"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\echo"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_echo - Win32 Release"
+
+
+"$(INTDIR)\mod_echo.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_echo.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_echo.so" /d LONG_NAME="echo_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_echo - Win32 Debug"
+
+
+"$(INTDIR)\mod_echo.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_echo.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_echo.so" /d LONG_NAME="echo_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_echo.c
+
+"$(INTDIR)\mod_echo.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/examples/Makefile.in b/modules/examples/Makefile.in
new file mode 100644
index 0000000..7c5c149
--- /dev/null
+++ b/modules/examples/Makefile.in
@@ -0,0 +1,3 @@
+# a modules Makefile has no explicit targets -- they will be defined by
+# whatever modules are enabled. just grab special.mk to deal with this.
+include $(top_srcdir)/build/special.mk
diff --git a/modules/examples/NWGNUcase_flt b/modules/examples/NWGNUcase_flt
new file mode 100644
index 0000000..df9a86f
--- /dev/null
+++ b/modules/examples/NWGNUcase_flt
@@ -0,0 +1,256 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = case_flt
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Case Filter Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = $(NLM_NAME) Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_case_filter.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ case_filter_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/examples/NWGNUcase_flt_in b/modules/examples/NWGNUcase_flt_in
new file mode 100644
index 0000000..7429a44
--- /dev/null
+++ b/modules/examples/NWGNUcase_flt_in
@@ -0,0 +1,256 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = case_flt_in
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Case Filter In Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = $(NLM_NAME) Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_case_filter_in.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ case_filter_in_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/examples/NWGNUexample_hooks b/modules/examples/NWGNUexample_hooks
new file mode 100644
index 0000000..06bc16a
--- /dev/null
+++ b/modules/examples/NWGNUexample_hooks
@@ -0,0 +1,257 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/server/mpm/netware \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = example_hooks
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Example Hook Callback Handler Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Example Hook Callback Handler Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/example_hooks.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_example_hooks.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ example_hooks_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/examples/NWGNUexample_ipc b/modules/examples/NWGNUexample_ipc
new file mode 100644
index 0000000..605794b
--- /dev/null
+++ b/modules/examples/NWGNUexample_ipc
@@ -0,0 +1,257 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/server/mpm/netware \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = example_ipc
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Example IPC Callback Handler Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Example IPC Callback Handler Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/example_ipc.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_example_ipc.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ example_ipc_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/examples/NWGNUmakefile b/modules/examples/NWGNUmakefile
new file mode 100644
index 0000000..5d716ae
--- /dev/null
+++ b/modules/examples/NWGNUmakefile
@@ -0,0 +1,257 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME =
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION =
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME =
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE =
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+# If there is only one element to build it needs to be included twice
+# in the below target list.
+# Normally if there is only one element to be built within a
+# directory, the makefile for the single element would be called NWGNUmakefile.
+# But if there are multiples, the parent NWGNUmakefile must reference more
+# than one submakefile. Because the experimental directory might vary in the
+# number of submakefiles, but for the moment only contains one, we reference
+# it twice to allow it parent NWGNUmakefile to work properly. If another
+# submakefile is added, the extra reference to the first NLM should be removed.
+TARGET_nlm = \
+ $(OBJDIR)/example_hooks.nlm \
+ $(OBJDIR)/example_ipc.nlm \
+ $(OBJDIR)/case_flt.nlm \
+ $(OBJDIR)/case_flt_in.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/examples/README b/modules/examples/README
new file mode 100644
index 0000000..2e03362
--- /dev/null
+++ b/modules/examples/README
@@ -0,0 +1,54 @@
+README for Apache 2.0 Example Module
+[April, 1997, updated May 2000]
+
+The files in the src/modules/examples directory under the Apache
+distribution directory tree are provided as an example to those that
+wish to write modules that use the Apache API.
+
+The main file is mod_example_hooks.c, which illustrates all the different
+callback mechanisms and call syntaces. By no means does an add-on
+module need to include routines for all of the callbacks - quite the
+contrary!
+
+The example module is an actual working module. If you link it into
+your server, enable the "example-hooks-handler" handler for a location,
+and then browse to that location, you will see a display of some of the
+tracing the example module did as the various callbacks were made.
+
+To include the example module in your server add --enable-example-hooks
+to the other ./configure arguments executed from the httpd source tree.
+After that run 'make'.
+
+To add another module of your own:
+
+ A. cp modules/examples/mod_example_hooks.c modules/examples/mod_myexample.c
+ B. Modify the file
+ C. Add an entry to modules/examples/config.m4, e.g.
+ APACHE_MODULE(myexample, my new module, , , no)
+ The last argument specifies if the module is built by-default
+ D. Build the server with --enable-myexample
+
+For windows, the process is slightly different;
+
+ A. copy modules\examples\mod_example_hooks.c modules\examples\mod_myexample.c
+ B. copy modules\examples\mod_example_hooks.dsp modules\examples\mod_myexample.dsp
+ C. replace the occurrences of 'example_hooks' with your module name.
+ D. add the new .dsp to your Apache.dsw workspace, with dependencies
+ on the libapr, libaprutil and libhttpd projects. With the newer
+ Developer Studio 2002 through 2005, when you add the new .dsp
+ file it will be converted to a .vcproj file.
+
+To activate the example module, include a block similar to the
+following in your httpd.conf file:
+
+ <Location /example-info>
+ SetHandler example-hooks-handler
+ </Location>
+
+As an alternative, you can put the following into a .htaccess file and
+then request the file "test.example" from that location:
+
+ AddHandler example-hooks-handler .example
+
+After reloading/restarting your server, you should be able to browse
+to this location and see the brief display mentioned earlier.
diff --git a/modules/examples/config.m4 b/modules/examples/config.m4
new file mode 100644
index 0000000..dab9e87
--- /dev/null
+++ b/modules/examples/config.m4
@@ -0,0 +1,9 @@
+
+APACHE_MODPATH_INIT(examples)
+
+APACHE_MODULE(example_hooks, Example hook callback handler module, , , no)
+APACHE_MODULE(case_filter, Example uppercase conversion filter, , , no)
+APACHE_MODULE(case_filter_in, Example uppercase conversion input filter, , , no)
+APACHE_MODULE(example_ipc, Example of shared memory and mutex usage, , , no)
+
+APACHE_MODPATH_FINISH
diff --git a/modules/examples/mod_case_filter.c b/modules/examples/mod_case_filter.c
new file mode 100644
index 0000000..409f65b
--- /dev/null
+++ b/modules/examples/mod_case_filter.c
@@ -0,0 +1,139 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "apr_buckets.h"
+#include "apr_general.h"
+#include "apr_lib.h"
+#include "util_filter.h"
+#include "http_request.h"
+
+#include <ctype.h>
+
+static const char s_szCaseFilterName[] = "CaseFilter";
+module AP_MODULE_DECLARE_DATA case_filter_module;
+
+typedef struct
+{
+ int bEnabled;
+} CaseFilterConfig;
+
+static void *CaseFilterCreateServerConfig(apr_pool_t *p, server_rec *s)
+{
+ CaseFilterConfig *pConfig = apr_pcalloc(p,sizeof *pConfig);
+
+ pConfig->bEnabled = 0;
+
+ return pConfig;
+}
+
+static void CaseFilterInsertFilter(request_rec *r)
+{
+ CaseFilterConfig *pConfig = ap_get_module_config(r->server->module_config,
+ &case_filter_module);
+
+ if (!pConfig->bEnabled)
+ return;
+
+ ap_add_output_filter(s_szCaseFilterName, NULL, r, r->connection);
+}
+
+static apr_status_t CaseFilterOutFilter(ap_filter_t *f,
+ apr_bucket_brigade *pbbIn)
+{
+ request_rec *r = f->r;
+ conn_rec *c = r->connection;
+ apr_bucket *pbktIn;
+ apr_bucket_brigade *pbbOut;
+
+ pbbOut = apr_brigade_create(r->pool, c->bucket_alloc);
+ for (pbktIn = APR_BRIGADE_FIRST(pbbIn);
+ pbktIn != APR_BRIGADE_SENTINEL(pbbIn);
+ pbktIn = APR_BUCKET_NEXT(pbktIn))
+ {
+ const char *data;
+ apr_size_t len;
+ char *buf;
+ apr_size_t n;
+ apr_bucket *pbktOut;
+
+ if (APR_BUCKET_IS_EOS(pbktIn)) {
+ apr_bucket *pbktEOS = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(pbbOut, pbktEOS);
+ continue;
+ }
+
+ /* read */
+ apr_bucket_read(pbktIn, &data, &len, APR_BLOCK_READ);
+
+ /* write */
+ buf = apr_bucket_alloc(len, c->bucket_alloc);
+ for (n=0 ; n < len ; ++n) {
+ buf[n] = apr_toupper(data[n]);
+ }
+
+ pbktOut = apr_bucket_heap_create(buf, len, apr_bucket_free,
+ c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(pbbOut, pbktOut);
+ }
+
+ /* Q: is there any advantage to passing a brigade for each bucket?
+ * A: obviously, it can cut down server resource consumption, if this
+ * experimental module was fed a file of 4MB, it would be using 8MB for
+ * the 'read' buckets and the 'write' buckets.
+ *
+ * Note it is more efficient to consume (destroy) each bucket as it's
+ * processed above than to do a single cleanup down here. In any case,
+ * don't let our caller pass the same buckets to us, twice;
+ */
+ apr_brigade_cleanup(pbbIn);
+ return ap_pass_brigade(f->next, pbbOut);
+}
+
+static const char *CaseFilterEnable(cmd_parms *cmd, void *dummy, int arg)
+{
+ CaseFilterConfig *pConfig = ap_get_module_config(cmd->server->module_config,
+ &case_filter_module);
+ pConfig->bEnabled = arg;
+
+ return NULL;
+}
+
+static const command_rec CaseFilterCmds[] =
+{
+ AP_INIT_FLAG("CaseFilter", CaseFilterEnable, NULL, RSRC_CONF,
+ "Run a case filter on this host"),
+ { NULL }
+};
+
+static void CaseFilterRegisterHooks(apr_pool_t *p)
+{
+ ap_hook_insert_filter(CaseFilterInsertFilter, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_register_output_filter(s_szCaseFilterName, CaseFilterOutFilter, NULL,
+ AP_FTYPE_RESOURCE);
+}
+
+AP_DECLARE_MODULE(case_filter) =
+{
+ STANDARD20_MODULE_STUFF,
+ NULL,
+ NULL,
+ CaseFilterCreateServerConfig,
+ NULL,
+ CaseFilterCmds,
+ CaseFilterRegisterHooks
+};
diff --git a/modules/examples/mod_case_filter.dep b/modules/examples/mod_case_filter.dep
new file mode 100644
index 0000000..2b8cfad
--- /dev/null
+++ b/modules/examples/mod_case_filter.dep
@@ -0,0 +1,46 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_case_filter.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_case_filter.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/examples/mod_case_filter.dsp b/modules/examples/mod_case_filter.dsp
new file mode 100644
index 0000000..8f65689
--- /dev/null
+++ b/modules/examples/mod_case_filter.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_case_filter" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_case_filter - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_case_filter.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For case_filter:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_case_filter.mak" CFG="mod_case_filter - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_case_filter - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_case_filter - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_case_filter - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_case_filter_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_case_filter.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_case_filter.so" /d LONG_NAME="case_filter_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_case_filter.so" /base:@..\..\os\win32\BaseAddr.ref,mod_case_filter.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_case_filter.so" /base:@..\..\os\win32\BaseAddr.ref,mod_case_filter.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_case_filter.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_case_filter - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_case_filter_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_case_filter.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_case_filter.so" /d LONG_NAME="case_filter_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_case_filter.so" /base:@..\..\os\win32\BaseAddr.ref,mod_case_filter.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_case_filter.so" /base:@..\..\os\win32\BaseAddr.ref,mod_case_filter.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_case_filter.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_case_filter - Win32 Release"
+# Name "mod_case_filter - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_case_filter.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/examples/mod_case_filter.mak b/modules/examples/mod_case_filter.mak
new file mode 100644
index 0000000..a7f04bf
--- /dev/null
+++ b/modules/examples/mod_case_filter.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_case_filter.dsp
+!IF "$(CFG)" == ""
+CFG=mod_case_filter - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_case_filter - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_case_filter - Win32 Release" && "$(CFG)" != "mod_case_filter - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_case_filter.mak" CFG="mod_case_filter - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_case_filter - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_case_filter - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_case_filter - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_case_filter.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_case_filter.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_case_filter.obj"
+ -@erase "$(INTDIR)\mod_case_filter.res"
+ -@erase "$(INTDIR)\mod_case_filter_src.idb"
+ -@erase "$(INTDIR)\mod_case_filter_src.pdb"
+ -@erase "$(OUTDIR)\mod_case_filter.exp"
+ -@erase "$(OUTDIR)\mod_case_filter.lib"
+ -@erase "$(OUTDIR)\mod_case_filter.pdb"
+ -@erase "$(OUTDIR)\mod_case_filter.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_case_filter_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_case_filter.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_case_filter.so" /d LONG_NAME="case_filter_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_case_filter.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_case_filter.pdb" /debug /out:"$(OUTDIR)\mod_case_filter.so" /implib:"$(OUTDIR)\mod_case_filter.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_case_filter.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_case_filter.obj" \
+ "$(INTDIR)\mod_case_filter.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_case_filter.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_case_filter.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_case_filter.so"
+ if exist .\Release\mod_case_filter.so.manifest mt.exe -manifest .\Release\mod_case_filter.so.manifest -outputresource:.\Release\mod_case_filter.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_case_filter - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_case_filter.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_case_filter.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_case_filter.obj"
+ -@erase "$(INTDIR)\mod_case_filter.res"
+ -@erase "$(INTDIR)\mod_case_filter_src.idb"
+ -@erase "$(INTDIR)\mod_case_filter_src.pdb"
+ -@erase "$(OUTDIR)\mod_case_filter.exp"
+ -@erase "$(OUTDIR)\mod_case_filter.lib"
+ -@erase "$(OUTDIR)\mod_case_filter.pdb"
+ -@erase "$(OUTDIR)\mod_case_filter.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_case_filter_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_case_filter.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_case_filter.so" /d LONG_NAME="case_filter_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_case_filter.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_case_filter.pdb" /debug /out:"$(OUTDIR)\mod_case_filter.so" /implib:"$(OUTDIR)\mod_case_filter.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_case_filter.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_case_filter.obj" \
+ "$(INTDIR)\mod_case_filter.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_case_filter.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_case_filter.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_case_filter.so"
+ if exist .\Debug\mod_case_filter.so.manifest mt.exe -manifest .\Debug\mod_case_filter.so.manifest -outputresource:.\Debug\mod_case_filter.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_case_filter.dep")
+!INCLUDE "mod_case_filter.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_case_filter.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_case_filter - Win32 Release" || "$(CFG)" == "mod_case_filter - Win32 Debug"
+
+!IF "$(CFG)" == "mod_case_filter - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\examples"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\examples"
+
+!ELSEIF "$(CFG)" == "mod_case_filter - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\examples"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\examples"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_case_filter - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\examples"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\examples"
+
+!ELSEIF "$(CFG)" == "mod_case_filter - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\examples"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\examples"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_case_filter - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\examples"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\examples"
+
+!ELSEIF "$(CFG)" == "mod_case_filter - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\examples"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\examples"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_case_filter - Win32 Release"
+
+
+"$(INTDIR)\mod_case_filter.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_case_filter.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_case_filter.so" /d LONG_NAME="case_filter_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_case_filter - Win32 Debug"
+
+
+"$(INTDIR)\mod_case_filter.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_case_filter.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_case_filter.so" /d LONG_NAME="case_filter_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_case_filter.c
+
+"$(INTDIR)\mod_case_filter.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/examples/mod_case_filter_in.c b/modules/examples/mod_case_filter_in.c
new file mode 100644
index 0000000..c70a9eb
--- /dev/null
+++ b/modules/examples/mod_case_filter_in.c
@@ -0,0 +1,160 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * An example input filter - this converts input to upper case. Note that
+ * because of the moment it gets inserted it does NOT convert request headers.
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "apr_buckets.h"
+#include "apr_general.h"
+#include "apr_lib.h"
+#include "util_filter.h"
+#include "http_request.h"
+
+#include <ctype.h>
+
+static const char s_szCaseFilterName[] = "CaseFilterIn";
+module AP_MODULE_DECLARE_DATA case_filter_in_module;
+
+typedef struct
+{
+ int bEnabled;
+} CaseFilterInConfig;
+
+typedef struct
+{
+ apr_bucket_brigade *pbbTmp;
+} CaseFilterInContext;
+
+static void *CaseFilterInCreateServerConfig(apr_pool_t *p, server_rec *s)
+{
+ CaseFilterInConfig *pConfig = apr_pcalloc(p, sizeof *pConfig);
+
+ pConfig->bEnabled = 0;
+
+ return pConfig;
+}
+
+static void CaseFilterInInsertFilter(request_rec *r)
+{
+ CaseFilterInConfig *pConfig = ap_get_module_config(r->server->module_config,
+ &case_filter_in_module);
+ if (!pConfig->bEnabled)
+ return;
+
+ ap_add_input_filter(s_szCaseFilterName, NULL, r, r->connection);
+}
+
+static apr_status_t CaseFilterInFilter(ap_filter_t *f,
+ apr_bucket_brigade *pbbOut,
+ ap_input_mode_t eMode,
+ apr_read_type_e eBlock,
+ apr_off_t nBytes)
+{
+ request_rec *r = f->r;
+ conn_rec *c = r->connection;
+ CaseFilterInContext *pCtx;
+ apr_status_t ret;
+
+ if (!(pCtx = f->ctx)) {
+ f->ctx = pCtx = apr_palloc(r->pool, sizeof *pCtx);
+ pCtx->pbbTmp = apr_brigade_create(r->pool, c->bucket_alloc);
+ }
+
+ if (APR_BRIGADE_EMPTY(pCtx->pbbTmp)) {
+ ret = ap_get_brigade(f->next, pCtx->pbbTmp, eMode, eBlock, nBytes);
+
+ if (eMode == AP_MODE_EATCRLF || ret != APR_SUCCESS)
+ return ret;
+ }
+
+ while (!APR_BRIGADE_EMPTY(pCtx->pbbTmp)) {
+ apr_bucket *pbktIn = APR_BRIGADE_FIRST(pCtx->pbbTmp);
+ apr_bucket *pbktOut;
+ const char *data;
+ apr_size_t len;
+ char *buf;
+ apr_size_t n;
+
+ /* It is tempting to do this...
+ * APR_BUCKET_REMOVE(pB);
+ * APR_BRIGADE_INSERT_TAIL(pbbOut,pB);
+ * and change the case of the bucket data, but that would be wrong
+ * for a file or socket buffer, for example...
+ */
+
+ if (APR_BUCKET_IS_EOS(pbktIn)) {
+ APR_BUCKET_REMOVE(pbktIn);
+ APR_BRIGADE_INSERT_TAIL(pbbOut, pbktIn);
+ break;
+ }
+
+ ret=apr_bucket_read(pbktIn, &data, &len, eBlock);
+ if (ret != APR_SUCCESS)
+ return ret;
+
+ buf = ap_malloc(len);
+ for (n=0 ; n < len ; ++n) {
+ buf[n] = apr_toupper(data[n]);
+ }
+
+ pbktOut = apr_bucket_heap_create(buf, len, free, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(pbbOut, pbktOut);
+ apr_bucket_delete(pbktIn);
+ }
+
+ return APR_SUCCESS;
+}
+
+static const char *CaseFilterInEnable(cmd_parms *cmd, void *dummy, int arg)
+{
+ CaseFilterInConfig *pConfig
+ = ap_get_module_config(cmd->server->module_config,
+ &case_filter_in_module);
+ pConfig->bEnabled=arg;
+
+ return NULL;
+}
+
+static const command_rec CaseFilterInCmds[] =
+{
+ AP_INIT_FLAG("CaseFilterIn", CaseFilterInEnable, NULL, RSRC_CONF,
+ "Run an input case filter on this host"),
+ { NULL }
+};
+
+
+static void CaseFilterInRegisterHooks(apr_pool_t *p)
+{
+ ap_hook_insert_filter(CaseFilterInInsertFilter, NULL, NULL,
+ APR_HOOK_MIDDLE);
+ ap_register_input_filter(s_szCaseFilterName, CaseFilterInFilter, NULL,
+ AP_FTYPE_RESOURCE);
+}
+
+AP_DECLARE_MODULE(case_filter_in) =
+{
+ STANDARD20_MODULE_STUFF,
+ NULL,
+ NULL,
+ CaseFilterInCreateServerConfig,
+ NULL,
+ CaseFilterInCmds,
+ CaseFilterInRegisterHooks
+};
diff --git a/modules/examples/mod_case_filter_in.dep b/modules/examples/mod_case_filter_in.dep
new file mode 100644
index 0000000..abf8c16
--- /dev/null
+++ b/modules/examples/mod_case_filter_in.dep
@@ -0,0 +1,46 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_case_filter_in.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_case_filter_in.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/examples/mod_case_filter_in.dsp b/modules/examples/mod_case_filter_in.dsp
new file mode 100644
index 0000000..8a00793
--- /dev/null
+++ b/modules/examples/mod_case_filter_in.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_case_filter_in" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_case_filter_in - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_case_filter_in.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For case_filter_in:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_case_filter_in.mak" CFG="mod_case_filter_in - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_case_filter_in - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_case_filter_in - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_case_filter_in - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_case_filter_in_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_case_filter_in.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_case_filter_in.so" /d LONG_NAME="case_filter_in_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_case_filter_in.so" /base:@..\..\os\win32\BaseAddr.ref,mod_case_filter_in.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_case_filter_in.so" /base:@..\..\os\win32\BaseAddr.ref,mod_case_filter_in.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_case_filter_in.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_case_filter_in - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_case_filter_in_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_case_filter_in.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_case_filter_in.so" /d LONG_NAME="case_filter_in_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_case_filter_in.so" /base:@..\..\os\win32\BaseAddr.ref,mod_case_filter_in.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_case_filter_in.so" /base:@..\..\os\win32\BaseAddr.ref,mod_case_filter_in.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_case_filter_in.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_case_filter_in - Win32 Release"
+# Name "mod_case_filter_in - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_case_filter_in.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/examples/mod_case_filter_in.mak b/modules/examples/mod_case_filter_in.mak
new file mode 100644
index 0000000..28bb848
--- /dev/null
+++ b/modules/examples/mod_case_filter_in.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_case_filter_in.dsp
+!IF "$(CFG)" == ""
+CFG=mod_case_filter_in - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_case_filter_in - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_case_filter_in - Win32 Release" && "$(CFG)" != "mod_case_filter_in - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_case_filter_in.mak" CFG="mod_case_filter_in - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_case_filter_in - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_case_filter_in - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_case_filter_in - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_case_filter_in.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_case_filter_in.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_case_filter_in.obj"
+ -@erase "$(INTDIR)\mod_case_filter_in.res"
+ -@erase "$(INTDIR)\mod_case_filter_in_src.idb"
+ -@erase "$(INTDIR)\mod_case_filter_in_src.pdb"
+ -@erase "$(OUTDIR)\mod_case_filter_in.exp"
+ -@erase "$(OUTDIR)\mod_case_filter_in.lib"
+ -@erase "$(OUTDIR)\mod_case_filter_in.pdb"
+ -@erase "$(OUTDIR)\mod_case_filter_in.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_case_filter_in_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_case_filter_in.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_case_filter_in.so" /d LONG_NAME="case_filter_in_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_case_filter_in.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_case_filter_in.pdb" /debug /out:"$(OUTDIR)\mod_case_filter_in.so" /implib:"$(OUTDIR)\mod_case_filter_in.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_case_filter_in.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_case_filter_in.obj" \
+ "$(INTDIR)\mod_case_filter_in.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_case_filter_in.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_case_filter_in.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_case_filter_in.so"
+ if exist .\Release\mod_case_filter_in.so.manifest mt.exe -manifest .\Release\mod_case_filter_in.so.manifest -outputresource:.\Release\mod_case_filter_in.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_case_filter_in - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_case_filter_in.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_case_filter_in.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_case_filter_in.obj"
+ -@erase "$(INTDIR)\mod_case_filter_in.res"
+ -@erase "$(INTDIR)\mod_case_filter_in_src.idb"
+ -@erase "$(INTDIR)\mod_case_filter_in_src.pdb"
+ -@erase "$(OUTDIR)\mod_case_filter_in.exp"
+ -@erase "$(OUTDIR)\mod_case_filter_in.lib"
+ -@erase "$(OUTDIR)\mod_case_filter_in.pdb"
+ -@erase "$(OUTDIR)\mod_case_filter_in.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_case_filter_in_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_case_filter_in.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_case_filter_in.so" /d LONG_NAME="case_filter_in_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_case_filter_in.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_case_filter_in.pdb" /debug /out:"$(OUTDIR)\mod_case_filter_in.so" /implib:"$(OUTDIR)\mod_case_filter_in.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_case_filter_in.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_case_filter_in.obj" \
+ "$(INTDIR)\mod_case_filter_in.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_case_filter_in.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_case_filter_in.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_case_filter_in.so"
+ if exist .\Debug\mod_case_filter_in.so.manifest mt.exe -manifest .\Debug\mod_case_filter_in.so.manifest -outputresource:.\Debug\mod_case_filter_in.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_case_filter_in.dep")
+!INCLUDE "mod_case_filter_in.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_case_filter_in.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_case_filter_in - Win32 Release" || "$(CFG)" == "mod_case_filter_in - Win32 Debug"
+
+!IF "$(CFG)" == "mod_case_filter_in - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\examples"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\examples"
+
+!ELSEIF "$(CFG)" == "mod_case_filter_in - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\examples"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\examples"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_case_filter_in - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\examples"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\examples"
+
+!ELSEIF "$(CFG)" == "mod_case_filter_in - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\examples"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\examples"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_case_filter_in - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\examples"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\examples"
+
+!ELSEIF "$(CFG)" == "mod_case_filter_in - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\examples"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\examples"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_case_filter_in - Win32 Release"
+
+
+"$(INTDIR)\mod_case_filter_in.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_case_filter_in.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_case_filter_in.so" /d LONG_NAME="case_filter_in_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_case_filter_in - Win32 Debug"
+
+
+"$(INTDIR)\mod_case_filter_in.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_case_filter_in.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_case_filter_in.so" /d LONG_NAME="case_filter_in_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_case_filter_in.c
+
+"$(INTDIR)\mod_case_filter_in.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/examples/mod_example_hooks.c b/modules/examples/mod_example_hooks.c
new file mode 100644
index 0000000..f7ef5a5
--- /dev/null
+++ b/modules/examples/mod_example_hooks.c
@@ -0,0 +1,1552 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Apache example_hooks module. Provide demonstrations of how modules do things.
+ * It is not meant to be used in a production server. Since it participates
+ * in all of the processing phases, it could conceivable interfere with
+ * the proper operation of other modules -- particularly the ones related
+ * to security.
+ *
+ * In the interest of brevity, all functions and structures internal to
+ * this module, but which may have counterparts in *real* modules, are
+ * prefixed with 'x_' instead of 'example_'.
+ *
+ * To use mod_example_hooks, configure the Apache build with
+ * --enable-example-hooks and compile. Set up a <Location> block in your
+ * configuration file like so:
+ *
+ * <Location /example>
+ * SetHandler example-hooks-handler
+ * </Location>
+ *
+ * When you look at that location on your server, you will see a backtrace of
+ * the callbacks that have been invoked up to that point. See the ErrorLog for
+ * more information on code paths that touch mod_example_hooks.
+ *
+ * IMPORTANT NOTES
+ * ===============
+ *
+ * Do NOT use this module on a production server. It attaches itself to every
+ * phase of the server runtime operations including startup, shutdown and
+ * request processing, and produces copious amounts of logging data. This will
+ * negatively affect server performance.
+ *
+ * Do NOT use mod_example_hooks as the basis for your own code. This module
+ * implements every callback hook offered by the Apache core, and your
+ * module will almost certainly not have to implement this much. If you
+ * want a simple module skeleton to start development, use apxs -g.
+ *
+ * XXX TO DO XXX
+ * =============
+ *
+ * * Enable HTML backtrace entries for more callbacks that are not directly
+ * associated with a request
+ * * Make sure every callback that posts an HTML backtrace entry does so in the * right category, so nothing gets overwritten
+ * * Implement some logic to show what happens in the parent, and what in the
+ * child(ren)
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_main.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "util_script.h"
+#include "http_connection.h"
+#ifdef HAVE_UNIX_SUEXEC
+#include "unixd.h"
+#endif
+#include "scoreboard.h"
+#include "mpm_common.h"
+
+#include "apr_strings.h"
+
+#include <stdio.h>
+
+/*--------------------------------------------------------------------------*/
+/* */
+/* Data declarations. */
+/* */
+/* Here are the static cells and structure declarations private to our */
+/* module. */
+/* */
+/*--------------------------------------------------------------------------*/
+
+/*
+ * Sample configuration record. Used for both per-directory and per-server
+ * configuration data.
+ *
+ * It's perfectly reasonable to have two different structures for the two
+ * different environments. The same command handlers will be called for
+ * both, though, so the handlers need to be able to tell them apart. One
+ * possibility is for both structures to start with an int which is 0 for
+ * one and 1 for the other.
+ *
+ * Note that while the per-directory and per-server configuration records are
+ * available to most of the module handlers, they should be treated as
+ * READ-ONLY by all except the command and merge handlers. Sometimes handlers
+ * are handed a record that applies to the current location by implication or
+ * inheritance, and modifying it will change the rules for other locations.
+ */
+typedef struct x_cfg {
+ int cmode; /* Environment to which record applies
+ * (directory, server, or combination).
+ */
+#define CONFIG_MODE_SERVER 1
+#define CONFIG_MODE_DIRECTORY 2
+#define CONFIG_MODE_COMBO 3 /* Shouldn't ever happen. */
+ int local; /* Boolean: "Example" directive declared
+ * here?
+ */
+ int congenital; /* Boolean: did we inherit an "Example"? */
+ char *trace; /* Pointer to trace string. */
+ char *loc; /* Location to which this record applies. */
+} x_cfg;
+
+/*
+ * String pointer to hold the startup trace. No harm working with a global until
+ * the server is (may be) multi-threaded.
+ */
+static const char *trace = NULL;
+
+/*
+ * Declare ourselves so the configuration routines can find and know us.
+ * We'll fill it in at the end of the module.
+ */
+module AP_MODULE_DECLARE_DATA example_hooks_module;
+
+/*--------------------------------------------------------------------------*/
+/* */
+/* The following pseudo-prototype declarations illustrate the parameters */
+/* passed to command handlers for the different types of directive */
+/* syntax. If an argument was specified in the directive definition */
+/* (look for "command_rec" below), it's available to the command handler */
+/* via the (void *) info field in the cmd_parms argument passed to the */
+/* handler (cmd->info for the examples below). */
+/* */
+/*--------------------------------------------------------------------------*/
+
+/*
+ * Command handler for a NO_ARGS directive. Declared in the command_rec
+ * list with
+ * AP_INIT_NO_ARGS("directive", function, mconfig, where, help)
+ *
+ * static const char *handle_NO_ARGS(cmd_parms *cmd, void *mconfig);
+ */
+
+/*
+ * Command handler for a RAW_ARGS directive. The "args" argument is the text
+ * of the commandline following the directive itself. Declared in the
+ * command_rec list with
+ * AP_INIT_RAW_ARGS("directive", function, mconfig, where, help)
+ *
+ * static const char *handle_RAW_ARGS(cmd_parms *cmd, void *mconfig,
+ * const char *args);
+ */
+
+/*
+ * Command handler for a FLAG directive. The single parameter is passed in
+ * "bool", which is either zero or not for Off or On respectively.
+ * Declared in the command_rec list with
+ * AP_INIT_FLAG("directive", function, mconfig, where, help)
+ *
+ * static const char *handle_FLAG(cmd_parms *cmd, void *mconfig, int bool);
+ */
+
+/*
+ * Command handler for a TAKE1 directive. The single parameter is passed in
+ * "word1". Declared in the command_rec list with
+ * AP_INIT_TAKE1("directive", function, mconfig, where, help)
+ *
+ * static const char *handle_TAKE1(cmd_parms *cmd, void *mconfig,
+ * char *word1);
+ */
+
+/*
+ * Command handler for a TAKE2 directive. TAKE2 commands must always have
+ * exactly two arguments. Declared in the command_rec list with
+ * AP_INIT_TAKE2("directive", function, mconfig, where, help)
+ *
+ * static const char *handle_TAKE2(cmd_parms *cmd, void *mconfig,
+ * char *word1, char *word2);
+ */
+
+/*
+ * Command handler for a TAKE3 directive. Like TAKE2, these must have exactly
+ * three arguments, or the parser complains and doesn't bother calling us.
+ * Declared in the command_rec list with
+ * AP_INIT_TAKE3("directive", function, mconfig, where, help)
+ *
+ * static const char *handle_TAKE3(cmd_parms *cmd, void *mconfig,
+ * char *word1, char *word2, char *word3);
+ */
+
+/*
+ * Command handler for a TAKE12 directive. These can take either one or two
+ * arguments.
+ * - word2 is a NULL pointer if no second argument was specified.
+ * Declared in the command_rec list with
+ * AP_INIT_TAKE12("directive", function, mconfig, where, help)
+ *
+ * static const char *handle_TAKE12(cmd_parms *cmd, void *mconfig,
+ * char *word1, char *word2);
+ */
+
+/*
+ * Command handler for a TAKE123 directive. A TAKE123 directive can be given,
+ * as might be expected, one, two, or three arguments.
+ * - word2 is a NULL pointer if no second argument was specified.
+ * - word3 is a NULL pointer if no third argument was specified.
+ * Declared in the command_rec list with
+ * AP_INIT_TAKE123("directive", function, mconfig, where, help)
+ *
+ * static const char *handle_TAKE123(cmd_parms *cmd, void *mconfig,
+ * char *word1, char *word2, char *word3);
+ */
+
+/*
+ * Command handler for a TAKE13 directive. Either one or three arguments are
+ * permitted - no two-parameters-only syntax is allowed.
+ * - word2 and word3 are NULL pointers if only one argument was specified.
+ * Declared in the command_rec list with
+ * AP_INIT_TAKE13("directive", function, mconfig, where, help)
+ *
+ * static const char *handle_TAKE13(cmd_parms *cmd, void *mconfig,
+ * char *word1, char *word2, char *word3);
+ */
+
+/*
+ * Command handler for a TAKE23 directive. At least two and as many as three
+ * arguments must be specified.
+ * - word3 is a NULL pointer if no third argument was specified.
+ * Declared in the command_rec list with
+ * AP_INIT_TAKE23("directive", function, mconfig, where, help)
+ *
+ * static const char *handle_TAKE23(cmd_parms *cmd, void *mconfig,
+ * char *word1, char *word2, char *word3);
+ */
+
+/*
+ * Command handler for a ITERATE directive.
+ * - Handler is called once for each of n arguments given to the directive.
+ * - word1 points to each argument in turn.
+ * Declared in the command_rec list with
+ * AP_INIT_ITERATE("directive", function, mconfig, where, help)
+ *
+ * static const char *handle_ITERATE(cmd_parms *cmd, void *mconfig,
+ * char *word1);
+ */
+
+/*
+ * Command handler for a ITERATE2 directive.
+ * - Handler is called once for each of the second and subsequent arguments
+ * given to the directive.
+ * - word1 is the same for each call for a particular directive instance (the
+ * first argument).
+ * - word2 points to each of the second and subsequent arguments in turn.
+ * Declared in the command_rec list with
+ * AP_INIT_ITERATE2("directive", function, mconfig, where, help)
+ *
+ * static const char *handle_ITERATE2(cmd_parms *cmd, void *mconfig,
+ * char *word1, char *word2);
+ */
+
+/*--------------------------------------------------------------------------*/
+/* */
+/* These routines are strictly internal to this module, and support its */
+/* operation. They are not referenced by any external portion of the */
+/* server. */
+/* */
+/*--------------------------------------------------------------------------*/
+
+/*
+ * Locate our directory configuration record for the current request.
+ */
+static x_cfg *our_dconfig(const request_rec *r)
+{
+ return (x_cfg *) ap_get_module_config(r->per_dir_config, &example_hooks_module);
+}
+
+/*
+ * The following utility routines are not used in the module. Don't
+ * compile them so -Wall doesn't complain about functions that are
+ * defined but not used.
+ */
+#if 0
+/*
+ * Locate our server configuration record for the specified server.
+ */
+static x_cfg *our_sconfig(const server_rec *s)
+{
+ return (x_cfg *) ap_get_module_config(s->module_config, &example_hooks_module);
+}
+
+/*
+ * Likewise for our configuration record for the specified request.
+ */
+static x_cfg *our_rconfig(const request_rec *r)
+{
+ return (x_cfg *) ap_get_module_config(r->request_config, &example_hooks_module);
+}
+#endif /* if 0 */
+
+/*
+ * Likewise for our configuration record for a connection.
+ */
+static x_cfg *our_cconfig(const conn_rec *c)
+{
+ return (x_cfg *) ap_get_module_config(c->conn_config, &example_hooks_module);
+}
+
+/*
+ * You *could* change the following if you wanted to see the calling
+ * sequence reported in the server's error_log, but beware - almost all of
+ * these co-routines are called for every single request, and the impact
+ * on the size (and readability) of the error_log is considerable.
+ */
+#ifndef EXAMPLE_LOG_EACH
+#define EXAMPLE_LOG_EACH 0
+#endif
+
+#if EXAMPLE_LOG_EACH
+static void example_log_each(apr_pool_t *p, server_rec *s, const char *note)
+{
+ if (s != NULL) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02991)
+ "mod_example_hooks: %s", note);
+ }
+ else {
+ apr_file_t *out = NULL;
+ apr_file_open_stderr(&out, p);
+ apr_file_printf(out, "mod_example_hooks traced in non-loggable "
+ "context: %s\n", note);
+ }
+}
+#endif
+
+/*
+ * This utility routine traces the hooks called when the server starts up.
+ * It leaves a trace in a global variable, so it should not be called from
+ * a hook handler that runs in a multi-threaded situation.
+ */
+
+static void trace_startup(apr_pool_t *p, server_rec *s, x_cfg *mconfig,
+ const char *note)
+{
+ const char *sofar;
+ char *where, *addon;
+
+#if EXAMPLE_LOG_EACH
+ example_log_each(p, s, note);
+#endif
+
+ /*
+ * If we weren't passed a configuration record, we can't figure out to
+ * what location this call applies. This only happens for co-routines
+ * that don't operate in a particular directory or server context. If we
+ * got a valid record, extract the location (directory or server) to which
+ * it applies.
+ */
+ where = (mconfig != NULL) ? mconfig->loc : "nowhere";
+ where = (where != NULL) ? where : "";
+
+ addon = apr_pstrcat(p,
+ " <li>\n"
+ " <dl>\n"
+ " <dt><samp>", note, "</samp></dt>\n"
+ " <dd><samp>[", where, "]</samp></dd>\n"
+ " </dl>\n"
+ " </li>\n",
+ NULL);
+
+ /*
+ * Make sure that we start with a valid string, even if we have never been
+ * called.
+ */
+ sofar = (trace == NULL) ? "" : trace;
+
+ trace = apr_pstrcat(p, sofar, addon, NULL);
+}
+
+
+/*
+ * This utility route traces the hooks called as a request is handled.
+ * It takes the current request as argument
+ */
+#define TRACE_NOTE "example-hooks-trace"
+
+static void trace_request(const request_rec *r, const char *note)
+{
+ const char *trace_copy, *sofar;
+ char *addon, *where;
+ x_cfg *cfg;
+
+#if EXAMPLE_LOG_EACH
+ example_log_each(r->pool, r->server, note);
+#endif
+
+ if ((sofar = apr_table_get(r->notes, TRACE_NOTE)) == NULL) {
+ sofar = "";
+ }
+
+ cfg = our_dconfig(r);
+
+ where = (cfg != NULL) ? cfg->loc : "nowhere";
+ where = (where != NULL) ? where : "";
+
+ addon = apr_pstrcat(r->pool,
+ " <li>\n"
+ " <dl>\n"
+ " <dt><samp>", note, "</samp></dt>\n"
+ " <dd><samp>[", where, "]</samp></dd>\n"
+ " </dl>\n"
+ " </li>\n",
+ NULL);
+
+ trace_copy = apr_pstrcat(r->pool, sofar, addon, NULL);
+ apr_table_set(r->notes, TRACE_NOTE, trace_copy);
+}
+
+/*
+ * This utility routine traces the hooks called while processing a
+ * Connection. Its trace is kept in the pool notes of the pool associated
+ * with the Connection.
+ */
+
+/*
+ * Key to get and set the userdata. We should be able to get away
+ * with a constant key, since in prefork mode the process will have
+ * the connection and its pool to itself entirely, and in
+ * multi-threaded mode each connection will have its own pool.
+ */
+#define CONN_NOTE "example-hooks-connection"
+
+static void trace_connection(conn_rec *c, const char *note)
+{
+ const char *trace_copy, *sofar;
+ char *addon, *where;
+ void *data;
+ x_cfg *cfg;
+
+#if EXAMPLE_LOG_EACH
+ example_log_each(c->pool, c->base_server, note);
+#endif
+
+ cfg = our_cconfig(c);
+
+ where = (cfg != NULL) ? cfg->loc : "nowhere";
+ where = (where != NULL) ? where : "";
+
+ addon = apr_pstrcat(c->pool,
+ " <li>\n"
+ " <dl>\n"
+ " <dt><samp>", note, "</samp></dt>\n"
+ " <dd><samp>[", where, "]</samp></dd>\n"
+ " </dl>\n"
+ " </li>\n",
+ NULL);
+
+ /* Find existing notes and copy */
+ apr_pool_userdata_get(&data, CONN_NOTE, c->pool);
+ sofar = (data == NULL) ? "" : (const char *) data;
+
+ /* Tack addon onto copy */
+ trace_copy = apr_pstrcat(c->pool, sofar, addon, NULL);
+
+ /*
+ * Stash copy back into pool notes. This call has a cleanup
+ * parameter, but we're not using it because the string has been
+ * allocated from that same pool. There is also an unused return
+ * value: we have nowhere to communicate any error that might
+ * occur, and will have to check for the existence of this data on
+ * the other end.
+ */
+ apr_pool_userdata_set((const void *) trace_copy, CONN_NOTE,
+ NULL, c->pool);
+}
+
+static void trace_nocontext(apr_pool_t *p, const char *file, int line,
+ const char *note)
+{
+ /*
+ * Since we have no request or connection to trace, or any idea
+ * from where this routine was called, there's really not much we
+ * can do. If we are not logging everything by way of the
+ * EXAMPLE_LOG_EACH constant, do nothing in this routine.
+ */
+
+#ifdef EXAMPLE_LOG_EACH
+ ap_log_perror(file, line, APLOG_MODULE_INDEX, APLOG_NOTICE, 0, p, "%s", note);
+#endif
+}
+
+
+/*--------------------------------------------------------------------------*/
+/* We prototyped the various syntax for command handlers (routines that */
+/* are called when the configuration parser detects a directive declared */
+/* by our module) earlier. Now we actually declare a "real" routine that */
+/* will be invoked by the parser when our "real" directive is */
+/* encountered. */
+/* */
+/* If a command handler encounters a problem processing the directive, it */
+/* signals this fact by returning a non-NULL pointer to a string */
+/* describing the problem. */
+/* */
+/* The magic return value DECLINE_CMD is used to deal with directives */
+/* that might be declared by multiple modules. If the command handler */
+/* returns NULL, the directive was processed; if it returns DECLINE_CMD, */
+/* the next module (if any) that declares the directive is given a chance */
+/* at it. If it returns any other value, it's treated as the text of an */
+/* error message. */
+/*--------------------------------------------------------------------------*/
+/*
+ * Command handler for the NO_ARGS "Example" directive. All we do is mark the
+ * call in the trace log, and flag the applicability of the directive to the
+ * current location in that location's configuration record.
+ */
+static const char *cmd_example(cmd_parms *cmd, void *mconfig)
+{
+ x_cfg *cfg = (x_cfg *) mconfig;
+
+ /*
+ * "Example Wuz Here"
+ */
+ cfg->local = 1;
+ trace_startup(cmd->pool, cmd->server, cfg, "cmd_example()");
+ return NULL;
+}
+
+/*
+ * This function gets called to create a per-directory configuration
+ * record. This will be called for the "default" server environment, and for
+ * each directory for which the parser finds any of our directives applicable.
+ * If a directory doesn't have any of our directives involved (i.e., they
+ * aren't in the .htaccess file, or a <Location>, <Directory>, or related
+ * block), this routine will *not* be called - the configuration for the
+ * closest ancestor is used.
+ *
+ * The return value is a pointer to the created module-specific
+ * structure.
+ */
+static void *x_create_dir_config(apr_pool_t *p, char *dirspec)
+{
+ x_cfg *cfg;
+ char *dname = dirspec;
+ char *note;
+
+ /*
+ * Allocate the space for our record from the pool supplied.
+ */
+ cfg = (x_cfg *) apr_pcalloc(p, sizeof(x_cfg));
+ /*
+ * Now fill in the defaults. If there are any `parent' configuration
+ * records, they'll get merged as part of a separate callback.
+ */
+ cfg->local = 0;
+ cfg->congenital = 0;
+ cfg->cmode = CONFIG_MODE_DIRECTORY;
+ /*
+ * Finally, add our trace to the callback list.
+ */
+ dname = (dname != NULL) ? dname : "";
+ cfg->loc = apr_pstrcat(p, "DIR(", dname, ")", NULL);
+ note = apr_psprintf(p, "x_create_dir_config(p == %pp, dirspec == %s)",
+ (void*) p, dirspec);
+ trace_startup(p, NULL, cfg, note);
+ return (void *) cfg;
+}
+
+/*
+ * This function gets called to merge two per-directory configuration
+ * records. This is typically done to cope with things like .htaccess files
+ * or <Location> directives for directories that are beneath one for which a
+ * configuration record was already created. The routine has the
+ * responsibility of creating a new record and merging the contents of the
+ * other two into it appropriately. If the module doesn't declare a merge
+ * routine, the record for the closest ancestor location (that has one) is
+ * used exclusively.
+ *
+ * The routine MUST NOT modify any of its arguments!
+ *
+ * The return value is a pointer to the created module-specific structure
+ * containing the merged values.
+ */
+static void *x_merge_dir_config(apr_pool_t *p, void *parent_conf,
+ void *newloc_conf)
+{
+
+ x_cfg *merged_config = (x_cfg *) apr_pcalloc(p, sizeof(x_cfg));
+ x_cfg *pconf = (x_cfg *) parent_conf;
+ x_cfg *nconf = (x_cfg *) newloc_conf;
+ char *note;
+
+ /*
+ * Some things get copied directly from the more-specific record, rather
+ * than getting merged.
+ */
+ merged_config->local = nconf->local;
+ merged_config->loc = apr_pstrdup(p, nconf->loc);
+ /*
+ * Others, like the setting of the `congenital' flag, get ORed in. The
+ * setting of that particular flag, for instance, is TRUE if it was ever
+ * true anywhere in the upstream configuration.
+ */
+ merged_config->congenital = (pconf->congenital | pconf->local);
+ /*
+ * If we're merging records for two different types of environment (server
+ * and directory), mark the new record appropriately. Otherwise, inherit
+ * the current value.
+ */
+ merged_config->cmode =
+ (pconf->cmode == nconf->cmode) ? pconf->cmode : CONFIG_MODE_COMBO;
+ /*
+ * Now just record our being called in the trace list. Include the
+ * locations we were asked to merge.
+ */
+ note = apr_psprintf(p, "x_merge_dir_config(p == %pp, parent_conf == "
+ "%pp, newloc_conf == %pp)", (void*) p,
+ (void*) parent_conf, (void*) newloc_conf);
+ trace_startup(p, NULL, merged_config, note);
+ return (void *) merged_config;
+}
+
+/*
+ * This function gets called to create a per-server configuration
+ * record. It will always be called for the "default" server.
+ *
+ * The return value is a pointer to the created module-specific
+ * structure.
+ */
+static void *x_create_server_config(apr_pool_t *p, server_rec *s)
+{
+
+ x_cfg *cfg;
+ char *sname = s->server_hostname;
+
+ /*
+ * As with the x_create_dir_config() reoutine, we allocate and fill
+ * in an empty record.
+ */
+ cfg = (x_cfg *) apr_pcalloc(p, sizeof(x_cfg));
+ cfg->local = 0;
+ cfg->congenital = 0;
+ cfg->cmode = CONFIG_MODE_SERVER;
+ /*
+ * Note that we were called in the trace list.
+ */
+ sname = (sname != NULL) ? sname : "";
+ cfg->loc = apr_pstrcat(p, "SVR(", sname, ")", NULL);
+ trace_startup(p, s, cfg, "x_create_server_config()");
+ return (void *) cfg;
+}
+
+/*
+ * This function gets called to merge two per-server configuration
+ * records. This is typically done to cope with things like virtual hosts and
+ * the default server configuration The routine has the responsibility of
+ * creating a new record and merging the contents of the other two into it
+ * appropriately. If the module doesn't declare a merge routine, the more
+ * specific existing record is used exclusively.
+ *
+ * The routine MUST NOT modify any of its arguments!
+ *
+ * The return value is a pointer to the created module-specific structure
+ * containing the merged values.
+ */
+static void *x_merge_server_config(apr_pool_t *p, void *server1_conf,
+ void *server2_conf)
+{
+
+ x_cfg *merged_config = (x_cfg *) apr_pcalloc(p, sizeof(x_cfg));
+ x_cfg *s1conf = (x_cfg *) server1_conf;
+ x_cfg *s2conf = (x_cfg *) server2_conf;
+ char *note;
+
+ /*
+ * Our inheritance rules are our own, and part of our module's semantics.
+ * Basically, just note whence we came.
+ */
+ merged_config->cmode =
+ (s1conf->cmode == s2conf->cmode) ? s1conf->cmode : CONFIG_MODE_COMBO;
+ merged_config->local = s2conf->local;
+ merged_config->congenital = (s1conf->congenital | s1conf->local);
+ merged_config->loc = apr_pstrdup(p, s2conf->loc);
+ /*
+ * Trace our call, including what we were asked to merge.
+ */
+ note = apr_pstrcat(p, "x_merge_server_config(\"", s1conf->loc, "\",\"",
+ s2conf->loc, "\")", NULL);
+ trace_startup(p, NULL, merged_config, note);
+ return (void *) merged_config;
+}
+
+
+/*--------------------------------------------------------------------------*
+ * *
+ * Now let's declare routines for each of the callback hooks in order. *
+ * (That's the order in which they're listed in the callback list, *not *
+ * the order in which the server calls them! See the command_rec *
+ * declaration near the bottom of this file.) Note that these may be *
+ * called for situations that don't relate primarily to our function - in *
+ * other words, the fixup handler shouldn't assume that the request has *
+ * to do with "example_hooks" stuff. *
+ * *
+ * With the exception of the content handler, all of our routines will be *
+ * called for each request, unless an earlier handler from another module *
+ * aborted the sequence. *
+ * *
+ * There are three types of hooks (see include/ap_config.h): *
+ * *
+ * VOID : No return code, run all handlers declared by any module *
+ * RUN_FIRST : Run all handlers until one returns something other *
+ * than DECLINED. Hook runner result is result of last callback *
+ * RUN_ALL : Run all handlers until one returns something other than OK *
+ * or DECLINED. The hook runner returns that other value. If *
+ * all hooks run, the hook runner returns OK. *
+ * *
+ * Handlers that are declared as "int" can return the following: *
+ * *
+ * OK Handler accepted the request and did its thing with it. *
+ * DECLINED Handler took no action. *
+ * HTTP_mumble Handler looked at request and found it wanting. *
+ * *
+ * See include/httpd.h for a list of HTTP_mumble status codes. Handlers *
+ * that are not declared as int return a valid pointer, or NULL if they *
+ * DECLINE to handle their phase for that specific request. Exceptions, if *
+ * any, are noted with each routine. *
+ *--------------------------------------------------------------------------*/
+
+/*
+ * This routine is called before the server processes the configuration
+ * files. There is no return value.
+ */
+static int x_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp)
+{
+ /*
+ * Log the call and exit.
+ */
+ trace_startup(pconf, NULL, NULL, "x_pre_config()");
+ return OK;
+}
+
+/*
+ * This routine is called after the server processes the configuration
+ * files. At this point the module may review and adjust its configuration
+ * settings in relation to one another and report any problems. On restart,
+ * this routine will be called twice, once in the startup process (which
+ * exits shortly after this phase) and once in the running server process.
+ *
+ * The return value is OK, DECLINED, or HTTP_mumble. If we return OK, the
+ * server will still call any remaining modules with an handler for this
+ * phase.
+ */
+static int x_check_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ /*
+ * Log the call and exit.
+ */
+ trace_startup(pconf, s, NULL, "x_check_config()");
+ return OK;
+}
+
+/*
+ * This routine is called when the -t command-line option is supplied.
+ * It executes only once, in the startup process, after the check_config
+ * phase and just before the process exits. At this point the module
+ * may output any information useful in configuration testing.
+ *
+ * This is a VOID hook: all defined handlers get called.
+ */
+static void x_test_config(apr_pool_t *pconf, server_rec *s)
+{
+ apr_file_t *out = NULL;
+
+ apr_file_open_stderr(&out, pconf);
+
+ apr_file_printf(out, "Example module configuration test routine\n");
+
+ trace_startup(pconf, s, NULL, "x_test_config()");
+}
+
+/*
+ * This routine is called to perform any module-specific log file
+ * openings. It is invoked just before the post_config phase
+ *
+ * The return value is OK, DECLINED, or HTTP_mumble. If we return OK, the
+ * server will still call any remaining modules with an handler for this
+ * phase.
+ */
+static int x_open_logs(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ /*
+ * Log the call and exit.
+ */
+ trace_startup(pconf, s, NULL, "x_open_logs()");
+ return OK;
+}
+
+/*
+ * This routine is called after the server finishes the configuration
+ * process. At this point the module may review and adjust its configuration
+ * settings in relation to one another and report any problems. On restart,
+ * this routine will be called only once, in the running server process.
+ *
+ * The return value is OK, DECLINED, or HTTP_mumble. If we return OK, the
+ * server will still call any remaining modules with an handler for this
+ * phase.
+ */
+static int x_post_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ /*
+ * Log the call and exit.
+ */
+ trace_startup(pconf, s, NULL, "x_post_config()");
+ return OK;
+}
+
+/*
+ * All our process-death routine does is add its trace to the log.
+ */
+static apr_status_t x_child_exit(void *data)
+{
+ char *note;
+ server_rec *s = data;
+ char *sname = s->server_hostname;
+
+ /*
+ * The arbitrary text we add to our trace entry indicates for which server
+ * we're being called.
+ */
+ sname = (sname != NULL) ? sname : "";
+ note = apr_pstrcat(s->process->pool, "x_child_exit(", sname, ")", NULL);
+ trace_startup(s->process->pool, s, NULL, note);
+ return APR_SUCCESS;
+}
+
+/*
+ * All our process initialiser does is add its trace to the log.
+ *
+ * This is a VOID hook: all defined handlers get called.
+ */
+static void x_child_init(apr_pool_t *p, server_rec *s)
+{
+ char *note;
+ char *sname = s->server_hostname;
+
+ /*
+ * The arbitrary text we add to our trace entry indicates for which server
+ * we're being called.
+ */
+ sname = (sname != NULL) ? sname : "";
+ note = apr_pstrcat(p, "x_child_init(", sname, ")", NULL);
+ trace_startup(p, s, NULL, note);
+
+ apr_pool_cleanup_register(p, s, x_child_exit, x_child_exit);
+}
+
+/*
+ * The hook runner for ap_hook_http_scheme is aliased to ap_http_scheme(),
+ * a routine that the core and other modules call when they need to know
+ * the URL scheme for the request. For instance, mod_ssl returns "https"
+ * if the server_rec associated with the request has SSL enabled.
+ *
+ * This hook was named 'ap_hook_http_method' in httpd 2.0.
+ *
+ * This is a RUN_FIRST hook: the first handler to return a non NULL
+ * value aborts the handler chain. The http_core module inserts a
+ * fallback handler (with APR_HOOK_REALLY_LAST preference) that returns
+ * "http".
+ */
+static const char *x_http_scheme(const request_rec *r)
+{
+ /*
+ * Log the call and exit.
+ */
+ trace_request(r, "x_http_scheme()");
+
+ /* We have no claims to make about the request scheme */
+ return NULL;
+}
+
+/*
+ * The runner for this hook is aliased to ap_default_port(), which the
+ * core and other modules call when they need to know the default port
+ * for a particular server. This is used for instance to omit the
+ * port number from a Redirect response Location header URL if the port
+ * number is equal to the default port for the service (like 80 for http).
+ *
+ * This is a RUN_FIRST hook: the first handler to return a non-zero
+ * value is the last one executed. The http_core module inserts a
+ * fallback handler (with APR_HOOK_REALLY_LAST order specifier) that
+ * returns 80.
+ */
+static apr_port_t x_default_port(const request_rec *r)
+{
+ /*
+ * Log the call and exit.
+ */
+ trace_request(r, "x_default_port()");
+ return 0;
+}
+
+/*
+ * This routine is called just before the handler gets invoked. It allows
+ * a module to insert a previously defined filter into the filter chain.
+ *
+ * No filter has been defined by this module, so we just log the call
+ * and exit.
+ *
+ * This is a VOID hook: all defined handlers get called.
+ */
+static void x_insert_filter(request_rec *r)
+{
+ /*
+ * Log the call and exit.
+ */
+ trace_request(r, "x_insert_filter()");
+}
+
+/*
+ * This routine is called to insert a previously defined error filter into
+ * the filter chain as the request is being processed.
+ *
+ * For the purpose of this example, we don't have a filter to insert,
+ * so just add to the trace and exit.
+ *
+ * This is a VOID hook: all defined handlers get called.
+ */
+static void x_insert_error_filter(request_rec *r)
+{
+ trace_request(r, "x_insert_error_filter()");
+}
+
+/*--------------------------------------------------------------------------*/
+/* */
+/* Now we declare our content handlers, which are invoked when the server */
+/* encounters a document which our module is supposed to have a chance to */
+/* see. (See mod_mime's SetHandler and AddHandler directives, and the */
+/* mod_info and mod_status examples, for more details.) */
+/* */
+/* Since content handlers are dumping data directly into the connection */
+/* (using the r*() routines, such as rputs() and rprintf()) without */
+/* intervention by other parts of the server, they need to make */
+/* sure any accumulated HTTP headers are sent first. This is done by */
+/* calling send_http_header(). Otherwise, no header will be sent at all, */
+/* and the output sent to the client will actually be HTTP-uncompliant. */
+/*--------------------------------------------------------------------------*/
+/*
+ * Sample content handler. All this does is display the call list that has
+ * been built up so far.
+ *
+ * This routine gets called for every request, unless another handler earlier
+ * in the callback chain has already handled the request. It is up to us to
+ * test the request_rec->handler field and see whether we are meant to handle
+ * this request.
+ *
+ * The content handler gets to write directly to the client using calls like
+ * ap_rputs() and ap_rprintf()
+ *
+ * This is a RUN_FIRST hook.
+ */
+static int x_handler(request_rec *r)
+{
+ x_cfg *dcfg;
+ char *note;
+ void *conn_data;
+ apr_status_t status;
+
+ dcfg = our_dconfig(r);
+ /*
+ * Add our trace to the log, and whether we get to write
+ * content for this request.
+ */
+ note = apr_pstrcat(r->pool, "x_handler(), handler is \"",
+ r->handler, "\"", NULL);
+ trace_request(r, note);
+
+ /* If it's not for us, get out as soon as possible. */
+ if (strcmp(r->handler, "example-hooks-handler")) {
+ return DECLINED;
+ }
+
+ /*
+ * Set the Content-type header. Note that we do not actually have to send
+ * the headers: this is done by the http core.
+ */
+ ap_set_content_type(r, "text/html");
+ /*
+ * If we're only supposed to send header information (HEAD request), we're
+ * already there.
+ */
+ if (r->header_only) {
+ return OK;
+ }
+
+ /*
+ * Now send our actual output. Since we tagged this as being
+ * "text/html", we need to embed any HTML.
+ */
+ ap_rputs(DOCTYPE_HTML_3_2, r);
+ ap_rputs("<HTML>\n", r);
+ ap_rputs(" <HEAD>\n", r);
+ ap_rputs(" <TITLE>mod_example_hooks Module Content-Handler Output\n", r);
+ ap_rputs(" </TITLE>\n", r);
+ ap_rputs(" </HEAD>\n", r);
+ ap_rputs(" <BODY>\n", r);
+ ap_rputs(" <H1><SAMP>mod_example_hooks</SAMP> Module Content-Handler Output\n", r);
+ ap_rputs(" </H1>\n", r);
+ ap_rputs(" <P>\n", r);
+ ap_rprintf(r, " Apache HTTP Server version: \"%s\"\n",
+ ap_get_server_banner());
+ ap_rputs(" <BR>\n", r);
+ ap_rprintf(r, " Server built: \"%s\"\n", ap_get_server_built());
+ ap_rputs(" </P>\n", r);
+ ap_rputs(" <P>\n", r);
+ ap_rputs(" The format for the callback trace is:\n", r);
+ ap_rputs(" </P>\n", r);
+ ap_rputs(" <DL>\n", r);
+ ap_rputs(" <DT><EM>n</EM>.<SAMP>&lt;routine-name&gt;", r);
+ ap_rputs("(&lt;routine-data&gt;)</SAMP>\n", r);
+ ap_rputs(" </DT>\n", r);
+ ap_rputs(" <DD><SAMP>[&lt;applies-to&gt;]</SAMP>\n", r);
+ ap_rputs(" </DD>\n", r);
+ ap_rputs(" </DL>\n", r);
+ ap_rputs(" <P>\n", r);
+ ap_rputs(" The <SAMP>&lt;routine-data&gt;</SAMP> is supplied by\n", r);
+ ap_rputs(" the routine when it requests the trace,\n", r);
+ ap_rputs(" and the <SAMP>&lt;applies-to&gt;</SAMP> is extracted\n", r);
+ ap_rputs(" from the configuration record at the time of the trace.\n", r);
+ ap_rputs(" <STRONG>SVR()</STRONG> indicates a server environment\n", r);
+ ap_rputs(" (blank means the main or default server, otherwise it's\n", r);
+ ap_rputs(" the name of the VirtualHost); <STRONG>DIR()</STRONG>\n", r);
+ ap_rputs(" indicates a location in the URL or filesystem\n", r);
+ ap_rputs(" namespace.\n", r);
+ ap_rputs(" </P>\n", r);
+ ap_rprintf(r, " <H2>Startup callbacks so far:</H2>\n <OL>\n%s </OL>\n",
+ trace);
+ ap_rputs(" <H2>Connection-specific callbacks so far:</H2>\n", r);
+
+ status = apr_pool_userdata_get(&conn_data, CONN_NOTE,
+ r->connection->pool);
+ if ((status == APR_SUCCESS) && conn_data) {
+ ap_rprintf(r, " <OL>\n%s </OL>\n", (char *) conn_data);
+ }
+ else {
+ ap_rputs(" <P>No connection-specific callback information was "
+ "retrieved.</P>\n", r);
+ }
+
+ ap_rputs(" <H2>Request-specific callbacks so far:</H2>\n", r);
+ ap_rprintf(r, " <OL>\n%s </OL>\n", apr_table_get(r->notes, TRACE_NOTE));
+ ap_rputs(" <H2>Environment for <EM>this</EM> call:</H2>\n", r);
+ ap_rputs(" <UL>\n", r);
+ ap_rprintf(r, " <LI>Applies-to: <SAMP>%s</SAMP>\n </LI>\n", dcfg->loc);
+ ap_rprintf(r, " <LI>\"Example\" directive declared here: %s\n </LI>\n",
+ (dcfg->local ? "YES" : "NO"));
+ ap_rprintf(r, " <LI>\"Example\" inherited: %s\n </LI>\n",
+ (dcfg->congenital ? "YES" : "NO"));
+ ap_rputs(" </UL>\n", r);
+ ap_rputs(" </BODY>\n", r);
+ ap_rputs("</HTML>\n", r);
+ /*
+ * We're all done, so cancel the timeout we set. Since this is probably
+ * the end of the request we *could* assume this would be done during
+ * post-processing - but it's possible that another handler might be
+ * called and inherit our outstanding timer. Not good; to each its own.
+ */
+ /*
+ * We did what we wanted to do, so tell the rest of the server we
+ * succeeded.
+ */
+ return OK;
+}
+
+/*
+ * The quick_handler hook presents modules with a very powerful opportunity to
+ * serve their content in a very early request phase. Note that this handler
+ * can not serve any requests from the file system because hooks like
+ * map_to_storage have not run. The quick_handler hook also runs before any
+ * authentication and access control.
+ *
+ * This hook is used by mod_cache to serve cached content.
+ *
+ * This is a RUN_FIRST hook. Return OK if you have served the request,
+ * DECLINED if you want processing to continue, or a HTTP_* error code to stop
+ * processing the request.
+ */
+static int x_quick_handler(request_rec *r, int lookup_uri)
+{
+ /*
+ * Log the call and exit.
+ */
+ trace_request(r, "x_quick_handler()");
+ return DECLINED;
+}
+
+/*
+ * This routine is called just after the server accepts the connection,
+ * but before it is handed off to a protocol module to be served. The point
+ * of this hook is to allow modules an opportunity to modify the connection
+ * as soon as possible. The core server uses this phase to setup the
+ * connection record based on the type of connection that is being used.
+ *
+ * This is a RUN_ALL hook.
+ */
+static int x_pre_connection(conn_rec *c, void *csd)
+{
+ char *note;
+
+ /*
+ * Log the call and exit.
+ */
+ note = apr_psprintf(c->pool, "x_pre_connection(c = %pp, p = %pp)",
+ (void*) c, (void*) c->pool);
+ trace_connection(c, note);
+
+ return OK;
+}
+
+/* This routine is used to actually process the connection that was received.
+ * Only protocol modules should implement this hook, as it gives them an
+ * opportunity to replace the standard HTTP processing with processing for
+ * some other protocol. Both echo and POP3 modules are available as
+ * examples.
+ *
+ * This is a RUN_FIRST hook.
+ */
+static int x_process_connection(conn_rec *c)
+{
+ trace_connection(c, "x_process_connection()");
+ return DECLINED;
+}
+
+/*
+ * This routine is called after the request has been read but before any other
+ * phases have been processed. This allows us to make decisions based upon
+ * the input header fields.
+ *
+ * This is a HOOK_VOID hook.
+ */
+static void x_pre_read_request(request_rec *r, conn_rec *c)
+{
+ /*
+ * We don't actually *do* anything here, except note the fact that we were
+ * called.
+ */
+ trace_request(r, "x_pre_read_request()");
+}
+
+/*
+ * This routine is called after the request has been read but before any other
+ * phases have been processed. This allows us to make decisions based upon
+ * the input header fields.
+ *
+ * This is a RUN_ALL hook.
+ */
+static int x_post_read_request(request_rec *r)
+{
+ /*
+ * We don't actually *do* anything here, except note the fact that we were
+ * called.
+ */
+ trace_request(r, "x_post_read_request()");
+ return DECLINED;
+}
+
+/*
+ * This routine gives our module an opportunity to translate the URI into an
+ * actual filename, before URL decoding happens.
+ *
+ * This is a RUN_FIRST hook.
+ */
+static int x_pre_translate_name(request_rec *r)
+{
+ /*
+ * We don't actually *do* anything here, except note the fact that we were
+ * called.
+ */
+ trace_request(r, "x_pre_translate_name()");
+ return DECLINED;
+}
+
+/*
+ * This routine gives our module an opportunity to translate the URI into an
+ * actual filename. If we don't do anything special, the server's default
+ * rules (Alias directives and the like) will continue to be followed.
+ *
+ * This is a RUN_FIRST hook.
+ */
+static int x_translate_name(request_rec *r)
+{
+ /*
+ * We don't actually *do* anything here, except note the fact that we were
+ * called.
+ */
+ trace_request(r, "x_translate_name()");
+ return DECLINED;
+}
+
+/*
+ * This routine maps r->filename to a physical file on disk. Useful for
+ * overriding default core behavior, including skipping mapping for
+ * requests that are not file based.
+ *
+ * This is a RUN_FIRST hook.
+ */
+static int x_map_to_storage(request_rec *r)
+{
+ /*
+ * We don't actually *do* anything here, except note the fact that we were
+ * called.
+ */
+ trace_request(r, "x_map_to_storage()");
+ return DECLINED;
+}
+
+/*
+ * this routine gives our module another chance to examine the request
+ * headers and to take special action. This is the first phase whose
+ * hooks' configuration directives can appear inside the <Directory>
+ * and similar sections, because at this stage the URI has been mapped
+ * to the filename. For example this phase can be used to block evil
+ * clients, while little resources were wasted on these.
+ *
+ * This is a RUN_ALL hook.
+ */
+static int x_header_parser(request_rec *r)
+{
+ /*
+ * We don't actually *do* anything here, except note the fact that we were
+ * called.
+ */
+ trace_request(r, "x_header_parser()");
+ return DECLINED;
+}
+
+
+/*
+ * This routine is called to check for any module-specific restrictions placed
+ * upon the requested resource. (See the mod_access_compat module for an
+ * example.)
+ *
+ * This is a RUN_ALL hook. The first handler to return a status other than OK
+ * or DECLINED (for instance, HTTP_FORBIDDEN) aborts the callback chain.
+ */
+static int x_check_access(request_rec *r)
+{
+ trace_request(r, "x_check_access()");
+ return DECLINED;
+}
+
+/*
+ * This routine is called to check the authentication information sent with
+ * the request (such as looking up the user in a database and verifying that
+ * the [encrypted] password sent matches the one in the database).
+ *
+ * This is a RUN_FIRST hook. The return value is OK, DECLINED, or some
+ * HTTP_mumble error (typically HTTP_UNAUTHORIZED).
+ */
+static int x_check_authn(request_rec *r)
+{
+ /*
+ * Don't do anything except log the call.
+ */
+ trace_request(r, "x_check_authn()");
+ return DECLINED;
+}
+
+/*
+ * This routine is called to check to see if the resource being requested
+ * requires authorisation.
+ *
+ * This is a RUN_FIRST hook. The return value is OK, DECLINED, or
+ * HTTP_mumble. If we return OK, no other modules are called during this
+ * phase.
+ *
+ * If *all* modules return DECLINED, the request is aborted with a server
+ * error.
+ */
+static int x_check_authz(request_rec *r)
+{
+ /*
+ * Log the call and return OK, or access will be denied (even though we
+ * didn't actually do anything).
+ */
+ trace_request(r, "x_check_authz()");
+ return DECLINED;
+}
+
+/*
+ * This routine is called to determine and/or set the various document type
+ * information bits, like Content-type (via r->content_type), language, et
+ * cetera.
+ *
+ * This is a RUN_FIRST hook.
+ */
+static int x_type_checker(request_rec *r)
+{
+ /*
+ * Log the call, but don't do anything else - and report truthfully that
+ * we didn't do anything.
+ */
+ trace_request(r, "x_type_checker()");
+ return DECLINED;
+}
+
+/*
+ * This routine is called to perform any module-specific fixing of header
+ * fields, et cetera. It is invoked just before any content-handler.
+ *
+ * This is a RUN_ALL HOOK.
+ */
+static int x_fixups(request_rec *r)
+{
+ /*
+ * Log the call and exit.
+ */
+ trace_request(r, "x_fixups()");
+ return DECLINED;
+}
+
+/*
+ * This routine is called to perform any module-specific logging activities
+ * over and above the normal server things.
+ *
+ * This is a RUN_ALL hook.
+ */
+static int x_log_transaction(request_rec *r)
+{
+ trace_request(r, "x_log_transaction()");
+ return DECLINED;
+}
+
+#ifdef HAVE_UNIX_SUEXEC
+
+/*
+ * This routine is called to find out under which user id to run suexec
+ * Unless our module runs CGI programs, there is no reason for us to
+ * mess with this information.
+ *
+ * This is a RUN_FIRST hook. The return value is a pointer to an
+ * ap_unix_identity_t or NULL.
+ */
+static ap_unix_identity_t *x_get_suexec_identity(const request_rec *r)
+{
+ trace_request(r, "x_get_suexec_identity()");
+ return NULL;
+}
+#endif
+
+/*
+ * This routine is called to create a connection. This hook is implemented
+ * by the Apache core: there is no known reason a module should override
+ * it.
+ *
+ * This is a RUN_FIRST hook.
+ *
+ * Return NULL to decline, a valid conn_rec pointer to accept.
+ */
+static conn_rec *x_create_connection(apr_pool_t *p, server_rec *server,
+ apr_socket_t *csd, long conn_id,
+ void *sbh, apr_bucket_alloc_t *alloc)
+{
+ trace_nocontext(p, __FILE__, __LINE__, "x_create_connection()");
+ return NULL;
+}
+
+/*
+ * This hook is defined in server/core.c, but it is not actually called
+ * or documented.
+ *
+ * This is a RUN_ALL hook.
+ */
+static int x_get_mgmt_items(apr_pool_t *p, const char *val, apr_hash_t *ht)
+{
+ /* We have nothing to do here but trace the call, and no context
+ * in which to trace it.
+ */
+ trace_nocontext(p, __FILE__, __LINE__, "x_check_config()");
+ return DECLINED;
+}
+
+/*
+ * This routine gets called shortly after the request_rec structure
+ * is created. It provides the opportunity to manipulae the request
+ * at a very early stage.
+ *
+ * This is a RUN_ALL hook.
+ */
+static int x_create_request(request_rec *r)
+{
+ /*
+ * We have a request_rec, but it is not filled in enough to give
+ * us a usable configuration. So, add a trace without context.
+ */
+ trace_nocontext( r->pool, __FILE__, __LINE__, "x_create_request()");
+ return DECLINED;
+}
+
+/*
+ * This routine gets called during the startup of the MPM.
+ * No known existing module implements this hook.
+ *
+ * This is a RUN_ALL hook.
+ */
+static int x_pre_mpm(apr_pool_t *p, ap_scoreboard_e sb_type)
+{
+ trace_nocontext(p, __FILE__, __LINE__, "x_pre_mpm()");
+ return DECLINED;
+}
+
+/*
+ * This hook gets run periodically by a maintenance function inside
+ * the MPM. Its exact purpose is unknown and undocumented at this time.
+ *
+ * This is a RUN_ALL hook.
+ */
+static int x_monitor(apr_pool_t *p, server_rec *s)
+{
+ trace_nocontext(p, __FILE__, __LINE__, "x_monitor()");
+ return DECLINED;
+}
+
+/*--------------------------------------------------------------------------*/
+/* */
+/* Which functions are responsible for which hooks in the server. */
+/* */
+/*--------------------------------------------------------------------------*/
+/*
+ * Each function our module provides to handle a particular hook is
+ * specified here. The functions are registered using
+ * ap_hook_foo(name, predecessors, successors, position)
+ * where foo is the name of the hook.
+ *
+ * The args are as follows:
+ * name -> the name of the function to call.
+ * predecessors -> a list of modules whose calls to this hook must be
+ * invoked before this module.
+ * successors -> a list of modules whose calls to this hook must be
+ * invoked after this module.
+ * position -> The relative position of this module. One of
+ * APR_HOOK_FIRST, APR_HOOK_MIDDLE, or APR_HOOK_LAST.
+ * Most modules will use APR_HOOK_MIDDLE. If multiple
+ * modules use the same relative position, Apache will
+ * determine which to call first.
+ * If your module relies on another module to run first,
+ * or another module running after yours, use the
+ * predecessors and/or successors.
+ *
+ * The number in brackets indicates the order in which the routine is called
+ * during request processing. Note that not all routines are necessarily
+ * called (such as if a resource doesn't have access restrictions).
+ * The actual delivery of content to the browser [9] is not handled by
+ * a hook; see the handler declarations below.
+ */
+static void x_register_hooks(apr_pool_t *p)
+{
+ trace = NULL;
+ ap_hook_pre_config(x_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_check_config(x_check_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_test_config(x_test_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_open_logs(x_open_logs, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_post_config(x_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_child_init(x_child_init, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_handler(x_handler, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_quick_handler(x_quick_handler, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_pre_connection(x_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_process_connection(x_process_connection, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_pre_read_request(x_pre_read_request, NULL, NULL,
+ APR_HOOK_MIDDLE);
+ /* [1] post read_request handling */
+ ap_hook_post_read_request(x_post_read_request, NULL, NULL,
+ APR_HOOK_MIDDLE);
+ ap_hook_log_transaction(x_log_transaction, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_http_scheme(x_http_scheme, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_default_port(x_default_port, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_pre_translate_name(x_pre_translate_name, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_translate_name(x_translate_name, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_map_to_storage(x_map_to_storage, NULL,NULL, APR_HOOK_MIDDLE);
+ ap_hook_header_parser(x_header_parser, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_fixups(x_fixups, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_type_checker(x_type_checker, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_check_access(x_check_access, NULL, NULL, APR_HOOK_MIDDLE,
+ AP_AUTH_INTERNAL_PER_CONF);
+ ap_hook_check_authn(x_check_authn, NULL, NULL, APR_HOOK_MIDDLE,
+ AP_AUTH_INTERNAL_PER_CONF);
+ ap_hook_check_authz(x_check_authz, NULL, NULL, APR_HOOK_MIDDLE,
+ AP_AUTH_INTERNAL_PER_CONF);
+ ap_hook_insert_filter(x_insert_filter, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_insert_error_filter(x_insert_error_filter, NULL, NULL, APR_HOOK_MIDDLE);
+#ifdef HAVE_UNIX_SUEXEC
+ ap_hook_get_suexec_identity(x_get_suexec_identity, NULL, NULL, APR_HOOK_MIDDLE);
+#endif
+ ap_hook_create_connection(x_create_connection, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_get_mgmt_items(x_get_mgmt_items, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_create_request(x_create_request, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_pre_mpm(x_pre_mpm, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_monitor(x_monitor, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+/*--------------------------------------------------------------------------*/
+/* */
+/* All of the routines have been declared now. Here's the list of */
+/* directives specific to our module, and information about where they */
+/* may appear and how the command parser should pass them to us for */
+/* processing. Note that care must be taken to ensure that there are NO */
+/* collisions of directive names between modules. */
+/* */
+/*--------------------------------------------------------------------------*/
+/*
+ * List of directives specific to our module.
+ */
+static const command_rec x_cmds[] =
+{
+ AP_INIT_NO_ARGS(
+ "Example", /* directive name */
+ cmd_example, /* config action routine */
+ NULL, /* argument to include in call */
+ OR_OPTIONS, /* where available */
+ "Example directive - no arguments" /* directive description */
+ ),
+ {NULL}
+};
+/*--------------------------------------------------------------------------*/
+/* */
+/* Finally, the list of callback routines and data structures that provide */
+/* the static hooks into our module from the other parts of the server. */
+/* */
+/*--------------------------------------------------------------------------*/
+/*
+ * Module definition for configuration. If a particular callback is not
+ * needed, replace its routine name below with the word NULL.
+ */
+AP_DECLARE_MODULE(example_hooks) =
+{
+ STANDARD20_MODULE_STUFF,
+ x_create_dir_config, /* per-directory config creator */
+ x_merge_dir_config, /* dir config merger */
+ x_create_server_config, /* server config creator */
+ x_merge_server_config, /* server config merger */
+ x_cmds, /* command table */
+ x_register_hooks, /* set up other request processing hooks */
+};
diff --git a/modules/examples/mod_example_hooks.dep b/modules/examples/mod_example_hooks.dep
new file mode 100644
index 0000000..a186c4b
--- /dev/null
+++ b/modules/examples/mod_example_hooks.dep
@@ -0,0 +1,62 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_example_hooks.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_example_hooks.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_mpm.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mpm_common.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/examples/mod_example_hooks.dsp b/modules/examples/mod_example_hooks.dsp
new file mode 100644
index 0000000..abf2fc4
--- /dev/null
+++ b/modules/examples/mod_example_hooks.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_example_hooks" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_example_hooks - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_example_hooks.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_example_hooks.mak" CFG="mod_example_hooks - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_example_hooks - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_example_hooks - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_example_hooks - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_example_hooks_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_example_hooks.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_example_hooks.so" /d LONG_NAME="example_hooks_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_example_hooks.so" /base:@..\..\os\win32\BaseAddr.ref,mod_example_hooks.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_example_hooks.so" /base:@..\..\os\win32\BaseAddr.ref,mod_example_hooks.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_example_hooks.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_example_hooks - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_example_hooks_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_example_hooks.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_example_hooks.so" /d LONG_NAME="example_hooks_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_example_hooks.so" /base:@..\..\os\win32\BaseAddr.ref,mod_example_hooks.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_example_hooks.so" /base:@..\..\os\win32\BaseAddr.ref,mod_example_hooks.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_example_hooks.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_example_hooks - Win32 Release"
+# Name "mod_example_hooks - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_example_hooks.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/examples/mod_example_hooks.mak b/modules/examples/mod_example_hooks.mak
new file mode 100644
index 0000000..2c1e562
--- /dev/null
+++ b/modules/examples/mod_example_hooks.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_example_hooks.dsp
+!IF "$(CFG)" == ""
+CFG=mod_example_hooks - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_example_hooks - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_example_hooks - Win32 Release" && "$(CFG)" != "mod_example_hooks - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_example_hooks.mak" CFG="mod_example_hooks - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_example_hooks - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_example_hooks - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_example_hooks - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_example_hooks.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_example_hooks.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_example_hooks.obj"
+ -@erase "$(INTDIR)\mod_example_hooks.res"
+ -@erase "$(INTDIR)\mod_example_hooks_src.idb"
+ -@erase "$(INTDIR)\mod_example_hooks_src.pdb"
+ -@erase "$(OUTDIR)\mod_example_hooks.exp"
+ -@erase "$(OUTDIR)\mod_example_hooks.lib"
+ -@erase "$(OUTDIR)\mod_example_hooks.pdb"
+ -@erase "$(OUTDIR)\mod_example_hooks.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_example_hooks_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_example_hooks.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_example_hooks.so" /d LONG_NAME="example_hooks_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_example_hooks.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_example_hooks.pdb" /debug /out:"$(OUTDIR)\mod_example_hooks.so" /implib:"$(OUTDIR)\mod_example_hooks.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_example_hooks.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_example_hooks.obj" \
+ "$(INTDIR)\mod_example_hooks.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_example_hooks.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_example_hooks.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_example_hooks.so"
+ if exist .\Release\mod_example_hooks.so.manifest mt.exe -manifest .\Release\mod_example_hooks.so.manifest -outputresource:.\Release\mod_example_hooks.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_example_hooks - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_example_hooks.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_example_hooks.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_example_hooks.obj"
+ -@erase "$(INTDIR)\mod_example_hooks.res"
+ -@erase "$(INTDIR)\mod_example_hooks_src.idb"
+ -@erase "$(INTDIR)\mod_example_hooks_src.pdb"
+ -@erase "$(OUTDIR)\mod_example_hooks.exp"
+ -@erase "$(OUTDIR)\mod_example_hooks.lib"
+ -@erase "$(OUTDIR)\mod_example_hooks.pdb"
+ -@erase "$(OUTDIR)\mod_example_hooks.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_example_hooks_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_example_hooks.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_example_hooks.so" /d LONG_NAME="example_hooks_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_example_hooks.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_example_hooks.pdb" /debug /out:"$(OUTDIR)\mod_example_hooks.so" /implib:"$(OUTDIR)\mod_example_hooks.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_example_hooks.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_example_hooks.obj" \
+ "$(INTDIR)\mod_example_hooks.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_example_hooks.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_example_hooks.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_example_hooks.so"
+ if exist .\Debug\mod_example_hooks.so.manifest mt.exe -manifest .\Debug\mod_example_hooks.so.manifest -outputresource:.\Debug\mod_example_hooks.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_example_hooks.dep")
+!INCLUDE "mod_example_hooks.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_example_hooks.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_example_hooks - Win32 Release" || "$(CFG)" == "mod_example_hooks - Win32 Debug"
+
+!IF "$(CFG)" == "mod_example_hooks - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\examples"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\examples"
+
+!ELSEIF "$(CFG)" == "mod_example_hooks - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\examples"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\examples"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_example_hooks - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\examples"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\examples"
+
+!ELSEIF "$(CFG)" == "mod_example_hooks - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\examples"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\examples"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_example_hooks - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\examples"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\examples"
+
+!ELSEIF "$(CFG)" == "mod_example_hooks - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\examples"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\examples"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_example_hooks - Win32 Release"
+
+
+"$(INTDIR)\mod_example_hooks.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_example_hooks.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_example_hooks.so" /d LONG_NAME="example_hooks_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_example_hooks - Win32 Debug"
+
+
+"$(INTDIR)\mod_example_hooks.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_example_hooks.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_example_hooks.so" /d LONG_NAME="example_hooks_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_example_hooks.c
+
+"$(INTDIR)\mod_example_hooks.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/examples/mod_example_ipc.c b/modules/examples/mod_example_ipc.c
new file mode 100644
index 0000000..6f48a84
--- /dev/null
+++ b/modules/examples/mod_example_ipc.c
@@ -0,0 +1,356 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * mod_example_ipc -- Apache sample module
+ *
+ * This module illustrates the use in an Apache 2.x module of the Interprocess
+ * Communications routines that come with APR. It is example code, and not meant
+ * to be used in a production server.
+ *
+ * To play with this sample module first compile it into a DSO file and install
+ * it into Apache's modules directory by running:
+ *
+ * $ /path/to/apache2/bin/apxs -c -i mod_example_ipc.c
+ *
+ * Then activate it in Apache's httpd.conf file for instance for the URL
+ * /example_ipc in as follows:
+ *
+ * # httpd.conf
+ * LoadModule example_ipc_module modules/mod_example_ipc.so
+ * <Location /example_ipc>
+ * SetHandler example_ipc
+ * </Location>
+ *
+ * Then restart Apache via
+ *
+ * $ /path/to/apache2/bin/apachectl restart
+ *
+ * The module allocates a counter in shared memory, which is incremented by the
+ * request handler under a mutex. After installation, activate the handler by
+ * hitting the URL configured above with ab at various concurrency levels to see
+ * how mutex contention affects server performance.
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "util_mutex.h"
+#include "ap_config.h"
+
+#if APR_HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#define HTML_HEADER "<html>\n<head>\n<title>Mod_example_IPC Status Page " \
+ "</title>\n</head>\n<body>\n<h1>Mod_example_IPC Status</h1>\n"
+#define HTML_FOOTER "</body>\n</html>\n"
+
+/* Number of microseconds to camp out on the mutex */
+#define CAMPOUT 10
+/* Maximum number of times we camp out before giving up */
+#define MAXCAMP 10
+/* Number of microseconds the handler sits on the lock once acquired. */
+#define SLEEPYTIME 1000
+
+apr_shm_t *exipc_shm; /* Pointer to shared memory block */
+char *shmfilename; /* Shared memory file name, used on some systems */
+apr_global_mutex_t *exipc_mutex; /* Lock around shared memory segment access */
+static const char *exipc_mutex_type = "example-ipc-shm";
+
+/* Data structure for shared memory block */
+typedef struct exipc_data {
+ apr_uint64_t counter;
+ /* More fields if necessary */
+} exipc_data;
+
+/*
+ * Clean up the shared memory block. This function is registered as
+ * cleanup function for the configuration pool, which gets called
+ * on restarts. It assures that the new children will not talk to a stale
+ * shared memory segment.
+ */
+static apr_status_t shm_cleanup_wrapper(void *unused)
+{
+ if (exipc_shm)
+ return apr_shm_destroy(exipc_shm);
+ return OK;
+}
+
+/*
+ * This routine is called in the parent; we must register our
+ * mutex type before the config is processed so that users can
+ * adjust the mutex settings using the Mutex directive.
+ */
+
+static int exipc_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp)
+{
+ ap_mutex_register(pconf, exipc_mutex_type, NULL, APR_LOCK_DEFAULT, 0);
+ return OK;
+}
+
+/*
+ * This routine is called in the parent, so we'll set up the shared
+ * memory segment and mutex here.
+ */
+
+static int exipc_post_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ apr_status_t rs;
+ exipc_data *base;
+ const char *tempdir;
+
+
+ /*
+ * Do nothing if we are not creating the final configuration.
+ * The parent process gets initialized a couple of times as the
+ * server starts up, and we don't want to create any more mutexes
+ * and shared memory segments than we're actually going to use.
+ */
+ if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG)
+ return OK;
+
+ /*
+ * The shared memory allocation routines take a file name.
+ * Depending on system-specific implementation of these
+ * routines, that file may or may not actually be created. We'd
+ * like to store those files in the operating system's designated
+ * temporary directory, which APR can point us to.
+ */
+ rs = apr_temp_dir_get(&tempdir, pconf);
+ if (APR_SUCCESS != rs) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rs, s, APLOGNO(02992)
+ "Failed to find temporary directory");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* Create the shared memory segment */
+
+ /*
+ * Create a unique filename using our pid. This information is
+ * stashed in the global variable so the children inherit it.
+ */
+ shmfilename = apr_psprintf(pconf, "%s/httpd_shm.%ld", tempdir,
+ (long int)getpid());
+
+ /* Now create that segment */
+ rs = apr_shm_create(&exipc_shm, sizeof(exipc_data),
+ (const char *) shmfilename, pconf);
+ if (APR_SUCCESS != rs) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rs, s, APLOGNO(02993)
+ "Failed to create shared memory segment on file %s",
+ shmfilename);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* Created it, now let's zero it out */
+ base = (exipc_data *)apr_shm_baseaddr_get(exipc_shm);
+ base->counter = 0;
+
+ /* Create global mutex */
+
+ rs = ap_global_mutex_create(&exipc_mutex, NULL, exipc_mutex_type, NULL,
+ s, pconf, 0);
+ if (APR_SUCCESS != rs) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /*
+ * Destroy the shm segment when the configuration pool gets destroyed. This
+ * happens on server restarts. The parent will then (above) allocate a new
+ * shm segment that the new children will bind to.
+ */
+ apr_pool_cleanup_register(pconf, NULL, shm_cleanup_wrapper,
+ apr_pool_cleanup_null);
+ return OK;
+}
+
+/*
+ * This routine gets called when a child inits. We use it to attach
+ * to the shared memory segment, and reinitialize the mutex.
+ */
+
+static void exipc_child_init(apr_pool_t *p, server_rec *s)
+{
+ apr_status_t rs;
+
+ /*
+ * Re-open the mutex for the child. Note we're reusing
+ * the mutex pointer global here.
+ */
+ rs = apr_global_mutex_child_init(&exipc_mutex,
+ apr_global_mutex_lockfile(exipc_mutex),
+ p);
+ if (APR_SUCCESS != rs) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rs, s, APLOGNO(02994)
+ "Failed to reopen mutex %s in child",
+ exipc_mutex_type);
+ /* There's really nothing else we can do here, since This
+ * routine doesn't return a status. If this ever goes wrong,
+ * it will turn Apache into a fork bomb. Let's hope it never
+ * will.
+ */
+ exit(1); /* Ugly, but what else? */
+ }
+}
+
+/* The sample content handler */
+static int exipc_handler(request_rec *r)
+{
+ int gotlock = 0;
+ int camped;
+ apr_time_t startcamp;
+ apr_int64_t timecamped;
+ apr_status_t rs;
+ exipc_data *base;
+
+ if (strcmp(r->handler, "example_ipc")) {
+ return DECLINED;
+ }
+
+ /*
+ * The main function of the handler, aside from sending the
+ * status page to the client, is to increment the counter in
+ * the shared memory segment. This action needs to be mutexed
+ * out using the global mutex.
+ */
+
+ /*
+ * First, acquire the lock. This code is a lot more involved than
+ * it usually needs to be, because the process based trylock
+ * routine is not implemented on unix platforms. I left it in to
+ * show how it would work if trylock worked, and for situations
+ * and platforms where trylock works.
+ */
+ for (camped = 0, timecamped = 0; camped < MAXCAMP; camped++) {
+ rs = apr_global_mutex_trylock(exipc_mutex);
+ if (APR_STATUS_IS_EBUSY(rs)) {
+ apr_sleep(CAMPOUT);
+ }
+ else if (APR_SUCCESS == rs) {
+ gotlock = 1;
+ break; /* Get out of the loop */
+ }
+ else if (APR_STATUS_IS_ENOTIMPL(rs)) {
+ /* If it's not implemented, just hang in the mutex. */
+ startcamp = apr_time_now();
+ rs = apr_global_mutex_lock(exipc_mutex);
+ timecamped = (apr_int64_t) (apr_time_now() - startcamp);
+ if (APR_SUCCESS == rs) {
+ gotlock = 1;
+ break; /* Out of the loop */
+ }
+ else {
+ /* Some error, log and bail */
+ ap_log_error(APLOG_MARK, APLOG_ERR, rs, r->server, APLOGNO(02995)
+ "Child %ld failed to acquire lock",
+ (long int)getpid());
+ break; /* Out of the loop without having the lock */
+ }
+ }
+ else {
+ /* Some other error, log and bail */
+ ap_log_error(APLOG_MARK, APLOG_ERR, rs, r->server, APLOGNO(02996)
+ "Child %ld failed to try and acquire lock",
+ (long int)getpid());
+ break; /* Out of the loop without having the lock */
+ }
+
+ /*
+ * The only way to get to this point is if the trylock worked
+ * and returned BUSY. So, bump the time and try again
+ */
+ timecamped += CAMPOUT;
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, r->server, APLOGNO(03187)
+ "Child %ld camping out on mutex for %" APR_INT64_T_FMT
+ " microseconds",
+ (long int) getpid(), timecamped);
+ } /* Lock acquisition loop */
+
+ /* Sleep for a millisecond to make it a little harder for
+ * httpd children to acquire the lock.
+ */
+ apr_sleep(SLEEPYTIME);
+
+ r->content_type = "text/html";
+
+ if (!r->header_only) {
+ ap_rputs(HTML_HEADER, r);
+ if (gotlock) {
+ /* Increment the counter */
+ base = (exipc_data *)apr_shm_baseaddr_get(exipc_shm);
+ base->counter++;
+ /* Send a page with our pid and the new value of the counter. */
+ ap_rprintf(r, "<p>Lock acquired after %ld microseoncds.</p>\n",
+ (long int) timecamped);
+ ap_rputs("<table border=\"1\">\n", r);
+ ap_rprintf(r, "<tr><td>Child pid:</td><td>%d</td></tr>\n",
+ (int) getpid());
+ ap_rprintf(r, "<tr><td>Counter:</td><td>%u</td></tr>\n",
+ (unsigned int)base->counter);
+ ap_rputs("</table>\n", r);
+ }
+ else {
+ /*
+ * Send a page saying that we couldn't get the lock. Don't say
+ * what the counter is, because without the lock the value could
+ * race.
+ */
+ ap_rprintf(r, "<p>Child %d failed to acquire lock "
+ "after camping out for %d microseconds.</p>\n",
+ (int) getpid(), (int) timecamped);
+ }
+ ap_rputs(HTML_FOOTER, r);
+ } /* r->header_only */
+
+ /* Release the lock */
+ if (gotlock)
+ rs = apr_global_mutex_unlock(exipc_mutex);
+ /* Swallowing the result because what are we going to do with it at
+ * this stage?
+ */
+
+ return OK;
+}
+
+static void exipc_register_hooks(apr_pool_t *p)
+{
+ ap_hook_pre_config(exipc_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_post_config(exipc_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_child_init(exipc_child_init, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_handler(exipc_handler, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+/* Dispatch list for API hooks */
+AP_DECLARE_MODULE(example_ipc) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-dir config structures */
+ NULL, /* merge per-dir config structures */
+ NULL, /* create per-server config structures */
+ NULL, /* merge per-server config structures */
+ NULL, /* table of config file commands */
+ exipc_register_hooks /* register hooks */
+};
diff --git a/modules/examples/mod_example_ipc.dep b/modules/examples/mod_example_ipc.dep
new file mode 100644
index 0000000..9859b81
--- /dev/null
+++ b/modules/examples/mod_example_ipc.dep
@@ -0,0 +1,56 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_example_ipc.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_example_ipc.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/examples/mod_example_ipc.dsp b/modules/examples/mod_example_ipc.dsp
new file mode 100644
index 0000000..278fcce
--- /dev/null
+++ b/modules/examples/mod_example_ipc.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_example_ipc" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_example_ipc - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_example_ipc.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_example_ipc.mak" CFG="mod_example_ipc - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_example_ipc - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_example_ipc - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_example_ipc - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_example_ipc_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_example_ipc.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_example_ipc.so" /d LONG_NAME="example_ipc_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_example_ipc.so" /base:@..\..\os\win32\BaseAddr.ref,mod_example_ipc.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_example_ipc.so" /base:@..\..\os\win32\BaseAddr.ref,mod_example_ipc.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_example_ipc.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_example_ipc - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_example_ipc_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_example_ipc.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_example_ipc.so" /d LONG_NAME="example_ipc_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_example_ipc.so" /base:@..\..\os\win32\BaseAddr.ref,mod_example_ipc.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_example_ipc.so" /base:@..\..\os\win32\BaseAddr.ref,mod_example_ipc.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_example_ipc.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_example_ipc - Win32 Release"
+# Name "mod_example_ipc - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_example_ipc.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/examples/mod_example_ipc.mak b/modules/examples/mod_example_ipc.mak
new file mode 100644
index 0000000..218924e
--- /dev/null
+++ b/modules/examples/mod_example_ipc.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_example_ipc.dsp
+!IF "$(CFG)" == ""
+CFG=mod_example_ipc - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_example_ipc - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_example_ipc - Win32 Release" && "$(CFG)" != "mod_example_ipc - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_example_ipc.mak" CFG="mod_example_ipc - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_example_ipc - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_example_ipc - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_example_ipc - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_example_ipc.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_example_ipc.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_example_ipc.obj"
+ -@erase "$(INTDIR)\mod_example_ipc.res"
+ -@erase "$(INTDIR)\mod_example_ipc_src.idb"
+ -@erase "$(INTDIR)\mod_example_ipc_src.pdb"
+ -@erase "$(OUTDIR)\mod_example_ipc.exp"
+ -@erase "$(OUTDIR)\mod_example_ipc.lib"
+ -@erase "$(OUTDIR)\mod_example_ipc.pdb"
+ -@erase "$(OUTDIR)\mod_example_ipc.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_example_ipc_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_example_ipc.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_example_ipc.so" /d LONG_NAME="example_ipc_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_example_ipc.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_example_ipc.pdb" /debug /out:"$(OUTDIR)\mod_example_ipc.so" /implib:"$(OUTDIR)\mod_example_ipc.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_example_ipc.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_example_ipc.obj" \
+ "$(INTDIR)\mod_example_ipc.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_example_ipc.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_example_ipc.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_example_ipc.so"
+ if exist .\Release\mod_example_ipc.so.manifest mt.exe -manifest .\Release\mod_example_ipc.so.manifest -outputresource:.\Release\mod_example_ipc.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_example_ipc - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_example_ipc.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_example_ipc.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_example_ipc.obj"
+ -@erase "$(INTDIR)\mod_example_ipc.res"
+ -@erase "$(INTDIR)\mod_example_ipc_src.idb"
+ -@erase "$(INTDIR)\mod_example_ipc_src.pdb"
+ -@erase "$(OUTDIR)\mod_example_ipc.exp"
+ -@erase "$(OUTDIR)\mod_example_ipc.lib"
+ -@erase "$(OUTDIR)\mod_example_ipc.pdb"
+ -@erase "$(OUTDIR)\mod_example_ipc.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_example_ipc_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_example_ipc.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_example_ipc.so" /d LONG_NAME="example_ipc_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_example_ipc.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_example_ipc.pdb" /debug /out:"$(OUTDIR)\mod_example_ipc.so" /implib:"$(OUTDIR)\mod_example_ipc.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_example_ipc.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_example_ipc.obj" \
+ "$(INTDIR)\mod_example_ipc.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_example_ipc.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_example_ipc.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_example_ipc.so"
+ if exist .\Debug\mod_example_ipc.so.manifest mt.exe -manifest .\Debug\mod_example_ipc.so.manifest -outputresource:.\Debug\mod_example_ipc.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_example_ipc.dep")
+!INCLUDE "mod_example_ipc.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_example_ipc.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_example_ipc - Win32 Release" || "$(CFG)" == "mod_example_ipc - Win32 Debug"
+
+!IF "$(CFG)" == "mod_example_ipc - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\examples"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\examples"
+
+!ELSEIF "$(CFG)" == "mod_example_ipc - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\examples"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\examples"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_example_ipc - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\examples"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\examples"
+
+!ELSEIF "$(CFG)" == "mod_example_ipc - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\examples"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\examples"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_example_ipc - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\examples"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\examples"
+
+!ELSEIF "$(CFG)" == "mod_example_ipc - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\examples"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\examples"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_example_ipc - Win32 Release"
+
+
+"$(INTDIR)\mod_example_ipc.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_example_ipc.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_example_ipc.so" /d LONG_NAME="example_ipc_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_example_ipc - Win32 Debug"
+
+
+"$(INTDIR)\mod_example_ipc.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_example_ipc.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_example_ipc.so" /d LONG_NAME="example_ipc_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_example_ipc.c
+
+"$(INTDIR)\mod_example_ipc.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/experimental/.indent.pro b/modules/experimental/.indent.pro
new file mode 100644
index 0000000..a9fbe9f
--- /dev/null
+++ b/modules/experimental/.indent.pro
@@ -0,0 +1,54 @@
+-i4 -npsl -di0 -br -nce -d0 -cli0 -npcs -nfc1
+-TBUFF
+-TFILE
+-TTRANS
+-TUINT4
+-T_trans
+-Tallow_options_t
+-Tapache_sfio
+-Tarray_header
+-Tbool_int
+-Tbuf_area
+-Tbuff_struct
+-Tbuffy
+-Tcmd_how
+-Tcmd_parms
+-Tcommand_rec
+-Tcommand_struct
+-Tconn_rec
+-Tcore_dir_config
+-Tcore_server_config
+-Tdir_maker_func
+-Tevent
+-Tglobals_s
+-Thandler_func
+-Thandler_rec
+-Tjoblist_s
+-Tlisten_rec
+-Tmerger_func
+-Tmode_t
+-Tmodule
+-Tmodule_struct
+-Tmutex
+-Tn_long
+-Tother_child_rec
+-Toverrides_t
+-Tparent_score
+-Tpid_t
+-Tpiped_log
+-Tpool
+-Trequest_rec
+-Trequire_line
+-Trlim_t
+-Tscoreboard
+-Tsemaphore
+-Tserver_addr_rec
+-Tserver_rec
+-Tserver_rec_chain
+-Tshort_score
+-Ttable
+-Ttable_entry
+-Tthread
+-Tu_wide_int
+-Tvtime_t
+-Twide_int
diff --git a/modules/experimental/Makefile.in b/modules/experimental/Makefile.in
new file mode 100644
index 0000000..7c5c149
--- /dev/null
+++ b/modules/experimental/Makefile.in
@@ -0,0 +1,3 @@
+# a modules Makefile has no explicit targets -- they will be defined by
+# whatever modules are enabled. just grab special.mk to deal with this.
+include $(top_srcdir)/build/special.mk
diff --git a/modules/experimental/NWGNUmakefile b/modules/experimental/NWGNUmakefile
new file mode 100644
index 0000000..fb5be35
--- /dev/null
+++ b/modules/experimental/NWGNUmakefile
@@ -0,0 +1,253 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME =
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION =
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME =
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE =
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+# If there is only one element to build it needs to be included twice
+# in the below target list.
+# Normally if there is only one element to be built within a
+# directory, the makefile for the single element would be called NWGNUmakefile.
+# But if there are multiples, the parent NWGNUmakefile must reference more
+# than one submakefile. Because the experimental directory might vary in the
+# number of submakefiles, but for the moment only contains one, we reference
+# it twice to allow it parent NWGNUmakefile to work properly. If another
+# submakefile is added, the extra reference to the first NLM should be removed.
+TARGET_nlm = \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/experimental/config.m4 b/modules/experimental/config.m4
new file mode 100644
index 0000000..6deaf65
--- /dev/null
+++ b/modules/experimental/config.m4
@@ -0,0 +1,4 @@
+
+APACHE_MODPATH_INIT(experimental)
+
+APACHE_MODPATH_FINISH
diff --git a/modules/filters/.indent.pro b/modules/filters/.indent.pro
new file mode 100644
index 0000000..a9fbe9f
--- /dev/null
+++ b/modules/filters/.indent.pro
@@ -0,0 +1,54 @@
+-i4 -npsl -di0 -br -nce -d0 -cli0 -npcs -nfc1
+-TBUFF
+-TFILE
+-TTRANS
+-TUINT4
+-T_trans
+-Tallow_options_t
+-Tapache_sfio
+-Tarray_header
+-Tbool_int
+-Tbuf_area
+-Tbuff_struct
+-Tbuffy
+-Tcmd_how
+-Tcmd_parms
+-Tcommand_rec
+-Tcommand_struct
+-Tconn_rec
+-Tcore_dir_config
+-Tcore_server_config
+-Tdir_maker_func
+-Tevent
+-Tglobals_s
+-Thandler_func
+-Thandler_rec
+-Tjoblist_s
+-Tlisten_rec
+-Tmerger_func
+-Tmode_t
+-Tmodule
+-Tmodule_struct
+-Tmutex
+-Tn_long
+-Tother_child_rec
+-Toverrides_t
+-Tparent_score
+-Tpid_t
+-Tpiped_log
+-Tpool
+-Trequest_rec
+-Trequire_line
+-Trlim_t
+-Tscoreboard
+-Tsemaphore
+-Tserver_addr_rec
+-Tserver_rec
+-Tserver_rec_chain
+-Tshort_score
+-Ttable
+-Ttable_entry
+-Tthread
+-Tu_wide_int
+-Tvtime_t
+-Twide_int
diff --git a/modules/filters/Makefile.in b/modules/filters/Makefile.in
new file mode 100644
index 0000000..167b343
--- /dev/null
+++ b/modules/filters/Makefile.in
@@ -0,0 +1,3 @@
+
+include $(top_srcdir)/build/special.mk
+
diff --git a/modules/filters/NWGNUcharsetl b/modules/filters/NWGNUcharsetl
new file mode 100644
index 0000000..9932635
--- /dev/null
+++ b/modules/filters/NWGNUcharsetl
@@ -0,0 +1,257 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ -DAP_WANT_DIR_TRANSLATION \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = charsetl
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Charset Lite Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = charsetl
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/charsetl.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_charset_lite.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ charset_lite_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/filters/NWGNUdeflate b/modules/filters/NWGNUdeflate
new file mode 100644
index 0000000..7f7204d
--- /dev/null
+++ b/modules/filters/NWGNUdeflate
@@ -0,0 +1,279 @@
+#
+# The MOD_DEFLATE module requires the ZLib source which
+# can be downloaded from http://www.gzip.org/zlib/
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(STDMOD)/ssl \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = deflate
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Deflate Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Deflate Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/deflate.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_deflate.o \
+ $(OBJDIR)/adler32.o \
+ $(OBJDIR)/crc32.o \
+ $(OBJDIR)/deflate.o \
+ $(OBJDIR)/inflate.o \
+ $(OBJDIR)/inffast.o \
+ $(OBJDIR)/inftrees.o \
+ $(OBJDIR)/trees.o \
+ $(OBJDIR)/zutil.o \
+ $(EOLIST)
+
+ifeq "$(wildcard $(ZLIBSDK)/infblock.c)" "$(ZLIBSDK)/infblock.c"
+FILES_nlm_objs += \
+ $(OBJDIR)/infblock.o \
+ $(OBJDIR)/infcodes.o \
+ $(OBJDIR)/infutil.o \
+ $(EOLIST)
+endif
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ deflate_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+#
+# Any specialized rules here
+#
+
+vpath %.c $(ZLIBSDK)
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/filters/NWGNUextfiltr b/modules/filters/NWGNUextfiltr
new file mode 100644
index 0000000..1fd57cb
--- /dev/null
+++ b/modules/filters/NWGNUextfiltr
@@ -0,0 +1,248 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = extfiltr
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) External Filter Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = ExtFilter Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/extfiltr.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_ext_filter.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ ext_filter_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/filters/NWGNUmakefile b/modules/filters/NWGNUmakefile
new file mode 100644
index 0000000..922abac
--- /dev/null
+++ b/modules/filters/NWGNUmakefile
@@ -0,0 +1,273 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME =
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION =
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME =
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE =
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/extfiltr.nlm \
+ $(OBJDIR)/charsetl.nlm \
+ $(OBJDIR)/mod_data.nlm \
+ $(OBJDIR)/mod_filter.nlm \
+ $(OBJDIR)/mod_request.nlm \
+ $(OBJDIR)/substitute.nlm \
+ $(OBJDIR)/modsed.nlm \
+ $(OBJDIR)/modbuffer.nlm \
+ $(OBJDIR)/ratelimit.nlm \
+ $(OBJDIR)/reqtimeout.nlm \
+ $(OBJDIR)/reflector.nlm \
+ $(EOLIST)
+
+# If the zlib library source exists then build the mod_deflate module
+ifneq "$(ZLIBSDK)" ""
+ifeq "$(wildcard $(ZLIBSDK))" "$(ZLIBSDK)"
+TARGET_nlm += $(OBJDIR)/deflate.nlm \
+ $(EOLIST)
+endif
+endif
+
+# If the libxml2 library source exists then build the mod_proxy_html module
+ifneq "$(LIBXML2SDK)" ""
+ifeq "$(wildcard $(LIBXML2SDK))" "$(LIBXML2SDK)"
+TARGET_nlm += \
+ $(OBJDIR)/proxyhtml.nlm \
+ $(OBJDIR)/xml2enc.nlm \
+ $(EOLIST)
+endif
+endif
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/filters/NWGNUmod_data b/modules/filters/NWGNUmod_data
new file mode 100644
index 0000000..7f46e9a
--- /dev/null
+++ b/modules/filters/NWGNUmod_data
@@ -0,0 +1,248 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = mod_data
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Data Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Filter Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/$(NLM_NAME).o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ data_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/filters/NWGNUmod_filter b/modules/filters/NWGNUmod_filter
new file mode 100644
index 0000000..bf323ee
--- /dev/null
+++ b/modules/filters/NWGNUmod_filter
@@ -0,0 +1,248 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = mod_filter
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Filter Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Filter Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/mod_filter.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_filter.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ filter_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/filters/NWGNUmod_request b/modules/filters/NWGNUmod_request
new file mode 100644
index 0000000..21f53cc
--- /dev/null
+++ b/modules/filters/NWGNUmod_request
@@ -0,0 +1,248 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = mod_request
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Request Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Request Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/mod_request.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_request.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ request_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/filters/NWGNUmodbuffer b/modules/filters/NWGNUmodbuffer
new file mode 100644
index 0000000..7652a35
--- /dev/null
+++ b/modules/filters/NWGNUmodbuffer
@@ -0,0 +1,256 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = modbuffer
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Buffer Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = modbuffer
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/modbuffer.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_buffer.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ buffer_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/filters/NWGNUmodsed b/modules/filters/NWGNUmodsed
new file mode 100644
index 0000000..158acb9
--- /dev/null
+++ b/modules/filters/NWGNUmodsed
@@ -0,0 +1,259 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = modsed
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) SED Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = modsed Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/modsed.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_sed.o \
+ $(OBJDIR)/regexp.o \
+ $(OBJDIR)/sed0.o \
+ $(OBJDIR)/sed1.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ sed_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/filters/NWGNUproxyhtml b/modules/filters/NWGNUproxyhtml
new file mode 100644
index 0000000..5337f3b
--- /dev/null
+++ b/modules/filters/NWGNUproxyhtml
@@ -0,0 +1,261 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(LIBXML2SDK)/include \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/modules/http \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ -L$(LIBXML2SDK)/lib -llibxml2.lib \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = proxyhtml
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Proxy HTML Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Proxy HTTP Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_proxy_html.o \
+ $(OBJDIR)/libprews.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ proxy \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+# Don't link with Winsock if standard sockets are being used
+ifndef USE_STDSOCKETS
+FILES_nlm_Ximports += @ws2nlm.imp \
+ $(EOLIST)
+endif
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ proxy_html_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+vpath %.c ../arch/netware
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/filters/NWGNUratelimit b/modules/filters/NWGNUratelimit
new file mode 100644
index 0000000..f75c814
--- /dev/null
+++ b/modules/filters/NWGNUratelimit
@@ -0,0 +1,256 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = ratelimit
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Rate Limit Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = ratelimit
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/ratelimit.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_ratelimit.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ ratelimit_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/filters/NWGNUreflector b/modules/filters/NWGNUreflector
new file mode 100644
index 0000000..67ca26d
--- /dev/null
+++ b/modules/filters/NWGNUreflector
@@ -0,0 +1,256 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = reflector
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Reflector Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = reflector
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/reflector.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_reflector.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ reflector_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/filters/NWGNUreqtimeout b/modules/filters/NWGNUreqtimeout
new file mode 100644
index 0000000..a99f82c
--- /dev/null
+++ b/modules/filters/NWGNUreqtimeout
@@ -0,0 +1,256 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = reqtimeout
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Request Timeout Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = $(NLM_NAME)
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_reqtimeout.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ reqtimeout_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/filters/NWGNUsubstitute b/modules/filters/NWGNUsubstitute
new file mode 100644
index 0000000..e5775f2
--- /dev/null
+++ b/modules/filters/NWGNUsubstitute
@@ -0,0 +1,256 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = substitute
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Substitute Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Substitute Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/substitute.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_substitute.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ substitute_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/filters/NWGNUxml2enc b/modules/filters/NWGNUxml2enc
new file mode 100644
index 0000000..117832e
--- /dev/null
+++ b/modules/filters/NWGNUxml2enc
@@ -0,0 +1,258 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(LIBXML2SDK)/include \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ -L$(LIBXML2SDK)/lib -llibxml2.lib \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = xml2enc
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) xml2enc Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Substitute Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_xml2enc.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ xml2enc_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/filters/config.m4 b/modules/filters/config.m4
new file mode 100644
index 0000000..810f0d7
--- /dev/null
+++ b/modules/filters/config.m4
@@ -0,0 +1,197 @@
+dnl modules enabled in this directory by default
+
+dnl APACHE_MODULE(name, helptext[, objects[, structname[, default[, config]]]])
+
+APACHE_MODPATH_INIT(filters)
+
+APACHE_MODULE(buffer, Filter Buffering, , , most)
+APACHE_MODULE(data, RFC2397 data encoder, , , )
+APACHE_MODULE(ratelimit, Output Bandwidth Limiting, , , most)
+APACHE_MODULE(reqtimeout, Limit time waiting for request from client, , , yes)
+APACHE_MODULE(ext_filter, external filter module, , , most)
+APACHE_MODULE(request, Request Body Filtering, , , most)
+APACHE_MODULE(include, Server Side Includes, , , most)
+APACHE_MODULE(filter, Smart Filtering, , , yes)
+APACHE_MODULE(reflector, Reflect request through the output filter stack, , , )
+APACHE_MODULE(substitute, response content rewrite-like filtering, , , most)
+
+sed_obj="mod_sed.lo sed0.lo sed1.lo regexp.lo"
+APACHE_MODULE(sed, filter request and/or response bodies through sed, $sed_obj, , most, [
+ if test "x$enable_sed" = "xshared"; then
+ # The only symbol which needs to be exported is the module
+ # structure, so ask libtool to hide libsed internals:
+ APR_ADDTO(MOD_SED_LDADD, [-export-symbols-regex sed_module])
+ fi
+])
+
+if test "$ac_cv_ebcdic" = "yes"; then
+# mod_charset_lite can be very useful on an ebcdic system,
+# so include it by default
+ APACHE_MODULE(charset_lite, character set translation. Enabled by default only on EBCDIC systems., , , yes)
+else
+ APACHE_MODULE(charset_lite, character set translation. Enabled by default only on EBCDIC systems., , , )
+fi
+
+
+APACHE_MODULE(deflate, Deflate transfer encoding support, , , most, [
+ AC_ARG_WITH(z, APACHE_HELP_STRING(--with-z=PATH,use a specific zlib library),
+ [
+ if test "x$withval" != "xyes" && test "x$withval" != "x"; then
+ ap_zlib_base="$withval"
+ ap_zlib_with="yes"
+ fi
+ ])
+ if test "x$ap_zlib_base" = "x"; then
+ AC_MSG_CHECKING([for zlib location])
+ AC_CACHE_VAL(ap_cv_zlib,[
+ for dir in /usr/local /usr ; do
+ if test -d $dir && test -f $dir/include/zlib.h; then
+ ap_cv_zlib=$dir
+ break
+ fi
+ done
+ ])
+ ap_zlib_base=$ap_cv_zlib
+ if test "x$ap_zlib_base" = "x"; then
+ enable_deflate=no
+ AC_MSG_RESULT([not found])
+ else
+ AC_MSG_RESULT([$ap_zlib_base])
+ fi
+ fi
+ if test "$enable_deflate" != "no"; then
+ ap_save_includes=$INCLUDES
+ ap_save_ldflags=$LDFLAGS
+ ap_save_cppflags=$CPPFLAGS
+ ap_zlib_ldflags=""
+ if test "$ap_zlib_base" != "/usr"; then
+ APR_ADDTO(INCLUDES, [-I${ap_zlib_base}/include])
+ APR_ADDTO(MOD_INCLUDES, [-I${ap_zlib_base}/include])
+ dnl put in CPPFLAGS temporarily so that AC_TRY_LINK below will work
+ CPPFLAGS="$CPPFLAGS $INCLUDES"
+ APR_ADDTO(LDFLAGS, [-L${ap_zlib_base}/lib])
+ APR_ADDTO(ap_zlib_ldflags, [-L${ap_zlib_base}/lib])
+ if test "x$ap_platform_runtime_link_flag" != "x"; then
+ APR_ADDTO(LDFLAGS, [$ap_platform_runtime_link_flag${ap_zlib_base}/lib])
+ APR_ADDTO(ap_zlib_ldflags, [$ap_platform_runtime_link_flag${ap_zlib_base}/lib])
+ fi
+ fi
+ APR_ADDTO(LIBS, [-lz])
+ AC_MSG_CHECKING([for zlib library])
+ AC_TRY_LINK([#include <zlib.h>], [int i = Z_OK;],
+ [AC_MSG_RESULT(found)
+ APR_ADDTO(MOD_DEFLATE_LDADD, [$ap_zlib_ldflags -lz])],
+ [AC_MSG_RESULT(not found)
+ enable_deflate=no
+ if test "x$ap_zlib_with" = "x"; then
+ AC_MSG_WARN([... Error, zlib was missing or unusable])
+ else
+ AC_MSG_ERROR([... Error, zlib was missing or unusable])
+ fi
+ ])
+ INCLUDES=$ap_save_includes
+ LDFLAGS=$ap_save_ldflags
+ CPPFLAGS=$ap_save_cppflags
+ APR_REMOVEFROM(LIBS, [-lz])
+ fi
+])
+
+AC_DEFUN([FIND_LIBXML2], [
+ AC_CACHE_CHECK([for libxml2], [ac_cv_libxml2], [
+ AC_ARG_WITH(libxml2,
+ [APACHE_HELP_STRING(--with-libxml2=PATH,location for libxml2)],
+ [test_paths="${with_libxml2}/include/libxml2 ${with_libxml2}/include ${with_libxml2}"],
+ [test_paths="/usr/include/libxml2 /usr/local/include/libxml2 /usr/include /usr/local/include"]
+ )
+ AC_MSG_CHECKING(for libxml2)
+ xml2_path=""
+ for x in ${test_paths}; do
+ if test -f "${x}/libxml/parser.h"; then
+ xml2_path="${x}"
+ break
+ fi
+ done
+ if test -n "${xml2_path}" ; then
+ ac_cv_libxml2=yes
+ XML2_INCLUDES="${xml2_path}"
+ else
+ ac_cv_libxml2=no
+ fi
+ ])
+])
+
+APACHE_MODULE(xml2enc, i18n support for markup filters, , , , [
+ FIND_LIBXML2
+ if test "$ac_cv_libxml2" = "yes" ; then
+ APR_ADDTO(MOD_CFLAGS, [-I${XML2_INCLUDES}])
+ APR_ADDTO(MOD_XML2ENC_LDADD, [-lxml2])
+ else
+ enable_xml2enc=no
+ fi
+])
+APACHE_MODULE(proxy_html, Fix HTML Links in a Reverse Proxy, , , , [
+ FIND_LIBXML2
+ if test "$ac_cv_libxml2" = "yes" ; then
+ APR_ADDTO(MOD_CFLAGS, [-I${XML2_INCLUDES}])
+ APR_ADDTO(MOD_PROXY_HTML_LDADD, [-lxml2])
+ else
+ enable_proxy_html=no
+ fi
+]
+)
+
+APACHE_MODULE(brotli, Brotli compression support, , , most, [
+ AC_ARG_WITH(brotli, APACHE_HELP_STRING(--with-brotli=PATH,Brotli installation directory),[
+ if test "$withval" != "yes" -a "x$withval" != "x"; then
+ ap_brotli_base="$withval"
+ ap_brotli_with=yes
+ fi
+ ])
+ ap_brotli_found=no
+ if test -n "$ap_brotli_base"; then
+ ap_save_cppflags=$CPPFLAGS
+ APR_ADDTO(CPPFLAGS, [-I${ap_brotli_base}/include])
+ AC_MSG_CHECKING([for Brotli library >= 0.6.0 via prefix])
+ AC_TRY_COMPILE(
+ [#include <brotli/encode.h>],[
+const uint8_t *o = BrotliEncoderTakeOutput((BrotliEncoderState*)0, (size_t*)0);
+if (o) return *o;],
+ [AC_MSG_RESULT(yes)
+ ap_brotli_found=yes
+ ap_brotli_cflags="-I${ap_brotli_base}/include"
+ ap_brotli_libs="-L${ap_brotli_base}/lib -lbrotlienc -lbrotlicommon"],
+ [AC_MSG_RESULT(no)]
+ )
+ CPPFLAGS=$ap_save_cppflags
+ else
+ if test -n "$PKGCONFIG"; then
+ AC_MSG_CHECKING([for Brotli library >= 0.6.0 via pkg-config])
+ if $PKGCONFIG --exists "libbrotlienc >= 0.6.0"; then
+ AC_MSG_RESULT(yes)
+ ap_brotli_found=yes
+ ap_brotli_cflags=`$PKGCONFIG libbrotlienc --cflags`
+ ap_brotli_libs=`$PKGCONFIG libbrotlienc --libs`
+ else
+ AC_MSG_RESULT(no)
+ fi
+ fi
+ fi
+ if test "$ap_brotli_found" = "yes"; then
+ APR_ADDTO(MOD_CFLAGS, [$ap_brotli_cflags])
+ APR_ADDTO(MOD_BROTLI_LDADD, [$ap_brotli_libs])
+ if test "$enable_brotli" = "shared"; then
+ dnl The only symbol which needs to be exported is the module
+ dnl structure, so ask libtool to hide everything else:
+ APR_ADDTO(MOD_BROTLI_LDADD, [-export-symbols-regex brotli_module])
+ fi
+ else
+ enable_brotli=no
+ if test "$ap_brotli_with" = "yes"; then
+ AC_MSG_ERROR([Brotli library was missing or unusable])
+ fi
+ fi
+])
+
+APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/$modpath_current])
+
+APACHE_MODPATH_FINISH
diff --git a/modules/filters/libsed.h b/modules/filters/libsed.h
new file mode 100644
index 0000000..0256b1e
--- /dev/null
+++ b/modules/filters/libsed.h
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2005, 2008 Sun Microsystems, Inc. All Rights Reserved.
+ * Use is subject to license terms.
+ *
+ * Copyright (c) 1984 AT&T
+ * All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LIBSED_H
+#define LIBSED_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <limits.h>
+
+#include "apr_file_io.h"
+
+#define SED_NLINES 256
+#define SED_DEPTH 20
+#define SED_LABSIZE 50
+#define SED_ABUFSIZE 20
+
+typedef struct sed_reptr_s sed_reptr_t;
+
+struct sed_reptr_s {
+ sed_reptr_t *next;
+ char *ad1;
+ char *ad2;
+ char *re1;
+ sed_reptr_t *lb1;
+ char *rhs;
+ int findex;
+ char command;
+ int gfl;
+ char pfl;
+ char negfl;
+ int nrep;
+};
+
+typedef struct sed_label_s sed_label_t;
+
+struct sed_label_s {
+ char asc[9];
+ sed_reptr_t *chain;
+ sed_reptr_t *address;
+};
+
+typedef apr_status_t (sed_err_fn_t)(void *data, const char *error);
+typedef apr_status_t (sed_write_fn_t)(void *ctx, char *buf, apr_size_t sz);
+
+typedef struct sed_commands_s sed_commands_t;
+#define NWFILES 11 /* 10 plus one for standard output */
+
+struct sed_commands_s {
+ sed_err_fn_t *errfn;
+ void *data;
+
+ apr_size_t lsize;
+ char *linebuf;
+ char *lbend;
+ const char *saveq;
+
+ char *cp;
+ char *lastre;
+ char *respace;
+ char sseof;
+ char *reend;
+ const char *earg;
+ int eflag;
+ int gflag;
+ int nflag;
+ apr_int64_t tlno[SED_NLINES];
+ int nlno;
+ int depth;
+
+ char *fname[NWFILES];
+ int nfiles;
+
+ sed_label_t ltab[SED_LABSIZE];
+ sed_label_t *labtab;
+ sed_label_t *lab;
+ sed_label_t *labend;
+
+ sed_reptr_t **cmpend[SED_DEPTH];
+ sed_reptr_t *ptrspace;
+ sed_reptr_t *ptrend;
+ sed_reptr_t *rep;
+ int nrep;
+ apr_pool_t *pool;
+ int canbefinal;
+};
+
+typedef struct sed_eval_s sed_eval_t;
+
+struct sed_eval_s {
+ sed_err_fn_t *errfn;
+ sed_write_fn_t *writefn;
+ void *data;
+
+ sed_commands_t *commands;
+
+ apr_int64_t lnum;
+ void *fout;
+
+ apr_size_t lsize;
+ char *linebuf;
+ char *lspend;
+
+ apr_size_t hsize;
+ char *holdbuf;
+ char *hspend;
+
+ apr_size_t gsize;
+ char *genbuf;
+ char *lcomend;
+
+ apr_file_t *fcode[NWFILES];
+ sed_reptr_t *abuf[SED_ABUFSIZE];
+ sed_reptr_t **aptr;
+ sed_reptr_t *pending;
+ unsigned char *inar;
+ int nrep;
+
+ int dolflag;
+ int sflag;
+ int jflag;
+ int delflag;
+ int lreadyflag;
+ int quitflag;
+ int finalflag;
+ int numpass;
+ int nullmatch;
+ int col;
+ apr_pool_t *pool;
+};
+
+apr_status_t sed_init_commands(sed_commands_t *commands, sed_err_fn_t *errfn, void *data,
+ apr_pool_t *p);
+apr_status_t sed_compile_string(sed_commands_t *commands, const char *s);
+apr_status_t sed_compile_file(sed_commands_t *commands, apr_file_t *fin);
+char* sed_get_finalize_error(const sed_commands_t *commands, apr_pool_t* pool);
+int sed_canbe_finalized(const sed_commands_t *commands);
+void sed_destroy_commands(sed_commands_t *commands);
+
+apr_status_t sed_init_eval(sed_eval_t *eval, sed_commands_t *commands,
+ sed_err_fn_t *errfn, void *data,
+ sed_write_fn_t *writefn, apr_pool_t *p);
+apr_status_t sed_reset_eval(sed_eval_t *eval, sed_commands_t *commands, sed_err_fn_t *errfn, void *data);
+apr_status_t sed_eval_buffer(sed_eval_t *eval, const char *buf, apr_size_t bufsz, void *fout);
+apr_status_t sed_eval_file(sed_eval_t *eval, apr_file_t *fin, void *fout);
+apr_status_t sed_finalize_eval(sed_eval_t *eval, void *f);
+void sed_destroy_eval(sed_eval_t *eval);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LIBSED_H */
diff --git a/modules/filters/mod_brotli.c b/modules/filters/mod_brotli.c
new file mode 100644
index 0000000..0f7d770
--- /dev/null
+++ b/modules/filters/mod_brotli.c
@@ -0,0 +1,608 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "httpd.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "apr_strings.h"
+
+#include <brotli/encode.h>
+
+module AP_MODULE_DECLARE_DATA brotli_module;
+
+typedef enum {
+ ETAG_MODE_ADDSUFFIX = 0,
+ ETAG_MODE_NOCHANGE = 1,
+ ETAG_MODE_REMOVE = 2
+} etag_mode_e;
+
+typedef struct brotli_server_config_t {
+ int quality;
+ int lgwin;
+ int lgblock;
+ etag_mode_e etag_mode;
+ const char *note_ratio_name;
+ const char *note_input_name;
+ const char *note_output_name;
+} brotli_server_config_t;
+
+static void *create_server_config(apr_pool_t *p, server_rec *s)
+{
+ brotli_server_config_t *conf = apr_pcalloc(p, sizeof(*conf));
+
+ /* These default values allow mod_brotli to behave similarly to
+ * mod_deflate in terms of compression speed and memory usage.
+ *
+ * The idea is that since Brotli (generally) gives better compression
+ * ratio than Deflate, simply enabling mod_brotli on the server
+ * will reduce the amount of transferred data while keeping everything
+ * else unchanged. See https://quixdb.github.io/squash-benchmark/
+ */
+ conf->quality = 5;
+ conf->lgwin = 18;
+ /* Zero is a special value for BROTLI_PARAM_LGBLOCK that allows
+ * Brotli to automatically select the optimal input block size based
+ * on other encoder parameters. See enc/quality.h: ComputeLgBlock().
+ */
+ conf->lgblock = 0;
+ conf->etag_mode = ETAG_MODE_ADDSUFFIX;
+
+ return conf;
+}
+
+static const char *set_filter_note(cmd_parms *cmd, void *dummy,
+ const char *arg1, const char *arg2)
+{
+ brotli_server_config_t *conf =
+ ap_get_module_config(cmd->server->module_config, &brotli_module);
+
+ if (!arg2) {
+ conf->note_ratio_name = arg1;
+ return NULL;
+ }
+
+ if (ap_cstr_casecmp(arg1, "Ratio") == 0) {
+ conf->note_ratio_name = arg2;
+ }
+ else if (ap_cstr_casecmp(arg1, "Input") == 0) {
+ conf->note_input_name = arg2;
+ }
+ else if (ap_cstr_casecmp(arg1, "Output") == 0) {
+ conf->note_output_name = arg2;
+ }
+ else {
+ return apr_psprintf(cmd->pool, "Unknown BrotliFilterNote type '%s'",
+ arg1);
+ }
+
+ return NULL;
+}
+
+static const char *set_compression_quality(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ brotli_server_config_t *conf =
+ ap_get_module_config(cmd->server->module_config, &brotli_module);
+ int val = atoi(arg);
+
+ if (val < 0 || val > 11) {
+ return "BrotliCompressionQuality must be between 0 and 11";
+ }
+
+ conf->quality = val;
+ return NULL;
+}
+
+static const char *set_compression_lgwin(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ brotli_server_config_t *conf =
+ ap_get_module_config(cmd->server->module_config, &brotli_module);
+ int val = atoi(arg);
+
+ if (val < 10 || val > 24) {
+ return "BrotliCompressionWindow must be between 10 and 24";
+ }
+
+ conf->lgwin = val;
+ return NULL;
+}
+
+static const char *set_compression_lgblock(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ brotli_server_config_t *conf =
+ ap_get_module_config(cmd->server->module_config, &brotli_module);
+ int val = atoi(arg);
+
+ if (val < 16 || val > 24) {
+ return "BrotliCompressionMaxInputBlock must be between 16 and 24";
+ }
+
+ conf->lgblock = val;
+ return NULL;
+}
+
+static const char *set_etag_mode(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ brotli_server_config_t *conf =
+ ap_get_module_config(cmd->server->module_config, &brotli_module);
+
+ if (ap_cstr_casecmp(arg, "AddSuffix") == 0) {
+ conf->etag_mode = ETAG_MODE_ADDSUFFIX;
+ }
+ else if (ap_cstr_casecmp(arg, "NoChange") == 0) {
+ conf->etag_mode = ETAG_MODE_NOCHANGE;
+ }
+ else if (ap_cstr_casecmp(arg, "Remove") == 0) {
+ conf->etag_mode = ETAG_MODE_REMOVE;
+ }
+ else {
+ return "BrotliAlterETag accepts only 'AddSuffix', 'NoChange' and 'Remove'";
+ }
+
+ return NULL;
+}
+
+typedef struct brotli_ctx_t {
+ BrotliEncoderState *state;
+ apr_bucket_brigade *bb;
+ apr_off_t total_in;
+ apr_off_t total_out;
+} brotli_ctx_t;
+
+static void *alloc_func(void *opaque, size_t size)
+{
+ return apr_bucket_alloc(size, opaque);
+}
+
+static void free_func(void *opaque, void *block)
+{
+ if (block) {
+ apr_bucket_free(block);
+ }
+}
+
+static apr_status_t cleanup_ctx(void *data)
+{
+ brotli_ctx_t *ctx = data;
+
+ BrotliEncoderDestroyInstance(ctx->state);
+ ctx->state = NULL;
+ return APR_SUCCESS;
+}
+
+static brotli_ctx_t *create_ctx(int quality,
+ int lgwin,
+ int lgblock,
+ apr_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ brotli_ctx_t *ctx = apr_pcalloc(pool, sizeof(*ctx));
+
+ ctx->state = BrotliEncoderCreateInstance(alloc_func, free_func, alloc);
+ BrotliEncoderSetParameter(ctx->state, BROTLI_PARAM_QUALITY, quality);
+ BrotliEncoderSetParameter(ctx->state, BROTLI_PARAM_LGWIN, lgwin);
+ BrotliEncoderSetParameter(ctx->state, BROTLI_PARAM_LGBLOCK, lgblock);
+ apr_pool_cleanup_register(pool, ctx, cleanup_ctx, apr_pool_cleanup_null);
+
+ ctx->bb = apr_brigade_create(pool, alloc);
+ ctx->total_in = 0;
+ ctx->total_out = 0;
+
+ return ctx;
+}
+
+static apr_status_t process_chunk(brotli_ctx_t *ctx,
+ const void *data,
+ apr_size_t len,
+ ap_filter_t *f)
+{
+ const apr_byte_t *next_in = data;
+ apr_size_t avail_in = len;
+
+ while (avail_in > 0) {
+ apr_byte_t *next_out = NULL;
+ apr_size_t avail_out = 0;
+
+ if (!BrotliEncoderCompressStream(ctx->state,
+ BROTLI_OPERATION_PROCESS,
+ &avail_in, &next_in,
+ &avail_out, &next_out, NULL)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, APLOGNO(03459)
+ "Error while compressing data");
+ return APR_EGENERAL;
+ }
+
+ if (BrotliEncoderHasMoreOutput(ctx->state)) {
+ apr_size_t output_len = 0;
+ const apr_byte_t *output;
+ apr_status_t rv;
+ apr_bucket *b;
+
+ /* Drain the accumulated output. Avoid copying the data by
+ * wrapping a pointer to the internal output buffer and passing
+ * it down to the next filter. The pointer is only valid until
+ * the next call to BrotliEncoderCompressStream(), but we're okay
+ * with that, since the brigade is cleaned up right after the
+ * ap_pass_brigade() call.
+ */
+ output = BrotliEncoderTakeOutput(ctx->state, &output_len);
+ ctx->total_out += output_len;
+
+ b = apr_bucket_transient_create((const char *)output, output_len,
+ ctx->bb->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
+
+ rv = ap_pass_brigade(f->next, ctx->bb);
+ apr_brigade_cleanup(ctx->bb);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+ }
+
+ ctx->total_in += len;
+ return APR_SUCCESS;
+}
+
+static apr_status_t flush(brotli_ctx_t *ctx,
+ BrotliEncoderOperation op,
+ ap_filter_t *f)
+{
+ while (1) {
+ const apr_byte_t *next_in = NULL;
+ apr_size_t avail_in = 0;
+ apr_byte_t *next_out = NULL;
+ apr_size_t avail_out = 0;
+ apr_size_t output_len;
+ const apr_byte_t *output;
+ apr_bucket *b;
+
+ if (!BrotliEncoderCompressStream(ctx->state, op,
+ &avail_in, &next_in,
+ &avail_out, &next_out, NULL)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, APLOGNO(03460)
+ "Error while compressing data");
+ return APR_EGENERAL;
+ }
+
+ if (!BrotliEncoderHasMoreOutput(ctx->state)) {
+ break;
+ }
+
+ /* A flush can require several calls to BrotliEncoderCompressStream(),
+ * so place the data on the heap (otherwise, the pointer will become
+ * invalid after the next call to BrotliEncoderCompressStream()).
+ */
+ output_len = 0;
+ output = BrotliEncoderTakeOutput(ctx->state, &output_len);
+ ctx->total_out += output_len;
+
+ b = apr_bucket_heap_create((const char *)output, output_len, NULL,
+ ctx->bb->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
+ }
+
+ return APR_SUCCESS;
+}
+
+static const char *get_content_encoding(request_rec *r)
+{
+ const char *encoding;
+
+ encoding = apr_table_get(r->headers_out, "Content-Encoding");
+ if (encoding) {
+ const char *err_enc;
+
+ err_enc = apr_table_get(r->err_headers_out, "Content-Encoding");
+ if (err_enc) {
+ encoding = apr_pstrcat(r->pool, encoding, ",", err_enc, NULL);
+ }
+ }
+ else {
+ encoding = apr_table_get(r->err_headers_out, "Content-Encoding");
+ }
+
+ if (r->content_encoding) {
+ encoding = encoding ? apr_pstrcat(r->pool, encoding, ",",
+ r->content_encoding, NULL)
+ : r->content_encoding;
+ }
+
+ return encoding;
+}
+
+static apr_status_t compress_filter(ap_filter_t *f, apr_bucket_brigade *bb)
+{
+ request_rec *r = f->r;
+ brotli_ctx_t *ctx = f->ctx;
+ apr_status_t rv;
+ brotli_server_config_t *conf;
+
+ if (APR_BRIGADE_EMPTY(bb)) {
+ return APR_SUCCESS;
+ }
+
+ conf = ap_get_module_config(r->server->module_config, &brotli_module);
+
+ if (!ctx) {
+ const char *encoding;
+ const char *token;
+ const char *accepts;
+ const char *q = NULL;
+
+ /* Only work on main request, not subrequests, that are not
+ * a 204 response with no content, and are not tagged with the
+ * no-brotli env variable, and are not a partial response to
+ * a Range request.
+ *
+ * Note that responding to 304 is handled separately to set
+ * the required headers (such as ETag) per RFC7232, 4.1.
+ */
+ if (r->main || r->status == HTTP_NO_CONTENT
+ || apr_table_get(r->subprocess_env, "no-brotli")
+ || apr_table_get(r->headers_out, "Content-Range")) {
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ /* Let's see what our current Content-Encoding is. */
+ encoding = get_content_encoding(r);
+
+ if (encoding) {
+ const char *tmp = encoding;
+
+ token = ap_get_token(r->pool, &tmp, 0);
+ while (token && *token) {
+ if (strcmp(token, "identity") != 0 &&
+ strcmp(token, "7bit") != 0 &&
+ strcmp(token, "8bit") != 0 &&
+ strcmp(token, "binary") != 0) {
+ /* The data is already encoded, do nothing. */
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ if (*tmp) {
+ ++tmp;
+ }
+ token = (*tmp) ? ap_get_token(r->pool, &tmp, 0) : NULL;
+ }
+ }
+
+ /* Even if we don't accept this request based on it not having
+ * the Accept-Encoding, we need to note that we were looking
+ * for this header and downstream proxies should be aware of
+ * that.
+ */
+ apr_table_mergen(r->headers_out, "Vary", "Accept-Encoding");
+
+ accepts = apr_table_get(r->headers_in, "Accept-Encoding");
+ if (!accepts) {
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ /* Do we have Accept-Encoding: br? */
+ token = ap_get_token(r->pool, &accepts, 0);
+ while (token && token[0] && ap_cstr_casecmp(token, "br") != 0) {
+ while (*accepts == ';') {
+ ++accepts;
+ ap_get_token(r->pool, &accepts, 1);
+ }
+
+ if (*accepts == ',') {
+ ++accepts;
+ }
+ token = (*accepts) ? ap_get_token(r->pool, &accepts, 0) : NULL;
+ }
+
+ /* Find the qvalue, if provided */
+ if (*accepts) {
+ while (*accepts == ';') {
+ ++accepts;
+ }
+ q = ap_get_token(r->pool, &accepts, 1);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "token: '%s' - q: '%s'", token ? token : "NULL", q);
+ }
+
+ /* No acceptable token found or q=0 */
+ if (!token || token[0] == '\0' ||
+ (q && strlen(q) >= 3 && strncmp("q=0.000", q, strlen(q)) == 0)) {
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ /* If the entire Content-Encoding is "identity", we can replace it. */
+ if (!encoding || ap_cstr_casecmp(encoding, "identity") == 0) {
+ apr_table_setn(r->headers_out, "Content-Encoding", "br");
+ } else {
+ apr_table_mergen(r->headers_out, "Content-Encoding", "br");
+ }
+
+ if (r->content_encoding) {
+ r->content_encoding = apr_table_get(r->headers_out,
+ "Content-Encoding");
+ }
+
+ apr_table_unset(r->headers_out, "Content-Length");
+ apr_table_unset(r->headers_out, "Content-MD5");
+
+ /* https://bz.apache.org/bugzilla/show_bug.cgi?id=39727
+ * https://bz.apache.org/bugzilla/show_bug.cgi?id=45023
+ *
+ * ETag must be unique among the possible representations, so a
+ * change to content-encoding requires a corresponding change to the
+ * ETag. We make this behavior configurable, and mimic mod_deflate's
+ * DeflateAlterETag with BrotliAlterETag to keep the transition from
+ * mod_deflate seamless.
+ */
+ if (conf->etag_mode == ETAG_MODE_REMOVE) {
+ apr_table_unset(r->headers_out, "ETag");
+ }
+ else if (conf->etag_mode == ETAG_MODE_ADDSUFFIX) {
+ const char *etag = apr_table_get(r->headers_out, "ETag");
+
+ if (etag) {
+ apr_size_t len = strlen(etag);
+
+ if (len > 2 && etag[len - 1] == '"') {
+ etag = apr_pstrmemdup(r->pool, etag, len - 1);
+ etag = apr_pstrcat(r->pool, etag, "-br\"", NULL);
+ apr_table_setn(r->headers_out, "ETag", etag);
+ }
+ }
+ }
+
+ /* For 304 responses, we only need to send out the headers. */
+ if (r->status == HTTP_NOT_MODIFIED) {
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ ctx = create_ctx(conf->quality, conf->lgwin, conf->lgblock,
+ f->c->bucket_alloc, r->pool);
+ f->ctx = ctx;
+ }
+
+ while (!APR_BRIGADE_EMPTY(bb)) {
+ apr_bucket *e = APR_BRIGADE_FIRST(bb);
+
+ /* Optimization: If we are a HEAD request and bytes_sent is not zero
+ * it means that we have passed the content-length filter once and
+ * have more data to send. This means that the content-length filter
+ * could not determine our content-length for the response to the
+ * HEAD request anyway (the associated GET request would deliver the
+ * body in chunked encoding) and we can stop compressing.
+ */
+ if (r->header_only && r->bytes_sent) {
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ if (APR_BUCKET_IS_EOS(e)) {
+ rv = flush(ctx, BROTLI_OPERATION_FINISH, f);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ /* Leave notes for logging. */
+ if (conf->note_input_name) {
+ apr_table_setn(r->notes, conf->note_input_name,
+ apr_off_t_toa(r->pool, ctx->total_in));
+ }
+ if (conf->note_output_name) {
+ apr_table_setn(r->notes, conf->note_output_name,
+ apr_off_t_toa(r->pool, ctx->total_out));
+ }
+ if (conf->note_ratio_name) {
+ if (ctx->total_in > 0) {
+ int ratio = (int) (ctx->total_out * 100 / ctx->total_in);
+
+ apr_table_setn(r->notes, conf->note_ratio_name,
+ apr_itoa(r->pool, ratio));
+ }
+ else {
+ apr_table_setn(r->notes, conf->note_ratio_name, "-");
+ }
+ }
+
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
+
+ rv = ap_pass_brigade(f->next, ctx->bb);
+ apr_brigade_cleanup(ctx->bb);
+ apr_pool_cleanup_run(r->pool, ctx, cleanup_ctx);
+ return rv;
+ }
+ else if (APR_BUCKET_IS_FLUSH(e)) {
+ rv = flush(ctx, BROTLI_OPERATION_FLUSH, f);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
+
+ rv = ap_pass_brigade(f->next, ctx->bb);
+ apr_brigade_cleanup(ctx->bb);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+ else if (APR_BUCKET_IS_METADATA(e)) {
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
+ }
+ else {
+ const char *data;
+ apr_size_t len;
+
+ rv = apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ rv = process_chunk(ctx, data, len, f);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ apr_bucket_delete(e);
+ }
+ }
+ return APR_SUCCESS;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_register_output_filter("BROTLI_COMPRESS", compress_filter, NULL,
+ AP_FTYPE_CONTENT_SET);
+}
+
+static const command_rec cmds[] = {
+ AP_INIT_TAKE12("BrotliFilterNote", set_filter_note,
+ NULL, RSRC_CONF,
+ "Set a note to report on compression ratio"),
+ AP_INIT_TAKE1("BrotliCompressionQuality", set_compression_quality,
+ NULL, RSRC_CONF,
+ "Compression quality between 0 and 11 (higher quality means "
+ "slower compression)"),
+ AP_INIT_TAKE1("BrotliCompressionWindow", set_compression_lgwin,
+ NULL, RSRC_CONF,
+ "Sliding window size between 10 and 24 (larger windows can "
+ "improve compression, but require more memory)"),
+ AP_INIT_TAKE1("BrotliCompressionMaxInputBlock", set_compression_lgblock,
+ NULL, RSRC_CONF,
+ "Maximum input block size between 16 and 24 (larger block "
+ "sizes require more memory)"),
+ AP_INIT_TAKE1("BrotliAlterETag", set_etag_mode,
+ NULL, RSRC_CONF,
+ "Set how mod_brotli should modify ETag response headers: "
+ "'AddSuffix' (default), 'NoChange', 'Remove'"),
+ {NULL}
+};
+
+AP_DECLARE_MODULE(brotli) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ create_server_config, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/filters/mod_brotli.dep b/modules/filters/mod_brotli.dep
new file mode 100644
index 0000000..adafc46
--- /dev/null
+++ b/modules/filters/mod_brotli.dep
@@ -0,0 +1,45 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_brotli.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_brotli.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/filters/mod_brotli.dsp b/modules/filters/mod_brotli.dsp
new file mode 100644
index 0000000..8576e95
--- /dev/null
+++ b/modules/filters/mod_brotli.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_brotli" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_brotli - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_brotli.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_brotli.mak" CFG="mod_brotli - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_brotli - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_brotli - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_brotli - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "HAVE_ZUTIL_H" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/brotli/include" /I "../../srclib/brotli/c/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_brotli_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_brotli.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_brotli.so" /d LONG_NAME="brotli_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_brotli.so" /libpath:"../../srclib/brotli" /base:@..\..\os\win32\BaseAddr.ref,mod_brotli.so
+# ADD LINK32 kernel32.lib brotlicommon.lib brotlienc.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_brotli.so" /libpath:"../../srclib/brotli" /base:@..\..\os\win32\BaseAddr.ref,mod_brotli.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_brotli.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_brotli - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/brotli/include" /I "../../srclib/brotli/c/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "HAVE_ZUTIL_H" /Fd"Debug\mod_brotli_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_brotli.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_brotli.so" /d LONG_NAME="brotli_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_brotli.so" /libpath:"../../srclib/brotli" /base:@..\..\os\win32\BaseAddr.ref,mod_brotli.so
+# ADD LINK32 kernel32.lib brotlicommon.lib brotlienc.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_brotli.so" /libpath:"../../srclib/brotli" /base:@..\..\os\win32\BaseAddr.ref,mod_brotli.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_brotli.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_brotli - Win32 Release"
+# Name "mod_brotli - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_brotli.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/filters/mod_brotli.mak b/modules/filters/mod_brotli.mak
new file mode 100644
index 0000000..79ec9da
--- /dev/null
+++ b/modules/filters/mod_brotli.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_brotli.dsp
+!IF "$(CFG)" == ""
+CFG=mod_brotli - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_brotli - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_brotli - Win32 Release" && "$(CFG)" != "mod_brotli - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_brotli.mak" CFG="mod_brotli - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_brotli - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_brotli - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_brotli - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_brotli.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_brotli.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_brotli.obj"
+ -@erase "$(INTDIR)\mod_brotli.res"
+ -@erase "$(INTDIR)\mod_brotli_src.idb"
+ -@erase "$(INTDIR)\mod_brotli_src.pdb"
+ -@erase "$(OUTDIR)\mod_brotli.exp"
+ -@erase "$(OUTDIR)\mod_brotli.lib"
+ -@erase "$(OUTDIR)\mod_brotli.pdb"
+ -@erase "$(OUTDIR)\mod_brotli.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/brotli/include" /I "../../srclib/brotli/c/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_brotli_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_brotli.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_brotli.so" /d LONG_NAME="brotli_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_brotli.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib brotlicommon.lib brotlienc.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_brotli.pdb" /libpath:"../../srclib/brotli" /debug /out:"$(OUTDIR)\mod_brotli.so" /implib:"$(OUTDIR)\mod_brotli.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_brotli.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_brotli.obj" \
+ "$(INTDIR)\mod_brotli.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_brotli.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_brotli.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_brotli.so"
+ if exist .\Release\mod_brotli.so.manifest mt.exe -manifest .\Release\mod_brotli.so.manifest -outputresource:.\Release\mod_brotli.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_brotli - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_brotli.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_brotli.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_brotli.obj"
+ -@erase "$(INTDIR)\mod_brotli.res"
+ -@erase "$(INTDIR)\mod_brotli_src.idb"
+ -@erase "$(INTDIR)\mod_brotli_src.pdb"
+ -@erase "$(OUTDIR)\mod_brotli.exp"
+ -@erase "$(OUTDIR)\mod_brotli.lib"
+ -@erase "$(OUTDIR)\mod_brotli.pdb"
+ -@erase "$(OUTDIR)\mod_brotli.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/brotli/include" /I "../../srclib/brotli/c/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "HAVE_ZUTIL_H" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_brotli_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_brotli.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_brotli.so" /d LONG_NAME="brotli_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_brotli.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib brotlicommon.lib brotlienc.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_brotli.pdb" /debug /libpath:"../../srclib/brotli" /out:"$(OUTDIR)\mod_brotli.so" /implib:"$(OUTDIR)\mod_brotli.lib" /libpath:"..\..\srclib\brotli\Debug\bin" /base:@..\..\os\win32\BaseAddr.ref,mod_brotli.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_brotli.obj" \
+ "$(INTDIR)\mod_brotli.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_brotli.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_brotli.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_brotli.so"
+ if exist .\Debug\mod_brotli.so.manifest mt.exe -manifest .\Debug\mod_brotli.so.manifest -outputresource:.\Debug\mod_brotli.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_brotli.dep")
+!INCLUDE "mod_brotli.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_brotli.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_brotli - Win32 Release" || "$(CFG)" == "mod_brotli - Win32 Debug"
+
+!IF "$(CFG)" == "mod_brotli - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\filters"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_brotli - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\filters"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_brotli - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\filters"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_brotli - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\filters"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_brotli - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\filters"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_brotli - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\filters"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\filters"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_brotli - Win32 Release"
+
+
+"$(INTDIR)\mod_brotli.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_brotli.res" /i "../../include" /i "../../srclib/apr/include" /i "\build4\httpd-2.4.23\build\win32" /d "NDEBUG" /d BIN_NAME="mod_brotli.so" /d LONG_NAME="brotli_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_brotli - Win32 Debug"
+
+
+"$(INTDIR)\mod_brotli.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_brotli.res" /i "../../include" /i "../../srclib/apr/include" /i "\build4\httpd-2.4.23\build\win32" /d "_DEBUG" /d BIN_NAME="mod_brotli.so" /d LONG_NAME="brotli_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_brotli.c
+
+"$(INTDIR)\mod_brotli.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/filters/mod_buffer.c b/modules/filters/mod_buffer.c
new file mode 100644
index 0000000..203e672
--- /dev/null
+++ b/modules/filters/mod_buffer.c
@@ -0,0 +1,353 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * mod_buffer.c --- Buffer the input and output filter stacks, collapse
+ * many small buckets into fewer large buckets.
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_buckets.h"
+#include "apr_lib.h"
+
+#include "ap_config.h"
+#include "util_filter.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "http_request.h"
+
+static const char bufferFilterName[] = "BUFFER";
+module AP_MODULE_DECLARE_DATA buffer_module;
+
+#define DEFAULT_BUFFER_SIZE 128*1024
+
+typedef struct buffer_conf {
+ apr_off_t size; /* size of the buffer */
+ int size_set; /* has the size been set */
+} buffer_conf;
+
+typedef struct buffer_ctx {
+ apr_bucket_brigade *bb;
+ apr_bucket_brigade *tmp;
+ buffer_conf *conf;
+ apr_off_t remaining;
+ int seen_eos;
+} buffer_ctx;
+
+/**
+ * Buffer buckets being written to the output filter stack.
+ */
+static apr_status_t buffer_out_filter(ap_filter_t *f, apr_bucket_brigade *bb)
+{
+ apr_bucket *e;
+ request_rec *r = f->r;
+ buffer_ctx *ctx = f->ctx;
+ apr_status_t rv = APR_SUCCESS;
+ int move = 0;
+
+ /* first time in? create a context */
+ if (!ctx) {
+
+ /* buffering won't work on subrequests, it would be nice if
+ * it did. Within subrequests, we have no EOS to check for,
+ * so we don't know when to flush the buffer to the network
+ */
+ if (f->r->main) {
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ ctx = f->ctx = apr_pcalloc(r->pool, sizeof(*ctx));
+ ctx->bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
+ ctx->conf = ap_get_module_config(f->r->per_dir_config, &buffer_module);
+ }
+
+ /* Do nothing if asked to filter nothing. */
+ if (APR_BRIGADE_EMPTY(bb)) {
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ /* Empty buffer means we can potentially optimise below */
+ if (APR_BRIGADE_EMPTY(ctx->bb)) {
+ move = 1;
+ }
+
+ while (APR_SUCCESS == rv && !APR_BRIGADE_EMPTY(bb)) {
+ const char *data;
+ apr_off_t len;
+ apr_size_t size;
+
+ e = APR_BRIGADE_FIRST(bb);
+
+ /* EOS means we are done. */
+ if (APR_BUCKET_IS_EOS(e)) {
+
+ /* should we add an etag? */
+
+ /* pass the EOS across */
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
+
+ /* pass what we have down the chain */
+ rv = ap_pass_brigade(f->next, ctx->bb);
+ continue;
+ }
+
+ /* A flush takes precedence over buffering */
+ if (APR_BUCKET_IS_FLUSH(e)) {
+
+ /* pass the flush bucket across */
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
+
+ /* pass what we have down the chain */
+ rv = ap_pass_brigade(f->next, ctx->bb);
+ continue;
+ }
+
+ /* metadata buckets are preserved as is */
+ if (APR_BUCKET_IS_METADATA(e)) {
+ /*
+ * Remove meta data bucket from old brigade and insert into the
+ * new.
+ */
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
+ continue;
+ }
+
+ /* is our buffer full?
+ * If so, send what we have down the filter chain. If the buffer
+ * gets full, we can no longer compute a content length.
+ */
+ apr_brigade_length(ctx->bb, 1, &len);
+ if (len > ctx->conf->size) {
+
+ /* pass what we have down the chain */
+ rv = ap_pass_brigade(f->next, ctx->bb);
+ if (rv) {
+ /* should break out of the loop, since our write to the client
+ * failed in some way. */
+ continue;
+ }
+ }
+
+ /* at this point we are ready to buffer.
+ * Buffering takes advantage of an optimisation in the handling of
+ * bucket brigades. Heap buckets are always created at a fixed
+ * size, regardless of the size of the data placed into them.
+ * The apr_brigade_write() call will first try and pack the data
+ * into any free space in the most recent heap bucket, before
+ * allocating a new bucket if necessary.
+ */
+ if (APR_SUCCESS == (rv = apr_bucket_read(e, &data, &size,
+ APR_BLOCK_READ))) {
+
+ /* further optimisation: if the buckets are already heap
+ * buckets, and the buckets stay exactly APR_BUCKET_BUFF_SIZE
+ * long (as they would be if we were reading bits of a
+ * large bucket), then move the buckets instead of copying
+ * them.
+ */
+ if (move && APR_BUCKET_IS_HEAP(e)) {
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
+ if (APR_BUCKET_BUFF_SIZE != size) {
+ move = 0;
+ }
+ } else {
+ apr_brigade_write(ctx->bb, NULL, NULL, data, size);
+ apr_bucket_delete(e);
+ }
+
+ }
+
+ }
+
+ return rv;
+
+}
+
+/**
+ * Buffer buckets being read from the input filter stack.
+ */
+static apr_status_t buffer_in_filter(ap_filter_t *f, apr_bucket_brigade *bb,
+ ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes)
+{
+ apr_bucket *e, *after;
+ apr_status_t rv;
+ buffer_ctx *ctx = f->ctx;
+
+ /* buffer on main requests only */
+ if (!ap_is_initial_req(f->r)) {
+ ap_remove_input_filter(f);
+ return ap_get_brigade(f->next, bb, mode, block, readbytes);
+ }
+
+ /* first time in? create a context */
+ if (!ctx) {
+ ctx = f->ctx = apr_pcalloc(f->r->pool, sizeof(*ctx));
+ ctx->bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
+ ctx->tmp = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
+ ctx->conf = ap_get_module_config(f->r->per_dir_config, &buffer_module);
+ }
+
+ /* just get out of the way of things we don't want. */
+ if (mode != AP_MODE_READBYTES) {
+ return ap_get_brigade(f->next, bb, mode, block, readbytes);
+ }
+
+ /* if our buffer is empty, read off the network until the buffer is full */
+ if (APR_BRIGADE_EMPTY(ctx->bb)) {
+ int seen_flush = 0;
+
+ ctx->remaining = ctx->conf->size;
+
+ while (!ctx->seen_eos && !seen_flush && ctx->remaining > 0) {
+ const char *data;
+ apr_size_t size = 0;
+
+ if (APR_BRIGADE_EMPTY(ctx->tmp)) {
+ rv = ap_get_brigade(f->next, ctx->tmp, mode, block,
+ ctx->remaining);
+
+ /* if an error was received, bail out now. If the error is
+ * EAGAIN and we have not yet seen an EOS, we will definitely
+ * be called again, at which point we will send our buffered
+ * data. Instead of sending EAGAIN, some filters return an
+ * empty brigade instead when data is not yet available. In
+ * this case, pass through the APR_SUCCESS and emulate the
+ * underlying filter.
+ */
+ if (rv != APR_SUCCESS || APR_BRIGADE_EMPTY(ctx->tmp)) {
+ return rv;
+ }
+ }
+
+ do {
+ e = APR_BRIGADE_FIRST(ctx->tmp);
+
+ /* if we see an EOS, we are done */
+ if (APR_BUCKET_IS_EOS(e)) {
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
+ ctx->seen_eos = 1;
+ break;
+ }
+
+ /* flush buckets clear the buffer */
+ if (APR_BUCKET_IS_FLUSH(e)) {
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
+ seen_flush = 1;
+ break;
+ }
+
+ /* pass metadata buckets through */
+ if (APR_BUCKET_IS_METADATA(e)) {
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
+ continue;
+ }
+
+ /* read the bucket in, pack it into the buffer */
+ if (APR_SUCCESS == (rv = apr_bucket_read(e, &data, &size,
+ APR_BLOCK_READ))) {
+ apr_brigade_write(ctx->bb, NULL, NULL, data, size);
+ ctx->remaining -= size;
+ apr_bucket_delete(e);
+ } else {
+ return rv;
+ }
+
+ } while (!APR_BRIGADE_EMPTY(ctx->tmp));
+ }
+ }
+
+ /* give the caller the data they asked for from the buffer */
+ apr_brigade_partition(ctx->bb, readbytes, &after);
+ e = APR_BRIGADE_FIRST(ctx->bb);
+ while (e != after) {
+ if (APR_BUCKET_IS_EOS(e)) {
+ /* last bucket read, step out of the way */
+ ap_remove_input_filter(f);
+ }
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ e = APR_BRIGADE_FIRST(ctx->bb);
+ }
+
+ return APR_SUCCESS;
+}
+
+static void *create_buffer_config(apr_pool_t *p, char *dummy)
+{
+ buffer_conf *new = (buffer_conf *) apr_pcalloc(p, sizeof(buffer_conf));
+
+ new->size_set = 0; /* unset */
+ new->size = DEFAULT_BUFFER_SIZE; /* default size */
+
+ return (void *) new;
+}
+
+static void *merge_buffer_config(apr_pool_t *p, void *basev, void *addv)
+{
+ buffer_conf *new = (buffer_conf *) apr_pcalloc(p, sizeof(buffer_conf));
+ buffer_conf *add = (buffer_conf *) addv;
+ buffer_conf *base = (buffer_conf *) basev;
+
+ new->size = (add->size_set == 0) ? base->size : add->size;
+ new->size_set = add->size_set || base->size_set;
+
+ return new;
+}
+
+static const char *set_buffer_size(cmd_parms *cmd, void *dconf, const char *arg)
+{
+ buffer_conf *conf = dconf;
+
+ if (APR_SUCCESS != apr_strtoff(&(conf->size), arg, NULL, 10) || conf->size
+ <= 0) {
+ return "BufferSize must be a size in bytes, and greater than zero";
+ }
+ conf->size_set = 1;
+
+ return NULL;
+}
+
+static const command_rec buffer_cmds[] = { AP_INIT_TAKE1("BufferSize",
+ set_buffer_size, NULL, ACCESS_CONF,
+ "Maximum size of the buffer used by the buffer filter"), { NULL } };
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_register_output_filter(bufferFilterName, buffer_out_filter, NULL,
+ AP_FTYPE_CONTENT_SET);
+ ap_register_input_filter(bufferFilterName, buffer_in_filter, NULL,
+ AP_FTYPE_CONTENT_SET);
+}
+
+AP_DECLARE_MODULE(buffer) = {
+ STANDARD20_MODULE_STUFF,
+ create_buffer_config, /* create per-directory config structure */
+ merge_buffer_config, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ buffer_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/filters/mod_buffer.dep b/modules/filters/mod_buffer.dep
new file mode 100644
index 0000000..6f77613
--- /dev/null
+++ b/modules/filters/mod_buffer.dep
@@ -0,0 +1,48 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_buffer.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_buffer.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/filters/mod_buffer.dsp b/modules/filters/mod_buffer.dsp
new file mode 100644
index 0000000..26834f5
--- /dev/null
+++ b/modules/filters/mod_buffer.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_buffer" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_buffer - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_buffer.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_buffer.mak" CFG="mod_buffer - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_buffer - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_buffer - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_buffer - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "HAVE_ZUTIL_H" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_buffer_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_buffer.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_buffer.so" /d LONG_NAME="buffer_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_buffer.so" /base:@..\..\os\win32\BaseAddr.ref,mod_buffer.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_buffer.so" /base:@..\..\os\win32\BaseAddr.ref,mod_buffer.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_buffer.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_buffer - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "HAVE_ZUTIL_H" /Fd"Debug\mod_buffer_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_buffer.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_buffer.so" /d LONG_NAME="buffer_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_buffer.so" /base:@..\..\os\win32\BaseAddr.ref,mod_buffer.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_buffer.so" /base:@..\..\os\win32\BaseAddr.ref,mod_buffer.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_buffer.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_buffer - Win32 Release"
+# Name "mod_buffer - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_buffer.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/filters/mod_buffer.mak b/modules/filters/mod_buffer.mak
new file mode 100644
index 0000000..d74bec2
--- /dev/null
+++ b/modules/filters/mod_buffer.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_buffer.dsp
+!IF "$(CFG)" == ""
+CFG=mod_buffer - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_buffer - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_buffer - Win32 Release" && "$(CFG)" != "mod_buffer - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_buffer.mak" CFG="mod_buffer - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_buffer - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_buffer - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_buffer - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_buffer.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_buffer.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_buffer.obj"
+ -@erase "$(INTDIR)\mod_buffer.res"
+ -@erase "$(INTDIR)\mod_buffer_src.idb"
+ -@erase "$(INTDIR)\mod_buffer_src.pdb"
+ -@erase "$(OUTDIR)\mod_buffer.exp"
+ -@erase "$(OUTDIR)\mod_buffer.lib"
+ -@erase "$(OUTDIR)\mod_buffer.pdb"
+ -@erase "$(OUTDIR)\mod_buffer.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_buffer_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_buffer.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_buffer.so" /d LONG_NAME="buffer_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_buffer.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_buffer.pdb" /debug /out:"$(OUTDIR)\mod_buffer.so" /implib:"$(OUTDIR)\mod_buffer.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_buffer.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_buffer.obj" \
+ "$(INTDIR)\mod_buffer.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_buffer.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_buffer.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_buffer.so"
+ if exist .\Release\mod_buffer.so.manifest mt.exe -manifest .\Release\mod_buffer.so.manifest -outputresource:.\Release\mod_buffer.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_buffer - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_buffer.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_buffer.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_buffer.obj"
+ -@erase "$(INTDIR)\mod_buffer.res"
+ -@erase "$(INTDIR)\mod_buffer_src.idb"
+ -@erase "$(INTDIR)\mod_buffer_src.pdb"
+ -@erase "$(OUTDIR)\mod_buffer.exp"
+ -@erase "$(OUTDIR)\mod_buffer.lib"
+ -@erase "$(OUTDIR)\mod_buffer.pdb"
+ -@erase "$(OUTDIR)\mod_buffer.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "HAVE_ZUTIL_H" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_buffer_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_buffer.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_buffer.so" /d LONG_NAME="buffer_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_buffer.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_buffer.pdb" /debug /out:"$(OUTDIR)\mod_buffer.so" /implib:"$(OUTDIR)\mod_buffer.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_buffer.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_buffer.obj" \
+ "$(INTDIR)\mod_buffer.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_buffer.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_buffer.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_buffer.so"
+ if exist .\Debug\mod_buffer.so.manifest mt.exe -manifest .\Debug\mod_buffer.so.manifest -outputresource:.\Debug\mod_buffer.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_buffer.dep")
+!INCLUDE "mod_buffer.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_buffer.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_buffer - Win32 Release" || "$(CFG)" == "mod_buffer - Win32 Debug"
+
+!IF "$(CFG)" == "mod_buffer - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\filters"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_buffer - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\filters"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_buffer - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\filters"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_buffer - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\filters"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_buffer - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\filters"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_buffer - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\filters"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\filters"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_buffer - Win32 Release"
+
+
+"$(INTDIR)\mod_buffer.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_buffer.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_buffer.so" /d LONG_NAME="buffer_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_buffer - Win32 Debug"
+
+
+"$(INTDIR)\mod_buffer.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_buffer.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_buffer.so" /d LONG_NAME="buffer_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_buffer.c
+
+"$(INTDIR)\mod_buffer.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/filters/mod_charset_lite.c b/modules/filters/mod_charset_lite.c
new file mode 100644
index 0000000..e3d1ce9
--- /dev/null
+++ b/modules/filters/mod_charset_lite.c
@@ -0,0 +1,1142 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * simple hokey charset recoding configuration module
+ *
+ * See mod_ebcdic and mod_charset for more thought-out examples. This
+ * one is just so Jeff can learn how a module works and experiment with
+ * basic character set recoding configuration.
+ *
+ * !!!This is an extremely cheap ripoff of mod_charset.c from Russian Apache!!!
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+
+#include "http_core.h"
+#include "http_log.h"
+#include "http_main.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "util_charset.h"
+#include "apr_buckets.h"
+#include "util_filter.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+#include "apr_xlate.h"
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#define OUTPUT_XLATE_BUF_SIZE (16*1024) /* size of translation buffer used on output */
+#define INPUT_XLATE_BUF_SIZE (8*1024) /* size of translation buffer used on input */
+
+#define XLATE_MIN_BUFF_LEFT 128 /* flush once there is no more than this much
+ * space left in the translation buffer
+ */
+
+#define FATTEST_CHAR 8 /* we don't handle chars wider than this that straddle
+ * two buckets
+ */
+
+/* extended error status codes; this is used in addition to an apr_status_t to
+ * track errors in the translation filter
+ */
+typedef enum {
+ EES_INIT = 0, /* no error info yet; value must be 0 for easy init */
+ EES_LIMIT, /* built-in restriction encountered */
+ EES_INCOMPLETE_CHAR, /* incomplete multi-byte char at end of content */
+ EES_BUCKET_READ,
+ EES_DOWNSTREAM, /* something bad happened in a filter below xlate */
+ EES_BAD_INPUT /* input data invalid */
+} ees_t;
+
+/* registered name of the output translation filter */
+#define XLATEOUT_FILTER_NAME "XLATEOUT"
+/* registered name of input translation filter */
+#define XLATEIN_FILTER_NAME "XLATEIN"
+
+typedef struct charset_dir_t {
+ const char *charset_source; /* source encoding */
+ const char *charset_default; /* how to ship on wire */
+ /** module does ap_add_*_filter()? */
+ enum {IA_INIT, IA_IMPADD, IA_NOIMPADD} implicit_add;
+ /** treat all mimetypes as text? */
+ enum {FX_INIT, FX_FORCE, FX_NOFORCE} force_xlate;
+} charset_dir_t;
+
+/* charset_filter_ctx_t is created for each filter instance; because the same
+ * filter code is used for translating in both directions, we need this context
+ * data to tell the filter which translation handle to use; it also can hold a
+ * character which was split between buckets
+ */
+typedef struct charset_filter_ctx_t {
+ apr_xlate_t *xlate;
+ int is_sb; /* single-byte translation? */
+ charset_dir_t *dc;
+ ees_t ees; /* extended error status */
+ apr_size_t saved;
+ char buf[FATTEST_CHAR]; /* we want to be able to build a complete char here */
+ int ran; /* has filter instance run before? */
+ int noop; /* should we pass brigades through unchanged? */
+ char *tmp; /* buffer for input filtering */
+ apr_bucket_brigade *bb; /* input buckets we couldn't finish translating */
+ apr_bucket_brigade *tmpbb; /* used for passing downstream */
+} charset_filter_ctx_t;
+
+/* charset_req_t is available via r->request_config if any translation is
+ * being performed
+ */
+typedef struct charset_req_t {
+ charset_dir_t *dc;
+ charset_filter_ctx_t *output_ctx, *input_ctx;
+} charset_req_t;
+
+module AP_MODULE_DECLARE_DATA charset_lite_module;
+
+static void *create_charset_dir_conf(apr_pool_t *p,char *dummy)
+{
+ charset_dir_t *dc = (charset_dir_t *)apr_pcalloc(p,sizeof(charset_dir_t));
+
+ return dc;
+}
+
+static void *merge_charset_dir_conf(apr_pool_t *p, void *basev, void *overridesv)
+{
+ charset_dir_t *a = (charset_dir_t *)apr_pcalloc (p, sizeof(charset_dir_t));
+ charset_dir_t *base = (charset_dir_t *)basev,
+ *over = (charset_dir_t *)overridesv;
+
+ /* If it is defined in the current container, use it. Otherwise, use the one
+ * from the enclosing container.
+ */
+
+ a->charset_default =
+ over->charset_default ? over->charset_default : base->charset_default;
+ a->charset_source =
+ over->charset_source ? over->charset_source : base->charset_source;
+ a->implicit_add =
+ over->implicit_add != IA_INIT ? over->implicit_add : base->implicit_add;
+ a->force_xlate=
+ over->force_xlate != FX_INIT ? over->force_xlate : base->force_xlate;
+ return a;
+}
+
+/* CharsetSourceEnc charset
+ */
+static const char *add_charset_source(cmd_parms *cmd, void *in_dc,
+ const char *name)
+{
+ charset_dir_t *dc = in_dc;
+
+ dc->charset_source = name;
+ return NULL;
+}
+
+/* CharsetDefault charset
+ */
+static const char *add_charset_default(cmd_parms *cmd, void *in_dc,
+ const char *name)
+{
+ charset_dir_t *dc = in_dc;
+
+ dc->charset_default = name;
+ return NULL;
+}
+
+/* CharsetOptions optionflag...
+ */
+static const char *add_charset_options(cmd_parms *cmd, void *in_dc,
+ const char *flag)
+{
+ charset_dir_t *dc = in_dc;
+
+ if (!strcasecmp(flag, "ImplicitAdd")) {
+ dc->implicit_add = IA_IMPADD;
+ }
+ else if (!strcasecmp(flag, "NoImplicitAdd")) {
+ dc->implicit_add = IA_NOIMPADD;
+ }
+ else if (!strcasecmp(flag, "TranslateAllMimeTypes")) {
+ dc->force_xlate = FX_FORCE;
+ }
+ else if (!strcasecmp(flag, "NoTranslateAllMimeTypes")) {
+ dc->force_xlate = FX_NOFORCE;
+ }
+ else {
+ return apr_pstrcat(cmd->temp_pool,
+ "Invalid CharsetOptions option: ",
+ flag,
+ NULL);
+ }
+
+ return NULL;
+}
+
+/* find_code_page() is a fixup hook that checks if the module is
+ * configured and the input or output potentially need to be translated.
+ * If so, context is initialized for the filters.
+ */
+static int find_code_page(request_rec *r)
+{
+ charset_dir_t *dc = ap_get_module_config(r->per_dir_config,
+ &charset_lite_module);
+ charset_req_t *reqinfo;
+ charset_filter_ctx_t *input_ctx, *output_ctx;
+ apr_status_t rv;
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
+ "uri: %s file: %s method: %d "
+ "imt: %s flags: %s%s%s %s->%s",
+ r->uri,
+ r->filename ? r->filename : "(none)",
+ r->method_number,
+ r->content_type ? r->content_type : "(unknown)",
+ r->main ? "S" : "", /* S if subrequest */
+ r->prev ? "R" : "", /* R if redirect */
+ r->proxyreq ? "P" : "", /* P if proxy */
+ dc->charset_source, dc->charset_default);
+
+ /* If we don't have a full directory configuration, bail out.
+ */
+ if (!dc->charset_source || !dc->charset_default) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01448)
+ "incomplete configuration: src %s, dst %s",
+ dc->charset_source ? dc->charset_source : "unspecified",
+ dc->charset_default ? dc->charset_default : "unspecified");
+ return DECLINED;
+ }
+
+ /* catch proxy requests */
+ if (r->proxyreq) {
+ return DECLINED;
+ }
+
+ /* mod_rewrite indicators */
+ if (r->filename
+ && (!strncmp(r->filename, "redirect:", 9)
+ || !strncmp(r->filename, "gone:", 5)
+ || !strncmp(r->filename, "passthrough:", 12)
+ || !strncmp(r->filename, "forbidden:", 10))) {
+ return DECLINED;
+ }
+
+ /* no translation when server and network charsets are set to the same value */
+ if (!strcasecmp(dc->charset_source, dc->charset_default)) {
+ return DECLINED;
+ }
+
+ /* Get storage for the request data and the output filter context.
+ * We rarely need the input filter context, so allocate that separately.
+ */
+ reqinfo = (charset_req_t *)apr_pcalloc(r->pool,
+ sizeof(charset_req_t) +
+ sizeof(charset_filter_ctx_t));
+ output_ctx = (charset_filter_ctx_t *)(reqinfo + 1);
+
+ reqinfo->dc = dc;
+ output_ctx->dc = dc;
+ output_ctx->tmpbb = apr_brigade_create(r->pool,
+ r->connection->bucket_alloc);
+ ap_set_module_config(r->request_config, &charset_lite_module, reqinfo);
+
+ reqinfo->output_ctx = output_ctx;
+
+ switch (r->method_number) {
+ case M_PUT:
+ case M_POST:
+ /* Set up input translation. Note: A request body can be included
+ * with the OPTIONS method, but for now we don't set up translation
+ * of it.
+ */
+ input_ctx = apr_pcalloc(r->pool, sizeof(charset_filter_ctx_t));
+ input_ctx->bb = apr_brigade_create(r->pool,
+ r->connection->bucket_alloc);
+ input_ctx->tmp = apr_palloc(r->pool, INPUT_XLATE_BUF_SIZE);
+ input_ctx->dc = dc;
+ reqinfo->input_ctx = input_ctx;
+ rv = apr_xlate_open(&input_ctx->xlate, dc->charset_source,
+ dc->charset_default, r->pool);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01449)
+ "can't open translation %s->%s",
+ dc->charset_default, dc->charset_source);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ if (apr_xlate_sb_get(input_ctx->xlate, &input_ctx->is_sb) != APR_SUCCESS) {
+ input_ctx->is_sb = 0;
+ }
+ }
+
+ return DECLINED;
+}
+
+static int configured_in_list(request_rec *r, const char *filter_name,
+ struct ap_filter_t *filter_list)
+{
+ struct ap_filter_t *filter = filter_list;
+
+ while (filter) {
+ if (!strcasecmp(filter_name, filter->frec->name)) {
+ return 1;
+ }
+ filter = filter->next;
+ }
+ return 0;
+}
+
+static int configured_on_input(request_rec *r, const char *filter_name)
+{
+ return configured_in_list(r, filter_name, r->input_filters);
+}
+
+static int configured_on_output(request_rec *r, const char *filter_name)
+{
+ return configured_in_list(r, filter_name, r->output_filters);
+}
+
+/* xlate_insert_filter() is a filter hook which decides whether or not
+ * to insert a translation filter for the current request.
+ */
+static void xlate_insert_filter(request_rec *r)
+{
+ /* Hey... don't be so quick to use reqinfo->dc here; reqinfo may be NULL */
+ charset_req_t *reqinfo = ap_get_module_config(r->request_config,
+ &charset_lite_module);
+ charset_dir_t *dc = ap_get_module_config(r->per_dir_config,
+ &charset_lite_module);
+
+ if (dc && (dc->implicit_add == IA_NOIMPADD)) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE6, 0, r,
+ "xlate output filter not added implicitly because "
+ "CharsetOptions included 'NoImplicitAdd'");
+ return;
+ }
+
+ if (reqinfo) {
+ if (reqinfo->output_ctx && !configured_on_output(r, XLATEOUT_FILTER_NAME)) {
+ ap_add_output_filter(XLATEOUT_FILTER_NAME, reqinfo->output_ctx, r,
+ r->connection);
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
+ "xlate output filter not added implicitly because %s",
+ !reqinfo->output_ctx ?
+ "no output configuration available" :
+ "another module added the filter");
+
+ if (reqinfo->input_ctx && !configured_on_input(r, XLATEIN_FILTER_NAME)) {
+ ap_add_input_filter(XLATEIN_FILTER_NAME, reqinfo->input_ctx, r,
+ r->connection);
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
+ "xlate input filter not added implicitly because %s",
+ !reqinfo->input_ctx ?
+ "no input configuration available" :
+ "another module added the filter");
+ }
+}
+
+/* stuff that sucks that I know of:
+ *
+ * bucket handling:
+ * why create an eos bucket when we see it come down the stream? just send the one
+ * passed as input... news flash: this will be fixed when xlate_out_filter() starts
+ * using the more generic xlate_brigade()
+ *
+ * translation mechanics:
+ * we don't handle characters that straddle more than two buckets; an error
+ * will be generated
+ */
+
+static apr_status_t send_bucket_downstream(ap_filter_t *f, apr_bucket *b)
+{
+ charset_filter_ctx_t *ctx = f->ctx;
+ apr_status_t rv;
+
+ APR_BRIGADE_INSERT_TAIL(ctx->tmpbb, b);
+ rv = ap_pass_brigade(f->next, ctx->tmpbb);
+ if (rv != APR_SUCCESS) {
+ ctx->ees = EES_DOWNSTREAM;
+ }
+ apr_brigade_cleanup(ctx->tmpbb);
+ return rv;
+}
+
+/* send_downstream() is passed the translated data; it puts it in a single-
+ * bucket brigade and passes the brigade to the next filter
+ */
+static apr_status_t send_downstream(ap_filter_t *f, const char *tmp, apr_size_t len)
+{
+ request_rec *r = f->r;
+ conn_rec *c = r->connection;
+ apr_bucket *b;
+
+ b = apr_bucket_transient_create(tmp, len, c->bucket_alloc);
+ return send_bucket_downstream(f, b);
+}
+
+static apr_status_t send_eos(ap_filter_t *f)
+{
+ request_rec *r = f->r;
+ conn_rec *c = r->connection;
+ apr_bucket_brigade *bb;
+ apr_bucket *b;
+ charset_filter_ctx_t *ctx = f->ctx;
+ apr_status_t rv;
+
+ bb = apr_brigade_create(r->pool, c->bucket_alloc);
+ b = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ rv = ap_pass_brigade(f->next, bb);
+ if (rv != APR_SUCCESS) {
+ ctx->ees = EES_DOWNSTREAM;
+ }
+ return rv;
+}
+
+static apr_status_t set_aside_partial_char(charset_filter_ctx_t *ctx,
+ const char *partial,
+ apr_size_t partial_len)
+{
+ apr_status_t rv;
+
+ if (sizeof(ctx->buf) > partial_len) {
+ ctx->saved = partial_len;
+ memcpy(ctx->buf, partial, partial_len);
+ rv = APR_SUCCESS;
+ }
+ else {
+ rv = APR_INCOMPLETE;
+ ctx->ees = EES_LIMIT; /* we don't handle chars this wide which straddle
+ * buckets
+ */
+ }
+ return rv;
+}
+
+static apr_status_t finish_partial_char(charset_filter_ctx_t *ctx,
+ /* input buffer: */
+ const char **cur_str,
+ apr_size_t *cur_len,
+ /* output buffer: */
+ char **out_str,
+ apr_size_t *out_len)
+{
+ apr_status_t rv;
+ apr_size_t tmp_input_len;
+
+ /* Keep adding bytes from the input string to the saved string until we
+ * 1) finish the input char
+ * 2) get an error
+ * or 3) run out of bytes to add
+ */
+
+ do {
+ ctx->buf[ctx->saved] = **cur_str;
+ ++ctx->saved;
+ ++*cur_str;
+ --*cur_len;
+ tmp_input_len = ctx->saved;
+ rv = apr_xlate_conv_buffer(ctx->xlate,
+ ctx->buf,
+ &tmp_input_len,
+ *out_str,
+ out_len);
+ } while (rv == APR_INCOMPLETE && *cur_len);
+
+ if (rv == APR_SUCCESS) {
+ ctx->saved = 0;
+ }
+ else {
+ ctx->ees = EES_LIMIT; /* code isn't smart enough to handle chars
+ * straddling more than two buckets
+ */
+ }
+
+ return rv;
+}
+
+static void log_xlate_error(ap_filter_t *f, apr_status_t rv)
+{
+ charset_filter_ctx_t *ctx = f->ctx;
+ const char *msg;
+ char msgbuf[100];
+ apr_size_t len;
+
+ switch(ctx->ees) {
+ case EES_LIMIT:
+ rv = 0;
+ msg = APLOGNO(02193) "xlate filter - a built-in restriction was encountered";
+ break;
+ case EES_BAD_INPUT:
+ rv = 0;
+ msg = APLOGNO(02194) "xlate filter - an input character was invalid";
+ break;
+ case EES_BUCKET_READ:
+ rv = 0;
+ msg = APLOGNO(02195) "xlate filter - bucket read routine failed";
+ break;
+ case EES_INCOMPLETE_CHAR:
+ rv = 0;
+ strcpy(msgbuf, APLOGNO(02196) "xlate filter - incomplete char at end of input - ");
+ len = ctx->saved;
+
+ /* We must ensure not to process more than what would fit in the
+ * remaining of the destination buffer, including terminating NULL */
+ if (len > (sizeof(msgbuf) - strlen(msgbuf) - 1) / 2)
+ len = (sizeof(msgbuf) - strlen(msgbuf) - 1) / 2;
+
+ ap_bin2hex(ctx->buf, len, msgbuf + strlen(msgbuf));
+ msg = msgbuf;
+ break;
+ case EES_DOWNSTREAM:
+ msg = APLOGNO(02197) "xlate filter - an error occurred in a lower filter";
+ break;
+ default:
+ msg = APLOGNO(02198) "xlate filter - returning error";
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(02997) "%s", msg);
+}
+
+/* chk_filter_chain() is called once per filter instance; it tries to
+ * determine if the current filter instance should be disabled because
+ * its translation is incompatible with the translation of an existing
+ * instance of the translate filter
+ *
+ * Example bad scenario:
+ *
+ * configured filter chain for the request:
+ * INCLUDES XLATEOUT(8859-1->UTS-16)
+ * configured filter chain for the subrequest:
+ * XLATEOUT(8859-1->UTS-16)
+ *
+ * When the subrequest is processed, the filter chain will be
+ * XLATEOUT(8859-1->UTS-16) XLATEOUT(8859-1->UTS-16)
+ * This makes no sense, so the instance of XLATEOUT added for the
+ * subrequest will be noop-ed.
+ *
+ * Example good scenario:
+ *
+ * configured filter chain for the request:
+ * INCLUDES XLATEOUT(8859-1->UTS-16)
+ * configured filter chain for the subrequest:
+ * XLATEOUT(IBM-1047->8859-1)
+ *
+ * When the subrequest is processed, the filter chain will be
+ * XLATEOUT(IBM-1047->8859-1) XLATEOUT(8859-1->UTS-16)
+ * This makes sense, so the instance of XLATEOUT added for the
+ * subrequest will be left alone and it will translate from
+ * IBM-1047->8859-1.
+ */
+static void chk_filter_chain(ap_filter_t *f)
+{
+ ap_filter_t *curf;
+ charset_filter_ctx_t *curctx, *last_xlate_ctx = NULL,
+ *ctx = f->ctx;
+ int output = !strcasecmp(f->frec->name, XLATEOUT_FILTER_NAME);
+
+ if (ctx->noop) {
+ return;
+ }
+
+ /* walk the filter chain; see if it makes sense for our filter to
+ * do any translation
+ */
+ curf = output ? f->r->output_filters : f->r->input_filters;
+ while (curf) {
+ if (!strcasecmp(curf->frec->name, f->frec->name) &&
+ curf->ctx) {
+ curctx = (charset_filter_ctx_t *)curf->ctx;
+ if (!last_xlate_ctx) {
+ last_xlate_ctx = curctx;
+ }
+ else {
+ if (strcmp(last_xlate_ctx->dc->charset_default,
+ curctx->dc->charset_source)) {
+ /* incompatible translation
+ * if our filter instance is incompatible with an instance
+ * already in place, noop our instance
+ * Notes:
+ * . We are only willing to noop our own instance.
+ * . It is possible to noop another instance which has not
+ * yet run, but this is not currently implemented.
+ * Hopefully it will not be needed.
+ * . It is not possible to noop an instance which has
+ * already run.
+ */
+ if (last_xlate_ctx == f->ctx) {
+ last_xlate_ctx->noop = 1;
+ if (APLOGrtrace1(f->r)) {
+ const char *symbol = output ? "->" : "<-";
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG,
+ 0, f->r, APLOGNO(01451)
+ "%s %s - disabling "
+ "translation %s%s%s; existing "
+ "translation %s%s%s",
+ f->r->uri ? "uri" : "file",
+ f->r->uri ? f->r->uri : f->r->filename,
+ last_xlate_ctx->dc->charset_source,
+ symbol,
+ last_xlate_ctx->dc->charset_default,
+ curctx->dc->charset_source,
+ symbol,
+ curctx->dc->charset_default);
+ }
+ }
+ else {
+ const char *symbol = output ? "->" : "<-";
+
+ ap_log_rerror(APLOG_MARK, APLOG_ERR,
+ 0, f->r, APLOGNO(01452)
+ "chk_filter_chain() - can't disable "
+ "translation %s%s%s; existing "
+ "translation %s%s%s",
+ last_xlate_ctx->dc->charset_source,
+ symbol,
+ last_xlate_ctx->dc->charset_default,
+ curctx->dc->charset_source,
+ symbol,
+ curctx->dc->charset_default);
+ }
+ break;
+ }
+ }
+ }
+ curf = curf->next;
+ }
+}
+
+/* xlate_brigade() is used to filter request and response bodies
+ *
+ * we'll stop when one of the following occurs:
+ * . we run out of buckets
+ * . we run out of space in the output buffer
+ * . we hit an error or metadata
+ *
+ * inputs:
+ * bb: brigade to process
+ * buffer: storage to hold the translated characters
+ * buffer_avail: size of buffer
+ * (and a few more uninteresting parms)
+ *
+ * outputs:
+ * return value: APR_SUCCESS or some error code
+ * bb: we've removed any buckets representing the
+ * translated characters; the eos bucket, if
+ * present, will be left in the brigade
+ * buffer: filled in with translated characters
+ * buffer_avail: updated with the bytes remaining
+ * hit_eos: did we hit an EOS bucket?
+ */
+static apr_status_t xlate_brigade(charset_filter_ctx_t *ctx,
+ apr_bucket_brigade *bb,
+ char *buffer,
+ apr_size_t *buffer_avail,
+ int *hit_eos)
+{
+ apr_bucket *b = NULL; /* set to NULL only to quiet some gcc */
+ apr_bucket *consumed_bucket;
+ const char *bucket;
+ apr_size_t bytes_in_bucket; /* total bytes read from current bucket */
+ apr_size_t bucket_avail; /* bytes left in current bucket */
+ apr_status_t rv = APR_SUCCESS;
+
+ *hit_eos = 0;
+ bucket_avail = 0;
+ consumed_bucket = NULL;
+ while (1) {
+ if (!bucket_avail) { /* no bytes left to process in the current bucket... */
+ if (consumed_bucket) {
+ apr_bucket_delete(consumed_bucket);
+ consumed_bucket = NULL;
+ }
+ b = APR_BRIGADE_FIRST(bb);
+ if (b == APR_BRIGADE_SENTINEL(bb) ||
+ APR_BUCKET_IS_METADATA(b)) {
+ break;
+ }
+ rv = apr_bucket_read(b, &bucket, &bytes_in_bucket, APR_BLOCK_READ);
+ if (rv != APR_SUCCESS) {
+ ctx->ees = EES_BUCKET_READ;
+ break;
+ }
+ bucket_avail = bytes_in_bucket;
+ consumed_bucket = b; /* for axing when we're done reading it */
+ }
+ if (bucket_avail) {
+ /* We've got data, so translate it. */
+ if (ctx->saved) {
+ /* Rats... we need to finish a partial character from the previous
+ * bucket.
+ *
+ * Strangely, finish_partial_char() increments the input buffer
+ * pointer but does not increment the output buffer pointer.
+ */
+ apr_size_t old_buffer_avail = *buffer_avail;
+ rv = finish_partial_char(ctx,
+ &bucket, &bucket_avail,
+ &buffer, buffer_avail);
+ buffer += old_buffer_avail - *buffer_avail;
+ }
+ else {
+ apr_size_t old_buffer_avail = *buffer_avail;
+ apr_size_t old_bucket_avail = bucket_avail;
+ rv = apr_xlate_conv_buffer(ctx->xlate,
+ bucket, &bucket_avail,
+ buffer,
+ buffer_avail);
+ buffer += old_buffer_avail - *buffer_avail;
+ bucket += old_bucket_avail - bucket_avail;
+
+ if (rv == APR_INCOMPLETE) { /* partial character at end of input */
+ /* We need to save the final byte(s) for next time; we can't
+ * convert it until we look at the next bucket.
+ */
+ rv = set_aside_partial_char(ctx, bucket, bucket_avail);
+ bucket_avail = 0;
+ }
+ }
+ if (rv != APR_SUCCESS) {
+ /* bad input byte or partial char too big to store */
+ break;
+ }
+ if (*buffer_avail < XLATE_MIN_BUFF_LEFT) {
+ /* if any data remains in the current bucket, split there */
+ if (bucket_avail) {
+ apr_bucket_split(b, bytes_in_bucket - bucket_avail);
+ }
+ apr_bucket_delete(b);
+ break;
+ }
+ }
+ }
+
+ if (!APR_BRIGADE_EMPTY(bb)) {
+ b = APR_BRIGADE_FIRST(bb);
+ if (APR_BUCKET_IS_EOS(b)) {
+ /* Leave the eos bucket in the brigade for reporting to
+ * subsequent filters.
+ */
+ *hit_eos = 1;
+ if (ctx->saved) {
+ /* Oops... we have a partial char from the previous bucket
+ * that won't be completed because there's no more data.
+ */
+ rv = APR_INCOMPLETE;
+ ctx->ees = EES_INCOMPLETE_CHAR;
+ }
+ }
+ }
+
+ return rv;
+}
+
+/* xlate_out_filter() handles (almost) arbitrary conversions from one charset
+ * to another...
+ * translation is determined in the fixup hook (find_code_page), which is
+ * where the filter's context data is set up... the context data gives us
+ * the translation handle
+ */
+static apr_status_t xlate_out_filter(ap_filter_t *f, apr_bucket_brigade *bb)
+{
+ charset_req_t *reqinfo = ap_get_module_config(f->r->request_config,
+ &charset_lite_module);
+ charset_dir_t *dc = ap_get_module_config(f->r->per_dir_config,
+ &charset_lite_module);
+ charset_filter_ctx_t *ctx = f->ctx;
+ apr_bucket *dptr, *consumed_bucket;
+ const char *cur_str;
+ apr_size_t cur_len, cur_avail;
+ char tmp[OUTPUT_XLATE_BUF_SIZE];
+ apr_size_t space_avail;
+ int done;
+ apr_status_t rv = APR_SUCCESS;
+
+ if (!ctx) {
+ /* this is SetOutputFilter path; grab the preallocated context,
+ * if any; note that if we decided not to do anything in an earlier
+ * handler, we won't even have a reqinfo
+ */
+ if (reqinfo) {
+ ctx = f->ctx = reqinfo->output_ctx;
+ reqinfo->output_ctx = NULL; /* prevent SNAFU if user coded us twice
+ * in the filter chain; we can't have two
+ * instances using the same context
+ */
+ }
+ if (!ctx) { /* no idea how to translate; don't do anything */
+ ctx = f->ctx = apr_pcalloc(f->r->pool, sizeof(charset_filter_ctx_t));
+ ctx->dc = dc;
+ ctx->noop = 1;
+ }
+ }
+
+ /* Check the mime type to see if translation should be performed.
+ */
+ if (!ctx->noop && ctx->xlate == NULL) {
+ const char *mime_type = f->r->content_type;
+
+ if (mime_type && (ap_cstr_casecmpn(mime_type, "text/", 5) == 0 ||
+#if APR_CHARSET_EBCDIC
+ /* On an EBCDIC machine, be willing to translate mod_autoindex-
+ * generated output. Otherwise, it doesn't look too cool.
+ *
+ * XXX This isn't a perfect fix because this doesn't trigger us
+ * to convert from the charset of the source code to ASCII. The
+ * general solution seems to be to allow a generator to set an
+ * indicator in the r specifying that the body is coded in the
+ * implementation character set (i.e., the charset of the source
+ * code). This would get several different types of documents
+ * translated properly: mod_autoindex output, mod_status output,
+ * mod_info output, hard-coded error documents, etc.
+ */
+ strcmp(mime_type, DIR_MAGIC_TYPE) == 0 ||
+#endif
+ ap_cstr_casecmpn(mime_type, "message/", 8) == 0 ||
+ dc->force_xlate == FX_FORCE)) {
+
+ rv = apr_xlate_open(&ctx->xlate,
+ dc->charset_default, dc->charset_source, f->r->pool);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(01453)
+ "can't open translation %s->%s",
+ dc->charset_source, dc->charset_default);
+ ctx->noop = 1;
+ }
+ else {
+ if (apr_xlate_sb_get(ctx->xlate, &ctx->is_sb) != APR_SUCCESS) {
+ ctx->is_sb = 0;
+ }
+ }
+ }
+ else {
+ ctx->noop = 1;
+ if (mime_type) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE6, 0, f->r,
+ "mime type is %s; no translation selected",
+ mime_type);
+ }
+ }
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE6, 0, f->r,
+ "xlate_out_filter() - "
+ "charset_source: %s charset_default: %s",
+ dc && dc->charset_source ? dc->charset_source : "(none)",
+ dc && dc->charset_default ? dc->charset_default : "(none)");
+
+ if (!ctx->ran) { /* filter never ran before */
+ chk_filter_chain(f);
+ ctx->ran = 1;
+ if (!ctx->noop && !ctx->is_sb) {
+ /* We're not converting between two single-byte charsets, so unset
+ * Content-Length since it is unlikely to remain the same.
+ */
+ apr_table_unset(f->r->headers_out, "Content-Length");
+ }
+ }
+
+ if (ctx->noop) {
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ dptr = APR_BRIGADE_FIRST(bb);
+ done = 0;
+ cur_len = 0;
+ space_avail = sizeof(tmp);
+ consumed_bucket = NULL;
+ while (!done) {
+ if (!cur_len) { /* no bytes left to process in the current bucket... */
+ if (consumed_bucket) {
+ apr_bucket_delete(consumed_bucket);
+ consumed_bucket = NULL;
+ }
+ if (dptr == APR_BRIGADE_SENTINEL(bb)) {
+ break;
+ }
+ if (APR_BUCKET_IS_EOS(dptr)) {
+ cur_len = -1; /* XXX yuck, but that tells us to send
+ * eos down; when we minimize our bb construction
+ * we'll fix this crap */
+ if (ctx->saved) {
+ /* Oops... we have a partial char from the previous bucket
+ * that won't be completed because there's no more data.
+ */
+ rv = APR_INCOMPLETE;
+ ctx->ees = EES_INCOMPLETE_CHAR;
+ }
+ break;
+ }
+ if (APR_BUCKET_IS_METADATA(dptr)) {
+ apr_bucket *metadata_bucket;
+ metadata_bucket = dptr;
+ dptr = APR_BUCKET_NEXT(dptr);
+ APR_BUCKET_REMOVE(metadata_bucket);
+ rv = send_bucket_downstream(f, metadata_bucket);
+ if (rv != APR_SUCCESS) {
+ done = 1;
+ }
+ continue;
+ }
+ rv = apr_bucket_read(dptr, &cur_str, &cur_len, APR_BLOCK_READ);
+ if (rv != APR_SUCCESS) {
+ ctx->ees = EES_BUCKET_READ;
+ break;
+ }
+ consumed_bucket = dptr; /* for axing when we're done reading it */
+ dptr = APR_BUCKET_NEXT(dptr); /* get ready for when we access the
+ * next bucket */
+ }
+ /* Try to fill up our tmp buffer with translated data. */
+ cur_avail = cur_len;
+
+ if (cur_len) { /* maybe we just hit the end of a pipe (len = 0) ? */
+ if (ctx->saved) {
+ /* Rats... we need to finish a partial character from the previous
+ * bucket.
+ */
+ char *tmp_tmp;
+
+ tmp_tmp = tmp + sizeof(tmp) - space_avail;
+ rv = finish_partial_char(ctx,
+ &cur_str, &cur_len,
+ &tmp_tmp, &space_avail);
+ }
+ else {
+ rv = apr_xlate_conv_buffer(ctx->xlate,
+ cur_str, &cur_avail,
+ tmp + sizeof(tmp) - space_avail, &space_avail);
+
+ /* Update input ptr and len after consuming some bytes */
+ cur_str += cur_len - cur_avail;
+ cur_len = cur_avail;
+
+ if (rv == APR_INCOMPLETE) { /* partial character at end of input */
+ /* We need to save the final byte(s) for next time; we can't
+ * convert it until we look at the next bucket.
+ */
+ rv = set_aside_partial_char(ctx, cur_str, cur_len);
+ cur_len = 0;
+ }
+ }
+ }
+
+ if (rv != APR_SUCCESS) {
+ /* bad input byte or partial char too big to store */
+ done = 1;
+ }
+
+ if (space_avail < XLATE_MIN_BUFF_LEFT) {
+ /* It is time to flush, as there is not enough space left in the
+ * current output buffer to bother with converting more data.
+ */
+ rv = send_downstream(f, tmp, sizeof(tmp) - space_avail);
+ if (rv != APR_SUCCESS) {
+ done = 1;
+ }
+
+ /* tmp is now empty */
+ space_avail = sizeof(tmp);
+ }
+ }
+
+ if (rv == APR_SUCCESS) {
+ if (space_avail < sizeof(tmp)) { /* gotta write out what we converted */
+ rv = send_downstream(f, tmp, sizeof(tmp) - space_avail);
+ }
+ }
+ if (rv == APR_SUCCESS) {
+ if (cur_len == -1) {
+ rv = send_eos(f);
+ }
+ }
+ else {
+ log_xlate_error(f, rv);
+ }
+
+ return rv;
+}
+
+static apr_status_t xlate_in_filter(ap_filter_t *f, apr_bucket_brigade *bb,
+ ap_input_mode_t mode, apr_read_type_e block,
+ apr_off_t readbytes)
+{
+ apr_status_t rv;
+ charset_req_t *reqinfo = ap_get_module_config(f->r->request_config,
+ &charset_lite_module);
+ charset_dir_t *dc = ap_get_module_config(f->r->per_dir_config,
+ &charset_lite_module);
+ charset_filter_ctx_t *ctx = f->ctx;
+ apr_size_t buffer_size;
+ int hit_eos;
+
+ /* just get out of the way of things we don't want. */
+ if (mode != AP_MODE_READBYTES) {
+ return ap_get_brigade(f->next, bb, mode, block, readbytes);
+ }
+
+ if (!ctx) {
+ /* this is SetInputFilter path; grab the preallocated context,
+ * if any; note that if we decided not to do anything in an earlier
+ * handler, we won't even have a reqinfo
+ */
+ if (reqinfo) {
+ ctx = f->ctx = reqinfo->input_ctx;
+ reqinfo->input_ctx = NULL; /* prevent SNAFU if user coded us twice
+ * in the filter chain; we can't have two
+ * instances using the same context
+ */
+ }
+ if (!ctx) { /* no idea how to translate; don't do anything */
+ ctx = f->ctx = apr_pcalloc(f->r->pool, sizeof(charset_filter_ctx_t));
+ ctx->dc = dc;
+ ctx->noop = 1;
+ }
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE6, 0, f->r,
+ "xlate_in_filter() - "
+ "charset_source: %s charset_default: %s",
+ dc && dc->charset_source ? dc->charset_source : "(none)",
+ dc && dc->charset_default ? dc->charset_default : "(none)");
+
+ if (!ctx->ran) { /* filter never ran before */
+ chk_filter_chain(f);
+ ctx->ran = 1;
+ if (!ctx->noop && !ctx->is_sb
+ && apr_table_get(f->r->headers_in, "Content-Length")) {
+ /* A Content-Length header is present, but it won't be valid after
+ * conversion because we're not converting between two single-byte
+ * charsets. This will affect most CGI scripts and may affect
+ * some modules.
+ * Content-Length can't be unset here because that would break
+ * being able to read the request body.
+ * Processing of chunked request bodies is not impacted by this
+ * filter since the length was not declared anyway.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, f->r,
+ "Request body length may change, resulting in "
+ "misprocessing by some modules or scripts");
+ }
+ }
+
+ if (ctx->noop) {
+ return ap_get_brigade(f->next, bb, mode, block, readbytes);
+ }
+
+ if (APR_BRIGADE_EMPTY(ctx->bb)) {
+ if ((rv = ap_get_brigade(f->next, bb, mode, block,
+ readbytes)) != APR_SUCCESS) {
+ return rv;
+ }
+ }
+ else {
+ APR_BRIGADE_PREPEND(bb, ctx->bb); /* first use the leftovers */
+ }
+
+ buffer_size = INPUT_XLATE_BUF_SIZE;
+ rv = xlate_brigade(ctx, bb, ctx->tmp, &buffer_size, &hit_eos);
+ if (rv == APR_SUCCESS) {
+ if (!hit_eos) {
+ /* move anything leftover into our context for next time;
+ * we don't currently "set aside" since the data came from
+ * down below, but I suspect that for long-term we need to
+ * do that
+ */
+ APR_BRIGADE_CONCAT(ctx->bb, bb);
+ }
+ if (buffer_size < INPUT_XLATE_BUF_SIZE) { /* do we have output? */
+ apr_bucket *e;
+
+ e = apr_bucket_heap_create(ctx->tmp,
+ INPUT_XLATE_BUF_SIZE - buffer_size,
+ NULL, f->r->connection->bucket_alloc);
+ /* make sure we insert at the head, because there may be
+ * an eos bucket already there, and the eos bucket should
+ * come after the data
+ */
+ APR_BRIGADE_INSERT_HEAD(bb, e);
+ }
+ else {
+ /* XXX need to get some more data... what if the last brigade
+ * we got had only the first byte of a multibyte char? we need
+ * to grab more data from the network instead of returning an
+ * empty brigade
+ */
+ }
+ /* If we have any metadata at the head of ctx->bb, go ahead and move it
+ * onto the end of bb to be returned to our caller.
+ */
+ if (!APR_BRIGADE_EMPTY(ctx->bb)) {
+ apr_bucket *b = APR_BRIGADE_FIRST(ctx->bb);
+ while (b != APR_BRIGADE_SENTINEL(ctx->bb)
+ && APR_BUCKET_IS_METADATA(b)) {
+ APR_BUCKET_REMOVE(b);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ b = APR_BRIGADE_FIRST(ctx->bb);
+ }
+ }
+ }
+ else {
+ log_xlate_error(f, rv);
+ }
+
+ return rv;
+}
+
+static const command_rec cmds[] =
+{
+ AP_INIT_TAKE1("CharsetSourceEnc",
+ add_charset_source,
+ NULL,
+ OR_FILEINFO,
+ "source (html,cgi,ssi) file charset"),
+ AP_INIT_TAKE1("CharsetDefault",
+ add_charset_default,
+ NULL,
+ OR_FILEINFO,
+ "name of default charset"),
+ AP_INIT_ITERATE("CharsetOptions",
+ add_charset_options,
+ NULL,
+ OR_FILEINFO,
+ "valid options: ImplicitAdd, NoImplicitAdd, TranslateAllMimeTypes, "
+ "NoTranslateAllMimeTypes"),
+ {NULL}
+};
+
+static void charset_register_hooks(apr_pool_t *p)
+{
+ ap_hook_fixups(find_code_page, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_insert_filter(xlate_insert_filter, NULL, NULL, APR_HOOK_REALLY_LAST);
+ ap_register_output_filter(XLATEOUT_FILTER_NAME, xlate_out_filter, NULL,
+ AP_FTYPE_RESOURCE);
+ ap_register_input_filter(XLATEIN_FILTER_NAME, xlate_in_filter, NULL,
+ AP_FTYPE_RESOURCE);
+}
+
+AP_DECLARE_MODULE(charset_lite) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_charset_dir_conf,
+ merge_charset_dir_conf,
+ NULL,
+ NULL,
+ cmds,
+ charset_register_hooks
+};
+
diff --git a/modules/filters/mod_charset_lite.dep b/modules/filters/mod_charset_lite.dep
new file mode 100644
index 0000000..3356086
--- /dev/null
+++ b/modules/filters/mod_charset_lite.dep
@@ -0,0 +1,60 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_charset_lite.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_charset_lite.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/filters/mod_charset_lite.dsp b/modules/filters/mod_charset_lite.dsp
new file mode 100644
index 0000000..0fd575f
--- /dev/null
+++ b/modules/filters/mod_charset_lite.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_charset_lite" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_charset_lite - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_charset_lite.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_charset_lite.mak" CFG="mod_charset_lite - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_charset_lite - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_charset_lite - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_charset_lite - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_charset_lite_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_charset_lite.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_charset_lite.so" /d LONG_NAME="charset_lite_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_charset_lite.so" /base:@..\..\os\win32\BaseAddr.ref,mod_charset_lite.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_charset_lite.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_charset_lite - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_charset_lite_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_charset_lite.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_charset_lite.so" /d LONG_NAME="charset_lite_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_charset_lite.so" /base:@..\..\os\win32\BaseAddr.ref,mod_charset_lite.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_charset_lite.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_charset_lite - Win32 Release"
+# Name "mod_charset_lite - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_charset_lite.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/filters/mod_charset_lite.exp b/modules/filters/mod_charset_lite.exp
new file mode 100644
index 0000000..3f0bf14
--- /dev/null
+++ b/modules/filters/mod_charset_lite.exp
@@ -0,0 +1 @@
+charset_lite_module
diff --git a/modules/filters/mod_charset_lite.mak b/modules/filters/mod_charset_lite.mak
new file mode 100644
index 0000000..b252470
--- /dev/null
+++ b/modules/filters/mod_charset_lite.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_charset_lite.dsp
+!IF "$(CFG)" == ""
+CFG=mod_charset_lite - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_charset_lite - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_charset_lite - Win32 Release" && "$(CFG)" != "mod_charset_lite - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_charset_lite.mak" CFG="mod_charset_lite - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_charset_lite - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_charset_lite - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_charset_lite - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_charset_lite.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_charset_lite.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_charset_lite.obj"
+ -@erase "$(INTDIR)\mod_charset_lite.res"
+ -@erase "$(INTDIR)\mod_charset_lite_src.idb"
+ -@erase "$(INTDIR)\mod_charset_lite_src.pdb"
+ -@erase "$(OUTDIR)\mod_charset_lite.exp"
+ -@erase "$(OUTDIR)\mod_charset_lite.lib"
+ -@erase "$(OUTDIR)\mod_charset_lite.pdb"
+ -@erase "$(OUTDIR)\mod_charset_lite.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_charset_lite_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_charset_lite.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_charset_lite.so" /d LONG_NAME="charset_lite_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_charset_lite.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_charset_lite.pdb" /debug /out:"$(OUTDIR)\mod_charset_lite.so" /implib:"$(OUTDIR)\mod_charset_lite.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_charset_lite.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_charset_lite.obj" \
+ "$(INTDIR)\mod_charset_lite.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_charset_lite.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_charset_lite.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_charset_lite.so"
+ if exist .\Release\mod_charset_lite.so.manifest mt.exe -manifest .\Release\mod_charset_lite.so.manifest -outputresource:.\Release\mod_charset_lite.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_charset_lite - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_charset_lite.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_charset_lite.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_charset_lite.obj"
+ -@erase "$(INTDIR)\mod_charset_lite.res"
+ -@erase "$(INTDIR)\mod_charset_lite_src.idb"
+ -@erase "$(INTDIR)\mod_charset_lite_src.pdb"
+ -@erase "$(OUTDIR)\mod_charset_lite.exp"
+ -@erase "$(OUTDIR)\mod_charset_lite.lib"
+ -@erase "$(OUTDIR)\mod_charset_lite.pdb"
+ -@erase "$(OUTDIR)\mod_charset_lite.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_charset_lite_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_charset_lite.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_charset_lite.so" /d LONG_NAME="charset_lite_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_charset_lite.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_charset_lite.pdb" /debug /out:"$(OUTDIR)\mod_charset_lite.so" /implib:"$(OUTDIR)\mod_charset_lite.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_charset_lite.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_charset_lite.obj" \
+ "$(INTDIR)\mod_charset_lite.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_charset_lite.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_charset_lite.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_charset_lite.so"
+ if exist .\Debug\mod_charset_lite.so.manifest mt.exe -manifest .\Debug\mod_charset_lite.so.manifest -outputresource:.\Debug\mod_charset_lite.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_charset_lite.dep")
+!INCLUDE "mod_charset_lite.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_charset_lite.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_charset_lite - Win32 Release" || "$(CFG)" == "mod_charset_lite - Win32 Debug"
+
+!IF "$(CFG)" == "mod_charset_lite - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\filters"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_charset_lite - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\filters"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_charset_lite - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\filters"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_charset_lite - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\filters"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_charset_lite - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\filters"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_charset_lite - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\filters"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\filters"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_charset_lite - Win32 Release"
+
+
+"$(INTDIR)\mod_charset_lite.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_charset_lite.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_charset_lite.so" /d LONG_NAME="charset_lite_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_charset_lite - Win32 Debug"
+
+
+"$(INTDIR)\mod_charset_lite.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_charset_lite.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_charset_lite.so" /d LONG_NAME="charset_lite_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_charset_lite.c
+
+"$(INTDIR)\mod_charset_lite.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/filters/mod_data.c b/modules/filters/mod_data.c
new file mode 100644
index 0000000..ddadd1b
--- /dev/null
+++ b/modules/filters/mod_data.c
@@ -0,0 +1,255 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * mod_data.c --- Turn the response into an rfc2397 data URL, suitable for
+ * displaying as inline content on a page.
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_buckets.h"
+#include "apr_base64.h"
+#include "apr_lib.h"
+
+#include "ap_config.h"
+#include "util_filter.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "http_request.h"
+#include "http_protocol.h"
+
+#define DATA_FILTER "DATA"
+
+module AP_MODULE_DECLARE_DATA data_module;
+
+typedef struct data_ctx
+{
+ unsigned char overflow[3];
+ int count;
+ apr_bucket_brigade *bb;
+} data_ctx;
+
+/**
+ * Create a data URL as follows:
+ *
+ * data:[<mime-type>;][charset=<charset>;]base64,<payload>
+ *
+ * Where:
+ *
+ * mime-type: The mime type of the original response.
+ * charset: The optional character set corresponding to the mime type.
+ * payload: A base64 version of the response body.
+ *
+ * The content type of the response is set to text/plain.
+ *
+ * The Content-Length header, if present, is updated with the new content
+ * length based on the increase in size expected from the base64 conversion.
+ * If the Content-Length header is too large to fit into an int, we remove
+ * the Content-Length header instead.
+ */
+static apr_status_t data_out_filter(ap_filter_t *f, apr_bucket_brigade *bb)
+{
+ apr_bucket *e, *ee;
+ request_rec *r = f->r;
+ data_ctx *ctx = f->ctx;
+ apr_status_t rv = APR_SUCCESS;
+
+ /* first time in? create a context */
+ if (!ctx) {
+ char *type;
+ char *charset = NULL;
+ char *end;
+ const char *content_length;
+
+ /* base64-ing won't work on subrequests, it would be nice if
+ * it did. Within subrequests, we have no EOS to check for,
+ * so we don't know when to flush the tail to the network.
+ */
+ if (!ap_is_initial_req(f->r)) {
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ ctx = f->ctx = apr_pcalloc(r->pool, sizeof(*ctx));
+ ctx->bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
+
+ type = apr_pstrdup(r->pool, r->content_type);
+ if (type) {
+ charset = strchr(type, ' ');
+ if (charset) {
+ *charset++ = 0;
+ end = strchr(charset, ' ');
+ if (end) {
+ *end++ = 0;
+ }
+ }
+ }
+
+ apr_brigade_printf(ctx->bb, NULL, NULL, "data:%s%s;base64,",
+ type ? type : "", charset ? charset : "");
+
+ content_length = apr_table_get(r->headers_out, "Content-Length");
+ if (content_length) {
+ apr_off_t len, clen;
+ apr_brigade_length(ctx->bb, 1, &len);
+ if (ap_parse_strict_length(&clen, content_length)
+ && clen < APR_INT32_MAX) {
+ ap_set_content_length(r, len +
+ apr_base64_encode_len((int)clen) - 1);
+ }
+ else {
+ apr_table_unset(r->headers_out, "Content-Length");
+ }
+ }
+
+ ap_set_content_type(r, "text/plain");
+
+ }
+
+ /* Do nothing if asked to filter nothing. */
+ if (APR_BRIGADE_EMPTY(bb)) {
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ while (APR_SUCCESS == rv && !APR_BRIGADE_EMPTY(bb)) {
+ const char *data;
+ apr_size_t size;
+ apr_size_t tail;
+ apr_size_t len;
+ /* buffer big enough for 8000 encoded bytes (6000 raw bytes) and terminator */
+ char buffer[APR_BUCKET_BUFF_SIZE + 1];
+ char encoded[((sizeof(ctx->overflow)) / 3) * 4 + 1];
+
+ e = APR_BRIGADE_FIRST(bb);
+
+ /* EOS means we are done. */
+ if (APR_BUCKET_IS_EOS(e)) {
+
+ /* write away the tail */
+ if (ctx->count) {
+ len = apr_base64_encode_binary(encoded, ctx->overflow,
+ ctx->count);
+ apr_brigade_write(ctx->bb, NULL, NULL, encoded, len - 1);
+ ctx->count = 0;
+ }
+
+ /* pass the EOS across */
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
+
+ /* pass what we have down the chain */
+ ap_remove_output_filter(f);
+ rv = ap_pass_brigade(f->next, ctx->bb);
+
+ /* pass any stray buckets after the EOS down the stack */
+ if ((APR_SUCCESS == rv) && (!APR_BRIGADE_EMPTY(bb))) {
+ rv = ap_pass_brigade(f->next, bb);
+ }
+ continue;
+ }
+
+ /* flush what we can, we can't flush the tail until EOS */
+ if (APR_BUCKET_IS_FLUSH(e)) {
+
+ /* pass the flush bucket across */
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
+
+ /* pass what we have down the chain */
+ rv = ap_pass_brigade(f->next, ctx->bb);
+ continue;
+ }
+
+ /* metadata buckets are preserved as is */
+ if (APR_BUCKET_IS_METADATA(e)) {
+ /*
+ * Remove meta data bucket from old brigade and insert into the
+ * new.
+ */
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
+ continue;
+ }
+
+ /* make sure we don't read more than 6000 bytes at a time */
+ apr_brigade_partition(bb, (APR_BUCKET_BUFF_SIZE / 4 * 3), &ee);
+
+ /* size will never be more than 6000 bytes */
+ if (APR_SUCCESS == (rv = apr_bucket_read(e, &data, &size,
+ APR_BLOCK_READ))) {
+
+ /* fill up and write out our overflow buffer if partially used */
+ while (size && ctx->count && ctx->count < sizeof(ctx->overflow)) {
+ ctx->overflow[ctx->count++] = *data++;
+ size--;
+ }
+ if (ctx->count == sizeof(ctx->overflow)) {
+ len = apr_base64_encode_binary(encoded, ctx->overflow,
+ sizeof(ctx->overflow));
+ apr_brigade_write(ctx->bb, NULL, NULL, encoded, len - 1);
+ ctx->count = 0;
+ }
+
+ /* write the main base64 chunk */
+ tail = size % sizeof(ctx->overflow);
+ size -= tail;
+ if (size) {
+ len = apr_base64_encode_binary(buffer,
+ (const unsigned char *) data, size);
+ apr_brigade_write(ctx->bb, NULL, NULL, buffer, len - 1);
+ }
+
+ /* save away any tail in the overflow buffer */
+ if (tail) {
+ memcpy(ctx->overflow, data + size, tail);
+ ctx->count += tail;
+ }
+
+ apr_bucket_delete(e);
+
+ /* pass what we have down the chain */
+ rv = ap_pass_brigade(f->next, ctx->bb);
+ if (rv) {
+ /* should break out of the loop, since our write to the client
+ * failed in some way. */
+ continue;
+ }
+
+ }
+
+ }
+
+ return rv;
+
+}
+
+static const command_rec data_cmds[] = { { NULL } };
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_register_output_filter(DATA_FILTER, data_out_filter, NULL,
+ AP_FTYPE_RESOURCE);
+}
+AP_DECLARE_MODULE(data) = { STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ data_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/filters/mod_data.dep b/modules/filters/mod_data.dep
new file mode 100644
index 0000000..dfcc43d
--- /dev/null
+++ b/modules/filters/mod_data.dep
@@ -0,0 +1,55 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_data.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_data.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_base64.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/filters/mod_data.dsp b/modules/filters/mod_data.dsp
new file mode 100644
index 0000000..e4cc23c
--- /dev/null
+++ b/modules/filters/mod_data.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_data" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_data - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_data.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_data.mak" CFG="mod_data - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_data - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_data - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_data - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "HAVE_ZUTIL_H" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_data_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_data.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_data.so" /d LONG_NAME="data_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_data.so" /base:@..\..\os\win32\BaseAddr.ref,mod_data.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_data.so" /base:@..\..\os\win32\BaseAddr.ref,mod_data.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_data.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_data - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "HAVE_ZUTIL_H" /Fd"Debug\mod_data_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_data.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_data.so" /d LONG_NAME="data_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_data.so" /base:@..\..\os\win32\BaseAddr.ref,mod_data.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_data.so" /base:@..\..\os\win32\BaseAddr.ref,mod_data.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_data.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_data - Win32 Release"
+# Name "mod_data - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_data.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/filters/mod_data.mak b/modules/filters/mod_data.mak
new file mode 100644
index 0000000..cc73f51
--- /dev/null
+++ b/modules/filters/mod_data.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_data.dsp
+!IF "$(CFG)" == ""
+CFG=mod_data - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_data - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_data - Win32 Release" && "$(CFG)" != "mod_data - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_data.mak" CFG="mod_data - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_data - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_data - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_data - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_data.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_data.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_data.obj"
+ -@erase "$(INTDIR)\mod_data.res"
+ -@erase "$(INTDIR)\mod_data_src.idb"
+ -@erase "$(INTDIR)\mod_data_src.pdb"
+ -@erase "$(OUTDIR)\mod_data.exp"
+ -@erase "$(OUTDIR)\mod_data.lib"
+ -@erase "$(OUTDIR)\mod_data.pdb"
+ -@erase "$(OUTDIR)\mod_data.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_data_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_data.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_data.so" /d LONG_NAME="data_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_data.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_data.pdb" /debug /out:"$(OUTDIR)\mod_data.so" /implib:"$(OUTDIR)\mod_data.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_data.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_data.obj" \
+ "$(INTDIR)\mod_data.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_data.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_data.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_data.so"
+ if exist .\Release\mod_data.so.manifest mt.exe -manifest .\Release\mod_data.so.manifest -outputresource:.\Release\mod_data.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_data - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_data.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_data.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_data.obj"
+ -@erase "$(INTDIR)\mod_data.res"
+ -@erase "$(INTDIR)\mod_data_src.idb"
+ -@erase "$(INTDIR)\mod_data_src.pdb"
+ -@erase "$(OUTDIR)\mod_data.exp"
+ -@erase "$(OUTDIR)\mod_data.lib"
+ -@erase "$(OUTDIR)\mod_data.pdb"
+ -@erase "$(OUTDIR)\mod_data.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "HAVE_ZUTIL_H" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_data_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_data.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_data.so" /d LONG_NAME="data_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_data.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_data.pdb" /debug /out:"$(OUTDIR)\mod_data.so" /implib:"$(OUTDIR)\mod_data.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_data.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_data.obj" \
+ "$(INTDIR)\mod_data.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_data.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_data.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_data.so"
+ if exist .\Debug\mod_data.so.manifest mt.exe -manifest .\Debug\mod_data.so.manifest -outputresource:.\Debug\mod_data.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_data.dep")
+!INCLUDE "mod_data.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_data.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_data - Win32 Release" || "$(CFG)" == "mod_data - Win32 Debug"
+
+!IF "$(CFG)" == "mod_data - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\filters"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_data - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\filters"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_data - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\filters"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_data - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\filters"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_data - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\filters"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_data - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\filters"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\filters"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_data - Win32 Release"
+
+
+"$(INTDIR)\mod_data.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_data.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_data.so" /d LONG_NAME="data_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_data - Win32 Debug"
+
+
+"$(INTDIR)\mod_data.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_data.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_data.so" /d LONG_NAME="data_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_data.c
+
+"$(INTDIR)\mod_data.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/filters/mod_deflate.c b/modules/filters/mod_deflate.c
new file mode 100644
index 0000000..2431fd7
--- /dev/null
+++ b/modules/filters/mod_deflate.c
@@ -0,0 +1,1936 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * mod_deflate.c: Perform deflate content-encoding on the fly
+ *
+ * Written by Ian Holsman, Justin Erenkrantz, and Nick Kew
+ */
+
+/*
+ * Portions of this software are based upon zlib code by Jean-loup Gailly
+ * (zlib functions gz_open and gzwrite, check_header)
+ */
+
+/* zlib flags */
+#define ASCII_FLAG 0x01 /* bit 0 set: file probably ascii text */
+#define HEAD_CRC 0x02 /* bit 1 set: header CRC present */
+#define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */
+#define ORIG_NAME 0x08 /* bit 3 set: original file name present */
+#define COMMENT 0x10 /* bit 4 set: file comment present */
+#define RESERVED 0xE0 /* bits 5..7: reserved */
+
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "http_core.h"
+#include "apr_lib.h"
+#include "apr_strings.h"
+#include "apr_general.h"
+#include "util_filter.h"
+#include "apr_buckets.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "http_ssl.h"
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "zlib.h"
+
+static const char deflateFilterName[] = "DEFLATE";
+module AP_MODULE_DECLARE_DATA deflate_module;
+
+#define AP_INFLATE_RATIO_LIMIT 200
+#define AP_INFLATE_RATIO_BURST 3
+
+typedef struct deflate_filter_config_t
+{
+ int windowSize;
+ int memlevel;
+ int compressionlevel;
+ apr_size_t bufferSize;
+ const char *note_ratio_name;
+ const char *note_input_name;
+ const char *note_output_name;
+} deflate_filter_config;
+
+typedef struct deflate_dirconf_t {
+ apr_off_t inflate_limit;
+ int ratio_limit,
+ ratio_burst;
+} deflate_dirconf_t;
+
+/* RFC 1952 Section 2.3 defines the gzip header:
+ *
+ * +---+---+---+---+---+---+---+---+---+---+
+ * |ID1|ID2|CM |FLG| MTIME |XFL|OS |
+ * +---+---+---+---+---+---+---+---+---+---+
+ */
+static const char gzip_header[10] =
+{ '\037', '\213', Z_DEFLATED, 0,
+ 0, 0, 0, 0, /* mtime */
+ 0, 0x03 /* Unix OS_CODE */
+};
+
+/* magic header */
+static const char deflate_magic[2] = { '\037', '\213' };
+
+/* windowsize is negative to suppress Zlib header */
+#define DEFAULT_COMPRESSION Z_DEFAULT_COMPRESSION
+#define DEFAULT_WINDOWSIZE -15
+#define DEFAULT_MEMLEVEL 9
+#define DEFAULT_BUFFERSIZE 8096
+
+/* Check whether a request is gzipped, so we can un-gzip it.
+ * If a request has multiple encodings, we need the gzip
+ * to be the outermost non-identity encoding.
+ */
+static int check_gzip(request_rec *r, apr_table_t *hdrs1, apr_table_t *hdrs2)
+{
+ int found = 0;
+ apr_table_t *hdrs = hdrs1;
+ const char *encoding = apr_table_get(hdrs, "Content-Encoding");
+
+ if (!encoding && (hdrs2 != NULL)) {
+ /* the output filter has two tables and a content_encoding to check */
+ encoding = apr_table_get(hdrs2, "Content-Encoding");
+ hdrs = hdrs2;
+ if (!encoding) {
+ encoding = r->content_encoding;
+ hdrs = NULL;
+ }
+ }
+ if (encoding && *encoding) {
+
+ /* check the usual/simple case first */
+ if (!ap_cstr_casecmp(encoding, "gzip")
+ || !ap_cstr_casecmp(encoding, "x-gzip")) {
+ found = 1;
+ if (hdrs) {
+ apr_table_unset(hdrs, "Content-Encoding");
+ }
+ else {
+ r->content_encoding = NULL;
+ }
+ }
+ else if (ap_strchr_c(encoding, ',') != NULL) {
+ /* If the outermost encoding isn't gzip, there's nothing
+ * we can do. So only check the last non-identity token
+ */
+ char *new_encoding = apr_pstrdup(r->pool, encoding);
+ char *ptr;
+ for(;;) {
+ char *token = ap_strrchr(new_encoding, ',');
+ if (!token) { /* gzip:identity or other:identity */
+ if (!ap_cstr_casecmp(new_encoding, "gzip")
+ || !ap_cstr_casecmp(new_encoding, "x-gzip")) {
+ found = 1;
+ if (hdrs) {
+ apr_table_unset(hdrs, "Content-Encoding");
+ }
+ else {
+ r->content_encoding = NULL;
+ }
+ }
+ break; /* seen all tokens */
+ }
+ for (ptr=token+1; apr_isspace(*ptr); ++ptr);
+ if (!ap_cstr_casecmp(ptr, "gzip")
+ || !ap_cstr_casecmp(ptr, "x-gzip")) {
+ *token = '\0';
+ if (hdrs) {
+ apr_table_setn(hdrs, "Content-Encoding", new_encoding);
+ }
+ else {
+ r->content_encoding = new_encoding;
+ }
+ found = 1;
+ }
+ else if (!ptr[0] || !ap_cstr_casecmp(ptr, "identity")) {
+ *token = '\0';
+ continue; /* strip the token and find the next one */
+ }
+ break; /* found a non-identity token */
+ }
+ }
+ }
+ /*
+ * If we have dealt with the headers above but content_encoding was set
+ * before sync it with the new value in the hdrs table as
+ * r->content_encoding takes precedence later on in the http_header_filter
+ * and hence would destroy what we have just set in the hdrs table.
+ */
+ if (hdrs && r->content_encoding) {
+ r->content_encoding = apr_table_get(hdrs, "Content-Encoding");
+ }
+ return found;
+}
+
+/* Outputs a long in LSB order to the given file
+ * only the bottom 4 bits are required for the deflate file format.
+ */
+static void putLong(unsigned char *string, unsigned long x)
+{
+ string[0] = (unsigned char)(x & 0xff);
+ string[1] = (unsigned char)((x & 0xff00) >> 8);
+ string[2] = (unsigned char)((x & 0xff0000) >> 16);
+ string[3] = (unsigned char)((x & 0xff000000) >> 24);
+}
+
+/* Inputs a string and returns a long.
+ */
+static unsigned long getLong(unsigned char *string)
+{
+ return ((unsigned long)string[0])
+ | (((unsigned long)string[1]) << 8)
+ | (((unsigned long)string[2]) << 16)
+ | (((unsigned long)string[3]) << 24);
+}
+
+static void *create_deflate_server_config(apr_pool_t *p, server_rec *s)
+{
+ deflate_filter_config *c = apr_pcalloc(p, sizeof *c);
+
+ c->memlevel = DEFAULT_MEMLEVEL;
+ c->windowSize = DEFAULT_WINDOWSIZE;
+ c->bufferSize = DEFAULT_BUFFERSIZE;
+ c->compressionlevel = DEFAULT_COMPRESSION;
+
+ return c;
+}
+
+static void *create_deflate_dirconf(apr_pool_t *p, char *dummy)
+{
+ deflate_dirconf_t *dc = apr_pcalloc(p, sizeof(*dc));
+ dc->ratio_limit = AP_INFLATE_RATIO_LIMIT;
+ dc->ratio_burst = AP_INFLATE_RATIO_BURST;
+ return dc;
+}
+
+static const char *deflate_set_window_size(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ deflate_filter_config *c = ap_get_module_config(cmd->server->module_config,
+ &deflate_module);
+ int i;
+
+ i = atoi(arg);
+
+ if (i < 1 || i > 15)
+ return "DeflateWindowSize must be between 1 and 15";
+
+ c->windowSize = i * -1;
+
+ return NULL;
+}
+
+static const char *deflate_set_buffer_size(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ deflate_filter_config *c = ap_get_module_config(cmd->server->module_config,
+ &deflate_module);
+ int n = atoi(arg);
+
+ if (n <= 0) {
+ return "DeflateBufferSize should be positive";
+ }
+
+ c->bufferSize = (apr_size_t)n;
+
+ return NULL;
+}
+static const char *deflate_set_note(cmd_parms *cmd, void *dummy,
+ const char *arg1, const char *arg2)
+{
+ deflate_filter_config *c = ap_get_module_config(cmd->server->module_config,
+ &deflate_module);
+
+ if (arg2 == NULL) {
+ c->note_ratio_name = arg1;
+ }
+ else if (!strcasecmp(arg1, "ratio")) {
+ c->note_ratio_name = arg2;
+ }
+ else if (!strcasecmp(arg1, "input")) {
+ c->note_input_name = arg2;
+ }
+ else if (!strcasecmp(arg1, "output")) {
+ c->note_output_name = arg2;
+ }
+ else {
+ return apr_psprintf(cmd->pool, "Unknown note type %s", arg1);
+ }
+
+ return NULL;
+}
+
+static const char *deflate_set_memlevel(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ deflate_filter_config *c = ap_get_module_config(cmd->server->module_config,
+ &deflate_module);
+ int i;
+
+ i = atoi(arg);
+
+ if (i < 1 || i > 9)
+ return "DeflateMemLevel must be between 1 and 9";
+
+ c->memlevel = i;
+
+ return NULL;
+}
+
+static const char *deflate_set_compressionlevel(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ deflate_filter_config *c = ap_get_module_config(cmd->server->module_config,
+ &deflate_module);
+ int i;
+
+ i = atoi(arg);
+
+ if (i < 1 || i > 9)
+ return "Compression Level must be between 1 and 9";
+
+ c->compressionlevel = i;
+
+ return NULL;
+}
+
+
+static const char *deflate_set_inflate_limit(cmd_parms *cmd, void *dirconf,
+ const char *arg)
+{
+ deflate_dirconf_t *dc = (deflate_dirconf_t*) dirconf;
+ char *errp;
+
+ if (APR_SUCCESS != apr_strtoff(&dc->inflate_limit, arg, &errp, 10)) {
+ return "DeflateInflateLimitRequestBody is not parsable.";
+ }
+ if (*errp || dc->inflate_limit < 0) {
+ return "DeflateInflateLimitRequestBody requires a non-negative integer.";
+ }
+
+ return NULL;
+}
+
+static const char *deflate_set_inflate_ratio_limit(cmd_parms *cmd,
+ void *dirconf,
+ const char *arg)
+{
+ deflate_dirconf_t *dc = (deflate_dirconf_t*) dirconf;
+ int i;
+
+ i = atoi(arg);
+ if (i <= 0)
+ return "DeflateInflateRatioLimit must be positive";
+
+ dc->ratio_limit = i;
+
+ return NULL;
+}
+
+static const char *deflate_set_inflate_ratio_burst(cmd_parms *cmd,
+ void *dirconf,
+ const char *arg)
+{
+ deflate_dirconf_t *dc = (deflate_dirconf_t*) dirconf;
+ int i;
+
+ i = atoi(arg);
+ if (i <= 0)
+ return "DeflateInflateRatioBurst must be positive";
+
+ dc->ratio_burst = i;
+
+ return NULL;
+}
+
+typedef struct deflate_ctx_t
+{
+ z_stream stream;
+ unsigned char *buffer;
+ unsigned long crc;
+ apr_bucket_brigade *bb, *proc_bb;
+ int (*libz_end_func)(z_streamp);
+ unsigned char *validation_buffer;
+ apr_size_t validation_buffer_length;
+ char header[10]; /* sizeof(gzip_header) */
+ apr_size_t header_len;
+ int zlib_flags;
+ int ratio_hits;
+ apr_off_t inflate_total;
+ unsigned int consume_pos,
+ consume_len;
+ unsigned int filter_init:1;
+ unsigned int done:1;
+} deflate_ctx;
+
+/* Number of validation bytes (CRC and length) after the compressed data */
+#define VALIDATION_SIZE 8
+/* Do not update ctx->crc, see comment in flush_libz_buffer */
+#define NO_UPDATE_CRC 0
+/* Do update ctx->crc, see comment in flush_libz_buffer */
+#define UPDATE_CRC 1
+
+static int flush_libz_buffer(deflate_ctx *ctx, deflate_filter_config *c,
+ struct apr_bucket_alloc_t *bucket_alloc,
+ int (*libz_func)(z_streamp, int), int flush,
+ int crc)
+{
+ int zRC = Z_OK;
+ int done = 0;
+ unsigned int deflate_len;
+ apr_bucket *b;
+
+ for (;;) {
+ deflate_len = c->bufferSize - ctx->stream.avail_out;
+
+ if (deflate_len != 0) {
+ /*
+ * Do we need to update ctx->crc? Usually this is the case for
+ * inflate action where we need to do a crc on the output, whereas
+ * in the deflate case we need to do a crc on the input
+ */
+ if (crc) {
+ ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer,
+ deflate_len);
+ }
+ b = apr_bucket_heap_create((char *)ctx->buffer,
+ deflate_len, NULL,
+ bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
+ ctx->stream.next_out = ctx->buffer;
+ ctx->stream.avail_out = c->bufferSize;
+ }
+
+ if (done)
+ break;
+
+ zRC = libz_func(&ctx->stream, flush);
+
+ /*
+ * We can ignore Z_BUF_ERROR because:
+ * When we call libz_func we can assume that
+ *
+ * - avail_in is zero (due to the surrounding code that calls
+ * flush_libz_buffer)
+ * - avail_out is non zero due to our actions some lines above
+ *
+ * So the only reason for Z_BUF_ERROR is that the internal libz
+ * buffers are now empty and thus we called libz_func one time
+ * too often. This does not hurt. It simply says that we are done.
+ */
+ if (zRC == Z_BUF_ERROR) {
+ zRC = Z_OK;
+ break;
+ }
+
+ done = (ctx->stream.avail_out != 0 || zRC == Z_STREAM_END);
+
+ if (zRC != Z_OK && zRC != Z_STREAM_END)
+ break;
+ }
+ return zRC;
+}
+
+static apr_status_t deflate_ctx_cleanup(void *data)
+{
+ deflate_ctx *ctx = (deflate_ctx *)data;
+
+ if (ctx)
+ ctx->libz_end_func(&ctx->stream);
+ return APR_SUCCESS;
+}
+
+/* ETag must be unique among the possible representations, so a change
+ * to content-encoding requires a corresponding change to the ETag.
+ * This routine appends -transform (e.g., -gzip) to the entity-tag
+ * value inside the double-quotes if an ETag has already been set
+ * and its value already contains double-quotes. PR 39727
+ */
+static void deflate_check_etag(request_rec *r, const char *transform)
+{
+ const char *etag = apr_table_get(r->headers_out, "ETag");
+ apr_size_t etaglen;
+
+ if ((etag && ((etaglen = strlen(etag)) > 2))) {
+ if (etag[etaglen - 1] == '"') {
+ apr_size_t transformlen = strlen(transform);
+ char *newtag = apr_palloc(r->pool, etaglen + transformlen + 2);
+ char *d = newtag;
+ char *e = d + etaglen - 1;
+ const char *s = etag;
+
+ for (; d < e; ++d, ++s) {
+ *d = *s; /* copy etag to newtag up to last quote */
+ }
+ *d++ = '-'; /* append dash to newtag */
+ s = transform;
+ e = d + transformlen;
+ for (; d < e; ++d, ++s) {
+ *d = *s; /* copy transform to newtag */
+ }
+ *d++ = '"'; /* append quote to newtag */
+ *d = '\0'; /* null terminate newtag */
+
+ apr_table_setn(r->headers_out, "ETag", newtag);
+ }
+ }
+}
+
+/* Check whether the (inflate) ratio exceeds the configured limit/burst. */
+static int check_ratio(request_rec *r, deflate_ctx *ctx,
+ const deflate_dirconf_t *dc)
+{
+ if (ctx->stream.total_in) {
+ int ratio = ctx->stream.total_out / ctx->stream.total_in;
+ if (ratio < dc->ratio_limit) {
+ ctx->ratio_hits = 0;
+ }
+ else if (++ctx->ratio_hits > dc->ratio_burst) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static int have_ssl_compression(request_rec *r)
+{
+ const char *comp;
+ comp = ap_ssl_var_lookup(r->pool, r->server, r->connection, r,
+ "SSL_COMPRESS_METHOD");
+ if (comp == NULL || *comp == '\0' || strcmp(comp, "NULL") == 0)
+ return 0;
+ return 1;
+}
+
+static apr_status_t deflate_out_filter(ap_filter_t *f,
+ apr_bucket_brigade *bb)
+{
+ apr_bucket *e;
+ request_rec *r = f->r;
+ deflate_ctx *ctx = f->ctx;
+ int zRC;
+ apr_size_t len = 0, blen;
+ const char *data;
+ deflate_filter_config *c;
+
+ /* Do nothing if asked to filter nothing. */
+ if (APR_BRIGADE_EMPTY(bb)) {
+ return APR_SUCCESS;
+ }
+
+ c = ap_get_module_config(r->server->module_config,
+ &deflate_module);
+
+ /* If we don't have a context, we need to ensure that it is okay to send
+ * the deflated content. If we have a context, that means we've done
+ * this before and we liked it.
+ * This could be not so nice if we always fail. But, if we succeed,
+ * we're in better shape.
+ */
+ if (!ctx) {
+ char *token;
+ const char *encoding;
+
+ if (have_ssl_compression(r)) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "Compression enabled at SSL level; not compressing "
+ "at HTTP level.");
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ /* We have checked above that bb is not empty */
+ e = APR_BRIGADE_LAST(bb);
+ if (APR_BUCKET_IS_EOS(e)) {
+ /*
+ * If we already know the size of the response, we can skip
+ * compression on responses smaller than the compression overhead.
+ * However, if we compress, we must initialize deflate_out before
+ * calling ap_pass_brigade() for the first time. Otherwise the
+ * headers will be sent to the client without
+ * "Content-Encoding: gzip".
+ */
+ e = APR_BRIGADE_FIRST(bb);
+ while (1) {
+ apr_status_t rc;
+ if (APR_BUCKET_IS_EOS(e)) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "Not compressing very small response of %"
+ APR_SIZE_T_FMT " bytes", len);
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+ if (APR_BUCKET_IS_METADATA(e)) {
+ e = APR_BUCKET_NEXT(e);
+ continue;
+ }
+
+ if (e->length == (apr_size_t)-1) {
+ rc = apr_bucket_read(e, &data, &blen, APR_BLOCK_READ);
+ if (rc != APR_SUCCESS)
+ return rc;
+ }
+ else {
+ blen = e->length;
+ }
+ len += blen;
+ /* 50 is for Content-Encoding and Vary headers and ETag suffix */
+ if (len > sizeof(gzip_header) + VALIDATION_SIZE + 50)
+ break;
+
+ e = APR_BUCKET_NEXT(e);
+ }
+ }
+
+ ctx = f->ctx = apr_pcalloc(r->pool, sizeof(*ctx));
+
+ /*
+ * Only work on main request, not subrequests,
+ * that are not a 204 response with no content
+ * and are not tagged with the no-gzip env variable
+ * and not a partial response to a Range request.
+ *
+ * Note that responding to 304 is handled separately to
+ * set the required headers (such as ETag) per RFC7232, 4.1.
+ */
+ if ((r->main != NULL) || (r->status == HTTP_NO_CONTENT) ||
+ apr_table_get(r->subprocess_env, "no-gzip") ||
+ apr_table_get(r->headers_out, "Content-Range")
+ ) {
+ if (APLOG_R_IS_LEVEL(r, APLOG_TRACE1)) {
+ const char *reason =
+ (r->main != NULL) ? "subrequest" :
+ (r->status == HTTP_NO_CONTENT) ? "no content" :
+ apr_table_get(r->subprocess_env, "no-gzip") ? "no-gzip" :
+ "content-range";
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "Not compressing (%s)", reason);
+ }
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ /* Some browsers might have problems with content types
+ * other than text/html, so set gzip-only-text/html
+ * (with browsermatch) for them
+ */
+ if (r->content_type == NULL
+ || strncmp(r->content_type, "text/html", 9)) {
+ const char *env_value = apr_table_get(r->subprocess_env,
+ "gzip-only-text/html");
+ if ( env_value && (strcmp(env_value,"1") == 0) ) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "Not compressing, (gzip-only-text/html)");
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+ }
+
+ /* Let's see what our current Content-Encoding is.
+ * If it's already encoded, don't compress again.
+ * (We could, but let's not.)
+ */
+ encoding = apr_table_get(r->headers_out, "Content-Encoding");
+ if (encoding) {
+ const char *err_enc;
+
+ err_enc = apr_table_get(r->err_headers_out, "Content-Encoding");
+ if (err_enc) {
+ encoding = apr_pstrcat(r->pool, encoding, ",", err_enc, NULL);
+ }
+ }
+ else {
+ encoding = apr_table_get(r->err_headers_out, "Content-Encoding");
+ }
+
+ if (r->content_encoding) {
+ encoding = encoding ? apr_pstrcat(r->pool, encoding, ",",
+ r->content_encoding, NULL)
+ : r->content_encoding;
+ }
+
+ if (encoding) {
+ const char *tmp = encoding;
+
+ token = ap_get_token(r->pool, &tmp, 0);
+ while (token && *token) {
+ /* stolen from mod_negotiation: */
+ if (strcmp(token, "identity") && strcmp(token, "7bit") &&
+ strcmp(token, "8bit") && strcmp(token, "binary")) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "Not compressing (content-encoding already "
+ " set: %s)", token);
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ /* Otherwise, skip token */
+ if (*tmp) {
+ ++tmp;
+ }
+ token = (*tmp) ? ap_get_token(r->pool, &tmp, 0) : NULL;
+ }
+ }
+
+ /* Even if we don't accept this request based on it not having
+ * the Accept-Encoding, we need to note that we were looking
+ * for this header and downstream proxies should be aware of that.
+ */
+ apr_table_mergen(r->headers_out, "Vary", "Accept-Encoding");
+
+ /* force-gzip will just force it out regardless if the browser
+ * can actually do anything with it.
+ */
+ if (!apr_table_get(r->subprocess_env, "force-gzip")) {
+ const char *accepts;
+ const char *q = NULL;
+
+ /* if they don't have the line, then they can't play */
+ accepts = apr_table_get(r->headers_in, "Accept-Encoding");
+ if (accepts == NULL) {
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ token = ap_get_token(r->pool, &accepts, 0);
+ while (token && token[0] && ap_cstr_casecmp(token, "gzip")) {
+ /* skip parameters, XXX: ;q=foo evaluation? */
+ while (*accepts == ';') {
+ ++accepts;
+ ap_get_token(r->pool, &accepts, 1);
+ }
+
+ /* retrieve next token */
+ if (*accepts == ',') {
+ ++accepts;
+ }
+ token = (*accepts) ? ap_get_token(r->pool, &accepts, 0) : NULL;
+ }
+
+ /* Find the qvalue, if provided */
+ if (*accepts) {
+ while (*accepts == ';') {
+ ++accepts;
+ }
+ q = ap_get_token(r->pool, &accepts, 1);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "token: '%s' - q: '%s'", token ? token : "NULL", q);
+ }
+
+ /* No acceptable token found or q=0 */
+ if (!token || token[0] == '\0' ||
+ (q && strlen(q) >= 3 && strncmp("q=0.000", q, strlen(q)) == 0)) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "Not compressing (no Accept-Encoding: gzip or q=0)");
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "Forcing compression (force-gzip set)");
+ }
+
+ /* At this point we have decided to filter the content. Let's try to
+ * to initialize zlib (except for 304 responses, where we will only
+ * send out the headers).
+ */
+
+ if (r->status != HTTP_NOT_MODIFIED) {
+ ctx->bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
+ ctx->buffer = apr_palloc(r->pool, c->bufferSize);
+ ctx->libz_end_func = deflateEnd;
+
+ zRC = deflateInit2(&ctx->stream, c->compressionlevel, Z_DEFLATED,
+ c->windowSize, c->memlevel,
+ Z_DEFAULT_STRATEGY);
+
+ if (zRC != Z_OK) {
+ deflateEnd(&ctx->stream);
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01383)
+ "unable to init Zlib: "
+ "deflateInit2 returned %d: URL %s",
+ zRC, r->uri);
+ /*
+ * Remove ourselves as it does not make sense to return:
+ * We are not able to init libz and pass data down the chain
+ * uncompressed.
+ */
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+ /*
+ * Register a cleanup function to ensure that we cleanup the internal
+ * libz resources.
+ */
+ apr_pool_cleanup_register(r->pool, ctx, deflate_ctx_cleanup,
+ apr_pool_cleanup_null);
+
+ /* Set the filter init flag so subsequent invocations know we are
+ * active.
+ */
+ ctx->filter_init = 1;
+ }
+
+ /*
+ * Zlib initialization worked, so we can now change the important
+ * content metadata before sending the response out.
+ */
+
+ /* If the entire Content-Encoding is "identity", we can replace it. */
+ if (!encoding || !ap_cstr_casecmp(encoding, "identity")) {
+ apr_table_setn(r->headers_out, "Content-Encoding", "gzip");
+ }
+ else {
+ apr_table_mergen(r->headers_out, "Content-Encoding", "gzip");
+ }
+ /* Fix r->content_encoding if it was set before */
+ if (r->content_encoding) {
+ r->content_encoding = apr_table_get(r->headers_out,
+ "Content-Encoding");
+ }
+ apr_table_unset(r->headers_out, "Content-Length");
+ apr_table_unset(r->headers_out, "Content-MD5");
+ deflate_check_etag(r, "gzip");
+
+ /* For a 304 response, only change the headers */
+ if (r->status == HTTP_NOT_MODIFIED) {
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ /* add immortal gzip header */
+ e = apr_bucket_immortal_create(gzip_header, sizeof gzip_header,
+ f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
+
+ /* initialize deflate output buffer */
+ ctx->stream.next_out = ctx->buffer;
+ ctx->stream.avail_out = c->bufferSize;
+ } else if (!ctx->filter_init) {
+ /* Hmm. We've run through the filter init before as we have a ctx,
+ * but we never initialized. We probably have a dangling ref. Bail.
+ */
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ while (!APR_BRIGADE_EMPTY(bb))
+ {
+ apr_bucket *b;
+
+ /*
+ * Optimization: If we are a HEAD request and bytes_sent is not zero
+ * it means that we have passed the content-length filter once and
+ * have more data to sent. This means that the content-length filter
+ * could not determine our content-length for the response to the
+ * HEAD request anyway (the associated GET request would deliver the
+ * body in chunked encoding) and we can stop compressing.
+ */
+ if (r->header_only && r->bytes_sent) {
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ e = APR_BRIGADE_FIRST(bb);
+
+ if (APR_BUCKET_IS_EOS(e)) {
+ char *buf;
+
+ ctx->stream.avail_in = 0; /* should be zero already anyway */
+ /* flush the remaining data from the zlib buffers */
+ flush_libz_buffer(ctx, c, f->c->bucket_alloc, deflate, Z_FINISH,
+ NO_UPDATE_CRC);
+
+ buf = apr_palloc(r->pool, VALIDATION_SIZE);
+ putLong((unsigned char *)&buf[0], ctx->crc);
+ putLong((unsigned char *)&buf[4], ctx->stream.total_in);
+
+ b = apr_bucket_pool_create(buf, VALIDATION_SIZE, r->pool,
+ f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01384)
+ "Zlib: Compressed %" APR_UINT64_T_FMT
+ " to %" APR_UINT64_T_FMT " : URL %s",
+ (apr_uint64_t)ctx->stream.total_in,
+ (apr_uint64_t)ctx->stream.total_out, r->uri);
+
+ /* leave notes for logging */
+ if (c->note_input_name) {
+ apr_table_setn(r->notes, c->note_input_name,
+ (ctx->stream.total_in > 0)
+ ? apr_off_t_toa(r->pool,
+ ctx->stream.total_in)
+ : "-");
+ }
+
+ if (c->note_output_name) {
+ apr_table_setn(r->notes, c->note_output_name,
+ (ctx->stream.total_out > 0)
+ ? apr_off_t_toa(r->pool,
+ ctx->stream.total_out)
+ : "-");
+ }
+
+ if (c->note_ratio_name) {
+ apr_table_setn(r->notes, c->note_ratio_name,
+ (ctx->stream.total_in > 0)
+ ? apr_itoa(r->pool,
+ (int)(ctx->stream.total_out
+ * 100
+ / ctx->stream.total_in))
+ : "-");
+ }
+
+ deflateEnd(&ctx->stream);
+ /* No need for cleanup any longer */
+ apr_pool_cleanup_kill(r->pool, ctx, deflate_ctx_cleanup);
+
+ /* Remove EOS from the old list, and insert into the new. */
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
+
+ /* Okay, we've seen the EOS.
+ * Time to pass it along down the chain.
+ */
+ return ap_pass_brigade(f->next, ctx->bb);
+ }
+
+ if (APR_BUCKET_IS_FLUSH(e)) {
+ apr_status_t rv;
+
+ /* flush the remaining data from the zlib buffers */
+ zRC = flush_libz_buffer(ctx, c, f->c->bucket_alloc, deflate,
+ Z_SYNC_FLUSH, NO_UPDATE_CRC);
+ if (zRC != Z_OK) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01385)
+ "Zlib error %d flushing zlib output buffer (%s)",
+ zRC, ctx->stream.msg);
+ return APR_EGENERAL;
+ }
+
+ /* Remove flush bucket from old brigade anf insert into the new. */
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
+ rv = ap_pass_brigade(f->next, ctx->bb);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ continue;
+ }
+
+ if (APR_BUCKET_IS_METADATA(e)) {
+ /*
+ * Remove meta data bucket from old brigade and insert into the
+ * new.
+ */
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
+ continue;
+ }
+
+ /* read */
+ apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
+ if (!len) {
+ apr_bucket_delete(e);
+ continue;
+ }
+ if (len > APR_INT32_MAX) {
+ apr_bucket_split(e, APR_INT32_MAX);
+ apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
+ }
+
+ /* This crc32 function is from zlib. */
+ ctx->crc = crc32(ctx->crc, (const Bytef *)data, len);
+
+ /* write */
+ ctx->stream.next_in = (unsigned char *)data; /* We just lost const-ness,
+ * but we'll just have to
+ * trust zlib */
+ ctx->stream.avail_in = len;
+
+ while (ctx->stream.avail_in != 0) {
+ if (ctx->stream.avail_out == 0) {
+ apr_status_t rv;
+
+ ctx->stream.next_out = ctx->buffer;
+ len = c->bufferSize - ctx->stream.avail_out;
+
+ b = apr_bucket_heap_create((char *)ctx->buffer, len,
+ NULL, f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
+ ctx->stream.avail_out = c->bufferSize;
+ /* Send what we have right now to the next filter. */
+ rv = ap_pass_brigade(f->next, ctx->bb);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+
+ zRC = deflate(&(ctx->stream), Z_NO_FLUSH);
+
+ if (zRC != Z_OK) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01386)
+ "Zlib error %d deflating data (%s)", zRC,
+ ctx->stream.msg);
+ return APR_EGENERAL;
+ }
+ }
+
+ apr_bucket_delete(e);
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t consume_zlib_flags(deflate_ctx *ctx,
+ const char **data, apr_size_t *len)
+{
+ if ((ctx->zlib_flags & EXTRA_FIELD)) {
+ /* Consume 2 bytes length prefixed data. */
+ if (ctx->consume_pos == 0) {
+ if (!*len) {
+ return APR_INCOMPLETE;
+ }
+ ctx->consume_len = (unsigned int)**data;
+ ctx->consume_pos++;
+ ++*data;
+ --*len;
+ }
+ if (ctx->consume_pos == 1) {
+ if (!*len) {
+ return APR_INCOMPLETE;
+ }
+ ctx->consume_len += ((unsigned int)**data) << 8;
+ ctx->consume_pos++;
+ ++*data;
+ --*len;
+ }
+ if (*len < ctx->consume_len) {
+ ctx->consume_len -= *len;
+ *len = 0;
+ return APR_INCOMPLETE;
+ }
+ *data += ctx->consume_len;
+ *len -= ctx->consume_len;
+
+ ctx->consume_len = ctx->consume_pos = 0;
+ ctx->zlib_flags &= ~EXTRA_FIELD;
+ }
+
+ if ((ctx->zlib_flags & ORIG_NAME)) {
+ /* Consume nul terminated string. */
+ while (*len && **data) {
+ ++*data;
+ --*len;
+ }
+ if (!*len) {
+ return APR_INCOMPLETE;
+ }
+ /* .. and nul. */
+ ++*data;
+ --*len;
+
+ ctx->zlib_flags &= ~ORIG_NAME;
+ }
+
+ if ((ctx->zlib_flags & COMMENT)) {
+ /* Consume nul terminated string. */
+ while (*len && **data) {
+ ++*data;
+ --*len;
+ }
+ if (!*len) {
+ return APR_INCOMPLETE;
+ }
+ /* .. and nul. */
+ ++*data;
+ --*len;
+
+ ctx->zlib_flags &= ~COMMENT;
+ }
+
+ if ((ctx->zlib_flags & HEAD_CRC)) {
+ /* Consume CRC16 (2 octets). */
+ if (ctx->consume_pos == 0) {
+ if (!*len) {
+ return APR_INCOMPLETE;
+ }
+ ctx->consume_pos++;
+ ++*data;
+ --*len;
+ }
+ if (!*len) {
+ return APR_INCOMPLETE;
+ }
+ ++*data;
+ --*len;
+
+ ctx->consume_pos = 0;
+ ctx->zlib_flags &= ~HEAD_CRC;
+ }
+
+ return APR_SUCCESS;
+}
+
+/* This is the deflate input filter (inflates). */
+static apr_status_t deflate_in_filter(ap_filter_t *f,
+ apr_bucket_brigade *bb,
+ ap_input_mode_t mode,
+ apr_read_type_e block,
+ apr_off_t readbytes)
+{
+ apr_bucket *bkt;
+ request_rec *r = f->r;
+ deflate_ctx *ctx = f->ctx;
+ int zRC;
+ apr_status_t rv;
+ deflate_filter_config *c;
+ deflate_dirconf_t *dc;
+ apr_off_t inflate_limit;
+
+ /* just get out of the way of things we don't want. */
+ if (mode != AP_MODE_READBYTES) {
+ return ap_get_brigade(f->next, bb, mode, block, readbytes);
+ }
+
+ c = ap_get_module_config(r->server->module_config, &deflate_module);
+ dc = ap_get_module_config(r->per_dir_config, &deflate_module);
+
+ if (!ctx || ctx->header_len < sizeof(ctx->header)) {
+ apr_size_t len;
+
+ if (!ctx) {
+ /* only work on main request/no subrequests */
+ if (!ap_is_initial_req(r)) {
+ ap_remove_input_filter(f);
+ return ap_get_brigade(f->next, bb, mode, block, readbytes);
+ }
+
+ /* We can't operate on Content-Ranges */
+ if (apr_table_get(r->headers_in, "Content-Range") != NULL) {
+ ap_remove_input_filter(f);
+ return ap_get_brigade(f->next, bb, mode, block, readbytes);
+ }
+
+ /* Check whether request body is gzipped.
+ *
+ * If it is, we're transforming the contents, invalidating
+ * some request headers including Content-Encoding.
+ *
+ * If not, we just remove ourself.
+ */
+ if (check_gzip(r, r->headers_in, NULL) == 0) {
+ ap_remove_input_filter(f);
+ return ap_get_brigade(f->next, bb, mode, block, readbytes);
+ }
+
+ f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx));
+ ctx->bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
+ ctx->proc_bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
+ ctx->buffer = apr_palloc(r->pool, c->bufferSize);
+ }
+
+ do {
+ apr_brigade_cleanup(ctx->bb);
+
+ len = sizeof(ctx->header) - ctx->header_len;
+ rv = ap_get_brigade(f->next, ctx->bb, AP_MODE_READBYTES, block,
+ len);
+
+ /* ap_get_brigade may return success with an empty brigade for
+ * a non-blocking read which would block (an empty brigade for
+ * a blocking read is an issue which is simply forwarded here).
+ */
+ if (rv != APR_SUCCESS || APR_BRIGADE_EMPTY(ctx->bb)) {
+ return rv;
+ }
+
+ /* zero length body? step aside */
+ bkt = APR_BRIGADE_FIRST(ctx->bb);
+ if (APR_BUCKET_IS_EOS(bkt)) {
+ if (ctx->header_len) {
+ /* If the header was (partially) read it's an error, this
+ * is not a gzip Content-Encoding, as claimed.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02619)
+ "Encountered premature end-of-stream while "
+ "reading inflate header");
+ return APR_EGENERAL;
+ }
+ APR_BUCKET_REMOVE(bkt);
+ APR_BRIGADE_INSERT_TAIL(bb, bkt);
+ ap_remove_input_filter(f);
+ return APR_SUCCESS;
+ }
+
+ rv = apr_brigade_flatten(ctx->bb,
+ ctx->header + ctx->header_len, &len);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ if (len && !ctx->header_len) {
+ apr_table_unset(r->headers_in, "Content-Length");
+ apr_table_unset(r->headers_in, "Content-MD5");
+ }
+ ctx->header_len += len;
+
+ } while (ctx->header_len < sizeof(ctx->header));
+
+ /* We didn't get the magic bytes. */
+ if (ctx->header[0] != deflate_magic[0] ||
+ ctx->header[1] != deflate_magic[1]) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01387)
+ "Zlib: Invalid header");
+ return APR_EGENERAL;
+ }
+
+ ctx->zlib_flags = ctx->header[3];
+ if ((ctx->zlib_flags & RESERVED)) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01388)
+ "Zlib: Invalid flags %02x", ctx->zlib_flags);
+ return APR_EGENERAL;
+ }
+
+ zRC = inflateInit2(&ctx->stream, c->windowSize);
+
+ if (zRC != Z_OK) {
+ f->ctx = NULL;
+ inflateEnd(&ctx->stream);
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01389)
+ "unable to init Zlib: "
+ "inflateInit2 returned %d: URL %s",
+ zRC, r->uri);
+ ap_remove_input_filter(f);
+ return ap_get_brigade(f->next, bb, mode, block, readbytes);
+ }
+
+ /* initialize deflate output buffer */
+ ctx->stream.next_out = ctx->buffer;
+ ctx->stream.avail_out = c->bufferSize;
+
+ apr_brigade_cleanup(ctx->bb);
+ }
+
+ inflate_limit = dc->inflate_limit;
+ if (inflate_limit == 0) {
+ /* The core is checking the deflated body, we'll check the inflated */
+ inflate_limit = ap_get_limit_req_body(f->r);
+ }
+
+ if (APR_BRIGADE_EMPTY(ctx->proc_bb)) {
+ rv = ap_get_brigade(f->next, ctx->bb, mode, block, readbytes);
+
+ /* Don't terminate on EAGAIN (or success with an empty brigade in
+ * non-blocking mode), just return focus.
+ */
+ if (block == APR_NONBLOCK_READ
+ && (APR_STATUS_IS_EAGAIN(rv)
+ || (rv == APR_SUCCESS && APR_BRIGADE_EMPTY(ctx->bb)))) {
+ return rv;
+ }
+ if (rv != APR_SUCCESS) {
+ inflateEnd(&ctx->stream);
+ return rv;
+ }
+
+ for (bkt = APR_BRIGADE_FIRST(ctx->bb);
+ bkt != APR_BRIGADE_SENTINEL(ctx->bb);
+ bkt = APR_BUCKET_NEXT(bkt))
+ {
+ const char *data;
+ apr_size_t len;
+
+ if (APR_BUCKET_IS_EOS(bkt)) {
+ if (!ctx->done) {
+ inflateEnd(&ctx->stream);
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02481)
+ "Encountered premature end-of-stream while inflating");
+ return APR_EGENERAL;
+ }
+
+ /* Move everything to the returning brigade. */
+ APR_BUCKET_REMOVE(bkt);
+ APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, bkt);
+ break;
+ }
+
+ if (APR_BUCKET_IS_FLUSH(bkt)) {
+ apr_bucket *tmp_b;
+
+ ctx->inflate_total += ctx->stream.avail_out;
+ zRC = inflate(&(ctx->stream), Z_SYNC_FLUSH);
+ ctx->inflate_total -= ctx->stream.avail_out;
+ if (zRC != Z_OK) {
+ inflateEnd(&ctx->stream);
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01391)
+ "Zlib error %d inflating data (%s)", zRC,
+ ctx->stream.msg);
+ return APR_EGENERAL;
+ }
+
+ if (inflate_limit && ctx->inflate_total > inflate_limit) {
+ inflateEnd(&ctx->stream);
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02647)
+ "Inflated content length of %" APR_OFF_T_FMT
+ " is larger than the configured limit"
+ " of %" APR_OFF_T_FMT,
+ ctx->inflate_total, inflate_limit);
+ return APR_ENOSPC;
+ }
+
+ if (!check_ratio(r, ctx, dc)) {
+ inflateEnd(&ctx->stream);
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02805)
+ "Inflated content ratio is larger than the "
+ "configured limit %i by %i time(s)",
+ dc->ratio_limit, dc->ratio_burst);
+ return APR_EINVAL;
+ }
+
+ len = c->bufferSize - ctx->stream.avail_out;
+ ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
+ tmp_b = apr_bucket_heap_create((char *)ctx->buffer, len,
+ NULL, f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_b);
+
+ ctx->stream.next_out = ctx->buffer;
+ ctx->stream.avail_out = c->bufferSize;
+
+ /* Flush everything so far in the returning brigade, but continue
+ * reading should EOS/more follow (don't lose them).
+ */
+ tmp_b = APR_BUCKET_PREV(bkt);
+ APR_BUCKET_REMOVE(bkt);
+ APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, bkt);
+ bkt = tmp_b;
+ continue;
+ }
+
+ /* sanity check - data after completed compressed body and before eos? */
+ if (ctx->done) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02482)
+ "Encountered extra data after compressed data");
+ return APR_EGENERAL;
+ }
+
+ /* read */
+ apr_bucket_read(bkt, &data, &len, APR_BLOCK_READ);
+ if (!len) {
+ continue;
+ }
+ if (len > APR_INT32_MAX) {
+ apr_bucket_split(bkt, APR_INT32_MAX);
+ apr_bucket_read(bkt, &data, &len, APR_BLOCK_READ);
+ }
+
+ if (ctx->zlib_flags) {
+ rv = consume_zlib_flags(ctx, &data, &len);
+ if (rv == APR_SUCCESS) {
+ ctx->zlib_flags = 0;
+ }
+ if (!len) {
+ continue;
+ }
+ }
+
+ /* pass through zlib inflate. */
+ ctx->stream.next_in = (unsigned char *)data;
+ ctx->stream.avail_in = (int)len;
+
+ if (!ctx->validation_buffer) {
+ while (ctx->stream.avail_in != 0) {
+ if (ctx->stream.avail_out == 0) {
+ apr_bucket *tmp_heap;
+
+ ctx->stream.next_out = ctx->buffer;
+ len = c->bufferSize - ctx->stream.avail_out;
+
+ ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
+ tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
+ NULL, f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
+ ctx->stream.avail_out = c->bufferSize;
+ }
+
+ ctx->inflate_total += ctx->stream.avail_out;
+ zRC = inflate(&ctx->stream, Z_NO_FLUSH);
+ ctx->inflate_total -= ctx->stream.avail_out;
+ if (zRC != Z_OK && zRC != Z_STREAM_END) {
+ inflateEnd(&ctx->stream);
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01392)
+ "Zlib error %d inflating data (%s)", zRC,
+ ctx->stream.msg);
+ return APR_EGENERAL;
+ }
+
+ if (inflate_limit && ctx->inflate_total > inflate_limit) {
+ inflateEnd(&ctx->stream);
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02648)
+ "Inflated content length of %" APR_OFF_T_FMT
+ " is larger than the configured limit"
+ " of %" APR_OFF_T_FMT,
+ ctx->inflate_total, inflate_limit);
+ return APR_ENOSPC;
+ }
+
+ if (!check_ratio(r, ctx, dc)) {
+ inflateEnd(&ctx->stream);
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02649)
+ "Inflated content ratio is larger than the "
+ "configured limit %i by %i time(s)",
+ dc->ratio_limit, dc->ratio_burst);
+ return APR_EINVAL;
+ }
+
+ if (zRC == Z_STREAM_END) {
+ ctx->validation_buffer = apr_pcalloc(r->pool,
+ VALIDATION_SIZE);
+ ctx->validation_buffer_length = 0;
+ break;
+ }
+ }
+ }
+
+ if (ctx->validation_buffer) {
+ apr_bucket *tmp_heap;
+ apr_size_t avail, valid;
+ unsigned char *buf = ctx->validation_buffer;
+
+ avail = ctx->stream.avail_in;
+ valid = (apr_size_t)VALIDATION_SIZE -
+ ctx->validation_buffer_length;
+
+ /*
+ * We have inflated all data. Now try to capture the
+ * validation bytes. We may not have them all available
+ * right now, but capture what is there.
+ */
+ if (avail < valid) {
+ memcpy(buf + ctx->validation_buffer_length,
+ ctx->stream.next_in, avail);
+ ctx->validation_buffer_length += avail;
+ continue;
+ }
+ memcpy(buf + ctx->validation_buffer_length,
+ ctx->stream.next_in, valid);
+ ctx->validation_buffer_length += valid;
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01393)
+ "Zlib: Inflated %" APR_UINT64_T_FMT
+ " to %" APR_UINT64_T_FMT " : URL %s",
+ (apr_uint64_t)ctx->stream.total_in,
+ (apr_uint64_t)ctx->stream.total_out, r->uri);
+
+ len = c->bufferSize - ctx->stream.avail_out;
+
+ ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
+ tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
+ NULL, f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
+ ctx->stream.avail_out = c->bufferSize;
+
+ {
+ unsigned long compCRC, compLen;
+ compCRC = getLong(buf);
+ if (ctx->crc != compCRC) {
+ inflateEnd(&ctx->stream);
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01394)
+ "Zlib: CRC error inflating data");
+ return APR_EGENERAL;
+ }
+ compLen = getLong(buf + VALIDATION_SIZE / 2);
+ /* gzip stores original size only as 4 byte value */
+ if ((ctx->stream.total_out & 0xFFFFFFFF) != compLen) {
+ inflateEnd(&ctx->stream);
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01395)
+ "Zlib: Length %" APR_UINT64_T_FMT
+ " of inflated data does not match"
+ " expected value %ld",
+ (apr_uint64_t)ctx->stream.total_out, compLen);
+ return APR_EGENERAL;
+ }
+ }
+
+ inflateEnd(&ctx->stream);
+
+ ctx->done = 1;
+
+ /* Did we have trailing data behind the closing 8 bytes? */
+ if (avail > valid) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02485)
+ "Encountered extra data after compressed data");
+ return APR_EGENERAL;
+ }
+ }
+
+ }
+ apr_brigade_cleanup(ctx->bb);
+ }
+
+ /* If we are about to return nothing for a 'blocking' read and we have
+ * some data in our zlib buffer, flush it out so we can return something.
+ */
+ if (block == APR_BLOCK_READ &&
+ APR_BRIGADE_EMPTY(ctx->proc_bb) &&
+ ctx->stream.avail_out < c->bufferSize) {
+ apr_bucket *tmp_heap;
+ apr_size_t len;
+ ctx->stream.next_out = ctx->buffer;
+ len = c->bufferSize - ctx->stream.avail_out;
+
+ ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
+ tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
+ NULL, f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
+ ctx->stream.avail_out = c->bufferSize;
+ }
+
+ if (!APR_BRIGADE_EMPTY(ctx->proc_bb)) {
+ if (apr_brigade_partition(ctx->proc_bb, readbytes, &bkt) == APR_INCOMPLETE) {
+ APR_BRIGADE_CONCAT(bb, ctx->proc_bb);
+ }
+ else {
+ APR_BRIGADE_CONCAT(bb, ctx->proc_bb);
+ apr_brigade_split_ex(bb, bkt, ctx->proc_bb);
+ }
+ if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb))) {
+ ap_remove_input_filter(f);
+ }
+ }
+
+ return APR_SUCCESS;
+}
+
+
+/* Filter to inflate for a content-transforming proxy. */
+static apr_status_t inflate_out_filter(ap_filter_t *f,
+ apr_bucket_brigade *bb)
+{
+ apr_bucket *e;
+ request_rec *r = f->r;
+ deflate_ctx *ctx = f->ctx;
+ int zRC;
+ apr_status_t rv;
+ deflate_filter_config *c;
+ deflate_dirconf_t *dc;
+
+ /* Do nothing if asked to filter nothing. */
+ if (APR_BRIGADE_EMPTY(bb)) {
+ return APR_SUCCESS;
+ }
+
+ c = ap_get_module_config(r->server->module_config, &deflate_module);
+ dc = ap_get_module_config(r->per_dir_config, &deflate_module);
+
+ if (!ctx) {
+
+ /*
+ * Only work on main request, not subrequests,
+ * that are not a 204 response with no content
+ * and not a partial response to a Range request,
+ * and only when Content-Encoding ends in gzip.
+ *
+ * Note that responding to 304 is handled separately to
+ * set the required headers (such as ETag) per RFC7232, 4.1.
+ */
+ if (!ap_is_initial_req(r) || (r->status == HTTP_NO_CONTENT) ||
+ (apr_table_get(r->headers_out, "Content-Range") != NULL) ||
+ (check_gzip(r, r->headers_out, r->err_headers_out) == 0)
+ ) {
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ /*
+ * At this point we have decided to filter the content, so change
+ * important content metadata before sending any response out.
+ * Content-Encoding was already reset by the check_gzip() call.
+ */
+ apr_table_unset(r->headers_out, "Content-Length");
+ apr_table_unset(r->headers_out, "Content-MD5");
+ deflate_check_etag(r, "gunzip");
+
+ /* For a 304 response, only change the headers */
+ if (r->status == HTTP_NOT_MODIFIED) {
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx));
+ ctx->bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
+ ctx->buffer = apr_palloc(r->pool, c->bufferSize);
+ ctx->libz_end_func = inflateEnd;
+ ctx->validation_buffer = NULL;
+ ctx->validation_buffer_length = 0;
+
+ zRC = inflateInit2(&ctx->stream, c->windowSize);
+
+ if (zRC != Z_OK) {
+ f->ctx = NULL;
+ inflateEnd(&ctx->stream);
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01397)
+ "unable to init Zlib: "
+ "inflateInit2 returned %d: URL %s",
+ zRC, r->uri);
+ /*
+ * Remove ourselves as it does not make sense to return:
+ * We are not able to init libz and pass data down the chain
+ * compressed.
+ */
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ /*
+ * Register a cleanup function to ensure that we cleanup the internal
+ * libz resources.
+ */
+ apr_pool_cleanup_register(r->pool, ctx, deflate_ctx_cleanup,
+ apr_pool_cleanup_null);
+
+ /* initialize inflate output buffer */
+ ctx->stream.next_out = ctx->buffer;
+ ctx->stream.avail_out = c->bufferSize;
+ }
+
+ while (!APR_BRIGADE_EMPTY(bb))
+ {
+ const char *data;
+ apr_bucket *b;
+ apr_size_t len;
+
+ e = APR_BRIGADE_FIRST(bb);
+
+ if (APR_BUCKET_IS_EOS(e)) {
+ /*
+ * We are really done now. Ensure that we never return here, even
+ * if a second EOS bucket falls down the chain. Thus remove
+ * ourselves.
+ */
+ ap_remove_output_filter(f);
+ /* should be zero already anyway */
+ ctx->stream.avail_in = 0;
+ /*
+ * Flush the remaining data from the zlib buffers. It is correct
+ * to use Z_SYNC_FLUSH in this case and not Z_FINISH as in the
+ * deflate case. In the inflate case Z_FINISH requires to have a
+ * large enough output buffer to put ALL data in otherwise it
+ * fails, whereas in the deflate case you can empty a filled output
+ * buffer and call it again until no more output can be created.
+ */
+ flush_libz_buffer(ctx, c, f->c->bucket_alloc, inflate, Z_SYNC_FLUSH,
+ UPDATE_CRC);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01398)
+ "Zlib: Inflated %" APR_UINT64_T_FMT
+ " to %" APR_UINT64_T_FMT " : URL %s",
+ (apr_uint64_t)ctx->stream.total_in,
+ (apr_uint64_t)ctx->stream.total_out, r->uri);
+
+ if (ctx->validation_buffer_length == VALIDATION_SIZE) {
+ unsigned long compCRC, compLen;
+ compCRC = getLong(ctx->validation_buffer);
+ if (ctx->crc != compCRC) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01399)
+ "Zlib: Checksum of inflated stream invalid");
+ return APR_EGENERAL;
+ }
+ ctx->validation_buffer += VALIDATION_SIZE / 2;
+ compLen = getLong(ctx->validation_buffer);
+ /* gzip stores original size only as 4 byte value */
+ if ((ctx->stream.total_out & 0xFFFFFFFF) != compLen) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01400)
+ "Zlib: Length of inflated stream invalid");
+ return APR_EGENERAL;
+ }
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01401)
+ "Zlib: Validation bytes not present");
+ return APR_EGENERAL;
+ }
+
+ inflateEnd(&ctx->stream);
+ /* No need for cleanup any longer */
+ apr_pool_cleanup_kill(r->pool, ctx, deflate_ctx_cleanup);
+
+ /* Remove EOS from the old list, and insert into the new. */
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
+
+ /*
+ * Okay, we've seen the EOS.
+ * Time to pass it along down the chain.
+ */
+ return ap_pass_brigade(f->next, ctx->bb);
+ }
+
+ if (APR_BUCKET_IS_FLUSH(e)) {
+ apr_status_t rv;
+
+ /* flush the remaining data from the zlib buffers */
+ zRC = flush_libz_buffer(ctx, c, f->c->bucket_alloc, inflate,
+ Z_SYNC_FLUSH, UPDATE_CRC);
+ if (zRC == Z_STREAM_END) {
+ if (ctx->validation_buffer == NULL) {
+ ctx->validation_buffer = apr_pcalloc(f->r->pool,
+ VALIDATION_SIZE);
+ }
+ }
+ else if (zRC != Z_OK) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01402)
+ "Zlib error %d flushing inflate buffer (%s)",
+ zRC, ctx->stream.msg);
+ return APR_EGENERAL;
+ }
+
+ /* Remove flush bucket from old brigade anf insert into the new. */
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
+ rv = ap_pass_brigade(f->next, ctx->bb);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ continue;
+ }
+
+ if (APR_BUCKET_IS_METADATA(e)) {
+ /*
+ * Remove meta data bucket from old brigade and insert into the
+ * new.
+ */
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
+ continue;
+ }
+
+ /* read */
+ apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
+ if (!len) {
+ apr_bucket_delete(e);
+ continue;
+ }
+ if (len > APR_INT32_MAX) {
+ apr_bucket_split(e, APR_INT32_MAX);
+ apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
+ }
+
+ /* first bucket contains zlib header */
+ if (ctx->header_len < sizeof(ctx->header)) {
+ apr_size_t rem;
+
+ rem = sizeof(ctx->header) - ctx->header_len;
+ if (len < rem) {
+ memcpy(ctx->header + ctx->header_len, data, len);
+ ctx->header_len += len;
+ apr_bucket_delete(e);
+ continue;
+ }
+ memcpy(ctx->header + ctx->header_len, data, rem);
+ ctx->header_len += rem;
+ {
+ int zlib_method;
+ zlib_method = ctx->header[2];
+ if (zlib_method != Z_DEFLATED) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01404)
+ "inflate: data not deflated!");
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+ if (ctx->header[0] != deflate_magic[0] ||
+ ctx->header[1] != deflate_magic[1]) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01405)
+ "inflate: bad header");
+ return APR_EGENERAL ;
+ }
+ ctx->zlib_flags = ctx->header[3];
+ if ((ctx->zlib_flags & RESERVED)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02620)
+ "inflate: bad flags %02x",
+ ctx->zlib_flags);
+ return APR_EGENERAL;
+ }
+ }
+ if (len == rem) {
+ apr_bucket_delete(e);
+ continue;
+ }
+ data += rem;
+ len -= rem;
+ }
+
+ if (ctx->zlib_flags) {
+ rv = consume_zlib_flags(ctx, &data, &len);
+ if (rv == APR_SUCCESS) {
+ ctx->zlib_flags = 0;
+ }
+ if (!len) {
+ apr_bucket_delete(e);
+ continue;
+ }
+ }
+
+ /* pass through zlib inflate. */
+ ctx->stream.next_in = (unsigned char *)data;
+ ctx->stream.avail_in = len;
+
+ if (ctx->validation_buffer) {
+ if (ctx->validation_buffer_length < VALIDATION_SIZE) {
+ apr_size_t copy_size;
+
+ copy_size = VALIDATION_SIZE - ctx->validation_buffer_length;
+ if (copy_size > ctx->stream.avail_in)
+ copy_size = ctx->stream.avail_in;
+ memcpy(ctx->validation_buffer + ctx->validation_buffer_length,
+ ctx->stream.next_in, copy_size);
+ /* Saved copy_size bytes */
+ ctx->stream.avail_in -= copy_size;
+ ctx->validation_buffer_length += copy_size;
+ }
+ if (ctx->stream.avail_in) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01407)
+ "Zlib: %d bytes of garbage at the end of "
+ "compressed stream.", ctx->stream.avail_in);
+ /*
+ * There is nothing worth consuming for zlib left, because it is
+ * either garbage data or the data has been copied to the
+ * validation buffer (processing validation data is no business
+ * for zlib). So set ctx->stream.avail_in to zero to indicate
+ * this to the following while loop.
+ */
+ ctx->stream.avail_in = 0;
+ }
+ }
+
+ while (ctx->stream.avail_in != 0) {
+ if (ctx->stream.avail_out == 0) {
+ ctx->stream.next_out = ctx->buffer;
+ len = c->bufferSize - ctx->stream.avail_out;
+
+ ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
+ b = apr_bucket_heap_create((char *)ctx->buffer, len,
+ NULL, f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
+ ctx->stream.avail_out = c->bufferSize;
+ /* Send what we have right now to the next filter. */
+ rv = ap_pass_brigade(f->next, ctx->bb);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+
+ zRC = inflate(&ctx->stream, Z_NO_FLUSH);
+
+ if (zRC != Z_OK && zRC != Z_STREAM_END) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01409)
+ "Zlib error %d inflating data (%s)", zRC,
+ ctx->stream.msg);
+ return APR_EGENERAL;
+ }
+
+ if (!check_ratio(r, ctx, dc)) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02650)
+ "Inflated content ratio is larger than the "
+ "configured limit %i by %i time(s)",
+ dc->ratio_limit, dc->ratio_burst);
+ return APR_EINVAL;
+ }
+
+ if (zRC == Z_STREAM_END) {
+ /*
+ * We have inflated all data. Now try to capture the
+ * validation bytes. We may not have them all available
+ * right now, but capture what is there.
+ */
+ ctx->validation_buffer = apr_pcalloc(f->r->pool,
+ VALIDATION_SIZE);
+ if (ctx->stream.avail_in > VALIDATION_SIZE) {
+ ctx->validation_buffer_length = VALIDATION_SIZE;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01408)
+ "Zlib: %d bytes of garbage at the end of "
+ "compressed stream.",
+ ctx->stream.avail_in - VALIDATION_SIZE);
+ }
+ else if (ctx->stream.avail_in > 0) {
+ ctx->validation_buffer_length = ctx->stream.avail_in;
+ }
+ if (ctx->validation_buffer_length)
+ memcpy(ctx->validation_buffer, ctx->stream.next_in,
+ ctx->validation_buffer_length);
+ break;
+ }
+ }
+
+ apr_bucket_delete(e);
+ }
+
+ return APR_SUCCESS;
+}
+
+static int mod_deflate_post_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ return OK;
+}
+
+
+#define PROTO_FLAGS AP_FILTER_PROTO_CHANGE|AP_FILTER_PROTO_CHANGE_LENGTH
+static void register_hooks(apr_pool_t *p)
+{
+ ap_register_output_filter(deflateFilterName, deflate_out_filter, NULL,
+ AP_FTYPE_CONTENT_SET);
+ ap_register_output_filter("INFLATE", inflate_out_filter, NULL,
+ AP_FTYPE_RESOURCE-1);
+ ap_register_input_filter(deflateFilterName, deflate_in_filter, NULL,
+ AP_FTYPE_CONTENT_SET);
+ ap_hook_post_config(mod_deflate_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+static const command_rec deflate_filter_cmds[] = {
+ AP_INIT_TAKE12("DeflateFilterNote", deflate_set_note, NULL, RSRC_CONF,
+ "Set a note to report on compression ratio"),
+ AP_INIT_TAKE1("DeflateWindowSize", deflate_set_window_size, NULL,
+ RSRC_CONF, "Set the Deflate window size (1-15)"),
+ AP_INIT_TAKE1("DeflateBufferSize", deflate_set_buffer_size, NULL, RSRC_CONF,
+ "Set the Deflate Buffer Size"),
+ AP_INIT_TAKE1("DeflateMemLevel", deflate_set_memlevel, NULL, RSRC_CONF,
+ "Set the Deflate Memory Level (1-9)"),
+ AP_INIT_TAKE1("DeflateCompressionLevel", deflate_set_compressionlevel, NULL, RSRC_CONF,
+ "Set the Deflate Compression Level (1-9)"),
+ AP_INIT_TAKE1("DeflateInflateLimitRequestBody", deflate_set_inflate_limit, NULL, OR_ALL,
+ "Set a limit on size of inflated input"),
+ AP_INIT_TAKE1("DeflateInflateRatioLimit", deflate_set_inflate_ratio_limit, NULL, OR_ALL,
+ "Set the inflate ratio limit above which inflation is "
+ "aborted (default: " APR_STRINGIFY(AP_INFLATE_RATIO_LIMIT) ")"),
+ AP_INIT_TAKE1("DeflateInflateRatioBurst", deflate_set_inflate_ratio_burst, NULL, OR_ALL,
+ "Set the maximum number of following inflate ratios above limit "
+ "(default: " APR_STRINGIFY(AP_INFLATE_RATIO_BURST) ")"),
+ {NULL}
+};
+
+AP_DECLARE_MODULE(deflate) = {
+ STANDARD20_MODULE_STUFF,
+ create_deflate_dirconf, /* dir config creater */
+ NULL, /* dir merger --- default is to override */
+ create_deflate_server_config, /* server config */
+ NULL, /* merge server config */
+ deflate_filter_cmds, /* command table */
+ register_hooks /* register hooks */
+};
diff --git a/modules/filters/mod_deflate.dep b/modules/filters/mod_deflate.dep
new file mode 100644
index 0000000..4715df0
--- /dev/null
+++ b/modules/filters/mod_deflate.dep
@@ -0,0 +1,52 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_deflate.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_deflate.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ "..\ssl\mod_ssl.h"\
+
diff --git a/modules/filters/mod_deflate.dsp b/modules/filters/mod_deflate.dsp
new file mode 100644
index 0000000..f990c97
--- /dev/null
+++ b/modules/filters/mod_deflate.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_deflate" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_deflate - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_deflate.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_deflate.mak" CFG="mod_deflate - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_deflate - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_deflate - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_deflate - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../ssl" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/zlib" /D "NDEBUG" /D "ZLIB_DLL" /D "HAVE_ZUTIL_H" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_deflate_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_deflate.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_deflate.so" /d LONG_NAME="deflate_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_deflate.so" /base:@..\..\os\win32\BaseAddr.ref,mod_deflate.so
+# ADD LINK32 kernel32.lib zdll.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_deflate.so" /libpath:"../../srclib/zlib" /base:@..\..\os\win32\BaseAddr.ref,mod_deflate.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_deflate.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_deflate - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../ssl" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/zlib" /D "_DEBUG" /D "ZLIB_DLL" /D "HAVE_ZUTIL_H" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_deflate_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_deflate.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_deflate.so" /d LONG_NAME="deflate_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_deflate.so" /base:@..\..\os\win32\BaseAddr.ref,mod_deflate.so
+# ADD LINK32 kernel32.lib zdll.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_deflate.so" /libpath:"../../srclib/zlib" /base:@..\..\os\win32\BaseAddr.ref,mod_deflate.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_deflate.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_deflate - Win32 Release"
+# Name "mod_deflate - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_deflate.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/filters/mod_deflate.exp b/modules/filters/mod_deflate.exp
new file mode 100644
index 0000000..9ec7688
--- /dev/null
+++ b/modules/filters/mod_deflate.exp
@@ -0,0 +1 @@
+deflate_module
diff --git a/modules/filters/mod_deflate.mak b/modules/filters/mod_deflate.mak
new file mode 100644
index 0000000..9579284
--- /dev/null
+++ b/modules/filters/mod_deflate.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_deflate.dsp
+!IF "$(CFG)" == ""
+CFG=mod_deflate - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_deflate - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_deflate - Win32 Release" && "$(CFG)" != "mod_deflate - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_deflate.mak" CFG="mod_deflate - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_deflate - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_deflate - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_deflate - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_deflate.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_deflate.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_deflate.obj"
+ -@erase "$(INTDIR)\mod_deflate.res"
+ -@erase "$(INTDIR)\mod_deflate_src.idb"
+ -@erase "$(INTDIR)\mod_deflate_src.pdb"
+ -@erase "$(OUTDIR)\mod_deflate.exp"
+ -@erase "$(OUTDIR)\mod_deflate.lib"
+ -@erase "$(OUTDIR)\mod_deflate.pdb"
+ -@erase "$(OUTDIR)\mod_deflate.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../ssl" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/zlib" /D "NDEBUG" /D "ZLIB_DLL" /D "HAVE_ZUTIL_H" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_deflate_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_deflate.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_deflate.so" /d LONG_NAME="deflate_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_deflate.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib zdll.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_deflate.pdb" /debug /out:"$(OUTDIR)\mod_deflate.so" /implib:"$(OUTDIR)\mod_deflate.lib" /libpath:"../../srclib/zlib" /base:@..\..\os\win32\BaseAddr.ref,mod_deflate.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_deflate.obj" \
+ "$(INTDIR)\mod_deflate.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_deflate.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_deflate.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_deflate.so"
+ if exist .\Release\mod_deflate.so.manifest mt.exe -manifest .\Release\mod_deflate.so.manifest -outputresource:.\Release\mod_deflate.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_deflate - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_deflate.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_deflate.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_deflate.obj"
+ -@erase "$(INTDIR)\mod_deflate.res"
+ -@erase "$(INTDIR)\mod_deflate_src.idb"
+ -@erase "$(INTDIR)\mod_deflate_src.pdb"
+ -@erase "$(OUTDIR)\mod_deflate.exp"
+ -@erase "$(OUTDIR)\mod_deflate.lib"
+ -@erase "$(OUTDIR)\mod_deflate.pdb"
+ -@erase "$(OUTDIR)\mod_deflate.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../ssl" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/zlib" /D "_DEBUG" /D "ZLIB_DLL" /D "HAVE_ZUTIL_H" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_deflate_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_deflate.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_deflate.so" /d LONG_NAME="deflate_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_deflate.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib zdll.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_deflate.pdb" /debug /out:"$(OUTDIR)\mod_deflate.so" /implib:"$(OUTDIR)\mod_deflate.lib" /libpath:"../../srclib/zlib" /base:@..\..\os\win32\BaseAddr.ref,mod_deflate.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_deflate.obj" \
+ "$(INTDIR)\mod_deflate.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_deflate.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_deflate.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_deflate.so"
+ if exist .\Debug\mod_deflate.so.manifest mt.exe -manifest .\Debug\mod_deflate.so.manifest -outputresource:.\Debug\mod_deflate.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_deflate.dep")
+!INCLUDE "mod_deflate.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_deflate.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_deflate - Win32 Release" || "$(CFG)" == "mod_deflate - Win32 Debug"
+
+!IF "$(CFG)" == "mod_deflate - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\filters"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_deflate - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\filters"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_deflate - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\filters"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_deflate - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\filters"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_deflate - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\filters"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_deflate - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\filters"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\filters"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_deflate - Win32 Release"
+
+
+"$(INTDIR)\mod_deflate.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_deflate.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_deflate.so" /d LONG_NAME="deflate_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_deflate - Win32 Debug"
+
+
+"$(INTDIR)\mod_deflate.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_deflate.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_deflate.so" /d LONG_NAME="deflate_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_deflate.c
+
+"$(INTDIR)\mod_deflate.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/filters/mod_ext_filter.c b/modules/filters/mod_ext_filter.c
new file mode 100644
index 0000000..7afd8dd
--- /dev/null
+++ b/modules/filters/mod_ext_filter.c
@@ -0,0 +1,954 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * mod_ext_filter allows Unix-style filters to filter http content.
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "http_protocol.h"
+
+#include "http_core.h"
+#include "apr_buckets.h"
+#include "util_filter.h"
+#include "util_script.h"
+#include "util_time.h"
+#include "apr_strings.h"
+#include "apr_hash.h"
+#include "apr_lib.h"
+#include "apr_poll.h"
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+typedef struct ef_server_t {
+ apr_pool_t *p;
+ apr_hash_t *h;
+} ef_server_t;
+
+typedef struct ef_filter_t {
+ const char *name;
+ enum {INPUT_FILTER=1, OUTPUT_FILTER} mode;
+ ap_filter_type ftype;
+ const char *command;
+ const char *enable_env;
+ const char *disable_env;
+ char **args;
+ const char *intype; /* list of IMTs we process (well, just one for now) */
+#define INTYPE_ALL (char *)1
+ const char *outtype; /* IMT of filtered output */
+#define OUTTYPE_UNCHANGED (char *)1
+ int preserves_content_length;
+} ef_filter_t;
+
+typedef struct ef_dir_t {
+ int log_stderr;
+ int onfail;
+} ef_dir_t;
+
+typedef struct ef_ctx_t {
+ apr_pool_t *p;
+ apr_proc_t *proc;
+ apr_procattr_t *procattr;
+ ef_dir_t *dc;
+ ef_filter_t *filter;
+ int noop, hit_eos;
+#if APR_FILES_AS_SOCKETS
+ apr_pollset_t *pollset;
+#endif
+} ef_ctx_t;
+
+module AP_MODULE_DECLARE_DATA ext_filter_module;
+static const server_rec *main_server;
+
+static apr_status_t ef_output_filter(ap_filter_t *, apr_bucket_brigade *);
+static apr_status_t ef_input_filter(ap_filter_t *, apr_bucket_brigade *,
+ ap_input_mode_t, apr_read_type_e,
+ apr_off_t);
+
+#define ERRFN_USERDATA_KEY "EXTFILTCHILDERRFN"
+
+static void *create_ef_dir_conf(apr_pool_t *p, char *dummy)
+{
+ ef_dir_t *dc = (ef_dir_t *)apr_pcalloc(p, sizeof(ef_dir_t));
+
+ dc->log_stderr = -1;
+ dc->onfail = -1;
+
+ return dc;
+}
+
+static void *create_ef_server_conf(apr_pool_t *p, server_rec *s)
+{
+ ef_server_t *conf;
+
+ conf = (ef_server_t *)apr_pcalloc(p, sizeof(ef_server_t));
+ conf->p = p;
+ conf->h = apr_hash_make(conf->p);
+ return conf;
+}
+
+static void *merge_ef_dir_conf(apr_pool_t *p, void *basev, void *overridesv)
+{
+ ef_dir_t *a = (ef_dir_t *)apr_pcalloc (p, sizeof(ef_dir_t));
+ ef_dir_t *base = (ef_dir_t *)basev, *over = (ef_dir_t *)overridesv;
+
+ if (over->log_stderr != -1) { /* if admin coded something... */
+ a->log_stderr = over->log_stderr;
+ }
+ else {
+ a->log_stderr = base->log_stderr;
+ }
+
+ if (over->onfail != -1) { /* if admin coded something... */
+ a->onfail = over->onfail;
+ }
+ else {
+ a->onfail = base->onfail;
+ }
+
+ return a;
+}
+
+static const char *add_options(cmd_parms *cmd, void *in_dc,
+ const char *arg)
+{
+ ef_dir_t *dc = in_dc;
+
+ if (!strcasecmp(arg, "LogStderr")) {
+ dc->log_stderr = 1;
+ }
+ else if (!strcasecmp(arg, "NoLogStderr")) {
+ dc->log_stderr = 0;
+ }
+ else if (!strcasecmp(arg, "Onfail=remove")) {
+ dc->onfail = 1;
+ }
+ else if (!strcasecmp(arg, "Onfail=abort")) {
+ dc->onfail = 0;
+ }
+ else {
+ return apr_pstrcat(cmd->temp_pool,
+ "Invalid ExtFilterOptions option: ",
+ arg,
+ NULL);
+ }
+
+ return NULL;
+}
+
+static const char *parse_cmd(apr_pool_t *p, const char **args, ef_filter_t *filter)
+{
+ if (**args == '"') {
+ const char *start = *args + 1;
+ char *parms;
+ int escaping = 0;
+ apr_status_t rv;
+
+ ++*args; /* move past leading " */
+ /* find true end of args string (accounting for escaped quotes) */
+ while (**args && (**args != '"' || (**args == '"' && escaping))) {
+ if (escaping) {
+ escaping = 0;
+ }
+ else if (**args == '\\') {
+ escaping = 1;
+ }
+ ++*args;
+ }
+ if (**args != '"') {
+ return "Expected cmd= delimiter";
+ }
+ /* copy *just* the arg string for parsing, */
+ parms = apr_pstrndup(p, start, *args - start);
+ ++*args; /* move past trailing " */
+
+ /* parse and tokenize the args. */
+ rv = apr_tokenize_to_argv(parms, &(filter->args), p);
+ if (rv != APR_SUCCESS) {
+ return "cmd= parse error";
+ }
+ }
+ else
+ {
+ /* simple path */
+ /* Allocate space for two argv pointers and parse the args. */
+ filter->args = (char **)apr_palloc(p, 2 * sizeof(char *));
+ filter->args[0] = ap_getword_white(p, args);
+ filter->args[1] = NULL; /* end of args */
+ }
+ if (!filter->args[0]) {
+ return "Invalid cmd= parameter";
+ }
+ filter->command = filter->args[0];
+
+ return NULL;
+}
+
+static const char *define_filter(cmd_parms *cmd, void *dummy, const char *args)
+{
+ ef_server_t *conf = ap_get_module_config(cmd->server->module_config,
+ &ext_filter_module);
+ const char *token;
+ const char *name;
+ char *normalized_name;
+ ef_filter_t *filter;
+
+ name = ap_getword_white(cmd->pool, &args);
+ if (!name) {
+ return "Filter name not found";
+ }
+
+ /* During request processing, we find information about the filter
+ * by looking up the filter name provided by core server in our
+ * hash table. But the core server has normalized the filter
+ * name by converting it to lower case. Thus, when adding the
+ * filter to our hash table we have to use lower case as well.
+ */
+ normalized_name = apr_pstrdup(cmd->pool, name);
+ ap_str_tolower(normalized_name);
+
+ if (apr_hash_get(conf->h, normalized_name, APR_HASH_KEY_STRING)) {
+ return apr_psprintf(cmd->pool, "ExtFilter %s is already defined",
+ name);
+ }
+
+ filter = (ef_filter_t *)apr_pcalloc(conf->p, sizeof(ef_filter_t));
+ filter->name = name;
+ filter->mode = OUTPUT_FILTER;
+ filter->ftype = AP_FTYPE_RESOURCE;
+ apr_hash_set(conf->h, normalized_name, APR_HASH_KEY_STRING, filter);
+
+ while (*args) {
+ while (apr_isspace(*args)) {
+ ++args;
+ }
+
+ /* Nasty parsing... I wish I could simply use ap_getword_white()
+ * here and then look at the token, but ap_getword_white() doesn't
+ * do the right thing when we have cmd="word word word"
+ */
+ if (!strncasecmp(args, "preservescontentlength", 22)) {
+ token = ap_getword_white(cmd->pool, &args);
+ if (!strcasecmp(token, "preservescontentlength")) {
+ filter->preserves_content_length = 1;
+ }
+ else {
+ return apr_psprintf(cmd->pool,
+ "mangled argument `%s'",
+ token);
+ }
+ continue;
+ }
+
+ if (!strncasecmp(args, "mode=", 5)) {
+ args += 5;
+ token = ap_getword_white(cmd->pool, &args);
+ if (!strcasecmp(token, "output")) {
+ filter->mode = OUTPUT_FILTER;
+ }
+ else if (!strcasecmp(token, "input")) {
+ filter->mode = INPUT_FILTER;
+ }
+ else {
+ return apr_psprintf(cmd->pool, "Invalid mode: `%s'",
+ token);
+ }
+ continue;
+ }
+
+ if (!strncasecmp(args, "ftype=", 6)) {
+ args += 6;
+ token = ap_getword_white(cmd->pool, &args);
+ filter->ftype = atoi(token);
+ continue;
+ }
+
+ if (!strncasecmp(args, "enableenv=", 10)) {
+ args += 10;
+ token = ap_getword_white(cmd->pool, &args);
+ filter->enable_env = token;
+ continue;
+ }
+
+ if (!strncasecmp(args, "disableenv=", 11)) {
+ args += 11;
+ token = ap_getword_white(cmd->pool, &args);
+ filter->disable_env = token;
+ continue;
+ }
+
+ if (!strncasecmp(args, "intype=", 7)) {
+ args += 7;
+ filter->intype = ap_getword_white(cmd->pool, &args);
+ continue;
+ }
+
+ if (!strncasecmp(args, "outtype=", 8)) {
+ args += 8;
+ filter->outtype = ap_getword_white(cmd->pool, &args);
+ continue;
+ }
+
+ if (!strncasecmp(args, "cmd=", 4)) {
+ args += 4;
+ if ((token = parse_cmd(cmd->pool, &args, filter))) {
+ return token;
+ }
+ continue;
+ }
+
+ return apr_psprintf(cmd->pool, "Unexpected parameter: `%s'",
+ args);
+ }
+
+ /* parsing is done... register the filter
+ */
+ if (filter->mode == OUTPUT_FILTER) {
+ /* XXX need a way to ensure uniqueness among all filters */
+ ap_register_output_filter(filter->name, ef_output_filter, NULL, filter->ftype);
+ }
+ else if (filter->mode == INPUT_FILTER) {
+ /* XXX need a way to ensure uniqueness among all filters */
+ ap_register_input_filter(filter->name, ef_input_filter, NULL, filter->ftype);
+ }
+ else {
+ ap_assert(1 != 1); /* we set the field wrong somehow */
+ }
+
+ return NULL;
+}
+
+static const command_rec cmds[] =
+{
+ AP_INIT_ITERATE("ExtFilterOptions",
+ add_options,
+ NULL,
+ ACCESS_CONF, /* same as SetInputFilter/SetOutputFilter */
+ "valid options: LogStderr, NoLogStderr"),
+ AP_INIT_RAW_ARGS("ExtFilterDefine",
+ define_filter,
+ NULL,
+ RSRC_CONF,
+ "Define an external filter"),
+ {NULL}
+};
+
+static int ef_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *main_s)
+{
+ main_server = main_s;
+ return OK;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_hook_post_config(ef_init, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+static apr_status_t set_resource_limits(request_rec *r,
+ apr_procattr_t *procattr)
+{
+#if defined(RLIMIT_CPU) || defined(RLIMIT_NPROC) || \
+ defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined (RLIMIT_AS)
+ core_dir_config *conf =
+ (core_dir_config *)ap_get_core_module_config(r->per_dir_config);
+ apr_status_t rv;
+
+#ifdef RLIMIT_CPU
+ rv = apr_procattr_limit_set(procattr, APR_LIMIT_CPU, conf->limit_cpu);
+ ap_assert(rv == APR_SUCCESS); /* otherwise, we're out of sync with APR */
+#endif
+#if defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined(RLIMIT_AS)
+ rv = apr_procattr_limit_set(procattr, APR_LIMIT_MEM, conf->limit_mem);
+ ap_assert(rv == APR_SUCCESS); /* otherwise, we're out of sync with APR */
+#endif
+#ifdef RLIMIT_NPROC
+ rv = apr_procattr_limit_set(procattr, APR_LIMIT_NPROC, conf->limit_nproc);
+ ap_assert(rv == APR_SUCCESS); /* otherwise, we're out of sync with APR */
+#endif
+
+#endif /* if at least one limit defined */
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t ef_close_file(void *vfile)
+{
+ return apr_file_close(vfile);
+}
+
+static void child_errfn(apr_pool_t *pool, apr_status_t err, const char *description)
+{
+ request_rec *r;
+ void *vr;
+ apr_file_t *stderr_log;
+ char time_str[APR_CTIME_LEN];
+
+ apr_pool_userdata_get(&vr, ERRFN_USERDATA_KEY, pool);
+ r = vr;
+ apr_file_open_stderr(&stderr_log, pool);
+ ap_recent_ctime(time_str, apr_time_now());
+ apr_file_printf(stderr_log,
+ "[%s] [client %s] mod_ext_filter (%d)%pm: %s\n",
+ time_str,
+ r->useragent_ip,
+ err,
+ &err,
+ description);
+}
+
+/* init_ext_filter_process: get the external filter process going
+ * This is per-filter-instance (i.e., per-request) initialization.
+ */
+static apr_status_t init_ext_filter_process(ap_filter_t *f)
+{
+ ef_ctx_t *ctx = f->ctx;
+ apr_status_t rc;
+ ef_dir_t *dc = ctx->dc;
+ const char * const *env;
+
+ ctx->proc = apr_pcalloc(ctx->p, sizeof(*ctx->proc));
+
+ rc = apr_procattr_create(&ctx->procattr, ctx->p);
+ ap_assert(rc == APR_SUCCESS);
+
+ rc = apr_procattr_io_set(ctx->procattr,
+ APR_CHILD_BLOCK,
+ APR_CHILD_BLOCK,
+ APR_CHILD_BLOCK);
+ ap_assert(rc == APR_SUCCESS);
+
+ rc = set_resource_limits(f->r, ctx->procattr);
+ ap_assert(rc == APR_SUCCESS);
+
+ if (dc->log_stderr > 0) {
+ rc = apr_procattr_child_err_set(ctx->procattr,
+ f->r->server->error_log, /* stderr in child */
+ NULL);
+ ap_assert(rc == APR_SUCCESS);
+ }
+
+ rc = apr_procattr_child_errfn_set(ctx->procattr, child_errfn);
+ ap_assert(rc == APR_SUCCESS);
+ apr_pool_userdata_set(f->r, ERRFN_USERDATA_KEY, apr_pool_cleanup_null, ctx->p);
+
+ rc = apr_procattr_error_check_set(ctx->procattr, 1);
+ if (rc != APR_SUCCESS) {
+ return rc;
+ }
+
+ /* add standard CGI variables as well as DOCUMENT_URI, DOCUMENT_PATH_INFO,
+ * and QUERY_STRING_UNESCAPED
+ */
+ ap_add_cgi_vars(f->r);
+ ap_add_common_vars(f->r);
+ apr_table_setn(f->r->subprocess_env, "DOCUMENT_URI", f->r->uri);
+ apr_table_setn(f->r->subprocess_env, "DOCUMENT_PATH_INFO", f->r->path_info);
+ if (f->r->args) {
+ /* QUERY_STRING is added by ap_add_cgi_vars */
+ char *arg_copy = apr_pstrdup(f->r->pool, f->r->args);
+ ap_unescape_url(arg_copy);
+ apr_table_setn(f->r->subprocess_env, "QUERY_STRING_UNESCAPED",
+ ap_escape_shell_cmd(f->r->pool, arg_copy));
+ }
+
+ env = (const char * const *) ap_create_environment(ctx->p,
+ f->r->subprocess_env);
+
+ rc = apr_proc_create(ctx->proc,
+ ctx->filter->command,
+ (const char * const *)ctx->filter->args,
+ env, /* environment */
+ ctx->procattr,
+ ctx->p);
+ if (rc != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, f->r, APLOGNO(01458)
+ "couldn't create child process to run `%s'",
+ ctx->filter->command);
+ return rc;
+ }
+
+ apr_pool_note_subprocess(ctx->p, ctx->proc, APR_KILL_AFTER_TIMEOUT);
+
+ /* We don't want the handle to the child's stdin inherited by any
+ * other processes created by httpd. Otherwise, when we close our
+ * handle, the child won't see EOF because another handle will still
+ * be open.
+ */
+
+ apr_pool_cleanup_register(ctx->p, ctx->proc->in,
+ apr_pool_cleanup_null, /* other mechanism */
+ ef_close_file);
+
+#if APR_FILES_AS_SOCKETS
+ {
+ apr_pollfd_t pfd = { 0 };
+
+ rc = apr_pollset_create(&ctx->pollset, 2, ctx->p, 0);
+ ap_assert(rc == APR_SUCCESS);
+
+ pfd.p = ctx->p;
+ pfd.desc_type = APR_POLL_FILE;
+ pfd.reqevents = APR_POLLOUT;
+ pfd.desc.f = ctx->proc->in;
+ rc = apr_pollset_add(ctx->pollset, &pfd);
+ ap_assert(rc == APR_SUCCESS);
+
+ pfd.reqevents = APR_POLLIN;
+ pfd.desc.f = ctx->proc->out;
+ rc = apr_pollset_add(ctx->pollset, &pfd);
+ ap_assert(rc == APR_SUCCESS);
+ }
+#endif
+
+ return APR_SUCCESS;
+}
+
+static const char *get_cfg_string(ef_dir_t *dc, ef_filter_t *filter, apr_pool_t *p)
+{
+ const char *log_stderr_str = dc->log_stderr < 1 ?
+ "NoLogStderr" : "LogStderr";
+ const char *preserve_content_length_str = filter->preserves_content_length ?
+ "PreservesContentLength" : "!PreserveContentLength";
+ const char *intype_str = !filter->intype ?
+ "*/*" : filter->intype;
+ const char *outtype_str = !filter->outtype ?
+ "(unchanged)" : filter->outtype;
+
+ return apr_psprintf(p,
+ "ExtFilterOptions %s %s ExtFilterInType %s "
+ "ExtFilterOuttype %s",
+ log_stderr_str, preserve_content_length_str,
+ intype_str, outtype_str);
+}
+
+static ef_filter_t *find_filter_def(const server_rec *s, const char *fname)
+{
+ ef_server_t *sc;
+ ef_filter_t *f;
+
+ sc = ap_get_module_config(s->module_config, &ext_filter_module);
+ f = apr_hash_get(sc->h, fname, APR_HASH_KEY_STRING);
+ if (!f && s != main_server) {
+ s = main_server;
+ sc = ap_get_module_config(s->module_config, &ext_filter_module);
+ f = apr_hash_get(sc->h, fname, APR_HASH_KEY_STRING);
+ }
+ return f;
+}
+
+static apr_status_t init_filter_instance(ap_filter_t *f)
+{
+ ef_ctx_t *ctx;
+ ef_dir_t *dc;
+ apr_status_t rv;
+
+ f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(ef_ctx_t));
+ dc = ap_get_module_config(f->r->per_dir_config,
+ &ext_filter_module);
+ ctx->dc = dc;
+ /* look for the user-defined filter */
+ ctx->filter = find_filter_def(f->r->server, f->frec->name);
+ if (!ctx->filter) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, APLOGNO(01459)
+ "couldn't find definition of filter '%s'",
+ f->frec->name);
+ return APR_EINVAL;
+ }
+ ctx->p = f->r->pool;
+ if (ctx->filter->intype &&
+ ctx->filter->intype != INTYPE_ALL) {
+ const char *ctypes;
+
+ if (ctx->filter->mode == INPUT_FILTER) {
+ ctypes = apr_table_get(f->r->headers_in, "Content-Type");
+ }
+ else {
+ ctypes = f->r->content_type;
+ }
+
+ if (ctypes) {
+ const char *ctype = ap_getword(f->r->pool, &ctypes, ';');
+
+ if (strcasecmp(ctx->filter->intype, ctype)) {
+ /* wrong IMT for us; don't mess with the output */
+ ctx->noop = 1;
+ }
+ }
+ else {
+ ctx->noop = 1;
+ }
+ }
+ if (ctx->filter->enable_env &&
+ !apr_table_get(f->r->subprocess_env, ctx->filter->enable_env)) {
+ /* an environment variable that enables the filter isn't set; bail */
+ ctx->noop = 1;
+ }
+ if (ctx->filter->disable_env &&
+ apr_table_get(f->r->subprocess_env, ctx->filter->disable_env)) {
+ /* an environment variable that disables the filter is set; bail */
+ ctx->noop = 1;
+ }
+ if (!ctx->noop) {
+ rv = init_ext_filter_process(f);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ if (ctx->filter->outtype &&
+ ctx->filter->outtype != OUTTYPE_UNCHANGED) {
+ ap_set_content_type(f->r, ctx->filter->outtype);
+ }
+ if (ctx->filter->preserves_content_length != 1) {
+ /* nasty, but needed to avoid confusing the browser
+ */
+ apr_table_unset(f->r->headers_out, "Content-Length");
+ }
+ }
+
+ if (APLOGrtrace1(f->r)) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, f->r,
+ "%sfiltering `%s' of type `%s' through `%s', cfg %s",
+ ctx->noop ? "NOT " : "",
+ f->r->uri ? f->r->uri : f->r->filename,
+ f->r->content_type ? f->r->content_type : "(unspecified)",
+ ctx->filter->command,
+ get_cfg_string(dc, ctx->filter, f->r->pool));
+ }
+
+ return APR_SUCCESS;
+}
+
+/* drain_available_output():
+ *
+ * if any data is available from the filter, read it and append it
+ * to the bucket brigade
+ */
+static apr_status_t drain_available_output(ap_filter_t *f,
+ apr_bucket_brigade *bb)
+{
+ request_rec *r = f->r;
+ conn_rec *c = r->connection;
+ ef_ctx_t *ctx = f->ctx;
+ apr_size_t len;
+ char buf[4096];
+ apr_status_t rv;
+ apr_bucket *b;
+
+ while (1) {
+ int lvl = APLOG_TRACE5;
+ len = sizeof(buf);
+ rv = apr_file_read(ctx->proc->out, buf, &len);
+ if (rv && !APR_STATUS_IS_EAGAIN(rv))
+ lvl = APLOG_DEBUG;
+ ap_log_rerror(APLOG_MARK, lvl, rv, r, APLOGNO(01460)
+ "apr_file_read(child output), len %" APR_SIZE_T_FMT, len);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ b = apr_bucket_heap_create(buf, len, NULL, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ return APR_SUCCESS;
+ }
+ /* we should never get here; if we do, a bogus error message would be
+ * the least of our problems
+ */
+ return APR_ANONYMOUS;
+}
+
+static apr_status_t pass_data_to_filter(ap_filter_t *f, const char *data,
+ apr_size_t len, apr_bucket_brigade *bb)
+{
+ ef_ctx_t *ctx = f->ctx;
+ apr_status_t rv;
+ apr_size_t bytes_written = 0;
+ apr_size_t tmplen;
+
+ do {
+ tmplen = len - bytes_written;
+ rv = apr_file_write_full(ctx->proc->in,
+ (const char *)data + bytes_written,
+ tmplen, &tmplen);
+ bytes_written += tmplen;
+ if (rv != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(rv)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(01461)
+ "apr_file_write(child input), len %" APR_SIZE_T_FMT,
+ tmplen);
+ return rv;
+ }
+ if (APR_STATUS_IS_EAGAIN(rv)) {
+ /* XXX handle blocking conditions here... if we block, we need
+ * to read data from the child process and pass it down to the
+ * next filter!
+ */
+ rv = drain_available_output(f, bb);
+ if (APR_STATUS_IS_EAGAIN(rv)) {
+#if APR_FILES_AS_SOCKETS
+ int num_events;
+ const apr_pollfd_t *pdesc;
+
+ rv = apr_pollset_poll(ctx->pollset, f->r->server->timeout,
+ &num_events, &pdesc);
+ if (rv != APR_SUCCESS && !APR_STATUS_IS_EINTR(rv)) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, f->r, APLOGNO(01462)
+ "apr_pollset_poll()");
+ /* some error such as APR_TIMEUP */
+ return rv;
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE6, rv, f->r,
+ "apr_pollset_poll()");
+#else /* APR_FILES_AS_SOCKETS */
+ /* Yuck... I'd really like to wait until I can read
+ * or write, but instead I have to sleep and try again
+ */
+ apr_sleep(apr_time_from_msec(100));
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE6, 0, f->r, "apr_sleep()");
+#endif /* APR_FILES_AS_SOCKETS */
+ }
+ else if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+ } while (bytes_written < len);
+ return rv;
+}
+
+/* ef_unified_filter:
+ *
+ * runs the bucket brigade bb through the filter and puts the result into
+ * bb, dropping the previous content of bb (the input)
+ */
+
+static int ef_unified_filter(ap_filter_t *f, apr_bucket_brigade *bb)
+{
+ request_rec *r = f->r;
+ conn_rec *c = r->connection;
+ ef_ctx_t *ctx = f->ctx;
+ apr_bucket *b;
+ apr_size_t len;
+ const char *data;
+ apr_status_t rv;
+ char buf[4096];
+ apr_bucket *eos = NULL;
+ apr_bucket_brigade *bb_tmp;
+
+ bb_tmp = apr_brigade_create(r->pool, c->bucket_alloc);
+
+ for (b = APR_BRIGADE_FIRST(bb);
+ b != APR_BRIGADE_SENTINEL(bb);
+ b = APR_BUCKET_NEXT(b))
+ {
+ if (APR_BUCKET_IS_EOS(b)) {
+ eos = b;
+ break;
+ }
+
+ if (AP_BUCKET_IS_ERROR(b)) {
+ apr_bucket *cpy;
+ apr_bucket_copy(b, &cpy);
+ APR_BRIGADE_INSERT_TAIL(bb_tmp, cpy);
+ break;
+ }
+
+ rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01463) "apr_bucket_read()");
+ return rv;
+ }
+
+ /* Good cast, we just tested len isn't negative */
+ if (len > 0 &&
+ (rv = pass_data_to_filter(f, data, (apr_size_t)len, bb_tmp))
+ != APR_SUCCESS) {
+ return rv;
+ }
+ }
+
+ apr_brigade_cleanup(bb);
+ APR_BRIGADE_CONCAT(bb, bb_tmp);
+ apr_brigade_destroy(bb_tmp);
+
+ if (eos) {
+ /* close the child's stdin to signal that no more data is coming;
+ * that will cause the child to finish generating output
+ */
+ if ((rv = apr_file_close(ctx->proc->in)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01464)
+ "apr_file_close(child input)");
+ return rv;
+ }
+ /* since we've seen eos and closed the child's stdin, set the proper pipe
+ * timeout; we don't care if we don't return from apr_file_read() for a while...
+ */
+ rv = apr_file_pipe_timeout_set(ctx->proc->out,
+ r->server->timeout);
+ if (rv) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01465)
+ "apr_file_pipe_timeout_set(child output)");
+ return rv;
+ }
+ }
+
+ do {
+ int lvl = APLOG_TRACE6;
+ len = sizeof(buf);
+ rv = apr_file_read(ctx->proc->out, buf, &len);
+ if (rv && !APR_STATUS_IS_EOF(rv) && !APR_STATUS_IS_EAGAIN(rv))
+ lvl = APLOG_ERR;
+ ap_log_rerror(APLOG_MARK, lvl, rv, r, APLOGNO(01466)
+ "apr_file_read(child output), len %" APR_SIZE_T_FMT, len);
+ if (APR_STATUS_IS_EAGAIN(rv)) {
+ if (eos) {
+ /* should not occur, because we have an APR timeout in place */
+ AP_DEBUG_ASSERT(1 != 1);
+ }
+ return APR_SUCCESS;
+ }
+
+ if (rv == APR_SUCCESS) {
+ b = apr_bucket_heap_create(buf, len, NULL, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ }
+ } while (rv == APR_SUCCESS);
+
+ if (!APR_STATUS_IS_EOF(rv)) {
+ return rv;
+ }
+
+ if (eos) {
+ b = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ ctx->hit_eos = 1;
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t ef_output_filter(ap_filter_t *f, apr_bucket_brigade *bb)
+{
+ request_rec *r = f->r;
+ ef_ctx_t *ctx = f->ctx;
+ apr_status_t rv;
+
+ if (!ctx) {
+ if ((rv = init_filter_instance(f)) != APR_SUCCESS) {
+ ctx = f->ctx;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01467)
+ "can't initialise output filter %s: %s",
+ f->frec->name,
+ (ctx->dc->onfail == 1) ? "removing" : "aborting");
+ ap_remove_output_filter(f);
+ if (ctx->dc->onfail == 1) {
+ return ap_pass_brigade(f->next, bb);
+ }
+ else {
+ apr_bucket *e;
+ f->r->status_line = "500 Internal Server Error";
+
+ apr_brigade_cleanup(bb);
+ e = ap_bucket_error_create(HTTP_INTERNAL_SERVER_ERROR,
+ NULL, r->pool,
+ f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ e = apr_bucket_eos_create(f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ ap_pass_brigade(f->next, bb);
+ return AP_FILTER_ERROR;
+ }
+ }
+ ctx = f->ctx;
+ }
+ if (ctx->noop) {
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ rv = ef_unified_filter(f, bb);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01468)
+ "ef_unified_filter() failed");
+ }
+
+ if ((rv = ap_pass_brigade(f->next, bb)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(01469)
+ "ap_pass_brigade() failed");
+ }
+ return rv;
+}
+
+static apr_status_t ef_input_filter(ap_filter_t *f, apr_bucket_brigade *bb,
+ ap_input_mode_t mode, apr_read_type_e block,
+ apr_off_t readbytes)
+{
+ ef_ctx_t *ctx = f->ctx;
+ apr_status_t rv;
+
+ /* just get out of the way of things we don't want. */
+ if (mode != AP_MODE_READBYTES) {
+ return ap_get_brigade(f->next, bb, mode, block, readbytes);
+ }
+
+ if (!ctx) {
+ if ((rv = init_filter_instance(f)) != APR_SUCCESS) {
+ ctx = f->ctx;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(01470)
+ "can't initialise input filter %s: %s",
+ f->frec->name,
+ (ctx->dc->onfail == 1) ? "removing" : "aborting");
+ ap_remove_input_filter(f);
+ if (ctx->dc->onfail == 1) {
+ return ap_get_brigade(f->next, bb, mode, block, readbytes);
+ }
+ else {
+ f->r->status = HTTP_INTERNAL_SERVER_ERROR;
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+ ctx = f->ctx;
+ }
+
+ if (ctx->hit_eos) {
+ /* Match behaviour of HTTP_IN if filter is re-invoked after
+ * hitting EOS: give back another EOS. */
+ apr_bucket *e = apr_bucket_eos_create(f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ return APR_SUCCESS;
+ }
+
+ if (ctx->noop) {
+ ap_remove_input_filter(f);
+ return ap_get_brigade(f->next, bb, mode, block, readbytes);
+ }
+
+ rv = ap_get_brigade(f->next, bb, mode, block, readbytes);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ rv = ef_unified_filter(f, bb);
+ return rv;
+}
+
+AP_DECLARE_MODULE(ext_filter) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_ef_dir_conf,
+ merge_ef_dir_conf,
+ create_ef_server_conf,
+ NULL,
+ cmds,
+ register_hooks
+};
diff --git a/modules/filters/mod_ext_filter.dep b/modules/filters/mod_ext_filter.dep
new file mode 100644
index 0000000..6b66202
--- /dev/null
+++ b/modules/filters/mod_ext_filter.dep
@@ -0,0 +1,58 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_ext_filter.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_ext_filter.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_script.h"\
+ "..\..\include\util_time.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/filters/mod_ext_filter.dsp b/modules/filters/mod_ext_filter.dsp
new file mode 100644
index 0000000..f87d5d7
--- /dev/null
+++ b/modules/filters/mod_ext_filter.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_ext_filter" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_ext_filter - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_ext_filter.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_ext_filter.mak" CFG="mod_ext_filter - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_ext_filter - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_ext_filter - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_ext_filter - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_ext_filter_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_ext_filter.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_ext_filter.so" /d LONG_NAME="ext_filter_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_ext_filter.so" /base:@..\..\os\win32\BaseAddr.ref,mod_ext_filter.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_ext_filter.so" /base:@..\..\os\win32\BaseAddr.ref,mod_ext_filter.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_ext_filter.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_ext_filter - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_ext_filter_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_ext_filter.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_ext_filter.so" /d LONG_NAME="ext_filter_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_ext_filter.so" /base:@..\..\os\win32\BaseAddr.ref,mod_ext_filter.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_ext_filter.so" /base:@..\..\os\win32\BaseAddr.ref,mod_ext_filter.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_ext_filter.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_ext_filter - Win32 Release"
+# Name "mod_ext_filter - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_ext_filter.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/filters/mod_ext_filter.exp b/modules/filters/mod_ext_filter.exp
new file mode 100644
index 0000000..ed3b8fc
--- /dev/null
+++ b/modules/filters/mod_ext_filter.exp
@@ -0,0 +1 @@
+ext_filter_module
diff --git a/modules/filters/mod_ext_filter.mak b/modules/filters/mod_ext_filter.mak
new file mode 100644
index 0000000..f03bff3
--- /dev/null
+++ b/modules/filters/mod_ext_filter.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_ext_filter.dsp
+!IF "$(CFG)" == ""
+CFG=mod_ext_filter - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_ext_filter - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_ext_filter - Win32 Release" && "$(CFG)" != "mod_ext_filter - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_ext_filter.mak" CFG="mod_ext_filter - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_ext_filter - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_ext_filter - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_ext_filter - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_ext_filter.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_ext_filter.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_ext_filter.obj"
+ -@erase "$(INTDIR)\mod_ext_filter.res"
+ -@erase "$(INTDIR)\mod_ext_filter_src.idb"
+ -@erase "$(INTDIR)\mod_ext_filter_src.pdb"
+ -@erase "$(OUTDIR)\mod_ext_filter.exp"
+ -@erase "$(OUTDIR)\mod_ext_filter.lib"
+ -@erase "$(OUTDIR)\mod_ext_filter.pdb"
+ -@erase "$(OUTDIR)\mod_ext_filter.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_ext_filter_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_ext_filter.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_ext_filter.so" /d LONG_NAME="ext_filter_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_ext_filter.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_ext_filter.pdb" /debug /out:"$(OUTDIR)\mod_ext_filter.so" /implib:"$(OUTDIR)\mod_ext_filter.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_ext_filter.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_ext_filter.obj" \
+ "$(INTDIR)\mod_ext_filter.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_ext_filter.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_ext_filter.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_ext_filter.so"
+ if exist .\Release\mod_ext_filter.so.manifest mt.exe -manifest .\Release\mod_ext_filter.so.manifest -outputresource:.\Release\mod_ext_filter.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_ext_filter - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_ext_filter.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_ext_filter.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_ext_filter.obj"
+ -@erase "$(INTDIR)\mod_ext_filter.res"
+ -@erase "$(INTDIR)\mod_ext_filter_src.idb"
+ -@erase "$(INTDIR)\mod_ext_filter_src.pdb"
+ -@erase "$(OUTDIR)\mod_ext_filter.exp"
+ -@erase "$(OUTDIR)\mod_ext_filter.lib"
+ -@erase "$(OUTDIR)\mod_ext_filter.pdb"
+ -@erase "$(OUTDIR)\mod_ext_filter.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_ext_filter_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_ext_filter.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_ext_filter.so" /d LONG_NAME="ext_filter_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_ext_filter.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_ext_filter.pdb" /debug /out:"$(OUTDIR)\mod_ext_filter.so" /implib:"$(OUTDIR)\mod_ext_filter.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_ext_filter.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_ext_filter.obj" \
+ "$(INTDIR)\mod_ext_filter.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_ext_filter.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_ext_filter.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_ext_filter.so"
+ if exist .\Debug\mod_ext_filter.so.manifest mt.exe -manifest .\Debug\mod_ext_filter.so.manifest -outputresource:.\Debug\mod_ext_filter.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_ext_filter.dep")
+!INCLUDE "mod_ext_filter.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_ext_filter.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_ext_filter - Win32 Release" || "$(CFG)" == "mod_ext_filter - Win32 Debug"
+
+!IF "$(CFG)" == "mod_ext_filter - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\filters"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_ext_filter - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\filters"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_ext_filter - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\filters"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_ext_filter - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\filters"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_ext_filter - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\filters"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_ext_filter - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\filters"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\filters"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_ext_filter - Win32 Release"
+
+
+"$(INTDIR)\mod_ext_filter.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_ext_filter.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_ext_filter.so" /d LONG_NAME="ext_filter_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_ext_filter - Win32 Debug"
+
+
+"$(INTDIR)\mod_ext_filter.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_ext_filter.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_ext_filter.so" /d LONG_NAME="ext_filter_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_ext_filter.c
+
+"$(INTDIR)\mod_ext_filter.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/filters/mod_filter.c b/modules/filters/mod_filter.c
new file mode 100644
index 0000000..5b5ecf6
--- /dev/null
+++ b/modules/filters/mod_filter.c
@@ -0,0 +1,767 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+#include "apr_lib.h"
+#include "apr_strings.h"
+#include "apr_hash.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_request.h"
+#include "http_log.h"
+#include "util_filter.h"
+#include "ap_expr.h"
+
+module AP_MODULE_DECLARE_DATA filter_module;
+
+/**
+ * @brief is a filter provider, as defined and implemented by mod_filter.
+ *
+ * The struct is a linked list, with dispatch criteria
+ * defined for each filter. The provider implementation itself is a
+ * (2.0-compatible) ap_filter_rec_t* frec.
+ */
+struct ap_filter_provider_t {
+ ap_expr_info_t *expr;
+ const char **types;
+
+ /** The filter that implements this provider */
+ ap_filter_rec_t *frec;
+
+ /** The next provider in the list */
+ ap_filter_provider_t *next;
+};
+
+/** we need provider_ctx to save ctx values set by providers in filter_init */
+typedef struct provider_ctx provider_ctx;
+struct provider_ctx {
+ ap_filter_provider_t *provider;
+ void *ctx;
+ provider_ctx *next;
+};
+typedef struct {
+ ap_out_filter_func func;
+ void *fctx;
+ provider_ctx *init_ctx;
+} harness_ctx;
+
+typedef struct mod_filter_chain {
+ const char *fname;
+ struct mod_filter_chain *next;
+} mod_filter_chain;
+
+typedef struct {
+ apr_hash_t *live_filters;
+ mod_filter_chain *chain;
+} mod_filter_cfg;
+
+typedef struct {
+ const char* range ;
+} mod_filter_ctx ;
+
+
+static void filter_trace(conn_rec *c, int debug, const char *fname,
+ apr_bucket_brigade *bb)
+{
+ apr_bucket *b;
+
+ switch (debug) {
+ case 0: /* normal, operational use */
+ return;
+ case 1: /* mod_diagnostics level */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(01375) "%s", fname);
+ for (b = APR_BRIGADE_FIRST(bb);
+ b != APR_BRIGADE_SENTINEL(bb);
+ b = APR_BUCKET_NEXT(b)) {
+
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(01376)
+ "%s: type: %s, length: %" APR_SIZE_T_FMT,
+ fname, b->type->name ? b->type->name : "(unknown)",
+ b->length);
+ }
+ break;
+ }
+}
+
+static int filter_init(ap_filter_t *f)
+{
+ ap_filter_provider_t *p;
+ provider_ctx *pctx;
+ int err;
+ ap_filter_rec_t *filter = f->frec;
+
+ harness_ctx *fctx = apr_pcalloc(f->r->pool, sizeof(harness_ctx));
+ for (p = filter->providers; p; p = p->next) {
+ if (p->frec->filter_init_func == filter_init) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, f->c, APLOGNO(01377)
+ "Chaining of FilterProviders not supported");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ else if (p->frec->filter_init_func) {
+ f->ctx = NULL;
+ if ((err = p->frec->filter_init_func(f)) != OK) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, f->c, APLOGNO(01378)
+ "filter_init for %s failed", p->frec->name);
+ return err; /* if anyone errors out here, so do we */
+ }
+ if (f->ctx != NULL) {
+ /* the filter init function set a ctx - we need to record it */
+ pctx = apr_pcalloc(f->r->pool, sizeof(provider_ctx));
+ pctx->provider = p;
+ pctx->ctx = f->ctx;
+ pctx->next = fctx->init_ctx;
+ fctx->init_ctx = pctx;
+ }
+ }
+ }
+ f->ctx = fctx;
+ return OK;
+}
+
+static int filter_lookup(ap_filter_t *f, ap_filter_rec_t *filter)
+{
+ ap_filter_provider_t *provider;
+ int match = 0;
+ const char *err = NULL;
+ request_rec *r = f->r;
+ harness_ctx *ctx = f->ctx;
+ provider_ctx *pctx;
+#ifndef NO_PROTOCOL
+ unsigned int proto_flags;
+ mod_filter_ctx *rctx = ap_get_module_config(r->request_config,
+ &filter_module);
+#endif
+
+ /* Check registered providers in order */
+ for (provider = filter->providers; provider; provider = provider->next) {
+ if (provider->expr) {
+ match = ap_expr_exec(r, provider->expr, &err);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01379)
+ "Error evaluating filter dispatch condition: %s",
+ err);
+ match = 0;
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+ "Expression condition for '%s' %s",
+ provider->frec->name,
+ match ? "matched" : "did not match");
+ }
+ else if (r->content_type) {
+ const char **type = provider->types;
+ size_t len = strcspn(r->content_type, "; \t");
+ AP_DEBUG_ASSERT(type != NULL);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r,
+ "Content-Type '%s' ...", r->content_type);
+ while (*type) {
+ /* Handle 'content-type;charset=...' correctly */
+ if (strncmp(*type, r->content_type, len) == 0
+ && (*type)[len] == '\0') {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r,
+ "... matched '%s'", *type);
+ match = 1;
+ break;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r,
+ "... did not match '%s'", *type);
+ }
+ type++;
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+ "Content-Type condition for '%s' %s",
+ provider->frec->name,
+ match ? "matched" : "did not match");
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+ "Content-Type condition for '%s' did not match: "
+ "no Content-Type", provider->frec->name);
+ }
+
+ if (match) {
+ /* condition matches this provider */
+#ifndef NO_PROTOCOL
+ /* check protocol
+ *
+ * FIXME:
+ * This is a quick hack and almost certainly buggy.
+ * The idea is that by putting this in mod_filter, we relieve
+ * filter implementations of the burden of fixing up HTTP headers
+ * for cases that are routinely affected by filters.
+ *
+ * Default is ALWAYS to do nothing, so as not to tread on the
+ * toes of filters which want to do it themselves.
+ *
+ */
+ proto_flags = provider->frec->proto_flags;
+
+ /* some specific things can't happen in a proxy */
+ if (r->proxyreq) {
+ if (proto_flags & AP_FILTER_PROTO_NO_PROXY) {
+ /* can't use this provider; try next */
+ continue;
+ }
+
+ if (proto_flags & AP_FILTER_PROTO_TRANSFORM) {
+ const char *str = apr_table_get(r->headers_out,
+ "Cache-Control");
+ if (str) {
+ if (ap_strcasestr(str, "no-transform")) {
+ /* can't use this provider; try next */
+ continue;
+ }
+ }
+ apr_table_addn(r->headers_out, "Warning",
+ apr_psprintf(r->pool,
+ "214 %s Transformation applied",
+ r->hostname));
+ }
+ }
+
+ /* things that are invalidated if the filter transforms content */
+ if (proto_flags & AP_FILTER_PROTO_CHANGE) {
+ apr_table_unset(r->headers_out, "Content-MD5");
+ apr_table_unset(r->headers_out, "ETag");
+ if (proto_flags & AP_FILTER_PROTO_CHANGE_LENGTH) {
+ apr_table_unset(r->headers_out, "Content-Length");
+ }
+ }
+
+ /* no-cache is for a filter that has different effect per-hit */
+ if (proto_flags & AP_FILTER_PROTO_NO_CACHE) {
+ apr_table_unset(r->headers_out, "Last-Modified");
+ apr_table_addn(r->headers_out, "Cache-Control", "no-cache");
+ }
+
+ if (proto_flags & AP_FILTER_PROTO_NO_BYTERANGE) {
+ apr_table_setn(r->headers_out, "Accept-Ranges", "none");
+ }
+ else if (rctx && rctx->range) {
+ /* restore range header we saved earlier */
+ apr_table_setn(r->headers_in, "Range", rctx->range);
+ rctx->range = NULL;
+ }
+#endif
+ for (pctx = ctx->init_ctx; pctx; pctx = pctx->next) {
+ if (pctx->provider == provider) {
+ ctx->fctx = pctx->ctx ;
+ }
+ }
+ ctx->func = provider->frec->filter_func.out_func;
+ return 1;
+ }
+ }
+
+ /* No provider matched */
+ return 0;
+}
+
+static apr_status_t filter_harness(ap_filter_t *f, apr_bucket_brigade *bb)
+{
+ apr_status_t ret;
+#ifndef NO_PROTOCOL
+ const char *cachecontrol;
+#endif
+ harness_ctx *ctx = f->ctx;
+ ap_filter_rec_t *filter = f->frec;
+
+ if (f->r->status != 200
+ && !apr_table_get(f->r->subprocess_env, "filter-errordocs")) {
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ filter_trace(f->c, filter->debug, f->frec->name, bb);
+
+ /* look up a handler function if we haven't already set it */
+ if (!ctx->func) {
+#ifndef NO_PROTOCOL
+ if (f->r->proxyreq) {
+ if (filter->proto_flags & AP_FILTER_PROTO_NO_PROXY) {
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ if (filter->proto_flags & AP_FILTER_PROTO_TRANSFORM) {
+ cachecontrol = apr_table_get(f->r->headers_out,
+ "Cache-Control");
+ if (cachecontrol) {
+ if (ap_strcasestr(cachecontrol, "no-transform")) {
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+ }
+ }
+ }
+#endif
+ if (!filter_lookup(f, filter)) {
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+ AP_DEBUG_ASSERT(ctx->func != NULL);
+ }
+
+ /* call the content filter with its own context, then restore our
+ * context
+ */
+ f->ctx = ctx->fctx;
+ ret = ctx->func(f, bb);
+ ctx->fctx = f->ctx;
+ f->ctx = ctx;
+
+ return ret;
+}
+
+#ifndef NO_PROTOCOL
+static const char *filter_protocol(cmd_parms *cmd, void *CFG, const char *fname,
+ const char *pname, const char *proto)
+{
+ static const char *sep = ";, \t";
+ char *arg;
+ char *tok = 0;
+ unsigned int flags = 0;
+ mod_filter_cfg *cfg = CFG;
+ ap_filter_provider_t *provider = NULL;
+ ap_filter_rec_t *filter = apr_hash_get(cfg->live_filters, fname,
+ APR_HASH_KEY_STRING);
+
+ if (!filter) {
+ return "FilterProtocol: No such filter";
+ }
+
+ /* Fixup the args: it's really pname that's optional */
+ if (proto == NULL) {
+ proto = pname;
+ pname = NULL;
+ }
+ else {
+ /* Find provider */
+ for (provider = filter->providers; provider; provider = provider->next) {
+ if (!strcasecmp(provider->frec->name, pname)) {
+ break;
+ }
+ }
+ if (!provider) {
+ return "FilterProtocol: No such provider for this filter";
+ }
+ }
+
+ /* Now set flags from our args */
+ for (arg = apr_strtok(apr_pstrdup(cmd->temp_pool, proto), sep, &tok);
+ arg; arg = apr_strtok(NULL, sep, &tok)) {
+
+ if (!strcasecmp(arg, "change=yes")) {
+ flags |= AP_FILTER_PROTO_CHANGE | AP_FILTER_PROTO_CHANGE_LENGTH;
+ }
+ if (!strcasecmp(arg, "change=no")) {
+ flags &= ~(AP_FILTER_PROTO_CHANGE | AP_FILTER_PROTO_CHANGE_LENGTH);
+ }
+ else if (!strcasecmp(arg, "change=1:1")) {
+ flags |= AP_FILTER_PROTO_CHANGE;
+ }
+ else if (!strcasecmp(arg, "byteranges=no")) {
+ flags |= AP_FILTER_PROTO_NO_BYTERANGE;
+ }
+ else if (!strcasecmp(arg, "proxy=no")) {
+ flags |= AP_FILTER_PROTO_NO_PROXY;
+ }
+ else if (!strcasecmp(arg, "proxy=transform")) {
+ flags |= AP_FILTER_PROTO_TRANSFORM;
+ }
+ else if (!strcasecmp(arg, "cache=no")) {
+ flags |= AP_FILTER_PROTO_NO_CACHE;
+ }
+ }
+
+ if (pname) {
+ provider->frec->proto_flags = flags;
+ }
+ else {
+ filter->proto_flags = flags;
+ }
+
+ return NULL;
+}
+#endif
+
+static const char *filter_declare(cmd_parms *cmd, void *CFG, const char *fname,
+ const char *place)
+{
+ mod_filter_cfg *cfg = (mod_filter_cfg *)CFG;
+ ap_filter_rec_t *filter;
+
+ filter = apr_pcalloc(cmd->pool, sizeof(ap_filter_rec_t));
+ apr_hash_set(cfg->live_filters, fname, APR_HASH_KEY_STRING, filter);
+
+ filter->name = fname;
+ filter->filter_init_func = filter_init;
+ filter->filter_func.out_func = filter_harness;
+ filter->ftype = AP_FTYPE_RESOURCE;
+ filter->next = NULL;
+
+ if (place) {
+ if (!strcasecmp(place, "CONTENT_SET")) {
+ filter->ftype = AP_FTYPE_CONTENT_SET;
+ }
+ else if (!strcasecmp(place, "PROTOCOL")) {
+ filter->ftype = AP_FTYPE_PROTOCOL;
+ }
+ else if (!strcasecmp(place, "CONNECTION")) {
+ filter->ftype = AP_FTYPE_CONNECTION;
+ }
+ else if (!strcasecmp(place, "NETWORK")) {
+ filter->ftype = AP_FTYPE_NETWORK;
+ }
+ }
+
+ return NULL;
+}
+
+static const char *add_filter(cmd_parms *cmd, void *CFG,
+ const char *fname, const char *pname,
+ const char *expr, const char **types)
+{
+ mod_filter_cfg *cfg = CFG;
+ ap_filter_provider_t *provider;
+ const char *c;
+ ap_filter_rec_t* frec;
+ ap_filter_rec_t* provider_frec;
+ ap_expr_info_t *node;
+ const char *err = NULL;
+
+ /* if provider has been registered, we can look it up */
+ provider_frec = ap_get_output_filter_handle(pname);
+ if (!provider_frec) {
+ return apr_psprintf(cmd->pool, "Unknown filter provider %s", pname);
+ }
+
+ /* fname has been declared with DeclareFilter, so we can look it up */
+ frec = apr_hash_get(cfg->live_filters, fname, APR_HASH_KEY_STRING);
+
+ /* or if provider is mod_filter itself, we can also look it up */
+ if (!frec) {
+ c = filter_declare(cmd, CFG, fname, NULL);
+ if ( c ) {
+ return c;
+ }
+ frec = apr_hash_get(cfg->live_filters, fname, APR_HASH_KEY_STRING);
+ frec->ftype = provider_frec->ftype;
+ }
+
+ if (!frec) {
+ return apr_psprintf(cmd->pool, "Undeclared smart filter %s", fname);
+ }
+
+ provider = apr_palloc(cmd->pool, sizeof(ap_filter_provider_t));
+ if (expr) {
+ node = ap_expr_parse_cmd(cmd, expr, 0, &err, NULL);
+ if (err) {
+ return apr_pstrcat(cmd->pool,
+ "Error parsing FilterProvider expression:", err,
+ NULL);
+ }
+ provider->expr = node;
+ provider->types = NULL;
+ }
+ else {
+ provider->types = types;
+ provider->expr = NULL;
+ }
+ provider->frec = provider_frec;
+ provider->next = frec->providers;
+ frec->providers = provider;
+ return NULL;
+}
+
+static const char *filter_provider(cmd_parms *cmd, void *CFG,
+ const char *fname, const char *pname,
+ const char *expr)
+{
+ return add_filter(cmd, CFG, fname, pname, expr, NULL);
+}
+
+static const char *filter_chain(cmd_parms *cmd, void *CFG, const char *arg)
+{
+ mod_filter_chain *p;
+ mod_filter_chain *q;
+ mod_filter_cfg *cfg = CFG;
+
+ switch (arg[0]) {
+ case '+': /* add to end of chain */
+ p = apr_pcalloc(cmd->pool, sizeof(mod_filter_chain));
+ p->fname = arg+1;
+ if (cfg->chain) {
+ for (q = cfg->chain; q->next; q = q->next);
+ q->next = p;
+ }
+ else {
+ cfg->chain = p;
+ }
+ break;
+
+ case '@': /* add to start of chain */
+ p = apr_palloc(cmd->pool, sizeof(mod_filter_chain));
+ p->fname = arg+1;
+ p->next = cfg->chain;
+ cfg->chain = p;
+ break;
+
+ case '-': /* remove from chain */
+ if (cfg->chain) {
+ if (strcasecmp(cfg->chain->fname, arg+1)) {
+ for (p = cfg->chain; p->next; p = p->next) {
+ if (!strcasecmp(p->next->fname, arg+1)) {
+ p->next = p->next->next;
+ }
+ }
+ }
+ else {
+ cfg->chain = cfg->chain->next;
+ }
+ }
+ break;
+
+ case '!': /* Empty the chain */
+ /** IG: Add a NULL provider to the beginning so that
+ * we can ensure that we'll empty everything before
+ * this when doing config merges later */
+ p = apr_pcalloc(cmd->pool, sizeof(mod_filter_chain));
+ p->fname = NULL;
+ cfg->chain = p;
+ break;
+
+ case '=': /* initialise chain with this arg */
+ /** IG: Prepend a NULL provider to the beginning as above */
+ p = apr_pcalloc(cmd->pool, sizeof(mod_filter_chain));
+ p->fname = NULL;
+ p->next = apr_pcalloc(cmd->pool, sizeof(mod_filter_chain));
+ p->next->fname = arg+1;
+ cfg->chain = p;
+ break;
+
+ default: /* add to end */
+ p = apr_pcalloc(cmd->pool, sizeof(mod_filter_chain));
+ p->fname = arg;
+ if (cfg->chain) {
+ for (q = cfg->chain; q->next; q = q->next);
+ q->next = p;
+ }
+ else {
+ cfg->chain = p;
+ }
+ break;
+ }
+
+ return NULL;
+}
+
+static const char *filter_bytype1(cmd_parms *cmd, void *CFG,
+ const char *pname, const char **types)
+{
+ const char *rv;
+ const char *fname;
+ int seen_name = 0;
+ mod_filter_cfg *cfg = CFG;
+
+ /* construct fname from name */
+ fname = apr_pstrcat(cmd->pool, "BYTYPE:", pname, NULL);
+
+ /* check whether this is already registered, in which case
+ * it's already in the filter chain
+ */
+ if (apr_hash_get(cfg->live_filters, fname, APR_HASH_KEY_STRING)) {
+ seen_name = 1;
+ }
+
+ rv = add_filter(cmd, CFG, fname, pname, NULL, types);
+
+ /* If it's the first time through, add to filterchain */
+ if (rv == NULL && !seen_name) {
+ rv = filter_chain(cmd, CFG, fname);
+ }
+ return rv;
+}
+
+static const char *filter_bytype(cmd_parms *cmd, void *CFG,
+ int argc, char *const argv[])
+{
+ /* back compatibility, need to parse multiple components in filter name */
+ char *pname;
+ char *strtok_state = NULL;
+ char *name;
+ const char **types;
+ const char *rv = NULL;
+ if (argc < 2)
+ return "AddOutputFilterByType requires at least two arguments";
+ name = apr_pstrdup(cmd->temp_pool, argv[0]);
+ types = apr_palloc(cmd->pool, argc * sizeof(char *));
+ memcpy(types, &argv[1], (argc - 1) * sizeof(char *));
+ types[argc-1] = NULL;
+ for (pname = apr_strtok(name, ";", &strtok_state);
+ pname != NULL && rv == NULL;
+ pname = apr_strtok(NULL, ";", &strtok_state)) {
+ rv = filter_bytype1(cmd, CFG, pname, types);
+ }
+ return rv;
+}
+
+static const char *filter_debug(cmd_parms *cmd, void *CFG, const char *fname,
+ const char *level)
+{
+ mod_filter_cfg *cfg = CFG;
+ ap_filter_rec_t *frec = apr_hash_get(cfg->live_filters, fname,
+ APR_HASH_KEY_STRING);
+ if (!frec) {
+ return apr_psprintf(cmd->pool, "Undeclared smart filter %s", fname);
+ }
+ frec->debug = atoi(level);
+
+ return NULL;
+}
+
+static void filter_insert(request_rec *r)
+{
+ mod_filter_chain *p;
+ ap_filter_rec_t *filter;
+ mod_filter_cfg *cfg = ap_get_module_config(r->per_dir_config,
+ &filter_module);
+#ifndef NO_PROTOCOL
+ int ranges = 1;
+ mod_filter_ctx *ctx = apr_pcalloc(r->pool, sizeof(mod_filter_ctx));
+ ap_set_module_config(r->request_config, &filter_module, ctx);
+#endif
+
+ /** IG: Now that we've merged to the final config, go one last time
+ * through the chain, and prune out the NULL filters */
+
+ for (p = cfg->chain; p; p = p->next) {
+ if (p->fname == NULL)
+ cfg->chain = p->next;
+ }
+
+ for (p = cfg->chain; p; p = p->next) {
+ filter = apr_hash_get(cfg->live_filters, p->fname, APR_HASH_KEY_STRING);
+ if (filter == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01380)
+ "Unknown filter %s not added", p->fname);
+ continue;
+ }
+ ap_add_output_filter_handle(filter, NULL, r, r->connection);
+#ifndef NO_PROTOCOL
+ if (ranges && (filter->proto_flags
+ & (AP_FILTER_PROTO_NO_BYTERANGE
+ | AP_FILTER_PROTO_CHANGE_LENGTH))) {
+ ctx->range = apr_table_get(r->headers_in, "Range");
+ apr_table_unset(r->headers_in, "Range");
+ ranges = 0;
+ }
+#endif
+ }
+}
+
+static void filter_hooks(apr_pool_t *pool)
+{
+ ap_hook_insert_filter(filter_insert, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+static void *filter_config(apr_pool_t *pool, char *x)
+{
+ mod_filter_cfg *cfg = apr_palloc(pool, sizeof(mod_filter_cfg));
+ cfg->live_filters = apr_hash_make(pool);
+ cfg->chain = NULL;
+ return cfg;
+}
+
+static void *filter_merge(apr_pool_t *pool, void *BASE, void *ADD)
+{
+ mod_filter_cfg *base = BASE;
+ mod_filter_cfg *add = ADD;
+ mod_filter_chain *savelink = 0;
+ mod_filter_chain *newlink;
+ mod_filter_chain *p;
+ mod_filter_cfg *conf = apr_palloc(pool, sizeof(mod_filter_cfg));
+
+ conf->live_filters = apr_hash_overlay(pool, add->live_filters,
+ base->live_filters);
+ if (base->chain && add->chain) {
+ for (p = base->chain; p; p = p->next) {
+ newlink = apr_pmemdup(pool, p, sizeof(mod_filter_chain));
+ if (newlink->fname == NULL) {
+ conf->chain = savelink = newlink;
+ }
+ else if (savelink) {
+ savelink->next = newlink;
+ savelink = newlink;
+ }
+ else {
+ conf->chain = savelink = newlink;
+ }
+ }
+
+ for (p = add->chain; p; p = p->next) {
+ newlink = apr_pmemdup(pool, p, sizeof(mod_filter_chain));
+ /** Filter out merged chain resets */
+ if (newlink->fname == NULL) {
+ conf->chain = savelink = newlink;
+ }
+ else if (savelink) {
+ savelink->next = newlink;
+ savelink = newlink;
+ }
+ else {
+ conf->chain = savelink = newlink;
+ }
+ }
+ }
+ else if (add->chain) {
+ conf->chain = add->chain;
+ }
+ else {
+ conf->chain = base->chain;
+ }
+
+ return conf;
+}
+
+static const command_rec filter_cmds[] = {
+ AP_INIT_TAKE12("FilterDeclare", filter_declare, NULL, OR_OPTIONS,
+ "filter-name [filter-type]"),
+ AP_INIT_TAKE3("FilterProvider", filter_provider, NULL, OR_OPTIONS,
+ "filter-name provider-name match-expression"),
+ AP_INIT_ITERATE("FilterChain", filter_chain, NULL, OR_OPTIONS,
+ "list of filter names with optional [+-=!@]"),
+ AP_INIT_TAKE2("FilterTrace", filter_debug, NULL, RSRC_CONF | ACCESS_CONF,
+ "filter-name debug-level"),
+ AP_INIT_TAKE_ARGV("AddOutputFilterByType", filter_bytype, NULL, OR_FILEINFO,
+ "output filter name followed by one or more content-types"),
+#ifndef NO_PROTOCOL
+ AP_INIT_TAKE23("FilterProtocol", filter_protocol, NULL, OR_OPTIONS,
+ "filter-name [provider-name] protocol-args"),
+#endif
+ { NULL }
+};
+
+AP_DECLARE_MODULE(filter) = {
+ STANDARD20_MODULE_STUFF,
+ filter_config,
+ filter_merge,
+ NULL,
+ NULL,
+ filter_cmds,
+ filter_hooks
+};
diff --git a/modules/filters/mod_filter.dep b/modules/filters/mod_filter.dep
new file mode 100644
index 0000000..1701264
--- /dev/null
+++ b/modules/filters/mod_filter.dep
@@ -0,0 +1,50 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_filter.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_filter.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/filters/mod_filter.dsp b/modules/filters/mod_filter.dsp
new file mode 100644
index 0000000..ee4d484
--- /dev/null
+++ b/modules/filters/mod_filter.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_filter" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_filter - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_filter.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_filter.mak" CFG="mod_filter - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_filter - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_filter - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_filter - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_filter_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_filter.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_filter.so" /d LONG_NAME="filter_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_filter.so" /base:@..\..\os\win32\BaseAddr.ref,mod_filter.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_filter.so" /base:@..\..\os\win32\BaseAddr.ref,mod_filter.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_filter.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_filter - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_filter_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_filter.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_filter.so" /d LONG_NAME="filter_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_filter.so" /base:@..\..\os\win32\BaseAddr.ref,mod_filter.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_filter.so" /base:@..\..\os\win32\BaseAddr.ref,mod_filter.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_filter.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_filter - Win32 Release"
+# Name "mod_filter - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_filter.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/filters/mod_filter.mak b/modules/filters/mod_filter.mak
new file mode 100644
index 0000000..c753d9b
--- /dev/null
+++ b/modules/filters/mod_filter.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_filter.dsp
+!IF "$(CFG)" == ""
+CFG=mod_filter - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_filter - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_filter - Win32 Release" && "$(CFG)" != "mod_filter - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_filter.mak" CFG="mod_filter - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_filter - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_filter - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_filter - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_filter.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_filter.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_filter.obj"
+ -@erase "$(INTDIR)\mod_filter.res"
+ -@erase "$(INTDIR)\mod_filter_src.idb"
+ -@erase "$(INTDIR)\mod_filter_src.pdb"
+ -@erase "$(OUTDIR)\mod_filter.exp"
+ -@erase "$(OUTDIR)\mod_filter.lib"
+ -@erase "$(OUTDIR)\mod_filter.pdb"
+ -@erase "$(OUTDIR)\mod_filter.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_filter_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_filter.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_filter.so" /d LONG_NAME="filter_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_filter.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_filter.pdb" /debug /out:"$(OUTDIR)\mod_filter.so" /implib:"$(OUTDIR)\mod_filter.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_filter.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_filter.obj" \
+ "$(INTDIR)\mod_filter.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_filter.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_filter.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_filter.so"
+ if exist .\Release\mod_filter.so.manifest mt.exe -manifest .\Release\mod_filter.so.manifest -outputresource:.\Release\mod_filter.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_filter - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_filter.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_filter.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_filter.obj"
+ -@erase "$(INTDIR)\mod_filter.res"
+ -@erase "$(INTDIR)\mod_filter_src.idb"
+ -@erase "$(INTDIR)\mod_filter_src.pdb"
+ -@erase "$(OUTDIR)\mod_filter.exp"
+ -@erase "$(OUTDIR)\mod_filter.lib"
+ -@erase "$(OUTDIR)\mod_filter.pdb"
+ -@erase "$(OUTDIR)\mod_filter.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_filter_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_filter.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_filter.so" /d LONG_NAME="filter_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_filter.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_filter.pdb" /debug /out:"$(OUTDIR)\mod_filter.so" /implib:"$(OUTDIR)\mod_filter.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_filter.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_filter.obj" \
+ "$(INTDIR)\mod_filter.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_filter.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_filter.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_filter.so"
+ if exist .\Debug\mod_filter.so.manifest mt.exe -manifest .\Debug\mod_filter.so.manifest -outputresource:.\Debug\mod_filter.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_filter.dep")
+!INCLUDE "mod_filter.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_filter.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_filter - Win32 Release" || "$(CFG)" == "mod_filter - Win32 Debug"
+
+!IF "$(CFG)" == "mod_filter - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\filters"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_filter - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\filters"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_filter - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\filters"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_filter - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\filters"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_filter - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\filters"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_filter - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\filters"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\filters"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_filter - Win32 Release"
+
+
+"$(INTDIR)\mod_filter.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_filter.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_filter.so" /d LONG_NAME="filter_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_filter - Win32 Debug"
+
+
+"$(INTDIR)\mod_filter.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_filter.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_filter.so" /d LONG_NAME="filter_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_filter.c
+
+"$(INTDIR)\mod_filter.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/filters/mod_include.c b/modules/filters/mod_include.c
new file mode 100644
index 0000000..584d8fb
--- /dev/null
+++ b/modules/filters/mod_include.c
@@ -0,0 +1,4238 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_thread_proc.h"
+#include "apr_hash.h"
+#include "apr_user.h"
+#include "apr_lib.h"
+#include "apr_optional.h"
+
+#define APR_WANT_STRFUNC
+#define APR_WANT_MEMFUNC
+#include "apr_want.h"
+
+#include "ap_config.h"
+#include "util_filter.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_request.h"
+#include "http_core.h"
+#include "http_protocol.h"
+#include "http_log.h"
+#include "http_main.h"
+#include "util_script.h"
+#include "http_core.h"
+#include "mod_include.h"
+#include "ap_expr.h"
+
+/* helper for Latin1 <-> entity encoding */
+#if APR_CHARSET_EBCDIC
+#include "util_ebcdic.h"
+#define RAW_ASCII_CHAR(ch) apr_xlate_conv_byte(ap_hdrs_from_ascii, \
+ (unsigned char)ch)
+#else /* APR_CHARSET_EBCDIC */
+#define RAW_ASCII_CHAR(ch) (ch)
+#endif /* !APR_CHARSET_EBCDIC */
+
+
+/*
+ * +-------------------------------------------------------+
+ * | |
+ * | Types and Structures
+ * | |
+ * +-------------------------------------------------------+
+ */
+
+/* sll used for string expansion */
+typedef struct result_item {
+ struct result_item *next;
+ apr_size_t len;
+ const char *string;
+} result_item_t;
+
+/* conditional expression parser stuff */
+typedef enum {
+ TOKEN_STRING,
+ TOKEN_RE,
+ TOKEN_AND,
+ TOKEN_OR,
+ TOKEN_NOT,
+ TOKEN_EQ,
+ TOKEN_NE,
+ TOKEN_RBRACE,
+ TOKEN_LBRACE,
+ TOKEN_GROUP,
+ TOKEN_GE,
+ TOKEN_LE,
+ TOKEN_GT,
+ TOKEN_LT,
+ TOKEN_ACCESS
+} token_type_t;
+
+typedef struct {
+ token_type_t type;
+ const char *value;
+#ifdef DEBUG_INCLUDE
+ const char *s;
+#endif
+} token_t;
+
+typedef struct parse_node {
+ struct parse_node *parent;
+ struct parse_node *left;
+ struct parse_node *right;
+ token_t token;
+ int value;
+ int done;
+#ifdef DEBUG_INCLUDE
+ int dump_done;
+#endif
+} parse_node_t;
+
+typedef enum {
+ XBITHACK_OFF,
+ XBITHACK_ON,
+ XBITHACK_FULL,
+ XBITHACK_UNSET
+} xbithack_t;
+
+typedef struct {
+ const char *default_error_msg;
+ const char *default_time_fmt;
+ const char *undefined_echo;
+ xbithack_t xbithack;
+ signed char lastmodified;
+ signed char etag;
+ signed char legacy_expr;
+} include_dir_config;
+
+typedef struct {
+ const char *default_start_tag;
+ const char *default_end_tag;
+} include_server_config;
+
+/* main parser states */
+typedef enum {
+ PARSE_PRE_HEAD,
+ PARSE_HEAD,
+ PARSE_DIRECTIVE,
+ PARSE_DIRECTIVE_POSTNAME,
+ PARSE_DIRECTIVE_TAIL,
+ PARSE_DIRECTIVE_POSTTAIL,
+ PARSE_PRE_ARG,
+ PARSE_ARG,
+ PARSE_ARG_NAME,
+ PARSE_ARG_POSTNAME,
+ PARSE_ARG_EQ,
+ PARSE_ARG_PREVAL,
+ PARSE_ARG_VAL,
+ PARSE_ARG_VAL_ESC,
+ PARSE_ARG_POSTVAL,
+ PARSE_TAIL,
+ PARSE_TAIL_SEQ,
+ PARSE_EXECUTE
+} parse_state_t;
+
+typedef struct arg_item {
+ struct arg_item *next;
+ char *name;
+ apr_size_t name_len;
+ char *value;
+ apr_size_t value_len;
+} arg_item_t;
+
+typedef struct {
+ const char *source;
+ const char *rexp;
+ apr_size_t nsub;
+ ap_regmatch_t match[AP_MAX_REG_MATCH];
+ int have_match;
+} backref_t;
+
+typedef struct {
+ unsigned int T[256];
+ unsigned int x;
+ apr_size_t pattern_len;
+} bndm_t;
+
+struct ssi_internal_ctx {
+ parse_state_t state;
+ int seen_eos;
+ int error;
+ char quote; /* quote character value (or \0) */
+ apr_size_t parse_pos; /* parse position of partial matches */
+ apr_size_t bytes_read;
+
+ apr_bucket_brigade *tmp_bb;
+
+ const char *start_seq;
+ bndm_t *start_seq_pat;
+ const char *end_seq;
+ apr_size_t end_seq_len;
+ char *directive; /* name of the current directive */
+ apr_size_t directive_len; /* length of the current directive name */
+
+ arg_item_t *current_arg; /* currently parsed argument */
+ arg_item_t *argv; /* all arguments */
+
+ backref_t *re; /* NULL if there wasn't a regex yet */
+
+ const char *undefined_echo;
+ apr_size_t undefined_echo_len;
+
+ char legacy_expr; /* use ap_expr or legacy mod_include
+ expression parser? */
+
+ ap_expr_eval_ctx_t *expr_eval_ctx; /* NULL if there wasn't an ap_expr yet */
+ const char *expr_vary_this; /* for use by ap_expr_eval_ctx */
+ const char *expr_err; /* for use by ap_expr_eval_ctx */
+#ifdef DEBUG_INCLUDE
+ struct {
+ ap_filter_t *f;
+ apr_bucket_brigade *bb;
+ } debug;
+#endif
+};
+
+
+/*
+ * +-------------------------------------------------------+
+ * | |
+ * | Debugging Utilities
+ * | |
+ * +-------------------------------------------------------+
+ */
+
+#ifdef DEBUG_INCLUDE
+
+#define TYPE_TOKEN(token, ttype) do { \
+ (token)->type = ttype; \
+ (token)->s = #ttype; \
+} while(0)
+
+#define CREATE_NODE(ctx, name) do { \
+ (name) = apr_palloc((ctx)->dpool, sizeof(*(name))); \
+ (name)->parent = (name)->left = (name)->right = NULL; \
+ (name)->done = 0; \
+ (name)->dump_done = 0; \
+} while(0)
+
+static void debug_printf(include_ctx_t *ctx, const char *fmt, ...)
+{
+ va_list ap;
+ char *debug__str;
+
+ va_start(ap, fmt);
+ debug__str = apr_pvsprintf(ctx->pool, fmt, ap);
+ va_end(ap);
+
+ APR_BRIGADE_INSERT_TAIL(ctx->intern->debug.bb, apr_bucket_pool_create(
+ debug__str, strlen(debug__str), ctx->pool,
+ ctx->intern->debug.f->c->bucket_alloc));
+}
+
+#define DUMP__CHILD(ctx, is, node, child) if (1) { \
+ parse_node_t *d__c = node->child; \
+ if (d__c) { \
+ if (!d__c->dump_done) { \
+ if (d__c->parent != node) { \
+ debug_printf(ctx, "!!! Parse tree is not consistent !!!\n"); \
+ if (!d__c->parent) { \
+ debug_printf(ctx, "Parent of " #child " child node is " \
+ "NULL.\n"); \
+ } \
+ else { \
+ debug_printf(ctx, "Parent of " #child " child node " \
+ "points to another node (of type %s)!\n", \
+ d__c->parent->token.s); \
+ } \
+ return; \
+ } \
+ node = d__c; \
+ continue; \
+ } \
+ } \
+ else { \
+ debug_printf(ctx, "%s(missing)\n", is); \
+ } \
+}
+
+static void debug_dump_tree(include_ctx_t *ctx, parse_node_t *root)
+{
+ parse_node_t *current;
+ char *is;
+
+ if (!root) {
+ debug_printf(ctx, " -- Parse Tree empty --\n\n");
+ return;
+ }
+
+ debug_printf(ctx, " ----- Parse Tree -----\n");
+ current = root;
+ is = " ";
+
+ while (current) {
+ switch (current->token.type) {
+ case TOKEN_STRING:
+ case TOKEN_RE:
+ debug_printf(ctx, "%s%s (%s)\n", is, current->token.s,
+ current->token.value);
+ current->dump_done = 1;
+ current = current->parent;
+ continue;
+
+ case TOKEN_NOT:
+ case TOKEN_GROUP:
+ case TOKEN_RBRACE:
+ case TOKEN_LBRACE:
+ if (!current->dump_done) {
+ debug_printf(ctx, "%s%s\n", is, current->token.s);
+ is = apr_pstrcat(ctx->dpool, is, " ", NULL);
+ current->dump_done = 1;
+ }
+
+ DUMP__CHILD(ctx, is, current, right)
+
+ if (!current->right || current->right->dump_done) {
+ is = apr_pstrmemdup(ctx->dpool, is, strlen(is) - 4);
+ if (current->right) current->right->dump_done = 0;
+ current = current->parent;
+ }
+ continue;
+
+ default:
+ if (!current->dump_done) {
+ debug_printf(ctx, "%s%s\n", is, current->token.s);
+ is = apr_pstrcat(ctx->dpool, is, " ", NULL);
+ current->dump_done = 1;
+ }
+
+ DUMP__CHILD(ctx, is, current, left)
+ DUMP__CHILD(ctx, is, current, right)
+
+ if ((!current->left || current->left->dump_done) &&
+ (!current->right || current->right->dump_done)) {
+
+ is = apr_pstrmemdup(ctx->dpool, is, strlen(is) - 4);
+ if (current->left) current->left->dump_done = 0;
+ if (current->right) current->right->dump_done = 0;
+ current = current->parent;
+ }
+ continue;
+ }
+ }
+
+ /* it is possible to call this function within the parser loop, to see
+ * how the tree is built. That way, we must cleanup after us to dump
+ * always the whole tree
+ */
+ root->dump_done = 0;
+ if (root->left) root->left->dump_done = 0;
+ if (root->right) root->right->dump_done = 0;
+
+ debug_printf(ctx, " --- End Parse Tree ---\n\n");
+}
+
+#define DEBUG_INIT(ctx, filter, brigade) do { \
+ (ctx)->intern->debug.f = filter; \
+ (ctx)->intern->debug.bb = brigade; \
+} while(0)
+
+#define DEBUG_PRINTF(arg) debug_printf arg
+
+#define DEBUG_DUMP_TOKEN(ctx, token) do { \
+ token_t *d__t = (token); \
+ \
+ if (d__t->type == TOKEN_STRING || d__t->type == TOKEN_RE) { \
+ DEBUG_PRINTF(((ctx), " Found: %s (%s)\n", d__t->s, d__t->value)); \
+ } \
+ else { \
+ DEBUG_PRINTF((ctx, " Found: %s\n", d__t->s)); \
+ } \
+} while(0)
+
+#define DEBUG_DUMP_EVAL(ctx, node) do { \
+ char c = '"'; \
+ switch ((node)->token.type) { \
+ case TOKEN_STRING: \
+ debug_printf((ctx), " Evaluate: %s (%s) -> %c\n", (node)->token.s,\
+ (node)->token.value, ((node)->value) ? '1':'0'); \
+ break; \
+ case TOKEN_AND: \
+ case TOKEN_OR: \
+ debug_printf((ctx), " Evaluate: %s (Left: %s; Right: %s) -> %c\n",\
+ (node)->token.s, \
+ (((node)->left->done) ? ((node)->left->value ?"1":"0") \
+ : "short circuited"), \
+ (((node)->right->done) ? ((node)->right->value?"1":"0") \
+ : "short circuited"), \
+ (node)->value ? '1' : '0'); \
+ break; \
+ case TOKEN_EQ: \
+ case TOKEN_NE: \
+ case TOKEN_GT: \
+ case TOKEN_GE: \
+ case TOKEN_LT: \
+ case TOKEN_LE: \
+ if ((node)->right->token.type == TOKEN_RE) c = '/'; \
+ debug_printf((ctx), " Compare: %s (\"%s\" with %c%s%c) -> %c\n", \
+ (node)->token.s, \
+ (node)->left->token.value, \
+ c, (node)->right->token.value, c, \
+ (node)->value ? '1' : '0'); \
+ break; \
+ default: \
+ debug_printf((ctx), " Evaluate: %s -> %c\n", (node)->token.s, \
+ (node)->value ? '1' : '0'); \
+ break; \
+ } \
+} while(0)
+
+#define DEBUG_DUMP_UNMATCHED(ctx, unmatched) do { \
+ if (unmatched) { \
+ DEBUG_PRINTF(((ctx), " Unmatched %c\n", (char)(unmatched))); \
+ } \
+} while(0)
+
+#define DEBUG_DUMP_COND(ctx, text) \
+ DEBUG_PRINTF(((ctx), "**** %s cond status=\"%c\"\n", (text), \
+ ((ctx)->flags & SSI_FLAG_COND_TRUE) ? '1' : '0'))
+
+#define DEBUG_DUMP_TREE(ctx, root) debug_dump_tree(ctx, root)
+
+#else /* DEBUG_INCLUDE */
+
+#define TYPE_TOKEN(token, ttype) (token)->type = ttype
+
+#define CREATE_NODE(ctx, name) do { \
+ (name) = apr_palloc((ctx)->dpool, sizeof(*(name))); \
+ (name)->parent = (name)->left = (name)->right = NULL; \
+ (name)->done = 0; \
+} while(0)
+
+#define DEBUG_INIT(ctx, f, bb)
+#define DEBUG_PRINTF(arg)
+#define DEBUG_DUMP_TOKEN(ctx, token)
+#define DEBUG_DUMP_EVAL(ctx, node)
+#define DEBUG_DUMP_UNMATCHED(ctx, unmatched)
+#define DEBUG_DUMP_COND(ctx, text)
+#define DEBUG_DUMP_TREE(ctx, root)
+
+#endif /* !DEBUG_INCLUDE */
+
+
+/*
+ * +-------------------------------------------------------+
+ * | |
+ * | Static Module Data
+ * | |
+ * +-------------------------------------------------------+
+ */
+
+/* global module structure */
+module AP_MODULE_DECLARE_DATA include_module;
+
+/* function handlers for include directives */
+static apr_hash_t *include_handlers;
+
+/* forward declaration of handler registry */
+static APR_OPTIONAL_FN_TYPE(ap_register_include_handler) *ssi_pfn_register;
+
+/* Sentinel value to store in subprocess_env for items that
+ * shouldn't be evaluated until/unless they're actually used
+ */
+static const char lazy_eval_sentinel = '\0';
+#define LAZY_VALUE (&lazy_eval_sentinel)
+
+/* default values */
+#define DEFAULT_START_SEQUENCE "<!--#"
+#define DEFAULT_END_SEQUENCE "-->"
+#define DEFAULT_ERROR_MSG "[an error occurred while processing this directive]"
+#define DEFAULT_TIME_FORMAT "%A, %d-%b-%Y %H:%M:%S %Z"
+#define DEFAULT_UNDEFINED_ECHO "(none)"
+
+#define UNSET -1
+
+#ifdef XBITHACK
+#define DEFAULT_XBITHACK XBITHACK_FULL
+#else
+#define DEFAULT_XBITHACK XBITHACK_OFF
+#endif
+
+
+/*
+ * +-------------------------------------------------------+
+ * | |
+ * | Environment/Expansion Functions
+ * | |
+ * +-------------------------------------------------------+
+ */
+
+/*
+ * decodes a string containing html entities or numeric character references.
+ * 's' is overwritten with the decoded string.
+ * If 's' is syntatically incorrect, then the followed fixups will be made:
+ * unknown entities will be left undecoded;
+ * references to unused numeric characters will be deleted.
+ * In particular, &#00; will not be decoded, but will be deleted.
+ */
+
+/* maximum length of any ISO-LATIN-1 HTML entity name. */
+#define MAXENTLEN (6)
+
+/* The following is a shrinking transformation, therefore safe. */
+
+/* Note: this function is deprecated in favour of apr_unescape_entity() in APR */
+static void decodehtml(char *s)
+{
+ int val, i, j;
+ char *p;
+ const char *ents;
+ static const char * const entlist[MAXENTLEN + 1] =
+ {
+ NULL, /* 0 */
+ NULL, /* 1 */
+ "lt\074gt\076", /* 2 */
+ "amp\046ETH\320eth\360", /* 3 */
+ "quot\042Auml\304Euml\313Iuml\317Ouml\326Uuml\334auml\344euml"
+ "\353iuml\357ouml\366uuml\374yuml\377", /* 4 */
+
+ "Acirc\302Aring\305AElig\306Ecirc\312Icirc\316Ocirc\324Ucirc"
+ "\333THORN\336szlig\337acirc\342aring\345aelig\346ecirc\352"
+ "icirc\356ocirc\364ucirc\373thorn\376", /* 5 */
+
+ "Agrave\300Aacute\301Atilde\303Ccedil\307Egrave\310Eacute\311"
+ "Igrave\314Iacute\315Ntilde\321Ograve\322Oacute\323Otilde"
+ "\325Oslash\330Ugrave\331Uacute\332Yacute\335agrave\340"
+ "aacute\341atilde\343ccedil\347egrave\350eacute\351igrave"
+ "\354iacute\355ntilde\361ograve\362oacute\363otilde\365"
+ "oslash\370ugrave\371uacute\372yacute\375" /* 6 */
+ };
+
+ /* Do a fast scan through the string until we find anything
+ * that needs more complicated handling
+ */
+ for (; *s != '&'; s++) {
+ if (*s == '\0') {
+ return;
+ }
+ }
+
+ for (p = s; *s != '\0'; s++, p++) {
+ if (*s != '&') {
+ *p = *s;
+ continue;
+ }
+ /* find end of entity */
+ for (i = 1; s[i] != ';' && s[i] != '\0'; i++) {
+ continue;
+ }
+
+ if (s[i] == '\0') { /* treat as normal data */
+ *p = *s;
+ continue;
+ }
+
+ /* is it numeric ? */
+ if (s[1] == '#') {
+ for (j = 2, val = 0; j < i && apr_isdigit(s[j]); j++) {
+ val = val * 10 + s[j] - '0';
+ }
+ s += i;
+ if (j < i || val <= 8 || (val >= 11 && val <= 31) ||
+ (val >= 127 && val <= 160) || val >= 256) {
+ p--; /* no data to output */
+ }
+ else {
+ *p = RAW_ASCII_CHAR(val);
+ }
+ }
+ else {
+ j = i - 1;
+ if (j > MAXENTLEN || entlist[j] == NULL) {
+ /* wrong length */
+ *p = '&';
+ continue; /* skip it */
+ }
+ for (ents = entlist[j]; *ents != '\0'; ents += i) {
+ if (strncmp(s + 1, ents, j) == 0) {
+ break;
+ }
+ }
+
+ if (*ents == '\0') {
+ *p = '&'; /* unknown */
+ }
+ else {
+ *p = RAW_ASCII_CHAR(((const unsigned char *) ents)[j]);
+ s += i;
+ }
+ }
+ }
+
+ *p = '\0';
+}
+
+static void add_include_vars(request_rec *r)
+{
+ apr_table_t *e = r->subprocess_env;
+ char *t;
+
+ apr_table_setn(e, "DATE_LOCAL", LAZY_VALUE);
+ apr_table_setn(e, "DATE_GMT", LAZY_VALUE);
+ apr_table_setn(e, "LAST_MODIFIED", LAZY_VALUE);
+ apr_table_setn(e, "DOCUMENT_URI", r->uri);
+ apr_table_setn(e, "DOCUMENT_ARGS", r->args ? r->args : "");
+ if (r->path_info && *r->path_info) {
+ apr_table_setn(e, "DOCUMENT_PATH_INFO", r->path_info);
+ }
+ apr_table_setn(e, "USER_NAME", LAZY_VALUE);
+ if (r->filename && (t = strrchr(r->filename, '/'))) {
+ apr_table_setn(e, "DOCUMENT_NAME", ++t);
+ }
+ else {
+ apr_table_setn(e, "DOCUMENT_NAME", r->uri);
+ }
+ if (r->args) {
+ char *arg_copy = apr_pstrdup(r->pool, r->args);
+
+ ap_unescape_url(arg_copy);
+ apr_table_setn(e, "QUERY_STRING_UNESCAPED",
+ ap_escape_shell_cmd(r->pool, arg_copy));
+ }
+}
+
+static const char *add_include_vars_lazy(request_rec *r, const char *var, const char *timefmt)
+{
+ char *val;
+ if (!strcasecmp(var, "DATE_LOCAL")) {
+ val = ap_ht_time(r->pool, r->request_time, timefmt, 0);
+ }
+ else if (!strcasecmp(var, "DATE_GMT")) {
+ val = ap_ht_time(r->pool, r->request_time, timefmt, 1);
+ }
+ else if (!strcasecmp(var, "LAST_MODIFIED")) {
+ val = ap_ht_time(r->pool, r->finfo.mtime, timefmt, 0);
+ }
+ else if (!strcasecmp(var, "USER_NAME")) {
+ if (apr_uid_name_get(&val, r->finfo.user, r->pool) != APR_SUCCESS) {
+ val = "<unknown>";
+ }
+ }
+ else {
+ val = NULL;
+ }
+
+ if (val) {
+ apr_table_setn(r->subprocess_env, var, val);
+ }
+ return val;
+}
+
+static const char *get_include_var(const char *var, include_ctx_t *ctx)
+{
+ const char *val;
+ request_rec *r = ctx->r;
+
+ if (apr_isdigit(*var) && !var[1]) {
+ apr_size_t idx = *var - '0';
+ backref_t *re = ctx->intern->re;
+
+ /* Handle $0 .. $9 from the last regex evaluated.
+ * The choice of returning NULL strings on not-found,
+ * v.s. empty strings on an empty match is deliberate.
+ */
+ if (!re || !re->have_match) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01329)
+ "regex capture $%" APR_SIZE_T_FMT " refers to no regex in %s",
+ idx, r->filename);
+ return NULL;
+ }
+ else if (re->nsub < idx || idx >= AP_MAX_REG_MATCH) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01330)
+ "regex capture $%" APR_SIZE_T_FMT
+ " is out of range (last regex was: '%s') in %s",
+ idx, re->rexp, r->filename);
+ return NULL;
+ }
+ else if (re->match[idx].rm_so < 0 || re->match[idx].rm_eo < 0) {
+ /* This particular subpattern was not used by the regex */
+ return NULL;
+ }
+ else {
+ val = apr_pstrmemdup(ctx->dpool, re->source + re->match[idx].rm_so,
+ re->match[idx].rm_eo - re->match[idx].rm_so);
+ }
+ }
+ else {
+ val = apr_table_get(r->subprocess_env, var);
+
+ if (val == LAZY_VALUE) {
+ val = add_include_vars_lazy(r, var, ctx->time_str);
+ }
+ }
+
+ return val;
+}
+
+static const char *include_expr_var_fn(ap_expr_eval_ctx_t *eval_ctx,
+ const void *data,
+ const char *arg)
+{
+ const char *res, *name = data;
+ include_ctx_t *ctx = eval_ctx->data;
+ if ((name[0] == 'e') || (name[0] == 'E')) {
+ /* keep legacy "env" semantics */
+ if ((res = apr_table_get(ctx->r->notes, arg)) != NULL)
+ return res;
+ else if ((res = get_include_var(arg, ctx)) != NULL)
+ return res;
+ else
+ return getenv(arg);
+ }
+ else {
+ return get_include_var(arg, ctx);
+ }
+}
+
+static int include_expr_lookup(ap_expr_lookup_parms *parms)
+{
+ switch (parms->type) {
+ case AP_EXPR_FUNC_STRING:
+ if (strcasecmp(parms->name, "v") == 0 ||
+ strcasecmp(parms->name, "reqenv") == 0 ||
+ strcasecmp(parms->name, "env") == 0) {
+ *parms->func = include_expr_var_fn;
+ *parms->data = parms->name;
+ return OK;
+ }
+ break;
+ /*
+ * We could also make the SSI vars available as %{...} style variables
+ * (AP_EXPR_FUNC_VAR), but this would create problems if we ever want
+ * to cache parsed expressions for performance reasons.
+ */
+ }
+ return ap_run_expr_lookup(parms);
+}
+
+
+/*
+ * Do variable substitution on strings
+ *
+ * (Note: If out==NULL, this function allocs a buffer for the resulting
+ * string from ctx->pool. The return value is always the parsed string)
+ */
+static char *ap_ssi_parse_string(include_ctx_t *ctx, const char *in, char *out,
+ apr_size_t length, int leave_name)
+{
+ request_rec *r = ctx->r;
+ result_item_t *result = NULL, *current = NULL;
+ apr_size_t outlen = 0, inlen, span;
+ char *ret = NULL, *eout = NULL;
+ const char *p;
+
+ if (out) {
+ /* sanity check, out && !length is not supported */
+ ap_assert(out && length);
+
+ ret = out;
+ eout = out + length - 1;
+ }
+
+ span = strcspn(in, "\\$");
+ inlen = strlen(in);
+
+ /* fast exit */
+ if (inlen == span) {
+ if (out) {
+ apr_cpystrn(out, in, length);
+ }
+ else {
+ ret = apr_pstrmemdup(ctx->pool, in, (length && length <= inlen)
+ ? length - 1 : inlen);
+ }
+
+ return ret;
+ }
+
+ /* well, actually something to do */
+ p = in + span;
+
+ if (out) {
+ if (span) {
+ memcpy(out, in, (out+span <= eout) ? span : (eout-out));
+ out += span;
+ }
+ }
+ else {
+ current = result = apr_palloc(ctx->dpool, sizeof(*result));
+ current->next = NULL;
+ current->string = in;
+ current->len = span;
+ outlen = span;
+ }
+
+ /* loop for specials */
+ do {
+ if ((out && out >= eout) || (length && outlen >= length)) {
+ break;
+ }
+
+ /* prepare next entry */
+ if (!out && current->len) {
+ current->next = apr_palloc(ctx->dpool, sizeof(*current->next));
+ current = current->next;
+ current->next = NULL;
+ current->len = 0;
+ }
+
+ /*
+ * escaped character
+ */
+ if (*p == '\\') {
+ if (out) {
+ *out++ = (p[1] == '$') ? *++p : *p;
+ ++p;
+ }
+ else {
+ current->len = 1;
+ current->string = (p[1] == '$') ? ++p : p;
+ ++p;
+ ++outlen;
+ }
+ }
+
+ /*
+ * variable expansion
+ */
+ else { /* *p == '$' */
+ const char *newp = NULL, *ep, *key = NULL;
+
+ if (*++p == '{') {
+ ep = ap_strchr_c(++p, '}');
+ if (!ep) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01331) "Missing '}' on "
+ "variable \"%s\" in %s", p, r->filename);
+ break;
+ }
+
+ if (p < ep) {
+ key = apr_pstrmemdup(ctx->dpool, p, ep - p);
+ newp = ep + 1;
+ }
+ p -= 2;
+ }
+ else {
+ ep = p;
+ while (*ep == '_' || apr_isalnum(*ep)) {
+ ++ep;
+ }
+
+ if (p < ep) {
+ key = apr_pstrmemdup(ctx->dpool, p, ep - p);
+ newp = ep;
+ }
+ --p;
+ }
+
+ /* empty name results in a copy of '$' in the output string */
+ if (!key) {
+ if (out) {
+ *out++ = *p++;
+ }
+ else {
+ current->len = 1;
+ current->string = p++;
+ ++outlen;
+ }
+ }
+ else {
+ const char *val = get_include_var(key, ctx);
+ apr_size_t len = 0;
+
+ if (val) {
+ len = strlen(val);
+ }
+ else if (leave_name) {
+ val = p;
+ len = ep - p;
+ }
+
+ if (val && len) {
+ if (out) {
+ memcpy(out, val, (out+len <= eout) ? len : (eout-out));
+ out += len;
+ }
+ else {
+ current->len = len;
+ current->string = val;
+ outlen += len;
+ }
+ }
+
+ p = newp;
+ }
+ }
+
+ if ((out && out >= eout) || (length && outlen >= length)) {
+ break;
+ }
+
+ /* check the remainder */
+ if (*p && (span = strcspn(p, "\\$")) > 0) {
+ if (!out && current->len) {
+ current->next = apr_palloc(ctx->dpool, sizeof(*current->next));
+ current = current->next;
+ current->next = NULL;
+ }
+
+ if (out) {
+ memcpy(out, p, (out+span <= eout) ? span : (eout-out));
+ out += span;
+ }
+ else {
+ current->len = span;
+ current->string = p;
+ outlen += span;
+ }
+
+ p += span;
+ }
+ } while (p < in+inlen);
+
+ /* assemble result */
+ if (out) {
+ if (out > eout) {
+ *eout = '\0';
+ }
+ else {
+ *out = '\0';
+ }
+ }
+ else {
+ const char *ep;
+
+ if (length && outlen > length) {
+ outlen = length - 1;
+ }
+
+ ret = out = apr_palloc(ctx->pool, outlen + 1);
+ ep = ret + outlen;
+
+ do {
+ if (result->len) {
+ memcpy(out, result->string, (out+result->len <= ep)
+ ? result->len : (ep-out));
+ out += result->len;
+ }
+ result = result->next;
+ } while (result && out < ep);
+
+ ret[outlen] = '\0';
+ }
+
+ return ret;
+}
+
+
+/*
+ * +-------------------------------------------------------+
+ * | |
+ * | Conditional Expression Parser
+ * | |
+ * +-------------------------------------------------------+
+ */
+
+static APR_INLINE int re_check(include_ctx_t *ctx, const char *string,
+ const char *rexp)
+{
+ ap_regex_t *compiled;
+ backref_t *re = ctx->intern->re;
+
+ compiled = ap_pregcomp(ctx->dpool, rexp, AP_REG_EXTENDED);
+ if (!compiled) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(02667)
+ "unable to compile pattern \"%s\"", rexp);
+ return -1;
+ }
+
+ if (!re) {
+ re = ctx->intern->re = apr_palloc(ctx->pool, sizeof(*re));
+ }
+
+ re->source = apr_pstrdup(ctx->pool, string);
+ re->rexp = apr_pstrdup(ctx->pool, rexp);
+ re->nsub = compiled->re_nsub;
+ re->have_match = !ap_regexec(compiled, string, AP_MAX_REG_MATCH,
+ re->match, 0);
+
+ ap_pregfree(ctx->dpool, compiled);
+ return re->have_match;
+}
+
+static int get_ptoken(include_ctx_t *ctx, const char **parse, token_t *token, token_t *previous)
+{
+ const char *p;
+ apr_size_t shift;
+ int unmatched;
+
+ token->value = NULL;
+
+ if (!*parse) {
+ return 0;
+ }
+
+ /* Skip leading white space */
+ while (apr_isspace(**parse)) {
+ ++*parse;
+ }
+
+ if (!**parse) {
+ *parse = NULL;
+ return 0;
+ }
+
+ TYPE_TOKEN(token, TOKEN_STRING); /* the default type */
+ p = *parse;
+ unmatched = 0;
+
+ switch (*(*parse)++) {
+ case '(':
+ TYPE_TOKEN(token, TOKEN_LBRACE);
+ return 0;
+ case ')':
+ TYPE_TOKEN(token, TOKEN_RBRACE);
+ return 0;
+ case '=':
+ if (**parse == '=') ++*parse;
+ TYPE_TOKEN(token, TOKEN_EQ);
+ return 0;
+ case '!':
+ if (**parse == '=') {
+ TYPE_TOKEN(token, TOKEN_NE);
+ ++*parse;
+ return 0;
+ }
+ TYPE_TOKEN(token, TOKEN_NOT);
+ return 0;
+ case '\'':
+ unmatched = '\'';
+ break;
+ case '/':
+ /* if last token was ACCESS, this token is STRING */
+ if (previous != NULL && TOKEN_ACCESS == previous->type) {
+ break;
+ }
+ TYPE_TOKEN(token, TOKEN_RE);
+ unmatched = '/';
+ break;
+ case '|':
+ if (**parse == '|') {
+ TYPE_TOKEN(token, TOKEN_OR);
+ ++*parse;
+ return 0;
+ }
+ break;
+ case '&':
+ if (**parse == '&') {
+ TYPE_TOKEN(token, TOKEN_AND);
+ ++*parse;
+ return 0;
+ }
+ break;
+ case '>':
+ if (**parse == '=') {
+ TYPE_TOKEN(token, TOKEN_GE);
+ ++*parse;
+ return 0;
+ }
+ TYPE_TOKEN(token, TOKEN_GT);
+ return 0;
+ case '<':
+ if (**parse == '=') {
+ TYPE_TOKEN(token, TOKEN_LE);
+ ++*parse;
+ return 0;
+ }
+ TYPE_TOKEN(token, TOKEN_LT);
+ return 0;
+ case '-':
+ if (**parse == 'A') {
+ TYPE_TOKEN(token, TOKEN_ACCESS);
+ ++*parse;
+ return 0;
+ }
+ break;
+ }
+
+ /* It's a string or regex token
+ * Now search for the next token, which finishes this string
+ */
+ shift = 0;
+ p = *parse = token->value = unmatched ? *parse : p;
+
+ for (; **parse; p = ++*parse) {
+ if (**parse == '\\') {
+ if (!*(++*parse)) {
+ p = *parse;
+ break;
+ }
+
+ ++shift;
+ }
+ else {
+ if (unmatched) {
+ if (**parse == unmatched) {
+ unmatched = 0;
+ ++*parse;
+ break;
+ }
+ } else if (apr_isspace(**parse)) {
+ break;
+ }
+ else {
+ int found = 0;
+
+ switch (**parse) {
+ case '(':
+ case ')':
+ case '=':
+ case '!':
+ case '<':
+ case '>':
+ ++found;
+ break;
+
+ case '|':
+ case '&':
+ if ((*parse)[1] == **parse) {
+ ++found;
+ }
+ break;
+ }
+
+ if (found) {
+ break;
+ }
+ }
+ }
+ }
+
+ if (unmatched) {
+ token->value = apr_pstrdup(ctx->dpool, "");
+ }
+ else {
+ apr_size_t len = p - token->value - shift;
+ char *c = apr_palloc(ctx->dpool, len + 1);
+
+ p = token->value;
+ token->value = c;
+
+ while (shift--) {
+ const char *e = ap_strchr_c(p, '\\');
+
+ memcpy(c, p, e-p);
+ c += e-p;
+ *c++ = *++e;
+ len -= e-p;
+ p = e+1;
+ }
+
+ if (len) {
+ memcpy(c, p, len);
+ }
+ c[len] = '\0';
+ }
+
+ return unmatched;
+}
+
+static int parse_expr(include_ctx_t *ctx, const char *expr, int *was_error)
+{
+ parse_node_t *new, *root = NULL, *current = NULL;
+ request_rec *r = ctx->r;
+ request_rec *rr = NULL;
+ const char *error = APLOGNO(03188) "Invalid expression \"%s\" in file %s";
+ const char *parse = expr;
+ unsigned regex = 0;
+
+ *was_error = 0;
+
+ if (!parse) {
+ return 0;
+ }
+
+ /* Create Parse Tree */
+ while (1) {
+ /* uncomment this to see how the tree a built:
+ *
+ * DEBUG_DUMP_TREE(ctx, root);
+ */
+ CREATE_NODE(ctx, new);
+
+ {
+#ifdef DEBUG_INCLUDE
+ int was_unmatched =
+#endif
+ get_ptoken(ctx, &parse, &new->token,
+ (current != NULL ? &current->token : NULL));
+ if (!parse)
+ break;
+
+ DEBUG_DUMP_UNMATCHED(ctx, was_unmatched);
+ DEBUG_DUMP_TOKEN(ctx, &new->token);
+ }
+
+ if (!current) {
+ switch (new->token.type) {
+ case TOKEN_STRING:
+ case TOKEN_NOT:
+ case TOKEN_ACCESS:
+ case TOKEN_LBRACE:
+ root = current = new;
+ continue;
+
+ default:
+ /* Intentional no APLOGNO */
+ /* error text provides APLOGNO */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error, expr,
+ r->filename);
+ *was_error = 1;
+ return 0;
+ }
+ }
+
+ switch (new->token.type) {
+ case TOKEN_STRING:
+ switch (current->token.type) {
+ case TOKEN_STRING:
+ current->token.value =
+ apr_pstrcat(ctx->dpool, current->token.value,
+ *current->token.value ? " " : "",
+ new->token.value, NULL);
+ continue;
+
+ case TOKEN_RE:
+ case TOKEN_RBRACE:
+ case TOKEN_GROUP:
+ break;
+
+ default:
+ new->parent = current;
+ current = current->right = new;
+ continue;
+ }
+ break;
+
+ case TOKEN_RE:
+ switch (current->token.type) {
+ case TOKEN_EQ:
+ case TOKEN_NE:
+ new->parent = current;
+ current = current->right = new;
+ ++regex;
+ continue;
+
+ default:
+ break;
+ }
+ break;
+
+ case TOKEN_AND:
+ case TOKEN_OR:
+ switch (current->token.type) {
+ case TOKEN_STRING:
+ case TOKEN_RE:
+ case TOKEN_GROUP:
+ current = current->parent;
+
+ while (current) {
+ switch (current->token.type) {
+ case TOKEN_AND:
+ case TOKEN_OR:
+ case TOKEN_LBRACE:
+ break;
+
+ default:
+ current = current->parent;
+ continue;
+ }
+ break;
+ }
+
+ if (!current) {
+ new->left = root;
+ root->parent = new;
+ current = root = new;
+ continue;
+ }
+
+ new->left = current->right;
+ new->left->parent = new;
+ new->parent = current;
+ current = current->right = new;
+ continue;
+
+ default:
+ break;
+ }
+ break;
+
+ case TOKEN_EQ:
+ case TOKEN_NE:
+ case TOKEN_GE:
+ case TOKEN_GT:
+ case TOKEN_LE:
+ case TOKEN_LT:
+ if (current->token.type == TOKEN_STRING) {
+ current = current->parent;
+
+ if (!current) {
+ new->left = root;
+ root->parent = new;
+ current = root = new;
+ continue;
+ }
+
+ switch (current->token.type) {
+ case TOKEN_LBRACE:
+ case TOKEN_AND:
+ case TOKEN_OR:
+ new->left = current->right;
+ new->left->parent = new;
+ new->parent = current;
+ current = current->right = new;
+ continue;
+
+ default:
+ break;
+ }
+ }
+ break;
+
+ case TOKEN_RBRACE:
+ while (current && current->token.type != TOKEN_LBRACE) {
+ current = current->parent;
+ }
+
+ if (current) {
+ TYPE_TOKEN(&current->token, TOKEN_GROUP);
+ continue;
+ }
+
+ error = APLOGNO(03189) "Unmatched ')' in \"%s\" in file %s";
+ break;
+
+ case TOKEN_NOT:
+ case TOKEN_ACCESS:
+ case TOKEN_LBRACE:
+ switch (current->token.type) {
+ case TOKEN_STRING:
+ case TOKEN_RE:
+ case TOKEN_RBRACE:
+ case TOKEN_GROUP:
+ break;
+
+ default:
+ current->right = new;
+ new->parent = current;
+ current = new;
+ continue;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ /* Intentional no APLOGNO */
+ /* error text provides APLOGNO */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error, expr, r->filename);
+ *was_error = 1;
+ return 0;
+ }
+
+ DEBUG_DUMP_TREE(ctx, root);
+
+ /* Evaluate Parse Tree */
+ current = root;
+ error = NULL;
+ while (current) {
+ switch (current->token.type) {
+ case TOKEN_STRING:
+ current->token.value =
+ ap_ssi_parse_string(ctx, current->token.value, NULL, 0,
+ SSI_EXPAND_DROP_NAME);
+ current->value = !!*current->token.value;
+ break;
+
+ case TOKEN_AND:
+ case TOKEN_OR:
+ if (!current->left || !current->right) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01332)
+ "Invalid expression \"%s\" in file %s",
+ expr, r->filename);
+ *was_error = 1;
+ return 0;
+ }
+
+ if (!current->left->done) {
+ switch (current->left->token.type) {
+ case TOKEN_STRING:
+ current->left->token.value =
+ ap_ssi_parse_string(ctx, current->left->token.value,
+ NULL, 0, SSI_EXPAND_DROP_NAME);
+ current->left->value = !!*current->left->token.value;
+ DEBUG_DUMP_EVAL(ctx, current->left);
+ current->left->done = 1;
+ break;
+
+ default:
+ current = current->left;
+ continue;
+ }
+ }
+
+ /* short circuit evaluation */
+ if (!current->right->done && !regex &&
+ ((current->token.type == TOKEN_AND && !current->left->value) ||
+ (current->token.type == TOKEN_OR && current->left->value))) {
+ current->value = current->left->value;
+ }
+ else {
+ if (!current->right->done) {
+ switch (current->right->token.type) {
+ case TOKEN_STRING:
+ current->right->token.value =
+ ap_ssi_parse_string(ctx,current->right->token.value,
+ NULL, 0, SSI_EXPAND_DROP_NAME);
+ current->right->value = !!*current->right->token.value;
+ DEBUG_DUMP_EVAL(ctx, current->right);
+ current->right->done = 1;
+ break;
+
+ default:
+ current = current->right;
+ continue;
+ }
+ }
+
+ if (current->token.type == TOKEN_AND) {
+ current->value = current->left->value &&
+ current->right->value;
+ }
+ else {
+ current->value = current->left->value ||
+ current->right->value;
+ }
+ }
+ break;
+
+ case TOKEN_EQ:
+ case TOKEN_NE:
+ if (!current->left || !current->right ||
+ current->left->token.type != TOKEN_STRING ||
+ (current->right->token.type != TOKEN_STRING &&
+ current->right->token.type != TOKEN_RE)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01333)
+ "Invalid expression \"%s\" in file %s",
+ expr, r->filename);
+ *was_error = 1;
+ return 0;
+ }
+ current->left->token.value =
+ ap_ssi_parse_string(ctx, current->left->token.value, NULL, 0,
+ SSI_EXPAND_DROP_NAME);
+ current->right->token.value =
+ ap_ssi_parse_string(ctx, current->right->token.value, NULL, 0,
+ SSI_EXPAND_DROP_NAME);
+
+ if (current->right->token.type == TOKEN_RE) {
+ current->value = re_check(ctx, current->left->token.value,
+ current->right->token.value);
+ --regex;
+ }
+ else {
+ current->value = !strcmp(current->left->token.value,
+ current->right->token.value);
+ }
+
+ if (current->token.type == TOKEN_NE) {
+ current->value = !current->value;
+ }
+ break;
+
+ case TOKEN_GE:
+ case TOKEN_GT:
+ case TOKEN_LE:
+ case TOKEN_LT:
+ if (!current->left || !current->right ||
+ current->left->token.type != TOKEN_STRING ||
+ current->right->token.type != TOKEN_STRING) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01334)
+ "Invalid expression \"%s\" in file %s",
+ expr, r->filename);
+ *was_error = 1;
+ return 0;
+ }
+
+ current->left->token.value =
+ ap_ssi_parse_string(ctx, current->left->token.value, NULL, 0,
+ SSI_EXPAND_DROP_NAME);
+ current->right->token.value =
+ ap_ssi_parse_string(ctx, current->right->token.value, NULL, 0,
+ SSI_EXPAND_DROP_NAME);
+
+ current->value = strcmp(current->left->token.value,
+ current->right->token.value);
+
+ switch (current->token.type) {
+ case TOKEN_GE: current->value = current->value >= 0; break;
+ case TOKEN_GT: current->value = current->value > 0; break;
+ case TOKEN_LE: current->value = current->value <= 0; break;
+ case TOKEN_LT: current->value = current->value < 0; break;
+ default: current->value = 0; break; /* should not happen */
+ }
+ break;
+
+ case TOKEN_NOT:
+ case TOKEN_GROUP:
+ if (current->right) {
+ if (!current->right->done) {
+ current = current->right;
+ continue;
+ }
+ current->value = current->right->value;
+ }
+ else {
+ current->value = 1;
+ }
+
+ if (current->token.type == TOKEN_NOT) {
+ current->value = !current->value;
+ }
+ break;
+
+ case TOKEN_ACCESS:
+ if (current->left || !current->right ||
+ (current->right->token.type != TOKEN_STRING &&
+ current->right->token.type != TOKEN_RE)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01335)
+ "Invalid expression \"%s\" in file %s: Token '-A' must be followed by a URI string.",
+ expr, r->filename);
+ *was_error = 1;
+ return 0;
+ }
+ current->right->token.value =
+ ap_ssi_parse_string(ctx, current->right->token.value, NULL, 0,
+ SSI_EXPAND_DROP_NAME);
+ rr = ap_sub_req_lookup_uri(current->right->token.value, r, NULL);
+ /* 400 and higher are considered access denied */
+ if (rr->status < HTTP_BAD_REQUEST) {
+ current->value = 1;
+ }
+ else {
+ current->value = 0;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rr->status, r, APLOGNO(01336)
+ "mod_include: The tested "
+ "subrequest -A \"%s\" returned an error code.",
+ current->right->token.value);
+ }
+ ap_destroy_sub_req(rr);
+ break;
+
+ case TOKEN_RE:
+ if (!error) {
+ error = APLOGNO(03190) "No operator before regex in expr \"%s\" in file %s";
+ }
+ case TOKEN_LBRACE:
+ if (!error) {
+ error = APLOGNO(03191) "Unmatched '(' in \"%s\" in file %s";
+ }
+ default:
+ if (!error) {
+ error = APLOGNO(03192) "internal parser error in \"%s\" in file %s";
+ }
+
+ /* Intentional no APLOGNO */
+ /* error text provides APLOGNO */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error, expr, r->filename);
+ *was_error = 1;
+ return 0;
+ }
+
+ DEBUG_DUMP_EVAL(ctx, current);
+ current->done = 1;
+ current = current->parent;
+ }
+
+ return (root ? root->value : 0);
+}
+
+/* same as above, but use common ap_expr syntax / API */
+static int parse_ap_expr(include_ctx_t *ctx, const char *expr, int *was_error)
+{
+ ap_expr_info_t *expr_info = apr_pcalloc(ctx->pool, sizeof (*expr_info));
+ const char *err;
+ int ret;
+ backref_t *re = ctx->intern->re;
+ ap_expr_eval_ctx_t *eval_ctx = ctx->intern->expr_eval_ctx;
+
+ expr_info->filename = ctx->r->filename;
+ expr_info->line_number = 0;
+ expr_info->module_index = APLOG_MODULE_INDEX;
+ expr_info->flags = AP_EXPR_FLAG_RESTRICTED;
+ err = ap_expr_parse(ctx->r->pool, ctx->r->pool, expr_info, expr,
+ include_expr_lookup);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(01337)
+ "Could not parse expr \"%s\" in %s: %s", expr,
+ ctx->r->filename, err);
+ *was_error = 1;
+ return 0;
+ }
+
+ if (!re) {
+ ctx->intern->re = re = apr_pcalloc(ctx->pool, sizeof(*re));
+ }
+ else {
+ /* ap_expr_exec_ctx() does not care about re->have_match but only about
+ * re->source
+ */
+ if (!re->have_match)
+ re->source = NULL;
+ }
+
+ if (!eval_ctx) {
+ eval_ctx = apr_pcalloc(ctx->pool, sizeof(*eval_ctx));
+ ctx->intern->expr_eval_ctx = eval_ctx;
+ eval_ctx->r = ctx->r;
+ eval_ctx->c = ctx->r->connection;
+ eval_ctx->s = ctx->r->server;
+ eval_ctx->p = ctx->r->pool;
+ eval_ctx->data = ctx;
+ eval_ctx->err = &ctx->intern->expr_err;
+ eval_ctx->vary_this = &ctx->intern->expr_vary_this;
+ eval_ctx->re_nmatch = AP_MAX_REG_MATCH;
+ eval_ctx->re_pmatch = re->match;
+ eval_ctx->re_source = &re->source;
+ }
+
+ eval_ctx->info = expr_info;
+ ret = ap_expr_exec_ctx(eval_ctx);
+ if (ret < 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(01338)
+ "Could not evaluate expr \"%s\" in %s: %s", expr,
+ ctx->r->filename, ctx->intern->expr_err);
+ *was_error = 1;
+ return 0;
+ }
+ *was_error = 0;
+ if (re->source)
+ re->have_match = 1;
+ return ret;
+}
+
+/*
+ * +-------------------------------------------------------+
+ * | |
+ * | Action Handlers
+ * | |
+ * +-------------------------------------------------------+
+ */
+
+/*
+ * Extract the next tag name and value.
+ * If there are no more tags, set the tag name to NULL.
+ * The tag value is html decoded if dodecode is non-zero.
+ * The tag value may be NULL if there is no tag value..
+ */
+static void ap_ssi_get_tag_and_value(include_ctx_t *ctx, char **tag,
+ char **tag_val, int dodecode)
+{
+ if (!ctx->intern->argv) {
+ *tag = NULL;
+ *tag_val = NULL;
+
+ return;
+ }
+
+ *tag_val = ctx->intern->argv->value;
+ *tag = ctx->intern->argv->name;
+
+ ctx->intern->argv = ctx->intern->argv->next;
+
+ if (dodecode && *tag_val) {
+ decodehtml(*tag_val);
+ }
+}
+
+static int find_file(request_rec *r, const char *directive, const char *tag,
+ char *tag_val, apr_finfo_t *finfo)
+{
+ char *to_send = tag_val;
+ request_rec *rr = NULL;
+ int ret=0;
+ char *error_fmt = NULL;
+ apr_status_t rv = APR_SUCCESS;
+
+ if (!strcmp(tag, "file")) {
+ char *newpath;
+
+ /* be safe; only files in this directory or below allowed */
+ rv = apr_filepath_merge(&newpath, NULL, tag_val,
+ APR_FILEPATH_SECUREROOTTEST |
+ APR_FILEPATH_NOTABSOLUTE, r->pool);
+
+ if (rv != APR_SUCCESS) {
+ error_fmt = APLOGNO(02668) "unable to access file \"%s\" "
+ "in parsed file %s";
+ }
+ else {
+ /* note: it is okay to pass NULL for the "next filter" since
+ we never attempt to "run" this sub request. */
+ rr = ap_sub_req_lookup_file(newpath, r, NULL);
+
+ if (rr->status == HTTP_OK && rr->finfo.filetype != APR_NOFILE) {
+ to_send = rr->filename;
+ if ((rv = apr_stat(finfo, to_send,
+ APR_FINFO_GPROT | APR_FINFO_MIN, rr->pool)) != APR_SUCCESS
+ && rv != APR_INCOMPLETE) {
+ error_fmt = APLOGNO(02669) "unable to get information "
+ "about \"%s\" in parsed file %s";
+ }
+ }
+ else {
+ error_fmt = APLOGNO(02670) "unable to lookup information "
+ "about \"%s\" in parsed file %s";
+ }
+ }
+
+ if (error_fmt) {
+ ret = -1;
+ /* Intentional no APLOGNO */
+ /* error_fmt provides APLOGNO */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR,
+ rv, r, error_fmt, to_send, r->filename);
+ }
+
+ if (rr) ap_destroy_sub_req(rr);
+
+ return ret;
+ }
+ else if (!strcmp(tag, "virtual")) {
+ /* note: it is okay to pass NULL for the "next filter" since
+ we never attempt to "run" this sub request. */
+ rr = ap_sub_req_lookup_uri(tag_val, r, NULL);
+
+ if (rr->status == HTTP_OK && rr->finfo.filetype != APR_NOFILE) {
+ memcpy((char *) finfo, (const char *) &rr->finfo,
+ sizeof(rr->finfo));
+ ap_destroy_sub_req(rr);
+ return 0;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01339) "unable to get "
+ "information about \"%s\" in parsed file %s",
+ tag_val, r->filename);
+ ap_destroy_sub_req(rr);
+ return -1;
+ }
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01340) "unknown parameter \"%s\" "
+ "to tag %s in %s", tag, directive, r->filename);
+ return -1;
+ }
+}
+
+/*
+ * <!--#comment blah blah blah ... -->
+ */
+static apr_status_t handle_comment(include_ctx_t *ctx, ap_filter_t *f,
+ apr_bucket_brigade *bb)
+{
+ return APR_SUCCESS;
+}
+
+/*
+ * <!--#include virtual|file="..." [onerror|virtual|file="..."] ... -->
+ *
+ * Output each file/virtual in turn until one of them returns an error.
+ * On error, ignore all further file/virtual attributes until we reach
+ * an onerror attribute, where we make an attempt to serve the onerror
+ * virtual url. If onerror fails, or no onerror is present, the default
+ * error string is inserted into the stream.
+ */
+static apr_status_t handle_include(include_ctx_t *ctx, ap_filter_t *f,
+ apr_bucket_brigade *bb)
+{
+ request_rec *r = f->r;
+ char *last_error;
+
+ if (!ctx->argc) {
+ ap_log_rerror(APLOG_MARK,
+ (ctx->flags & SSI_FLAG_PRINTING)
+ ? APLOG_ERR : APLOG_WARNING,
+ 0, r, APLOGNO(01341)
+ "missing argument for include element in %s",
+ r->filename);
+ }
+
+ if (!(ctx->flags & SSI_FLAG_PRINTING)) {
+ return APR_SUCCESS;
+ }
+
+ if (!ctx->argc) {
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ return APR_SUCCESS;
+ }
+
+ last_error = NULL;
+ while (1) {
+ char *tag = NULL;
+ char *tag_val = NULL;
+ request_rec *rr = NULL;
+ char *error_fmt = NULL;
+ char *parsed_string;
+ apr_status_t rv = APR_SUCCESS;
+ int status = 0;
+
+ ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
+ if (!tag || !tag_val) {
+ break;
+ }
+
+ if (strcmp(tag, "virtual") && strcmp(tag, "file") && strcmp(tag,
+ "onerror")) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01342) "unknown parameter "
+ "\"%s\" to tag include in %s", tag, r->filename);
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ break;
+ }
+
+ parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
+ SSI_EXPAND_DROP_NAME);
+ if (tag[0] == 'f') {
+ char *newpath;
+
+ /* be safe; only files in this directory or below allowed */
+ rv = apr_filepath_merge(&newpath, NULL, parsed_string,
+ APR_FILEPATH_SECUREROOTTEST |
+ APR_FILEPATH_NOTABSOLUTE, ctx->dpool);
+
+ if (rv != APR_SUCCESS) {
+ error_fmt = "unable to include file \"%s\" in parsed file %s";
+ }
+ else {
+ rr = ap_sub_req_lookup_file(newpath, r, f->next);
+ }
+ }
+ else if ((tag[0] == 'v' && !last_error)
+ || (tag[0] == 'o' && last_error)) {
+ if (r->kept_body) {
+ rr = ap_sub_req_method_uri(r->method, parsed_string, r, f->next);
+ }
+ else {
+ rr = ap_sub_req_lookup_uri(parsed_string, r, f->next);
+ }
+ }
+ else {
+ continue;
+ }
+
+ if (!error_fmt && rr->status != HTTP_OK) {
+ error_fmt = "unable to include \"%s\" in parsed file %s, subrequest setup returned %d";
+ }
+
+ if (!error_fmt && (ctx->flags & SSI_FLAG_NO_EXEC) &&
+ rr->content_type && strncmp(rr->content_type, "text/", 5)) {
+
+ error_fmt = "unable to include potential exec \"%s\" in parsed "
+ "file %s, content type not text/*";
+ }
+
+ /* See the Kludge in includes_filter for why.
+ * Basically, it puts a bread crumb in here, then looks
+ * for the crumb later to see if its been here.
+ */
+ if (rr) {
+ ap_set_module_config(rr->request_config, &include_module, r);
+ }
+
+ if (!error_fmt && ((status = ap_run_sub_req(rr)))) {
+ error_fmt = "unable to include \"%s\" in parsed file %s, subrequest returned %d";
+ }
+
+ if (error_fmt) {
+ /* Intentional no APLOGNO */
+ /* error text is also sent to client */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, error_fmt, tag_val,
+ r->filename, status ? status : rr ? rr->status : 0);
+ if (last_error) {
+ /* onerror threw an error, give up completely */
+ break;
+ }
+ last_error = error_fmt;
+ }
+ else {
+ last_error = NULL;
+ }
+
+ /* Do *not* destroy the subrequest here; it may have allocated
+ * variables in this r->subprocess_env in the subrequest's
+ * r->pool, so that pool must survive as long as this request.
+ * Yes, this is a memory leak. */
+
+ }
+
+ if (last_error) {
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ }
+
+ return APR_SUCCESS;
+}
+
+/*
+ * <!--#echo [decoding="..."] [encoding="..."] var="..." [decoding="..."]
+ * [encoding="..."] var="..." ... -->
+ */
+static apr_status_t handle_echo(include_ctx_t *ctx, ap_filter_t *f,
+ apr_bucket_brigade *bb)
+{
+ const char *encoding = "entity", *decoding = "none";
+ request_rec *r = f->r;
+ int error = 0;
+
+ if (!ctx->argc) {
+ ap_log_rerror(APLOG_MARK,
+ (ctx->flags & SSI_FLAG_PRINTING)
+ ? APLOG_ERR : APLOG_WARNING,
+ 0, r, APLOGNO(01343)
+ "missing argument for echo element in %s",
+ r->filename);
+ }
+
+ if (!(ctx->flags & SSI_FLAG_PRINTING)) {
+ return APR_SUCCESS;
+ }
+
+ if (!ctx->argc) {
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ return APR_SUCCESS;
+ }
+
+ while (1) {
+ char *tag = NULL;
+ char *tag_val = NULL;
+
+ ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
+ if (!tag || !tag_val) {
+ break;
+ }
+
+ if (!strcmp(tag, "var")) {
+ const char *val;
+ const char *echo_text = NULL;
+ apr_size_t e_len;
+
+ val = get_include_var(ap_ssi_parse_string(ctx, tag_val, NULL,
+ 0, SSI_EXPAND_DROP_NAME),
+ ctx);
+
+ if (val) {
+ char *last = NULL;
+ char *e, *d, *token;
+
+ echo_text = val;
+
+ d = apr_pstrdup(ctx->pool, decoding);
+ token = apr_strtok(d, ", \t", &last);
+
+ while (token) {
+ if (!ap_cstr_casecmp(token, "none")) {
+ /* do nothing */
+ }
+ else if (!ap_cstr_casecmp(token, "url")) {
+ char *buf = apr_pstrdup(ctx->pool, echo_text);
+ ap_unescape_url(buf);
+ echo_text = buf;
+ }
+ else if (!ap_cstr_casecmp(token, "urlencoded")) {
+ char *buf = apr_pstrdup(ctx->pool, echo_text);
+ ap_unescape_urlencoded(buf);
+ echo_text = buf;
+ }
+ else if (!ap_cstr_casecmp(token, "entity")) {
+ char *buf = apr_pstrdup(ctx->pool, echo_text);
+ decodehtml(buf);
+ echo_text = buf;
+ }
+ else if (!ap_cstr_casecmp(token, "base64")) {
+ echo_text = ap_pbase64decode(ctx->dpool, echo_text);
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01344) "unknown value "
+ "\"%s\" to parameter \"decoding\" of tag echo in "
+ "%s", token, r->filename);
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ error = 1;
+ break;
+ }
+ token = apr_strtok(NULL, ", \t", &last);
+ }
+
+ e = apr_pstrdup(ctx->pool, encoding);
+ token = apr_strtok(e, ", \t", &last);
+
+ while (token) {
+ if (!ap_cstr_casecmp(token, "none")) {
+ /* do nothing */
+ }
+ else if (!ap_cstr_casecmp(token, "url")) {
+ echo_text = ap_escape_uri(ctx->dpool, echo_text);
+ }
+ else if (!ap_cstr_casecmp(token, "urlencoded")) {
+ echo_text = ap_escape_urlencoded(ctx->dpool, echo_text);
+ }
+ else if (!ap_cstr_casecmp(token, "entity")) {
+ echo_text = ap_escape_html2(ctx->dpool, echo_text, 0);
+ }
+ else if (!ap_cstr_casecmp(token, "base64")) {
+ char *buf;
+ buf = ap_pbase64encode(ctx->dpool, (char *)echo_text);
+ echo_text = buf;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01345) "unknown value "
+ "\"%s\" to parameter \"encoding\" of tag echo in "
+ "%s", token, r->filename);
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ error = 1;
+ break;
+ }
+ token = apr_strtok(NULL, ", \t", &last);
+ }
+
+ e_len = strlen(echo_text);
+ }
+ else {
+ echo_text = ctx->intern->undefined_echo;
+ e_len = ctx->intern->undefined_echo_len;
+ }
+
+ if (error) {
+ break;
+ }
+
+ APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(
+ apr_pmemdup(ctx->pool, echo_text, e_len),
+ e_len, ctx->pool, f->c->bucket_alloc));
+ }
+ else if (!strcmp(tag, "decoding")) {
+ decoding = tag_val;
+ }
+ else if (!strcmp(tag, "encoding")) {
+ encoding = tag_val;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01346) "unknown parameter "
+ "\"%s\" in tag echo of %s", tag, r->filename);
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ break;
+ }
+ }
+
+ return APR_SUCCESS;
+}
+
+/*
+ * <!--#config [timefmt="..."] [sizefmt="..."] [errmsg="..."]
+ * [echomsg="..."] -->
+ */
+static apr_status_t handle_config(include_ctx_t *ctx, ap_filter_t *f,
+ apr_bucket_brigade *bb)
+{
+ request_rec *r = f->r;
+ apr_table_t *env = r->subprocess_env;
+
+ if (!ctx->argc) {
+ ap_log_rerror(APLOG_MARK,
+ (ctx->flags & SSI_FLAG_PRINTING)
+ ? APLOG_ERR : APLOG_WARNING,
+ 0, r, APLOGNO(01347)
+ "missing argument for config element in %s",
+ r->filename);
+ }
+
+ if (!(ctx->flags & SSI_FLAG_PRINTING)) {
+ return APR_SUCCESS;
+ }
+
+ if (!ctx->argc) {
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ return APR_SUCCESS;
+ }
+
+ while (1) {
+ char *tag = NULL;
+ char *tag_val = NULL;
+
+ ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_RAW);
+ if (!tag || !tag_val) {
+ break;
+ }
+
+ if (!strcmp(tag, "errmsg")) {
+ ctx->error_str = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
+ SSI_EXPAND_DROP_NAME);
+ }
+ else if (!strcmp(tag, "echomsg")) {
+ ctx->intern->undefined_echo =
+ ap_ssi_parse_string(ctx, tag_val, NULL, 0,SSI_EXPAND_DROP_NAME);
+ ctx->intern->undefined_echo_len=strlen(ctx->intern->undefined_echo);
+ }
+ else if (!strcmp(tag, "timefmt")) {
+ apr_time_t date = r->request_time;
+
+ ctx->time_str = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
+ SSI_EXPAND_DROP_NAME);
+
+ apr_table_setn(env, "DATE_LOCAL", ap_ht_time(r->pool, date,
+ ctx->time_str, 0));
+ apr_table_setn(env, "DATE_GMT", ap_ht_time(r->pool, date,
+ ctx->time_str, 1));
+ apr_table_setn(env, "LAST_MODIFIED",
+ ap_ht_time(r->pool, r->finfo.mtime,
+ ctx->time_str, 0));
+ }
+ else if (!strcmp(tag, "sizefmt")) {
+ char *parsed_string;
+
+ parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
+ SSI_EXPAND_DROP_NAME);
+ if (!strcmp(parsed_string, "bytes")) {
+ ctx->flags |= SSI_FLAG_SIZE_IN_BYTES;
+ }
+ else if (!strcmp(parsed_string, "abbrev")) {
+ ctx->flags &= SSI_FLAG_SIZE_ABBREV;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01348) "unknown value "
+ "\"%s\" to parameter \"sizefmt\" of tag config "
+ "in %s", parsed_string, r->filename);
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ break;
+ }
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01349) "unknown parameter "
+ "\"%s\" to tag config in %s", tag, r->filename);
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ break;
+ }
+ }
+
+ return APR_SUCCESS;
+}
+
+/*
+ * <!--#fsize virtual|file="..." [virtual|file="..."] ... -->
+ */
+static apr_status_t handle_fsize(include_ctx_t *ctx, ap_filter_t *f,
+ apr_bucket_brigade *bb)
+{
+ request_rec *r = f->r;
+
+ if (!ctx->argc) {
+ ap_log_rerror(APLOG_MARK,
+ (ctx->flags & SSI_FLAG_PRINTING)
+ ? APLOG_ERR : APLOG_WARNING,
+ 0, r, APLOGNO(01350)
+ "missing argument for fsize element in %s",
+ r->filename);
+ }
+
+ if (!(ctx->flags & SSI_FLAG_PRINTING)) {
+ return APR_SUCCESS;
+ }
+
+ if (!ctx->argc) {
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ return APR_SUCCESS;
+ }
+
+ while (1) {
+ char *tag = NULL;
+ char *tag_val = NULL;
+ apr_finfo_t finfo;
+ char *parsed_string;
+
+ ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
+ if (!tag || !tag_val) {
+ break;
+ }
+
+ parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
+ SSI_EXPAND_DROP_NAME);
+
+ if (!find_file(r, "fsize", tag, parsed_string, &finfo)) {
+ char *buf;
+ apr_size_t len;
+
+ if (!(ctx->flags & SSI_FLAG_SIZE_IN_BYTES)) {
+ buf = apr_strfsize(finfo.size, apr_palloc(ctx->pool, 5));
+ len = 4; /* omit the \0 terminator */
+ }
+ else {
+ apr_size_t l, x, pos;
+ char *tmp;
+
+ tmp = apr_psprintf(ctx->dpool, "%" APR_OFF_T_FMT, finfo.size);
+ len = l = strlen(tmp);
+
+ for (x = 0; x < l; ++x) {
+ if (x && !((l - x) % 3)) {
+ ++len;
+ }
+ }
+
+ if (len == l) {
+ buf = apr_pstrmemdup(ctx->pool, tmp, len);
+ }
+ else {
+ buf = apr_palloc(ctx->pool, len);
+
+ for (pos = x = 0; x < l; ++x) {
+ if (x && !((l - x) % 3)) {
+ buf[pos++] = ',';
+ }
+ buf[pos++] = tmp[x];
+ }
+ }
+ }
+
+ APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(buf, len,
+ ctx->pool, f->c->bucket_alloc));
+ }
+ else {
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ break;
+ }
+ }
+
+ return APR_SUCCESS;
+}
+
+/*
+ * <!--#flastmod virtual|file="..." [virtual|file="..."] ... -->
+ */
+static apr_status_t handle_flastmod(include_ctx_t *ctx, ap_filter_t *f,
+ apr_bucket_brigade *bb)
+{
+ request_rec *r = f->r;
+
+ if (!ctx->argc) {
+ ap_log_rerror(APLOG_MARK,
+ (ctx->flags & SSI_FLAG_PRINTING)
+ ? APLOG_ERR : APLOG_WARNING,
+ 0, r, APLOGNO(01351)
+ "missing argument for flastmod element in %s",
+ r->filename);
+ }
+
+ if (!(ctx->flags & SSI_FLAG_PRINTING)) {
+ return APR_SUCCESS;
+ }
+
+ if (!ctx->argc) {
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ return APR_SUCCESS;
+ }
+
+ while (1) {
+ char *tag = NULL;
+ char *tag_val = NULL;
+ apr_finfo_t finfo;
+ char *parsed_string;
+
+ ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
+ if (!tag || !tag_val) {
+ break;
+ }
+
+ parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
+ SSI_EXPAND_DROP_NAME);
+
+ if (!find_file(r, "flastmod", tag, parsed_string, &finfo)) {
+ char *t_val;
+ apr_size_t t_len;
+
+ t_val = ap_ht_time(ctx->pool, finfo.mtime, ctx->time_str, 0);
+ t_len = strlen(t_val);
+
+ APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(t_val, t_len,
+ ctx->pool, f->c->bucket_alloc));
+ }
+ else {
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ break;
+ }
+ }
+
+ return APR_SUCCESS;
+}
+
+/*
+ * <!--#if expr="..." -->
+ */
+static apr_status_t handle_if(include_ctx_t *ctx, ap_filter_t *f,
+ apr_bucket_brigade *bb)
+{
+ char *tag = NULL;
+ char *expr = NULL;
+ request_rec *r = f->r;
+ int expr_ret, was_error;
+
+ if (ctx->argc != 1) {
+ ap_log_rerror(APLOG_MARK,
+ (ctx->flags & SSI_FLAG_PRINTING)
+ ? APLOG_ERR : APLOG_WARNING,
+ 0, r,
+ (ctx->argc)
+ ? APLOGNO(01352) "too many arguments for if element in %s"
+ : APLOGNO(01353) "missing expr argument for if element in %s",
+ r->filename);
+ }
+
+ if (!(ctx->flags & SSI_FLAG_PRINTING)) {
+ ++(ctx->if_nesting_level);
+ return APR_SUCCESS;
+ }
+
+ if (ctx->argc != 1) {
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ return APR_SUCCESS;
+ }
+
+ ap_ssi_get_tag_and_value(ctx, &tag, &expr, SSI_VALUE_RAW);
+
+ if (strcmp(tag, "expr")) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01354) "unknown parameter \"%s\" "
+ "to tag if in %s", tag, r->filename);
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ return APR_SUCCESS;
+ }
+
+ if (!expr) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01355) "missing expr value for if "
+ "element in %s", r->filename);
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ return APR_SUCCESS;
+ }
+
+ DEBUG_PRINTF((ctx, "**** if expr=\"%s\"\n", expr));
+
+ if (ctx->intern->legacy_expr)
+ expr_ret = parse_expr(ctx, expr, &was_error);
+ else
+ expr_ret = parse_ap_expr(ctx, expr, &was_error);
+
+ if (was_error) {
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ return APR_SUCCESS;
+ }
+
+ if (expr_ret) {
+ ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
+ }
+ else {
+ ctx->flags &= SSI_FLAG_CLEAR_PRINT_COND;
+ }
+
+ DEBUG_DUMP_COND(ctx, " if");
+
+ ctx->if_nesting_level = 0;
+
+ return APR_SUCCESS;
+}
+
+/*
+ * <!--#elif expr="..." -->
+ */
+static apr_status_t handle_elif(include_ctx_t *ctx, ap_filter_t *f,
+ apr_bucket_brigade *bb)
+{
+ char *tag = NULL;
+ char *expr = NULL;
+ request_rec *r = f->r;
+ int expr_ret, was_error;
+
+ if (ctx->argc != 1) {
+ ap_log_rerror(APLOG_MARK,
+ (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
+ 0, r,
+ (ctx->argc)
+ ? APLOGNO(01356) "too many arguments for if element in %s"
+ : APLOGNO(01357) "missing expr argument for if element in %s",
+ r->filename);
+ }
+
+ if (ctx->if_nesting_level) {
+ return APR_SUCCESS;
+ }
+
+ if (ctx->argc != 1) {
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ return APR_SUCCESS;
+ }
+
+ ap_ssi_get_tag_and_value(ctx, &tag, &expr, SSI_VALUE_RAW);
+
+ if (strcmp(tag, "expr")) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01358) "unknown parameter \"%s\" "
+ "to tag if in %s", tag, r->filename);
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ return APR_SUCCESS;
+ }
+
+ if (!expr) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01359) "missing expr in elif "
+ "statement: %s", r->filename);
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ return APR_SUCCESS;
+ }
+
+ DEBUG_PRINTF((ctx, "**** elif expr=\"%s\"\n", expr));
+ DEBUG_DUMP_COND(ctx, " elif");
+
+ if (ctx->flags & SSI_FLAG_COND_TRUE) {
+ ctx->flags &= SSI_FLAG_CLEAR_PRINTING;
+ return APR_SUCCESS;
+ }
+
+ if (ctx->intern->legacy_expr)
+ expr_ret = parse_expr(ctx, expr, &was_error);
+ else
+ expr_ret = parse_ap_expr(ctx, expr, &was_error);
+
+ if (was_error) {
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ return APR_SUCCESS;
+ }
+
+ if (expr_ret) {
+ ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
+ }
+ else {
+ ctx->flags &= SSI_FLAG_CLEAR_PRINT_COND;
+ }
+
+ DEBUG_DUMP_COND(ctx, " elif");
+
+ return APR_SUCCESS;
+}
+
+/*
+ * <!--#else -->
+ */
+static apr_status_t handle_else(include_ctx_t *ctx, ap_filter_t *f,
+ apr_bucket_brigade *bb)
+{
+ request_rec *r = f->r;
+
+ if (ctx->argc) {
+ ap_log_rerror(APLOG_MARK,
+ (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
+ 0, r, APLOGNO(01360)
+ "else directive does not take tags in %s",
+ r->filename);
+ }
+
+ if (ctx->if_nesting_level) {
+ return APR_SUCCESS;
+ }
+
+ if (ctx->argc) {
+ if (ctx->flags & SSI_FLAG_PRINTING) {
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ }
+
+ return APR_SUCCESS;
+ }
+
+ DEBUG_DUMP_COND(ctx, " else");
+
+ if (ctx->flags & SSI_FLAG_COND_TRUE) {
+ ctx->flags &= SSI_FLAG_CLEAR_PRINTING;
+ }
+ else {
+ ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
+ }
+
+ return APR_SUCCESS;
+}
+
+/*
+ * <!--#endif -->
+ */
+static apr_status_t handle_endif(include_ctx_t *ctx, ap_filter_t *f,
+ apr_bucket_brigade *bb)
+{
+ request_rec *r = f->r;
+
+ if (ctx->argc) {
+ ap_log_rerror(APLOG_MARK,
+ (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
+ 0, r, APLOGNO(01361)
+ "endif directive does not take tags in %s",
+ r->filename);
+ }
+
+ if (ctx->if_nesting_level) {
+ --(ctx->if_nesting_level);
+ return APR_SUCCESS;
+ }
+
+ if (ctx->argc) {
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ return APR_SUCCESS;
+ }
+
+ DEBUG_DUMP_COND(ctx, "endif");
+
+ ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
+
+ return APR_SUCCESS;
+}
+
+/*
+ * <!--#set var="..." value="..." ... -->
+ */
+static apr_status_t handle_set(include_ctx_t *ctx, ap_filter_t *f,
+ apr_bucket_brigade *bb)
+{
+ const char *encoding = "none", *decoding = "none";
+ char *var = NULL;
+ request_rec *r = f->r;
+ request_rec *sub = r->main;
+ apr_pool_t *p = r->pool;
+ int error = 0;
+
+ if (ctx->argc < 2) {
+ ap_log_rerror(APLOG_MARK,
+ (ctx->flags & SSI_FLAG_PRINTING)
+ ? APLOG_ERR : APLOG_WARNING,
+ 0, r,
+ APLOGNO(01362) "missing argument for set element in %s",
+ r->filename);
+ }
+
+ if (!(ctx->flags & SSI_FLAG_PRINTING)) {
+ return APR_SUCCESS;
+ }
+
+ if (ctx->argc < 2) {
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ return APR_SUCCESS;
+ }
+
+ /* we need to use the 'main' request pool to set notes as that is
+ * a notes lifetime
+ */
+ while (sub) {
+ p = sub->pool;
+ sub = sub->main;
+ }
+
+ while (1) {
+ char *tag = NULL;
+ char *tag_val = NULL;
+
+ ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_RAW);
+
+ if (!tag || !tag_val) {
+ break;
+ }
+
+ if (!strcmp(tag, "var")) {
+ decodehtml(tag_val);
+ var = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
+ SSI_EXPAND_DROP_NAME);
+ }
+ else if (!strcmp(tag, "decoding")) {
+ decoding = tag_val;
+ }
+ else if (!strcmp(tag, "encoding")) {
+ encoding = tag_val;
+ }
+ else if (!strcmp(tag, "value")) {
+ char *parsed_string;
+
+ if (!var) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01363) "variable must "
+ "precede value in set directive in %s",
+ r->filename);
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ break;
+ }
+
+ parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
+ SSI_EXPAND_DROP_NAME);
+
+ if (parsed_string) {
+ char *last = NULL;
+ char *e, *d, *token;
+
+ d = apr_pstrdup(ctx->pool, decoding);
+ token = apr_strtok(d, ", \t", &last);
+
+ while (token) {
+ if (!ap_cstr_casecmp(token, "none")) {
+ /* do nothing */
+ }
+ else if (!ap_cstr_casecmp(token, "url")) {
+ char *buf = apr_pstrdup(ctx->pool, parsed_string);
+ ap_unescape_url(buf);
+ parsed_string = buf;
+ }
+ else if (!ap_cstr_casecmp(token, "urlencoded")) {
+ char *buf = apr_pstrdup(ctx->pool, parsed_string);
+ ap_unescape_urlencoded(buf);
+ parsed_string = buf;
+ }
+ else if (!ap_cstr_casecmp(token, "entity")) {
+ char *buf = apr_pstrdup(ctx->pool, parsed_string);
+ decodehtml(buf);
+ parsed_string = buf;
+ }
+ else if (!ap_cstr_casecmp(token, "base64")) {
+ parsed_string = ap_pbase64decode(ctx->dpool, parsed_string);
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01364) "unknown value "
+ "\"%s\" to parameter \"decoding\" of tag set in "
+ "%s", token, r->filename);
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ error = 1;
+ break;
+ }
+ token = apr_strtok(NULL, ", \t", &last);
+ }
+
+ e = apr_pstrdup(ctx->pool, encoding);
+ token = apr_strtok(e, ", \t", &last);
+
+ while (token) {
+ if (!ap_cstr_casecmp(token, "none")) {
+ /* do nothing */
+ }
+ else if (!ap_cstr_casecmp(token, "url")) {
+ parsed_string = ap_escape_uri(ctx->dpool, parsed_string);
+ }
+ else if (!ap_cstr_casecmp(token, "urlencoded")) {
+ parsed_string = ap_escape_urlencoded(ctx->dpool, parsed_string);
+ }
+ else if (!ap_cstr_casecmp(token, "entity")) {
+ parsed_string = ap_escape_html2(ctx->dpool, parsed_string, 0);
+ }
+ else if (!ap_cstr_casecmp(token, "base64")) {
+ char *buf;
+ buf = ap_pbase64encode(ctx->dpool, (char *)parsed_string);
+ parsed_string = buf;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01365) "unknown value "
+ "\"%s\" to parameter \"encoding\" of tag set in "
+ "%s", token, r->filename);
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ error = 1;
+ break;
+ }
+ token = apr_strtok(NULL, ", \t", &last);
+ }
+
+ }
+
+ if (error) {
+ break;
+ }
+
+ apr_table_setn(r->subprocess_env, apr_pstrdup(p, var),
+ apr_pstrdup(p, parsed_string));
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01366) "Invalid tag for set "
+ "directive in %s", r->filename);
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ break;
+ }
+ }
+
+ return APR_SUCCESS;
+}
+
+/*
+ * <!--#printenv -->
+ */
+static apr_status_t handle_printenv(include_ctx_t *ctx, ap_filter_t *f,
+ apr_bucket_brigade *bb)
+{
+ request_rec *r = f->r;
+ const apr_array_header_t *arr;
+ const apr_table_entry_t *elts;
+ int i;
+
+ if (ctx->argc) {
+ ap_log_rerror(APLOG_MARK,
+ (ctx->flags & SSI_FLAG_PRINTING)
+ ? APLOG_ERR : APLOG_WARNING,
+ 0, r,
+ APLOGNO(01367) "printenv directive does not take tags in %s",
+ r->filename);
+ }
+
+ if (!(ctx->flags & SSI_FLAG_PRINTING)) {
+ return APR_SUCCESS;
+ }
+
+ if (ctx->argc) {
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ return APR_SUCCESS;
+ }
+
+ arr = apr_table_elts(r->subprocess_env);
+ elts = (apr_table_entry_t *)arr->elts;
+
+ for (i = 0; i < arr->nelts; ++i) {
+ const char *key_text, *val_text;
+
+ /* get key */
+ key_text = ap_escape_html(ctx->dpool, elts[i].key);
+
+ /* get value */
+ val_text = elts[i].val;
+ if (val_text == LAZY_VALUE)
+ val_text = add_include_vars_lazy(r, elts[i].key, ctx->time_str);
+ val_text = ap_escape_html(ctx->dpool, val_text);
+
+ apr_brigade_putstrs(bb, NULL, NULL, key_text, "=", val_text, "\n",
+ NULL);
+ }
+
+ ctx->flush_now = 1;
+ return APR_SUCCESS;
+}
+
+
+/*
+ * +-------------------------------------------------------+
+ * | |
+ * | Main Includes-Filter Engine
+ * | |
+ * +-------------------------------------------------------+
+ */
+
+/* This is an implementation of the BNDM search algorithm.
+ *
+ * Fast and Flexible String Matching by Combining Bit-parallelism and
+ * Suffix Automata (2001)
+ * Gonzalo Navarro, Mathieu Raffinot
+ *
+ * http://www-igm.univ-mlv.fr/~raffinot/ftp/jea2001.ps.gz
+ *
+ * Initial code submitted by Sascha Schumann.
+ */
+
+/* Precompile the bndm_t data structure. */
+static bndm_t *bndm_compile(apr_pool_t *pool, const char *n, apr_size_t nl)
+{
+ unsigned int x;
+ const char *ne = n + nl;
+ bndm_t *t = apr_palloc(pool, sizeof(*t));
+
+ memset(t->T, 0, sizeof(unsigned int) * 256);
+ t->pattern_len = nl;
+
+ for (x = 1; n < ne; x <<= 1) {
+ t->T[(unsigned char) *n++] |= x;
+ }
+
+ t->x = x - 1;
+
+ return t;
+}
+
+/* Implements the BNDM search algorithm (as described above).
+ *
+ * h - the string to look in
+ * hl - length of the string to look for
+ * t - precompiled bndm structure against the pattern
+ *
+ * Returns the count of character that is the first match or hl if no
+ * match is found.
+ */
+static apr_size_t bndm(bndm_t *t, const char *h, apr_size_t hl)
+{
+ const char *skip;
+ const char *he, *p, *pi;
+ unsigned int *T, x, d;
+ apr_size_t nl;
+
+ he = h + hl;
+
+ T = t->T;
+ x = t->x;
+ nl = t->pattern_len;
+
+ pi = h - 1; /* pi: p initial */
+ p = pi + nl; /* compare window right to left. point to the first char */
+
+ while (p < he) {
+ skip = p;
+ d = x;
+ do {
+ d &= T[(unsigned char) *p--];
+ if (!d) {
+ break;
+ }
+ if ((d & 1)) {
+ if (p != pi) {
+ skip = p;
+ }
+ else {
+ return p - h + 1;
+ }
+ }
+ d >>= 1;
+ } while (d);
+
+ pi = skip;
+ p = pi + nl;
+ }
+
+ return hl;
+}
+
+/*
+ * returns the index position of the first byte of start_seq (or the len of
+ * the buffer as non-match)
+ */
+static apr_size_t find_start_sequence(include_ctx_t *ctx, const char *data,
+ apr_size_t len)
+{
+ struct ssi_internal_ctx *intern = ctx->intern;
+ apr_size_t slen = intern->start_seq_pat->pattern_len;
+ apr_size_t index;
+ const char *p, *ep;
+
+ if (len < slen) {
+ p = data; /* try partial match at the end of the buffer (below) */
+ }
+ else {
+ /* try fast bndm search over the buffer
+ * (hopefully the whole start sequence can be found in this buffer)
+ */
+ index = bndm(intern->start_seq_pat, data, len);
+
+ /* wow, found it. ready. */
+ if (index < len) {
+ intern->state = PARSE_DIRECTIVE;
+ return index;
+ }
+ else {
+ /* ok, the pattern can't be found as whole in the buffer,
+ * check the end for a partial match
+ */
+ p = data + len - slen + 1;
+ }
+ }
+
+ ep = data + len;
+ do {
+ while (p < ep && *p != *intern->start_seq) {
+ ++p;
+ }
+
+ index = p - data;
+
+ /* found a possible start_seq start */
+ if (p < ep) {
+ apr_size_t pos = 1;
+
+ ++p;
+ while (p < ep && *p == intern->start_seq[pos]) {
+ ++p;
+ ++pos;
+ }
+
+ /* partial match found. Store the info for the next round */
+ if (p == ep) {
+ intern->state = PARSE_HEAD;
+ intern->parse_pos = pos;
+ return index;
+ }
+ }
+
+ /* we must try all combinations; consider (e.g.) SSIStartTag "--->"
+ * and a string data of "--.-" and the end of the buffer
+ */
+ p = data + index + 1;
+ } while (p < ep);
+
+ /* no match */
+ return len;
+}
+
+/*
+ * returns the first byte *after* the partial (or final) match.
+ *
+ * If we had to trick with the start_seq start, 'release' returns the
+ * number of chars of the start_seq which appeared not to be part of a
+ * full tag and may have to be passed down the filter chain.
+ */
+static apr_size_t find_partial_start_sequence(include_ctx_t *ctx,
+ const char *data,
+ apr_size_t len,
+ apr_size_t *release)
+{
+ struct ssi_internal_ctx *intern = ctx->intern;
+ apr_size_t pos, spos = 0;
+ apr_size_t slen = intern->start_seq_pat->pattern_len;
+ const char *p, *ep;
+
+ pos = intern->parse_pos;
+ ep = data + len;
+ *release = 0;
+
+ do {
+ p = data;
+
+ while (p < ep && pos < slen && *p == intern->start_seq[pos]) {
+ ++p;
+ ++pos;
+ }
+
+ /* full match */
+ if (pos == slen) {
+ intern->state = PARSE_DIRECTIVE;
+ return (p - data);
+ }
+
+ /* the whole buffer is a partial match */
+ if (p == ep) {
+ intern->parse_pos = pos;
+ return (p - data);
+ }
+
+ /* No match so far, but again:
+ * We must try all combinations, since the start_seq is a random
+ * user supplied string
+ *
+ * So: look if the first char of start_seq appears somewhere within
+ * the current partial match. If it does, try to start a match that
+ * begins with this offset. (This can happen, if a strange
+ * start_seq like "---->" spans buffers)
+ */
+ if (spos < intern->parse_pos) {
+ do {
+ ++spos;
+ ++*release;
+ p = intern->start_seq + spos;
+ pos = intern->parse_pos - spos;
+
+ while (pos && *p != *intern->start_seq) {
+ ++p;
+ ++spos;
+ ++*release;
+ --pos;
+ }
+
+ /* if a matching beginning char was found, try to match the
+ * remainder of the old buffer.
+ */
+ if (pos > 1) {
+ apr_size_t t = 1;
+
+ ++p;
+ while (t < pos && *p == intern->start_seq[t]) {
+ ++p;
+ ++t;
+ }
+
+ if (t == pos) {
+ /* yeah, another partial match found in the *old*
+ * buffer, now test the *current* buffer for
+ * continuing match
+ */
+ break;
+ }
+ }
+ } while (pos > 1);
+
+ if (pos) {
+ continue;
+ }
+ }
+
+ break;
+ } while (1); /* work hard to find a match ;-) */
+
+ /* no match at all, release all (wrongly) matched chars so far */
+ *release = intern->parse_pos;
+ intern->state = PARSE_PRE_HEAD;
+ return 0;
+}
+
+/*
+ * returns the position after the directive
+ */
+static apr_size_t find_directive(include_ctx_t *ctx, const char *data,
+ apr_size_t len, char ***store,
+ apr_size_t **store_len)
+{
+ struct ssi_internal_ctx *intern = ctx->intern;
+ const char *p = data;
+ const char *ep = data + len;
+ apr_size_t pos;
+
+ switch (intern->state) {
+ case PARSE_DIRECTIVE:
+ while (p < ep && !apr_isspace(*p)) {
+ /* we have to consider the case of missing space between directive
+ * and end_seq (be somewhat lenient), e.g. <!--#printenv-->
+ */
+ if (*p == *intern->end_seq) {
+ intern->state = PARSE_DIRECTIVE_TAIL;
+ intern->parse_pos = 1;
+ ++p;
+ return (p - data);
+ }
+ ++p;
+ }
+
+ if (p < ep) { /* found delimiter whitespace */
+ intern->state = PARSE_DIRECTIVE_POSTNAME;
+ *store = &intern->directive;
+ *store_len = &intern->directive_len;
+ }
+
+ break;
+
+ case PARSE_DIRECTIVE_TAIL:
+ pos = intern->parse_pos;
+
+ while (p < ep && pos < intern->end_seq_len &&
+ *p == intern->end_seq[pos]) {
+ ++p;
+ ++pos;
+ }
+
+ /* full match, we're done */
+ if (pos == intern->end_seq_len) {
+ intern->state = PARSE_DIRECTIVE_POSTTAIL;
+ *store = &intern->directive;
+ *store_len = &intern->directive_len;
+ break;
+ }
+
+ /* partial match, the buffer is too small to match fully */
+ if (p == ep) {
+ intern->parse_pos = pos;
+ break;
+ }
+
+ /* no match. continue normal parsing */
+ intern->state = PARSE_DIRECTIVE;
+ return 0;
+
+ case PARSE_DIRECTIVE_POSTTAIL:
+ intern->state = PARSE_EXECUTE;
+ intern->directive_len -= intern->end_seq_len;
+ /* continue immediately with the next state */
+
+ case PARSE_DIRECTIVE_POSTNAME:
+ if (PARSE_DIRECTIVE_POSTNAME == intern->state) {
+ intern->state = PARSE_PRE_ARG;
+ }
+ ctx->argc = 0;
+ intern->argv = NULL;
+
+ if (!intern->directive_len) {
+ intern->error = 1;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(01368) "missing "
+ "directive name in parsed document %s",
+ ctx->r->filename);
+ }
+ else {
+ char *sp = intern->directive;
+ char *sep = intern->directive + intern->directive_len;
+
+ /* normalize directive name */
+ for (; sp < sep; ++sp) {
+ *sp = apr_tolower(*sp);
+ }
+ }
+
+ return 0;
+
+ default:
+ /* get a rid of a gcc warning about unhandled enumerations */
+ break;
+ }
+
+ return (p - data);
+}
+
+/*
+ * find out whether the next token is (a possible) end_seq or an argument
+ */
+static apr_size_t find_arg_or_tail(include_ctx_t *ctx, const char *data,
+ apr_size_t len)
+{
+ struct ssi_internal_ctx *intern = ctx->intern;
+ const char *p = data;
+ const char *ep = data + len;
+
+ /* skip leading WS */
+ while (p < ep && apr_isspace(*p)) {
+ ++p;
+ }
+
+ /* buffer doesn't consist of whitespaces only */
+ if (p < ep) {
+ intern->state = (*p == *intern->end_seq) ? PARSE_TAIL : PARSE_ARG;
+ }
+
+ return (p - data);
+}
+
+/*
+ * test the stream for end_seq. If it doesn't match at all, it must be an
+ * argument
+ */
+static apr_size_t find_tail(include_ctx_t *ctx, const char *data,
+ apr_size_t len)
+{
+ struct ssi_internal_ctx *intern = ctx->intern;
+ const char *p = data;
+ const char *ep = data + len;
+ apr_size_t pos = intern->parse_pos;
+
+ if (PARSE_TAIL == intern->state) {
+ intern->state = PARSE_TAIL_SEQ;
+ pos = intern->parse_pos = 0;
+ }
+
+ while (p < ep && pos < intern->end_seq_len && *p == intern->end_seq[pos]) {
+ ++p;
+ ++pos;
+ }
+
+ /* bingo, full match */
+ if (pos == intern->end_seq_len) {
+ intern->state = PARSE_EXECUTE;
+ return (p - data);
+ }
+
+ /* partial match, the buffer is too small to match fully */
+ if (p == ep) {
+ intern->parse_pos = pos;
+ return (p - data);
+ }
+
+ /* no match. It must be an argument string then
+ * The caller should cleanup and rewind to the reparse point
+ */
+ intern->state = PARSE_ARG;
+ return 0;
+}
+
+/*
+ * extract name=value from the buffer
+ * A pcre-pattern could look (similar to):
+ * name\s*(?:=\s*(["'`]?)value\1(?>\s*))?
+ */
+static apr_size_t find_argument(include_ctx_t *ctx, const char *data,
+ apr_size_t len, char ***store,
+ apr_size_t **store_len)
+{
+ struct ssi_internal_ctx *intern = ctx->intern;
+ const char *p = data;
+ const char *ep = data + len;
+
+ switch (intern->state) {
+ case PARSE_ARG:
+ /*
+ * create argument structure and append it to the current list
+ */
+ intern->current_arg = apr_palloc(ctx->dpool,
+ sizeof(*intern->current_arg));
+ intern->current_arg->next = NULL;
+
+ ++(ctx->argc);
+ if (!intern->argv) {
+ intern->argv = intern->current_arg;
+ }
+ else {
+ arg_item_t *newarg = intern->argv;
+
+ while (newarg->next) {
+ newarg = newarg->next;
+ }
+ newarg->next = intern->current_arg;
+ }
+
+ /* check whether it's a valid one. If it begins with a quote, we
+ * can safely assume, someone forgot the name of the argument
+ */
+ switch (*p) {
+ case '"': case '\'': case '`':
+ *store = NULL;
+
+ intern->state = PARSE_ARG_VAL;
+ intern->quote = *p++;
+ intern->current_arg->name = NULL;
+ intern->current_arg->name_len = 0;
+ intern->error = 1;
+
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(01369) "missing "
+ "argument name for value to tag %s in %s",
+ apr_pstrmemdup(ctx->r->pool, intern->directive,
+ intern->directive_len),
+ ctx->r->filename);
+
+ return (p - data);
+
+ default:
+ intern->state = PARSE_ARG_NAME;
+ }
+ /* continue immediately with next state */
+
+ case PARSE_ARG_NAME:
+ while (p < ep && !apr_isspace(*p) && *p != '=') {
+ ++p;
+ }
+
+ if (p < ep) {
+ intern->state = PARSE_ARG_POSTNAME;
+ *store = &intern->current_arg->name;
+ *store_len = &intern->current_arg->name_len;
+ return (p - data);
+ }
+ break;
+
+ case PARSE_ARG_POSTNAME:
+ intern->current_arg->name = apr_pstrmemdup(ctx->dpool,
+ intern->current_arg->name,
+ intern->current_arg->name_len);
+ if (!intern->current_arg->name_len) {
+ intern->error = 1;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(01370) "missing "
+ "argument name for value to tag %s in %s",
+ apr_pstrmemdup(ctx->r->pool, intern->directive,
+ intern->directive_len),
+ ctx->r->filename);
+ }
+ else {
+ ap_str_tolower(intern->current_arg->name);
+ }
+
+ intern->state = PARSE_ARG_EQ;
+ /* continue with next state immediately */
+
+ case PARSE_ARG_EQ:
+ *store = NULL;
+
+ while (p < ep && apr_isspace(*p)) {
+ ++p;
+ }
+
+ if (p < ep) {
+ if (*p == '=') {
+ intern->state = PARSE_ARG_PREVAL;
+ ++p;
+ }
+ else { /* no value */
+ intern->current_arg->value = NULL;
+ intern->state = PARSE_PRE_ARG;
+ }
+
+ return (p - data);
+ }
+ break;
+
+ case PARSE_ARG_PREVAL:
+ *store = NULL;
+
+ while (p < ep && apr_isspace(*p)) {
+ ++p;
+ }
+
+ /* buffer doesn't consist of whitespaces only */
+ if (p < ep) {
+ intern->state = PARSE_ARG_VAL;
+ switch (*p) {
+ case '"': case '\'': case '`':
+ intern->quote = *p++;
+ break;
+ default:
+ intern->quote = '\0';
+ break;
+ }
+
+ return (p - data);
+ }
+ break;
+
+ case PARSE_ARG_VAL_ESC:
+ if (*p == intern->quote) {
+ ++p;
+ }
+ intern->state = PARSE_ARG_VAL;
+ /* continue with next state immediately */
+
+ case PARSE_ARG_VAL:
+ for (; p < ep; ++p) {
+ if (intern->quote && *p == '\\') {
+ ++p;
+ if (p == ep) {
+ intern->state = PARSE_ARG_VAL_ESC;
+ break;
+ }
+
+ if (*p != intern->quote) {
+ --p;
+ }
+ }
+ else if (intern->quote && *p == intern->quote) {
+ ++p;
+ *store = &intern->current_arg->value;
+ *store_len = &intern->current_arg->value_len;
+ intern->state = PARSE_ARG_POSTVAL;
+ break;
+ }
+ else if (!intern->quote && apr_isspace(*p)) {
+ ++p;
+ *store = &intern->current_arg->value;
+ *store_len = &intern->current_arg->value_len;
+ intern->state = PARSE_ARG_POSTVAL;
+ break;
+ }
+ }
+
+ return (p - data);
+
+ case PARSE_ARG_POSTVAL:
+ /*
+ * The value is still the raw input string. Finally clean it up.
+ */
+ --(intern->current_arg->value_len);
+
+ /* strip quote escaping \ from the string */
+ if (intern->quote) {
+ apr_size_t shift = 0;
+ char *sp;
+
+ sp = intern->current_arg->value;
+ ep = intern->current_arg->value + intern->current_arg->value_len;
+ while (sp < ep && *sp != '\\') {
+ ++sp;
+ }
+ for (; sp < ep; ++sp) {
+ if (*sp == '\\' && sp[1] == intern->quote) {
+ ++sp;
+ ++shift;
+ }
+ if (shift) {
+ *(sp-shift) = *sp;
+ }
+ }
+
+ intern->current_arg->value_len -= shift;
+ }
+
+ intern->current_arg->value[intern->current_arg->value_len] = '\0';
+ intern->state = PARSE_PRE_ARG;
+
+ return 0;
+
+ default:
+ /* get a rid of a gcc warning about unhandled enumerations */
+ break;
+ }
+
+ return len; /* partial match of something */
+}
+
+/*
+ * This is the main loop over the current bucket brigade.
+ */
+static apr_status_t send_parsed_content(ap_filter_t *f, apr_bucket_brigade *bb)
+{
+ include_ctx_t *ctx = f->ctx;
+ struct ssi_internal_ctx *intern = ctx->intern;
+ request_rec *r = f->r;
+ apr_bucket *b = APR_BRIGADE_FIRST(bb);
+ apr_bucket_brigade *pass_bb;
+ apr_status_t rv = APR_SUCCESS;
+ char *magic; /* magic pointer for sentinel use */
+
+ /* fast exit */
+ if (APR_BRIGADE_EMPTY(bb)) {
+ return APR_SUCCESS;
+ }
+
+ /* we may crash, since already cleaned up; hand over the responsibility
+ * to the next filter;-)
+ */
+ if (intern->seen_eos) {
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ /* All stuff passed along has to be put into that brigade */
+ pass_bb = apr_brigade_create(ctx->pool, f->c->bucket_alloc);
+
+ /* initialization for this loop */
+ intern->bytes_read = 0;
+ intern->error = 0;
+ ctx->flush_now = 0;
+
+ /* loop over the current bucket brigade */
+ while (b != APR_BRIGADE_SENTINEL(bb)) {
+ const char *data = NULL;
+ apr_size_t len, index, release;
+ apr_bucket *newb = NULL;
+ char **store = &magic;
+ apr_size_t *store_len = NULL;
+
+ /* handle meta buckets before reading any data */
+ if (APR_BUCKET_IS_METADATA(b)) {
+ newb = APR_BUCKET_NEXT(b);
+
+ APR_BUCKET_REMOVE(b);
+
+ if (APR_BUCKET_IS_EOS(b)) {
+ intern->seen_eos = 1;
+
+ /* Hit end of stream, time for cleanup ... But wait!
+ * Perhaps we're not ready yet. We may have to loop one or
+ * two times again to finish our work. In that case, we
+ * just re-insert the EOS bucket to allow for an extra loop.
+ *
+ * PARSE_EXECUTE means, we've hit a directive just before the
+ * EOS, which is now waiting for execution.
+ *
+ * PARSE_DIRECTIVE_POSTTAIL means, we've hit a directive with
+ * no argument and no space between directive and end_seq
+ * just before the EOS. (consider <!--#printenv--> as last
+ * or only string within the stream). This state, however,
+ * just cleans up and turns itself to PARSE_EXECUTE, which
+ * will be passed through within the next (and actually
+ * last) round.
+ */
+ if (PARSE_EXECUTE == intern->state ||
+ PARSE_DIRECTIVE_POSTTAIL == intern->state) {
+ APR_BUCKET_INSERT_BEFORE(newb, b);
+ }
+ else {
+ break; /* END OF STREAM */
+ }
+ }
+ else {
+ APR_BRIGADE_INSERT_TAIL(pass_bb, b);
+
+ if (APR_BUCKET_IS_FLUSH(b)) {
+ ctx->flush_now = 1;
+ }
+
+ b = newb;
+ continue;
+ }
+ }
+
+ /* enough is enough ... */
+ if (ctx->flush_now ||
+ intern->bytes_read > AP_MIN_BYTES_TO_WRITE) {
+
+ if (!APR_BRIGADE_EMPTY(pass_bb)) {
+ rv = ap_pass_brigade(f->next, pass_bb);
+ if (rv != APR_SUCCESS) {
+ apr_brigade_destroy(pass_bb);
+ return rv;
+ }
+ }
+
+ ctx->flush_now = 0;
+ intern->bytes_read = 0;
+ }
+
+ /* read the current bucket data */
+ len = 0;
+ if (!intern->seen_eos) {
+ if (intern->bytes_read > 0) {
+ rv = apr_bucket_read(b, &data, &len, APR_NONBLOCK_READ);
+ if (APR_STATUS_IS_EAGAIN(rv)) {
+ ctx->flush_now = 1;
+ continue;
+ }
+ }
+
+ if (!len || rv != APR_SUCCESS) {
+ rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
+ }
+
+ if (rv != APR_SUCCESS) {
+ apr_brigade_destroy(pass_bb);
+ return rv;
+ }
+
+ intern->bytes_read += len;
+ }
+
+ /* zero length bucket, fetch next one */
+ if (!len && !intern->seen_eos) {
+ b = APR_BUCKET_NEXT(b);
+ continue;
+ }
+
+ /*
+ * it's actually a data containing bucket, start/continue parsing
+ */
+
+ switch (intern->state) {
+ /* no current tag; search for start sequence */
+ case PARSE_PRE_HEAD:
+ index = find_start_sequence(ctx, data, len);
+
+ if (index < len) {
+ apr_bucket_split(b, index);
+ }
+
+ newb = APR_BUCKET_NEXT(b);
+ if (ctx->flags & SSI_FLAG_PRINTING) {
+ APR_BUCKET_REMOVE(b);
+ APR_BRIGADE_INSERT_TAIL(pass_bb, b);
+ }
+ else {
+ apr_bucket_delete(b);
+ }
+
+ if (index < len) {
+ /* now delete the start_seq stuff from the remaining bucket */
+ if (PARSE_DIRECTIVE == intern->state) { /* full match */
+ apr_bucket_split(newb, intern->start_seq_pat->pattern_len);
+ ctx->flush_now = 1; /* pass pre-tag stuff */
+ }
+
+ b = APR_BUCKET_NEXT(newb);
+ apr_bucket_delete(newb);
+ }
+ else {
+ b = newb;
+ }
+
+ break;
+
+ /* we're currently looking for the end of the start sequence */
+ case PARSE_HEAD:
+ index = find_partial_start_sequence(ctx, data, len, &release);
+
+ /* check if we mismatched earlier and have to release some chars */
+ if (release && (ctx->flags & SSI_FLAG_PRINTING)) {
+ char *to_release = apr_pmemdup(ctx->pool, intern->start_seq, release);
+
+ newb = apr_bucket_pool_create(to_release, release, ctx->pool,
+ f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(pass_bb, newb);
+ }
+
+ if (index) { /* any match */
+ /* now delete the start_seq stuff from the remaining bucket */
+ if (PARSE_DIRECTIVE == intern->state) { /* final match */
+ apr_bucket_split(b, index);
+ ctx->flush_now = 1; /* pass pre-tag stuff */
+ }
+ newb = APR_BUCKET_NEXT(b);
+ apr_bucket_delete(b);
+ b = newb;
+ }
+
+ break;
+
+ /* we're currently grabbing the directive name */
+ case PARSE_DIRECTIVE:
+ case PARSE_DIRECTIVE_POSTNAME:
+ case PARSE_DIRECTIVE_TAIL:
+ case PARSE_DIRECTIVE_POSTTAIL:
+ index = find_directive(ctx, data, len, &store, &store_len);
+
+ if (index) {
+ apr_bucket_split(b, index);
+ newb = APR_BUCKET_NEXT(b);
+ }
+
+ if (store) {
+ if (index) {
+ APR_BUCKET_REMOVE(b);
+ apr_bucket_setaside(b, r->pool);
+ APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
+ b = newb;
+ }
+
+ /* time for cleanup? */
+ if (store != &magic) {
+ apr_brigade_pflatten(intern->tmp_bb, store, store_len,
+ ctx->dpool);
+ apr_brigade_cleanup(intern->tmp_bb);
+ }
+ }
+ else if (index) {
+ apr_bucket_delete(b);
+ b = newb;
+ }
+
+ break;
+
+ /* skip WS and find out what comes next (arg or end_seq) */
+ case PARSE_PRE_ARG:
+ index = find_arg_or_tail(ctx, data, len);
+
+ if (index) { /* skipped whitespaces */
+ if (index < len) {
+ apr_bucket_split(b, index);
+ }
+ newb = APR_BUCKET_NEXT(b);
+ apr_bucket_delete(b);
+ b = newb;
+ }
+
+ break;
+
+ /* currently parsing name[=val] */
+ case PARSE_ARG:
+ case PARSE_ARG_NAME:
+ case PARSE_ARG_POSTNAME:
+ case PARSE_ARG_EQ:
+ case PARSE_ARG_PREVAL:
+ case PARSE_ARG_VAL:
+ case PARSE_ARG_VAL_ESC:
+ case PARSE_ARG_POSTVAL:
+ index = find_argument(ctx, data, len, &store, &store_len);
+
+ if (index) {
+ apr_bucket_split(b, index);
+ newb = APR_BUCKET_NEXT(b);
+ }
+
+ if (store) {
+ if (index) {
+ APR_BUCKET_REMOVE(b);
+ apr_bucket_setaside(b, r->pool);
+ APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
+ b = newb;
+ }
+
+ /* time for cleanup? */
+ if (store != &magic) {
+ apr_brigade_pflatten(intern->tmp_bb, store, store_len,
+ ctx->dpool);
+ apr_brigade_cleanup(intern->tmp_bb);
+ }
+ }
+ else if (index) {
+ apr_bucket_delete(b);
+ b = newb;
+ }
+
+ break;
+
+ /* try to match end_seq at current pos. */
+ case PARSE_TAIL:
+ case PARSE_TAIL_SEQ:
+ index = find_tail(ctx, data, len);
+
+ switch (intern->state) {
+ case PARSE_EXECUTE: /* full match */
+ apr_bucket_split(b, index);
+ newb = APR_BUCKET_NEXT(b);
+ apr_bucket_delete(b);
+ b = newb;
+ break;
+
+ case PARSE_ARG: /* no match */
+ /* PARSE_ARG must reparse at the beginning */
+ APR_BRIGADE_PREPEND(bb, intern->tmp_bb);
+ b = APR_BRIGADE_FIRST(bb);
+ break;
+
+ default: /* partial match */
+ newb = APR_BUCKET_NEXT(b);
+ APR_BUCKET_REMOVE(b);
+ apr_bucket_setaside(b, r->pool);
+ APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
+ b = newb;
+ break;
+ }
+
+ break;
+
+ /* now execute the parsed directive, cleanup the space and
+ * start again with PARSE_PRE_HEAD
+ */
+ case PARSE_EXECUTE:
+ /* if there was an error, it was already logged; just stop here */
+ if (intern->error) {
+ if (ctx->flags & SSI_FLAG_PRINTING) {
+ SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
+ intern->error = 0;
+ }
+ }
+ else {
+ include_handler_fn_t *handle_func;
+
+ handle_func =
+ (include_handler_fn_t *)apr_hash_get(include_handlers, intern->directive,
+ intern->directive_len);
+
+ if (handle_func) {
+ DEBUG_INIT(ctx, f, pass_bb);
+ rv = handle_func(ctx, f, pass_bb);
+ if (rv != APR_SUCCESS) {
+ apr_brigade_destroy(pass_bb);
+ return rv;
+ }
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01371)
+ "unknown directive \"%s\" in parsed doc %s",
+ apr_pstrmemdup(r->pool, intern->directive,
+ intern->directive_len),
+ r->filename);
+ if (ctx->flags & SSI_FLAG_PRINTING) {
+ SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
+ }
+ }
+ }
+
+ /* cleanup */
+ apr_pool_clear(ctx->dpool);
+ apr_brigade_cleanup(intern->tmp_bb);
+
+ /* Oooof. Done here, start next round */
+ intern->state = PARSE_PRE_HEAD;
+ break;
+
+ } /* switch(ctx->state) */
+
+ } /* while (brigade) */
+
+ /* End of stream. Final cleanup */
+ if (intern->seen_eos) {
+ if (PARSE_HEAD == intern->state) {
+ if (ctx->flags & SSI_FLAG_PRINTING) {
+ char *to_release = apr_pmemdup(ctx->pool, intern->start_seq,
+ intern->parse_pos);
+
+ APR_BRIGADE_INSERT_TAIL(pass_bb,
+ apr_bucket_pool_create(to_release,
+ intern->parse_pos, ctx->pool,
+ f->c->bucket_alloc));
+ }
+ }
+ else if (PARSE_PRE_HEAD != intern->state) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01372)
+ "SSI directive was not properly finished at the end "
+ "of parsed document %s", r->filename);
+ if (ctx->flags & SSI_FLAG_PRINTING) {
+ SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
+ }
+ }
+
+ if (!(ctx->flags & SSI_FLAG_PRINTING)) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01373)
+ "missing closing endif directive in parsed document"
+ " %s", r->filename);
+ }
+
+ /* cleanup our temporary memory */
+ apr_brigade_destroy(intern->tmp_bb);
+ apr_pool_destroy(ctx->dpool);
+
+ /* don't forget to finally insert the EOS bucket */
+ APR_BRIGADE_INSERT_TAIL(pass_bb, b);
+ }
+
+ /* if something's left over, pass it along */
+ if (!APR_BRIGADE_EMPTY(pass_bb)) {
+ rv = ap_pass_brigade(f->next, pass_bb);
+ }
+ else {
+ rv = APR_SUCCESS;
+ apr_brigade_destroy(pass_bb);
+ }
+ return rv;
+}
+
+
+/*
+ * +-------------------------------------------------------+
+ * | |
+ * | Runtime Hooks
+ * | |
+ * +-------------------------------------------------------+
+ */
+
+static int includes_setup(ap_filter_t *f)
+{
+ include_dir_config *conf = ap_get_module_config(f->r->per_dir_config,
+ &include_module);
+
+ /* When our xbithack value isn't set to full or our platform isn't
+ * providing group-level protection bits or our group-level bits do not
+ * have group-execite on, we will set the no_local_copy value to 1 so
+ * that we will not send 304s.
+ */
+ if ((conf->xbithack != XBITHACK_FULL)
+ || !(f->r->finfo.valid & APR_FINFO_GPROT)
+ || !(f->r->finfo.protection & APR_GEXECUTE)) {
+ f->r->no_local_copy = 1;
+ }
+
+ /* Don't allow ETag headers to be generated - see RFC2616 - 13.3.4.
+ * We don't know if we are going to be including a file or executing
+ * a program - in either case a strong ETag header will likely be invalid.
+ */
+ if (conf->etag <= 0) {
+ apr_table_setn(f->r->notes, "no-etag", "");
+ }
+
+ return OK;
+}
+
+static apr_status_t includes_filter(ap_filter_t *f, apr_bucket_brigade *b)
+{
+ request_rec *r = f->r;
+ request_rec *parent;
+ include_dir_config *conf = ap_get_module_config(r->per_dir_config,
+ &include_module);
+
+ include_server_config *sconf= ap_get_module_config(r->server->module_config,
+ &include_module);
+
+ if (!(ap_allow_options(r) & OPT_INCLUDES)) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01374)
+ "mod_include: Options +Includes (or IncludesNoExec) "
+ "wasn't set, INCLUDES filter removed: %s", r->uri);
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, b);
+ }
+
+ if (!f->ctx) {
+ struct ssi_internal_ctx *intern;
+ include_ctx_t *ctx;
+
+ /* create context for this filter */
+ f->ctx = ctx = apr_palloc(r->pool, sizeof(*ctx));
+ ctx->r = r;
+ ctx->intern = intern = apr_palloc(r->pool, sizeof(*ctx->intern));
+ ctx->pool = r->pool;
+ apr_pool_create(&ctx->dpool, ctx->pool);
+ apr_pool_tag(ctx->dpool, "includes_dpool");
+
+ /* runtime data */
+ intern->tmp_bb = apr_brigade_create(ctx->pool, f->c->bucket_alloc);
+ intern->seen_eos = 0;
+ intern->state = PARSE_PRE_HEAD;
+ ctx->flags = (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
+ if ((ap_allow_options(r) & OPT_INC_WITH_EXEC) == 0) {
+ ctx->flags |= SSI_FLAG_NO_EXEC;
+ }
+ intern->legacy_expr = (conf->legacy_expr > 0);
+ intern->expr_eval_ctx = NULL;
+ intern->expr_err = NULL;
+ intern->expr_vary_this = NULL;
+
+ ctx->if_nesting_level = 0;
+ intern->re = NULL;
+
+ ctx->error_str = conf->default_error_msg ? conf->default_error_msg :
+ DEFAULT_ERROR_MSG;
+ ctx->time_str = conf->default_time_fmt ? conf->default_time_fmt :
+ DEFAULT_TIME_FORMAT;
+ intern->start_seq = sconf->default_start_tag;
+ intern->start_seq_pat = bndm_compile(ctx->pool, intern->start_seq,
+ strlen(intern->start_seq));
+ intern->end_seq = sconf->default_end_tag;
+ intern->end_seq_len = strlen(intern->end_seq);
+ intern->undefined_echo = conf->undefined_echo ? conf->undefined_echo :
+ DEFAULT_UNDEFINED_ECHO;
+ intern->undefined_echo_len = strlen(intern->undefined_echo);
+ }
+
+ if ((parent = ap_get_module_config(r->request_config, &include_module))) {
+ /* Kludge --- for nested includes, we want to keep the subprocess
+ * environment of the base document (for compatibility); that means
+ * torquing our own last_modified date as well so that the
+ * LAST_MODIFIED variable gets reset to the proper value if the
+ * nested document resets <!--#config timefmt -->.
+ */
+ r->subprocess_env = r->main->subprocess_env;
+ apr_pool_join(r->main->pool, r->pool);
+ r->finfo.mtime = r->main->finfo.mtime;
+ }
+ else {
+ /* we're not a nested include, so we create an initial
+ * environment */
+ ap_add_common_vars(r);
+ ap_add_cgi_vars(r);
+ add_include_vars(r);
+ }
+ /* Always unset the content-length. There is no way to know if
+ * the content will be modified at some point by send_parsed_content.
+ * It is very possible for us to not find any content in the first
+ * 9k of the file, but still have to modify the content of the file.
+ * If we are going to pass the file through send_parsed_content, then
+ * the content-length should just be unset.
+ */
+ apr_table_unset(f->r->headers_out, "Content-Length");
+
+ /* Always unset the Last-Modified field - see RFC2616 - 13.3.4.
+ * We don't know if we are going to be including a file or executing
+ * a program which may change the Last-Modified header or make the
+ * content completely dynamic. Therefore, we can't support these
+ * headers.
+ *
+ * Exception: XBitHack full means we *should* set the
+ * Last-Modified field.
+ *
+ * SSILastModified on means we *should* set the Last-Modified field
+ * if not present, or respect an existing value if present.
+ */
+
+ /* Must we respect the last modified header? By default, no */
+ if (conf->lastmodified > 0) {
+
+ /* update the last modified if we have a valid time, and only if
+ * we don't already have a valid last modified.
+ */
+ if (r->finfo.valid & APR_FINFO_MTIME
+ && !apr_table_get(f->r->headers_out, "Last-Modified")) {
+ ap_update_mtime(r, r->finfo.mtime);
+ ap_set_last_modified(r);
+ }
+
+ }
+
+ /* Assure the platform supports Group protections */
+ else if (((conf->xbithack == XBITHACK_FULL ||
+ (conf->xbithack == XBITHACK_UNSET &&
+ DEFAULT_XBITHACK == XBITHACK_FULL))
+ && (r->finfo.valid & APR_FINFO_GPROT)
+ && (r->finfo.protection & APR_GEXECUTE))) {
+ ap_update_mtime(r, r->finfo.mtime);
+ ap_set_last_modified(r);
+ }
+ else {
+ apr_table_unset(f->r->headers_out, "Last-Modified");
+ }
+
+ /* add QUERY stuff to env cause it ain't yet */
+ if (r->args) {
+ char *arg_copy = apr_pstrdup(r->pool, r->args);
+
+ apr_table_setn(r->subprocess_env, "QUERY_STRING", r->args);
+ ap_unescape_url(arg_copy);
+ apr_table_setn(r->subprocess_env, "QUERY_STRING_UNESCAPED",
+ ap_escape_shell_cmd(r->pool, arg_copy));
+ }
+
+ return send_parsed_content(f, b);
+}
+
+static int include_fixup(request_rec *r)
+{
+ if (r->handler && (strcmp(r->handler, "server-parsed") == 0))
+ {
+ if (!r->content_type || !*r->content_type) {
+ ap_set_content_type(r, "text/html");
+ }
+ r->handler = "default-handler";
+ }
+ else
+#if defined(OS2) || defined(WIN32) || defined(NETWARE)
+ /* These OS's don't support xbithack. This is being worked on. */
+ {
+ return DECLINED;
+ }
+#else
+ {
+ include_dir_config *conf = ap_get_module_config(r->per_dir_config,
+ &include_module);
+
+ if (conf->xbithack == XBITHACK_OFF ||
+ (DEFAULT_XBITHACK == XBITHACK_OFF &&
+ conf->xbithack == XBITHACK_UNSET))
+ {
+ return DECLINED;
+ }
+
+ if (!(r->finfo.protection & APR_UEXECUTE)) {
+ return DECLINED;
+ }
+
+ if (!r->content_type || strncmp(r->content_type, "text/html", 9)) {
+ return DECLINED;
+ }
+ }
+#endif
+
+ /* We always return declined, because the default handler actually
+ * serves the file. All we have to do is add the filter.
+ */
+ ap_add_output_filter("INCLUDES", NULL, r, r->connection);
+ return DECLINED;
+}
+
+
+/*
+ * +-------------------------------------------------------+
+ * | |
+ * | Configuration Handling
+ * | |
+ * +-------------------------------------------------------+
+ */
+
+static void *create_includes_dir_config(apr_pool_t *p, char *dummy)
+{
+ include_dir_config *result = apr_pcalloc(p, sizeof(include_dir_config));
+
+ result->xbithack = XBITHACK_UNSET;
+ result->lastmodified = UNSET;
+ result->etag = UNSET;
+ result->legacy_expr = UNSET;
+
+ return result;
+}
+
+#define MERGE(b,o,n,val,unset) n->val = o->val != unset ? o->val : b->val
+static void *merge_includes_dir_config(apr_pool_t *p, void *basev, void *overridesv)
+{
+ include_dir_config *base = (include_dir_config *)basev,
+ *over = (include_dir_config *)overridesv,
+ *new = apr_palloc(p, sizeof(include_dir_config));
+ MERGE(base, over, new, default_error_msg, NULL);
+ MERGE(base, over, new, default_time_fmt, NULL);
+ MERGE(base, over, new, undefined_echo, NULL);
+ MERGE(base, over, new, xbithack, XBITHACK_UNSET);
+ MERGE(base, over, new, lastmodified, UNSET);
+ MERGE(base, over, new, etag, UNSET);
+ MERGE(base, over, new, legacy_expr, UNSET);
+ return new;
+}
+
+static void *create_includes_server_config(apr_pool_t *p, server_rec *server)
+{
+ include_server_config *result;
+
+ result = apr_palloc(p, sizeof(include_server_config));
+ result->default_end_tag = DEFAULT_END_SEQUENCE;
+ result->default_start_tag = DEFAULT_START_SEQUENCE;
+
+ return result;
+}
+
+static const char *set_xbithack(cmd_parms *cmd, void *mconfig, const char *arg)
+{
+ include_dir_config *conf = mconfig;
+
+ if (!strcasecmp(arg, "off")) {
+ conf->xbithack = XBITHACK_OFF;
+ }
+ else if (!strcasecmp(arg, "on")) {
+ conf->xbithack = XBITHACK_ON;
+ }
+ else if (!strcasecmp(arg, "full")) {
+ conf->xbithack = XBITHACK_FULL;
+ }
+ else {
+ return "XBitHack must be set to Off, On, or Full";
+ }
+
+ return NULL;
+}
+
+static const char *set_default_start_tag(cmd_parms *cmd, void *mconfig,
+ const char *tag)
+{
+ include_server_config *conf;
+ const char *p = tag;
+
+ /* be consistent. (See below in set_default_end_tag) */
+ while (*p) {
+ if (apr_isspace(*p)) {
+ return "SSIStartTag may not contain any whitespaces";
+ }
+ ++p;
+ }
+
+ conf= ap_get_module_config(cmd->server->module_config , &include_module);
+ conf->default_start_tag = tag;
+
+ return NULL;
+}
+
+static const char *set_default_end_tag(cmd_parms *cmd, void *mconfig,
+ const char *tag)
+{
+ include_server_config *conf;
+ const char *p = tag;
+
+ /* sanity check. The parser may fail otherwise */
+ while (*p) {
+ if (apr_isspace(*p)) {
+ return "SSIEndTag may not contain any whitespaces";
+ }
+ ++p;
+ }
+
+ conf= ap_get_module_config(cmd->server->module_config , &include_module);
+ conf->default_end_tag = tag;
+
+ return NULL;
+}
+
+static const char *set_undefined_echo(cmd_parms *cmd, void *mconfig,
+ const char *msg)
+{
+ include_dir_config *conf = mconfig;
+ conf->undefined_echo = msg;
+
+ return NULL;
+}
+
+static const char *set_default_error_msg(cmd_parms *cmd, void *mconfig,
+ const char *msg)
+{
+ include_dir_config *conf = mconfig;
+ conf->default_error_msg = msg;
+
+ return NULL;
+}
+
+static const char *set_default_time_fmt(cmd_parms *cmd, void *mconfig,
+ const char *fmt)
+{
+ include_dir_config *conf = mconfig;
+ conf->default_time_fmt = fmt;
+
+ return NULL;
+}
+
+
+/*
+ * +-------------------------------------------------------+
+ * | |
+ * | Module Initialization and Configuration
+ * | |
+ * +-------------------------------------------------------+
+ */
+
+static int include_post_config(apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ include_handlers = apr_hash_make(p);
+
+ ssi_pfn_register = APR_RETRIEVE_OPTIONAL_FN(ap_register_include_handler);
+
+ if (ssi_pfn_register) {
+ ssi_pfn_register("if", handle_if);
+ ssi_pfn_register("set", handle_set);
+ ssi_pfn_register("else", handle_else);
+ ssi_pfn_register("elif", handle_elif);
+ ssi_pfn_register("echo", handle_echo);
+ ssi_pfn_register("endif", handle_endif);
+ ssi_pfn_register("fsize", handle_fsize);
+ ssi_pfn_register("config", handle_config);
+ ssi_pfn_register("comment", handle_comment);
+ ssi_pfn_register("include", handle_include);
+ ssi_pfn_register("flastmod", handle_flastmod);
+ ssi_pfn_register("printenv", handle_printenv);
+ }
+
+ return OK;
+}
+
+static const command_rec includes_cmds[] =
+{
+ AP_INIT_TAKE1("XBitHack", set_xbithack, NULL, OR_OPTIONS,
+ "Off, On, or Full"),
+ AP_INIT_TAKE1("SSIErrorMsg", set_default_error_msg, NULL, OR_ALL,
+ "a string"),
+ AP_INIT_TAKE1("SSITimeFormat", set_default_time_fmt, NULL, OR_ALL,
+ "a strftime(3) formatted string"),
+ AP_INIT_TAKE1("SSIStartTag", set_default_start_tag, NULL, RSRC_CONF,
+ "SSI Start String Tag"),
+ AP_INIT_TAKE1("SSIEndTag", set_default_end_tag, NULL, RSRC_CONF,
+ "SSI End String Tag"),
+ AP_INIT_TAKE1("SSIUndefinedEcho", set_undefined_echo, NULL, OR_ALL,
+ "String to be displayed if an echoed variable is undefined"),
+ AP_INIT_FLAG("SSILegacyExprParser", ap_set_flag_slot_char,
+ (void *)APR_OFFSETOF(include_dir_config, legacy_expr),
+ OR_LIMIT,
+ "Whether to use the legacy expression parser compatible "
+ "with <= 2.2.x. Limited to 'on' or 'off'"),
+ AP_INIT_FLAG("SSILastModified", ap_set_flag_slot_char,
+ (void *)APR_OFFSETOF(include_dir_config, lastmodified),
+ OR_LIMIT, "Whether to set the last modified header or respect "
+ "an existing header. Limited to 'on' or 'off'"),
+ AP_INIT_FLAG("SSIEtag", ap_set_flag_slot_char,
+ (void *)APR_OFFSETOF(include_dir_config, etag),
+ OR_LIMIT, "Whether to allow the generation of ETags within the server. "
+ "Existing ETags will be preserved. Limited to 'on' or 'off'"),
+ {NULL}
+};
+
+static void ap_register_include_handler(char *tag, include_handler_fn_t *func)
+{
+ apr_hash_set(include_handlers, tag, strlen(tag), (const void *)func);
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ APR_REGISTER_OPTIONAL_FN(ap_ssi_get_tag_and_value);
+ APR_REGISTER_OPTIONAL_FN(ap_ssi_parse_string);
+ APR_REGISTER_OPTIONAL_FN(ap_register_include_handler);
+ ap_hook_post_config(include_post_config, NULL, NULL, APR_HOOK_REALLY_FIRST);
+ ap_hook_fixups(include_fixup, NULL, NULL, APR_HOOK_LAST);
+ ap_register_output_filter("INCLUDES", includes_filter, includes_setup,
+ AP_FTYPE_RESOURCE);
+}
+
+AP_DECLARE_MODULE(include) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_includes_dir_config, /* dir config creater */
+ merge_includes_dir_config, /* dir config merger */
+ create_includes_server_config,/* server config */
+ NULL, /* merge server config */
+ includes_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/filters/mod_include.dep b/modules/filters/mod_include.dep
new file mode 100644
index 0000000..8e4e3e5
--- /dev/null
+++ b/modules/filters/mod_include.dep
@@ -0,0 +1,63 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_include.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_include.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_include.h"\
+
diff --git a/modules/filters/mod_include.dsp b/modules/filters/mod_include.dsp
new file mode 100644
index 0000000..2b60f15
--- /dev/null
+++ b/modules/filters/mod_include.dsp
@@ -0,0 +1,115 @@
+# Microsoft Developer Studio Project File - Name="mod_include" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_include - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_include.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_include.mak" CFG="mod_include - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_include - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_include - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_include - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_include_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_include.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_include.so" /d LONG_NAME="include_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_include.so" /base:@..\..\os\win32\BaseAddr.ref,mod_include.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_include.so" /base:@..\..\os\win32\BaseAddr.ref,mod_include.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_include.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_include - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_include_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_include.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_include.so" /d LONG_NAME="include_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_include.so" /base:@..\..\os\win32\BaseAddr.ref,mod_include.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_include.so" /base:@..\..\os\win32\BaseAddr.ref,mod_include.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_include.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_include - Win32 Release"
+# Name "mod_include - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_include.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\mod_include.h
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/filters/mod_include.exp b/modules/filters/mod_include.exp
new file mode 100644
index 0000000..112e1c4
--- /dev/null
+++ b/modules/filters/mod_include.exp
@@ -0,0 +1 @@
+include_module
diff --git a/modules/filters/mod_include.h b/modules/filters/mod_include.h
new file mode 100644
index 0000000..73714a2
--- /dev/null
+++ b/modules/filters/mod_include.h
@@ -0,0 +1,120 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file mod_include.h
+ * @brief Server Side Include Filter Extension Module for Apache
+ *
+ * @defgroup MOD_INCLUDE mod_include
+ * @ingroup APACHE_MODS
+ * @{
+ */
+
+#ifndef _MOD_INCLUDE_H
+#define _MOD_INCLUDE_H 1
+
+#include "apr_pools.h"
+#include "apr_optional.h"
+
+/*
+ * Constants used for ap_ssi_get_tag_and_value's decode parameter
+ */
+#define SSI_VALUE_DECODED 1
+#define SSI_VALUE_RAW 0
+
+/*
+ * Constants used for ap_ssi_parse_string's leave_name parameter
+ */
+#define SSI_EXPAND_LEAVE_NAME 1
+#define SSI_EXPAND_DROP_NAME 0
+
+/*
+ * This macro creates a bucket which contains an error message and appends it
+ * to the current pass brigade
+ */
+#define SSI_CREATE_ERROR_BUCKET(ctx, f, bb) APR_BRIGADE_INSERT_TAIL((bb), \
+ apr_bucket_pool_create(apr_pstrdup((ctx)->pool, (ctx)->error_str), \
+ strlen((ctx)->error_str), (ctx)->pool, \
+ (f)->c->bucket_alloc))
+
+/*
+ * These constants are used to set or clear flag bits.
+ */
+#define SSI_FLAG_PRINTING (1<<0) /* Printing conditional lines. */
+#define SSI_FLAG_COND_TRUE (1<<1) /* Conditional eval'd to true. */
+#define SSI_FLAG_SIZE_IN_BYTES (1<<2) /* Sizes displayed in bytes. */
+#define SSI_FLAG_NO_EXEC (1<<3) /* No Exec in current context. */
+
+#define SSI_FLAG_SIZE_ABBREV (~(SSI_FLAG_SIZE_IN_BYTES))
+#define SSI_FLAG_CLEAR_PRINT_COND (~((SSI_FLAG_PRINTING) | \
+ (SSI_FLAG_COND_TRUE)))
+#define SSI_FLAG_CLEAR_PRINTING (~(SSI_FLAG_PRINTING))
+
+/*
+ * The public SSI context structure
+ */
+typedef struct {
+ /* permanent pool, use this for creating bucket data */
+ apr_pool_t *pool;
+
+ /* temp pool; will be cleared after the execution of every directive */
+ apr_pool_t *dpool;
+
+ /* See the SSI_FLAG_XXXXX definitions. */
+ int flags;
+
+ /* nesting of *invisible* ifs */
+ int if_nesting_level;
+
+ /* if true, the current buffer will be passed down the filter chain before
+ * continuing with next input bucket and the variable will be reset to
+ * false.
+ */
+ int flush_now;
+
+ /* argument counter (of the current directive) */
+ unsigned argc;
+
+ /* currently configured error string */
+ const char *error_str;
+
+ /* currently configured time format */
+ const char *time_str;
+
+ /* the current request */
+ request_rec *r;
+
+ /* pointer to internal (non-public) data, don't touch */
+ struct ssi_internal_ctx *intern;
+
+} include_ctx_t;
+
+typedef apr_status_t (include_handler_fn_t)(include_ctx_t *, ap_filter_t *,
+ apr_bucket_brigade *);
+
+APR_DECLARE_OPTIONAL_FN(void, ap_ssi_get_tag_and_value,
+ (include_ctx_t *ctx, char **tag, char **tag_val,
+ int dodecode));
+
+APR_DECLARE_OPTIONAL_FN(char*, ap_ssi_parse_string,
+ (include_ctx_t *ctx, const char *in, char *out,
+ apr_size_t length, int leave_name));
+
+APR_DECLARE_OPTIONAL_FN(void, ap_register_include_handler,
+ (char *tag, include_handler_fn_t *func));
+
+#endif /* MOD_INCLUDE */
+/** @} */
diff --git a/modules/filters/mod_include.mak b/modules/filters/mod_include.mak
new file mode 100644
index 0000000..a9c3fed
--- /dev/null
+++ b/modules/filters/mod_include.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_include.dsp
+!IF "$(CFG)" == ""
+CFG=mod_include - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_include - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_include - Win32 Release" && "$(CFG)" != "mod_include - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_include.mak" CFG="mod_include - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_include - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_include - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_include - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_include.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_include.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_include.obj"
+ -@erase "$(INTDIR)\mod_include.res"
+ -@erase "$(INTDIR)\mod_include_src.idb"
+ -@erase "$(INTDIR)\mod_include_src.pdb"
+ -@erase "$(OUTDIR)\mod_include.exp"
+ -@erase "$(OUTDIR)\mod_include.lib"
+ -@erase "$(OUTDIR)\mod_include.pdb"
+ -@erase "$(OUTDIR)\mod_include.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_include_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_include.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_include.so" /d LONG_NAME="include_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_include.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_include.pdb" /debug /out:"$(OUTDIR)\mod_include.so" /implib:"$(OUTDIR)\mod_include.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_include.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_include.obj" \
+ "$(INTDIR)\mod_include.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_include.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_include.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_include.so"
+ if exist .\Release\mod_include.so.manifest mt.exe -manifest .\Release\mod_include.so.manifest -outputresource:.\Release\mod_include.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_include - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_include.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_include.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_include.obj"
+ -@erase "$(INTDIR)\mod_include.res"
+ -@erase "$(INTDIR)\mod_include_src.idb"
+ -@erase "$(INTDIR)\mod_include_src.pdb"
+ -@erase "$(OUTDIR)\mod_include.exp"
+ -@erase "$(OUTDIR)\mod_include.lib"
+ -@erase "$(OUTDIR)\mod_include.pdb"
+ -@erase "$(OUTDIR)\mod_include.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_include_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_include.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_include.so" /d LONG_NAME="include_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_include.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_include.pdb" /debug /out:"$(OUTDIR)\mod_include.so" /implib:"$(OUTDIR)\mod_include.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_include.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_include.obj" \
+ "$(INTDIR)\mod_include.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_include.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_include.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_include.so"
+ if exist .\Debug\mod_include.so.manifest mt.exe -manifest .\Debug\mod_include.so.manifest -outputresource:.\Debug\mod_include.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_include.dep")
+!INCLUDE "mod_include.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_include.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_include - Win32 Release" || "$(CFG)" == "mod_include - Win32 Debug"
+
+!IF "$(CFG)" == "mod_include - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\filters"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_include - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\filters"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_include - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\filters"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_include - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\filters"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_include - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\filters"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_include - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\filters"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\filters"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_include - Win32 Release"
+
+
+"$(INTDIR)\mod_include.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_include.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_include.so" /d LONG_NAME="include_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_include - Win32 Debug"
+
+
+"$(INTDIR)\mod_include.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_include.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_include.so" /d LONG_NAME="include_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_include.c
+
+"$(INTDIR)\mod_include.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/filters/mod_proxy_html.c b/modules/filters/mod_proxy_html.c
new file mode 100644
index 0000000..7783da1
--- /dev/null
+++ b/modules/filters/mod_proxy_html.c
@@ -0,0 +1,1353 @@
+/* Copyright (c) 2003-11, WebThing Ltd
+ * Copyright (c) 2011-, The Apache Software Foundation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* GO_FASTER
+ You can #define GO_FASTER to disable trace logging.
+*/
+
+#ifdef GO_FASTER
+#define VERBOSE(x)
+#define VERBOSEB(x)
+#else
+#define VERBOSE(x) if (verbose) x
+#define VERBOSEB(x) if (verbose) {x}
+#endif
+
+/* libxml2 includes unicode/[...].h files which uses C++ comments */
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic warning "-Wcomment"
+#elif defined(__GNUC__)
+#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic warning "-Wcomment"
+#endif
+#endif
+
+/* libxml2 */
+#include <libxml/HTMLparser.h>
+
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#elif defined(__GNUC__)
+#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
+#pragma GCC diagnostic pop
+#endif
+#endif
+
+#include "http_protocol.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "apr_strings.h"
+#include "apr_hash.h"
+#include "apr_strmatch.h"
+#include "apr_lib.h"
+
+#include "apr_optional.h"
+#include "mod_xml2enc.h"
+#include "http_request.h"
+#include "ap_expr.h"
+
+/* globals set once at startup */
+static ap_rxplus_t *old_expr;
+static ap_regex_t *seek_meta;
+static const apr_strmatch_pattern* seek_content;
+static apr_status_t (*xml2enc_charset)(request_rec*, xmlCharEncoding*, const char**) = NULL;
+static apr_status_t (*xml2enc_filter)(request_rec*, const char*, unsigned int) = NULL;
+
+module AP_MODULE_DECLARE_DATA proxy_html_module;
+
+#define M_HTML 0x01
+#define M_EVENTS 0x02
+#define M_CDATA 0x04
+#define M_REGEX 0x08
+#define M_ATSTART 0x10
+#define M_ATEND 0x20
+#define M_LAST 0x40
+#define M_NOTLAST 0x80
+#define M_INTERPOLATE_TO 0x100
+#define M_INTERPOLATE_FROM 0x200
+
+typedef struct {
+ const char *val;
+} tattr;
+typedef struct {
+ unsigned int start;
+ unsigned int end;
+} meta;
+typedef struct urlmap {
+ struct urlmap *next;
+ unsigned int flags;
+ unsigned int regflags;
+ union {
+ const char *c;
+ ap_regex_t *r;
+ } from;
+ const char *to;
+ ap_expr_info_t *cond;
+} urlmap;
+typedef struct {
+ urlmap *map;
+ const char *doctype;
+ const char *etag;
+ unsigned int flags;
+ int bufsz;
+ apr_hash_t *links;
+ apr_array_header_t *events;
+ const char *charset_out;
+ int extfix;
+ int metafix;
+ int strip_comments;
+ int interp;
+ int enabled;
+} proxy_html_conf;
+typedef struct {
+ ap_filter_t *f;
+ proxy_html_conf *cfg;
+ htmlParserCtxtPtr parser;
+ apr_bucket_brigade *bb;
+ char *buf;
+ size_t offset;
+ size_t avail;
+ const char *encoding;
+ urlmap *map;
+ char rbuf[4];
+ apr_size_t rlen;
+ apr_size_t rmin;
+} saxctxt;
+
+
+#define NORM_LC 0x1
+#define NORM_MSSLASH 0x2
+#define NORM_RESET 0x4
+static htmlSAXHandler sax;
+
+typedef enum { ATTR_IGNORE, ATTR_URI, ATTR_EVENT } rewrite_t;
+
+static const char *const fpi_html =
+ "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01//EN\">\n";
+static const char *const fpi_html_legacy =
+ "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n";
+static const char *const fpi_xhtml =
+ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n";
+static const char *const fpi_xhtml_legacy =
+ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
+static const char *const fpi_html5 = "<!DOCTYPE html>\n";
+static const char *const html_etag = ">";
+static const char *const xhtml_etag = " />";
+/*#define DEFAULT_DOCTYPE fpi_html */
+static const char *const DEFAULT_DOCTYPE = "";
+#define DEFAULT_ETAG html_etag
+
+static void normalise(unsigned int flags, char *str)
+{
+ char *p;
+ if (flags & NORM_LC)
+ for (p = str; *p; ++p)
+ if (isupper(*p))
+ *p = tolower(*p);
+
+ if (flags & NORM_MSSLASH)
+ for (p = ap_strchr(str, '\\'); p; p = ap_strchr(p+1, '\\'))
+ *p = '/';
+
+}
+#define consume_buffer(ctx,inbuf,bytes,flag) \
+ htmlParseChunk(ctx->parser, inbuf, bytes, flag)
+
+#define AP_fwrite(ctx,inbuf,bytes,flush) \
+ ap_fwrite(ctx->f->next, ctx->bb, inbuf, bytes);
+
+/* This is always utf-8 on entry. We can convert charset within FLUSH */
+#define FLUSH AP_fwrite(ctx, (chars+begin), (i-begin), 0); begin = i+1
+static void pcharacters(void *ctxt, const xmlChar *uchars, int length)
+{
+ const char *chars = (const char*) uchars;
+ saxctxt *ctx = (saxctxt*) ctxt;
+ int i;
+ int begin;
+ for (begin=i=0; i<length; i++) {
+ switch (chars[i]) {
+ case '&' : FLUSH; ap_fputs(ctx->f->next, ctx->bb, "&amp;"); break;
+ case '<' : FLUSH; ap_fputs(ctx->f->next, ctx->bb, "&lt;"); break;
+ case '>' : FLUSH; ap_fputs(ctx->f->next, ctx->bb, "&gt;"); break;
+ case '"' : FLUSH; ap_fputs(ctx->f->next, ctx->bb, "&quot;"); break;
+ default : break;
+ }
+ }
+ FLUSH;
+}
+
+static void preserve(saxctxt *ctx, const size_t len)
+{
+ char *newbuf;
+ if (len <= (ctx->avail - ctx->offset))
+ return;
+ else while (len > (ctx->avail - ctx->offset))
+ ctx->avail += ctx->cfg->bufsz;
+
+ newbuf = realloc(ctx->buf, ctx->avail);
+ if (newbuf != ctx->buf) {
+ if (ctx->buf)
+ apr_pool_cleanup_kill(ctx->f->r->pool, ctx->buf,
+ (int(*)(void*))free);
+ apr_pool_cleanup_register(ctx->f->r->pool, newbuf,
+ (int(*)(void*))free, apr_pool_cleanup_null);
+ ctx->buf = newbuf;
+ }
+}
+
+static void pappend(saxctxt *ctx, const char *buf, const size_t len)
+{
+ preserve(ctx, len);
+ memcpy(ctx->buf+ctx->offset, buf, len);
+ ctx->offset += len;
+}
+
+static void dump_content(saxctxt *ctx)
+{
+ urlmap *m;
+ char *found;
+ size_t s_from, s_to;
+ size_t match;
+ char c = 0;
+ int nmatch;
+ ap_regmatch_t pmatch[10];
+ char *subs;
+ size_t len, offs;
+ urlmap *themap = ctx->map;
+#ifndef GO_FASTER
+ int verbose = APLOGrtrace1(ctx->f->r);
+#endif
+
+ pappend(ctx, &c, 1); /* append null byte */
+ /* parse the text for URLs */
+ for (m = themap; m; m = m->next) {
+ if (!(m->flags & M_CDATA))
+ continue;
+ if (m->flags & M_REGEX) {
+ nmatch = 10;
+ offs = 0;
+ while (!ap_regexec(m->from.r, ctx->buf+offs, nmatch, pmatch, 0)) {
+ match = pmatch[0].rm_so;
+ s_from = pmatch[0].rm_eo - match;
+ subs = ap_pregsub(ctx->f->r->pool, m->to, ctx->buf+offs,
+ nmatch, pmatch);
+ s_to = strlen(subs);
+ len = strlen(ctx->buf);
+ offs += match;
+ VERBOSEB(
+ const char *f = apr_pstrndup(ctx->f->r->pool,
+ ctx->buf + offs, s_from);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, ctx->f->r,
+ "C/RX: match at %s, substituting %s", f, subs);
+ )
+ if (s_to > s_from) {
+ preserve(ctx, s_to - s_from);
+ memmove(ctx->buf+offs+s_to, ctx->buf+offs+s_from,
+ len + 1 - s_from - offs);
+ memcpy(ctx->buf+offs, subs, s_to);
+ }
+ else {
+ memcpy(ctx->buf + offs, subs, s_to);
+ memmove(ctx->buf+offs+s_to, ctx->buf+offs+s_from,
+ len + 1 - s_from - offs);
+ }
+ offs += s_to;
+ }
+ }
+ else {
+ s_from = strlen(m->from.c);
+ s_to = strlen(m->to);
+ for (found = strstr(ctx->buf, m->from.c); found;
+ found = strstr(ctx->buf+match+s_to, m->from.c)) {
+ match = found - ctx->buf;
+ if ((m->flags & M_ATSTART) && (match != 0))
+ break;
+ len = strlen(ctx->buf);
+ if ((m->flags & M_ATEND) && (match < (len - s_from)))
+ continue;
+ VERBOSE(ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, ctx->f->r,
+ "C: matched %s, substituting %s",
+ m->from.c, m->to));
+ if (s_to > s_from) {
+ preserve(ctx, s_to - s_from);
+ memmove(ctx->buf+match+s_to, ctx->buf+match+s_from,
+ len + 1 - s_from - match);
+ memcpy(ctx->buf+match, m->to, s_to);
+ }
+ else {
+ memcpy(ctx->buf+match, m->to, s_to);
+ memmove(ctx->buf+match+s_to, ctx->buf+match+s_from,
+ len + 1 - s_from - match);
+ }
+ }
+ }
+ }
+ AP_fwrite(ctx, ctx->buf, strlen(ctx->buf), 1);
+}
+static void pcdata(void *ctxt, const xmlChar *uchars, int length)
+{
+ const char *chars = (const char*) uchars;
+ saxctxt *ctx = (saxctxt*) ctxt;
+ if (ctx->cfg->extfix) {
+ pappend(ctx, chars, length);
+ }
+ else {
+ /* not sure if this should force-flush
+ * (i.e. can one cdata section come in multiple calls?)
+ */
+ AP_fwrite(ctx, chars, length, 0);
+ }
+}
+static void pcomment(void *ctxt, const xmlChar *uchars)
+{
+ const char *chars = (const char*) uchars;
+ saxctxt *ctx = (saxctxt*) ctxt;
+ if (ctx->cfg->strip_comments)
+ return;
+
+ if (ctx->cfg->extfix) {
+ pappend(ctx, "<!--", 4);
+ pappend(ctx, chars, strlen(chars));
+ pappend(ctx, "-->", 3);
+ }
+ else {
+ ap_fputs(ctx->f->next, ctx->bb, "<!--");
+ AP_fwrite(ctx, chars, strlen(chars), 1);
+ ap_fputs(ctx->f->next, ctx->bb, "-->");
+ dump_content(ctx);
+ }
+}
+static void pendElement(void *ctxt, const xmlChar *uname)
+{
+ saxctxt *ctx = (saxctxt*) ctxt;
+ const char *name = (const char*) uname;
+ const htmlElemDesc* desc = htmlTagLookup(uname);
+
+ if ((ctx->cfg->doctype == fpi_html) || (ctx->cfg->doctype == fpi_xhtml)) {
+ /* enforce html */
+ if (!desc || desc->depr)
+ return;
+
+ }
+ else if ((ctx->cfg->doctype == fpi_html_legacy)
+ || (ctx->cfg->doctype == fpi_xhtml_legacy)) {
+ /* enforce html legacy */
+ if (!desc)
+ return;
+ }
+ /* TODO - implement HTML "allowed here" using the stack */
+ /* nah. Keeping the stack is too much overhead */
+
+ if (ctx->offset > 0) {
+ dump_content(ctx);
+ ctx->offset = 0; /* having dumped it, we can re-use the memory */
+ }
+ if (!desc || !desc->empty) {
+ ap_fprintf(ctx->f->next, ctx->bb, "</%s>", name);
+ }
+}
+
+static void pstartElement(void *ctxt, const xmlChar *uname,
+ const xmlChar** uattrs)
+{
+ int required_attrs;
+ int num_match;
+ size_t offs, len;
+ char *subs;
+ rewrite_t is_uri;
+ const char** a;
+ urlmap *m;
+ size_t s_to, s_from, match;
+ char *found;
+ saxctxt *ctx = (saxctxt*) ctxt;
+ size_t nmatch;
+ ap_regmatch_t pmatch[10];
+#ifndef GO_FASTER
+ int verbose = APLOGrtrace1(ctx->f->r);
+#endif
+ apr_array_header_t *linkattrs;
+ int i;
+ const char *name = (const char*) uname;
+ const char** attrs = (const char**) uattrs;
+ const htmlElemDesc* desc = htmlTagLookup(uname);
+ urlmap *themap = ctx->map;
+#ifdef HAVE_STACK
+ const void** descp;
+#endif
+ int enforce = 0;
+ if ((ctx->cfg->doctype == fpi_html) || (ctx->cfg->doctype == fpi_xhtml)) {
+ /* enforce html */
+ if (!desc || desc->depr) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->f->r, APLOGNO(01416)
+ "Bogus HTML element %s dropped", name);
+ return;
+ }
+ enforce = 2;
+ }
+ else if ((ctx->cfg->doctype == fpi_html_legacy)
+ || (ctx->cfg->doctype == fpi_xhtml_legacy)) {
+ /* enforce html legacy */
+ if (!desc) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->f->r, APLOGNO(01417)
+ "Deprecated HTML element %s dropped", name);
+ return;
+ }
+ enforce = 1;
+ }
+#ifdef HAVE_STACK
+ descp = apr_array_push(ctx->stack);
+ *descp = desc;
+ /* TODO - implement HTML "allowed here" */
+#endif
+
+ ap_fputc(ctx->f->next, ctx->bb, '<');
+ ap_fputs(ctx->f->next, ctx->bb, name);
+
+ required_attrs = 0;
+ if ((enforce > 0) && (desc != NULL) && (desc->attrs_req != NULL))
+ for (a = desc->attrs_req; *a; a++)
+ ++required_attrs;
+
+ if (attrs) {
+ linkattrs = apr_hash_get(ctx->cfg->links, name, APR_HASH_KEY_STRING);
+ for (a = attrs; *a; a += 2) {
+ if (desc && enforce > 0) {
+ switch (htmlAttrAllowed(desc, (xmlChar*)*a, 2-enforce)) {
+ case HTML_INVALID:
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->f->r, APLOGNO(01418)
+ "Bogus HTML attribute %s of %s dropped",
+ *a, name);
+ continue;
+ case HTML_DEPRECATED:
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->f->r, APLOGNO(01419)
+ "Deprecated HTML attribute %s of %s dropped",
+ *a, name);
+ continue;
+ case HTML_REQUIRED:
+ required_attrs--; /* cross off the number still needed */
+ /* fallthrough - required implies valid */
+ default:
+ break;
+ }
+ }
+ ctx->offset = 0;
+ if (a[1]) {
+ pappend(ctx, a[1], strlen(a[1])+1);
+ is_uri = ATTR_IGNORE;
+ if (linkattrs) {
+ tattr *attrs = (tattr*) linkattrs->elts;
+ for (i=0; i < linkattrs->nelts; ++i) {
+ if (!strcmp(*a, attrs[i].val)) {
+ is_uri = ATTR_URI;
+ break;
+ }
+ }
+ }
+ if ((is_uri == ATTR_IGNORE) && ctx->cfg->extfix
+ && (ctx->cfg->events != NULL)) {
+ for (i=0; i < ctx->cfg->events->nelts; ++i) {
+ tattr *attrs = (tattr*) ctx->cfg->events->elts;
+ if (!strcmp(*a, attrs[i].val)) {
+ is_uri = ATTR_EVENT;
+ break;
+ }
+ }
+ }
+ switch (is_uri) {
+ case ATTR_URI:
+ num_match = 0;
+ for (m = themap; m; m = m->next) {
+ if (!(m->flags & M_HTML))
+ continue;
+ if (m->flags & M_REGEX) {
+ nmatch = 10;
+ if (!ap_regexec(m->from.r, ctx->buf, nmatch,
+ pmatch, 0)) {
+ ++num_match;
+ offs = match = pmatch[0].rm_so;
+ s_from = pmatch[0].rm_eo - match;
+ subs = ap_pregsub(ctx->f->r->pool, m->to,
+ ctx->buf, nmatch, pmatch);
+ VERBOSE({
+ const char *f;
+ f = apr_pstrndup(ctx->f->r->pool,
+ ctx->buf + offs, s_from);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0,
+ ctx->f->r,
+ "H/RX: match at %s, substituting %s",
+ f, subs);
+ })
+ s_to = strlen(subs);
+ len = strlen(ctx->buf);
+ if (s_to > s_from) {
+ preserve(ctx, s_to - s_from);
+ memmove(ctx->buf+offs+s_to,
+ ctx->buf+offs+s_from,
+ len + 1 - s_from - offs);
+ memcpy(ctx->buf+offs, subs, s_to);
+ }
+ else {
+ memcpy(ctx->buf + offs, subs, s_to);
+ memmove(ctx->buf+offs+s_to,
+ ctx->buf+offs+s_from,
+ len + 1 - s_from - offs);
+ }
+ }
+ } else {
+ s_from = strlen(m->from.c);
+ if (!strncasecmp(ctx->buf, m->from.c, s_from)) {
+ ++num_match;
+ s_to = strlen(m->to);
+ len = strlen(ctx->buf);
+ VERBOSE(ap_log_rerror(APLOG_MARK, APLOG_TRACE3,
+ 0, ctx->f->r,
+ "H: matched %s, substituting %s",
+ m->from.c, m->to));
+ if (s_to > s_from) {
+ preserve(ctx, s_to - s_from);
+ memmove(ctx->buf+s_to, ctx->buf+s_from,
+ len + 1 - s_from);
+ memcpy(ctx->buf, m->to, s_to);
+ }
+ else { /* it fits in the existing space */
+ memcpy(ctx->buf, m->to, s_to);
+ memmove(ctx->buf+s_to, ctx->buf+s_from,
+ len + 1 - s_from);
+ }
+ break;
+ }
+ }
+ /* URIs only want one match unless overridden in the config */
+ if ((num_match > 0) && !(m->flags & M_NOTLAST))
+ break;
+ }
+ break;
+ case ATTR_EVENT:
+ for (m = themap; m; m = m->next) {
+ num_match = 0; /* reset here since we're working per-rule */
+ if (!(m->flags & M_EVENTS))
+ continue;
+ if (m->flags & M_REGEX) {
+ nmatch = 10;
+ offs = 0;
+ while (!ap_regexec(m->from.r, ctx->buf+offs,
+ nmatch, pmatch, 0)) {
+ match = pmatch[0].rm_so;
+ s_from = pmatch[0].rm_eo - match;
+ subs = ap_pregsub(ctx->f->r->pool, m->to, ctx->buf+offs,
+ nmatch, pmatch);
+ VERBOSE({
+ const char *f;
+ f = apr_pstrndup(ctx->f->r->pool,
+ ctx->buf + offs, s_from);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0,
+ ctx->f->r,
+ "E/RX: match at %s, substituting %s",
+ f, subs);
+ })
+ s_to = strlen(subs);
+ offs += match;
+ len = strlen(ctx->buf);
+ if (s_to > s_from) {
+ preserve(ctx, s_to - s_from);
+ memmove(ctx->buf+offs+s_to,
+ ctx->buf+offs+s_from,
+ len + 1 - s_from - offs);
+ memcpy(ctx->buf+offs, subs, s_to);
+ }
+ else {
+ memcpy(ctx->buf + offs, subs, s_to);
+ memmove(ctx->buf+offs+s_to,
+ ctx->buf+offs+s_from,
+ len + 1 - s_from - offs);
+ }
+ offs += s_to;
+ ++num_match;
+ }
+ }
+ else {
+ found = strstr(ctx->buf, m->from.c);
+ if ((m->flags & M_ATSTART) && (found != ctx->buf))
+ continue;
+ while (found) {
+ s_from = strlen(m->from.c);
+ s_to = strlen(m->to);
+ match = found - ctx->buf;
+ if ((s_from < strlen(found))
+ && (m->flags & M_ATEND)) {
+ found = strstr(ctx->buf+match+s_from,
+ m->from.c);
+ continue;
+ }
+ else {
+ found = strstr(ctx->buf+match+s_to,
+ m->from.c);
+ }
+ VERBOSE(ap_log_rerror(APLOG_MARK, APLOG_TRACE3,
+ 0, ctx->f->r,
+ "E: matched %s, substituting %s",
+ m->from.c, m->to));
+ len = strlen(ctx->buf);
+ if (s_to > s_from) {
+ preserve(ctx, s_to - s_from);
+ memmove(ctx->buf+match+s_to,
+ ctx->buf+match+s_from,
+ len + 1 - s_from - match);
+ memcpy(ctx->buf+match, m->to, s_to);
+ }
+ else {
+ memcpy(ctx->buf+match, m->to, s_to);
+ memmove(ctx->buf+match+s_to,
+ ctx->buf+match+s_from,
+ len + 1 - s_from - match);
+ }
+ ++num_match;
+ }
+ }
+ if (num_match && (m->flags & M_LAST))
+ break;
+ }
+ break;
+ case ATTR_IGNORE:
+ break;
+ }
+ }
+ if (!a[1])
+ ap_fputstrs(ctx->f->next, ctx->bb, " ", a[0], NULL);
+ else {
+
+ if (ctx->cfg->flags != 0)
+ normalise(ctx->cfg->flags, ctx->buf);
+
+ /* write the attribute, using pcharacters to html-escape
+ anything that needs it in the value.
+ */
+ ap_fputstrs(ctx->f->next, ctx->bb, " ", a[0], "=\"", NULL);
+ pcharacters(ctx, (const xmlChar*)ctx->buf, strlen(ctx->buf));
+ ap_fputc(ctx->f->next, ctx->bb, '"');
+ }
+ }
+ }
+ ctx->offset = 0;
+ if (desc && desc->empty)
+ ap_fputs(ctx->f->next, ctx->bb, ctx->cfg->etag);
+ else
+ ap_fputc(ctx->f->next, ctx->bb, '>');
+
+ if ((enforce > 0) && (required_attrs > 0)) {
+ /* if there are more required attributes than we found then complain */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->f->r, APLOGNO(01420)
+ "HTML element %s is missing %d required attributes",
+ name, required_attrs);
+ }
+}
+
+static meta *metafix(request_rec *r, const char *buf, apr_size_t len)
+{
+ meta *ret = NULL;
+ size_t offs = 0;
+ const char *p;
+ const char *q;
+ char *header;
+ char *content;
+ ap_regmatch_t pmatch[2];
+ char delim;
+
+ while (offs < len &&
+ !ap_regexec_len(seek_meta, buf + offs, len - offs, 2, pmatch, 0)) {
+ header = NULL;
+ content = NULL;
+ p = buf+offs+pmatch[1].rm_eo;
+ while (!apr_isalpha(*++p));
+ for (q = p; apr_isalnum(*q) || (*q == '-'); ++q);
+ header = apr_pstrmemdup(r->pool, p, q-p);
+ if (ap_cstr_casecmpn(header, "Content-", 8)) {
+ /* find content=... string */
+ p = apr_strmatch(seek_content, buf+offs+pmatch[0].rm_so,
+ pmatch[0].rm_eo - pmatch[0].rm_so);
+ /* if it doesn't contain "content", ignore, don't crash! */
+ if (p != NULL) {
+ while (*p) {
+ p += 7;
+ while (apr_isspace(*p))
+ ++p;
+ /* XXX Should we search for another content= pattern? */
+ if (*p != '=')
+ break;
+ while (*p && apr_isspace(*++p));
+ if ((*p == '\'') || (*p == '"')) {
+ delim = *p++;
+ for (q = p; *q && *q != delim; ++q);
+ /* No terminating delimiter found? Skip the bogus directive */
+ if (*q != delim)
+ break;
+ } else {
+ for (q = p; *q && !apr_isspace(*q) && (*q != '>'); ++q);
+ }
+ content = apr_pstrmemdup(r->pool, p, q-p);
+ break;
+ }
+ }
+ }
+ else if (!ap_cstr_casecmpn(header, "Content-Type", 12)) {
+ ret = apr_palloc(r->pool, sizeof(meta));
+ ret->start = offs+pmatch[0].rm_so;
+ ret->end = offs+pmatch[0].rm_eo;
+ }
+ if (header && content) {
+#ifndef GO_FASTER
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+ "Adding header [%s: %s] from HTML META",
+ header, content);
+#endif
+ apr_table_setn(r->headers_out, header, content);
+ }
+ offs += pmatch[0].rm_eo;
+ }
+ return ret;
+}
+
+static const char *interpolate_vars(request_rec *r, const char *str)
+{
+ const char *start;
+ const char *end;
+ const char *delim;
+ const char *before;
+ const char *after;
+ const char *replacement;
+ const char *var;
+ for (;;) {
+ if ((start = ap_strstr_c(str, "${")) == NULL)
+ break;
+
+ if ((end = ap_strchr_c(start+2, '}')) == NULL)
+ break;
+
+ delim = ap_strchr_c(start+2, '|');
+
+ /* Restrict delim to ${...} */
+ if (delim && delim >= end) {
+ delim = NULL;
+ }
+
+ before = apr_pstrmemdup(r->pool, str, start-str);
+ after = end+1;
+ if (delim) {
+ var = apr_pstrmemdup(r->pool, start+2, delim-start-2);
+ }
+ else {
+ var = apr_pstrmemdup(r->pool, start+2, end-start-2);
+ }
+ replacement = apr_table_get(r->subprocess_env, var);
+ if (!replacement) {
+ if (delim)
+ replacement = apr_pstrmemdup(r->pool, delim+1, end-delim-1);
+ else
+ replacement = "";
+ }
+ str = apr_pstrcat(r->pool, before, replacement, after, NULL);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "Interpolating %s => %s", var, replacement);
+ }
+ return str;
+}
+static void fixup_rules(saxctxt *ctx)
+{
+ urlmap *newp;
+ urlmap *p;
+ urlmap *prev = NULL;
+ request_rec *r = ctx->f->r;
+
+ for (p = ctx->cfg->map; p; p = p->next) {
+ if (p->cond != NULL) {
+ const char *err;
+ int ok = ap_expr_exec(r, p->cond, &err);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01421)
+ "Error evaluating expr: %s", err);
+ }
+ if (ok == 0) {
+ continue; /* condition is unsatisfied */
+ }
+ }
+
+ newp = apr_pmemdup(r->pool, p, sizeof(urlmap));
+
+ if (newp->flags & M_INTERPOLATE_FROM) {
+ newp->from.c = interpolate_vars(r, newp->from.c);
+ if (!newp->from.c || !*newp->from.c)
+ continue; /* don't use empty from-pattern */
+ if (newp->flags & M_REGEX) {
+ newp->from.r = ap_pregcomp(r->pool, newp->from.c,
+ newp->regflags);
+ }
+ }
+ if (newp->flags & M_INTERPOLATE_TO) {
+ newp->to = interpolate_vars(r, newp->to);
+ }
+ /* evaluate p->cond; continue if unsatisfied */
+ /* create new urlmap with memcpy and append to map */
+ /* interpolate from if flagged to do so */
+ /* interpolate to if flagged to do so */
+
+ if (prev != NULL)
+ prev->next = newp;
+ else
+ ctx->map = newp;
+ prev = newp;
+ }
+
+ if (prev)
+ prev->next = NULL;
+}
+
+static saxctxt *check_filter_init (ap_filter_t *f)
+{
+ saxctxt *fctx;
+ if (!f->ctx) {
+ proxy_html_conf *cfg;
+ const char *force;
+ const char *errmsg = NULL;
+ cfg = ap_get_module_config(f->r->per_dir_config, &proxy_html_module);
+ force = apr_table_get(f->r->subprocess_env, "PROXY_HTML_FORCE");
+
+ if (!force) {
+ if (!f->r->proxyreq) {
+ errmsg = "Non-proxy request; not inserting proxy-html filter";
+ }
+ else if (!f->r->content_type) {
+ errmsg = "No content-type; bailing out of proxy-html filter";
+ }
+ else if (ap_cstr_casecmpn(f->r->content_type, "text/html", 9) &&
+ ap_cstr_casecmpn(f->r->content_type,
+ "application/xhtml+xml", 21)) {
+ errmsg = "Non-HTML content; not inserting proxy-html filter";
+ }
+ }
+ if (!cfg->links) {
+ errmsg = "No links configured: nothing for proxy-html filter to do";
+ }
+
+ if (errmsg) {
+#ifndef GO_FASTER
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, f->r, "%s", errmsg);
+#endif
+ ap_remove_output_filter(f);
+ return NULL;
+ }
+
+ fctx = f->ctx = apr_pcalloc(f->r->pool, sizeof(saxctxt));
+ fctx->f = f;
+ fctx->bb = apr_brigade_create(f->r->pool,
+ f->r->connection->bucket_alloc);
+ fctx->cfg = cfg;
+ apr_table_unset(f->r->headers_out, "Content-Length");
+
+ if (cfg->interp)
+ fixup_rules(fctx);
+ else
+ fctx->map = cfg->map;
+ /* defer dealing with charset_out until after sniffing charset_in
+ * so we can support setting one to t'other.
+ */
+ }
+ return f->ctx;
+}
+
+static void prepend_rbuf(saxctxt *ctxt, apr_bucket_brigade *bb)
+{
+ if (ctxt->rlen) {
+ apr_bucket *b = apr_bucket_transient_create(ctxt->rbuf,
+ ctxt->rlen,
+ bb->bucket_alloc);
+ APR_BRIGADE_INSERT_HEAD(bb, b);
+ ctxt->rlen = 0;
+ }
+}
+
+static apr_status_t proxy_html_filter(ap_filter_t *f, apr_bucket_brigade *bb)
+{
+ apr_bucket* b;
+ meta *m = NULL;
+ xmlCharEncoding enc;
+ const char *buf = 0;
+ apr_size_t bytes = 0;
+#ifndef USE_OLD_LIBXML2
+ int xmlopts = XML_PARSE_RECOVER | XML_PARSE_NONET |
+ XML_PARSE_NOBLANKS | XML_PARSE_NOERROR | XML_PARSE_NOWARNING;
+#endif
+
+ saxctxt *ctxt = check_filter_init(f);
+ if (!ctxt)
+ return ap_pass_brigade(f->next, bb);
+ for (b = APR_BRIGADE_FIRST(bb);
+ b != APR_BRIGADE_SENTINEL(bb);
+ b = APR_BUCKET_NEXT(b)) {
+ if (APR_BUCKET_IS_METADATA(b)) {
+ if (APR_BUCKET_IS_EOS(b)) {
+ if (ctxt->parser != NULL) {
+ consume_buffer(ctxt, "", 0, 1);
+ }
+ else {
+ prepend_rbuf(ctxt, ctxt->bb);
+ }
+ APR_BRIGADE_INSERT_TAIL(ctxt->bb,
+ apr_bucket_eos_create(ctxt->bb->bucket_alloc));
+ ap_pass_brigade(ctxt->f->next, ctxt->bb);
+ apr_brigade_cleanup(ctxt->bb);
+ }
+ else if (APR_BUCKET_IS_FLUSH(b)) {
+ /* pass on flush, except at start where it would cause
+ * headers to be sent before doc sniffing
+ */
+ if (ctxt->parser != NULL) {
+ ap_fflush(ctxt->f->next, ctxt->bb);
+ }
+ }
+ }
+ else if (apr_bucket_read(b, &buf, &bytes, APR_BLOCK_READ)
+ == APR_SUCCESS) {
+ if (ctxt->parser == NULL) {
+ const char *cenc;
+
+ /* For documents smaller than four bytes, there is no reason to do
+ * HTML rewriting. The URL schema (i.e. 'http') needs four bytes alone.
+ * And the HTML parser needs at least four bytes to initialise correctly.
+ */
+ ctxt->rmin += bytes;
+ if (ctxt->rmin < sizeof(ctxt->rbuf)) {
+ memcpy(ctxt->rbuf + ctxt->rlen, buf, bytes);
+ ctxt->rlen += bytes;
+ continue;
+ }
+ if (ctxt->rlen && ctxt->rlen < sizeof(ctxt->rbuf)) {
+ apr_size_t rem = sizeof(ctxt->rbuf) - ctxt->rlen;
+ memcpy(ctxt->rbuf + ctxt->rlen, buf, rem);
+ ctxt->rlen += rem;
+ buf += rem;
+ bytes -= rem;
+ }
+
+ if (!xml2enc_charset ||
+ (xml2enc_charset(f->r, &enc, &cenc) != APR_SUCCESS)) {
+ if (!xml2enc_charset)
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, f->r, APLOGNO(01422)
+ "No i18n support found. Install mod_xml2enc if required");
+ enc = XML_CHAR_ENCODING_NONE;
+ ap_set_content_type(f->r, "text/html;charset=utf-8");
+ }
+ else {
+ /* if we wanted a non-default charset_out, insert the
+ * xml2enc filter now that we've sniffed it
+ */
+ if (ctxt->cfg->charset_out && xml2enc_filter) {
+ if (*ctxt->cfg->charset_out != '*')
+ cenc = ctxt->cfg->charset_out;
+ xml2enc_filter(f->r, cenc, ENCIO_OUTPUT);
+ ap_set_content_type(f->r,
+ apr_pstrcat(f->r->pool,
+ "text/html;charset=",
+ cenc, NULL));
+ }
+ else /* Normal case, everything worked, utf-8 output */
+ ap_set_content_type(f->r, "text/html;charset=utf-8");
+ }
+
+ ap_fputs(f->next, ctxt->bb, ctxt->cfg->doctype);
+
+ if (ctxt->rlen) {
+ ctxt->parser = htmlCreatePushParserCtxt(&sax, ctxt,
+ ctxt->rbuf,
+ ctxt->rlen,
+ NULL, enc);
+ }
+ else {
+ ctxt->parser = htmlCreatePushParserCtxt(&sax, ctxt, buf, 4,
+ NULL, enc);
+ buf += 4;
+ bytes -= 4;
+ }
+ if (ctxt->parser == NULL) {
+ prepend_rbuf(ctxt, bb);
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+ ctxt->rlen = 0;
+ apr_pool_cleanup_register(f->r->pool, ctxt->parser,
+ (int(*)(void*))htmlFreeParserCtxt,
+ apr_pool_cleanup_null);
+#ifndef USE_OLD_LIBXML2
+ if (xmlopts = xmlCtxtUseOptions(ctxt->parser, xmlopts), xmlopts)
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, f->r, APLOGNO(01423)
+ "Unsupported parser opts %x", xmlopts);
+#endif
+ if (ctxt->cfg->metafix)
+ m = metafix(f->r, buf, bytes);
+ if (m) {
+ consume_buffer(ctxt, buf, m->start, 0);
+ consume_buffer(ctxt, buf+m->end, bytes-m->end, 0);
+ }
+ else {
+ consume_buffer(ctxt, buf, bytes, 0);
+ }
+ }
+ else {
+ consume_buffer(ctxt, buf, bytes, 0);
+ }
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, APLOGNO(01424)
+ "Error in bucket read");
+ }
+ }
+ /*ap_fflush(ctxt->f->next, ctxt->bb); // uncomment for debug */
+ apr_brigade_cleanup(bb);
+ return APR_SUCCESS;
+}
+
+static void *proxy_html_config(apr_pool_t *pool, char *x)
+{
+ proxy_html_conf *ret = apr_pcalloc(pool, sizeof(proxy_html_conf));
+ ret->doctype = DEFAULT_DOCTYPE;
+ ret->etag = DEFAULT_ETAG;
+ ret->bufsz = 8192;
+ /* ret->interp = 1; */
+ /* don't initialise links and events until they get set/used */
+ return ret;
+}
+
+static void *proxy_html_merge(apr_pool_t *pool, void *BASE, void *ADD)
+{
+ proxy_html_conf *base = (proxy_html_conf *) BASE;
+ proxy_html_conf *add = (proxy_html_conf *) ADD;
+ proxy_html_conf *conf = apr_palloc(pool, sizeof(proxy_html_conf));
+
+ /* don't merge declarations - just use the most specific */
+ conf->links = (add->links == NULL) ? base->links : add->links;
+ conf->events = (add->events == NULL) ? base->events : add->events;
+
+ conf->charset_out = (add->charset_out == NULL)
+ ? base->charset_out : add->charset_out;
+
+ if (add->map && base->map) {
+ urlmap *a;
+ conf->map = NULL;
+ for (a = base->map; a; a = a->next) {
+ urlmap *save = conf->map;
+ conf->map = apr_pmemdup(pool, a, sizeof(urlmap));
+ conf->map->next = save;
+ }
+ for (a = add->map; a; a = a->next) {
+ urlmap *save = conf->map;
+ conf->map = apr_pmemdup(pool, a, sizeof(urlmap));
+ conf->map->next = save;
+ }
+ }
+ else
+ conf->map = add->map ? add->map : base->map;
+
+ conf->doctype = (add->doctype == DEFAULT_DOCTYPE)
+ ? base->doctype : add->doctype;
+ conf->etag = (add->etag == DEFAULT_ETAG) ? base->etag : add->etag;
+ conf->bufsz = add->bufsz;
+ if (add->flags & NORM_RESET) {
+ conf->flags = add->flags ^ NORM_RESET;
+ conf->metafix = add->metafix;
+ conf->extfix = add->extfix;
+ conf->interp = add->interp;
+ conf->strip_comments = add->strip_comments;
+ conf->enabled = add->enabled;
+ }
+ else {
+ conf->flags = base->flags | add->flags;
+ conf->metafix = base->metafix | add->metafix;
+ conf->extfix = base->extfix | add->extfix;
+ conf->interp = base->interp | add->interp;
+ conf->strip_comments = base->strip_comments | add->strip_comments;
+ conf->enabled = add->enabled | base->enabled;
+ }
+ return conf;
+}
+#define REGFLAG(n,s,c) ((s&&(ap_strchr_c((s),(c))!=NULL)) ? (n) : 0)
+#define XREGFLAG(n,s,c) ((!s||(ap_strchr_c((s),(c))==NULL)) ? (n) : 0)
+static const char *comp_urlmap(cmd_parms *cmd, urlmap *newmap,
+ const char *from, const char *to,
+ const char *flags, const char *cond)
+{
+ const char *err = NULL;
+ newmap->flags
+ = XREGFLAG(M_HTML,flags,'h')
+ | XREGFLAG(M_EVENTS,flags,'e')
+ | XREGFLAG(M_CDATA,flags,'c')
+ | REGFLAG(M_ATSTART,flags,'^')
+ | REGFLAG(M_ATEND,flags,'$')
+ | REGFLAG(M_REGEX,flags,'R')
+ | REGFLAG(M_LAST,flags,'L')
+ | REGFLAG(M_NOTLAST,flags,'l')
+ | REGFLAG(M_INTERPOLATE_TO,flags,'V')
+ | REGFLAG(M_INTERPOLATE_FROM,flags,'v');
+
+ if ((newmap->flags & M_INTERPOLATE_FROM) || !(newmap->flags & M_REGEX)) {
+ newmap->from.c = from;
+ newmap->to = to;
+ }
+ else {
+ newmap->regflags
+ = REGFLAG(AP_REG_EXTENDED,flags,'x')
+ | REGFLAG(AP_REG_ICASE,flags,'i')
+ | REGFLAG(AP_REG_NOSUB,flags,'n')
+ | REGFLAG(AP_REG_NEWLINE,flags,'s');
+ newmap->from.r = ap_pregcomp(cmd->pool, from, newmap->regflags);
+ newmap->to = to;
+ }
+ if (cond != NULL) {
+ /* back-compatibility: support old-style ENV expressions
+ * by converting to ap_expr syntax.
+ *
+ * 1. var --> env(var)
+ * 2. var=val --> env(var)=val
+ * 3. !var --> !env(var)
+ * 4. !var=val --> env(var)!=val
+ */
+ char *newcond = NULL;
+ if (ap_rxplus_exec(cmd->temp_pool, old_expr, cond, &newcond)) {
+ /* we got a substitution. Check for the case (3) above
+ * that the regexp gets wrong: a negation without a comparison.
+ */
+ if ((cond[0] == '!') && !ap_strchr_c(cond, '=')) {
+ memmove(newcond+1, newcond, strlen(newcond)-1);
+ newcond[0] = '!';
+ }
+ cond = newcond;
+ }
+ newmap->cond = ap_expr_parse_cmd(cmd, cond, 0, &err, NULL);
+ }
+ else {
+ newmap->cond = NULL;
+ }
+ return err;
+}
+
+static const char *set_urlmap(cmd_parms *cmd, void *CFG, const char *args)
+{
+ proxy_html_conf *cfg = (proxy_html_conf *)CFG;
+ urlmap *map;
+ apr_pool_t *pool = cmd->pool;
+ urlmap *newmap;
+ const char *usage =
+ "Usage: ProxyHTMLURLMap from-pattern to-pattern [flags] [cond]";
+ const char *from;
+ const char *to;
+ const char *flags;
+ const char *cond = NULL;
+
+ if (from = ap_getword_conf(cmd->pool, &args), !from)
+ return usage;
+ if (to = ap_getword_conf(cmd->pool, &args), !to)
+ return usage;
+ flags = ap_getword_conf(cmd->pool, &args);
+ if (flags && *flags)
+ cond = ap_getword_conf(cmd->pool, &args);
+ if (cond && !*cond)
+ cond = NULL;
+
+ /* the args look OK, so let's use them */
+ newmap = apr_palloc(pool, sizeof(urlmap));
+ newmap->next = NULL;
+ if (cfg->map) {
+ for (map = cfg->map; map->next; map = map->next);
+ map->next = newmap;
+ }
+ else
+ cfg->map = newmap;
+
+ return comp_urlmap(cmd, newmap, from, to, flags, cond);
+}
+
+static const char *set_doctype(cmd_parms *cmd, void *CFG,
+ const char *t, const char *l)
+{
+ proxy_html_conf *cfg = (proxy_html_conf *)CFG;
+ if (!strcasecmp(t, "xhtml")) {
+ cfg->etag = xhtml_etag;
+ if (l && !strcasecmp(l, "legacy"))
+ cfg->doctype = fpi_xhtml_legacy;
+ else
+ cfg->doctype = fpi_xhtml;
+ }
+ else if (!strcasecmp(t, "html")) {
+ cfg->etag = html_etag;
+ if (l && !strcasecmp(l, "legacy"))
+ cfg->doctype = fpi_html_legacy;
+ else
+ cfg->doctype = fpi_html;
+ }
+ else if (!strcasecmp(t, "html5")) {
+ cfg->etag = html_etag;
+ cfg->doctype = fpi_html5;
+ }
+ else {
+ cfg->doctype = t;
+ if (l && ((l[0] == 'x') || (l[0] == 'X')))
+ cfg->etag = xhtml_etag;
+ else
+ cfg->etag = html_etag;
+ }
+ return NULL;
+}
+
+static const char *set_flags(cmd_parms *cmd, void *CFG, const char *arg)
+{
+ proxy_html_conf *cfg = CFG;
+ if (arg && *arg) {
+ if (!strcasecmp(arg, "lowercase"))
+ cfg->flags |= NORM_LC;
+ else if (!strcasecmp(arg, "dospath"))
+ cfg->flags |= NORM_MSSLASH;
+ else if (!strcasecmp(arg, "reset"))
+ cfg->flags |= NORM_RESET;
+ }
+ return NULL;
+}
+
+static const char *set_events(cmd_parms *cmd, void *CFG, const char *arg)
+{
+ tattr *attr;
+ proxy_html_conf *cfg = CFG;
+ if (cfg->events == NULL)
+ cfg->events = apr_array_make(cmd->pool, 20, sizeof(tattr));
+ attr = apr_array_push(cfg->events);
+ attr->val = arg;
+ return NULL;
+}
+
+static const char *set_links(cmd_parms *cmd, void *CFG,
+ const char *elt, const char *att)
+{
+ apr_array_header_t *attrs;
+ tattr *attr;
+ proxy_html_conf *cfg = CFG;
+
+ if (cfg->links == NULL)
+ cfg->links = apr_hash_make(cmd->pool);
+
+ attrs = apr_hash_get(cfg->links, elt, APR_HASH_KEY_STRING);
+ if (!attrs) {
+ attrs = apr_array_make(cmd->pool, 2, sizeof(tattr*));
+ apr_hash_set(cfg->links, elt, APR_HASH_KEY_STRING, attrs);
+ }
+ attr = apr_array_push(attrs);
+ attr->val = att;
+ return NULL;
+}
+static const command_rec proxy_html_cmds[] = {
+ AP_INIT_ITERATE("ProxyHTMLEvents", set_events, NULL,
+ RSRC_CONF|ACCESS_CONF,
+ "Strings to be treated as scripting events"),
+ AP_INIT_ITERATE2("ProxyHTMLLinks", set_links, NULL,
+ RSRC_CONF|ACCESS_CONF, "Declare HTML Attributes"),
+ AP_INIT_RAW_ARGS("ProxyHTMLURLMap", set_urlmap, NULL,
+ RSRC_CONF|ACCESS_CONF, "Map URL From To"),
+ AP_INIT_TAKE12("ProxyHTMLDoctype", set_doctype, NULL,
+ RSRC_CONF|ACCESS_CONF, "(HTML|XHTML) [Legacy]"),
+ AP_INIT_ITERATE("ProxyHTMLFixups", set_flags, NULL,
+ RSRC_CONF|ACCESS_CONF, "Options are lowercase, dospath"),
+ AP_INIT_FLAG("ProxyHTMLMeta", ap_set_flag_slot,
+ (void*)APR_OFFSETOF(proxy_html_conf, metafix),
+ RSRC_CONF|ACCESS_CONF, "Fix META http-equiv elements"),
+ AP_INIT_FLAG("ProxyHTMLInterp", ap_set_flag_slot,
+ (void*)APR_OFFSETOF(proxy_html_conf, interp),
+ RSRC_CONF|ACCESS_CONF,
+ "Support interpolation and conditions in URLMaps"),
+ AP_INIT_FLAG("ProxyHTMLExtended", ap_set_flag_slot,
+ (void*)APR_OFFSETOF(proxy_html_conf, extfix),
+ RSRC_CONF|ACCESS_CONF, "Map URLs in Javascript and CSS"),
+ AP_INIT_FLAG("ProxyHTMLStripComments", ap_set_flag_slot,
+ (void*)APR_OFFSETOF(proxy_html_conf, strip_comments),
+ RSRC_CONF|ACCESS_CONF, "Strip out comments"),
+ AP_INIT_TAKE1("ProxyHTMLBufSize", ap_set_int_slot,
+ (void*)APR_OFFSETOF(proxy_html_conf, bufsz),
+ RSRC_CONF|ACCESS_CONF, "Buffer size"),
+ AP_INIT_TAKE1("ProxyHTMLCharsetOut", ap_set_string_slot,
+ (void*)APR_OFFSETOF(proxy_html_conf, charset_out),
+ RSRC_CONF|ACCESS_CONF, "Usage: ProxyHTMLCharsetOut charset"),
+ AP_INIT_FLAG("ProxyHTMLEnable", ap_set_flag_slot,
+ (void*)APR_OFFSETOF(proxy_html_conf, enabled),
+ RSRC_CONF|ACCESS_CONF,
+ "Enable proxy-html and xml2enc filters"),
+ { NULL }
+};
+static int mod_proxy_html(apr_pool_t *p, apr_pool_t *p1, apr_pool_t *p2)
+{
+ seek_meta = ap_pregcomp(p, "<meta[^>]*(http-equiv)[^>]*>",
+ AP_REG_EXTENDED|AP_REG_ICASE);
+ seek_content = apr_strmatch_precompile(p, "content", 0);
+ memset(&sax, 0, sizeof(htmlSAXHandler));
+ sax.startElement = pstartElement;
+ sax.endElement = pendElement;
+ sax.characters = pcharacters;
+ sax.comment = pcomment;
+ sax.cdataBlock = pcdata;
+ xml2enc_charset = APR_RETRIEVE_OPTIONAL_FN(xml2enc_charset);
+ xml2enc_filter = APR_RETRIEVE_OPTIONAL_FN(xml2enc_filter);
+ if (!xml2enc_charset) {
+ ap_log_perror(APLOG_MARK, APLOG_NOTICE, 0, p2, APLOGNO(01425)
+ "I18n support in mod_proxy_html requires mod_xml2enc. "
+ "Without it, non-ASCII characters in proxied pages are "
+ "likely to display incorrectly.");
+ }
+
+ /* old_expr only needs to last the life of the config phase */
+ old_expr = ap_rxplus_compile(p1, "s/^(!)?(\\w+)((=)(.+))?$/reqenv('$2')$1$4'$5'/");
+ return OK;
+}
+static void proxy_html_insert(request_rec *r)
+{
+ proxy_html_conf *cfg;
+ cfg = ap_get_module_config(r->per_dir_config, &proxy_html_module);
+ if (cfg->enabled) {
+ if (xml2enc_filter)
+ xml2enc_filter(r, NULL, ENCIO_INPUT_CHECKS);
+ ap_add_output_filter("proxy-html", NULL, r, r->connection);
+ }
+}
+static void proxy_html_hooks(apr_pool_t *p)
+{
+ static const char *aszSucc[] = { "mod_filter.c", NULL };
+ ap_register_output_filter_protocol("proxy-html", proxy_html_filter,
+ NULL, AP_FTYPE_RESOURCE,
+ AP_FILTER_PROTO_CHANGE|AP_FILTER_PROTO_CHANGE_LENGTH);
+ /* move this to pre_config so old_expr is available to interpret
+ * old-style conditions on URL maps.
+ */
+ ap_hook_pre_config(mod_proxy_html, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_insert_filter(proxy_html_insert, NULL, aszSucc, APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(proxy_html) = {
+ STANDARD20_MODULE_STUFF,
+ proxy_html_config,
+ proxy_html_merge,
+ NULL,
+ NULL,
+ proxy_html_cmds,
+ proxy_html_hooks
+};
diff --git a/modules/filters/mod_proxy_html.dep b/modules/filters/mod_proxy_html.dep
new file mode 100644
index 0000000..1a6ed08
--- /dev/null
+++ b/modules/filters/mod_proxy_html.dep
@@ -0,0 +1,58 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_proxy_html.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_proxy_html.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_strmatch.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_xml2enc.h"\
+
diff --git a/modules/filters/mod_proxy_html.dsp b/modules/filters/mod_proxy_html.dsp
new file mode 100644
index 0000000..1d5d54e
--- /dev/null
+++ b/modules/filters/mod_proxy_html.dsp
@@ -0,0 +1,123 @@
+# Microsoft Developer Studio Project File - Name="mod_proxy_html" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_proxy_html - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_html.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_html.mak" CFG="mod_proxy_html - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy_html - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy_html - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_proxy_html - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/libxml2/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_proxy_html_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG" /i "../../include" /i "../../srclib/apr/include" /I "../../srclib/apr-util/include" /i "../../srclib/libxml2/include" /d BIN_NAME="mod_proxy_html.so" /d LONG_NAME="proxy_html_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_proxy_html.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_html.so
+# ADD LINK32 kernel32.lib libxml2.lib /nologo /subsystem:windows /dll /incremental:no /libpath:"../../srclib/libxml2/win32/bin.msvc" /debug /out:".\Release\mod_proxy_html.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_html.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_proxy_html.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_proxy_html - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/libxml2/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_proxy_html_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG" /i "../../include" /i "../../srclib/apr/include" /I "../../srclib/apr-util/include" /i "../../srclib/libxml2/include" /d BIN_NAME="mod_proxy_html.so" /d LONG_NAME="proxy_html_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_proxy_html.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_html.so
+# ADD LINK32 kernel32.lib libxml2.lib /nologo /subsystem:windows /dll /incremental:no /libpath:"../../srclib/libxml2/win32/bin.msvc" /debug /out:".\Debug\mod_proxy_html.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_html.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_proxy_html.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_proxy_html - Win32 Release"
+# Name "mod_proxy_html - Win32 Debug"
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h"
+# Begin Source File
+
+SOURCE=.\mod_xml2enc.h
+# End Source File
+# End Group
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\mod_proxy_html.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Group
+# End Target
+# End Project
diff --git a/modules/filters/mod_proxy_html.mak b/modules/filters/mod_proxy_html.mak
new file mode 100644
index 0000000..c3579be
--- /dev/null
+++ b/modules/filters/mod_proxy_html.mak
@@ -0,0 +1,352 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_proxy_html.dsp
+!IF "$(CFG)" == ""
+CFG=mod_proxy_html - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_proxy_html - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_proxy_html - Win32 Release" && "$(CFG)" != "mod_proxy_html - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_html.mak" CFG="mod_proxy_html - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy_html - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy_html - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_html - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_proxy_html.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_proxy_html.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\httpd.res"
+ -@erase "$(INTDIR)\mod_proxy_html.obj"
+ -@erase "$(INTDIR)\mod_proxy_html_src.idb"
+ -@erase "$(INTDIR)\mod_proxy_html_src.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_html.exp"
+ -@erase "$(OUTDIR)\mod_proxy_html.lib"
+ -@erase "$(OUTDIR)\mod_proxy_html.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_html.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/libxml2/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_proxy_html_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\httpd.res" /i "../../include" /i "../../srclib/apr/include" /i "../../srclib/apr-util/include" /i "../../srclib/libxml2/include" /d "NDEBUG" /d BIN_NAME="mod_proxy_html.so" /d LONG_NAME="proxy_html_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_proxy_html.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib libxml2.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_proxy_html.pdb" /debug /out:"$(OUTDIR)\mod_proxy_html.so" /implib:"$(OUTDIR)\mod_proxy_html.lib" /libpath:"../../srclib/libxml2/win32/bin.msvc" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_html.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_proxy_html.obj" \
+ "$(INTDIR)\httpd.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_proxy_html.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_proxy_html.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_proxy_html.so"
+ if exist .\Release\mod_proxy_html.so.manifest mt.exe -manifest .\Release\mod_proxy_html.so.manifest -outputresource:.\Release\mod_proxy_html.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_proxy_html - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_proxy_html.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_proxy_html.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\httpd.res"
+ -@erase "$(INTDIR)\mod_proxy_html.obj"
+ -@erase "$(INTDIR)\mod_proxy_html_src.idb"
+ -@erase "$(INTDIR)\mod_proxy_html_src.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_html.exp"
+ -@erase "$(OUTDIR)\mod_proxy_html.lib"
+ -@erase "$(OUTDIR)\mod_proxy_html.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_html.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/libxml2/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_proxy_html_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\httpd.res" /i "../../include" /i "../../srclib/apr/include" /i "../../srclib/apr-util/include" /i "../../srclib/libxml2/include" /d "_DEBUG" /d BIN_NAME="mod_proxy_html.so" /d LONG_NAME="proxy_html_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_proxy_html.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib libxml2.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_proxy_html.pdb" /debug /out:"$(OUTDIR)\mod_proxy_html.so" /implib:"$(OUTDIR)\mod_proxy_html.lib" /libpath:"../../srclib/libxml2/win32/bin.msvc" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_html.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_proxy_html.obj" \
+ "$(INTDIR)\httpd.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_proxy_html.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_proxy_html.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_proxy_html.so"
+ if exist .\Debug\mod_proxy_html.so.manifest mt.exe -manifest .\Debug\mod_proxy_html.so.manifest -outputresource:.\Debug\mod_proxy_html.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_proxy_html.dep")
+!INCLUDE "mod_proxy_html.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_proxy_html.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_proxy_html - Win32 Release" || "$(CFG)" == "mod_proxy_html - Win32 Debug"
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_proxy_html - Win32 Release"
+
+
+"$(INTDIR)\httpd.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\httpd.res" /i "../../include" /i "../../srclib/apr/include" /i "../../srclib/apr-util/include" /i "../../srclib/libxml2/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_proxy_html.so" /d LONG_NAME="proxy_html_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_proxy_html - Win32 Debug"
+
+
+"$(INTDIR)\httpd.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\httpd.res" /i "../../include" /i "../../srclib/apr/include" /i "../../srclib/apr-util/include" /i "../../srclib/libxml2/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_proxy_html.so" /d LONG_NAME="proxy_html_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_proxy_html.c
+
+"$(INTDIR)\mod_proxy_html.obj" : $(SOURCE) "$(INTDIR)"
+
+
+!IF "$(CFG)" == "mod_proxy_html - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\filters"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_proxy_html - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\filters"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_html - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\filters"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_proxy_html - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\filters"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_html - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\filters"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_proxy_html - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\filters"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\filters"
+
+!ENDIF
+
+
+!ENDIF
+
diff --git a/modules/filters/mod_ratelimit.c b/modules/filters/mod_ratelimit.c
new file mode 100644
index 0000000..d16eb39
--- /dev/null
+++ b/modules/filters/mod_ratelimit.c
@@ -0,0 +1,340 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "util_filter.h"
+
+#include "mod_ratelimit.h"
+
+#define RATE_LIMIT_FILTER_NAME "RATE_LIMIT"
+#define RATE_INTERVAL_MS (200)
+
+typedef enum rl_state_e
+{
+ RATE_LIMIT,
+ RATE_FULLSPEED
+} rl_state_e;
+
+typedef struct rl_ctx_t
+{
+ int speed;
+ int chunk_size;
+ int burst;
+ int do_sleep;
+ rl_state_e state;
+ apr_bucket_brigade *tmpbb;
+ apr_bucket_brigade *holdingbb;
+} rl_ctx_t;
+
+#if defined(RLFDEBUG)
+static void brigade_dump(request_rec *r, apr_bucket_brigade *bb)
+{
+ apr_bucket *e;
+ int i = 0;
+
+ for (e = APR_BRIGADE_FIRST(bb);
+ e != APR_BRIGADE_SENTINEL(bb); e = APR_BUCKET_NEXT(e), i++) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(03193)
+ "brigade: [%d] %s", i, e->type->name);
+
+ }
+}
+#endif /* RLFDEBUG */
+
+static apr_status_t
+rate_limit_filter(ap_filter_t *f, apr_bucket_brigade *bb)
+{
+ apr_status_t rv = APR_SUCCESS;
+ rl_ctx_t *ctx = f->ctx;
+ apr_bucket_alloc_t *ba = f->r->connection->bucket_alloc;
+
+ /* Set up our rl_ctx_t on first use */
+ if (ctx == NULL) {
+ const char *rl = NULL;
+ int ratelimit;
+ int burst = 0;
+
+ /* no subrequests. */
+ if (f->r->main != NULL) {
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ /* Configuration: rate limit */
+ rl = apr_table_get(f->r->subprocess_env, "rate-limit");
+
+ if (rl == NULL) {
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ /* rl is in kilo bytes / second */
+ ratelimit = atoi(rl) * 1024;
+ if (ratelimit <= 0) {
+ /* remove ourselves */
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r,
+ APLOGNO(03488) "rl: disabling: rate-limit = %s (too high?)", rl);
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ /* Configuration: optional initial burst */
+ rl = apr_table_get(f->r->subprocess_env, "rate-initial-burst");
+ if (rl != NULL) {
+ burst = atoi(rl) * 1024;
+ if (burst <= 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r,
+ APLOGNO(03489) "rl: disabling burst: rate-initial-burst = %s (too high?)", rl);
+ burst = 0;
+ }
+ }
+
+ /* Set up our context */
+ ctx = apr_palloc(f->r->pool, sizeof(rl_ctx_t));
+ f->ctx = ctx;
+ ctx->state = RATE_LIMIT;
+ ctx->speed = ratelimit;
+ ctx->burst = burst;
+ ctx->do_sleep = 0;
+
+ /* calculate how many bytes / interval we want to send */
+ /* speed is bytes / second, so, how many (speed / 1000 % interval) */
+ ctx->chunk_size = (ctx->speed / (1000 / RATE_INTERVAL_MS));
+ ctx->tmpbb = apr_brigade_create(f->r->pool, ba);
+ ctx->holdingbb = apr_brigade_create(f->r->pool, ba);
+ }
+ else {
+ APR_BRIGADE_PREPEND(bb, ctx->holdingbb);
+ }
+
+ while (!APR_BRIGADE_EMPTY(bb)) {
+ apr_bucket *e;
+
+ if (ctx->state == RATE_FULLSPEED) {
+ /* Find where we 'stop' going full speed. */
+ for (e = APR_BRIGADE_FIRST(bb);
+ e != APR_BRIGADE_SENTINEL(bb); e = APR_BUCKET_NEXT(e)) {
+ if (AP_RL_BUCKET_IS_END(e)) {
+ apr_brigade_split_ex(bb, e, ctx->holdingbb);
+ ctx->state = RATE_LIMIT;
+ break;
+ }
+ }
+
+ e = apr_bucket_flush_create(ba);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ rv = ap_pass_brigade(f->next, bb);
+ apr_brigade_cleanup(bb);
+
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, f->r, APLOGNO(01455)
+ "rl: full speed brigade pass failed.");
+ return rv;
+ }
+ }
+ else {
+ for (e = APR_BRIGADE_FIRST(bb);
+ e != APR_BRIGADE_SENTINEL(bb); e = APR_BUCKET_NEXT(e)) {
+ if (AP_RL_BUCKET_IS_START(e)) {
+ apr_brigade_split_ex(bb, e, ctx->holdingbb);
+ ctx->state = RATE_FULLSPEED;
+ break;
+ }
+ }
+
+ while (!APR_BRIGADE_EMPTY(bb)) {
+ apr_off_t len = ctx->chunk_size + ctx->burst;
+
+ APR_BRIGADE_CONCAT(ctx->tmpbb, bb);
+
+ /*
+ * Pull next chunk of data; the initial amount is our
+ * burst allotment (if any) plus a chunk. All subsequent
+ * iterations are just chunks with whatever remaining
+ * burst amounts we have left (in case not done in the
+ * first bucket).
+ */
+ rv = apr_brigade_partition(ctx->tmpbb, len, &e);
+ if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(01456)
+ "rl: partition failed.");
+ return rv;
+ }
+ /* Send next metadata now if any */
+ while (e != APR_BRIGADE_SENTINEL(ctx->tmpbb)
+ && APR_BUCKET_IS_METADATA(e)) {
+ e = APR_BUCKET_NEXT(e);
+ }
+ if (e != APR_BRIGADE_SENTINEL(ctx->tmpbb)) {
+ apr_brigade_split_ex(ctx->tmpbb, e, bb);
+ }
+ else {
+ apr_brigade_length(ctx->tmpbb, 1, &len);
+ }
+
+ /*
+ * Adjust the burst amount depending on how much
+ * we've done up to now.
+ */
+ if (ctx->burst) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r,
+ APLOGNO(03485) "rl: burst %d; len %"APR_OFF_T_FMT, ctx->burst, len);
+ if (len < ctx->burst) {
+ ctx->burst -= len;
+ }
+ else {
+ ctx->burst = 0;
+ }
+ }
+
+ e = APR_BRIGADE_LAST(ctx->tmpbb);
+ if (APR_BUCKET_IS_EOS(e)) {
+ ap_remove_output_filter(f);
+ }
+ else if (!APR_BUCKET_IS_FLUSH(e)) {
+ if (APR_BRIGADE_EMPTY(bb)) {
+ /* Wait for more (or next call) */
+ break;
+ }
+ e = apr_bucket_flush_create(ba);
+ APR_BRIGADE_INSERT_TAIL(ctx->tmpbb, e);
+ }
+
+#if defined(RLFDEBUG)
+ brigade_dump(f->r, ctx->tmpbb);
+ brigade_dump(f->r, bb);
+#endif /* RLFDEBUG */
+
+ if (ctx->do_sleep) {
+ apr_sleep(RATE_INTERVAL_MS * 1000);
+ }
+ else {
+ ctx->do_sleep = 1;
+ }
+
+ rv = ap_pass_brigade(f->next, ctx->tmpbb);
+ apr_brigade_cleanup(ctx->tmpbb);
+
+ if (rv != APR_SUCCESS) {
+ /* Most often, user disconnects from stream */
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, f->r, APLOGNO(01457)
+ "rl: brigade pass failed.");
+ return rv;
+ }
+ }
+ }
+
+ if (!APR_BRIGADE_EMPTY(ctx->holdingbb)) {
+ /* Any rate-limited data in tmpbb is sent unlimited along
+ * with the rest.
+ */
+ APR_BRIGADE_CONCAT(bb, ctx->tmpbb);
+ APR_BRIGADE_CONCAT(bb, ctx->holdingbb);
+ }
+ }
+
+#if defined(RLFDEBUG)
+ brigade_dump(f->r, ctx->tmpbb);
+#endif /* RLFDEBUG */
+
+ /* Save remaining tmpbb with the correct lifetime for the next call */
+ return ap_save_brigade(f, &ctx->holdingbb, &ctx->tmpbb, f->r->pool);
+}
+
+
+static apr_status_t
+rl_bucket_read(apr_bucket *b, const char **str,
+ apr_size_t *len, apr_read_type_e block)
+{
+ *str = NULL;
+ *len = 0;
+ return APR_SUCCESS;
+}
+
+AP_RL_DECLARE(apr_bucket *)
+ ap_rl_end_create(apr_bucket_alloc_t *list)
+{
+ apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
+
+ APR_BUCKET_INIT(b);
+ b->free = apr_bucket_free;
+ b->list = list;
+ b->length = 0;
+ b->start = 0;
+ b->data = NULL;
+ b->type = &ap_rl_bucket_type_end;
+
+ return b;
+}
+
+AP_RL_DECLARE(apr_bucket *)
+ ap_rl_start_create(apr_bucket_alloc_t *list)
+{
+ apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
+
+ APR_BUCKET_INIT(b);
+ b->free = apr_bucket_free;
+ b->list = list;
+ b->length = 0;
+ b->start = 0;
+ b->data = NULL;
+ b->type = &ap_rl_bucket_type_start;
+
+ return b;
+}
+
+
+
+AP_RL_DECLARE_DATA const apr_bucket_type_t ap_rl_bucket_type_end = {
+ "RL_END", 5, APR_BUCKET_METADATA,
+ apr_bucket_destroy_noop,
+ rl_bucket_read,
+ apr_bucket_setaside_noop,
+ apr_bucket_split_notimpl,
+ apr_bucket_simple_copy
+};
+
+
+AP_RL_DECLARE_DATA const apr_bucket_type_t ap_rl_bucket_type_start = {
+ "RL_START", 5, APR_BUCKET_METADATA,
+ apr_bucket_destroy_noop,
+ rl_bucket_read,
+ apr_bucket_setaside_noop,
+ apr_bucket_split_notimpl,
+ apr_bucket_simple_copy
+};
+
+
+
+
+static void register_hooks(apr_pool_t *p)
+{
+ /* run after mod_deflate etc etc, but not at connection level, ie, mod_ssl. */
+ ap_register_output_filter(RATE_LIMIT_FILTER_NAME, rate_limit_filter,
+ NULL, AP_FTYPE_CONNECTION - 1);
+}
+
+AP_DECLARE_MODULE(ratelimit) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ NULL, /* command apr_table_t */
+ register_hooks
+};
diff --git a/modules/filters/mod_ratelimit.dep b/modules/filters/mod_ratelimit.dep
new file mode 100644
index 0000000..5015e98
--- /dev/null
+++ b/modules/filters/mod_ratelimit.dep
@@ -0,0 +1,45 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_ratelimit.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_ratelimit.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_ratelimit.h"\
+
diff --git a/modules/filters/mod_ratelimit.dsp b/modules/filters/mod_ratelimit.dsp
new file mode 100644
index 0000000..99bb595
--- /dev/null
+++ b/modules/filters/mod_ratelimit.dsp
@@ -0,0 +1,115 @@
+# Microsoft Developer Studio Project File - Name="mod_ratelimit" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_ratelimit - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_ratelimit.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_ratelimit.mak" CFG="mod_ratelimit - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_ratelimit - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_ratelimit - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_ratelimit - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "AP_RL_DECLARE_EXPORT" /Fd"Release\mod_ratelimit_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_ratelimit.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_ratelimit.so" /d LONG_NAME="ratelimit_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_ratelimit.so" /base:@..\..\os\win32\BaseAddr.ref,mod_ratelimit.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_ratelimit.so" /base:@..\..\os\win32\BaseAddr.ref,mod_ratelimit.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_ratelimit.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_ratelimit - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "AP_RL_DECLARE_EXPORT" /Fd"Debug\mod_ratelimit_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_ratelimit.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_ratelimit.so" /d LONG_NAME="ratelimit_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_ratelimit.so" /base:@..\..\os\win32\BaseAddr.ref,mod_ratelimit.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_ratelimit.so" /base:@..\..\os\win32\BaseAddr.ref,mod_ratelimit.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_ratelimit.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_ratelimit - Win32 Release"
+# Name "mod_ratelimit - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_ratelimit.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\mod_ratelimit.h
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/filters/mod_ratelimit.h b/modules/filters/mod_ratelimit.h
new file mode 100644
index 0000000..6c69bb1
--- /dev/null
+++ b/modules/filters/mod_ratelimit.h
@@ -0,0 +1,51 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MOD_RATELIMIT_H_
+#define _MOD_RATELIMIT_H_
+
+/* Create a set of AP_RL_DECLARE(type), AP_RL_DECLARE_NONSTD(type) and
+ * AP_RL_DECLARE_DATA with appropriate export and import tags for the platform
+ */
+#if !defined(WIN32)
+#define AP_RL_DECLARE(type) type
+#define AP_RL_DECLARE_NONSTD(type) type
+#define AP_RL_DECLARE_DATA
+#elif defined(AP_RL_DECLARE_STATIC)
+#define AP_RL_DECLARE(type) type __stdcall
+#define AP_RL_DECLARE_NONSTD(type) type
+#define AP_RL_DECLARE_DATA
+#elif defined(AP_RL_DECLARE_EXPORT)
+#define AP_RL_DECLARE(type) __declspec(dllexport) type __stdcall
+#define AP_RL_DECLARE_NONSTD(type) __declspec(dllexport) type
+#define AP_RL_DECLARE_DATA __declspec(dllexport)
+#else
+#define AP_RL_DECLARE(type) __declspec(dllimport) type __stdcall
+#define AP_RL_DECLARE_NONSTD(type) __declspec(dllimport) type
+#define AP_RL_DECLARE_DATA __declspec(dllimport)
+#endif
+
+AP_RL_DECLARE_DATA extern const apr_bucket_type_t ap_rl_bucket_type_end;
+AP_RL_DECLARE_DATA extern const apr_bucket_type_t ap_rl_bucket_type_start;
+
+#define AP_RL_BUCKET_IS_END(e) (e->type == &ap_rl_bucket_type_end)
+#define AP_RL_BUCKET_IS_START(e) (e->type == &ap_rl_bucket_type_start)
+
+/* TODO: Make these Optional Functions, so that module load order doesn't matter. */
+AP_RL_DECLARE(apr_bucket*) ap_rl_end_create(apr_bucket_alloc_t *list);
+AP_RL_DECLARE(apr_bucket*) ap_rl_start_create(apr_bucket_alloc_t *list);
+
+#endif
diff --git a/modules/filters/mod_ratelimit.mak b/modules/filters/mod_ratelimit.mak
new file mode 100644
index 0000000..e50e892
--- /dev/null
+++ b/modules/filters/mod_ratelimit.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_ratelimit.dsp
+!IF "$(CFG)" == ""
+CFG=mod_ratelimit - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_ratelimit - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_ratelimit - Win32 Release" && "$(CFG)" != "mod_ratelimit - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_ratelimit.mak" CFG="mod_ratelimit - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_ratelimit - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_ratelimit - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_ratelimit - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_ratelimit.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_ratelimit.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_ratelimit.obj"
+ -@erase "$(INTDIR)\mod_ratelimit.res"
+ -@erase "$(INTDIR)\mod_ratelimit_src.idb"
+ -@erase "$(INTDIR)\mod_ratelimit_src.pdb"
+ -@erase "$(OUTDIR)\mod_ratelimit.exp"
+ -@erase "$(OUTDIR)\mod_ratelimit.lib"
+ -@erase "$(OUTDIR)\mod_ratelimit.pdb"
+ -@erase "$(OUTDIR)\mod_ratelimit.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "AP_RL_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_ratelimit_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_ratelimit.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_ratelimit.so" /d LONG_NAME="ratelimit_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_ratelimit.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_ratelimit.pdb" /debug /out:"$(OUTDIR)\mod_ratelimit.so" /implib:"$(OUTDIR)\mod_ratelimit.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_ratelimit.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_ratelimit.obj" \
+ "$(INTDIR)\mod_ratelimit.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_ratelimit.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_ratelimit.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_ratelimit.so"
+ if exist .\Release\mod_ratelimit.so.manifest mt.exe -manifest .\Release\mod_ratelimit.so.manifest -outputresource:.\Release\mod_ratelimit.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_ratelimit - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_ratelimit.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_ratelimit.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_ratelimit.obj"
+ -@erase "$(INTDIR)\mod_ratelimit.res"
+ -@erase "$(INTDIR)\mod_ratelimit_src.idb"
+ -@erase "$(INTDIR)\mod_ratelimit_src.pdb"
+ -@erase "$(OUTDIR)\mod_ratelimit.exp"
+ -@erase "$(OUTDIR)\mod_ratelimit.lib"
+ -@erase "$(OUTDIR)\mod_ratelimit.pdb"
+ -@erase "$(OUTDIR)\mod_ratelimit.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "AP_RL_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_ratelimit_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_ratelimit.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_ratelimit.so" /d LONG_NAME="ratelimit_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_ratelimit.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_ratelimit.pdb" /debug /out:"$(OUTDIR)\mod_ratelimit.so" /implib:"$(OUTDIR)\mod_ratelimit.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_ratelimit.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_ratelimit.obj" \
+ "$(INTDIR)\mod_ratelimit.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_ratelimit.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_ratelimit.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_ratelimit.so"
+ if exist .\Debug\mod_ratelimit.so.manifest mt.exe -manifest .\Debug\mod_ratelimit.so.manifest -outputresource:.\Debug\mod_ratelimit.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_ratelimit.dep")
+!INCLUDE "mod_ratelimit.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_ratelimit.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_ratelimit - Win32 Release" || "$(CFG)" == "mod_ratelimit - Win32 Debug"
+
+!IF "$(CFG)" == "mod_ratelimit - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\filters"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_ratelimit - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\filters"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_ratelimit - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\filters"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_ratelimit - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\filters"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_ratelimit - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\filters"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_ratelimit - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\filters"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\filters"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_ratelimit - Win32 Release"
+
+
+"$(INTDIR)\mod_ratelimit.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_ratelimit.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_ratelimit.so" /d LONG_NAME="ratelimit_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_ratelimit - Win32 Debug"
+
+
+"$(INTDIR)\mod_ratelimit.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_ratelimit.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_ratelimit.so" /d LONG_NAME="ratelimit_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_ratelimit.c
+
+"$(INTDIR)\mod_ratelimit.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/filters/mod_reflector.c b/modules/filters/mod_reflector.c
new file mode 100644
index 0000000..5979cb8
--- /dev/null
+++ b/modules/filters/mod_reflector.c
@@ -0,0 +1,231 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "apr_strings.h"
+#include "apr_tables.h"
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "mod_core.h"
+
+module AP_MODULE_DECLARE_DATA reflector_module;
+
+typedef struct {
+ apr_table_t *headers;
+} reflector_cfg;
+
+static int header_do(void *dummy, const char *key, const char *value)
+{
+ request_rec *r = (request_rec *) dummy;
+ const char *payload;
+
+ payload = apr_table_get(r->headers_in, key);
+ if (payload) {
+ apr_table_setn(r->headers_out, value, payload);
+ }
+
+ return 1;
+}
+
+static int reflector_handler(request_rec * r)
+{
+ apr_bucket_brigade *bbin, *bbout;
+ reflector_cfg *conf;
+ apr_status_t status;
+
+ if (strcmp(r->handler, "reflector")) {
+ return DECLINED;
+ }
+
+ conf = (reflector_cfg *) ap_get_module_config(r->per_dir_config,
+ &reflector_module);
+
+ ap_allow_methods(r, 1, "POST", "OPTIONS", NULL);
+
+ if (r->method_number == M_OPTIONS) {
+ return ap_send_http_options(r);
+ }
+
+ else if (r->method_number == M_POST) {
+ const char *content_length, *content_type;
+ int seen_eos;
+
+ /*
+ * Sometimes we'll get in a state where the input handling has
+ * detected an error where we want to drop the connection, so if
+ * that's the case, don't read the data as that is what we're trying
+ * to avoid.
+ *
+ * This function is also a no-op on a subrequest.
+ */
+ if (r->main || r->connection->keepalive == AP_CONN_CLOSE ||
+ ap_status_drops_connection(r->status)) {
+ return OK;
+ }
+
+ /* copy headers from in to out if configured */
+ apr_table_do(header_do, r, conf->headers, NULL);
+
+ /* last modified defaults to now, unless otherwise set on the way in */
+ if (!apr_table_get(r->headers_out, "Last-Modified")) {
+ ap_update_mtime(r, apr_time_now());
+ ap_set_last_modified(r);
+ }
+ ap_set_accept_ranges(r);
+
+ /* reflect the content length, if present */
+ if ((content_length = apr_table_get(r->headers_in, "Content-Length"))) {
+ apr_off_t clen;
+
+ if (!ap_parse_strict_length(&clen, content_length)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10243)
+ "reflector_handler: invalid content-length '%s'",
+ content_length);
+ return HTTP_BAD_REQUEST;
+ }
+
+ ap_set_content_length(r, clen);
+ }
+
+ /* reflect the content type, if present */
+ if ((content_type = apr_table_get(r->headers_in, "Content-Type"))) {
+
+ ap_set_content_type(r, content_type);
+
+ }
+
+ bbin = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+ bbout = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+
+ seen_eos = 0;
+ do {
+ apr_bucket *bucket;
+
+ status = ap_get_brigade(r->input_filters, bbin, AP_MODE_READBYTES,
+ APR_BLOCK_READ, HUGE_STRING_LEN);
+
+ if (status != APR_SUCCESS) {
+ apr_brigade_destroy(bbin);
+ return ap_map_http_request_error(status, HTTP_BAD_REQUEST);
+ }
+
+ for (bucket = APR_BRIGADE_FIRST(bbin);
+ bucket != APR_BRIGADE_SENTINEL(bbin);
+ bucket = APR_BUCKET_NEXT(bucket)) {
+ const char *data;
+ apr_size_t len;
+
+ if (APR_BUCKET_IS_EOS(bucket)) {
+ seen_eos = 1;
+ break;
+ }
+
+ /* These are metadata buckets. */
+ if (bucket->length == 0) {
+ continue;
+ }
+
+ /*
+ * We MUST read because in case we have an unknown-length
+ * bucket or one that morphs, we want to exhaust it.
+ */
+ status = apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ);
+ if (status != APR_SUCCESS) {
+ apr_brigade_destroy(bbin);
+ return HTTP_BAD_REQUEST;
+ }
+
+ apr_brigade_write(bbout, NULL, NULL, data, len);
+
+ status = ap_pass_brigade(r->output_filters, bbout);
+ if (status != APR_SUCCESS) {
+ /* no way to know what type of error occurred */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO(01410)
+ "reflector_handler: ap_pass_brigade returned %i",
+ status);
+ return AP_FILTER_ERROR;
+ }
+
+ }
+
+ apr_brigade_cleanup(bbin);
+
+ } while (!seen_eos);
+
+ return OK;
+
+ }
+
+ else {
+ return HTTP_METHOD_NOT_ALLOWED;
+ }
+
+}
+
+static void *create_reflector_dir_config(apr_pool_t * p, char *d)
+{
+ reflector_cfg *conf = apr_pcalloc(p, sizeof(reflector_cfg));
+
+ conf->headers = apr_table_make(p, 8);
+
+ return conf;
+}
+
+static void *merge_reflector_dir_config(apr_pool_t * p, void *basev, void *addv)
+{
+ reflector_cfg *new = (reflector_cfg *) apr_pcalloc(p,
+ sizeof(reflector_cfg));
+ reflector_cfg *add = (reflector_cfg *) addv;
+ reflector_cfg *base = (reflector_cfg *) basev;
+
+ new->headers = apr_table_overlay(p, add->headers, base->headers);
+
+ return new;
+}
+
+static const char *reflector_header(cmd_parms * cmd, void *dummy, const char *in,
+ const char *out)
+{
+ reflector_cfg *cfg = (reflector_cfg *) dummy;
+
+ apr_table_addn(cfg->headers, in, out ? out : in);
+
+ return NULL;
+}
+
+static void reflector_hooks(apr_pool_t * p)
+{
+ ap_hook_handler(reflector_handler, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+static const command_rec reflector_cmds[] = {
+ AP_INIT_TAKE12("ReflectorHeader", reflector_header, NULL, OR_OPTIONS,
+ "Header to reflect back in the response, with an optional new name."),
+ {NULL}
+};
+
+AP_DECLARE_MODULE(reflector) = {
+ STANDARD20_MODULE_STUFF,
+ create_reflector_dir_config,
+ merge_reflector_dir_config,
+ NULL,
+ NULL,
+ reflector_cmds,
+ reflector_hooks
+};
diff --git a/modules/filters/mod_reflector.dep b/modules/filters/mod_reflector.dep
new file mode 100644
index 0000000..dc91ddf
--- /dev/null
+++ b/modules/filters/mod_reflector.dep
@@ -0,0 +1,57 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_reflector.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_reflector.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_core.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/filters/mod_reflector.dsp b/modules/filters/mod_reflector.dsp
new file mode 100644
index 0000000..6f284df
--- /dev/null
+++ b/modules/filters/mod_reflector.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_reflector" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_reflector - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_reflector.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_reflector.mak" CFG="mod_reflector - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_reflector - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_reflector - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_reflector - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "AP_RL_DECLARE_EXPORT" /Fd"Release\mod_reflector_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_reflector.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_reflector.so" /d LONG_NAME="reflector_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_reflector.so" /base:@..\..\os\win32\BaseAddr.ref,mod_reflector.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_reflector.so" /base:@..\..\os\win32\BaseAddr.ref,mod_reflector.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_reflector.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_reflector - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "AP_RL_DECLARE_EXPORT" /Fd"Debug\mod_reflector_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_reflector.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_reflector.so" /d LONG_NAME="reflector_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_reflector.so" /base:@..\..\os\win32\BaseAddr.ref,mod_reflector.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_reflector.so" /base:@..\..\os\win32\BaseAddr.ref,mod_reflector.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_reflector.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_reflector - Win32 Release"
+# Name "mod_reflector - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_reflector.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/filters/mod_reflector.mak b/modules/filters/mod_reflector.mak
new file mode 100644
index 0000000..d38b35c
--- /dev/null
+++ b/modules/filters/mod_reflector.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_reflector.dsp
+!IF "$(CFG)" == ""
+CFG=mod_reflector - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_reflector - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_reflector - Win32 Release" && "$(CFG)" != "mod_reflector - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_reflector.mak" CFG="mod_reflector - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_reflector - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_reflector - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_reflector - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_reflector.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_reflector.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_reflector.obj"
+ -@erase "$(INTDIR)\mod_reflector.res"
+ -@erase "$(INTDIR)\mod_reflector_src.idb"
+ -@erase "$(INTDIR)\mod_reflector_src.pdb"
+ -@erase "$(OUTDIR)\mod_reflector.exp"
+ -@erase "$(OUTDIR)\mod_reflector.lib"
+ -@erase "$(OUTDIR)\mod_reflector.pdb"
+ -@erase "$(OUTDIR)\mod_reflector.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "AP_RL_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_reflector_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_reflector.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_reflector.so" /d LONG_NAME="reflector_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_reflector.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_reflector.pdb" /debug /out:"$(OUTDIR)\mod_reflector.so" /implib:"$(OUTDIR)\mod_reflector.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_reflector.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_reflector.obj" \
+ "$(INTDIR)\mod_reflector.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_reflector.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_reflector.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_reflector.so"
+ if exist .\Release\mod_reflector.so.manifest mt.exe -manifest .\Release\mod_reflector.so.manifest -outputresource:.\Release\mod_reflector.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_reflector - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_reflector.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_reflector.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_reflector.obj"
+ -@erase "$(INTDIR)\mod_reflector.res"
+ -@erase "$(INTDIR)\mod_reflector_src.idb"
+ -@erase "$(INTDIR)\mod_reflector_src.pdb"
+ -@erase "$(OUTDIR)\mod_reflector.exp"
+ -@erase "$(OUTDIR)\mod_reflector.lib"
+ -@erase "$(OUTDIR)\mod_reflector.pdb"
+ -@erase "$(OUTDIR)\mod_reflector.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "AP_RL_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_reflector_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_reflector.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_reflector.so" /d LONG_NAME="reflector_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_reflector.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_reflector.pdb" /debug /out:"$(OUTDIR)\mod_reflector.so" /implib:"$(OUTDIR)\mod_reflector.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_reflector.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_reflector.obj" \
+ "$(INTDIR)\mod_reflector.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_reflector.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_reflector.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_reflector.so"
+ if exist .\Debug\mod_reflector.so.manifest mt.exe -manifest .\Debug\mod_reflector.so.manifest -outputresource:.\Debug\mod_reflector.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_reflector.dep")
+!INCLUDE "mod_reflector.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_reflector.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_reflector - Win32 Release" || "$(CFG)" == "mod_reflector - Win32 Debug"
+
+!IF "$(CFG)" == "mod_reflector - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\filters"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_reflector - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\filters"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_reflector - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\filters"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_reflector - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\filters"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_reflector - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\filters"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_reflector - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\filters"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\filters"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_reflector - Win32 Release"
+
+
+"$(INTDIR)\mod_reflector.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_reflector.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_reflector.so" /d LONG_NAME="reflector_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_reflector - Win32 Debug"
+
+
+"$(INTDIR)\mod_reflector.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_reflector.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_reflector.so" /d LONG_NAME="reflector_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_reflector.c
+
+"$(INTDIR)\mod_reflector.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/filters/mod_reqtimeout.c b/modules/filters/mod_reqtimeout.c
new file mode 100644
index 0000000..0ebd78a
--- /dev/null
+++ b/modules/filters/mod_reqtimeout.c
@@ -0,0 +1,670 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_request.h"
+#include "http_connection.h"
+#include "http_protocol.h"
+#include "http_log.h"
+#include "http_core.h"
+#include "util_filter.h"
+#define APR_WANT_STRFUNC
+#include "apr_strings.h"
+#include "apr_version.h"
+
+module AP_MODULE_DECLARE_DATA reqtimeout_module;
+
+#define UNSET -1
+#define MRT_DEFAULT_handshake_TIMEOUT 0 /* disabled */
+#define MRT_DEFAULT_handshake_MAX_TIMEOUT 0
+#define MRT_DEFAULT_handshake_MIN_RATE 0
+#define MRT_DEFAULT_header_TIMEOUT 20
+#define MRT_DEFAULT_header_MAX_TIMEOUT 40
+#define MRT_DEFAULT_header_MIN_RATE 500
+#define MRT_DEFAULT_body_TIMEOUT 20
+#define MRT_DEFAULT_body_MAX_TIMEOUT 0
+#define MRT_DEFAULT_body_MIN_RATE 500
+
+typedef struct
+{
+ int timeout; /* timeout in secs */
+ int max_timeout; /* max timeout in secs */
+ int min_rate; /* min rate in bytes/s */
+ apr_time_t rate_factor; /* scale factor (#usecs per min_rate) */
+} reqtimeout_stage_t;
+
+typedef struct
+{
+ reqtimeout_stage_t handshake; /* Handshaking (TLS) */
+ reqtimeout_stage_t header; /* Reading the HTTP header */
+ reqtimeout_stage_t body; /* Reading the HTTP body */
+} reqtimeout_srv_cfg;
+
+/* this struct is used both as conn_config and as filter context */
+typedef struct
+{
+ apr_time_t timeout_at;
+ apr_time_t max_timeout_at;
+ reqtimeout_stage_t cur_stage;
+ int in_keep_alive;
+ char *type;
+ apr_socket_t *socket;
+ apr_bucket_brigade *tmpbb;
+} reqtimeout_con_cfg;
+
+static const char *const reqtimeout_filter_name = "reqtimeout";
+static int default_handshake_rate_factor;
+static int default_header_rate_factor;
+static int default_body_rate_factor;
+
+static void extend_timeout(reqtimeout_con_cfg *ccfg, apr_bucket_brigade *bb)
+{
+ apr_off_t len;
+ apr_time_t new_timeout_at;
+
+ if (apr_brigade_length(bb, 0, &len) != APR_SUCCESS || len <= 0)
+ return;
+
+ new_timeout_at = ccfg->timeout_at + len * ccfg->cur_stage.rate_factor;
+ if (ccfg->max_timeout_at > 0 && new_timeout_at > ccfg->max_timeout_at) {
+ ccfg->timeout_at = ccfg->max_timeout_at;
+ }
+ else {
+ ccfg->timeout_at = new_timeout_at;
+ }
+}
+
+static apr_status_t check_time_left(reqtimeout_con_cfg *ccfg,
+ apr_time_t *time_left_p,
+ apr_time_t now)
+{
+ if (!now)
+ now = apr_time_now();
+ *time_left_p = ccfg->timeout_at - now;
+ if (*time_left_p <= 0)
+ return APR_TIMEUP;
+
+ if (*time_left_p < apr_time_from_sec(1)) {
+ *time_left_p = apr_time_from_sec(1);
+ }
+ return APR_SUCCESS;
+}
+
+static apr_status_t have_lf_or_eos(apr_bucket_brigade *bb)
+{
+ apr_bucket *b = APR_BRIGADE_LAST(bb);
+
+ for ( ; b != APR_BRIGADE_SENTINEL(bb) ; b = APR_BUCKET_PREV(b) ) {
+ const char *str;
+ apr_size_t len;
+ apr_status_t rv;
+
+ if (APR_BUCKET_IS_EOS(b))
+ return APR_SUCCESS;
+
+ if (APR_BUCKET_IS_METADATA(b))
+ continue;
+
+ rv = apr_bucket_read(b, &str, &len, APR_BLOCK_READ);
+ if (rv != APR_SUCCESS)
+ return rv;
+
+ if (len == 0)
+ continue;
+
+ if (str[len-1] == APR_ASCII_LF)
+ return APR_SUCCESS;
+ }
+ return APR_INCOMPLETE;
+}
+
+/*
+ * Append bbIn to bbOut and merge small buckets, to avoid DoS by high memory
+ * usage
+ */
+static apr_status_t brigade_append(apr_bucket_brigade *bbOut, apr_bucket_brigade *bbIn)
+{
+ while (!APR_BRIGADE_EMPTY(bbIn)) {
+ apr_bucket *e = APR_BRIGADE_FIRST(bbIn);
+ const char *str;
+ apr_size_t len;
+ apr_status_t rv;
+
+ rv = apr_bucket_read(e, &str, &len, APR_BLOCK_READ);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ APR_BUCKET_REMOVE(e);
+ if (APR_BUCKET_IS_METADATA(e) || len > APR_BUCKET_BUFF_SIZE/4) {
+ APR_BRIGADE_INSERT_TAIL(bbOut, e);
+ }
+ else {
+ if (len > 0) {
+ rv = apr_brigade_write(bbOut, NULL, NULL, str, len);
+ if (rv != APR_SUCCESS) {
+ apr_bucket_destroy(e);
+ return rv;
+ }
+ }
+ apr_bucket_destroy(e);
+ }
+ }
+ return APR_SUCCESS;
+}
+
+
+#define MIN(x,y) ((x) < (y) ? (x) : (y))
+static apr_status_t reqtimeout_filter(ap_filter_t *f,
+ apr_bucket_brigade *bb,
+ ap_input_mode_t mode,
+ apr_read_type_e block,
+ apr_off_t readbytes)
+{
+ apr_time_t time_left;
+ apr_time_t now = 0;
+ apr_status_t rv;
+ apr_interval_time_t saved_sock_timeout = UNSET;
+ reqtimeout_con_cfg *ccfg = f->ctx;
+
+ if (ccfg->in_keep_alive) {
+ /* For this read[_request line()], wait for the first byte using the
+ * normal keep-alive timeout (hence don't take this expected idle time
+ * into account to setup the connection expiry below).
+ */
+ ccfg->in_keep_alive = 0;
+ rv = ap_get_brigade(f->next, bb, AP_MODE_SPECULATIVE, block, 1);
+ if (rv != APR_SUCCESS || APR_BRIGADE_EMPTY(bb)) {
+ return rv;
+ }
+ apr_brigade_cleanup(bb);
+ }
+
+ if (ccfg->cur_stage.timeout > 0) {
+ /* set new timeout */
+ now = apr_time_now();
+ ccfg->timeout_at = now + apr_time_from_sec(ccfg->cur_stage.timeout);
+ ccfg->cur_stage.timeout = 0;
+ if (ccfg->cur_stage.max_timeout > 0) {
+ ccfg->max_timeout_at = now + apr_time_from_sec(ccfg->cur_stage.max_timeout);
+ ccfg->cur_stage.max_timeout = 0;
+ }
+ }
+ else if (ccfg->timeout_at == 0) {
+ /* no timeout set, or in between requests */
+ return ap_get_brigade(f->next, bb, mode, block, readbytes);
+ }
+
+ if (!ccfg->socket) {
+ ccfg->socket = ap_get_conn_socket(f->c);
+ }
+
+ rv = check_time_left(ccfg, &time_left, now);
+ if (rv != APR_SUCCESS)
+ goto out;
+
+ if (block == APR_NONBLOCK_READ || mode == AP_MODE_INIT
+ || mode == AP_MODE_EATCRLF) {
+ rv = ap_get_brigade(f->next, bb, mode, block, readbytes);
+ if (ccfg->cur_stage.rate_factor && rv == APR_SUCCESS) {
+ extend_timeout(ccfg, bb);
+ }
+ return rv;
+ }
+
+ rv = apr_socket_timeout_get(ccfg->socket, &saved_sock_timeout);
+ AP_DEBUG_ASSERT(rv == APR_SUCCESS);
+
+ rv = apr_socket_timeout_set(ccfg->socket, MIN(time_left, saved_sock_timeout));
+ AP_DEBUG_ASSERT(rv == APR_SUCCESS);
+
+ if (mode == AP_MODE_GETLINE) {
+ /*
+ * For a blocking AP_MODE_GETLINE read, apr_brigade_split_line()
+ * would loop until a whole line has been read. As this would make it
+ * impossible to enforce a total timeout, we only do non-blocking
+ * reads.
+ */
+ apr_off_t remaining = HUGE_STRING_LEN;
+ do {
+ apr_off_t bblen;
+#if APR_MAJOR_VERSION < 2
+ apr_int32_t nsds;
+ apr_interval_time_t poll_timeout;
+ apr_pollfd_t pollset;
+#endif
+
+ rv = ap_get_brigade(f->next, bb, AP_MODE_GETLINE, APR_NONBLOCK_READ, remaining);
+ if (rv != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(rv)) {
+ break;
+ }
+
+ if (!APR_BRIGADE_EMPTY(bb)) {
+ if (ccfg->cur_stage.rate_factor) {
+ extend_timeout(ccfg, bb);
+ }
+
+ rv = have_lf_or_eos(bb);
+ if (rv != APR_INCOMPLETE) {
+ break;
+ }
+
+ rv = apr_brigade_length(bb, 1, &bblen);
+ if (rv != APR_SUCCESS) {
+ break;
+ }
+ remaining -= bblen;
+ if (remaining <= 0) {
+ break;
+ }
+
+ /* Haven't got a whole line yet, save what we have ... */
+ if (!ccfg->tmpbb) {
+ ccfg->tmpbb = apr_brigade_create(f->c->pool, f->c->bucket_alloc);
+ }
+ rv = brigade_append(ccfg->tmpbb, bb);
+ if (rv != APR_SUCCESS)
+ break;
+ }
+
+ /* ... and wait for more */
+#if APR_MAJOR_VERSION < 2
+ pollset.p = f->c->pool;
+ pollset.desc_type = APR_POLL_SOCKET;
+ pollset.reqevents = APR_POLLIN|APR_POLLHUP;
+ pollset.desc.s = ccfg->socket;
+ apr_socket_timeout_get(ccfg->socket, &poll_timeout);
+ rv = apr_poll(&pollset, 1, &nsds, poll_timeout);
+#else
+ rv = apr_socket_wait(ccfg->socket, APR_WAIT_READ);
+#endif
+ if (rv != APR_SUCCESS)
+ break;
+
+ rv = check_time_left(ccfg, &time_left, 0);
+ if (rv != APR_SUCCESS)
+ break;
+
+ rv = apr_socket_timeout_set(ccfg->socket,
+ MIN(time_left, saved_sock_timeout));
+ AP_DEBUG_ASSERT(rv == APR_SUCCESS);
+
+ } while (1);
+
+ if (ccfg->tmpbb)
+ APR_BRIGADE_PREPEND(bb, ccfg->tmpbb);
+
+ }
+ else { /* mode != AP_MODE_GETLINE */
+ rv = ap_get_brigade(f->next, bb, mode, block, readbytes);
+ /* Don't extend the timeout in speculative mode, wait for
+ * the real (relevant) bytes to be asked later, within the
+ * currently allotted time.
+ */
+ if (ccfg->cur_stage.rate_factor && rv == APR_SUCCESS
+ && mode != AP_MODE_SPECULATIVE) {
+ extend_timeout(ccfg, bb);
+ }
+ }
+
+ apr_socket_timeout_set(ccfg->socket, saved_sock_timeout);
+
+out:
+ if (APR_STATUS_IS_TIMEUP(rv)) {
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, f->c, APLOGNO(01382)
+ "Request %s read timeout", ccfg->type);
+ /*
+ * If we allow a normal lingering close, the client may keep this
+ * process/thread busy for another 30s (MAX_SECS_TO_LINGER).
+ * Therefore we tell ap_lingering_close() to shorten this period to
+ * 2s (SECONDS_TO_LINGER).
+ */
+ apr_table_setn(f->c->notes, "short-lingering-close", "1");
+
+ /*
+ * Also, we must not allow keep-alive requests, as
+ * ap_finalize_protocol() may ignore our error status (if the timeout
+ * happened on a request body that is discarded).
+ */
+ f->c->keepalive = AP_CONN_CLOSE;
+ }
+ return rv;
+}
+
+static apr_status_t reqtimeout_eor(ap_filter_t *f, apr_bucket_brigade *bb)
+{
+ if (!APR_BRIGADE_EMPTY(bb) && AP_BUCKET_IS_EOR(APR_BRIGADE_LAST(bb))) {
+ reqtimeout_con_cfg *ccfg = f->ctx;
+ ccfg->timeout_at = 0;
+ }
+ return ap_pass_brigade(f->next, bb);
+}
+
+#define INIT_STAGE(cfg, ccfg, stage) do { \
+ if (cfg->stage.timeout != UNSET) { \
+ ccfg->cur_stage.timeout = cfg->stage.timeout; \
+ ccfg->cur_stage.max_timeout = cfg->stage.max_timeout; \
+ ccfg->cur_stage.rate_factor = cfg->stage.rate_factor; \
+ } \
+ else { \
+ ccfg->cur_stage.timeout = MRT_DEFAULT_##stage##_TIMEOUT; \
+ ccfg->cur_stage.max_timeout = MRT_DEFAULT_##stage##_MAX_TIMEOUT; \
+ ccfg->cur_stage.rate_factor = default_##stage##_rate_factor; \
+ } \
+} while (0)
+
+static int reqtimeout_init(conn_rec *c)
+{
+ reqtimeout_con_cfg *ccfg;
+ reqtimeout_srv_cfg *cfg;
+
+ cfg = ap_get_module_config(c->base_server->module_config,
+ &reqtimeout_module);
+ AP_DEBUG_ASSERT(cfg != NULL);
+
+ /* For compatibility, handshake timeout is disabled when UNSET (< 0) */
+ if (cfg->handshake.timeout <= 0
+ && cfg->header.timeout == 0
+ && cfg->body.timeout == 0) {
+ /* disabled for this vhost */
+ return DECLINED;
+ }
+
+ ccfg = ap_get_module_config(c->conn_config, &reqtimeout_module);
+ if (ccfg == NULL) {
+ ccfg = apr_pcalloc(c->pool, sizeof(reqtimeout_con_cfg));
+ ap_set_module_config(c->conn_config, &reqtimeout_module, ccfg);
+ ap_add_output_filter(reqtimeout_filter_name, ccfg, NULL, c);
+ ap_add_input_filter(reqtimeout_filter_name, ccfg, NULL, c);
+
+ ccfg->type = "handshake";
+ if (cfg->handshake.timeout > 0) {
+ INIT_STAGE(cfg, ccfg, handshake);
+ }
+ }
+
+ /* we are not handling the connection, we just do initialization */
+ return DECLINED;
+}
+
+static void reqtimeout_before_header(request_rec *r, conn_rec *c)
+{
+ reqtimeout_srv_cfg *cfg;
+ reqtimeout_con_cfg *ccfg =
+ ap_get_module_config(c->conn_config, &reqtimeout_module);
+
+ if (ccfg == NULL) {
+ /* not configured for this connection */
+ return;
+ }
+
+ cfg = ap_get_module_config(c->base_server->module_config,
+ &reqtimeout_module);
+ AP_DEBUG_ASSERT(cfg != NULL);
+
+ /* (Re)set the state for this new request, but ccfg->socket and
+ * ccfg->tmpbb which have the lifetime of the connection.
+ */
+ ccfg->type = "header";
+ ccfg->timeout_at = 0;
+ ccfg->max_timeout_at = 0;
+ ccfg->in_keep_alive = (c->keepalives > 0);
+ INIT_STAGE(cfg, ccfg, header);
+}
+
+static int reqtimeout_before_body(request_rec *r)
+{
+ reqtimeout_srv_cfg *cfg;
+ reqtimeout_con_cfg *ccfg =
+ ap_get_module_config(r->connection->conn_config, &reqtimeout_module);
+
+ if (ccfg == NULL) {
+ /* not configured for this connection */
+ return OK;
+ }
+ cfg = ap_get_module_config(r->server->module_config,
+ &reqtimeout_module);
+ AP_DEBUG_ASSERT(cfg != NULL);
+
+ ccfg->type = "body";
+ ccfg->timeout_at = 0;
+ ccfg->max_timeout_at = 0;
+ if (r->method_number == M_CONNECT) {
+ /* disabled for a CONNECT request */
+ ccfg->cur_stage.timeout = 0;
+ }
+ else {
+ INIT_STAGE(cfg, ccfg, body);
+ }
+ return OK;
+}
+
+#define UNSET_STAGE(cfg, stage) do { \
+ cfg->stage.timeout = UNSET; \
+ cfg->stage.max_timeout = UNSET; \
+ cfg->stage.min_rate = UNSET; \
+} while (0)
+
+static void *reqtimeout_create_srv_config(apr_pool_t *p, server_rec *s)
+{
+ reqtimeout_srv_cfg *cfg = apr_pcalloc(p, sizeof(reqtimeout_srv_cfg));
+
+ UNSET_STAGE(cfg, handshake);
+ UNSET_STAGE(cfg, header);
+ UNSET_STAGE(cfg, body);
+
+ return cfg;
+}
+
+#define MERGE_INT(cfg, base, add, val) \
+ cfg->val = (add->val == UNSET) ? base->val : add->val
+#define MERGE_STAGE(cfg, base, add, stage) do { \
+ MERGE_INT(cfg, base, add, stage.timeout); \
+ MERGE_INT(cfg, base, add, stage.max_timeout); \
+ MERGE_INT(cfg, base, add, stage.min_rate); \
+ cfg->stage.rate_factor = (cfg->stage.min_rate == UNSET) \
+ ? base->stage.rate_factor \
+ : add->stage.rate_factor; \
+} while (0)
+
+static void *reqtimeout_merge_srv_config(apr_pool_t *p, void *base_, void *add_)
+{
+ reqtimeout_srv_cfg *base = base_;
+ reqtimeout_srv_cfg *add = add_;
+ reqtimeout_srv_cfg *cfg = apr_pcalloc(p, sizeof(reqtimeout_srv_cfg));
+
+ MERGE_STAGE(cfg, base, add, handshake);
+ MERGE_STAGE(cfg, base, add, header);
+ MERGE_STAGE(cfg, base, add, body);
+
+ return cfg;
+}
+
+static const char *parse_int(apr_pool_t *p, const char *arg, int *val)
+{
+ char *endptr;
+ *val = strtol(arg, &endptr, 10);
+
+ if (arg == endptr) {
+ return apr_psprintf(p, "Value '%s' not numerical", endptr);
+ }
+ if (*endptr != '\0') {
+ return apr_psprintf(p, "Cannot parse '%s'", endptr);
+ }
+ if (*val < 0) {
+ return "Value must be non-negative";
+ }
+ return NULL;
+}
+
+static const char *set_reqtimeout_param(reqtimeout_srv_cfg *conf,
+ apr_pool_t *p,
+ const char *key,
+ const char *val)
+{
+ const char *ret = NULL;
+ char *rate_str = NULL, *initial_str, *max_str = NULL;
+ reqtimeout_stage_t *stage;
+
+ if (!strcasecmp(key, "handshake")) {
+ stage = &conf->handshake;
+ }
+ else if (!strcasecmp(key, "header")) {
+ stage = &conf->header;
+ }
+ else if (!strcasecmp(key, "body")) {
+ stage = &conf->body;
+ }
+ else {
+ return "Unknown RequestReadTimeout parameter";
+ }
+
+ memset(stage, 0, sizeof(*stage));
+
+ if ((rate_str = ap_strcasestr(val, ",minrate="))) {
+ initial_str = apr_pstrndup(p, val, rate_str - val);
+ rate_str += strlen(",minrate=");
+ ret = parse_int(p, rate_str, &stage->min_rate);
+ if (ret)
+ return ret;
+
+ if (stage->min_rate == 0)
+ return "Minimum data rate must be larger than 0";
+
+ if ((max_str = strchr(initial_str, '-'))) {
+ *max_str++ = '\0';
+ ret = parse_int(p, max_str, &stage->max_timeout);
+ if (ret)
+ return ret;
+ }
+
+ ret = parse_int(p, initial_str, &stage->timeout);
+ }
+ else {
+ if (ap_strchr_c(val, '-'))
+ return "Must set MinRate option if using timeout range";
+ ret = parse_int(p, val, &stage->timeout);
+ }
+ if (ret)
+ return ret;
+
+ if (stage->max_timeout && stage->timeout >= stage->max_timeout) {
+ return "Maximum timeout must be larger than initial timeout";
+ }
+
+ if (stage->min_rate) {
+ stage->rate_factor = apr_time_from_sec(1) / stage->min_rate;
+ }
+
+ return NULL;
+}
+
+static const char *set_reqtimeouts(cmd_parms *cmd, void *mconfig,
+ const char *arg)
+{
+ reqtimeout_srv_cfg *conf =
+ ap_get_module_config(cmd->server->module_config,
+ &reqtimeout_module);
+
+ while (*arg) {
+ char *word, *val;
+ const char *err;
+
+ word = ap_getword_conf(cmd->temp_pool, &arg);
+ val = strchr(word, '=');
+ if (!val) {
+ return "Invalid RequestReadTimeout parameter. Parameter must be "
+ "in the form 'key=value'";
+ }
+ else
+ *val++ = '\0';
+
+ err = set_reqtimeout_param(conf, cmd->pool, word, val);
+
+ if (err)
+ return apr_psprintf(cmd->temp_pool, "RequestReadTimeout: %s=%s: %s",
+ word, val, err);
+ }
+
+ return NULL;
+
+}
+
+static void reqtimeout_hooks(apr_pool_t *pool)
+{
+ /*
+ * mod_ssl is AP_FTYPE_CONNECTION + 5 and mod_reqtimeout needs to
+ * be called before mod_ssl for the handshake stage to catch SSL traffic.
+ */
+ ap_register_input_filter(reqtimeout_filter_name, reqtimeout_filter, NULL,
+ AP_FTYPE_CONNECTION + 8);
+
+ /*
+ * We need to pause timeout detection in between requests, for
+ * speculative and non-blocking reads, so between each outgoing EOR
+ * and the next pre_read_request call.
+ */
+ ap_register_output_filter(reqtimeout_filter_name, reqtimeout_eor, NULL,
+ AP_FTYPE_CONNECTION);
+
+ /*
+ * mod_reqtimeout needs to be called before ap_process_http_request (which
+ * is run at APR_HOOK_REALLY_LAST) but after all other protocol modules.
+ * This ensures that it only influences normal http connections and not
+ * e.g. mod_ftp. We still process it first though, for the handshake stage
+ * to work with/before mod_ssl, but since it's disabled by default it won't
+ * influence non-HTTP modules unless configured explicitly. Also, if
+ * mod_reqtimeout used the pre_connection hook, it would be inserted on
+ * mod_proxy's backend connections, and we don't want this.
+ */
+ ap_hook_process_connection(reqtimeout_init, NULL, NULL, APR_HOOK_FIRST);
+
+ ap_hook_pre_read_request(reqtimeout_before_header, NULL, NULL,
+ APR_HOOK_MIDDLE);
+ ap_hook_post_read_request(reqtimeout_before_body, NULL, NULL,
+ APR_HOOK_MIDDLE);
+
+#if MRT_DEFAULT_handshake_MIN_RATE
+ default_handshake_rate_factor = apr_time_from_sec(1) /
+ MRT_DEFAULT_handshake_MIN_RATE;
+#endif
+#if MRT_DEFAULT_header_MIN_RATE
+ default_header_rate_factor = apr_time_from_sec(1) /
+ MRT_DEFAULT_header_MIN_RATE;
+#endif
+#if MRT_DEFAULT_body_MIN_RATE
+ default_body_rate_factor = apr_time_from_sec(1) /
+ MRT_DEFAULT_body_MIN_RATE;
+#endif
+}
+
+static const command_rec reqtimeout_cmds[] = {
+ AP_INIT_RAW_ARGS("RequestReadTimeout", set_reqtimeouts, NULL, RSRC_CONF,
+ "Set various timeout parameters for TLS handshake and/or "
+ "reading request headers and body"),
+ {NULL}
+};
+
+AP_DECLARE_MODULE(reqtimeout) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-dir config structures */
+ NULL, /* merge per-dir config structures */
+ reqtimeout_create_srv_config, /* create per-server config structures */
+ reqtimeout_merge_srv_config, /* merge per-server config structures */
+ reqtimeout_cmds, /* table of config file commands */
+ reqtimeout_hooks
+};
diff --git a/modules/filters/mod_reqtimeout.dep b/modules/filters/mod_reqtimeout.dep
new file mode 100644
index 0000000..1b7679f
--- /dev/null
+++ b/modules/filters/mod_reqtimeout.dep
@@ -0,0 +1,58 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_reqtimeout.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_reqtimeout.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_version.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/filters/mod_reqtimeout.dsp b/modules/filters/mod_reqtimeout.dsp
new file mode 100644
index 0000000..078480d
--- /dev/null
+++ b/modules/filters/mod_reqtimeout.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_reqtimeout" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_reqtimeout - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_reqtimeout.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_reqtimeout.mak" CFG="mod_reqtimeout - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_reqtimeout - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_reqtimeout - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_reqtimeout - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "AP_RL_DECLARE_EXPORT" /Fd"Release\mod_reqtimeout_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_reqtimeout.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_reqtimeout.so" /d LONG_NAME="reqtimeout_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_reqtimeout.so" /base:@..\..\os\win32\BaseAddr.ref,mod_reqtimeout.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_reqtimeout.so" /base:@..\..\os\win32\BaseAddr.ref,mod_reqtimeout.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_reqtimeout.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_reqtimeout - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "AP_RL_DECLARE_EXPORT" /Fd"Debug\mod_reqtimeout_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_reqtimeout.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_reqtimeout.so" /d LONG_NAME="reqtimeout_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_reqtimeout.so" /base:@..\..\os\win32\BaseAddr.ref,mod_reqtimeout.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_reqtimeout.so" /base:@..\..\os\win32\BaseAddr.ref,mod_reqtimeout.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_reqtimeout.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_reqtimeout - Win32 Release"
+# Name "mod_reqtimeout - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_reqtimeout.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/filters/mod_reqtimeout.mak b/modules/filters/mod_reqtimeout.mak
new file mode 100644
index 0000000..459272e
--- /dev/null
+++ b/modules/filters/mod_reqtimeout.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_reqtimeout.dsp
+!IF "$(CFG)" == ""
+CFG=mod_reqtimeout - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_reqtimeout - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_reqtimeout - Win32 Release" && "$(CFG)" != "mod_reqtimeout - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_reqtimeout.mak" CFG="mod_reqtimeout - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_reqtimeout - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_reqtimeout - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_reqtimeout - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_reqtimeout.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_reqtimeout.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_reqtimeout.obj"
+ -@erase "$(INTDIR)\mod_reqtimeout.res"
+ -@erase "$(INTDIR)\mod_reqtimeout_src.idb"
+ -@erase "$(INTDIR)\mod_reqtimeout_src.pdb"
+ -@erase "$(OUTDIR)\mod_reqtimeout.exp"
+ -@erase "$(OUTDIR)\mod_reqtimeout.lib"
+ -@erase "$(OUTDIR)\mod_reqtimeout.pdb"
+ -@erase "$(OUTDIR)\mod_reqtimeout.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "AP_RL_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_reqtimeout_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_reqtimeout.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_reqtimeout.so" /d LONG_NAME="reqtimeout_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_reqtimeout.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_reqtimeout.pdb" /debug /out:"$(OUTDIR)\mod_reqtimeout.so" /implib:"$(OUTDIR)\mod_reqtimeout.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_reqtimeout.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_reqtimeout.obj" \
+ "$(INTDIR)\mod_reqtimeout.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_reqtimeout.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_reqtimeout.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_reqtimeout.so"
+ if exist .\Release\mod_reqtimeout.so.manifest mt.exe -manifest .\Release\mod_reqtimeout.so.manifest -outputresource:.\Release\mod_reqtimeout.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_reqtimeout - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_reqtimeout.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_reqtimeout.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_reqtimeout.obj"
+ -@erase "$(INTDIR)\mod_reqtimeout.res"
+ -@erase "$(INTDIR)\mod_reqtimeout_src.idb"
+ -@erase "$(INTDIR)\mod_reqtimeout_src.pdb"
+ -@erase "$(OUTDIR)\mod_reqtimeout.exp"
+ -@erase "$(OUTDIR)\mod_reqtimeout.lib"
+ -@erase "$(OUTDIR)\mod_reqtimeout.pdb"
+ -@erase "$(OUTDIR)\mod_reqtimeout.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "AP_RL_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_reqtimeout_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_reqtimeout.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_reqtimeout.so" /d LONG_NAME="reqtimeout_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_reqtimeout.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_reqtimeout.pdb" /debug /out:"$(OUTDIR)\mod_reqtimeout.so" /implib:"$(OUTDIR)\mod_reqtimeout.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_reqtimeout.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_reqtimeout.obj" \
+ "$(INTDIR)\mod_reqtimeout.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_reqtimeout.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_reqtimeout.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_reqtimeout.so"
+ if exist .\Debug\mod_reqtimeout.so.manifest mt.exe -manifest .\Debug\mod_reqtimeout.so.manifest -outputresource:.\Debug\mod_reqtimeout.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_reqtimeout.dep")
+!INCLUDE "mod_reqtimeout.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_reqtimeout.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_reqtimeout - Win32 Release" || "$(CFG)" == "mod_reqtimeout - Win32 Debug"
+
+!IF "$(CFG)" == "mod_reqtimeout - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\filters"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_reqtimeout - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\filters"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_reqtimeout - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\filters"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_reqtimeout - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\filters"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_reqtimeout - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\filters"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_reqtimeout - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\filters"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\filters"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_reqtimeout - Win32 Release"
+
+
+"$(INTDIR)\mod_reqtimeout.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_reqtimeout.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_reqtimeout.so" /d LONG_NAME="reqtimeout_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_reqtimeout - Win32 Debug"
+
+
+"$(INTDIR)\mod_reqtimeout.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_reqtimeout.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_reqtimeout.so" /d LONG_NAME="reqtimeout_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_reqtimeout.c
+
+"$(INTDIR)\mod_reqtimeout.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/filters/mod_request.c b/modules/filters/mod_request.c
new file mode 100644
index 0000000..1768edc
--- /dev/null
+++ b/modules/filters/mod_request.c
@@ -0,0 +1,393 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * mod_request.c --- HTTP routines to set aside or process request bodies.
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_buckets.h"
+#include "apr_lib.h"
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_protocol.h"
+#include "http_log.h" /* For errors detected in basic auth common
+ * support code... */
+#include "http_request.h"
+
+#include "mod_request.h"
+
+/* Handles for core filters */
+static ap_filter_rec_t *keep_body_input_filter_handle;
+static ap_filter_rec_t *kept_body_input_filter_handle;
+
+static apr_status_t bail_out_on_error(apr_bucket_brigade *bb,
+ ap_filter_t *f,
+ int http_error)
+{
+ apr_bucket *e;
+
+ apr_brigade_cleanup(bb);
+ e = ap_bucket_error_create(http_error,
+ NULL, f->r->pool,
+ f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ e = apr_bucket_eos_create(f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ return ap_pass_brigade(f->r->output_filters, bb);
+}
+
+typedef struct keep_body_filter_ctx {
+ apr_off_t remaining;
+ apr_off_t keep_body;
+} keep_body_ctx_t;
+
+/**
+ * This is the KEEP_BODY_INPUT filter for HTTP requests, for times when the
+ * body should be set aside for future use by other modules.
+ */
+static apr_status_t keep_body_filter(ap_filter_t *f, apr_bucket_brigade *b,
+ ap_input_mode_t mode,
+ apr_read_type_e block,
+ apr_off_t readbytes)
+{
+ apr_bucket *e;
+ keep_body_ctx_t *ctx = f->ctx;
+ apr_status_t rv;
+ apr_bucket *bucket;
+ apr_off_t len = 0;
+
+ if (!ctx) {
+ const char *lenp;
+ request_dir_conf *dconf = ap_get_module_config(f->r->per_dir_config,
+ &request_module);
+
+ /* must we step out of the way? */
+ if (!dconf->keep_body || f->r->kept_body) {
+ ap_remove_input_filter(f);
+ return ap_get_brigade(f->next, b, mode, block, readbytes);
+ }
+
+ f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx));
+
+ /* fail fast if the content length exceeds keep body */
+ lenp = apr_table_get(f->r->headers_in, "Content-Length");
+ if (lenp) {
+
+ /* Protects against over/underflow, non-digit chars in the
+ * string, leading plus/minus signs, trailing characters and
+ * a negative number.
+ */
+ if (!ap_parse_strict_length(&ctx->remaining, lenp)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, APLOGNO(01411)
+ "Invalid Content-Length '%s'", lenp);
+
+ ap_remove_input_filter(f);
+ return bail_out_on_error(b, f, HTTP_REQUEST_ENTITY_TOO_LARGE);
+ }
+
+ /* If we have a limit in effect and we know the C-L ahead of
+ * time, stop it here if it is invalid.
+ */
+ if (dconf->keep_body < ctx->remaining) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, APLOGNO(01412)
+ "Requested content-length of %" APR_OFF_T_FMT
+ " is larger than the configured limit"
+ " of %" APR_OFF_T_FMT, ctx->remaining, dconf->keep_body);
+ ap_remove_input_filter(f);
+ return bail_out_on_error(b, f, HTTP_REQUEST_ENTITY_TOO_LARGE);
+ }
+
+ }
+
+ f->r->kept_body = apr_brigade_create(f->r->pool, f->r->connection->bucket_alloc);
+ ctx->remaining = dconf->keep_body;
+ }
+
+ /* get the brigade from upstream, and read it in to get its length */
+ rv = ap_get_brigade(f->next, b, mode, block, readbytes);
+ if (rv == APR_SUCCESS) {
+ rv = apr_brigade_length(b, 1, &len);
+ }
+
+ /* does the length take us over the limit? */
+ if (APR_SUCCESS == rv && len > ctx->remaining) {
+ if (f->r->kept_body) {
+ apr_brigade_cleanup(f->r->kept_body);
+ f->r->kept_body = NULL;
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, APLOGNO(01413)
+ "Requested content-length of %" APR_OFF_T_FMT
+ " is larger than the configured limit"
+ " of %" APR_OFF_T_FMT, len, ctx->keep_body);
+ return bail_out_on_error(b, f, HTTP_REQUEST_ENTITY_TOO_LARGE);
+ }
+ ctx->remaining -= len;
+
+ /* pass any errors downstream */
+ if (rv != APR_SUCCESS) {
+ if (f->r->kept_body) {
+ apr_brigade_cleanup(f->r->kept_body);
+ f->r->kept_body = NULL;
+ }
+ return rv;
+ }
+
+ /* all is well, set aside the buckets */
+ for (bucket = APR_BRIGADE_FIRST(b);
+ bucket != APR_BRIGADE_SENTINEL(b);
+ bucket = APR_BUCKET_NEXT(bucket))
+ {
+ apr_bucket_copy(bucket, &e);
+ APR_BRIGADE_INSERT_TAIL(f->r->kept_body, e);
+ }
+
+ return APR_SUCCESS;
+}
+
+
+typedef struct kept_body_filter_ctx {
+ apr_off_t offset;
+ apr_off_t remaining;
+} kept_body_ctx_t;
+
+/**
+ * Initialisation of filter to handle a kept body on subrequests.
+ *
+ * If a body is to be reinserted into a subrequest, any chunking will have
+ * been removed from the body during storage. We need to change the request
+ * from Transfer-Encoding: chunked to an explicit Content-Length.
+ */
+static int kept_body_filter_init(ap_filter_t *f)
+{
+ apr_off_t length = 0;
+ request_rec *r = f->r;
+ apr_bucket_brigade *kept_body = r->kept_body;
+
+ if (kept_body) {
+ apr_table_unset(r->headers_in, "Transfer-Encoding");
+ apr_brigade_length(kept_body, 1, &length);
+ apr_table_setn(r->headers_in, "Content-Length", apr_off_t_toa(r->pool, length));
+ }
+
+ return OK;
+}
+
+/**
+ * Filter to handle a kept body on subrequests.
+ *
+ * If a body has been previously kept by the request, and if a subrequest wants
+ * to re-insert the body into the request, this input filter makes it happen.
+ */
+static apr_status_t kept_body_filter(ap_filter_t *f, apr_bucket_brigade *b,
+ ap_input_mode_t mode,
+ apr_read_type_e block,
+ apr_off_t readbytes)
+{
+ request_rec *r = f->r;
+ apr_bucket_brigade *kept_body = r->kept_body;
+ kept_body_ctx_t *ctx = f->ctx;
+ apr_bucket *ec, *e2;
+ apr_status_t rv;
+
+ /* just get out of the way of things we don't want. */
+ if (!kept_body || (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE)) {
+ return ap_get_brigade(f->next, b, mode, block, readbytes);
+ }
+
+ /* set up the context if it does not already exist */
+ if (!ctx) {
+ f->ctx = ctx = apr_palloc(f->r->pool, sizeof(*ctx));
+ ctx->offset = 0;
+ apr_brigade_length(kept_body, 1, &ctx->remaining);
+ }
+
+ /* kept_body is finished, send next filter */
+ if (ctx->remaining <= 0) {
+ return ap_get_brigade(f->next, b, mode, block, readbytes);
+ }
+
+ /* send all of the kept_body, but no more */
+ if (readbytes > ctx->remaining) {
+ readbytes = ctx->remaining;
+ }
+
+ /* send part of the kept_body */
+ if ((rv = apr_brigade_partition(kept_body, ctx->offset, &ec)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01414)
+ "apr_brigade_partition() failed on kept_body at %" APR_OFF_T_FMT, ctx->offset);
+ return rv;
+ }
+ if ((rv = apr_brigade_partition(kept_body, ctx->offset + readbytes, &e2)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01415)
+ "apr_brigade_partition() failed on kept_body at %" APR_OFF_T_FMT, ctx->offset + readbytes);
+ return rv;
+ }
+
+ do {
+ apr_bucket *foo;
+ const char *str;
+ apr_size_t len;
+
+ if (apr_bucket_copy(ec, &foo) != APR_SUCCESS) {
+ /* As above; this should not fail since the bucket has
+ * a known length, but just to be sure, this takes
+ * care of uncopyable buckets that do somehow manage
+ * to slip through. */
+ /* XXX: check for failure? */
+ apr_bucket_read(ec, &str, &len, APR_BLOCK_READ);
+ apr_bucket_copy(ec, &foo);
+ }
+ APR_BRIGADE_INSERT_TAIL(b, foo);
+ ec = APR_BUCKET_NEXT(ec);
+ } while (ec != e2);
+
+ ctx->remaining -= readbytes;
+ ctx->offset += readbytes;
+
+ return APR_SUCCESS;
+}
+
+/**
+ * Check whether this filter is not already present.
+ */
+static int request_is_filter_present(request_rec * r, ap_filter_rec_t *fn)
+{
+ ap_filter_t * f = r->input_filters;
+ while (f) {
+ if (f->frec == fn) {
+ return 1;
+ }
+ f = f->next;
+ }
+ return 0;
+}
+
+/**
+ * Insert filter hook.
+ *
+ * Add the KEEP_BODY filter to the request, if the admin wants to keep
+ * the body using the KeptBodySize directive.
+ *
+ * As a precaution, any pre-existing instances of either the kept_body or
+ * keep_body filters will be removed before the filter is added.
+ *
+ * @param r The request
+ */
+static void ap_request_insert_filter(request_rec * r)
+{
+ request_dir_conf *conf = ap_get_module_config(r->per_dir_config,
+ &request_module);
+
+ if (r->kept_body) {
+ if (!request_is_filter_present(r, kept_body_input_filter_handle)) {
+ ap_add_input_filter_handle(kept_body_input_filter_handle,
+ NULL, r, r->connection);
+ }
+ }
+ else if (conf->keep_body) {
+ if (!request_is_filter_present(r, kept_body_input_filter_handle)) {
+ ap_add_input_filter_handle(keep_body_input_filter_handle,
+ NULL, r, r->connection);
+ }
+ }
+}
+
+/*
+ * Remove the kept_body and keep_body filters from this specific request.
+ */
+static void ap_request_remove_filter(request_rec *r)
+{
+ ap_filter_t *f = r->input_filters;
+
+ while (f) {
+ if (f->frec->filter_func.in_func == kept_body_filter ||
+ f->frec->filter_func.in_func == keep_body_filter) {
+ ap_remove_input_filter(f);
+ }
+ f = f->next;
+ }
+}
+
+static void *create_request_dir_config(apr_pool_t *p, char *dummy)
+{
+ request_dir_conf *new =
+ (request_dir_conf *) apr_pcalloc(p, sizeof(request_dir_conf));
+
+ new->keep_body_set = 0; /* unset */
+ new->keep_body = 0; /* don't by default */
+
+ return (void *) new;
+}
+
+static void *merge_request_dir_config(apr_pool_t *p, void *basev, void *addv)
+{
+ request_dir_conf *new = (request_dir_conf *) apr_pcalloc(p, sizeof(request_dir_conf));
+ request_dir_conf *add = (request_dir_conf *) addv;
+ request_dir_conf *base = (request_dir_conf *) basev;
+
+ new->keep_body = (add->keep_body_set == 0) ? base->keep_body : add->keep_body;
+ new->keep_body_set = add->keep_body_set || base->keep_body_set;
+
+ return new;
+}
+
+static const char *set_kept_body_size(cmd_parms *cmd, void *dconf,
+ const char *arg)
+{
+ request_dir_conf *conf = dconf;
+ char *end = NULL;
+
+ if (APR_SUCCESS != apr_strtoff(&(conf->keep_body), arg, &end, 10)
+ || conf->keep_body < 0 || *end) {
+ return "KeptBodySize must be a valid size in bytes, or zero.";
+ }
+ conf->keep_body_set = 1;
+
+ return NULL;
+}
+
+static const command_rec request_cmds[] = {
+ AP_INIT_TAKE1("KeptBodySize", set_kept_body_size, NULL, ACCESS_CONF,
+ "Maximum size of request bodies kept aside for use by filters"),
+ { NULL }
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+ keep_body_input_filter_handle =
+ ap_register_input_filter(KEEP_BODY_FILTER, keep_body_filter,
+ NULL, AP_FTYPE_RESOURCE);
+ kept_body_input_filter_handle =
+ ap_register_input_filter(KEPT_BODY_FILTER, kept_body_filter,
+ kept_body_filter_init, AP_FTYPE_RESOURCE);
+ ap_hook_insert_filter(ap_request_insert_filter, NULL, NULL, APR_HOOK_LAST);
+ APR_REGISTER_OPTIONAL_FN(ap_request_insert_filter);
+ APR_REGISTER_OPTIONAL_FN(ap_request_remove_filter);
+}
+
+AP_DECLARE_MODULE(request) = {
+ STANDARD20_MODULE_STUFF,
+ create_request_dir_config, /* create per-directory config structure */
+ merge_request_dir_config, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ request_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/filters/mod_request.dep b/modules/filters/mod_request.dep
new file mode 100644
index 0000000..ae332a7
--- /dev/null
+++ b/modules/filters/mod_request.dep
@@ -0,0 +1,55 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_request.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_request.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_request.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/filters/mod_request.dsp b/modules/filters/mod_request.dsp
new file mode 100644
index 0000000..4032e00
--- /dev/null
+++ b/modules/filters/mod_request.dsp
@@ -0,0 +1,115 @@
+# Microsoft Developer Studio Project File - Name="mod_request" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_request - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_request.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_request.mak" CFG="mod_request - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_request - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_request - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_request - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_request_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_request.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_request.so" /d LONG_NAME="request_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_request.so" /base:@..\..\os\win32\BaseAddr.ref,mod_request.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_request.so" /base:@..\..\os\win32\BaseAddr.ref,mod_request.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_request.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_request - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_request_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_request.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_request.so" /d LONG_NAME="request_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_request.so" /base:@..\..\os\win32\BaseAddr.ref,mod_request.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_request.so" /base:@..\..\os\win32\BaseAddr.ref,mod_request.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_request.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_request - Win32 Release"
+# Name "mod_request - Win32 Debug"
+# Begin Source File
+
+SOURCE=..\..\include\mod_request.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\mod_request.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/filters/mod_request.mak b/modules/filters/mod_request.mak
new file mode 100644
index 0000000..7ceb603
--- /dev/null
+++ b/modules/filters/mod_request.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_request.dsp
+!IF "$(CFG)" == ""
+CFG=mod_request - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_request - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_request - Win32 Release" && "$(CFG)" != "mod_request - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_request.mak" CFG="mod_request - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_request - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_request - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_request - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_request.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_request.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_request.obj"
+ -@erase "$(INTDIR)\mod_request.res"
+ -@erase "$(INTDIR)\mod_request_src.idb"
+ -@erase "$(INTDIR)\mod_request_src.pdb"
+ -@erase "$(OUTDIR)\mod_request.exp"
+ -@erase "$(OUTDIR)\mod_request.lib"
+ -@erase "$(OUTDIR)\mod_request.pdb"
+ -@erase "$(OUTDIR)\mod_request.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_request_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_request.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_request.so" /d LONG_NAME="request_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_request.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_request.pdb" /debug /out:"$(OUTDIR)\mod_request.so" /implib:"$(OUTDIR)\mod_request.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_request.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_request.obj" \
+ "$(INTDIR)\mod_request.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_request.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_request.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_request.so"
+ if exist .\Release\mod_request.so.manifest mt.exe -manifest .\Release\mod_request.so.manifest -outputresource:.\Release\mod_request.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_request - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_request.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_request.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_request.obj"
+ -@erase "$(INTDIR)\mod_request.res"
+ -@erase "$(INTDIR)\mod_request_src.idb"
+ -@erase "$(INTDIR)\mod_request_src.pdb"
+ -@erase "$(OUTDIR)\mod_request.exp"
+ -@erase "$(OUTDIR)\mod_request.lib"
+ -@erase "$(OUTDIR)\mod_request.pdb"
+ -@erase "$(OUTDIR)\mod_request.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_request_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_request.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_request.so" /d LONG_NAME="request_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_request.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_request.pdb" /debug /out:"$(OUTDIR)\mod_request.so" /implib:"$(OUTDIR)\mod_request.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_request.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_request.obj" \
+ "$(INTDIR)\mod_request.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_request.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_request.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_request.so"
+ if exist .\Debug\mod_request.so.manifest mt.exe -manifest .\Debug\mod_request.so.manifest -outputresource:.\Debug\mod_request.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_request.dep")
+!INCLUDE "mod_request.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_request.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_request - Win32 Release" || "$(CFG)" == "mod_request - Win32 Debug"
+
+!IF "$(CFG)" == "mod_request - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\filters"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_request - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\filters"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_request - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\filters"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_request - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\filters"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_request - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\filters"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_request - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\filters"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\filters"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_request - Win32 Release"
+
+
+"$(INTDIR)\mod_request.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_request.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_request.so" /d LONG_NAME="request_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_request - Win32 Debug"
+
+
+"$(INTDIR)\mod_request.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_request.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_request.so" /d LONG_NAME="request_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_request.c
+
+"$(INTDIR)\mod_request.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/filters/mod_sed.c b/modules/filters/mod_sed.c
new file mode 100644
index 0000000..12cb04a
--- /dev/null
+++ b/modules/filters/mod_sed.c
@@ -0,0 +1,537 @@
+/*
+ * Copyright (c) 2005, 2008 Sun Microsystems, Inc. All Rights Reserved.
+ * Use is subject to license terms.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "apr_strings.h"
+#include "apr_general.h"
+#include "util_filter.h"
+#include "apr_buckets.h"
+#include "http_request.h"
+#include "libsed.h"
+
+static const char *sed_filter_name = "Sed";
+#define MODSED_OUTBUF_SIZE 8000
+#define MAX_TRANSIENT_BUCKETS 50
+
+typedef struct sed_expr_config
+{
+ sed_commands_t *sed_cmds;
+ const char *last_error;
+} sed_expr_config;
+
+typedef struct sed_config
+{
+ sed_expr_config output;
+ sed_expr_config input;
+} sed_config;
+
+/* Context for filter invocation for single HTTP request */
+typedef struct sed_filter_ctxt
+{
+ sed_eval_t eval;
+ ap_filter_t *f;
+ request_rec *r;
+ apr_bucket_brigade *bb;
+ apr_bucket_brigade *bbinp;
+ char *outbuf;
+ char *curoutbuf;
+ apr_size_t bufsize;
+ apr_pool_t *tpool;
+ int numbuckets;
+} sed_filter_ctxt;
+
+module AP_MODULE_DECLARE_DATA sed_module;
+
+/* This function will be call back from libsed functions if there is any error
+ * happened during execution of sed scripts
+ */
+static apr_status_t log_sed_errf(void *data, const char *error)
+{
+ request_rec *r = (request_rec *) data;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02998) "%s", error);
+ return APR_SUCCESS;
+}
+
+/* This function will be call back from libsed functions if there is any
+ * compilation error.
+ */
+static apr_status_t sed_compile_errf(void *data, const char *error)
+{
+ sed_expr_config *sed_cfg = (sed_expr_config *) data;
+ sed_cfg->last_error = error;
+ return APR_SUCCESS;
+}
+
+/* clear the temporary pool (used for transient buckets)
+ */
+static void clear_ctxpool(sed_filter_ctxt* ctx)
+{
+ apr_pool_clear(ctx->tpool);
+ ctx->outbuf = NULL;
+ ctx->curoutbuf = NULL;
+ ctx->numbuckets = 0;
+}
+
+/* alloc_outbuf
+ * allocate output buffer
+ */
+static void alloc_outbuf(sed_filter_ctxt* ctx)
+{
+ ctx->outbuf = apr_palloc(ctx->tpool, ctx->bufsize + 1);
+ ctx->curoutbuf = ctx->outbuf;
+}
+
+/* append_bucket
+ * Allocate a new bucket from buf and sz and append to ctx->bb
+ */
+static apr_status_t append_bucket(sed_filter_ctxt* ctx, char* buf, apr_size_t sz)
+{
+ apr_status_t status = APR_SUCCESS;
+ apr_bucket *b;
+ if (ctx->tpool == ctx->r->pool) {
+ /* We are not using transient bucket */
+ b = apr_bucket_pool_create(buf, sz, ctx->r->pool,
+ ctx->r->connection->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
+ }
+ else {
+ /* We are using transient bucket */
+ b = apr_bucket_transient_create(buf, sz,
+ ctx->r->connection->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
+ ctx->numbuckets++;
+ if (ctx->numbuckets >= MAX_TRANSIENT_BUCKETS) {
+ b = apr_bucket_flush_create(ctx->r->connection->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
+ status = ap_pass_brigade(ctx->f->next, ctx->bb);
+ apr_brigade_cleanup(ctx->bb);
+ clear_ctxpool(ctx);
+ }
+ }
+ return status;
+}
+
+/*
+ * flush_output_buffer
+ * Flush the output data (stored in ctx->outbuf)
+ */
+static apr_status_t flush_output_buffer(sed_filter_ctxt *ctx)
+{
+ apr_size_t size = ctx->curoutbuf - ctx->outbuf;
+ char *out;
+ apr_status_t status = APR_SUCCESS;
+ if ((ctx->outbuf == NULL) || (size <=0))
+ return status;
+ out = apr_pmemdup(ctx->tpool, ctx->outbuf, size);
+ status = append_bucket(ctx, out, size);
+ ctx->curoutbuf = ctx->outbuf;
+ return status;
+}
+
+/* This is a call back function. When libsed wants to generate the output,
+ * this function will be invoked.
+ */
+static apr_status_t sed_write_output(void *dummy, char *buf, apr_size_t sz)
+{
+ /* dummy is basically filter context. Context is passed during invocation
+ * of sed_eval_buffer
+ */
+ apr_size_t remainbytes = 0;
+ apr_status_t status = APR_SUCCESS;
+ sed_filter_ctxt *ctx = (sed_filter_ctxt *) dummy;
+ if (ctx->outbuf == NULL) {
+ alloc_outbuf(ctx);
+ }
+ remainbytes = ctx->bufsize - (ctx->curoutbuf - ctx->outbuf);
+ if (sz >= remainbytes) {
+ if (remainbytes > 0) {
+ memcpy(ctx->curoutbuf, buf, remainbytes);
+ buf += remainbytes;
+ sz -= remainbytes;
+ ctx->curoutbuf += remainbytes;
+ }
+ /* buffer is now full */
+ status = append_bucket(ctx, ctx->outbuf, ctx->bufsize);
+ if (status == APR_SUCCESS) {
+ /* if size is bigger than the allocated buffer directly add to output
+ * brigade */
+ if (sz >= ctx->bufsize) {
+ char* newbuf = apr_pmemdup(ctx->tpool, buf, sz);
+ status = append_bucket(ctx, newbuf, sz);
+ if (status == APR_SUCCESS) {
+ /* old buffer is now used so allocate new buffer */
+ alloc_outbuf(ctx);
+ }
+ else {
+ clear_ctxpool(ctx);
+ }
+ }
+ else {
+ /* old buffer is now used so allocate new buffer */
+ alloc_outbuf(ctx);
+ memcpy(ctx->curoutbuf, buf, sz);
+ ctx->curoutbuf += sz;
+ }
+ }
+ else {
+ clear_ctxpool(ctx);
+ }
+ }
+ else {
+ memcpy(ctx->curoutbuf, buf, sz);
+ ctx->curoutbuf += sz;
+ }
+ return status;
+}
+
+/* Compile a sed expression. Compiled context is saved in sed_cfg->sed_cmds.
+ * Memory required for compilation context is allocated from cmd->pool.
+ */
+static apr_status_t compile_sed_expr(sed_expr_config *sed_cfg,
+ cmd_parms *cmd,
+ const char *expr)
+{
+ apr_status_t status = APR_SUCCESS;
+
+ if (!sed_cfg->sed_cmds) {
+ sed_commands_t *sed_cmds;
+ sed_cmds = apr_pcalloc(cmd->pool, sizeof(sed_commands_t));
+ status = sed_init_commands(sed_cmds, sed_compile_errf, sed_cfg,
+ cmd->pool);
+ if (status != APR_SUCCESS) {
+ sed_destroy_commands(sed_cmds);
+ return status;
+ }
+ sed_cfg->sed_cmds = sed_cmds;
+ }
+ status = sed_compile_string(sed_cfg->sed_cmds, expr);
+ if (status != APR_SUCCESS) {
+ sed_destroy_commands(sed_cfg->sed_cmds);
+ sed_cfg->sed_cmds = NULL;
+ }
+ return status;
+}
+
+/* sed eval cleanup function */
+static apr_status_t sed_eval_cleanup(void *data)
+{
+ sed_eval_t *eval = (sed_eval_t *) data;
+ sed_destroy_eval(eval);
+ return APR_SUCCESS;
+}
+
+/* Initialize sed filter context. If successful then context is set in f->ctx
+ */
+static apr_status_t init_context(ap_filter_t *f, sed_expr_config *sed_cfg, int usetpool)
+{
+ apr_status_t status;
+ sed_filter_ctxt* ctx;
+ request_rec *r = f->r;
+ /* Create the context. Call sed_init_eval. libsed will generated
+ * output by calling sed_write_output and generates any error by
+ * invoking log_sed_errf.
+ */
+ ctx = apr_pcalloc(r->pool, sizeof(sed_filter_ctxt));
+ ctx->r = r;
+ ctx->bb = NULL;
+ ctx->numbuckets = 0;
+ ctx->f = f;
+ status = sed_init_eval(&ctx->eval, sed_cfg->sed_cmds, log_sed_errf,
+ r, &sed_write_output, r->pool);
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+ apr_pool_cleanup_register(r->pool, &ctx->eval, sed_eval_cleanup,
+ apr_pool_cleanup_null);
+ ctx->bufsize = MODSED_OUTBUF_SIZE;
+ if (usetpool) {
+ apr_pool_create(&(ctx->tpool), r->pool);
+ apr_pool_tag(ctx->tpool, "sed_tpool");
+ }
+ else {
+ ctx->tpool = r->pool;
+ }
+ alloc_outbuf(ctx);
+ f->ctx = ctx;
+ return APR_SUCCESS;
+}
+
+/* Entry function for Sed output filter */
+static apr_status_t sed_response_filter(ap_filter_t *f,
+ apr_bucket_brigade *bb)
+{
+ apr_bucket *b;
+ apr_status_t status = APR_SUCCESS;
+ sed_config *cfg = ap_get_module_config(f->r->per_dir_config,
+ &sed_module);
+ sed_filter_ctxt *ctx = f->ctx;
+ sed_expr_config *sed_cfg = &cfg->output;
+
+ if ((sed_cfg == NULL) || (sed_cfg->sed_cmds == NULL)) {
+ /* No sed expressions */
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ if (ctx == NULL) {
+
+ if (APR_BUCKET_IS_EOS(APR_BRIGADE_FIRST(bb))) {
+ /* no need to run sed filter for Head requests */
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ status = init_context(f, sed_cfg, 1);
+ if (status != APR_SUCCESS)
+ return status;
+ ctx = f->ctx;
+ apr_table_unset(f->r->headers_out, "Content-Length");
+
+ ctx->bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
+ }
+
+ /* Here is the main logic. Iterate through all the buckets, read the
+ * content of the bucket, call sed_eval_buffer on the data.
+ * sed_eval_buffer will read the data line by line, run filters on each
+ * line. sed_eval_buffer will generates the output by calling
+ * sed_write_output which will add the output to ctx->bb. At the end of
+ * the loop, ctx->bb is passed to the next filter in chain. At the end of
+ * the data, if new line is not found then sed_eval_buffer will store the
+ * data in its own buffer.
+ *
+ * Once eos bucket is found then sed_finalize_eval will flush the rest of
+ * the data. If there is no new line in last line of data, new line is
+ * appended (that is a solaris sed behavior). libsed's internal memory for
+ * evaluation is allocated on request's pool so it will be cleared once
+ * request is over.
+ *
+ * If flush bucket is found then append the flush bucket to ctx->bb
+ * and pass it to next filter. There may be some data which will still be
+ * in sed's internal buffer which can't be flushed until new line
+ * character is arrived.
+ */
+ while (!APR_BRIGADE_EMPTY(bb)) {
+ b = APR_BRIGADE_FIRST(bb);
+ if (APR_BUCKET_IS_EOS(b)) {
+ /* Now clean up the internal sed buffer */
+ sed_finalize_eval(&ctx->eval, ctx);
+ status = flush_output_buffer(ctx);
+ if (status != APR_SUCCESS) {
+ break;
+ }
+ /* Move the eos bucket to ctx->bb brigade */
+ APR_BUCKET_REMOVE(b);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
+ }
+ else if (APR_BUCKET_IS_FLUSH(b)) {
+ status = flush_output_buffer(ctx);
+ if (status != APR_SUCCESS) {
+ break;
+ }
+ /* Move the flush bucket to ctx->bb brigade */
+ APR_BUCKET_REMOVE(b);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
+ }
+ else {
+ if (!APR_BUCKET_IS_METADATA(b)) {
+ const char *buf = NULL;
+ apr_size_t bytes = 0;
+
+ status = apr_bucket_read(b, &buf, &bytes, APR_BLOCK_READ);
+ if (status == APR_SUCCESS) {
+ status = sed_eval_buffer(&ctx->eval, buf, bytes, ctx);
+ }
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, f->r, APLOGNO(10394) "error evaluating sed on output");
+ break;
+ }
+ }
+ apr_bucket_delete(b);
+ }
+ }
+ if (status == APR_SUCCESS) {
+ status = flush_output_buffer(ctx);
+ }
+ if (!APR_BRIGADE_EMPTY(ctx->bb)) {
+ if (status == APR_SUCCESS) {
+ status = ap_pass_brigade(f->next, ctx->bb);
+ }
+ apr_brigade_cleanup(ctx->bb);
+ }
+ clear_ctxpool(ctx);
+ return status;
+}
+
+/* Entry function for Sed input filter */
+static apr_status_t sed_request_filter(ap_filter_t *f,
+ apr_bucket_brigade *bb,
+ ap_input_mode_t mode,
+ apr_read_type_e block,
+ apr_off_t readbytes)
+{
+ sed_config *cfg = ap_get_module_config(f->r->per_dir_config,
+ &sed_module);
+ sed_filter_ctxt *ctx = f->ctx;
+ apr_status_t status;
+ apr_bucket_brigade *bbinp;
+ sed_expr_config *sed_cfg = &cfg->input;
+
+ if (mode != AP_MODE_READBYTES) {
+ return ap_get_brigade(f->next, bb, mode, block, readbytes);
+ }
+
+ if ((sed_cfg == NULL) || (sed_cfg->sed_cmds == NULL)) {
+ /* No sed expression */
+ return ap_get_brigade(f->next, bb, mode, block, readbytes);
+ }
+
+ if (!ctx) {
+ if (!ap_is_initial_req(f->r)) {
+ ap_remove_input_filter(f);
+ /* XXX : Should we filter the sub requests too */
+ return ap_get_brigade(f->next, bb, mode, block, readbytes);
+ }
+ status = init_context(f, sed_cfg, 0);
+ if (status != APR_SUCCESS)
+ return status;
+ ctx = f->ctx;
+ ctx->bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
+ ctx->bbinp = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
+ }
+
+ bbinp = ctx->bbinp;
+
+ /* Here is the logic :
+ * Read the readbytes data from next level fiter into bbinp. Loop through
+ * the buckets in bbinp and read the data from buckets and invoke
+ * sed_eval_buffer on the data. libsed will generate its output using
+ * sed_write_output which will add data in ctx->bb. Do it until it have
+ * at least one bucket in ctx->bb. At the end of data eos bucket
+ * should be there.
+ *
+ * Once eos bucket is seen, then invoke sed_finalize_eval to clear the
+ * output. If the last byte of data is not a new line character then sed
+ * will add a new line to the data that is default sed behaviour. Note
+ * that using this filter with POST data, caller may not expect this
+ * behaviour.
+ *
+ * If next level fiter generate the flush bucket, we can't do much about
+ * it. If we want to return the flush bucket in brigade bb (to the caller)
+ * the question is where to add it?
+ */
+ while (APR_BRIGADE_EMPTY(ctx->bb)) {
+ apr_bucket *b;
+
+ /* read the bytes from next level filter */
+ apr_brigade_cleanup(bbinp);
+ status = ap_get_brigade(f->next, bbinp, mode, block, readbytes);
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+ for (b = APR_BRIGADE_FIRST(bbinp); b != APR_BRIGADE_SENTINEL(bbinp);
+ b = APR_BUCKET_NEXT(b)) {
+ const char *buf = NULL;
+ apr_size_t bytes;
+
+ if (APR_BUCKET_IS_EOS(b)) {
+ /* eos bucket. Clear the internal sed buffers */
+ sed_finalize_eval(&ctx->eval, ctx);
+ flush_output_buffer(ctx);
+ APR_BUCKET_REMOVE(b);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
+ break;
+ }
+ else if (APR_BUCKET_IS_FLUSH(b)) {
+ /* What should we do with flush bucket */
+ continue;
+ }
+ if (apr_bucket_read(b, &buf, &bytes, APR_BLOCK_READ)
+ == APR_SUCCESS) {
+ status = sed_eval_buffer(&ctx->eval, buf, bytes, ctx);
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, f->r, APLOGNO(10395) "error evaluating sed on input");
+ return status;
+ }
+ flush_output_buffer(ctx);
+ }
+ }
+ }
+
+ if (!APR_BRIGADE_EMPTY(ctx->bb)) {
+ apr_bucket *b = NULL;
+
+ if (apr_brigade_partition(ctx->bb, readbytes, &b) == APR_INCOMPLETE) {
+ APR_BRIGADE_CONCAT(bb, ctx->bb);
+ }
+ else {
+ APR_BRIGADE_CONCAT(bb, ctx->bb);
+ apr_brigade_split_ex(bb, b, ctx->bb);
+ }
+ }
+ return APR_SUCCESS;
+}
+
+static const char *sed_add_expr(cmd_parms *cmd, void *cfg, const char *arg)
+{
+ int offset = (int) (long) cmd->info;
+ sed_expr_config *sed_cfg =
+ (sed_expr_config *) (((char *) cfg) + offset);
+ if (compile_sed_expr(sed_cfg, cmd, arg) != APR_SUCCESS) {
+ return apr_psprintf(cmd->temp_pool,
+ "Failed to compile sed expression. %s",
+ sed_cfg->last_error);
+ }
+ return NULL;
+}
+
+static void *create_sed_dir_config(apr_pool_t *p, char *s)
+{
+ sed_config *cfg = apr_pcalloc(p, sizeof(sed_config));
+ return cfg;
+}
+
+static const command_rec sed_filter_cmds[] = {
+ AP_INIT_TAKE1("OutputSed", sed_add_expr,
+ (void *) APR_OFFSETOF(sed_config, output),
+ ACCESS_CONF,
+ "Sed regular expression for Response"),
+ AP_INIT_TAKE1("InputSed", sed_add_expr,
+ (void *) APR_OFFSETOF(sed_config, input),
+ ACCESS_CONF,
+ "Sed regular expression for Request"),
+ {NULL}
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_register_output_filter(sed_filter_name, sed_response_filter, NULL,
+ AP_FTYPE_RESOURCE);
+ ap_register_input_filter(sed_filter_name, sed_request_filter, NULL,
+ AP_FTYPE_RESOURCE);
+}
+
+AP_DECLARE_MODULE(sed) = {
+ STANDARD20_MODULE_STUFF,
+ create_sed_dir_config, /* dir config creater */
+ NULL, /* dir merger --- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ sed_filter_cmds, /* command table */
+ register_hooks /* register hooks */
+};
diff --git a/modules/filters/mod_sed.dep b/modules/filters/mod_sed.dep
new file mode 100644
index 0000000..6f1ef2b
--- /dev/null
+++ b/modules/filters/mod_sed.dep
@@ -0,0 +1,109 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_sed.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_sed.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\libsed.h"\
+
+
+.\regexp.c : \
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\libsed.h"\
+ ".\regexp.h"\
+ ".\sed.h"\
+
+
+.\sed0.c : \
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\libsed.h"\
+ ".\regexp.h"\
+ ".\sed.h"\
+
+
+.\sed1.c : \
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\libsed.h"\
+ ".\regexp.h"\
+ ".\sed.h"\
+
diff --git a/modules/filters/mod_sed.dsp b/modules/filters/mod_sed.dsp
new file mode 100644
index 0000000..5b1d642
--- /dev/null
+++ b/modules/filters/mod_sed.dsp
@@ -0,0 +1,135 @@
+# Microsoft Developer Studio Project File - Name="mod_sed" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_sed - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_sed.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_sed.mak" CFG="mod_sed - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_sed - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_sed - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_sed - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_sed_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_sed.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_sed.so" /d LONG_NAME="sed_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_sed.so" /base:@..\..\os\win32\BaseAddr.ref,mod_sed.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_sed.so" /base:@..\..\os\win32\BaseAddr.ref,mod_sed.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_sed.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_sed - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_sed_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_sed.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_sed.so" /d LONG_NAME="sed_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_sed.so" /base:@..\..\os\win32\BaseAddr.ref,mod_sed.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_sed.so" /base:@..\..\os\win32\BaseAddr.ref,mod_sed.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_sed.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_sed - Win32 Release"
+# Name "mod_sed - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\libsed.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\mod_sed.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\regexp.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\regexp.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\sed.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\sed0.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\sed1.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/filters/mod_sed.mak b/modules/filters/mod_sed.mak
new file mode 100644
index 0000000..c997b23
--- /dev/null
+++ b/modules/filters/mod_sed.mak
@@ -0,0 +1,380 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_sed.dsp
+!IF "$(CFG)" == ""
+CFG=mod_sed - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_sed - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_sed - Win32 Release" && "$(CFG)" != "mod_sed - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_sed.mak" CFG="mod_sed - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_sed - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_sed - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_sed - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_sed.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_sed.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_sed.obj"
+ -@erase "$(INTDIR)\mod_sed.res"
+ -@erase "$(INTDIR)\mod_sed_src.idb"
+ -@erase "$(INTDIR)\mod_sed_src.pdb"
+ -@erase "$(INTDIR)\regexp.obj"
+ -@erase "$(INTDIR)\sed0.obj"
+ -@erase "$(INTDIR)\sed1.obj"
+ -@erase "$(OUTDIR)\mod_sed.exp"
+ -@erase "$(OUTDIR)\mod_sed.lib"
+ -@erase "$(OUTDIR)\mod_sed.pdb"
+ -@erase "$(OUTDIR)\mod_sed.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_sed_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_sed.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_sed.so" /d LONG_NAME="sed_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_sed.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_sed.pdb" /debug /out:"$(OUTDIR)\mod_sed.so" /implib:"$(OUTDIR)\mod_sed.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_sed.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_sed.obj" \
+ "$(INTDIR)\regexp.obj" \
+ "$(INTDIR)\sed0.obj" \
+ "$(INTDIR)\sed1.obj" \
+ "$(INTDIR)\mod_sed.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_sed.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_sed.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_sed.so"
+ if exist .\Release\mod_sed.so.manifest mt.exe -manifest .\Release\mod_sed.so.manifest -outputresource:.\Release\mod_sed.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_sed - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_sed.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_sed.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_sed.obj"
+ -@erase "$(INTDIR)\mod_sed.res"
+ -@erase "$(INTDIR)\mod_sed_src.idb"
+ -@erase "$(INTDIR)\mod_sed_src.pdb"
+ -@erase "$(INTDIR)\regexp.obj"
+ -@erase "$(INTDIR)\sed0.obj"
+ -@erase "$(INTDIR)\sed1.obj"
+ -@erase "$(OUTDIR)\mod_sed.exp"
+ -@erase "$(OUTDIR)\mod_sed.lib"
+ -@erase "$(OUTDIR)\mod_sed.pdb"
+ -@erase "$(OUTDIR)\mod_sed.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_sed_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_sed.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_sed.so" /d LONG_NAME="sed_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_sed.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_sed.pdb" /debug /out:"$(OUTDIR)\mod_sed.so" /implib:"$(OUTDIR)\mod_sed.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_sed.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_sed.obj" \
+ "$(INTDIR)\regexp.obj" \
+ "$(INTDIR)\sed0.obj" \
+ "$(INTDIR)\sed1.obj" \
+ "$(INTDIR)\mod_sed.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_sed.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_sed.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_sed.so"
+ if exist .\Debug\mod_sed.so.manifest mt.exe -manifest .\Debug\mod_sed.so.manifest -outputresource:.\Debug\mod_sed.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_sed.dep")
+!INCLUDE "mod_sed.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_sed.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_sed - Win32 Release" || "$(CFG)" == "mod_sed - Win32 Debug"
+
+!IF "$(CFG)" == "mod_sed - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\filters"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_sed - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\filters"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_sed - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\filters"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_sed - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\filters"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_sed - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\filters"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_sed - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\filters"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\filters"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_sed - Win32 Release"
+
+
+"$(INTDIR)\mod_sed.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_sed.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_sed.so" /d LONG_NAME="sed_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_sed - Win32 Debug"
+
+
+"$(INTDIR)\mod_sed.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_sed.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_sed.so" /d LONG_NAME="sed_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_sed.c
+
+"$(INTDIR)\mod_sed.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\regexp.c
+
+"$(INTDIR)\regexp.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\sed0.c
+
+"$(INTDIR)\sed0.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\sed1.c
+
+"$(INTDIR)\sed1.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/filters/mod_substitute.c b/modules/filters/mod_substitute.c
new file mode 100644
index 0000000..d454bf3
--- /dev/null
+++ b/modules/filters/mod_substitute.c
@@ -0,0 +1,766 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * mod_substitute.c: Perform content rewriting on the fly
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "apr_general.h"
+#include "apr_strings.h"
+#include "apr_strmatch.h"
+#include "apr_lib.h"
+#include "util_filter.h"
+#include "util_varbuf.h"
+#include "apr_buckets.h"
+#include "http_request.h"
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+/*
+ * We want to limit the memory usage in a way that is predictable.
+ * Therefore we limit the resulting length of the line.
+ * This is the default value.
+ */
+#define AP_SUBST_MAX_LINE_LENGTH (1024*1024)
+
+static const char substitute_filter_name[] = "SUBSTITUTE";
+
+module AP_MODULE_DECLARE_DATA substitute_module;
+
+typedef struct subst_pattern_t {
+ const apr_strmatch_pattern *pattern;
+ const ap_regex_t *regexp;
+ const char *replacement;
+ apr_size_t replen;
+ apr_size_t patlen;
+ int flatten;
+ const char *from;
+} subst_pattern_t;
+
+typedef struct {
+ apr_array_header_t *patterns;
+ apr_size_t max_line_length;
+ int max_line_length_set;
+ int inherit_before;
+} subst_dir_conf;
+
+typedef struct {
+ apr_bucket_brigade *linebb;
+ apr_bucket_brigade *linesbb;
+ apr_bucket_brigade *passbb;
+ apr_bucket_brigade *pattbb;
+ apr_pool_t *tpool;
+} substitute_module_ctx;
+
+static void *create_substitute_dcfg(apr_pool_t *p, char *d)
+{
+ subst_dir_conf *dcfg =
+ (subst_dir_conf *) apr_palloc(p, sizeof(subst_dir_conf));
+
+ dcfg->patterns = apr_array_make(p, 10, sizeof(subst_pattern_t));
+ dcfg->max_line_length = AP_SUBST_MAX_LINE_LENGTH;
+ dcfg->max_line_length_set = 0;
+ dcfg->inherit_before = -1;
+ return dcfg;
+}
+
+static void *merge_substitute_dcfg(apr_pool_t *p, void *basev, void *overv)
+{
+ subst_dir_conf *a =
+ (subst_dir_conf *) apr_palloc(p, sizeof(subst_dir_conf));
+ subst_dir_conf *base = (subst_dir_conf *) basev;
+ subst_dir_conf *over = (subst_dir_conf *) overv;
+
+ a->inherit_before = (over->inherit_before != -1)
+ ? over->inherit_before
+ : base->inherit_before;
+ /* SubstituteInheritBefore wasn't the default behavior until 2.5.x,
+ * and may be re-disabled as desired; the original default behavior
+ * was to apply inherited subst patterns after locally scoped patterns.
+ * In later 2.2 and 2.4 versions, SubstituteInheritBefore may be toggled
+ * 'on' to follow the corrected/expected behavior, without violating POLS.
+ */
+ if (a->inherit_before == 1) {
+ a->patterns = apr_array_append(p, base->patterns,
+ over->patterns);
+ }
+ else {
+ a->patterns = apr_array_append(p, over->patterns,
+ base->patterns);
+ }
+ a->max_line_length = over->max_line_length_set ?
+ over->max_line_length : base->max_line_length;
+ a->max_line_length_set = over->max_line_length_set
+ | base->max_line_length_set;
+ return a;
+}
+
+#define AP_MAX_BUCKETS 1000
+
+#define SEDRMPATBCKT(b, offset, tmp_b, patlen) do { \
+ apr_bucket_split(b, offset); \
+ tmp_b = APR_BUCKET_NEXT(b); \
+ apr_bucket_split(tmp_b, patlen); \
+ b = APR_BUCKET_NEXT(tmp_b); \
+ apr_bucket_delete(tmp_b); \
+} while (0)
+
+#define CAP2LINEMAX(n) ((n) < (apr_size_t)200 ? (int)(n) : 200)
+
+static apr_status_t do_pattmatch(ap_filter_t *f, apr_bucket *inb,
+ apr_bucket_brigade *mybb,
+ apr_pool_t *pool)
+{
+ int i;
+ int force_quick = 0;
+ ap_regmatch_t regm[AP_MAX_REG_MATCH];
+ apr_size_t bytes;
+ apr_size_t len;
+ const char *buff;
+ struct ap_varbuf vb;
+ apr_bucket *b;
+ apr_bucket *tmp_b;
+
+ subst_dir_conf *cfg =
+ (subst_dir_conf *) ap_get_module_config(f->r->per_dir_config,
+ &substitute_module);
+ subst_pattern_t *script;
+
+ APR_BRIGADE_INSERT_TAIL(mybb, inb);
+ ap_varbuf_init(pool, &vb, 0);
+
+ script = (subst_pattern_t *) cfg->patterns->elts;
+ /*
+ * Simple optimization. If we only have one pattern, then
+ * we can safely avoid the overhead of flattening
+ */
+ if (cfg->patterns->nelts == 1) {
+ force_quick = 1;
+ }
+ for (i = 0; i < cfg->patterns->nelts; i++) {
+ for (b = APR_BRIGADE_FIRST(mybb);
+ b != APR_BRIGADE_SENTINEL(mybb);
+ b = APR_BUCKET_NEXT(b)) {
+ if (APR_BUCKET_IS_METADATA(b)) {
+ /*
+ * we should NEVER see this, because we should never
+ * be passed any, but "handle" it just in case.
+ */
+ continue;
+ }
+ if (apr_bucket_read(b, &buff, &bytes, APR_BLOCK_READ)
+ == APR_SUCCESS) {
+ int have_match = 0;
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
+ "Line read (%" APR_SIZE_T_FMT " bytes): %.*s",
+ bytes, CAP2LINEMAX(bytes), buff);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
+ "Replacing %s:'%s' by '%s'",
+ script->pattern ? "string" :
+ script->regexp ? "regex" :
+ "unknown",
+ script->from, script->replacement);
+
+ vb.strlen = 0;
+ if (script->pattern) {
+ const char *repl;
+ /*
+ * space_left counts how many bytes we have left until the
+ * line length reaches max_line_length.
+ */
+ apr_size_t space_left = cfg->max_line_length;
+ apr_size_t repl_len = strlen(script->replacement);
+ while ((repl = apr_strmatch(script->pattern, buff, bytes)))
+ {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
+ "Matching found, result: '%s'",
+ script->replacement);
+ have_match = 1;
+ /* get offset into buff for pattern */
+ len = (apr_size_t) (repl - buff);
+ if (script->flatten && !force_quick) {
+ /*
+ * We are flattening the buckets here, meaning
+ * that we don't do the fast bucket splits.
+ * Instead we copy over what the buckets would
+ * contain and use them. This is slow, since we
+ * are constanting allocing space and copying
+ * strings.
+ */
+ if (vb.strlen + len + repl_len > cfg->max_line_length)
+ return APR_ENOMEM;
+ ap_varbuf_strmemcat(&vb, buff, len);
+ ap_varbuf_strmemcat(&vb, script->replacement, repl_len);
+ }
+ else {
+ /*
+ * The string before the match but after the
+ * previous match (if any) has length 'len'.
+ * Check if we still have space for this string and
+ * the replacement string.
+ */
+ if (space_left < len + repl_len)
+ return APR_ENOMEM;
+ space_left -= len + repl_len;
+ /*
+ * We now split off the string before the match
+ * as its own bucket, then isolate the matched
+ * string and delete it.
+ */
+ SEDRMPATBCKT(b, len, tmp_b, script->patlen);
+ /*
+ * Finally, we create a bucket that contains the
+ * replacement...
+ */
+ tmp_b = apr_bucket_transient_create(script->replacement,
+ script->replen,
+ f->r->connection->bucket_alloc);
+ /* ... and insert it */
+ APR_BUCKET_INSERT_BEFORE(b, tmp_b);
+ }
+ /* now we need to adjust buff for all these changes */
+ len += script->patlen;
+ bytes -= len;
+ buff += len;
+ }
+ if (have_match) {
+ if (script->flatten && !force_quick) {
+ /* XXX: we should check for AP_MAX_BUCKETS here and
+ * XXX: call ap_pass_brigade accordingly
+ */
+ char *copy = ap_varbuf_pdup(pool, &vb, NULL, 0,
+ buff, bytes, &len);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
+ "New line (%" APR_SIZE_T_FMT " bytes): %.*s",
+ len, CAP2LINEMAX(len), copy);
+ tmp_b = apr_bucket_pool_create(copy, len, pool,
+ f->r->connection->bucket_alloc);
+ APR_BUCKET_INSERT_BEFORE(b, tmp_b);
+ apr_bucket_delete(b);
+ b = tmp_b;
+ }
+ else {
+ /*
+ * We want the behaviour to be predictable.
+ * Therefore we try to always error out if the
+ * line length is larger than the limit,
+ * regardless of the content of the line. So,
+ * let's check if the remaining non-matching
+ * string does not exceed the limit.
+ */
+ if (space_left < b->length)
+ return APR_ENOMEM;
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
+ "New line (%" APR_SIZE_T_FMT " bytes): %.*s",
+ bytes, CAP2LINEMAX(bytes), buff);
+ }
+ }
+ }
+ else if (script->regexp) {
+ int left = bytes;
+ const char *pos = buff;
+ char *repl;
+ apr_size_t space_left = cfg->max_line_length;
+ while (!ap_regexec_len(script->regexp, pos, left,
+ AP_MAX_REG_MATCH, regm, 0)) {
+ apr_status_t rv;
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
+ "Matching found");
+ have_match = 1;
+ if (script->flatten && !force_quick) {
+ /* check remaining buffer size */
+ /* Note that the last param in ap_varbuf_regsub below
+ * must stay positive. If it gets 0, it would mean
+ * unlimited space available. */
+ if (vb.strlen + regm[0].rm_so >= cfg->max_line_length)
+ return APR_ENOMEM;
+ /* copy bytes before the match */
+ if (regm[0].rm_so > 0)
+ ap_varbuf_strmemcat(&vb, pos, regm[0].rm_so);
+ /* add replacement string, last argument is unsigned! */
+ rv = ap_varbuf_regsub(&vb, script->replacement, pos,
+ AP_MAX_REG_MATCH, regm,
+ cfg->max_line_length - vb.strlen);
+ if (rv != APR_SUCCESS)
+ return rv;
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
+ "Result: '%s'", vb.buf);
+ }
+ else {
+ apr_size_t repl_len;
+ /* account for string before the match */
+ if (space_left <= regm[0].rm_so)
+ return APR_ENOMEM;
+ space_left -= regm[0].rm_so;
+ rv = ap_pregsub_ex(pool, &repl,
+ script->replacement, pos,
+ AP_MAX_REG_MATCH, regm,
+ space_left);
+ if (rv != APR_SUCCESS)
+ return rv;
+ repl_len = strlen(repl);
+ space_left -= repl_len;
+ len = (apr_size_t) (regm[0].rm_eo - regm[0].rm_so);
+ SEDRMPATBCKT(b, regm[0].rm_so, tmp_b, len);
+ tmp_b = apr_bucket_transient_create(repl, repl_len,
+ f->r->connection->bucket_alloc);
+ APR_BUCKET_INSERT_BEFORE(b, tmp_b);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
+ "Result: '%s'", repl);
+ }
+ /*
+ * reset to past what we just did. pos now maps to b
+ * again
+ */
+ pos += regm[0].rm_eo;
+ left -= regm[0].rm_eo;
+ }
+ if (have_match && script->flatten && !force_quick) {
+ char *copy;
+ /* Copy result plus the part after the last match into
+ * a bucket.
+ */
+ copy = ap_varbuf_pdup(pool, &vb, NULL, 0, pos, left,
+ &len);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
+ "New line (%" APR_SIZE_T_FMT " bytes): %.*s",
+ len, CAP2LINEMAX(len), copy);
+ tmp_b = apr_bucket_pool_create(copy, len, pool,
+ f->r->connection->bucket_alloc);
+ APR_BUCKET_INSERT_BEFORE(b, tmp_b);
+ apr_bucket_delete(b);
+ b = tmp_b;
+ }
+ }
+ else {
+ ap_assert(0);
+ continue;
+ }
+ }
+ }
+ script++;
+ }
+ ap_varbuf_free(&vb);
+ return APR_SUCCESS;
+}
+
+static apr_status_t substitute_filter(ap_filter_t *f, apr_bucket_brigade *bb)
+{
+ apr_size_t bytes;
+ apr_size_t len;
+ apr_size_t fbytes;
+ const char *buff;
+ const char *nl = NULL;
+ char *bflat;
+ apr_bucket *b;
+ apr_bucket *tmp_b;
+ apr_bucket_brigade *tmp_bb = NULL;
+ apr_status_t rv;
+ subst_dir_conf *cfg =
+ (subst_dir_conf *) ap_get_module_config(f->r->per_dir_config,
+ &substitute_module);
+
+ substitute_module_ctx *ctx = f->ctx;
+
+ /*
+ * First time around? Create the saved bb that we used for each pass
+ * through. Note that we can also get here when we explicitly clear ctx,
+ * for error handling
+ */
+ if (!ctx) {
+ f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx));
+ /*
+ * Create all the temporary brigades we need and reuse them to avoid
+ * creating them over and over again from r->pool which would cost a
+ * lot of memory in some cases.
+ */
+ ctx->linebb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
+ ctx->linesbb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
+ ctx->pattbb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
+ /*
+ * Everything to be passed to the next filter goes in
+ * here, our pass brigade.
+ */
+ ctx->passbb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
+ /* Create our temporary pool only once */
+ apr_pool_create(&(ctx->tpool), f->r->pool);
+ apr_pool_tag(ctx->tpool, "substitute_tpool");
+ apr_table_unset(f->r->headers_out, "Content-Length");
+ }
+
+ /*
+ * Shortcircuit processing
+ */
+ if (APR_BRIGADE_EMPTY(bb))
+ return APR_SUCCESS;
+
+ /*
+ * Here's the concept:
+ * Read in the data and look for newlines. Once we
+ * find a full "line", add it to our working brigade.
+ * If we've finished reading the brigade and we have
+ * any left over data (not a "full" line), store that
+ * for the next pass.
+ *
+ * Note: anything stored in ctx->linebb for sure does not have
+ * a newline char, so we don't concat that bb with the
+ * new bb, since we would spending time searching for the newline
+ * in data we know it doesn't exist. So instead, we simply scan
+ * our current bb and, if we see a newline, prepend ctx->linebb
+ * to the front of it. This makes the code much less straight-
+ * forward (otherwise we could APR_BRIGADE_CONCAT(ctx->linebb, bb)
+ * and just scan for newlines and not bother with needing to know
+ * when ctx->linebb needs to be reset) but also faster. We'll take
+ * the speed.
+ *
+ * Note: apr_brigade_split_line would be nice here, but we
+ * really can't use it since we need more control and we want
+ * to re-use already read bucket data.
+ *
+ * See mod_include if still confused :)
+ */
+
+ while ((b = APR_BRIGADE_FIRST(bb)) && (b != APR_BRIGADE_SENTINEL(bb))) {
+ if (APR_BUCKET_IS_EOS(b)) {
+ /*
+ * if we see the EOS, then we need to pass along everything we
+ * have. But if the ctx->linebb isn't empty, then we need to add
+ * that to the end of what we'll be passing.
+ */
+ if (!APR_BRIGADE_EMPTY(ctx->linebb)) {
+ rv = apr_brigade_pflatten(ctx->linebb, &bflat,
+ &fbytes, ctx->tpool);
+ if (rv != APR_SUCCESS)
+ goto err;
+ if (fbytes > cfg->max_line_length) {
+ rv = APR_ENOMEM;
+ goto err;
+ }
+ tmp_b = apr_bucket_transient_create(bflat, fbytes,
+ f->r->connection->bucket_alloc);
+ rv = do_pattmatch(f, tmp_b, ctx->pattbb, ctx->tpool);
+ if (rv != APR_SUCCESS)
+ goto err;
+ APR_BRIGADE_CONCAT(ctx->passbb, ctx->pattbb);
+ apr_brigade_cleanup(ctx->linebb);
+ }
+ APR_BUCKET_REMOVE(b);
+ APR_BRIGADE_INSERT_TAIL(ctx->passbb, b);
+ }
+ /*
+ * No need to handle FLUSH buckets separately as we call
+ * ap_pass_brigade anyway at the end of the loop.
+ */
+ else if (APR_BUCKET_IS_METADATA(b)) {
+ APR_BUCKET_REMOVE(b);
+ APR_BRIGADE_INSERT_TAIL(ctx->passbb, b);
+ }
+ else {
+ /*
+ * We have actual "data" so read in as much as we can and start
+ * scanning and splitting from our read buffer
+ */
+ rv = apr_bucket_read(b, &buff, &bytes, APR_BLOCK_READ);
+ if (rv != APR_SUCCESS || bytes == 0) {
+ apr_bucket_delete(b);
+ }
+ else {
+ int num = 0;
+ while (bytes > 0) {
+ nl = memchr(buff, '\n', bytes);
+ if (nl) {
+ len = (apr_size_t) (nl - buff) + 1;
+ /* split *after* the newline */
+ apr_bucket_split(b, len);
+ /*
+ * We've likely read more data, so bypass rereading
+ * bucket data and continue scanning through this
+ * buffer
+ */
+ bytes -= len;
+ buff += len;
+ /*
+ * we need b to be updated for future potential
+ * splitting
+ */
+ tmp_b = APR_BUCKET_NEXT(b);
+ APR_BUCKET_REMOVE(b);
+ /*
+ * Hey, we found a newline! Don't forget the old
+ * stuff that needs to be added to the front. So we
+ * add the split bucket to the end, flatten the whole
+ * bb, morph the whole shebang into a bucket which is
+ * then added to the tail of the newline bb.
+ */
+ if (!APR_BRIGADE_EMPTY(ctx->linebb)) {
+ APR_BRIGADE_INSERT_TAIL(ctx->linebb, b);
+ rv = apr_brigade_pflatten(ctx->linebb, &bflat,
+ &fbytes, ctx->tpool);
+ if (rv != APR_SUCCESS)
+ goto err;
+ if (fbytes > cfg->max_line_length) {
+ /* Avoid pflattening further lines, we will
+ * abort later on anyway.
+ */
+ rv = APR_ENOMEM;
+ goto err;
+ }
+ b = apr_bucket_transient_create(bflat, fbytes,
+ f->r->connection->bucket_alloc);
+ apr_brigade_cleanup(ctx->linebb);
+ }
+ rv = do_pattmatch(f, b, ctx->pattbb, ctx->tpool);
+ if (rv != APR_SUCCESS)
+ goto err;
+ /*
+ * Count how many buckets we have in ctx->passbb
+ * so far. Yes, this is correct we count ctx->passbb
+ * and not ctx->pattbb as we do not reset num on every
+ * iteration.
+ */
+ for (b = APR_BRIGADE_FIRST(ctx->pattbb);
+ b != APR_BRIGADE_SENTINEL(ctx->pattbb);
+ b = APR_BUCKET_NEXT(b)) {
+ num++;
+ }
+ APR_BRIGADE_CONCAT(ctx->passbb, ctx->pattbb);
+ /*
+ * If the number of buckets in ctx->passbb reaches an
+ * "insane" level, we consume much memory for all the
+ * buckets as such. So lets flush them down the chain
+ * in this case and thus clear ctx->passbb. This frees
+ * the buckets memory for further processing.
+ * Usually this condition should not become true, but
+ * it is a safety measure for edge cases.
+ */
+ if (num > AP_MAX_BUCKETS) {
+ b = apr_bucket_flush_create(
+ f->r->connection->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(ctx->passbb, b);
+ rv = ap_pass_brigade(f->next, ctx->passbb);
+ apr_brigade_cleanup(ctx->passbb);
+ num = 0;
+ apr_pool_clear(ctx->tpool);
+ if (rv != APR_SUCCESS)
+ goto err;
+ }
+ b = tmp_b;
+ }
+ else {
+ /*
+ * no newline in whatever is left of this buffer so
+ * tuck data away and get next bucket
+ */
+ APR_BUCKET_REMOVE(b);
+ APR_BRIGADE_INSERT_TAIL(ctx->linebb, b);
+ bytes = 0;
+ }
+ }
+ }
+ }
+ if (!APR_BRIGADE_EMPTY(ctx->passbb)) {
+ rv = ap_pass_brigade(f->next, ctx->passbb);
+ apr_brigade_cleanup(ctx->passbb);
+ if (rv != APR_SUCCESS)
+ goto err;
+ }
+ apr_pool_clear(ctx->tpool);
+ }
+
+ /* Anything left we want to save/setaside for the next go-around */
+ if (!APR_BRIGADE_EMPTY(ctx->linebb)) {
+ /*
+ * Provide ap_save_brigade with an existing empty brigade
+ * (ctx->linesbb) to avoid creating a new one.
+ */
+ ap_save_brigade(f, &(ctx->linesbb), &(ctx->linebb), f->r->pool);
+ tmp_bb = ctx->linebb;
+ ctx->linebb = ctx->linesbb;
+ ctx->linesbb = tmp_bb;
+ }
+
+ return APR_SUCCESS;
+err:
+ if (rv == APR_ENOMEM)
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, APLOGNO(01328) "Line too long, URI %s",
+ f->r->uri);
+ apr_pool_clear(ctx->tpool);
+ return rv;
+}
+
+static const char *set_pattern(cmd_parms *cmd, void *cfg, const char *line)
+{
+ char *from = NULL;
+ char *to = NULL;
+ char *flags = NULL;
+ char *ourline;
+ char delim;
+ subst_pattern_t *nscript;
+ int is_pattern = 0;
+ int ignore_case = 0;
+ int flatten = 1;
+ ap_regex_t *r = NULL;
+
+ if (apr_tolower(*line) != 's') {
+ return "Bad Substitute format, must be an s/// pattern";
+ }
+ ourline = apr_pstrdup(cmd->pool, line);
+ delim = *++ourline;
+ if (delim)
+ from = ++ourline;
+ if (from) {
+ if (*ourline != delim) {
+ while (*++ourline && *ourline != delim);
+ }
+ if (*ourline) {
+ *ourline = '\0';
+ to = ++ourline;
+ }
+ }
+ if (to) {
+ if (*ourline != delim) {
+ while (*++ourline && *ourline != delim);
+ }
+ if (*ourline) {
+ *ourline = '\0';
+ flags = ++ourline;
+ }
+ }
+
+ if (!delim || !from || !*from || !to) {
+ return "Bad Substitute format, must be a complete s/// pattern";
+ }
+
+ if (flags) {
+ while (*flags) {
+ delim = apr_tolower(*flags); /* re-use */
+ if (delim == 'i')
+ ignore_case = 1;
+ else if (delim == 'n')
+ is_pattern = 1;
+ else if (delim == 'f')
+ flatten = 1;
+ else if (delim == 'q')
+ flatten = 0;
+ else
+ return "Bad Substitute flag, only s///[infq] are supported";
+ flags++;
+ }
+ }
+
+ /* first see if we can compile the regex */
+ if (!is_pattern) {
+ int flags = AP_REG_NO_DEFAULT
+ | (ap_regcomp_get_default_cflags() & AP_REG_DOLLAR_ENDONLY)
+ | (ignore_case ? AP_REG_ICASE : 0);
+ r = ap_pregcomp(cmd->pool, from, flags);
+ if (!r)
+ return "Substitute could not compile regex";
+ }
+ nscript = apr_array_push(((subst_dir_conf *) cfg)->patterns);
+ /* init the new entries */
+ nscript->pattern = NULL;
+ nscript->regexp = NULL;
+ nscript->replacement = NULL;
+ nscript->patlen = 0;
+ nscript->from = from;
+
+ if (is_pattern) {
+ nscript->patlen = strlen(from);
+ nscript->pattern = apr_strmatch_precompile(cmd->pool, from,
+ !ignore_case);
+ }
+ else {
+ nscript->regexp = r;
+ }
+
+ nscript->replacement = to;
+ nscript->replen = strlen(to);
+ nscript->flatten = flatten;
+
+ return NULL;
+}
+
+#define KBYTE 1024
+#define MBYTE 1048576
+#define GBYTE 1073741824
+
+static const char *set_max_line_length(cmd_parms *cmd, void *cfg, const char *arg)
+{
+ subst_dir_conf *dcfg = (subst_dir_conf *)cfg;
+ apr_off_t max;
+ char *end;
+ apr_status_t rv;
+
+ rv = apr_strtoff(&max, arg, &end, 10);
+ if (rv == APR_SUCCESS) {
+ if ((*end == 'K' || *end == 'k') && !end[1]) {
+ max *= KBYTE;
+ }
+ else if ((*end == 'M' || *end == 'm') && !end[1]) {
+ max *= MBYTE;
+ }
+ else if ((*end == 'G' || *end == 'g') && !end[1]) {
+ max *= GBYTE;
+ }
+ else if (*end && /* neither empty nor [Bb] */
+ ((*end != 'B' && *end != 'b') || end[1])) {
+ rv = APR_EGENERAL;
+ }
+ }
+
+ if (rv != APR_SUCCESS || max < 0)
+ {
+ return "SubstituteMaxLineLength must be a non-negative integer optionally "
+ "suffixed with 'b', 'k', 'm' or 'g'.";
+ }
+ dcfg->max_line_length = (apr_size_t)max;
+ dcfg->max_line_length_set = 1;
+ return NULL;
+}
+
+#define PROTO_FLAGS AP_FILTER_PROTO_CHANGE|AP_FILTER_PROTO_CHANGE_LENGTH
+static void register_hooks(apr_pool_t *pool)
+{
+ ap_register_output_filter(substitute_filter_name, substitute_filter,
+ NULL, AP_FTYPE_RESOURCE);
+}
+
+static const command_rec substitute_cmds[] = {
+ AP_INIT_TAKE1("Substitute", set_pattern, NULL, OR_FILEINFO,
+ "Pattern to filter the response content (s/foo/bar/[inf])"),
+ AP_INIT_TAKE1("SubstituteMaxLineLength", set_max_line_length, NULL, OR_FILEINFO,
+ "Maximum line length"),
+ AP_INIT_FLAG("SubstituteInheritBefore", ap_set_flag_slot,
+ (void *)APR_OFFSETOF(subst_dir_conf, inherit_before), OR_FILEINFO,
+ "Apply inherited patterns before those of the current context"),
+ {NULL}
+};
+
+AP_DECLARE_MODULE(substitute) = {
+ STANDARD20_MODULE_STUFF,
+ create_substitute_dcfg, /* dir config creater */
+ merge_substitute_dcfg, /* dir merger --- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ substitute_cmds, /* command table */
+ register_hooks /* register hooks */
+};
diff --git a/modules/filters/mod_substitute.dep b/modules/filters/mod_substitute.dep
new file mode 100644
index 0000000..b501b34
--- /dev/null
+++ b/modules/filters/mod_substitute.dep
@@ -0,0 +1,53 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_substitute.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_substitute.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_varbuf.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_strmatch.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/filters/mod_substitute.dsp b/modules/filters/mod_substitute.dsp
new file mode 100644
index 0000000..b0bd34f
--- /dev/null
+++ b/modules/filters/mod_substitute.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_substitute" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_substitute - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_substitute.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_substitute.mak" CFG="mod_substitute - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_substitute - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_substitute - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_substitute - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_substitute_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_substitute.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_substitute.so" /d LONG_NAME="substitute_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_substitute.so" /base:@..\..\os\win32\BaseAddr.ref,mod_substitute.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_substitute.so" /base:@..\..\os\win32\BaseAddr.ref,mod_substitute.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_substitute.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_substitute - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_substitute_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_substitute.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_substitute.so" /d LONG_NAME="substitute_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_substitute.so" /base:@..\..\os\win32\BaseAddr.ref,mod_substitute.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_substitute.so" /base:@..\..\os\win32\BaseAddr.ref,mod_substitute.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_substitute.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_substitute - Win32 Release"
+# Name "mod_substitute - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_substitute.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/filters/mod_substitute.mak b/modules/filters/mod_substitute.mak
new file mode 100644
index 0000000..f1538e8
--- /dev/null
+++ b/modules/filters/mod_substitute.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_substitute.dsp
+!IF "$(CFG)" == ""
+CFG=mod_substitute - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to mod_substitute - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "mod_substitute - Win32 Release" && "$(CFG)" != "mod_substitute - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_substitute.mak" CFG="mod_substitute - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_substitute - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_substitute - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_substitute - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_substitute.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_substitute.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_substitute.obj"
+ -@erase "$(INTDIR)\mod_substitute.res"
+ -@erase "$(INTDIR)\mod_substitute_src.idb"
+ -@erase "$(INTDIR)\mod_substitute_src.pdb"
+ -@erase "$(OUTDIR)\mod_substitute.exp"
+ -@erase "$(OUTDIR)\mod_substitute.lib"
+ -@erase "$(OUTDIR)\mod_substitute.pdb"
+ -@erase "$(OUTDIR)\mod_substitute.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_substitute_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_substitute.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_substitute.so" /d LONG_NAME="substitute_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_substitute.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_substitute.pdb" /debug /out:"$(OUTDIR)\mod_substitute.so" /implib:"$(OUTDIR)\mod_substitute.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_substitute.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_substitute.obj" \
+ "$(INTDIR)\mod_substitute.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_substitute.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_substitute.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_substitute.so"
+ if exist .\Release\mod_substitute.so.manifest mt.exe -manifest .\Release\mod_substitute.so.manifest -outputresource:.\Release\mod_substitute.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_substitute - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_substitute.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_substitute.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_substitute.obj"
+ -@erase "$(INTDIR)\mod_substitute.res"
+ -@erase "$(INTDIR)\mod_substitute_src.idb"
+ -@erase "$(INTDIR)\mod_substitute_src.pdb"
+ -@erase "$(OUTDIR)\mod_substitute.exp"
+ -@erase "$(OUTDIR)\mod_substitute.lib"
+ -@erase "$(OUTDIR)\mod_substitute.pdb"
+ -@erase "$(OUTDIR)\mod_substitute.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_substitute_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL"
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_substitute.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_substitute.so" /d LONG_NAME="substitute_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_substitute.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_substitute.pdb" /debug /out:"$(OUTDIR)\mod_substitute.so" /implib:"$(OUTDIR)\mod_substitute.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_substitute.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_substitute.obj" \
+ "$(INTDIR)\mod_substitute.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_substitute.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_substitute.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_substitute.so"
+ if exist .\Debug\mod_substitute.so.manifest mt.exe -manifest .\Debug\mod_substitute.so.manifest -outputresource:.\Debug\mod_substitute.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_substitute.dep")
+!INCLUDE "mod_substitute.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_substitute.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_substitute - Win32 Release" || "$(CFG)" == "mod_substitute - Win32 Debug"
+
+!IF "$(CFG)" == "mod_substitute - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\filters"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_substitute - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\filters"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_substitute - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\filters"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_substitute - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\filters"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_substitute - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\filters"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_substitute - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\filters"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\filters"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_substitute - Win32 Release"
+
+
+"$(INTDIR)\mod_substitute.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_substitute.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_substitute.so" /d LONG_NAME="substitute_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_substitute - Win32 Debug"
+
+
+"$(INTDIR)\mod_substitute.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_substitute.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_substitute.so" /d LONG_NAME="substitute_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_substitute.c
+
+"$(INTDIR)\mod_substitute.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/filters/mod_xml2enc.c b/modules/filters/mod_xml2enc.c
new file mode 100644
index 0000000..76046b4
--- /dev/null
+++ b/modules/filters/mod_xml2enc.c
@@ -0,0 +1,676 @@
+/* Copyright (c) 2007-11, WebThing Ltd
+ * Copyright (c) 2011-, The Apache Software Foundation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if defined(WIN32)
+#define XML2ENC_DECLARE_EXPORT
+#endif
+
+#include <ctype.h>
+
+/* libxml2 includes unicode/[...].h files which uses C++ comments */
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic warning "-Wcomment"
+#elif defined(__GNUC__)
+#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic warning "-Wcomment"
+#endif
+#endif
+
+/* libxml2 */
+#include <libxml/encoding.h>
+
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#elif defined(__GNUC__)
+#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
+#pragma GCC diagnostic pop
+#endif
+#endif
+
+#include "http_protocol.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "apr_strings.h"
+#include "apr_xlate.h"
+
+#include "apr_optional.h"
+#include "mod_xml2enc.h"
+
+module AP_MODULE_DECLARE_DATA xml2enc_module;
+
+#define BUFLEN 8192
+#define BUF_MIN 4096
+#define APR_BRIGADE_DO(b,bb) for (b = APR_BRIGADE_FIRST(bb); \
+ b != APR_BRIGADE_SENTINEL(bb); \
+ b = APR_BUCKET_NEXT(b))
+
+#define ENC_INITIALISED 0x100
+#define ENC_SEEN_EOS 0x200
+#define ENC_SKIPTO ENCIO_SKIPTO
+
+#define HAVE_ENCODING(enc) \
+ (((enc)!=XML_CHAR_ENCODING_NONE)&&((enc)!=XML_CHAR_ENCODING_ERROR))
+
+/*
+ * XXX: Check all those ap_assert()s and replace those that should not happen
+ * XXX: with AP_DEBUG_ASSERT and those that may happen with proper error
+ * XXX: handling.
+ */
+typedef struct {
+ xmlCharEncoding xml2enc;
+ char* buf;
+ apr_size_t bytes;
+ apr_xlate_t* convset;
+ unsigned int flags;
+ apr_off_t bblen;
+ apr_bucket_brigade* bbnext;
+ apr_bucket_brigade* bbsave;
+ const char* encoding;
+} xml2ctx;
+
+typedef struct {
+ const char* default_charset;
+ xmlCharEncoding default_encoding;
+ apr_array_header_t* skipto;
+} xml2cfg;
+
+typedef struct {
+ const char* val;
+} tattr;
+
+static ap_regex_t* seek_meta_ctype;
+static ap_regex_t* seek_charset;
+
+static apr_status_t xml2enc_filter(request_rec* r, const char* enc,
+ unsigned int mode)
+{
+ /* set up a ready-initialised ctx to convert to enc, and insert filter */
+ apr_xlate_t* convset;
+ apr_status_t rv;
+ unsigned int flags = (mode ^ ENCIO);
+ if ((mode & ENCIO) == ENCIO_OUTPUT) {
+ rv = apr_xlate_open(&convset, enc, "UTF-8", r->pool);
+ flags |= ENC_INITIALISED;
+ }
+ else if ((mode & ENCIO) == ENCIO_INPUT) {
+ rv = apr_xlate_open(&convset, "UTF-8", enc, r->pool);
+ flags |= ENC_INITIALISED;
+ }
+ else if ((mode & ENCIO) == ENCIO_INPUT_CHECKS) {
+ convset = NULL;
+ rv = APR_SUCCESS; /* we'll initialise later by sniffing */
+ }
+ else {
+ rv = APR_EGENERAL;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01426)
+ "xml2enc: bad mode %x", mode);
+ }
+ if (rv == APR_SUCCESS) {
+ xml2ctx* ctx = apr_pcalloc(r->pool, sizeof(xml2ctx));
+ ctx->flags = flags;
+ if (flags & ENC_INITIALISED) {
+ ctx->convset = convset;
+ ctx->bblen = BUFLEN;
+ ctx->buf = apr_palloc(r->pool, (apr_size_t)ctx->bblen);
+ }
+ ap_add_output_filter("xml2enc", ctx, r, r->connection);
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01427)
+ "xml2enc: Charset %s not supported.", enc) ;
+ }
+ return rv;
+}
+
+/* This needs to operate only when we're using htmlParser */
+/* Different modules may apply different rules here. Ho, hum. */
+static void fix_skipto(request_rec* r, xml2ctx* ctx)
+{
+ apr_status_t rv;
+ xml2cfg* cfg = ap_get_module_config(r->per_dir_config, &xml2enc_module);
+ if ((cfg->skipto != NULL) && (ctx->flags & ENC_SKIPTO)) {
+ int found = 0;
+ char* p = ap_strchr(ctx->buf, '<');
+ tattr* starts = (tattr*) cfg->skipto->elts;
+ while (!found && p && *p) {
+ int i;
+ for (i = 0; i < cfg->skipto->nelts; ++i) {
+ if (!strncasecmp(p+1, starts[i].val, strlen(starts[i].val))) {
+ /* found a starting element. Strip all that comes before. */
+ apr_bucket* b;
+ apr_bucket* bstart;
+ rv = apr_brigade_partition(ctx->bbsave, (p-ctx->buf),
+ &bstart);
+ ap_assert(rv == APR_SUCCESS);
+ while (b = APR_BRIGADE_FIRST(ctx->bbsave), b != bstart) {
+ apr_bucket_delete(b);
+ }
+ ctx->bytes -= (p-ctx->buf);
+ ctx->buf = p ;
+ found = 1;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01428)
+ "Skipped to first <%s> element",
+ starts[i].val) ;
+ break;
+ }
+ }
+ p = ap_strchr(p+1, '<');
+ }
+ if (p == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01429)
+ "Failed to find start of recognised HTML!");
+ }
+ }
+}
+static void sniff_encoding(request_rec* r, xml2ctx* ctx)
+{
+ xml2cfg* cfg = NULL; /* initialise to shut compiler warnings up */
+ char* p ;
+ apr_bucket* cutb;
+ apr_bucket* cute;
+ apr_bucket* b;
+ ap_regmatch_t match[2] ;
+ apr_status_t rv;
+ const char* ctype = r->content_type;
+
+ if (ctype) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01430)
+ "Content-Type is %s", ctype) ;
+
+ /* If we've got it in the HTTP headers, there's nothing to do */
+ if (ctype && (p = ap_strcasestr(ctype, "charset=") , p != NULL)) {
+ p += 8 ;
+ if (ctx->encoding = apr_pstrndup(r->pool, p, strcspn(p, " ;") ),
+ ctx->encoding) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01431)
+ "Got charset %s from HTTP headers", ctx->encoding) ;
+ ctx->xml2enc = xmlParseCharEncoding(ctx->encoding);
+ }
+ }
+ }
+
+ /* to sniff, first we look for BOM */
+ if (ctx->xml2enc == XML_CHAR_ENCODING_NONE) {
+ ctx->xml2enc = xmlDetectCharEncoding((const xmlChar*)ctx->buf,
+ ctx->bytes);
+ if (HAVE_ENCODING(ctx->xml2enc)) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01432)
+ "Got charset from XML rules.") ;
+ ctx->encoding = xmlGetCharEncodingName(ctx->xml2enc);
+ }
+ }
+
+ /* If none of the above, look for a META-thingey */
+ /* also we're probably about to invalidate it, so we remove it. */
+ if (ap_regexec(seek_meta_ctype, ctx->buf, 1, match, 0) == 0 ) {
+ /* get markers on the start and end of the match */
+ rv = apr_brigade_partition(ctx->bbsave, match[0].rm_eo, &cute);
+ ap_assert(rv == APR_SUCCESS);
+ rv = apr_brigade_partition(ctx->bbsave, match[0].rm_so, &cutb);
+ ap_assert(rv == APR_SUCCESS);
+ /* now set length of useful buf for start-of-data hooks */
+ ctx->bytes = match[0].rm_so;
+ if (ctx->encoding == NULL) {
+ p = apr_pstrndup(r->pool, ctx->buf + match[0].rm_so,
+ match[0].rm_eo - match[0].rm_so) ;
+ if (ap_regexec(seek_charset, p, 2, match, 0) == 0) {
+ if (ctx->encoding = apr_pstrndup(r->pool, p+match[1].rm_so,
+ match[1].rm_eo - match[1].rm_so),
+ ctx->encoding) {
+ ctx->xml2enc = xmlParseCharEncoding(ctx->encoding);
+ if (HAVE_ENCODING(ctx->xml2enc))
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01433)
+ "Got charset %s from HTML META", ctx->encoding) ;
+ }
+ }
+ }
+
+ /* cut out the <meta> we're invalidating */
+ while (cutb != cute) {
+ b = APR_BUCKET_NEXT(cutb);
+ apr_bucket_delete(cutb);
+ cutb = b;
+ }
+ /* and leave a string */
+ ctx->buf[ctx->bytes] = 0;
+ }
+
+ /* either it's set to something we found or it's still the default */
+ /* Aaargh! libxml2 has undocumented <META-crap> support. So this fails
+ * if metafix is not active. Have to make it conditional.
+ *
+ * No, that means no-metafix breaks things. Deal immediately with
+ * this particular instance of metafix.
+ */
+ if (!HAVE_ENCODING(ctx->xml2enc)) {
+ cfg = ap_get_module_config(r->per_dir_config, &xml2enc_module);
+ if (!ctx->encoding) {
+ ctx->encoding = cfg->default_charset?cfg->default_charset:"ISO-8859-1";
+ }
+ /* Unsupported charset. Can we get (iconv) support through apr_xlate? */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01434)
+ "Charset %s not supported by libxml2; trying apr_xlate",
+ ctx->encoding);
+ if (apr_xlate_open(&ctx->convset, "UTF-8", ctx->encoding, r->pool)
+ == APR_SUCCESS) {
+ ctx->xml2enc = XML_CHAR_ENCODING_UTF8 ;
+ } else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01435)
+ "Charset %s not supported. Consider aliasing it?",
+ ctx->encoding) ;
+ }
+ }
+
+ if (!HAVE_ENCODING(ctx->xml2enc)) {
+ /* Use configuration default as a last resort */
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01436)
+ "No usable charset information; using configuration default");
+ ctx->xml2enc = (cfg->default_encoding == XML_CHAR_ENCODING_NONE)
+ ? XML_CHAR_ENCODING_8859_1 : cfg->default_encoding ;
+ }
+ if (ctype && ctx->encoding) {
+ if (ap_regexec(seek_charset, ctype, 2, match, 0)) {
+ r->content_type = apr_pstrcat(r->pool, ctype, ";charset=utf-8",
+ NULL);
+ } else {
+ char* str = apr_palloc(r->pool, strlen(r->content_type) + 13
+ - (match[0].rm_eo - match[0].rm_so) + 1);
+ memcpy(str, r->content_type, match[1].rm_so);
+ memcpy(str + match[1].rm_so, "utf-8", 5);
+ strcpy(str + match[1].rm_so + 5, r->content_type+match[1].rm_eo);
+ r->content_type = str;
+ }
+ }
+}
+
+static apr_status_t xml2enc_filter_init(ap_filter_t* f)
+{
+ xml2ctx* ctx;
+ if (!f->ctx) {
+ xml2cfg* cfg = ap_get_module_config(f->r->per_dir_config,
+ &xml2enc_module);
+ f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(xml2ctx));
+ ctx->xml2enc = XML_CHAR_ENCODING_NONE;
+ if (cfg->skipto != NULL) {
+ ctx->flags |= ENC_SKIPTO;
+ }
+ }
+ return APR_SUCCESS;
+}
+static apr_status_t xml2enc_ffunc(ap_filter_t* f, apr_bucket_brigade* bb)
+{
+ xml2ctx* ctx = f->ctx;
+ apr_status_t rv;
+ apr_bucket* b;
+ apr_bucket* bstart;
+ apr_size_t insz = 0;
+ int pending_meta = 0;
+ char *ctype;
+ char *p;
+
+ if (!ctx || !f->r->content_type) {
+ /* log error about configuring this */
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb) ;
+ }
+
+ ctype = apr_pstrdup(f->r->pool, f->r->content_type);
+ for (p = ctype; *p; ++p)
+ if (isupper(*p))
+ *p = tolower(*p);
+
+ /* only act if starts-with "text/" or contains "xml" */
+ if (strncmp(ctype, "text/", 5) && !strstr(ctype, "xml")) {
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb) ;
+ }
+
+ if (ctx->bbsave == NULL) {
+ ctx->bbsave = apr_brigade_create(f->r->pool,
+ f->r->connection->bucket_alloc);
+ }
+ /* append to any data left over from last time */
+ APR_BRIGADE_CONCAT(ctx->bbsave, bb);
+
+ if (!(ctx->flags & ENC_INITIALISED)) {
+ /* some kind of initialisation required */
+ /* Turn all this off when post-processing */
+
+ /* if we don't have enough data to sniff but more's to come, wait */
+ apr_brigade_length(ctx->bbsave, 0, &ctx->bblen);
+ if ((ctx->bblen < BUF_MIN) && (ctx->bblen != -1)) {
+ APR_BRIGADE_DO(b, ctx->bbsave) {
+ if (APR_BUCKET_IS_EOS(b)) {
+ ctx->flags |= ENC_SEEN_EOS;
+ break;
+ }
+ }
+ if (!(ctx->flags & ENC_SEEN_EOS)) {
+ /* not enough data to sniff. Wait for more */
+ APR_BRIGADE_DO(b, ctx->bbsave) {
+ rv = apr_bucket_setaside(b, f->r->pool);
+ ap_assert(rv == APR_SUCCESS);
+ }
+ return APR_SUCCESS;
+ }
+ }
+ if (ctx->bblen == -1) {
+ ctx->bblen = BUFLEN-1;
+ }
+
+ /* flatten it into a NULL-terminated string */
+ ctx->buf = apr_palloc(f->r->pool, (apr_size_t)(ctx->bblen+1));
+ ctx->bytes = (apr_size_t)ctx->bblen;
+ rv = apr_brigade_flatten(ctx->bbsave, ctx->buf, &ctx->bytes);
+ ap_assert(rv == APR_SUCCESS);
+ ctx->buf[ctx->bytes] = 0;
+ sniff_encoding(f->r, ctx);
+
+ /* FIXME: hook here for rewriting start-of-data? */
+ /* nah, we only have one action here - call it inline */
+ fix_skipto(f->r, ctx);
+
+ /* we might change the Content-Length, so let's force its re-calculation */
+ apr_table_unset(f->r->headers_out, "Content-Length");
+
+ /* consume the data we just sniffed */
+ /* we need to omit any <meta> we just invalidated */
+ ctx->flags |= ENC_INITIALISED;
+ ap_set_module_config(f->r->request_config, &xml2enc_module, ctx);
+ }
+ if (ctx->bbnext == NULL) {
+ ctx->bbnext = apr_brigade_create(f->r->pool,
+ f->r->connection->bucket_alloc);
+ }
+
+ if (!ctx->convset) {
+ rv = ap_pass_brigade(f->next, ctx->bbsave);
+ apr_brigade_cleanup(ctx->bbsave);
+ ap_remove_output_filter(f);
+ return rv;
+ }
+ /* move the data back to bb */
+ APR_BRIGADE_CONCAT(bb, ctx->bbsave);
+
+ while (!APR_BRIGADE_EMPTY(bb)) {
+ b = APR_BRIGADE_FIRST(bb);
+ ctx->bytes = 0;
+ if (APR_BUCKET_IS_METADATA(b)) {
+ APR_BUCKET_REMOVE(b);
+ APR_BRIGADE_INSERT_TAIL(ctx->bbnext, b);
+ /* Besides FLUSH, aggregate meta buckets to send them at
+ * once below. This resource filter is over on EOS.
+ */
+ pending_meta = 1;
+ if (APR_BUCKET_IS_EOS(b)) {
+ ap_remove_output_filter(f);
+ APR_BRIGADE_CONCAT(ctx->bbnext, bb);
+ }
+ else if (!APR_BUCKET_IS_FLUSH(b)) {
+ continue;
+ }
+ }
+ if (pending_meta) {
+ pending_meta = 0;
+ /* passing meta bucket down the chain */
+ rv = ap_pass_brigade(f->next, ctx->bbnext);
+ apr_brigade_cleanup(ctx->bbnext);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ continue;
+ }
+ /* data bucket */
+ {
+ char* buf;
+ apr_size_t bytes = 0;
+ char fixbuf[BUFLEN];
+ apr_bucket* bdestroy = NULL;
+ if (insz > 0) { /* we have dangling data. Flatten it. */
+ buf = fixbuf;
+ bytes = BUFLEN;
+ rv = apr_brigade_flatten(bb, buf, &bytes);
+ ap_assert(rv == APR_SUCCESS);
+ if (bytes == insz) {
+ /* this is only what we've already tried to convert.
+ * The brigade is exhausted.
+ * Save remaining data for next time round
+ */
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, APLOGNO(01437)
+ "xml2enc: Setting aside %" APR_SIZE_T_FMT
+ " unconverted bytes", bytes);
+ rv = ap_fflush(f->next, ctx->bbnext);
+ APR_BRIGADE_CONCAT(ctx->bbsave, bb);
+ APR_BRIGADE_DO(b, ctx->bbsave) {
+ ap_assert(apr_bucket_setaside(b, f->r->pool)
+ == APR_SUCCESS);
+ }
+ return rv;
+ }
+ /* remove the data we've just read */
+ rv = apr_brigade_partition(bb, bytes, &bstart);
+ while (b = APR_BRIGADE_FIRST(bb), b != bstart) {
+ apr_bucket_delete(b);
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, APLOGNO(01438)
+ "xml2enc: consuming %" APR_SIZE_T_FMT
+ " bytes flattened", bytes);
+ }
+ else {
+ rv = apr_bucket_read(b, (const char**)&buf, &bytes,
+ APR_BLOCK_READ);
+ APR_BUCKET_REMOVE(b);
+ bdestroy = b; /* can't destroy until finished with the data */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, APLOGNO(01439)
+ "xml2enc: consuming %" APR_SIZE_T_FMT
+ " bytes from bucket", bytes);
+ }
+ /* OK, we've got some input we can use in [buf,bytes] */
+ if (rv == APR_SUCCESS) {
+ apr_size_t consumed;
+ xml2enc_run_preprocess(f, &buf, &bytes);
+ consumed = insz = bytes;
+ while (insz > 0) {
+ apr_status_t rv2;
+ if (ctx->bytes == ctx->bblen) {
+ /* nothing was converted last time!
+ * break out of this loop!
+ */
+ b = apr_bucket_transient_create(buf+(bytes - insz), insz,
+ bb->bucket_alloc);
+ APR_BRIGADE_INSERT_HEAD(bb, b);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, APLOGNO(01440)
+ "xml2enc: reinserting %" APR_SIZE_T_FMT
+ " unconsumed bytes from bucket", insz);
+ break;
+ }
+ ctx->bytes = (apr_size_t)ctx->bblen;
+ rv = apr_xlate_conv_buffer(ctx->convset, buf+(bytes - insz),
+ &insz, ctx->buf, &ctx->bytes);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, f->r, APLOGNO(01441)
+ "xml2enc: converted %" APR_SIZE_T_FMT
+ "/%" APR_OFF_T_FMT " bytes", consumed - insz,
+ ctx->bblen - ctx->bytes);
+ consumed = insz;
+ rv2 = ap_fwrite(f->next, ctx->bbnext, ctx->buf,
+ (apr_size_t)ctx->bblen - ctx->bytes);
+ if (rv2 != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv2, f->r, APLOGNO(01442)
+ "ap_fwrite failed");
+ return rv2;
+ }
+ switch (rv) {
+ case APR_SUCCESS:
+ continue;
+ case APR_EINCOMPLETE:
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, APLOGNO(01443)
+ "INCOMPLETE");
+ continue; /* If outbuf too small, go round again.
+ * If it was inbuf, we'll break out when
+ * we test ctx->bytes == ctx->bblen
+ */
+ case APR_EINVAL: /* try skipping one bad byte */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, APLOGNO(01444)
+ "Skipping invalid byte(s) in input stream!");
+ --insz;
+ continue;
+ default:
+ /* Erk! What's this?
+ * Bail out, flush, and hope to eat the buf raw
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(01445)
+ "Failed to convert input; trying it raw") ;
+ ctx->convset = NULL;
+ rv = ap_fflush(f->next, ctx->bbnext);
+ if (rv != APR_SUCCESS)
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, f->r, APLOGNO(01446)
+ "ap_fflush failed");
+ apr_brigade_cleanup(ctx->bbnext);
+ }
+ }
+ } else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(01447)
+ "xml2enc: error reading data") ;
+ }
+ if (bdestroy)
+ apr_bucket_destroy(bdestroy);
+ if (rv != APR_SUCCESS)
+ return rv;
+ }
+ }
+ if (pending_meta) {
+ /* passing pending meta bucket down the chain before leaving */
+ rv = ap_pass_brigade(f->next, ctx->bbnext);
+ apr_brigade_cleanup(ctx->bbnext);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t xml2enc_charset(request_rec* r, xmlCharEncoding* encp,
+ const char** encoding)
+{
+ xml2ctx* ctx = ap_get_module_config(r->request_config, &xml2enc_module);
+ if (!ctx || !(ctx->flags & ENC_INITIALISED)) {
+ return APR_EAGAIN;
+ }
+ *encp = ctx->xml2enc;
+ *encoding = ctx->encoding;
+ return HAVE_ENCODING(ctx->xml2enc) ? APR_SUCCESS : APR_EGENERAL;
+}
+
+#define PROTO_FLAGS AP_FILTER_PROTO_CHANGE|AP_FILTER_PROTO_CHANGE_LENGTH
+static void xml2enc_hooks(apr_pool_t* pool)
+{
+ ap_register_output_filter_protocol("xml2enc", xml2enc_ffunc,
+ xml2enc_filter_init,
+ AP_FTYPE_RESOURCE, PROTO_FLAGS);
+ APR_REGISTER_OPTIONAL_FN(xml2enc_filter);
+ APR_REGISTER_OPTIONAL_FN(xml2enc_charset);
+ seek_meta_ctype = ap_pregcomp(pool,
+ "(<meta[^>]*http-equiv[ \t\r\n='\"]*content-type[^>]*>)",
+ AP_REG_EXTENDED|AP_REG_ICASE) ;
+ seek_charset = ap_pregcomp(pool, "charset=([A-Za-z0-9_-]+)",
+ AP_REG_EXTENDED|AP_REG_ICASE) ;
+}
+static const char* set_alias(cmd_parms* cmd, void* CFG,
+ const char* charset, const char* alias)
+{
+ const char* errmsg = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (errmsg != NULL)
+ return errmsg ;
+ else if (xmlAddEncodingAlias(charset, alias) == 0)
+ return NULL;
+ else
+ return "Error setting charset alias";
+}
+
+static const char* set_default(cmd_parms* cmd, void* CFG, const char* charset)
+{
+ xml2cfg* cfg = CFG;
+ cfg->default_charset = charset;
+ cfg->default_encoding = xmlParseCharEncoding(charset);
+ switch(cfg->default_encoding) {
+ case XML_CHAR_ENCODING_NONE:
+ return "Default charset not found";
+ case XML_CHAR_ENCODING_ERROR:
+ return "Invalid or unsupported default charset";
+ default:
+ return NULL;
+ }
+}
+static const char* set_skipto(cmd_parms* cmd, void* CFG, const char* arg)
+{
+ tattr* attr;
+ xml2cfg* cfg = CFG;
+ if (cfg->skipto == NULL)
+ cfg->skipto = apr_array_make(cmd->pool, 4, sizeof(tattr));
+ attr = apr_array_push(cfg->skipto) ;
+ attr->val = arg;
+ return NULL;
+}
+
+static const command_rec xml2enc_cmds[] = {
+ AP_INIT_TAKE1("xml2EncDefault", set_default, NULL, OR_ALL,
+ "Usage: xml2EncDefault charset"),
+ AP_INIT_ITERATE2("xml2EncAlias", set_alias, NULL, RSRC_CONF,
+ "EncodingAlias charset alias [more aliases]"),
+ AP_INIT_ITERATE("xml2StartParse", set_skipto, NULL, OR_ALL,
+ "Ignore anything in front of the first of these elements"),
+ { NULL }
+};
+static void* xml2enc_config(apr_pool_t* pool, char* x)
+{
+ xml2cfg* ret = apr_pcalloc(pool, sizeof(xml2cfg));
+ ret->default_encoding = XML_CHAR_ENCODING_NONE ;
+ return ret;
+}
+
+static void* xml2enc_merge(apr_pool_t* pool, void* BASE, void* ADD)
+{
+ xml2cfg* base = BASE;
+ xml2cfg* add = ADD;
+ xml2cfg* ret = apr_pcalloc(pool, sizeof(xml2cfg));
+ ret->default_encoding = (add->default_encoding == XML_CHAR_ENCODING_NONE)
+ ? base->default_encoding : add->default_encoding ;
+ ret->default_charset = add->default_charset
+ ? add->default_charset : base->default_charset;
+ ret->skipto = add->skipto ? add->skipto : base->skipto;
+ return ret;
+}
+
+AP_DECLARE_MODULE(xml2enc) = {
+ STANDARD20_MODULE_STUFF,
+ xml2enc_config,
+ xml2enc_merge,
+ NULL,
+ NULL,
+ xml2enc_cmds,
+ xml2enc_hooks
+};
+
+APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(xml2enc, XML2ENC, int, preprocess,
+ (ap_filter_t *f, char** bufp, apr_size_t* bytesp),
+ (f, bufp, bytesp), OK, DECLINED)
diff --git a/modules/filters/mod_xml2enc.dep b/modules/filters/mod_xml2enc.dep
new file mode 100644
index 0000000..4c89d07
--- /dev/null
+++ b/modules/filters/mod_xml2enc.dep
@@ -0,0 +1,54 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_xml2enc.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_xml2enc.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_xml2enc.h"\
+
diff --git a/modules/filters/mod_xml2enc.dsp b/modules/filters/mod_xml2enc.dsp
new file mode 100644
index 0000000..69e2904
--- /dev/null
+++ b/modules/filters/mod_xml2enc.dsp
@@ -0,0 +1,123 @@
+# Microsoft Developer Studio Project File - Name="mod_xml2enc" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_xml2enc - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_xml2enc.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_xml2enc.mak" CFG="mod_xml2enc - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_xml2enc - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_xml2enc - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_xml2enc - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/libxml2/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_xml2enc_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG" /i "../../include" /i "../../srclib/apr/include" /i "../../srclib/apr-util/include" /i "../../srclib/libxml2/include" /d BIN_NAME="mod_xml2enc.so" /d LONG_NAME="xml2enc_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_xml2enc.so" /base:@..\..\os\win32\BaseAddr.ref,mod_xml2enc.so
+# ADD LINK32 kernel32.lib libxml2.lib /nologo /subsystem:windows /dll /incremental:no /debug /libpath:"../../srclib/libxml2/win32/bin.msvc" /out:".\Release\mod_xml2enc.so" /base:@..\..\os\win32\BaseAddr.ref,mod_xml2enc.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_xml2enc.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_xml2enc - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/libxml2/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_xml2enc_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG" /i "../../include" /i "../../srclib/apr/include" /i "../../srclib/apr-util/include" /i "../../srclib/libxml2/include" /d BIN_NAME="mod_xml2enc.so" /d LONG_NAME="xml2enc_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_xml2enc.so" /base:@..\..\os\win32\BaseAddr.ref,mod_xml2enc.so
+# ADD LINK32 kernel32.lib libxml2.lib /nologo /subsystem:windows /dll /incremental:no /debug /libpath:"../../srclib/libxml2/win32/bin.msvc" /out:".\Debug\mod_xml2enc.so" /base:@..\..\os\win32\BaseAddr.ref,mod_xml2enc.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_xml2enc.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_xml2enc - Win32 Release"
+# Name "mod_xml2enc - Win32 Debug"
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h"
+# Begin Source File
+
+SOURCE=.\mod_xml2enc.h
+# End Source File
+# End Group
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\mod_xml2enc.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Group
+# End Target
+# End Project
diff --git a/modules/filters/mod_xml2enc.h b/modules/filters/mod_xml2enc.h
new file mode 100644
index 0000000..90d0e0f
--- /dev/null
+++ b/modules/filters/mod_xml2enc.h
@@ -0,0 +1,55 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MOD_XML2ENC
+#define MOD_XML2ENC
+
+#define ENCIO_INPUT 0x01
+#define ENCIO_OUTPUT 0x02
+#define ENCIO_INPUT_CHECKS 0x04
+#define ENCIO (ENCIO_INPUT|ENCIO_OUTPUT|ENCIO_INPUT_CHECKS)
+#define ENCIO_SKIPTO 0x10
+
+/* declarations to deal with WIN32 compile-flag-in-source-code crap */
+#if !defined(WIN32)
+#define XML2ENC_DECLARE(type) type
+#define XML2ENC_DECLARE_NONSTD(type) type
+#define XML2ENC_DECLARE_DATA
+#elif defined(XML2ENC_DECLARE_STATIC)
+#define XML2ENC_DECLARE(type) type __stdcall
+#define XML2ENC_DECLARE_NONSTD(type) type
+#define XML2ENC_DECLARE_DATA
+#elif defined(XML2ENC_DECLARE_EXPORT)
+#define XML2ENC_DECLARE(type) __declspec(dllexport) type __stdcall
+#define XML2ENC_DECLARE_NONSTD(type) __declspec(dllexport) type
+#define XML2ENC_DECLARE_DATA __declspec(dllexport)
+#else
+#define XML2ENC_DECLARE(type) __declspec(dllimport) type __stdcall
+#define XML2ENC_DECLARE_NONSTD(type) __declspec(dllimport) type
+#define XML2ENC_DECLARE_DATA __declspec(dllimport)
+#endif
+
+APR_DECLARE_OPTIONAL_FN(apr_status_t, xml2enc_charset,
+ (request_rec* r, xmlCharEncoding* enc,
+ const char** cenc));
+
+APR_DECLARE_OPTIONAL_FN(apr_status_t, xml2enc_filter,
+ (request_rec* r, const char* enc, unsigned int mode));
+
+APR_DECLARE_EXTERNAL_HOOK(xml2enc, XML2ENC, int, preprocess,
+ (ap_filter_t *f, char** bufp, apr_size_t* bytesp))
+
+#endif
diff --git a/modules/filters/mod_xml2enc.mak b/modules/filters/mod_xml2enc.mak
new file mode 100644
index 0000000..028c736
--- /dev/null
+++ b/modules/filters/mod_xml2enc.mak
@@ -0,0 +1,352 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_xml2enc.dsp
+!IF "$(CFG)" == ""
+CFG=mod_xml2enc - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_xml2enc - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_xml2enc - Win32 Release" && "$(CFG)" != "mod_xml2enc - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_xml2enc.mak" CFG="mod_xml2enc - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_xml2enc - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_xml2enc - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_xml2enc - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_xml2enc.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_xml2enc.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\httpd.res"
+ -@erase "$(INTDIR)\mod_xml2enc.obj"
+ -@erase "$(INTDIR)\mod_xml2enc_src.idb"
+ -@erase "$(INTDIR)\mod_xml2enc_src.pdb"
+ -@erase "$(OUTDIR)\mod_xml2enc.exp"
+ -@erase "$(OUTDIR)\mod_xml2enc.lib"
+ -@erase "$(OUTDIR)\mod_xml2enc.pdb"
+ -@erase "$(OUTDIR)\mod_xml2enc.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/libxml2/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_xml2enc_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\httpd.res" /i "../../include" /i "../../srclib/apr/include" /i "../../srclib/apr-util/include" /i "../../srclib/libxml2/include" /d "NDEBUG" /d BIN_NAME="mod_xml2enc.so" /d LONG_NAME="xml2enc_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_xml2enc.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib libxml2.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_xml2enc.pdb" /debug /out:"$(OUTDIR)\mod_xml2enc.so" /implib:"$(OUTDIR)\mod_xml2enc.lib" /libpath:"../../srclib/libxml2/win32/bin.msvc" /base:@..\..\os\win32\BaseAddr.ref,mod_xml2enc.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_xml2enc.obj" \
+ "$(INTDIR)\httpd.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_xml2enc.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_xml2enc.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_xml2enc.so"
+ if exist .\Release\mod_xml2enc.so.manifest mt.exe -manifest .\Release\mod_xml2enc.so.manifest -outputresource:.\Release\mod_xml2enc.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_xml2enc - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_xml2enc.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_xml2enc.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\httpd.res"
+ -@erase "$(INTDIR)\mod_xml2enc.obj"
+ -@erase "$(INTDIR)\mod_xml2enc_src.idb"
+ -@erase "$(INTDIR)\mod_xml2enc_src.pdb"
+ -@erase "$(OUTDIR)\mod_xml2enc.exp"
+ -@erase "$(OUTDIR)\mod_xml2enc.lib"
+ -@erase "$(OUTDIR)\mod_xml2enc.pdb"
+ -@erase "$(OUTDIR)\mod_xml2enc.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/libxml2/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_xml2enc_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\httpd.res" /i "../../include" /i "../../srclib/apr/include" /i "../../srclib/apr-util/include" /i "../../srclib/libxml2/include" /d "_DEBUG" /d BIN_NAME="mod_xml2enc.so" /d LONG_NAME="xml2enc_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_xml2enc.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib libxml2.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_xml2enc.pdb" /debug /out:"$(OUTDIR)\mod_xml2enc.so" /implib:"$(OUTDIR)\mod_xml2enc.lib" /libpath:"../../srclib/libxml2/win32/bin.msvc" /base:@..\..\os\win32\BaseAddr.ref,mod_xml2enc.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_xml2enc.obj" \
+ "$(INTDIR)\httpd.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_xml2enc.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_xml2enc.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_xml2enc.so"
+ if exist .\Debug\mod_xml2enc.so.manifest mt.exe -manifest .\Debug\mod_xml2enc.so.manifest -outputresource:.\Debug\mod_xml2enc.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_xml2enc.dep")
+!INCLUDE "mod_xml2enc.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_xml2enc.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_xml2enc - Win32 Release" || "$(CFG)" == "mod_xml2enc - Win32 Debug"
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_xml2enc - Win32 Release"
+
+
+"$(INTDIR)\httpd.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\httpd.res" /i "../../include" /i "../../srclib/apr/include" /i "../../srclib/apr-util/include" /i "../../srclib/libxml2/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_xml2enc.so" /d LONG_NAME="xml2enc_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_xml2enc - Win32 Debug"
+
+
+"$(INTDIR)\httpd.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\httpd.res" /i "../../include" /i "../../srclib/apr/include" /i "../../srclib/apr-util/include" /i "../../srclib/libxml2/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_xml2enc.so" /d LONG_NAME="xml2enc_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_xml2enc.c
+
+"$(INTDIR)\mod_xml2enc.obj" : $(SOURCE) "$(INTDIR)"
+
+
+!IF "$(CFG)" == "mod_xml2enc - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\filters"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_xml2enc - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\filters"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_xml2enc - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\filters"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_xml2enc - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\filters"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\filters"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_xml2enc - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\filters"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\filters"
+
+!ELSEIF "$(CFG)" == "mod_xml2enc - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\filters"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\filters"
+
+!ENDIF
+
+
+!ENDIF
+
diff --git a/modules/filters/regexp.c b/modules/filters/regexp.c
new file mode 100644
index 0000000..4acccca
--- /dev/null
+++ b/modules/filters/regexp.c
@@ -0,0 +1,599 @@
+/*
+ * Copyright (c) 2005, 2008 Sun Microsystems, Inc. All Rights Reserved.
+ * Use is subject to license terms.
+ *
+ * Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T
+ * All Rights Reserved
+ *
+ * University Copyright- Copyright (c) 1982, 1986, 1988
+ * The Regents of the University of California
+ * All Rights Reserved
+ *
+ * University Acknowledgment- Portions of this document are derived from
+ * software developed by the University of California, Berkeley, and its
+ * contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Code moved from regexp.h */
+
+#include "apr.h"
+#include "apr_lib.h"
+#if APR_HAVE_LIMITS_H
+#include <limits.h>
+#endif
+#if APR_HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#include "libsed.h"
+#include "regexp.h"
+#include "sed.h"
+
+#define GETC() ((unsigned char)*sp++)
+#define PEEKC() ((unsigned char)*sp)
+#define UNGETC(c) (--sp)
+#define SEDCOMPILE_ERROR(c) { \
+ regerrno = c; \
+ goto out; \
+ }
+#define ecmp(s1, s2, n) (strncmp(s1, s2, n) == 0)
+#define uletter(c) (isalpha(c) || c == '_')
+
+
+static unsigned char bittab[] = { 1, 2, 4, 8, 16, 32, 64, 128 };
+
+static int regerr(sed_commands_t *commands, int err);
+static void comperr(sed_commands_t *commands, char *msg);
+static void getrnge(char *str, step_vars_storage *vars);
+static int _advance(char *, char *, step_vars_storage *);
+extern int sed_step(char *p1, char *p2, int circf, step_vars_storage *vars);
+
+
+static void comperr(sed_commands_t *commands, char *msg)
+{
+ command_errf(commands, msg, commands->linebuf);
+}
+
+/*
+*/
+static int regerr(sed_commands_t *commands, int err)
+{
+ switch(err) {
+ case 0:
+ /* No error */
+ break;
+ case 11:
+ comperr(commands, "Range endpoint too large: %s");
+ break;
+
+ case 16:
+ comperr(commands, "Bad number: %s");
+ break;
+
+ case 25:
+ comperr(commands, "``\\digit'' out of range: %s");
+ break;
+
+ case 36:
+ comperr(commands, "Illegal or missing delimiter: %s");
+ break;
+
+ case 41:
+ comperr(commands, "No remembered search string: %s");
+ break;
+
+ case 42:
+ comperr(commands, "\\( \\) imbalance: %s");
+ break;
+
+ case 43:
+ comperr(commands, "Too many \\(: %s");
+ break;
+
+ case 44:
+ comperr(commands, "More than 2 numbers given in \\{ \\}: %s");
+ break;
+
+ case 45:
+ comperr(commands, "} expected after \\: %s");
+ break;
+
+ case 46:
+ comperr(commands, "First number exceeds second in \\{ \\}: %s");
+ break;
+
+ case 49:
+ comperr(commands, "[ ] imbalance: %s");
+ break;
+
+ case 50:
+ comperr(commands, SEDERR_TMMES);
+ break;
+
+ default:
+ comperr(commands, "Unknown regexp error code %s\n");
+ break;
+ }
+ return (0);
+}
+
+
+char *sed_compile(sed_commands_t *commands, sed_comp_args *compargs,
+ char *ep, char *endbuf, int seof)
+{
+ int c;
+ int eof = seof;
+ char *lastep;
+ int cclcnt;
+ char bracket[NBRA], *bracketp;
+ int closed;
+ int neg;
+ int lc;
+ int i, cflg;
+ int iflag; /* used for non-ascii characters in brackets */
+ char *sp = commands->cp;
+ int regerrno = 0;
+
+ lastep = 0;
+ if ((c = GETC()) == eof || c == '\n') {
+ if (c == '\n') {
+ UNGETC(c);
+ }
+ commands->cp = sp;
+ goto out;
+ }
+ bracketp = bracket;
+ compargs->circf = closed = compargs->nbra = 0;
+ if (c == '^')
+ compargs->circf++;
+ else
+ UNGETC(c);
+ while (1) {
+ if (ep >= endbuf)
+ SEDCOMPILE_ERROR(50);
+ c = GETC();
+ if (c != '*' && ((c != '\\') || (PEEKC() != '{')))
+ lastep = ep;
+ if (c == eof) {
+ *ep++ = CCEOF;
+ if (bracketp != bracket)
+ SEDCOMPILE_ERROR(42);
+ commands->cp = sp;
+ goto out;
+ }
+ switch (c) {
+
+ case '.':
+ *ep++ = CDOT;
+ continue;
+
+ case '\n':
+ SEDCOMPILE_ERROR(36);
+ commands->cp = sp;
+ goto out;
+ case '*':
+ if (lastep == 0 || *lastep == CBRA || *lastep == CKET)
+ goto defchar;
+ *lastep |= STAR;
+ continue;
+
+ case '$':
+ if (PEEKC() != eof && PEEKC() != '\n')
+ goto defchar;
+ *ep++ = CDOL;
+ continue;
+
+ case '[':
+ if (&ep[17] >= endbuf)
+ SEDCOMPILE_ERROR(50);
+
+ *ep++ = CCL;
+ lc = 0;
+ for (i = 0; i < 16; i++)
+ ep[i] = 0;
+
+ neg = 0;
+ if ((c = GETC()) == '^') {
+ neg = 1;
+ c = GETC();
+ }
+ iflag = 1;
+ do {
+ c &= 0377;
+ if (c == '\0' || c == '\n')
+ SEDCOMPILE_ERROR(49);
+ if ((c & 0200) && iflag) {
+ iflag = 0;
+ if (&ep[32] >= endbuf)
+ SEDCOMPILE_ERROR(50);
+ ep[-1] = CXCL;
+ for (i = 16; i < 32; i++)
+ ep[i] = 0;
+ }
+ if (c == '-' && lc != 0) {
+ if ((c = GETC()) == ']') {
+ PLACE('-');
+ break;
+ }
+ if ((c & 0200) && iflag) {
+ iflag = 0;
+ if (&ep[32] >= endbuf)
+ SEDCOMPILE_ERROR(50);
+ ep[-1] = CXCL;
+ for (i = 16; i < 32; i++)
+ ep[i] = 0;
+ }
+ while (lc < c) {
+ PLACE(lc);
+ lc++;
+ }
+ }
+ lc = c;
+ PLACE(c);
+ } while ((c = GETC()) != ']');
+
+ if (iflag)
+ iflag = 16;
+ else
+ iflag = 32;
+
+ if (neg) {
+ if (iflag == 32) {
+ for (cclcnt = 0; cclcnt < iflag;
+ cclcnt++)
+ ep[cclcnt] ^= 0377;
+ ep[0] &= 0376;
+ } else {
+ ep[-1] = NCCL;
+ /* make nulls match so test fails */
+ ep[0] |= 01;
+ }
+ }
+
+ ep += iflag;
+
+ continue;
+
+ case '\\':
+ switch (c = GETC()) {
+
+ case '(':
+ if (compargs->nbra >= NBRA)
+ SEDCOMPILE_ERROR(43);
+ *bracketp++ = compargs->nbra;
+ *ep++ = CBRA;
+ *ep++ = compargs->nbra++;
+ continue;
+
+ case ')':
+ if (bracketp <= bracket)
+ SEDCOMPILE_ERROR(42);
+ *ep++ = CKET;
+ *ep++ = *--bracketp;
+ closed++;
+ continue;
+
+ case '{':
+ if (lastep == (char *) 0)
+ goto defchar;
+ *lastep |= RNGE;
+ cflg = 0;
+ nlim:
+ c = GETC();
+ i = 0;
+ do {
+ if ('0' <= c && c <= '9')
+ i = 10 * i + c - '0';
+ else
+ SEDCOMPILE_ERROR(16);
+ } while (((c = GETC()) != '\\') && (c != ','));
+ if (i >= 255)
+ SEDCOMPILE_ERROR(11);
+ *ep++ = i;
+ if (c == ',') {
+ if (cflg++)
+ SEDCOMPILE_ERROR(44);
+ if ((c = GETC()) == '\\')
+ *ep++ = (char) 255;
+ else {
+ UNGETC(c);
+ goto nlim;
+ /* get 2'nd number */
+ }
+ }
+ if (GETC() != '}')
+ SEDCOMPILE_ERROR(45);
+ if (!cflg) /* one number */
+ *ep++ = i;
+ else if ((ep[-1] & 0377) < (ep[-2] & 0377))
+ SEDCOMPILE_ERROR(46);
+ continue;
+
+ case '\n':
+ SEDCOMPILE_ERROR(36);
+
+ case 'n':
+ c = '\n';
+ goto defchar;
+
+ default:
+ if (c >= '1' && c <= '9') {
+ if ((c -= '1') >= closed)
+ SEDCOMPILE_ERROR(25);
+ *ep++ = CBACK;
+ *ep++ = c;
+ continue;
+ }
+ }
+ /* Drop through to default to use \ to turn off special chars */
+
+ defchar:
+ default:
+ lastep = ep;
+ *ep++ = CCHR;
+ *ep++ = c;
+ }
+ }
+out:
+ if (regerrno) {
+ regerr(commands, regerrno);
+ return (char*) NULL;
+ }
+ /* XXX : Basant : what extra */
+ /* int reglength = (int)(ep - expbuf); */
+ return ep;
+}
+
+int sed_step(char *p1, char *p2, int circf, step_vars_storage *vars)
+{
+ int c;
+
+
+ if (circf) {
+ vars->loc1 = p1;
+ return (_advance(p1, p2, vars));
+ }
+ /* fast check for first character */
+ if (*p2 == CCHR) {
+ c = p2[1];
+ do {
+ if (*p1 != c)
+ continue;
+ if (_advance(p1, p2, vars)) {
+ vars->loc1 = p1;
+ return (1);
+ }
+ } while (*p1++);
+ return (0);
+ }
+ /* regular algorithm */
+ do {
+ if (_advance(p1, p2, vars)) {
+ vars->loc1 = p1;
+ return (1);
+ }
+ } while (*p1++);
+ return (0);
+}
+
+static int _advance(char *lp, char *ep, step_vars_storage *vars)
+{
+ char *curlp;
+ int c;
+ char *bbeg;
+ char neg;
+ int ct;
+ int epint; /* int value of *ep */
+
+ while (1) {
+ neg = 0;
+ switch (*ep++) {
+
+ case CCHR:
+ if (*ep++ == *lp++)
+ continue;
+ return (0);
+
+ case CDOT:
+ if (*lp++)
+ continue;
+ return (0);
+
+ case CDOL:
+ if (*lp == 0)
+ continue;
+ return (0);
+
+ case CCEOF:
+ vars->loc2 = lp;
+ return (1);
+
+ case CXCL:
+ c = (unsigned char)*lp++;
+ if (ISTHERE(c)) {
+ ep += 32;
+ continue;
+ }
+ return (0);
+
+ case NCCL:
+ neg = 1;
+
+ case CCL:
+ c = *lp++;
+ if (((c & 0200) == 0 && ISTHERE(c)) ^ neg) {
+ ep += 16;
+ continue;
+ }
+ return (0);
+
+ case CBRA:
+ epint = (int) *ep;
+ vars->braslist[epint] = lp;
+ ep++;
+ continue;
+
+ case CKET:
+ epint = (int) *ep;
+ vars->braelist[epint] = lp;
+ ep++;
+ continue;
+
+ case CCHR | RNGE:
+ c = *ep++;
+ getrnge(ep, vars);
+ while (vars->low--)
+ if (*lp++ != c)
+ return (0);
+ curlp = lp;
+ while (vars->size--)
+ if (*lp++ != c)
+ break;
+ if (vars->size < 0)
+ lp++;
+ ep += 2;
+ goto star;
+
+ case CDOT | RNGE:
+ getrnge(ep, vars);
+ while (vars->low--)
+ if (*lp++ == '\0')
+ return (0);
+ curlp = lp;
+ while (vars->size--)
+ if (*lp++ == '\0')
+ break;
+ if (vars->size < 0)
+ lp++;
+ ep += 2;
+ goto star;
+
+ case CXCL | RNGE:
+ getrnge(ep + 32, vars);
+ while (vars->low--) {
+ c = (unsigned char)*lp++;
+ if (!ISTHERE(c))
+ return (0);
+ }
+ curlp = lp;
+ while (vars->size--) {
+ c = (unsigned char)*lp++;
+ if (!ISTHERE(c))
+ break;
+ }
+ if (vars->size < 0)
+ lp++;
+ ep += 34; /* 32 + 2 */
+ goto star;
+
+ case NCCL | RNGE:
+ neg = 1;
+
+ case CCL | RNGE:
+ getrnge(ep + 16, vars);
+ while (vars->low--) {
+ c = *lp++;
+ if (((c & 0200) || !ISTHERE(c)) ^ neg)
+ return (0);
+ }
+ curlp = lp;
+ while (vars->size--) {
+ c = *lp++;
+ if (((c & 0200) || !ISTHERE(c)) ^ neg)
+ break;
+ }
+ if (vars->size < 0)
+ lp++;
+ ep += 18; /* 16 + 2 */
+ goto star;
+
+ case CBACK:
+ epint = (int) *ep;
+ bbeg = vars->braslist[epint];
+ ct = vars->braelist[epint] - bbeg;
+ ep++;
+
+ if (ecmp(bbeg, lp, ct)) {
+ lp += ct;
+ continue;
+ }
+ return (0);
+
+ case CBACK | STAR:
+ epint = (int) *ep;
+ bbeg = vars->braslist[epint];
+ ct = vars->braelist[epint] - bbeg;
+ ep++;
+ curlp = lp;
+ while (ecmp(bbeg, lp, ct))
+ lp += ct;
+
+ while (lp >= curlp) {
+ if (_advance(lp, ep, vars))
+ return (1);
+ lp -= ct;
+ }
+ return (0);
+
+
+ case CDOT | STAR:
+ curlp = lp;
+ while (*lp++);
+ goto star;
+
+ case CCHR | STAR:
+ curlp = lp;
+ while (*lp++ == *ep);
+ ep++;
+ goto star;
+
+ case CXCL | STAR:
+ curlp = lp;
+ do {
+ c = (unsigned char)*lp++;
+ } while (ISTHERE(c));
+ ep += 32;
+ goto star;
+
+ case NCCL | STAR:
+ neg = 1;
+
+ case CCL | STAR:
+ curlp = lp;
+ do {
+ c = *lp++;
+ } while (((c & 0200) == 0 && ISTHERE(c)) ^ neg);
+ ep += 16;
+ goto star;
+
+ star:
+ do {
+ if (--lp == vars->locs)
+ break;
+ if (_advance(lp, ep, vars))
+ return (1);
+ } while (lp > curlp);
+ return (0);
+
+ }
+ }
+}
+
+static void getrnge(char *str, step_vars_storage *vars)
+{
+ vars->low = *str++ & 0377;
+ vars->size = ((*str & 0377) == 255)? 20000: (*str &0377) - vars->low;
+}
+
+
diff --git a/modules/filters/regexp.h b/modules/filters/regexp.h
new file mode 100644
index 0000000..dc4993a
--- /dev/null
+++ b/modules/filters/regexp.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2005, 2008 Sun Microsystems, Inc. All Rights Reserved.
+ * Use is subject to license terms.
+ *
+ * Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T
+ * All Rights Reserved
+ *
+ * University Copyright- Copyright (c) 1982, 1986, 1988
+ * The Regents of the University of California
+ * All Rights Reserved
+ *
+ * University Acknowledgment- Portions of this document are derived from
+ * software developed by the University of California, Berkeley, and its
+ * contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _REGEXP_H
+#define _REGEXP_H
+
+#include "libsed.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define CBRA 2
+#define CCHR 4
+#define CDOT 8
+#define CCL 12
+#define CXCL 16
+#define CDOL 20
+#define CCEOF 22
+#define CKET 24
+#define CBACK 36
+#define NCCL 40
+
+#define STAR 01
+#define RNGE 03
+
+#define NBRA 9
+
+#define PLACE(c) ep[c >> 3] |= bittab[c & 07]
+#define ISTHERE(c) (ep[c >> 3] & bittab[c & 07])
+
+typedef struct _step_vars_storage {
+ char *loc1, *loc2, *locs;
+ char *braslist[NBRA];
+ char *braelist[NBRA];
+ int low;
+ int size;
+} step_vars_storage;
+
+typedef struct _sed_comp_args {
+ int circf; /* Regular expression starts with ^ */
+ int nbra; /* braces count */
+} sed_comp_args;
+
+extern char *sed_compile(sed_commands_t *commands, sed_comp_args *compargs,
+ char *ep, char *endbuf, int seof);
+extern void command_errf(sed_commands_t *commands, const char *fmt, ...)
+ __attribute__((format(printf,2,3)));
+
+#define SEDERR_CGMES "command garbled: %s"
+#define SEDERR_SMMES "Space missing before filename: %s"
+#define SEDERR_TMMES "too much command text: %s"
+#define SEDERR_LTLMES "label too long: %s"
+#define SEDERR_ULMES "undefined label: %s"
+#define SEDERR_DLMES "duplicate labels: %s"
+#define SEDERR_TMLMES "too many labels: %s"
+#define SEDERR_AD0MES "no addresses allowed: %s"
+#define SEDERR_AD1MES "only one address allowed: %s"
+#define SEDERR_TOOBIG "suffix too large: %s"
+#define SEDERR_OOMMES "out of memory"
+#define SEDERR_COPFMES "cannot open pattern file: %s"
+#define SEDERR_COIFMES "cannot open input file: %s"
+#define SEDERR_TMOMES "too many {'s"
+#define SEDERR_TMCMES "too many }'s"
+#define SEDERR_NRMES "first RE may not be null"
+#define SEDERR_UCMES "unrecognized command: %s"
+#define SEDERR_TMWFMES "too many files in w commands"
+#define SEDERR_COMES "cannot open %s"
+#define SEDERR_CCMES "cannot create %s"
+#define SEDERR_TMLNMES "too many line numbers"
+#define SEDERR_TMAMES "too many appends after line %" APR_INT64_T_FMT
+#define SEDERR_TMRMES "too many reads after line %" APR_INT64_T_FMT
+#define SEDERR_DOORNG "``\\digit'' out of range: %s"
+#define SEDERR_EDMOSUB "ending delimiter missing on substitution: %s"
+#define SEDERR_EDMOSTR "ending delimiter missing on string: %s"
+#define SEDERR_FNTL "file name too long: %s"
+#define SEDERR_CLTL "command line too long"
+#define SEDERR_TSNTSS "transform strings not the same size: %s"
+#define SEDERR_OLTL "output line too long."
+#define SEDERR_HSOVERFLOW "hold space overflowed."
+#define SEDERR_INTERNAL "internal sed error"
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _REGEXP_H */
diff --git a/modules/filters/sed.h b/modules/filters/sed.h
new file mode 100644
index 0000000..cc3c03e
--- /dev/null
+++ b/modules/filters/sed.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2005, 2008 Sun Microsystems, Inc. All Rights Reserved.
+ * Use is subject to license terms.
+ *
+ * Copyright (c) 1984 AT&T
+ * All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _SED_H
+#define _SED_H
+
+#include <stdlib.h>
+#include <limits.h>
+
+#define CEND 16
+#define CLNUM 14
+
+#define RESIZE 10000
+#define LBSIZE 1000
+
+#define ACOM 01
+#define BCOM 020
+#define CCOM 02
+#define CDCOM 025
+#define CNCOM 022
+#define COCOM 017
+#define CPCOM 023
+#define DCOM 03
+#define ECOM 015
+#define EQCOM 013
+#define FCOM 016
+#define GCOM 027
+#define CGCOM 030
+#define HCOM 031
+#define CHCOM 032
+#define ICOM 04
+#define LCOM 05
+#define NCOM 012
+#define PCOM 010
+#define QCOM 011
+#define RCOM 06
+#define SCOM 07
+#define TCOM 021
+#define WCOM 014
+#define CWCOM 024
+#define YCOM 026
+#define XCOM 033
+
+#endif /* _SED_H */
diff --git a/modules/filters/sed0.c b/modules/filters/sed0.c
new file mode 100644
index 0000000..a044f64
--- /dev/null
+++ b/modules/filters/sed0.c
@@ -0,0 +1,1026 @@
+/*
+ * Copyright (c) 2005, 2008 Sun Microsystems, Inc. All Rights Reserved.
+ * Use is subject to license terms.
+ *
+ * Copyright (c) 1984 AT&T
+ * All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "libsed.h"
+#include "sed.h"
+#include "regexp.h"
+
+#define CCEOF 22
+
+static int fcomp(sed_commands_t *commands, apr_file_t *fin);
+static char *compsub(sed_commands_t *commands,
+ sed_comp_args *compargs, char *rhsbuf);
+static int rline(sed_commands_t *commands, apr_file_t *fin,
+ char *lbuf, char *lbend);
+static char *address(sed_commands_t *commands, char *expbuf,
+ apr_status_t* status);
+static char *text(sed_commands_t *commands, char *textbuf, char *endbuf);
+static sed_label_t *search(sed_commands_t *commands);
+static char *ycomp(sed_commands_t *commands, char *expbuf);
+static char *comple(sed_commands_t *commands, sed_comp_args *compargs,
+ char *x1, char *ep, char *x3, char x4);
+static sed_reptr_t *alloc_reptr(sed_commands_t *commands);
+static int check_finalized(const sed_commands_t *commands);
+
+void command_errf(sed_commands_t *commands, const char *fmt, ...)
+{
+ if (commands->errfn && commands->pool) {
+ va_list args;
+ const char* error;
+ va_start(args, fmt);
+ error = apr_pvsprintf(commands->pool, fmt, args);
+ commands->errfn(commands->data, error);
+ va_end(args);
+ }
+}
+
+/*
+ * sed_init_commands
+ */
+apr_status_t sed_init_commands(sed_commands_t *commands, sed_err_fn_t *errfn, void *data,
+ apr_pool_t *p)
+{
+ memset(commands, 0, sizeof(*commands));
+
+ commands->errfn = errfn;
+ commands->data = data;
+
+ commands->labtab = commands->ltab;
+ commands->lab = commands->labtab + 1;
+ commands->pool = p;
+
+ commands->respace = apr_pcalloc(p, RESIZE);
+ if (commands->respace == NULL) {
+ command_errf(commands, SEDERR_OOMMES);
+ return APR_EGENERAL;
+ }
+
+ commands->rep = alloc_reptr(commands);
+ if (commands->rep == NULL)
+ return APR_EGENERAL;
+
+ commands->rep->ad1 = commands->respace;
+ commands->reend = &commands->respace[RESIZE - 1];
+ commands->labend = &commands->labtab[SED_LABSIZE];
+ commands->canbefinal = 1;
+
+ return APR_SUCCESS;
+}
+
+/*
+ * sed_destroy_commands
+ */
+void sed_destroy_commands(sed_commands_t *commands)
+{
+}
+
+/*
+ * sed_compile_string
+ */
+apr_status_t sed_compile_string(sed_commands_t *commands, const char *s)
+{
+ apr_status_t rv;
+
+ commands->earg = s;
+ commands->eflag = 1;
+
+ rv = fcomp(commands, NULL);
+ if (rv == APR_SUCCESS)
+ commands->canbefinal = check_finalized(commands);
+
+ commands->eflag = 0;
+
+ return (rv != 0 ? APR_EGENERAL : APR_SUCCESS);
+}
+
+/*
+ * sed_compile_file
+ */
+apr_status_t sed_compile_file(sed_commands_t *commands, apr_file_t *fin)
+{
+ apr_status_t rv = fcomp(commands, fin);
+ return (rv != 0 ? APR_EGENERAL : APR_SUCCESS);
+}
+
+/*
+ * sed_get_finalize_error
+ */
+char* sed_get_finalize_error(const sed_commands_t *commands, apr_pool_t* pool)
+{
+ const sed_label_t *lab;
+ if (commands->depth) {
+ return SEDERR_TMOMES;
+ }
+
+ /* Empty branch chain is not a issue */
+ for (lab = commands->labtab + 1; lab < commands->lab; lab++) {
+ char *error;
+ if (lab->address == 0) {
+ error = apr_psprintf(pool, SEDERR_ULMES, lab->asc);
+ return error;
+ }
+
+ if (lab->chain) {
+ return SEDERR_INTERNAL;
+ }
+ }
+ return NULL;
+}
+
+/*
+ * sed_canbe_finalized
+ */
+int sed_canbe_finalized(const sed_commands_t *commands)
+{
+ return commands->canbefinal;
+}
+
+/*
+ * check_finalized
+ */
+static int check_finalized(const sed_commands_t *commands)
+{
+ const sed_label_t *lab;
+ if (commands->depth) {
+ return 0;
+ }
+
+ /* Empty branch chain is not a issue */
+ for (lab = commands->labtab + 1; lab < commands->lab; lab++) {
+ if (lab->address == 0 || (lab->chain)) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+/*
+ * dechain
+ */
+static void dechain(sed_label_t *lpt, sed_reptr_t *address)
+{
+ sed_reptr_t *rep;
+ if ((lpt == NULL) || (lpt->chain == NULL) || (address == NULL))
+ return;
+ rep = lpt->chain;
+ while (rep->lb1) {
+ sed_reptr_t *next;
+
+ next = rep->lb1;
+ rep->lb1 = address;
+ rep = next;
+ }
+ rep->lb1 = address;
+ lpt->chain = NULL;
+}
+
+/*
+ * fcomp
+ */
+static int fcomp(sed_commands_t *commands, apr_file_t *fin)
+{
+ char *p, *op, *tp;
+ sed_reptr_t *pt, *pt1;
+ int i, ii;
+ sed_label_t *lpt;
+ char fnamebuf[APR_PATH_MAX];
+ apr_status_t status;
+ sed_comp_args compargs;
+
+ op = commands->lastre;
+ if (!commands->linebuf) {
+ commands->linebuf = apr_pcalloc(commands->pool, LBSIZE + 1);
+ }
+
+ if (rline(commands, fin, commands->linebuf,
+ (commands->linebuf + LBSIZE + 1)) < 0)
+ return 0;
+ if (*commands->linebuf == '#') {
+ if (commands->linebuf[1] == 'n')
+ commands->nflag = 1;
+ }
+ else {
+ commands->cp = commands->linebuf;
+ goto comploop;
+ }
+
+ for (;;) {
+ if (rline(commands, fin, commands->linebuf,
+ (commands->linebuf + LBSIZE + 1)) < 0)
+ break;
+
+ commands->cp = commands->linebuf;
+
+comploop:
+ while (*commands->cp == ' ' || *commands->cp == '\t')
+ commands->cp++;
+ if (*commands->cp == '\0' || *commands->cp == '#')
+ continue;
+ if (*commands->cp == ';') {
+ commands->cp++;
+ goto comploop;
+ }
+
+ p = address(commands, commands->rep->ad1, &status);
+ if (status != APR_SUCCESS) {
+ command_errf(commands, SEDERR_CGMES, commands->linebuf);
+ return -1;
+ }
+
+ if (p == commands->rep->ad1) {
+ if (op)
+ commands->rep->ad1 = op;
+ else {
+ command_errf(commands, SEDERR_NRMES);
+ return -1;
+ }
+ } else if (p == 0) {
+ p = commands->rep->ad1;
+ commands->rep->ad1 = 0;
+ } else {
+ op = commands->rep->ad1;
+ if (*commands->cp == ',' || *commands->cp == ';') {
+ commands->cp++;
+ commands->rep->ad2 = p;
+ p = address(commands, commands->rep->ad2, &status);
+ if ((status != APR_SUCCESS) || (p == 0)) {
+ command_errf(commands, SEDERR_CGMES, commands->linebuf);
+ return -1;
+ }
+ if (p == commands->rep->ad2)
+ commands->rep->ad2 = op;
+ else
+ op = commands->rep->ad2;
+ } else
+ commands->rep->ad2 = 0;
+ }
+
+ if(p > &commands->respace[RESIZE-1]) {
+ command_errf(commands, SEDERR_TMMES, commands->linebuf);
+ return -1;
+ }
+
+ while (*commands->cp == ' ' || *commands->cp == '\t')
+ commands->cp++;
+
+swit:
+ switch(*commands->cp++) {
+ default:
+ command_errf(commands, SEDERR_UCMES, commands->linebuf);
+ return -1;
+
+ case '!':
+ commands->rep->negfl = 1;
+ goto swit;
+
+ case '{':
+ commands->rep->command = BCOM;
+ commands->rep->negfl = !(commands->rep->negfl);
+ commands->cmpend[commands->depth++] = &commands->rep->lb1;
+ commands->rep = alloc_reptr(commands);
+ commands->rep->ad1 = p;
+ if (*commands->cp == '\0')
+ continue;
+ goto comploop;
+
+ case '}':
+ if (commands->rep->ad1) {
+ command_errf(commands, SEDERR_AD0MES, commands->linebuf);
+ return -1;
+ }
+
+ if (--commands->depth < 0) {
+ command_errf(commands, SEDERR_TMCMES);
+ return -1;
+ }
+ *commands->cmpend[commands->depth] = commands->rep;
+
+ commands->rep->ad1 = p;
+ continue;
+
+ case '=':
+ commands->rep->command = EQCOM;
+ if (commands->rep->ad2) {
+ command_errf(commands, SEDERR_AD1MES, commands->linebuf);
+ return -1;
+ }
+ break;
+
+ case ':':
+ if (commands->rep->ad1) {
+ command_errf(commands, SEDERR_AD0MES, commands->linebuf);
+ return -1;
+ }
+
+ while (*commands->cp++ == ' ');
+ commands->cp--;
+
+ tp = commands->lab->asc;
+ while ((*tp++ = *commands->cp++)) {
+ if (tp >= &(commands->lab->asc[8])) {
+ command_errf(commands, SEDERR_LTLMES, commands->linebuf);
+ return -1;
+ }
+ }
+ *--tp = '\0';
+
+ if ((lpt = search(commands)) != NULL) {
+ if (lpt->address) {
+ command_errf(commands, SEDERR_DLMES, commands->linebuf);
+ return -1;
+ }
+ dechain(lpt, commands->rep);
+ } else {
+ commands->lab->chain = 0;
+ lpt = commands->lab;
+ if (++commands->lab >= commands->labend) {
+ command_errf(commands, SEDERR_TMLMES, commands->linebuf);
+ return -1;
+ }
+ }
+ lpt->address = commands->rep;
+ commands->rep->ad1 = p;
+
+ continue;
+
+ case 'a':
+ commands->rep->command = ACOM;
+ if (commands->rep->ad2) {
+ command_errf(commands, SEDERR_AD1MES, commands->linebuf);
+ return -1;
+ }
+ if (*commands->cp == '\\')
+ commands->cp++;
+ if (*commands->cp++ != '\n') {
+ command_errf(commands, SEDERR_CGMES, commands->linebuf);
+ return -1;
+ }
+ commands->rep->re1 = p;
+ p = text(commands, commands->rep->re1, commands->reend);
+ if (p == NULL)
+ return -1;
+ break;
+
+ case 'c':
+ commands->rep->command = CCOM;
+ if (*commands->cp == '\\') commands->cp++;
+ if (*commands->cp++ != ('\n')) {
+ command_errf(commands, SEDERR_CGMES, commands->linebuf);
+ return -1;
+ }
+ commands->rep->re1 = p;
+ p = text(commands, commands->rep->re1, commands->reend);
+ if (p == NULL)
+ return -1;
+ break;
+
+ case 'i':
+ commands->rep->command = ICOM;
+ if (commands->rep->ad2) {
+ command_errf(commands, SEDERR_AD1MES, commands->linebuf);
+ return -1;
+ }
+ if (*commands->cp == '\\') commands->cp++;
+ if (*commands->cp++ != ('\n')) {
+ command_errf(commands, SEDERR_CGMES, commands->linebuf);
+ return -1;
+ }
+ commands->rep->re1 = p;
+ p = text(commands, commands->rep->re1, commands->reend);
+ if (p == NULL)
+ return -1;
+ break;
+
+ case 'g':
+ commands->rep->command = GCOM;
+ break;
+
+ case 'G':
+ commands->rep->command = CGCOM;
+ break;
+
+ case 'h':
+ commands->rep->command = HCOM;
+ break;
+
+ case 'H':
+ commands->rep->command = CHCOM;
+ break;
+
+ case 't':
+ commands->rep->command = TCOM;
+ goto jtcommon;
+
+ case 'b':
+ commands->rep->command = BCOM;
+jtcommon:
+ while (*commands->cp++ == ' ');
+ commands->cp--;
+
+ if (*commands->cp == '\0') {
+ if ((pt = commands->labtab->chain) != NULL) {
+ while ((pt1 = pt->lb1) != NULL)
+ pt = pt1;
+ pt->lb1 = commands->rep;
+ } else
+ commands->labtab->chain = commands->rep;
+ break;
+ }
+ tp = commands->lab->asc;
+ while ((*tp++ = *commands->cp++))
+ if (tp >= &(commands->lab->asc[8])) {
+ command_errf(commands, SEDERR_LTLMES, commands->linebuf);
+ return -1;
+ }
+ commands->cp--;
+ *--tp = '\0';
+
+ if ((lpt = search(commands)) != NULL) {
+ if (lpt->address) {
+ commands->rep->lb1 = lpt->address;
+ } else {
+ pt = lpt->chain;
+ while ((pt1 = pt->lb1) != NULL)
+ pt = pt1;
+ pt->lb1 = commands->rep;
+ }
+ } else {
+ commands->lab->chain = commands->rep;
+ commands->lab->address = 0;
+ if (++commands->lab >= commands->labend) {
+ command_errf(commands, SEDERR_TMLMES, commands->linebuf);
+ return -1;
+ }
+ }
+ break;
+
+ case 'n':
+ commands->rep->command = NCOM;
+ break;
+
+ case 'N':
+ commands->rep->command = CNCOM;
+ break;
+
+ case 'p':
+ commands->rep->command = PCOM;
+ break;
+
+ case 'P':
+ commands->rep->command = CPCOM;
+ break;
+
+ case 'r':
+ commands->rep->command = RCOM;
+ if (commands->rep->ad2) {
+ command_errf(commands, SEDERR_AD1MES, commands->linebuf);
+ return -1;
+ }
+ if (*commands->cp++ != ' ') {
+ command_errf(commands, SEDERR_CGMES, commands->linebuf);
+ return -1;
+ }
+ commands->rep->re1 = p;
+ p = text(commands, commands->rep->re1, commands->reend);
+ if (p == NULL)
+ return -1;
+ break;
+
+ case 'd':
+ commands->rep->command = DCOM;
+ break;
+
+ case 'D':
+ commands->rep->command = CDCOM;
+ commands->rep->lb1 = commands->ptrspace;
+ break;
+
+ case 'q':
+ commands->rep->command = QCOM;
+ if (commands->rep->ad2) {
+ command_errf(commands, SEDERR_AD1MES, commands->linebuf);
+ return -1;
+ }
+ break;
+
+ case 'l':
+ commands->rep->command = LCOM;
+ break;
+
+ case 's':
+ commands->rep->command = SCOM;
+ commands->sseof = *commands->cp++;
+ commands->rep->re1 = p;
+ p = comple(commands, &compargs, (char *) 0, commands->rep->re1,
+ commands->reend, commands->sseof);
+ if (p == NULL)
+ return -1;
+ if (p == commands->rep->re1) {
+ if (op)
+ commands->rep->re1 = op;
+ else {
+ command_errf(commands, SEDERR_NRMES);
+ return -1;
+ }
+ } else
+ op = commands->rep->re1;
+ commands->rep->rhs = p;
+
+ p = compsub(commands, &compargs, commands->rep->rhs);
+ if ((p) == NULL)
+ return -1;
+
+ if (*commands->cp == 'g') {
+ commands->cp++;
+ commands->rep->gfl = 999;
+ } else if (commands->gflag)
+ commands->rep->gfl = 999;
+
+ if (*commands->cp >= '1' && *commands->cp <= '9') {
+ i = *commands->cp - '0';
+ commands->cp++;
+ while (1) {
+ ii = *commands->cp;
+ if (ii < '0' || ii > '9')
+ break;
+ i = i*10 + ii - '0';
+ if (i > 512) {
+ command_errf(commands, SEDERR_TOOBIG, commands->linebuf);
+ return -1;
+ }
+ commands->cp++;
+ }
+ commands->rep->gfl = i;
+ }
+
+ if (*commands->cp == 'p') {
+ commands->cp++;
+ commands->rep->pfl = 1;
+ }
+
+ if (*commands->cp == 'P') {
+ commands->cp++;
+ commands->rep->pfl = 2;
+ }
+
+ if (*commands->cp == 'w') {
+ commands->cp++;
+ if (*commands->cp++ != ' ') {
+ command_errf(commands, SEDERR_SMMES, commands->linebuf);
+ return -1;
+ }
+ if (text(commands, fnamebuf, &fnamebuf[APR_PATH_MAX-1]) == NULL) {
+ command_errf(commands, SEDERR_FNTL, commands->linebuf);
+ return -1;
+ }
+ for (i = commands->nfiles - 1; i >= 0; i--)
+ if (strcmp(fnamebuf,commands->fname[i]) == 0) {
+ commands->rep->findex = i;
+ goto done;
+ }
+ if (commands->nfiles >= NWFILES) {
+ command_errf(commands, SEDERR_TMWFMES);
+ return -1;
+ }
+ commands->fname[commands->nfiles] =
+ apr_pstrdup(commands->pool, fnamebuf);
+ if (commands->fname[commands->nfiles] == NULL) {
+ command_errf(commands, SEDERR_OOMMES);
+ return -1;
+ }
+ commands->rep->findex = commands->nfiles++;
+ }
+ break;
+
+ case 'w':
+ commands->rep->command = WCOM;
+ if (*commands->cp++ != ' ') {
+ command_errf(commands, SEDERR_SMMES, commands->linebuf);
+ return -1;
+ }
+ if (text(commands, fnamebuf, &fnamebuf[APR_PATH_MAX-1]) == NULL) {
+ command_errf(commands, SEDERR_FNTL, commands->linebuf);
+ return -1;
+ }
+ for (i = commands->nfiles - 1; i >= 0; i--)
+ if (strcmp(fnamebuf, commands->fname[i]) == 0) {
+ commands->rep->findex = i;
+ goto done;
+ }
+ if (commands->nfiles >= NWFILES) {
+ command_errf(commands, SEDERR_TMWFMES);
+ return -1;
+ }
+ if ((commands->fname[commands->nfiles] =
+ apr_pstrdup(commands->pool, fnamebuf)) == NULL) {
+ command_errf(commands, SEDERR_OOMMES);
+ return -1;
+ }
+
+ commands->rep->findex = commands->nfiles++;
+ break;
+
+ case 'x':
+ commands->rep->command = XCOM;
+ break;
+
+ case 'y':
+ commands->rep->command = YCOM;
+ commands->sseof = *commands->cp++;
+ commands->rep->re1 = p;
+ p = ycomp(commands, commands->rep->re1);
+ if (p == NULL)
+ return -1;
+ break;
+ }
+done:
+ commands->rep = alloc_reptr(commands);
+
+ commands->rep->ad1 = p;
+
+ if (*commands->cp++ != '\0') {
+ if (commands->cp[-1] == ';')
+ goto comploop;
+ command_errf(commands, SEDERR_CGMES, commands->linebuf);
+ return -1;
+ }
+ }
+ commands->rep->command = 0;
+ commands->lastre = op;
+
+ return 0;
+}
+
+static char *compsub(sed_commands_t *commands,
+ sed_comp_args *compargs, char *rhsbuf)
+{
+ char *p, *q;
+
+ p = rhsbuf;
+ q = commands->cp;
+ for(;;) {
+ if(p > &commands->respace[RESIZE-1]) {
+ command_errf(commands, SEDERR_TMMES, commands->linebuf);
+ return NULL;
+ }
+ if((*p = *q++) == '\\') {
+ p++;
+ if(p > &commands->respace[RESIZE-1]) {
+ command_errf(commands, SEDERR_TMMES, commands->linebuf);
+ return NULL;
+ }
+ *p = *q++;
+ if(*p > compargs->nbra + '0' && *p <= '9') {
+ command_errf(commands, SEDERR_DOORNG, commands->linebuf);
+ return NULL;
+ }
+ p++;
+ continue;
+ }
+ if(*p == commands->sseof) {
+ *p++ = '\0';
+ commands->cp = q;
+ return(p);
+ }
+ if(*p++ == '\0') {
+ command_errf(commands, SEDERR_EDMOSUB, commands->linebuf);
+ return NULL;
+ }
+ }
+}
+
+/*
+ * rline
+ */
+static int rline(sed_commands_t *commands, apr_file_t *fin,
+ char *lbuf, char *lbend)
+{
+ char *p;
+ const char *q;
+ int t;
+ apr_size_t bytes_read;
+
+ p = lbuf;
+
+ if(commands->eflag) {
+ if(commands->eflag > 0) {
+ commands->eflag = -1;
+ q = commands->earg;
+ while((t = *q++) != '\0') {
+ if(t == '\n') {
+ commands->saveq = q;
+ goto out1;
+ }
+ if (p < lbend)
+ *p++ = t;
+ if(t == '\\') {
+ if((t = *q++) == '\0') {
+ commands->saveq = NULL;
+ return(-1);
+ }
+ if (p < lbend)
+ *p++ = t;
+ }
+ }
+ commands->saveq = NULL;
+
+ out1:
+ if (p == lbend) {
+ command_errf(commands, SEDERR_CLTL);
+ return -1;
+ }
+ *p = '\0';
+ return(1);
+ }
+ if((q = commands->saveq) == 0) return(-1);
+
+ while((t = *q++) != '\0') {
+ if(t == '\n') {
+ commands->saveq = q;
+ goto out2;
+ }
+ if(p < lbend)
+ *p++ = t;
+ if(t == '\\') {
+ if((t = *q++) == '\0') {
+ commands->saveq = NULL;
+ return(-1);
+ }
+ if (p < lbend)
+ *p++ = t;
+ }
+ }
+ commands->saveq = NULL;
+
+ out2:
+ if (p == lbend) {
+ command_errf(commands, SEDERR_CLTL);
+ return -1;
+ }
+ *p = '\0';
+ return(1);
+ }
+
+ bytes_read = 1;
+ /* XXX extremely inefficient 1 byte reads */
+ while (apr_file_read(fin, &t, &bytes_read) != APR_SUCCESS) {
+ if(t == '\n') {
+ if (p == lbend) {
+ command_errf(commands, SEDERR_CLTL);
+ return -1;
+ }
+ *p = '\0';
+ return(1);
+ }
+ if (p < lbend)
+ *p++ = t;
+ if(t == '\\') {
+ bytes_read = 1;
+ if (apr_file_read(fin, &t, &bytes_read) != APR_SUCCESS) {
+ return -1;
+ }
+ if(p < lbend)
+ *p++ = t;
+ }
+ bytes_read = 1;
+ }
+ return(-1);
+}
+
+/*
+ * address
+ */
+static char *address(sed_commands_t *commands, char *expbuf,
+ apr_status_t* status)
+{
+ char *rcp;
+ apr_int64_t lno;
+ sed_comp_args compargs;
+
+ *status = APR_SUCCESS;
+ if(*commands->cp == '$') {
+ if (expbuf > &commands->respace[RESIZE-2]) {
+ command_errf(commands, SEDERR_TMMES, commands->linebuf);
+ *status = APR_EGENERAL;
+ return NULL;
+ }
+ commands->cp++;
+ *expbuf++ = CEND;
+ *expbuf++ = CCEOF;
+ return(expbuf);
+ }
+ if (*commands->cp == '/' || *commands->cp == '\\' ) {
+ if ( *commands->cp == '\\' )
+ commands->cp++;
+ commands->sseof = *commands->cp++;
+ return(comple(commands, &compargs, (char *) 0, expbuf, commands->reend,
+ commands->sseof));
+ }
+
+ rcp = commands->cp;
+ lno = 0;
+
+ while(*rcp >= '0' && *rcp <= '9')
+ lno = lno*10 + *rcp++ - '0';
+
+ if(rcp > commands->cp) {
+ if (expbuf > &commands->respace[RESIZE-3]) {
+ command_errf(commands, SEDERR_TMMES, commands->linebuf);
+ *status = APR_EGENERAL;
+ return NULL;
+ }
+ *expbuf++ = CLNUM;
+ *expbuf++ = commands->nlno;
+ commands->tlno[commands->nlno++] = lno;
+ if(commands->nlno >= SED_NLINES) {
+ command_errf(commands, SEDERR_TMLNMES);
+ *status = APR_EGENERAL;
+ return NULL;
+ }
+ *expbuf++ = CCEOF;
+ commands->cp = rcp;
+ return(expbuf);
+ }
+ return(NULL);
+}
+
+/*
+ * text
+ */
+static char *text(sed_commands_t *commands, char *textbuf, char *tbend)
+{
+ char *p, *q;
+
+ p = textbuf;
+ q = commands->cp;
+#ifndef S5EMUL
+ /*
+ * Strip off indentation from text to be inserted.
+ */
+ while(*q == '\t' || *q == ' ') q++;
+#endif
+ for(;;) {
+
+ if(p > tbend)
+ return(NULL); /* overflowed the buffer */
+ if((*p = *q++) == '\\')
+ *p = *q++;
+ if(*p == '\0') {
+ commands->cp = --q;
+ return(++p);
+ }
+#ifndef S5EMUL
+ /*
+ * Strip off indentation from text to be inserted.
+ */
+ if(*p == '\n') {
+ while(*q == '\t' || *q == ' ') q++;
+ }
+#endif
+ p++;
+ }
+}
+
+
+/*
+ * search
+ */
+static sed_label_t *search(sed_commands_t *commands)
+{
+ sed_label_t *rp;
+ sed_label_t *ptr;
+
+ rp = commands->labtab;
+ ptr = commands->lab;
+ while (rp < ptr) {
+ if (strcmp(rp->asc, ptr->asc) == 0)
+ return rp;
+ rp++;
+ }
+
+ return 0;
+}
+
+/*
+ * ycomp
+ */
+static char *ycomp(sed_commands_t *commands, char *expbuf)
+{
+ char c;
+ int cint; /* integer value of char c */
+ char *ep, *tsp;
+ int i;
+ char *sp;
+
+ ep = expbuf;
+ if(ep + 0377 > &commands->respace[RESIZE-1]) {
+ command_errf(commands, SEDERR_TMMES, commands->linebuf);
+ return NULL;
+ }
+ sp = commands->cp;
+ for(tsp = commands->cp; (c = *tsp) != commands->sseof; tsp++) {
+ if(c == '\\')
+ tsp++;
+ if(c == '\0' || c == '\n') {
+ command_errf(commands, SEDERR_EDMOSTR, commands->linebuf);
+ return NULL;
+ }
+ }
+ tsp++;
+ memset(ep, 0, 0400);
+
+ while((c = *sp++) != commands->sseof) {
+ c &= 0377;
+ if(c == '\\' && *sp == 'n') {
+ sp++;
+ c = '\n';
+ }
+ cint = (int) c;
+ if((ep[cint] = *tsp++) == '\\' && *tsp == 'n') {
+ ep[cint] = '\n';
+ tsp++;
+ }
+ if(ep[cint] == commands->sseof || ep[cint] == '\0') {
+ command_errf(commands, SEDERR_TSNTSS, commands->linebuf);
+ }
+ }
+ if(*tsp != commands->sseof) {
+ if(*tsp == '\0') {
+ command_errf(commands, SEDERR_EDMOSTR, commands->linebuf);
+ }
+ else {
+ command_errf(commands, SEDERR_TSNTSS, commands->linebuf);
+ }
+ return NULL;
+ }
+ commands->cp = ++tsp;
+
+ for(i = 0; i < 0400; i++)
+ if(ep[i] == 0)
+ ep[i] = i;
+
+ return(ep + 0400);
+}
+
+/*
+ * comple
+ */
+static char *comple(sed_commands_t *commands, sed_comp_args *compargs,
+ char *x1, char *ep, char *x3, char x4)
+{
+ char *p;
+
+ p = sed_compile(commands, compargs, ep + 1, x3, x4);
+ if(p == ep + 1)
+ return(ep);
+ *ep = compargs->circf;
+ return(p);
+}
+
+/*
+ * alloc_reptr
+ */
+static sed_reptr_t *alloc_reptr(sed_commands_t *commands)
+{
+ sed_reptr_t *var;
+
+ var = apr_pcalloc(commands->pool, sizeof(sed_reptr_t));
+ if (var == NULL) {
+ command_errf(commands, SEDERR_OOMMES);
+ return 0;
+ }
+
+ var->nrep = commands->nrep;
+ var->findex = -1;
+ commands->nrep++;
+
+ if (commands->ptrspace == NULL)
+ commands->ptrspace = var;
+ else
+ commands->ptrend->next = var;
+
+ commands->ptrend = var;
+ commands->labtab->address = var;
+ return var;
+}
+
+
diff --git a/modules/filters/sed1.c b/modules/filters/sed1.c
new file mode 100644
index 0000000..047f49b
--- /dev/null
+++ b/modules/filters/sed1.c
@@ -0,0 +1,1110 @@
+/*
+ * Copyright (c) 2005, 2008 Sun Microsystems, Inc. All Rights Reserved.
+ * Use is subject to license terms.
+ *
+ * Copyright (c) 1984 AT&T
+ * All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr.h"
+#include "apr_lib.h"
+#include "libsed.h"
+#include "sed.h"
+#include "apr_strings.h"
+#include "regexp.h"
+
+static const char *const trans[040] = {
+ "\\01",
+ "\\02",
+ "\\03",
+ "\\04",
+ "\\05",
+ "\\06",
+ "\\07",
+ "\\10",
+ "\\11",
+ "\n",
+ "\\13",
+ "\\14",
+ "\\15",
+ "\\16",
+ "\\17",
+ "\\20",
+ "\\21",
+ "\\22",
+ "\\23",
+ "\\24",
+ "\\25",
+ "\\26",
+ "\\27",
+ "\\30",
+ "\\31",
+ "\\32",
+ "\\33",
+ "\\34",
+ "\\35",
+ "\\36",
+ "\\37"
+};
+static const char rub[] = {"\\177"};
+
+extern int sed_step(char *p1, char *p2, int circf, step_vars_storage *vars);
+static int substitute(sed_eval_t *eval, sed_reptr_t *ipc,
+ step_vars_storage *step_vars);
+static apr_status_t execute(sed_eval_t *eval);
+static int match(sed_eval_t *eval, char *expbuf, int gf,
+ step_vars_storage *step_vars);
+static apr_status_t dosub(sed_eval_t *eval, char *rhsbuf, int n,
+ step_vars_storage *step_vars);
+static char *place(sed_eval_t *eval, char *asp, char *al1, char *al2);
+static apr_status_t command(sed_eval_t *eval, sed_reptr_t *ipc,
+ step_vars_storage *step_vars);
+static apr_status_t wline(sed_eval_t *eval, char *buf, apr_size_t sz);
+static apr_status_t arout(sed_eval_t *eval);
+
+static void eval_errf(sed_eval_t *eval, const char *fmt, ...)
+{
+ if (eval->errfn && eval->pool) {
+ va_list args;
+ const char* error;
+ va_start(args, fmt);
+ error = apr_pvsprintf(eval->pool, fmt, args);
+ eval->errfn(eval->data, error);
+ va_end(args);
+ }
+}
+
+#define INIT_BUF_SIZE 1024
+#define MAX_BUF_SIZE 1024*8192
+
+/*
+ * grow_buffer
+ */
+static apr_status_t grow_buffer(apr_pool_t *pool, char **buffer,
+ char **spend, apr_size_t *cursize,
+ apr_size_t newsize)
+{
+ char* newbuffer = NULL;
+ apr_size_t spendsize = 0;
+ if (*cursize >= newsize) {
+ return APR_SUCCESS;
+ }
+ /* Avoid number of times realloc is called. It could cause huge memory
+ * requirement if line size is huge e.g 2 MB */
+ if (newsize < *cursize * 2) {
+ newsize = *cursize * 2;
+ }
+
+ /* Align it to 4 KB boundary */
+ newsize = (newsize + ((1 << 12) - 1)) & ~((1 << 12) - 1);
+ if (newsize > MAX_BUF_SIZE) {
+ return APR_ENOMEM;
+ }
+ newbuffer = apr_pcalloc(pool, newsize);
+ if (*spend && *buffer && (*cursize > 0)) {
+ spendsize = *spend - *buffer;
+ }
+ if ((*cursize > 0) && *buffer) {
+ memcpy(newbuffer, *buffer, *cursize);
+ }
+ *buffer = newbuffer;
+ *cursize = newsize;
+ if (spend != buffer) {
+ *spend = *buffer + spendsize;
+ }
+ return APR_SUCCESS;
+}
+
+/*
+ * grow_line_buffer
+ */
+static apr_status_t grow_line_buffer(sed_eval_t *eval, apr_size_t newsize)
+{
+ return grow_buffer(eval->pool, &eval->linebuf, &eval->lspend,
+ &eval->lsize, newsize);
+}
+
+/*
+ * grow_hold_buffer
+ */
+static apr_status_t grow_hold_buffer(sed_eval_t *eval, apr_size_t newsize)
+{
+ return grow_buffer(eval->pool, &eval->holdbuf, &eval->hspend,
+ &eval->hsize, newsize);
+}
+
+/*
+ * grow_gen_buffer
+ */
+static apr_status_t grow_gen_buffer(sed_eval_t *eval, apr_size_t newsize,
+ char **gspend)
+{
+ apr_status_t rc = 0;
+ if (gspend == NULL) {
+ gspend = &eval->genbuf;
+ }
+ rc = grow_buffer(eval->pool, &eval->genbuf, gspend,
+ &eval->gsize, newsize);
+ if (rc == APR_SUCCESS) {
+ eval->lcomend = &eval->genbuf[71];
+ }
+ return rc;
+}
+
+/*
+ * appendmem_to_linebuf
+ */
+static apr_status_t appendmem_to_linebuf(sed_eval_t *eval, const char* sz, apr_size_t len)
+{
+ apr_status_t rc = 0;
+ apr_size_t reqsize = (eval->lspend - eval->linebuf) + len;
+ if (eval->lsize < reqsize) {
+ rc = grow_line_buffer(eval, reqsize);
+ if (rc != APR_SUCCESS) {
+ return rc;
+ }
+ }
+ memcpy(eval->lspend, sz, len);
+ eval->lspend += len;
+ return APR_SUCCESS;
+}
+
+/*
+ * append_to_linebuf
+ */
+static apr_status_t append_to_linebuf(sed_eval_t *eval, const char* sz,
+ step_vars_storage *step_vars)
+{
+ apr_size_t len = strlen(sz);
+ char *old_linebuf = eval->linebuf;
+ apr_status_t rc = 0;
+ /* Copy string including null character */
+ rc = appendmem_to_linebuf(eval, sz, len + 1);
+ if (rc != APR_SUCCESS) {
+ return rc;
+ }
+ --eval->lspend; /* lspend will now point to NULL character */
+ /* Sync step_vars after a possible linebuf expansion */
+ if (step_vars && old_linebuf != eval->linebuf) {
+ if (step_vars->loc1) {
+ step_vars->loc1 = step_vars->loc1 - old_linebuf + eval->linebuf;
+ }
+ if (step_vars->loc2) {
+ step_vars->loc2 = step_vars->loc2 - old_linebuf + eval->linebuf;
+ }
+ if (step_vars->locs) {
+ step_vars->locs = step_vars->locs - old_linebuf + eval->linebuf;
+ }
+ }
+ return APR_SUCCESS;
+}
+
+/*
+ * copy_to_linebuf
+ */
+static apr_status_t copy_to_linebuf(sed_eval_t *eval, const char* sz,
+ step_vars_storage *step_vars)
+{
+ eval->lspend = eval->linebuf;
+ return append_to_linebuf(eval, sz, step_vars);
+}
+
+/*
+ * append_to_holdbuf
+ */
+static apr_status_t append_to_holdbuf(sed_eval_t *eval, const char* sz)
+{
+ apr_size_t len = strlen(sz);
+ apr_size_t reqsize = (eval->hspend - eval->holdbuf) + len + 1;
+ apr_status_t rc = 0;
+ if (eval->hsize <= reqsize) {
+ rc = grow_hold_buffer(eval, reqsize);
+ if (rc != APR_SUCCESS) {
+ return rc;
+ }
+ }
+ memcpy(eval->hspend, sz, len + 1);
+ /* hspend will now point to NULL character */
+ eval->hspend += len;
+ return APR_SUCCESS;
+}
+
+/*
+ * copy_to_holdbuf
+ */
+static apr_status_t copy_to_holdbuf(sed_eval_t *eval, const char* sz)
+{
+ eval->hspend = eval->holdbuf;
+ return append_to_holdbuf(eval, sz);
+}
+
+/*
+ * append_to_genbuf
+ */
+static apr_status_t append_to_genbuf(sed_eval_t *eval, const char* sz, char **gspend)
+{
+ apr_size_t len = strlen(sz);
+ apr_size_t reqsize = (*gspend - eval->genbuf) + len + 1;
+ apr_status_t rc = 0;
+ if (eval->gsize < reqsize) {
+ rc = grow_gen_buffer(eval, reqsize, gspend);
+ if (rc != APR_SUCCESS) {
+ return rc;
+ }
+ }
+ memcpy(*gspend, sz, len + 1);
+ /* *gspend will now point to NULL character */
+ *gspend += len;
+ return APR_SUCCESS;
+}
+
+/*
+ * copy_to_genbuf
+ */
+static apr_status_t copy_to_genbuf(sed_eval_t *eval, const char* sz)
+{
+ apr_size_t len = strlen(sz);
+ apr_size_t reqsize = len + 1;
+ apr_status_t rc = APR_SUCCESS;;
+ if (eval->gsize < reqsize) {
+ rc = grow_gen_buffer(eval, reqsize, NULL);
+ if (rc != APR_SUCCESS) {
+ return rc;
+ }
+ }
+ memcpy(eval->genbuf, sz, len + 1);
+ return rc;
+}
+
+/*
+ * sed_init_eval
+ */
+apr_status_t sed_init_eval(sed_eval_t *eval, sed_commands_t *commands, sed_err_fn_t *errfn, void *data, sed_write_fn_t *writefn, apr_pool_t* p)
+{
+ memset(eval, 0, sizeof(*eval));
+ eval->pool = p;
+ eval->writefn = writefn;
+ return sed_reset_eval(eval, commands, errfn, data);
+}
+
+/*
+ * sed_reset_eval
+ */
+apr_status_t sed_reset_eval(sed_eval_t *eval, sed_commands_t *commands, sed_err_fn_t *errfn, void *data)
+{
+ int i;
+
+ eval->errfn = errfn;
+ eval->data = data;
+
+ eval->commands = commands;
+
+ eval->lnum = 0;
+ eval->fout = NULL;
+
+ if (eval->linebuf == NULL) {
+ eval->lsize = INIT_BUF_SIZE;
+ eval->linebuf = apr_pcalloc(eval->pool, eval->lsize);
+ }
+ if (eval->holdbuf == NULL) {
+ eval->hsize = INIT_BUF_SIZE;
+ eval->holdbuf = apr_pcalloc(eval->pool, eval->hsize);
+ }
+ if (eval->genbuf == NULL) {
+ eval->gsize = INIT_BUF_SIZE;
+ eval->genbuf = apr_pcalloc(eval->pool, eval->gsize);
+ }
+ eval->lspend = eval->linebuf;
+ eval->hspend = eval->holdbuf;
+ eval->lcomend = &eval->genbuf[71];
+
+ for (i = 0; i < sizeof(eval->abuf) / sizeof(eval->abuf[0]); i++)
+ eval->abuf[i] = NULL;
+ eval->aptr = eval->abuf;
+ eval->pending = NULL;
+ eval->inar = apr_pcalloc(eval->pool, commands->nrep * sizeof(unsigned char));
+ eval->nrep = commands->nrep;
+
+ eval->dolflag = 0;
+ eval->sflag = 0;
+ eval->jflag = 0;
+ eval->delflag = 0;
+ eval->lreadyflag = 0;
+ eval->quitflag = 0;
+ eval->finalflag = 1; /* assume we're evaluating only one file/stream */
+ eval->numpass = 0;
+ eval->nullmatch = 0;
+ eval->col = 0;
+
+ for (i = 0; i < commands->nfiles; i++) {
+ const char* filename = commands->fname[i];
+ if (apr_file_open(&eval->fcode[i], filename,
+ APR_WRITE | APR_CREATE, APR_OS_DEFAULT,
+ eval->pool) != APR_SUCCESS) {
+ eval_errf(eval, SEDERR_COMES, filename);
+ return APR_EGENERAL;
+ }
+ }
+
+ return APR_SUCCESS;
+}
+
+/*
+ * sed_destroy_eval
+ */
+void sed_destroy_eval(sed_eval_t *eval)
+{
+ int i;
+ /* eval->linebuf, eval->holdbuf, eval->genbuf and eval->inar are allocated
+ * on pool. It will be freed when pool will be freed */
+ for (i = 0; i < eval->commands->nfiles; i++) {
+ if (eval->fcode[i] != NULL) {
+ apr_file_close(eval->fcode[i]);
+ eval->fcode[i] = NULL;
+ }
+ }
+}
+
+/*
+ * sed_eval_file
+ */
+apr_status_t sed_eval_file(sed_eval_t *eval, apr_file_t *fin, void *fout)
+{
+ for (;;) {
+ char buf[1024];
+ apr_size_t read_bytes = 0;
+
+ read_bytes = sizeof(buf);
+ if (apr_file_read(fin, buf, &read_bytes) != APR_SUCCESS)
+ break;
+
+ if (sed_eval_buffer(eval, buf, read_bytes, fout) != APR_SUCCESS)
+ return APR_EGENERAL;
+
+ if (eval->quitflag)
+ return APR_SUCCESS;
+ }
+
+ return sed_finalize_eval(eval, fout);
+}
+
+/*
+ * sed_eval_buffer
+ */
+apr_status_t sed_eval_buffer(sed_eval_t *eval, const char *buf, apr_size_t bufsz, void *fout)
+{
+ apr_status_t rv;
+
+ if (eval->quitflag)
+ return APR_SUCCESS;
+
+ if (!sed_canbe_finalized(eval->commands)) {
+ /* Commands were not finalized properly. */
+ const char* error = sed_get_finalize_error(eval->commands, eval->pool);
+ if (error) {
+ eval_errf(eval, error);
+ return APR_EGENERAL;
+ }
+ }
+
+ eval->fout = fout;
+
+ /* Process leftovers */
+ if (bufsz && eval->lreadyflag) {
+ eval->lreadyflag = 0;
+ eval->lspend--;
+ *eval->lspend = '\0';
+ rv = execute(eval);
+ if (rv != APR_SUCCESS)
+ return rv;
+ }
+
+ while (bufsz) {
+ apr_status_t rc = 0;
+ char *n;
+ apr_size_t llen;
+
+ n = memchr(buf, '\n', bufsz);
+ if (n == NULL)
+ break;
+
+ llen = n - buf;
+ if (llen == bufsz - 1) {
+ /* This might be the last line; delay its processing */
+ eval->lreadyflag = 1;
+ break;
+ }
+
+ rc = appendmem_to_linebuf(eval, buf, llen + 1);
+ if (rc != APR_SUCCESS) {
+ return rc;
+ }
+ --eval->lspend;
+ /* replace new line character with NULL */
+ *eval->lspend = '\0';
+ buf += (llen + 1);
+ bufsz -= (llen + 1);
+ rv = execute(eval);
+ if (rv != APR_SUCCESS)
+ return rv;
+ if (eval->quitflag)
+ break;
+ }
+
+ /* Save the leftovers for later */
+ if (bufsz) {
+ apr_status_t rc = appendmem_to_linebuf(eval, buf, bufsz);
+ if (rc != APR_SUCCESS) {
+ return rc;
+ }
+ }
+
+ return APR_SUCCESS;
+}
+
+/*
+ * sed_finalize_eval
+ */
+apr_status_t sed_finalize_eval(sed_eval_t *eval, void *fout)
+{
+ if (eval->quitflag)
+ return APR_SUCCESS;
+
+ if (eval->finalflag)
+ eval->dolflag = 1;
+
+ eval->fout = fout;
+
+ /* Process leftovers */
+ if (eval->lspend > eval->linebuf) {
+ apr_status_t rv;
+ apr_status_t rc = 0;
+
+ if (eval->lreadyflag) {
+ eval->lreadyflag = 0;
+ eval->lspend--;
+ } else {
+ /* Code can probably reach here when last character in output
+ * buffer is not a newline.
+ */
+ /* Assure space for NULL */
+ rc = append_to_linebuf(eval, "", NULL);
+ if (rc != APR_SUCCESS) {
+ return rc;
+ }
+ }
+
+ *eval->lspend = '\0';
+ rv = execute(eval);
+ if (rv != APR_SUCCESS)
+ return rv;
+ }
+
+ eval->quitflag = 1;
+
+ return APR_SUCCESS;
+}
+
+/*
+ * execute
+ */
+static apr_status_t execute(sed_eval_t *eval)
+{
+ sed_reptr_t *ipc = eval->commands->ptrspace;
+ step_vars_storage step_vars;
+ apr_status_t rv = APR_SUCCESS;
+
+ eval->lnum++;
+
+ eval->sflag = 0;
+
+ if (eval->pending) {
+ ipc = eval->pending;
+ eval->pending = NULL;
+ }
+
+ memset(&step_vars, 0, sizeof(step_vars));
+
+ while (ipc->command) {
+ char *p1;
+ char *p2;
+ int c;
+
+ p1 = ipc->ad1;
+ p2 = ipc->ad2;
+
+ if (p1) {
+
+ if (eval->inar[ipc->nrep]) {
+ if (*p2 == CEND) {
+ p1 = 0;
+ } else if (*p2 == CLNUM) {
+ c = (unsigned char)p2[1];
+ if (eval->lnum > eval->commands->tlno[c]) {
+ eval->inar[ipc->nrep] = 0;
+ if (ipc->negfl)
+ goto yes;
+ ipc = ipc->next;
+ continue;
+ }
+ if (eval->lnum == eval->commands->tlno[c]) {
+ eval->inar[ipc->nrep] = 0;
+ }
+ } else if (match(eval, p2, 0, &step_vars)) {
+ eval->inar[ipc->nrep] = 0;
+ }
+ } else if (*p1 == CEND) {
+ if (!eval->dolflag) {
+ if (ipc->negfl)
+ goto yes;
+ ipc = ipc->next;
+ continue;
+ }
+ } else if (*p1 == CLNUM) {
+ c = (unsigned char)p1[1];
+ if (eval->lnum != eval->commands->tlno[c]) {
+ if (ipc->negfl)
+ goto yes;
+ ipc = ipc->next;
+ continue;
+ }
+ if (p2)
+ eval->inar[ipc->nrep] = 1;
+ } else if (match(eval, p1, 0, &step_vars)) {
+ if (p2)
+ eval->inar[ipc->nrep] = 1;
+ } else {
+ if (ipc->negfl)
+ goto yes;
+ ipc = ipc->next;
+ continue;
+ }
+ }
+
+ if (ipc->negfl) {
+ ipc = ipc->next;
+ continue;
+ }
+
+yes:
+ rv = command(eval, ipc, &step_vars);
+ if (rv != APR_SUCCESS)
+ return rv;
+
+ if (eval->quitflag)
+ return APR_SUCCESS;
+
+ if (eval->pending)
+ return APR_SUCCESS;
+
+ if (eval->delflag)
+ break;
+
+ if (eval->jflag) {
+ eval->jflag = 0;
+ if ((ipc = ipc->lb1) == 0) {
+ ipc = eval->commands->ptrspace;
+ break;
+ }
+ } else
+ ipc = ipc->next;
+ }
+
+ if (!eval->commands->nflag && !eval->delflag) {
+ rv = wline(eval, eval->linebuf, eval->lspend - eval->linebuf);
+ if (rv != APR_SUCCESS)
+ return rv;
+ }
+
+ if (eval->aptr > eval->abuf)
+ rv = arout(eval);
+
+ eval->delflag = 0;
+
+ eval->lspend = eval->linebuf;
+
+ return rv;
+}
+
+/*
+ * match
+ */
+static int match(sed_eval_t *eval, char *expbuf, int gf,
+ step_vars_storage *step_vars)
+{
+ char *p1;
+ int circf;
+
+ if (gf) {
+ if (*expbuf) return(0);
+ step_vars->locs = p1 = step_vars->loc2;
+ } else {
+ p1 = eval->linebuf;
+ step_vars->locs = 0;
+ }
+
+ circf = *expbuf++;
+ return(sed_step(p1, expbuf, circf, step_vars));
+}
+
+/*
+ * substitute
+ */
+static int substitute(sed_eval_t *eval, sed_reptr_t *ipc,
+ step_vars_storage *step_vars)
+{
+ if (match(eval, ipc->re1, 0, step_vars) == 0) return(0);
+
+ eval->numpass = 0;
+ eval->sflag = 0; /* Flags if any substitution was made */
+ if (dosub(eval, ipc->rhs, ipc->gfl, step_vars) != APR_SUCCESS)
+ return -1;
+
+ if (ipc->gfl) {
+ while (*step_vars->loc2) {
+ if (match(eval, ipc->re1, 1, step_vars) == 0) break;
+ if (dosub(eval, ipc->rhs, ipc->gfl, step_vars) != APR_SUCCESS)
+ return -1;
+ }
+ }
+ return(eval->sflag);
+}
+
+/*
+ * dosub
+ */
+static apr_status_t dosub(sed_eval_t *eval, char *rhsbuf, int n,
+ step_vars_storage *step_vars)
+{
+ char *lp, *sp, *rp;
+ int c;
+ apr_status_t rv = APR_SUCCESS;
+
+ if (n > 0 && n < 999) {
+ eval->numpass++;
+ if (n != eval->numpass) return APR_SUCCESS;
+ }
+ eval->sflag = 1;
+ lp = eval->linebuf;
+ sp = eval->genbuf;
+ rp = rhsbuf;
+ sp = place(eval, sp, lp, step_vars->loc1);
+ if (sp == NULL) {
+ return APR_EGENERAL;
+ }
+ while ((c = *rp++) != 0) {
+ if (c == '&') {
+ sp = place(eval, sp, step_vars->loc1, step_vars->loc2);
+ if (sp == NULL) {
+ return APR_EGENERAL;
+ }
+ }
+ else if (c == '\\') {
+ c = *rp++;
+ if (c >= '1' && c < NBRA+'1') {
+ sp = place(eval, sp, step_vars->braslist[c-'1'],
+ step_vars->braelist[c-'1']);
+ if (sp == NULL)
+ return APR_EGENERAL;
+ }
+ else
+ *sp++ = c;
+ } else
+ *sp++ = c;
+ if (sp >= eval->genbuf + eval->gsize) {
+ /* expand genbuf and set the sp appropriately */
+ rv = grow_gen_buffer(eval, eval->gsize + 1024, &sp);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+ }
+ lp = step_vars->loc2;
+ step_vars->loc2 = sp - eval->genbuf + eval->linebuf;
+ rv = append_to_genbuf(eval, lp, &sp);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ rv = copy_to_linebuf(eval, eval->genbuf, step_vars);
+ return rv;
+}
+
+/*
+ * place
+ */
+static char *place(sed_eval_t *eval, char *asp, char *al1, char *al2)
+{
+ char *sp = asp;
+ apr_size_t n = al2 - al1;
+ apr_size_t reqsize = (sp - eval->genbuf) + n + 1;
+
+ if (eval->gsize < reqsize) {
+ apr_status_t rc = grow_gen_buffer(eval, reqsize, &sp);
+ if (rc != APR_SUCCESS) {
+ return NULL;
+ }
+ }
+ memcpy(sp, al1, n);
+ return sp + n;
+}
+
+/*
+ * command
+ */
+static apr_status_t command(sed_eval_t *eval, sed_reptr_t *ipc,
+ step_vars_storage *step_vars)
+{
+ int i;
+ char *p1, *p2;
+ const char *p3;
+ int length;
+ char sz[32]; /* 32 bytes enough to store 64 bit integer in decimal */
+ apr_status_t rv = APR_SUCCESS;
+
+
+ switch(ipc->command) {
+
+ case ACOM:
+ if (eval->aptr >= &eval->abuf[SED_ABUFSIZE]) {
+ eval_errf(eval, SEDERR_TMAMES, eval->lnum);
+ } else {
+ *eval->aptr++ = ipc;
+ *eval->aptr = NULL;
+ }
+ break;
+
+ case CCOM:
+ eval->delflag = 1;
+ if (!eval->inar[ipc->nrep] || eval->dolflag) {
+ for (p1 = ipc->re1; *p1; p1++)
+ ;
+ rv = wline(eval, ipc->re1, p1 - ipc->re1);
+ }
+ break;
+
+ case DCOM:
+ eval->delflag++;
+ break;
+
+ case CDCOM:
+ p1 = eval->linebuf;
+
+ while (*p1 != '\n') {
+ if (*p1++ == 0) {
+ eval->delflag++;
+ return APR_SUCCESS;
+ }
+ }
+
+ p1++;
+ rv = copy_to_linebuf(eval, p1, step_vars);
+ if (rv != APR_SUCCESS) return rv;
+ eval->jflag++;
+ break;
+
+ case EQCOM:
+ length = apr_snprintf(sz, sizeof(sz), "%d", (int) eval->lnum);
+ rv = wline(eval, sz, length);
+ break;
+
+ case GCOM:
+ rv = copy_to_linebuf(eval, eval->holdbuf, step_vars);
+ if (rv != APR_SUCCESS) return rv;
+ break;
+
+ case CGCOM:
+ rv = append_to_linebuf(eval, "\n", step_vars);
+ if (rv != APR_SUCCESS) return rv;
+ rv = append_to_linebuf(eval, eval->holdbuf, step_vars);
+ if (rv != APR_SUCCESS) return rv;
+ break;
+
+ case HCOM:
+ rv = copy_to_holdbuf(eval, eval->linebuf);
+ if (rv != APR_SUCCESS) return rv;
+ break;
+
+ case CHCOM:
+ rv = append_to_holdbuf(eval, "\n");
+ if (rv != APR_SUCCESS) return rv;
+ rv = append_to_holdbuf(eval, eval->linebuf);
+ if (rv != APR_SUCCESS) return rv;
+ break;
+
+ case ICOM:
+ for (p1 = ipc->re1; *p1; p1++);
+ rv = wline(eval, ipc->re1, p1 - ipc->re1);
+ break;
+
+ case BCOM:
+ eval->jflag = 1;
+ break;
+
+ case LCOM:
+ p1 = eval->linebuf;
+ p2 = eval->genbuf;
+ eval->genbuf[72] = 0;
+ while (*p1) {
+ if ((unsigned char)*p1 >= 040) {
+ if (*p1 == 0177) {
+ p3 = rub;
+ while ((*p2++ = *p3++) != 0)
+ if (p2 >= eval->lcomend) {
+ *p2 = '\\';
+ rv = wline(eval, eval->genbuf,
+ strlen(eval->genbuf));
+ if (rv != APR_SUCCESS)
+ return rv;
+ p2 = eval->genbuf;
+ }
+ p2--;
+ p1++;
+ continue;
+ }
+ if (!isprint(*p1 & 0377)) {
+ *p2++ = '\\';
+ if (p2 >= eval->lcomend) {
+ *p2 = '\\';
+ rv = wline(eval, eval->genbuf,
+ strlen(eval->genbuf));
+ if (rv != APR_SUCCESS)
+ return rv;
+ p2 = eval->genbuf;
+ }
+ *p2++ = (*p1 >> 6) + '0';
+ if (p2 >= eval->lcomend) {
+ *p2 = '\\';
+ rv = wline(eval, eval->genbuf,
+ strlen(eval->genbuf));
+ if (rv != APR_SUCCESS)
+ return rv;
+ p2 = eval->genbuf;
+ }
+ *p2++ = ((*p1 >> 3) & 07) + '0';
+ if (p2 >= eval->lcomend) {
+ *p2 = '\\';
+ rv = wline(eval, eval->genbuf,
+ strlen(eval->genbuf));
+ if (rv != APR_SUCCESS)
+ return rv;
+ p2 = eval->genbuf;
+ }
+ *p2++ = (*p1++ & 07) + '0';
+ if (p2 >= eval->lcomend) {
+ *p2 = '\\';
+ rv = wline(eval, eval->genbuf,
+ strlen(eval->genbuf));
+ if (rv != APR_SUCCESS)
+ return rv;
+ p2 = eval->genbuf;
+ }
+ } else {
+ *p2++ = *p1++;
+ if (p2 >= eval->lcomend) {
+ *p2 = '\\';
+ rv = wline(eval, eval->genbuf,
+ strlen(eval->genbuf));
+ if (rv != APR_SUCCESS)
+ return rv;
+ p2 = eval->genbuf;
+ }
+ }
+ } else {
+ p3 = trans[(unsigned char)*p1-1];
+ while ((*p2++ = *p3++) != 0)
+ if (p2 >= eval->lcomend) {
+ *p2 = '\\';
+ rv = wline(eval, eval->genbuf,
+ strlen(eval->genbuf));
+ if (rv != APR_SUCCESS)
+ return rv;
+ p2 = eval->genbuf;
+ }
+ p2--;
+ p1++;
+ }
+ }
+ *p2 = 0;
+ rv = wline(eval, eval->genbuf, strlen(eval->genbuf));
+ break;
+
+ case NCOM:
+ if (!eval->commands->nflag) {
+ rv = wline(eval, eval->linebuf, eval->lspend - eval->linebuf);
+ if (rv != APR_SUCCESS)
+ return rv;
+ }
+
+ if (eval->aptr > eval->abuf) {
+ rv = arout(eval);
+ if (rv != APR_SUCCESS)
+ return rv;
+ }
+ eval->lspend = eval->linebuf;
+ eval->pending = ipc->next;
+ break;
+
+ case CNCOM:
+ if (eval->aptr > eval->abuf) {
+ rv = arout(eval);
+ if (rv != APR_SUCCESS)
+ return rv;
+ }
+ rv = append_to_linebuf(eval, "\n", step_vars);
+ if (rv != APR_SUCCESS) return rv;
+ eval->pending = ipc->next;
+ break;
+
+ case PCOM:
+ rv = wline(eval, eval->linebuf, eval->lspend - eval->linebuf);
+ break;
+
+ case CPCOM:
+ for (p1 = eval->linebuf; *p1 != '\n' && *p1 != '\0'; p1++);
+ rv = wline(eval, eval->linebuf, p1 - eval->linebuf);
+ break;
+
+ case QCOM:
+ if (!eval->commands->nflag) {
+ rv = wline(eval, eval->linebuf, eval->lspend - eval->linebuf);
+ if (rv != APR_SUCCESS)
+ break;
+ }
+
+ if (eval->aptr > eval->abuf) {
+ rv = arout(eval);
+ if (rv != APR_SUCCESS)
+ return rv;
+ }
+
+ eval->quitflag = 1;
+ break;
+
+ case RCOM:
+ if (eval->aptr >= &eval->abuf[SED_ABUFSIZE]) {
+ eval_errf(eval, SEDERR_TMRMES, eval->lnum);
+ } else {
+ *eval->aptr++ = ipc;
+ *eval->aptr = NULL;
+ }
+ break;
+
+ case SCOM:
+ i = substitute(eval, ipc, step_vars);
+ if (i == -1) {
+ return APR_EGENERAL;
+ }
+ if (ipc->pfl && eval->commands->nflag && i) {
+ if (ipc->pfl == 1) {
+ rv = wline(eval, eval->linebuf, eval->lspend -
+ eval->linebuf);
+ if (rv != APR_SUCCESS)
+ return rv;
+ } else {
+ for (p1 = eval->linebuf; *p1 != '\n' && *p1 != '\0'; p1++);
+ rv = wline(eval, eval->linebuf, p1 - eval->linebuf);
+ if (rv != APR_SUCCESS)
+ return rv;
+ }
+ }
+ if (i && (ipc->findex >= 0) && eval->fcode[ipc->findex])
+ apr_file_printf(eval->fcode[ipc->findex], "%s\n",
+ eval->linebuf);
+ break;
+
+ case TCOM:
+ if (eval->sflag == 0) break;
+ eval->sflag = 0;
+ eval->jflag = 1;
+ break;
+
+ case WCOM:
+ if (ipc->findex >= 0)
+ apr_file_printf(eval->fcode[ipc->findex], "%s\n",
+ eval->linebuf);
+ break;
+
+ case XCOM:
+ rv = copy_to_genbuf(eval, eval->linebuf);
+ if (rv != APR_SUCCESS) return rv;
+ rv = copy_to_linebuf(eval, eval->holdbuf, step_vars);
+ if (rv != APR_SUCCESS) return rv;
+ rv = copy_to_holdbuf(eval, eval->genbuf);
+ if (rv != APR_SUCCESS) return rv;
+ break;
+
+ case YCOM:
+ p1 = eval->linebuf;
+ p2 = ipc->re1;
+ while ((*p1 = p2[(unsigned char)*p1]) != 0) p1++;
+ break;
+ }
+ return rv;
+}
+
+/*
+ * arout
+ */
+static apr_status_t arout(sed_eval_t *eval)
+{
+ apr_status_t rv = APR_SUCCESS;
+ eval->aptr = eval->abuf - 1;
+ while (*++eval->aptr) {
+ if ((*eval->aptr)->command == ACOM) {
+ char *p1;
+
+ for (p1 = (*eval->aptr)->re1; *p1; p1++);
+ rv = wline(eval, (*eval->aptr)->re1, p1 - (*eval->aptr)->re1);
+ if (rv != APR_SUCCESS)
+ return rv;
+ } else {
+ apr_file_t *fi = NULL;
+ char buf[512];
+ apr_size_t n = sizeof(buf);
+
+ if (apr_file_open(&fi, (*eval->aptr)->re1, APR_READ, 0, eval->pool)
+ != APR_SUCCESS)
+ continue;
+ while ((apr_file_read(fi, buf, &n)) == APR_SUCCESS) {
+ if (n == 0)
+ break;
+ rv = eval->writefn(eval->fout, buf, n);
+ if (rv != APR_SUCCESS) {
+ apr_file_close(fi);
+ return rv;
+ }
+ n = sizeof(buf);
+ }
+ apr_file_close(fi);
+ }
+ }
+ eval->aptr = eval->abuf;
+ *eval->aptr = NULL;
+ return rv;
+}
+
+/*
+ * wline
+ */
+static apr_status_t wline(sed_eval_t *eval, char *buf, apr_size_t sz)
+{
+ apr_status_t rv = APR_SUCCESS;
+ rv = eval->writefn(eval->fout, buf, sz);
+ if (rv != APR_SUCCESS)
+ return rv;
+ rv = eval->writefn(eval->fout, "\n", 1);
+ return rv;
+}
+
diff --git a/modules/generators/.indent.pro b/modules/generators/.indent.pro
new file mode 100644
index 0000000..a9fbe9f
--- /dev/null
+++ b/modules/generators/.indent.pro
@@ -0,0 +1,54 @@
+-i4 -npsl -di0 -br -nce -d0 -cli0 -npcs -nfc1
+-TBUFF
+-TFILE
+-TTRANS
+-TUINT4
+-T_trans
+-Tallow_options_t
+-Tapache_sfio
+-Tarray_header
+-Tbool_int
+-Tbuf_area
+-Tbuff_struct
+-Tbuffy
+-Tcmd_how
+-Tcmd_parms
+-Tcommand_rec
+-Tcommand_struct
+-Tconn_rec
+-Tcore_dir_config
+-Tcore_server_config
+-Tdir_maker_func
+-Tevent
+-Tglobals_s
+-Thandler_func
+-Thandler_rec
+-Tjoblist_s
+-Tlisten_rec
+-Tmerger_func
+-Tmode_t
+-Tmodule
+-Tmodule_struct
+-Tmutex
+-Tn_long
+-Tother_child_rec
+-Toverrides_t
+-Tparent_score
+-Tpid_t
+-Tpiped_log
+-Tpool
+-Trequest_rec
+-Trequire_line
+-Trlim_t
+-Tscoreboard
+-Tsemaphore
+-Tserver_addr_rec
+-Tserver_rec
+-Tserver_rec_chain
+-Tshort_score
+-Ttable
+-Ttable_entry
+-Tthread
+-Tu_wide_int
+-Tvtime_t
+-Twide_int
diff --git a/modules/generators/Makefile.in b/modules/generators/Makefile.in
new file mode 100644
index 0000000..167b343
--- /dev/null
+++ b/modules/generators/Makefile.in
@@ -0,0 +1,3 @@
+
+include $(top_srcdir)/build/special.mk
+
diff --git a/modules/generators/NWGNUautoindex b/modules/generators/NWGNUautoindex
new file mode 100644
index 0000000..e8080c3
--- /dev/null
+++ b/modules/generators/NWGNUautoindex
@@ -0,0 +1,249 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/modules/http \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = autoindex
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Autoindex Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Autoindex Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/autoindex.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_autoindex.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ autoindex_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/generators/NWGNUinfo b/modules/generators/NWGNUinfo
new file mode 100644
index 0000000..810db8a
--- /dev/null
+++ b/modules/generators/NWGNUinfo
@@ -0,0 +1,248 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = info
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Info Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Info Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/info.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_info.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ info_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/generators/NWGNUmakefile b/modules/generators/NWGNUmakefile
new file mode 100644
index 0000000..df9cfec
--- /dev/null
+++ b/modules/generators/NWGNUmakefile
@@ -0,0 +1,249 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME =
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION =
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME =
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE =
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/mod_asis.nlm \
+ $(OBJDIR)/autoindex.nlm \
+ $(OBJDIR)/mod_cgi.nlm \
+ $(OBJDIR)/info.nlm \
+ $(OBJDIR)/status.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/generators/NWGNUmod_asis b/modules/generators/NWGNUmod_asis
new file mode 100644
index 0000000..c7df5ae
--- /dev/null
+++ b/modules/generators/NWGNUmod_asis
@@ -0,0 +1,249 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/modules/http \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = mod_asis
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) ASIS Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Mod_asis Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/mod_asis.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_asis.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ asis_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/generators/NWGNUmod_cgi b/modules/generators/NWGNUmod_cgi
new file mode 100644
index 0000000..bbb9578
--- /dev/null
+++ b/modules/generators/NWGNUmod_cgi
@@ -0,0 +1,249 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/modules/http \
+ $(AP_WORK)/modules/filters \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = mod_cgi
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) CGI Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Mod_cgi Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/mod_cgi.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_cgi.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ cgi_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
diff --git a/modules/generators/NWGNUstatus b/modules/generators/NWGNUstatus
new file mode 100644
index 0000000..1dc850e
--- /dev/null
+++ b/modules/generators/NWGNUstatus
@@ -0,0 +1,248 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = status
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Status Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Status Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/status.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_status.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ status_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/generators/config5.m4 b/modules/generators/config5.m4
new file mode 100644
index 0000000..bf29521
--- /dev/null
+++ b/modules/generators/config5.m4
@@ -0,0 +1,81 @@
+dnl modules enabled in this directory by default
+
+dnl APACHE_MODULE(name, helptext[, objects[, structname[, default[, config]]]])
+
+APACHE_MODPATH_INIT(generators)
+
+APACHE_MODULE(status, process/thread monitoring, , , yes)
+APACHE_MODULE(autoindex, directory listing, , , yes)
+APACHE_MODULE(asis, as-is filetypes, , , )
+APACHE_MODULE(info, server information, , , most)
+APACHE_MODULE(suexec, set uid and gid for spawned processes, , , no, [
+ other_targets=suexec ] )
+
+# Is mod_cgid needed?
+case $host in
+ *mingw*)
+ dnl No fork+thread+fd issues, and cgid doesn't work anyway.
+ cgid_needed="no"
+ ;;
+ *)
+ if ap_mpm_is_threaded; then
+ dnl if we are using a threaded MPM on Unix, we can get better
+ dnl performance with mod_cgid, and also avoid potential issues
+ dnl with forking from a threaded process.
+ cgid_needed="yes"
+ else
+ dnl if we are using a non-threaded MPM, it makes little sense to
+ dnl use mod_cgid, and it just opens up holes we don't need.
+ cgid_needed="no"
+ fi
+ ;;
+esac
+
+if test $cgid_needed = "yes"; then
+ APACHE_MODULE(cgid, CGI scripts. Enabled by default with threaded MPMs, , , most, [
+ case $host in
+ *-solaris2*)
+ case `uname -r` in
+ 5.10)
+ dnl Does the system have the appropriate patches?
+ case `uname -p` in
+ i386)
+ patch_id="120665"
+ ;;
+ sparc)
+ patch_id="120664"
+ ;;
+ *)
+ AC_MSG_WARN([Unknown platform])
+ patch_id="120664"
+ ;;
+ esac
+ AC_MSG_CHECKING([for Solaris patch $patch_id])
+ showrev -p | grep "$patch_id" >/dev/null 2>&1
+ if test $? -eq 1; then
+ dnl Solaris 11 (next release) as of snv_19 doesn't have this problem.
+ dnl It may be possible to use /kernel/drv/tl from later releases.
+ AC_MSG_ERROR([Please apply either patch # 120664 (Sparc) or # 120665 (x86).
+Without these patches, mod_cgid is non-functional on Solaris 10 due to an OS
+bug with AF_UNIX sockets.
+If you can not apply these patches, you can do one of the following:
+ - run configure with --disable-cgid
+ - switch to the prefork MPM
+For more info: <http://issues.apache.org/bugzilla/show_bug.cgi?id=34264>])
+ else
+ AC_MSG_RESULT(yes)
+ fi
+ ;;
+ esac
+ ;;
+ esac
+ ])
+ APACHE_MODULE(cgi, CGI scripts. Enabled by default with non-threaded MPMs, , , no)
+else
+ APACHE_MODULE(cgi, CGI scripts. Enabled by default with non-threaded MPMs, , , most)
+ APACHE_MODULE(cgid, CGI scripts. Enabled by default with threaded MPMs, , , no)
+fi
+
+APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/$modpath_current])
+
+APACHE_MODPATH_FINISH
diff --git a/modules/generators/mod_asis.c b/modules/generators/mod_asis.c
new file mode 100644
index 0000000..c2b651b
--- /dev/null
+++ b/modules/generators/mod_asis.c
@@ -0,0 +1,128 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr_strings.h"
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_protocol.h"
+#include "http_log.h"
+#include "util_script.h"
+#include "http_main.h"
+#include "http_request.h"
+
+#include "mod_core.h"
+
+#define ASIS_MAGIC_TYPE "httpd/send-as-is"
+
+static int asis_handler(request_rec *r)
+{
+ apr_file_t *f;
+ apr_status_t rv;
+ const char *location;
+
+ if (strcmp(r->handler, ASIS_MAGIC_TYPE) && strcmp(r->handler, "send-as-is")) {
+ return DECLINED;
+ }
+
+ r->allowed |= (AP_METHOD_BIT << M_GET);
+ if (r->method_number != M_GET) {
+ return DECLINED;
+ }
+
+ if (r->finfo.filetype == APR_NOFILE) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01233)
+ "File does not exist: %s", r->filename);
+ return HTTP_NOT_FOUND;
+ }
+
+ if ((rv = apr_file_open(&f, r->filename, APR_READ,
+ APR_OS_DEFAULT, r->pool)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01234)
+ "file permissions deny server access: %s", r->filename);
+ return HTTP_FORBIDDEN;
+ }
+
+ ap_scan_script_header_err_ex(r, f, NULL, APLOG_MODULE_INDEX);
+ location = apr_table_get(r->headers_out, "Location");
+
+ if (location && location[0] == '/' &&
+ ((r->status == HTTP_OK) || ap_is_HTTP_REDIRECT(r->status))) {
+
+ apr_file_close(f);
+
+ /* Internal redirect -- fake-up a pseudo-request */
+ r->status = HTTP_OK;
+
+ /* This redirect needs to be a GET no matter what the original
+ * method was.
+ */
+ r->method = "GET";
+ r->method_number = M_GET;
+
+ ap_internal_redirect_handler(location, r);
+ return OK;
+ }
+
+ if (!r->header_only) {
+ conn_rec *c = r->connection;
+ apr_bucket_brigade *bb;
+ apr_bucket *b;
+ apr_off_t pos = 0;
+
+ rv = apr_file_seek(f, APR_CUR, &pos);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01235)
+ "mod_asis: failed to find end-of-headers position "
+ "for %s", r->filename);
+ apr_file_close(f);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ bb = apr_brigade_create(r->pool, c->bucket_alloc);
+ apr_brigade_insert_file(bb, f, pos, r->finfo.size - pos, r->pool);
+
+ b = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ rv = ap_pass_brigade(r->output_filters, bb);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01236)
+ "mod_asis: ap_pass_brigade failed for file %s", r->filename);
+ return AP_FILTER_ERROR;
+ }
+ }
+ else {
+ apr_file_close(f);
+ }
+
+ return OK;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_hook_handler(asis_handler,NULL,NULL,APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(asis) =
+{
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ NULL, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/generators/mod_asis.dep b/modules/generators/mod_asis.dep
new file mode 100644
index 0000000..cbfe300
--- /dev/null
+++ b/modules/generators/mod_asis.dep
@@ -0,0 +1,56 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_asis.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_asis.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_core.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/generators/mod_asis.dsp b/modules/generators/mod_asis.dsp
new file mode 100644
index 0000000..bad34f0
--- /dev/null
+++ b/modules/generators/mod_asis.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_asis" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_asis - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_asis.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_asis.mak" CFG="mod_asis - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_asis - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_asis - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_asis - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_asis_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_asis.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_asis.so" /d LONG_NAME="asis_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_asis.so" /base:@..\..\os\win32\BaseAddr.ref,mod_asis.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_asis.so" /base:@..\..\os\win32\BaseAddr.ref,mod_asis.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_asis.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_asis - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_asis_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_asis.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_asis.so" /d LONG_NAME="asis_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_asis.so" /base:@..\..\os\win32\BaseAddr.ref,mod_asis.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_asis.so" /base:@..\..\os\win32\BaseAddr.ref,mod_asis.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_asis.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_asis - Win32 Release"
+# Name "mod_asis - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_asis.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/generators/mod_asis.exp b/modules/generators/mod_asis.exp
new file mode 100644
index 0000000..4f347d9
--- /dev/null
+++ b/modules/generators/mod_asis.exp
@@ -0,0 +1 @@
+asis_module
diff --git a/modules/generators/mod_asis.mak b/modules/generators/mod_asis.mak
new file mode 100644
index 0000000..f069aac
--- /dev/null
+++ b/modules/generators/mod_asis.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_asis.dsp
+!IF "$(CFG)" == ""
+CFG=mod_asis - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_asis - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_asis - Win32 Release" && "$(CFG)" != "mod_asis - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_asis.mak" CFG="mod_asis - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_asis - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_asis - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_asis - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_asis.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_asis.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_asis.obj"
+ -@erase "$(INTDIR)\mod_asis.res"
+ -@erase "$(INTDIR)\mod_asis_src.idb"
+ -@erase "$(INTDIR)\mod_asis_src.pdb"
+ -@erase "$(OUTDIR)\mod_asis.exp"
+ -@erase "$(OUTDIR)\mod_asis.lib"
+ -@erase "$(OUTDIR)\mod_asis.pdb"
+ -@erase "$(OUTDIR)\mod_asis.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_asis_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_asis.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_asis.so" /d LONG_NAME="asis_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_asis.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_asis.pdb" /debug /out:"$(OUTDIR)\mod_asis.so" /implib:"$(OUTDIR)\mod_asis.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_asis.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_asis.obj" \
+ "$(INTDIR)\mod_asis.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_asis.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_asis.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_asis.so"
+ if exist .\Release\mod_asis.so.manifest mt.exe -manifest .\Release\mod_asis.so.manifest -outputresource:.\Release\mod_asis.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_asis - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_asis.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_asis.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_asis.obj"
+ -@erase "$(INTDIR)\mod_asis.res"
+ -@erase "$(INTDIR)\mod_asis_src.idb"
+ -@erase "$(INTDIR)\mod_asis_src.pdb"
+ -@erase "$(OUTDIR)\mod_asis.exp"
+ -@erase "$(OUTDIR)\mod_asis.lib"
+ -@erase "$(OUTDIR)\mod_asis.pdb"
+ -@erase "$(OUTDIR)\mod_asis.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_asis_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_asis.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_asis.so" /d LONG_NAME="asis_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_asis.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_asis.pdb" /debug /out:"$(OUTDIR)\mod_asis.so" /implib:"$(OUTDIR)\mod_asis.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_asis.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_asis.obj" \
+ "$(INTDIR)\mod_asis.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_asis.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_asis.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_asis.so"
+ if exist .\Debug\mod_asis.so.manifest mt.exe -manifest .\Debug\mod_asis.so.manifest -outputresource:.\Debug\mod_asis.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_asis.dep")
+!INCLUDE "mod_asis.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_asis.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_asis - Win32 Release" || "$(CFG)" == "mod_asis - Win32 Debug"
+
+!IF "$(CFG)" == "mod_asis - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\generators"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\generators"
+
+!ELSEIF "$(CFG)" == "mod_asis - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\generators"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\generators"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_asis - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\generators"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\generators"
+
+!ELSEIF "$(CFG)" == "mod_asis - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\generators"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\generators"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_asis - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\generators"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\generators"
+
+!ELSEIF "$(CFG)" == "mod_asis - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\generators"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\generators"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_asis - Win32 Release"
+
+
+"$(INTDIR)\mod_asis.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_asis.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_asis.so" /d LONG_NAME="asis_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_asis - Win32 Debug"
+
+
+"$(INTDIR)\mod_asis.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_asis.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_asis.so" /d LONG_NAME="asis_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_asis.c
+
+"$(INTDIR)\mod_asis.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/generators/mod_autoindex.c b/modules/generators/mod_autoindex.c
new file mode 100644
index 0000000..cb44603
--- /dev/null
+++ b/modules/generators/mod_autoindex.c
@@ -0,0 +1,2350 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * mod_autoindex.c: Handles the on-the-fly html index generation
+ *
+ * Rob McCool
+ * 3/23/93
+ *
+ * Adapted to Apache by rst.
+ *
+ * Version sort added by Martin Pool <mbp@humbug.org.au>.
+ */
+
+#include "apr_strings.h"
+#include "apr_fnmatch.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_request.h"
+#include "http_protocol.h"
+#include "http_log.h"
+#include "http_main.h"
+#include "util_script.h"
+
+#include "mod_core.h"
+
+module AP_MODULE_DECLARE_DATA autoindex_module;
+
+/****************************************************************
+ *
+ * Handling configuration directives...
+ */
+
+#define NO_OPTIONS (1 << 0) /* Indexing options */
+#define ICONS_ARE_LINKS (1 << 1)
+#define SCAN_HTML_TITLES (1 << 2)
+#define SUPPRESS_ICON (1 << 3)
+#define SUPPRESS_LAST_MOD (1 << 4)
+#define SUPPRESS_SIZE (1 << 5)
+#define SUPPRESS_DESC (1 << 6)
+#define SUPPRESS_PREAMBLE (1 << 7)
+#define SUPPRESS_COLSORT (1 << 8)
+#define SUPPRESS_RULES (1 << 9)
+#define FOLDERS_FIRST (1 << 10)
+#define VERSION_SORT (1 << 11)
+#define TRACK_MODIFIED (1 << 12)
+#define FANCY_INDEXING (1 << 13)
+#define TABLE_INDEXING (1 << 14)
+#define IGNORE_CLIENT (1 << 15)
+#define IGNORE_CASE (1 << 16)
+#define EMIT_XHTML (1 << 17)
+#define SHOW_FORBIDDEN (1 << 18)
+#define ADDALTCLASS (1 << 19)
+#define OPTION_UNSET (1 << 20)
+
+#define K_NOADJUST 0
+#define K_ADJUST 1
+#define K_UNSET 2
+
+/*
+ * Define keys for sorting.
+ */
+#define K_NAME 'N' /* Sort by file name (default) */
+#define K_LAST_MOD 'M' /* Last modification date */
+#define K_SIZE 'S' /* Size (absolute, not as displayed) */
+#define K_DESC 'D' /* Description */
+#define K_VALID "NMSD" /* String containing _all_ valid K_ opts */
+
+#define D_ASCENDING 'A'
+#define D_DESCENDING 'D'
+#define D_VALID "AD" /* String containing _all_ valid D_ opts */
+
+/*
+ * These are the dimensions of the default icons supplied with Apache.
+ */
+#define DEFAULT_ICON_WIDTH 20
+#define DEFAULT_ICON_HEIGHT 22
+
+/*
+ * Other default dimensions.
+ */
+#define DEFAULT_NAME_WIDTH 23
+#define DEFAULT_DESC_WIDTH 23
+
+struct item {
+ char *type;
+ char *apply_to;
+ char *apply_path;
+ char *data;
+};
+
+typedef struct ai_desc_t {
+ char *pattern;
+ char *description;
+ int full_path;
+ int wildcards;
+} ai_desc_t;
+
+typedef struct autoindex_config_struct {
+
+ char *default_icon;
+ char *style_sheet;
+ char *head_insert;
+ char *header;
+ char *readme;
+ apr_int32_t opts;
+ apr_int32_t incremented_opts;
+ apr_int32_t decremented_opts;
+ int name_width;
+ int name_adjust;
+ int desc_width;
+ int desc_adjust;
+ int icon_width;
+ int icon_height;
+ char default_keyid;
+ char default_direction;
+
+ apr_array_header_t *icon_list;
+ apr_array_header_t *alt_list;
+ apr_array_header_t *desc_list;
+ apr_array_header_t *ign_list;
+ int ign_noinherit;
+
+ char *ctype;
+ char *charset;
+ char *datetime_format;
+} autoindex_config_rec;
+
+static char c_by_encoding, c_by_type, c_by_path;
+
+#define BY_ENCODING &c_by_encoding
+#define BY_TYPE &c_by_type
+#define BY_PATH &c_by_path
+
+static APR_INLINE int response_is_html(request_rec *r)
+{
+ char *ctype = ap_field_noparam(r->pool, r->content_type);
+
+ return !ap_cstr_casecmp(ctype, "text/html")
+ || !ap_cstr_casecmp(ctype, "application/xhtml+xml");
+}
+
+/*
+ * This routine puts the standard HTML header at the top of the index page.
+ * We include the DOCTYPE because we may be using features therefrom (i.e.,
+ * HEIGHT and WIDTH attributes on the icons if we're FancyIndexing).
+ */
+static void emit_preamble(request_rec *r, int xhtml, const char *title)
+{
+ autoindex_config_rec *d;
+
+ d = (autoindex_config_rec *) ap_get_module_config(r->per_dir_config,
+ &autoindex_module);
+
+ if (xhtml) {
+ ap_rvputs(r, DOCTYPE_XHTML_1_0T,
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
+ " <head>\n <title>Index of ", title,
+ "</title>\n", NULL);
+ } else {
+ ap_rvputs(r, DOCTYPE_HTML_3_2,
+ "<html>\n <head>\n"
+ " <title>Index of ", title,
+ "</title>\n", NULL);
+ }
+
+ if (d->style_sheet != NULL) {
+ ap_rvputs(r, " <link rel=\"stylesheet\" href=\"", d->style_sheet,
+ "\" type=\"text/css\"", xhtml ? " />\n" : ">\n", NULL);
+ }
+ if (d->head_insert != NULL) {
+ ap_rputs(d->head_insert, r);
+ }
+ ap_rputs(" </head>\n <body>\n", r);
+}
+
+static void push_item(apr_array_header_t *arr, char *type, const char *to,
+ const char *path, const char *data)
+{
+ struct item *p = (struct item *) apr_array_push(arr);
+
+ if (!to) {
+ to = "";
+ }
+ if (!path) {
+ path = "";
+ }
+
+ p->type = type;
+ p->data = apr_pstrdup(arr->pool, data);
+ p->apply_path = apr_pstrcat(arr->pool, path, "*", NULL);
+
+ if ((type == BY_PATH) && (!ap_is_matchexp(to))) {
+ p->apply_to = apr_pstrcat(arr->pool, "*", to, NULL);
+ }
+ else {
+ p->apply_to = apr_pstrdup(arr->pool, to);
+ }
+}
+
+static const char *add_alt(cmd_parms *cmd, void *d, const char *alt,
+ const char *to)
+{
+ if (cmd->info == BY_PATH) {
+ if (!strcmp(to, "**DIRECTORY**")) {
+ to = "^^DIRECTORY^^";
+ }
+ }
+ if (cmd->info == BY_ENCODING) {
+ char *tmp = apr_pstrdup(cmd->temp_pool, to);
+ ap_str_tolower(tmp);
+ to = tmp;
+ }
+
+ push_item(((autoindex_config_rec *) d)->alt_list, cmd->info, to,
+ cmd->path, alt);
+ return NULL;
+}
+
+static const char *add_icon(cmd_parms *cmd, void *d, const char *icon,
+ const char *to)
+{
+ char *iconbak = apr_pstrdup(cmd->temp_pool, icon);
+
+ if (icon[0] == '(') {
+ char *alt;
+ char *cl = strchr(iconbak, ')');
+
+ if (cl == NULL) {
+ return "missing closing paren";
+ }
+ alt = ap_getword_nc(cmd->temp_pool, &iconbak, ',');
+ *cl = '\0'; /* Lose closing paren */
+ add_alt(cmd, d, &alt[1], to);
+ }
+ if (cmd->info == BY_PATH) {
+ if (!strcmp(to, "**DIRECTORY**")) {
+ to = "^^DIRECTORY^^";
+ }
+ }
+ if (cmd->info == BY_ENCODING) {
+ char *tmp = apr_pstrdup(cmd->temp_pool, to);
+ ap_str_tolower(tmp);
+ to = tmp;
+ }
+
+ push_item(((autoindex_config_rec *) d)->icon_list, cmd->info, to,
+ cmd->path, iconbak);
+ return NULL;
+}
+
+/*
+ * Add description text for a filename pattern. If the pattern has
+ * wildcards already (or we need to add them), add leading and
+ * trailing wildcards to it to ensure substring processing. If the
+ * pattern contains a '/' anywhere, force wildcard matching mode,
+ * add a slash to the prefix so that "bar/bletch" won't be matched
+ * by "foobar/bletch", and make a note that there's a delimiter;
+ * the matching routine simplifies to just the actual filename
+ * whenever it can. This allows definitions in parent directories
+ * to be made for files in subordinate ones using relative paths.
+ */
+
+/*
+ * Absent a strcasestr() function, we have to force wildcards on
+ * systems for which "AAA" and "aaa" mean the same file.
+ */
+#ifdef CASE_BLIND_FILESYSTEM
+#define WILDCARDS_REQUIRED 1
+#else
+#define WILDCARDS_REQUIRED 0
+#endif
+
+static const char *add_desc(cmd_parms *cmd, void *d, const char *desc,
+ const char *to)
+{
+ autoindex_config_rec *dcfg = (autoindex_config_rec *) d;
+ ai_desc_t *desc_entry;
+ char *prefix = "";
+
+ desc_entry = (ai_desc_t *) apr_array_push(dcfg->desc_list);
+ desc_entry->full_path = (ap_strchr_c(to, '/') == NULL) ? 0 : 1;
+ desc_entry->wildcards = (WILDCARDS_REQUIRED
+ || desc_entry->full_path
+ || apr_fnmatch_test(to));
+ if (desc_entry->wildcards) {
+ prefix = desc_entry->full_path ? "*/" : "*";
+ desc_entry->pattern = apr_pstrcat(dcfg->desc_list->pool,
+ prefix, to, "*", NULL);
+ }
+ else {
+ desc_entry->pattern = apr_pstrdup(dcfg->desc_list->pool, to);
+ }
+ desc_entry->description = apr_pstrdup(dcfg->desc_list->pool, desc);
+ return NULL;
+}
+
+static const char *add_ignore(cmd_parms *cmd, void *d, const char *ext)
+{
+ push_item(((autoindex_config_rec *) d)->ign_list, 0, ext, cmd->path, NULL);
+ return NULL;
+}
+
+static const char *add_opts(cmd_parms *cmd, void *d, int argc, char *const argv[])
+{
+ int i;
+ char *w;
+ apr_int32_t opts;
+ apr_int32_t opts_add;
+ apr_int32_t opts_remove;
+ char action;
+ autoindex_config_rec *d_cfg = (autoindex_config_rec *) d;
+
+ opts = d_cfg->opts;
+ opts_add = d_cfg->incremented_opts;
+ opts_remove = d_cfg->decremented_opts;
+
+ for (i = 0; i < argc; i++) {
+ int option = 0;
+ w = argv[i];
+
+ if ((*w == '+') || (*w == '-')) {
+ action = *(w++);
+ }
+ else {
+ action = '\0';
+ }
+ if (!strcasecmp(w, "FancyIndexing")) {
+ option = FANCY_INDEXING;
+ }
+ else if (!strcasecmp(w, "FoldersFirst")) {
+ option = FOLDERS_FIRST;
+ }
+ else if (!strcasecmp(w, "HTMLTable")) {
+ option = TABLE_INDEXING;
+ }
+ else if (!strcasecmp(w, "IconsAreLinks")) {
+ option = ICONS_ARE_LINKS;
+ }
+ else if (!strcasecmp(w, "IgnoreCase")) {
+ option = IGNORE_CASE;
+ }
+ else if (!strcasecmp(w, "IgnoreClient")) {
+ option = IGNORE_CLIENT;
+ }
+ else if (!strcasecmp(w, "ScanHTMLTitles")) {
+ option = SCAN_HTML_TITLES;
+ }
+ else if (!strcasecmp(w, "SuppressColumnSorting")) {
+ option = SUPPRESS_COLSORT;
+ }
+ else if (!strcasecmp(w, "SuppressDescription")) {
+ option = SUPPRESS_DESC;
+ }
+ else if (!strcasecmp(w, "SuppressHTMLPreamble")) {
+ option = SUPPRESS_PREAMBLE;
+ }
+ else if (!strcasecmp(w, "SuppressIcon")) {
+ option = SUPPRESS_ICON;
+ }
+ else if (!strcasecmp(w, "SuppressLastModified")) {
+ option = SUPPRESS_LAST_MOD;
+ }
+ else if (!strcasecmp(w, "SuppressSize")) {
+ option = SUPPRESS_SIZE;
+ }
+ else if (!strcasecmp(w, "SuppressRules")) {
+ option = SUPPRESS_RULES;
+ }
+ else if (!strcasecmp(w, "TrackModified")) {
+ option = TRACK_MODIFIED;
+ }
+ else if (!strcasecmp(w, "VersionSort")) {
+ option = VERSION_SORT;
+ }
+ else if (!strcasecmp(w, "XHTML")) {
+ option = EMIT_XHTML;
+ }
+ else if (!strcasecmp(w, "ShowForbidden")) {
+ option = SHOW_FORBIDDEN;
+ }
+ else if (!strcasecmp(w, "AddAltClass")) {
+ option = ADDALTCLASS;
+ }
+ else if (!strcasecmp(w, "None")) {
+ if (action != '\0') {
+ return "Cannot combine '+' or '-' with 'None' keyword";
+ }
+ opts = NO_OPTIONS;
+ opts_add = 0;
+ opts_remove = 0;
+ }
+ else if (!strcasecmp(w, "IconWidth")) {
+ if (action != '-') {
+ d_cfg->icon_width = DEFAULT_ICON_WIDTH;
+ }
+ else {
+ d_cfg->icon_width = 0;
+ }
+ }
+ else if (!strncasecmp(w, "IconWidth=", 10)) {
+ if (action == '-') {
+ return "Cannot combine '-' with IconWidth=n";
+ }
+ d_cfg->icon_width = atoi(&w[10]);
+ }
+ else if (!strcasecmp(w, "IconHeight")) {
+ if (action != '-') {
+ d_cfg->icon_height = DEFAULT_ICON_HEIGHT;
+ }
+ else {
+ d_cfg->icon_height = 0;
+ }
+ }
+ else if (!strncasecmp(w, "IconHeight=", 11)) {
+ if (action == '-') {
+ return "Cannot combine '-' with IconHeight=n";
+ }
+ d_cfg->icon_height = atoi(&w[11]);
+ }
+ else if (!strcasecmp(w, "NameWidth")) {
+ if (action != '-') {
+ return "NameWidth with no value may only appear as "
+ "'-NameWidth'";
+ }
+ d_cfg->name_width = DEFAULT_NAME_WIDTH;
+ d_cfg->name_adjust = K_NOADJUST;
+ }
+ else if (!strncasecmp(w, "NameWidth=", 10)) {
+ if (action == '-') {
+ return "Cannot combine '-' with NameWidth=n";
+ }
+ if (w[10] == '*') {
+ d_cfg->name_adjust = K_ADJUST;
+ }
+ else {
+ int width = atoi(&w[10]);
+
+ if (width && (width < 5)) {
+ return "NameWidth value must be greater than 5";
+ }
+ d_cfg->name_width = width;
+ d_cfg->name_adjust = K_NOADJUST;
+ }
+ }
+ else if (!strcasecmp(w, "DescriptionWidth")) {
+ if (action != '-') {
+ return "DescriptionWidth with no value may only appear as "
+ "'-DescriptionWidth'";
+ }
+ d_cfg->desc_width = DEFAULT_DESC_WIDTH;
+ d_cfg->desc_adjust = K_NOADJUST;
+ }
+ else if (!strncasecmp(w, "DescriptionWidth=", 17)) {
+ if (action == '-') {
+ return "Cannot combine '-' with DescriptionWidth=n";
+ }
+ if (w[17] == '*') {
+ d_cfg->desc_adjust = K_ADJUST;
+ }
+ else {
+ int width = atoi(&w[17]);
+
+ if (width && (width < 12)) {
+ return "DescriptionWidth value must be greater than 12";
+ }
+ d_cfg->desc_width = width;
+ d_cfg->desc_adjust = K_NOADJUST;
+ }
+ }
+ else if (!strncasecmp(w, "Type=", 5)) {
+ d_cfg->ctype = apr_pstrdup(cmd->pool, &w[5]);
+ }
+ else if (!strncasecmp(w, "Charset=", 8)) {
+ d_cfg->charset = apr_pstrdup(cmd->pool, &w[8]);
+ }
+ else if (!strcasecmp(w, "UseOldDateFormat")) {
+ d_cfg->datetime_format = "%d-%b-%Y %H:%M";
+ }
+ else {
+ return "Invalid directory indexing option";
+ }
+ if (action == '\0') {
+ opts |= option;
+ opts_add = 0;
+ opts_remove = 0;
+ }
+ else if (action == '+') {
+ opts_add |= option;
+ opts_remove &= ~option;
+ }
+ else {
+ opts_remove |= option;
+ opts_add &= ~option;
+ }
+ }
+ if ((opts & NO_OPTIONS) && (opts & ~NO_OPTIONS)) {
+ return "Cannot combine other IndexOptions keywords with 'None'";
+ }
+ d_cfg->incremented_opts = opts_add;
+ d_cfg->decremented_opts = opts_remove;
+ d_cfg->opts = opts;
+ return NULL;
+}
+
+static const char *set_default_order(cmd_parms *cmd, void *m,
+ const char *direction, const char *key)
+{
+ autoindex_config_rec *d_cfg = (autoindex_config_rec *) m;
+
+ if (!strcasecmp(direction, "Ascending")) {
+ d_cfg->default_direction = D_ASCENDING;
+ }
+ else if (!strcasecmp(direction, "Descending")) {
+ d_cfg->default_direction = D_DESCENDING;
+ }
+ else {
+ return "First keyword must be 'Ascending' or 'Descending'";
+ }
+
+ if (!strcasecmp(key, "Name")) {
+ d_cfg->default_keyid = K_NAME;
+ }
+ else if (!strcasecmp(key, "Date")) {
+ d_cfg->default_keyid = K_LAST_MOD;
+ }
+ else if (!strcasecmp(key, "Size")) {
+ d_cfg->default_keyid = K_SIZE;
+ }
+ else if (!strcasecmp(key, "Description")) {
+ d_cfg->default_keyid = K_DESC;
+ }
+ else {
+ return "Second keyword must be 'Name', 'Date', 'Size', or "
+ "'Description'";
+ }
+
+ return NULL;
+}
+
+#define DIR_CMD_PERMS OR_INDEXES
+
+static const command_rec autoindex_cmds[] =
+{
+ AP_INIT_ITERATE2("AddIcon", add_icon, BY_PATH, DIR_CMD_PERMS,
+ "an icon URL followed by one or more filenames"),
+ AP_INIT_ITERATE2("AddIconByType", add_icon, BY_TYPE, DIR_CMD_PERMS,
+ "an icon URL followed by one or more MIME types"),
+ AP_INIT_ITERATE2("AddIconByEncoding", add_icon, BY_ENCODING, DIR_CMD_PERMS,
+ "an icon URL followed by one or more content encodings"),
+ AP_INIT_ITERATE2("AddAlt", add_alt, BY_PATH, DIR_CMD_PERMS,
+ "alternate descriptive text followed by one or more "
+ "filenames"),
+ AP_INIT_ITERATE2("AddAltByType", add_alt, BY_TYPE, DIR_CMD_PERMS,
+ "alternate descriptive text followed by one or more MIME "
+ "types"),
+ AP_INIT_ITERATE2("AddAltByEncoding", add_alt, BY_ENCODING, DIR_CMD_PERMS,
+ "alternate descriptive text followed by one or more "
+ "content encodings"),
+ AP_INIT_TAKE_ARGV("IndexOptions", add_opts, NULL, DIR_CMD_PERMS,
+ "one or more index options [+|-][]"),
+ AP_INIT_TAKE2("IndexOrderDefault", set_default_order, NULL, DIR_CMD_PERMS,
+ "{Ascending,Descending} {Name,Size,Description,Date}"),
+ AP_INIT_ITERATE("IndexIgnore", add_ignore, NULL, DIR_CMD_PERMS,
+ "one or more file extensions"),
+ AP_INIT_FLAG("IndexIgnoreReset", ap_set_flag_slot,
+ (void *)APR_OFFSETOF(autoindex_config_rec, ign_noinherit),
+ DIR_CMD_PERMS,
+ "Reset the inherited list of IndexIgnore filenames"),
+ AP_INIT_ITERATE2("AddDescription", add_desc, BY_PATH, DIR_CMD_PERMS,
+ "Descriptive text followed by one or more filenames"),
+ AP_INIT_TAKE1("HeaderName", ap_set_string_slot,
+ (void *)APR_OFFSETOF(autoindex_config_rec, header),
+ DIR_CMD_PERMS, "a filename"),
+ AP_INIT_TAKE1("ReadmeName", ap_set_string_slot,
+ (void *)APR_OFFSETOF(autoindex_config_rec, readme),
+ DIR_CMD_PERMS, "a filename"),
+ AP_INIT_RAW_ARGS("FancyIndexing", ap_set_deprecated, NULL, OR_ALL,
+ "The FancyIndexing directive is no longer supported. "
+ "Use IndexOptions FancyIndexing."),
+ AP_INIT_TAKE1("DefaultIcon", ap_set_string_slot,
+ (void *)APR_OFFSETOF(autoindex_config_rec, default_icon),
+ DIR_CMD_PERMS, "an icon URL"),
+ AP_INIT_TAKE1("IndexStyleSheet", ap_set_string_slot,
+ (void *)APR_OFFSETOF(autoindex_config_rec, style_sheet),
+ DIR_CMD_PERMS, "URL to style sheet"),
+ AP_INIT_TAKE1("IndexHeadInsert", ap_set_string_slot,
+ (void *)APR_OFFSETOF(autoindex_config_rec, head_insert),
+ DIR_CMD_PERMS, "String to insert in HTML HEAD section"),
+ {NULL}
+};
+
+static void *create_autoindex_config(apr_pool_t *p, char *dummy)
+{
+ autoindex_config_rec *new =
+ (autoindex_config_rec *) apr_pcalloc(p, sizeof(autoindex_config_rec));
+
+ new->icon_width = 0;
+ new->icon_height = 0;
+ new->name_width = DEFAULT_NAME_WIDTH;
+ new->name_adjust = K_UNSET;
+ new->desc_width = DEFAULT_DESC_WIDTH;
+ new->desc_adjust = K_UNSET;
+ new->icon_list = apr_array_make(p, 4, sizeof(struct item));
+ new->alt_list = apr_array_make(p, 4, sizeof(struct item));
+ new->desc_list = apr_array_make(p, 4, sizeof(ai_desc_t));
+ new->ign_list = apr_array_make(p, 4, sizeof(struct item));
+ new->opts = OPTION_UNSET;
+ new->incremented_opts = 0;
+ new->decremented_opts = 0;
+ new->default_keyid = '\0';
+ new->default_direction = '\0';
+
+ return (void *) new;
+}
+
+static void *merge_autoindex_configs(apr_pool_t *p, void *basev, void *addv)
+{
+ autoindex_config_rec *new;
+ autoindex_config_rec *base = (autoindex_config_rec *) basev;
+ autoindex_config_rec *add = (autoindex_config_rec *) addv;
+
+ new = (autoindex_config_rec *) apr_pcalloc(p, sizeof(autoindex_config_rec));
+ new->default_icon = add->default_icon ? add->default_icon
+ : base->default_icon;
+ new->style_sheet = add->style_sheet ? add->style_sheet
+ : base->style_sheet;
+ new->head_insert = add->head_insert ? add->head_insert
+ : base->head_insert;
+ new->header = add->header ? add->header
+ : base->header;
+ new->readme = add->readme ? add->readme
+ : base->readme;
+ new->icon_height = add->icon_height ? add->icon_height : base->icon_height;
+ new->icon_width = add->icon_width ? add->icon_width : base->icon_width;
+
+ new->ctype = add->ctype ? add->ctype : base->ctype;
+ new->charset = add->charset ? add->charset : base->charset;
+ new->datetime_format = add->datetime_format ? add->datetime_format : base->datetime_format;
+
+ new->alt_list = apr_array_append(p, add->alt_list, base->alt_list);
+ new->desc_list = apr_array_append(p, add->desc_list, base->desc_list);
+ new->icon_list = apr_array_append(p, add->icon_list, base->icon_list);
+ new->ign_list = add->ign_noinherit ? add->ign_list : apr_array_append(p, add->ign_list, base->ign_list);
+ if (add->opts == NO_OPTIONS) {
+ /*
+ * If the current directory explicitly says 'no options' then we also
+ * clear any incremental mods from being inheritable further down.
+ */
+ new->opts = NO_OPTIONS;
+ new->incremented_opts = 0;
+ new->decremented_opts = 0;
+ }
+ else {
+ /*
+ * If there were any nonincremental options selected for
+ * this directory, they dominate and we don't inherit *anything.*
+ * Contrariwise, we *do* inherit if the only settings here are
+ * incremental ones.
+ */
+ if (add->opts == OPTION_UNSET) {
+ new->incremented_opts = (base->incremented_opts
+ | add->incremented_opts)
+ & ~add->decremented_opts;
+ new->decremented_opts = (base->decremented_opts
+ | add->decremented_opts);
+ /*
+ * We may have incremental settings, so make sure we don't
+ * inadvertently inherit an IndexOptions None from above.
+ */
+ new->opts = (base->opts & ~NO_OPTIONS);
+ }
+ else {
+ /*
+ * There are local nonincremental settings, which clear
+ * all inheritance from above. They *are* the new base settings.
+ */
+ new->opts = add->opts;
+ }
+ /*
+ * We're guaranteed that there'll be no overlap between
+ * the add-options and the remove-options.
+ */
+ new->opts |= new->incremented_opts;
+ new->opts &= ~new->decremented_opts;
+ }
+ /*
+ * Inherit the NameWidth settings if there aren't any specific to
+ * the new location; otherwise we'll end up using the defaults set in the
+ * config-rec creation routine.
+ */
+ if (add->name_adjust == K_UNSET) {
+ new->name_width = base->name_width;
+ new->name_adjust = base->name_adjust;
+ }
+ else {
+ new->name_width = add->name_width;
+ new->name_adjust = add->name_adjust;
+ }
+
+ /*
+ * Likewise for DescriptionWidth.
+ */
+ if (add->desc_adjust == K_UNSET) {
+ new->desc_width = base->desc_width;
+ new->desc_adjust = base->desc_adjust;
+ }
+ else {
+ new->desc_width = add->desc_width;
+ new->desc_adjust = add->desc_adjust;
+ }
+
+ new->default_keyid = add->default_keyid ? add->default_keyid
+ : base->default_keyid;
+ new->default_direction = add->default_direction ? add->default_direction
+ : base->default_direction;
+ return new;
+}
+
+/****************************************************************
+ *
+ * Looking things up in config entries...
+ */
+
+/* Structure used to hold entries when we're actually building an index */
+
+struct ent {
+ char *name;
+ char *icon;
+ char *alt;
+ char *desc;
+ apr_off_t size;
+ apr_time_t lm;
+ struct ent *next;
+ int ascending, ignore_case, version_sort;
+ char key;
+ int isdir;
+};
+
+static char *find_item(const char *content_type, const char *content_encoding,
+ char *path, apr_array_header_t *list, int path_only)
+{
+ struct item *items = (struct item *) list->elts;
+ int i;
+
+ for (i = 0; i < list->nelts; ++i) {
+ struct item *p = &items[i];
+
+ /* Special cased for ^^DIRECTORY^^ and ^^BLANKICON^^ */
+ if ((path[0] == '^') || (!ap_strcmp_match(path, p->apply_path))) {
+ if (!*(p->apply_to)) {
+ return p->data;
+ }
+ else if (p->type == BY_PATH || path[0] == '^') {
+ if (!ap_strcmp_match(path, p->apply_to)) {
+ return p->data;
+ }
+ }
+ else if (!path_only) {
+ if (!content_encoding) {
+ if (p->type == BY_TYPE) {
+ if (content_type
+ && !ap_strcasecmp_match(content_type,
+ p->apply_to)) {
+ return p->data;
+ }
+ }
+ }
+ else {
+ if (p->type == BY_ENCODING) {
+ if (!ap_strcasecmp_match(content_encoding,
+ p->apply_to)) {
+ return p->data;
+ }
+ }
+ }
+ }
+ }
+ }
+ return NULL;
+}
+
+static char *find_item_by_request(request_rec *r, apr_array_header_t *list, int path_only)
+{
+ return find_item(ap_field_noparam(r->pool, r->content_type),
+ r->content_encoding, r->filename, list, path_only);
+}
+
+#define find_icon(d,p,t) find_item_by_request(p,d->icon_list,t)
+#define find_alt(d,p,t) find_item_by_request(p,d->alt_list,t)
+#define find_default_icon(d,n) find_item(NULL, NULL, n, d->icon_list, 1)
+#define find_default_alt(d,n) find_item(NULL, NULL, n, d->alt_list, 1)
+
+/*
+ * Look through the list of pattern/description pairs and return the first one
+ * if any) that matches the filename in the request. If multiple patterns
+ * match, only the first one is used; since the order in the array is the
+ * same as the order in which directives were processed, earlier matching
+ * directives will dominate.
+ */
+
+#ifdef CASE_BLIND_FILESYSTEM
+#define MATCH_FLAGS APR_FNM_CASE_BLIND
+#else
+#define MATCH_FLAGS 0
+#endif
+
+static char *find_desc(autoindex_config_rec *dcfg, const char *filename_full)
+{
+ int i;
+ ai_desc_t *list = (ai_desc_t *) dcfg->desc_list->elts;
+ const char *filename_only;
+ const char *filename;
+
+ /*
+ * If the filename includes a path, extract just the name itself
+ * for the simple matches.
+ */
+ if ((filename_only = ap_strrchr_c(filename_full, '/')) == NULL) {
+ filename_only = filename_full;
+ }
+ else {
+ filename_only++;
+ }
+ for (i = 0; i < dcfg->desc_list->nelts; ++i) {
+ ai_desc_t *tuple = &list[i];
+ int found;
+
+ /*
+ * Only use the full-path filename if the pattern contains '/'s.
+ */
+ filename = (tuple->full_path) ? filename_full : filename_only;
+ /*
+ * Make the comparison using the cheapest method; only do
+ * wildcard checking if we must.
+ */
+ if (tuple->wildcards) {
+ found = (apr_fnmatch(tuple->pattern, filename, MATCH_FLAGS) == 0);
+ }
+ else {
+ found = (ap_strstr_c(filename, tuple->pattern) != NULL);
+ }
+ if (found) {
+ return tuple->description;
+ }
+ }
+ return NULL;
+}
+
+static int ignore_entry(autoindex_config_rec *d, char *path)
+{
+ apr_array_header_t *list = d->ign_list;
+ struct item *items = (struct item *) list->elts;
+ char *tt;
+ int i;
+
+ if ((tt = strrchr(path, '/')) == NULL) {
+ tt = path;
+ }
+ else {
+ tt++;
+ }
+
+ for (i = 0; i < list->nelts; ++i) {
+ struct item *p = &items[i];
+ char *ap;
+
+ if ((ap = strrchr(p->apply_to, '/')) == NULL) {
+ ap = p->apply_to;
+ }
+ else {
+ ap++;
+ }
+
+#ifndef CASE_BLIND_FILESYSTEM
+ if (!ap_strcmp_match(path, p->apply_path)
+ && !ap_strcmp_match(tt, ap)) {
+ return 1;
+ }
+#else /* !CASE_BLIND_FILESYSTEM */
+ /*
+ * On some platforms, the match must be case-blind. This is really
+ * a factor of the filesystem involved, but we can't detect that
+ * reliably - so we have to granularise at the OS level.
+ */
+ if (!ap_strcasecmp_match(path, p->apply_path)
+ && !ap_strcasecmp_match(tt, ap)) {
+ return 1;
+ }
+#endif /* !CASE_BLIND_FILESYSTEM */
+ }
+ return 0;
+}
+
+/*****************************************************************
+ *
+ * Actually generating output
+ */
+
+/*
+ * Elements of the emitted document:
+ * Preamble
+ * Emitted unless SUPPRESS_PREAMBLE is set AND ap_run_sub_req
+ * succeeds for the (content_type == text/html) header file.
+ * Header file
+ * Emitted if found (and able).
+ * H1 tag line
+ * Emitted if a header file is NOT emitted.
+ * Directory stuff
+ * Always emitted.
+ * HR
+ * Emitted if FANCY_INDEXING is set.
+ * Readme file
+ * Emitted if found (and able).
+ * ServerSig
+ * Emitted if ServerSignature is not Off AND a readme file
+ * is NOT emitted.
+ * Postamble
+ * Emitted unless SUPPRESS_PREAMBLE is set AND ap_run_sub_req
+ * succeeds for the (content_type == text/html) readme file.
+ */
+
+
+/*
+ * emit a plain text file
+ */
+static void do_emit_plain(request_rec *r, apr_file_t *f)
+{
+ char buf[AP_IOBUFSIZE + 1];
+ int ch;
+ apr_size_t i, c, n;
+ apr_status_t rv;
+
+ ap_rputs("<pre>\n", r);
+ while (!apr_file_eof(f)) {
+ do {
+ n = sizeof(char) * AP_IOBUFSIZE;
+ rv = apr_file_read(f, buf, &n);
+ } while (APR_STATUS_IS_EINTR(rv));
+ if (n == 0 || rv != APR_SUCCESS) {
+ /* ###: better error here? */
+ break;
+ }
+ buf[n] = '\0';
+ c = 0;
+ while (c < n) {
+ for (i = c; i < n; i++) {
+ if (buf[i] == '<' || buf[i] == '>' || buf[i] == '&') {
+ break;
+ }
+ }
+ ch = buf[i];
+ buf[i] = '\0';
+ ap_rputs(&buf[c], r);
+ if (ch == '<') {
+ ap_rputs("&lt;", r);
+ }
+ else if (ch == '>') {
+ ap_rputs("&gt;", r);
+ }
+ else if (ch == '&') {
+ ap_rputs("&amp;", r);
+ }
+ c = i + 1;
+ }
+ }
+ ap_rputs("</pre>\n", r);
+}
+
+/*
+ * Handle the preamble through the H1 tag line, inclusive. Locate
+ * the file with a subrequests. Process text/html documents by actually
+ * running the subrequest; text/xxx documents get copied verbatim,
+ * and any other content type is ignored. This means that a non-text
+ * document (such as HEADER.gif) might get multiviewed as the result
+ * instead of a text document, meaning nothing will be displayed, but
+ * oh well.
+ */
+static void emit_head(request_rec *r, char *header_fname, int suppress_amble,
+ int emit_xhtml, char *title)
+{
+ autoindex_config_rec *d;
+ apr_table_t *hdrs = r->headers_in;
+ apr_file_t *f = NULL;
+ request_rec *rr = NULL;
+ int emit_amble = 1;
+ int emit_H1 = 1;
+ const char *r_accept;
+ const char *r_accept_enc;
+
+ /*
+ * If there's a header file, send a subrequest to look for it. If it's
+ * found and html do the subrequest, otherwise handle it
+ */
+ r_accept = apr_table_get(hdrs, "Accept");
+ r_accept_enc = apr_table_get(hdrs, "Accept-Encoding");
+ apr_table_setn(hdrs, "Accept", "text/html, text/plain");
+ apr_table_unset(hdrs, "Accept-Encoding");
+
+
+ if ((header_fname != NULL) && r->args) {
+ header_fname = apr_pstrcat(r->pool, header_fname, "?", r->args, NULL);
+ }
+
+ if ((header_fname != NULL)
+ && (rr = ap_sub_req_lookup_uri(header_fname, r, r->output_filters))
+ && (rr->status == HTTP_OK)
+ && (rr->filename != NULL)
+ && (rr->finfo.filetype == APR_REG)) {
+ /*
+ * Check for the two specific cases we allow: text/html and
+ * text/anything-else. The former is allowed to be processed for
+ * SSIs.
+ */
+ if (rr->content_type != NULL) {
+ if (response_is_html(rr)) {
+ ap_filter_t *f;
+ /* Hope everything will work... */
+ emit_amble = 0;
+ emit_H1 = 0;
+
+ if (! suppress_amble) {
+ emit_preamble(r, emit_xhtml, title);
+ }
+ /* This is a hack, but I can't find any better way to do this.
+ * The problem is that we have already created the sub-request,
+ * but we just inserted the OLD_WRITE filter, and the
+ * sub-request needs to pass its data through the OLD_WRITE
+ * filter, or things go horribly wrong (missing data, data in
+ * the wrong order, etc). To fix it, if you create a
+ * sub-request and then insert the OLD_WRITE filter before you
+ * run the request, you need to make sure that the sub-request
+ * data goes through the OLD_WRITE filter. Just steal this
+ * code. The long-term solution is to remove the ap_r*
+ * functions.
+ */
+ for (f=rr->output_filters;
+ f->frec != ap_subreq_core_filter_handle; f = f->next);
+ f->next = r->output_filters;
+
+ /*
+ * If there's a problem running the subrequest, display the
+ * preamble if we didn't do it before -- the header file
+ * didn't get displayed.
+ */
+ if (ap_run_sub_req(rr) != OK) {
+ /* It didn't work */
+ emit_amble = suppress_amble;
+ emit_H1 = 1;
+ }
+ }
+ else if (!ap_cstr_casecmpn("text/", rr->content_type, 5)) {
+ /*
+ * If we can open the file, prefix it with the preamble
+ * regardless; since we'll be sending a <pre> block around
+ * the file's contents, any HTML header it had won't end up
+ * where it belongs.
+ */
+ if (apr_file_open(&f, rr->filename, APR_READ,
+ APR_OS_DEFAULT, r->pool) == APR_SUCCESS) {
+ emit_preamble(r, emit_xhtml, title);
+ emit_amble = 0;
+ do_emit_plain(r, f);
+ apr_file_close(f);
+ emit_H1 = 0;
+ }
+ }
+ }
+ }
+
+ if (r_accept) {
+ apr_table_setn(hdrs, "Accept", r_accept);
+ }
+ else {
+ apr_table_unset(hdrs, "Accept");
+ }
+
+ if (r_accept_enc) {
+ apr_table_setn(hdrs, "Accept-Encoding", r_accept_enc);
+ }
+
+ if (emit_amble) {
+ emit_preamble(r, emit_xhtml, title);
+ }
+
+ d = (autoindex_config_rec *) ap_get_module_config(r->per_dir_config, &autoindex_module);
+
+ if (emit_H1) {
+ if (d->style_sheet != NULL) {
+ /* Insert style id if stylesheet used */
+ ap_rvputs(r, " <h1 id=\"indextitle\">Index of ", title, "</h1>\n", NULL);
+ } else {
+ ap_rvputs(r, "<h1>Index of ", title, "</h1>\n", NULL);
+ }
+ }
+ if (rr != NULL) {
+ ap_destroy_sub_req(rr);
+ }
+}
+
+
+/*
+ * Handle the Readme file through the postamble, inclusive. Locate
+ * the file with a subrequests. Process text/html documents by actually
+ * running the subrequest; text/xxx documents get copied verbatim,
+ * and any other content type is ignored. This means that a non-text
+ * document (such as FOOTER.gif) might get multiviewed as the result
+ * instead of a text document, meaning nothing will be displayed, but
+ * oh well.
+ */
+static void emit_tail(request_rec *r, char *readme_fname, int suppress_amble)
+{
+ apr_file_t *f = NULL;
+ request_rec *rr = NULL;
+ int suppress_post = 0;
+ int suppress_sig = 0;
+
+ /*
+ * If there's a readme file, send a subrequest to look for it. If it's
+ * found and a text file, handle it -- otherwise fall through and
+ * pretend there's nothing there.
+ */
+ if ((readme_fname != NULL)
+ && (rr = ap_sub_req_lookup_uri(readme_fname, r, r->output_filters))
+ && (rr->status == HTTP_OK)
+ && (rr->filename != NULL)
+ && rr->finfo.filetype == APR_REG) {
+ /*
+ * Check for the two specific cases we allow: text/html and
+ * text/anything-else. The former is allowed to be processed for
+ * SSIs.
+ */
+ if (rr->content_type != NULL) {
+ if (response_is_html(rr)) {
+ ap_filter_t *f;
+ for (f=rr->output_filters;
+ f->frec != ap_subreq_core_filter_handle; f = f->next);
+ f->next = r->output_filters;
+
+
+ if (ap_run_sub_req(rr) == OK) {
+ /* worked... */
+ suppress_sig = 1;
+ suppress_post = suppress_amble;
+ }
+ }
+ else if (!ap_cstr_casecmpn("text/", rr->content_type, 5)) {
+ /*
+ * If we can open the file, suppress the signature.
+ */
+ if (apr_file_open(&f, rr->filename, APR_READ,
+ APR_OS_DEFAULT, r->pool) == APR_SUCCESS) {
+ do_emit_plain(r, f);
+ apr_file_close(f);
+ suppress_sig = 1;
+ }
+ }
+ }
+ }
+
+ if (!suppress_sig) {
+ ap_rputs(ap_psignature("", r), r);
+ }
+ if (!suppress_post) {
+ ap_rputs("</body></html>\n", r);
+ }
+ if (rr != NULL) {
+ ap_destroy_sub_req(rr);
+ }
+}
+
+
+static char *find_title(request_rec *r)
+{
+ char titlebuf[MAX_STRING_LEN], *find = "<title>";
+ apr_file_t *thefile = NULL;
+ int x, y, p;
+ apr_size_t n;
+
+ if (r->status != HTTP_OK) {
+ return NULL;
+ }
+ if ((r->content_type != NULL)
+ && (response_is_html(r)
+ || !strcmp(r->content_type, INCLUDES_MAGIC_TYPE))
+ && !r->content_encoding) {
+ if (apr_file_open(&thefile, r->filename, APR_READ,
+ APR_OS_DEFAULT, r->pool) != APR_SUCCESS) {
+ return NULL;
+ }
+ n = sizeof(char) * (MAX_STRING_LEN - 1);
+ apr_file_read(thefile, titlebuf, &n);
+ if (n == 0) {
+ apr_file_close(thefile);
+ return NULL;
+ }
+ titlebuf[n] = '\0';
+ for (x = 0, p = 0; titlebuf[x]; x++) {
+ if (apr_tolower(titlebuf[x]) == find[p]) {
+ if (!find[++p]) {
+ if ((p = ap_ind(&titlebuf[++x], '<')) != -1) {
+ titlebuf[x + p] = '\0';
+ }
+ /* Scan for line breaks for Tanmoy's secretary */
+ for (y = x; titlebuf[y]; y++) {
+ if ((titlebuf[y] == CR) || (titlebuf[y] == LF)) {
+ if (y == x) {
+ x++;
+ }
+ else {
+ titlebuf[y] = ' ';
+ }
+ }
+ }
+ apr_file_close(thefile);
+ return apr_pstrdup(r->pool, &titlebuf[x]);
+ }
+ }
+ else {
+ p = 0;
+ }
+ }
+ apr_file_close(thefile);
+ }
+ return NULL;
+}
+
+static struct ent *make_parent_entry(apr_int32_t autoindex_opts,
+ autoindex_config_rec *d,
+ request_rec *r, char keyid,
+ char direction)
+{
+ struct ent *p = (struct ent *) apr_pcalloc(r->pool, sizeof(struct ent));
+ char *testpath;
+ /*
+ * p->name is now the true parent URI.
+ * testpath is a crafted lie, so that the syntax '/some/..'
+ * (or simply '..')be used to describe 'up' from '/some/'
+ * when processeing IndexIgnore, and Icon|Alt|Desc configs.
+ */
+
+ /* The output has always been to the parent. Don't make ourself
+ * our own parent (worthless cyclical reference).
+ */
+ if (!(p->name = ap_make_full_path(r->pool, r->uri, "../"))) {
+ return (NULL);
+ }
+ if (!ap_normalize_path(p->name, AP_NORMALIZE_ALLOW_RELATIVE |
+ AP_NORMALIZE_NOT_ABOVE_ROOT)
+ || p->name[0] == '\0') {
+ return (NULL);
+ }
+
+ /* IndexIgnore has always compared "/thispath/.." */
+ testpath = ap_make_full_path(r->pool, r->filename, "..");
+ if (ignore_entry(d, testpath)) {
+ return (NULL);
+ }
+
+ p->size = -1;
+ p->lm = -1;
+ p->key = apr_toupper(keyid);
+ p->ascending = (apr_toupper(direction) == D_ASCENDING);
+ p->version_sort = autoindex_opts & VERSION_SORT;
+ if (autoindex_opts & FANCY_INDEXING) {
+ if (!(p->icon = find_default_icon(d, testpath))) {
+ p->icon = find_default_icon(d, "^^DIRECTORY^^");
+ }
+ if (!(p->alt = find_default_alt(d, testpath))) {
+ if (!(p->alt = find_default_alt(d, "^^DIRECTORY^^"))) {
+ /* Special alt text for parent dir to distinguish it from other directories
+ this is essential when trying to style this dir entry via AddAltClass */
+ p->alt = "PARENTDIR";
+ }
+ }
+ p->desc = find_desc(d, testpath);
+ }
+ return p;
+}
+
+static struct ent *make_autoindex_entry(const apr_finfo_t *dirent,
+ int autoindex_opts,
+ autoindex_config_rec *d,
+ request_rec *r, char keyid,
+ char direction,
+ const char *pattern)
+{
+ request_rec *rr;
+ struct ent *p;
+ int show_forbidden = 0;
+
+ /* Dot is ignored, Parent is handled by make_parent_entry() */
+ if ((dirent->name[0] == '.') && (!dirent->name[1]
+ || ((dirent->name[1] == '.') && !dirent->name[2])))
+ return (NULL);
+
+ /*
+ * On some platforms, the match must be case-blind. This is really
+ * a factor of the filesystem involved, but we can't detect that
+ * reliably - so we have to granularise at the OS level.
+ */
+ if (pattern && (apr_fnmatch(pattern, dirent->name,
+ APR_FNM_NOESCAPE | APR_FNM_PERIOD
+#ifdef CASE_BLIND_FILESYSTEM
+ | APR_FNM_CASE_BLIND
+#endif
+ )
+ != APR_SUCCESS)) {
+ return (NULL);
+ }
+
+ if (ignore_entry(d, ap_make_full_path(r->pool,
+ r->filename, dirent->name))) {
+ return (NULL);
+ }
+
+ if (!(rr = ap_sub_req_lookup_dirent(dirent, r, AP_SUBREQ_NO_ARGS, NULL))) {
+ return (NULL);
+ }
+
+ if ((autoindex_opts & SHOW_FORBIDDEN)
+ && (rr->status == HTTP_UNAUTHORIZED || rr->status == HTTP_FORBIDDEN)) {
+ show_forbidden = 1;
+ }
+
+ if ((rr->finfo.filetype != APR_DIR && rr->finfo.filetype != APR_REG)
+ || !(rr->status == OK || ap_is_HTTP_SUCCESS(rr->status)
+ || ap_is_HTTP_REDIRECT(rr->status)
+ || show_forbidden == 1)) {
+ ap_destroy_sub_req(rr);
+ return (NULL);
+ }
+
+ p = (struct ent *) apr_pcalloc(r->pool, sizeof(struct ent));
+ if (dirent->filetype == APR_DIR) {
+ p->name = apr_pstrcat(r->pool, dirent->name, "/", NULL);
+ }
+ else {
+ p->name = apr_pstrdup(r->pool, dirent->name);
+ }
+ p->size = -1;
+ p->icon = NULL;
+ p->alt = NULL;
+ p->desc = NULL;
+ p->lm = -1;
+ p->isdir = 0;
+ p->key = apr_toupper(keyid);
+ p->ascending = (apr_toupper(direction) == D_ASCENDING);
+ p->version_sort = !!(autoindex_opts & VERSION_SORT);
+ p->ignore_case = !!(autoindex_opts & IGNORE_CASE);
+
+ if (autoindex_opts & (FANCY_INDEXING | TABLE_INDEXING)) {
+ p->lm = rr->finfo.mtime;
+ if (dirent->filetype == APR_DIR) {
+ if (autoindex_opts & FOLDERS_FIRST) {
+ p->isdir = 1;
+ }
+ rr->filename = ap_make_dirstr_parent (rr->pool, rr->filename);
+
+ /* omit the trailing slash (1.3 compat) */
+ rr->filename[strlen(rr->filename) - 1] = '\0';
+
+ if (!(p->icon = find_icon(d, rr, 1))) {
+ p->icon = find_default_icon(d, "^^DIRECTORY^^");
+ }
+ if (!(p->alt = find_alt(d, rr, 1))) {
+ if (!(p->alt = find_default_alt(d, "^^DIRECTORY^^"))) {
+ p->alt = "DIR";
+ }
+ }
+ }
+ else {
+ p->icon = find_icon(d, rr, 0);
+ p->alt = find_alt(d, rr, 0);
+ p->size = rr->finfo.size;
+ }
+
+ p->desc = find_desc(d, rr->filename);
+
+ if ((!p->desc) && (autoindex_opts & SCAN_HTML_TITLES)) {
+ p->desc = apr_pstrdup(r->pool, find_title(rr));
+ }
+ }
+ ap_destroy_sub_req(rr);
+ /*
+ * We don't need to take any special action for the file size key.
+ * If we did, it would go here.
+ */
+ if (keyid == K_LAST_MOD) {
+ if (p->lm < 0) {
+ p->lm = 0;
+ }
+ }
+ return (p);
+}
+
+static char *terminate_description(autoindex_config_rec *d, char *desc,
+ apr_int32_t autoindex_opts, int desc_width)
+{
+ int maxsize = desc_width;
+ int x;
+
+ /*
+ * If there's no DescriptionWidth in effect, default to the old
+ * behaviour of adjusting the description size depending upon
+ * what else is being displayed. Otherwise, stick with the
+ * setting.
+ */
+ if (d->desc_adjust == K_UNSET) {
+ if (autoindex_opts & SUPPRESS_ICON) {
+ maxsize += 6;
+ }
+ if (autoindex_opts & SUPPRESS_LAST_MOD) {
+ maxsize += 19;
+ }
+ if (autoindex_opts & SUPPRESS_SIZE) {
+ maxsize += 7;
+ }
+ }
+ for (x = 0; desc[x] && ((maxsize > 0) || (desc[x] == '<')); x++) {
+ if (desc[x] == '<') {
+ while (desc[x] != '>') {
+ if (!desc[x]) {
+ maxsize = 0;
+ break;
+ }
+ ++x;
+ }
+ }
+ else if (desc[x] == '&') {
+ /* entities like &auml; count as one character */
+ --maxsize;
+ for ( ; desc[x] != ';'; ++x) {
+ if (desc[x] == '\0') {
+ maxsize = 0;
+ break;
+ }
+ }
+ }
+ else {
+ --maxsize;
+ }
+ }
+ if (!maxsize && desc[x] != '\0') {
+ desc[x - 1] = '>'; /* Grump. */
+ desc[x] = '\0'; /* Double Grump! */
+ }
+ return desc;
+}
+
+/*
+ * Emit the anchor for the specified field. If a field is the key for the
+ * current request, the link changes its meaning to reverse the order when
+ * selected again. Non-active fields always start in ascending order.
+ */
+static void emit_link(request_rec *r, const char *anchor, char column,
+ char curkey, char curdirection,
+ const char *colargs, int nosort)
+{
+ if (!nosort) {
+ char qvalue[9];
+
+ qvalue[0] = '?';
+ qvalue[1] = 'C';
+ qvalue[2] = '=';
+ qvalue[3] = column;
+ qvalue[4] = ';';
+ qvalue[5] = 'O';
+ qvalue[6] = '=';
+ /* reverse? */
+ qvalue[7] = ((curkey == column) && (curdirection == D_ASCENDING))
+ ? D_DESCENDING : D_ASCENDING;
+ qvalue[8] = '\0';
+ ap_rvputs(r, "<a href=\"", qvalue, colargs ? colargs : "",
+ "\">", anchor, "</a>", NULL);
+ }
+ else {
+ ap_rputs(anchor, r);
+ }
+}
+
+static void output_directories(struct ent **ar, int n,
+ autoindex_config_rec *d, request_rec *r,
+ apr_int32_t autoindex_opts, char keyid,
+ char direction, const char *colargs)
+{
+ int x;
+ apr_size_t rv;
+ char *tp;
+ int static_columns = !!(autoindex_opts & SUPPRESS_COLSORT);
+ apr_pool_t *scratch;
+ int name_width;
+ int desc_width;
+ char *datetime_format;
+ char *name_scratch;
+ char *pad_scratch;
+ char *breakrow = "";
+
+ apr_pool_create(&scratch, r->pool);
+ apr_pool_tag(scratch, "autoindex_scratch");
+
+ name_width = d->name_width;
+ desc_width = d->desc_width;
+ datetime_format = d->datetime_format ? d->datetime_format : "%Y-%m-%d %H:%M";
+
+ if ((autoindex_opts & (FANCY_INDEXING | TABLE_INDEXING))
+ == FANCY_INDEXING) {
+ if (d->name_adjust == K_ADJUST) {
+ for (x = 0; x < n; x++) {
+ int t = strlen(ar[x]->name);
+ if (t > name_width) {
+ name_width = t;
+ }
+ }
+ }
+
+ if (d->desc_adjust == K_ADJUST) {
+ for (x = 0; x < n; x++) {
+ if (ar[x]->desc != NULL) {
+ int t = strlen(ar[x]->desc);
+ if (t > desc_width) {
+ desc_width = t;
+ }
+ }
+ }
+ }
+ }
+ name_scratch = apr_palloc(r->pool, name_width + 1);
+ pad_scratch = apr_palloc(r->pool, name_width + 1);
+ memset(pad_scratch, ' ', name_width);
+ pad_scratch[name_width] = '\0';
+
+ if (autoindex_opts & TABLE_INDEXING) {
+ int cols = 1;
+ if (d->style_sheet != NULL) {
+ /* Emit table with style id */
+ ap_rputs(" <table id=\"indexlist\">\n <tr class=\"indexhead\">", r);
+ } else {
+ ap_rputs(" <table>\n <tr>", r);
+ }
+ if (!(autoindex_opts & SUPPRESS_ICON)) {
+ ap_rvputs(r, "<th", (d->style_sheet != NULL) ? " class=\"indexcolicon\">" : " valign=\"top\">", NULL);
+ if ((tp = find_default_icon(d, "^^BLANKICON^^"))) {
+ ap_rvputs(r, "<img src=\"", ap_escape_html(scratch, tp),
+ "\" alt=\"[ICO]\"", NULL);
+ if (d->icon_width) {
+ ap_rprintf(r, " width=\"%d\"", d->icon_width);
+ }
+ if (d->icon_height) {
+ ap_rprintf(r, " height=\"%d\"", d->icon_height);
+ }
+
+ if (autoindex_opts & EMIT_XHTML) {
+ ap_rputs(" /", r);
+ }
+ ap_rputs("></th>", r);
+ }
+ else {
+ ap_rputs("&nbsp;</th>", r);
+ }
+
+ ++cols;
+ }
+ ap_rvputs(r, "<th", (d->style_sheet != NULL) ? " class=\"indexcolname\">" : ">", NULL);
+ emit_link(r, "Name", K_NAME, keyid, direction,
+ colargs, static_columns);
+ if (!(autoindex_opts & SUPPRESS_LAST_MOD)) {
+ ap_rvputs(r, "</th><th", (d->style_sheet != NULL) ? " class=\"indexcollastmod\">" : ">", NULL);
+ emit_link(r, "Last modified", K_LAST_MOD, keyid, direction,
+ colargs, static_columns);
+ ++cols;
+ }
+ if (!(autoindex_opts & SUPPRESS_SIZE)) {
+ ap_rvputs(r, "</th><th", (d->style_sheet != NULL) ? " class=\"indexcolsize\">" : ">", NULL);
+ emit_link(r, "Size", K_SIZE, keyid, direction,
+ colargs, static_columns);
+ ++cols;
+ }
+ if (!(autoindex_opts & SUPPRESS_DESC)) {
+ ap_rvputs(r, "</th><th", (d->style_sheet != NULL) ? " class=\"indexcoldesc\">" : ">", NULL);
+ emit_link(r, "Description", K_DESC, keyid, direction,
+ colargs, static_columns);
+ ++cols;
+ }
+ if (!(autoindex_opts & SUPPRESS_RULES)) {
+ breakrow = apr_psprintf(r->pool,
+ " <tr%s><th colspan=\"%d\">"
+ "<hr%s></th></tr>\n",
+ (d->style_sheet != NULL) ? " class=\"indexbreakrow\"" : "",
+ cols,
+ (autoindex_opts & EMIT_XHTML) ? " /" : "");
+ }
+ ap_rvputs(r, "</th></tr>\n", breakrow, NULL);
+ }
+ else if (autoindex_opts & FANCY_INDEXING) {
+ ap_rputs("<pre>", r);
+ if (!(autoindex_opts & SUPPRESS_ICON)) {
+ if ((tp = find_default_icon(d, "^^BLANKICON^^"))) {
+ ap_rvputs(r, "<img src=\"", ap_escape_html(scratch, tp),
+ "\" alt=\"Icon \"", NULL);
+ if (d->icon_width) {
+ ap_rprintf(r, " width=\"%d\"", d->icon_width);
+ }
+ if (d->icon_height) {
+ ap_rprintf(r, " height=\"%d\"", d->icon_height);
+ }
+
+ if (autoindex_opts & EMIT_XHTML) {
+ ap_rputs(" /", r);
+ }
+ ap_rputs("> ", r);
+ }
+ else {
+ ap_rputs(" ", r);
+ }
+ }
+ emit_link(r, "Name", K_NAME, keyid, direction,
+ colargs, static_columns);
+ ap_rputs(pad_scratch + 4, r);
+ /*
+ * Emit the guaranteed-at-least-one-space-between-columns byte.
+ */
+ ap_rputs(" ", r);
+ if (!(autoindex_opts & SUPPRESS_LAST_MOD)) {
+ emit_link(r, "Last modified", K_LAST_MOD, keyid, direction,
+ colargs, static_columns);
+ ap_rputs(" ", r);
+ }
+ if (!(autoindex_opts & SUPPRESS_SIZE)) {
+ emit_link(r, "Size", K_SIZE, keyid, direction,
+ colargs, static_columns);
+ ap_rputs(" ", r);
+ }
+ if (!(autoindex_opts & SUPPRESS_DESC)) {
+ emit_link(r, "Description", K_DESC, keyid, direction,
+ colargs, static_columns);
+ }
+ if (!(autoindex_opts & SUPPRESS_RULES)) {
+ ap_rputs("<hr", r);
+ if (autoindex_opts & EMIT_XHTML) {
+ ap_rputs(" /", r);
+ }
+ ap_rputs(">", r);
+ }
+ else {
+ ap_rputc('\n', r);
+ }
+ }
+ else {
+ ap_rputs("<ul>", r);
+ }
+
+ for (x = 0; x < n; x++) {
+ char *anchor, *t, *t2;
+ int nwidth;
+
+ apr_pool_clear(scratch);
+
+ t = ar[x]->name;
+ anchor = ap_escape_html(scratch, ap_os_escape_path(scratch, t, 0));
+
+ if (!x && t[0] == '/') {
+ t2 = "Parent Directory";
+ }
+ else {
+ t2 = t;
+ }
+
+ if (autoindex_opts & TABLE_INDEXING) {
+ /* Even/Odd rows for IndexStyleSheet */
+ if (d->style_sheet != NULL) {
+ if (ar[x]->alt && (autoindex_opts & ADDALTCLASS)) {
+ /* Include alt text in class name, distinguish between odd and even rows */
+ char *altclass = apr_pstrdup(scratch, ar[x]->alt);
+ ap_str_tolower(altclass);
+ ap_rvputs(r, " <tr class=\"", ( x & 0x1) ? "odd-" : "even-", altclass, "\">", NULL);
+ } else {
+ /* Distinguish between odd and even rows */
+ ap_rvputs(r, " <tr class=\"", ( x & 0x1) ? "odd" : "even", "\">", NULL);
+ }
+ } else {
+ ap_rputs("<tr>", r);
+ }
+
+ if (!(autoindex_opts & SUPPRESS_ICON)) {
+ ap_rvputs(r, "<td", (d->style_sheet != NULL) ? " class=\"indexcolicon\">" : " valign=\"top\">", NULL);
+ if (autoindex_opts & ICONS_ARE_LINKS) {
+ ap_rvputs(r, "<a href=\"", anchor, "\">", NULL);
+ }
+ if ((ar[x]->icon) || d->default_icon) {
+ ap_rvputs(r, "<img src=\"",
+ ap_escape_html(scratch,
+ ar[x]->icon ? ar[x]->icon
+ : d->default_icon),
+ "\" alt=\"[", (ar[x]->alt ? ar[x]->alt : " "),
+ "]\"", NULL);
+ if (d->icon_width) {
+ ap_rprintf(r, " width=\"%d\"", d->icon_width);
+ }
+ if (d->icon_height) {
+ ap_rprintf(r, " height=\"%d\"", d->icon_height);
+ }
+
+ if (autoindex_opts & EMIT_XHTML) {
+ ap_rputs(" /", r);
+ }
+ ap_rputs(">", r);
+ }
+ else {
+ ap_rputs("&nbsp;", r);
+ }
+ if (autoindex_opts & ICONS_ARE_LINKS) {
+ ap_rputs("</a></td>", r);
+ }
+ else {
+ ap_rputs("</td>", r);
+ }
+ }
+ if (d->name_adjust == K_ADJUST) {
+ ap_rvputs(r, "<td", (d->style_sheet != NULL) ? " class=\"indexcolname\">" : ">", "<a href=\"", anchor, "\">",
+ ap_escape_html(scratch, t2), "</a>", NULL);
+ }
+ else {
+ nwidth = strlen(t2);
+ if (nwidth > name_width) {
+ memcpy(name_scratch, t2, name_width - 3);
+ name_scratch[name_width - 3] = '.';
+ name_scratch[name_width - 2] = '.';
+ name_scratch[name_width - 1] = '>';
+ name_scratch[name_width] = 0;
+ t2 = name_scratch;
+ nwidth = name_width;
+ }
+ ap_rvputs(r, "<td", (d->style_sheet != NULL) ? " class=\"indexcolname\">" : ">", "<a href=\"", anchor, "\">",
+ ap_escape_html(scratch, t2),
+ "</a>", pad_scratch + nwidth, NULL);
+ }
+ if (!(autoindex_opts & SUPPRESS_LAST_MOD)) {
+ if (ar[x]->lm != -1) {
+ char time_str[32];
+ apr_time_exp_t ts;
+ apr_time_exp_lt(&ts, ar[x]->lm);
+ apr_strftime(time_str, &rv, sizeof(time_str),
+ datetime_format,
+ &ts);
+ ap_rvputs(r, "</td><td", (d->style_sheet != NULL) ? " class=\"indexcollastmod\">" : " align=\"right\">", time_str, " ", NULL);
+ }
+ else {
+ ap_rvputs(r, "</td><td", (d->style_sheet != NULL) ? " class=\"indexcollastmod\">&nbsp;" : ">&nbsp;", NULL);
+ }
+ }
+ if (!(autoindex_opts & SUPPRESS_SIZE)) {
+ char buf[5];
+ ap_rvputs(r, "</td><td", (d->style_sheet != NULL) ? " class=\"indexcolsize\">" : " align=\"right\">",
+ apr_strfsize(ar[x]->size, buf), NULL);
+ }
+ if (!(autoindex_opts & SUPPRESS_DESC)) {
+ if (ar[x]->desc) {
+ if (d->desc_adjust == K_ADJUST) {
+ ap_rvputs(r, "</td><td", (d->style_sheet != NULL) ? " class=\"indexcoldesc\">" : ">", ar[x]->desc, NULL);
+ }
+ else {
+ ap_rvputs(r, "</td><td", (d->style_sheet != NULL) ? " class=\"indexcoldesc\">" : ">",
+ terminate_description(d, ar[x]->desc,
+ autoindex_opts,
+ desc_width), NULL);
+ }
+ }
+ else {
+ ap_rvputs(r, "</td><td", (d->style_sheet != NULL) ? " class=\"indexcoldesc\">" : ">", "&nbsp;", NULL);
+ }
+ }
+ ap_rputs("</td></tr>\n", r);
+ }
+ else if (autoindex_opts & FANCY_INDEXING) {
+ if (!(autoindex_opts & SUPPRESS_ICON)) {
+ if (autoindex_opts & ICONS_ARE_LINKS) {
+ ap_rvputs(r, "<a href=\"", anchor, "\">", NULL);
+ }
+ if ((ar[x]->icon) || d->default_icon) {
+ ap_rvputs(r, "<img src=\"",
+ ap_escape_html(scratch,
+ ar[x]->icon ? ar[x]->icon
+ : d->default_icon),
+ "\" alt=\"[", (ar[x]->alt ? ar[x]->alt : " "),
+ "]\"", NULL);
+ if (d->icon_width) {
+ ap_rprintf(r, " width=\"%d\"", d->icon_width);
+ }
+ if (d->icon_height) {
+ ap_rprintf(r, " height=\"%d\"", d->icon_height);
+ }
+
+ if (autoindex_opts & EMIT_XHTML) {
+ ap_rputs(" /", r);
+ }
+ ap_rputs(">", r);
+ }
+ else {
+ ap_rputs(" ", r);
+ }
+ if (autoindex_opts & ICONS_ARE_LINKS) {
+ ap_rputs("</a> ", r);
+ }
+ else {
+ ap_rputc(' ', r);
+ }
+ }
+ nwidth = strlen(t2);
+ if (nwidth > name_width) {
+ memcpy(name_scratch, t2, name_width - 3);
+ name_scratch[name_width - 3] = '.';
+ name_scratch[name_width - 2] = '.';
+ name_scratch[name_width - 1] = '>';
+ name_scratch[name_width] = 0;
+ t2 = name_scratch;
+ nwidth = name_width;
+ }
+ ap_rvputs(r, "<a href=\"", anchor, "\">",
+ ap_escape_html(scratch, t2),
+ "</a>", pad_scratch + nwidth, NULL);
+ /*
+ * The blank before the storm.. er, before the next field.
+ */
+ ap_rputs(" ", r);
+ if (!(autoindex_opts & SUPPRESS_LAST_MOD)) {
+ if (ar[x]->lm != -1) {
+ char time_str[32];
+ apr_time_exp_t ts;
+ apr_time_exp_lt(&ts, ar[x]->lm);
+ apr_strftime(time_str, &rv, sizeof(time_str),
+ datetime_format,
+ &ts);
+ ap_rvputs(r, time_str, " ", NULL);
+ }
+ else {
+ /* Length="1975-04-07 01:23 " (default in 2.4 and later) or
+ * Length="07-Apr-1975 01:24 ". (2.2 and UseOldDateFormat)
+ * See 'datetime_format' above.
+ */
+ ap_rputs(" ", r);
+ }
+ }
+ if (!(autoindex_opts & SUPPRESS_SIZE)) {
+ char buf[5];
+ ap_rputs(apr_strfsize(ar[x]->size, buf), r);
+ ap_rputs(" ", r);
+ }
+ if (!(autoindex_opts & SUPPRESS_DESC)) {
+ if (ar[x]->desc) {
+ ap_rputs(terminate_description(d, ar[x]->desc,
+ autoindex_opts,
+ desc_width), r);
+ }
+ }
+ ap_rputc('\n', r);
+ }
+ else {
+ ap_rvputs(r, "<li><a href=\"", anchor, "\"> ",
+ ap_escape_html(scratch, t2),
+ "</a></li>\n", NULL);
+ }
+ }
+ if (autoindex_opts & TABLE_INDEXING) {
+ ap_rvputs(r, breakrow, "</table>\n", NULL);
+ }
+ else if (autoindex_opts & FANCY_INDEXING) {
+ if (!(autoindex_opts & SUPPRESS_RULES)) {
+ ap_rputs("<hr", r);
+ if (autoindex_opts & EMIT_XHTML) {
+ ap_rputs(" /", r);
+ }
+ ap_rputs("></pre>\n", r);
+ }
+ else {
+ ap_rputs("</pre>\n", r);
+ }
+ }
+ else {
+ ap_rputs("</ul>\n", r);
+ }
+}
+
+/*
+ * Compare two file entries according to the sort criteria. The return
+ * is essentially a signum function value.
+ */
+
+static int dsortf(struct ent **e1, struct ent **e2)
+{
+ struct ent *c1;
+ struct ent *c2;
+ int result = 0;
+
+ /*
+ * First, see if either of the entries is for the parent directory.
+ * If so, that *always* sorts lower than anything else.
+ */
+ if ((*e1)->name[0] == '/') {
+ return -1;
+ }
+ if ((*e2)->name[0] == '/') {
+ return 1;
+ }
+ /*
+ * Now see if one's a directory and one isn't, if we're set
+ * isdir for FOLDERS_FIRST.
+ */
+ if ((*e1)->isdir != (*e2)->isdir) {
+ return (*e1)->isdir ? -1 : 1;
+ }
+ /*
+ * All of our comparisons will be of the c1 entry against the c2 one,
+ * so assign them appropriately to take care of the ordering.
+ */
+ if ((*e1)->ascending) {
+ c1 = *e1;
+ c2 = *e2;
+ }
+ else {
+ c1 = *e2;
+ c2 = *e1;
+ }
+
+ switch (c1->key) {
+ case K_LAST_MOD:
+ if (c1->lm > c2->lm) {
+ return 1;
+ }
+ else if (c1->lm < c2->lm) {
+ return -1;
+ }
+ break;
+ case K_SIZE:
+ if (c1->size > c2->size) {
+ return 1;
+ }
+ else if (c1->size < c2->size) {
+ return -1;
+ }
+ break;
+ case K_DESC:
+ if (c1->version_sort) {
+ result = apr_strnatcmp(c1->desc ? c1->desc : "",
+ c2->desc ? c2->desc : "");
+ }
+ else {
+ result = strcmp(c1->desc ? c1->desc : "",
+ c2->desc ? c2->desc : "");
+ }
+ if (result) {
+ return result;
+ }
+ break;
+ }
+
+ /* names may identical when treated case-insensitively,
+ * so always fall back on strcmp() flavors to put entries
+ * in deterministic order. This means that 'ABC' and 'abc'
+ * will always appear in the same order, rather than
+ * variably between 'ABC abc' and 'abc ABC' order.
+ */
+
+ if (c1->version_sort) {
+ if (c1->ignore_case) {
+ result = apr_strnatcasecmp (c1->name, c2->name);
+ }
+ if (!result) {
+ result = apr_strnatcmp(c1->name, c2->name);
+ }
+ }
+
+ /* The names may be identical in respects other than
+ * filename case when strnatcmp is used above, so fall back
+ * to strcmp on conflicts so that fn1.01.zzz and fn1.1.zzz
+ * are also sorted in a deterministic order.
+ */
+
+ if (!result && c1->ignore_case) {
+ result = strcasecmp (c1->name, c2->name);
+ }
+
+ if (!result) {
+ result = strcmp (c1->name, c2->name);
+ }
+
+ return result;
+}
+
+
+static int index_directory(request_rec *r,
+ autoindex_config_rec *autoindex_conf)
+{
+ char *title_name = ap_escape_html(r->pool, r->uri);
+ char *title_endp;
+ char *name = r->filename;
+ char *pstring = NULL;
+ apr_finfo_t dirent;
+ apr_dir_t *thedir;
+ apr_status_t status;
+ int num_ent = 0, x;
+ struct ent *head, *p;
+ struct ent **ar = NULL;
+ const char *qstring;
+ apr_int32_t autoindex_opts = autoindex_conf->opts;
+ char keyid;
+ char direction;
+ char *colargs;
+ char *fullpath;
+ apr_size_t dirpathlen;
+ char *ctype = "text/html";
+ char *charset;
+
+ if ((status = apr_dir_open(&thedir, name, r->pool)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01275)
+ "Can't open directory for index: %s", r->filename);
+ return HTTP_FORBIDDEN;
+ }
+
+ if (autoindex_conf->ctype) {
+ ctype = autoindex_conf->ctype;
+ }
+ if (autoindex_conf->charset) {
+ charset = autoindex_conf->charset;
+ }
+ else {
+#if APR_HAS_UNICODE_FS
+ charset = "UTF-8";
+#else
+ charset = "ISO-8859-1";
+#endif
+ }
+ if (*charset) {
+ ap_set_content_type(r, apr_pstrcat(r->pool, ctype, ";charset=",
+ charset, NULL));
+ }
+ else {
+ ap_set_content_type(r, ctype);
+ }
+
+ if (autoindex_opts & TRACK_MODIFIED) {
+ ap_update_mtime(r, r->finfo.mtime);
+ ap_set_last_modified(r);
+ ap_set_etag(r);
+ }
+ if (r->header_only) {
+ apr_dir_close(thedir);
+ return 0;
+ }
+
+ /*
+ * If there is no specific ordering defined for this directory,
+ * default to ascending by filename.
+ */
+ keyid = autoindex_conf->default_keyid
+ ? autoindex_conf->default_keyid : K_NAME;
+ direction = autoindex_conf->default_direction
+ ? autoindex_conf->default_direction : D_ASCENDING;
+
+ /*
+ * Figure out what sort of indexing (if any) we're supposed to use.
+ *
+ * If no QUERY_STRING was specified or client query strings have been
+ * explicitly disabled.
+ * If we are ignoring the client, suppress column sorting as well.
+ */
+ if (autoindex_opts & IGNORE_CLIENT) {
+ qstring = NULL;
+ autoindex_opts |= SUPPRESS_COLSORT;
+ colargs = "";
+ }
+ else {
+ char fval[5], vval[5], *ppre = "", *epattern = "";
+ fval[0] = '\0'; vval[0] = '\0';
+ qstring = r->args;
+
+ while (qstring && *qstring) {
+
+ /* C= First Sort key Column (N, M, S, D) */
+ if ( qstring[0] == 'C' && qstring[1] == '='
+ && qstring[2] && strchr(K_VALID, qstring[2])
+ && ( qstring[3] == '&' || qstring[3] == ';'
+ || !qstring[3])) {
+ keyid = qstring[2];
+ qstring += qstring[3] ? 4 : 3;
+ }
+
+ /* O= Sort order (A, D) */
+ else if ( qstring[0] == 'O' && qstring[1] == '='
+ && ( (qstring[2] == D_ASCENDING)
+ || (qstring[2] == D_DESCENDING))
+ && ( qstring[3] == '&' || qstring[3] == ';'
+ || !qstring[3])) {
+ direction = qstring[2];
+ qstring += qstring[3] ? 4 : 3;
+ }
+
+ /* F= Output Format (0 plain, 1 fancy (pre), 2 table) */
+ else if ( qstring[0] == 'F' && qstring[1] == '='
+ && qstring[2] && strchr("012", qstring[2])
+ && ( qstring[3] == '&' || qstring[3] == ';'
+ || !qstring[3])) {
+ if (qstring[2] == '0') {
+ autoindex_opts &= ~(FANCY_INDEXING | TABLE_INDEXING);
+ }
+ else if (qstring[2] == '1') {
+ autoindex_opts = (autoindex_opts | FANCY_INDEXING)
+ & ~TABLE_INDEXING;
+ }
+ else if (qstring[2] == '2') {
+ autoindex_opts |= FANCY_INDEXING | TABLE_INDEXING;
+ }
+ strcpy(fval, ";F= ");
+ fval[3] = qstring[2];
+ qstring += qstring[3] ? 4 : 3;
+ }
+
+ /* V= Version sort (0, 1) */
+ else if ( qstring[0] == 'V' && qstring[1] == '='
+ && (qstring[2] == '0' || qstring[2] == '1')
+ && ( qstring[3] == '&' || qstring[3] == ';'
+ || !qstring[3])) {
+ if (qstring[2] == '0') {
+ autoindex_opts &= ~VERSION_SORT;
+ }
+ else if (qstring[2] == '1') {
+ autoindex_opts |= VERSION_SORT;
+ }
+ strcpy(vval, ";V= ");
+ vval[3] = qstring[2];
+ qstring += qstring[3] ? 4 : 3;
+ }
+
+ /* P= wildcard pattern (*.foo) */
+ else if (qstring[0] == 'P' && qstring[1] == '=') {
+ const char *eos = qstring += 2; /* for efficiency */
+
+ while (*eos && *eos != '&' && *eos != ';') {
+ ++eos;
+ }
+
+ if (eos == qstring) {
+ pstring = NULL;
+ }
+ else {
+ pstring = apr_pstrndup(r->pool, qstring, eos - qstring);
+ if (ap_unescape_url(pstring) != OK) {
+ /* ignore the pattern, if it's bad. */
+ pstring = NULL;
+ }
+ else {
+ ppre = ";P=";
+ /* be correct */
+ epattern = ap_escape_uri(r->pool, pstring);
+ }
+ }
+
+ if (*eos && *++eos) {
+ qstring = eos;
+ }
+ else {
+ qstring = NULL;
+ }
+ }
+
+ /* Syntax error? Ignore the remainder! */
+ else {
+ qstring = NULL;
+ }
+ }
+ colargs = apr_pstrcat(r->pool, fval, vval, ppre, epattern, NULL);
+ }
+
+ /* Spew HTML preamble */
+ title_endp = title_name + strlen(title_name) - 1;
+
+ while (title_endp > title_name && *title_endp == '/') {
+ *title_endp-- = '\0';
+ }
+
+ emit_head(r, autoindex_conf->header,
+ autoindex_opts & SUPPRESS_PREAMBLE,
+ autoindex_opts & EMIT_XHTML, title_name);
+
+ /*
+ * Since we don't know how many dir. entries there are, put them into a
+ * linked list and then arrayificate them so qsort can use them.
+ */
+ head = NULL;
+ p = make_parent_entry(autoindex_opts, autoindex_conf, r, keyid, direction);
+ if (p != NULL) {
+ p->next = head;
+ head = p;
+ num_ent++;
+ }
+ fullpath = apr_palloc(r->pool, APR_PATH_MAX);
+ dirpathlen = strlen(name);
+ memcpy(fullpath, name, dirpathlen);
+
+ do {
+ status = apr_dir_read(&dirent, APR_FINFO_MIN | APR_FINFO_NAME, thedir);
+ if (APR_STATUS_IS_INCOMPLETE(status)) {
+ continue; /* ignore un-stat()able files */
+ }
+ else if (status != APR_SUCCESS) {
+ break;
+ }
+
+ /* We want to explode symlinks here. */
+ if (dirent.filetype == APR_LNK) {
+ const char *savename;
+ apr_finfo_t fi;
+ /* We *must* have FNAME. */
+ savename = dirent.name;
+ apr_cpystrn(fullpath + dirpathlen, dirent.name,
+ APR_PATH_MAX - dirpathlen);
+ status = apr_stat(&fi, fullpath,
+ dirent.valid & ~(APR_FINFO_NAME), r->pool);
+ if (status != APR_SUCCESS) {
+ /* Something bad happened, skip this file. */
+ continue;
+ }
+ memcpy(&dirent, &fi, sizeof(fi));
+ dirent.name = savename;
+ dirent.valid |= APR_FINFO_NAME;
+ }
+ p = make_autoindex_entry(&dirent, autoindex_opts, autoindex_conf, r,
+ keyid, direction, pstring);
+ if (p != NULL) {
+ p->next = head;
+ head = p;
+ num_ent++;
+ }
+ } while (1);
+
+ if (num_ent > 0) {
+ ar = (struct ent **) apr_palloc(r->pool,
+ num_ent * sizeof(struct ent *));
+ p = head;
+ x = 0;
+ while (p) {
+ ar[x++] = p;
+ p = p->next;
+ }
+
+ qsort((void *) ar, num_ent, sizeof(struct ent *),
+ (int (*)(const void *, const void *)) dsortf);
+ }
+ output_directories(ar, num_ent, autoindex_conf, r, autoindex_opts,
+ keyid, direction, colargs);
+ apr_dir_close(thedir);
+
+ emit_tail(r, autoindex_conf->readme,
+ autoindex_opts & SUPPRESS_PREAMBLE);
+
+ return 0;
+}
+
+/* The formal handler... */
+
+static int handle_autoindex(request_rec *r)
+{
+ autoindex_config_rec *d;
+ int allow_opts;
+
+ if (strcmp(r->handler,DIR_MAGIC_TYPE) && !AP_IS_DEFAULT_HANDLER_NAME(r->handler)) {
+ return DECLINED;
+ }
+ if (r->finfo.filetype != APR_DIR) {
+ return DECLINED;
+ }
+
+ allow_opts = ap_allow_options(r);
+
+ d = (autoindex_config_rec *) ap_get_module_config(r->per_dir_config,
+ &autoindex_module);
+
+ r->allowed |= (AP_METHOD_BIT << M_GET);
+ if (r->method_number != M_GET) {
+ return DECLINED;
+ }
+
+ /* OK, nothing easy. Trot out the heavy artillery... */
+
+ if (allow_opts & OPT_INDEXES) {
+ int errstatus;
+
+ if ((errstatus = ap_discard_request_body(r)) != OK) {
+ return errstatus;
+ }
+
+ /* KLUDGE --- make the sub_req lookups happen in the right directory.
+ * Fixing this in the sub_req_lookup functions themselves is difficult,
+ * and would probably break virtual includes...
+ */
+
+ if (r->filename[strlen(r->filename) - 1] != '/') {
+ r->filename = apr_pstrcat(r->pool, r->filename, "/", NULL);
+ }
+ return index_directory(r, d);
+ }
+ else {
+ const char *index_names = apr_table_get(r->notes, "dir-index-names");
+
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01276)
+ "Cannot serve directory %s: No matching DirectoryIndex (%s) found, and "
+ "server-generated directory index forbidden by "
+ "Options directive",
+ r->filename,
+ index_names ? index_names : "none");
+ return HTTP_FORBIDDEN;
+ }
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_hook_handler(handle_autoindex,NULL,NULL,APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(autoindex) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_autoindex_config, /* dir config creater */
+ merge_autoindex_configs, /* dir merger --- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ autoindex_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/generators/mod_autoindex.dep b/modules/generators/mod_autoindex.dep
new file mode 100644
index 0000000..1d5ca61
--- /dev/null
+++ b/modules/generators/mod_autoindex.dep
@@ -0,0 +1,61 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_autoindex.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_autoindex.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_core.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/generators/mod_autoindex.dsp b/modules/generators/mod_autoindex.dsp
new file mode 100644
index 0000000..1f6ec05
--- /dev/null
+++ b/modules/generators/mod_autoindex.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_autoindex" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_autoindex - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_autoindex.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_autoindex.mak" CFG="mod_autoindex - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_autoindex - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_autoindex - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_autoindex - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_autoindex_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_autoindex.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_autoindex.so" /d LONG_NAME="autoindex_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_autoindex.so" /base:@..\..\os\win32\BaseAddr.ref,mod_autoindex.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_autoindex.so" /base:@..\..\os\win32\BaseAddr.ref,mod_autoindex.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_autoindex.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_autoindex - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_autoindex_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_autoindex.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_autoindex.so" /d LONG_NAME="autoindex_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_autoindex.so" /base:@..\..\os\win32\BaseAddr.ref,mod_autoindex.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_autoindex.so" /base:@..\..\os\win32\BaseAddr.ref,mod_autoindex.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_autoindex.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_autoindex - Win32 Release"
+# Name "mod_autoindex - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_autoindex.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/generators/mod_autoindex.exp b/modules/generators/mod_autoindex.exp
new file mode 100644
index 0000000..90f4057
--- /dev/null
+++ b/modules/generators/mod_autoindex.exp
@@ -0,0 +1 @@
+autoindex_module
diff --git a/modules/generators/mod_autoindex.mak b/modules/generators/mod_autoindex.mak
new file mode 100644
index 0000000..daebb5d
--- /dev/null
+++ b/modules/generators/mod_autoindex.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_autoindex.dsp
+!IF "$(CFG)" == ""
+CFG=mod_autoindex - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_autoindex - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_autoindex - Win32 Release" && "$(CFG)" != "mod_autoindex - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_autoindex.mak" CFG="mod_autoindex - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_autoindex - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_autoindex - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_autoindex - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_autoindex.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_autoindex.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_autoindex.obj"
+ -@erase "$(INTDIR)\mod_autoindex.res"
+ -@erase "$(INTDIR)\mod_autoindex_src.idb"
+ -@erase "$(INTDIR)\mod_autoindex_src.pdb"
+ -@erase "$(OUTDIR)\mod_autoindex.exp"
+ -@erase "$(OUTDIR)\mod_autoindex.lib"
+ -@erase "$(OUTDIR)\mod_autoindex.pdb"
+ -@erase "$(OUTDIR)\mod_autoindex.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_autoindex_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_autoindex.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_autoindex.so" /d LONG_NAME="autoindex_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_autoindex.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_autoindex.pdb" /debug /out:"$(OUTDIR)\mod_autoindex.so" /implib:"$(OUTDIR)\mod_autoindex.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_autoindex.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_autoindex.obj" \
+ "$(INTDIR)\mod_autoindex.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_autoindex.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_autoindex.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_autoindex.so"
+ if exist .\Release\mod_autoindex.so.manifest mt.exe -manifest .\Release\mod_autoindex.so.manifest -outputresource:.\Release\mod_autoindex.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_autoindex - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_autoindex.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_autoindex.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_autoindex.obj"
+ -@erase "$(INTDIR)\mod_autoindex.res"
+ -@erase "$(INTDIR)\mod_autoindex_src.idb"
+ -@erase "$(INTDIR)\mod_autoindex_src.pdb"
+ -@erase "$(OUTDIR)\mod_autoindex.exp"
+ -@erase "$(OUTDIR)\mod_autoindex.lib"
+ -@erase "$(OUTDIR)\mod_autoindex.pdb"
+ -@erase "$(OUTDIR)\mod_autoindex.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_autoindex_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_autoindex.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_autoindex.so" /d LONG_NAME="autoindex_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_autoindex.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_autoindex.pdb" /debug /out:"$(OUTDIR)\mod_autoindex.so" /implib:"$(OUTDIR)\mod_autoindex.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_autoindex.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_autoindex.obj" \
+ "$(INTDIR)\mod_autoindex.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_autoindex.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_autoindex.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_autoindex.so"
+ if exist .\Debug\mod_autoindex.so.manifest mt.exe -manifest .\Debug\mod_autoindex.so.manifest -outputresource:.\Debug\mod_autoindex.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_autoindex.dep")
+!INCLUDE "mod_autoindex.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_autoindex.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_autoindex - Win32 Release" || "$(CFG)" == "mod_autoindex - Win32 Debug"
+
+!IF "$(CFG)" == "mod_autoindex - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\generators"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\generators"
+
+!ELSEIF "$(CFG)" == "mod_autoindex - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\generators"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\generators"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_autoindex - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\generators"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\generators"
+
+!ELSEIF "$(CFG)" == "mod_autoindex - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\generators"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\generators"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_autoindex - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\generators"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\generators"
+
+!ELSEIF "$(CFG)" == "mod_autoindex - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\generators"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\generators"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_autoindex - Win32 Release"
+
+
+"$(INTDIR)\mod_autoindex.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_autoindex.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_autoindex.so" /d LONG_NAME="autoindex_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_autoindex - Win32 Debug"
+
+
+"$(INTDIR)\mod_autoindex.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_autoindex.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_autoindex.so" /d LONG_NAME="autoindex_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_autoindex.c
+
+"$(INTDIR)\mod_autoindex.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/generators/mod_cgi.c b/modules/generators/mod_cgi.c
new file mode 100644
index 0000000..7e4b126
--- /dev/null
+++ b/modules/generators/mod_cgi.c
@@ -0,0 +1,1277 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * http_script: keeps all script-related ramblings together.
+ *
+ * Compliant to CGI/1.1 spec
+ *
+ * Adapted by rst from original NCSA code by Rob McCool
+ *
+ * This modules uses a httpd core function (ap_add_common_vars) to add some new env vars,
+ * like REDIRECT_URL and REDIRECT_QUERY_STRING for custom error responses and DOCUMENT_ROOT.
+ * It also adds SERVER_ADMIN - useful for scripts to know who to mail when they fail.
+ *
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_thread_proc.h" /* for RLIMIT stuff */
+#include "apr_optional.h"
+#include "apr_buckets.h"
+#include "apr_lib.h"
+#include "apr_poll.h"
+
+#define APR_WANT_STRFUNC
+#define APR_WANT_MEMFUNC
+#include "apr_want.h"
+
+#include "util_filter.h"
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_request.h"
+#include "http_core.h"
+#include "http_protocol.h"
+#include "http_main.h"
+#include "http_log.h"
+#include "util_script.h"
+#include "ap_mpm.h"
+#include "mod_core.h"
+#include "mod_cgi.h"
+
+#if APR_HAVE_STRUCT_RLIMIT
+#if defined (RLIMIT_CPU) || defined (RLIMIT_NPROC) || defined (RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined(RLIMIT_AS)
+#define AP_CGI_USE_RLIMIT
+#endif
+#endif
+
+module AP_MODULE_DECLARE_DATA cgi_module;
+
+static APR_OPTIONAL_FN_TYPE(ap_register_include_handler) *cgi_pfn_reg_with_ssi;
+static APR_OPTIONAL_FN_TYPE(ap_ssi_get_tag_and_value) *cgi_pfn_gtv;
+static APR_OPTIONAL_FN_TYPE(ap_ssi_parse_string) *cgi_pfn_ps;
+static APR_OPTIONAL_FN_TYPE(ap_cgi_build_command) *cgi_build_command;
+
+/* Read and discard the data in the brigade produced by a CGI script */
+static void discard_script_output(apr_bucket_brigade *bb);
+
+/* KLUDGE --- for back-combatibility, we don't have to check ExecCGI
+ * in ScriptAliased directories, which means we need to know if this
+ * request came through ScriptAlias or not... so the Alias module
+ * leaves a note for us.
+ */
+
+static int is_scriptaliased(request_rec *r)
+{
+ const char *t = apr_table_get(r->notes, "alias-forced-type");
+ return t && (!strcasecmp(t, "cgi-script"));
+}
+
+/* Configuration stuff */
+
+#define DEFAULT_LOGBYTES 10385760
+#define DEFAULT_BUFBYTES 1024
+
+typedef struct {
+ const char *logname;
+ long logbytes;
+ apr_size_t bufbytes;
+} cgi_server_conf;
+
+static void *create_cgi_config(apr_pool_t *p, server_rec *s)
+{
+ cgi_server_conf *c =
+ (cgi_server_conf *) apr_pcalloc(p, sizeof(cgi_server_conf));
+
+ c->logname = NULL;
+ c->logbytes = DEFAULT_LOGBYTES;
+ c->bufbytes = DEFAULT_BUFBYTES;
+
+ return c;
+}
+
+static void *merge_cgi_config(apr_pool_t *p, void *basev, void *overridesv)
+{
+ cgi_server_conf *base = (cgi_server_conf *) basev,
+ *overrides = (cgi_server_conf *) overridesv;
+
+ return overrides->logname ? overrides : base;
+}
+
+static const char *set_scriptlog(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ server_rec *s = cmd->server;
+ cgi_server_conf *conf = ap_get_module_config(s->module_config,
+ &cgi_module);
+
+ conf->logname = ap_server_root_relative(cmd->pool, arg);
+
+ if (!conf->logname) {
+ return apr_pstrcat(cmd->pool, "Invalid ScriptLog path ",
+ arg, NULL);
+ }
+
+ return NULL;
+}
+
+static const char *set_scriptlog_length(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ server_rec *s = cmd->server;
+ cgi_server_conf *conf = ap_get_module_config(s->module_config,
+ &cgi_module);
+
+ conf->logbytes = atol(arg);
+ return NULL;
+}
+
+static const char *set_scriptlog_buffer(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ server_rec *s = cmd->server;
+ cgi_server_conf *conf = ap_get_module_config(s->module_config,
+ &cgi_module);
+
+ conf->bufbytes = atoi(arg);
+ return NULL;
+}
+
+static const command_rec cgi_cmds[] =
+{
+AP_INIT_TAKE1("ScriptLog", set_scriptlog, NULL, RSRC_CONF,
+ "the name of a log for script debugging info"),
+AP_INIT_TAKE1("ScriptLogLength", set_scriptlog_length, NULL, RSRC_CONF,
+ "the maximum length (in bytes) of the script debug log"),
+AP_INIT_TAKE1("ScriptLogBuffer", set_scriptlog_buffer, NULL, RSRC_CONF,
+ "the maximum size (in bytes) to record of a POST request"),
+ {NULL}
+};
+
+static int log_scripterror(request_rec *r, cgi_server_conf * conf, int ret,
+ apr_status_t rv, char *logno, char *error)
+{
+ apr_file_t *f = NULL;
+ apr_finfo_t finfo;
+ char time_str[APR_CTIME_LEN];
+ int log_flags = rv ? APLOG_ERR : APLOG_ERR;
+
+ /* Intentional no APLOGNO */
+ /* Callee provides APLOGNO in error text */
+ ap_log_rerror(APLOG_MARK, log_flags, rv, r,
+ "%s%s: %s", logno ? logno : "", error, r->filename);
+
+ /* XXX Very expensive mainline case! Open, then getfileinfo! */
+ if (!conf->logname ||
+ ((apr_stat(&finfo, conf->logname,
+ APR_FINFO_SIZE, r->pool) == APR_SUCCESS) &&
+ (finfo.size > conf->logbytes)) ||
+ (apr_file_open(&f, conf->logname,
+ APR_APPEND|APR_WRITE|APR_CREATE, APR_OS_DEFAULT,
+ r->pool) != APR_SUCCESS)) {
+ return ret;
+ }
+
+ /* "%% [Wed Jun 19 10:53:21 1996] GET /cgi-bin/printenv HTTP/1.0" */
+ apr_ctime(time_str, apr_time_now());
+ apr_file_printf(f, "%%%% [%s] %s %s%s%s %s\n", time_str, r->method, r->uri,
+ r->args ? "?" : "", r->args ? r->args : "", r->protocol);
+ /* "%% 500 /usr/local/apache/cgi-bin */
+ apr_file_printf(f, "%%%% %d %s\n", ret, r->filename);
+
+ apr_file_printf(f, "%%error\n%s\n", error);
+
+ apr_file_close(f);
+ return ret;
+}
+
+/* Soak up stderr from a script and redirect it to the error log.
+ */
+static apr_status_t log_script_err(request_rec *r, apr_file_t *script_err)
+{
+ char argsbuffer[HUGE_STRING_LEN];
+ char *newline;
+ apr_status_t rv;
+ cgi_server_conf *conf = ap_get_module_config(r->server->module_config, &cgi_module);
+
+ while ((rv = apr_file_gets(argsbuffer, HUGE_STRING_LEN,
+ script_err)) == APR_SUCCESS) {
+ newline = strchr(argsbuffer, '\n');
+ if (newline) {
+ *newline = '\0';
+ }
+ log_scripterror(r, conf, r->status, 0, APLOGNO(01215), argsbuffer);
+ }
+
+ return rv;
+}
+
+static int log_script(request_rec *r, cgi_server_conf * conf, int ret,
+ char *dbuf, const char *sbuf, apr_bucket_brigade *bb,
+ apr_file_t *script_err)
+{
+ const apr_array_header_t *hdrs_arr = apr_table_elts(r->headers_in);
+ const apr_table_entry_t *hdrs = (const apr_table_entry_t *) hdrs_arr->elts;
+ char argsbuffer[HUGE_STRING_LEN];
+ apr_file_t *f = NULL;
+ apr_bucket *e;
+ const char *buf;
+ apr_size_t len;
+ apr_status_t rv;
+ int first;
+ int i;
+ apr_finfo_t finfo;
+ char time_str[APR_CTIME_LEN];
+
+ /* XXX Very expensive mainline case! Open, then getfileinfo! */
+ if (!conf->logname ||
+ ((apr_stat(&finfo, conf->logname,
+ APR_FINFO_SIZE, r->pool) == APR_SUCCESS) &&
+ (finfo.size > conf->logbytes)) ||
+ (apr_file_open(&f, conf->logname,
+ APR_APPEND|APR_WRITE|APR_CREATE, APR_OS_DEFAULT,
+ r->pool) != APR_SUCCESS)) {
+ /* Soak up script output */
+ discard_script_output(bb);
+ log_script_err(r, script_err);
+ return ret;
+ }
+
+ /* "%% [Wed Jun 19 10:53:21 1996] GET /cgi-bin/printenv HTTP/1.0" */
+ apr_ctime(time_str, apr_time_now());
+ apr_file_printf(f, "%%%% [%s] %s %s%s%s %s\n", time_str, r->method, r->uri,
+ r->args ? "?" : "", r->args ? r->args : "", r->protocol);
+ /* "%% 500 /usr/local/apache/cgi-bin" */
+ apr_file_printf(f, "%%%% %d %s\n", ret, r->filename);
+
+ apr_file_puts("%request\n", f);
+ for (i = 0; i < hdrs_arr->nelts; ++i) {
+ if (!hdrs[i].key)
+ continue;
+ apr_file_printf(f, "%s: %s\n", hdrs[i].key, hdrs[i].val);
+ }
+ if ((r->method_number == M_POST || r->method_number == M_PUT) &&
+ *dbuf) {
+ apr_file_printf(f, "\n%s\n", dbuf);
+ }
+
+ apr_file_puts("%response\n", f);
+ hdrs_arr = apr_table_elts(r->err_headers_out);
+ hdrs = (const apr_table_entry_t *) hdrs_arr->elts;
+
+ for (i = 0; i < hdrs_arr->nelts; ++i) {
+ if (!hdrs[i].key)
+ continue;
+ apr_file_printf(f, "%s: %s\n", hdrs[i].key, hdrs[i].val);
+ }
+
+ if (sbuf && *sbuf)
+ apr_file_printf(f, "%s\n", sbuf);
+
+ first = 1;
+ for (e = APR_BRIGADE_FIRST(bb);
+ e != APR_BRIGADE_SENTINEL(bb);
+ e = APR_BUCKET_NEXT(e))
+ {
+ if (APR_BUCKET_IS_EOS(e)) {
+ break;
+ }
+ rv = apr_bucket_read(e, &buf, &len, APR_BLOCK_READ);
+ if (rv != APR_SUCCESS || (len == 0)) {
+ break;
+ }
+ if (first) {
+ apr_file_puts("%stdout\n", f);
+ first = 0;
+ }
+ apr_file_write(f, buf, &len);
+ apr_file_puts("\n", f);
+ }
+
+ if (apr_file_gets(argsbuffer, HUGE_STRING_LEN, script_err) == APR_SUCCESS) {
+ apr_file_puts("%stderr\n", f);
+ apr_file_puts(argsbuffer, f);
+ while (apr_file_gets(argsbuffer, HUGE_STRING_LEN,
+ script_err) == APR_SUCCESS) {
+ apr_file_puts(argsbuffer, f);
+ }
+ apr_file_puts("\n", f);
+ }
+
+ apr_brigade_destroy(bb);
+ apr_file_close(script_err);
+
+ apr_file_close(f);
+ return ret;
+}
+
+
+/* This is the special environment used for running the "exec cmd="
+ * variety of SSI directives.
+ */
+static void add_ssi_vars(request_rec *r)
+{
+ apr_table_t *e = r->subprocess_env;
+
+ if (r->path_info && r->path_info[0] != '\0') {
+ request_rec *pa_req;
+
+ apr_table_setn(e, "PATH_INFO", ap_escape_shell_cmd(r->pool,
+ r->path_info));
+
+ pa_req = ap_sub_req_lookup_uri(ap_escape_uri(r->pool, r->path_info),
+ r, NULL);
+ if (pa_req->filename) {
+ apr_table_setn(e, "PATH_TRANSLATED",
+ apr_pstrcat(r->pool, pa_req->filename,
+ pa_req->path_info, NULL));
+ }
+ ap_destroy_sub_req(pa_req);
+ }
+
+ if (r->args) {
+ char *arg_copy = apr_pstrdup(r->pool, r->args);
+
+ apr_table_setn(e, "QUERY_STRING", r->args);
+ ap_unescape_url(arg_copy);
+ apr_table_setn(e, "QUERY_STRING_UNESCAPED",
+ ap_escape_shell_cmd(r->pool, arg_copy));
+ }
+}
+
+static void cgi_child_errfn(apr_pool_t *pool, apr_status_t err,
+ const char *description)
+{
+ apr_file_t *stderr_log;
+
+ apr_file_open_stderr(&stderr_log, pool);
+ /* Escape the logged string because it may be something that
+ * came in over the network.
+ */
+ apr_file_printf(stderr_log,
+ "(%d)%pm: %s\n",
+ err,
+ &err,
+#ifndef AP_UNSAFE_ERROR_LOG_UNESCAPED
+ ap_escape_logitem(pool,
+#endif
+ description
+#ifndef AP_UNSAFE_ERROR_LOG_UNESCAPED
+ )
+#endif
+ );
+}
+
+static apr_status_t run_cgi_child(apr_file_t **script_out,
+ apr_file_t **script_in,
+ apr_file_t **script_err,
+ const char *command,
+ const char * const argv[],
+ request_rec *r,
+ apr_pool_t *p,
+ cgi_exec_info_t *e_info)
+{
+ const char * const *env;
+ apr_procattr_t *procattr;
+ apr_proc_t *procnew;
+ apr_status_t rc = APR_SUCCESS;
+
+#ifdef AP_CGI_USE_RLIMIT
+ core_dir_config *conf = ap_get_core_module_config(r->per_dir_config);
+#endif
+
+#ifdef DEBUG_CGI
+#ifdef OS2
+ /* Under OS/2 need to use device con. */
+ FILE *dbg = fopen("con", "w");
+#else
+ FILE *dbg = fopen("/dev/tty", "w");
+#endif
+ int i;
+#endif
+
+ RAISE_SIGSTOP(CGI_CHILD);
+#ifdef DEBUG_CGI
+ fprintf(dbg, "Attempting to exec %s as CGI child (argv0 = %s)\n",
+ r->filename, argv[0]);
+#endif
+
+ env = (const char * const *)ap_create_environment(p, r->subprocess_env);
+
+#ifdef DEBUG_CGI
+ fprintf(dbg, "Environment: \n");
+ for (i = 0; env[i]; ++i)
+ fprintf(dbg, "'%s'\n", env[i]);
+ fclose(dbg);
+#endif
+
+ /* Transmute ourselves into the script.
+ * NB only ISINDEX scripts get decoded arguments.
+ */
+ if (((rc = apr_procattr_create(&procattr, p)) != APR_SUCCESS) ||
+ ((rc = apr_procattr_io_set(procattr,
+ e_info->in_pipe,
+ e_info->out_pipe,
+ e_info->err_pipe)) != APR_SUCCESS) ||
+ ((rc = apr_procattr_dir_set(procattr,
+ ap_make_dirstr_parent(r->pool,
+ r->filename))) != APR_SUCCESS) ||
+#if defined(RLIMIT_CPU) && defined(AP_CGI_USE_RLIMIT)
+ ((rc = apr_procattr_limit_set(procattr, APR_LIMIT_CPU,
+ conf->limit_cpu)) != APR_SUCCESS) ||
+#endif
+#if defined(AP_CGI_USE_RLIMIT) && (defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined(RLIMIT_AS))
+ ((rc = apr_procattr_limit_set(procattr, APR_LIMIT_MEM,
+ conf->limit_mem)) != APR_SUCCESS) ||
+#endif
+#if defined(RLIMIT_NPROC) && defined(AP_CGI_USE_RLIMIT)
+ ((rc = apr_procattr_limit_set(procattr, APR_LIMIT_NPROC,
+ conf->limit_nproc)) != APR_SUCCESS) ||
+#endif
+ ((rc = apr_procattr_cmdtype_set(procattr,
+ e_info->cmd_type)) != APR_SUCCESS) ||
+
+ ((rc = apr_procattr_detach_set(procattr,
+ e_info->detached)) != APR_SUCCESS) ||
+ ((rc = apr_procattr_addrspace_set(procattr,
+ e_info->addrspace)) != APR_SUCCESS) ||
+ ((rc = apr_procattr_child_errfn_set(procattr, cgi_child_errfn)) != APR_SUCCESS)) {
+ /* Something bad happened, tell the world. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(01216)
+ "couldn't set child process attributes: %s", r->filename);
+ }
+ else {
+ procnew = apr_pcalloc(p, sizeof(*procnew));
+ rc = ap_os_create_privileged_process(r, procnew, command, argv, env,
+ procattr, p);
+
+ if (rc != APR_SUCCESS) {
+ /* Bad things happened. Everyone should have cleaned up. */
+ /* Intentional no APLOGNO */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_TOCLIENT, rc, r,
+ "couldn't create child process: %d: %s", rc,
+ apr_filepath_name_get(r->filename));
+ }
+ else {
+ apr_pool_note_subprocess(p, procnew, APR_KILL_AFTER_TIMEOUT);
+
+ *script_in = procnew->out;
+ if (!*script_in)
+ return APR_EBADF;
+ apr_file_pipe_timeout_set(*script_in, r->server->timeout);
+
+ if (e_info->prog_type == RUN_AS_CGI) {
+ *script_out = procnew->in;
+ if (!*script_out)
+ return APR_EBADF;
+ apr_file_pipe_timeout_set(*script_out, r->server->timeout);
+
+ *script_err = procnew->err;
+ if (!*script_err)
+ return APR_EBADF;
+ apr_file_pipe_timeout_set(*script_err, r->server->timeout);
+ }
+ }
+ }
+ return (rc);
+}
+
+
+static apr_status_t default_build_command(const char **cmd, const char ***argv,
+ request_rec *r, apr_pool_t *p,
+ cgi_exec_info_t *e_info)
+{
+ int numwords, x, idx;
+ char *w;
+ const char *args = NULL;
+
+ if (e_info->process_cgi) {
+ *cmd = r->filename;
+ /* Do not process r->args if they contain an '=' assignment
+ */
+ if (r->args && r->args[0] && !ap_strchr_c(r->args, '=')) {
+ args = r->args;
+ }
+ }
+
+ if (!args) {
+ numwords = 1;
+ }
+ else {
+ /* count the number of keywords */
+ for (x = 0, numwords = 2; args[x]; x++) {
+ if (args[x] == '+') {
+ ++numwords;
+ }
+ }
+ }
+ /* Everything is - 1 to account for the first parameter
+ * which is the program name.
+ */
+ if (numwords > APACHE_ARG_MAX - 1) {
+ numwords = APACHE_ARG_MAX - 1; /* Truncate args to prevent overrun */
+ }
+ *argv = apr_palloc(p, (numwords + 2) * sizeof(char *));
+ (*argv)[0] = *cmd;
+ for (x = 1, idx = 1; x < numwords; x++) {
+ w = ap_getword_nulls(p, &args, '+');
+ ap_unescape_url(w);
+ (*argv)[idx++] = ap_escape_shell_cmd(p, w);
+ }
+ (*argv)[idx] = NULL;
+
+ return APR_SUCCESS;
+}
+
+static void discard_script_output(apr_bucket_brigade *bb)
+{
+ apr_bucket *e;
+ const char *buf;
+ apr_size_t len;
+
+ for (e = APR_BRIGADE_FIRST(bb);
+ e != APR_BRIGADE_SENTINEL(bb) && !APR_BUCKET_IS_EOS(e);
+ e = APR_BRIGADE_FIRST(bb))
+ {
+ if (apr_bucket_read(e, &buf, &len, APR_BLOCK_READ)) {
+ break;
+ }
+ apr_bucket_delete(e);
+ }
+}
+
+#if APR_FILES_AS_SOCKETS
+
+/* A CGI bucket type is needed to catch any output to stderr from the
+ * script; see PR 22030. */
+static const apr_bucket_type_t bucket_type_cgi;
+
+struct cgi_bucket_data {
+ apr_pollset_t *pollset;
+ request_rec *r;
+};
+
+/* Create a CGI bucket using pipes from script stdout 'out'
+ * and stderr 'err', for request 'r'. */
+static apr_bucket *cgi_bucket_create(request_rec *r,
+ apr_file_t *out, apr_file_t *err,
+ apr_bucket_alloc_t *list)
+{
+ apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
+ apr_status_t rv;
+ apr_pollfd_t fd;
+ struct cgi_bucket_data *data = apr_palloc(r->pool, sizeof *data);
+
+ APR_BUCKET_INIT(b);
+ b->free = apr_bucket_free;
+ b->list = list;
+ b->type = &bucket_type_cgi;
+ b->length = (apr_size_t)(-1);
+ b->start = -1;
+
+ /* Create the pollset */
+ rv = apr_pollset_create(&data->pollset, 2, r->pool, 0);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01217)
+ "apr_pollset_create(); check system or user limits");
+ return NULL;
+ }
+
+ fd.desc_type = APR_POLL_FILE;
+ fd.reqevents = APR_POLLIN;
+ fd.p = r->pool;
+ fd.desc.f = out; /* script's stdout */
+ fd.client_data = (void *)1;
+ rv = apr_pollset_add(data->pollset, &fd);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01218)
+ "apr_pollset_add(); check system or user limits");
+ return NULL;
+ }
+
+ fd.desc.f = err; /* script's stderr */
+ fd.client_data = (void *)2;
+ rv = apr_pollset_add(data->pollset, &fd);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01219)
+ "apr_pollset_add(); check system or user limits");
+ return NULL;
+ }
+
+ data->r = r;
+ b->data = data;
+ return b;
+}
+
+/* Create a duplicate CGI bucket using given bucket data */
+static apr_bucket *cgi_bucket_dup(struct cgi_bucket_data *data,
+ apr_bucket_alloc_t *list)
+{
+ apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
+ APR_BUCKET_INIT(b);
+ b->free = apr_bucket_free;
+ b->list = list;
+ b->type = &bucket_type_cgi;
+ b->length = (apr_size_t)(-1);
+ b->start = -1;
+ b->data = data;
+ return b;
+}
+
+/* Handle stdout from CGI child. Duplicate of logic from the _read
+ * method of the real APR pipe bucket implementation. */
+static apr_status_t cgi_read_stdout(apr_bucket *a, apr_file_t *out,
+ const char **str, apr_size_t *len)
+{
+ char *buf;
+ apr_status_t rv;
+
+ *str = NULL;
+ *len = APR_BUCKET_BUFF_SIZE;
+ buf = apr_bucket_alloc(*len, a->list); /* XXX: check for failure? */
+
+ rv = apr_file_read(out, buf, len);
+
+ if (rv != APR_SUCCESS && rv != APR_EOF) {
+ apr_bucket_free(buf);
+ return rv;
+ }
+
+ if (*len > 0) {
+ struct cgi_bucket_data *data = a->data;
+ apr_bucket_heap *h;
+
+ /* Change the current bucket to refer to what we read */
+ a = apr_bucket_heap_make(a, buf, *len, apr_bucket_free);
+ h = a->data;
+ h->alloc_len = APR_BUCKET_BUFF_SIZE; /* note the real buffer size */
+ *str = buf;
+ APR_BUCKET_INSERT_AFTER(a, cgi_bucket_dup(data, a->list));
+ }
+ else {
+ apr_bucket_free(buf);
+ a = apr_bucket_immortal_make(a, "", 0);
+ *str = a->data;
+ }
+ return rv;
+}
+
+/* Read method of CGI bucket: polls on stderr and stdout of the child,
+ * sending any stderr output immediately away to the error log. */
+static apr_status_t cgi_bucket_read(apr_bucket *b, const char **str,
+ apr_size_t *len, apr_read_type_e block)
+{
+ struct cgi_bucket_data *data = b->data;
+ apr_interval_time_t timeout;
+ apr_status_t rv;
+ int gotdata = 0;
+
+ timeout = block == APR_NONBLOCK_READ ? 0 : data->r->server->timeout;
+
+ do {
+ const apr_pollfd_t *results;
+ apr_int32_t num;
+
+ rv = apr_pollset_poll(data->pollset, timeout, &num, &results);
+ if (APR_STATUS_IS_TIMEUP(rv)) {
+ if (timeout) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, data->r, APLOGNO(01220)
+ "Timeout waiting for output from CGI script %s",
+ data->r->filename);
+ return rv;
+ }
+ else {
+ return APR_EAGAIN;
+ }
+ }
+ else if (APR_STATUS_IS_EINTR(rv)) {
+ continue;
+ }
+ else if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, data->r, APLOGNO(01221)
+ "poll failed waiting for CGI child");
+ return rv;
+ }
+
+ for (; num; num--, results++) {
+ if (results[0].client_data == (void *)1) {
+ /* stdout */
+ rv = cgi_read_stdout(b, results[0].desc.f, str, len);
+ if (APR_STATUS_IS_EOF(rv)) {
+ rv = APR_SUCCESS;
+ }
+ gotdata = 1;
+ } else {
+ /* stderr */
+ apr_status_t rv2 = log_script_err(data->r, results[0].desc.f);
+ if (APR_STATUS_IS_EOF(rv2)) {
+ apr_pollset_remove(data->pollset, &results[0]);
+ }
+ }
+ }
+
+ } while (!gotdata);
+
+ return rv;
+}
+
+static const apr_bucket_type_t bucket_type_cgi = {
+ "CGI", 5, APR_BUCKET_DATA,
+ apr_bucket_destroy_noop,
+ cgi_bucket_read,
+ apr_bucket_setaside_notimpl,
+ apr_bucket_split_notimpl,
+ apr_bucket_copy_notimpl
+};
+
+#endif
+
+static int cgi_handler(request_rec *r)
+{
+ int nph;
+ apr_size_t dbpos = 0;
+ const char *argv0;
+ const char *command;
+ const char **argv;
+ char *dbuf = NULL;
+ apr_file_t *script_out = NULL, *script_in = NULL, *script_err = NULL;
+ apr_bucket_brigade *bb;
+ apr_bucket *b;
+ int is_included;
+ int seen_eos, child_stopped_reading;
+ apr_pool_t *p;
+ cgi_server_conf *conf;
+ apr_status_t rv;
+ cgi_exec_info_t e_info;
+ conn_rec *c;
+
+ if (strcmp(r->handler, CGI_MAGIC_TYPE) && strcmp(r->handler, "cgi-script")) {
+ return DECLINED;
+ }
+
+ c = r->connection;
+
+ is_included = !strcmp(r->protocol, "INCLUDED");
+
+ p = r->main ? r->main->pool : r->pool;
+
+ argv0 = apr_filepath_name_get(r->filename);
+ nph = !(strncmp(argv0, "nph-", 4));
+ conf = ap_get_module_config(r->server->module_config, &cgi_module);
+
+ if (!(ap_allow_options(r) & OPT_EXECCGI) && !is_scriptaliased(r))
+ return log_scripterror(r, conf, HTTP_FORBIDDEN, 0, APLOGNO(02809),
+ "Options ExecCGI is off in this directory");
+ if (nph && is_included)
+ return log_scripterror(r, conf, HTTP_FORBIDDEN, 0, APLOGNO(02810),
+ "attempt to include NPH CGI script");
+
+ if (r->finfo.filetype == APR_NOFILE)
+ return log_scripterror(r, conf, HTTP_NOT_FOUND, 0, APLOGNO(02811),
+ "script not found or unable to stat");
+ if (r->finfo.filetype == APR_DIR)
+ return log_scripterror(r, conf, HTTP_FORBIDDEN, 0, APLOGNO(02812),
+ "attempt to invoke directory as script");
+
+ if ((r->used_path_info == AP_REQ_REJECT_PATH_INFO) &&
+ r->path_info && *r->path_info)
+ {
+ /* default to accept */
+ return log_scripterror(r, conf, HTTP_NOT_FOUND, 0, APLOGNO(02813),
+ "AcceptPathInfo off disallows user's path");
+ }
+/*
+ if (!ap_suexec_enabled) {
+ if (!ap_can_exec(&r->finfo))
+ return log_scripterror(r, conf, HTTP_FORBIDDEN, 0, APLOGNO(03194)
+ "file permissions deny server execution");
+ }
+
+*/
+ ap_add_common_vars(r);
+ ap_add_cgi_vars(r);
+
+ e_info.process_cgi = 1;
+ e_info.cmd_type = APR_PROGRAM;
+ e_info.detached = 0;
+ e_info.in_pipe = APR_CHILD_BLOCK;
+ e_info.out_pipe = APR_CHILD_BLOCK;
+ e_info.err_pipe = APR_CHILD_BLOCK;
+ e_info.prog_type = RUN_AS_CGI;
+ e_info.bb = NULL;
+ e_info.ctx = NULL;
+ e_info.next = NULL;
+ e_info.addrspace = 0;
+
+ /* build the command line */
+ if ((rv = cgi_build_command(&command, &argv, r, p, &e_info)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01222)
+ "don't know how to spawn child process: %s",
+ r->filename);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* run the script in its own process */
+ if ((rv = run_cgi_child(&script_out, &script_in, &script_err,
+ command, argv, r, p, &e_info)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01223)
+ "couldn't spawn child process: %s", r->filename);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* Transfer any put/post args, CERN style...
+ * Note that we already ignore SIGPIPE in the core server.
+ */
+ bb = apr_brigade_create(r->pool, c->bucket_alloc);
+ seen_eos = 0;
+ child_stopped_reading = 0;
+ if (conf->logname) {
+ dbuf = apr_palloc(r->pool, conf->bufbytes + 1);
+ dbpos = 0;
+ }
+ do {
+ apr_bucket *bucket;
+
+ rv = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES,
+ APR_BLOCK_READ, HUGE_STRING_LEN);
+
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01225)
+ "Error reading request entity data");
+ return ap_map_http_request_error(rv, HTTP_BAD_REQUEST);
+ }
+
+ for (bucket = APR_BRIGADE_FIRST(bb);
+ bucket != APR_BRIGADE_SENTINEL(bb);
+ bucket = APR_BUCKET_NEXT(bucket))
+ {
+ const char *data;
+ apr_size_t len;
+
+ if (APR_BUCKET_IS_EOS(bucket)) {
+ seen_eos = 1;
+ break;
+ }
+
+ /* We can't do much with this. */
+ if (APR_BUCKET_IS_FLUSH(bucket)) {
+ continue;
+ }
+
+ /* If the child stopped, we still must read to EOS. */
+ if (child_stopped_reading) {
+ continue;
+ }
+
+ /* read */
+ apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ);
+
+ if (conf->logname && dbpos < conf->bufbytes) {
+ int cursize;
+
+ if ((dbpos + len) > conf->bufbytes) {
+ cursize = conf->bufbytes - dbpos;
+ }
+ else {
+ cursize = len;
+ }
+ memcpy(dbuf + dbpos, data, cursize);
+ dbpos += cursize;
+ }
+
+ /* Keep writing data to the child until done or too much time
+ * elapses with no progress or an error occurs.
+ */
+ rv = apr_file_write_full(script_out, data, len, NULL);
+
+ if (rv != APR_SUCCESS) {
+ /* silly script stopped reading, soak up remaining message */
+ child_stopped_reading = 1;
+ }
+ }
+ apr_brigade_cleanup(bb);
+ }
+ while (!seen_eos);
+
+ if (conf->logname) {
+ dbuf[dbpos] = '\0';
+ }
+ /* Is this flush really needed? */
+ apr_file_flush(script_out);
+ apr_file_close(script_out);
+
+ AP_DEBUG_ASSERT(script_in != NULL);
+
+#if APR_FILES_AS_SOCKETS
+ apr_file_pipe_timeout_set(script_in, 0);
+ apr_file_pipe_timeout_set(script_err, 0);
+
+ b = cgi_bucket_create(r, script_in, script_err, c->bucket_alloc);
+ if (b == NULL)
+ return HTTP_INTERNAL_SERVER_ERROR;
+#else
+ b = apr_bucket_pipe_create(script_in, c->bucket_alloc);
+#endif
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ b = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+
+ /* Handle script return... */
+ if (!nph) {
+ const char *location;
+ char sbuf[MAX_STRING_LEN];
+ int ret;
+
+ if ((ret = ap_scan_script_header_err_brigade_ex(r, bb, sbuf,
+ APLOG_MODULE_INDEX)))
+ {
+ ret = log_script(r, conf, ret, dbuf, sbuf, bb, script_err);
+
+ /*
+ * ret could be HTTP_NOT_MODIFIED in the case that the CGI script
+ * does not set an explicit status and ap_meets_conditions, which
+ * is called by ap_scan_script_header_err_brigade, detects that
+ * the conditions of the requests are met and the response is
+ * not modified.
+ * In this case set r->status and return OK in order to prevent
+ * running through the error processing stack as this would
+ * break with mod_cache, if the conditions had been set by
+ * mod_cache itself to validate a stale entity.
+ * BTW: We circumvent the error processing stack anyway if the
+ * CGI script set an explicit status code (whatever it is) and
+ * the only possible values for ret here are:
+ *
+ * HTTP_NOT_MODIFIED (set by ap_meets_conditions)
+ * HTTP_PRECONDITION_FAILED (set by ap_meets_conditions)
+ * HTTP_INTERNAL_SERVER_ERROR (if something went wrong during the
+ * processing of the response of the CGI script, e.g broken headers
+ * or a crashed CGI process).
+ */
+ if (ret == HTTP_NOT_MODIFIED) {
+ r->status = ret;
+ return OK;
+ }
+
+ return ret;
+ }
+
+ location = apr_table_get(r->headers_out, "Location");
+
+ if (location && r->status == 200) {
+ /* For a redirect whether internal or not, discard any
+ * remaining stdout from the script, and log any remaining
+ * stderr output, as normal. */
+ discard_script_output(bb);
+ apr_brigade_destroy(bb);
+ apr_file_pipe_timeout_set(script_err, r->server->timeout);
+ log_script_err(r, script_err);
+ }
+
+ if (location && location[0] == '/' && r->status == 200) {
+ /* This redirect needs to be a GET no matter what the original
+ * method was.
+ */
+ r->method = "GET";
+ r->method_number = M_GET;
+
+ /* We already read the message body (if any), so don't allow
+ * the redirected request to think it has one. We can ignore
+ * Transfer-Encoding, since we used REQUEST_CHUNKED_ERROR.
+ */
+ apr_table_unset(r->headers_in, "Content-Length");
+
+ ap_internal_redirect_handler(location, r);
+ return OK;
+ }
+ else if (location && r->status == 200) {
+ /* XXX: Note that if a script wants to produce its own Redirect
+ * body, it now has to explicitly *say* "Status: 302"
+ */
+ return HTTP_MOVED_TEMPORARILY;
+ }
+
+ rv = ap_pass_brigade(r->output_filters, bb);
+ }
+ else /* nph */ {
+ struct ap_filter_t *cur;
+
+ /* get rid of all filters up through protocol... since we
+ * haven't parsed off the headers, there is no way they can
+ * work
+ */
+
+ cur = r->proto_output_filters;
+ while (cur && cur->frec->ftype < AP_FTYPE_CONNECTION) {
+ cur = cur->next;
+ }
+ r->output_filters = r->proto_output_filters = cur;
+
+ rv = ap_pass_brigade(r->output_filters, bb);
+ }
+
+ /* don't soak up script output if errors occurred writing it
+ * out... otherwise, we prolong the life of the script when the
+ * connection drops or we stopped sending output for some other
+ * reason */
+ if (rv == APR_SUCCESS && !r->connection->aborted) {
+ apr_file_pipe_timeout_set(script_err, r->server->timeout);
+ log_script_err(r, script_err);
+ }
+
+ apr_file_close(script_err);
+
+ return OK; /* NOT r->status, even if it has changed. */
+}
+
+/*============================================================================
+ *============================================================================
+ * This is the beginning of the cgi filter code moved from mod_include. This
+ * is the code required to handle the "exec" SSI directive.
+ *============================================================================
+ *============================================================================*/
+static apr_status_t include_cgi(include_ctx_t *ctx, ap_filter_t *f,
+ apr_bucket_brigade *bb, char *s)
+{
+ request_rec *r = f->r;
+ request_rec *rr = ap_sub_req_lookup_uri(s, r, f->next);
+ int rr_status;
+
+ if (rr->status != HTTP_OK) {
+ ap_destroy_sub_req(rr);
+ return APR_EGENERAL;
+ }
+
+ /* No hardwired path info or query allowed */
+ if ((rr->path_info && rr->path_info[0]) || rr->args) {
+ ap_destroy_sub_req(rr);
+ return APR_EGENERAL;
+ }
+ if (rr->finfo.filetype != APR_REG) {
+ ap_destroy_sub_req(rr);
+ return APR_EGENERAL;
+ }
+
+ /* Script gets parameters of the *document*, for back compatibility */
+ rr->path_info = r->path_info; /* hard to get right; see mod_cgi.c */
+ rr->args = r->args;
+
+ /* Force sub_req to be treated as a CGI request, even if ordinary
+ * typing rules would have called it something else.
+ */
+ ap_set_content_type(rr, CGI_MAGIC_TYPE);
+
+ /* Run it. */
+ rr_status = ap_run_sub_req(rr);
+ if (ap_is_HTTP_REDIRECT(rr_status)) {
+ const char *location = apr_table_get(rr->headers_out, "Location");
+
+ if (location) {
+ char *buffer;
+
+ location = ap_escape_html(rr->pool, location);
+ buffer = apr_pstrcat(ctx->pool, "<a href=\"", location, "\">",
+ location, "</a>", NULL);
+
+ APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(buffer,
+ strlen(buffer), ctx->pool,
+ f->c->bucket_alloc));
+ }
+ }
+
+ ap_destroy_sub_req(rr);
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t include_cmd(include_ctx_t *ctx, ap_filter_t *f,
+ apr_bucket_brigade *bb, const char *command)
+{
+ cgi_exec_info_t e_info;
+ const char **argv;
+ apr_file_t *script_out = NULL, *script_in = NULL, *script_err = NULL;
+ apr_status_t rv;
+ request_rec *r = f->r;
+
+ add_ssi_vars(r);
+
+ e_info.process_cgi = 0;
+ e_info.cmd_type = APR_SHELLCMD;
+ e_info.detached = 0;
+ e_info.in_pipe = APR_NO_PIPE;
+ e_info.out_pipe = APR_FULL_BLOCK;
+ e_info.err_pipe = APR_NO_PIPE;
+ e_info.prog_type = RUN_AS_SSI;
+ e_info.bb = &bb;
+ e_info.ctx = ctx;
+ e_info.next = f->next;
+ e_info.addrspace = 0;
+
+ if ((rv = cgi_build_command(&command, &argv, r, r->pool,
+ &e_info)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01226)
+ "don't know how to spawn cmd child process: %s",
+ r->filename);
+ return rv;
+ }
+
+ /* run the script in its own process */
+ if ((rv = run_cgi_child(&script_out, &script_in, &script_err,
+ command, argv, r, r->pool,
+ &e_info)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01227)
+ "couldn't spawn child process: %s", r->filename);
+ return rv;
+ }
+
+ APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pipe_create(script_in,
+ f->c->bucket_alloc));
+ ctx->flush_now = 1;
+
+ /* We can't close the pipe here, because we may return before the
+ * full CGI has been sent to the network. That's okay though,
+ * because we can rely on the pool to close the pipe for us.
+ */
+ return APR_SUCCESS;
+}
+
+static apr_status_t handle_exec(include_ctx_t *ctx, ap_filter_t *f,
+ apr_bucket_brigade *bb)
+{
+ char *tag = NULL;
+ char *tag_val = NULL;
+ request_rec *r = f->r;
+ char *file = r->filename;
+ char parsed_string[MAX_STRING_LEN];
+
+ if (!ctx->argc) {
+ ap_log_rerror(APLOG_MARK,
+ (ctx->flags & SSI_FLAG_PRINTING)
+ ? APLOG_ERR : APLOG_WARNING,
+ 0, r, APLOGNO(03195)
+ "missing argument for exec element in %s", r->filename);
+ }
+
+ if (!(ctx->flags & SSI_FLAG_PRINTING)) {
+ return APR_SUCCESS;
+ }
+
+ if (!ctx->argc) {
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ return APR_SUCCESS;
+ }
+
+ if (ctx->flags & SSI_FLAG_NO_EXEC) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01228) "exec used but not allowed "
+ "in %s", r->filename);
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ return APR_SUCCESS;
+ }
+
+ while (1) {
+ cgi_pfn_gtv(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
+ if (!tag || !tag_val) {
+ break;
+ }
+
+ if (!strcmp(tag, "cmd")) {
+ apr_status_t rv;
+
+ cgi_pfn_ps(ctx, tag_val, parsed_string, sizeof(parsed_string),
+ SSI_EXPAND_LEAVE_NAME);
+
+ rv = include_cmd(ctx, f, bb, parsed_string);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01229) "execution failure "
+ "for parameter \"%s\" to tag exec in file %s",
+ tag, r->filename);
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ break;
+ }
+ }
+ else if (!strcmp(tag, "cgi")) {
+ apr_status_t rv;
+
+ cgi_pfn_ps(ctx, tag_val, parsed_string, sizeof(parsed_string),
+ SSI_EXPAND_DROP_NAME);
+
+ rv = include_cgi(ctx, f, bb, parsed_string);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01230) "invalid CGI ref "
+ "\"%s\" in %s", tag_val, file);
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ break;
+ }
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01231) "unknown parameter "
+ "\"%s\" to tag exec in %s", tag, file);
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ break;
+ }
+ }
+
+ return APR_SUCCESS;
+}
+
+
+/*============================================================================
+ *============================================================================
+ * This is the end of the cgi filter code moved from mod_include.
+ *============================================================================
+ *============================================================================*/
+
+
+static int cgi_post_config(apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ cgi_pfn_reg_with_ssi = APR_RETRIEVE_OPTIONAL_FN(ap_register_include_handler);
+ cgi_pfn_gtv = APR_RETRIEVE_OPTIONAL_FN(ap_ssi_get_tag_and_value);
+ cgi_pfn_ps = APR_RETRIEVE_OPTIONAL_FN(ap_ssi_parse_string);
+
+ if ((cgi_pfn_reg_with_ssi) && (cgi_pfn_gtv) && (cgi_pfn_ps)) {
+ /* Required by mod_include filter. This is how mod_cgi registers
+ * with mod_include to provide processing of the exec directive.
+ */
+ cgi_pfn_reg_with_ssi("exec", handle_exec);
+ }
+
+ /* This is the means by which unusual (non-unix) os's may find alternate
+ * means to run a given command (e.g. shebang/registry parsing on Win32)
+ */
+ cgi_build_command = APR_RETRIEVE_OPTIONAL_FN(ap_cgi_build_command);
+ if (!cgi_build_command) {
+ cgi_build_command = default_build_command;
+ }
+ return OK;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ static const char * const aszPre[] = { "mod_include.c", NULL };
+ ap_hook_handler(cgi_handler, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_post_config(cgi_post_config, aszPre, NULL, APR_HOOK_REALLY_FIRST);
+}
+
+AP_DECLARE_MODULE(cgi) =
+{
+ STANDARD20_MODULE_STUFF,
+ NULL, /* dir config creater */
+ NULL, /* dir merger --- default is to override */
+ create_cgi_config, /* server config */
+ merge_cgi_config, /* merge server config */
+ cgi_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/generators/mod_cgi.dep b/modules/generators/mod_cgi.dep
new file mode 100644
index 0000000..3da4698
--- /dev/null
+++ b/modules/generators/mod_cgi.dep
@@ -0,0 +1,64 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_cgi.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_cgi.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_mpm.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_core.h"\
+ "..\..\include\mod_include.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_cgi.h"\
+
diff --git a/modules/generators/mod_cgi.dsp b/modules/generators/mod_cgi.dsp
new file mode 100644
index 0000000..4fe58e0
--- /dev/null
+++ b/modules/generators/mod_cgi.dsp
@@ -0,0 +1,115 @@
+# Microsoft Developer Studio Project File - Name="mod_cgi" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_cgi - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_cgi.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_cgi.mak" CFG="mod_cgi - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_cgi - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_cgi - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_cgi - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_cgi_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_cgi.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_cgi.so" /d LONG_NAME="cgi_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_cgi.so" /base:@..\..\os\win32\BaseAddr.ref,mod_cgi.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_cgi.so" /base:@..\..\os\win32\BaseAddr.ref,mod_cgi.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_cgi.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_cgi - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_cgi_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_cgi.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_cgi.so" /d LONG_NAME="cgi_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_cgi.so" /base:@..\..\os\win32\BaseAddr.ref,mod_cgi.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_cgi.so" /base:@..\..\os\win32\BaseAddr.ref,mod_cgi.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_cgi.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_cgi - Win32 Release"
+# Name "mod_cgi - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_cgi.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\mod_cgi.h
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/generators/mod_cgi.exp b/modules/generators/mod_cgi.exp
new file mode 100644
index 0000000..96ea0c2
--- /dev/null
+++ b/modules/generators/mod_cgi.exp
@@ -0,0 +1 @@
+cgi_module
diff --git a/modules/generators/mod_cgi.h b/modules/generators/mod_cgi.h
new file mode 100644
index 0000000..424c6f2
--- /dev/null
+++ b/modules/generators/mod_cgi.h
@@ -0,0 +1,67 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file mod_cgi.h
+ * @brief CGI Script Execution Extension Module for Apache
+ *
+ * @defgroup MOD_CGI mod_cgi
+ * @ingroup APACHE_MODS
+ * @{
+ */
+
+#ifndef _MOD_CGI_H
+#define _MOD_CGI_H 1
+
+#include "mod_include.h"
+
+typedef enum {RUN_AS_SSI, RUN_AS_CGI} prog_types;
+
+typedef struct {
+ apr_int32_t in_pipe;
+ apr_int32_t out_pipe;
+ apr_int32_t err_pipe;
+ int process_cgi;
+ apr_cmdtype_e cmd_type;
+ apr_int32_t detached;
+ prog_types prog_type;
+ apr_bucket_brigade **bb;
+ include_ctx_t *ctx;
+ ap_filter_t *next;
+ apr_int32_t addrspace;
+} cgi_exec_info_t;
+
+/**
+ * Registerable optional function to override CGI behavior;
+ * Reprocess the command and arguments to execute the given CGI script.
+ * @param cmd Pointer to the command to execute (may be overridden)
+ * @param argv Pointer to the arguments to pass (may be overridden)
+ * @param r The current request
+ * @param p The pool to allocate correct cmd/argv elements within.
+ * @param e_info pass e_info.cmd_type (Set to APR_SHELLCMD or APR_PROGRAM on entry)
+ and e_info.detached (Should the child start in detached state?)
+ * @remark This callback may be registered by the os-specific module
+ * to correct the command and arguments for apr_proc_create invocation
+ * on a given os. mod_cgi will call the function if registered.
+ */
+APR_DECLARE_OPTIONAL_FN(apr_status_t, ap_cgi_build_command,
+ (const char **cmd, const char ***argv,
+ request_rec *r, apr_pool_t *p,
+ cgi_exec_info_t *e_info));
+
+#endif /* _MOD_CGI_H */
+/** @} */
+
diff --git a/modules/generators/mod_cgi.mak b/modules/generators/mod_cgi.mak
new file mode 100644
index 0000000..bd5e7e7
--- /dev/null
+++ b/modules/generators/mod_cgi.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_cgi.dsp
+!IF "$(CFG)" == ""
+CFG=mod_cgi - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_cgi - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_cgi - Win32 Release" && "$(CFG)" != "mod_cgi - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_cgi.mak" CFG="mod_cgi - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_cgi - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_cgi - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_cgi - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_cgi.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_cgi.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_cgi.obj"
+ -@erase "$(INTDIR)\mod_cgi.res"
+ -@erase "$(INTDIR)\mod_cgi_src.idb"
+ -@erase "$(INTDIR)\mod_cgi_src.pdb"
+ -@erase "$(OUTDIR)\mod_cgi.exp"
+ -@erase "$(OUTDIR)\mod_cgi.lib"
+ -@erase "$(OUTDIR)\mod_cgi.pdb"
+ -@erase "$(OUTDIR)\mod_cgi.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_cgi_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_cgi.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_cgi.so" /d LONG_NAME="cgi_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_cgi.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_cgi.pdb" /debug /out:"$(OUTDIR)\mod_cgi.so" /implib:"$(OUTDIR)\mod_cgi.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_cgi.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_cgi.obj" \
+ "$(INTDIR)\mod_cgi.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_cgi.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_cgi.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_cgi.so"
+ if exist .\Release\mod_cgi.so.manifest mt.exe -manifest .\Release\mod_cgi.so.manifest -outputresource:.\Release\mod_cgi.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_cgi - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_cgi.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_cgi.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_cgi.obj"
+ -@erase "$(INTDIR)\mod_cgi.res"
+ -@erase "$(INTDIR)\mod_cgi_src.idb"
+ -@erase "$(INTDIR)\mod_cgi_src.pdb"
+ -@erase "$(OUTDIR)\mod_cgi.exp"
+ -@erase "$(OUTDIR)\mod_cgi.lib"
+ -@erase "$(OUTDIR)\mod_cgi.pdb"
+ -@erase "$(OUTDIR)\mod_cgi.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_cgi_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_cgi.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_cgi.so" /d LONG_NAME="cgi_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_cgi.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_cgi.pdb" /debug /out:"$(OUTDIR)\mod_cgi.so" /implib:"$(OUTDIR)\mod_cgi.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_cgi.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_cgi.obj" \
+ "$(INTDIR)\mod_cgi.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_cgi.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_cgi.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_cgi.so"
+ if exist .\Debug\mod_cgi.so.manifest mt.exe -manifest .\Debug\mod_cgi.so.manifest -outputresource:.\Debug\mod_cgi.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_cgi.dep")
+!INCLUDE "mod_cgi.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_cgi.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_cgi - Win32 Release" || "$(CFG)" == "mod_cgi - Win32 Debug"
+
+!IF "$(CFG)" == "mod_cgi - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\generators"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\generators"
+
+!ELSEIF "$(CFG)" == "mod_cgi - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\generators"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\generators"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_cgi - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\generators"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\generators"
+
+!ELSEIF "$(CFG)" == "mod_cgi - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\generators"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\generators"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_cgi - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\generators"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\generators"
+
+!ELSEIF "$(CFG)" == "mod_cgi - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\generators"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\generators"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_cgi - Win32 Release"
+
+
+"$(INTDIR)\mod_cgi.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_cgi.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_cgi.so" /d LONG_NAME="cgi_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_cgi - Win32 Debug"
+
+
+"$(INTDIR)\mod_cgi.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_cgi.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_cgi.so" /d LONG_NAME="cgi_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_cgi.c
+
+"$(INTDIR)\mod_cgi.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/generators/mod_cgid.c b/modules/generators/mod_cgid.c
new file mode 100644
index 0000000..2258a68
--- /dev/null
+++ b/modules/generators/mod_cgid.c
@@ -0,0 +1,1978 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * http_script: keeps all script-related ramblings together.
+ *
+ * Compliant to cgi/1.1 spec
+ *
+ * Adapted by rst from original NCSA code by Rob McCool
+ *
+ * This modules uses a httpd core function (ap_add_common_vars) to add some new env vars,
+ * like REDIRECT_URL and REDIRECT_QUERY_STRING for custom error responses and DOCUMENT_ROOT.
+ * It also adds SERVER_ADMIN - useful for scripts to know who to mail when they fail.
+ *
+ */
+
+#include "apr_lib.h"
+#include "apr_strings.h"
+#include "apr_general.h"
+#include "apr_file_io.h"
+#include "apr_portable.h"
+#include "apr_buckets.h"
+#include "apr_optional.h"
+#include "apr_signal.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#if APR_HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if APR_HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#include "util_filter.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_request.h"
+#include "http_core.h"
+#include "http_protocol.h"
+#include "http_main.h"
+#include "http_log.h"
+#include "util_script.h"
+#include "ap_mpm.h"
+#include "mpm_common.h"
+#include "mod_suexec.h"
+#include "../filters/mod_include.h"
+
+#include "mod_core.h"
+
+
+/* ### should be tossed in favor of APR */
+#include <sys/stat.h>
+#include <sys/un.h> /* for sockaddr_un */
+
+#if APR_HAVE_STRUCT_RLIMIT
+#if defined (RLIMIT_CPU) || defined (RLIMIT_NPROC) || defined (RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined(RLIMIT_AS)
+#define AP_CGID_USE_RLIMIT
+#endif
+#endif
+
+module AP_MODULE_DECLARE_DATA cgid_module;
+
+static int cgid_start(apr_pool_t *p, server_rec *main_server, apr_proc_t *procnew);
+static int cgid_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *main_server);
+static int handle_exec(include_ctx_t *ctx, ap_filter_t *f, apr_bucket_brigade *bb);
+
+static APR_OPTIONAL_FN_TYPE(ap_register_include_handler) *cgid_pfn_reg_with_ssi;
+static APR_OPTIONAL_FN_TYPE(ap_ssi_get_tag_and_value) *cgid_pfn_gtv;
+static APR_OPTIONAL_FN_TYPE(ap_ssi_parse_string) *cgid_pfn_ps;
+
+static apr_pool_t *pcgi = NULL;
+static pid_t daemon_pid;
+static int daemon_should_exit = 0;
+static server_rec *root_server = NULL;
+static apr_pool_t *root_pool = NULL;
+static const char *sockname;
+static struct sockaddr_un *server_addr;
+static apr_socklen_t server_addr_len;
+static pid_t parent_pid;
+static ap_unix_identity_t empty_ugid = { (uid_t)-1, (gid_t)-1, -1 };
+
+typedef struct {
+ apr_interval_time_t timeout;
+} cgid_dirconf;
+
+/* The APR other-child API doesn't tell us how the daemon exited
+ * (SIGSEGV vs. exit(1)). The other-child maintenance function
+ * needs to decide whether to restart the daemon after a failure
+ * based on whether or not it exited due to a fatal startup error
+ * or something that happened at steady-state. This exit status
+ * is unlikely to collide with exit signals.
+ */
+#define DAEMON_STARTUP_ERROR 254
+
+/* Read and discard the data in the brigade produced by a CGI script */
+static void discard_script_output(apr_bucket_brigade *bb);
+
+/* This doer will only ever be called when we are sure that we have
+ * a valid ugid.
+ */
+static ap_unix_identity_t *cgid_suexec_id_doer(const request_rec *r)
+{
+ return (ap_unix_identity_t *)
+ ap_get_module_config(r->request_config, &cgid_module);
+}
+
+/* KLUDGE --- for back-combatibility, we don't have to check ExecCGI
+ * in ScriptAliased directories, which means we need to know if this
+ * request came through ScriptAlias or not... so the Alias module
+ * leaves a note for us.
+ */
+
+static int is_scriptaliased(request_rec *r)
+{
+ const char *t = apr_table_get(r->notes, "alias-forced-type");
+ return t && (!strcasecmp(t, "cgi-script"));
+}
+
+/* Configuration stuff */
+
+#define DEFAULT_LOGBYTES 10385760
+#define DEFAULT_BUFBYTES 1024
+#define DEFAULT_SOCKET "cgisock"
+
+#define CGI_REQ 1
+#define SSI_REQ 2
+#define GETPID_REQ 3 /* get the pid of script created for prior request */
+
+#define ERRFN_USERDATA_KEY "CGIDCHILDERRFN"
+
+/* DEFAULT_CGID_LISTENBACKLOG controls the max depth on the unix socket's
+ * pending connection queue. If a bunch of cgi requests arrive at about
+ * the same time, connections from httpd threads/processes will back up
+ * in the queue while the cgid process slowly forks off a child to process
+ * each connection on the unix socket. If the queue is too short, the
+ * httpd process will get ECONNREFUSED when trying to connect.
+ */
+#ifndef DEFAULT_CGID_LISTENBACKLOG
+#define DEFAULT_CGID_LISTENBACKLOG 100
+#endif
+
+/* DEFAULT_CONNECT_ATTEMPTS controls how many times we'll try to connect
+ * to the cgi daemon from the thread/process handling the cgi request.
+ * Generally we want to retry when we get ECONNREFUSED since it is
+ * probably because the listen queue is full. We need to try harder so
+ * the client doesn't see it as a 503 error.
+ *
+ * Set this to 0 to continually retry until the connect works or Apache
+ * terminates.
+ */
+#ifndef DEFAULT_CONNECT_ATTEMPTS
+#define DEFAULT_CONNECT_ATTEMPTS 15
+#endif
+
+#ifndef DEFAULT_CONNECT_STARTUP_DELAY
+#define DEFAULT_CONNECT_STARTUP_DELAY 60
+#endif
+
+typedef struct {
+ const char *logname;
+ long logbytes;
+ int bufbytes;
+} cgid_server_conf;
+
+#ifdef AP_CGID_USE_RLIMIT
+typedef struct {
+#ifdef RLIMIT_CPU
+ int limit_cpu_set;
+ struct rlimit limit_cpu;
+#endif
+#if defined (RLIMIT_DATA) || defined (RLIMIT_VMEM) || defined(RLIMIT_AS)
+ int limit_mem_set;
+ struct rlimit limit_mem;
+#endif
+#ifdef RLIMIT_NPROC
+ int limit_nproc_set;
+ struct rlimit limit_nproc;
+#endif
+
+} cgid_rlimit_t;
+#endif
+
+typedef struct {
+ int req_type; /* request type (CGI_REQ, SSI_REQ, etc.) */
+ unsigned long conn_id; /* connection id; daemon uses this as a hash value
+ * to find the script pid when it is time for that
+ * process to be cleaned up
+ */
+ pid_t ppid; /* sanity check for config problems leading to
+ * wrong cgid socket use
+ */
+ int env_count;
+ ap_unix_identity_t ugid;
+ apr_size_t filename_len;
+ apr_size_t argv0_len;
+ apr_size_t uri_len;
+ apr_size_t args_len;
+ int loglevel; /* to stuff in server_rec */
+
+#ifdef AP_CGID_USE_RLIMIT
+ cgid_rlimit_t limits;
+#endif
+} cgid_req_t;
+
+/* This routine is called to create the argument list to be passed
+ * to the CGI script. When suexec is enabled, the suexec path, user, and
+ * group are the first three arguments to be passed; if not, all three
+ * must be NULL. The query info is split into separate arguments, where
+ * "+" is the separator between keyword arguments.
+ *
+ * Do not process the args if they containing an '=' assignment.
+ */
+static char **create_argv(apr_pool_t *p, char *path, char *user, char *group,
+ char *av0, const char *args)
+{
+ int x, numwords;
+ char **av;
+ char *w;
+ int idx = 0;
+
+ if (!(*args) || ap_strchr_c(args, '=')) {
+ numwords = 0;
+ }
+ else {
+ /* count the number of keywords */
+
+ for (x = 0, numwords = 1; args[x]; x++) {
+ if (args[x] == '+') {
+ ++numwords;
+ }
+ }
+ }
+
+ if (numwords > APACHE_ARG_MAX - 5) {
+ numwords = APACHE_ARG_MAX - 5; /* Truncate args to prevent overrun */
+ }
+ av = (char **) apr_pcalloc(p, (numwords + 5) * sizeof(char *));
+
+ if (path) {
+ av[idx++] = path;
+ }
+ if (user) {
+ av[idx++] = user;
+ }
+ if (group) {
+ av[idx++] = group;
+ }
+
+ av[idx++] = apr_pstrdup(p, av0);
+
+ for (x = 1; x <= numwords; x++) {
+ w = ap_getword_nulls(p, &args, '+');
+ ap_unescape_url(w);
+ av[idx++] = ap_escape_shell_cmd(p, w);
+ }
+ av[idx] = NULL;
+ return av;
+}
+
+#if APR_HAS_OTHER_CHILD
+static void cgid_maint(int reason, void *data, apr_wait_t status)
+{
+ apr_proc_t *proc = data;
+ int mpm_state;
+ int stopping;
+
+ switch (reason) {
+ case APR_OC_REASON_DEATH:
+ apr_proc_other_child_unregister(data);
+ /* If apache is not terminating or restarting,
+ * restart the cgid daemon
+ */
+ stopping = 1; /* if MPM doesn't support query,
+ * assume we shouldn't restart daemon
+ */
+ if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state) == APR_SUCCESS &&
+ mpm_state != AP_MPMQ_STOPPING) {
+ stopping = 0;
+ }
+ if (!stopping) {
+ if (status == DAEMON_STARTUP_ERROR) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, ap_server_conf, APLOGNO(01238)
+ "cgid daemon failed to initialize");
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(01239)
+ "cgid daemon process died, restarting");
+ cgid_start(root_pool, root_server, proc);
+ }
+ }
+ break;
+ case APR_OC_REASON_RESTART:
+ /* don't do anything; server is stopping or restarting */
+ apr_proc_other_child_unregister(data);
+ break;
+ case APR_OC_REASON_LOST:
+ /* Restart the child cgid daemon process */
+ apr_proc_other_child_unregister(data);
+ cgid_start(root_pool, root_server, proc);
+ break;
+ case APR_OC_REASON_UNREGISTER:
+ /* we get here when pcgi is cleaned up; pcgi gets cleaned
+ * up when pconf gets cleaned up
+ */
+ kill(proc->pid, SIGHUP); /* send signal to daemon telling it to die */
+
+ /* Remove the cgi socket, we must do it here in order to try and
+ * guarantee the same permissions as when the socket was created.
+ */
+ if (unlink(sockname) < 0 && errno != ENOENT) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, errno, ap_server_conf, APLOGNO(01240)
+ "Couldn't unlink unix domain socket %s",
+ sockname);
+ }
+ break;
+ }
+}
+#endif
+
+static apr_status_t close_unix_socket(void *thefd)
+{
+ int fd = (int)((long)thefd);
+
+ return close(fd);
+}
+
+/* deal with incomplete reads and signals
+ * assume you really have to read buf_size bytes
+ */
+static apr_status_t sock_read(int fd, void *vbuf, size_t buf_size)
+{
+ char *buf = vbuf;
+ int rc;
+ size_t bytes_read = 0;
+
+ do {
+ do {
+ rc = read(fd, buf + bytes_read, buf_size - bytes_read);
+ } while (rc < 0 && errno == EINTR);
+ switch(rc) {
+ case -1:
+ return errno;
+ case 0: /* unexpected */
+ return ECONNRESET;
+ default:
+ bytes_read += rc;
+ }
+ } while (bytes_read < buf_size);
+
+ return APR_SUCCESS;
+}
+
+/* deal with signals
+ */
+static apr_status_t sock_write(int fd, const void *buf, size_t buf_size)
+{
+ int rc;
+
+ do {
+ rc = write(fd, buf, buf_size);
+ } while (rc < 0 && errno == EINTR);
+ if (rc < 0) {
+ return errno;
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t sock_writev(int fd, request_rec *r, int count, ...)
+{
+ va_list ap;
+ int rc;
+ struct iovec *vec;
+ int i;
+
+ vec = (struct iovec *)apr_palloc(r->pool, count * sizeof(struct iovec));
+ va_start(ap, count);
+ for (i = 0; i < count; i++) {
+ vec[i].iov_base = va_arg(ap, caddr_t);
+ vec[i].iov_len = va_arg(ap, apr_size_t);
+ }
+ va_end(ap);
+
+ do {
+ rc = writev(fd, vec, count);
+ } while (rc < 0 && errno == EINTR);
+ if (rc < 0) {
+ return errno;
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t get_req(int fd, request_rec *r, char **argv0, char ***env,
+ cgid_req_t *req)
+{
+ int i;
+ char **environ;
+ core_request_config *temp_core;
+ void **rconf;
+ apr_status_t stat;
+
+ r->server = apr_pcalloc(r->pool, sizeof(server_rec));
+
+ /* read the request header */
+ stat = sock_read(fd, req, sizeof(*req));
+ if (stat != APR_SUCCESS) {
+ return stat;
+ }
+ r->server->log.level = req->loglevel;
+ if (req->req_type == GETPID_REQ) {
+ /* no more data sent for this request */
+ return APR_SUCCESS;
+ }
+
+ /* handle module indexes and such */
+ rconf = (void **)ap_create_request_config(r->pool);
+
+ temp_core = (core_request_config *)apr_palloc(r->pool, sizeof(core_module));
+ rconf[AP_CORE_MODULE_INDEX] = (void *)temp_core;
+ r->request_config = (ap_conf_vector_t *)rconf;
+ ap_set_module_config(r->request_config, &cgid_module, (void *)&req->ugid);
+
+ /* Read the filename, argv0, uri, and args */
+ r->filename = apr_pcalloc(r->pool, req->filename_len + 1);
+ *argv0 = apr_pcalloc(r->pool, req->argv0_len + 1);
+ r->uri = apr_pcalloc(r->pool, req->uri_len + 1);
+ if ((stat = sock_read(fd, r->filename, req->filename_len)) != APR_SUCCESS ||
+ (stat = sock_read(fd, *argv0, req->argv0_len)) != APR_SUCCESS ||
+ (stat = sock_read(fd, r->uri, req->uri_len)) != APR_SUCCESS) {
+ return stat;
+ }
+
+ r->args = apr_pcalloc(r->pool, req->args_len + 1); /* empty string if no args */
+ if (req->args_len) {
+ if ((stat = sock_read(fd, r->args, req->args_len)) != APR_SUCCESS) {
+ return stat;
+ }
+ }
+
+ /* read the environment variables */
+ environ = apr_pcalloc(r->pool, (req->env_count + 2) *sizeof(char *));
+ for (i = 0; i < req->env_count; i++) {
+ apr_size_t curlen;
+
+ if ((stat = sock_read(fd, &curlen, sizeof(curlen))) != APR_SUCCESS) {
+ return stat;
+ }
+ environ[i] = apr_pcalloc(r->pool, curlen + 1);
+ if ((stat = sock_read(fd, environ[i], curlen)) != APR_SUCCESS) {
+ return stat;
+ }
+ }
+ *env = environ;
+
+#ifdef AP_CGID_USE_RLIMIT
+ if ((stat = sock_read(fd, &(req->limits), sizeof(cgid_rlimit_t))) != APR_SUCCESS)
+ return stat;
+#endif
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t send_req(int fd, request_rec *r, char *argv0, char **env,
+ int req_type)
+{
+ int i;
+ cgid_req_t req = {0};
+ apr_status_t stat;
+ ap_unix_identity_t * ugid = ap_run_get_suexec_identity(r);
+ core_dir_config *core_conf = ap_get_core_module_config(r->per_dir_config);
+
+
+ if (ugid == NULL) {
+ req.ugid = empty_ugid;
+ } else {
+ memcpy(&req.ugid, ugid, sizeof(ap_unix_identity_t));
+ }
+
+ req.req_type = req_type;
+ req.ppid = parent_pid;
+ req.conn_id = r->connection->id;
+ for (req.env_count = 0; env[req.env_count]; req.env_count++) {
+ continue;
+ }
+ req.filename_len = strlen(r->filename);
+ req.argv0_len = strlen(argv0);
+ req.uri_len = strlen(r->uri);
+ req.args_len = r->args ? strlen(r->args) : 0;
+ req.loglevel = r->server->log.level;
+
+ /* Write the request header */
+ if (req.args_len) {
+ stat = sock_writev(fd, r, 5,
+ &req, sizeof(req),
+ r->filename, req.filename_len,
+ argv0, req.argv0_len,
+ r->uri, req.uri_len,
+ r->args, req.args_len);
+ } else {
+ stat = sock_writev(fd, r, 4,
+ &req, sizeof(req),
+ r->filename, req.filename_len,
+ argv0, req.argv0_len,
+ r->uri, req.uri_len);
+ }
+
+ if (stat != APR_SUCCESS) {
+ return stat;
+ }
+
+ /* write the environment variables */
+ for (i = 0; i < req.env_count; i++) {
+ apr_size_t curlen = strlen(env[i]);
+
+ if ((stat = sock_writev(fd, r, 2, &curlen, sizeof(curlen),
+ env[i], curlen)) != APR_SUCCESS) {
+ return stat;
+ }
+ }
+#if defined(RLIMIT_CPU) && defined(AP_CGID_USE_RLIMIT)
+ if (core_conf->limit_cpu) {
+ req.limits.limit_cpu = *(core_conf->limit_cpu);
+ req.limits.limit_cpu_set = 1;
+ }
+ else {
+ req.limits.limit_cpu_set = 0;
+ }
+#endif
+
+#if defined(AP_CGID_USE_RLIMIT) && (defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined(RLIMIT_AS))
+ if (core_conf->limit_mem) {
+ req.limits.limit_mem = *(core_conf->limit_mem);
+ req.limits.limit_mem_set = 1;
+ }
+ else {
+ req.limits.limit_mem_set = 0;
+ }
+
+#endif
+
+#if defined(RLIMIT_NPROC) && defined(AP_CGID_USE_RLIMIT)
+ if (core_conf->limit_nproc) {
+ req.limits.limit_nproc = *(core_conf->limit_nproc);
+ req.limits.limit_nproc_set = 1;
+ }
+ else {
+ req.limits.limit_nproc_set = 0;
+ }
+#endif
+
+#ifdef AP_CGID_USE_RLIMIT
+ if ( (stat = sock_write(fd, &(req.limits), sizeof(cgid_rlimit_t))) != APR_SUCCESS)
+ return stat;
+#endif
+
+ return APR_SUCCESS;
+}
+
+static void daemon_signal_handler(int sig)
+{
+ if (sig == SIGHUP) {
+ ++daemon_should_exit;
+ }
+}
+
+static void cgid_child_errfn(apr_pool_t *pool, apr_status_t err,
+ const char *description)
+{
+ request_rec *r;
+ void *vr;
+
+ apr_pool_userdata_get(&vr, ERRFN_USERDATA_KEY, pool);
+ r = vr;
+
+ /* sure we got r, but don't call ap_log_rerror() because we don't
+ * have r->headers_in and possibly other storage referenced by
+ * ap_log_rerror()
+ */
+ ap_log_error(APLOG_MARK, APLOG_ERR, err, r->server, APLOGNO(01241) "%s", description);
+}
+
+static int cgid_server(void *data)
+{
+ int sd, sd2, rc;
+ mode_t omask;
+ apr_pool_t *ptrans;
+ server_rec *main_server = data;
+ apr_hash_t *script_hash = apr_hash_make(pcgi);
+ apr_status_t rv;
+
+ apr_pool_create(&ptrans, pcgi);
+ apr_pool_tag(ptrans, "cgid_ptrans");
+
+ apr_signal(SIGCHLD, SIG_IGN);
+ apr_signal(SIGHUP, daemon_signal_handler);
+
+ /* Close our copy of the listening sockets */
+ ap_close_listeners();
+
+ /* cgid should use its own suexec doer */
+ ap_hook_get_suexec_identity(cgid_suexec_id_doer, NULL, NULL,
+ APR_HOOK_REALLY_FIRST);
+ apr_hook_sort_all();
+
+ if ((sd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, errno, main_server, APLOGNO(01242)
+ "Couldn't create unix domain socket");
+ return errno;
+ }
+
+ apr_pool_cleanup_register(pcgi, (void *)((long)sd),
+ close_unix_socket, close_unix_socket);
+
+ omask = umask(0077); /* so that only Apache can use socket */
+ rc = bind(sd, (struct sockaddr *)server_addr, server_addr_len);
+ umask(omask); /* can't fail, so can't clobber errno */
+ if (rc < 0) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, errno, main_server, APLOGNO(01243)
+ "Couldn't bind unix domain socket %s",
+ sockname);
+ return errno;
+ }
+
+ /* Not all flavors of unix use the current umask for AF_UNIX perms */
+ rv = apr_file_perms_set(sockname, APR_FPROT_UREAD|APR_FPROT_UWRITE|APR_FPROT_UEXECUTE);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, main_server, APLOGNO(01244)
+ "Couldn't set permissions on unix domain socket %s",
+ sockname);
+ return rv;
+ }
+
+ if (listen(sd, DEFAULT_CGID_LISTENBACKLOG) < 0) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, errno, main_server, APLOGNO(01245)
+ "Couldn't listen on unix domain socket");
+ return errno;
+ }
+
+ if (!geteuid()) {
+ if (chown(sockname, ap_unixd_config.user_id, -1) < 0) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, errno, main_server, APLOGNO(01246)
+ "Couldn't change owner of unix domain socket %s",
+ sockname);
+ return errno;
+ }
+ }
+
+ /* if running as root, switch to configured user/group */
+ if ((rc = ap_run_drop_privileges(pcgi, ap_server_conf)) != 0) {
+ return rc;
+ }
+
+ while (!daemon_should_exit) {
+ int errfileno = STDERR_FILENO;
+ char *argv0 = NULL;
+ char **env = NULL;
+ const char * const *argv;
+ apr_int32_t in_pipe;
+ apr_int32_t out_pipe;
+ apr_int32_t err_pipe;
+ apr_cmdtype_e cmd_type;
+ request_rec *r;
+ apr_procattr_t *procattr = NULL;
+ apr_proc_t *procnew = NULL;
+ apr_file_t *inout;
+ cgid_req_t cgid_req;
+ apr_status_t stat;
+ void *key;
+ apr_socklen_t len;
+ struct sockaddr_un unix_addr;
+
+ apr_pool_clear(ptrans);
+
+ len = sizeof(unix_addr);
+ sd2 = accept(sd, (struct sockaddr *)&unix_addr, &len);
+ if (sd2 < 0) {
+#if defined(ENETDOWN)
+ if (errno == ENETDOWN) {
+ /* The network has been shut down, no need to continue. Die gracefully */
+ ++daemon_should_exit;
+ }
+#endif
+ if (errno != EINTR) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, errno,
+ (server_rec *)data, APLOGNO(01247)
+ "Error accepting on cgid socket");
+ }
+ continue;
+ }
+
+ r = apr_pcalloc(ptrans, sizeof(request_rec));
+ procnew = apr_pcalloc(ptrans, sizeof(*procnew));
+ r->pool = ptrans;
+ stat = get_req(sd2, r, &argv0, &env, &cgid_req);
+ if (stat != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, stat,
+ main_server, APLOGNO(01248)
+ "Error reading request on cgid socket");
+ close(sd2);
+ continue;
+ }
+
+ if (cgid_req.ppid != parent_pid) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, main_server, APLOGNO(01249)
+ "CGI request received from wrong server instance; "
+ "see ScriptSock directive");
+ close(sd2);
+ continue;
+ }
+
+ if (cgid_req.req_type == GETPID_REQ) {
+ pid_t pid;
+ apr_status_t rv;
+
+ pid = (pid_t)((long)apr_hash_get(script_hash, &cgid_req.conn_id, sizeof(cgid_req.conn_id)));
+ rv = sock_write(sd2, &pid, sizeof(pid));
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv,
+ main_server, APLOGNO(01250)
+ "Error writing pid %" APR_PID_T_FMT " to handler", pid);
+ }
+ close(sd2);
+ continue;
+ }
+
+ apr_os_file_put(&r->server->error_log, &errfileno, 0, r->pool);
+ apr_os_file_put(&inout, &sd2, 0, r->pool);
+
+ if (cgid_req.req_type == SSI_REQ) {
+ in_pipe = APR_NO_PIPE;
+ out_pipe = APR_FULL_BLOCK;
+ err_pipe = APR_NO_PIPE;
+ cmd_type = APR_SHELLCMD;
+ }
+ else {
+ in_pipe = APR_CHILD_BLOCK;
+ out_pipe = APR_CHILD_BLOCK;
+ err_pipe = APR_CHILD_BLOCK;
+ cmd_type = APR_PROGRAM;
+ }
+
+ if (((rc = apr_procattr_create(&procattr, ptrans)) != APR_SUCCESS) ||
+ ((cgid_req.req_type == CGI_REQ) &&
+ (((rc = apr_procattr_io_set(procattr,
+ in_pipe,
+ out_pipe,
+ err_pipe)) != APR_SUCCESS) ||
+ /* XXX apr_procattr_child_*_set() is creating an unnecessary
+ * pipe between this process and the child being created...
+ * It is cleaned up with the temporary pool for this request.
+ */
+ ((rc = apr_procattr_child_err_set(procattr, r->server->error_log, NULL)) != APR_SUCCESS) ||
+ ((rc = apr_procattr_child_in_set(procattr, inout, NULL)) != APR_SUCCESS))) ||
+ ((rc = apr_procattr_child_out_set(procattr, inout, NULL)) != APR_SUCCESS) ||
+ ((rc = apr_procattr_dir_set(procattr,
+ ap_make_dirstr_parent(r->pool, r->filename))) != APR_SUCCESS) ||
+ ((rc = apr_procattr_cmdtype_set(procattr, cmd_type)) != APR_SUCCESS) ||
+#ifdef AP_CGID_USE_RLIMIT
+#ifdef RLIMIT_CPU
+ ( (cgid_req.limits.limit_cpu_set) && ((rc = apr_procattr_limit_set(procattr, APR_LIMIT_CPU,
+ &cgid_req.limits.limit_cpu)) != APR_SUCCESS)) ||
+#endif
+#if defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined(RLIMIT_AS)
+ ( (cgid_req.limits.limit_mem_set) && ((rc = apr_procattr_limit_set(procattr, APR_LIMIT_MEM,
+ &cgid_req.limits.limit_mem)) != APR_SUCCESS)) ||
+#endif
+#ifdef RLIMIT_NPROC
+ ( (cgid_req.limits.limit_nproc_set) && ((rc = apr_procattr_limit_set(procattr, APR_LIMIT_NPROC,
+ &cgid_req.limits.limit_nproc)) != APR_SUCCESS)) ||
+#endif
+#endif
+
+ ((rc = apr_procattr_child_errfn_set(procattr, cgid_child_errfn)) != APR_SUCCESS)) {
+ /* Something bad happened, tell the world.
+ * ap_log_rerror() won't work because the header table used by
+ * ap_log_rerror() hasn't been replicated in the phony r
+ */
+ ap_log_error(APLOG_MARK, APLOG_ERR, rc, r->server, APLOGNO(01251)
+ "couldn't set child process attributes: %s", r->filename);
+
+ procnew->pid = 0; /* no process to clean up */
+ close(sd2);
+ }
+ else {
+ apr_pool_userdata_set(r, ERRFN_USERDATA_KEY, apr_pool_cleanup_null, ptrans);
+
+ argv = (const char * const *)create_argv(r->pool, NULL, NULL, NULL, argv0, r->args);
+
+ /* We want to close sd2 for the new CGI process too.
+ * If it is left open it'll make ap_pass_brigade() block
+ * waiting for EOF if CGI forked something running long.
+ * close(sd2) here should be okay, as CGI channel
+ * is already dup()ed by apr_procattr_child_{in,out}_set()
+ * above.
+ */
+ close(sd2);
+
+ if (memcmp(&empty_ugid, &cgid_req.ugid, sizeof(empty_ugid))) {
+ /* We have a valid identity, and can be sure that
+ * cgid_suexec_id_doer will return a valid ugid
+ */
+ rc = ap_os_create_privileged_process(r, procnew, argv0, argv,
+ (const char * const *)env,
+ procattr, ptrans);
+ } else {
+ rc = apr_proc_create(procnew, argv0, argv,
+ (const char * const *)env,
+ procattr, ptrans);
+ }
+
+ if (rc != APR_SUCCESS) {
+ /* Bad things happened. Everyone should have cleaned up.
+ * ap_log_rerror() won't work because the header table used by
+ * ap_log_rerror() hasn't been replicated in the phony r
+ */
+ ap_log_error(APLOG_MARK, APLOG_ERR, rc, r->server, APLOGNO(01252)
+ "couldn't create child process: %d: %s", rc,
+ apr_filepath_name_get(r->filename));
+
+ procnew->pid = 0; /* no process to clean up */
+ }
+ }
+
+ /* If the script process was created, remember the pid for
+ * later cleanup. If the script process wasn't created, clear
+ * out any prior pid with the same key.
+ *
+ * We don't want to leak storage for the key, so only allocate
+ * a key if the key doesn't exist yet in the hash; there are
+ * only a limited number of possible keys (one for each
+ * possible thread in the server), so we can allocate a copy
+ * of the key the first time a thread has a cgid request.
+ * Note that apr_hash_set() only uses the storage passed in
+ * for the key if it is adding the key to the hash for the
+ * first time; new key storage isn't needed for replacing the
+ * existing value of a key.
+ */
+
+ if (apr_hash_get(script_hash, &cgid_req.conn_id, sizeof(cgid_req.conn_id))) {
+ key = &cgid_req.conn_id;
+ }
+ else {
+ key = apr_pmemdup(pcgi, &cgid_req.conn_id, sizeof(cgid_req.conn_id));
+ }
+ apr_hash_set(script_hash, key, sizeof(cgid_req.conn_id),
+ (void *)((long)procnew->pid));
+ }
+ return -1; /* should be <= 0 to distinguish from startup errors */
+}
+
+static int cgid_start(apr_pool_t *p, server_rec *main_server,
+ apr_proc_t *procnew)
+{
+
+ daemon_should_exit = 0; /* clear setting from previous generation */
+ if ((daemon_pid = fork()) < 0) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, errno, main_server, APLOGNO(01253)
+ "mod_cgid: Couldn't spawn cgid daemon process");
+ return DECLINED;
+ }
+ else if (daemon_pid == 0) {
+ if (pcgi == NULL) {
+ apr_pool_create(&pcgi, p);
+ apr_pool_tag(pcgi, "cgid_pcgi");
+ }
+ exit(cgid_server(main_server) > 0 ? DAEMON_STARTUP_ERROR : -1);
+ }
+ procnew->pid = daemon_pid;
+ procnew->err = procnew->in = procnew->out = NULL;
+ apr_pool_note_subprocess(p, procnew, APR_KILL_AFTER_TIMEOUT);
+#if APR_HAS_OTHER_CHILD
+ apr_proc_other_child_register(procnew, cgid_maint, procnew, NULL, p);
+#endif
+ return OK;
+}
+
+static int cgid_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp)
+{
+ sockname = ap_append_pid(pconf, DEFAULT_SOCKET, ".");
+ return OK;
+}
+
+static int cgid_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp,
+ server_rec *main_server)
+{
+ apr_proc_t *procnew = NULL;
+ const char *userdata_key = "cgid_init";
+ int ret = OK;
+ void *data;
+
+ root_server = main_server;
+ root_pool = p;
+
+ apr_pool_userdata_get(&data, userdata_key, main_server->process->pool);
+ if (!data) {
+ procnew = apr_pcalloc(main_server->process->pool, sizeof(*procnew));
+ procnew->pid = -1;
+ procnew->err = procnew->in = procnew->out = NULL;
+ apr_pool_userdata_set((const void *)procnew, userdata_key,
+ apr_pool_cleanup_null, main_server->process->pool);
+ return ret;
+ }
+ else {
+ procnew = data;
+ }
+
+ if (ap_state_query(AP_SQ_MAIN_STATE) != AP_SQ_MS_CREATE_PRE_CONFIG) {
+ char *tmp_sockname;
+
+ parent_pid = getpid();
+ tmp_sockname = ap_runtime_dir_relative(p, sockname);
+ if (strlen(tmp_sockname) > sizeof(server_addr->sun_path) - 1) {
+ tmp_sockname[sizeof(server_addr->sun_path)] = '\0';
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, main_server, APLOGNO(01254)
+ "The length of the ScriptSock path exceeds maximum, "
+ "truncating to %s", tmp_sockname);
+ }
+ sockname = tmp_sockname;
+
+ server_addr_len = APR_OFFSETOF(struct sockaddr_un, sun_path) + strlen(sockname);
+ server_addr = (struct sockaddr_un *)apr_palloc(p, server_addr_len + 1);
+ server_addr->sun_family = AF_UNIX;
+ strcpy(server_addr->sun_path, sockname);
+
+ ret = cgid_start(p, main_server, procnew);
+ if (ret != OK ) {
+ return ret;
+ }
+ cgid_pfn_reg_with_ssi = APR_RETRIEVE_OPTIONAL_FN(ap_register_include_handler);
+ cgid_pfn_gtv = APR_RETRIEVE_OPTIONAL_FN(ap_ssi_get_tag_and_value);
+ cgid_pfn_ps = APR_RETRIEVE_OPTIONAL_FN(ap_ssi_parse_string);
+
+ if ((cgid_pfn_reg_with_ssi) && (cgid_pfn_gtv) && (cgid_pfn_ps)) {
+ /* Required by mod_include filter. This is how mod_cgid registers
+ * with mod_include to provide processing of the exec directive.
+ */
+ cgid_pfn_reg_with_ssi("exec", handle_exec);
+ }
+ }
+ return ret;
+}
+
+static void *create_cgid_config(apr_pool_t *p, server_rec *s)
+{
+ cgid_server_conf *c =
+ (cgid_server_conf *) apr_pcalloc(p, sizeof(cgid_server_conf));
+
+ c->logname = NULL;
+ c->logbytes = DEFAULT_LOGBYTES;
+ c->bufbytes = DEFAULT_BUFBYTES;
+ return c;
+}
+
+static void *merge_cgid_config(apr_pool_t *p, void *basev, void *overridesv)
+{
+ cgid_server_conf *base = (cgid_server_conf *) basev, *overrides = (cgid_server_conf *) overridesv;
+
+ return overrides->logname ? overrides : base;
+}
+
+static void *create_cgid_dirconf(apr_pool_t *p, char *dummy)
+{
+ cgid_dirconf *c = (cgid_dirconf *) apr_pcalloc(p, sizeof(cgid_dirconf));
+ return c;
+}
+
+static const char *set_scriptlog(cmd_parms *cmd, void *dummy, const char *arg)
+
+{
+ server_rec *s = cmd->server;
+ cgid_server_conf *conf = ap_get_module_config(s->module_config,
+ &cgid_module);
+
+ conf->logname = ap_server_root_relative(cmd->pool, arg);
+
+ if (!conf->logname) {
+ return apr_pstrcat(cmd->pool, "Invalid ScriptLog path ",
+ arg, NULL);
+ }
+ return NULL;
+}
+
+static const char *set_scriptlog_length(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ server_rec *s = cmd->server;
+ cgid_server_conf *conf = ap_get_module_config(s->module_config,
+ &cgid_module);
+
+ conf->logbytes = atol(arg);
+ return NULL;
+}
+
+static const char *set_scriptlog_buffer(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ server_rec *s = cmd->server;
+ cgid_server_conf *conf = ap_get_module_config(s->module_config,
+ &cgid_module);
+
+ conf->bufbytes = atoi(arg);
+ return NULL;
+}
+
+static const char *set_script_socket(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ /* Make sure the pid is appended to the sockname */
+ sockname = ap_append_pid(cmd->pool, arg, ".");
+ sockname = ap_runtime_dir_relative(cmd->pool, sockname);
+
+ if (!sockname) {
+ return apr_pstrcat(cmd->pool, "Invalid ScriptSock path",
+ arg, NULL);
+ }
+
+ return NULL;
+}
+static const char *set_script_timeout(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ cgid_dirconf *dc = dummy;
+
+ if (ap_timeout_parameter_parse(arg, &dc->timeout, "s") != APR_SUCCESS) {
+ return "CGIDScriptTimeout has wrong format";
+ }
+
+ return NULL;
+}
+static const command_rec cgid_cmds[] =
+{
+ AP_INIT_TAKE1("ScriptLog", set_scriptlog, NULL, RSRC_CONF,
+ "the name of a log for script debugging info"),
+ AP_INIT_TAKE1("ScriptLogLength", set_scriptlog_length, NULL, RSRC_CONF,
+ "the maximum length (in bytes) of the script debug log"),
+ AP_INIT_TAKE1("ScriptLogBuffer", set_scriptlog_buffer, NULL, RSRC_CONF,
+ "the maximum size (in bytes) to record of a POST request"),
+ AP_INIT_TAKE1("ScriptSock", set_script_socket, NULL, RSRC_CONF,
+ "the name of the socket to use for communication with "
+ "the cgi daemon."),
+ AP_INIT_TAKE1("CGIDScriptTimeout", set_script_timeout, NULL, RSRC_CONF | ACCESS_CONF,
+ "The amount of time to wait between successful reads from "
+ "the CGI script, in seconds."),
+
+ {NULL}
+};
+
+static int log_scripterror(request_rec *r, cgid_server_conf * conf, int ret,
+ apr_status_t rv, char *error)
+{
+ apr_file_t *f = NULL;
+ struct stat finfo;
+ char time_str[APR_CTIME_LEN];
+ int log_flags = rv ? APLOG_ERR : APLOG_ERR;
+
+ /* Intentional no APLOGNO */
+ /* Callee provides APLOGNO in error text */
+ ap_log_rerror(APLOG_MARK, log_flags, rv, r,
+ "%s: %s", error, r->filename);
+
+ /* XXX Very expensive mainline case! Open, then getfileinfo! */
+ if (!conf->logname ||
+ ((stat(conf->logname, &finfo) == 0)
+ && (finfo.st_size > conf->logbytes)) ||
+ (apr_file_open(&f, conf->logname,
+ APR_APPEND|APR_WRITE|APR_CREATE, APR_OS_DEFAULT, r->pool) != APR_SUCCESS)) {
+ return ret;
+ }
+
+ /* "%% [Wed Jun 19 10:53:21 1996] GET /cgid-bin/printenv HTTP/1.0" */
+ apr_ctime(time_str, apr_time_now());
+ apr_file_printf(f, "%%%% [%s] %s %s%s%s %s\n", time_str, r->method, r->uri,
+ r->args ? "?" : "", r->args ? r->args : "", r->protocol);
+ /* "%% 500 /usr/local/apache/cgid-bin */
+ apr_file_printf(f, "%%%% %d %s\n", ret, r->filename);
+
+ apr_file_printf(f, "%%error\n%s\n", error);
+
+ apr_file_close(f);
+ return ret;
+}
+
+static int log_script(request_rec *r, cgid_server_conf * conf, int ret,
+ char *dbuf, const char *sbuf, apr_bucket_brigade *bb,
+ apr_file_t *script_err)
+{
+ const apr_array_header_t *hdrs_arr = apr_table_elts(r->headers_in);
+ const apr_table_entry_t *hdrs = (apr_table_entry_t *) hdrs_arr->elts;
+ char argsbuffer[HUGE_STRING_LEN];
+ apr_file_t *f = NULL;
+ apr_bucket *e;
+ const char *buf;
+ apr_size_t len;
+ apr_status_t rv;
+ int first;
+ int i;
+ struct stat finfo;
+ char time_str[APR_CTIME_LEN];
+
+ /* XXX Very expensive mainline case! Open, then getfileinfo! */
+ if (!conf->logname ||
+ ((stat(conf->logname, &finfo) == 0)
+ && (finfo.st_size > conf->logbytes)) ||
+ (apr_file_open(&f, conf->logname,
+ APR_APPEND|APR_WRITE|APR_CREATE, APR_OS_DEFAULT, r->pool) != APR_SUCCESS)) {
+ /* Soak up script output */
+ discard_script_output(bb);
+ if (script_err) {
+ while (apr_file_gets(argsbuffer, HUGE_STRING_LEN,
+ script_err) == APR_SUCCESS)
+ continue;
+ }
+ return ret;
+ }
+
+ /* "%% [Wed Jun 19 10:53:21 1996] GET /cgid-bin/printenv HTTP/1.0" */
+ apr_ctime(time_str, apr_time_now());
+ apr_file_printf(f, "%%%% [%s] %s %s%s%s %s\n", time_str, r->method, r->uri,
+ r->args ? "?" : "", r->args ? r->args : "", r->protocol);
+ /* "%% 500 /usr/local/apache/cgid-bin" */
+ apr_file_printf(f, "%%%% %d %s\n", ret, r->filename);
+
+ apr_file_puts("%request\n", f);
+ for (i = 0; i < hdrs_arr->nelts; ++i) {
+ if (!hdrs[i].key)
+ continue;
+ apr_file_printf(f, "%s: %s\n", hdrs[i].key, hdrs[i].val);
+ }
+ if ((r->method_number == M_POST || r->method_number == M_PUT)
+ && *dbuf) {
+ apr_file_printf(f, "\n%s\n", dbuf);
+ }
+
+ apr_file_puts("%response\n", f);
+ hdrs_arr = apr_table_elts(r->err_headers_out);
+ hdrs = (const apr_table_entry_t *) hdrs_arr->elts;
+
+ for (i = 0; i < hdrs_arr->nelts; ++i) {
+ if (!hdrs[i].key)
+ continue;
+ apr_file_printf(f, "%s: %s\n", hdrs[i].key, hdrs[i].val);
+ }
+
+ if (sbuf && *sbuf)
+ apr_file_printf(f, "%s\n", sbuf);
+
+ first = 1;
+
+ for (e = APR_BRIGADE_FIRST(bb);
+ e != APR_BRIGADE_SENTINEL(bb);
+ e = APR_BUCKET_NEXT(e))
+ {
+ if (APR_BUCKET_IS_EOS(e)) {
+ break;
+ }
+ rv = apr_bucket_read(e, &buf, &len, APR_BLOCK_READ);
+ if (rv != APR_SUCCESS || (len == 0)) {
+ break;
+ }
+ if (first) {
+ apr_file_puts("%stdout\n", f);
+ first = 0;
+ }
+ apr_file_write_full(f, buf, len, NULL);
+ apr_file_puts("\n", f);
+ }
+
+ if (script_err) {
+ if (apr_file_gets(argsbuffer, HUGE_STRING_LEN,
+ script_err) == APR_SUCCESS) {
+ apr_file_puts("%stderr\n", f);
+ apr_file_puts(argsbuffer, f);
+ while (apr_file_gets(argsbuffer, HUGE_STRING_LEN,
+ script_err) == APR_SUCCESS)
+ apr_file_puts(argsbuffer, f);
+ apr_file_puts("\n", f);
+ }
+ }
+
+ if (script_err) {
+ apr_file_close(script_err);
+ }
+
+ apr_file_close(f);
+ return ret;
+}
+
+static int connect_to_daemon(int *sdptr, request_rec *r,
+ cgid_server_conf *conf)
+{
+ int sd;
+ int connect_tries;
+ int connect_errno;
+ apr_interval_time_t sliding_timer;
+
+ connect_tries = 0;
+ sliding_timer = 100000; /* 100 milliseconds */
+ while (1) {
+ connect_errno = 0;
+ ++connect_tries;
+ if ((sd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
+ return log_scripterror(r, conf, HTTP_INTERNAL_SERVER_ERROR, errno,
+ APLOGNO(01255) "unable to create socket to cgi daemon");
+ }
+ if (connect(sd, (struct sockaddr *)server_addr, server_addr_len) < 0) {
+ /* Save errno for later */
+ connect_errno = errno;
+ /* ECONNREFUSED means the listen queue is full; ENOENT means that
+ * the cgid server either hasn't started up yet, or we're pointing
+ * at the wrong socket file */
+ if ((errno == ECONNREFUSED || errno == ENOENT) &&
+ connect_tries < DEFAULT_CONNECT_ATTEMPTS) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, errno, r, APLOGNO(01256)
+ "connect #%d to cgi daemon failed, sleeping before retry",
+ connect_tries);
+ close(sd);
+ apr_sleep(sliding_timer);
+ if (sliding_timer < apr_time_from_sec(2)) {
+ sliding_timer *= 2;
+ }
+ }
+ else {
+ close(sd);
+ return log_scripterror(r, conf, HTTP_SERVICE_UNAVAILABLE, errno, APLOGNO(01257)
+ "unable to connect to cgi daemon after multiple tries");
+ }
+ }
+ else {
+ apr_pool_cleanup_register(r->pool, (void *)((long)sd),
+ close_unix_socket, apr_pool_cleanup_null);
+ break; /* we got connected! */
+ }
+
+ /* If we didn't find the socket but the server was not recently restarted,
+ * chances are there's something wrong with the cgid daemon
+ */
+ if (connect_errno == ENOENT &&
+ apr_time_sec(apr_time_now() - ap_scoreboard_image->global->restart_time) >
+ DEFAULT_CONNECT_STARTUP_DELAY) {
+ return log_scripterror(r, conf, HTTP_SERVICE_UNAVAILABLE, connect_errno,
+ apr_pstrcat(r->pool, APLOGNO(02833) "ScriptSock ", sockname, " does not exist", NULL));
+ }
+
+ /* gotta try again, but make sure the cgid daemon is still around */
+ if (connect_errno != ENOENT && kill(daemon_pid, 0) != 0) {
+ return log_scripterror(r, conf, HTTP_SERVICE_UNAVAILABLE, connect_errno, APLOGNO(01258)
+ "cgid daemon is gone; is Apache terminating?");
+ }
+ }
+ *sdptr = sd;
+ return OK;
+}
+
+static void discard_script_output(apr_bucket_brigade *bb)
+{
+ apr_bucket *e;
+ const char *buf;
+ apr_size_t len;
+
+ for (e = APR_BRIGADE_FIRST(bb);
+ e != APR_BRIGADE_SENTINEL(bb) && !APR_BUCKET_IS_EOS(e);
+ e = APR_BRIGADE_FIRST(bb))
+ {
+ if (apr_bucket_read(e, &buf, &len, APR_BLOCK_READ)) {
+ break;
+ }
+ apr_bucket_delete(e);
+ }
+}
+
+/****************************************************************
+ *
+ * Actual cgid handling...
+ */
+
+struct cleanup_script_info {
+ request_rec *r;
+ cgid_server_conf *conf;
+ pid_t pid;
+};
+
+static apr_status_t dead_yet(pid_t pid, apr_interval_time_t max_wait)
+{
+ apr_interval_time_t interval = 10000; /* 10 ms */
+ apr_interval_time_t total = 0;
+
+ do {
+#ifdef _AIX
+ /* On AIX, for processes like mod_cgid's script children where
+ * SIGCHLD is ignored, kill(pid,0) returns success for up to
+ * one second after the script child exits, based on when a
+ * daemon runs to clean up unnecessary process table entries.
+ * getpgid() can report the proper info (-1/ESRCH) immediately.
+ */
+ if (getpgid(pid) < 0) {
+#else
+ if (kill(pid, 0) < 0) {
+#endif
+ return APR_SUCCESS;
+ }
+ apr_sleep(interval);
+ total = total + interval;
+ if (interval < 500000) {
+ interval *= 2;
+ }
+ } while (total < max_wait);
+ return APR_EGENERAL;
+}
+
+static apr_status_t cleanup_nonchild_process(request_rec *r, pid_t pid)
+{
+ kill(pid, SIGTERM); /* in case it isn't dead yet */
+ if (dead_yet(pid, apr_time_from_sec(3)) == APR_SUCCESS) {
+ return APR_SUCCESS;
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01259)
+ "CGI process %" APR_PID_T_FMT " didn't exit, sending SIGKILL",
+ pid);
+ kill(pid, SIGKILL);
+ if (dead_yet(pid, apr_time_from_sec(3)) == APR_SUCCESS) {
+ return APR_SUCCESS;
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01260)
+ "CGI process %" APR_PID_T_FMT " didn't exit, sending SIGKILL again",
+ pid);
+ kill(pid, SIGKILL);
+
+ return APR_EGENERAL;
+}
+
+static apr_status_t get_cgi_pid(request_rec *r, cgid_server_conf *conf, pid_t *pid) {
+ cgid_req_t req = {0};
+ apr_status_t stat;
+ int rc, sd;
+
+ rc = connect_to_daemon(&sd, r, conf);
+ if (rc != OK) {
+ return APR_EGENERAL;
+ }
+
+ req.req_type = GETPID_REQ;
+ req.ppid = parent_pid;
+ req.conn_id = r->connection->id;
+
+ stat = sock_write(sd, &req, sizeof(req));
+ if (stat != APR_SUCCESS) {
+ return stat;
+ }
+
+ /* wait for pid of script */
+ stat = sock_read(sd, pid, sizeof(*pid));
+ if (stat != APR_SUCCESS) {
+ return stat;
+ }
+
+ if (pid == 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01261)
+ "daemon couldn't find CGI process for connection %lu",
+ r->connection->id);
+ return APR_EGENERAL;
+ }
+
+ return APR_SUCCESS;
+}
+
+
+static apr_status_t cleanup_script(void *vptr)
+{
+ struct cleanup_script_info *info = vptr;
+ return cleanup_nonchild_process(info->r, info->pid);
+}
+
+static int cgid_handler(request_rec *r)
+{
+ int retval, nph, dbpos;
+ char *argv0, *dbuf;
+ apr_bucket_brigade *bb;
+ apr_bucket *b;
+ cgid_server_conf *conf;
+ int is_included;
+ int seen_eos, child_stopped_reading;
+ int sd;
+ char **env;
+ apr_file_t *tempsock;
+ struct cleanup_script_info *info;
+ apr_status_t rv;
+ cgid_dirconf *dc;
+
+ if (strcmp(r->handler, CGI_MAGIC_TYPE) && strcmp(r->handler, "cgi-script")) {
+ return DECLINED;
+ }
+
+ conf = ap_get_module_config(r->server->module_config, &cgid_module);
+ dc = ap_get_module_config(r->per_dir_config, &cgid_module);
+
+
+ is_included = !strcmp(r->protocol, "INCLUDED");
+
+ if ((argv0 = strrchr(r->filename, '/')) != NULL) {
+ argv0++;
+ }
+ else {
+ argv0 = r->filename;
+ }
+
+ nph = !(strncmp(argv0, "nph-", 4));
+
+ argv0 = r->filename;
+
+ if (!(ap_allow_options(r) & OPT_EXECCGI) && !is_scriptaliased(r)) {
+ return log_scripterror(r, conf, HTTP_FORBIDDEN, 0, APLOGNO(01262)
+ "Options ExecCGI is off in this directory");
+ }
+
+ if (nph && is_included) {
+ return log_scripterror(r, conf, HTTP_FORBIDDEN, 0, APLOGNO(01263)
+ "attempt to include NPH CGI script");
+ }
+
+#if defined(OS2) || defined(WIN32)
+#error mod_cgid does not work on this platform. If you teach it to, look
+#error at mod_cgi.c for required code in this path.
+#else
+ if (r->finfo.filetype == APR_NOFILE) {
+ return log_scripterror(r, conf, HTTP_NOT_FOUND, 0, APLOGNO(01264)
+ "script not found or unable to stat");
+ }
+#endif
+ if (r->finfo.filetype == APR_DIR) {
+ return log_scripterror(r, conf, HTTP_FORBIDDEN, 0, APLOGNO(01265)
+ "attempt to invoke directory as script");
+ }
+
+ if ((r->used_path_info == AP_REQ_REJECT_PATH_INFO) &&
+ r->path_info && *r->path_info)
+ {
+ /* default to accept */
+ return log_scripterror(r, conf, HTTP_NOT_FOUND, 0, APLOGNO(01266)
+ "AcceptPathInfo off disallows user's path");
+ }
+ /*
+ if (!ap_suexec_enabled) {
+ if (!ap_can_exec(&r->finfo))
+ return log_scripterror(r, conf, HTTP_FORBIDDEN, 0, APLOGNO(01267)
+ "file permissions deny server execution");
+ }
+ */
+
+ /*
+ * httpd core function used to add common environment variables like
+ * DOCUMENT_ROOT.
+ */
+ ap_add_common_vars(r);
+ ap_add_cgi_vars(r);
+ env = ap_create_environment(r->pool, r->subprocess_env);
+
+ if ((retval = connect_to_daemon(&sd, r, conf)) != OK) {
+ return retval;
+ }
+
+ rv = send_req(sd, r, argv0, env, CGI_REQ);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01268)
+ "write to cgi daemon process");
+ }
+
+ info = apr_palloc(r->pool, sizeof(struct cleanup_script_info));
+ info->conf = conf;
+ info->r = r;
+ rv = get_cgi_pid(r, conf, &(info->pid));
+
+ if (APR_SUCCESS == rv){
+ apr_pool_cleanup_register(r->pool, info,
+ cleanup_script,
+ apr_pool_cleanup_null);
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, "error determining cgi PID");
+ }
+
+ /* We are putting the socket discriptor into an apr_file_t so that we can
+ * use a pipe bucket to send the data to the client. APR will create
+ * a cleanup for the apr_file_t which will close the socket, so we'll
+ * get rid of the cleanup we registered when we created the socket.
+ */
+
+ apr_os_pipe_put_ex(&tempsock, &sd, 1, r->pool);
+ if (dc->timeout > 0) {
+ apr_file_pipe_timeout_set(tempsock, dc->timeout);
+ }
+ else {
+ apr_file_pipe_timeout_set(tempsock, r->server->timeout);
+ }
+ apr_pool_cleanup_kill(r->pool, (void *)((long)sd), close_unix_socket);
+
+ /* Transfer any put/post args, CERN style...
+ * Note that we already ignore SIGPIPE in the core server.
+ */
+ bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+ seen_eos = 0;
+ child_stopped_reading = 0;
+ dbuf = NULL;
+ dbpos = 0;
+ if (conf->logname) {
+ dbuf = apr_palloc(r->pool, conf->bufbytes + 1);
+ }
+ do {
+ apr_bucket *bucket;
+
+ rv = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES,
+ APR_BLOCK_READ, HUGE_STRING_LEN);
+
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01270)
+ "Error reading request entity data");
+ return ap_map_http_request_error(rv, HTTP_BAD_REQUEST);
+ }
+
+ for (bucket = APR_BRIGADE_FIRST(bb);
+ bucket != APR_BRIGADE_SENTINEL(bb);
+ bucket = APR_BUCKET_NEXT(bucket))
+ {
+ const char *data;
+ apr_size_t len;
+
+ if (APR_BUCKET_IS_EOS(bucket)) {
+ seen_eos = 1;
+ break;
+ }
+
+ /* We can't do much with this. */
+ if (APR_BUCKET_IS_FLUSH(bucket)) {
+ continue;
+ }
+
+ /* If the child stopped, we still must read to EOS. */
+ if (child_stopped_reading) {
+ continue;
+ }
+
+ /* read */
+ apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ);
+
+ if (conf->logname && dbpos < conf->bufbytes) {
+ int cursize;
+
+ if ((dbpos + len) > conf->bufbytes) {
+ cursize = conf->bufbytes - dbpos;
+ }
+ else {
+ cursize = len;
+ }
+ memcpy(dbuf + dbpos, data, cursize);
+ dbpos += cursize;
+ }
+
+ /* Keep writing data to the child until done or too much time
+ * elapses with no progress or an error occurs.
+ */
+ rv = apr_file_write_full(tempsock, data, len, NULL);
+
+ if (rv != APR_SUCCESS) {
+ /* silly script stopped reading, soak up remaining message */
+ child_stopped_reading = 1;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02651)
+ "Error writing request body to script %s",
+ r->filename);
+
+ }
+ }
+ apr_brigade_cleanup(bb);
+ }
+ while (!seen_eos);
+
+ if (conf->logname) {
+ dbuf[dbpos] = '\0';
+ }
+
+ /* we're done writing, or maybe we didn't write at all;
+ * force EOF on child's stdin so that the cgi detects end (or
+ * absence) of data
+ */
+ shutdown(sd, 1);
+
+ /* Handle script return... */
+ if (!nph) {
+ conn_rec *c = r->connection;
+ const char *location;
+ char sbuf[MAX_STRING_LEN];
+ int ret;
+
+ bb = apr_brigade_create(r->pool, c->bucket_alloc);
+ b = apr_bucket_pipe_create(tempsock, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ b = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+
+ if ((ret = ap_scan_script_header_err_brigade_ex(r, bb, sbuf,
+ APLOG_MODULE_INDEX)))
+ {
+ ret = log_script(r, conf, ret, dbuf, sbuf, bb, NULL);
+
+ /*
+ * ret could be HTTP_NOT_MODIFIED in the case that the CGI script
+ * does not set an explicit status and ap_meets_conditions, which
+ * is called by ap_scan_script_header_err_brigade, detects that
+ * the conditions of the requests are met and the response is
+ * not modified.
+ * In this case set r->status and return OK in order to prevent
+ * running through the error processing stack as this would
+ * break with mod_cache, if the conditions had been set by
+ * mod_cache itself to validate a stale entity.
+ * BTW: We circumvent the error processing stack anyway if the
+ * CGI script set an explicit status code (whatever it is) and
+ * the only possible values for ret here are:
+ *
+ * HTTP_NOT_MODIFIED (set by ap_meets_conditions)
+ * HTTP_PRECONDITION_FAILED (set by ap_meets_conditions)
+ * HTTP_INTERNAL_SERVER_ERROR (if something went wrong during the
+ * processing of the response of the CGI script, e.g broken headers
+ * or a crashed CGI process).
+ */
+ if (ret == HTTP_NOT_MODIFIED) {
+ r->status = ret;
+ return OK;
+ }
+
+ return ret;
+ }
+
+ location = apr_table_get(r->headers_out, "Location");
+
+ if (location && location[0] == '/' && r->status == 200) {
+
+ /* Soak up all the script output */
+ discard_script_output(bb);
+ apr_brigade_destroy(bb);
+ /* This redirect needs to be a GET no matter what the original
+ * method was.
+ */
+ r->method = "GET";
+ r->method_number = M_GET;
+
+ /* We already read the message body (if any), so don't allow
+ * the redirected request to think it has one. We can ignore
+ * Transfer-Encoding, since we used REQUEST_CHUNKED_ERROR.
+ */
+ apr_table_unset(r->headers_in, "Content-Length");
+
+ ap_internal_redirect_handler(location, r);
+ return OK;
+ }
+ else if (location && r->status == 200) {
+ /* XXX: Note that if a script wants to produce its own Redirect
+ * body, it now has to explicitly *say* "Status: 302"
+ */
+ discard_script_output(bb);
+ apr_brigade_destroy(bb);
+ return HTTP_MOVED_TEMPORARILY;
+ }
+
+ rv = ap_pass_brigade(r->output_filters, bb);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r,
+ "Failed to flush CGI output to client");
+ }
+ }
+
+ if (nph) {
+ conn_rec *c = r->connection;
+ struct ap_filter_t *cur;
+
+ /* get rid of all filters up through protocol... since we
+ * haven't parsed off the headers, there is no way they can
+ * work
+ */
+
+ cur = r->proto_output_filters;
+ while (cur && cur->frec->ftype < AP_FTYPE_CONNECTION) {
+ cur = cur->next;
+ }
+ r->output_filters = r->proto_output_filters = cur;
+
+ bb = apr_brigade_create(r->pool, c->bucket_alloc);
+ b = apr_bucket_pipe_create(tempsock, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ b = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ ap_pass_brigade(r->output_filters, bb);
+ }
+
+ return OK; /* NOT r->status, even if it has changed. */
+}
+
+
+
+
+/*============================================================================
+ *============================================================================
+ * This is the beginning of the cgi filter code moved from mod_include. This
+ * is the code required to handle the "exec" SSI directive.
+ *============================================================================
+ *============================================================================*/
+static apr_status_t include_cgi(include_ctx_t *ctx, ap_filter_t *f,
+ apr_bucket_brigade *bb, char *s)
+{
+ request_rec *r = f->r;
+ request_rec *rr = ap_sub_req_lookup_uri(s, r, f->next);
+ int rr_status;
+
+ if (rr->status != HTTP_OK) {
+ ap_destroy_sub_req(rr);
+ return APR_EGENERAL;
+ }
+
+ /* No hardwired path info or query allowed */
+ if ((rr->path_info && rr->path_info[0]) || rr->args) {
+ ap_destroy_sub_req(rr);
+ return APR_EGENERAL;
+ }
+ if (rr->finfo.filetype != APR_REG) {
+ ap_destroy_sub_req(rr);
+ return APR_EGENERAL;
+ }
+
+ /* Script gets parameters of the *document*, for back compatibility */
+ rr->path_info = r->path_info; /* hard to get right; see mod_cgi.c */
+ rr->args = r->args;
+
+ /* Force sub_req to be treated as a CGI request, even if ordinary
+ * typing rules would have called it something else.
+ */
+ ap_set_content_type(rr, CGI_MAGIC_TYPE);
+
+ /* Run it. */
+ rr_status = ap_run_sub_req(rr);
+ if (ap_is_HTTP_REDIRECT(rr_status)) {
+ const char *location = apr_table_get(rr->headers_out, "Location");
+
+ if (location) {
+ char *buffer;
+
+ location = ap_escape_html(rr->pool, location);
+ buffer = apr_pstrcat(ctx->pool, "<a href=\"", location, "\">",
+ location, "</a>", NULL);
+
+ APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(buffer,
+ strlen(buffer), ctx->pool,
+ f->c->bucket_alloc));
+ }
+ }
+
+ ap_destroy_sub_req(rr);
+
+ return APR_SUCCESS;
+}
+
+/* This is the special environment used for running the "exec cmd="
+ * variety of SSI directives.
+ */
+static void add_ssi_vars(request_rec *r)
+{
+ apr_table_t *e = r->subprocess_env;
+
+ if (r->path_info && r->path_info[0] != '\0') {
+ request_rec *pa_req;
+
+ apr_table_setn(e, "PATH_INFO", ap_escape_shell_cmd(r->pool, r->path_info));
+
+ pa_req = ap_sub_req_lookup_uri(ap_escape_uri(r->pool, r->path_info), r, NULL);
+ if (pa_req->filename) {
+ apr_table_setn(e, "PATH_TRANSLATED",
+ apr_pstrcat(r->pool, pa_req->filename, pa_req->path_info, NULL));
+ }
+ ap_destroy_sub_req(pa_req);
+ }
+
+ if (r->args) {
+ char *arg_copy = apr_pstrdup(r->pool, r->args);
+
+ apr_table_setn(e, "QUERY_STRING", r->args);
+ ap_unescape_url(arg_copy);
+ apr_table_setn(e, "QUERY_STRING_UNESCAPED", ap_escape_shell_cmd(r->pool, arg_copy));
+ }
+}
+
+static int include_cmd(include_ctx_t *ctx, ap_filter_t *f,
+ apr_bucket_brigade *bb, char *command)
+{
+ char **env;
+ int sd;
+ int retval;
+ apr_file_t *tempsock = NULL;
+ request_rec *r = f->r;
+ cgid_server_conf *conf = ap_get_module_config(r->server->module_config,
+ &cgid_module);
+ cgid_dirconf *dc = ap_get_module_config(r->per_dir_config, &cgid_module);
+
+ struct cleanup_script_info *info;
+ apr_status_t rv;
+
+ add_ssi_vars(r);
+ env = ap_create_environment(r->pool, r->subprocess_env);
+
+ if ((retval = connect_to_daemon(&sd, r, conf)) != OK) {
+ return retval;
+ }
+
+ send_req(sd, r, command, env, SSI_REQ);
+
+ info = apr_palloc(r->pool, sizeof(struct cleanup_script_info));
+ info->conf = conf;
+ info->r = r;
+ rv = get_cgi_pid(r, conf, &(info->pid));
+ if (APR_SUCCESS == rv) {
+ /* for this type of request, the script is invoked through an
+ * intermediate shell process... cleanup_script is only able
+ * to knock out the shell process, not the actual script
+ */
+ apr_pool_cleanup_register(r->pool, info,
+ cleanup_script,
+ apr_pool_cleanup_null);
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, "error determining cgi PID (for SSI)");
+ }
+
+ apr_pool_cleanup_register(r->pool, info,
+ cleanup_script,
+ apr_pool_cleanup_null);
+
+ /* We are putting the socket discriptor into an apr_file_t so that we can
+ * use a pipe bucket to send the data to the client. APR will create
+ * a cleanup for the apr_file_t which will close the socket, so we'll
+ * get rid of the cleanup we registered when we created the socket.
+ */
+ apr_os_pipe_put_ex(&tempsock, &sd, 1, r->pool);
+ if (dc->timeout > 0) {
+ apr_file_pipe_timeout_set(tempsock, dc->timeout);
+ }
+ else {
+ apr_file_pipe_timeout_set(tempsock, r->server->timeout);
+ }
+
+ apr_pool_cleanup_kill(r->pool, (void *)((long)sd), close_unix_socket);
+
+ APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pipe_create(tempsock,
+ f->c->bucket_alloc));
+ ctx->flush_now = 1;
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t handle_exec(include_ctx_t *ctx, ap_filter_t *f,
+ apr_bucket_brigade *bb)
+{
+ char *tag = NULL;
+ char *tag_val = NULL;
+ request_rec *r = f->r;
+ char *file = r->filename;
+ char parsed_string[MAX_STRING_LEN];
+
+ if (!ctx->argc) {
+ ap_log_rerror(APLOG_MARK,
+ (ctx->flags & SSI_FLAG_PRINTING)
+ ? APLOG_ERR : APLOG_WARNING,
+ 0, r, APLOGNO(03196)
+ "missing argument for exec element in %s", r->filename);
+ }
+
+ if (!(ctx->flags & SSI_FLAG_PRINTING)) {
+ return APR_SUCCESS;
+ }
+
+ if (!ctx->argc) {
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ return APR_SUCCESS;
+ }
+
+ if (ctx->flags & SSI_FLAG_NO_EXEC) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01271) "exec used but not allowed "
+ "in %s", r->filename);
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ return APR_SUCCESS;
+ }
+
+ while (1) {
+ cgid_pfn_gtv(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
+ if (!tag || !tag_val) {
+ break;
+ }
+
+ if (!strcmp(tag, "cmd")) {
+ apr_status_t rv;
+
+ cgid_pfn_ps(ctx, tag_val, parsed_string, sizeof(parsed_string),
+ SSI_EXPAND_LEAVE_NAME);
+
+ rv = include_cmd(ctx, f, bb, parsed_string);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01272)
+ "execution failure for parameter \"%s\" "
+ "to tag exec in file %s", tag, r->filename);
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ break;
+ }
+ }
+ else if (!strcmp(tag, "cgi")) {
+ apr_status_t rv;
+
+ cgid_pfn_ps(ctx, tag_val, parsed_string, sizeof(parsed_string),
+ SSI_EXPAND_DROP_NAME);
+
+ rv = include_cgi(ctx, f, bb, parsed_string);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01273) "invalid CGI ref "
+ "\"%s\" in %s", tag_val, file);
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ break;
+ }
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01274) "unknown parameter "
+ "\"%s\" to tag exec in %s", tag, file);
+ SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+ break;
+ }
+ }
+
+ return APR_SUCCESS;
+}
+/*============================================================================
+ *============================================================================
+ * This is the end of the cgi filter code moved from mod_include.
+ *============================================================================
+ *============================================================================*/
+
+
+static void register_hook(apr_pool_t *p)
+{
+ static const char * const aszPre[] = { "mod_include.c", NULL };
+
+ ap_hook_pre_config(cgid_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_post_config(cgid_init, aszPre, NULL, APR_HOOK_MIDDLE);
+ ap_hook_handler(cgid_handler, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(cgid) = {
+ STANDARD20_MODULE_STUFF,
+ create_cgid_dirconf, /* dir config creater */
+ NULL, /* dir merger --- default is to override */
+ create_cgid_config, /* server config */
+ merge_cgid_config, /* merge server config */
+ cgid_cmds, /* command table */
+ register_hook /* register_handlers */
+};
+
diff --git a/modules/generators/mod_cgid.exp b/modules/generators/mod_cgid.exp
new file mode 100644
index 0000000..5f10d48
--- /dev/null
+++ b/modules/generators/mod_cgid.exp
@@ -0,0 +1 @@
+cgid_module
diff --git a/modules/generators/mod_info.c b/modules/generators/mod_info.c
new file mode 100644
index 0000000..1662242
--- /dev/null
+++ b/modules/generators/mod_info.c
@@ -0,0 +1,1018 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Info Module. Display configuration information for the server and
+ * all included modules.
+ *
+ * <Location /server-info>
+ * SetHandler server-info
+ * </Location>
+ *
+ * GET /server-info - Returns full configuration page for server and all modules
+ * GET /server-info?server - Returns server configuration only
+ * GET /server-info?module_name - Returns configuration for a single module
+ * GET /server-info?list - Returns quick list of included modules
+ * GET /server-info?config - Returns full configuration
+ * GET /server-info?hooks - Returns a listing of the modules active for each hook
+ *
+ * Original Author:
+ * Rasmus Lerdorf <rasmus vex.net>, May 1996
+ *
+ * Modified By:
+ * Lou Langholtz <ldl usi.utah.edu>, July 1997
+ *
+ * Apache 2.0 Port:
+ * Ryan Morgan <rmorgan covalent.net>, August 2000
+ *
+ */
+
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+#include "apr_version.h"
+#if APR_MAJOR_VERSION < 2
+#include "apu_version.h"
+#endif
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_main.h"
+#include "http_protocol.h"
+#include "http_connection.h"
+#include "http_request.h"
+#include "util_script.h"
+#include "ap_mpm.h"
+#include "mpm_common.h"
+#include "ap_provider.h"
+#include <stdio.h>
+#include <stdlib.h>
+
+typedef struct
+{
+ const char *name; /* matching module name */
+ const char *info; /* additional info */
+} info_entry;
+
+typedef struct
+{
+ apr_array_header_t *more_info;
+} info_svr_conf;
+
+module AP_MODULE_DECLARE_DATA info_module;
+
+/* current file name when doing -DDUMP_CONFIG */
+static const char *dump_config_fn_info;
+/* file handle when doing -DDUMP_CONFIG */
+static apr_file_t *out = NULL;
+
+static void *create_info_config(apr_pool_t * p, server_rec * s)
+{
+ info_svr_conf *conf =
+ (info_svr_conf *) apr_pcalloc(p, sizeof(info_svr_conf));
+
+ conf->more_info = apr_array_make(p, 20, sizeof(info_entry));
+ return conf;
+}
+
+static void *merge_info_config(apr_pool_t * p, void *basev, void *overridesv)
+{
+ info_svr_conf *new =
+ (info_svr_conf *) apr_pcalloc(p, sizeof(info_svr_conf));
+ info_svr_conf *base = (info_svr_conf *) basev;
+ info_svr_conf *overrides = (info_svr_conf *) overridesv;
+
+ new->more_info =
+ apr_array_append(p, overrides->more_info, base->more_info);
+ return new;
+}
+
+static void put_int_flush_right(request_rec * r, int i, int field)
+{
+ if (field > 1 || i > 9)
+ put_int_flush_right(r, i / 10, field - 1);
+ if (i) {
+ if (r)
+ ap_rputc('0' + i % 10, r);
+ else
+ apr_file_putc((char)('0' + i % 10), out);
+ }
+ else {
+ if (r)
+ ap_rputs("&nbsp;", r);
+ else
+ apr_file_printf(out, " ");
+ }
+}
+
+static void set_fn_info(request_rec *r, const char *name)
+{
+ if (r)
+ ap_set_module_config(r->request_config, &info_module, (void *)name);
+ else
+ dump_config_fn_info = name;
+}
+
+static const char *get_fn_info(request_rec *r)
+{
+ if (r)
+ return ap_get_module_config(r->request_config, &info_module);
+ else
+ return dump_config_fn_info;
+}
+
+
+static void mod_info_indent(request_rec * r, int nest,
+ const char *thisfn, int linenum)
+{
+ int i;
+ const char *prevfn = get_fn_info(r);
+ if (thisfn == NULL)
+ thisfn = "*UNKNOWN*";
+ if (prevfn == NULL || 0 != strcmp(prevfn, thisfn)) {
+ if (r) {
+ thisfn = ap_escape_html(r->pool, thisfn);
+ ap_rprintf(r, "<dd><tt><strong>In file: %s</strong></tt></dd>\n",
+ thisfn);
+ }
+ else {
+ apr_file_printf(out, "# In file: %s\n", thisfn);
+ }
+ set_fn_info(r, thisfn);
+ }
+
+ if (r) {
+ ap_rputs("<dd><tt>", r);
+ put_int_flush_right(r, linenum > 0 ? linenum : 0, 4);
+ ap_rputs(":&nbsp;", r);
+ }
+ else if (linenum > 0) {
+ for (i = 1; i <= nest; ++i)
+ apr_file_printf(out, " ");
+ apr_file_putc('#', out);
+ put_int_flush_right(r, linenum, 4);
+ apr_file_printf(out, ":\n");
+ }
+
+ for (i = 1; i <= nest; ++i) {
+ if (r)
+ ap_rputs("&nbsp;&nbsp;", r);
+ else
+ apr_file_printf(out, " ");
+ }
+}
+
+static void mod_info_show_cmd(request_rec * r, const ap_directive_t * dir,
+ int nest)
+{
+ mod_info_indent(r, nest, dir->filename, dir->line_num);
+ if (r)
+ ap_rprintf(r, "%s <i>%s</i></tt></dd>\n",
+ ap_escape_html(r->pool, dir->directive),
+ ap_escape_html(r->pool, dir->args));
+ else
+ apr_file_printf(out, "%s %s\n", dir->directive, dir->args);
+}
+
+static void mod_info_show_open(request_rec * r, const ap_directive_t * dir,
+ int nest)
+{
+ mod_info_indent(r, nest, dir->filename, dir->line_num);
+ if (r)
+ ap_rprintf(r, "%s %s</tt></dd>\n",
+ ap_escape_html(r->pool, dir->directive),
+ ap_escape_html(r->pool, dir->args));
+ else
+ apr_file_printf(out, "%s %s\n", dir->directive, dir->args);
+}
+
+static void mod_info_show_close(request_rec * r, const ap_directive_t * dir,
+ int nest)
+{
+ const char *dirname = dir->directive;
+ mod_info_indent(r, nest, dir->filename, 0);
+ if (*dirname == '<') {
+ if (r)
+ ap_rprintf(r, "&lt;/%s&gt;</tt></dd>",
+ ap_escape_html(r->pool, dirname + 1));
+ else
+ apr_file_printf(out, "</%s>\n", dirname + 1);
+ }
+ else {
+ if (r)
+ ap_rprintf(r, "/%s</tt></dd>", ap_escape_html(r->pool, dirname));
+ else
+ apr_file_printf(out, "/%s\n", dirname);
+ }
+}
+
+static int mod_info_has_cmd(const command_rec * cmds, ap_directive_t * dir)
+{
+ const command_rec *cmd;
+ if (cmds == NULL)
+ return 1;
+ for (cmd = cmds; cmd->name; ++cmd) {
+ if (ap_cstr_casecmp(cmd->name, dir->directive) == 0)
+ return 1;
+ }
+ return 0;
+}
+
+static void mod_info_show_parents(request_rec * r, ap_directive_t * node,
+ int from, int to)
+{
+ if (from < to)
+ mod_info_show_parents(r, node->parent, from, to - 1);
+ mod_info_show_open(r, node, to);
+}
+
+static int mod_info_module_cmds(request_rec * r, const command_rec * cmds,
+ ap_directive_t * node, int from, int level)
+{
+ int shown = from;
+ ap_directive_t *dir;
+ if (level == 0)
+ set_fn_info(r, NULL);
+ for (dir = node; dir; dir = dir->next) {
+ if (dir->first_child != NULL) {
+ if (level < mod_info_module_cmds(r, cmds, dir->first_child,
+ shown, level + 1)) {
+ shown = level;
+ mod_info_show_close(r, dir, level);
+ }
+ }
+ else if (mod_info_has_cmd(cmds, dir)) {
+ if (shown < level) {
+ mod_info_show_parents(r, dir->parent, shown, level - 1);
+ shown = level;
+ }
+ mod_info_show_cmd(r, dir, level);
+ }
+ }
+ return shown;
+}
+
+typedef struct
+{ /*XXX: should get something from apr_hooks.h instead */
+ void (*pFunc) (void); /* just to get the right size */
+ const char *szName;
+ const char *const *aszPredecessors;
+ const char *const *aszSuccessors;
+ int nOrder;
+} hook_struct_t;
+
+/*
+ * hook_get_t is a pointer to a function that takes void as an argument and
+ * returns a pointer to an apr_array_header_t. The nasty WIN32 ifdef
+ * is required to account for the fact that the ap_hook* calls all use
+ * STDCALL calling convention.
+ */
+typedef apr_array_header_t *(
+#ifdef WIN32
+ __stdcall
+#endif
+ * hook_get_t) (void);
+
+typedef struct
+{
+ const char *name;
+ hook_get_t get;
+} hook_lookup_t;
+
+static const hook_lookup_t startup_hooks[] = {
+ {"Pre-Config", ap_hook_get_pre_config},
+ {"Check Configuration", ap_hook_get_check_config},
+ {"Test Configuration", ap_hook_get_test_config},
+ {"Post Configuration", ap_hook_get_post_config},
+ {"Open Logs", ap_hook_get_open_logs},
+ {"Pre-MPM", ap_hook_get_pre_mpm},
+ {"MPM", ap_hook_get_mpm},
+ {"Drop Privileges", ap_hook_get_drop_privileges},
+ {"Retrieve Optional Functions", ap_hook_get_optional_fn_retrieve},
+ {"Child Init", ap_hook_get_child_init},
+ {NULL},
+};
+
+static const hook_lookup_t request_hooks[] = {
+ {"Pre-Connection", ap_hook_get_pre_connection},
+ {"Create Connection", ap_hook_get_create_connection},
+ {"Process Connection", ap_hook_get_process_connection},
+ {"Create Request", ap_hook_get_create_request},
+ {"Pre-Read Request", ap_hook_get_pre_read_request},
+ {"Post-Read Request", ap_hook_get_post_read_request},
+ {"Header Parse", ap_hook_get_header_parser},
+ {"HTTP Scheme", ap_hook_get_http_scheme},
+ {"Default Port", ap_hook_get_default_port},
+ {"Quick Handler", ap_hook_get_quick_handler},
+ {"Pre-Translate Name", ap_hook_get_pre_translate_name},
+ {"Translate Name", ap_hook_get_translate_name},
+ {"Map to Storage", ap_hook_get_map_to_storage},
+ {"Check Access", ap_hook_get_access_checker_ex},
+ {"Check Access (legacy)", ap_hook_get_access_checker},
+ {"Verify User ID", ap_hook_get_check_user_id},
+ {"Note Authentication Failure", ap_hook_get_note_auth_failure},
+ {"Verify User Access", ap_hook_get_auth_checker},
+ {"Check Type", ap_hook_get_type_checker},
+ {"Fixups", ap_hook_get_fixups},
+ {"Insert Filters", ap_hook_get_insert_filter},
+ {"Content Handlers", ap_hook_get_handler},
+ {"Transaction Logging", ap_hook_get_log_transaction},
+ {"Insert Errors", ap_hook_get_insert_error_filter},
+ {"Generate Log ID", ap_hook_get_generate_log_id},
+ {NULL},
+};
+
+static const hook_lookup_t other_hooks[] = {
+ {"Monitor", ap_hook_get_monitor},
+ {"Child Status", ap_hook_get_child_status},
+ {"End Generation", ap_hook_get_end_generation},
+ {"Error Logging", ap_hook_get_error_log},
+ {"Query MPM Attributes", ap_hook_get_mpm_query},
+ {"Query MPM Name", ap_hook_get_mpm_get_name},
+ {"Register Timed Callback", ap_hook_get_mpm_register_timed_callback},
+ {"Extend Expression Parser", ap_hook_get_expr_lookup},
+ {"Set Management Items", ap_hook_get_get_mgmt_items},
+#if AP_ENABLE_EXCEPTION_HOOK
+ {"Handle Fatal Exceptions", ap_hook_get_fatal_exception},
+#endif
+ {NULL},
+};
+
+static int module_find_hook(module * modp, hook_get_t hook_get)
+{
+ int i;
+ apr_array_header_t *hooks = hook_get();
+ hook_struct_t *elts;
+
+ if (!hooks) {
+ return 0;
+ }
+
+ elts = (hook_struct_t *) hooks->elts;
+
+ for (i = 0; i < hooks->nelts; i++) {
+ if (strcmp(elts[i].szName, modp->name) == 0) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static void module_participate(request_rec * r,
+ module * modp,
+ const hook_lookup_t *lookup, int *comma)
+{
+ if (module_find_hook(modp, lookup->get)) {
+ if (*comma) {
+ ap_rputs(", ", r);
+ }
+ ap_rvputs(r, "<tt>", lookup->name, "</tt>", NULL);
+ *comma = 1;
+ }
+}
+
+static void module_request_hook_participate(request_rec * r, module * modp)
+{
+ int i, comma = 0;
+
+ ap_rputs("<dt><strong>Request Phase Participation:</strong>\n", r);
+
+ for (i = 0; request_hooks[i].name; i++) {
+ module_participate(r, modp, &request_hooks[i], &comma);
+ }
+
+ if (!comma) {
+ ap_rputs("<tt> <em>none</em></tt>", r);
+ }
+ ap_rputs("</dt>\n", r);
+}
+
+static const char *find_more_info(server_rec * s, const char *module_name)
+{
+ int i;
+ info_svr_conf *conf =
+ (info_svr_conf *) ap_get_module_config(s->module_config,
+ &info_module);
+ info_entry *entry = (info_entry *) conf->more_info->elts;
+
+ if (!module_name) {
+ return 0;
+ }
+ for (i = 0; i < conf->more_info->nelts; i++) {
+ if (!strcmp(module_name, entry->name)) {
+ return entry->info;
+ }
+ entry++;
+ }
+ return 0;
+}
+
+static int show_server_settings(request_rec * r)
+{
+ server_rec *serv = r->server;
+ int max_daemons, forked, threaded;
+
+ ap_rputs("<h2><a name=\"server\">Server Settings</a></h2>", r);
+ ap_rprintf(r,
+ "<dl><dt><strong>Server Version:</strong> "
+ "<font size=\"+1\"><tt>%s</tt></font></dt>\n",
+ ap_get_server_description());
+ ap_rprintf(r,
+ "<dt><strong>Server Built:</strong> "
+ "<font size=\"+1\"><tt>%s</tt></font></dt>\n",
+ ap_get_server_built());
+ ap_rprintf(r,
+ "<dt><strong>Server loaded APR Version:</strong> "
+ "<tt>%s</tt></dt>\n", apr_version_string());
+ ap_rprintf(r,
+ "<dt><strong>Compiled with APR Version:</strong> "
+ "<tt>%s</tt></dt>\n", APR_VERSION_STRING);
+#if APR_MAJOR_VERSION < 2
+ ap_rprintf(r,
+ "<dt><strong>Server loaded APU Version:</strong> "
+ "<tt>%s</tt></dt>\n", apu_version_string());
+ ap_rprintf(r,
+ "<dt><strong>Compiled with APU Version:</strong> "
+ "<tt>%s</tt></dt>\n", APU_VERSION_STRING);
+#endif
+ ap_rprintf(r,
+ "<dt><strong>Server loaded PCRE Version:</strong> "
+ "<tt>%s</tt></dt>\n", ap_pcre_version_string(AP_REG_PCRE_LOADED));
+ ap_rprintf(r,
+ "<dt><strong>Compiled with PCRE Version:</strong> "
+ "<tt>%s</tt></dt>\n", ap_pcre_version_string(AP_REG_PCRE_COMPILED));
+ ap_rprintf(r,
+ "<dt><strong>Module Magic Number:</strong> "
+ "<tt>%d:%d</tt></dt>\n", MODULE_MAGIC_NUMBER_MAJOR,
+ MODULE_MAGIC_NUMBER_MINOR);
+ ap_rprintf(r,
+ "<dt><strong>Hostname/port:</strong> "
+ "<tt>%s:%u</tt></dt>\n",
+ ap_escape_html(r->pool, ap_get_server_name(r)),
+ ap_get_server_port(r));
+ ap_rprintf(r,
+ "<dt><strong>Timeouts:</strong> "
+ "<tt>connection: %d &nbsp;&nbsp; "
+ "keep-alive: %d</tt></dt>",
+ (int) (apr_time_sec(serv->timeout)),
+ (int) (apr_time_sec(serv->keep_alive_timeout)));
+ ap_mpm_query(AP_MPMQ_MAX_DAEMON_USED, &max_daemons);
+ ap_mpm_query(AP_MPMQ_IS_THREADED, &threaded);
+ ap_mpm_query(AP_MPMQ_IS_FORKED, &forked);
+ ap_rprintf(r, "<dt><strong>MPM Name:</strong> <tt>%s</tt></dt>\n",
+ ap_show_mpm());
+ ap_rprintf(r,
+ "<dt><strong>MPM Information:</strong> "
+ "<tt>Max Daemons: %d Threaded: %s Forked: %s</tt></dt>\n",
+ max_daemons, threaded ? "yes" : "no", forked ? "yes" : "no");
+ ap_rprintf(r,
+ "<dt><strong>Server Architecture:</strong> "
+ "<tt>%ld-bit</tt></dt>\n", 8 * (long) sizeof(void *));
+ ap_rprintf(r,
+ "<dt><strong>Server Root:</strong> "
+ "<tt>%s</tt></dt>\n", ap_server_root);
+ ap_rprintf(r,
+ "<dt><strong>Config File:</strong> "
+ "<tt>%s</tt></dt>\n", ap_conftree->filename);
+
+ ap_rputs("<dt><strong>Server Built With:</strong>\n"
+ "<tt style=\"white-space: pre;\">\n", r);
+
+ /* TODO: Not all of these defines are getting set like they do in main.c.
+ * Missing some headers?
+ */
+
+#ifdef BIG_SECURITY_HOLE
+ ap_rputs(" -D BIG_SECURITY_HOLE\n", r);
+#endif
+
+#ifdef SECURITY_HOLE_PASS_AUTHORIZATION
+ ap_rputs(" -D SECURITY_HOLE_PASS_AUTHORIZATION\n", r);
+#endif
+
+#ifdef OS
+ ap_rputs(" -D OS=\"" OS "\"\n", r);
+#endif
+
+#ifdef HAVE_SHMGET
+ ap_rputs(" -D HAVE_SHMGET\n", r);
+#endif
+
+#if APR_FILE_BASED_SHM
+ ap_rputs(" -D APR_FILE_BASED_SHM\n", r);
+#endif
+
+#if APR_HAS_SENDFILE
+ ap_rputs(" -D APR_HAS_SENDFILE\n", r);
+#endif
+
+#if APR_HAS_MMAP
+ ap_rputs(" -D APR_HAS_MMAP\n", r);
+#endif
+
+#ifdef NO_WRITEV
+ ap_rputs(" -D NO_WRITEV\n", r);
+#endif
+
+#ifdef NO_LINGCLOSE
+ ap_rputs(" -D NO_LINGCLOSE\n", r);
+#endif
+
+#if APR_HAVE_IPV6
+ ap_rputs(" -D APR_HAVE_IPV6 (IPv4-mapped addresses ", r);
+#ifdef AP_ENABLE_V4_MAPPED
+ ap_rputs("enabled)\n", r);
+#else
+ ap_rputs("disabled)\n", r);
+#endif
+#endif
+
+#if APR_USE_FLOCK_SERIALIZE
+ ap_rputs(" -D APR_USE_FLOCK_SERIALIZE\n", r);
+#endif
+
+#if APR_USE_SYSVSEM_SERIALIZE
+ ap_rputs(" -D APR_USE_SYSVSEM_SERIALIZE\n", r);
+#endif
+
+#if APR_USE_POSIXSEM_SERIALIZE
+ ap_rputs(" -D APR_USE_POSIXSEM_SERIALIZE\n", r);
+#endif
+
+#if APR_USE_FCNTL_SERIALIZE
+ ap_rputs(" -D APR_USE_FCNTL_SERIALIZE\n", r);
+#endif
+
+#if APR_USE_PROC_PTHREAD_SERIALIZE
+ ap_rputs(" -D APR_USE_PROC_PTHREAD_SERIALIZE\n", r);
+#endif
+#if APR_PROCESS_LOCK_IS_GLOBAL
+ ap_rputs(" -D APR_PROCESS_LOCK_IS_GLOBAL\n", r);
+#endif
+
+#ifdef SINGLE_LISTEN_UNSERIALIZED_ACCEPT
+ ap_rputs(" -D SINGLE_LISTEN_UNSERIALIZED_ACCEPT\n", r);
+#endif
+
+#if APR_HAS_OTHER_CHILD
+ ap_rputs(" -D APR_HAS_OTHER_CHILD\n", r);
+#endif
+
+#ifdef AP_HAVE_RELIABLE_PIPED_LOGS
+ ap_rputs(" -D AP_HAVE_RELIABLE_PIPED_LOGS\n", r);
+#endif
+
+#ifdef BUFFERED_LOGS
+ ap_rputs(" -D BUFFERED_LOGS\n", r);
+#ifdef PIPE_BUF
+ ap_rprintf(r, " -D PIPE_BUF=%ld\n", (long) PIPE_BUF);
+#endif
+#endif
+
+#if APR_CHARSET_EBCDIC
+ ap_rputs(" -D APR_CHARSET_EBCDIC\n", r);
+#endif
+
+#ifdef NEED_HASHBANG_EMUL
+ ap_rputs(" -D NEED_HASHBANG_EMUL\n", r);
+#endif
+
+/* This list displays the compiled in default paths: */
+#ifdef HTTPD_ROOT
+ ap_rputs(" -D HTTPD_ROOT=\"" HTTPD_ROOT "\"\n", r);
+#endif
+
+#ifdef SUEXEC_BIN
+ ap_rputs(" -D SUEXEC_BIN=\"" SUEXEC_BIN "\"\n", r);
+#endif
+
+#ifdef DEFAULT_PIDLOG
+ ap_rputs(" -D DEFAULT_PIDLOG=\"" DEFAULT_PIDLOG "\"\n", r);
+#endif
+
+#ifdef DEFAULT_SCOREBOARD
+ ap_rputs(" -D DEFAULT_SCOREBOARD=\"" DEFAULT_SCOREBOARD "\"\n", r);
+#endif
+
+#ifdef DEFAULT_ERRORLOG
+ ap_rputs(" -D DEFAULT_ERRORLOG=\"" DEFAULT_ERRORLOG "\"\n", r);
+#endif
+
+
+#ifdef AP_TYPES_CONFIG_FILE
+ ap_rputs(" -D AP_TYPES_CONFIG_FILE=\"" AP_TYPES_CONFIG_FILE "\"\n", r);
+#endif
+
+#ifdef SERVER_CONFIG_FILE
+ ap_rputs(" -D SERVER_CONFIG_FILE=\"" SERVER_CONFIG_FILE "\"\n", r);
+#endif
+ ap_rputs("</tt></dt>\n", r);
+ ap_rputs("</dl><hr />", r);
+ return 0;
+}
+
+static int dump_a_hook(request_rec * r, hook_get_t hook_get)
+{
+ int i;
+ char qs;
+ hook_struct_t *elts;
+ apr_array_header_t *hooks = hook_get();
+
+ if (!hooks) {
+ return 0;
+ }
+
+ if (r->args && strcasecmp(r->args, "hooks") == 0) {
+ qs = '?';
+ }
+ else {
+ qs = '#';
+ }
+
+ elts = (hook_struct_t *) hooks->elts;
+
+ for (i = 0; i < hooks->nelts; i++) {
+ ap_rprintf(r,
+ "&nbsp;&nbsp; %02d <a href=\"%c%s\">%s</a> <br/>",
+ elts[i].nOrder, qs, elts[i].szName, elts[i].szName);
+ }
+ return 0;
+}
+
+static int show_active_hooks(request_rec * r)
+{
+ int i;
+ ap_rputs("<h2><a name=\"startup_hooks\">Startup Hooks</a></h2>\n<dl>", r);
+
+ for (i = 0; startup_hooks[i].name; i++) {
+ ap_rprintf(r, "<dt><strong>%s:</strong>\n <br /><tt>\n",
+ startup_hooks[i].name);
+ dump_a_hook(r, startup_hooks[i].get);
+ ap_rputs("\n </tt>\n</dt>\n", r);
+ }
+
+ ap_rputs
+ ("</dl>\n<hr />\n<h2><a name=\"request_hooks\">Request Hooks</a></h2>\n<dl>",
+ r);
+
+ for (i = 0; request_hooks[i].name; i++) {
+ ap_rprintf(r, "<dt><strong>%s:</strong>\n <br /><tt>\n",
+ request_hooks[i].name);
+ dump_a_hook(r, request_hooks[i].get);
+ ap_rputs("\n </tt>\n</dt>\n", r);
+ }
+
+ ap_rputs
+ ("</dl>\n<hr />\n<h2><a name=\"other_hooks\">Other Hooks</a></h2>\n<dl>",
+ r);
+
+ for (i = 0; other_hooks[i].name; i++) {
+ ap_rprintf(r, "<dt><strong>%s:</strong>\n <br /><tt>\n",
+ other_hooks[i].name);
+ dump_a_hook(r, other_hooks[i].get);
+ ap_rputs("\n </tt>\n</dt>\n", r);
+ }
+
+ ap_rputs("</dl>\n<hr />\n", r);
+
+ return 0;
+}
+
+static int cmp_provider_groups(const void *a_, const void *b_)
+{
+ const ap_list_provider_groups_t *a = a_, *b = b_;
+ int ret = strcmp(a->provider_group, b->provider_group);
+ if (!ret)
+ ret = strcmp(a->provider_version, b->provider_version);
+ return ret;
+}
+
+static int cmp_provider_names(const void *a_, const void *b_)
+{
+ const ap_list_provider_names_t *a = a_, *b = b_;
+ return strcmp(a->provider_name, b->provider_name);
+}
+
+static void show_providers(request_rec *r)
+{
+ apr_array_header_t *groups = ap_list_provider_groups(r->pool);
+ ap_list_provider_groups_t *group;
+ apr_array_header_t *names;
+ ap_list_provider_names_t *name;
+ int i,j;
+ const char *cur_group = NULL;
+
+ qsort(groups->elts, groups->nelts, sizeof(ap_list_provider_groups_t),
+ cmp_provider_groups);
+ ap_rputs("<h2><a name=\"providers\">Providers</a></h2>\n<dl>", r);
+
+ for (i = 0; i < groups->nelts; i++) {
+ group = &APR_ARRAY_IDX(groups, i, ap_list_provider_groups_t);
+ if (!cur_group || strcmp(cur_group, group->provider_group) != 0) {
+ if (cur_group)
+ ap_rputs("\n</dt>\n", r);
+ cur_group = group->provider_group;
+ ap_rprintf(r, "<dt><strong>%s</strong> (version <tt>%s</tt>):"
+ "\n <br />\n", cur_group, group->provider_version);
+ }
+ names = ap_list_provider_names(r->pool, group->provider_group,
+ group->provider_version);
+ qsort(names->elts, names->nelts, sizeof(ap_list_provider_names_t),
+ cmp_provider_names);
+ for (j = 0; j < names->nelts; j++) {
+ name = &APR_ARRAY_IDX(names, j, ap_list_provider_names_t);
+ ap_rprintf(r, "<tt>&nbsp;&nbsp;%s</tt><br/>", name->provider_name);
+ }
+ }
+ if (cur_group)
+ ap_rputs("\n</dt>\n", r);
+ ap_rputs("</dl>\n<hr />\n", r);
+}
+
+static int cmp_module_name(const void *a_, const void *b_)
+{
+ const module * const *a = a_;
+ const module * const *b = b_;
+ return strcmp((*a)->name, (*b)->name);
+}
+
+static apr_array_header_t *get_sorted_modules(apr_pool_t *p)
+{
+ apr_array_header_t *arr = apr_array_make(p, 64, sizeof(module *));
+ module *modp, **entry;
+ for (modp = ap_top_module; modp; modp = modp->next) {
+ entry = &APR_ARRAY_PUSH(arr, module *);
+ *entry = modp;
+ }
+ qsort(arr->elts, arr->nelts, sizeof(module *), cmp_module_name);
+ return arr;
+}
+
+static int display_info(request_rec * r)
+{
+ module *modp = NULL;
+ const char *more_info;
+ const command_rec *cmd;
+ apr_array_header_t *modules = NULL;
+ int i;
+
+ if (strcmp(r->handler, "server-info")) {
+ return DECLINED;
+ }
+
+ r->allowed |= (AP_METHOD_BIT << M_GET);
+ if (r->method_number != M_GET) {
+ return DECLINED;
+ }
+
+ ap_set_content_type(r, "text/html; charset=ISO-8859-1");
+
+ ap_rputs(DOCTYPE_XHTML_1_0T
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
+ "<head>\n"
+ " <title>Server Information</title>\n" "</head>\n", r);
+ ap_rputs("<body><h1 style=\"text-align: center\">"
+ "Apache Server Information</h1>\n", r);
+ if (!r->args || ap_cstr_casecmp(r->args, "list")) {
+ if (!r->args) {
+ ap_rputs("<dl><dt><tt>Subpages:<br />", r);
+ ap_rputs("<a href=\"?config\">Configuration Files</a>, "
+ "<a href=\"?server\">Server Settings</a>, "
+ "<a href=\"?list\">Module List</a>, "
+ "<a href=\"?hooks\">Active Hooks</a>, "
+ "<a href=\"?providers\">Available Providers</a>", r);
+ ap_rputs("</tt></dt></dl><hr />", r);
+
+ ap_rputs("<dl><dt><tt>Sections:<br />", r);
+ ap_rputs("<a href=\"#modules\">Loaded Modules</a>, "
+ "<a href=\"#server\">Server Settings</a>, "
+ "<a href=\"#startup_hooks\">Startup Hooks</a>, "
+ "<a href=\"#request_hooks\">Request Hooks</a>, "
+ "<a href=\"#other_hooks\">Other Hooks</a>, "
+ "<a href=\"#providers\">Providers</a>", r);
+ ap_rputs("</tt></dt></dl><hr />", r);
+
+ ap_rputs("<h2><a name=\"modules\">Loaded Modules</a></h2>"
+ "<dl><dt><tt>", r);
+
+ modules = get_sorted_modules(r->pool);
+ for (i = 0; i < modules->nelts; i++) {
+ modp = APR_ARRAY_IDX(modules, i, module *);
+ ap_rprintf(r, "<a href=\"#%s\">%s</a>", modp->name,
+ modp->name);
+ if (i < modules->nelts) {
+ ap_rputs(", ", r);
+ }
+ }
+ ap_rputs("</tt></dt></dl><hr />", r);
+ }
+
+ if (!r->args || !ap_cstr_casecmp(r->args, "server")) {
+ show_server_settings(r);
+ }
+
+ if (!r->args || !ap_cstr_casecmp(r->args, "hooks")) {
+ show_active_hooks(r);
+ }
+
+ if (!r->args || !ap_cstr_casecmp(r->args, "providers")) {
+ show_providers(r);
+ }
+
+ if (r->args && 0 == ap_cstr_casecmp(r->args, "config")) {
+ ap_rputs("<dl><dt><strong>Configuration:</strong>\n", r);
+ mod_info_module_cmds(r, NULL, ap_conftree, 0, 0);
+ ap_rputs("</dl><hr />", r);
+ }
+ else {
+ int comma = 0;
+ if (!modules)
+ modules = get_sorted_modules(r->pool);
+ for (i = 0; i < modules->nelts; i++) {
+ modp = APR_ARRAY_IDX(modules, i, module *);
+ if (!r->args || !ap_cstr_casecmp(modp->name, r->args)) {
+ ap_rprintf(r,
+ "<dl><dt><a name=\"%s\"><strong>Module Name:</strong></a> "
+ "<font size=\"+1\"><tt><a href=\"?%s\">%s</a></tt></font></dt>\n",
+ modp->name, modp->name, modp->name);
+ ap_rputs("<dt><strong>Content handlers:</strong> ", r);
+
+ if (module_find_hook(modp, ap_hook_get_handler)) {
+ ap_rputs("<tt> <em>yes</em></tt>", r);
+ }
+ else {
+ ap_rputs("<tt> <em>none</em></tt>", r);
+ }
+
+ ap_rputs("</dt>", r);
+ ap_rputs
+ ("<dt><strong>Configuration Phase Participation:</strong>\n",
+ r);
+ if (modp->create_dir_config) {
+ if (comma) {
+ ap_rputs(", ", r);
+ }
+ ap_rputs("<tt>Create Directory Config</tt>", r);
+ comma = 1;
+ }
+ if (modp->merge_dir_config) {
+ if (comma) {
+ ap_rputs(", ", r);
+ }
+ ap_rputs("<tt>Merge Directory Configs</tt>", r);
+ comma = 1;
+ }
+ if (modp->create_server_config) {
+ if (comma) {
+ ap_rputs(", ", r);
+ }
+ ap_rputs("<tt>Create Server Config</tt>", r);
+ comma = 1;
+ }
+ if (modp->merge_server_config) {
+ if (comma) {
+ ap_rputs(", ", r);
+ }
+ ap_rputs("<tt>Merge Server Configs</tt>", r);
+ comma = 1;
+ }
+ if (!comma)
+ ap_rputs("<tt> <em>none</em></tt>", r);
+ comma = 0;
+ ap_rputs("</dt>", r);
+
+ module_request_hook_participate(r, modp);
+
+ cmd = modp->cmds;
+ if (cmd) {
+ ap_rputs
+ ("<dt><strong>Module Directives:</strong></dt>",
+ r);
+ while (cmd) {
+ if (cmd->name) {
+ ap_rprintf(r, "<dd><tt>%s%s - <i>",
+ ap_escape_html(r->pool, cmd->name),
+ cmd->name[0] == '<' ? "&gt;" : "");
+ if (cmd->errmsg) {
+ ap_rputs(ap_escape_html(r->pool, cmd->errmsg), r);
+ }
+ ap_rputs("</i></tt></dd>\n", r);
+ }
+ else {
+ break;
+ }
+ cmd++;
+ }
+ ap_rputs
+ ("<dt><strong>Current Configuration:</strong></dt>\n",
+ r);
+ mod_info_module_cmds(r, modp->cmds, ap_conftree, 0,
+ 0);
+ }
+ else {
+ ap_rputs
+ ("<dt><strong>Module Directives:</strong> <tt>none</tt></dt>",
+ r);
+ }
+ more_info = find_more_info(r->server, modp->name);
+ if (more_info) {
+ ap_rputs
+ ("<dt><strong>Additional Information:</strong>\n</dt><dd>",
+ r);
+ ap_rputs(more_info, r);
+ ap_rputs("</dd>", r);
+ }
+ ap_rputs("</dl><hr />\n", r);
+ if (r->args) {
+ break;
+ }
+ }
+ }
+ if (!modp && r->args && ap_cstr_casecmp(r->args, "server")) {
+ ap_rputs("<p><b>No such module</b></p>\n", r);
+ }
+ }
+ }
+ else {
+ ap_rputs("<dl><dt>Server Module List</dt>", r);
+ modules = get_sorted_modules(r->pool);
+ for (i = 0; i < modules->nelts; i++) {
+ modp = APR_ARRAY_IDX(modules, i, module *);
+ ap_rputs("<dd>", r);
+ ap_rputs(modp->name, r);
+ ap_rputs("</dd>", r);
+ }
+ ap_rputs("</dl><hr />", r);
+ }
+ ap_rputs(ap_psignature("", r), r);
+ ap_rputs("</body></html>\n", r);
+ /* Done, turn off timeout, close file and return */
+ return 0;
+}
+
+static const char *add_module_info(cmd_parms * cmd, void *dummy,
+ const char *name, const char *info)
+{
+ server_rec *s = cmd->server;
+ info_svr_conf *conf =
+ (info_svr_conf *) ap_get_module_config(s->module_config,
+ &info_module);
+ info_entry *new = apr_array_push(conf->more_info);
+
+ new->name = name;
+ new->info = info;
+ return NULL;
+}
+
+static const command_rec info_cmds[] = {
+ AP_INIT_TAKE2("AddModuleInfo", add_module_info, NULL, RSRC_CONF,
+ "a module name and additional information on that module"),
+ {NULL}
+};
+
+static int check_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp,
+ server_rec *s)
+{
+ if (ap_exists_config_define("DUMP_CONFIG")) {
+ apr_file_open_stdout(&out, ptemp);
+ mod_info_module_cmds(NULL, NULL, ap_conftree, 0, 0);
+ }
+
+ return DECLINED;
+}
+
+
+static void register_hooks(apr_pool_t * p)
+{
+ ap_hook_handler(display_info, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_check_config(check_config, NULL, NULL, APR_HOOK_FIRST);
+}
+
+AP_DECLARE_MODULE(info) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* dir config creater */
+ NULL, /* dir merger --- default is to override */
+ create_info_config, /* server config */
+ merge_info_config, /* merge server config */
+ info_cmds, /* command apr_table_t */
+ register_hooks
+};
diff --git a/modules/generators/mod_info.dep b/modules/generators/mod_info.dep
new file mode 100644
index 0000000..cdfc02b
--- /dev/null
+++ b/modules/generators/mod_info.dep
@@ -0,0 +1,66 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_info.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_info.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_mpm.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mpm_common.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr-util\include\apu_version.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_version.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/generators/mod_info.dsp b/modules/generators/mod_info.dsp
new file mode 100644
index 0000000..524ebf8
--- /dev/null
+++ b/modules/generators/mod_info.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_info" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_info - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_info.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_info.mak" CFG="mod_info - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_info - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_info - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_info - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_info_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_info.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_info.so" /d LONG_NAME="info_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_info.so" /base:@..\..\os\win32\BaseAddr.ref,mod_info.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_info.so" /base:@..\..\os\win32\BaseAddr.ref,mod_info.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_info.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_info - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_info_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_info.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_info.so" /d LONG_NAME="info_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_info.so" /base:@..\..\os\win32\BaseAddr.ref,mod_info.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_info.so" /base:@..\..\os\win32\BaseAddr.ref,mod_info.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_info.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_info - Win32 Release"
+# Name "mod_info - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_info.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/generators/mod_info.exp b/modules/generators/mod_info.exp
new file mode 100644
index 0000000..c304fa7
--- /dev/null
+++ b/modules/generators/mod_info.exp
@@ -0,0 +1 @@
+info_module
diff --git a/modules/generators/mod_info.mak b/modules/generators/mod_info.mak
new file mode 100644
index 0000000..b0de8a1
--- /dev/null
+++ b/modules/generators/mod_info.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_info.dsp
+!IF "$(CFG)" == ""
+CFG=mod_info - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_info - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_info - Win32 Release" && "$(CFG)" != "mod_info - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_info.mak" CFG="mod_info - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_info - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_info - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_info - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_info.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_info.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_info.obj"
+ -@erase "$(INTDIR)\mod_info.res"
+ -@erase "$(INTDIR)\mod_info_src.idb"
+ -@erase "$(INTDIR)\mod_info_src.pdb"
+ -@erase "$(OUTDIR)\mod_info.exp"
+ -@erase "$(OUTDIR)\mod_info.lib"
+ -@erase "$(OUTDIR)\mod_info.pdb"
+ -@erase "$(OUTDIR)\mod_info.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_info_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_info.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_info.so" /d LONG_NAME="info_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_info.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_info.pdb" /debug /out:"$(OUTDIR)\mod_info.so" /implib:"$(OUTDIR)\mod_info.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_info.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_info.obj" \
+ "$(INTDIR)\mod_info.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_info.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_info.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_info.so"
+ if exist .\Release\mod_info.so.manifest mt.exe -manifest .\Release\mod_info.so.manifest -outputresource:.\Release\mod_info.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_info - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_info.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_info.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_info.obj"
+ -@erase "$(INTDIR)\mod_info.res"
+ -@erase "$(INTDIR)\mod_info_src.idb"
+ -@erase "$(INTDIR)\mod_info_src.pdb"
+ -@erase "$(OUTDIR)\mod_info.exp"
+ -@erase "$(OUTDIR)\mod_info.lib"
+ -@erase "$(OUTDIR)\mod_info.pdb"
+ -@erase "$(OUTDIR)\mod_info.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_info_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_info.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_info.so" /d LONG_NAME="info_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_info.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_info.pdb" /debug /out:"$(OUTDIR)\mod_info.so" /implib:"$(OUTDIR)\mod_info.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_info.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_info.obj" \
+ "$(INTDIR)\mod_info.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_info.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_info.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_info.so"
+ if exist .\Debug\mod_info.so.manifest mt.exe -manifest .\Debug\mod_info.so.manifest -outputresource:.\Debug\mod_info.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_info.dep")
+!INCLUDE "mod_info.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_info.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_info - Win32 Release" || "$(CFG)" == "mod_info - Win32 Debug"
+
+!IF "$(CFG)" == "mod_info - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\generators"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\generators"
+
+!ELSEIF "$(CFG)" == "mod_info - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\generators"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\generators"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_info - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\generators"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\generators"
+
+!ELSEIF "$(CFG)" == "mod_info - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\generators"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\generators"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_info - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\generators"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\generators"
+
+!ELSEIF "$(CFG)" == "mod_info - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\generators"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\generators"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_info - Win32 Release"
+
+
+"$(INTDIR)\mod_info.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_info.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_info.so" /d LONG_NAME="info_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_info - Win32 Debug"
+
+
+"$(INTDIR)\mod_info.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_info.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_info.so" /d LONG_NAME="info_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_info.c
+
+"$(INTDIR)\mod_info.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/generators/mod_status.c b/modules/generators/mod_status.c
new file mode 100644
index 0000000..5917953
--- /dev/null
+++ b/modules/generators/mod_status.c
@@ -0,0 +1,1051 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Status Module. Display lots of internal data about how Apache is
+ * performing and the state of all children processes.
+ *
+ * To enable this, add the following lines into any config file:
+ *
+ * <Location /server-status>
+ * SetHandler server-status
+ * </Location>
+ *
+ * You may want to protect this location by password or domain so no one
+ * else can look at it. Then you can access the statistics with a URL like:
+ *
+ * http://your_server_name/server-status
+ *
+ * /server-status - Returns page using tables
+ * /server-status?notable - Returns page for browsers without table support
+ * /server-status?refresh - Returns page with 1 second refresh
+ * /server-status?refresh=6 - Returns page with refresh every 6 seconds
+ * /server-status?auto - Returns page with data for automatic parsing
+ *
+ * Mark Cox, mark@ukweb.com, November 1995
+ *
+ * 12.11.95 Initial version for www.telescope.org
+ * 13.3.96 Updated to remove rprintf's [Mark]
+ * 18.3.96 Added CPU usage, process information, and tidied [Ben Laurie]
+ * 18.3.96 Make extra Scoreboard variables #definable
+ * 25.3.96 Make short report have full precision [Ben Laurie suggested]
+ * 25.3.96 Show uptime better [Mark/Ben Laurie]
+ * 29.3.96 Better HTML and explanation [Mark/Rob Hartill suggested]
+ * 09.4.96 Added message for non-STATUS compiled version
+ * 18.4.96 Added per child and per slot counters [Jim Jagielski]
+ * 01.5.96 Table format, cleanup, even more spiffy data [Chuck Murcko/Jim J.]
+ * 18.5.96 Adapted to use new rprintf() routine, incidentally fixing a missing
+ * piece in short reports [Ben Laurie]
+ * 21.5.96 Additional Status codes (DNS and LOGGING only enabled if
+ * extended STATUS is enabled) [George Burgyan/Jim J.]
+ * 10.8.98 Allow for extended status info at runtime (no more STATUS)
+ * [Jim J.]
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_protocol.h"
+#include "http_main.h"
+#include "ap_mpm.h"
+#include "util_script.h"
+#include <time.h>
+#include "scoreboard.h"
+#include "http_log.h"
+#include "mod_status.h"
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+#include "apr_strings.h"
+
+#define STATUS_MAXLINE 64
+
+#define KBYTE 1024
+#define MBYTE 1048576L
+#define GBYTE 1073741824L
+
+#ifndef DEFAULT_TIME_FORMAT
+#define DEFAULT_TIME_FORMAT "%A, %d-%b-%Y %H:%M:%S %Z"
+#endif
+
+#define STATUS_MAGIC_TYPE "application/x-httpd-status"
+
+module AP_MODULE_DECLARE_DATA status_module;
+
+static int server_limit, thread_limit, threads_per_child, max_servers,
+ is_async;
+
+/* Implement 'ap_run_status_hook'. */
+APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(ap, STATUS, int, status_hook,
+ (request_rec *r, int flags),
+ (r, flags),
+ OK, DECLINED)
+
+#ifdef HAVE_TIMES
+/* ugh... need to know if we're running with a pthread implementation
+ * such as linuxthreads that treats individual threads as distinct
+ * processes; that affects how we add up CPU time in a process
+ */
+static pid_t child_pid;
+#endif
+
+/* Format the number of bytes nicely */
+static void format_byte_out(request_rec *r, apr_off_t bytes)
+{
+ if (bytes < (5 * KBYTE))
+ ap_rprintf(r, "%d B", (int) bytes);
+ else if (bytes < (MBYTE / 2))
+ ap_rprintf(r, "%.1f kB", (float) bytes / KBYTE);
+ else if (bytes < (GBYTE / 2))
+ ap_rprintf(r, "%.1f MB", (float) bytes / MBYTE);
+ else
+ ap_rprintf(r, "%.1f GB", (float) bytes / GBYTE);
+}
+
+static void format_kbyte_out(request_rec *r, apr_off_t kbytes)
+{
+ if (kbytes < KBYTE)
+ ap_rprintf(r, "%d kB", (int) kbytes);
+ else if (kbytes < MBYTE)
+ ap_rprintf(r, "%.1f MB", (float) kbytes / KBYTE);
+ else
+ ap_rprintf(r, "%.1f GB", (float) kbytes / MBYTE);
+}
+
+static void show_time(request_rec *r, apr_uint32_t tsecs)
+{
+ int days, hrs, mins, secs;
+
+ secs = (int)(tsecs % 60);
+ tsecs /= 60;
+ mins = (int)(tsecs % 60);
+ tsecs /= 60;
+ hrs = (int)(tsecs % 24);
+ days = (int)(tsecs / 24);
+
+ if (days)
+ ap_rprintf(r, " %d day%s", days, days == 1 ? "" : "s");
+
+ if (hrs)
+ ap_rprintf(r, " %d hour%s", hrs, hrs == 1 ? "" : "s");
+
+ if (mins)
+ ap_rprintf(r, " %d minute%s", mins, mins == 1 ? "" : "s");
+
+ if (secs)
+ ap_rprintf(r, " %d second%s", secs, secs == 1 ? "" : "s");
+}
+
+/* Main handler for x-httpd-status requests */
+
+/* ID values for command table */
+
+#define STAT_OPT_END -1
+#define STAT_OPT_REFRESH 0
+#define STAT_OPT_NOTABLE 1
+#define STAT_OPT_AUTO 2
+
+struct stat_opt {
+ int id;
+ const char *form_data_str;
+ const char *hdr_out_str;
+};
+
+static const struct stat_opt status_options[] = /* see #defines above */
+{
+ {STAT_OPT_REFRESH, "refresh", "Refresh"},
+ {STAT_OPT_NOTABLE, "notable", NULL},
+ {STAT_OPT_AUTO, "auto", NULL},
+ {STAT_OPT_END, NULL, NULL}
+};
+
+/* add another state for slots above the MaxRequestWorkers setting */
+#define SERVER_DISABLED SERVER_NUM_STATUS
+#define MOD_STATUS_NUM_STATUS (SERVER_NUM_STATUS+1)
+
+static char status_flags[MOD_STATUS_NUM_STATUS];
+
+static int status_handler(request_rec *r)
+{
+ const char *loc;
+ apr_time_t nowtime;
+ apr_uint32_t up_time;
+ ap_loadavg_t t;
+ int j, i, res, written;
+ int ready;
+ int busy;
+ unsigned long count;
+ unsigned long lres, my_lres, conn_lres;
+ apr_off_t bytes, my_bytes, conn_bytes;
+ apr_off_t bcount, kbcount;
+ long req_time;
+ apr_time_t duration_global;
+ apr_time_t duration_slot;
+ int short_report;
+ int no_table_report;
+ global_score *global_record;
+ worker_score *ws_record;
+ process_score *ps_record;
+ char *stat_buffer;
+ pid_t *pid_buffer, worker_pid;
+ int *thread_idle_buffer = NULL;
+ int *thread_busy_buffer = NULL;
+ clock_t tu, ts, tcu, tcs;
+ clock_t gu, gs, gcu, gcs;
+ ap_generation_t mpm_generation, worker_generation;
+#ifdef HAVE_TIMES
+ float tick;
+ int times_per_thread;
+#endif
+
+ if (strcmp(r->handler, STATUS_MAGIC_TYPE) && strcmp(r->handler,
+ "server-status")) {
+ return DECLINED;
+ }
+
+#ifdef HAVE_TIMES
+ times_per_thread = getpid() != child_pid;
+#endif
+
+ ap_mpm_query(AP_MPMQ_GENERATION, &mpm_generation);
+
+#ifdef HAVE_TIMES
+#ifdef _SC_CLK_TCK
+ tick = sysconf(_SC_CLK_TCK);
+#else
+ tick = HZ;
+#endif
+#endif
+
+ ready = 0;
+ busy = 0;
+ count = 0;
+ bcount = 0;
+ kbcount = 0;
+ duration_global = 0;
+ short_report = 0;
+ no_table_report = 0;
+
+ if (!ap_exists_scoreboard_image()) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01237)
+ "Server status unavailable in inetd mode");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ pid_buffer = apr_palloc(r->pool, server_limit * sizeof(pid_t));
+ stat_buffer = apr_palloc(r->pool, server_limit * thread_limit * sizeof(char));
+ if (is_async) {
+ thread_idle_buffer = apr_palloc(r->pool, server_limit * sizeof(int));
+ thread_busy_buffer = apr_palloc(r->pool, server_limit * sizeof(int));
+ }
+
+ nowtime = apr_time_now();
+#ifdef HAVE_TIMES
+ global_record = ap_get_scoreboard_global();
+ gu = global_record->times.tms_utime;
+ gs = global_record->times.tms_stime;
+ gcu = global_record->times.tms_cutime;
+ gcs = global_record->times.tms_cstime;
+#else
+ gu = gs = gcu = gcs = 0;
+#endif
+ tu = ts = tcu = tcs = 0;
+
+ r->allowed = (AP_METHOD_BIT << M_GET);
+ if (r->method_number != M_GET)
+ return DECLINED;
+
+ ap_set_content_type(r, "text/html; charset=ISO-8859-1");
+
+ /*
+ * Simple table-driven form data set parser that lets you alter the header
+ */
+
+ if (r->args) {
+ i = 0;
+ while (status_options[i].id != STAT_OPT_END) {
+ if ((loc = ap_strstr_c(r->args,
+ status_options[i].form_data_str)) != NULL) {
+ switch (status_options[i].id) {
+ case STAT_OPT_REFRESH: {
+ apr_size_t len = strlen(status_options[i].form_data_str);
+ long t = 0;
+
+ if (*(loc + len ) == '=') {
+ t = atol(loc + len + 1);
+ }
+ apr_table_setn(r->headers_out,
+ status_options[i].hdr_out_str,
+ apr_ltoa(r->pool, t < 1 ? 10 : t));
+ break;
+ }
+ case STAT_OPT_NOTABLE:
+ no_table_report = 1;
+ break;
+ case STAT_OPT_AUTO:
+ ap_set_content_type(r, "text/plain; charset=ISO-8859-1");
+ short_report = 1;
+ break;
+ }
+ }
+
+ i++;
+ }
+ }
+
+ ws_record = apr_palloc(r->pool, sizeof *ws_record);
+
+ for (i = 0; i < server_limit; ++i) {
+#ifdef HAVE_TIMES
+ clock_t proc_tu = 0, proc_ts = 0, proc_tcu = 0, proc_tcs = 0;
+ clock_t tmp_tu, tmp_ts, tmp_tcu, tmp_tcs;
+#endif
+
+ ps_record = ap_get_scoreboard_process(i);
+ if (is_async) {
+ thread_idle_buffer[i] = 0;
+ thread_busy_buffer[i] = 0;
+ }
+ for (j = 0; j < thread_limit; ++j) {
+ int indx = (i * thread_limit) + j;
+
+ ap_copy_scoreboard_worker(ws_record, i, j);
+ res = ws_record->status;
+
+ if ((i >= max_servers || j >= threads_per_child)
+ && (res == SERVER_DEAD))
+ stat_buffer[indx] = status_flags[SERVER_DISABLED];
+ else
+ stat_buffer[indx] = status_flags[res];
+
+ if (!ps_record->quiescing
+ && ps_record->pid) {
+ if (res == SERVER_READY) {
+ if (ps_record->generation == mpm_generation)
+ ready++;
+ if (is_async)
+ thread_idle_buffer[i]++;
+ }
+ else if (res != SERVER_DEAD &&
+ res != SERVER_STARTING &&
+ res != SERVER_IDLE_KILL) {
+ busy++;
+ if (is_async) {
+ if (res == SERVER_GRACEFUL)
+ thread_idle_buffer[i]++;
+ else
+ thread_busy_buffer[i]++;
+ }
+ }
+ }
+
+ /* XXX what about the counters for quiescing/seg faulted
+ * processes? should they be counted or not? GLA
+ */
+ if (ap_extended_status) {
+ lres = ws_record->access_count;
+ bytes = ws_record->bytes_served;
+
+ if (lres != 0 || (res != SERVER_READY && res != SERVER_DEAD)) {
+#ifdef HAVE_TIMES
+ tmp_tu = ws_record->times.tms_utime;
+ tmp_ts = ws_record->times.tms_stime;
+ tmp_tcu = ws_record->times.tms_cutime;
+ tmp_tcs = ws_record->times.tms_cstime;
+
+ if (times_per_thread) {
+ proc_tu += tmp_tu;
+ proc_ts += tmp_ts;
+ proc_tcu += tmp_tcu;
+ proc_tcs += tmp_tcs;
+ }
+ else {
+ if (tmp_tu > proc_tu ||
+ tmp_ts > proc_ts ||
+ tmp_tcu > proc_tcu ||
+ tmp_tcs > proc_tcs) {
+ proc_tu = tmp_tu;
+ proc_ts = tmp_ts;
+ proc_tcu = tmp_tcu;
+ proc_tcs = tmp_tcs;
+ }
+ }
+#endif /* HAVE_TIMES */
+
+ count += lres;
+ bcount += bytes;
+ duration_global += ws_record->duration;
+
+ if (bcount >= KBYTE) {
+ kbcount += (bcount >> 10);
+ bcount = bcount & 0x3ff;
+ }
+ }
+ }
+ }
+#ifdef HAVE_TIMES
+ tu += proc_tu;
+ ts += proc_ts;
+ tcu += proc_tcu;
+ tcs += proc_tcs;
+#endif
+ pid_buffer[i] = ps_record->pid;
+ }
+
+ /* up_time in seconds */
+ up_time = (apr_uint32_t) apr_time_sec(nowtime -
+ ap_scoreboard_image->global->restart_time);
+ ap_get_loadavg(&t);
+
+ if (!short_report) {
+ ap_rputs(DOCTYPE_HTML_3_2
+ "<html><head>\n"
+ "<title>Apache Status</title>\n"
+ "</head><body>\n"
+ "<h1>Apache Server Status for ", r);
+ ap_rvputs(r, ap_escape_html(r->pool, ap_get_server_name(r)),
+ " (via ", r->connection->local_ip,
+ ")</h1>\n\n", NULL);
+ ap_rvputs(r, "<dl><dt>Server Version: ",
+ ap_get_server_description(), "</dt>\n", NULL);
+ ap_rvputs(r, "<dt>Server MPM: ",
+ ap_show_mpm(), "</dt>\n", NULL);
+ ap_rvputs(r, "<dt>Server Built: ",
+ ap_get_server_built(), "\n</dt></dl><hr /><dl>\n", NULL);
+ ap_rvputs(r, "<dt>Current Time: ",
+ ap_ht_time(r->pool, nowtime, DEFAULT_TIME_FORMAT, 0),
+ "</dt>\n", NULL);
+ ap_rvputs(r, "<dt>Restart Time: ",
+ ap_ht_time(r->pool,
+ ap_scoreboard_image->global->restart_time,
+ DEFAULT_TIME_FORMAT, 0),
+ "</dt>\n", NULL);
+ ap_rprintf(r, "<dt>Parent Server Config. Generation: %d</dt>\n",
+ ap_state_query(AP_SQ_CONFIG_GEN));
+ ap_rprintf(r, "<dt>Parent Server MPM Generation: %d</dt>\n",
+ (int)mpm_generation);
+ ap_rputs("<dt>Server uptime: ", r);
+ show_time(r, up_time);
+ ap_rputs("</dt>\n", r);
+ ap_rprintf(r, "<dt>Server load: %.2f %.2f %.2f</dt>\n",
+ t.loadavg, t.loadavg5, t.loadavg15);
+ }
+ else {
+ ap_rvputs(r, ap_get_server_name(r), "\n", NULL);
+ ap_rvputs(r, "ServerVersion: ",
+ ap_get_server_description(), "\n", NULL);
+ ap_rvputs(r, "ServerMPM: ",
+ ap_show_mpm(), "\n", NULL);
+ ap_rvputs(r, "Server Built: ",
+ ap_get_server_built(), "\n", NULL);
+ ap_rvputs(r, "CurrentTime: ",
+ ap_ht_time(r->pool, nowtime, DEFAULT_TIME_FORMAT, 0),
+ "\n", NULL);
+ ap_rvputs(r, "RestartTime: ",
+ ap_ht_time(r->pool,
+ ap_scoreboard_image->global->restart_time,
+ DEFAULT_TIME_FORMAT, 0),
+ "\n", NULL);
+ ap_rprintf(r, "ParentServerConfigGeneration: %d\n",
+ ap_state_query(AP_SQ_CONFIG_GEN));
+ ap_rprintf(r, "ParentServerMPMGeneration: %d\n",
+ (int)mpm_generation);
+ ap_rprintf(r, "ServerUptimeSeconds: %u\n",
+ up_time);
+ ap_rputs("ServerUptime:", r);
+ show_time(r, up_time);
+ ap_rputs("\n", r);
+ ap_rprintf(r, "Load1: %.2f\nLoad5: %.2f\nLoad15: %.2f\n",
+ t.loadavg, t.loadavg5, t.loadavg15);
+ }
+
+ if (ap_extended_status) {
+ clock_t cpu = gu + gs + gcu + gcs + tu + ts + tcu + tcs;
+ if (short_report) {
+ ap_rprintf(r, "Total Accesses: %lu\nTotal kBytes: %"
+ APR_OFF_T_FMT "\nTotal Duration: %"
+ APR_TIME_T_FMT "\n",
+ count, kbcount, apr_time_as_msec(duration_global));
+
+#ifdef HAVE_TIMES
+ /* Allow for OS/2 not having CPU stats */
+ ap_rprintf(r, "CPUUser: %g\nCPUSystem: %g\nCPUChildrenUser: %g\nCPUChildrenSystem: %g\n",
+ (gu + tu) / tick, (gs + ts) / tick, (gcu + tcu) / tick, (gcs + tcs) / tick);
+
+ if (cpu)
+ ap_rprintf(r, "CPULoad: %g\n",
+ cpu / tick / up_time * 100.);
+#endif
+
+ ap_rprintf(r, "Uptime: %ld\n", (long) (up_time));
+ if (up_time > 0) {
+ ap_rprintf(r, "ReqPerSec: %g\n",
+ (float) count / (float) up_time);
+
+ ap_rprintf(r, "BytesPerSec: %g\n",
+ KBYTE * (float) kbcount / (float) up_time);
+ }
+ if (count > 0) {
+ ap_rprintf(r, "BytesPerReq: %g\n",
+ KBYTE * (float) kbcount / (float) count);
+ ap_rprintf(r, "DurationPerReq: %g\n",
+ (float) apr_time_as_msec(duration_global) / (float) count);
+ }
+ }
+ else { /* !short_report */
+ ap_rprintf(r, "<dt>Total accesses: %lu - Total Traffic: ", count);
+ format_kbyte_out(r, kbcount);
+ ap_rprintf(r, " - Total Duration: %" APR_TIME_T_FMT "</dt>\n",
+ apr_time_as_msec(duration_global));
+
+#ifdef HAVE_TIMES
+ /* Allow for OS/2 not having CPU stats */
+ ap_rprintf(r, "<dt>CPU Usage: u%g s%g cu%g cs%g",
+ (gu + tu) / tick, (gs + ts) / tick, (gcu + tcu) / tick, (gcs + tcs) / tick);
+
+ if (cpu)
+ ap_rprintf(r, " - %.3g%% CPU load</dt>\n",
+ cpu / tick / up_time * 100.);
+ else
+ ap_rputs("</dt>\n", r);
+#endif
+
+ ap_rputs("<dt>", r);
+ if (up_time > 0) {
+ ap_rprintf(r, "%.3g requests/sec - ",
+ (float) count / (float) up_time);
+
+ format_byte_out(r, (unsigned long)(KBYTE * (float) kbcount
+ / (float) up_time));
+ ap_rputs("/second", r);
+ }
+
+ if (count > 0) {
+ if (up_time > 0)
+ ap_rputs(" - ", r);
+ format_byte_out(r, (unsigned long)(KBYTE * (float) kbcount
+ / (float) count));
+ ap_rprintf(r, "/request - %g ms/request",
+ (float) apr_time_as_msec(duration_global) / (float) count);
+ }
+
+ ap_rputs("</dt>\n", r);
+ } /* short_report */
+ } /* ap_extended_status */
+
+ if (!short_report)
+ ap_rprintf(r, "<dt>%d requests currently being processed, "
+ "%d idle workers</dt>\n", busy, ready);
+ else
+ ap_rprintf(r, "BusyWorkers: %d\nIdleWorkers: %d\n", busy, ready);
+
+ if (!short_report)
+ ap_rputs("</dl>", r);
+
+ if (is_async) {
+ int write_completion = 0, lingering_close = 0, keep_alive = 0,
+ connections = 0, stopping = 0, procs = 0;
+ /*
+ * These differ from 'busy' and 'ready' in how gracefully finishing
+ * threads are counted. XXX: How to make this clear in the html?
+ */
+ int busy_workers = 0, idle_workers = 0;
+ if (!short_report)
+ ap_rputs("\n\n<table rules=\"all\" cellpadding=\"1%\">\n"
+ "<tr><th rowspan=\"2\">Slot</th>"
+ "<th rowspan=\"2\">PID</th>"
+ "<th rowspan=\"2\">Stopping</th>"
+ "<th colspan=\"2\">Connections</th>\n"
+ "<th colspan=\"2\">Threads</th>"
+ "<th colspan=\"3\">Async connections</th></tr>\n"
+ "<tr><th>total</th><th>accepting</th>"
+ "<th>busy</th><th>idle</th>"
+ "<th>writing</th><th>keep-alive</th><th>closing</th></tr>\n", r);
+ for (i = 0; i < server_limit; ++i) {
+ ps_record = ap_get_scoreboard_process(i);
+ if (ps_record->pid) {
+ connections += ps_record->connections;
+ write_completion += ps_record->write_completion;
+ keep_alive += ps_record->keep_alive;
+ lingering_close += ps_record->lingering_close;
+ busy_workers += thread_busy_buffer[i];
+ idle_workers += thread_idle_buffer[i];
+ procs++;
+ if (ps_record->quiescing) {
+ stopping++;
+ }
+ if (!short_report) {
+ const char *dying = "no";
+ const char *old = "";
+ if (ps_record->quiescing) {
+ dying = "yes";
+ }
+ if (ps_record->generation != mpm_generation)
+ old = " (old gen)";
+ ap_rprintf(r, "<tr><td>%u</td><td>%" APR_PID_T_FMT "</td>"
+ "<td>%s%s</td>"
+ "<td>%u</td><td>%s</td>"
+ "<td>%u</td><td>%u</td>"
+ "<td>%u</td><td>%u</td><td>%u</td>"
+ "</tr>\n",
+ i, ps_record->pid,
+ dying, old,
+ ps_record->connections,
+ ps_record->not_accepting ? "no" : "yes",
+ thread_busy_buffer[i],
+ thread_idle_buffer[i],
+ ps_record->write_completion,
+ ps_record->keep_alive,
+ ps_record->lingering_close);
+ }
+ }
+ }
+ if (!short_report) {
+ ap_rprintf(r, "<tr><td>Sum</td>"
+ "<td>%d</td><td>%d</td>"
+ "<td>%d</td><td>&nbsp;</td>"
+ "<td>%d</td><td>%d</td>"
+ "<td>%d</td><td>%d</td><td>%d</td>"
+ "</tr>\n</table>\n",
+ procs, stopping,
+ connections,
+ busy_workers, idle_workers,
+ write_completion, keep_alive, lingering_close);
+ }
+ else {
+ ap_rprintf(r, "Processes: %d\n"
+ "Stopping: %d\n"
+ "BusyWorkers: %d\n"
+ "IdleWorkers: %d\n"
+ "ConnsTotal: %d\n"
+ "ConnsAsyncWriting: %d\n"
+ "ConnsAsyncKeepAlive: %d\n"
+ "ConnsAsyncClosing: %d\n",
+ procs, stopping,
+ busy_workers, idle_workers,
+ connections,
+ write_completion, keep_alive, lingering_close);
+ }
+ }
+
+ /* send the scoreboard 'table' out */
+ if (!short_report)
+ ap_rputs("<pre>", r);
+ else
+ ap_rputs("Scoreboard: ", r);
+
+ written = 0;
+ for (i = 0; i < server_limit; ++i) {
+ for (j = 0; j < thread_limit; ++j) {
+ int indx = (i * thread_limit) + j;
+ if (stat_buffer[indx] != status_flags[SERVER_DISABLED]) {
+ ap_rputc(stat_buffer[indx], r);
+ if ((written % STATUS_MAXLINE == (STATUS_MAXLINE - 1))
+ && !short_report)
+ ap_rputs("\n", r);
+ written++;
+ }
+ }
+ }
+
+
+ if (short_report)
+ ap_rputs("\n", r);
+ else {
+ ap_rputs("</pre>\n"
+ "<p>Scoreboard Key:<br />\n"
+ "\"<b><code>_</code></b>\" Waiting for Connection, \n"
+ "\"<b><code>S</code></b>\" Starting up, \n"
+ "\"<b><code>R</code></b>\" Reading Request,<br />\n"
+ "\"<b><code>W</code></b>\" Sending Reply, \n"
+ "\"<b><code>K</code></b>\" Keepalive (read), \n"
+ "\"<b><code>D</code></b>\" DNS Lookup,<br />\n"
+ "\"<b><code>C</code></b>\" Closing connection, \n"
+ "\"<b><code>L</code></b>\" Logging, \n"
+ "\"<b><code>G</code></b>\" Gracefully finishing,<br /> \n"
+ "\"<b><code>I</code></b>\" Idle cleanup of worker, \n"
+ "\"<b><code>.</code></b>\" Open slot with no current process<br />\n"
+ "</p>\n", r);
+ if (!ap_extended_status) {
+ int j;
+ int k = 0;
+ ap_rputs("PID Key: <br />\n"
+ "<pre>\n", r);
+ for (i = 0; i < server_limit; ++i) {
+ for (j = 0; j < thread_limit; ++j) {
+ int indx = (i * thread_limit) + j;
+
+ if (stat_buffer[indx] != '.') {
+ ap_rprintf(r, " %" APR_PID_T_FMT
+ " in state: %c ", pid_buffer[i],
+ stat_buffer[indx]);
+
+ if (++k >= 3) {
+ ap_rputs("\n", r);
+ k = 0;
+ } else
+ ap_rputs(",", r);
+ }
+ }
+ }
+
+ ap_rputs("\n"
+ "</pre>\n", r);
+ }
+ }
+
+ if (ap_extended_status && !short_report) {
+ if (no_table_report)
+ ap_rputs("<hr /><h2>Server Details</h2>\n\n", r);
+ else
+ ap_rputs("\n\n<table border=\"0\"><tr>"
+ "<th>Srv</th><th>PID</th><th>Acc</th>"
+ "<th>M</th>"
+#ifdef HAVE_TIMES
+ "<th>CPU\n</th>"
+#endif
+ "<th>SS</th><th>Req</th><th>Dur</th>"
+ "<th>Conn</th><th>Child</th><th>Slot</th>"
+ "<th>Client</th><th>Protocol</th><th>VHost</th>"
+ "<th>Request</th></tr>\n\n", r);
+
+ for (i = 0; i < server_limit; ++i) {
+ for (j = 0; j < thread_limit; ++j) {
+ ap_copy_scoreboard_worker(ws_record, i, j);
+
+ if (ws_record->access_count == 0 &&
+ (ws_record->status == SERVER_READY ||
+ ws_record->status == SERVER_DEAD)) {
+ continue;
+ }
+
+ ps_record = ap_get_scoreboard_process(i);
+
+ if (ws_record->start_time == 0L)
+ req_time = 0L;
+ else
+ req_time = (long)
+ apr_time_as_msec(ws_record->stop_time -
+ ws_record->start_time);
+ if (req_time < 0L)
+ req_time = 0L;
+
+ lres = ws_record->access_count;
+ my_lres = ws_record->my_access_count;
+ conn_lres = ws_record->conn_count;
+ bytes = ws_record->bytes_served;
+ my_bytes = ws_record->my_bytes_served;
+ conn_bytes = ws_record->conn_bytes;
+ duration_slot = ws_record->duration;
+ if (ws_record->pid) { /* MPM sets per-worker pid and generation */
+ worker_pid = ws_record->pid;
+ worker_generation = ws_record->generation;
+ }
+ else {
+ worker_pid = ps_record->pid;
+ worker_generation = ps_record->generation;
+ }
+
+ if (no_table_report) {
+ if (ws_record->status == SERVER_DEAD)
+ ap_rprintf(r,
+ "<b>Server %d-%d</b> (-): %d|%lu|%lu [",
+ i, (int)worker_generation,
+ (int)conn_lres, my_lres, lres);
+ else
+ ap_rprintf(r,
+ "<b>Server %d-%d</b> (%"
+ APR_PID_T_FMT "): %d|%lu|%lu [",
+ i, (int) worker_generation,
+ worker_pid,
+ (int)conn_lres, my_lres, lres);
+
+ switch (ws_record->status) {
+ case SERVER_READY:
+ ap_rputs("Ready", r);
+ break;
+ case SERVER_STARTING:
+ ap_rputs("Starting", r);
+ break;
+ case SERVER_BUSY_READ:
+ ap_rputs("<b>Read</b>", r);
+ break;
+ case SERVER_BUSY_WRITE:
+ ap_rputs("<b>Write</b>", r);
+ break;
+ case SERVER_BUSY_KEEPALIVE:
+ ap_rputs("<b>Keepalive</b>", r);
+ break;
+ case SERVER_BUSY_LOG:
+ ap_rputs("<b>Logging</b>", r);
+ break;
+ case SERVER_BUSY_DNS:
+ ap_rputs("<b>DNS lookup</b>", r);
+ break;
+ case SERVER_CLOSING:
+ ap_rputs("<b>Closing</b>", r);
+ break;
+ case SERVER_DEAD:
+ ap_rputs("Dead", r);
+ break;
+ case SERVER_GRACEFUL:
+ ap_rputs("Graceful", r);
+ break;
+ case SERVER_IDLE_KILL:
+ ap_rputs("Dying", r);
+ break;
+ default:
+ ap_rputs("?STATE?", r);
+ break;
+ }
+
+ ap_rprintf(r, "] "
+#ifdef HAVE_TIMES
+ "u%g s%g cu%g cs%g"
+#endif
+ "\n %ld %ld %" APR_TIME_T_FMT "(",
+#ifdef HAVE_TIMES
+ ws_record->times.tms_utime / tick,
+ ws_record->times.tms_stime / tick,
+ ws_record->times.tms_cutime / tick,
+ ws_record->times.tms_cstime / tick,
+#endif
+ (long)apr_time_sec(nowtime -
+ ws_record->last_used),
+ (long) req_time, apr_time_as_msec(duration_slot));
+
+ format_byte_out(r, conn_bytes);
+ ap_rputs("|", r);
+ format_byte_out(r, my_bytes);
+ ap_rputs("|", r);
+ format_byte_out(r, bytes);
+ ap_rputs(")\n", r);
+ ap_rprintf(r,
+ " <i>%s {%s}</i> <i>(%s)</i> <b>[%s]</b><br />\n\n",
+ ap_escape_html(r->pool,
+ ws_record->client64),
+ ap_escape_html(r->pool,
+ ap_escape_logitem(r->pool,
+ ws_record->request)),
+ ap_escape_html(r->pool,
+ ws_record->protocol),
+ ap_escape_html(r->pool,
+ ws_record->vhost));
+ }
+ else { /* !no_table_report */
+ if (ws_record->status == SERVER_DEAD)
+ ap_rprintf(r,
+ "<tr><td><b>%d-%d</b></td><td>-</td><td>%d/%lu/%lu",
+ i, (int)worker_generation,
+ (int)conn_lres, my_lres, lres);
+ else
+ ap_rprintf(r,
+ "<tr><td><b>%d-%d</b></td><td>%"
+ APR_PID_T_FMT
+ "</td><td>%d/%lu/%lu",
+ i, (int)worker_generation,
+ worker_pid,
+ (int)conn_lres,
+ my_lres, lres);
+
+ switch (ws_record->status) {
+ case SERVER_READY:
+ ap_rputs("</td><td>_", r);
+ break;
+ case SERVER_STARTING:
+ ap_rputs("</td><td><b>S</b>", r);
+ break;
+ case SERVER_BUSY_READ:
+ ap_rputs("</td><td><b>R</b>", r);
+ break;
+ case SERVER_BUSY_WRITE:
+ ap_rputs("</td><td><b>W</b>", r);
+ break;
+ case SERVER_BUSY_KEEPALIVE:
+ ap_rputs("</td><td><b>K</b>", r);
+ break;
+ case SERVER_BUSY_LOG:
+ ap_rputs("</td><td><b>L</b>", r);
+ break;
+ case SERVER_BUSY_DNS:
+ ap_rputs("</td><td><b>D</b>", r);
+ break;
+ case SERVER_CLOSING:
+ ap_rputs("</td><td><b>C</b>", r);
+ break;
+ case SERVER_DEAD:
+ ap_rputs("</td><td>.", r);
+ break;
+ case SERVER_GRACEFUL:
+ ap_rputs("</td><td>G", r);
+ break;
+ case SERVER_IDLE_KILL:
+ ap_rputs("</td><td>I", r);
+ break;
+ default:
+ ap_rputs("</td><td>?", r);
+ break;
+ }
+
+ ap_rprintf(r,
+ "\n</td>"
+#ifdef HAVE_TIMES
+ "<td>%.2f</td>"
+#endif
+ "<td>%ld</td><td>%ld</td><td>%" APR_TIME_T_FMT,
+#ifdef HAVE_TIMES
+ (ws_record->times.tms_utime +
+ ws_record->times.tms_stime +
+ ws_record->times.tms_cutime +
+ ws_record->times.tms_cstime) / tick,
+#endif
+ (long)apr_time_sec(nowtime -
+ ws_record->last_used),
+ (long)req_time, apr_time_as_msec(duration_slot));
+
+ ap_rprintf(r, "</td><td>%-1.1f</td><td>%-2.2f</td><td>%-2.2f\n",
+ (float)conn_bytes / KBYTE, (float) my_bytes / MBYTE,
+ (float)bytes / MBYTE);
+
+ ap_rprintf(r, "</td><td>%s</td><td>%s</td><td nowrap>%s</td>"
+ "<td nowrap>%s</td></tr>\n\n",
+ ap_escape_html(r->pool,
+ ws_record->client64),
+ ap_escape_html(r->pool,
+ ws_record->protocol),
+ ap_escape_html(r->pool,
+ ws_record->vhost),
+ ap_escape_html(r->pool,
+ ap_escape_logitem(r->pool,
+ ws_record->request)));
+ } /* no_table_report */
+ } /* for (j...) */
+ } /* for (i...) */
+
+ if (!no_table_report) {
+ ap_rputs("</table>\n \
+<hr /> \
+<table>\n \
+<tr><th>Srv</th><td>Child Server number - generation</td></tr>\n \
+<tr><th>PID</th><td>OS process ID</td></tr>\n \
+<tr><th>Acc</th><td>Number of accesses this connection / this child / this slot</td></tr>\n \
+<tr><th>M</th><td>Mode of operation</td></tr>\n"
+
+#ifdef HAVE_TIMES
+"<tr><th>CPU</th><td>CPU usage, number of seconds</td></tr>\n"
+#endif
+
+"<tr><th>SS</th><td>Seconds since beginning of most recent request</td></tr>\n \
+<tr><th>Req</th><td>Milliseconds required to process most recent request</td></tr>\n \
+<tr><th>Dur</th><td>Sum of milliseconds required to process all requests</td></tr>\n \
+<tr><th>Conn</th><td>Kilobytes transferred this connection</td></tr>\n \
+<tr><th>Child</th><td>Megabytes transferred this child</td></tr>\n \
+<tr><th>Slot</th><td>Total megabytes transferred this slot</td></tr>\n \
+</table>\n", r);
+ }
+ } /* if (ap_extended_status && !short_report) */
+ else {
+
+ if (!short_report) {
+ ap_rputs("<hr />To obtain a full report with current status "
+ "information you need to use the "
+ "<code>ExtendedStatus On</code> directive.\n", r);
+ }
+ }
+
+ {
+ /* Run extension hooks to insert extra content. */
+ int flags =
+ (short_report ? AP_STATUS_SHORT : 0) |
+ (no_table_report ? AP_STATUS_NOTABLE : 0) |
+ (ap_extended_status ? AP_STATUS_EXTENDED : 0);
+
+ ap_run_status_hook(r, flags);
+ }
+
+ if (!short_report) {
+ ap_rputs(ap_psignature("<hr />\n",r), r);
+ ap_rputs("</body></html>\n", r);
+ }
+
+ return 0;
+}
+
+static int status_pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp)
+{
+ /* When mod_status is loaded, default our ExtendedStatus to 'on'
+ * other modules which prefer verbose scoreboards may play a similar game.
+ * If left to their own requirements, mpm modules can make do with simple
+ * scoreboard entries.
+ */
+ ap_extended_status = 1;
+ return OK;
+}
+
+static int status_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp,
+ server_rec *s)
+{
+ status_flags[SERVER_DEAD] = '.'; /* We don't want to assume these are in */
+ status_flags[SERVER_READY] = '_'; /* any particular order in scoreboard.h */
+ status_flags[SERVER_STARTING] = 'S';
+ status_flags[SERVER_BUSY_READ] = 'R';
+ status_flags[SERVER_BUSY_WRITE] = 'W';
+ status_flags[SERVER_BUSY_KEEPALIVE] = 'K';
+ status_flags[SERVER_BUSY_LOG] = 'L';
+ status_flags[SERVER_BUSY_DNS] = 'D';
+ status_flags[SERVER_CLOSING] = 'C';
+ status_flags[SERVER_GRACEFUL] = 'G';
+ status_flags[SERVER_IDLE_KILL] = 'I';
+ status_flags[SERVER_DISABLED] = ' ';
+ ap_mpm_query(AP_MPMQ_HARD_LIMIT_THREADS, &thread_limit);
+ ap_mpm_query(AP_MPMQ_HARD_LIMIT_DAEMONS, &server_limit);
+ ap_mpm_query(AP_MPMQ_MAX_THREADS, &threads_per_child);
+ /* work around buggy MPMs */
+ if (threads_per_child == 0)
+ threads_per_child = 1;
+ ap_mpm_query(AP_MPMQ_MAX_DAEMONS, &max_servers);
+ ap_mpm_query(AP_MPMQ_IS_ASYNC, &is_async);
+ return OK;
+}
+
+#ifdef HAVE_TIMES
+static void status_child_init(apr_pool_t *p, server_rec *s)
+{
+ child_pid = getpid();
+}
+#endif
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_hook_handler(status_handler, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_pre_config(status_pre_config, NULL, NULL, APR_HOOK_LAST);
+ ap_hook_post_config(status_init, NULL, NULL, APR_HOOK_MIDDLE);
+#ifdef HAVE_TIMES
+ ap_hook_child_init(status_child_init, NULL, NULL, APR_HOOK_MIDDLE);
+#endif
+}
+
+AP_DECLARE_MODULE(status) =
+{
+ STANDARD20_MODULE_STUFF,
+ NULL, /* dir config creater */
+ NULL, /* dir merger --- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ NULL, /* command table */
+ register_hooks /* register_hooks */
+};
diff --git a/modules/generators/mod_status.dep b/modules/generators/mod_status.dep
new file mode 100644
index 0000000..7ec631b
--- /dev/null
+++ b/modules/generators/mod_status.dep
@@ -0,0 +1,60 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_status.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_status.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_mpm.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_status.h"\
+
diff --git a/modules/generators/mod_status.dsp b/modules/generators/mod_status.dsp
new file mode 100644
index 0000000..4596640
--- /dev/null
+++ b/modules/generators/mod_status.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_status" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_status - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_status.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_status.mak" CFG="mod_status - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_status - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_status - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_status - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "STATUS_DECLARE_EXPORT" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "STATUS_DECLARE_EXPORT" /Fd"Release\mod_status_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_status.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_status.so" /d LONG_NAME="status_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_status.so" /base:@..\..\os\win32\BaseAddr.ref,mod_status.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_status.so" /base:@..\..\os\win32\BaseAddr.ref,mod_status.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_status.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_status - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "STATUS_DECLARE_EXPORT" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "STATUS_DECLARE_EXPORT" /Fd"Debug\mod_status_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_status.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_status.so" /d LONG_NAME="status_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_status.so" /base:@..\..\os\win32\BaseAddr.ref,mod_status.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_status.so" /base:@..\..\os\win32\BaseAddr.ref,mod_status.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_status.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_status - Win32 Release"
+# Name "mod_status - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_status.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/generators/mod_status.exp b/modules/generators/mod_status.exp
new file mode 100644
index 0000000..5438093
--- /dev/null
+++ b/modules/generators/mod_status.exp
@@ -0,0 +1 @@
+status_module
diff --git a/modules/generators/mod_status.h b/modules/generators/mod_status.h
new file mode 100644
index 0000000..721b96d
--- /dev/null
+++ b/modules/generators/mod_status.h
@@ -0,0 +1,64 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file mod_status.h
+ * @brief Status Report Extension Module to Apache
+ *
+ * @defgroup MOD_STATUS mod_status
+ * @ingroup APACHE_MODS
+ * @{
+ */
+
+#ifndef MOD_STATUS_H
+#define MOD_STATUS_H
+
+#include "ap_config.h"
+#include "httpd.h"
+
+#define AP_STATUS_SHORT (0x1) /* short, non-HTML report requested */
+#define AP_STATUS_NOTABLE (0x2) /* HTML report without tables */
+#define AP_STATUS_EXTENDED (0x4) /* detailed report */
+
+#if !defined(WIN32)
+#define STATUS_DECLARE(type) type
+#define STATUS_DECLARE_NONSTD(type) type
+#define STATUS_DECLARE_DATA
+#elif defined(STATUS_DECLARE_STATIC)
+#define STATUS_DECLARE(type) type __stdcall
+#define STATUS_DECLARE_NONSTD(type) type
+#define STATUS_DECLARE_DATA
+#elif defined(STATUS_DECLARE_EXPORT)
+#define STATUS_DECLARE(type) __declspec(dllexport) type __stdcall
+#define STATUS_DECLARE_NONSTD(type) __declspec(dllexport) type
+#define STATUS_DECLARE_DATA __declspec(dllexport)
+#else
+#define STATUS_DECLARE(type) __declspec(dllimport) type __stdcall
+#define STATUS_DECLARE_NONSTD(type) __declspec(dllimport) type
+#define STATUS_DECLARE_DATA __declspec(dllimport)
+#endif
+
+/* Optional hooks which can insert extra content into the mod_status
+ * output. FLAGS will be set to the bitwise OR of any of the
+ * AP_STATUS_* flags.
+ *
+ * Implementations of this hook should generate content using
+ * functions in the ap_rputs/ap_rprintf family; each hook should
+ * return OK or DECLINED. */
+APR_DECLARE_EXTERNAL_HOOK(ap, STATUS, int, status_hook,
+ (request_rec *r, int flags))
+#endif
+/** @} */
diff --git a/modules/generators/mod_status.mak b/modules/generators/mod_status.mak
new file mode 100644
index 0000000..168bbc8
--- /dev/null
+++ b/modules/generators/mod_status.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_status.dsp
+!IF "$(CFG)" == ""
+CFG=mod_status - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_status - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_status - Win32 Release" && "$(CFG)" != "mod_status - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_status.mak" CFG="mod_status - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_status - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_status - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_status - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_status.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_status.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_status.obj"
+ -@erase "$(INTDIR)\mod_status.res"
+ -@erase "$(INTDIR)\mod_status_src.idb"
+ -@erase "$(INTDIR)\mod_status_src.pdb"
+ -@erase "$(OUTDIR)\mod_status.exp"
+ -@erase "$(OUTDIR)\mod_status.lib"
+ -@erase "$(OUTDIR)\mod_status.pdb"
+ -@erase "$(OUTDIR)\mod_status.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "STATUS_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_status_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_status.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_status.so" /d LONG_NAME="status_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_status.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_status.pdb" /debug /out:"$(OUTDIR)\mod_status.so" /implib:"$(OUTDIR)\mod_status.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_status.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_status.obj" \
+ "$(INTDIR)\mod_status.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_status.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_status.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_status.so"
+ if exist .\Release\mod_status.so.manifest mt.exe -manifest .\Release\mod_status.so.manifest -outputresource:.\Release\mod_status.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_status - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_status.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_status.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_status.obj"
+ -@erase "$(INTDIR)\mod_status.res"
+ -@erase "$(INTDIR)\mod_status_src.idb"
+ -@erase "$(INTDIR)\mod_status_src.pdb"
+ -@erase "$(OUTDIR)\mod_status.exp"
+ -@erase "$(OUTDIR)\mod_status.lib"
+ -@erase "$(OUTDIR)\mod_status.pdb"
+ -@erase "$(OUTDIR)\mod_status.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "STATUS_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_status_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_status.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_status.so" /d LONG_NAME="status_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_status.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_status.pdb" /debug /out:"$(OUTDIR)\mod_status.so" /implib:"$(OUTDIR)\mod_status.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_status.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_status.obj" \
+ "$(INTDIR)\mod_status.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_status.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_status.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_status.so"
+ if exist .\Debug\mod_status.so.manifest mt.exe -manifest .\Debug\mod_status.so.manifest -outputresource:.\Debug\mod_status.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_status.dep")
+!INCLUDE "mod_status.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_status.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_status - Win32 Release" || "$(CFG)" == "mod_status - Win32 Debug"
+
+!IF "$(CFG)" == "mod_status - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\generators"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\generators"
+
+!ELSEIF "$(CFG)" == "mod_status - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\generators"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\generators"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_status - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\generators"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\generators"
+
+!ELSEIF "$(CFG)" == "mod_status - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\generators"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\generators"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_status - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\generators"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\generators"
+
+!ELSEIF "$(CFG)" == "mod_status - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\generators"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\generators"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_status - Win32 Release"
+
+
+"$(INTDIR)\mod_status.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_status.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_status.so" /d LONG_NAME="status_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_status - Win32 Debug"
+
+
+"$(INTDIR)\mod_status.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_status.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_status.so" /d LONG_NAME="status_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_status.c
+
+"$(INTDIR)\mod_status.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/generators/mod_suexec.c b/modules/generators/mod_suexec.c
new file mode 100644
index 0000000..a71b0a8
--- /dev/null
+++ b/modules/generators/mod_suexec.c
@@ -0,0 +1,139 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_request.h"
+#include "apr_strings.h"
+#include "unixd.h"
+#include "mpm_common.h"
+#include "mod_suexec.h"
+
+module AP_MODULE_DECLARE_DATA suexec_module;
+
+/*
+ * Create a configuration specific to this module for a server or directory
+ * location, and fill it with the default settings.
+ */
+static void *mkconfig(apr_pool_t *p)
+{
+ suexec_config_t *cfg = apr_palloc(p, sizeof(suexec_config_t));
+
+ cfg->active = 0;
+ return cfg;
+}
+
+/*
+ * Respond to a callback to create configuration record for a server or
+ * vhost environment.
+ */
+static void *create_mconfig_for_server(apr_pool_t *p, server_rec *s)
+{
+ return mkconfig(p);
+}
+
+/*
+ * Respond to a callback to create a config record for a specific directory.
+ */
+static void *create_mconfig_for_directory(apr_pool_t *p, char *dir)
+{
+ return mkconfig(p);
+}
+
+static const char *set_suexec_ugid(cmd_parms *cmd, void *mconfig,
+ const char *uid, const char *gid)
+{
+ suexec_config_t *cfg = (suexec_config_t *) mconfig;
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_CONTEXT);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ if (!ap_unixd_config.suexec_enabled) {
+ return apr_pstrcat(cmd->pool, "SuexecUserGroup configured, but "
+ "suEXEC is disabled: ",
+ ap_unixd_config.suexec_disabled_reason, NULL);
+ }
+
+ cfg->ugid.uid = ap_uname2id(uid);
+ cfg->ugid.gid = ap_gname2id(gid);
+ cfg->ugid.userdir = 0;
+ cfg->active = 1;
+
+ return NULL;
+}
+
+static ap_unix_identity_t *get_suexec_id_doer(const request_rec *r)
+{
+ suexec_config_t *cfg =
+ (suexec_config_t *) ap_get_module_config(r->per_dir_config, &suexec_module);
+
+ return cfg->active ? &cfg->ugid : NULL;
+}
+
+#define SUEXEC_POST_CONFIG_USERDATA "suexec_post_config_userdata"
+static int suexec_post_config(apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ void *reported;
+
+ apr_pool_userdata_get(&reported, SUEXEC_POST_CONFIG_USERDATA,
+ s->process->pool);
+
+ if ((reported == NULL) && ap_unixd_config.suexec_enabled) {
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, APLOGNO(01232)
+ "suEXEC mechanism enabled (wrapper: %s)", SUEXEC_BIN);
+
+ apr_pool_userdata_set((void *)1, SUEXEC_POST_CONFIG_USERDATA,
+ apr_pool_cleanup_null, s->process->pool);
+ }
+
+ return OK;
+}
+#undef SUEXEC_POST_CONFIG_USERDATA
+
+/*
+ * Define the directives specific to this module. This structure is referenced
+ * later by the 'module' structure.
+ */
+static const command_rec suexec_cmds[] =
+{
+ /* XXX - Another important reason not to allow this in .htaccess is that
+ * the ap_[ug]name2id() is not thread-safe */
+ AP_INIT_TAKE2("SuexecUserGroup", set_suexec_ugid, NULL, RSRC_CONF,
+ "User and group for spawned processes"),
+ { NULL }
+};
+
+static void suexec_hooks(apr_pool_t *p)
+{
+ ap_hook_get_suexec_identity(get_suexec_id_doer,NULL,NULL,APR_HOOK_MIDDLE);
+ ap_hook_post_config(suexec_post_config,NULL,NULL,APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(suexec) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_mconfig_for_directory, /* create per-dir config */
+ NULL, /* merge per-dir config */
+ create_mconfig_for_server, /* server config */
+ NULL, /* merge server config */
+ suexec_cmds, /* command table */
+ suexec_hooks /* register hooks */
+};
diff --git a/modules/generators/mod_suexec.h b/modules/generators/mod_suexec.h
new file mode 100644
index 0000000..80e2504
--- /dev/null
+++ b/modules/generators/mod_suexec.h
@@ -0,0 +1,33 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file mod_suexec.h
+ * @brief SuExec Extension Module for Apache
+ *
+ * @defgroup MOD_SUEXEC mod_suexec
+ * @ingroup APACHE_MODS
+ * @{
+ */
+
+#include "unixd.h"
+
+typedef struct {
+ ap_unix_identity_t ugid;
+ int active;
+} suexec_config_t;
+
+/** @}*/
diff --git a/modules/http/.indent.pro b/modules/http/.indent.pro
new file mode 100644
index 0000000..a9fbe9f
--- /dev/null
+++ b/modules/http/.indent.pro
@@ -0,0 +1,54 @@
+-i4 -npsl -di0 -br -nce -d0 -cli0 -npcs -nfc1
+-TBUFF
+-TFILE
+-TTRANS
+-TUINT4
+-T_trans
+-Tallow_options_t
+-Tapache_sfio
+-Tarray_header
+-Tbool_int
+-Tbuf_area
+-Tbuff_struct
+-Tbuffy
+-Tcmd_how
+-Tcmd_parms
+-Tcommand_rec
+-Tcommand_struct
+-Tconn_rec
+-Tcore_dir_config
+-Tcore_server_config
+-Tdir_maker_func
+-Tevent
+-Tglobals_s
+-Thandler_func
+-Thandler_rec
+-Tjoblist_s
+-Tlisten_rec
+-Tmerger_func
+-Tmode_t
+-Tmodule
+-Tmodule_struct
+-Tmutex
+-Tn_long
+-Tother_child_rec
+-Toverrides_t
+-Tparent_score
+-Tpid_t
+-Tpiped_log
+-Tpool
+-Trequest_rec
+-Trequire_line
+-Trlim_t
+-Tscoreboard
+-Tsemaphore
+-Tserver_addr_rec
+-Tserver_rec
+-Tserver_rec_chain
+-Tshort_score
+-Ttable
+-Ttable_entry
+-Tthread
+-Tu_wide_int
+-Tvtime_t
+-Twide_int
diff --git a/modules/http/Makefile.in b/modules/http/Makefile.in
new file mode 100644
index 0000000..167b343
--- /dev/null
+++ b/modules/http/Makefile.in
@@ -0,0 +1,3 @@
+
+include $(top_srcdir)/build/special.mk
+
diff --git a/modules/http/byterange_filter.c b/modules/http/byterange_filter.c
new file mode 100644
index 0000000..5ebe853
--- /dev/null
+++ b/modules/http/byterange_filter.c
@@ -0,0 +1,610 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * byterange_filter.c --- HTTP byterange filter and friends.
+ */
+
+#include "apr.h"
+
+#include "apr_strings.h"
+#include "apr_buckets.h"
+#include "apr_lib.h"
+#include "apr_signal.h"
+
+#define APR_WANT_STDIO /* for sscanf */
+#define APR_WANT_STRFUNC
+#define APR_WANT_MEMFUNC
+#include "apr_want.h"
+
+#include "util_filter.h"
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_protocol.h"
+#include "http_main.h"
+#include "http_request.h"
+#include "http_vhost.h"
+#include "http_log.h" /* For errors detected in basic auth common
+ * support code... */
+#include "apr_date.h" /* For apr_date_parse_http and APR_DATE_BAD */
+#include "util_charset.h"
+#include "util_ebcdic.h"
+#include "util_time.h"
+
+#include "mod_core.h"
+
+#if APR_HAVE_STDARG_H
+#include <stdarg.h>
+#endif
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#ifndef AP_DEFAULT_MAX_RANGES
+#define AP_DEFAULT_MAX_RANGES 200
+#endif
+#ifndef AP_DEFAULT_MAX_OVERLAPS
+#define AP_DEFAULT_MAX_OVERLAPS 20
+#endif
+#ifndef AP_DEFAULT_MAX_REVERSALS
+#define AP_DEFAULT_MAX_REVERSALS 20
+#endif
+
+#define MAX_PREALLOC_RANGES 100
+
+APLOG_USE_MODULE(http);
+
+typedef struct indexes_t {
+ apr_off_t start;
+ apr_off_t end;
+} indexes_t;
+
+/*
+ * Returns: number of ranges (merged) or -1 for no-good
+ */
+static int ap_set_byterange(request_rec *r, apr_off_t clength,
+ apr_array_header_t **indexes,
+ int *overlaps, int *reversals)
+{
+ const char *range;
+ const char *ct;
+ char *cur;
+ apr_array_header_t *merged;
+ int num_ranges = 0, unsatisfiable = 0;
+ apr_off_t ostart = 0, oend = 0, sum_lengths = 0;
+ int in_merge = 0;
+ indexes_t *idx;
+ int ranges = 1;
+ int i;
+ const char *it;
+
+ *overlaps = 0;
+ *reversals = 0;
+
+ if (r->assbackwards) {
+ return 0;
+ }
+
+ /*
+ * Check for Range request-header (HTTP/1.1) or Request-Range for
+ * backwards-compatibility with second-draft Luotonen/Franks
+ * byte-ranges (e.g. Netscape Navigator 2-3).
+ *
+ * We support this form, with Request-Range, and (farther down) we
+ * send multipart/x-byteranges instead of multipart/byteranges for
+ * Request-Range based requests to work around a bug in Netscape
+ * Navigator 2-3 and MSIE 3.
+ */
+
+ if (!(range = apr_table_get(r->headers_in, "Range"))) {
+ range = apr_table_get(r->headers_in, "Request-Range");
+ }
+
+ if (!range || strncasecmp(range, "bytes=", 6) || r->status != HTTP_OK) {
+ return 0;
+ }
+
+ /* is content already a single range? */
+ if (apr_table_get(r->headers_out, "Content-Range")) {
+ return 0;
+ }
+
+ /* is content already a multiple range? */
+ if ((ct = apr_table_get(r->headers_out, "Content-Type"))
+ && (!strncasecmp(ct, "multipart/byteranges", 20)
+ || !strncasecmp(ct, "multipart/x-byteranges", 22))) {
+ return 0;
+ }
+
+ /*
+ * Check the If-Range header for Etag or Date.
+ */
+ if (AP_CONDITION_NOMATCH == ap_condition_if_range(r, r->headers_out)) {
+ return 0;
+ }
+
+ range += 6;
+ it = range;
+ while (*it) {
+ if (*it++ == ',') {
+ ranges++;
+ }
+ }
+ it = range;
+ if (ranges > MAX_PREALLOC_RANGES) {
+ ranges = MAX_PREALLOC_RANGES;
+ }
+ *indexes = apr_array_make(r->pool, ranges, sizeof(indexes_t));
+ while ((cur = ap_getword(r->pool, &range, ','))) {
+ char *dash;
+ apr_off_t number, start, end;
+
+ if (!*cur)
+ break;
+
+ /*
+ * Per RFC 2616 14.35.1: If there is at least one syntactically invalid
+ * byte-range-spec, we must ignore the whole header.
+ */
+
+ if (!(dash = strchr(cur, '-'))) {
+ return 0;
+ }
+
+ if (dash == cur) {
+ /* In the form "-5" */
+ if (!ap_parse_strict_length(&number, dash+1)) {
+ return 0;
+ }
+ if (number < 1) {
+ return 0;
+ }
+ start = clength - number;
+ end = clength - 1;
+ }
+ else {
+ *dash++ = '\0';
+ if (!ap_parse_strict_length(&number, cur)) {
+ return 0;
+ }
+ start = number;
+ if (*dash) {
+ if (!ap_parse_strict_length(&number, dash)) {
+ return 0;
+ }
+ end = number;
+ if (start > end) {
+ return 0;
+ }
+ }
+ else { /* "5-" */
+ end = clength - 1;
+ /*
+ * special case: 0-
+ * ignore all other ranges provided
+ * return as a single range: 0-
+ */
+ if (start == 0) {
+ num_ranges = 0;
+ sum_lengths = 0;
+ in_merge = 1;
+ oend = end;
+ ostart = start;
+ apr_array_clear(*indexes);
+ break;
+ }
+ }
+ }
+
+ if (start < 0) {
+ start = 0;
+ }
+ if (start >= clength) {
+ unsatisfiable = 1;
+ continue;
+ }
+ if (end >= clength) {
+ end = clength - 1;
+ }
+
+ if (!in_merge) {
+ /* new set */
+ ostart = start;
+ oend = end;
+ in_merge = 1;
+ continue;
+ }
+ in_merge = 0;
+
+ if (start >= ostart && end <= oend) {
+ in_merge = 1;
+ }
+
+ if (start < ostart && end >= ostart-1) {
+ ostart = start;
+ ++*reversals;
+ in_merge = 1;
+ }
+ if (end >= oend && start <= oend+1 ) {
+ oend = end;
+ in_merge = 1;
+ }
+
+ if (in_merge) {
+ ++*overlaps;
+ continue;
+ } else {
+ idx = (indexes_t *)apr_array_push(*indexes);
+ idx->start = ostart;
+ idx->end = oend;
+ sum_lengths += oend - ostart + 1;
+ /* new set again */
+ in_merge = 1;
+ ostart = start;
+ oend = end;
+ num_ranges++;
+ }
+ }
+
+ if (in_merge) {
+ idx = (indexes_t *)apr_array_push(*indexes);
+ idx->start = ostart;
+ idx->end = oend;
+ sum_lengths += oend - ostart + 1;
+ num_ranges++;
+ }
+ else if (num_ranges == 0 && unsatisfiable) {
+ /* If all ranges are unsatisfiable, we should return 416 */
+ return -1;
+ }
+ if (sum_lengths > clength) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "Sum of ranges larger than file, ignoring.");
+ return 0;
+ }
+
+ /*
+ * create the merged table now, now that we know we need it
+ */
+ merged = apr_array_make(r->pool, num_ranges, sizeof(char *));
+ idx = (indexes_t *)(*indexes)->elts;
+ for (i = 0; i < (*indexes)->nelts; i++, idx++) {
+ char **new = (char **)apr_array_push(merged);
+ *new = apr_psprintf(r->pool, "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT,
+ idx->start, idx->end);
+ }
+
+ r->status = HTTP_PARTIAL_CONTENT;
+ r->range = apr_array_pstrcat(r->pool, merged, ',');
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01583)
+ "Range: %s | %s (%d : %d : %"APR_OFF_T_FMT")",
+ it, r->range, *overlaps, *reversals, clength);
+
+ return num_ranges;
+}
+
+/*
+ * Here we try to be compatible with clients that want multipart/x-byteranges
+ * instead of multipart/byteranges (also see above), as per HTTP/1.1. We
+ * look for the Request-Range header (e.g. Netscape 2 and 3) as an indication
+ * that the browser supports an older protocol. We also check User-Agent
+ * for Microsoft Internet Explorer 3, which needs this as well.
+ */
+static int use_range_x(request_rec *r)
+{
+ const char *ua;
+ return (apr_table_get(r->headers_in, "Request-Range")
+ || ((ua = apr_table_get(r->headers_in, "User-Agent"))
+ && ap_strstr_c(ua, "MSIE 3")));
+}
+
+#define BYTERANGE_FMT "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT "/%" APR_OFF_T_FMT
+
+static apr_status_t copy_brigade_range(apr_bucket_brigade *bb,
+ apr_bucket_brigade *bbout,
+ apr_off_t start,
+ apr_off_t end)
+{
+ apr_bucket *first = NULL, *last = NULL, *out_first = NULL, *e;
+ apr_uint64_t pos = 0, off_first = 0, off_last = 0;
+ apr_status_t rv;
+ apr_uint64_t start64, end64;
+ apr_off_t pofft = 0;
+
+ /*
+ * Once we know that start and end are >= 0 convert everything to apr_uint64_t.
+ * See the comments in apr_brigade_partition why.
+ * In short apr_off_t (for values >= 0)and apr_size_t fit into apr_uint64_t.
+ */
+ start64 = (apr_uint64_t)start;
+ end64 = (apr_uint64_t)end;
+
+ if (start < 0 || end < 0 || start64 > end64)
+ return APR_EINVAL;
+
+ for (e = APR_BRIGADE_FIRST(bb);
+ e != APR_BRIGADE_SENTINEL(bb);
+ e = APR_BUCKET_NEXT(e))
+ {
+ apr_uint64_t elen64;
+ /* we know that no bucket has undefined length (-1) */
+ AP_DEBUG_ASSERT(e->length != (apr_size_t)(-1));
+ elen64 = (apr_uint64_t)e->length;
+ if (!first && (elen64 + pos > start64)) {
+ first = e;
+ off_first = pos;
+ }
+ if (elen64 + pos > end64) {
+ last = e;
+ off_last = pos;
+ break;
+ }
+ pos += elen64;
+ }
+ if (!first || !last)
+ return APR_EINVAL;
+
+ e = first;
+ while (1)
+ {
+ apr_bucket *copy;
+ AP_DEBUG_ASSERT(e != APR_BRIGADE_SENTINEL(bb));
+ rv = apr_bucket_copy(e, &copy);
+ if (rv != APR_SUCCESS) {
+ apr_brigade_cleanup(bbout);
+ return rv;
+ }
+
+ APR_BRIGADE_INSERT_TAIL(bbout, copy);
+ if (e == first) {
+ if (off_first != start64) {
+ rv = apr_bucket_split(copy, (apr_size_t)(start64 - off_first));
+ if (rv != APR_SUCCESS) {
+ apr_brigade_cleanup(bbout);
+ return rv;
+ }
+ out_first = APR_BUCKET_NEXT(copy);
+ apr_bucket_delete(copy);
+ }
+ else {
+ out_first = copy;
+ }
+ }
+ if (e == last) {
+ if (e == first) {
+ off_last += start64 - off_first;
+ copy = out_first;
+ }
+ if (end64 - off_last != (apr_uint64_t)e->length) {
+ rv = apr_bucket_split(copy, (apr_size_t)(end64 + 1 - off_last));
+ if (rv != APR_SUCCESS) {
+ apr_brigade_cleanup(bbout);
+ return rv;
+ }
+ copy = APR_BUCKET_NEXT(copy);
+ if (copy != APR_BRIGADE_SENTINEL(bbout)) {
+ apr_bucket_delete(copy);
+ }
+ }
+ break;
+ }
+ e = APR_BUCKET_NEXT(e);
+ }
+
+ AP_DEBUG_ASSERT(APR_SUCCESS == apr_brigade_length(bbout, 1, &pofft));
+ pos = (apr_uint64_t)pofft;
+ AP_DEBUG_ASSERT(pos == end64 - start64 + 1);
+ return APR_SUCCESS;
+}
+
+static apr_status_t send_416(ap_filter_t *f, apr_bucket_brigade *tmpbb)
+{
+ apr_bucket *e;
+ conn_rec *c = f->r->connection;
+ ap_remove_output_filter(f);
+ f->r->status = HTTP_OK;
+ e = ap_bucket_error_create(HTTP_RANGE_NOT_SATISFIABLE, NULL,
+ f->r->pool, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(tmpbb, e);
+ e = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(tmpbb, e);
+ return ap_pass_brigade(f->next, tmpbb);
+}
+
+AP_CORE_DECLARE_NONSTD(apr_status_t) ap_byterange_filter(ap_filter_t *f,
+ apr_bucket_brigade *bb)
+{
+ request_rec *r = f->r;
+ conn_rec *c = r->connection;
+ apr_bucket *e;
+ apr_bucket_brigade *bsend;
+ apr_bucket_brigade *tmpbb;
+ apr_off_t range_start;
+ apr_off_t range_end;
+ apr_off_t clength = 0;
+ apr_status_t rv;
+ int found = 0;
+ int num_ranges;
+ char *bound_head = NULL;
+ apr_array_header_t *indexes;
+ indexes_t *idx;
+ int i;
+ int original_status;
+ int max_ranges, max_overlaps, max_reversals;
+ int overlaps = 0, reversals = 0;
+ core_dir_config *core_conf = ap_get_core_module_config(r->per_dir_config);
+
+ max_ranges = ( (core_conf->max_ranges >= 0 || core_conf->max_ranges == AP_MAXRANGES_UNLIMITED)
+ ? core_conf->max_ranges
+ : AP_DEFAULT_MAX_RANGES );
+ max_overlaps = ( (core_conf->max_overlaps >= 0 || core_conf->max_overlaps == AP_MAXRANGES_UNLIMITED)
+ ? core_conf->max_overlaps
+ : AP_DEFAULT_MAX_OVERLAPS );
+ max_reversals = ( (core_conf->max_reversals >= 0 || core_conf->max_reversals == AP_MAXRANGES_UNLIMITED)
+ ? core_conf->max_reversals
+ : AP_DEFAULT_MAX_REVERSALS );
+ /*
+ * Iterate through the brigade until reaching EOS or a bucket with
+ * unknown length.
+ */
+ for (e = APR_BRIGADE_FIRST(bb);
+ (e != APR_BRIGADE_SENTINEL(bb) && !APR_BUCKET_IS_EOS(e)
+ && e->length != (apr_size_t)-1);
+ e = APR_BUCKET_NEXT(e)) {
+ clength += e->length;
+ }
+
+ /*
+ * Don't attempt to do byte range work if this brigade doesn't
+ * contain an EOS, or if any of the buckets has an unknown length;
+ * this avoids the cases where it is expensive to perform
+ * byteranging (i.e. may require arbitrary amounts of memory).
+ */
+ if (!APR_BUCKET_IS_EOS(e) || clength <= 0) {
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ original_status = r->status;
+ num_ranges = ap_set_byterange(r, clength, &indexes, &overlaps, &reversals);
+
+ /* No Ranges or we hit a limit? We have nothing to do, get out of the way. */
+ if (num_ranges == 0 ||
+ (max_ranges >= 0 && num_ranges > max_ranges) ||
+ (max_overlaps >= 0 && overlaps > max_overlaps) ||
+ (max_reversals >= 0 && reversals > max_reversals)) {
+ r->status = original_status;
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ /* this brigade holds what we will be sending */
+ bsend = apr_brigade_create(r->pool, c->bucket_alloc);
+
+ if (num_ranges < 0)
+ return send_416(f, bsend);
+
+ if (num_ranges > 1) {
+ /* Is ap_make_content_type required here? */
+ const char *orig_ct = ap_make_content_type(r, r->content_type);
+
+ ap_set_content_type(r, apr_pstrcat(r->pool, "multipart",
+ use_range_x(r) ? "/x-" : "/",
+ "byteranges; boundary=",
+ ap_multipart_boundary, NULL));
+
+ if (orig_ct) {
+ bound_head = apr_pstrcat(r->pool,
+ CRLF "--", ap_multipart_boundary,
+ CRLF "Content-type: ",
+ orig_ct,
+ CRLF "Content-range: bytes ",
+ NULL);
+ }
+ else {
+ /* if we have no type for the content, do our best */
+ bound_head = apr_pstrcat(r->pool,
+ CRLF "--", ap_multipart_boundary,
+ CRLF "Content-range: bytes ",
+ NULL);
+ }
+ ap_xlate_proto_to_ascii(bound_head, strlen(bound_head));
+ }
+
+ tmpbb = apr_brigade_create(r->pool, c->bucket_alloc);
+
+ idx = (indexes_t *)indexes->elts;
+ for (i = 0; i < indexes->nelts; i++, idx++) {
+ range_start = idx->start;
+ range_end = idx->end;
+
+ rv = copy_brigade_range(bb, tmpbb, range_start, range_end);
+ if (rv != APR_SUCCESS ) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01584)
+ "copy_brigade_range() failed [%" APR_OFF_T_FMT
+ "-%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT "]",
+ range_start, range_end, clength);
+ continue;
+ }
+ found = 1;
+
+ /*
+ * For single range requests, we must produce Content-Range header.
+ * Otherwise, we need to produce the multipart boundaries.
+ */
+ if (num_ranges == 1) {
+ apr_table_setn(r->headers_out, "Content-Range",
+ apr_psprintf(r->pool, "bytes " BYTERANGE_FMT,
+ range_start, range_end, clength));
+ }
+ else {
+ char *ts;
+
+ e = apr_bucket_pool_create(bound_head, strlen(bound_head),
+ r->pool, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bsend, e);
+
+ ts = apr_psprintf(r->pool, BYTERANGE_FMT CRLF CRLF,
+ range_start, range_end, clength);
+ ap_xlate_proto_to_ascii(ts, strlen(ts));
+ e = apr_bucket_pool_create(ts, strlen(ts), r->pool,
+ c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bsend, e);
+ }
+
+ APR_BRIGADE_CONCAT(bsend, tmpbb);
+ if (i && !(i & 0x1F)) {
+ /*
+ * Every now and then, pass what we have down the filter chain.
+ * In this case, the content-length filter cannot calculate and
+ * set the content length and we must remove any Content-Length
+ * header already present.
+ */
+ apr_table_unset(r->headers_out, "Content-Length");
+ if ((rv = ap_pass_brigade(f->next, bsend)) != APR_SUCCESS)
+ return rv;
+ apr_brigade_cleanup(bsend);
+ }
+ }
+
+ if (found == 0) {
+ /* bsend is assumed to be empty if we get here. */
+ return send_416(f, bsend);
+ }
+
+ if (num_ranges > 1) {
+ char *end;
+
+ /* add the final boundary */
+ end = apr_pstrcat(r->pool, CRLF "--", ap_multipart_boundary, "--" CRLF,
+ NULL);
+ ap_xlate_proto_to_ascii(end, strlen(end));
+ e = apr_bucket_pool_create(end, strlen(end), r->pool, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bsend, e);
+ }
+
+ e = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bsend, e);
+
+ /* we're done with the original content - all of our data is in bsend. */
+ apr_brigade_cleanup(bb);
+ apr_brigade_destroy(tmpbb);
+
+ /* send our multipart output */
+ return ap_pass_brigade(f->next, bsend);
+}
diff --git a/modules/http/chunk_filter.c b/modules/http/chunk_filter.c
new file mode 100644
index 0000000..cb1501a
--- /dev/null
+++ b/modules/http/chunk_filter.c
@@ -0,0 +1,197 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * chunk_filter.c --- HTTP/1.1 chunked transfer encoding filter.
+ */
+
+#include "apr_strings.h"
+#include "apr_thread_proc.h" /* for RLIMIT stuff */
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_connection.h"
+#include "http_core.h"
+#include "http_protocol.h" /* For index_of_response(). Grump. */
+#include "http_request.h"
+
+#include "util_filter.h"
+#include "util_ebcdic.h"
+#include "ap_mpm.h"
+#include "scoreboard.h"
+
+#include "mod_core.h"
+
+/*
+ * A pointer to this is used to memorize in the filter context that a bad
+ * gateway error bucket had been seen. It is used as an invented unique pointer.
+ */
+static char bad_gateway_seen;
+
+apr_status_t ap_http_chunk_filter(ap_filter_t *f, apr_bucket_brigade *b)
+{
+#define ASCII_CRLF "\015\012"
+#define ASCII_ZERO "\060"
+ conn_rec *c = f->r->connection;
+ apr_bucket_brigade *more, *tmp;
+ apr_bucket *e;
+ apr_status_t rv;
+
+ for (more = tmp = NULL; b; b = more, more = NULL) {
+ apr_off_t bytes = 0;
+ apr_bucket *eos = NULL;
+ apr_bucket *flush = NULL;
+ /* XXX: chunk_hdr must remain at this scope since it is used in a
+ * transient bucket.
+ */
+ char chunk_hdr[20]; /* enough space for the snprintf below */
+
+
+ for (e = APR_BRIGADE_FIRST(b);
+ e != APR_BRIGADE_SENTINEL(b);
+ e = APR_BUCKET_NEXT(e))
+ {
+ if (APR_BUCKET_IS_EOS(e)) {
+ /* there shouldn't be anything after the eos */
+ ap_remove_output_filter(f);
+ eos = e;
+ break;
+ }
+ if (AP_BUCKET_IS_ERROR(e)
+ && (((ap_bucket_error *)(e->data))->status
+ == HTTP_BAD_GATEWAY)) {
+ /*
+ * We had a broken backend. Memorize this in the filter
+ * context.
+ */
+ f->ctx = &bad_gateway_seen;
+ continue;
+ }
+ if (APR_BUCKET_IS_FLUSH(e)) {
+ flush = e;
+ if (e != APR_BRIGADE_LAST(b)) {
+ more = apr_brigade_split_ex(b, APR_BUCKET_NEXT(e), tmp);
+ }
+ break;
+ }
+ else if (e->length == (apr_size_t)-1) {
+ /* unknown amount of data (e.g. a pipe) */
+ const char *data;
+ apr_size_t len;
+
+ rv = apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ if (len > 0) {
+ /*
+ * There may be a new next bucket representing the
+ * rest of the data stream on which a read() may
+ * block so we pass down what we have so far.
+ */
+ bytes += len;
+ more = apr_brigade_split_ex(b, APR_BUCKET_NEXT(e), tmp);
+ break;
+ }
+ else {
+ /* If there was nothing in this bucket then we can
+ * safely move on to the next one without pausing
+ * to pass down what we have counted up so far.
+ */
+ continue;
+ }
+ }
+ else {
+ bytes += e->length;
+ }
+ }
+
+ /*
+ * XXX: if there aren't very many bytes at this point it may
+ * be a good idea to set them aside and return for more,
+ * unless we haven't finished counting this brigade yet.
+ */
+ /* if there are content bytes, then wrap them in a chunk */
+ if (bytes > 0) {
+ apr_size_t hdr_len;
+ /*
+ * Insert the chunk header, specifying the number of bytes in
+ * the chunk.
+ */
+ hdr_len = apr_snprintf(chunk_hdr, sizeof(chunk_hdr),
+ "%" APR_UINT64_T_HEX_FMT CRLF, (apr_uint64_t)bytes);
+ ap_xlate_proto_to_ascii(chunk_hdr, hdr_len);
+ e = apr_bucket_transient_create(chunk_hdr, hdr_len,
+ c->bucket_alloc);
+ APR_BRIGADE_INSERT_HEAD(b, e);
+
+ /*
+ * Insert the end-of-chunk CRLF before an EOS or
+ * FLUSH bucket, or appended to the brigade
+ */
+ e = apr_bucket_immortal_create(ASCII_CRLF, 2, c->bucket_alloc);
+ if (eos != NULL) {
+ APR_BUCKET_INSERT_BEFORE(eos, e);
+ }
+ else if (flush != NULL) {
+ APR_BUCKET_INSERT_BEFORE(flush, e);
+ }
+ else {
+ APR_BRIGADE_INSERT_TAIL(b, e);
+ }
+ }
+
+ /* RFC 2616, Section 3.6.1
+ *
+ * If there is an EOS bucket, then prefix it with:
+ * 1) the last-chunk marker ("0" CRLF)
+ * 2) the trailer
+ * 3) the end-of-chunked body CRLF
+ *
+ * We only do this if we have not seen an error bucket with
+ * status HTTP_BAD_GATEWAY. We have memorized an
+ * error bucket that we had seen in the filter context.
+ * The error bucket with status HTTP_BAD_GATEWAY indicates that the
+ * connection to the backend (mod_proxy) broke in the middle of the
+ * response. In order to signal the client that something went wrong
+ * we do not create the last-chunk marker and set c->keepalive to
+ * AP_CONN_CLOSE in the core output filter.
+ *
+ * XXX: it would be nice to combine this with the end-of-chunk
+ * marker above, but this is a bit more straight-forward for
+ * now.
+ */
+ if (eos && !f->ctx) {
+ /* XXX: (2) trailers ... does not yet exist */
+ e = apr_bucket_immortal_create(ASCII_ZERO ASCII_CRLF
+ /* <trailers> */
+ ASCII_CRLF, 5, c->bucket_alloc);
+ APR_BUCKET_INSERT_BEFORE(eos, e);
+ }
+
+ /* pass the brigade to the next filter. */
+ rv = ap_pass_brigade(f->next, b);
+ apr_brigade_cleanup(b);
+ if (rv != APR_SUCCESS || eos != NULL) {
+ return rv;
+ }
+ tmp = b;
+ }
+ return APR_SUCCESS;
+}
diff --git a/modules/http/config.m4 b/modules/http/config.m4
new file mode 100644
index 0000000..6496007
--- /dev/null
+++ b/modules/http/config.m4
@@ -0,0 +1,20 @@
+dnl modules enabled in this directory by default
+
+APACHE_MODPATH_INIT(http)
+
+http_objects="http_core.lo http_protocol.lo http_request.lo http_filters.lo chunk_filter.lo byterange_filter.lo http_etag.lo"
+
+dnl mod_http should only be built as a static module for now.
+dnl this will hopefully be "fixed" at some point in the future by
+dnl refactoring mod_http and moving some things to the core and
+dnl vice versa so that the core does not depend upon mod_http.
+if test "$enable_http" = "yes"; then
+ enable_http="static"
+elif test "$enable_http" = "shared"; then
+ AC_MSG_ERROR([mod_http can not be built as a shared DSO])
+fi
+
+APACHE_MODULE(http,[HTTP protocol handling. The http module is a basic one that enables the server to function as an HTTP server. It is only useful to disable it if you want to use another protocol module instead. Don't disable this module unless you are really sure what you are doing. Note: This module will always be linked statically.], $http_objects, , static)
+APACHE_MODULE(mime, mapping of file-extension to MIME. Disabling this module is normally not recommended., , , yes)
+
+APACHE_MODPATH_FINISH
diff --git a/modules/http/http_core.c b/modules/http/http_core.c
new file mode 100644
index 0000000..c6cb473
--- /dev/null
+++ b/modules/http/http_core.c
@@ -0,0 +1,326 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr_strings.h"
+#include "apr_thread_proc.h" /* for RLIMIT stuff */
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_connection.h"
+#include "http_core.h"
+#include "http_protocol.h" /* For index_of_response(). Grump. */
+#include "http_request.h"
+
+#include "util_filter.h"
+#include "util_ebcdic.h"
+#include "ap_mpm.h"
+#include "scoreboard.h"
+
+#include "mod_core.h"
+
+/* Handles for core filters */
+AP_DECLARE_DATA ap_filter_rec_t *ap_http_input_filter_handle;
+AP_DECLARE_DATA ap_filter_rec_t *ap_http_header_filter_handle;
+AP_DECLARE_DATA ap_filter_rec_t *ap_chunk_filter_handle;
+AP_DECLARE_DATA ap_filter_rec_t *ap_http_outerror_filter_handle;
+AP_DECLARE_DATA ap_filter_rec_t *ap_byterange_filter_handle;
+
+AP_DECLARE_DATA const char *ap_multipart_boundary;
+
+/* If we are using an MPM That Supports Async Connections,
+ * use a different processing function
+ */
+static int async_mpm = 0;
+
+static const char *set_keep_alive_timeout(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ apr_interval_time_t timeout;
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_CONTEXT);
+ if (err != NULL) {
+ return err;
+ }
+
+ /* Stolen from mod_proxy.c */
+ if (ap_timeout_parameter_parse(arg, &timeout, "s") != APR_SUCCESS)
+ return "KeepAliveTimeout has wrong format";
+ cmd->server->keep_alive_timeout = timeout;
+
+ /* We don't want to take into account whether or not KeepAliveTimeout is
+ * set for the main server, because if no http_module directive is used
+ * for a vhost, it will inherit the http_srv_cfg from the main server.
+ * However keep_alive_timeout_set helps determine whether the vhost should
+ * use its own configured timeout or the one from the vhost declared first
+ * on the same IP:port (ie. c->base_server, and the legacy behaviour).
+ */
+ if (cmd->server->is_virtual) {
+ cmd->server->keep_alive_timeout_set = 1;
+ }
+ return NULL;
+}
+
+static const char *set_keep_alive(cmd_parms *cmd, void *dummy,
+ int arg)
+{
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_CONTEXT);
+ if (err != NULL) {
+ return err;
+ }
+
+ cmd->server->keep_alive = arg;
+ return NULL;
+}
+
+static const char *set_keep_alive_max(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_CONTEXT);
+ if (err != NULL) {
+ return err;
+ }
+
+ cmd->server->keep_alive_max = atoi(arg);
+ return NULL;
+}
+
+static const command_rec http_cmds[] = {
+ AP_INIT_TAKE1("KeepAliveTimeout", set_keep_alive_timeout, NULL, RSRC_CONF,
+ "Keep-Alive timeout duration (sec)"),
+ AP_INIT_TAKE1("MaxKeepAliveRequests", set_keep_alive_max, NULL, RSRC_CONF,
+ "Maximum number of Keep-Alive requests per connection, "
+ "or 0 for infinite"),
+ AP_INIT_FLAG("KeepAlive", set_keep_alive, NULL, RSRC_CONF,
+ "Whether persistent connections should be On or Off"),
+ { NULL }
+};
+
+static const char *http_scheme(const request_rec *r)
+{
+ /*
+ * The http module shouldn't return anything other than
+ * "http" (the default) or "https".
+ */
+ if (r->server->server_scheme &&
+ (strcmp(r->server->server_scheme, "https") == 0))
+ return "https";
+
+ return "http";
+}
+
+static apr_port_t http_port(const request_rec *r)
+{
+ if (r->server->server_scheme &&
+ (strcmp(r->server->server_scheme, "https") == 0))
+ return DEFAULT_HTTPS_PORT;
+
+ return DEFAULT_HTTP_PORT;
+}
+
+static int ap_process_http_async_connection(conn_rec *c)
+{
+ request_rec *r = NULL;
+ conn_state_t *cs = c->cs;
+
+ AP_DEBUG_ASSERT(cs != NULL);
+ AP_DEBUG_ASSERT(cs->state == CONN_STATE_READ_REQUEST_LINE);
+
+ if (cs->state == CONN_STATE_READ_REQUEST_LINE) {
+ ap_update_child_status_from_conn(c->sbh, SERVER_BUSY_READ, c);
+ if (ap_extended_status) {
+ ap_set_conn_count(c->sbh, r, c->keepalives);
+ }
+ if ((r = ap_read_request(c))) {
+ if (r->status == HTTP_OK) {
+ cs->state = CONN_STATE_HANDLER;
+ if (ap_extended_status) {
+ ap_set_conn_count(c->sbh, r, c->keepalives + 1);
+ }
+ ap_update_child_status(c->sbh, SERVER_BUSY_WRITE, r);
+ ap_process_async_request(r);
+ /* After the call to ap_process_request, the
+ * request pool may have been deleted. We set
+ * r=NULL here to ensure that any dereference
+ * of r that might be added later in this function
+ * will result in a segfault immediately instead
+ * of nondeterministic failures later.
+ */
+ r = NULL;
+ }
+
+ if (cs->state != CONN_STATE_WRITE_COMPLETION &&
+ cs->state != CONN_STATE_SUSPENDED) {
+ /* Something went wrong; close the connection */
+ cs->state = CONN_STATE_LINGER;
+ }
+ }
+ else { /* ap_read_request failed - client may have closed */
+ cs->state = CONN_STATE_LINGER;
+ }
+ }
+
+ return OK;
+}
+
+static int ap_process_http_sync_connection(conn_rec *c)
+{
+ request_rec *r;
+ conn_state_t *cs = c->cs;
+ apr_socket_t *csd = NULL;
+ int mpm_state = 0;
+
+ /*
+ * Read and process each request found on our connection
+ * until no requests are left or we decide to close.
+ */
+
+ ap_update_child_status_from_conn(c->sbh, SERVER_BUSY_READ, c);
+ while ((r = ap_read_request(c)) != NULL) {
+ apr_interval_time_t keep_alive_timeout = r->server->keep_alive_timeout;
+
+ /* To preserve legacy behaviour, use the keepalive timeout from the
+ * base server (first on this IP:port) when none is explicitly
+ * configured on this server.
+ */
+ if (!r->server->keep_alive_timeout_set) {
+ keep_alive_timeout = c->base_server->keep_alive_timeout;
+ }
+
+ if (r->status == HTTP_OK) {
+ if (cs)
+ cs->state = CONN_STATE_HANDLER;
+ ap_update_child_status(c->sbh, SERVER_BUSY_WRITE, r);
+ ap_process_request(r);
+ /* After the call to ap_process_request, the
+ * request pool will have been deleted. We set
+ * r=NULL here to ensure that any dereference
+ * of r that might be added later in this function
+ * will result in a segfault immediately instead
+ * of nondeterministic failures later.
+ */
+ r = NULL;
+ }
+
+ if (c->keepalive != AP_CONN_KEEPALIVE || c->aborted)
+ break;
+
+ ap_update_child_status(c->sbh, SERVER_BUSY_KEEPALIVE, NULL);
+
+ if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state)) {
+ break;
+ }
+
+ if (mpm_state == AP_MPMQ_STOPPING) {
+ break;
+ }
+
+ if (!csd) {
+ csd = ap_get_conn_socket(c);
+ }
+ apr_socket_opt_set(csd, APR_INCOMPLETE_READ, 1);
+ apr_socket_timeout_set(csd, keep_alive_timeout);
+ /* Go straight to select() to wait for the next request */
+ }
+
+ return OK;
+}
+
+static int ap_process_http_connection(conn_rec *c)
+{
+ if (async_mpm && !c->clogging_input_filters) {
+ return ap_process_http_async_connection(c);
+ }
+ else {
+ return ap_process_http_sync_connection(c);
+ }
+}
+
+static int http_create_request(request_rec *r)
+{
+ if (!r->main && !r->prev) {
+ ap_add_output_filter_handle(ap_byterange_filter_handle,
+ NULL, r, r->connection);
+ ap_add_output_filter_handle(ap_content_length_filter_handle,
+ NULL, r, r->connection);
+ ap_add_output_filter_handle(ap_http_header_filter_handle,
+ NULL, r, r->connection);
+ ap_add_output_filter_handle(ap_http_outerror_filter_handle,
+ NULL, r, r->connection);
+ }
+
+ return OK;
+}
+
+static int http_send_options(request_rec *r)
+{
+ if ((r->method_number == M_OPTIONS) && r->uri && (r->uri[0] == '*') &&
+ (r->uri[1] == '\0')) {
+ return DONE; /* Send HTTP pong, without Allow header */
+ }
+ return DECLINED;
+}
+
+static int http_post_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
+{
+ apr_uint64_t val;
+ if (ap_mpm_query(AP_MPMQ_IS_ASYNC, &async_mpm) != APR_SUCCESS) {
+ async_mpm = 0;
+ }
+ ap_random_insecure_bytes(&val, sizeof(val));
+ ap_multipart_boundary = apr_psprintf(p, "%0" APR_UINT64_T_HEX_FMT, val);
+
+ return OK;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_hook_post_config(http_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_process_connection(ap_process_http_connection, NULL, NULL,
+ APR_HOOK_REALLY_LAST);
+ ap_hook_map_to_storage(ap_send_http_trace,NULL,NULL,APR_HOOK_MIDDLE);
+ ap_hook_map_to_storage(http_send_options,NULL,NULL,APR_HOOK_MIDDLE);
+ ap_hook_http_scheme(http_scheme,NULL,NULL,APR_HOOK_REALLY_LAST);
+ ap_hook_default_port(http_port,NULL,NULL,APR_HOOK_REALLY_LAST);
+ ap_hook_create_request(http_create_request, NULL, NULL, APR_HOOK_REALLY_LAST);
+ ap_http_input_filter_handle =
+ ap_register_input_filter("HTTP_IN", ap_http_filter,
+ NULL, AP_FTYPE_PROTOCOL);
+ ap_http_header_filter_handle =
+ ap_register_output_filter("HTTP_HEADER", ap_http_header_filter,
+ NULL, AP_FTYPE_PROTOCOL);
+ ap_chunk_filter_handle =
+ ap_register_output_filter("CHUNK", ap_http_chunk_filter,
+ NULL, AP_FTYPE_TRANSCODE);
+ ap_http_outerror_filter_handle =
+ ap_register_output_filter("HTTP_OUTERROR", ap_http_outerror_filter,
+ NULL, AP_FTYPE_PROTOCOL);
+ ap_byterange_filter_handle =
+ ap_register_output_filter("BYTERANGE", ap_byterange_filter,
+ NULL, AP_FTYPE_PROTOCOL);
+ ap_method_registry_init(p);
+}
+
+AP_DECLARE_MODULE(http) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ http_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/http/http_etag.c b/modules/http/http_etag.c
new file mode 100644
index 0000000..af74549
--- /dev/null
+++ b/modules/http/http_etag.c
@@ -0,0 +1,413 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr_strings.h"
+#include "apr_thread_proc.h" /* for RLIMIT stuff */
+#include "apr_sha1.h"
+#include "apr_base64.h"
+#include "apr_buckets.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_connection.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_protocol.h" /* For index_of_response(). Grump. */
+#include "http_request.h"
+
+#if APR_HAS_MMAP
+#include "apr_mmap.h"
+#endif /* APR_HAS_MMAP */
+
+#define SHA1_DIGEST_BASE64_LEN 4*(APR_SHA1_DIGESTSIZE/3)
+
+/* Generate the human-readable hex representation of an apr_uint64_t
+ * (basically a faster version of 'sprintf("%llx")')
+ */
+#define HEX_DIGITS "0123456789abcdef"
+static char *etag_uint64_to_hex(char *next, apr_uint64_t u)
+{
+ int printing = 0;
+ int shift = sizeof(apr_uint64_t) * 8 - 4;
+ do {
+ unsigned short next_digit = (unsigned short)
+ ((u >> shift) & (apr_uint64_t)0xf);
+ if (next_digit) {
+ *next++ = HEX_DIGITS[next_digit];
+ printing = 1;
+ }
+ else if (printing) {
+ *next++ = HEX_DIGITS[next_digit];
+ }
+ shift -= 4;
+ } while (shift);
+ *next++ = HEX_DIGITS[u & (apr_uint64_t)0xf];
+ return next;
+}
+
+#define ETAG_WEAK "W/"
+#define CHARS_PER_UINT64 (sizeof(apr_uint64_t) * 2)
+
+static void etag_start(char *etag, const char *weak, char **next)
+{
+ if (weak) {
+ while (*weak) {
+ *etag++ = *weak++;
+ }
+ }
+ *etag++ = '"';
+
+ *next = etag;
+}
+
+static void etag_end(char *next, const char *vlv, apr_size_t vlv_len)
+{
+ if (vlv) {
+ *next++ = ';';
+ apr_cpystrn(next, vlv, vlv_len);
+ }
+ else {
+ *next++ = '"';
+ *next = '\0';
+ }
+}
+
+/*
+ * Construct a strong ETag by creating a SHA1 hash across the file content.
+ */
+static char *make_digest_etag(request_rec *r, etag_rec *er, char *vlv,
+ apr_size_t vlv_len, char *weak, apr_size_t weak_len)
+{
+ apr_sha1_ctx_t context;
+ unsigned char digest[APR_SHA1_DIGESTSIZE];
+ apr_file_t *fd = NULL;
+ core_dir_config *cfg;
+ char *etag, *next;
+ apr_bucket_brigade *bb;
+ apr_bucket *e;
+
+ apr_size_t nbytes;
+ apr_off_t offset = 0, zero = 0, len = 0;
+ apr_status_t status;
+
+ cfg = (core_dir_config *)ap_get_core_module_config(r->per_dir_config);
+
+ if (er->fd) {
+ fd = er->fd;
+ }
+ else if (er->pathname) {
+ if ((status = apr_file_open(&fd, er->pathname, APR_READ | APR_BINARY,
+ 0, r->pool)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(10251)
+ "Make etag: could not open %s", er->pathname);
+ return "";
+ }
+ }
+ if (!fd) {
+ return "";
+ }
+
+ if ((status = apr_file_seek(fd, APR_CUR, &offset)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(10252)
+ "Make etag: could not seek");
+ if (er->pathname) {
+ apr_file_close(fd);
+ }
+ return "";
+ }
+
+ if ((status = apr_file_seek(fd, APR_END, &len)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(10258)
+ "Make etag: could not seek");
+ if (er->pathname) {
+ apr_file_close(fd);
+ }
+ return "";
+ }
+
+ if ((status = apr_file_seek(fd, APR_SET, &zero)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(10253)
+ "Make etag: could not seek");
+ if (er->pathname) {
+ apr_file_close(fd);
+ }
+ return "";
+ }
+
+ bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+
+ e = apr_brigade_insert_file(bb, fd, 0, len, r->pool);
+
+#if APR_HAS_MMAP
+ if (cfg->enable_mmap == ENABLE_MMAP_OFF) {
+ (void)apr_bucket_file_enable_mmap(e, 0);
+ }
+#endif
+
+ apr_sha1_init(&context);
+ while (!APR_BRIGADE_EMPTY(bb))
+ {
+ const char *str;
+
+ e = APR_BRIGADE_FIRST(bb);
+
+ if ((status = apr_bucket_read(e, &str, &nbytes, APR_BLOCK_READ)) != APR_SUCCESS) {
+ apr_brigade_destroy(bb);
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(10254)
+ "Make etag: could not read");
+ if (er->pathname) {
+ apr_file_close(fd);
+ }
+ return "";
+ }
+
+ apr_sha1_update(&context, str, nbytes);
+ apr_bucket_delete(e);
+ }
+
+ if ((status = apr_file_seek(fd, APR_SET, &offset)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(10255)
+ "Make etag: could not seek");
+ if (er->pathname) {
+ apr_file_close(fd);
+ }
+ return "";
+ }
+ apr_sha1_final(digest, &context);
+
+ etag = apr_palloc(r->pool, weak_len + sizeof("\"\"") +
+ SHA1_DIGEST_BASE64_LEN + vlv_len + 4);
+
+ etag_start(etag, weak, &next);
+ next += apr_base64_encode_binary(next, digest, APR_SHA1_DIGESTSIZE) - 1;
+ etag_end(next, vlv, vlv_len);
+
+ if (er->pathname) {
+ apr_file_close(fd);
+ }
+
+ return etag;
+}
+
+/*
+ * Construct an entity tag (ETag) from resource information. If it's a real
+ * file, build in some of the file characteristics. If the modification time
+ * is newer than (request-time minus 1 second), mark the ETag as weak - it
+ * could be modified again in as short an interval.
+ */
+AP_DECLARE(char *) ap_make_etag_ex(request_rec *r, etag_rec *er)
+{
+ char *weak = NULL;
+ apr_size_t weak_len = 0, vlv_len = 0;
+ char *etag, *next, *vlv;
+ core_dir_config *cfg;
+ etag_components_t etag_bits;
+ etag_components_t bits_added;
+
+ cfg = (core_dir_config *)ap_get_core_module_config(r->per_dir_config);
+ etag_bits = (cfg->etag_bits & (~ cfg->etag_remove)) | cfg->etag_add;
+
+ if (er->force_weak) {
+ weak = ETAG_WEAK;
+ weak_len = sizeof(ETAG_WEAK);
+ }
+
+ if (r->vlist_validator) {
+
+ /* If we have a variant list validator (vlv) due to the
+ * response being negotiated, then we create a structured
+ * entity tag which merges the variant etag with the variant
+ * list validator (vlv). This merging makes revalidation
+ * somewhat safer, ensures that caches which can deal with
+ * Vary will (eventually) be updated if the set of variants is
+ * changed, and is also a protocol requirement for transparent
+ * content negotiation.
+ */
+
+ /* if the variant list validator is weak, we make the whole
+ * structured etag weak. If we would not, then clients could
+ * have problems merging range responses if we have different
+ * variants with the same non-globally-unique strong etag.
+ */
+
+ vlv = r->vlist_validator;
+ if (vlv[0] == 'W') {
+ vlv += 3;
+ weak = ETAG_WEAK;
+ weak_len = sizeof(ETAG_WEAK);
+ }
+ else {
+ vlv++;
+ }
+ vlv_len = strlen(vlv);
+
+ }
+ else {
+ vlv = NULL;
+ vlv_len = 0;
+ }
+
+ /*
+ * Did a module flag the need for a strong etag, or did the
+ * configuration tell us to generate a digest?
+ */
+ if (er->finfo->filetype == APR_REG &&
+ (AP_REQUEST_IS_STRONG_ETAG(r) || (etag_bits & ETAG_DIGEST))) {
+
+ return make_digest_etag(r, er, vlv, vlv_len, weak, weak_len);
+ }
+
+ /*
+ * If it's a file (or we wouldn't be here) and no ETags
+ * should be set for files, return an empty string and
+ * note it for the header-sender to ignore.
+ */
+ if (etag_bits & ETAG_NONE) {
+ return "";
+ }
+
+ if (etag_bits == ETAG_UNSET) {
+ etag_bits = ETAG_BACKWARD;
+ }
+ /*
+ * Make an ETag header out of various pieces of information. We use
+ * the last-modified date and, if we have a real file, the
+ * length and inode number - note that this doesn't have to match
+ * the content-length (i.e. includes), it just has to be unique
+ * for the file.
+ *
+ * If the request was made within a second of the last-modified date,
+ * we send a weak tag instead of a strong one, since it could
+ * be modified again later in the second, and the validation
+ * would be incorrect.
+ */
+ if ((er->request_time - er->finfo->mtime < (1 * APR_USEC_PER_SEC))) {
+ weak = ETAG_WEAK;
+ weak_len = sizeof(ETAG_WEAK);
+ }
+
+ if (er->finfo->filetype != APR_NOFILE) {
+ /*
+ * ETag gets set to [W/]"inode-size-mtime", modulo any
+ * FileETag keywords.
+ */
+ etag = apr_palloc(r->pool, weak_len + sizeof("\"--\"") +
+ 3 * CHARS_PER_UINT64 + vlv_len + 2);
+
+ etag_start(etag, weak, &next);
+
+ bits_added = 0;
+ if (etag_bits & ETAG_INODE) {
+ next = etag_uint64_to_hex(next, er->finfo->inode);
+ bits_added |= ETAG_INODE;
+ }
+ if (etag_bits & ETAG_SIZE) {
+ if (bits_added != 0) {
+ *next++ = '-';
+ }
+ next = etag_uint64_to_hex(next, er->finfo->size);
+ bits_added |= ETAG_SIZE;
+ }
+ if (etag_bits & ETAG_MTIME) {
+ if (bits_added != 0) {
+ *next++ = '-';
+ }
+ next = etag_uint64_to_hex(next, er->finfo->mtime);
+ }
+
+ etag_end(next, vlv, vlv_len);
+
+ }
+ else {
+ /*
+ * Not a file document, so just use the mtime: [W/]"mtime"
+ */
+ etag = apr_palloc(r->pool, weak_len + sizeof("\"\"") +
+ CHARS_PER_UINT64 + vlv_len + 2);
+
+ etag_start(etag, weak, &next);
+ next = etag_uint64_to_hex(next, er->finfo->mtime);
+ etag_end(next, vlv, vlv_len);
+
+ }
+
+ return etag;
+}
+
+AP_DECLARE(char *) ap_make_etag(request_rec *r, int force_weak)
+{
+ etag_rec er;
+
+ er.vlist_validator = NULL;
+ er.request_time = r->request_time;
+ er.finfo = &r->finfo;
+ er.pathname = r->filename;
+ er.fd = NULL;
+ er.force_weak = force_weak;
+
+ return ap_make_etag_ex(r, &er);
+}
+
+AP_DECLARE(void) ap_set_etag(request_rec *r)
+{
+ char *etag;
+
+ etag_rec er;
+
+ er.vlist_validator = r->vlist_validator;
+ er.request_time = r->request_time;
+ er.finfo = &r->finfo;
+ er.pathname = r->filename;
+ er.fd = NULL;
+ er.force_weak = 0;
+
+ etag = ap_make_etag_ex(r, &er);
+
+ if (etag && etag[0]) {
+ apr_table_setn(r->headers_out, "ETag", etag);
+ }
+ else {
+ apr_table_setn(r->notes, "no-etag", "omit");
+ }
+
+}
+
+AP_DECLARE(void) ap_set_etag_fd(request_rec *r, apr_file_t *fd)
+{
+ char *etag;
+
+ etag_rec er;
+
+ er.vlist_validator = r->vlist_validator;
+ er.request_time = r->request_time;
+ er.finfo = &r->finfo;
+ er.pathname = NULL;
+ er.fd = fd;
+ er.force_weak = 0;
+
+ etag = ap_make_etag_ex(r, &er);
+
+ if (etag && etag[0]) {
+ apr_table_setn(r->headers_out, "ETag", etag);
+ }
+ else {
+ apr_table_setn(r->notes, "no-etag", "omit");
+ }
+
+}
diff --git a/modules/http/http_filters.c b/modules/http/http_filters.c
new file mode 100644
index 0000000..1a8df34
--- /dev/null
+++ b/modules/http/http_filters.c
@@ -0,0 +1,1941 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * http_filter.c --- HTTP routines which either filters or deal with filters.
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_buckets.h"
+#include "apr_lib.h"
+#include "apr_signal.h"
+
+#define APR_WANT_STDIO /* for sscanf */
+#define APR_WANT_STRFUNC
+#define APR_WANT_MEMFUNC
+#include "apr_want.h"
+
+#include "util_filter.h"
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_protocol.h"
+#include "http_main.h"
+#include "http_request.h"
+#include "http_vhost.h"
+#include "http_connection.h"
+#include "http_log.h" /* For errors detected in basic auth common
+ * support code... */
+#include "apr_date.h" /* For apr_date_parse_http and APR_DATE_BAD */
+#include "util_charset.h"
+#include "util_ebcdic.h"
+#include "util_time.h"
+
+#include "mod_core.h"
+
+#if APR_HAVE_STDARG_H
+#include <stdarg.h>
+#endif
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+APLOG_USE_MODULE(http);
+
+typedef struct http_filter_ctx
+{
+ apr_off_t remaining;
+ apr_off_t limit;
+ apr_off_t limit_used;
+ apr_int32_t chunk_used;
+ apr_int32_t chunk_bws;
+ apr_int32_t chunkbits;
+ enum
+ {
+ BODY_NONE, /* streamed data */
+ BODY_LENGTH, /* data constrained by content length */
+ BODY_CHUNK, /* chunk expected */
+ BODY_CHUNK_PART, /* chunk digits */
+ BODY_CHUNK_EXT, /* chunk extension */
+ BODY_CHUNK_CR, /* got space(s) after digits, expect [CR]LF or ext */
+ BODY_CHUNK_LF, /* got CR after digits or ext, expect LF */
+ BODY_CHUNK_DATA, /* data constrained by chunked encoding */
+ BODY_CHUNK_END, /* chunked data terminating CRLF */
+ BODY_CHUNK_END_LF, /* got CR after data, expect LF */
+ BODY_CHUNK_TRAILER /* trailers */
+ } state;
+ unsigned int eos_sent :1,
+ seen_data:1;
+ apr_bucket_brigade *bb;
+} http_ctx_t;
+
+/* bail out if some error in the HTTP input filter happens */
+static apr_status_t bail_out_on_error(http_ctx_t *ctx,
+ ap_filter_t *f,
+ int http_error)
+{
+ apr_bucket *e;
+ apr_bucket_brigade *bb = ctx->bb;
+
+ apr_brigade_cleanup(bb);
+
+ if (f->r->proxyreq == PROXYREQ_RESPONSE) {
+ switch (http_error) {
+ case HTTP_REQUEST_ENTITY_TOO_LARGE:
+ return APR_ENOSPC;
+
+ case HTTP_REQUEST_TIME_OUT:
+ return APR_INCOMPLETE;
+
+ case HTTP_NOT_IMPLEMENTED:
+ return APR_ENOTIMPL;
+
+ default:
+ return APR_EGENERAL;
+ }
+ }
+
+ e = ap_bucket_error_create(http_error,
+ NULL, f->r->pool,
+ f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ e = apr_bucket_eos_create(f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ ctx->eos_sent = 1;
+ /* If chunked encoding / content-length are corrupt, we may treat parts
+ * of this request's body as the next one's headers.
+ * To be safe, disable keep-alive.
+ */
+ f->r->connection->keepalive = AP_CONN_CLOSE;
+ return ap_pass_brigade(f->r->output_filters, bb);
+}
+
+/**
+ * Parse a chunk line with optional extension, detect overflow.
+ * There are several error cases:
+ * 1) If the chunk link is misformatted, APR_EINVAL is returned.
+ * 2) If the conversion would require too many bits, APR_EGENERAL is returned.
+ * 3) If the conversion used the correct number of bits, but an overflow
+ * caused only the sign bit to flip, then APR_ENOSPC is returned.
+ * A negative chunk length always indicates an overflow error.
+ */
+static apr_status_t parse_chunk_size(http_ctx_t *ctx, const char *buffer,
+ apr_size_t len, int linelimit, int strict)
+{
+ apr_size_t i = 0;
+
+ while (i < len) {
+ char c = buffer[i];
+
+ ap_xlate_proto_from_ascii(&c, 1);
+
+ /* handle CRLF after the chunk */
+ if (ctx->state == BODY_CHUNK_END
+ || ctx->state == BODY_CHUNK_END_LF) {
+ if (c == LF) {
+ if (strict && (ctx->state != BODY_CHUNK_END_LF)) {
+ /*
+ * CR missing before LF.
+ */
+ return APR_EINVAL;
+ }
+ ctx->state = BODY_CHUNK;
+ }
+ else if (c == CR && ctx->state == BODY_CHUNK_END) {
+ ctx->state = BODY_CHUNK_END_LF;
+ }
+ else {
+ /*
+ * CRLF expected.
+ */
+ return APR_EINVAL;
+ }
+ i++;
+ continue;
+ }
+
+ /* handle start of the chunk */
+ if (ctx->state == BODY_CHUNK) {
+ if (!apr_isxdigit(c)) {
+ /*
+ * Detect invalid character at beginning. This also works for
+ * empty chunk size lines.
+ */
+ return APR_EINVAL;
+ }
+ else {
+ ctx->state = BODY_CHUNK_PART;
+ }
+ ctx->remaining = 0;
+ ctx->chunkbits = sizeof(apr_off_t) * 8;
+ ctx->chunk_used = 0;
+ ctx->chunk_bws = 0;
+ }
+
+ if (c == LF) {
+ if (strict && (ctx->state != BODY_CHUNK_LF)) {
+ /*
+ * CR missing before LF.
+ */
+ return APR_EINVAL;
+ }
+ if (ctx->remaining) {
+ ctx->state = BODY_CHUNK_DATA;
+ }
+ else {
+ ctx->state = BODY_CHUNK_TRAILER;
+ }
+ }
+ else if (ctx->state == BODY_CHUNK_LF) {
+ /*
+ * LF expected.
+ */
+ return APR_EINVAL;
+ }
+ else if (c == CR) {
+ ctx->state = BODY_CHUNK_LF;
+ }
+ else if (c == ';') {
+ ctx->state = BODY_CHUNK_EXT;
+ }
+ else if (ctx->state == BODY_CHUNK_EXT) {
+ /*
+ * Control chars (excluding tabs) are invalid.
+ * TODO: more precisely limit input
+ */
+ if (c != '\t' && apr_iscntrl(c)) {
+ return APR_EINVAL;
+ }
+ }
+ else if (c == ' ' || c == '\t') {
+ /* Be lenient up to 10 implied *LWS, a legacy of RFC 2616,
+ * and noted as errata to RFC7230;
+ * https://www.rfc-editor.org/errata_search.php?rfc=7230&eid=4667
+ */
+ ctx->state = BODY_CHUNK_CR;
+ if (++ctx->chunk_bws > 10) {
+ return APR_EINVAL;
+ }
+ }
+ else if (ctx->state == BODY_CHUNK_CR) {
+ /*
+ * ';', CR or LF expected.
+ */
+ return APR_EINVAL;
+ }
+ else if (ctx->state == BODY_CHUNK_PART) {
+ int xvalue;
+
+ /* ignore leading zeros */
+ if (!ctx->remaining && c == '0') {
+ i++;
+ continue;
+ }
+
+ ctx->chunkbits -= 4;
+ if (ctx->chunkbits < 0) {
+ /* overflow */
+ return APR_ENOSPC;
+ }
+
+ if (c >= '0' && c <= '9') {
+ xvalue = c - '0';
+ }
+ else if (c >= 'A' && c <= 'F') {
+ xvalue = c - 'A' + 0xa;
+ }
+ else if (c >= 'a' && c <= 'f') {
+ xvalue = c - 'a' + 0xa;
+ }
+ else {
+ /* bogus character */
+ return APR_EINVAL;
+ }
+
+ ctx->remaining = (ctx->remaining << 4) | xvalue;
+ if (ctx->remaining < 0) {
+ /* overflow */
+ return APR_ENOSPC;
+ }
+ }
+ else {
+ /* Should not happen */
+ return APR_EGENERAL;
+ }
+
+ i++;
+ }
+
+ /* sanity check */
+ ctx->chunk_used += len;
+ if (ctx->chunk_used < 0 || ctx->chunk_used > linelimit) {
+ return APR_ENOSPC;
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t read_chunked_trailers(http_ctx_t *ctx, ap_filter_t *f,
+ apr_bucket_brigade *b, int merge)
+{
+ int rv;
+ apr_bucket *e;
+ request_rec *r = f->r;
+ apr_table_t *saved_headers_in = r->headers_in;
+ int saved_status = r->status;
+
+ r->status = HTTP_OK;
+ r->headers_in = r->trailers_in;
+ apr_table_clear(r->headers_in);
+ ap_get_mime_headers(r);
+
+ if(r->status == HTTP_OK) {
+ r->status = saved_status;
+ e = apr_bucket_eos_create(f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(b, e);
+ ctx->eos_sent = 1;
+ rv = APR_SUCCESS;
+ }
+ else {
+ const char *error_notes = apr_table_get(r->notes,
+ "error-notes");
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02656)
+ "Error while reading HTTP trailer: %i%s%s",
+ r->status, error_notes ? ": " : "",
+ error_notes ? error_notes : "");
+ rv = APR_EINVAL;
+ }
+
+ if(!merge) {
+ r->headers_in = saved_headers_in;
+ }
+ else {
+ r->headers_in = apr_table_overlay(r->pool, saved_headers_in,
+ r->trailers_in);
+ }
+
+ return rv;
+}
+
+/* This is the HTTP_INPUT filter for HTTP requests and responses from
+ * proxied servers (mod_proxy). It handles chunked and content-length
+ * bodies. This can only be inserted/used after the headers
+ * are successfully parsed.
+ */
+apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b,
+ ap_input_mode_t mode, apr_read_type_e block,
+ apr_off_t readbytes)
+{
+ core_server_config *conf =
+ (core_server_config *) ap_get_module_config(f->r->server->module_config,
+ &core_module);
+ int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE);
+ apr_bucket *e;
+ http_ctx_t *ctx = f->ctx;
+ apr_status_t rv;
+ int http_error = HTTP_REQUEST_ENTITY_TOO_LARGE;
+ int again;
+
+ /* just get out of the way of things we don't want. */
+ if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE) {
+ return ap_get_brigade(f->next, b, mode, block, readbytes);
+ }
+
+ if (!ctx) {
+ const char *tenc, *lenp;
+ f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx));
+ ctx->state = BODY_NONE;
+ ctx->bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
+
+ /* LimitRequestBody does not apply to proxied responses.
+ * Consider implementing this check in its own filter.
+ * Would adding a directive to limit the size of proxied
+ * responses be useful?
+ */
+ if (!f->r->proxyreq) {
+ ctx->limit = ap_get_limit_req_body(f->r);
+ }
+ else {
+ ctx->limit = 0;
+ }
+
+ tenc = apr_table_get(f->r->headers_in, "Transfer-Encoding");
+ lenp = apr_table_get(f->r->headers_in, "Content-Length");
+
+ if (tenc) {
+ if (ap_is_chunked(f->r->pool, tenc)) {
+ ctx->state = BODY_CHUNK;
+ }
+ else if (f->r->proxyreq == PROXYREQ_RESPONSE) {
+ /* http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-23
+ * Section 3.3.3.3: "If a Transfer-Encoding header field is
+ * present in a response and the chunked transfer coding is not
+ * the final encoding, the message body length is determined by
+ * reading the connection until it is closed by the server."
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, APLOGNO(02555)
+ "Unknown Transfer-Encoding: %s; "
+ "using read-until-close", tenc);
+ tenc = NULL;
+ }
+ else {
+ /* Something that isn't a HTTP request, unless some future
+ * edition defines new transfer encodings, is unsupported.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, APLOGNO(01585)
+ "Unknown Transfer-Encoding: %s", tenc);
+ return bail_out_on_error(ctx, f, HTTP_BAD_REQUEST);
+ }
+ lenp = NULL;
+ }
+ if (lenp) {
+ ctx->state = BODY_LENGTH;
+
+ /* Protects against over/underflow, non-digit chars in the
+ * string, leading plus/minus signs, trailing characters and
+ * a negative number.
+ */
+ if (!ap_parse_strict_length(&ctx->remaining, lenp)) {
+ ctx->remaining = 0;
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, APLOGNO(01587)
+ "Invalid Content-Length");
+
+ return bail_out_on_error(ctx, f, HTTP_BAD_REQUEST);
+ }
+
+ /* If we have a limit in effect and we know the C-L ahead of
+ * time, stop it here if it is invalid.
+ */
+ if (ctx->limit && ctx->limit < ctx->remaining) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, APLOGNO(01588)
+ "Requested content-length of %" APR_OFF_T_FMT
+ " is larger than the configured limit"
+ " of %" APR_OFF_T_FMT, ctx->remaining, ctx->limit);
+ return bail_out_on_error(ctx, f, HTTP_REQUEST_ENTITY_TOO_LARGE);
+ }
+ }
+
+ /* If we don't have a request entity indicated by the headers, EOS.
+ * (BODY_NONE is a valid intermediate state due to trailers,
+ * but it isn't a valid starting state.)
+ *
+ * RFC 2616 Section 4.4 note 5 states that connection-close
+ * is invalid for a request entity - request bodies must be
+ * denoted by C-L or T-E: chunked.
+ *
+ * Note that since the proxy uses this filter to handle the
+ * proxied *response*, proxy responses MUST be exempt.
+ */
+ if (ctx->state == BODY_NONE && f->r->proxyreq != PROXYREQ_RESPONSE) {
+ e = apr_bucket_eos_create(f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(b, e);
+ ctx->eos_sent = 1;
+ return APR_SUCCESS;
+ }
+ }
+
+ /* Since we're about to read data, send 100-Continue if needed.
+ * Only valid on chunked and C-L bodies where the C-L is > 0.
+ *
+ * If the read is to be nonblocking though, the caller may not want to
+ * handle this just now (e.g. mod_proxy_http), and is prepared to read
+ * nothing if the client really waits for 100 continue, so we don't
+ * send it now and wait for later blocking read.
+ *
+ * In any case, even if r->expecting remains set at the end of the
+ * request handling, ap_set_keepalive() will finally do the right
+ * thing (i.e. "Connection: close" the connection).
+ */
+ if (block == APR_BLOCK_READ
+ && (ctx->state == BODY_CHUNK
+ || (ctx->state == BODY_LENGTH && ctx->remaining > 0))
+ && f->r->expecting_100 && f->r->proto_num >= HTTP_VERSION(1,1)
+ && !(ctx->eos_sent || f->r->eos_sent || f->r->bytes_sent)) {
+ if (!ap_is_HTTP_SUCCESS(f->r->status)) {
+ ctx->state = BODY_NONE;
+ ctx->eos_sent = 1; /* send EOS below */
+ }
+ else if (!ctx->seen_data) {
+ int saved_status = f->r->status;
+ const char *saved_status_line = f->r->status_line;
+ f->r->status = HTTP_CONTINUE;
+ f->r->status_line = NULL;
+ ap_send_interim_response(f->r, 0);
+ AP_DEBUG_ASSERT(!f->r->expecting_100);
+ f->r->status_line = saved_status_line;
+ f->r->status = saved_status;
+ }
+ else {
+ /* https://tools.ietf.org/html/rfc7231#section-5.1.1
+ * A server MAY omit sending a 100 (Continue) response if it
+ * has already received some or all of the message body for
+ * the corresponding request [...]
+ */
+ f->r->expecting_100 = 0;
+ }
+ }
+
+ /* sanity check in case we're read twice */
+ if (ctx->eos_sent) {
+ e = apr_bucket_eos_create(f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(b, e);
+ return APR_SUCCESS;
+ }
+
+ do {
+ apr_brigade_cleanup(b);
+ again = 0; /* until further notice */
+
+ /* read and handle the brigade */
+ switch (ctx->state) {
+ case BODY_CHUNK:
+ case BODY_CHUNK_PART:
+ case BODY_CHUNK_EXT:
+ case BODY_CHUNK_CR:
+ case BODY_CHUNK_LF:
+ case BODY_CHUNK_END:
+ case BODY_CHUNK_END_LF: {
+
+ rv = ap_get_brigade(f->next, b, AP_MODE_GETLINE, block, 0);
+
+ /* for timeout */
+ if (block == APR_NONBLOCK_READ
+ && ((rv == APR_SUCCESS && APR_BRIGADE_EMPTY(b))
+ || (APR_STATUS_IS_EAGAIN(rv)))) {
+ return APR_EAGAIN;
+ }
+
+ if (rv == APR_EOF) {
+ return APR_INCOMPLETE;
+ }
+
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ e = APR_BRIGADE_FIRST(b);
+ while (e != APR_BRIGADE_SENTINEL(b)) {
+ const char *buffer;
+ apr_size_t len;
+
+ if (!APR_BUCKET_IS_METADATA(e)) {
+ int parsing = 0;
+
+ rv = apr_bucket_read(e, &buffer, &len, APR_BLOCK_READ);
+ if (rv == APR_SUCCESS) {
+ parsing = 1;
+ if (len > 0) {
+ ctx->seen_data = 1;
+ }
+ rv = parse_chunk_size(ctx, buffer, len,
+ f->r->server->limit_req_fieldsize, strict);
+ }
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, rv, f->r, APLOGNO(01590)
+ "Error reading/parsing chunk %s ",
+ (APR_ENOSPC == rv) ? "(overflow)" : "");
+ if (parsing) {
+ if (rv != APR_ENOSPC) {
+ http_error = HTTP_BAD_REQUEST;
+ }
+ return bail_out_on_error(ctx, f, http_error);
+ }
+ return rv;
+ }
+ }
+
+ apr_bucket_delete(e);
+ e = APR_BRIGADE_FIRST(b);
+ }
+ again = 1; /* come around again */
+
+ if (ctx->state == BODY_CHUNK_TRAILER) {
+ /* Treat UNSET as DISABLE - trailers aren't merged by default */
+ return read_chunked_trailers(ctx, f, b,
+ conf->merge_trailers == AP_MERGE_TRAILERS_ENABLE);
+ }
+
+ break;
+ }
+ case BODY_NONE:
+ case BODY_LENGTH:
+ case BODY_CHUNK_DATA: {
+
+ /* Ensure that the caller can not go over our boundary point. */
+ if (ctx->state != BODY_NONE && ctx->remaining < readbytes) {
+ readbytes = ctx->remaining;
+ }
+ if (readbytes > 0) {
+ apr_off_t totalread;
+
+ rv = ap_get_brigade(f->next, b, mode, block, readbytes);
+
+ /* for timeout */
+ if (block == APR_NONBLOCK_READ
+ && ((rv == APR_SUCCESS && APR_BRIGADE_EMPTY(b))
+ || (APR_STATUS_IS_EAGAIN(rv)))) {
+ return APR_EAGAIN;
+ }
+
+ if (rv == APR_EOF && ctx->state != BODY_NONE
+ && ctx->remaining > 0) {
+ return APR_INCOMPLETE;
+ }
+
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ /* How many bytes did we just read? */
+ apr_brigade_length(b, 0, &totalread);
+ if (totalread > 0) {
+ ctx->seen_data = 1;
+ }
+
+ /* If this happens, we have a bucket of unknown length. Die because
+ * it means our assumptions have changed. */
+ AP_DEBUG_ASSERT(totalread >= 0);
+
+ if (ctx->state != BODY_NONE) {
+ ctx->remaining -= totalread;
+ if (ctx->remaining > 0) {
+ e = APR_BRIGADE_LAST(b);
+ if (APR_BUCKET_IS_EOS(e)) {
+ apr_bucket_delete(e);
+ return APR_INCOMPLETE;
+ }
+ }
+ else if (ctx->state == BODY_CHUNK_DATA) {
+ /* next chunk please */
+ ctx->state = BODY_CHUNK_END;
+ ctx->chunk_used = 0;
+ }
+ }
+
+ /* We have a limit in effect. */
+ if (ctx->limit) {
+ /* FIXME: Note that we might get slightly confused on
+ * chunked inputs as we'd need to compensate for the chunk
+ * lengths which may not really count. This seems to be up
+ * for interpretation.
+ */
+ ctx->limit_used += totalread;
+ if (ctx->limit < ctx->limit_used) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r,
+ APLOGNO(01591) "Read content length of "
+ "%" APR_OFF_T_FMT " is larger than the "
+ "configured limit of %" APR_OFF_T_FMT,
+ ctx->limit_used, ctx->limit);
+ return bail_out_on_error(ctx, f,
+ HTTP_REQUEST_ENTITY_TOO_LARGE);
+ }
+ }
+ }
+
+ /* If we have no more bytes remaining on a C-L request,
+ * save the caller a round trip to discover EOS.
+ */
+ if (ctx->state == BODY_LENGTH && ctx->remaining == 0) {
+ e = apr_bucket_eos_create(f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(b, e);
+ ctx->eos_sent = 1;
+ }
+
+ break;
+ }
+ case BODY_CHUNK_TRAILER: {
+
+ rv = ap_get_brigade(f->next, b, mode, block, readbytes);
+
+ /* for timeout */
+ if (block == APR_NONBLOCK_READ
+ && ((rv == APR_SUCCESS && APR_BRIGADE_EMPTY(b))
+ || (APR_STATUS_IS_EAGAIN(rv)))) {
+ return APR_EAGAIN;
+ }
+
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ break;
+ }
+ default: {
+ /* Should not happen */
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, APLOGNO(02901)
+ "Unexpected body state (%i)", (int)ctx->state);
+ return APR_EGENERAL;
+ }
+ }
+
+ } while (again);
+
+ return APR_SUCCESS;
+}
+
+struct check_header_ctx {
+ request_rec *r;
+ int strict;
+};
+
+/* check a single header, to be used with apr_table_do() */
+static int check_header(struct check_header_ctx *ctx,
+ const char *name, const char **val)
+{
+ const char *pos, *end;
+ char *dst = NULL;
+
+ if (name[0] == '\0') {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(02428)
+ "Empty response header name, aborting request");
+ return 0;
+ }
+
+ if (ctx->strict) {
+ end = ap_scan_http_token(name);
+ }
+ else {
+ end = ap_scan_vchar_obstext(name);
+ }
+ if (*end) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(02429)
+ "Response header name '%s' contains invalid "
+ "characters, aborting request",
+ name);
+ return 0;
+ }
+
+ for (pos = *val; *pos; pos = end) {
+ end = ap_scan_http_field_content(pos);
+ if (*end) {
+ if (end[0] != CR || end[1] != LF || (end[2] != ' ' &&
+ end[2] != '\t')) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(02430)
+ "Response header '%s' value of '%s' contains "
+ "invalid characters, aborting request",
+ name, pos);
+ return 0;
+ }
+ if (!dst) {
+ *val = dst = apr_palloc(ctx->r->pool, strlen(*val) + 1);
+ }
+ }
+ if (dst) {
+ memcpy(dst, pos, end - pos);
+ dst += end - pos;
+ if (*end) {
+ /* skip folding and replace with a single space */
+ end += 3 + strspn(end + 3, "\t ");
+ *dst++ = ' ';
+ }
+ }
+ }
+ if (dst) {
+ *dst = '\0';
+ }
+ return 1;
+}
+
+static int check_headers_table(apr_table_t *t, struct check_header_ctx *ctx)
+{
+ const apr_array_header_t *headers = apr_table_elts(t);
+ apr_table_entry_t *header;
+ int i;
+
+ for (i = 0; i < headers->nelts; ++i) {
+ header = &APR_ARRAY_IDX(headers, i, apr_table_entry_t);
+ if (!header->key) {
+ continue;
+ }
+ if (!check_header(ctx, header->key, (const char **)&header->val)) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+/**
+ * Check headers for HTTP conformance
+ * @return 1 if ok, 0 if bad
+ */
+static APR_INLINE int check_headers(request_rec *r)
+{
+ struct check_header_ctx ctx;
+ core_server_config *conf =
+ ap_get_core_module_config(r->server->module_config);
+
+ ctx.r = r;
+ ctx.strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE);
+ return check_headers_table(r->headers_out, &ctx) &&
+ check_headers_table(r->err_headers_out, &ctx);
+}
+
+static int check_headers_recursion(request_rec *r)
+{
+ void *check = NULL;
+ apr_pool_userdata_get(&check, "check_headers_recursion", r->pool);
+ if (check) {
+ return 1;
+ }
+ apr_pool_userdata_setn("true", "check_headers_recursion", NULL, r->pool);
+ return 0;
+}
+
+typedef struct header_struct {
+ apr_pool_t *pool;
+ apr_bucket_brigade *bb;
+} header_struct;
+
+/* Send a single HTTP header field to the client. Note that this function
+ * is used in calls to apr_table_do(), so don't change its interface.
+ * It returns true unless there was a write error of some kind.
+ */
+static int form_header_field(header_struct *h,
+ const char *fieldname, const char *fieldval)
+{
+#if APR_CHARSET_EBCDIC
+ char *headfield;
+ apr_size_t len;
+
+ headfield = apr_pstrcat(h->pool, fieldname, ": ", fieldval, CRLF, NULL);
+ len = strlen(headfield);
+
+ ap_xlate_proto_to_ascii(headfield, len);
+ apr_brigade_write(h->bb, NULL, NULL, headfield, len);
+#else
+ struct iovec vec[4];
+ struct iovec *v = vec;
+ v->iov_base = (void *)fieldname;
+ v->iov_len = strlen(fieldname);
+ v++;
+ v->iov_base = ": ";
+ v->iov_len = sizeof(": ") - 1;
+ v++;
+ v->iov_base = (void *)fieldval;
+ v->iov_len = strlen(fieldval);
+ v++;
+ v->iov_base = CRLF;
+ v->iov_len = sizeof(CRLF) - 1;
+ apr_brigade_writev(h->bb, NULL, NULL, vec, 4);
+#endif /* !APR_CHARSET_EBCDIC */
+ return 1;
+}
+
+/* This routine is called by apr_table_do and merges all instances of
+ * the passed field values into a single array that will be further
+ * processed by some later routine. Originally intended to help split
+ * and recombine multiple Vary fields, though it is generic to any field
+ * consisting of comma/space-separated tokens.
+ */
+static int uniq_field_values(void *d, const char *key, const char *val)
+{
+ apr_array_header_t *values;
+ char *start;
+ char *e;
+ char **strpp;
+ int i;
+
+ values = (apr_array_header_t *)d;
+
+ e = apr_pstrdup(values->pool, val);
+
+ do {
+ /* Find a non-empty fieldname */
+
+ while (*e == ',' || apr_isspace(*e)) {
+ ++e;
+ }
+ if (*e == '\0') {
+ break;
+ }
+ start = e;
+ while (*e != '\0' && *e != ',' && !apr_isspace(*e)) {
+ ++e;
+ }
+ if (*e != '\0') {
+ *e++ = '\0';
+ }
+
+ /* Now add it to values if it isn't already represented.
+ * Could be replaced by a ap_array_strcasecmp() if we had one.
+ */
+ for (i = 0, strpp = (char **) values->elts; i < values->nelts;
+ ++i, ++strpp) {
+ if (*strpp && ap_cstr_casecmp(*strpp, start) == 0) {
+ break;
+ }
+ }
+ if (i == values->nelts) { /* if not found */
+ *(char **)apr_array_push(values) = start;
+ }
+ } while (*e != '\0');
+
+ return 1;
+}
+
+/*
+ * Since some clients choke violently on multiple Vary fields, or
+ * Vary fields with duplicate tokens, combine any multiples and remove
+ * any duplicates.
+ */
+static void fixup_vary(request_rec *r)
+{
+ apr_array_header_t *varies;
+
+ varies = apr_array_make(r->pool, 5, sizeof(char *));
+
+ /* Extract all Vary fields from the headers_out, separate each into
+ * its comma-separated fieldname values, and then add them to varies
+ * if not already present in the array.
+ */
+ apr_table_do(uniq_field_values, varies, r->headers_out, "Vary", NULL);
+
+ /* If we found any, replace old Vary fields with unique-ified value */
+
+ if (varies->nelts > 0) {
+ apr_table_setn(r->headers_out, "Vary",
+ apr_array_pstrcat(r->pool, varies, ','));
+ }
+}
+
+/* Send a request's HTTP response headers to the client.
+ */
+static apr_status_t send_all_header_fields(header_struct *h,
+ const request_rec *r)
+{
+ const apr_array_header_t *elts;
+ const apr_table_entry_t *t_elt;
+ const apr_table_entry_t *t_end;
+ struct iovec *vec;
+ struct iovec *vec_next;
+
+ elts = apr_table_elts(r->headers_out);
+ if (elts->nelts == 0) {
+ return APR_SUCCESS;
+ }
+ t_elt = (const apr_table_entry_t *)(elts->elts);
+ t_end = t_elt + elts->nelts;
+ vec = (struct iovec *)apr_palloc(h->pool, 4 * elts->nelts *
+ sizeof(struct iovec));
+ vec_next = vec;
+
+ /* For each field, generate
+ * name ": " value CRLF
+ */
+ do {
+ vec_next->iov_base = (void*)(t_elt->key);
+ vec_next->iov_len = strlen(t_elt->key);
+ vec_next++;
+ vec_next->iov_base = ": ";
+ vec_next->iov_len = sizeof(": ") - 1;
+ vec_next++;
+ vec_next->iov_base = (void*)(t_elt->val);
+ vec_next->iov_len = strlen(t_elt->val);
+ vec_next++;
+ vec_next->iov_base = CRLF;
+ vec_next->iov_len = sizeof(CRLF) - 1;
+ vec_next++;
+ t_elt++;
+ } while (t_elt < t_end);
+
+ if (APLOGrtrace4(r)) {
+ t_elt = (const apr_table_entry_t *)(elts->elts);
+ do {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r, " %s: %s",
+ ap_escape_logitem(r->pool, t_elt->key),
+ ap_escape_logitem(r->pool, t_elt->val));
+ t_elt++;
+ } while (t_elt < t_end);
+ }
+
+#if APR_CHARSET_EBCDIC
+ {
+ apr_size_t len;
+ char *tmp = apr_pstrcatv(r->pool, vec, vec_next - vec, &len);
+ ap_xlate_proto_to_ascii(tmp, len);
+ return apr_brigade_write(h->bb, NULL, NULL, tmp, len);
+ }
+#else
+ return apr_brigade_writev(h->bb, NULL, NULL, vec, vec_next - vec);
+#endif
+}
+
+/* Confirm that the status line is well-formed and matches r->status.
+ * If they don't match, a filter may have negated the status line set by a
+ * handler.
+ * Zap r->status_line if bad.
+ */
+static apr_status_t validate_status_line(request_rec *r)
+{
+ char *end;
+
+ if (r->status_line) {
+ int len = strlen(r->status_line);
+ if (len < 3
+ || apr_strtoi64(r->status_line, &end, 10) != r->status
+ || (end - 3) != r->status_line
+ || (len >= 4 && ! apr_isspace(r->status_line[3]))) {
+ r->status_line = NULL;
+ return APR_EGENERAL;
+ }
+ /* Since we passed the above check, we know that length three
+ * is equivalent to only a 3 digit numeric http status.
+ * RFC2616 mandates a trailing space, let's add it.
+ */
+ if (len == 3) {
+ r->status_line = apr_pstrcat(r->pool, r->status_line, " ", NULL);
+ return APR_EGENERAL;
+ }
+ return APR_SUCCESS;
+ }
+ return APR_EGENERAL;
+}
+
+/*
+ * Determine the protocol to use for the response. Potentially downgrade
+ * to HTTP/1.0 in some situations and/or turn off keepalives.
+ *
+ * also prepare r->status_line.
+ */
+static void basic_http_header_check(request_rec *r,
+ const char **protocol)
+{
+ apr_status_t rv;
+
+ if (r->assbackwards) {
+ /* no such thing as a response protocol */
+ return;
+ }
+
+ rv = validate_status_line(r);
+
+ if (!r->status_line) {
+ r->status_line = ap_get_status_line(r->status);
+ } else if (rv != APR_SUCCESS) {
+ /* Status line is OK but our own reason phrase
+ * would be preferred if defined
+ */
+ const char *tmp = ap_get_status_line(r->status);
+ if (!strncmp(tmp, r->status_line, 3)) {
+ r->status_line = tmp;
+ }
+ }
+
+ /* Note that we must downgrade before checking for force responses. */
+ if (r->proto_num > HTTP_VERSION(1,0)
+ && apr_table_get(r->subprocess_env, "downgrade-1.0")) {
+ r->proto_num = HTTP_VERSION(1,0);
+ }
+
+ /* kludge around broken browsers when indicated by force-response-1.0
+ */
+ if (r->proto_num == HTTP_VERSION(1,0)
+ && apr_table_get(r->subprocess_env, "force-response-1.0")) {
+ *protocol = "HTTP/1.0";
+ r->connection->keepalive = AP_CONN_CLOSE;
+ }
+ else {
+ *protocol = AP_SERVER_PROTOCOL;
+ }
+
+}
+
+/* fill "bb" with a barebones/initial HTTP response header */
+static void basic_http_header(request_rec *r, apr_bucket_brigade *bb,
+ const char *protocol)
+{
+ char *date = NULL;
+ const char *proxy_date = NULL;
+ const char *server = NULL;
+ const char *us = ap_get_server_banner();
+ header_struct h;
+ struct iovec vec[4];
+
+ if (r->assbackwards) {
+ /* there are no headers to send */
+ return;
+ }
+
+ /* Output the HTTP/1.x Status-Line and the Date and Server fields */
+
+ vec[0].iov_base = (void *)protocol;
+ vec[0].iov_len = strlen(protocol);
+ vec[1].iov_base = (void *)" ";
+ vec[1].iov_len = sizeof(" ") - 1;
+ vec[2].iov_base = (void *)(r->status_line);
+ vec[2].iov_len = strlen(r->status_line);
+ vec[3].iov_base = (void *)CRLF;
+ vec[3].iov_len = sizeof(CRLF) - 1;
+#if APR_CHARSET_EBCDIC
+ {
+ char *tmp;
+ apr_size_t len;
+ tmp = apr_pstrcatv(r->pool, vec, 4, &len);
+ ap_xlate_proto_to_ascii(tmp, len);
+ apr_brigade_write(bb, NULL, NULL, tmp, len);
+ }
+#else
+ apr_brigade_writev(bb, NULL, NULL, vec, 4);
+#endif
+
+ h.pool = r->pool;
+ h.bb = bb;
+
+ /*
+ * keep the set-by-proxy server and date headers, otherwise
+ * generate a new server header / date header
+ */
+ if (r->proxyreq != PROXYREQ_NONE) {
+ proxy_date = apr_table_get(r->headers_out, "Date");
+ if (!proxy_date) {
+ /*
+ * proxy_date needs to be const. So use date for the creation of
+ * our own Date header and pass it over to proxy_date later to
+ * avoid a compiler warning.
+ */
+ date = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
+ ap_recent_rfc822_date(date, r->request_time);
+ }
+ server = apr_table_get(r->headers_out, "Server");
+ }
+ else {
+ date = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
+ ap_recent_rfc822_date(date, r->request_time);
+ }
+
+ form_header_field(&h, "Date", proxy_date ? proxy_date : date );
+
+ if (!server && *us)
+ server = us;
+ if (server)
+ form_header_field(&h, "Server", server);
+
+ if (APLOGrtrace3(r)) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
+ "Response sent with status %d%s",
+ r->status,
+ APLOGrtrace4(r) ? ", headers:" : "");
+
+ /*
+ * Date and Server are less interesting, use TRACE5 for them while
+ * using TRACE4 for the other headers.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, " Date: %s",
+ proxy_date ? proxy_date : date );
+ if (server)
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, " Server: %s",
+ server);
+ }
+
+
+ /* unset so we don't send them again */
+ apr_table_unset(r->headers_out, "Date"); /* Avoid bogosity */
+ if (server) {
+ apr_table_unset(r->headers_out, "Server");
+ }
+}
+
+AP_DECLARE(void) ap_basic_http_header(request_rec *r, apr_bucket_brigade *bb)
+{
+ const char *protocol = NULL;
+
+ basic_http_header_check(r, &protocol);
+ basic_http_header(r, bb, protocol);
+}
+
+static void terminate_header(apr_bucket_brigade *bb)
+{
+ char crlf[] = CRLF;
+ apr_size_t buflen;
+
+ buflen = strlen(crlf);
+ ap_xlate_proto_to_ascii(crlf, buflen);
+ apr_brigade_write(bb, NULL, NULL, crlf, buflen);
+}
+
+AP_DECLARE_NONSTD(int) ap_send_http_trace(request_rec *r)
+{
+ core_server_config *conf;
+ int rv;
+ apr_bucket_brigade *bb;
+ header_struct h;
+ apr_bucket *b;
+ int body;
+ char *bodyread = NULL, *bodyoff;
+ apr_size_t bodylen = 0;
+ apr_size_t bodybuf;
+ long res = -1; /* init to avoid gcc -Wall warning */
+
+ if (r->method_number != M_TRACE) {
+ return DECLINED;
+ }
+
+ /* Get the original request */
+ while (r->prev) {
+ r = r->prev;
+ }
+ conf = ap_get_core_module_config(r->server->module_config);
+
+ if (conf->trace_enable == AP_TRACE_DISABLE) {
+ apr_table_setn(r->notes, "error-notes",
+ "TRACE denied by server configuration");
+ return HTTP_METHOD_NOT_ALLOWED;
+ }
+
+ if (conf->trace_enable == AP_TRACE_EXTENDED)
+ /* XXX: should be = REQUEST_CHUNKED_PASS */
+ body = REQUEST_CHUNKED_DECHUNK;
+ else
+ body = REQUEST_NO_BODY;
+
+ if ((rv = ap_setup_client_block(r, body))) {
+ if (rv == HTTP_REQUEST_ENTITY_TOO_LARGE)
+ apr_table_setn(r->notes, "error-notes",
+ "TRACE with a request body is not allowed");
+ return rv;
+ }
+
+ if (ap_should_client_block(r)) {
+
+ if (r->remaining > 0) {
+ if (r->remaining > 65536) {
+ apr_table_setn(r->notes, "error-notes",
+ "Extended TRACE request bodies cannot exceed 64k\n");
+ return HTTP_REQUEST_ENTITY_TOO_LARGE;
+ }
+ /* always 32 extra bytes to catch chunk header exceptions */
+ bodybuf = (apr_size_t)r->remaining + 32;
+ }
+ else {
+ /* Add an extra 8192 for chunk headers */
+ bodybuf = 73730;
+ }
+
+ bodyoff = bodyread = apr_palloc(r->pool, bodybuf);
+
+ /* only while we have enough for a chunked header */
+ while ((!bodylen || bodybuf >= 32) &&
+ (res = ap_get_client_block(r, bodyoff, bodybuf)) > 0) {
+ bodylen += res;
+ bodybuf -= res;
+ bodyoff += res;
+ }
+ if (res > 0 && bodybuf < 32) {
+ /* discard_rest_of_request_body into our buffer */
+ while (ap_get_client_block(r, bodyread, bodylen) > 0)
+ ;
+ apr_table_setn(r->notes, "error-notes",
+ "Extended TRACE request bodies cannot exceed 64k\n");
+ return HTTP_REQUEST_ENTITY_TOO_LARGE;
+ }
+
+ if (res < 0) {
+ return HTTP_BAD_REQUEST;
+ }
+ }
+
+ ap_set_content_type(r, "message/http");
+
+ /* Now we recreate the request, and echo it back */
+
+ bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+#if APR_CHARSET_EBCDIC
+ {
+ char *tmp;
+ apr_size_t len;
+ len = strlen(r->the_request);
+ tmp = apr_pmemdup(r->pool, r->the_request, len);
+ ap_xlate_proto_to_ascii(tmp, len);
+ apr_brigade_putstrs(bb, NULL, NULL, tmp, CRLF_ASCII, NULL);
+ }
+#else
+ apr_brigade_putstrs(bb, NULL, NULL, r->the_request, CRLF, NULL);
+#endif
+ h.pool = r->pool;
+ h.bb = bb;
+ apr_table_do((int (*) (void *, const char *, const char *))
+ form_header_field, (void *) &h, r->headers_in, NULL);
+ apr_brigade_puts(bb, NULL, NULL, CRLF_ASCII);
+
+ /* If configured to accept a body, echo the body */
+ if (bodylen) {
+ b = apr_bucket_pool_create(bodyread, bodylen,
+ r->pool, bb->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ }
+
+ ap_pass_brigade(r->output_filters, bb);
+
+ return DONE;
+}
+
+typedef struct header_filter_ctx {
+ int headers_sent;
+} header_filter_ctx;
+
+AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f,
+ apr_bucket_brigade *b)
+{
+ request_rec *r = f->r;
+ conn_rec *c = r->connection;
+ const char *clheader;
+ int header_only = (r->header_only || AP_STATUS_IS_HEADER_ONLY(r->status));
+ const char *protocol = NULL;
+ apr_bucket *e;
+ apr_bucket_brigade *b2;
+ header_struct h;
+ header_filter_ctx *ctx = f->ctx;
+ const char *ctype;
+ ap_bucket_error *eb = NULL;
+ apr_status_t rv = APR_SUCCESS;
+ int recursive_error = 0;
+
+ AP_DEBUG_ASSERT(!r->main);
+
+ if (!ctx) {
+ ctx = f->ctx = apr_pcalloc(r->pool, sizeof(header_filter_ctx));
+ }
+ else if (ctx->headers_sent) {
+ /* Eat body if response must not have one. */
+ if (header_only) {
+ /* Still next filters may be waiting for EOS, so pass it (alone)
+ * when encountered and be done with this filter.
+ */
+ e = APR_BRIGADE_LAST(b);
+ if (e != APR_BRIGADE_SENTINEL(b) && APR_BUCKET_IS_EOS(e)) {
+ APR_BUCKET_REMOVE(e);
+ apr_brigade_cleanup(b);
+ APR_BRIGADE_INSERT_HEAD(b, e);
+ ap_remove_output_filter(f);
+ rv = ap_pass_brigade(f->next, b);
+ }
+ apr_brigade_cleanup(b);
+ return rv;
+ }
+ }
+
+ for (e = APR_BRIGADE_FIRST(b);
+ e != APR_BRIGADE_SENTINEL(b);
+ e = APR_BUCKET_NEXT(e))
+ {
+ if (AP_BUCKET_IS_ERROR(e) && !eb) {
+ eb = e->data;
+ continue;
+ }
+ /*
+ * If we see an EOC bucket it is a signal that we should get out
+ * of the way doing nothing.
+ */
+ if (AP_BUCKET_IS_EOC(e)) {
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, b);
+ }
+ }
+
+ if (!ctx->headers_sent && !check_headers(r)) {
+ /* We may come back here from ap_die() below,
+ * so clear anything from this response.
+ */
+ apr_table_clear(r->headers_out);
+ apr_table_clear(r->err_headers_out);
+ apr_brigade_cleanup(b);
+
+ /* Don't recall ap_die() if we come back here (from its own internal
+ * redirect or error response), otherwise we can end up in infinite
+ * recursion; better fall through with 500, minimal headers and an
+ * empty body (EOS only).
+ */
+ if (!check_headers_recursion(r)) {
+ ap_die(HTTP_INTERNAL_SERVER_ERROR, r);
+ return AP_FILTER_ERROR;
+ }
+ r->status = HTTP_INTERNAL_SERVER_ERROR;
+ e = ap_bucket_eoc_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(b, e);
+ e = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(b, e);
+ r->content_type = r->content_encoding = NULL;
+ r->content_languages = NULL;
+ ap_set_content_length(r, 0);
+ recursive_error = 1;
+ }
+ else if (eb) {
+ int status;
+ status = eb->status;
+ apr_brigade_cleanup(b);
+ ap_die(status, r);
+ return AP_FILTER_ERROR;
+ }
+
+ if (r->assbackwards) {
+ r->sent_bodyct = 1;
+ ap_remove_output_filter(f);
+ rv = ap_pass_brigade(f->next, b);
+ goto out;
+ }
+
+ /*
+ * Now that we are ready to send a response, we need to combine the two
+ * header field tables into a single table. If we don't do this, our
+ * later attempts to set or unset a given fieldname might be bypassed.
+ */
+ if (!apr_is_empty_table(r->err_headers_out)) {
+ r->headers_out = apr_table_overlay(r->pool, r->err_headers_out,
+ r->headers_out);
+ }
+
+ /*
+ * Remove the 'Vary' header field if the client can't handle it.
+ * Since this will have nasty effects on HTTP/1.1 caches, force
+ * the response into HTTP/1.0 mode.
+ *
+ * Note: the force-response-1.0 should come before the call to
+ * basic_http_header_check()
+ */
+ if (apr_table_get(r->subprocess_env, "force-no-vary") != NULL) {
+ apr_table_unset(r->headers_out, "Vary");
+ r->proto_num = HTTP_VERSION(1,0);
+ apr_table_setn(r->subprocess_env, "force-response-1.0", "1");
+ }
+ else {
+ fixup_vary(r);
+ }
+
+ /*
+ * Now remove any ETag response header field if earlier processing
+ * says so (such as a 'FileETag None' directive).
+ */
+ if (apr_table_get(r->notes, "no-etag") != NULL) {
+ apr_table_unset(r->headers_out, "ETag");
+ }
+
+ /* determine the protocol and whether we should use keepalives. */
+ basic_http_header_check(r, &protocol);
+ ap_set_keepalive(r);
+
+ if (AP_STATUS_IS_HEADER_ONLY(r->status)) {
+ apr_table_unset(r->headers_out, "Transfer-Encoding");
+ apr_table_unset(r->headers_out, "Content-Length");
+ r->content_type = r->content_encoding = NULL;
+ r->content_languages = NULL;
+ r->clength = r->chunked = 0;
+ }
+ else if (r->chunked) {
+ apr_table_mergen(r->headers_out, "Transfer-Encoding", "chunked");
+ apr_table_unset(r->headers_out, "Content-Length");
+ }
+
+ ctype = ap_make_content_type(r, r->content_type);
+ if (ctype) {
+ apr_table_setn(r->headers_out, "Content-Type", ctype);
+ }
+
+ if (r->content_encoding) {
+ apr_table_setn(r->headers_out, "Content-Encoding",
+ r->content_encoding);
+ }
+
+ if (!apr_is_empty_array(r->content_languages)) {
+ int i;
+ char *token;
+ char **languages = (char **)(r->content_languages->elts);
+ const char *field = apr_table_get(r->headers_out, "Content-Language");
+
+ while (field && (token = ap_get_list_item(r->pool, &field)) != NULL) {
+ for (i = 0; i < r->content_languages->nelts; ++i) {
+ if (!ap_cstr_casecmp(token, languages[i]))
+ break;
+ }
+ if (i == r->content_languages->nelts) {
+ *((char **) apr_array_push(r->content_languages)) = token;
+ }
+ }
+
+ field = apr_array_pstrcat(r->pool, r->content_languages, ',');
+ apr_table_setn(r->headers_out, "Content-Language", field);
+ }
+
+ /*
+ * Control cachability for non-cacheable responses if not already set by
+ * some other part of the server configuration.
+ */
+ if (r->no_cache && !apr_table_get(r->headers_out, "Expires")) {
+ char *date = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
+ ap_recent_rfc822_date(date, r->request_time);
+ apr_table_addn(r->headers_out, "Expires", date);
+ }
+
+ /* This is a hack, but I can't find anyway around it. The idea is that
+ * we don't want to send out 0 Content-Lengths if it is a head request.
+ * This happens when modules try to outsmart the server, and return
+ * if they see a HEAD request. Apache 1.3 handlers were supposed to
+ * just return in that situation, and the core handled the HEAD. In
+ * 2.0, if a handler returns, then the core sends an EOS bucket down
+ * the filter stack, and the content-length filter computes a C-L of
+ * zero and that gets put in the headers, and we end up sending a
+ * zero C-L to the client. We can't just remove the C-L filter,
+ * because well behaved 2.0 handlers will send their data down the stack,
+ * and we will compute a real C-L for the head request. RBB
+ */
+ if (r->header_only
+ && (clheader = apr_table_get(r->headers_out, "Content-Length"))
+ && !strcmp(clheader, "0")) {
+ apr_table_unset(r->headers_out, "Content-Length");
+ }
+
+ b2 = apr_brigade_create(r->pool, c->bucket_alloc);
+ basic_http_header(r, b2, protocol);
+
+ h.pool = r->pool;
+ h.bb = b2;
+
+ send_all_header_fields(&h, r);
+
+ terminate_header(b2);
+
+ if (header_only) {
+ e = APR_BRIGADE_LAST(b);
+ if (e != APR_BRIGADE_SENTINEL(b) && APR_BUCKET_IS_EOS(e)) {
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(b2, e);
+ ap_remove_output_filter(f);
+ }
+ apr_brigade_cleanup(b);
+ }
+
+ rv = ap_pass_brigade(f->next, b2);
+ apr_brigade_cleanup(b2);
+ ctx->headers_sent = 1;
+
+ if (rv != APR_SUCCESS || header_only) {
+ goto out;
+ }
+
+ r->sent_bodyct = 1; /* Whatever follows is real body stuff... */
+
+ if (r->chunked) {
+ /* We can't add this filter until we have already sent the headers.
+ * If we add it before this point, then the headers will be chunked
+ * as well, and that is just wrong.
+ */
+ ap_add_output_filter("CHUNK", NULL, r, r->connection);
+ }
+
+ /* Don't remove this filter until after we have added the CHUNK filter.
+ * Otherwise, f->next won't be the CHUNK filter and thus the first
+ * brigade won't be chunked properly.
+ */
+ ap_remove_output_filter(f);
+ rv = ap_pass_brigade(f->next, b);
+out:
+ if (recursive_error) {
+ return AP_FILTER_ERROR;
+ }
+ return rv;
+}
+
+/*
+ * Map specific APR codes returned by the filter stack to HTTP error
+ * codes, or the default status code provided. Use it as follows:
+ *
+ * return ap_map_http_request_error(rv, HTTP_BAD_REQUEST);
+ *
+ * If the filter has already handled the error, AP_FILTER_ERROR will
+ * be returned, which is cleanly passed through.
+ *
+ * These mappings imply that the filter stack is reading from the
+ * downstream client, the proxy will map these codes differently.
+ */
+AP_DECLARE(int) ap_map_http_request_error(apr_status_t rv, int status)
+{
+ switch (rv) {
+ case AP_FILTER_ERROR:
+ return AP_FILTER_ERROR;
+
+ case APR_ENOSPC:
+ return HTTP_REQUEST_ENTITY_TOO_LARGE;
+
+ case APR_ENOTIMPL:
+ return HTTP_NOT_IMPLEMENTED;
+
+ case APR_TIMEUP:
+ case APR_ETIMEDOUT:
+ return HTTP_REQUEST_TIME_OUT;
+
+ default:
+ return status;
+ }
+}
+
+/* In HTTP/1.1, any method can have a body. However, most GET handlers
+ * wouldn't know what to do with a request body if they received one.
+ * This helper routine tests for and reads any message body in the request,
+ * simply discarding whatever it receives. We need to do this because
+ * failing to read the request body would cause it to be interpreted
+ * as the next request on a persistent connection.
+ *
+ * Since we return an error status if the request is malformed, this
+ * routine should be called at the beginning of a no-body handler, e.g.,
+ *
+ * if ((retval = ap_discard_request_body(r)) != OK) {
+ * return retval;
+ * }
+ */
+AP_DECLARE(int) ap_discard_request_body(request_rec *r)
+{
+ int rc = OK;
+ conn_rec *c = r->connection;
+ apr_bucket_brigade *bb;
+
+ /* Sometimes we'll get in a state where the input handling has
+ * detected an error where we want to drop the connection, so if
+ * that's the case, don't read the data as that is what we're trying
+ * to avoid.
+ *
+ * This function is also a no-op on a subrequest.
+ */
+ if (r->main || c->keepalive == AP_CONN_CLOSE) {
+ return OK;
+ }
+ if (ap_status_drops_connection(r->status)) {
+ c->keepalive = AP_CONN_CLOSE;
+ return OK;
+ }
+
+ bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+ for (;;) {
+ apr_status_t rv;
+
+ rv = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES,
+ APR_BLOCK_READ, HUGE_STRING_LEN);
+ if (rv != APR_SUCCESS) {
+ rc = ap_map_http_request_error(rv, HTTP_BAD_REQUEST);
+ goto cleanup;
+ }
+
+ while (!APR_BRIGADE_EMPTY(bb)) {
+ apr_bucket *b = APR_BRIGADE_FIRST(bb);
+
+ if (APR_BUCKET_IS_EOS(b)) {
+ goto cleanup;
+ }
+
+ /* There is no need to read empty or metadata buckets or
+ * buckets of known length, but we MUST read buckets of
+ * unknown length in order to exhaust them.
+ */
+ if (b->length == (apr_size_t)-1) {
+ apr_size_t len;
+ const char *data;
+
+ rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
+ if (rv != APR_SUCCESS) {
+ rc = HTTP_BAD_REQUEST;
+ goto cleanup;
+ }
+ }
+
+ apr_bucket_delete(b);
+ }
+ }
+
+cleanup:
+ apr_brigade_cleanup(bb);
+ if (rc != OK) {
+ c->keepalive = AP_CONN_CLOSE;
+ }
+ return rc;
+}
+
+/* Here we deal with getting the request message body from the client.
+ * Whether or not the request contains a body is signaled by the presence
+ * of a non-zero Content-Length or by a Transfer-Encoding: chunked.
+ *
+ * Note that this is more complicated than it was in Apache 1.1 and prior
+ * versions, because chunked support means that the module does less.
+ *
+ * The proper procedure is this:
+ *
+ * 1. Call ap_setup_client_block() near the beginning of the request
+ * handler. This will set up all the necessary properties, and will
+ * return either OK, or an error code. If the latter, the module should
+ * return that error code. The second parameter selects the policy to
+ * apply if the request message indicates a body, and how a chunked
+ * transfer-coding should be interpreted. Choose one of
+ *
+ * REQUEST_NO_BODY Send 413 error if message has any body
+ * REQUEST_CHUNKED_ERROR Send 411 error if body without Content-Length
+ * REQUEST_CHUNKED_DECHUNK If chunked, remove the chunks for me.
+ * REQUEST_CHUNKED_PASS If chunked, pass the chunk headers with body.
+ *
+ * In order to use the last two options, the caller MUST provide a buffer
+ * large enough to hold a chunk-size line, including any extensions.
+ *
+ * 2. When you are ready to read a body (if any), call ap_should_client_block().
+ * This will tell the module whether or not to read input. If it is 0,
+ * the module should assume that there is no message body to read.
+ *
+ * 3. Finally, call ap_get_client_block in a loop. Pass it a buffer and its size.
+ * It will put data into the buffer (not necessarily a full buffer), and
+ * return the length of the input block. When it is done reading, it will
+ * return 0 if EOF, or -1 if there was an error.
+ * If an error occurs on input, we force an end to keepalive.
+ *
+ * This step also sends a 100 Continue response to HTTP/1.1 clients if appropriate.
+ */
+
+AP_DECLARE(int) ap_setup_client_block(request_rec *r, int read_policy)
+{
+ const char *tenc = apr_table_get(r->headers_in, "Transfer-Encoding");
+ const char *lenp = apr_table_get(r->headers_in, "Content-Length");
+ apr_off_t limit_req_body = ap_get_limit_req_body(r);
+
+ r->read_body = read_policy;
+ r->read_chunked = 0;
+ r->remaining = 0;
+
+ if (tenc) {
+ if (ap_cstr_casecmp(tenc, "chunked")) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01592)
+ "Unknown Transfer-Encoding %s", tenc);
+ return HTTP_NOT_IMPLEMENTED;
+ }
+ if (r->read_body == REQUEST_CHUNKED_ERROR) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01593)
+ "chunked Transfer-Encoding forbidden: %s", r->uri);
+ return (lenp) ? HTTP_BAD_REQUEST : HTTP_LENGTH_REQUIRED;
+ }
+
+ r->read_chunked = 1;
+ }
+ else if (lenp) {
+ if (!ap_parse_strict_length(&r->remaining, lenp)) {
+ r->remaining = 0;
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01594)
+ "Invalid Content-Length '%s'", lenp);
+ return HTTP_BAD_REQUEST;
+ }
+ }
+
+ if ((r->read_body == REQUEST_NO_BODY)
+ && (r->read_chunked || (r->remaining > 0))) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01595)
+ "%s with body is not allowed for %s", r->method, r->uri);
+ return HTTP_REQUEST_ENTITY_TOO_LARGE;
+ }
+
+ if (limit_req_body > 0 && (r->remaining > limit_req_body)) {
+ /* will be logged when the body is discarded */
+ return HTTP_REQUEST_ENTITY_TOO_LARGE;
+ }
+
+#ifdef AP_DEBUG
+ {
+ /* Make sure ap_getline() didn't leave any droppings. */
+ core_request_config *req_cfg =
+ (core_request_config *)ap_get_core_module_config(r->request_config);
+ AP_DEBUG_ASSERT(APR_BRIGADE_EMPTY(req_cfg->bb));
+ }
+#endif
+
+ return OK;
+}
+
+AP_DECLARE(int) ap_should_client_block(request_rec *r)
+{
+ /* First check if we have already read the request body */
+
+ if (r->read_length || (!r->read_chunked && (r->remaining <= 0))) {
+ return 0;
+ }
+
+ return 1;
+}
+
+/* get_client_block is called in a loop to get the request message body.
+ * This is quite simple if the client includes a content-length
+ * (the normal case), but gets messy if the body is chunked. Note that
+ * r->remaining is used to maintain state across calls and that
+ * r->read_length is the total number of bytes given to the caller
+ * across all invocations. It is messy because we have to be careful not
+ * to read past the data provided by the client, since these reads block.
+ * Returns 0 on End-of-body, -1 on error or premature chunk end.
+ *
+ */
+AP_DECLARE(long) ap_get_client_block(request_rec *r, char *buffer,
+ apr_size_t bufsiz)
+{
+ apr_status_t rv;
+ apr_bucket_brigade *bb;
+
+ if (r->remaining < 0 || (!r->read_chunked && r->remaining == 0)) {
+ return 0;
+ }
+
+ bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+ if (bb == NULL) {
+ r->connection->keepalive = AP_CONN_CLOSE;
+ return -1;
+ }
+
+ rv = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES,
+ APR_BLOCK_READ, bufsiz);
+
+ /* We lose the failure code here. This is why ap_get_client_block should
+ * not be used.
+ */
+ if (rv == AP_FILTER_ERROR) {
+ /* AP_FILTER_ERROR means a filter has responded already,
+ * we are DONE.
+ */
+ apr_brigade_destroy(bb);
+ return -1;
+ }
+ if (rv != APR_SUCCESS) {
+ /* if we actually fail here, we want to just return and
+ * stop trying to read data from the client.
+ */
+ r->connection->keepalive = AP_CONN_CLOSE;
+ apr_brigade_destroy(bb);
+ return -1;
+ }
+
+ /* If this fails, it means that a filter is written incorrectly and that
+ * it needs to learn how to properly handle APR_BLOCK_READ requests by
+ * returning data when requested.
+ */
+ AP_DEBUG_ASSERT(!APR_BRIGADE_EMPTY(bb));
+
+ /* Check to see if EOS in the brigade.
+ *
+ * If so, we have to leave a nugget for the *next* ap_get_client_block
+ * call to return 0.
+ */
+ if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb))) {
+ if (r->read_chunked) {
+ r->remaining = -1;
+ }
+ else {
+ r->remaining = 0;
+ }
+ }
+
+ rv = apr_brigade_flatten(bb, buffer, &bufsiz);
+ if (rv != APR_SUCCESS) {
+ apr_brigade_destroy(bb);
+ return -1;
+ }
+
+ /* XXX yank me? */
+ r->read_length += bufsiz;
+
+ apr_brigade_destroy(bb);
+ return bufsiz;
+}
+
+/* Context struct for ap_http_outerror_filter */
+typedef struct {
+ int seen_eoc;
+ int first_error;
+} outerror_filter_ctx_t;
+
+/* Filter to handle any error buckets on output */
+apr_status_t ap_http_outerror_filter(ap_filter_t *f,
+ apr_bucket_brigade *b)
+{
+ request_rec *r = f->r;
+ outerror_filter_ctx_t *ctx = (outerror_filter_ctx_t *)(f->ctx);
+ apr_bucket *e;
+
+ /* Create context if none is present */
+ if (!ctx) {
+ ctx = apr_pcalloc(r->pool, sizeof(outerror_filter_ctx_t));
+ f->ctx = ctx;
+ }
+ for (e = APR_BRIGADE_FIRST(b);
+ e != APR_BRIGADE_SENTINEL(b);
+ e = APR_BUCKET_NEXT(e))
+ {
+ if (AP_BUCKET_IS_ERROR(e)) {
+ /*
+ * Start of error handling state tree. Just one condition
+ * right now :)
+ */
+ if (((ap_bucket_error *)(e->data))->status == HTTP_BAD_GATEWAY) {
+ /* stream aborted and we have not ended it yet */
+ r->connection->keepalive = AP_CONN_CLOSE;
+ }
+ /*
+ * Memorize the status code of the first error bucket for possible
+ * later use.
+ */
+ if (!ctx->first_error) {
+ ctx->first_error = ((ap_bucket_error *)(e->data))->status;
+ }
+ continue;
+ }
+ /* Detect EOC buckets and memorize this in the context. */
+ if (AP_BUCKET_IS_EOC(e)) {
+ r->connection->keepalive = AP_CONN_CLOSE;
+ ctx->seen_eoc = 1;
+ }
+ }
+ /*
+ * Remove all data buckets that are in a brigade after an EOC bucket
+ * was seen, as an EOC bucket tells us that no (further) resource
+ * and protocol data should go out to the client. OTOH meta buckets
+ * are still welcome as they might trigger needed actions down in
+ * the chain (e.g. in network filters like SSL).
+ * Remark 1: It is needed to dump ALL data buckets in the brigade
+ * since an filter in between might have inserted data
+ * buckets BEFORE the EOC bucket sent by the original
+ * sender and we do NOT want this data to be sent.
+ * Remark 2: Dumping all data buckets here does not necessarily mean
+ * that no further data is send to the client as:
+ * 1. Network filters like SSL can still be triggered via
+ * meta buckets to talk with the client e.g. for a
+ * clean shutdown.
+ * 2. There could be still data that was buffered before
+ * down in the chain that gets flushed by a FLUSH or an
+ * EOS bucket.
+ */
+ if (ctx->seen_eoc) {
+ /*
+ * Set the request status to the status of the first error bucket.
+ * This should ensure that we log an appropriate status code in
+ * the access log.
+ * We need to set r->status on each call after we noticed an EOC as
+ * data bucket generators like ap_die might have changed the status
+ * code. But we know better in this case and insist on the status
+ * code that we have seen in the error bucket.
+ */
+ if (ctx->first_error) {
+ r->status = ctx->first_error;
+ }
+ for (e = APR_BRIGADE_FIRST(b);
+ e != APR_BRIGADE_SENTINEL(b);
+ e = APR_BUCKET_NEXT(e))
+ {
+ if (!APR_BUCKET_IS_METADATA(e)) {
+ APR_BUCKET_REMOVE(e);
+ }
+ }
+ }
+
+ return ap_pass_brigade(f->next, b);
+}
diff --git a/modules/http/http_protocol.c b/modules/http/http_protocol.c
new file mode 100644
index 0000000..d031f24
--- /dev/null
+++ b/modules/http/http_protocol.c
@@ -0,0 +1,1671 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * http_protocol.c --- routines which directly communicate with the client.
+ *
+ * Code originally by Rob McCool; much redone by Robert S. Thau
+ * and the Apache Software Foundation.
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_buckets.h"
+#include "apr_lib.h"
+#include "apr_signal.h"
+
+#define APR_WANT_STDIO /* for sscanf */
+#define APR_WANT_STRFUNC
+#define APR_WANT_MEMFUNC
+#include "apr_want.h"
+
+#include "util_filter.h"
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_protocol.h"
+#include "http_main.h"
+#include "http_request.h"
+#include "http_vhost.h"
+#include "http_log.h" /* For errors detected in basic auth common
+ * support code... */
+#include "apr_date.h" /* For apr_date_parse_http and APR_DATE_BAD */
+#include "util_charset.h"
+#include "util_ebcdic.h"
+#include "util_time.h"
+#include "ap_mpm.h"
+
+#include "mod_core.h"
+
+#if APR_HAVE_STDARG_H
+#include <stdarg.h>
+#endif
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+APLOG_USE_MODULE(http);
+
+/* New Apache routine to map status codes into array indices
+ * e.g. 100 -> 0, 101 -> 1, 200 -> 2 ...
+ * The number of status lines must equal the value of
+ * RESPONSE_CODES (httpd.h) and must be listed in order.
+ * No gaps are allowed between X00 and the largest Xnn
+ * for any X (see ap_index_of_response).
+ * When adding a new code here, add a define to httpd.h
+ * as well.
+ */
+
+static const char * const status_lines[RESPONSE_CODES] =
+{
+ "100 Continue",
+ "101 Switching Protocols",
+ "102 Processing",
+#define LEVEL_200 3
+ "200 OK",
+ "201 Created",
+ "202 Accepted",
+ "203 Non-Authoritative Information",
+ "204 No Content",
+ "205 Reset Content",
+ "206 Partial Content",
+ "207 Multi-Status",
+ "208 Already Reported",
+ NULL, /* 209 */
+ NULL, /* 210 */
+ NULL, /* 211 */
+ NULL, /* 212 */
+ NULL, /* 213 */
+ NULL, /* 214 */
+ NULL, /* 215 */
+ NULL, /* 216 */
+ NULL, /* 217 */
+ NULL, /* 218 */
+ NULL, /* 219 */
+ NULL, /* 220 */
+ NULL, /* 221 */
+ NULL, /* 222 */
+ NULL, /* 223 */
+ NULL, /* 224 */
+ NULL, /* 225 */
+ "226 IM Used",
+#define LEVEL_300 30
+ "300 Multiple Choices",
+ "301 Moved Permanently",
+ "302 Found",
+ "303 See Other",
+ "304 Not Modified",
+ "305 Use Proxy",
+ NULL, /* 306 */
+ "307 Temporary Redirect",
+ "308 Permanent Redirect",
+#define LEVEL_400 39
+ "400 Bad Request",
+ "401 Unauthorized",
+ "402 Payment Required",
+ "403 Forbidden",
+ "404 Not Found",
+ "405 Method Not Allowed",
+ "406 Not Acceptable",
+ "407 Proxy Authentication Required",
+ "408 Request Timeout",
+ "409 Conflict",
+ "410 Gone",
+ "411 Length Required",
+ "412 Precondition Failed",
+ "413 Request Entity Too Large",
+ "414 Request-URI Too Long",
+ "415 Unsupported Media Type",
+ "416 Requested Range Not Satisfiable",
+ "417 Expectation Failed",
+ NULL, /* 418 */
+ NULL, /* 419 */
+ NULL, /* 420 */
+ "421 Misdirected Request",
+ "422 Unprocessable Entity",
+ "423 Locked",
+ "424 Failed Dependency",
+ NULL, /* 425 */
+ "426 Upgrade Required",
+ NULL, /* 427 */
+ "428 Precondition Required",
+ "429 Too Many Requests",
+ NULL, /* 430 */
+ "431 Request Header Fields Too Large",
+ NULL, /* 432 */
+ NULL, /* 433 */
+ NULL, /* 434 */
+ NULL, /* 435 */
+ NULL, /* 436 */
+ NULL, /* 437 */
+ NULL, /* 438 */
+ NULL, /* 439 */
+ NULL, /* 440 */
+ NULL, /* 441 */
+ NULL, /* 442 */
+ NULL, /* 443 */
+ NULL, /* 444 */
+ NULL, /* 445 */
+ NULL, /* 446 */
+ NULL, /* 447 */
+ NULL, /* 448 */
+ NULL, /* 449 */
+ NULL, /* 450 */
+ "451 Unavailable For Legal Reasons",
+#define LEVEL_500 91
+ "500 Internal Server Error",
+ "501 Not Implemented",
+ "502 Bad Gateway",
+ "503 Service Unavailable",
+ "504 Gateway Timeout",
+ "505 HTTP Version Not Supported",
+ "506 Variant Also Negotiates",
+ "507 Insufficient Storage",
+ "508 Loop Detected",
+ NULL, /* 509 */
+ "510 Not Extended",
+ "511 Network Authentication Required"
+};
+
+APR_HOOK_STRUCT(
+ APR_HOOK_LINK(insert_error_filter)
+)
+
+AP_IMPLEMENT_HOOK_VOID(insert_error_filter, (request_rec *r), (r))
+
+/* The index of the first bit field that is used to index into a limit
+ * bitmask. M_INVALID + 1 to METHOD_NUMBER_LAST.
+ */
+#define METHOD_NUMBER_FIRST (M_INVALID + 1)
+
+/* The max method number. Method numbers are used to shift bitmasks,
+ * so this cannot exceed 63, and all bits high is equal to -1, which is a
+ * special flag, so the last bit used has index 62.
+ */
+#define METHOD_NUMBER_LAST 62
+
+static int is_mpm_running(void)
+{
+ int mpm_state = 0;
+
+ if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state)) {
+ return 0;
+ }
+
+ if (mpm_state == AP_MPMQ_STOPPING) {
+ return 0;
+ }
+
+ return 1;
+}
+
+
+AP_DECLARE(int) ap_set_keepalive(request_rec *r)
+{
+ int ka_sent = 0;
+ int left = r->server->keep_alive_max - r->connection->keepalives;
+ int wimpy = ap_find_token(r->pool,
+ apr_table_get(r->headers_out, "Connection"),
+ "close");
+ const char *conn = apr_table_get(r->headers_in, "Connection");
+
+ /* The following convoluted conditional determines whether or not
+ * the current connection should remain persistent after this response
+ * (a.k.a. HTTP Keep-Alive) and whether or not the output message
+ * body should use the HTTP/1.1 chunked transfer-coding. In English,
+ *
+ * IF we have not marked this connection as errored;
+ * and the client isn't expecting 100-continue (PR47087 - more
+ * input here could be the client continuing when we're
+ * closing the request).
+ * and the response body has a defined length due to the status code
+ * being 304 or 204, the request method being HEAD, already
+ * having defined Content-Length or Transfer-Encoding: chunked, or
+ * the request version being HTTP/1.1 and thus capable of being set
+ * as chunked [we know the (r->chunked = 1) side-effect is ugly];
+ * and the server configuration enables keep-alive;
+ * and the server configuration has a reasonable inter-request timeout;
+ * and there is no maximum # requests or the max hasn't been reached;
+ * and the response status does not require a close;
+ * and the response generator has not already indicated close;
+ * and the client did not request non-persistence (Connection: close);
+ * and we haven't been configured to ignore the buggy twit
+ * or they're a buggy twit coming through a HTTP/1.1 proxy
+ * and the client is requesting an HTTP/1.0-style keep-alive
+ * or the client claims to be HTTP/1.1 compliant (perhaps a proxy);
+ * and this MPM process is not already exiting
+ * THEN we can be persistent, which requires more headers be output.
+ *
+ * Note that the condition evaluation order is extremely important.
+ */
+ if ((r->connection->keepalive != AP_CONN_CLOSE)
+ && !r->expecting_100
+ && (r->header_only
+ || AP_STATUS_IS_HEADER_ONLY(r->status)
+ || apr_table_get(r->headers_out, "Content-Length")
+ || ap_is_chunked(r->pool,
+ apr_table_get(r->headers_out,
+ "Transfer-Encoding"))
+ || ((r->proto_num >= HTTP_VERSION(1,1))
+ && (r->chunked = 1))) /* THIS CODE IS CORRECT, see above. */
+ && r->server->keep_alive
+ && (r->server->keep_alive_timeout > 0)
+ && ((r->server->keep_alive_max == 0)
+ || (left > 0))
+ && !ap_status_drops_connection(r->status)
+ && !wimpy
+ && !ap_find_token(r->pool, conn, "close")
+ && (!apr_table_get(r->subprocess_env, "nokeepalive")
+ || apr_table_get(r->headers_in, "Via"))
+ && ((ka_sent = ap_find_token(r->pool, conn, "keep-alive"))
+ || (r->proto_num >= HTTP_VERSION(1,1)))
+ && is_mpm_running()) {
+
+ r->connection->keepalive = AP_CONN_KEEPALIVE;
+ r->connection->keepalives++;
+
+ /* If they sent a Keep-Alive token, send one back */
+ if (ka_sent) {
+ if (r->server->keep_alive_max) {
+ apr_table_setn(r->headers_out, "Keep-Alive",
+ apr_psprintf(r->pool, "timeout=%d, max=%d",
+ (int)apr_time_sec(r->server->keep_alive_timeout),
+ left));
+ }
+ else {
+ apr_table_setn(r->headers_out, "Keep-Alive",
+ apr_psprintf(r->pool, "timeout=%d",
+ (int)apr_time_sec(r->server->keep_alive_timeout)));
+ }
+ apr_table_mergen(r->headers_out, "Connection", "Keep-Alive");
+ }
+
+ return 1;
+ }
+
+ /* Otherwise, we need to indicate that we will be closing this
+ * connection immediately after the current response.
+ *
+ * We only really need to send "close" to HTTP/1.1 clients, but we
+ * always send it anyway, because a broken proxy may identify itself
+ * as HTTP/1.0, but pass our request along with our HTTP/1.1 tag
+ * to a HTTP/1.1 client. Better safe than sorry.
+ */
+ if (!wimpy) {
+ apr_table_mergen(r->headers_out, "Connection", "close");
+ }
+
+ /*
+ * If we had previously been a keepalive connection and this
+ * is the last one, then bump up the number of keepalives
+ * we've had
+ */
+ if ((r->connection->keepalive != AP_CONN_CLOSE)
+ && r->server->keep_alive_max
+ && !left) {
+ r->connection->keepalives++;
+ }
+ r->connection->keepalive = AP_CONN_CLOSE;
+
+ return 0;
+}
+
+AP_DECLARE(ap_condition_e) ap_condition_if_match(request_rec *r,
+ apr_table_t *headers)
+{
+ const char *if_match, *etag;
+
+ /* A server MUST use the strong comparison function (see section 13.3.3)
+ * to compare the entity tags in If-Match.
+ */
+ if ((if_match = apr_table_get(r->headers_in, "If-Match")) != NULL) {
+ if (if_match[0] == '*'
+ || ((etag = apr_table_get(headers, "ETag")) != NULL
+ && ap_find_etag_strong(r->pool, if_match, etag))) {
+ return AP_CONDITION_STRONG;
+ }
+ else {
+ return AP_CONDITION_NOMATCH;
+ }
+ }
+
+ return AP_CONDITION_NONE;
+}
+
+AP_DECLARE(ap_condition_e) ap_condition_if_unmodified_since(request_rec *r,
+ apr_table_t *headers)
+{
+ const char *if_unmodified;
+
+ if_unmodified = apr_table_get(r->headers_in, "If-Unmodified-Since");
+ if (if_unmodified) {
+ apr_int64_t mtime, reqtime;
+
+ apr_time_t ius = apr_time_sec(apr_date_parse_http(if_unmodified));
+
+ /* All of our comparisons must be in seconds, because that's the
+ * highest time resolution the HTTP specification allows.
+ */
+ mtime = apr_time_sec(apr_date_parse_http(
+ apr_table_get(headers, "Last-Modified")));
+ if (mtime == APR_DATE_BAD) {
+ mtime = apr_time_sec(r->mtime ? r->mtime : apr_time_now());
+ }
+
+ reqtime = apr_time_sec(apr_date_parse_http(
+ apr_table_get(headers, "Date")));
+ if (!reqtime) {
+ reqtime = apr_time_sec(r->request_time);
+ }
+
+ if ((ius != APR_DATE_BAD) && (mtime > ius)) {
+ if (reqtime < mtime + 60) {
+ if (apr_table_get(r->headers_in, "Range")) {
+ /* weak matches not allowed with Range requests */
+ return AP_CONDITION_NOMATCH;
+ }
+ else {
+ return AP_CONDITION_WEAK;
+ }
+ }
+ else {
+ return AP_CONDITION_STRONG;
+ }
+ }
+ else {
+ return AP_CONDITION_NOMATCH;
+ }
+ }
+
+ return AP_CONDITION_NONE;
+}
+
+AP_DECLARE(ap_condition_e) ap_condition_if_none_match(request_rec *r,
+ apr_table_t *headers)
+{
+ const char *if_nonematch, *etag;
+
+ if_nonematch = apr_table_get(r->headers_in, "If-None-Match");
+ if (if_nonematch != NULL) {
+
+ if (if_nonematch[0] == '*') {
+ return AP_CONDITION_STRONG;
+ }
+
+ /* See section 13.3.3 for rules on how to determine if two entities tags
+ * match. The weak comparison function can only be used with GET or HEAD
+ * requests.
+ */
+ if (r->method_number == M_GET) {
+ if ((etag = apr_table_get(headers, "ETag")) != NULL) {
+ if (apr_table_get(r->headers_in, "Range")) {
+ if (ap_find_etag_strong(r->pool, if_nonematch, etag)) {
+ return AP_CONDITION_STRONG;
+ }
+ }
+ else {
+ if (ap_find_etag_weak(r->pool, if_nonematch, etag)) {
+ return AP_CONDITION_WEAK;
+ }
+ }
+ }
+ }
+
+ else if ((etag = apr_table_get(headers, "ETag")) != NULL
+ && ap_find_etag_strong(r->pool, if_nonematch, etag)) {
+ return AP_CONDITION_STRONG;
+ }
+ return AP_CONDITION_NOMATCH;
+ }
+
+ return AP_CONDITION_NONE;
+}
+
+AP_DECLARE(ap_condition_e) ap_condition_if_modified_since(request_rec *r,
+ apr_table_t *headers)
+{
+ const char *if_modified_since;
+
+ if ((if_modified_since = apr_table_get(r->headers_in, "If-Modified-Since"))
+ != NULL) {
+ apr_int64_t mtime;
+ apr_int64_t ims, reqtime;
+
+ /* All of our comparisons must be in seconds, because that's the
+ * highest time resolution the HTTP specification allows.
+ */
+
+ mtime = apr_time_sec(apr_date_parse_http(
+ apr_table_get(headers, "Last-Modified")));
+ if (mtime == APR_DATE_BAD) {
+ mtime = apr_time_sec(r->mtime ? r->mtime : apr_time_now());
+ }
+
+ reqtime = apr_time_sec(apr_date_parse_http(
+ apr_table_get(headers, "Date")));
+ if (!reqtime) {
+ reqtime = apr_time_sec(r->request_time);
+ }
+
+ ims = apr_time_sec(apr_date_parse_http(if_modified_since));
+
+ if (ims >= mtime && ims <= reqtime) {
+ if (reqtime < mtime + 60) {
+ if (apr_table_get(r->headers_in, "Range")) {
+ /* weak matches not allowed with Range requests */
+ return AP_CONDITION_NOMATCH;
+ }
+ else {
+ return AP_CONDITION_WEAK;
+ }
+ }
+ else {
+ return AP_CONDITION_STRONG;
+ }
+ }
+ else {
+ return AP_CONDITION_NOMATCH;
+ }
+ }
+
+ return AP_CONDITION_NONE;
+}
+
+AP_DECLARE(ap_condition_e) ap_condition_if_range(request_rec *r,
+ apr_table_t *headers)
+{
+ const char *if_range, *etag;
+
+ if ((if_range = apr_table_get(r->headers_in, "If-Range"))
+ && apr_table_get(r->headers_in, "Range")) {
+ if (if_range[0] == '"') {
+
+ if ((etag = apr_table_get(headers, "ETag"))
+ && !strcmp(if_range, etag)) {
+ return AP_CONDITION_STRONG;
+ }
+ else {
+ return AP_CONDITION_NOMATCH;
+ }
+
+ }
+ else {
+ apr_int64_t mtime;
+ apr_int64_t rtime, reqtime;
+
+ /* All of our comparisons must be in seconds, because that's the
+ * highest time resolution the HTTP specification allows.
+ */
+
+ mtime = apr_time_sec(apr_date_parse_http(
+ apr_table_get(headers, "Last-Modified")));
+ if (mtime == APR_DATE_BAD) {
+ mtime = apr_time_sec(r->mtime ? r->mtime : apr_time_now());
+ }
+
+ reqtime = apr_time_sec(apr_date_parse_http(
+ apr_table_get(headers, "Date")));
+ if (!reqtime) {
+ reqtime = apr_time_sec(r->request_time);
+ }
+
+ rtime = apr_time_sec(apr_date_parse_http(if_range));
+
+ if (rtime == mtime) {
+ if (reqtime < mtime + 60) {
+ /* weak matches not allowed with Range requests */
+ return AP_CONDITION_NOMATCH;
+ }
+ else {
+ return AP_CONDITION_STRONG;
+ }
+ }
+ else {
+ return AP_CONDITION_NOMATCH;
+ }
+ }
+ }
+
+ return AP_CONDITION_NONE;
+}
+
+AP_DECLARE(int) ap_meets_conditions(request_rec *r)
+{
+ int not_modified = -1; /* unset by default */
+ ap_condition_e cond;
+
+ /* Check for conditional requests --- note that we only want to do
+ * this if we are successful so far and we are not processing a
+ * subrequest or an ErrorDocument.
+ *
+ * The order of the checks is important, since ETag checks are supposed
+ * to be more accurate than checks relative to the modification time.
+ * However, not all documents are guaranteed to *have* ETags, and some
+ * might have Last-Modified values w/o ETags, so this gets a little
+ * complicated.
+ */
+
+ if (!ap_is_HTTP_SUCCESS(r->status) || r->no_local_copy) {
+ return OK;
+ }
+
+ /* If an If-Match request-header field was given
+ * AND the field value is not "*" (meaning match anything)
+ * AND if our strong ETag does not match any entity tag in that field,
+ * respond with a status of 412 (Precondition Failed).
+ */
+ cond = ap_condition_if_match(r, r->headers_out);
+ if (AP_CONDITION_NOMATCH == cond) {
+ return HTTP_PRECONDITION_FAILED;
+ }
+
+ /* Else if a valid If-Unmodified-Since request-header field was given
+ * AND the requested resource has been modified since the time
+ * specified in this field, then the server MUST
+ * respond with a status of 412 (Precondition Failed).
+ */
+ cond = ap_condition_if_unmodified_since(r, r->headers_out);
+ if (AP_CONDITION_NOMATCH == cond) {
+ not_modified = 0;
+ }
+ else if (cond >= AP_CONDITION_WEAK) {
+ return HTTP_PRECONDITION_FAILED;
+ }
+
+ /* If an If-None-Match request-header field was given
+ * AND the field value is "*" (meaning match anything)
+ * OR our ETag matches any of the entity tags in that field, fail.
+ *
+ * If the request method was GET or HEAD, failure means the server
+ * SHOULD respond with a 304 (Not Modified) response.
+ * For all other request methods, failure means the server MUST
+ * respond with a status of 412 (Precondition Failed).
+ *
+ * GET or HEAD allow weak etag comparison, all other methods require
+ * strong comparison. We can only use weak if it's not a range request.
+ */
+ cond = ap_condition_if_none_match(r, r->headers_out);
+ if (AP_CONDITION_NOMATCH == cond) {
+ not_modified = 0;
+ }
+ else if (cond >= AP_CONDITION_WEAK) {
+ if (r->method_number == M_GET) {
+ if (not_modified) {
+ not_modified = 1;
+ }
+ }
+ else {
+ return HTTP_PRECONDITION_FAILED;
+ }
+ }
+
+ /* If a valid If-Modified-Since request-header field was given
+ * AND it is a GET or HEAD request
+ * AND the requested resource has not been modified since the time
+ * specified in this field, then the server MUST
+ * respond with a status of 304 (Not Modified).
+ * A date later than the server's current request time is invalid.
+ */
+ cond = ap_condition_if_modified_since(r, r->headers_out);
+ if (AP_CONDITION_NOMATCH == cond) {
+ not_modified = 0;
+ }
+ else if (cond >= AP_CONDITION_WEAK) {
+ if (r->method_number == M_GET) {
+ if (not_modified) {
+ not_modified = 1;
+ }
+ }
+ }
+
+ /* If an If-Range and an Range header is present, we must return
+ * 200 OK. The byterange filter will convert it to a range response.
+ */
+ cond = ap_condition_if_range(r, r->headers_out);
+ if (cond > AP_CONDITION_NONE) {
+ return OK;
+ }
+
+ if (not_modified == 1) {
+ return HTTP_NOT_MODIFIED;
+ }
+
+ return OK;
+}
+
+/**
+ * Singleton registry of additional methods. This maps new method names
+ * such as "MYGET" to methnums, which are int offsets into bitmasks.
+ *
+ * This follows the same technique as standard M_GET, M_POST, etc. These
+ * are dynamically assigned when modules are loaded and <Limit GET MYGET>
+ * directives are processed.
+ */
+static apr_hash_t *methods_registry = NULL;
+static int cur_method_number = METHOD_NUMBER_FIRST;
+
+/* internal function to register one method/number pair */
+static void register_one_method(apr_pool_t *p, const char *methname,
+ int methnum)
+{
+ int *pnum = apr_palloc(p, sizeof(*pnum));
+
+ *pnum = methnum;
+ apr_hash_set(methods_registry, methname, APR_HASH_KEY_STRING, pnum);
+}
+
+/* This internal function is used to clear the method registry
+ * and reset the cur_method_number counter.
+ */
+static apr_status_t ap_method_registry_destroy(void *notused)
+{
+ methods_registry = NULL;
+ cur_method_number = METHOD_NUMBER_FIRST;
+ return APR_SUCCESS;
+}
+
+AP_DECLARE(void) ap_method_registry_init(apr_pool_t *p)
+{
+ methods_registry = apr_hash_make(p);
+ apr_pool_cleanup_register(p, NULL,
+ ap_method_registry_destroy,
+ apr_pool_cleanup_null);
+
+ /* put all the standard methods into the registry hash to ease the
+ * mapping operations between name and number
+ * HEAD is a special-instance of the GET method and shares the same ID
+ */
+ register_one_method(p, "GET", M_GET);
+ register_one_method(p, "HEAD", M_GET);
+ register_one_method(p, "PUT", M_PUT);
+ register_one_method(p, "POST", M_POST);
+ register_one_method(p, "DELETE", M_DELETE);
+ register_one_method(p, "CONNECT", M_CONNECT);
+ register_one_method(p, "OPTIONS", M_OPTIONS);
+ register_one_method(p, "TRACE", M_TRACE);
+ register_one_method(p, "PATCH", M_PATCH);
+ register_one_method(p, "PROPFIND", M_PROPFIND);
+ register_one_method(p, "PROPPATCH", M_PROPPATCH);
+ register_one_method(p, "MKCOL", M_MKCOL);
+ register_one_method(p, "COPY", M_COPY);
+ register_one_method(p, "MOVE", M_MOVE);
+ register_one_method(p, "LOCK", M_LOCK);
+ register_one_method(p, "UNLOCK", M_UNLOCK);
+ register_one_method(p, "VERSION-CONTROL", M_VERSION_CONTROL);
+ register_one_method(p, "CHECKOUT", M_CHECKOUT);
+ register_one_method(p, "UNCHECKOUT", M_UNCHECKOUT);
+ register_one_method(p, "CHECKIN", M_CHECKIN);
+ register_one_method(p, "UPDATE", M_UPDATE);
+ register_one_method(p, "LABEL", M_LABEL);
+ register_one_method(p, "REPORT", M_REPORT);
+ register_one_method(p, "MKWORKSPACE", M_MKWORKSPACE);
+ register_one_method(p, "MKACTIVITY", M_MKACTIVITY);
+ register_one_method(p, "BASELINE-CONTROL", M_BASELINE_CONTROL);
+ register_one_method(p, "MERGE", M_MERGE);
+}
+
+AP_DECLARE(int) ap_method_register(apr_pool_t *p, const char *methname)
+{
+ int *methnum;
+
+ if (methods_registry == NULL) {
+ ap_method_registry_init(p);
+ }
+
+ if (methname == NULL) {
+ return M_INVALID;
+ }
+
+ /* Check if the method was previously registered. If it was
+ * return the associated method number.
+ */
+ methnum = (int *)apr_hash_get(methods_registry, methname,
+ APR_HASH_KEY_STRING);
+ if (methnum != NULL)
+ return *methnum;
+
+ if (cur_method_number > METHOD_NUMBER_LAST) {
+ /* The method registry has run out of dynamically
+ * assignable method numbers. Log this and return M_INVALID.
+ */
+ ap_log_perror(APLOG_MARK, APLOG_ERR, 0, p, APLOGNO(01610)
+ "Maximum new request methods %d reached while "
+ "registering method %s.",
+ METHOD_NUMBER_LAST, methname);
+ return M_INVALID;
+ }
+
+ register_one_method(p, methname, cur_method_number);
+ return cur_method_number++;
+}
+
+#define UNKNOWN_METHOD (-1)
+
+static int lookup_builtin_method(const char *method, apr_size_t len)
+{
+ /* Note: the following code was generated by the "shilka" tool from
+ the "cocom" parsing/compilation toolkit. It is an optimized lookup
+ based on analysis of the input keywords. Postprocessing was done
+ on the shilka output, but the basic structure and analysis is
+ from there. Should new HTTP methods be added, then manual insertion
+ into this code is fine, or simply re-running the shilka tool on
+ the appropriate input. */
+
+ /* Note: it is also quite reasonable to just use our method_registry,
+ but I'm assuming (probably incorrectly) we want more speed here
+ (based on the optimizations the previous code was doing). */
+
+ switch (len)
+ {
+ case 3:
+ switch (method[0])
+ {
+ case 'P':
+ return (method[1] == 'U'
+ && method[2] == 'T'
+ ? M_PUT : UNKNOWN_METHOD);
+ case 'G':
+ return (method[1] == 'E'
+ && method[2] == 'T'
+ ? M_GET : UNKNOWN_METHOD);
+ default:
+ return UNKNOWN_METHOD;
+ }
+
+ case 4:
+ switch (method[0])
+ {
+ case 'H':
+ return (method[1] == 'E'
+ && method[2] == 'A'
+ && method[3] == 'D'
+ ? M_GET : UNKNOWN_METHOD);
+ case 'P':
+ return (method[1] == 'O'
+ && method[2] == 'S'
+ && method[3] == 'T'
+ ? M_POST : UNKNOWN_METHOD);
+ case 'M':
+ return (method[1] == 'O'
+ && method[2] == 'V'
+ && method[3] == 'E'
+ ? M_MOVE : UNKNOWN_METHOD);
+ case 'L':
+ return (method[1] == 'O'
+ && method[2] == 'C'
+ && method[3] == 'K'
+ ? M_LOCK : UNKNOWN_METHOD);
+ case 'C':
+ return (method[1] == 'O'
+ && method[2] == 'P'
+ && method[3] == 'Y'
+ ? M_COPY : UNKNOWN_METHOD);
+ default:
+ return UNKNOWN_METHOD;
+ }
+
+ case 5:
+ switch (method[2])
+ {
+ case 'T':
+ return (memcmp(method, "PATCH", 5) == 0
+ ? M_PATCH : UNKNOWN_METHOD);
+ case 'R':
+ return (memcmp(method, "MERGE", 5) == 0
+ ? M_MERGE : UNKNOWN_METHOD);
+ case 'C':
+ return (memcmp(method, "MKCOL", 5) == 0
+ ? M_MKCOL : UNKNOWN_METHOD);
+ case 'B':
+ return (memcmp(method, "LABEL", 5) == 0
+ ? M_LABEL : UNKNOWN_METHOD);
+ case 'A':
+ return (memcmp(method, "TRACE", 5) == 0
+ ? M_TRACE : UNKNOWN_METHOD);
+ default:
+ return UNKNOWN_METHOD;
+ }
+
+ case 6:
+ switch (method[0])
+ {
+ case 'U':
+ switch (method[5])
+ {
+ case 'K':
+ return (memcmp(method, "UNLOCK", 6) == 0
+ ? M_UNLOCK : UNKNOWN_METHOD);
+ case 'E':
+ return (memcmp(method, "UPDATE", 6) == 0
+ ? M_UPDATE : UNKNOWN_METHOD);
+ default:
+ return UNKNOWN_METHOD;
+ }
+ case 'R':
+ return (memcmp(method, "REPORT", 6) == 0
+ ? M_REPORT : UNKNOWN_METHOD);
+ case 'D':
+ return (memcmp(method, "DELETE", 6) == 0
+ ? M_DELETE : UNKNOWN_METHOD);
+ default:
+ return UNKNOWN_METHOD;
+ }
+
+ case 7:
+ switch (method[1])
+ {
+ case 'P':
+ return (memcmp(method, "OPTIONS", 7) == 0
+ ? M_OPTIONS : UNKNOWN_METHOD);
+ case 'O':
+ return (memcmp(method, "CONNECT", 7) == 0
+ ? M_CONNECT : UNKNOWN_METHOD);
+ case 'H':
+ return (memcmp(method, "CHECKIN", 7) == 0
+ ? M_CHECKIN : UNKNOWN_METHOD);
+ default:
+ return UNKNOWN_METHOD;
+ }
+
+ case 8:
+ switch (method[0])
+ {
+ case 'P':
+ return (memcmp(method, "PROPFIND", 8) == 0
+ ? M_PROPFIND : UNKNOWN_METHOD);
+ case 'C':
+ return (memcmp(method, "CHECKOUT", 8) == 0
+ ? M_CHECKOUT : UNKNOWN_METHOD);
+ default:
+ return UNKNOWN_METHOD;
+ }
+
+ case 9:
+ return (memcmp(method, "PROPPATCH", 9) == 0
+ ? M_PROPPATCH : UNKNOWN_METHOD);
+
+ case 10:
+ switch (method[0])
+ {
+ case 'U':
+ return (memcmp(method, "UNCHECKOUT", 10) == 0
+ ? M_UNCHECKOUT : UNKNOWN_METHOD);
+ case 'M':
+ return (memcmp(method, "MKACTIVITY", 10) == 0
+ ? M_MKACTIVITY : UNKNOWN_METHOD);
+ default:
+ return UNKNOWN_METHOD;
+ }
+
+ case 11:
+ return (memcmp(method, "MKWORKSPACE", 11) == 0
+ ? M_MKWORKSPACE : UNKNOWN_METHOD);
+
+ case 15:
+ return (memcmp(method, "VERSION-CONTROL", 15) == 0
+ ? M_VERSION_CONTROL : UNKNOWN_METHOD);
+
+ case 16:
+ return (memcmp(method, "BASELINE-CONTROL", 16) == 0
+ ? M_BASELINE_CONTROL : UNKNOWN_METHOD);
+
+ default:
+ return UNKNOWN_METHOD;
+ }
+
+ /* NOTREACHED */
+}
+
+/* Get the method number associated with the given string, assumed to
+ * contain an HTTP method. Returns M_INVALID if not recognized.
+ *
+ * This is the first step toward placing method names in a configurable
+ * list. Hopefully it (and other routines) can eventually be moved to
+ * something like a mod_http_methods.c, complete with config stuff.
+ */
+AP_DECLARE(int) ap_method_number_of(const char *method)
+{
+ int len = strlen(method);
+ int which = lookup_builtin_method(method, len);
+
+ if (which != UNKNOWN_METHOD)
+ return which;
+
+ /* check if the method has been dynamically registered */
+ if (methods_registry != NULL) {
+ int *methnum = apr_hash_get(methods_registry, method, len);
+
+ if (methnum != NULL) {
+ return *methnum;
+ }
+ }
+
+ return M_INVALID;
+}
+
+/*
+ * Turn a known method number into a name.
+ */
+AP_DECLARE(const char *) ap_method_name_of(apr_pool_t *p, int methnum)
+{
+ apr_hash_index_t *hi = apr_hash_first(p, methods_registry);
+
+ /* scan through the hash table, looking for a value that matches
+ the provided method number. */
+ for (; hi; hi = apr_hash_next(hi)) {
+ const void *key;
+ void *val;
+
+ apr_hash_this(hi, &key, NULL, &val);
+ if (*(int *)val == methnum)
+ return key;
+ }
+
+ /* it wasn't found in the hash */
+ return NULL;
+}
+
+/* The index is found by its offset from the x00 code of each level.
+ * Although this is fast, it will need to be replaced if some nutcase
+ * decides to define a high-numbered code before the lower numbers.
+ * If that sad event occurs, replace the code below with a linear search
+ * from status_lines[shortcut[i]] to status_lines[shortcut[i+1]-1];
+ * or use NULL to fill the gaps.
+ */
+static int index_of_response(int status)
+{
+ static int shortcut[6] = {0, LEVEL_200, LEVEL_300, LEVEL_400, LEVEL_500,
+ RESPONSE_CODES};
+ int i, pos;
+
+ if (status < 100) { /* Below 100 is illegal for HTTP status */
+ return -1;
+ }
+ if (status > 999) { /* Above 999 is also illegal for HTTP status */
+ return -1;
+ }
+
+ for (i = 0; i < 5; i++) {
+ status -= 100;
+ if (status < 100) {
+ pos = (status + shortcut[i]);
+ if (pos < shortcut[i + 1] && status_lines[pos] != NULL) {
+ return pos;
+ }
+ else {
+ break;
+ }
+ }
+ }
+ return -2; /* Status unknown (falls in gap) or above 600 */
+}
+
+AP_DECLARE(int) ap_index_of_response(int status)
+{
+ int index = index_of_response(status);
+ return (index < 0) ? LEVEL_500 : index;
+}
+
+AP_DECLARE(const char *) ap_get_status_line_ex(apr_pool_t *p, int status)
+{
+ int index = index_of_response(status);
+ if (index >= 0) {
+ return status_lines[index];
+ }
+ else if (index == -2) {
+ return apr_psprintf(p, "%i Status %i", status, status);
+ }
+ return status_lines[LEVEL_500];
+}
+
+AP_DECLARE(const char *) ap_get_status_line(int status)
+{
+ return status_lines[ap_index_of_response(status)];
+}
+
+/* Build the Allow field-value from the request handler method mask.
+ */
+static char *make_allow(request_rec *r)
+{
+ apr_int64_t mask;
+ apr_array_header_t *allow = apr_array_make(r->pool, 10, sizeof(char *));
+ apr_hash_index_t *hi = apr_hash_first(r->pool, methods_registry);
+ /* For TRACE below */
+ core_server_config *conf =
+ ap_get_core_module_config(r->server->module_config);
+
+ mask = r->allowed_methods->method_mask;
+
+ for (; hi; hi = apr_hash_next(hi)) {
+ const void *key;
+ void *val;
+
+ apr_hash_this(hi, &key, NULL, &val);
+ if ((mask & (AP_METHOD_BIT << *(int *)val)) != 0) {
+ APR_ARRAY_PUSH(allow, const char *) = key;
+ }
+ }
+
+ /* TRACE is tested on a per-server basis */
+ if (conf->trace_enable != AP_TRACE_DISABLE)
+ *(const char **)apr_array_push(allow) = "TRACE";
+
+ /* ### this is rather annoying. we should enforce registration of
+ ### these methods */
+ if ((mask & (AP_METHOD_BIT << M_INVALID))
+ && (r->allowed_methods->method_list != NULL)
+ && (r->allowed_methods->method_list->nelts != 0)) {
+ apr_array_cat(allow, r->allowed_methods->method_list);
+ }
+
+ return apr_array_pstrcat(r->pool, allow, ',');
+}
+
+AP_DECLARE(int) ap_send_http_options(request_rec *r)
+{
+ if (r->assbackwards) {
+ return DECLINED;
+ }
+
+ apr_table_setn(r->headers_out, "Allow", make_allow(r));
+
+ /* the request finalization will send an EOS, which will flush all
+ * the headers out (including the Allow header)
+ */
+
+ return OK;
+}
+
+AP_DECLARE(void) ap_set_content_type(request_rec *r, const char *ct)
+{
+ if (!ct) {
+ r->content_type = NULL;
+ }
+ else if (!r->content_type || strcmp(r->content_type, ct)) {
+ r->content_type = ct;
+ }
+}
+
+AP_DECLARE(void) ap_set_accept_ranges(request_rec *r)
+{
+ core_dir_config *d = ap_get_core_module_config(r->per_dir_config);
+ apr_table_setn(r->headers_out, "Accept-Ranges",
+ (d->max_ranges == AP_MAXRANGES_NORANGES) ? "none"
+ : "bytes");
+}
+static const char *add_optional_notes(request_rec *r,
+ const char *prefix,
+ const char *key,
+ const char *suffix)
+{
+ const char *notes, *result;
+
+ if ((notes = apr_table_get(r->notes, key)) == NULL) {
+ result = apr_pstrcat(r->pool, prefix, suffix, NULL);
+ }
+ else {
+ result = apr_pstrcat(r->pool, prefix, notes, suffix, NULL);
+ }
+
+ return result;
+}
+
+/* construct and return the default error message for a given
+ * HTTP defined error code
+ */
+static const char *get_canned_error_string(int status,
+ request_rec *r,
+ const char *location)
+{
+ apr_pool_t *p = r->pool;
+ const char *error_notes, *h1, *s1;
+
+ switch (status) {
+ case HTTP_MOVED_PERMANENTLY:
+ case HTTP_MOVED_TEMPORARILY:
+ case HTTP_TEMPORARY_REDIRECT:
+ case HTTP_PERMANENT_REDIRECT:
+ return(apr_pstrcat(p,
+ "<p>The document has moved <a href=\"",
+ ap_escape_html(r->pool, location),
+ "\">here</a>.</p>\n",
+ NULL));
+ case HTTP_SEE_OTHER:
+ return(apr_pstrcat(p,
+ "<p>The answer to your request is located "
+ "<a href=\"",
+ ap_escape_html(r->pool, location),
+ "\">here</a>.</p>\n",
+ NULL));
+ case HTTP_USE_PROXY:
+ return("<p>This resource is only accessible "
+ "through the proxy\n"
+ "<br />\nYou will need to configure "
+ "your client to use that proxy.</p>\n");
+ case HTTP_PROXY_AUTHENTICATION_REQUIRED:
+ case HTTP_UNAUTHORIZED:
+ return("<p>This server could not verify that you\n"
+ "are authorized to access the document\n"
+ "requested. Either you supplied the wrong\n"
+ "credentials (e.g., bad password), or your\n"
+ "browser doesn't understand how to supply\n"
+ "the credentials required.</p>\n");
+ case HTTP_BAD_REQUEST:
+ return(add_optional_notes(r,
+ "<p>Your browser sent a request that "
+ "this server could not understand.<br />\n",
+ "error-notes",
+ "</p>\n"));
+ case HTTP_FORBIDDEN:
+ return(add_optional_notes(r, "<p>You don't have permission to access this resource.", "error-notes", "</p>\n"));
+ case HTTP_NOT_FOUND:
+ return("<p>The requested URL was not found on this server.</p>\n");
+ case HTTP_METHOD_NOT_ALLOWED:
+ return(apr_pstrcat(p,
+ "<p>The requested method ",
+ ap_escape_html(r->pool, r->method),
+ " is not allowed for this URL.</p>\n",
+ NULL));
+ case HTTP_NOT_ACCEPTABLE:
+ return(add_optional_notes(r,
+ "<p>An appropriate representation of the requested resource "
+ "could not be found on this server.</p>\n",
+ "variant-list", ""));
+ case HTTP_MULTIPLE_CHOICES:
+ return(add_optional_notes(r, "", "variant-list", ""));
+ case HTTP_LENGTH_REQUIRED:
+ s1 = apr_pstrcat(p,
+ "<p>A request of the requested method ",
+ ap_escape_html(r->pool, r->method),
+ " requires a valid Content-length.<br />\n",
+ NULL);
+ return(add_optional_notes(r, s1, "error-notes", "</p>\n"));
+ case HTTP_PRECONDITION_FAILED:
+ return("<p>The precondition on the request "
+ "for this URL evaluated to false.</p>\n");
+ case HTTP_NOT_IMPLEMENTED:
+ s1 = apr_pstrcat(p,
+ "<p>",
+ ap_escape_html(r->pool, r->method),
+ " not supported for current URL.<br />\n",
+ NULL);
+ return(add_optional_notes(r, s1, "error-notes", "</p>\n"));
+ case HTTP_BAD_GATEWAY:
+ s1 = "<p>The proxy server received an invalid" CRLF
+ "response from an upstream server.<br />" CRLF;
+ return(add_optional_notes(r, s1, "error-notes", "</p>\n"));
+ case HTTP_VARIANT_ALSO_VARIES:
+ return("<p>A variant for the requested "
+ "resource\n<pre>\n"
+ "\n</pre>\nis itself a negotiable resource. "
+ "This indicates a configuration error.</p>\n");
+ case HTTP_REQUEST_TIME_OUT:
+ return("<p>Server timeout waiting for the HTTP request from the client.</p>\n");
+ case HTTP_GONE:
+ return("<p>The requested resource is no longer available on this server"
+ " and there is no forwarding address.\n"
+ "Please remove all references to this resource.</p>\n");
+ case HTTP_REQUEST_ENTITY_TOO_LARGE:
+ return(apr_pstrcat(p,
+ "The requested resource does not allow request data with ",
+ ap_escape_html(r->pool, r->method),
+ " requests, or the amount of data provided in\n"
+ "the request exceeds the capacity limit.\n",
+ NULL));
+ case HTTP_REQUEST_URI_TOO_LARGE:
+ s1 = "<p>The requested URL's length exceeds the capacity\n"
+ "limit for this server.<br />\n";
+ return(add_optional_notes(r, s1, "error-notes", "</p>\n"));
+ case HTTP_UNSUPPORTED_MEDIA_TYPE:
+ return("<p>The supplied request data is not in a format\n"
+ "acceptable for processing by this resource.</p>\n");
+ case HTTP_RANGE_NOT_SATISFIABLE:
+ return("<p>None of the range-specifier values in the Range\n"
+ "request-header field overlap the current extent\n"
+ "of the selected resource.</p>\n");
+ case HTTP_EXPECTATION_FAILED:
+ s1 = apr_table_get(r->headers_in, "Expect");
+ if (s1)
+ s1 = apr_pstrcat(p,
+ "<p>The expectation given in the Expect request-header\n"
+ "field could not be met by this server.\n"
+ "The client sent<pre>\n Expect: ",
+ ap_escape_html(r->pool, s1), "\n</pre>\n",
+ NULL);
+ else
+ s1 = "<p>No expectation was seen, the Expect request-header \n"
+ "field was not presented by the client.\n";
+ return add_optional_notes(r, s1, "error-notes", "</p>"
+ "<p>Only the 100-continue expectation is supported.</p>\n");
+ case HTTP_UNPROCESSABLE_ENTITY:
+ return("<p>The server understands the media type of the\n"
+ "request entity, but was unable to process the\n"
+ "contained instructions.</p>\n");
+ case HTTP_LOCKED:
+ return("<p>The requested resource is currently locked.\n"
+ "The lock must be released or proper identification\n"
+ "given before the method can be applied.</p>\n");
+ case HTTP_FAILED_DEPENDENCY:
+ return("<p>The method could not be performed on the resource\n"
+ "because the requested action depended on another\n"
+ "action and that other action failed.</p>\n");
+ case HTTP_UPGRADE_REQUIRED:
+ return("<p>The requested resource can only be retrieved\n"
+ "using SSL. The server is willing to upgrade the current\n"
+ "connection to SSL, but your client doesn't support it.\n"
+ "Either upgrade your client, or try requesting the page\n"
+ "using https://\n");
+ case HTTP_PRECONDITION_REQUIRED:
+ return("<p>The request is required to be conditional.</p>\n");
+ case HTTP_TOO_MANY_REQUESTS:
+ return("<p>The user has sent too many requests\n"
+ "in a given amount of time.</p>\n");
+ case HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE:
+ return("<p>The server refused this request because\n"
+ "the request header fields are too large.</p>\n");
+ case HTTP_INSUFFICIENT_STORAGE:
+ return("<p>The method could not be performed on the resource\n"
+ "because the server is unable to store the\n"
+ "representation needed to successfully complete the\n"
+ "request. There is insufficient free space left in\n"
+ "your storage allocation.</p>\n");
+ case HTTP_SERVICE_UNAVAILABLE:
+ return("<p>The server is temporarily unable to service your\n"
+ "request due to maintenance downtime or capacity\n"
+ "problems. Please try again later.</p>\n");
+ case HTTP_GATEWAY_TIME_OUT:
+ return("<p>The gateway did not receive a timely response\n"
+ "from the upstream server or application.</p>\n");
+ case HTTP_LOOP_DETECTED:
+ return("<p>The server terminated an operation because\n"
+ "it encountered an infinite loop.</p>\n");
+ case HTTP_NOT_EXTENDED:
+ return("<p>A mandatory extension policy in the request is not\n"
+ "accepted by the server for this resource.</p>\n");
+ case HTTP_NETWORK_AUTHENTICATION_REQUIRED:
+ return("<p>The client needs to authenticate to gain\n"
+ "network access.</p>\n");
+ case HTTP_MISDIRECTED_REQUEST:
+ return("<p>The client needs a new connection for this\n"
+ "request as the requested host name does not match\n"
+ "the Server Name Indication (SNI) in use for this\n"
+ "connection.</p>\n");
+ case HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ return(add_optional_notes(r,
+ "<p>Access to this URL has been denied for legal reasons.<br />\n",
+ "error-notes", "</p>\n"));
+ default: /* HTTP_INTERNAL_SERVER_ERROR */
+ /*
+ * This comparison to expose error-notes could be modified to
+ * use a configuration directive and export based on that
+ * directive. For now "*" is used to designate an error-notes
+ * that is totally safe for any user to see (ie lacks paths,
+ * database passwords, etc.)
+ */
+ if (((error_notes = apr_table_get(r->notes,
+ "error-notes")) != NULL)
+ && (h1 = apr_table_get(r->notes, "verbose-error-to")) != NULL
+ && (strcmp(h1, "*") == 0)) {
+ return(apr_pstrcat(p, error_notes, "<p />\n", NULL));
+ }
+ else {
+ return(apr_pstrcat(p,
+ "<p>The server encountered an internal "
+ "error or\n"
+ "misconfiguration and was unable to complete\n"
+ "your request.</p>\n"
+ "<p>Please contact the server "
+ "administrator at \n ",
+ ap_escape_html(r->pool,
+ r->server->server_admin),
+ " to inform them of the time this "
+ "error occurred,\n"
+ " and the actions you performed just before "
+ "this error.</p>\n"
+ "<p>More information about this error "
+ "may be available\n"
+ "in the server error log.</p>\n",
+ NULL));
+ }
+ /*
+ * It would be nice to give the user the information they need to
+ * fix the problem directly since many users don't have access to
+ * the error_log (think University sites) even though they can easily
+ * get this error by misconfiguring an htaccess file. However, the
+ * e error notes tend to include the real file pathname in this case,
+ * which some people consider to be a breach of privacy. Until we
+ * can figure out a way to remove the pathname, leave this commented.
+ *
+ * if ((error_notes = apr_table_get(r->notes,
+ * "error-notes")) != NULL) {
+ * return(apr_pstrcat(p, error_notes, "<p />\n", NULL);
+ * }
+ * else {
+ * return "";
+ * }
+ */
+ }
+}
+
+/* We should have named this send_canned_response, since it is used for any
+ * response that can be generated by the server from the request record.
+ * This includes all 204 (no content), 3xx (redirect), 4xx (client error),
+ * and 5xx (server error) messages that have not been redirected to another
+ * handler via the ErrorDocument feature.
+ */
+AP_DECLARE(void) ap_send_error_response(request_rec *r, int recursive_error)
+{
+ int status = r->status;
+ int idx = ap_index_of_response(status);
+ char *custom_response;
+ const char *location = apr_table_get(r->headers_out, "Location");
+
+ /* At this point, we are starting the response over, so we have to reset
+ * this value.
+ */
+ r->eos_sent = 0;
+
+ /* and we need to get rid of any RESOURCE filters that might be lurking
+ * around, thinking they are in the middle of the original request
+ */
+
+ r->output_filters = r->proto_output_filters;
+
+ ap_run_insert_error_filter(r);
+
+ /* We need to special-case the handling of 204 and 304 responses,
+ * since they have specific HTTP requirements and do not include a
+ * message body. Note that being assbackwards here is not an option.
+ */
+ if (AP_STATUS_IS_HEADER_ONLY(status)) {
+ ap_finalize_request_protocol(r);
+ return;
+ }
+
+ /*
+ * It's possible that the Location field might be in r->err_headers_out
+ * instead of r->headers_out; use the latter if possible, else the
+ * former.
+ */
+ if (location == NULL) {
+ location = apr_table_get(r->err_headers_out, "Location");
+ }
+
+ if (!r->assbackwards) {
+ apr_table_t *tmp = r->headers_out;
+
+ /* For all HTTP/1.x responses for which we generate the message,
+ * we need to avoid inheriting the "normal status" header fields
+ * that may have been set by the request handler before the
+ * error or redirect, except for Location on external redirects.
+ */
+ r->headers_out = r->err_headers_out;
+ r->err_headers_out = tmp;
+ apr_table_clear(r->err_headers_out);
+
+ if (ap_is_HTTP_REDIRECT(status) || (status == HTTP_CREATED)) {
+ if ((location != NULL) && *location) {
+ apr_table_setn(r->headers_out, "Location", location);
+ }
+ else {
+ location = ""; /* avoids coredump when printing, below */
+ }
+ }
+
+ r->content_languages = NULL;
+ r->content_encoding = NULL;
+ r->clength = 0;
+
+ if (apr_table_get(r->subprocess_env,
+ "suppress-error-charset") != NULL) {
+ core_request_config *request_conf =
+ ap_get_core_module_config(r->request_config);
+ request_conf->suppress_charset = 1; /* avoid adding default
+ * charset later
+ */
+ ap_set_content_type(r, "text/html");
+ }
+ else {
+ ap_set_content_type(r, "text/html; charset=iso-8859-1");
+ }
+
+ if ((status == HTTP_METHOD_NOT_ALLOWED)
+ || (status == HTTP_NOT_IMPLEMENTED)) {
+ apr_table_setn(r->headers_out, "Allow", make_allow(r));
+ }
+
+ if (r->header_only) {
+ ap_finalize_request_protocol(r);
+ return;
+ }
+ }
+
+ if ((custom_response = ap_response_code_string(r, idx))) {
+ /*
+ * We have a custom response output. This should only be
+ * a text-string to write back. But if the ErrorDocument
+ * was a local redirect and the requested resource failed
+ * for any reason, the custom_response will still hold the
+ * redirect URL. We don't really want to output this URL
+ * as a text message, so first check the custom response
+ * string to ensure that it is a text-string (using the
+ * same test used in ap_die(), i.e. does it start with a ").
+ *
+ * If it's not a text string, we've got a recursive error or
+ * an external redirect. If it's a recursive error, ap_die passes
+ * us the second error code so we can write both, and has already
+ * backed up to the original error. If it's an external redirect,
+ * it hasn't happened yet; we may never know if it fails.
+ */
+ if (custom_response[0] == '\"') {
+ ap_rputs(custom_response + 1, r);
+ ap_finalize_request_protocol(r);
+ return;
+ }
+ }
+ {
+ const char *title = status_lines[idx];
+ const char *h1;
+
+ /* Accept a status_line set by a module, but only if it begins
+ * with the correct 3 digit status code
+ */
+ if (r->status_line) {
+ char *end;
+ int len = strlen(r->status_line);
+ if (len >= 3
+ && apr_strtoi64(r->status_line, &end, 10) == r->status
+ && (end - 3) == r->status_line
+ && (len < 4 || apr_isspace(r->status_line[3]))
+ && (len < 5 || apr_isalnum(r->status_line[4]))) {
+ /* Since we passed the above check, we know that length three
+ * is equivalent to only a 3 digit numeric http status.
+ * RFC2616 mandates a trailing space, let's add it.
+ * If we have an empty reason phrase, we also add "Unknown Reason".
+ */
+ if (len == 3) {
+ r->status_line = apr_pstrcat(r->pool, r->status_line, " Unknown Reason", NULL);
+ } else if (len == 4) {
+ r->status_line = apr_pstrcat(r->pool, r->status_line, "Unknown Reason", NULL);
+ }
+ title = r->status_line;
+ }
+ }
+
+ /* folks decided they didn't want the error code in the H1 text */
+ h1 = &title[4];
+
+ /* can't count on a charset filter being in place here,
+ * so do ebcdic->ascii translation explicitly (if needed)
+ */
+
+ ap_rvputs_proto_in_ascii(r,
+ DOCTYPE_HTML_2_0
+ "<html><head>\n<title>", title,
+ "</title>\n</head><body>\n<h1>", h1, "</h1>\n",
+ NULL);
+
+ ap_rvputs_proto_in_ascii(r,
+ get_canned_error_string(status, r, location),
+ NULL);
+
+ if (recursive_error) {
+ ap_rvputs_proto_in_ascii(r, "<p>Additionally, a ",
+ status_lines[ap_index_of_response(recursive_error)],
+ "\nerror was encountered while trying to use an "
+ "ErrorDocument to handle the request.</p>\n", NULL);
+ }
+ ap_rvputs_proto_in_ascii(r, ap_psignature("<hr>\n", r), NULL);
+ ap_rvputs_proto_in_ascii(r, "</body></html>\n", NULL);
+ }
+ ap_finalize_request_protocol(r);
+}
+
+/*
+ * Create a new method list with the specified number of preallocated
+ * extension slots.
+ */
+AP_DECLARE(ap_method_list_t *) ap_make_method_list(apr_pool_t *p, int nelts)
+{
+ ap_method_list_t *ml;
+
+ ml = (ap_method_list_t *) apr_palloc(p, sizeof(ap_method_list_t));
+ ml->method_mask = 0;
+ ml->method_list = apr_array_make(p, nelts, sizeof(char *));
+ return ml;
+}
+
+/*
+ * Make a copy of a method list (primarily for subrequests that may
+ * subsequently change it; don't want them changing the parent's, too!).
+ */
+AP_DECLARE(void) ap_copy_method_list(ap_method_list_t *dest,
+ ap_method_list_t *src)
+{
+ int i;
+ char **imethods;
+ char **omethods;
+
+ dest->method_mask = src->method_mask;
+ imethods = (char **) src->method_list->elts;
+ for (i = 0; i < src->method_list->nelts; ++i) {
+ omethods = (char **) apr_array_push(dest->method_list);
+ *omethods = apr_pstrdup(dest->method_list->pool, imethods[i]);
+ }
+}
+
+/*
+ * Return true if the specified HTTP method is in the provided
+ * method list.
+ */
+AP_DECLARE(int) ap_method_in_list(ap_method_list_t *l, const char *method)
+{
+ int methnum;
+
+ /*
+ * If it's one of our known methods, use the shortcut and check the
+ * bitmask.
+ */
+ methnum = ap_method_number_of(method);
+ if (methnum != M_INVALID) {
+ return !!(l->method_mask & (AP_METHOD_BIT << methnum));
+ }
+ /*
+ * Otherwise, see if the method name is in the array of string names.
+ */
+ if ((l->method_list == NULL) || (l->method_list->nelts == 0)) {
+ return 0;
+ }
+
+ return ap_array_str_contains(l->method_list, method);
+}
+
+/*
+ * Add the specified method to a method list (if it isn't already there).
+ */
+AP_DECLARE(void) ap_method_list_add(ap_method_list_t *l, const char *method)
+{
+ int methnum;
+ const char **xmethod;
+
+ /*
+ * If it's one of our known methods, use the shortcut and use the
+ * bitmask.
+ */
+ methnum = ap_method_number_of(method);
+ if (methnum != M_INVALID) {
+ l->method_mask |= (AP_METHOD_BIT << methnum);
+ return;
+ }
+ /*
+ * Otherwise, see if the method name is in the array of string names.
+ */
+ if (ap_array_str_contains(l->method_list, method)) {
+ return;
+ }
+
+ xmethod = (const char **) apr_array_push(l->method_list);
+ *xmethod = method;
+}
+
+/*
+ * Remove the specified method from a method list.
+ */
+AP_DECLARE(void) ap_method_list_remove(ap_method_list_t *l,
+ const char *method)
+{
+ int methnum;
+ char **methods;
+
+ /*
+ * If it's a known methods, either builtin or registered
+ * by a module, use the bitmask.
+ */
+ methnum = ap_method_number_of(method);
+ if (methnum != M_INVALID) {
+ l->method_mask &= ~(AP_METHOD_BIT << methnum);
+ return;
+ }
+ /*
+ * Otherwise, see if the method name is in the array of string names.
+ */
+ if (l->method_list->nelts != 0) {
+ int i, j, k;
+ methods = (char **)l->method_list->elts;
+ for (i = 0; i < l->method_list->nelts; ) {
+ if (strcmp(method, methods[i]) == 0) {
+ for (j = i, k = i + 1; k < l->method_list->nelts; ++j, ++k) {
+ methods[j] = methods[k];
+ }
+ --l->method_list->nelts;
+ }
+ else {
+ ++i;
+ }
+ }
+ }
+}
+
+/*
+ * Reset a method list to be completely empty.
+ */
+AP_DECLARE(void) ap_clear_method_list(ap_method_list_t *l)
+{
+ l->method_mask = 0;
+ l->method_list->nelts = 0;
+}
+
diff --git a/modules/http/http_request.c b/modules/http/http_request.c
new file mode 100644
index 0000000..d59cfe2
--- /dev/null
+++ b/modules/http/http_request.c
@@ -0,0 +1,861 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * http_request.c: functions to get and process requests
+ *
+ * Rob McCool 3/21/93
+ *
+ * Thoroughly revamped by rst for Apache. NB this file reads
+ * best from the bottom up.
+ *
+ */
+
+#include "apr_strings.h"
+#include "apr_file_io.h"
+#include "apr_fnmatch.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_request.h"
+#include "http_core.h"
+#include "http_protocol.h"
+#include "http_log.h"
+#include "http_main.h"
+#include "util_filter.h"
+#include "util_charset.h"
+#include "scoreboard.h"
+
+#include "mod_core.h"
+
+#if APR_HAVE_STDARG_H
+#include <stdarg.h>
+#endif
+
+APLOG_USE_MODULE(http);
+
+/*****************************************************************
+ *
+ * Mainline request processing...
+ */
+
+/* XXX A cleaner and faster way to do this might be to pass the request_rec
+ * down the filter chain as a parameter. It would need to change for
+ * subrequest vs. main request filters; perhaps the subrequest filter could
+ * make the switch.
+ */
+static void update_r_in_filters(ap_filter_t *f,
+ request_rec *from,
+ request_rec *to)
+{
+ while (f) {
+ if (f->r == from) {
+ f->r = to;
+ }
+ f = f->next;
+ }
+}
+
+static void ap_die_r(int type, request_rec *r, int recursive_error)
+{
+ char *custom_response;
+ request_rec *r_1st_err = r;
+
+ if (type == OK || type == DONE) {
+ ap_finalize_request_protocol(r);
+ return;
+ }
+
+ if (!ap_is_HTTP_VALID_RESPONSE(type)) {
+ ap_filter_t *next;
+
+ /*
+ * Check if we still have the ap_http_header_filter in place. If
+ * this is the case we should not ignore the error here because
+ * it means that we have not sent any response at all and never
+ * will. This is bad. Sent an internal server error instead.
+ */
+ next = r->output_filters;
+ while (next && (next->frec != ap_http_header_filter_handle)) {
+ next = next->next;
+ }
+
+ /*
+ * If next != NULL then we left the while above because of
+ * next->frec == ap_http_header_filter
+ */
+ if (next) {
+ if (type != AP_FILTER_ERROR) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01579)
+ "Invalid response status %i", type);
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02831)
+ "Response from AP_FILTER_ERROR");
+ }
+ type = HTTP_INTERNAL_SERVER_ERROR;
+ }
+ else {
+ return;
+ }
+ }
+
+ /*
+ * The following takes care of Apache redirects to custom response URLs
+ * Note that if we are already dealing with the response to some other
+ * error condition, we just report on the original error, and give up on
+ * any attempt to handle the other thing "intelligently"...
+ */
+ if (recursive_error != HTTP_OK) {
+ while (r_1st_err->prev && (r_1st_err->prev->status != HTTP_OK))
+ r_1st_err = r_1st_err->prev; /* Get back to original error */
+
+ if (r_1st_err != r) {
+ /* The recursive error was caused by an ErrorDocument specifying
+ * an internal redirect to a bad URI. ap_internal_redirect has
+ * changed the filter chains to point to the ErrorDocument's
+ * request_rec. Back out those changes so we can safely use the
+ * original failing request_rec to send the canned error message.
+ *
+ * ap_send_error_response gets rid of existing resource filters
+ * on the output side, so we can skip those.
+ */
+ update_r_in_filters(r_1st_err->proto_output_filters, r, r_1st_err);
+ update_r_in_filters(r_1st_err->input_filters, r, r_1st_err);
+ }
+
+ custom_response = NULL; /* Do NOT retry the custom thing! */
+ }
+ else {
+ int error_index = ap_index_of_response(type);
+ custom_response = ap_response_code_string(r, error_index);
+ recursive_error = 0;
+ }
+
+ r->status = type;
+
+ /*
+ * This test is done here so that none of the auth modules needs to know
+ * about proxy authentication. They treat it like normal auth, and then
+ * we tweak the status.
+ */
+ if (HTTP_UNAUTHORIZED == r->status && PROXYREQ_PROXY == r->proxyreq) {
+ r->status = HTTP_PROXY_AUTHENTICATION_REQUIRED;
+ }
+
+ /* If we don't want to keep the connection, make sure we mark that the
+ * connection is not eligible for keepalive. If we want to keep the
+ * connection, be sure that the request body (if any) has been read.
+ */
+ if (ap_status_drops_connection(r->status)) {
+ r->connection->keepalive = AP_CONN_CLOSE;
+ }
+
+ /*
+ * Two types of custom redirects --- plain text, and URLs. Plain text has
+ * a leading '"', so the URL code, here, is triggered on its absence
+ */
+
+ if (custom_response && custom_response[0] != '"') {
+
+ if (ap_is_url(custom_response)) {
+ /*
+ * The URL isn't local, so lets drop through the rest of this
+ * apache code, and continue with the usual REDIRECT handler.
+ * But note that the client will ultimately see the wrong
+ * status...
+ */
+ r->status = HTTP_MOVED_TEMPORARILY;
+ apr_table_setn(r->headers_out, "Location", custom_response);
+ }
+ else if (custom_response[0] == '/') {
+ const char *error_notes, *original_method;
+ int original_method_number;
+ r->no_local_copy = 1; /* Do NOT send HTTP_NOT_MODIFIED for
+ * error documents! */
+ /*
+ * This redirect needs to be a GET no matter what the original
+ * method was.
+ */
+ apr_table_setn(r->subprocess_env, "REQUEST_METHOD", r->method);
+
+ /*
+ * Provide a special method for modules to communicate
+ * more informative (than the plain canned) messages to us.
+ * Propagate them to ErrorDocuments via the ERROR_NOTES variable:
+ */
+ if ((error_notes = apr_table_get(r->notes,
+ "error-notes")) != NULL) {
+ apr_table_setn(r->subprocess_env, "ERROR_NOTES", error_notes);
+ }
+ original_method = r->method;
+ original_method_number = r->method_number;
+ r->method = "GET";
+ r->method_number = M_GET;
+ ap_internal_redirect(custom_response, r);
+ /* preserve ability to see %<m in the access log */
+ r->method = original_method;
+ r->method_number = original_method_number;
+ return;
+ }
+ else {
+ /*
+ * Dumb user has given us a bad url to redirect to --- fake up
+ * dying with a recursive server error...
+ */
+ recursive_error = HTTP_INTERNAL_SERVER_ERROR;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01580)
+ "Invalid error redirection directive: %s",
+ custom_response);
+ }
+ }
+ ap_send_error_response(r_1st_err, recursive_error);
+}
+
+AP_DECLARE(void) ap_die(int type, request_rec *r)
+{
+ ap_die_r(type, r, r->status);
+}
+
+AP_DECLARE(apr_status_t) ap_check_pipeline(conn_rec *c, apr_bucket_brigade *bb,
+ unsigned int max_blank_lines)
+{
+ apr_status_t rv = APR_EOF;
+ ap_input_mode_t mode = AP_MODE_SPECULATIVE;
+ unsigned int num_blank_lines = 0;
+ apr_size_t cr = 0;
+ char buf[2];
+
+ while (c->keepalive != AP_CONN_CLOSE && !c->aborted) {
+ apr_size_t len = cr + 1;
+
+ apr_brigade_cleanup(bb);
+ rv = ap_get_brigade(c->input_filters, bb, mode,
+ APR_NONBLOCK_READ, len);
+ if (rv != APR_SUCCESS || APR_BRIGADE_EMPTY(bb)) {
+ if (mode == AP_MODE_READBYTES) {
+ /* Unexpected error, stop with this connection */
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c, APLOGNO(02967)
+ "Can't consume pipelined empty lines");
+ c->keepalive = AP_CONN_CLOSE;
+ rv = APR_EGENERAL;
+ }
+ else if (rv != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(rv)) {
+ /* Pipe is dead */
+ c->keepalive = AP_CONN_CLOSE;
+ }
+ else {
+ /* Pipe is up and empty */
+ rv = APR_EAGAIN;
+ }
+ break;
+ }
+ if (!max_blank_lines) {
+ apr_off_t n = 0;
+ /* Single read asked, (non-meta-)data available? */
+ rv = apr_brigade_length(bb, 0, &n);
+ if (rv == APR_SUCCESS && n <= 0) {
+ rv = APR_EAGAIN;
+ }
+ break;
+ }
+
+ /* Lookup and consume blank lines */
+ rv = apr_brigade_flatten(bb, buf, &len);
+ if (rv != APR_SUCCESS || len != cr + 1) {
+ int log_level;
+ if (mode == AP_MODE_READBYTES) {
+ /* Unexpected error, stop with this connection */
+ c->keepalive = AP_CONN_CLOSE;
+ log_level = APLOG_ERR;
+ rv = APR_EGENERAL;
+ }
+ else {
+ /* Let outside (non-speculative/blocking) read determine
+ * where this possible failure comes from (metadata,
+ * morphed EOF socket, ...). Debug only here.
+ */
+ log_level = APLOG_DEBUG;
+ rv = APR_SUCCESS;
+ }
+ ap_log_cerror(APLOG_MARK, log_level, rv, c, APLOGNO(02968)
+ "Can't check pipelined data");
+ break;
+ }
+
+ if (mode == AP_MODE_READBYTES) {
+ /* [CR]LF consumed, try next */
+ mode = AP_MODE_SPECULATIVE;
+ cr = 0;
+ }
+ else if (cr) {
+ AP_DEBUG_ASSERT(len == 2 && buf[0] == APR_ASCII_CR);
+ if (buf[1] == APR_ASCII_LF) {
+ /* consume this CRLF */
+ mode = AP_MODE_READBYTES;
+ num_blank_lines++;
+ }
+ else {
+ /* CR(?!LF) is data */
+ break;
+ }
+ }
+ else {
+ if (buf[0] == APR_ASCII_LF) {
+ /* consume this LF */
+ mode = AP_MODE_READBYTES;
+ num_blank_lines++;
+ }
+ else if (buf[0] == APR_ASCII_CR) {
+ cr = 1;
+ }
+ else {
+ /* Not [CR]LF, some data */
+ break;
+ }
+ }
+ if (num_blank_lines > max_blank_lines) {
+ /* Enough blank lines with this connection,
+ * stop and don't recycle it.
+ */
+ c->keepalive = AP_CONN_CLOSE;
+ rv = APR_NOTFOUND;
+ break;
+ }
+ }
+
+ return rv;
+}
+
+#define RETRIEVE_BRIGADE_FROM_POOL(bb, key, pool, allocator) do { \
+ apr_pool_userdata_get((void **)&bb, key, pool); \
+ if (bb == NULL) { \
+ bb = apr_brigade_create(pool, allocator); \
+ apr_pool_userdata_setn((const void *)bb, key, NULL, pool); \
+ } \
+ else { \
+ apr_brigade_cleanup(bb); \
+ } \
+} while(0)
+
+AP_DECLARE(void) ap_process_request_after_handler(request_rec *r)
+{
+ apr_bucket_brigade *bb;
+ apr_bucket *b;
+ conn_rec *c = r->connection;
+ apr_status_t rv;
+
+ /* Send an EOR bucket through the output filter chain. When
+ * this bucket is destroyed, the request will be logged and
+ * its pool will be freed
+ */
+ RETRIEVE_BRIGADE_FROM_POOL(bb, "ap_process_request_after_handler_brigade",
+ c->pool, c->bucket_alloc);
+ b = ap_bucket_eor_create(c->bucket_alloc, r);
+ APR_BRIGADE_INSERT_HEAD(bb, b);
+
+ ap_pass_brigade(c->output_filters, bb);
+
+ /* The EOR bucket has either been handled by an output filter (eg.
+ * deleted or moved to a buffered_bb => no more in bb), or an error
+ * occured before that (eg. c->aborted => still in bb) and we ought
+ * to destroy it now. So cleanup any remaining bucket along with
+ * the orphan request (if any).
+ */
+ apr_brigade_cleanup(bb);
+
+ /* From here onward, it is no longer safe to reference r
+ * or r->pool, because r->pool may have been destroyed
+ * already by the EOR bucket's cleanup function.
+ */
+
+ /* Check pipeline consuming blank lines, they must not be interpreted as
+ * the next pipelined request, otherwise we would block on the next read
+ * without flushing data, and hence possibly delay pending response(s)
+ * until the next/real request comes in or the keepalive timeout expires.
+ */
+ rv = ap_check_pipeline(c, bb, DEFAULT_LIMIT_BLANK_LINES);
+ c->data_in_input_filters = (rv == APR_SUCCESS);
+ apr_brigade_cleanup(bb);
+
+ if (c->cs)
+ c->cs->state = (c->aborted) ? CONN_STATE_LINGER
+ : CONN_STATE_WRITE_COMPLETION;
+ AP_PROCESS_REQUEST_RETURN((uintptr_t)r, r->uri, r->status);
+ if (ap_extended_status) {
+ ap_time_process_request(c->sbh, STOP_PREQUEST);
+ }
+}
+
+void ap_process_async_request(request_rec *r)
+{
+ conn_rec *c = r->connection;
+ int access_status;
+
+ /* Give quick handlers a shot at serving the request on the fast
+ * path, bypassing all of the other Apache hooks.
+ *
+ * This hook was added to enable serving files out of a URI keyed
+ * content cache ( e.g., Mike Abbott's Quick Shortcut Cache,
+ * described here: http://oss.sgi.com/projects/apache/mod_qsc.html )
+ *
+ * It may have other uses as well, such as routing requests directly to
+ * content handlers that have the ability to grok HTTP and do their
+ * own access checking, etc (e.g. servlet engines).
+ *
+ * Use this hook with extreme care and only if you know what you are
+ * doing.
+ */
+ AP_PROCESS_REQUEST_ENTRY((uintptr_t)r, r->uri);
+ if (ap_extended_status) {
+ ap_time_process_request(r->connection->sbh, START_PREQUEST);
+ }
+
+ if (APLOGrtrace4(r)) {
+ int i;
+ const apr_array_header_t *t_h = apr_table_elts(r->headers_in);
+ const apr_table_entry_t *t_elt = (apr_table_entry_t *)t_h->elts;
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r,
+ "Headers received from client:");
+ for (i = 0; i < t_h->nelts; i++, t_elt++) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r, " %s: %s",
+ ap_escape_logitem(r->pool, t_elt->key),
+ ap_escape_logitem(r->pool, t_elt->val));
+ }
+ }
+
+#if APR_HAS_THREADS
+ apr_thread_mutex_create(&r->invoke_mtx, APR_THREAD_MUTEX_DEFAULT, r->pool);
+ apr_thread_mutex_lock(r->invoke_mtx);
+#endif
+ access_status = ap_run_quick_handler(r, 0); /* Not a look-up request */
+ if (access_status == DECLINED) {
+ access_status = ap_process_request_internal(r);
+ if (access_status == OK) {
+ access_status = ap_invoke_handler(r);
+ }
+ }
+
+ if (access_status == SUSPENDED) {
+ /* TODO: Should move these steps into a generic function, so modules
+ * working on a suspended request can also call _ENTRY again.
+ */
+ AP_PROCESS_REQUEST_RETURN((uintptr_t)r, r->uri, access_status);
+ if (ap_extended_status) {
+ ap_time_process_request(c->sbh, STOP_PREQUEST);
+ }
+ if (c->cs)
+ c->cs->state = CONN_STATE_SUSPENDED;
+#if APR_HAS_THREADS
+ apr_thread_mutex_unlock(r->invoke_mtx);
+#endif
+ return;
+ }
+#if APR_HAS_THREADS
+ apr_thread_mutex_unlock(r->invoke_mtx);
+#endif
+
+ ap_die_r(access_status, r, HTTP_OK);
+
+ ap_process_request_after_handler(r);
+}
+
+AP_DECLARE(void) ap_process_request(request_rec *r)
+{
+ apr_bucket_brigade *bb;
+ apr_bucket *b;
+ conn_rec *c = r->connection;
+ apr_status_t rv;
+
+ ap_process_async_request(r);
+
+ if (!c->data_in_input_filters) {
+ RETRIEVE_BRIGADE_FROM_POOL(bb, "ap_process_request_brigade",
+ c->pool, c->bucket_alloc);
+ b = apr_bucket_flush_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_HEAD(bb, b);
+ rv = ap_pass_brigade(c->output_filters, bb);
+ if (APR_STATUS_IS_TIMEUP(rv)) {
+ /*
+ * Notice a timeout as an error message. This might be
+ * valuable for detecting clients with broken network
+ * connections or possible DoS attacks.
+ */
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, rv, c, APLOGNO(01581)
+ "flushing data to the client");
+ }
+ apr_brigade_cleanup(bb);
+ }
+ if (ap_extended_status) {
+ ap_time_process_request(c->sbh, STOP_PREQUEST);
+ }
+}
+
+static apr_table_t *rename_original_env(apr_pool_t *p, apr_table_t *t)
+{
+ const apr_array_header_t *env_arr = apr_table_elts(t);
+ const apr_table_entry_t *elts = (const apr_table_entry_t *) env_arr->elts;
+ apr_table_t *new = apr_table_make(p, env_arr->nalloc);
+ int i;
+
+ for (i = 0; i < env_arr->nelts; ++i) {
+ if (!elts[i].key)
+ continue;
+ apr_table_setn(new, apr_pstrcat(p, "REDIRECT_", elts[i].key, NULL),
+ elts[i].val);
+ }
+
+ return new;
+}
+
+static request_rec *internal_internal_redirect(const char *new_uri,
+ request_rec *r) {
+ int access_status;
+ request_rec *new;
+ const char *vary_header;
+
+ if (ap_is_recursion_limit_exceeded(r)) {
+ ap_die(HTTP_INTERNAL_SERVER_ERROR, r);
+ return NULL;
+ }
+
+ new = (request_rec *) apr_pcalloc(r->pool, sizeof(request_rec));
+
+ new->connection = r->connection;
+ new->server = r->server;
+ new->pool = r->pool;
+
+ /*
+ * A whole lot of this really ought to be shared with http_protocol.c...
+ * another missing cleanup. It's particularly inappropriate to be
+ * setting header_only, etc., here.
+ */
+
+ new->method = r->method;
+ new->method_number = r->method_number;
+ new->allowed_methods = ap_make_method_list(new->pool, 2);
+ ap_parse_uri(new, new_uri);
+ new->parsed_uri.port_str = r->parsed_uri.port_str;
+ new->parsed_uri.port = r->parsed_uri.port;
+
+ new->request_config = ap_create_request_config(r->pool);
+
+ new->per_dir_config = r->server->lookup_defaults;
+
+ new->prev = r;
+ r->next = new;
+
+ new->useragent_addr = r->useragent_addr;
+ new->useragent_ip = r->useragent_ip;
+
+ /* Must have prev and next pointers set before calling create_request
+ * hook.
+ */
+ ap_run_create_request(new);
+
+ /* Inherit the rest of the protocol info... */
+
+ new->the_request = r->the_request;
+
+ new->allowed = r->allowed;
+
+ new->status = r->status;
+ new->assbackwards = r->assbackwards;
+ new->header_only = r->header_only;
+ new->protocol = r->protocol;
+ new->proto_num = r->proto_num;
+ new->hostname = r->hostname;
+ new->request_time = r->request_time;
+ new->main = r->main;
+
+ new->headers_in = r->headers_in;
+ new->trailers_in = r->trailers_in;
+ new->headers_out = apr_table_make(r->pool, 12);
+ if (ap_is_HTTP_REDIRECT(new->status)) {
+ const char *location = apr_table_get(r->headers_out, "Location");
+ if (location)
+ apr_table_setn(new->headers_out, "Location", location);
+ }
+
+ /* A module (like mod_rewrite) can force an internal redirect
+ * to carry over the Vary header (if present).
+ */
+ if (apr_table_get(r->notes, "redirect-keeps-vary")) {
+ if((vary_header = apr_table_get(r->headers_out, "Vary"))) {
+ apr_table_setn(new->headers_out, "Vary", vary_header);
+ }
+ }
+
+ new->err_headers_out = r->err_headers_out;
+ new->trailers_out = apr_table_make(r->pool, 5);
+ new->subprocess_env = rename_original_env(r->pool, r->subprocess_env);
+ new->notes = apr_table_make(r->pool, 5);
+
+ new->htaccess = r->htaccess;
+ new->no_cache = r->no_cache;
+ new->expecting_100 = r->expecting_100;
+ new->no_local_copy = r->no_local_copy;
+ new->read_length = r->read_length; /* We can only read it once */
+ new->vlist_validator = r->vlist_validator;
+
+ new->proto_output_filters = r->proto_output_filters;
+ new->proto_input_filters = r->proto_input_filters;
+
+ new->input_filters = new->proto_input_filters;
+
+ if (new->main) {
+ ap_filter_t *f, *nextf;
+
+ /* If this is a subrequest, the filter chain may contain a
+ * mixture of filters specific to the old request (r), and
+ * some inherited from r->main. Here, inherit that filter
+ * chain, and remove all those which are specific to the old
+ * request; ensuring the subreq filter is left in place. */
+ new->output_filters = r->output_filters;
+
+ f = new->output_filters;
+ do {
+ nextf = f->next;
+
+ if (f->r == r && f->frec != ap_subreq_core_filter_handle) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01582)
+ "dropping filter '%s' in internal redirect from %s to %s",
+ f->frec->name, r->unparsed_uri, new_uri);
+
+ /* To remove the filter, first set f->r to the *new*
+ * request_rec, so that ->output_filters on 'new' is
+ * changed (if necessary) when removing the filter. */
+ f->r = new;
+ ap_remove_output_filter(f);
+ }
+
+ f = nextf;
+
+ /* Stop at the protocol filters. If a protocol filter has
+ * been newly installed for this resource, better leave it
+ * in place, though it's probably a misconfiguration or
+ * filter bug to get into this state. */
+ } while (f && f != new->proto_output_filters);
+ }
+ else {
+ /* If this is not a subrequest, clear out all
+ * resource-specific filters. */
+ new->output_filters = new->proto_output_filters;
+ }
+
+ update_r_in_filters(new->input_filters, r, new);
+ update_r_in_filters(new->output_filters, r, new);
+
+ apr_table_setn(new->subprocess_env, "REDIRECT_STATUS",
+ apr_itoa(r->pool, r->status));
+
+ /* Begin by presuming any module can make its own path_info assumptions,
+ * until some module interjects and changes the value.
+ */
+ new->used_path_info = AP_REQ_DEFAULT_PATH_INFO;
+
+#if APR_HAS_THREADS
+ new->invoke_mtx = r->invoke_mtx;
+#endif
+
+ /*
+ * XXX: hmm. This is because mod_setenvif and mod_unique_id really need
+ * to do their thing on internal redirects as well. Perhaps this is a
+ * misnamed function.
+ */
+ if ((access_status = ap_post_read_request(new))) {
+ ap_die(access_status, new);
+ return NULL;
+ }
+
+ return new;
+}
+
+/* XXX: Is this function is so bogus and fragile that we deep-6 it? */
+AP_DECLARE(void) ap_internal_fast_redirect(request_rec *rr, request_rec *r)
+{
+ /* We need to tell POOL_DEBUG that we're guaranteeing that rr->pool
+ * will exist as long as r->pool. Otherwise we run into troubles because
+ * some values in this request will be allocated in r->pool, and others in
+ * rr->pool.
+ */
+ apr_pool_join(r->pool, rr->pool);
+ r->proxyreq = rr->proxyreq;
+ r->no_cache = (r->no_cache && rr->no_cache);
+ r->no_local_copy = (r->no_local_copy && rr->no_local_copy);
+ r->mtime = rr->mtime;
+ r->uri = rr->uri;
+ r->filename = rr->filename;
+ r->canonical_filename = rr->canonical_filename;
+ r->path_info = rr->path_info;
+ r->args = rr->args;
+ r->finfo = rr->finfo;
+ r->handler = rr->handler;
+ ap_set_content_type(r, rr->content_type);
+ r->content_encoding = rr->content_encoding;
+ r->content_languages = rr->content_languages;
+ r->per_dir_config = rr->per_dir_config;
+ /* copy output headers from subrequest, but leave negotiation headers */
+ r->notes = apr_table_overlay(r->pool, rr->notes, r->notes);
+ r->headers_out = apr_table_overlay(r->pool, rr->headers_out,
+ r->headers_out);
+ r->err_headers_out = apr_table_overlay(r->pool, rr->err_headers_out,
+ r->err_headers_out);
+ r->trailers_out = apr_table_overlay(r->pool, rr->trailers_out,
+ r->trailers_out);
+ r->subprocess_env = apr_table_overlay(r->pool, rr->subprocess_env,
+ r->subprocess_env);
+
+ r->output_filters = rr->output_filters;
+ r->input_filters = rr->input_filters;
+
+ /* If any filters pointed at the now-defunct rr, we must point them
+ * at our "new" instance of r. In particular, some of rr's structures
+ * will now be bogus (say rr->headers_out). If a filter tried to modify
+ * their f->r structure when it is pointing to rr, the real request_rec
+ * will not get updated. Fix that here.
+ */
+ update_r_in_filters(r->input_filters, rr, r);
+ update_r_in_filters(r->output_filters, rr, r);
+
+ if (r->main) {
+ ap_filter_t *next = r->output_filters;
+ while (next && (next != r->proto_output_filters)) {
+ if (next->frec == ap_subreq_core_filter_handle) {
+ break;
+ }
+ next = next->next;
+ }
+ if (!next || next == r->proto_output_filters) {
+ ap_add_output_filter_handle(ap_subreq_core_filter_handle,
+ NULL, r, r->connection);
+ }
+ }
+ else {
+ /*
+ * We need to check if we now have the SUBREQ_CORE filter in our filter
+ * chain. If this is the case we need to remove it since we are NO
+ * subrequest. But we need to keep in mind that the SUBREQ_CORE filter
+ * does not necessarily need to be the first filter in our chain. So we
+ * need to go through the chain. But we only need to walk up the chain
+ * until the proto_output_filters as the SUBREQ_CORE filter is below the
+ * protocol filters.
+ */
+ ap_filter_t *next;
+
+ next = r->output_filters;
+ while (next && (next->frec != ap_subreq_core_filter_handle)
+ && (next != r->proto_output_filters)) {
+ next = next->next;
+ }
+ if (next && (next->frec == ap_subreq_core_filter_handle)) {
+ ap_remove_output_filter(next);
+ }
+ }
+}
+
+AP_DECLARE(void) ap_internal_redirect(const char *new_uri, request_rec *r)
+{
+ int access_status;
+ request_rec *new = internal_internal_redirect(new_uri, r);
+
+ AP_INTERNAL_REDIRECT(r->uri, new_uri);
+
+ /* ap_die was already called, if an error occured */
+ if (!new) {
+ return;
+ }
+
+ access_status = ap_run_quick_handler(new, 0); /* Not a look-up request */
+ if (access_status == DECLINED) {
+ access_status = ap_process_request_internal(new);
+ if (access_status == OK) {
+ access_status = ap_invoke_handler(new);
+ }
+ }
+ ap_die(access_status, new);
+}
+
+/* This function is designed for things like actions or CGI scripts, when
+ * using AddHandler, and you want to preserve the content type across
+ * an internal redirect.
+ */
+AP_DECLARE(void) ap_internal_redirect_handler(const char *new_uri, request_rec *r)
+{
+ int access_status;
+ request_rec *new = internal_internal_redirect(new_uri, r);
+
+ /* ap_die was already called, if an error occured */
+ if (!new) {
+ return;
+ }
+
+ if (r->handler)
+ ap_set_content_type(new, r->content_type);
+ access_status = ap_process_request_internal(new);
+ if (access_status == OK) {
+ access_status = ap_invoke_handler(new);
+ }
+ ap_die(access_status, new);
+}
+
+AP_DECLARE(void) ap_allow_methods(request_rec *r, int reset, ...)
+{
+ const char *method;
+ va_list methods;
+
+ /*
+ * Get rid of any current settings if requested; not just the
+ * well-known methods but any extensions as well.
+ */
+ if (reset) {
+ ap_clear_method_list(r->allowed_methods);
+ }
+
+ va_start(methods, reset);
+ while ((method = va_arg(methods, const char *)) != NULL) {
+ ap_method_list_add(r->allowed_methods, method);
+ }
+ va_end(methods);
+}
+
+AP_DECLARE(void) ap_allow_standard_methods(request_rec *r, int reset, ...)
+{
+ int method;
+ va_list methods;
+ apr_int64_t mask;
+
+ /*
+ * Get rid of any current settings if requested; not just the
+ * well-known methods but any extensions as well.
+ */
+ if (reset) {
+ ap_clear_method_list(r->allowed_methods);
+ }
+
+ mask = 0;
+ va_start(methods, reset);
+ while ((method = va_arg(methods, int)) != -1) {
+ mask |= (AP_METHOD_BIT << method);
+ }
+ va_end(methods);
+
+ r->allowed_methods->method_mask |= mask;
+}
diff --git a/modules/http/mod_mime.c b/modules/http/mod_mime.c
new file mode 100644
index 0000000..03d1c41
--- /dev/null
+++ b/modules/http/mod_mime.c
@@ -0,0 +1,1024 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * http_mime.c: Sends/gets MIME headers for requests
+ *
+ * Rob McCool
+ *
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+#include "apr_hash.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "http_request.h"
+#include "http_protocol.h"
+
+/* XXXX - fix me / EBCDIC
+ * there was a cludge here which would use its
+ * own version apr_isascii(). Indicating that
+ * on some platforms that might be needed.
+ *
+ * #define OS_ASC(c) (c) -- for mere mortals
+ * or
+ * #define OS_ASC(c) (ebcdic2ascii[c]) -- for dino's
+ *
+ * #define apr_isascii(c) ((OS_ASC(c) & 0x80) == 0)
+ */
+
+/* XXXXX - fix me - See note with NOT_PROXY
+ */
+
+typedef struct attrib_info {
+ char *name;
+ int offset;
+} attrib_info;
+
+/* Information to which an extension can be mapped
+ */
+typedef struct extension_info {
+ char *forced_type; /* Additional AddTyped stuff */
+ char *encoding_type; /* Added with AddEncoding... */
+ char *language_type; /* Added with AddLanguage... */
+ char *handler; /* Added with AddHandler... */
+ char *charset_type; /* Added with AddCharset... */
+ char *input_filters; /* Added with AddInputFilter... */
+ char *output_filters; /* Added with AddOutputFilter... */
+} extension_info;
+
+#define MULTIMATCH_UNSET 0
+#define MULTIMATCH_ANY 1
+#define MULTIMATCH_NEGOTIATED 2
+#define MULTIMATCH_HANDLERS 4
+#define MULTIMATCH_FILTERS 8
+
+typedef struct {
+ apr_hash_t *extension_mappings; /* Map from extension name to
+ * extension_info structure */
+
+ apr_array_header_t *remove_mappings; /* A simple list, walked once */
+
+ char *default_language; /* Language if no AddLanguage ext found */
+
+ int multimatch; /* Extensions to include in multiview matching
+ * for filenames, e.g. Filters and Handlers
+ */
+ int use_path_info; /* If set to 0, only use filename.
+ * If set to 1, append PATH_INFO to filename for
+ * lookups.
+ * If set to 2, this value is unset and is
+ * effectively 0.
+ */
+} mime_dir_config;
+
+typedef struct param_s {
+ char *attr;
+ char *val;
+ struct param_s *next;
+} param;
+
+typedef struct {
+ const char *type;
+ apr_size_t type_len;
+ const char *subtype;
+ apr_size_t subtype_len;
+ param *param;
+} content_type;
+
+static char tspecial[] = {
+ '(', ')', '<', '>', '@', ',', ';', ':',
+ '\\', '"', '/', '[', ']', '?', '=',
+ '\0'
+};
+
+module AP_MODULE_DECLARE_DATA mime_module;
+
+static void *create_mime_dir_config(apr_pool_t *p, char *dummy)
+{
+ mime_dir_config *new = apr_palloc(p, sizeof(mime_dir_config));
+
+ new->extension_mappings = NULL;
+ new->remove_mappings = NULL;
+
+ new->default_language = NULL;
+
+ new->multimatch = MULTIMATCH_UNSET;
+
+ new->use_path_info = 2;
+
+ return new;
+}
+/*
+ * Overlay one hash table of extension_mappings onto another
+ */
+static void *overlay_extension_mappings(apr_pool_t *p,
+ const void *key,
+ apr_ssize_t klen,
+ const void *overlay_val,
+ const void *base_val,
+ const void *data)
+{
+ const extension_info *overlay_info = (const extension_info *)overlay_val;
+ const extension_info *base_info = (const extension_info *)base_val;
+ extension_info *new_info = apr_pmemdup(p, base_info, sizeof(extension_info));
+
+ if (overlay_info->forced_type) {
+ new_info->forced_type = overlay_info->forced_type;
+ }
+ if (overlay_info->encoding_type) {
+ new_info->encoding_type = overlay_info->encoding_type;
+ }
+ if (overlay_info->language_type) {
+ new_info->language_type = overlay_info->language_type;
+ }
+ if (overlay_info->handler) {
+ new_info->handler = overlay_info->handler;
+ }
+ if (overlay_info->charset_type) {
+ new_info->charset_type = overlay_info->charset_type;
+ }
+ if (overlay_info->input_filters) {
+ new_info->input_filters = overlay_info->input_filters;
+ }
+ if (overlay_info->output_filters) {
+ new_info->output_filters = overlay_info->output_filters;
+ }
+
+ return new_info;
+}
+
+/* Member is the offset within an extension_info of the pointer to reset
+ */
+static void remove_items(apr_pool_t *p, apr_array_header_t *remove,
+ apr_hash_t *mappings)
+{
+ attrib_info *suffix = (attrib_info *) remove->elts;
+ int i;
+ for (i = 0; i < remove->nelts; i++) {
+ extension_info *exinfo = apr_hash_get(mappings,
+ suffix[i].name,
+ APR_HASH_KEY_STRING);
+ if (exinfo && *(const char**)((char *)exinfo + suffix[i].offset)) {
+ extension_info *copyinfo = exinfo;
+ exinfo = apr_pmemdup(p, copyinfo, sizeof(*exinfo));
+ apr_hash_set(mappings, suffix[i].name,
+ APR_HASH_KEY_STRING, exinfo);
+
+ *(const char**)((char *)exinfo + suffix[i].offset) = NULL;
+ }
+ }
+}
+
+static void *merge_mime_dir_configs(apr_pool_t *p, void *basev, void *addv)
+{
+ mime_dir_config *base = (mime_dir_config *)basev;
+ mime_dir_config *add = (mime_dir_config *)addv;
+ mime_dir_config *new = apr_palloc(p, sizeof(mime_dir_config));
+
+ if (base->extension_mappings && add->extension_mappings) {
+ new->extension_mappings = apr_hash_merge(p, add->extension_mappings,
+ base->extension_mappings,
+ overlay_extension_mappings,
+ NULL);
+ }
+ else {
+ if (base->extension_mappings == NULL) {
+ new->extension_mappings = add->extension_mappings;
+ }
+ else {
+ new->extension_mappings = base->extension_mappings;
+ }
+ /* We may not be merging the tables, but if we potentially will change
+ * an exinfo member, then we are about to trounce it anyways.
+ * We must have a copy for safety.
+ */
+ if (new->extension_mappings && add->remove_mappings) {
+ new->extension_mappings =
+ apr_hash_copy(p, new->extension_mappings);
+ }
+ }
+
+ if (new->extension_mappings) {
+ if (add->remove_mappings)
+ remove_items(p, add->remove_mappings, new->extension_mappings);
+ }
+ new->remove_mappings = NULL;
+
+ new->default_language = add->default_language ?
+ add->default_language : base->default_language;
+
+ new->multimatch = (add->multimatch != MULTIMATCH_UNSET) ?
+ add->multimatch : base->multimatch;
+
+ if ((add->use_path_info & 2) == 0) {
+ new->use_path_info = add->use_path_info;
+ }
+ else {
+ new->use_path_info = base->use_path_info;
+ }
+
+ return new;
+}
+
+static const char *add_extension_info(cmd_parms *cmd, void *m_,
+ const char *value_, const char* ext)
+{
+ mime_dir_config *m=m_;
+ extension_info *exinfo;
+ int offset = (int) (long) cmd->info;
+ char *key = apr_pstrdup(cmd->temp_pool, ext);
+ char *value = apr_pstrdup(cmd->pool, value_);
+ ap_str_tolower(value);
+ ap_str_tolower(key);
+
+ if (*key == '.') {
+ ++key;
+ }
+ if (!m->extension_mappings) {
+ m->extension_mappings = apr_hash_make(cmd->pool);
+ exinfo = NULL;
+ }
+ else {
+ exinfo = (extension_info*)apr_hash_get(m->extension_mappings, key,
+ APR_HASH_KEY_STRING);
+ }
+ if (!exinfo) {
+ exinfo = apr_pcalloc(cmd->pool, sizeof(extension_info));
+ key = apr_pstrdup(cmd->pool, key);
+ apr_hash_set(m->extension_mappings, key, APR_HASH_KEY_STRING, exinfo);
+ }
+ *(const char**)((char *)exinfo + offset) = value;
+ return NULL;
+}
+
+/*
+ * As RemoveType should also override the info from TypesConfig, we add an
+ * empty string as type instead of actually removing the type.
+ */
+static const char *remove_extension_type(cmd_parms *cmd, void *m_,
+ const char *ext)
+{
+ return add_extension_info(cmd, m_, "", ext);
+}
+
+/*
+ * Note handler names are un-added with each per_dir_config merge.
+ * This keeps the association from being inherited, but not
+ * from being re-added at a subordinate level.
+ */
+static const char *remove_extension_info(cmd_parms *cmd, void *m_,
+ const char *ext)
+{
+ mime_dir_config *m = (mime_dir_config *) m_;
+ attrib_info *suffix;
+ if (*ext == '.') {
+ ++ext;
+ }
+ if (!m->remove_mappings) {
+ m->remove_mappings = apr_array_make(cmd->pool, 4, sizeof(*suffix));
+ }
+ suffix = (attrib_info *)apr_array_push(m->remove_mappings);
+ suffix->name = apr_pstrdup(cmd->pool, ext);
+ ap_str_tolower(suffix->name);
+ suffix->offset = (int) (long) cmd->info;
+ return NULL;
+}
+
+/* The sole bit of server configuration that the MIME module has is
+ * the name of its config file, so...
+ */
+
+static const char *set_types_config(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ ap_set_module_config(cmd->server->module_config, &mime_module,
+ (void *)arg);
+ return NULL;
+}
+
+static const char *multiviews_match(cmd_parms *cmd, void *m_,
+ const char *include)
+{
+ mime_dir_config *m = (mime_dir_config *) m_;
+ const char *errmsg;
+
+ errmsg = ap_check_cmd_context(cmd, NOT_IN_LOCATION);
+ if (errmsg != NULL) {
+ return errmsg;
+ }
+
+ if (strcasecmp(include, "Any") == 0) {
+ if (m->multimatch && (m->multimatch & ~MULTIMATCH_ANY)) {
+ return "Any is incompatible with NegotiatedOnly, "
+ "Filters and Handlers";
+ }
+ m->multimatch |= MULTIMATCH_ANY;
+ }
+ else if (strcasecmp(include, "NegotiatedOnly") == 0) {
+ if (m->multimatch && (m->multimatch & ~MULTIMATCH_NEGOTIATED)) {
+ return "NegotiatedOnly is incompatible with Any, "
+ "Filters and Handlers";
+ }
+ m->multimatch |= MULTIMATCH_NEGOTIATED;
+ }
+ else if (strcasecmp(include, "Filters") == 0) {
+ if (m->multimatch && (m->multimatch & (MULTIMATCH_NEGOTIATED
+ | MULTIMATCH_ANY))) {
+ return "Filters is incompatible with Any and NegotiatedOnly";
+ }
+ m->multimatch |= MULTIMATCH_FILTERS;
+ }
+ else if (strcasecmp(include, "Handlers") == 0) {
+ if (m->multimatch && (m->multimatch & (MULTIMATCH_NEGOTIATED
+ | MULTIMATCH_ANY))) {
+ return "Handlers is incompatible with Any and NegotiatedOnly";
+ }
+ m->multimatch |= MULTIMATCH_HANDLERS;
+ }
+ else {
+ return apr_psprintf(cmd->pool, "Unrecognized option '%s'", include);
+ }
+
+ return NULL;
+}
+
+static const command_rec mime_cmds[] =
+{
+ AP_INIT_ITERATE2("AddCharset", add_extension_info,
+ (void *)APR_OFFSETOF(extension_info, charset_type), OR_FILEINFO,
+ "a charset (e.g., iso-2022-jp), followed by one or more "
+ "file extensions"),
+ AP_INIT_ITERATE2("AddEncoding", add_extension_info,
+ (void *)APR_OFFSETOF(extension_info, encoding_type), OR_FILEINFO,
+ "an encoding (e.g., gzip), followed by one or more file extensions"),
+ AP_INIT_ITERATE2("AddHandler", add_extension_info,
+ (void *)APR_OFFSETOF(extension_info, handler), OR_FILEINFO,
+ "a handler name followed by one or more file extensions"),
+ AP_INIT_ITERATE2("AddInputFilter", add_extension_info,
+ (void *)APR_OFFSETOF(extension_info, input_filters), OR_FILEINFO,
+ "input filter name (or ; delimited names) followed by one or "
+ "more file extensions"),
+ AP_INIT_ITERATE2("AddLanguage", add_extension_info,
+ (void *)APR_OFFSETOF(extension_info, language_type), OR_FILEINFO,
+ "a language (e.g., fr), followed by one or more file extensions"),
+ AP_INIT_ITERATE2("AddOutputFilter", add_extension_info,
+ (void *)APR_OFFSETOF(extension_info, output_filters), OR_FILEINFO,
+ "output filter name (or ; delimited names) followed by one or "
+ "more file extensions"),
+ AP_INIT_ITERATE2("AddType", add_extension_info,
+ (void *)APR_OFFSETOF(extension_info, forced_type), OR_FILEINFO,
+ "a mime type followed by one or more file extensions"),
+ AP_INIT_TAKE1("DefaultLanguage", ap_set_string_slot,
+ (void*)APR_OFFSETOF(mime_dir_config, default_language), OR_FILEINFO,
+ "language to use for documents with no other language file extension"),
+ AP_INIT_ITERATE("MultiviewsMatch", multiviews_match, NULL, OR_FILEINFO,
+ "NegotiatedOnly (default), Handlers and/or Filters, or Any"),
+ AP_INIT_ITERATE("RemoveCharset", remove_extension_info,
+ (void *)APR_OFFSETOF(extension_info, charset_type), OR_FILEINFO,
+ "one or more file extensions"),
+ AP_INIT_ITERATE("RemoveEncoding", remove_extension_info,
+ (void *)APR_OFFSETOF(extension_info, encoding_type), OR_FILEINFO,
+ "one or more file extensions"),
+ AP_INIT_ITERATE("RemoveHandler", remove_extension_info,
+ (void *)APR_OFFSETOF(extension_info, handler), OR_FILEINFO,
+ "one or more file extensions"),
+ AP_INIT_ITERATE("RemoveInputFilter", remove_extension_info,
+ (void *)APR_OFFSETOF(extension_info, input_filters), OR_FILEINFO,
+ "one or more file extensions"),
+ AP_INIT_ITERATE("RemoveLanguage", remove_extension_info,
+ (void *)APR_OFFSETOF(extension_info, language_type), OR_FILEINFO,
+ "one or more file extensions"),
+ AP_INIT_ITERATE("RemoveOutputFilter", remove_extension_info,
+ (void *)APR_OFFSETOF(extension_info, output_filters), OR_FILEINFO,
+ "one or more file extensions"),
+ AP_INIT_ITERATE("RemoveType", remove_extension_type,
+ (void *)APR_OFFSETOF(extension_info, forced_type), OR_FILEINFO,
+ "one or more file extensions"),
+ AP_INIT_TAKE1("TypesConfig", set_types_config, NULL, RSRC_CONF,
+ "the MIME types config file"),
+ AP_INIT_FLAG("ModMimeUsePathInfo", ap_set_flag_slot,
+ (void *)APR_OFFSETOF(mime_dir_config, use_path_info), ACCESS_CONF,
+ "Set to 'yes' to allow mod_mime to use path info for type checking"),
+ {NULL}
+};
+
+static apr_hash_t *mime_type_extensions;
+
+static int mime_post_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
+{
+ ap_configfile_t *f;
+ char l[MAX_STRING_LEN];
+ const char *types_confname = ap_get_module_config(s->module_config,
+ &mime_module);
+ apr_status_t status;
+
+ if (!types_confname) {
+ types_confname = AP_TYPES_CONFIG_FILE;
+ }
+
+ types_confname = ap_server_root_relative(p, types_confname);
+ if (!types_confname) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, APR_EBADPATH, s, APLOGNO(01596)
+ "Invalid mime types config path %s",
+ (const char *)ap_get_module_config(s->module_config,
+ &mime_module));
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ if ((status = ap_pcfg_openfile(&f, ptemp, types_confname))
+ != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, status, s, APLOGNO(01597)
+ "could not open mime types config file %s.",
+ types_confname);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ mime_type_extensions = apr_hash_make(p);
+
+ while (!(ap_cfg_getline(l, MAX_STRING_LEN, f))) {
+ const char *ll = l, *ct;
+
+ if (l[0] == '#') {
+ continue;
+ }
+ ct = ap_getword_conf(p, &ll);
+
+ while (ll[0]) {
+ char *ext = ap_getword_conf(p, &ll);
+ ap_str_tolower(ext);
+ apr_hash_set(mime_type_extensions, ext, APR_HASH_KEY_STRING, ct);
+ }
+ }
+ ap_cfg_closefile(f);
+ return OK;
+}
+
+static const char *zap_sp(const char *s)
+{
+ if (s == NULL) {
+ return (NULL);
+ }
+ if (*s == '\0') {
+ return (s);
+ }
+
+ /* skip prefixed white space */
+ for (; *s == ' ' || *s == '\t' || *s == '\n'; s++)
+ ;
+
+ return (s);
+}
+
+static char *zap_sp_and_dup(apr_pool_t *p, const char *start,
+ const char *end, apr_size_t *len)
+{
+ while ((start < end) && apr_isspace(*start)) {
+ start++;
+ }
+ while ((end > start) && apr_isspace(*(end - 1))) {
+ end--;
+ }
+ if (len) {
+ *len = end - start;
+ }
+ return apr_pstrmemdup(p, start, end - start);
+}
+
+static int is_token(char c)
+{
+ int res;
+
+ res = (apr_isascii(c) && apr_isgraph(c)
+ && (strchr(tspecial, c) == NULL)) ? 1 : -1;
+ return res;
+}
+
+static int is_qtext(char c)
+{
+ int res;
+
+ res = (apr_isascii(c) && (c != '"') && (c != '\\') && (c != '\n'))
+ ? 1 : -1;
+ return res;
+}
+
+static int is_quoted_pair(const char *s)
+{
+ int res = -1;
+ int c;
+
+ if (*s == '\\') {
+ c = (int) *(s + 1);
+ if (c && apr_isascii(c)) {
+ res = 1;
+ }
+ }
+ return (res);
+}
+
+static content_type *analyze_ct(request_rec *r, const char *s)
+{
+ const char *cp, *mp;
+ char *attribute, *value;
+ int quoted = 0;
+ server_rec * ss = r->server;
+ apr_pool_t * p = r->pool;
+
+ content_type *ctp;
+ param *pp, *npp;
+
+ /* initialize ctp */
+ ctp = (content_type *)apr_palloc(p, sizeof(content_type));
+ ctp->type = NULL;
+ ctp->subtype = NULL;
+ ctp->param = NULL;
+
+ mp = s;
+
+ /* getting a type */
+ cp = mp;
+ while (apr_isspace(*cp)) {
+ cp++;
+ }
+ if (!*cp) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, APLOGNO(01598)
+ "mod_mime: analyze_ct: cannot get media type from '%s'",
+ (const char *) mp);
+ return (NULL);
+ }
+ ctp->type = cp;
+ do {
+ cp++;
+ } while (*cp && (*cp != '/') && !apr_isspace(*cp) && (*cp != ';'));
+ if (!*cp || (*cp == ';')) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, APLOGNO(01599)
+ "Cannot get media type from '%s'",
+ (const char *) mp);
+ return (NULL);
+ }
+ while (apr_isspace(*cp)) {
+ cp++;
+ }
+ if (*cp != '/') {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, APLOGNO(01600)
+ "mod_mime: analyze_ct: cannot get media type from '%s'",
+ (const char *) mp);
+ return (NULL);
+ }
+ ctp->type_len = cp - ctp->type;
+
+ cp++; /* skip the '/' */
+
+ /* getting a subtype */
+ while (apr_isspace(*cp)) {
+ cp++;
+ }
+ if (!*cp) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, APLOGNO(01601)
+ "Cannot get media subtype.");
+ return (NULL);
+ }
+ ctp->subtype = cp;
+ do {
+ cp++;
+ } while (*cp && !apr_isspace(*cp) && (*cp != ';'));
+ ctp->subtype_len = cp - ctp->subtype;
+ while (apr_isspace(*cp)) {
+ cp++;
+ }
+
+ if (*cp == '\0') {
+ return (ctp);
+ }
+
+ /* getting parameters */
+ cp++; /* skip the ';' */
+ cp = zap_sp(cp);
+ if (cp == NULL || *cp == '\0') {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, APLOGNO(01602)
+ "Cannot get media parameter.");
+ return (NULL);
+ }
+ mp = cp;
+ attribute = NULL;
+ value = NULL;
+
+ while (cp != NULL && *cp != '\0') {
+ if (attribute == NULL) {
+ if (is_token(*cp) > 0) {
+ cp++;
+ continue;
+ }
+ else if (*cp == ' ' || *cp == '\t' || *cp == '\n') {
+ cp++;
+ continue;
+ }
+ else if (*cp == '=') {
+ attribute = zap_sp_and_dup(p, mp, cp, NULL);
+ if (attribute == NULL || *attribute == '\0') {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, APLOGNO(01603)
+ "Cannot get media parameter.");
+ return (NULL);
+ }
+ cp++;
+ cp = zap_sp(cp);
+ if (cp == NULL || *cp == '\0') {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, APLOGNO(01604)
+ "Cannot get media parameter.");
+ return (NULL);
+ }
+ mp = cp;
+ continue;
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, APLOGNO(01605)
+ "Cannot get media parameter.");
+ return (NULL);
+ }
+ }
+ else {
+ if (mp == cp) {
+ if (*cp == '"') {
+ quoted = 1;
+ cp++;
+ }
+ else {
+ quoted = 0;
+ }
+ }
+ if (quoted > 0) {
+ while (quoted && *cp != '\0') {
+ if (is_qtext(*cp) > 0) {
+ cp++;
+ }
+ else if (is_quoted_pair(cp) > 0) {
+ cp += 2;
+ }
+ else if (*cp == '"') {
+ cp++;
+ while (*cp == ' ' || *cp == '\t' || *cp == '\n') {
+ cp++;
+ }
+ if (*cp != ';' && *cp != '\0') {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, APLOGNO(01606)
+ "Cannot get media parameter.");
+ return(NULL);
+ }
+ quoted = 0;
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, APLOGNO(01607)
+ "Cannot get media parameter.");
+ return (NULL);
+ }
+ }
+ }
+ else {
+ while (1) {
+ if (is_token(*cp) > 0) {
+ cp++;
+ }
+ else if (*cp == '\0' || *cp == ';') {
+ break;
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, APLOGNO(01608)
+ "Cannot get media parameter.");
+ return (NULL);
+ }
+ }
+ }
+ value = zap_sp_and_dup(p, mp, cp, NULL);
+ if (value == NULL || *value == '\0') {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, APLOGNO(01609)
+ "Cannot get media parameter.");
+ return (NULL);
+ }
+
+ pp = apr_palloc(p, sizeof(param));
+ pp->attr = attribute;
+ pp->val = value;
+ pp->next = NULL;
+
+ if (ctp->param == NULL) {
+ ctp->param = pp;
+ }
+ else {
+ npp = ctp->param;
+ while (npp->next) {
+ npp = npp->next;
+ }
+ npp->next = pp;
+ }
+ quoted = 0;
+ attribute = NULL;
+ value = NULL;
+ if (*cp == '\0') {
+ break;
+ }
+ cp++;
+ mp = cp;
+ }
+ }
+ return (ctp);
+}
+
+/*
+ * find_ct is the hook routine for determining content-type and other
+ * MIME-related metadata. It assumes that r->filename has already been
+ * set and stat has been called for r->finfo. It also assumes that the
+ * non-path base file name is not the empty string unless it is a dir.
+ */
+static int find_ct(request_rec *r)
+{
+ mime_dir_config *conf;
+ apr_array_header_t *exception_list;
+ char *ext;
+ const char *fn, *fntmp, *type, *charset = NULL, *resource_name;
+ int found_metadata = 0;
+
+ if (r->finfo.filetype == APR_DIR) {
+ ap_set_content_type(r, DIR_MAGIC_TYPE);
+ return OK;
+ }
+
+ if (!r->filename) {
+ return DECLINED;
+ }
+
+ conf = (mime_dir_config *)ap_get_module_config(r->per_dir_config,
+ &mime_module);
+ exception_list = apr_array_make(r->pool, 2, sizeof(char *));
+
+ /* If use_path_info is explicitly set to on (value & 1 == 1), append. */
+ if (conf->use_path_info & 1) {
+ resource_name = apr_pstrcat(r->pool, r->filename, r->path_info, NULL);
+ }
+ else {
+ resource_name = r->filename;
+ }
+
+ /* Always drop the path leading up to the file name.
+ */
+ if ((fn = ap_strrchr_c(resource_name, '/')) == NULL) {
+ fn = resource_name;
+ }
+ else {
+ ++fn;
+ }
+
+
+ /* The exception list keeps track of those filename components that
+ * are not associated with extensions indicating metadata.
+ * The base name is always the first exception (i.e., "txt.html" has
+ * a basename of "txt" even though it might look like an extension).
+ * Leading dots are considered to be part of the base name (a file named
+ * ".png" is likely not a png file but just a hidden file called png).
+ */
+ fntmp = fn;
+ while (*fntmp == '.')
+ fntmp++;
+ fntmp = ap_strchr_c(fntmp, '.');
+ if (fntmp) {
+ ext = apr_pstrmemdup(r->pool, fn, fntmp - fn);
+ fn = fntmp + 1;
+ }
+ else {
+ ext = apr_pstrdup(r->pool, fn);
+ fn += strlen(fn);
+ }
+
+ *((const char **)apr_array_push(exception_list)) = ext;
+
+ /* Parse filename extensions which can be in any order
+ */
+ while (*fn && (ext = ap_getword(r->pool, &fn, '.'))) {
+ const extension_info *exinfo = NULL;
+ int found;
+ char *extcase;
+
+ if (*ext == '\0') { /* ignore empty extensions "bad..html" */
+ continue;
+ }
+
+ found = 0;
+
+ /* Save the ext in extcase before converting it to lower case.
+ */
+ extcase = apr_pstrdup(r->pool, ext);
+ ap_str_tolower(ext);
+
+ if (conf->extension_mappings != NULL) {
+ exinfo = (extension_info*)apr_hash_get(conf->extension_mappings,
+ ext, APR_HASH_KEY_STRING);
+ }
+
+ if (exinfo == NULL || !exinfo->forced_type) {
+ if ((type = apr_hash_get(mime_type_extensions, ext,
+ APR_HASH_KEY_STRING)) != NULL) {
+ ap_set_content_type(r, (char*) type);
+ found = 1;
+ }
+ }
+
+ if (exinfo != NULL) {
+
+ /* empty string is treated as special case for RemoveType */
+ if (exinfo->forced_type && *exinfo->forced_type) {
+ ap_set_content_type(r, exinfo->forced_type);
+ found = 1;
+ }
+
+ if (exinfo->charset_type) {
+ charset = exinfo->charset_type;
+ found = 1;
+ }
+ if (exinfo->language_type) {
+ if (!r->content_languages) {
+ r->content_languages = apr_array_make(r->pool, 2,
+ sizeof(char *));
+ }
+ *((const char **)apr_array_push(r->content_languages))
+ = exinfo->language_type;
+ found = 1;
+ }
+ if (exinfo->encoding_type) {
+ if (!r->content_encoding) {
+ r->content_encoding = exinfo->encoding_type;
+ }
+ else {
+ /* XXX should eliminate duplicate entities
+ *
+ * ah no. Order is important and double encoding is neither
+ * forbidden nor impossible. -- nd
+ */
+ r->content_encoding = apr_pstrcat(r->pool,
+ r->content_encoding,
+ ", ",
+ exinfo->encoding_type,
+ NULL);
+ }
+ found = 1;
+ }
+ /* The following extensions are not 'Found'. That is, they don't
+ * make any contribution to metadata negotiation, so they must have
+ * been explicitly requested by name.
+ */
+ if (exinfo->handler && r->proxyreq == PROXYREQ_NONE) {
+ r->handler = exinfo->handler;
+ if (conf->multimatch & MULTIMATCH_HANDLERS) {
+ found = 1;
+ }
+ }
+ /* XXX Two significant problems; 1, we don't check to see if we are
+ * setting redundant filters. 2, we insert these in the types
+ * config hook, which may be too early (dunno.)
+ */
+ if (exinfo->input_filters) {
+ const char *filter, *filters = exinfo->input_filters;
+ while (*filters
+ && (filter = ap_getword(r->pool, &filters, ';'))) {
+ ap_add_input_filter(filter, NULL, r, r->connection);
+ }
+ if (conf->multimatch & MULTIMATCH_FILTERS) {
+ found = 1;
+ }
+ }
+ if (exinfo->output_filters) {
+ const char *filter, *filters = exinfo->output_filters;
+ while (*filters
+ && (filter = ap_getword(r->pool, &filters, ';'))) {
+ ap_add_output_filter(filter, NULL, r, r->connection);
+ }
+ if (conf->multimatch & MULTIMATCH_FILTERS) {
+ found = 1;
+ }
+ }
+ }
+
+ if (found || (conf->multimatch & MULTIMATCH_ANY)) {
+ found_metadata = 1;
+ }
+ else {
+ *((const char **) apr_array_push(exception_list)) = extcase;
+ }
+ }
+
+ /*
+ * Need to set a notes entry on r for unrecognized elements.
+ * Somebody better claim them! If we did absolutely nothing,
+ * skip the notes to alert mod_negotiation we are clueless.
+ */
+ if (found_metadata) {
+ apr_table_setn(r->notes, "ap-mime-exceptions-list",
+ (void *)exception_list);
+ }
+
+ if (r->content_type) {
+ content_type *ctp;
+ int override = 0;
+
+ if ((ctp = analyze_ct(r, r->content_type))) {
+ param *pp = ctp->param;
+ char *base_content_type = apr_palloc(r->pool, ctp->type_len +
+ ctp->subtype_len +
+ sizeof("/"));
+ char *tmp = base_content_type;
+ memcpy(tmp, ctp->type, ctp->type_len);
+ tmp += ctp->type_len;
+ *tmp++ = '/';
+ memcpy(tmp, ctp->subtype, ctp->subtype_len);
+ tmp += ctp->subtype_len;
+ *tmp = 0;
+ ap_set_content_type(r, base_content_type);
+ while (pp != NULL) {
+ if (charset && !strcmp(pp->attr, "charset")) {
+ if (!override) {
+ ap_set_content_type(r,
+ apr_pstrcat(r->pool,
+ r->content_type,
+ "; charset=",
+ charset,
+ NULL));
+ override = 1;
+ }
+ }
+ else {
+ ap_set_content_type(r,
+ apr_pstrcat(r->pool,
+ r->content_type,
+ "; ", pp->attr,
+ "=", pp->val,
+ NULL));
+ }
+ pp = pp->next;
+ }
+ if (charset && !override) {
+ ap_set_content_type(r, apr_pstrcat(r->pool, r->content_type,
+ "; charset=", charset,
+ NULL));
+ }
+ }
+ }
+
+ /* Set default language, if none was specified by the extensions
+ * and we have a DefaultLanguage setting in force
+ */
+
+ if (!r->content_languages && conf->default_language) {
+ const char **new;
+
+ r->content_languages = apr_array_make(r->pool, 2, sizeof(char *));
+ new = (const char **)apr_array_push(r->content_languages);
+ *new = conf->default_language;
+ }
+
+ if (!r->content_type) {
+ return DECLINED;
+ }
+
+ return OK;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_hook_post_config(mime_post_config,NULL,NULL,APR_HOOK_MIDDLE);
+ ap_hook_type_checker(find_ct,NULL,NULL,APR_HOOK_MIDDLE);
+ /*
+ * this hook seems redundant ... is there any reason a type checker isn't
+ * allowed to do this already? I'd think that fixups in general would be
+ * the last opportunity to get the filters right.
+ * ap_hook_insert_filter(mime_insert_filters,NULL,NULL,APR_HOOK_MIDDLE);
+ */
+}
+
+AP_DECLARE_MODULE(mime) = {
+ STANDARD20_MODULE_STUFF,
+ create_mime_dir_config, /* create per-directory config structure */
+ merge_mime_dir_configs, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ mime_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/http/mod_mime.dep b/modules/http/mod_mime.dep
new file mode 100644
index 0000000..7a195a1
--- /dev/null
+++ b/modules/http/mod_mime.dep
@@ -0,0 +1,55 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_mime.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_mime.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/http/mod_mime.dsp b/modules/http/mod_mime.dsp
new file mode 100644
index 0000000..71ba2ad
--- /dev/null
+++ b/modules/http/mod_mime.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_mime" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_mime - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_mime.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_mime.mak" CFG="mod_mime - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_mime - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_mime - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_mime - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_mime_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_mime.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_mime.so" /d LONG_NAME="mime_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_mime.so" /base:@..\..\os\win32\BaseAddr.ref,mod_mime.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_mime.so" /base:@..\..\os\win32\BaseAddr.ref,mod_mime.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_mime.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_mime - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_mime_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_mime.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_mime.so" /d LONG_NAME="mime_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_mime.so" /base:@..\..\os\win32\BaseAddr.ref,mod_mime.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_mime.so" /base:@..\..\os\win32\BaseAddr.ref,mod_mime.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_mime.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_mime - Win32 Release"
+# Name "mod_mime - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_mime.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/http/mod_mime.exp b/modules/http/mod_mime.exp
new file mode 100644
index 0000000..f2e38db
--- /dev/null
+++ b/modules/http/mod_mime.exp
@@ -0,0 +1 @@
+mime_module
diff --git a/modules/http/mod_mime.mak b/modules/http/mod_mime.mak
new file mode 100644
index 0000000..14d106f
--- /dev/null
+++ b/modules/http/mod_mime.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_mime.dsp
+!IF "$(CFG)" == ""
+CFG=mod_mime - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_mime - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_mime - Win32 Release" && "$(CFG)" != "mod_mime - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_mime.mak" CFG="mod_mime - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_mime - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_mime - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_mime - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_mime.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_mime.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_mime.obj"
+ -@erase "$(INTDIR)\mod_mime.res"
+ -@erase "$(INTDIR)\mod_mime_src.idb"
+ -@erase "$(INTDIR)\mod_mime_src.pdb"
+ -@erase "$(OUTDIR)\mod_mime.exp"
+ -@erase "$(OUTDIR)\mod_mime.lib"
+ -@erase "$(OUTDIR)\mod_mime.pdb"
+ -@erase "$(OUTDIR)\mod_mime.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_mime_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_mime.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_mime.so" /d LONG_NAME="mime_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_mime.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_mime.pdb" /debug /out:"$(OUTDIR)\mod_mime.so" /implib:"$(OUTDIR)\mod_mime.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_mime.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_mime.obj" \
+ "$(INTDIR)\mod_mime.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_mime.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_mime.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_mime.so"
+ if exist .\Release\mod_mime.so.manifest mt.exe -manifest .\Release\mod_mime.so.manifest -outputresource:.\Release\mod_mime.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_mime - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_mime.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_mime.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_mime.obj"
+ -@erase "$(INTDIR)\mod_mime.res"
+ -@erase "$(INTDIR)\mod_mime_src.idb"
+ -@erase "$(INTDIR)\mod_mime_src.pdb"
+ -@erase "$(OUTDIR)\mod_mime.exp"
+ -@erase "$(OUTDIR)\mod_mime.lib"
+ -@erase "$(OUTDIR)\mod_mime.pdb"
+ -@erase "$(OUTDIR)\mod_mime.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_mime_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_mime.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_mime.so" /d LONG_NAME="mime_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_mime.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_mime.pdb" /debug /out:"$(OUTDIR)\mod_mime.so" /implib:"$(OUTDIR)\mod_mime.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_mime.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_mime.obj" \
+ "$(INTDIR)\mod_mime.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_mime.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_mime.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_mime.so"
+ if exist .\Debug\mod_mime.so.manifest mt.exe -manifest .\Debug\mod_mime.so.manifest -outputresource:.\Debug\mod_mime.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_mime.dep")
+!INCLUDE "mod_mime.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_mime.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_mime - Win32 Release" || "$(CFG)" == "mod_mime - Win32 Debug"
+
+!IF "$(CFG)" == "mod_mime - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\http"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\http"
+
+!ELSEIF "$(CFG)" == "mod_mime - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\http"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\http"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_mime - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\http"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\http"
+
+!ELSEIF "$(CFG)" == "mod_mime - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\http"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\http"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_mime - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\http"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\http"
+
+!ELSEIF "$(CFG)" == "mod_mime - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\http"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\http"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_mime - Win32 Release"
+
+
+"$(INTDIR)\mod_mime.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_mime.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_mime.so" /d LONG_NAME="mime_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_mime - Win32 Debug"
+
+
+"$(INTDIR)\mod_mime.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_mime.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_mime.so" /d LONG_NAME="mime_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_mime.c
+
+"$(INTDIR)\mod_mime.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/http2/Makefile.in b/modules/http2/Makefile.in
new file mode 100644
index 0000000..4395bc3
--- /dev/null
+++ b/modules/http2/Makefile.in
@@ -0,0 +1,20 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# standard stuff
+#
+
+include $(top_srcdir)/build/special.mk
diff --git a/modules/http2/NWGNUmakefile b/modules/http2/NWGNUmakefile
new file mode 100644
index 0000000..d4a51ed
--- /dev/null
+++ b/modules/http2/NWGNUmakefile
@@ -0,0 +1,246 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME =
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION =
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME =
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE =
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/mod_http2.nlm \
+ $(OBJDIR)/proxyht2.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/http2/NWGNUmod_http2 b/modules/http2/NWGNUmod_http2
new file mode 100644
index 0000000..f6d4a38
--- /dev/null
+++ b/modules/http2/NWGNUmod_http2
@@ -0,0 +1,395 @@
+#
+# This Makefile requires the environment var NGH2SRC
+# pointing to the base directory of nghttp2 source tree.
+#
+
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(SRC)/include \
+ $(NGH2SRC)/lib/ \
+ $(NGH2SRC)/lib/includes \
+ $(SERVER)/mpm/NetWare \
+ $(STDMOD)/ssl \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ -DHAVE_CONFIG_H \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ -L$(OBJDIR) \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = mod_http2
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) HTTP2 Support module (w/ NGHTTP2 Lib)
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = $(NLM_NAME)
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 65536
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If this is specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# Declare all target files (you must add your files here)
+#
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(OBJDIR)/nghttp2.lib \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/h2_alt_svc.o \
+ $(OBJDIR)/h2_bucket_beam.o \
+ $(OBJDIR)/h2_bucket_eos.o \
+ $(OBJDIR)/h2_config.o \
+ $(OBJDIR)/h2_conn.o \
+ $(OBJDIR)/h2_conn_io.o \
+ $(OBJDIR)/h2_ctx.o \
+ $(OBJDIR)/h2_filter.o \
+ $(OBJDIR)/h2_from_h1.o \
+ $(OBJDIR)/h2_h2.o \
+ $(OBJDIR)/h2_mplx.o \
+ $(OBJDIR)/h2_ngn_shed.o \
+ $(OBJDIR)/h2_push.o \
+ $(OBJDIR)/h2_request.o \
+ $(OBJDIR)/h2_headers.o \
+ $(OBJDIR)/h2_session.o \
+ $(OBJDIR)/h2_stream.o \
+ $(OBJDIR)/h2_switch.o \
+ $(OBJDIR)/h2_task.o \
+ $(OBJDIR)/h2_util.o \
+ $(OBJDIR)/h2_workers.o \
+ $(OBJDIR)/mod_http2.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(OBJDIR)/nghttp2.lib \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ Libc \
+ Apache2 \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @libc.imp \
+ @aprlib.imp \
+ @httpd.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ @$(OBJDIR)/mod_http2.imp \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs := $(sort $(patsubst $(NGH2SRC)/lib/%.c,$(OBJDIR)/%.o,$(wildcard $(NGH2SRC)/lib/*.c)))
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(NGH2SRC)/lib/config.h $(TARGET_lib)
+
+nlms :: libs $(OBJDIR)/mod_http2.imp $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+clean ::
+ $(call DEL,$(NGH2SRC)/lib/config.h)
+#
+# Any specialized rules here
+#
+vpath %.c $(NGH2SRC)/lib
+
+$(NGH2SRC)/lib/config.h : NWGNUmod_http2
+ @-$(RM) $@
+ @echo $(DL)GEN $@$(DL)
+ @echo $(DL)/* For NetWare target.$(DL) > $@
+ @echo $(DL)** Do not edit - created by Make!$(DL) >> $@
+ @echo $(DL)*/$(DL) >> $@
+ @echo $(DL)#ifndef NGH2_CONFIG_H$(DL) >> $@
+ @echo $(DL)#define NGH2_CONFIG_H$(DL) >> $@
+ @echo #define HAVE_ARPA_INET_H 1 >> $@
+ @echo #define HAVE_CHOWN 1 >> $@
+ @echo #define HAVE_DECL_STRERROR_R 1 >> $@
+ @echo #define HAVE_DLFCN_H 1 >> $@
+ @echo #define HAVE_DUP2 1 >> $@
+ @echo #define HAVE_FCNTL_H 1 >> $@
+ @echo #define HAVE_GETCWD 1 >> $@
+ @echo #define HAVE_INTTYPES_H 1 >> $@
+ @echo #define HAVE_LIMITS_H 1 >> $@
+ @echo #define HAVE_LOCALTIME_R 1 >> $@
+ @echo #define HAVE_MALLOC 1 >> $@
+ @echo #define HAVE_MEMCHR 1 >> $@
+ @echo #define HAVE_MEMMOVE 1 >> $@
+ @echo #define HAVE_MEMORY_H 1 >> $@
+ @echo #define HAVE_MEMSET 1 >> $@
+ @echo #define HAVE_NETDB_H 1 >> $@
+ @echo #define HAVE_NETINET_IN_H 1 >> $@
+ @echo #define HAVE_PTRDIFF_T 1 >> $@
+ @echo #define HAVE_PWD_H 1 >> $@
+ @echo #define HAVE_SOCKET 1 >> $@
+ @echo #define HAVE_SQRT 1 >> $@
+ @echo #define HAVE_STDDEF_H 1 >> $@
+ @echo #define HAVE_STDINT_H 1 >> $@
+ @echo #define HAVE_STDLIB_H 1 >> $@
+ @echo #define HAVE_STRCHR 1 >> $@
+ @echo #define HAVE_STRDUP 1 >> $@
+ @echo #define HAVE_STRERROR 1 >> $@
+ @echo #define HAVE_STRERROR_R 1 >> $@
+ @echo #define HAVE_STRINGS_H 1 >> $@
+ @echo #define HAVE_STRING_H 1 >> $@
+ @echo #define HAVE_STRSTR 1 >> $@
+ @echo #define HAVE_STRTOL 1 >> $@
+ @echo #define HAVE_STRTOUL 1 >> $@
+ @echo #define HAVE_SYSLOG_H 1 >> $@
+ @echo #define HAVE_SYS_SOCKET_H 1 >> $@
+ @echo #define HAVE_SYS_STAT_H 1 >> $@
+ @echo #define HAVE_SYS_TIME_H 1 >> $@
+ @echo #define HAVE_SYS_TYPES_H 1 >> $@
+ @echo #define HAVE_TIME_H 1 >> $@
+ @echo #define HAVE_UNISTD_H 1 >> $@
+
+ @echo #define SIZEOF_INT_P 4 >> $@
+ @echo #define STDC_HEADERS 1 >> $@
+ @echo #define STRERROR_R_CHAR_P 4 >> $@
+
+# Hint to compiler a function parameter is not used
+ @echo #define _U_ >> $@
+
+ @echo #ifndef __cplusplus >> $@
+ @echo #define inline __inline >> $@
+ @echo #endif >> $@
+
+ @echo $(DL)#endif /* NGH2_CONFIG_H */$(DL) >> $@
+
+#
+# Exports from mod_http2 for mod_proxy_http2
+$(OBJDIR)/mod_http2.imp : NWGNUmod_http2
+ @-$(RM) $@
+ @echo $(DL)GEN $@$(DL)
+ @echo $(DL) (HTTP2)$(DL) > $@
+ @echo $(DL) http2_module,$(DL) >> $@
+ @echo $(DL) nghttp2_is_fatal,$(DL) >> $@
+ @echo $(DL) nghttp2_option_del,$(DL) >> $@
+ @echo $(DL) nghttp2_option_new,$(DL) >> $@
+ @echo $(DL) nghttp2_option_set_no_auto_window_update,$(DL) >> $@
+ @echo $(DL) nghttp2_option_set_peer_max_concurrent_streams,$(DL) >> $@
+ @echo $(DL) nghttp2_session_callbacks_del,$(DL) >> $@
+ @echo $(DL) nghttp2_session_callbacks_new,$(DL) >> $@
+ @echo $(DL) nghttp2_session_callbacks_set_before_frame_send_callback,$(DL) >> $@
+ @echo $(DL) nghttp2_session_callbacks_set_on_data_chunk_recv_callback,$(DL) >> $@
+ @echo $(DL) nghttp2_session_callbacks_set_on_frame_recv_callback,$(DL) >> $@
+ @echo $(DL) nghttp2_session_callbacks_set_on_header_callback,$(DL) >> $@
+ @echo $(DL) nghttp2_session_callbacks_set_on_stream_close_callback,$(DL) >> $@
+ @echo $(DL) nghttp2_session_callbacks_set_send_callback,$(DL) >> $@
+ @echo $(DL) nghttp2_session_client_new2,$(DL) >> $@
+ @echo $(DL) nghttp2_session_consume,$(DL) >> $@
+ @echo $(DL) nghttp2_session_consume_connection,$(DL) >> $@
+ @echo $(DL) nghttp2_session_del,$(DL) >> $@
+ @echo $(DL) nghttp2_session_get_remote_settings,$(DL) >> $@
+ @echo $(DL) nghttp2_session_get_stream_user_data,$(DL) >> $@
+ @echo $(DL) nghttp2_session_mem_recv,$(DL) >> $@
+ @echo $(DL) nghttp2_session_resume_data,$(DL) >> $@
+ @echo $(DL) nghttp2_session_send,$(DL) >> $@
+ @echo $(DL) nghttp2_session_want_read,$(DL) >> $@
+ @echo $(DL) nghttp2_session_want_write,$(DL) >> $@
+ @echo $(DL) nghttp2_strerror,$(DL) >> $@
+ @echo $(DL) nghttp2_submit_goaway,$(DL) >> $@
+ @echo $(DL) nghttp2_submit_ping,$(DL) >> $@
+ @echo $(DL) nghttp2_submit_request,$(DL) >> $@
+ @echo $(DL) nghttp2_submit_rst_stream,$(DL) >> $@
+ @echo $(DL) nghttp2_submit_settings,$(DL) >> $@
+ @echo $(DL) nghttp2_submit_window_update,$(DL) >> $@
+ @echo $(DL) nghttp2_version$(DL) >> $@
+
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/http2/NWGNUproxyht2 b/modules/http2/NWGNUproxyht2
new file mode 100644
index 0000000..ca44ad7
--- /dev/null
+++ b/modules/http2/NWGNUproxyht2
@@ -0,0 +1,288 @@
+#
+# This Makefile requires the environment var NGH2SRC
+# pointing to the base directory of nghttp2 source tree.
+#
+
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(SRC)/include \
+ $(NGH2SRC)/lib/includes \
+ $(STDMOD)/proxy \
+ $(SERVER)/mpm/NetWare \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ -L$(OBJDIR) \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = proxyht2
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) HTTP2 Proxy module
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = $(NLM_NAME)
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 65536
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If this is specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# Declare all target files (you must add your files here)
+#
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_proxy_http2.o \
+ $(OBJDIR)/h2_proxy_session.o \
+ $(OBJDIR)/h2_proxy_util.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ Libc \
+ Apache2 \
+ mod_proxy \
+ mod_http2 \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @libc.imp \
+ @aprlib.imp \
+ @httpd.imp \
+ @$(OBJDIR)/mod_http2.imp \
+ ap_proxy_acquire_connection \
+ ap_proxy_canon_netloc \
+ ap_proxy_canonenc \
+ ap_proxy_connect_backend \
+ ap_proxy_connection_create \
+ ap_proxy_cookie_reverse_map \
+ ap_proxy_determine_connection \
+ ap_proxy_location_reverse_map \
+ ap_proxy_port_of_scheme \
+ ap_proxy_release_connection \
+ ap_proxy_ssl_connection_cleanup \
+ ap_sock_disable_nagle \
+ proxy_hook_canon_handler \
+ proxy_hook_scheme_handler \
+ proxy_module \
+ proxy_run_detach_backend \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ proxy_http2_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs :=
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+clean ::
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/http2/README.h2 b/modules/http2/README.h2
new file mode 100644
index 0000000..f2956f3
--- /dev/null
+++ b/modules/http2/README.h2
@@ -0,0 +1,70 @@
+The http2 module adds support for the HTTP/2 protocol to the server.
+
+Specifically, it supports the protocols "h2" (HTTP2 over TLS) and "h2c"
+(HTTP2 over plain HTTP connections via Upgrade). Additionally it offers
+the "direct" mode for both encrypted and unencrypted connections.
+
+You may enable it for the whole server or specific virtual hosts only.
+
+
+BUILD
+
+If you have libnghttp2 (https://nghttp2.org) installed on your system, simply
+add
+
+ --enable-http2
+
+to your httpd ./configure invocation. Should libnghttp2 reside in a unusual
+location, add
+
+ --with-nghttp2=<path>
+
+to ./configure. <path> is expected to be the installation prefix, so there
+should be a <path>/lib/libnghttp2.*. If your system support pkg-config,
+<path>/lib/pkgconfig/libnghttp2.pc will be inspected.
+
+If you want to link nghttp2 statically into the mod_http2 module, you may
+similarly to mod_ssl add
+
+ --enable-nghttp2-staticlib-deps
+
+For this, the lib directory should only contain the libnghttp2.a, not its
+shared cousins.
+
+
+CONFIGURATION
+
+If mod_http2 is enabled for a site or not depends on the new "Protocols"
+directive. This directive list all protocols enabled for a server or
+virtual host.
+
+If you do not specify "Protocols" all available protocols are enabled. For
+sites using TLS, the protocol supported by mod_http2 is "h2". For cleartext
+http:, the offered protocol is "h2c".
+
+The following is an example of a server that only supports http/1.1 in
+general and offers h2 for a specific virtual host.
+
+ ...
+ Protocols http/1.1
+ <virtualhost *:443>
+ Protocols h2 http/1.1
+ ...
+ </virtualhost>
+
+Please see the documentation of mod_http2 for a complete list and explanation
+of all options.
+
+
+TLS CONFIGURATION
+
+If you want to use HTTP/2 with a browser, most modern browsers will support
+it without further configuration. However, browsers so far only support
+HTTP/2 over TLS and are especially picky about the certificate and
+encryption ciphers used.
+
+Server admins may look for up-to-date information about "modern" TLS
+compatibility under:
+
+ https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility
+
diff --git a/modules/http2/config2.m4 b/modules/http2/config2.m4
new file mode 100644
index 0000000..f89f5ba
--- /dev/null
+++ b/modules/http2/config2.m4
@@ -0,0 +1,238 @@
+dnl Licensed to the Apache Software Foundation (ASF) under one or more
+dnl contributor license agreements. See the NOTICE file distributed with
+dnl this work for additional information regarding copyright ownership.
+dnl The ASF licenses this file to You under the Apache License, Version 2.0
+dnl (the "License"); you may not use this file except in compliance with
+dnl the License. You may obtain a copy of the License at
+dnl
+dnl http://www.apache.org/licenses/LICENSE-2.0
+dnl
+dnl Unless required by applicable law or agreed to in writing, software
+dnl distributed under the License is distributed on an "AS IS" BASIS,
+dnl WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+dnl See the License for the specific language governing permissions and
+dnl limitations under the License.
+
+dnl # start of module specific part
+APACHE_MODPATH_INIT(http2)
+
+dnl # list of module object files
+http2_objs="dnl
+mod_http2.lo dnl
+h2_bucket_beam.lo dnl
+h2_bucket_eos.lo dnl
+h2_c1.lo dnl
+h2_c1_io.lo dnl
+h2_c2.lo dnl
+h2_c2_filter.lo dnl
+h2_config.lo dnl
+h2_conn_ctx.lo dnl
+h2_headers.lo dnl
+h2_mplx.lo dnl
+h2_protocol.lo dnl
+h2_push.lo dnl
+h2_request.lo dnl
+h2_session.lo dnl
+h2_stream.lo dnl
+h2_switch.lo dnl
+h2_util.lo dnl
+h2_workers.lo dnl
+"
+
+dnl
+dnl APACHE_CHECK_NGHTTP2
+dnl
+dnl Configure for nghttp2, giving preference to
+dnl "--with-nghttp2=<path>" if it was specified.
+dnl
+AC_DEFUN([APACHE_CHECK_NGHTTP2],[
+ AC_CACHE_CHECK([for nghttp2], [ac_cv_nghttp2], [
+ dnl initialise the variables we use
+ ac_cv_nghttp2=no
+ ap_nghttp2_found=""
+ ap_nghttp2_base=""
+ ap_nghttp2_libs=""
+
+ dnl Determine the nghttp2 base directory, if any
+ AC_MSG_CHECKING([for user-provided nghttp2 base directory])
+ AC_ARG_WITH(nghttp2, APACHE_HELP_STRING(--with-nghttp2=PATH, nghttp2 installation directory), [
+ dnl If --with-nghttp2 specifies a directory, we use that directory
+ if test "x$withval" != "xyes" -a "x$withval" != "x"; then
+ dnl This ensures $withval is actually a directory and that it is absolute
+ ap_nghttp2_base="`cd $withval ; pwd`"
+ fi
+ ])
+ if test "x$ap_nghttp2_base" = "x"; then
+ AC_MSG_RESULT(none)
+ else
+ AC_MSG_RESULT($ap_nghttp2_base)
+ fi
+
+ dnl Run header and version checks
+ saved_CPPFLAGS="$CPPFLAGS"
+ saved_LIBS="$LIBS"
+ saved_LDFLAGS="$LDFLAGS"
+
+ dnl Before doing anything else, load in pkg-config variables
+ if test -n "$PKGCONFIG"; then
+ saved_PKG_CONFIG_PATH="$PKG_CONFIG_PATH"
+ AC_MSG_CHECKING([for pkg-config along $PKG_CONFIG_PATH])
+ if test "x$ap_nghttp2_base" != "x" ; then
+ if test -f "${ap_nghttp2_base}/lib/pkgconfig/libnghttp2.pc"; then
+ dnl Ensure that the given path is used by pkg-config too, otherwise
+ dnl the system libnghttp2.pc might be picked up instead.
+ PKG_CONFIG_PATH="${ap_nghttp2_base}/lib/pkgconfig${PKG_CONFIG_PATH+:}${PKG_CONFIG_PATH}"
+ export PKG_CONFIG_PATH
+ elif test -f "${ap_nghttp2_base}/lib64/pkgconfig/libnghttp2.pc"; then
+ dnl Ensure that the given path is used by pkg-config too, otherwise
+ dnl the system libnghttp2.pc might be picked up instead.
+ PKG_CONFIG_PATH="${ap_nghttp2_base}/lib64/pkgconfig${PKG_CONFIG_PATH+:}${PKG_CONFIG_PATH}"
+ export PKG_CONFIG_PATH
+ fi
+ fi
+ AC_ARG_ENABLE(nghttp2-staticlib-deps,APACHE_HELP_STRING(--enable-nghttp2-staticlib-deps,[link mod_http2 with dependencies of libnghttp2's static libraries (as indicated by "pkg-config --static"). Must be specified in addition to --enable-http2.]), [
+ if test "$enableval" = "yes"; then
+ PKGCONFIG_LIBOPTS="--static"
+ fi
+ ])
+ ap_nghttp2_libs="`$PKGCONFIG $PKGCONFIG_LIBOPTS --libs-only-l --silence-errors libnghttp2`"
+ if test $? -eq 0; then
+ ap_nghttp2_found="yes"
+ pkglookup="`$PKGCONFIG --cflags-only-I libnghttp2`"
+ APR_ADDTO(CPPFLAGS, [$pkglookup])
+ APR_ADDTO(MOD_CFLAGS, [$pkglookup])
+ pkglookup="`$PKGCONFIG $PKGCONFIG_LIBOPTS --libs-only-L libnghttp2`"
+ APR_ADDTO(LDFLAGS, [$pkglookup])
+ APR_ADDTO(MOD_LDFLAGS, [$pkglookup])
+ pkglookup="`$PKGCONFIG $PKGCONFIG_LIBOPTS --libs-only-other libnghttp2`"
+ APR_ADDTO(LDFLAGS, [$pkglookup])
+ APR_ADDTO(MOD_LDFLAGS, [$pkglookup])
+ fi
+ PKG_CONFIG_PATH="$saved_PKG_CONFIG_PATH"
+ fi
+
+ dnl fall back to the user-supplied directory if not found via pkg-config
+ if test "x$ap_nghttp2_base" != "x" -a "x$ap_nghttp2_found" = "x"; then
+ APR_ADDTO(CPPFLAGS, [-I$ap_nghttp2_base/include])
+ APR_ADDTO(MOD_CFLAGS, [-I$ap_nghttp2_base/include])
+ APR_ADDTO(LDFLAGS, [-L$ap_nghttp2_base/lib])
+ APR_ADDTO(MOD_LDFLAGS, [-L$ap_nghttp2_base/lib])
+ if test "x$ap_platform_runtime_link_flag" != "x"; then
+ APR_ADDTO(LDFLAGS, [$ap_platform_runtime_link_flag$ap_nghttp2_base/lib])
+ APR_ADDTO(MOD_LDFLAGS, [$ap_platform_runtime_link_flag$ap_nghttp2_base/lib])
+ fi
+ fi
+
+ AC_MSG_CHECKING([for nghttp2 version >= 1.2.1])
+ AC_TRY_COMPILE([#include <nghttp2/nghttp2ver.h>],[
+#if !defined(NGHTTP2_VERSION_NUM)
+#error "Missing nghttp2 version"
+#endif
+#if NGHTTP2_VERSION_NUM < 0x010201
+#error "Unsupported nghttp2 version " NGHTTP2_VERSION_TEXT
+#endif],
+ [AC_MSG_RESULT(OK)
+ ac_cv_nghttp2=yes],
+ [AC_MSG_RESULT(FAILED)])
+
+ if test "x$ac_cv_nghttp2" = "xyes"; then
+ ap_nghttp2_libs="${ap_nghttp2_libs:--lnghttp2} `$apr_config --libs`"
+ APR_ADDTO(MOD_LDFLAGS, [$ap_nghttp2_libs])
+ APR_ADDTO(LIBS, [$ap_nghttp2_libs])
+
+ dnl Run library and function checks
+ liberrors=""
+ AC_CHECK_HEADERS([nghttp2/nghttp2.h])
+ AC_CHECK_FUNCS([nghttp2_session_server_new2], [], [liberrors="yes"])
+ if test "x$liberrors" != "x"; then
+ AC_MSG_WARN([nghttp2 library is unusable])
+ fi
+dnl # nghttp2 >= 1.3.0: access to stream weights
+ AC_CHECK_FUNCS([nghttp2_stream_get_weight], [], [liberrors="yes"])
+ if test "x$liberrors" != "x"; then
+ AC_MSG_WARN([nghttp2 version >= 1.3.0 is required])
+ fi
+dnl # nghttp2 >= 1.5.0: changing stream priorities
+ AC_CHECK_FUNCS([nghttp2_session_change_stream_priority],
+ [APR_ADDTO(MOD_CPPFLAGS, ["-DH2_NG2_CHANGE_PRIO"])], [])
+dnl # nghttp2 >= 1.14.0: invalid header callback
+ AC_CHECK_FUNCS([nghttp2_session_callbacks_set_on_invalid_header_callback],
+ [APR_ADDTO(MOD_CPPFLAGS, ["-DH2_NG2_INVALID_HEADER_CB"])], [])
+dnl # nghttp2 >= 1.15.0: get/set stream window sizes
+ AC_CHECK_FUNCS([nghttp2_session_get_stream_local_window_size],
+ [APR_ADDTO(MOD_CPPFLAGS, ["-DH2_NG2_LOCAL_WIN_SIZE"])], [])
+dnl # nghttp2 >= 1.15.0: don't keep info on closed streams
+ AC_CHECK_FUNCS([nghttp2_option_set_no_closed_streams],
+ [APR_ADDTO(MOD_CPPFLAGS, ["-DH2_NG2_NO_CLOSED_STREAMS"])], [])
+dnl # nghttp2 >= 1.50.0: rfc9113 leading/trailing whitespec strictness
+ AC_CHECK_FUNCS([nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation],
+ [APR_ADDTO(MOD_CPPFLAGS, ["-DH2_NG2_RFC9113_STRICTNESS"])], [])
+ else
+ AC_MSG_WARN([nghttp2 version is too old])
+ fi
+
+ dnl restore
+ CPPFLAGS="$saved_CPPFLAGS"
+ LIBS="$saved_LIBS"
+ LDFLAGS="$saved_LDFLAGS"
+ ])
+ if test "x$ac_cv_nghttp2" = "xyes"; then
+ AC_DEFINE(HAVE_NGHTTP2, 1, [Define if nghttp2 is available])
+ fi
+])
+
+
+dnl # hook module into the Autoconf mechanism (--enable-http2)
+APACHE_MODULE(http2, [HTTP/2 protocol handling in addition to HTTP protocol
+handling. Implemented by mod_http2. This module requires a libnghttp2 installation.
+See --with-nghttp2 on how to manage non-standard locations. This module
+is usually linked shared and requires loading. ], $http2_objs, , most, [
+ APACHE_CHECK_OPENSSL
+ if test "$ac_cv_openssl" = "yes" ; then
+ APR_ADDTO(MOD_CPPFLAGS, ["-DH2_OPENSSL"])
+ fi
+
+ APACHE_CHECK_NGHTTP2
+ if test "$ac_cv_nghttp2" = "yes" ; then
+ if test "x$enable_http2" = "xshared"; then
+ # The only symbol which needs to be exported is the module
+ # structure, so ask libtool to hide everything else:
+ APR_ADDTO(MOD_HTTP2_LDADD, [-export-symbols-regex http2_module])
+ fi
+ else
+ enable_http2=no
+ fi
+])
+
+# Ensure that other modules can pick up mod_http2.h
+# icing: hold back for now until it is more stable
+#APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/$modpath_current])
+
+
+
+dnl # list of module object files
+proxy_http2_objs="dnl
+mod_proxy_http2.lo dnl
+h2_proxy_session.lo dnl
+h2_proxy_util.lo dnl
+"
+
+dnl # hook module into the Autoconf mechanism (--enable-proxy_http2)
+APACHE_MODULE(proxy_http2, [HTTP/2 proxy module. This module requires a libnghttp2 installation.
+See --with-nghttp2 on how to manage non-standard locations. Also requires --enable-proxy.], $proxy_http2_objs, , no, [
+ APACHE_CHECK_NGHTTP2
+ if test "$ac_cv_nghttp2" = "yes" ; then
+ if test "x$enable_http2" = "xshared"; then
+ # The only symbol which needs to be exported is the module
+ # structure, so ask libtool to hide everything else:
+ APR_ADDTO(MOD_PROXY_HTTP2_LDADD, [-export-symbols-regex proxy_http2_module])
+ fi
+ else
+ enable_proxy_http2=no
+ fi
+], proxy)
+
+
+dnl # end of module specific part
+APACHE_MODPATH_FINISH
+
diff --git a/modules/http2/h2.h b/modules/http2/h2.h
new file mode 100644
index 0000000..250e726
--- /dev/null
+++ b/modules/http2/h2.h
@@ -0,0 +1,192 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __mod_h2__h2__
+#define __mod_h2__h2__
+
+#include <apr_version.h>
+#include <ap_mmn.h>
+
+struct h2_session;
+struct h2_stream;
+
+/*
+ * When apr pollsets can poll file descriptors (e.g. pipes),
+ * we use it for polling stream input/output.
+ */
+#ifdef H2_NO_PIPES
+#define H2_USE_PIPES 0
+#else
+#define H2_USE_PIPES (APR_FILES_AS_SOCKETS && APR_VERSION_AT_LEAST(1,6,0))
+#endif
+
+/**
+ * The magic PRIamble of RFC 7540 that is always sent when starting
+ * a h2 communication.
+ */
+extern const char *H2_MAGIC_TOKEN;
+
+#define H2_ERR_NO_ERROR (0x00)
+#define H2_ERR_PROTOCOL_ERROR (0x01)
+#define H2_ERR_INTERNAL_ERROR (0x02)
+#define H2_ERR_FLOW_CONTROL_ERROR (0x03)
+#define H2_ERR_SETTINGS_TIMEOUT (0x04)
+#define H2_ERR_STREAM_CLOSED (0x05)
+#define H2_ERR_FRAME_SIZE_ERROR (0x06)
+#define H2_ERR_REFUSED_STREAM (0x07)
+#define H2_ERR_CANCEL (0x08)
+#define H2_ERR_COMPRESSION_ERROR (0x09)
+#define H2_ERR_CONNECT_ERROR (0x0a)
+#define H2_ERR_ENHANCE_YOUR_CALM (0x0b)
+#define H2_ERR_INADEQUATE_SECURITY (0x0c)
+#define H2_ERR_HTTP_1_1_REQUIRED (0x0d)
+
+#define H2_HEADER_METHOD ":method"
+#define H2_HEADER_METHOD_LEN 7
+#define H2_HEADER_SCHEME ":scheme"
+#define H2_HEADER_SCHEME_LEN 7
+#define H2_HEADER_AUTH ":authority"
+#define H2_HEADER_AUTH_LEN 10
+#define H2_HEADER_PATH ":path"
+#define H2_HEADER_PATH_LEN 5
+#define H2_CRLF "\r\n"
+
+/* Size of the frame header itself in HTTP/2 */
+#define H2_FRAME_HDR_LEN 9
+
+/* Max data size to write so it fits inside a TLS record */
+#define H2_DATA_CHUNK_SIZE ((16*1024) - 100 - H2_FRAME_HDR_LEN)
+
+/* Maximum number of padding bytes in a frame, rfc7540 */
+#define H2_MAX_PADLEN 256
+/* Initial default window size, RFC 7540 ch. 6.5.2 */
+#define H2_INITIAL_WINDOW_SIZE ((64*1024)-1)
+
+#define H2_STREAM_CLIENT_INITIATED(id) (id&0x01)
+
+#define H2_ALEN(a) (sizeof(a)/sizeof((a)[0]))
+
+#define H2MAX(x,y) ((x) > (y) ? (x) : (y))
+#define H2MIN(x,y) ((x) < (y) ? (x) : (y))
+
+typedef enum {
+ H2_DEPENDANT_AFTER,
+ H2_DEPENDANT_INTERLEAVED,
+ H2_DEPENDANT_BEFORE,
+} h2_dependency;
+
+typedef struct h2_priority {
+ h2_dependency dependency;
+ int weight;
+} h2_priority;
+
+typedef enum {
+ H2_PUSH_NONE,
+ H2_PUSH_DEFAULT,
+ H2_PUSH_HEAD,
+ H2_PUSH_FAST_LOAD,
+} h2_push_policy;
+
+typedef enum {
+ H2_SESSION_ST_INIT, /* send initial SETTINGS, etc. */
+ H2_SESSION_ST_DONE, /* finished, connection close */
+ H2_SESSION_ST_IDLE, /* nothing to write, expecting data inc */
+ H2_SESSION_ST_BUSY, /* read/write without stop */
+ H2_SESSION_ST_WAIT, /* waiting for c1 incoming + c2s output */
+ H2_SESSION_ST_CLEANUP, /* pool is being cleaned up */
+} h2_session_state;
+
+typedef struct h2_session_props {
+ int accepted_max; /* the highest remote stream id was/will be handled */
+ int completed_max; /* the highest remote stream completed */
+ int emitted_count; /* the number of local streams sent */
+ int emitted_max; /* the highest local stream id sent */
+ int error; /* the last session error encountered */
+ const char *error_msg; /* the short message given on the error */
+ unsigned int accepting : 1; /* if the session is accepting new streams */
+ unsigned int shutdown : 1; /* if the final GOAWAY has been sent */
+} h2_session_props;
+
+typedef enum h2_stream_state_t {
+ H2_SS_IDLE,
+ H2_SS_RSVD_R,
+ H2_SS_RSVD_L,
+ H2_SS_OPEN,
+ H2_SS_CLOSED_R,
+ H2_SS_CLOSED_L,
+ H2_SS_CLOSED,
+ H2_SS_CLEANUP,
+ H2_SS_MAX
+} h2_stream_state_t;
+
+typedef enum {
+ H2_SEV_CLOSED_L,
+ H2_SEV_CLOSED_R,
+ H2_SEV_CANCELLED,
+ H2_SEV_EOS_SENT,
+ H2_SEV_IN_ERROR,
+ H2_SEV_IN_DATA_PENDING,
+ H2_SEV_OUT_C1_BLOCK,
+} h2_stream_event_t;
+
+
+/* h2_request is the transformer of HTTP2 streams into HTTP/1.1 internal
+ * format that will be fed to various httpd input filters to finally
+ * become a request_rec to be handled by soemone.
+ */
+typedef struct h2_request h2_request;
+struct h2_request {
+ const char *method; /* pseudo header values, see ch. 8.1.2.3 */
+ const char *scheme;
+ const char *authority;
+ const char *path;
+ apr_table_t *headers;
+
+ apr_time_t request_time;
+ apr_off_t raw_bytes; /* RAW network bytes that generated this request - if known. */
+ int http_status; /* Store a possible HTTP status code that gets
+ * defined before creating the dummy HTTP/1.1
+ * request e.g. due to an error already
+ * detected.
+ */
+};
+
+/*
+ * A possible HTTP status code is not defined yet. See the http_status field
+ * in struct h2_request above for further explanation.
+ */
+#define H2_HTTP_STATUS_UNSET (0)
+
+typedef apr_status_t h2_io_data_cb(void *ctx, const char *data, apr_off_t len);
+
+typedef int h2_stream_pri_cmp_fn(int stream_id1, int stream_id2, void *session);
+typedef struct h2_stream *h2_stream_get_fn(struct h2_session *session, int stream_id);
+
+/* Note key to attach stream id to conn_rec/request_rec instances */
+#define H2_HDR_CONFORMANCE "http2-hdr-conformance"
+#define H2_HDR_CONFORMANCE_UNSAFE "unsafe"
+#define H2_PUSH_MODE_NOTE "http2-push-mode"
+
+
+#if AP_MODULE_MAGIC_AT_LEAST(20211221, 6)
+#define AP_HAS_RESPONSE_BUCKETS 1
+
+#else /* AP_MODULE_MAGIC_AT_LEAST(20211221, 6) */
+#define AP_HAS_RESPONSE_BUCKETS 0
+
+#endif /* else AP_MODULE_MAGIC_AT_LEAST(20211221, 6) */
+
+#endif /* defined(__mod_h2__h2__) */
diff --git a/modules/http2/h2_bucket_beam.c b/modules/http2/h2_bucket_beam.c
new file mode 100644
index 0000000..cbf7f34
--- /dev/null
+++ b/modules/http2/h2_bucket_beam.c
@@ -0,0 +1,825 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <apr_lib.h>
+#include <apr_atomic.h>
+#include <apr_strings.h>
+#include <apr_time.h>
+#include <apr_buckets.h>
+#include <apr_thread_mutex.h>
+#include <apr_thread_cond.h>
+
+#include <httpd.h>
+#include <http_protocol.h>
+#include <http_log.h>
+
+#include "h2_private.h"
+#include "h2_conn_ctx.h"
+#include "h2_headers.h"
+#include "h2_util.h"
+#include "h2_bucket_beam.h"
+
+
+#define H2_BLIST_INIT(b) APR_RING_INIT(&(b)->list, apr_bucket, link);
+#define H2_BLIST_SENTINEL(b) APR_RING_SENTINEL(&(b)->list, apr_bucket, link)
+#define H2_BLIST_EMPTY(b) APR_RING_EMPTY(&(b)->list, apr_bucket, link)
+#define H2_BLIST_FIRST(b) APR_RING_FIRST(&(b)->list)
+#define H2_BLIST_LAST(b) APR_RING_LAST(&(b)->list)
+#define H2_BLIST_INSERT_HEAD(b, e) do { \
+ apr_bucket *ap__b = (e); \
+ APR_RING_INSERT_HEAD(&(b)->list, ap__b, apr_bucket, link); \
+ } while (0)
+#define H2_BLIST_INSERT_TAIL(b, e) do { \
+ apr_bucket *ap__b = (e); \
+ APR_RING_INSERT_TAIL(&(b)->list, ap__b, apr_bucket, link); \
+ } while (0)
+#define H2_BLIST_CONCAT(a, b) do { \
+ APR_RING_CONCAT(&(a)->list, &(b)->list, apr_bucket, link); \
+ } while (0)
+#define H2_BLIST_PREPEND(a, b) do { \
+ APR_RING_PREPEND(&(a)->list, &(b)->list, apr_bucket, link); \
+ } while (0)
+
+
+static int buffer_is_empty(h2_bucket_beam *beam);
+static apr_off_t get_buffered_data_len(h2_bucket_beam *beam);
+
+static int h2_blist_count(h2_blist *blist)
+{
+ apr_bucket *b;
+ int count = 0;
+
+ for (b = H2_BLIST_FIRST(blist); b != H2_BLIST_SENTINEL(blist);
+ b = APR_BUCKET_NEXT(b)) {
+ ++count;
+ }
+ return count;
+}
+
+#define H2_BEAM_LOG(beam, c, level, rv, msg, bb) \
+ do { \
+ if (APLOG_C_IS_LEVEL((c),(level))) { \
+ char buffer[4 * 1024]; \
+ apr_size_t len, bmax = sizeof(buffer)/sizeof(buffer[0]); \
+ len = bb? h2_util_bb_print(buffer, bmax, "", "", bb) : 0; \
+ ap_log_cerror(APLOG_MARK, (level), rv, (c), \
+ "BEAM[%s,%s%sdata=%ld,buckets(send/consumed)=%d/%d]: %s %s", \
+ (beam)->name, \
+ (beam)->aborted? "aborted," : "", \
+ buffer_is_empty(beam)? "empty," : "", \
+ (long)get_buffered_data_len(beam), \
+ h2_blist_count(&(beam)->buckets_to_send), \
+ h2_blist_count(&(beam)->buckets_consumed), \
+ (msg), len? buffer : ""); \
+ } \
+ } while (0)
+
+
+static int bucket_is_mmap(apr_bucket *b)
+{
+#if APR_HAS_MMAP
+ return APR_BUCKET_IS_MMAP(b);
+#else
+ /* if it is not defined as enabled, it should always be no */
+ return 0;
+#endif
+}
+
+static apr_off_t bucket_mem_used(apr_bucket *b)
+{
+ if (APR_BUCKET_IS_FILE(b) || bucket_is_mmap(b)) {
+ return 0;
+ }
+ else {
+ /* should all have determinate length */
+ return (apr_off_t)b->length;
+ }
+}
+
+static int report_consumption(h2_bucket_beam *beam, int locked)
+{
+ int rv = 0;
+ apr_off_t len = beam->recv_bytes - beam->recv_bytes_reported;
+ h2_beam_io_callback *cb = beam->cons_io_cb;
+
+ if (len > 0) {
+ if (cb) {
+ void *ctx = beam->cons_ctx;
+
+ if (locked) apr_thread_mutex_unlock(beam->lock);
+ cb(ctx, beam, len);
+ if (locked) apr_thread_mutex_lock(beam->lock);
+ rv = 1;
+ }
+ beam->recv_bytes_reported += len;
+ }
+ return rv;
+}
+
+static apr_size_t calc_buffered(h2_bucket_beam *beam)
+{
+ apr_size_t len = 0;
+ apr_bucket *b;
+ for (b = H2_BLIST_FIRST(&beam->buckets_to_send);
+ b != H2_BLIST_SENTINEL(&beam->buckets_to_send);
+ b = APR_BUCKET_NEXT(b)) {
+ if (b->length == ((apr_size_t)-1)) {
+ /* do not count */
+ }
+ else if (APR_BUCKET_IS_FILE(b) || bucket_is_mmap(b)) {
+ /* if unread, has no real mem footprint. */
+ }
+ else {
+ len += b->length;
+ }
+ }
+ return len;
+}
+
+static void purge_consumed_buckets(h2_bucket_beam *beam)
+{
+ apr_bucket *b;
+ /* delete all sender buckets in purge brigade, needs to be called
+ * from sender thread only */
+ while (!H2_BLIST_EMPTY(&beam->buckets_consumed)) {
+ b = H2_BLIST_FIRST(&beam->buckets_consumed);
+ apr_bucket_delete(b);
+ }
+}
+
+static apr_size_t calc_space_left(h2_bucket_beam *beam)
+{
+ if (beam->max_buf_size > 0) {
+ apr_size_t len = calc_buffered(beam);
+ return (beam->max_buf_size > len? (beam->max_buf_size - len) : 0);
+ }
+ return APR_SIZE_MAX;
+}
+
+static int buffer_is_empty(h2_bucket_beam *beam)
+{
+ return H2_BLIST_EMPTY(&beam->buckets_to_send);
+}
+
+static apr_status_t wait_not_empty(h2_bucket_beam *beam, conn_rec *c, apr_read_type_e block)
+{
+ apr_status_t rv = APR_SUCCESS;
+
+ while (buffer_is_empty(beam) && APR_SUCCESS == rv) {
+ if (beam->aborted) {
+ rv = APR_ECONNABORTED;
+ }
+ else if (beam->closed) {
+ rv = APR_EOF;
+ }
+ else if (APR_BLOCK_READ != block) {
+ rv = APR_EAGAIN;
+ }
+ else if (beam->timeout > 0) {
+ H2_BEAM_LOG(beam, c, APLOG_TRACE2, rv, "wait_not_empty, timeout", NULL);
+ rv = apr_thread_cond_timedwait(beam->change, beam->lock, beam->timeout);
+ }
+ else {
+ H2_BEAM_LOG(beam, c, APLOG_TRACE2, rv, "wait_not_empty, forever", NULL);
+ rv = apr_thread_cond_wait(beam->change, beam->lock);
+ }
+ }
+ return rv;
+}
+
+static apr_status_t wait_not_full(h2_bucket_beam *beam, conn_rec *c,
+ apr_read_type_e block,
+ apr_size_t *pspace_left)
+{
+ apr_status_t rv = APR_SUCCESS;
+ apr_size_t left;
+
+ while (0 == (left = calc_space_left(beam)) && APR_SUCCESS == rv) {
+ if (beam->aborted) {
+ rv = APR_ECONNABORTED;
+ }
+ else if (block != APR_BLOCK_READ) {
+ rv = APR_EAGAIN;
+ }
+ else {
+ if (beam->timeout > 0) {
+ H2_BEAM_LOG(beam, c, APLOG_TRACE2, rv, "wait_not_full, timeout", NULL);
+ rv = apr_thread_cond_timedwait(beam->change, beam->lock, beam->timeout);
+ }
+ else {
+ H2_BEAM_LOG(beam, c, APLOG_TRACE2, rv, "wait_not_full, forever", NULL);
+ rv = apr_thread_cond_wait(beam->change, beam->lock);
+ }
+ }
+ }
+ *pspace_left = left;
+ return rv;
+}
+
+static void h2_blist_cleanup(h2_blist *bl)
+{
+ apr_bucket *e;
+
+ while (!H2_BLIST_EMPTY(bl)) {
+ e = H2_BLIST_FIRST(bl);
+ apr_bucket_delete(e);
+ }
+}
+
+static void beam_shutdown(h2_bucket_beam *beam, apr_shutdown_how_e how)
+{
+ if (!beam->pool) {
+ /* pool being cleared already */
+ return;
+ }
+
+ /* shutdown both receiver and sender? */
+ if (how == APR_SHUTDOWN_READWRITE) {
+ beam->cons_io_cb = NULL;
+ beam->recv_cb = NULL;
+ }
+
+ /* shutdown sender (or both)? */
+ if (how != APR_SHUTDOWN_READ) {
+ h2_blist_cleanup(&beam->buckets_to_send);
+ purge_consumed_buckets(beam);
+ }
+}
+
+static apr_status_t beam_cleanup(void *data)
+{
+ h2_bucket_beam *beam = data;
+ beam_shutdown(beam, APR_SHUTDOWN_READWRITE);
+ beam->pool = NULL; /* the pool is clearing now */
+ return APR_SUCCESS;
+}
+
+apr_status_t h2_beam_destroy(h2_bucket_beam *beam, conn_rec *c)
+{
+ if (beam->pool) {
+ H2_BEAM_LOG(beam, c, APLOG_TRACE2, 0, "destroy", NULL);
+ apr_pool_cleanup_run(beam->pool, beam, beam_cleanup);
+ }
+ H2_BEAM_LOG(beam, c, APLOG_TRACE2, 0, "destroyed", NULL);
+ return APR_SUCCESS;
+}
+
+apr_status_t h2_beam_create(h2_bucket_beam **pbeam, conn_rec *from,
+ apr_pool_t *pool, int id, const char *tag,
+ apr_size_t max_buf_size,
+ apr_interval_time_t timeout)
+{
+ h2_bucket_beam *beam;
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(from);
+ apr_status_t rv;
+
+ beam = apr_pcalloc(pool, sizeof(*beam));
+ beam->pool = pool;
+ beam->from = from;
+ beam->id = id;
+ beam->name = apr_psprintf(pool, "%s-%d-%s",
+ conn_ctx->id, id, tag);
+
+ H2_BLIST_INIT(&beam->buckets_to_send);
+ H2_BLIST_INIT(&beam->buckets_consumed);
+ beam->tx_mem_limits = 1;
+ beam->max_buf_size = max_buf_size;
+ beam->timeout = timeout;
+
+ rv = apr_thread_mutex_create(&beam->lock, APR_THREAD_MUTEX_DEFAULT, pool);
+ if (APR_SUCCESS != rv) goto cleanup;
+ rv = apr_thread_cond_create(&beam->change, pool);
+ if (APR_SUCCESS != rv) goto cleanup;
+ apr_pool_pre_cleanup_register(pool, beam, beam_cleanup);
+
+cleanup:
+ H2_BEAM_LOG(beam, from, APLOG_TRACE2, rv, "created", NULL);
+ *pbeam = (APR_SUCCESS == rv)? beam : NULL;
+ return rv;
+}
+
+void h2_beam_buffer_size_set(h2_bucket_beam *beam, apr_size_t buffer_size)
+{
+ apr_thread_mutex_lock(beam->lock);
+ beam->max_buf_size = buffer_size;
+ apr_thread_mutex_unlock(beam->lock);
+}
+
+void h2_beam_set_copy_files(h2_bucket_beam * beam, int enabled)
+{
+ apr_thread_mutex_lock(beam->lock);
+ beam->copy_files = enabled;
+ apr_thread_mutex_unlock(beam->lock);
+}
+
+apr_size_t h2_beam_buffer_size_get(h2_bucket_beam *beam)
+{
+ apr_size_t buffer_size = 0;
+
+ apr_thread_mutex_lock(beam->lock);
+ buffer_size = beam->max_buf_size;
+ apr_thread_mutex_unlock(beam->lock);
+ return buffer_size;
+}
+
+apr_interval_time_t h2_beam_timeout_get(h2_bucket_beam *beam)
+{
+ apr_interval_time_t timeout;
+
+ apr_thread_mutex_lock(beam->lock);
+ timeout = beam->timeout;
+ apr_thread_mutex_unlock(beam->lock);
+ return timeout;
+}
+
+void h2_beam_timeout_set(h2_bucket_beam *beam, apr_interval_time_t timeout)
+{
+ apr_thread_mutex_lock(beam->lock);
+ beam->timeout = timeout;
+ apr_thread_mutex_unlock(beam->lock);
+}
+
+void h2_beam_abort(h2_bucket_beam *beam, conn_rec *c)
+{
+ apr_thread_mutex_lock(beam->lock);
+ beam->aborted = 1;
+ if (c == beam->from) {
+ /* sender aborts */
+ if (beam->send_cb) {
+ beam->send_cb(beam->send_ctx, beam);
+ }
+ if (beam->was_empty_cb && buffer_is_empty(beam)) {
+ beam->was_empty_cb(beam->was_empty_ctx, beam);
+ }
+ /* no more consumption reporting to sender */
+ report_consumption(beam, 1);
+ beam->cons_ctx = NULL;
+
+ beam_shutdown(beam, APR_SHUTDOWN_WRITE);
+ }
+ else {
+ /* receiver aborts */
+ beam_shutdown(beam, APR_SHUTDOWN_READ);
+ }
+ apr_thread_cond_broadcast(beam->change);
+ apr_thread_mutex_unlock(beam->lock);
+}
+
+void h2_beam_close(h2_bucket_beam *beam, conn_rec *c)
+{
+ apr_thread_mutex_lock(beam->lock);
+ if (!beam->closed) {
+ /* should only be called from sender */
+ ap_assert(c == beam->from);
+ beam->closed = 1;
+ if (beam->send_cb) {
+ beam->send_cb(beam->send_ctx, beam);
+ }
+ if (beam->was_empty_cb && buffer_is_empty(beam)) {
+ beam->was_empty_cb(beam->was_empty_ctx, beam);
+ }
+ apr_thread_cond_broadcast(beam->change);
+ }
+ apr_thread_mutex_unlock(beam->lock);
+}
+
+static apr_status_t append_bucket(h2_bucket_beam *beam,
+ apr_bucket_brigade *bb,
+ apr_read_type_e block,
+ apr_size_t *pspace_left,
+ apr_off_t *pwritten)
+{
+ apr_bucket *b;
+ const char *data;
+ apr_size_t len;
+ apr_status_t rv = APR_SUCCESS;
+ int can_beam = 0;
+
+ (void)block;
+ if (beam->aborted) {
+ rv = APR_ECONNABORTED;
+ goto cleanup;
+ }
+
+ ap_assert(beam->pool);
+
+ b = APR_BRIGADE_FIRST(bb);
+ if (APR_BUCKET_IS_METADATA(b)) {
+ APR_BUCKET_REMOVE(b);
+ apr_bucket_setaside(b, beam->pool);
+ H2_BLIST_INSERT_TAIL(&beam->buckets_to_send, b);
+ goto cleanup;
+ }
+ /* non meta bucket */
+
+ /* in case of indeterminate length, we need to read the bucket,
+ * so that it transforms itself into something stable. */
+ if (b->length == ((apr_size_t)-1)) {
+ rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
+ if (rv != APR_SUCCESS) goto cleanup;
+ }
+
+ if (APR_BUCKET_IS_FILE(b)) {
+ /* For file buckets the problem is their internal readpool that
+ * is used on the first read to allocate buffer/mmap.
+ * Since setting aside a file bucket will de-register the
+ * file cleanup function from the previous pool, we need to
+ * call that only from the sender thread.
+ *
+ * Currently, we do not handle file bucket with refcount > 1 as
+ * the beam is then not in complete control of the file's lifetime.
+ * Which results in the bug that a file get closed by the receiver
+ * while the sender or the beam still have buckets using it.
+ *
+ * Additionally, we allow callbacks to prevent beaming file
+ * handles across. The use case for this is to limit the number
+ * of open file handles and rather use a less efficient beam
+ * transport. */
+ apr_bucket_file *bf = b->data;
+ can_beam = !beam->copy_files && (bf->refcount.refcount == 1);
+ }
+ else if (bucket_is_mmap(b)) {
+ can_beam = !beam->copy_files;
+ }
+
+ if (b->length == 0) {
+ apr_bucket_delete(b);
+ rv = APR_SUCCESS;
+ goto cleanup;
+ }
+
+ if (!*pspace_left) {
+ rv = APR_EAGAIN;
+ goto cleanup;
+ }
+
+ /* bucket is accepted and added to beam->buckets_to_send */
+ if (APR_BUCKET_IS_HEAP(b)) {
+ /* For heap buckets, a read from a receiver thread is fine. The
+ * data will be there and live until the bucket itself is
+ * destroyed. */
+ rv = apr_bucket_setaside(b, beam->pool);
+ if (rv != APR_SUCCESS) goto cleanup;
+ }
+ else if (can_beam && (APR_BUCKET_IS_FILE(b) || bucket_is_mmap(b))) {
+ rv = apr_bucket_setaside(b, beam->pool);
+ if (rv != APR_SUCCESS) goto cleanup;
+ }
+ else {
+ /* we know of no special shortcut to transfer the bucket to
+ * another pool without copying. So we make it a heap bucket. */
+ apr_bucket *b2;
+
+ rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
+ if (rv != APR_SUCCESS) goto cleanup;
+ /* this allocates and copies data */
+ b2 = apr_bucket_heap_create(data, len, NULL, bb->bucket_alloc);
+ apr_bucket_delete(b);
+ b = b2;
+ APR_BRIGADE_INSERT_HEAD(bb, b);
+ }
+
+ APR_BUCKET_REMOVE(b);
+ H2_BLIST_INSERT_TAIL(&beam->buckets_to_send, b);
+ *pwritten += (apr_off_t)b->length;
+ if (b->length > *pspace_left) {
+ *pspace_left = 0;
+ }
+ else {
+ *pspace_left -= b->length;
+ }
+
+cleanup:
+ return rv;
+}
+
+apr_status_t h2_beam_send(h2_bucket_beam *beam, conn_rec *from,
+ apr_bucket_brigade *sender_bb,
+ apr_read_type_e block,
+ apr_off_t *pwritten)
+{
+ apr_status_t rv = APR_SUCCESS;
+ apr_size_t space_left = 0;
+ int was_empty;
+
+ ap_assert(beam->pool);
+
+ /* Called from the sender thread to add buckets to the beam */
+ apr_thread_mutex_lock(beam->lock);
+ ap_assert(beam->from == from);
+ ap_assert(sender_bb);
+ H2_BEAM_LOG(beam, from, APLOG_TRACE2, rv, "start send", sender_bb);
+ purge_consumed_buckets(beam);
+ *pwritten = 0;
+ was_empty = buffer_is_empty(beam);
+
+ space_left = calc_space_left(beam);
+ while (!APR_BRIGADE_EMPTY(sender_bb) && APR_SUCCESS == rv) {
+ rv = append_bucket(beam, sender_bb, block, &space_left, pwritten);
+ if (beam->aborted) {
+ goto cleanup;
+ }
+ else if (APR_EAGAIN == rv) {
+ /* bucket was not added, as beam buffer has no space left.
+ * Trigger event callbacks, so receiver can know there is something
+ * to receive before we do a conditional wait. */
+ purge_consumed_buckets(beam);
+ if (beam->send_cb) {
+ beam->send_cb(beam->send_ctx, beam);
+ }
+ if (was_empty && beam->was_empty_cb) {
+ beam->was_empty_cb(beam->was_empty_ctx, beam);
+ }
+ rv = wait_not_full(beam, from, block, &space_left);
+ if (APR_SUCCESS != rv) {
+ break;
+ }
+ was_empty = buffer_is_empty(beam);
+ }
+ }
+
+cleanup:
+ if (beam->send_cb && !buffer_is_empty(beam)) {
+ beam->send_cb(beam->send_ctx, beam);
+ }
+ if (was_empty && beam->was_empty_cb && !buffer_is_empty(beam)) {
+ beam->was_empty_cb(beam->was_empty_ctx, beam);
+ }
+ apr_thread_cond_broadcast(beam->change);
+
+ report_consumption(beam, 1);
+ if (beam->aborted) {
+ rv = APR_ECONNABORTED;
+ }
+ H2_BEAM_LOG(beam, from, APLOG_TRACE2, rv, "end send", sender_bb);
+ apr_thread_mutex_unlock(beam->lock);
+ return rv;
+}
+
+apr_status_t h2_beam_receive(h2_bucket_beam *beam,
+ conn_rec *to,
+ apr_bucket_brigade *bb,
+ apr_read_type_e block,
+ apr_off_t readbytes)
+{
+ apr_bucket *bsender, *brecv, *ng;
+ int transferred = 0;
+ apr_status_t rv = APR_SUCCESS;
+ apr_off_t remain;
+ int consumed_buckets = 0;
+
+ apr_thread_mutex_lock(beam->lock);
+ H2_BEAM_LOG(beam, to, APLOG_TRACE2, 0, "start receive", bb);
+ if (readbytes <= 0) {
+ readbytes = (apr_off_t)APR_SIZE_MAX;
+ }
+ remain = readbytes;
+
+transfer:
+ if (beam->aborted) {
+ beam_shutdown(beam, APR_SHUTDOWN_READ);
+ rv = APR_ECONNABORTED;
+ goto leave;
+ }
+
+ ap_assert(beam->pool);
+
+ /* transfer from our sender brigade, transforming sender buckets to
+ * receiver ones until we have enough */
+ while (remain >= 0 && !H2_BLIST_EMPTY(&beam->buckets_to_send)) {
+
+ brecv = NULL;
+ bsender = H2_BLIST_FIRST(&beam->buckets_to_send);
+ if (bsender->length > 0 && remain <= 0) {
+ break;
+ }
+
+ if (APR_BUCKET_IS_METADATA(bsender)) {
+ /* we need a real copy into the receivers bucket_alloc */
+ if (APR_BUCKET_IS_EOS(bsender)) {
+ /* this closes the beam */
+ beam->closed = 1;
+ brecv = apr_bucket_eos_create(bb->bucket_alloc);
+ }
+ else if (APR_BUCKET_IS_FLUSH(bsender)) {
+ brecv = apr_bucket_flush_create(bb->bucket_alloc);
+ }
+#if AP_HAS_RESPONSE_BUCKETS
+ else if (AP_BUCKET_IS_RESPONSE(bsender)) {
+ brecv = ap_bucket_response_clone(bsender, bb->p, bb->bucket_alloc);
+ }
+ else if (AP_BUCKET_IS_REQUEST(bsender)) {
+ brecv = ap_bucket_request_clone(bsender, bb->p, bb->bucket_alloc);
+ }
+ else if (AP_BUCKET_IS_HEADERS(bsender)) {
+ brecv = ap_bucket_headers_clone(bsender, bb->p, bb->bucket_alloc);
+ }
+#else
+ else if (H2_BUCKET_IS_HEADERS(bsender)) {
+ brecv = h2_bucket_headers_clone(bsender, bb->p, bb->bucket_alloc);
+ }
+#endif /* AP_HAS_RESPONSE_BUCKETS */
+ else if (AP_BUCKET_IS_ERROR(bsender)) {
+ ap_bucket_error *eb = bsender->data;
+ brecv = ap_bucket_error_create(eb->status, eb->data,
+ bb->p, bb->bucket_alloc);
+ }
+ }
+ else if (bsender->length == 0) {
+ /* nop */
+ }
+#if APR_HAS_MMAP
+ else if (APR_BUCKET_IS_MMAP(bsender)) {
+ apr_bucket_mmap *bmmap = bsender->data;
+ apr_mmap_t *mmap;
+ rv = apr_mmap_dup(&mmap, bmmap->mmap, bb->p);
+ if (rv != APR_SUCCESS) goto leave;
+ brecv = apr_bucket_mmap_create(mmap, bsender->start, bsender->length, bb->bucket_alloc);
+ }
+#endif
+ else if (APR_BUCKET_IS_FILE(bsender)) {
+ /* This is setaside into the target brigade pool so that
+ * any read operation messes with that pool and not
+ * the sender one. */
+ apr_bucket_file *f = (apr_bucket_file *)bsender->data;
+ apr_file_t *fd = f->fd;
+ int setaside = (f->readpool != bb->p);
+
+ if (setaside) {
+ rv = apr_file_setaside(&fd, fd, bb->p);
+ if (rv != APR_SUCCESS) goto leave;
+ }
+ ng = apr_brigade_insert_file(bb, fd, bsender->start, (apr_off_t)bsender->length,
+ bb->p);
+#if APR_HAS_MMAP
+ /* disable mmap handling as this leads to segfaults when
+ * the underlying file is changed while memory pointer has
+ * been handed out. See also PR 59348 */
+ apr_bucket_file_enable_mmap(ng, 0);
+#endif
+ remain -= bsender->length;
+ ++transferred;
+ }
+ else {
+ const char *data;
+ apr_size_t dlen;
+ /* we did that when the bucket was added, so this should
+ * give us the same data as before without changing the bucket
+ * or anything (pool) connected to it. */
+ rv = apr_bucket_read(bsender, &data, &dlen, APR_BLOCK_READ);
+ if (rv != APR_SUCCESS) goto leave;
+ rv = apr_brigade_write(bb, NULL, NULL, data, dlen);
+ if (rv != APR_SUCCESS) goto leave;
+
+ remain -= dlen;
+ ++transferred;
+ }
+
+ if (brecv) {
+ /* we have a proxy that we can give the receiver */
+ APR_BRIGADE_INSERT_TAIL(bb, brecv);
+ remain -= brecv->length;
+ ++transferred;
+ }
+ APR_BUCKET_REMOVE(bsender);
+ H2_BLIST_INSERT_TAIL(&beam->buckets_consumed, bsender);
+ beam->recv_bytes += bsender->length;
+ ++consumed_buckets;
+ }
+
+ if (beam->recv_cb && consumed_buckets > 0) {
+ beam->recv_cb(beam->recv_ctx, beam);
+ }
+
+ if (transferred) {
+ apr_thread_cond_broadcast(beam->change);
+ rv = APR_SUCCESS;
+ }
+ else if (beam->aborted) {
+ rv = APR_ECONNABORTED;
+ }
+ else if (beam->closed) {
+ rv = APR_EOF;
+ }
+ else {
+ rv = wait_not_empty(beam, to, block);
+ if (rv != APR_SUCCESS) {
+ goto leave;
+ }
+ goto transfer;
+ }
+
+leave:
+ H2_BEAM_LOG(beam, to, APLOG_TRACE2, rv, "end receive", bb);
+ apr_thread_mutex_unlock(beam->lock);
+ return rv;
+}
+
+void h2_beam_on_consumed(h2_bucket_beam *beam,
+ h2_beam_io_callback *io_cb, void *ctx)
+{
+ apr_thread_mutex_lock(beam->lock);
+ beam->cons_io_cb = io_cb;
+ beam->cons_ctx = ctx;
+ apr_thread_mutex_unlock(beam->lock);
+}
+
+void h2_beam_on_received(h2_bucket_beam *beam,
+ h2_beam_ev_callback *recv_cb, void *ctx)
+{
+ apr_thread_mutex_lock(beam->lock);
+ beam->recv_cb = recv_cb;
+ beam->recv_ctx = ctx;
+ apr_thread_mutex_unlock(beam->lock);
+}
+
+void h2_beam_on_send(h2_bucket_beam *beam,
+ h2_beam_ev_callback *send_cb, void *ctx)
+{
+ apr_thread_mutex_lock(beam->lock);
+ beam->send_cb = send_cb;
+ beam->send_ctx = ctx;
+ apr_thread_mutex_unlock(beam->lock);
+}
+
+void h2_beam_on_was_empty(h2_bucket_beam *beam,
+ h2_beam_ev_callback *was_empty_cb, void *ctx)
+{
+ apr_thread_mutex_lock(beam->lock);
+ beam->was_empty_cb = was_empty_cb;
+ beam->was_empty_ctx = ctx;
+ apr_thread_mutex_unlock(beam->lock);
+}
+
+
+static apr_off_t get_buffered_data_len(h2_bucket_beam *beam)
+{
+ apr_bucket *b;
+ apr_off_t l = 0;
+
+ for (b = H2_BLIST_FIRST(&beam->buckets_to_send);
+ b != H2_BLIST_SENTINEL(&beam->buckets_to_send);
+ b = APR_BUCKET_NEXT(b)) {
+ /* should all have determinate length */
+ l += b->length;
+ }
+ return l;
+}
+
+apr_off_t h2_beam_get_buffered(h2_bucket_beam *beam)
+{
+ apr_off_t l = 0;
+
+ apr_thread_mutex_lock(beam->lock);
+ l = get_buffered_data_len(beam);
+ apr_thread_mutex_unlock(beam->lock);
+ return l;
+}
+
+apr_off_t h2_beam_get_mem_used(h2_bucket_beam *beam)
+{
+ apr_bucket *b;
+ apr_off_t l = 0;
+
+ apr_thread_mutex_lock(beam->lock);
+ for (b = H2_BLIST_FIRST(&beam->buckets_to_send);
+ b != H2_BLIST_SENTINEL(&beam->buckets_to_send);
+ b = APR_BUCKET_NEXT(b)) {
+ l += bucket_mem_used(b);
+ }
+ apr_thread_mutex_unlock(beam->lock);
+ return l;
+}
+
+int h2_beam_empty(h2_bucket_beam *beam)
+{
+ int empty = 1;
+
+ apr_thread_mutex_lock(beam->lock);
+ empty = buffer_is_empty(beam);
+ apr_thread_mutex_unlock(beam->lock);
+ return empty;
+}
+
+int h2_beam_report_consumption(h2_bucket_beam *beam)
+{
+ int rv = 0;
+
+ apr_thread_mutex_lock(beam->lock);
+ rv = report_consumption(beam, 1);
+ apr_thread_mutex_unlock(beam->lock);
+ return rv;
+}
diff --git a/modules/http2/h2_bucket_beam.h b/modules/http2/h2_bucket_beam.h
new file mode 100644
index 0000000..2a9d5f0
--- /dev/null
+++ b/modules/http2/h2_bucket_beam.h
@@ -0,0 +1,248 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef h2_bucket_beam_h
+#define h2_bucket_beam_h
+
+#include "h2_conn_ctx.h"
+
+struct apr_thread_mutex_t;
+struct apr_thread_cond_t;
+
+/**
+ * A h2_bucket_beam solves the task of transferring buckets, esp. their data,
+ * across threads with as little copying as possible.
+ */
+
+typedef struct h2_bucket_beam h2_bucket_beam;
+
+typedef void h2_beam_io_callback(void *ctx, h2_bucket_beam *beam,
+ apr_off_t bytes);
+typedef void h2_beam_ev_callback(void *ctx, h2_bucket_beam *beam);
+
+/**
+ * h2_blist can hold a list of buckets just like apr_bucket_brigade, but
+ * does not to any allocations or related features.
+ */
+typedef struct {
+ APR_RING_HEAD(h2_bucket_list, apr_bucket) list;
+} h2_blist;
+
+struct h2_bucket_beam {
+ int id;
+ const char *name;
+ conn_rec *from;
+ apr_pool_t *pool;
+ h2_blist buckets_to_send;
+ h2_blist buckets_consumed;
+
+ apr_size_t max_buf_size;
+ apr_interval_time_t timeout;
+
+ int aborted;
+ int closed;
+ int tx_mem_limits; /* only memory size counts on transfers */
+ int copy_files;
+
+ struct apr_thread_mutex_t *lock;
+ struct apr_thread_cond_t *change;
+
+ h2_beam_ev_callback *was_empty_cb; /* event: beam changed to non-empty in h2_beam_send() */
+ void *was_empty_ctx;
+ h2_beam_ev_callback *recv_cb; /* event: buckets were transfered in h2_beam_receive() */
+ void *recv_ctx;
+ h2_beam_ev_callback *send_cb; /* event: buckets were added in h2_beam_send() */
+ void *send_ctx;
+
+ apr_off_t recv_bytes; /* amount of bytes transferred in h2_beam_receive() */
+ apr_off_t recv_bytes_reported; /* amount of bytes reported as received via callback */
+ h2_beam_io_callback *cons_io_cb; /* report: recv_bytes deltas for sender */
+ void *cons_ctx;
+};
+
+/**
+ * Creates a new bucket beam for transfer of buckets across threads.
+ *
+ * The pool the beam is created with will be protected by the given
+ * mutex and will be used in multiple threads. It needs a pool allocator
+ * that is only used inside that same mutex.
+ *
+ * @param pbeam will hold the created beam on return
+ * @param c_from connection from which buchets are sent
+ * @param pool pool owning the beam, beam will cleanup when pool released
+ * @param id identifier of the beam
+ * @param tag tag identifying beam for logging
+ * @param buffer_size maximum memory footprint of buckets buffered in beam, or
+ * 0 for no limitation
+ * @param timeout timeout for blocking operations
+ */
+apr_status_t h2_beam_create(h2_bucket_beam **pbeam,
+ conn_rec *from,
+ apr_pool_t *pool,
+ int id, const char *tag,
+ apr_size_t buffer_size,
+ apr_interval_time_t timeout);
+
+/**
+ * Destroys the beam immediately without cleanup.
+ */
+apr_status_t h2_beam_destroy(h2_bucket_beam *beam, conn_rec *c);
+
+/**
+ * Switch copying of file buckets on/off.
+ */
+void h2_beam_set_copy_files(h2_bucket_beam * beam, int enabled);
+
+/**
+ * Send buckets from the given brigade through the beam.
+ * This can block of the amount of bucket data is above the buffer limit.
+ * @param beam the beam to add buckets to
+ * @param from the connection the sender operates on, must be the same as
+ * used to create the beam
+ * @param bb the brigade to take buckets from
+ * @param block if the sending should block when the buffer is full
+ * @param pwritten on return, contains the number of data bytes sent
+ * @return APR_SUCCESS when buckets were added to the beam. This can be
+ * a partial transfer and other buckets may still remain in bb
+ * APR_EAGAIN on non-blocking send when the buffer is full
+ * APR_TIMEUP on blocking semd that time out
+ * APR_ECONNABORTED when beam has been aborted
+ */
+apr_status_t h2_beam_send(h2_bucket_beam *beam, conn_rec *from,
+ apr_bucket_brigade *bb,
+ apr_read_type_e block,
+ apr_off_t *pwritten);
+
+/**
+ * Receive buckets from the beam into the given brigade. The caller is
+ * operating on connection `to`.
+ * @param beam the beam to receive buckets from
+ * @param to the connection the receiver is working with
+ * @param bb the bucket brigade to append to
+ * @param block if the read should block when buckets are unavailable
+ * @param readbytes the amount of data the receiver wants
+ * @return APR_SUCCESS when buckets were appended
+ * APR_EAGAIN on non-blocking read when no buckets are available
+ * APR_TIMEUP on blocking reads that time out
+ * APR_ECONNABORTED when beam has been aborted
+ */
+apr_status_t h2_beam_receive(h2_bucket_beam *beam, conn_rec *to,
+ apr_bucket_brigade *bb,
+ apr_read_type_e block,
+ apr_off_t readbytes);
+
+/**
+ * Determine if beam is empty.
+ */
+int h2_beam_empty(h2_bucket_beam *beam);
+
+/**
+ * Abort the beam, either from receiving or sending side.
+ *
+ * @param beam the beam to abort
+ * @param c the connection the caller is working with
+ */
+void h2_beam_abort(h2_bucket_beam *beam, conn_rec *c);
+
+/**
+ * Close the beam. Make certain an EOS is sent.
+ *
+ * @param beam the beam to abort
+ * @param c the connection the caller is working with
+ */
+void h2_beam_close(h2_bucket_beam *beam, conn_rec *c);
+
+/**
+ * Set/get the timeout for blocking sebd/receive operations.
+ */
+void h2_beam_timeout_set(h2_bucket_beam *beam,
+ apr_interval_time_t timeout);
+
+apr_interval_time_t h2_beam_timeout_get(h2_bucket_beam *beam);
+
+/**
+ * Set/get the maximum buffer size for beam data (memory footprint).
+ */
+void h2_beam_buffer_size_set(h2_bucket_beam *beam,
+ apr_size_t buffer_size);
+apr_size_t h2_beam_buffer_size_get(h2_bucket_beam *beam);
+
+/**
+ * Register a callback to be invoked on the sender side with the
+ * amount of bytes that have been consumed by the receiver, since the
+ * last callback invocation or reset.
+ * @param beam the beam to set the callback on
+ * @param io_cb the callback or NULL, called on sender with bytes consumed
+ * @param ctx the context to use in callback invocation
+ *
+ * Call from the sender side, io callbacks invoked on sender side, ev callback
+ * from any side.
+ */
+void h2_beam_on_consumed(h2_bucket_beam *beam,
+ h2_beam_io_callback *io_cb, void *ctx);
+
+/**
+ * Register a callback to be invoked on the receiver side whenever
+ * buckets have been transfered in a h2_beam_receive() call.
+ * @param beam the beam to set the callback on
+ * @param recv_cb the callback or NULL, called when buckets are received
+ * @param ctx the context to use in callback invocation
+ */
+void h2_beam_on_received(h2_bucket_beam *beam,
+ h2_beam_ev_callback *recv_cb, void *ctx);
+
+/**
+ * Register a call back from the sender side to be invoked when send
+ * has added buckets to the beam.
+ * Unregister by passing a NULL on_send_cb.
+ * @param beam the beam to set the callback on
+ * @param on_send_cb the callback to invoke after buckets were added
+ * @param ctx the context to use in callback invocation
+ */
+void h2_beam_on_send(h2_bucket_beam *beam,
+ h2_beam_ev_callback *on_send_cb, void *ctx);
+
+/**
+ * Register a call back from the sender side to be invoked when send
+ * has added to a previously empty beam.
+ * Unregister by passing a NULL was_empty_cb.
+ * @param beam the beam to set the callback on
+ * @param was_empty_cb the callback to invoke on blocked send
+ * @param ctx the context to use in callback invocation
+ */
+void h2_beam_on_was_empty(h2_bucket_beam *beam,
+ h2_beam_ev_callback *was_empty_cb, void *ctx);
+
+/**
+ * Call any registered consumed handler, if any changes have happened
+ * since the last invocation.
+ * @return !=0 iff a handler has been called
+ *
+ * Needs to be invoked from the sending side.
+ */
+int h2_beam_report_consumption(h2_bucket_beam *beam);
+
+/**
+ * Get the amount of bytes currently buffered in the beam (unread).
+ */
+apr_off_t h2_beam_get_buffered(h2_bucket_beam *beam);
+
+/**
+ * Get the memory used by the buffered buckets, approximately.
+ */
+apr_off_t h2_beam_get_mem_used(h2_bucket_beam *beam);
+
+#endif /* h2_bucket_beam_h */
diff --git a/modules/http2/h2_bucket_eos.c b/modules/http2/h2_bucket_eos.c
new file mode 100644
index 0000000..fa46a30
--- /dev/null
+++ b/modules/http2/h2_bucket_eos.c
@@ -0,0 +1,112 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stddef.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_connection.h>
+#include <http_log.h>
+#include <http_protocol.h>
+
+#include "h2_private.h"
+#include "h2.h"
+#include "h2_mplx.h"
+#include "h2_stream.h"
+#include "h2_bucket_eos.h"
+
+typedef struct {
+ apr_bucket_refcount refcount;
+ h2_stream *stream;
+} h2_bucket_eos;
+
+static apr_status_t bucket_cleanup(void *data)
+{
+ h2_stream **pstream = data;
+
+ if (*pstream) {
+ /* If bucket_destroy is called after us, this prevents
+ * bucket_destroy from trying to destroy the stream again. */
+ *pstream = NULL;
+ }
+ return APR_SUCCESS;
+}
+
+static apr_status_t bucket_read(apr_bucket *b, const char **str,
+ apr_size_t *len, apr_read_type_e block)
+{
+ (void)b;
+ (void)block;
+ *str = NULL;
+ *len = 0;
+ return APR_SUCCESS;
+}
+
+apr_bucket *h2_bucket_eos_make(apr_bucket *b, h2_stream *stream)
+{
+ h2_bucket_eos *h;
+
+ h = apr_bucket_alloc(sizeof(*h), b->list);
+ h->stream = stream;
+
+ b = apr_bucket_shared_make(b, h, 0, 0);
+ b->type = &h2_bucket_type_eos;
+
+ return b;
+}
+
+apr_bucket *h2_bucket_eos_create(apr_bucket_alloc_t *list,
+ h2_stream *stream)
+{
+ apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
+
+ APR_BUCKET_INIT(b);
+ b->free = apr_bucket_free;
+ b->list = list;
+ b = h2_bucket_eos_make(b, stream);
+ if (stream) {
+ h2_bucket_eos *h = b->data;
+ apr_pool_pre_cleanup_register(stream->pool, &h->stream, bucket_cleanup);
+ }
+ return b;
+}
+
+static void bucket_destroy(void *data)
+{
+ h2_bucket_eos *h = data;
+
+ if (apr_bucket_shared_destroy(h)) {
+ h2_stream *stream = h->stream;
+ if (stream && stream->pool) {
+ apr_pool_cleanup_kill(stream->pool, &h->stream, bucket_cleanup);
+ }
+ apr_bucket_free(h);
+ if (stream) {
+ h2_stream_dispatch(stream, H2_SEV_EOS_SENT);
+ }
+ }
+}
+
+const apr_bucket_type_t h2_bucket_type_eos = {
+ "H2EOS", 5, APR_BUCKET_METADATA,
+ bucket_destroy,
+ bucket_read,
+ apr_bucket_setaside_noop,
+ apr_bucket_split_notimpl,
+ apr_bucket_shared_copy
+};
+
diff --git a/modules/http2/h2_bucket_eos.h b/modules/http2/h2_bucket_eos.h
new file mode 100644
index 0000000..04e32e3
--- /dev/null
+++ b/modules/http2/h2_bucket_eos.h
@@ -0,0 +1,32 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mod_http2_h2_bucket_stream_eos_h
+#define mod_http2_h2_bucket_stream_eos_h
+
+struct h2_stream;
+
+/** End Of HTTP/2 STREAM (H2EOS) bucket */
+extern const apr_bucket_type_t h2_bucket_type_eos;
+
+#define H2_BUCKET_IS_H2EOS(e) (e->type == &h2_bucket_type_eos)
+
+apr_bucket *h2_bucket_eos_make(apr_bucket *b, struct h2_stream *stream);
+
+apr_bucket *h2_bucket_eos_create(apr_bucket_alloc_t *list,
+ struct h2_stream *stream);
+
+#endif /* mod_http2_h2_bucket_stream_eos_h */
diff --git a/modules/http2/h2_c1.c b/modules/http2/h2_c1.c
new file mode 100644
index 0000000..afb26fc
--- /dev/null
+++ b/modules/http2/h2_c1.c
@@ -0,0 +1,323 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <apr_strings.h>
+
+#include <ap_mpm.h>
+#include <ap_mmn.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_config.h>
+#include <http_log.h>
+#include <http_connection.h>
+#include <http_protocol.h>
+#include <http_request.h>
+#include <http_ssl.h>
+
+#include <mpm_common.h>
+
+#include "h2_private.h"
+#include "h2.h"
+#include "h2_bucket_beam.h"
+#include "h2_config.h"
+#include "h2_conn_ctx.h"
+#include "h2_mplx.h"
+#include "h2_session.h"
+#include "h2_stream.h"
+#include "h2_protocol.h"
+#include "h2_workers.h"
+#include "h2_c1.h"
+#include "h2_version.h"
+#include "h2_util.h"
+
+static struct h2_workers *workers;
+
+static int async_mpm;
+
+APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_in) *h2_c_logio_add_bytes_in;
+APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_out) *h2_c_logio_add_bytes_out;
+
+apr_status_t h2_c1_child_init(apr_pool_t *pool, server_rec *s)
+{
+ apr_status_t status = APR_SUCCESS;
+ int minw, maxw;
+ apr_time_t idle_limit;
+
+ status = ap_mpm_query(AP_MPMQ_IS_ASYNC, &async_mpm);
+ if (status != APR_SUCCESS) {
+ /* some MPMs do not implemnent this */
+ async_mpm = 0;
+ status = APR_SUCCESS;
+ }
+
+ h2_config_init(pool);
+
+ h2_get_workers_config(s, &minw, &maxw, &idle_limit);
+ workers = h2_workers_create(s, pool, maxw, minw, idle_limit);
+
+ h2_c_logio_add_bytes_in = APR_RETRIEVE_OPTIONAL_FN(ap_logio_add_bytes_in);
+ h2_c_logio_add_bytes_out = APR_RETRIEVE_OPTIONAL_FN(ap_logio_add_bytes_out);
+
+ return h2_mplx_c1_child_init(pool, s);
+}
+
+void h2_c1_child_stopping(apr_pool_t *pool, int graceful)
+{
+ if (workers) {
+ h2_workers_shutdown(workers, graceful);
+ }
+}
+
+
+apr_status_t h2_c1_setup(conn_rec *c, request_rec *r, server_rec *s)
+{
+ h2_session *session;
+ h2_conn_ctx_t *ctx;
+ apr_status_t rv;
+
+ if (!workers) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02911)
+ "workers not initialized");
+ rv = APR_EGENERAL;
+ goto cleanup;
+ }
+
+ rv = h2_session_create(&session, c, r, s, workers);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ ctx = h2_conn_ctx_get(c);
+ ap_assert(ctx);
+ h2_conn_ctx_assign_session(ctx, session);
+ /* remove the input filter of mod_reqtimeout, now that the connection
+ * is established and we have switched to h2. reqtimeout has supervised
+ * possibly configured handshake timeouts and needs to get out of the way
+ * now since the rest of its state handling assumes http/1.x to take place. */
+ ap_remove_input_filter_byhandle(c->input_filters, "reqtimeout");
+
+cleanup:
+ return rv;
+}
+
+apr_status_t h2_c1_run(conn_rec *c)
+{
+ apr_status_t status;
+ int mpm_state = 0;
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
+
+ ap_assert(conn_ctx);
+ ap_assert(conn_ctx->session);
+ do {
+ if (c->cs) {
+ c->cs->sense = CONN_SENSE_DEFAULT;
+ c->cs->state = CONN_STATE_HANDLER;
+ }
+
+ status = h2_session_process(conn_ctx->session, async_mpm);
+
+ if (APR_STATUS_IS_EOF(status)) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c,
+ H2_SSSN_LOG(APLOGNO(03045), conn_ctx->session,
+ "process, closing conn"));
+ c->keepalive = AP_CONN_CLOSE;
+ }
+ else {
+ c->keepalive = AP_CONN_KEEPALIVE;
+ }
+
+ if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state)) {
+ break;
+ }
+ } while (!async_mpm
+ && c->keepalive == AP_CONN_KEEPALIVE
+ && mpm_state != AP_MPMQ_STOPPING);
+
+ if (c->cs) {
+ switch (conn_ctx->session->state) {
+ case H2_SESSION_ST_INIT:
+ case H2_SESSION_ST_IDLE:
+ case H2_SESSION_ST_BUSY:
+ case H2_SESSION_ST_WAIT:
+ c->cs->state = CONN_STATE_WRITE_COMPLETION;
+ if (c->cs && !conn_ctx->session->remote.emitted_count) {
+ /* let the MPM know that we are not done and want
+ * the Timeout behaviour instead of a KeepAliveTimeout
+ * See PR 63534.
+ */
+ c->cs->sense = CONN_SENSE_WANT_READ;
+ }
+ break;
+ case H2_SESSION_ST_CLEANUP:
+ case H2_SESSION_ST_DONE:
+ default:
+ c->cs->state = CONN_STATE_LINGER;
+ break;
+ }
+ }
+
+ return APR_SUCCESS;
+}
+
+apr_status_t h2_c1_pre_close(struct h2_conn_ctx_t *ctx, conn_rec *c)
+{
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
+
+ if (conn_ctx && conn_ctx->session) {
+ apr_status_t status = h2_session_pre_close(conn_ctx->session, async_mpm);
+ return (status == APR_SUCCESS)? DONE : status;
+ }
+ return DONE;
+}
+
+int h2_c1_allows_direct(conn_rec *c)
+{
+ if (!c->master) {
+ int is_tls = ap_ssl_conn_is_ssl(c);
+ const char *needed_protocol = is_tls? "h2" : "h2c";
+ int h2_direct = h2_config_cgeti(c, H2_CONF_DIRECT);
+
+ if (h2_direct < 0) {
+ h2_direct = is_tls? 0 : 1;
+ }
+ return (h2_direct && ap_is_allowed_protocol(c, NULL, NULL, needed_protocol));
+ }
+ return 0;
+}
+
+int h2_c1_can_upgrade(request_rec *r)
+{
+ if (!r->connection->master) {
+ int h2_upgrade = h2_config_rgeti(r, H2_CONF_UPGRADE);
+ return h2_upgrade > 0 || (h2_upgrade < 0 && !ap_ssl_conn_is_ssl(r->connection));
+ }
+ return 0;
+}
+
+static int h2_c1_hook_process_connection(conn_rec* c)
+{
+ apr_status_t status;
+ h2_conn_ctx_t *ctx;
+
+ if (c->master) goto declined;
+ ctx = h2_conn_ctx_get(c);
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, process_conn");
+ if (!ctx && c->keepalives == 0) {
+ const char *proto = ap_get_protocol(c);
+
+ if (APLOGctrace1(c)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, process_conn, "
+ "new connection using protocol '%s', direct=%d, "
+ "tls acceptable=%d", proto, h2_c1_allows_direct(c),
+ h2_protocol_is_acceptable_c1(c, NULL, 1));
+ }
+
+ if (!strcmp(AP_PROTOCOL_HTTP1, proto)
+ && h2_c1_allows_direct(c)
+ && h2_protocol_is_acceptable_c1(c, NULL, 1)) {
+ /* Fresh connection still is on http/1.1 and H2Direct is enabled.
+ * Otherwise connection is in a fully acceptable state.
+ * -> peek at the first 24 incoming bytes
+ */
+ apr_bucket_brigade *temp;
+ char *peek = NULL;
+ apr_size_t peeklen;
+
+ temp = apr_brigade_create(c->pool, c->bucket_alloc);
+ status = ap_get_brigade(c->input_filters, temp,
+ AP_MODE_SPECULATIVE, APR_BLOCK_READ, 24);
+
+ if (status != APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, APLOGNO(03054)
+ "h2_h2, error reading 24 bytes speculative");
+ apr_brigade_destroy(temp);
+ return DECLINED;
+ }
+
+ apr_brigade_pflatten(temp, &peek, &peeklen, c->pool);
+ if ((peeklen >= 24) && !memcmp(H2_MAGIC_TOKEN, peek, 24)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "h2_h2, direct mode detected");
+ ctx = h2_conn_ctx_create_for_c1(c, c->base_server,
+ ap_ssl_conn_is_ssl(c)? "h2" : "h2c");
+ }
+ else if (APLOGctrace2(c)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
+ "h2_h2, not detected in %d bytes(base64): %s",
+ (int)peeklen, h2_util_base64url_encode(peek, peeklen, c->pool));
+ }
+ apr_brigade_destroy(temp);
+ }
+ }
+
+ if (!ctx) goto declined;
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "process_conn");
+ if (!ctx->session) {
+ status = h2_c1_setup(c, NULL, ctx->server? ctx->server : c->base_server);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c, "conn_setup");
+ if (status != APR_SUCCESS) {
+ h2_conn_ctx_detach(c);
+ return !OK;
+ }
+ }
+ h2_c1_run(c);
+ return OK;
+
+declined:
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, declined");
+ return DECLINED;
+}
+
+static int h2_c1_hook_pre_close(conn_rec *c)
+{
+ h2_conn_ctx_t *ctx;
+
+ /* secondary connection? */
+ if (c->master) {
+ return DECLINED;
+ }
+
+ ctx = h2_conn_ctx_get(c);
+ if (ctx) {
+ /* If the session has been closed correctly already, we will not
+ * find a h2_conn_ctx_there. The presence indicates that the session
+ * is still ongoing. */
+ return h2_c1_pre_close(ctx, c);
+ }
+ return DECLINED;
+}
+
+static const char* const mod_ssl[] = { "mod_ssl.c", NULL};
+static const char* const mod_reqtimeout[] = { "mod_ssl.c", "mod_reqtimeout.c", NULL};
+
+void h2_c1_register_hooks(void)
+{
+ /* Our main processing needs to run quite late. Definitely after mod_ssl,
+ * as we need its connection filters, but also before reqtimeout as its
+ * method of timeouts is specific to HTTP/1.1 (as of now).
+ * The core HTTP/1 processing run as REALLY_LAST, so we will have
+ * a chance to take over before it.
+ */
+ ap_hook_process_connection(h2_c1_hook_process_connection,
+ mod_reqtimeout, NULL, APR_HOOK_LAST);
+
+ /* One last chance to properly say goodbye if we have not done so
+ * already. */
+ ap_hook_pre_close_connection(h2_c1_hook_pre_close, NULL, mod_ssl, APR_HOOK_LAST);
+}
+
diff --git a/modules/http2/h2_c1.h b/modules/http2/h2_c1.h
new file mode 100644
index 0000000..41527f6
--- /dev/null
+++ b/modules/http2/h2_c1.h
@@ -0,0 +1,83 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __mod_h2__h2_c1__
+#define __mod_h2__h2_c1__
+
+#include <http_core.h>
+
+struct h2_conn_ctx_t;
+
+extern APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_in) *h2_c_logio_add_bytes_in;
+extern APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_out) *h2_c_logio_add_bytes_out;
+
+/* Initialize this child process for h2 primary connection work,
+ * to be called once during child init before multi processing
+ * starts.
+ */
+apr_status_t h2_c1_child_init(apr_pool_t *pool, server_rec *s);
+
+/**
+ * Setup the primary connection and our context for HTTP/2 processing
+ *
+ * @param c the connection HTTP/2 is starting on
+ * @param r the upgrade request that still awaits an answer, optional
+ * @param s the server selected for this connection (can be != c->base_server)
+ */
+apr_status_t h2_c1_setup(conn_rec *c, request_rec *r, server_rec *s);
+
+/**
+ * Run the HTTP/2 primary connection in synchronous fashion.
+ * Return when the HTTP/2 session is done
+ * and the connection will close or a fatal error occurred.
+ *
+ * @param c the http2 connection to run
+ * @return APR_SUCCESS when session is done.
+ */
+apr_status_t h2_c1_run(conn_rec *c);
+
+/**
+ * The primary connection is about to close. If we have not send a GOAWAY
+ * yet, this is the last chance.
+ */
+apr_status_t h2_c1_pre_close(struct h2_conn_ctx_t *ctx, conn_rec *c);
+
+/**
+ * Check if the connection allows a direct detection of HTTPP/2,
+ * as configurable by the H2Direct directive.
+ * @param c the connection to check on
+ * @return != 0 if direct detection is enabled
+ */
+int h2_c1_allows_direct(conn_rec *c);
+
+/**
+ * Check if the "Upgrade" HTTP/1.1 mode of protocol switching is enabled
+ * for the given request.
+ * @param r the request to check
+ * @return != 0 iff Upgrade switching is enabled
+ */
+int h2_c1_can_upgrade(request_rec *r);
+
+/* Register hooks for h2 handling on primary connections.
+ */
+void h2_c1_register_hooks(void);
+
+/**
+ * Child is about to be stopped, release unused resources
+ */
+void h2_c1_child_stopping(apr_pool_t *pool, int graceful);
+
+#endif /* defined(__mod_h2__h2_c1__) */
diff --git a/modules/http2/h2_c1_io.c b/modules/http2/h2_c1_io.c
new file mode 100644
index 0000000..ade8836
--- /dev/null
+++ b/modules/http2/h2_c1_io.c
@@ -0,0 +1,545 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <apr_strings.h>
+#include <ap_mpm.h>
+#include <mpm_common.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_log.h>
+#include <http_connection.h>
+#include <http_protocol.h>
+#include <http_request.h>
+#include <http_ssl.h>
+
+#include "h2_private.h"
+#include "h2_bucket_eos.h"
+#include "h2_config.h"
+#include "h2_c1.h"
+#include "h2_c1_io.h"
+#include "h2_protocol.h"
+#include "h2_session.h"
+#include "h2_util.h"
+
+#define TLS_DATA_MAX (16*1024)
+
+/* Calculated like this: assuming MTU 1500 bytes
+ * 1500 - 40 (IP) - 20 (TCP) - 40 (TCP options)
+ * - TLS overhead (60-100)
+ * ~= 1300 bytes */
+#define WRITE_SIZE_INITIAL 1300
+
+/* The maximum we'd like to write in one chunk is
+ * the max size of a TLS record. When pushing
+ * many frames down the h2 connection, this might
+ * align differently because of headers and other
+ * frames or simply as not sufficient data is
+ * in a response body.
+ * However keeping frames at or below this limit
+ * should make optimizations at the layer that writes
+ * to TLS easier.
+ */
+#define WRITE_SIZE_MAX (TLS_DATA_MAX)
+
+#define BUF_REMAIN ((apr_size_t)(bmax-off))
+
+static void h2_c1_io_bb_log(conn_rec *c, int stream_id, int level,
+ const char *tag, apr_bucket_brigade *bb)
+{
+ char buffer[16 * 1024];
+ const char *line = "(null)";
+ int bmax = sizeof(buffer)/sizeof(buffer[0]);
+ int off = 0;
+ apr_bucket *b;
+
+ (void)stream_id;
+ if (bb) {
+ memset(buffer, 0, bmax--);
+ for (b = APR_BRIGADE_FIRST(bb);
+ bmax && (b != APR_BRIGADE_SENTINEL(bb));
+ b = APR_BUCKET_NEXT(b)) {
+
+ if (APR_BUCKET_IS_METADATA(b)) {
+ if (APR_BUCKET_IS_EOS(b)) {
+ off += apr_snprintf(buffer+off, BUF_REMAIN, "eos ");
+ }
+ else if (APR_BUCKET_IS_FLUSH(b)) {
+ off += apr_snprintf(buffer+off, BUF_REMAIN, "flush ");
+ }
+ else if (AP_BUCKET_IS_EOR(b)) {
+ off += apr_snprintf(buffer+off, BUF_REMAIN, "eor ");
+ }
+ else if (H2_BUCKET_IS_H2EOS(b)) {
+ off += apr_snprintf(buffer+off, BUF_REMAIN, "h2eos ");
+ }
+ else {
+ off += apr_snprintf(buffer+off, BUF_REMAIN, "meta(unknown) ");
+ }
+ }
+ else {
+ const char *btype = "data";
+ if (APR_BUCKET_IS_FILE(b)) {
+ btype = "file";
+ }
+ else if (APR_BUCKET_IS_PIPE(b)) {
+ btype = "pipe";
+ }
+ else if (APR_BUCKET_IS_SOCKET(b)) {
+ btype = "socket";
+ }
+ else if (APR_BUCKET_IS_HEAP(b)) {
+ btype = "heap";
+ }
+ else if (APR_BUCKET_IS_TRANSIENT(b)) {
+ btype = "transient";
+ }
+ else if (APR_BUCKET_IS_IMMORTAL(b)) {
+ btype = "immortal";
+ }
+#if APR_HAS_MMAP
+ else if (APR_BUCKET_IS_MMAP(b)) {
+ btype = "mmap";
+ }
+#endif
+ else if (APR_BUCKET_IS_POOL(b)) {
+ btype = "pool";
+ }
+
+ off += apr_snprintf(buffer+off, BUF_REMAIN, "%s[%ld] ",
+ btype,
+ (long)(b->length == ((apr_size_t)-1)? -1UL : b->length));
+ }
+ }
+ line = *buffer? buffer : "(empty)";
+ }
+ /* Intentional no APLOGNO */
+ ap_log_cerror(APLOG_MARK, level, 0, c, "h2_session(%ld)-%s: %s",
+ c->id, tag, line);
+
+}
+#define C1_IO_BB_LOG(c, stream_id, level, tag, bb) \
+ if (APLOG_C_IS_LEVEL(c, level)) { \
+ h2_c1_io_bb_log((c), (stream_id), (level), (tag), (bb)); \
+ }
+
+
+apr_status_t h2_c1_io_init(h2_c1_io *io, h2_session *session)
+{
+ conn_rec *c = session->c1;
+
+ io->session = session;
+ io->output = apr_brigade_create(c->pool, c->bucket_alloc);
+ io->is_tls = ap_ssl_conn_is_ssl(session->c1);
+ io->buffer_output = io->is_tls;
+ io->flush_threshold = 4 * (apr_size_t)h2_config_sgeti64(session->s, H2_CONF_STREAM_MAX_MEM);
+
+ if (io->buffer_output) {
+ /* This is what we start with,
+ * see https://issues.apache.org/jira/browse/TS-2503
+ */
+ io->warmup_size = h2_config_sgeti64(session->s, H2_CONF_TLS_WARMUP_SIZE);
+ io->cooldown_usecs = (h2_config_sgeti(session->s, H2_CONF_TLS_COOLDOWN_SECS)
+ * APR_USEC_PER_SEC);
+ io->cooldown_usecs = 0;
+ io->write_size = (io->cooldown_usecs > 0?
+ WRITE_SIZE_INITIAL : WRITE_SIZE_MAX);
+ }
+ else {
+ io->warmup_size = 0;
+ io->cooldown_usecs = 0;
+ io->write_size = 0;
+ }
+
+ if (APLOGctrace1(c)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, c,
+ "h2_c1_io(%ld): init, buffering=%d, warmup_size=%ld, "
+ "cd_secs=%f", c->id, io->buffer_output,
+ (long)io->warmup_size,
+ ((double)io->cooldown_usecs/APR_USEC_PER_SEC));
+ }
+
+ return APR_SUCCESS;
+}
+
+static void append_scratch(h2_c1_io *io)
+{
+ if (io->scratch && io->slen > 0) {
+ apr_bucket *b = apr_bucket_heap_create(io->scratch, io->slen,
+ apr_bucket_free,
+ io->session->c1->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(io->output, b);
+ io->buffered_len += io->slen;
+ io->scratch = NULL;
+ io->slen = io->ssize = 0;
+ }
+}
+
+static apr_size_t assure_scratch_space(h2_c1_io *io) {
+ apr_size_t remain = io->ssize - io->slen;
+ if (io->scratch && remain == 0) {
+ append_scratch(io);
+ }
+ if (!io->scratch) {
+ /* we control the size and it is larger than what buckets usually
+ * allocate. */
+ io->scratch = apr_bucket_alloc(io->write_size, io->session->c1->bucket_alloc);
+ io->ssize = io->write_size;
+ io->slen = 0;
+ remain = io->ssize;
+ }
+ return remain;
+}
+
+static apr_status_t read_to_scratch(h2_c1_io *io, apr_bucket *b)
+{
+ apr_status_t status;
+ const char *data;
+ apr_size_t len;
+
+ if (!b->length) {
+ return APR_SUCCESS;
+ }
+
+ ap_assert(b->length <= (io->ssize - io->slen));
+ if (APR_BUCKET_IS_FILE(b)) {
+ apr_bucket_file *f = (apr_bucket_file *)b->data;
+ apr_file_t *fd = f->fd;
+ apr_off_t offset = b->start;
+
+ len = b->length;
+ /* file buckets will read 8000 byte chunks and split
+ * themselves. However, we do know *exactly* how many
+ * bytes we need where. So we read the file directly to
+ * where we need it.
+ */
+ status = apr_file_seek(fd, APR_SET, &offset);
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+ status = apr_file_read(fd, io->scratch + io->slen, &len);
+ if (status != APR_SUCCESS && status != APR_EOF) {
+ return status;
+ }
+ io->slen += len;
+ }
+ else if (APR_BUCKET_IS_MMAP(b)) {
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, io->session->c1,
+ "h2_c1_io(%ld): seeing mmap bucket of size %ld, scratch remain=%ld",
+ io->session->c1->id, (long)b->length, (long)(io->ssize - io->slen));
+ status = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
+ if (status == APR_SUCCESS) {
+ memcpy(io->scratch+io->slen, data, len);
+ io->slen += len;
+ }
+ }
+ else {
+ status = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
+ if (status == APR_SUCCESS) {
+ memcpy(io->scratch+io->slen, data, len);
+ io->slen += len;
+ }
+ }
+ return status;
+}
+
+static apr_status_t pass_output(h2_c1_io *io, int flush)
+{
+ conn_rec *c = io->session->c1;
+ apr_off_t bblen;
+ apr_status_t rv;
+
+ append_scratch(io);
+ if (flush) {
+ if (!APR_BUCKET_IS_FLUSH(APR_BRIGADE_LAST(io->output))) {
+ apr_bucket *b = apr_bucket_flush_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(io->output, b);
+ }
+ }
+ if (APR_BRIGADE_EMPTY(io->output)) {
+ return APR_SUCCESS;
+ }
+
+ io->unflushed = !APR_BUCKET_IS_FLUSH(APR_BRIGADE_LAST(io->output));
+ apr_brigade_length(io->output, 0, &bblen);
+ C1_IO_BB_LOG(c, 0, APLOG_TRACE2, "out", io->output);
+
+ rv = ap_pass_brigade(c->output_filters, io->output);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ io->buffered_len = 0;
+ io->bytes_written += (apr_size_t)bblen;
+
+ if (io->write_size < WRITE_SIZE_MAX
+ && io->bytes_written >= io->warmup_size) {
+ /* connection is hot, use max size */
+ io->write_size = WRITE_SIZE_MAX;
+ }
+ else if (io->cooldown_usecs > 0
+ && io->write_size > WRITE_SIZE_INITIAL) {
+ apr_time_t now = apr_time_now();
+ if ((now - io->last_write) >= io->cooldown_usecs) {
+ /* long time not written, reset write size */
+ io->write_size = WRITE_SIZE_INITIAL;
+ io->bytes_written = 0;
+ }
+ else {
+ io->last_write = now;
+ }
+ }
+
+cleanup:
+ if (APR_SUCCESS != rv) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c, APLOGNO(03044)
+ "h2_c1_io(%ld): pass_out brigade %ld bytes",
+ c->id, (long)bblen);
+ }
+ apr_brigade_cleanup(io->output);
+ return rv;
+}
+
+int h2_c1_io_needs_flush(h2_c1_io *io)
+{
+ return io->buffered_len >= io->flush_threshold;
+}
+
+int h2_c1_io_pending(h2_c1_io *io)
+{
+ return !APR_BRIGADE_EMPTY(io->output) || (io->scratch && io->slen > 0);
+}
+
+apr_status_t h2_c1_io_pass(h2_c1_io *io)
+{
+ apr_status_t rv = APR_SUCCESS;
+
+ if (h2_c1_io_pending(io)) {
+ rv = pass_output(io, 0);
+ }
+ return rv;
+}
+
+apr_status_t h2_c1_io_assure_flushed(h2_c1_io *io)
+{
+ apr_status_t rv = APR_SUCCESS;
+
+ if (h2_c1_io_pending(io) || io->unflushed) {
+ rv = pass_output(io, 1);
+ if (APR_SUCCESS != rv) goto cleanup;
+ }
+cleanup:
+ return rv;
+}
+
+apr_status_t h2_c1_io_add_data(h2_c1_io *io, const char *data, size_t length)
+{
+ apr_status_t status = APR_SUCCESS;
+ apr_size_t remain;
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, io->session->c1,
+ "h2_c1_io(%ld): adding %ld data bytes",
+ io->session->c1->id, (long)length);
+ if (io->buffer_output) {
+ while (length > 0) {
+ remain = assure_scratch_space(io);
+ if (remain >= length) {
+ memcpy(io->scratch + io->slen, data, length);
+ io->slen += length;
+ length = 0;
+ }
+ else {
+ memcpy(io->scratch + io->slen, data, remain);
+ io->slen += remain;
+ data += remain;
+ length -= remain;
+ }
+ }
+ }
+ else {
+ status = apr_brigade_write(io->output, NULL, NULL, data, length);
+ io->buffered_len += length;
+ }
+ return status;
+}
+
+apr_status_t h2_c1_io_append(h2_c1_io *io, apr_bucket_brigade *bb)
+{
+ apr_bucket *b;
+ apr_status_t rv = APR_SUCCESS;
+
+ while (!APR_BRIGADE_EMPTY(bb)) {
+ b = APR_BRIGADE_FIRST(bb);
+ if (APR_BUCKET_IS_METADATA(b) || APR_BUCKET_IS_MMAP(b)) {
+ /* need to finish any open scratch bucket, as meta data
+ * needs to be forward "in order". */
+ append_scratch(io);
+ APR_BUCKET_REMOVE(b);
+ APR_BRIGADE_INSERT_TAIL(io->output, b);
+ }
+ else if (io->buffer_output) {
+ apr_size_t remain = assure_scratch_space(io);
+ if (b->length > remain) {
+ apr_bucket_split(b, remain);
+ if (io->slen == 0) {
+ /* complete write_size bucket, append unchanged */
+ APR_BUCKET_REMOVE(b);
+ APR_BRIGADE_INSERT_TAIL(io->output, b);
+ io->buffered_len += b->length;
+ continue;
+ }
+ }
+ else {
+ /* bucket fits in remain, copy to scratch */
+ rv = read_to_scratch(io, b);
+ apr_bucket_delete(b);
+ if (APR_SUCCESS != rv) goto cleanup;
+ continue;
+ }
+ }
+ else {
+ /* no buffering, forward buckets setaside on flush */
+ apr_bucket_setaside(b, io->session->c1->pool);
+ APR_BUCKET_REMOVE(b);
+ APR_BRIGADE_INSERT_TAIL(io->output, b);
+ io->buffered_len += b->length;
+ }
+ }
+cleanup:
+ return rv;
+}
+
+static apr_status_t c1_in_feed_bucket(h2_session *session,
+ apr_bucket *b, apr_ssize_t *inout_len)
+{
+ apr_status_t rv = APR_SUCCESS;
+ apr_size_t len;
+ const char *data;
+ ssize_t n;
+
+ rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
+ while (APR_SUCCESS == rv && len > 0) {
+ n = nghttp2_session_mem_recv(session->ngh2, (const uint8_t *)data, len);
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, session->c1,
+ H2_SSSN_MSG(session, "fed %ld bytes to nghttp2, %ld read"),
+ (long)len, (long)n);
+ if (n < 0) {
+ if (nghttp2_is_fatal((int)n)) {
+ h2_session_event(session, H2_SESSION_EV_PROTO_ERROR,
+ (int)n, nghttp2_strerror((int)n));
+ rv = APR_EGENERAL;
+ }
+ }
+ else {
+ *inout_len += n;
+ if ((apr_ssize_t)len <= n) {
+ break;
+ }
+ len -= (apr_size_t)n;
+ data += n;
+ }
+ }
+
+ return rv;
+}
+
+static apr_status_t c1_in_feed_brigade(h2_session *session,
+ apr_bucket_brigade *bb,
+ apr_ssize_t *inout_len)
+{
+ apr_status_t rv = APR_SUCCESS;
+ apr_bucket* b;
+
+ *inout_len = 0;
+ while (!APR_BRIGADE_EMPTY(bb)) {
+ b = APR_BRIGADE_FIRST(bb);
+ if (!APR_BUCKET_IS_METADATA(b)) {
+ rv = c1_in_feed_bucket(session, b, inout_len);
+ if (APR_SUCCESS != rv) goto cleanup;
+ }
+ apr_bucket_delete(b);
+ }
+cleanup:
+ apr_brigade_cleanup(bb);
+ return rv;
+}
+
+static apr_status_t read_and_feed(h2_session *session)
+{
+ apr_ssize_t bytes_fed, bytes_requested;
+ apr_status_t rv;
+
+ bytes_requested = H2MAX(APR_BUCKET_BUFF_SIZE, session->max_stream_mem * 4);
+ rv = ap_get_brigade(session->c1->input_filters,
+ session->bbtmp, AP_MODE_READBYTES,
+ APR_NONBLOCK_READ, bytes_requested);
+
+ if (APR_SUCCESS == rv) {
+ if (!APR_BRIGADE_EMPTY(session->bbtmp)) {
+ h2_util_bb_log(session->c1, session->id, APLOG_TRACE2, "c1 in",
+ session->bbtmp);
+ rv = c1_in_feed_brigade(session, session->bbtmp, &bytes_fed);
+ session->io.bytes_read += bytes_fed;
+ }
+ else {
+ rv = APR_EAGAIN;
+ }
+ }
+ return rv;
+}
+
+apr_status_t h2_c1_read(h2_session *session)
+{
+ apr_status_t rv;
+
+ /* H2_IN filter handles all incoming data against the session.
+ * We just pull at the filter chain to make it happen */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+ H2_SSSN_MSG(session, "session_read start"));
+ rv = read_and_feed(session);
+
+ if (APR_SUCCESS == rv) {
+ h2_session_dispatch_event(session, H2_SESSION_EV_INPUT_PENDING, 0, NULL);
+ }
+ else if (APR_STATUS_IS_EAGAIN(rv)) {
+ /* Signal that we have exhausted the input momentarily.
+ * This might switch to polling the socket */
+ h2_session_dispatch_event(session, H2_SESSION_EV_INPUT_EXHAUSTED, 0, NULL);
+ }
+ else if (APR_SUCCESS != rv) {
+ if (APR_STATUS_IS_ETIMEDOUT(rv)
+ || APR_STATUS_IS_ECONNABORTED(rv)
+ || APR_STATUS_IS_ECONNRESET(rv)
+ || APR_STATUS_IS_EOF(rv)
+ || APR_STATUS_IS_EBADF(rv)) {
+ /* common status for a client that has left */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, session->c1,
+ H2_SSSN_MSG(session, "input gone"));
+ }
+ else {
+ /* uncommon status, log on INFO so that we see this */
+ ap_log_cerror( APLOG_MARK, APLOG_DEBUG, rv, session->c1,
+ H2_SSSN_LOG(APLOGNO(02950), session,
+ "error reading, terminating"));
+ }
+ h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
+ }
+
+ apr_brigade_cleanup(session->bbtmp);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, session->c1,
+ H2_SSSN_MSG(session, "session_read done"));
+ return rv;
+}
diff --git a/modules/http2/h2_c1_io.h b/modules/http2/h2_c1_io.h
new file mode 100644
index 0000000..d891ffb
--- /dev/null
+++ b/modules/http2/h2_c1_io.h
@@ -0,0 +1,100 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __mod_h2__h2_c1_io__
+#define __mod_h2__h2_c1_io__
+
+struct h2_config;
+struct h2_session;
+
+/* h2_io is the basic handler of a httpd connection. It keeps two brigades,
+ * one for input, one for output and works with the installed connection
+ * filters.
+ * The read is done via a callback function, so that input can be processed
+ * directly without copying.
+ */
+typedef struct {
+ struct h2_session *session;
+ apr_bucket_brigade *output;
+
+ int is_tls;
+ int unflushed;
+ apr_time_t cooldown_usecs;
+ apr_int64_t warmup_size;
+
+ apr_size_t write_size;
+ apr_time_t last_write;
+ apr_int64_t bytes_read;
+ apr_int64_t bytes_written;
+
+ int buffer_output;
+ apr_off_t buffered_len;
+ apr_off_t flush_threshold;
+ unsigned int is_flushed : 1;
+
+ char *scratch;
+ apr_size_t ssize;
+ apr_size_t slen;
+} h2_c1_io;
+
+apr_status_t h2_c1_io_init(h2_c1_io *io, struct h2_session *session);
+
+/**
+ * Append data to the buffered output.
+ * @param buf the data to append
+ * @param length the length of the data to append
+ */
+apr_status_t h2_c1_io_add_data(h2_c1_io *io,
+ const char *buf,
+ size_t length);
+
+apr_status_t h2_c1_io_add(h2_c1_io *io, apr_bucket *b);
+
+apr_status_t h2_c1_io_append(h2_c1_io *io, apr_bucket_brigade *bb);
+
+/**
+ * Pass any buffered data on to the connection output filters.
+ * @param io the connection io
+ */
+apr_status_t h2_c1_io_pass(h2_c1_io *io);
+
+/**
+ * if there is any data pendiong or was any data send
+ * since the last FLUSH, send out a FLUSH now.
+ */
+apr_status_t h2_c1_io_assure_flushed(h2_c1_io *io);
+
+/**
+ * Check if the buffered amount of data needs flushing.
+ */
+int h2_c1_io_needs_flush(h2_c1_io *io);
+
+/**
+ * Check if we have output pending.
+ */
+int h2_c1_io_pending(h2_c1_io *io);
+
+struct h2_session;
+
+/**
+ * Read c1 input and pass it on to nghttp2.
+ * @param session the session
+ * @param when_pending != 0 if only pending input (sitting in filters)
+ * needs to be read
+ */
+apr_status_t h2_c1_read(struct h2_session *session);
+
+#endif /* defined(__mod_h2__h2_c1_io__) */
diff --git a/modules/http2/h2_c2.c b/modules/http2/h2_c2.c
new file mode 100644
index 0000000..44a08d0
--- /dev/null
+++ b/modules/http2/h2_c2.c
@@ -0,0 +1,864 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stddef.h>
+
+#include <apr_atomic.h>
+#include <apr_strings.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_config.h>
+#include <http_connection.h>
+#include <http_protocol.h>
+#include <http_request.h>
+#include <http_log.h>
+#include <http_vhost.h>
+#include <util_filter.h>
+#include <ap_mmn.h>
+#include <ap_mpm.h>
+#include <mpm_common.h>
+#include <mod_core.h>
+#include <scoreboard.h>
+
+#include "h2_private.h"
+#include "h2.h"
+#include "h2_bucket_beam.h"
+#include "h2_c1.h"
+#include "h2_config.h"
+#include "h2_conn_ctx.h"
+#include "h2_c2_filter.h"
+#include "h2_protocol.h"
+#include "h2_mplx.h"
+#include "h2_request.h"
+#include "h2_headers.h"
+#include "h2_session.h"
+#include "h2_stream.h"
+#include "h2_c2.h"
+#include "h2_util.h"
+
+
+static module *mpm_module;
+static int mpm_supported = 1;
+static apr_socket_t *dummy_socket;
+
+#if AP_HAS_RESPONSE_BUCKETS
+
+static ap_filter_rec_t *c2_net_in_filter_handle;
+static ap_filter_rec_t *c2_net_out_filter_handle;
+static ap_filter_rec_t *c2_request_in_filter_handle;
+static ap_filter_rec_t *c2_notes_out_filter_handle;
+
+#endif /* AP_HAS_RESPONSE_BUCKETS */
+
+static void check_modules(int force)
+{
+ static int checked = 0;
+ int i;
+
+ if (force || !checked) {
+ for (i = 0; ap_loaded_modules[i]; ++i) {
+ module *m = ap_loaded_modules[i];
+
+ if (!strcmp("event.c", m->name)) {
+ mpm_module = m;
+ break;
+ }
+ else if (!strcmp("motorz.c", m->name)) {
+ mpm_module = m;
+ break;
+ }
+ else if (!strcmp("mpm_netware.c", m->name)) {
+ mpm_module = m;
+ break;
+ }
+ else if (!strcmp("prefork.c", m->name)) {
+ mpm_module = m;
+ /* While http2 can work really well on prefork, it collides
+ * today's use case for prefork: running single-thread app engines
+ * like php. If we restrict h2_workers to 1 per process, php will
+ * work fine, but browser will be limited to 1 active request at a
+ * time. */
+ mpm_supported = 0;
+ break;
+ }
+ else if (!strcmp("simple_api.c", m->name)) {
+ mpm_module = m;
+ mpm_supported = 0;
+ break;
+ }
+ else if (!strcmp("mpm_winnt.c", m->name)) {
+ mpm_module = m;
+ break;
+ }
+ else if (!strcmp("worker.c", m->name)) {
+ mpm_module = m;
+ break;
+ }
+ }
+ checked = 1;
+ }
+}
+
+const char *h2_conn_mpm_name(void)
+{
+ check_modules(0);
+ return mpm_module? mpm_module->name : "unknown";
+}
+
+int h2_mpm_supported(void)
+{
+ check_modules(0);
+ return mpm_supported;
+}
+
+apr_status_t h2_c2_child_init(apr_pool_t *pool, server_rec *s)
+{
+ check_modules(1);
+ return apr_socket_create(&dummy_socket, APR_INET, SOCK_STREAM,
+ APR_PROTO_TCP, pool);
+}
+
+void h2_c2_destroy(conn_rec *c2)
+{
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c2,
+ "h2_c2(%s): destroy", c2->log_id);
+ apr_pool_destroy(c2->pool);
+}
+
+void h2_c2_abort(conn_rec *c2, conn_rec *from)
+{
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c2);
+
+ AP_DEBUG_ASSERT(conn_ctx);
+ AP_DEBUG_ASSERT(conn_ctx->stream_id);
+ if (conn_ctx->beam_in) {
+ h2_beam_abort(conn_ctx->beam_in, from);
+ }
+ if (conn_ctx->beam_out) {
+ h2_beam_abort(conn_ctx->beam_out, from);
+ }
+ c2->aborted = 1;
+}
+
+typedef struct {
+ apr_bucket_brigade *bb; /* c2: data in holding area */
+} h2_c2_fctx_in_t;
+
+static apr_status_t h2_c2_filter_in(ap_filter_t* f,
+ apr_bucket_brigade* bb,
+ ap_input_mode_t mode,
+ apr_read_type_e block,
+ apr_off_t readbytes)
+{
+ h2_conn_ctx_t *conn_ctx;
+ h2_c2_fctx_in_t *fctx = f->ctx;
+ apr_status_t status = APR_SUCCESS;
+ apr_bucket *b;
+ apr_off_t bblen;
+ apr_size_t rmax = (readbytes < APR_INT32_MAX)?
+ (apr_size_t)readbytes : APR_INT32_MAX;
+
+ conn_ctx = h2_conn_ctx_get(f->c);
+ AP_DEBUG_ASSERT(conn_ctx);
+
+ if (mode == AP_MODE_INIT) {
+ return ap_get_brigade(f->c->input_filters, bb, mode, block, readbytes);
+ }
+
+ if (f->c->aborted) {
+ return APR_ECONNABORTED;
+ }
+
+ if (APLOGctrace3(f->c)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, f->c,
+ "h2_c2_in(%s-%d): read, mode=%d, block=%d, readbytes=%ld",
+ conn_ctx->id, conn_ctx->stream_id, mode, block,
+ (long)readbytes);
+ }
+
+ if (!fctx) {
+ fctx = apr_pcalloc(f->c->pool, sizeof(*fctx));
+ f->ctx = fctx;
+ fctx->bb = apr_brigade_create(f->c->pool, f->c->bucket_alloc);
+ if (!conn_ctx->beam_in) {
+ b = apr_bucket_eos_create(f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(fctx->bb, b);
+ }
+ }
+
+ while (APR_BRIGADE_EMPTY(fctx->bb)) {
+ /* Get more input data for our request. */
+ if (APLOGctrace2(f->c)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, f->c,
+ "h2_c2_in(%s-%d): get more data from mplx, block=%d, "
+ "readbytes=%ld",
+ conn_ctx->id, conn_ctx->stream_id, block, (long)readbytes);
+ }
+ if (conn_ctx->beam_in) {
+ if (conn_ctx->pipe_in[H2_PIPE_OUT]) {
+receive:
+ status = h2_beam_receive(conn_ctx->beam_in, f->c, fctx->bb, APR_NONBLOCK_READ,
+ conn_ctx->mplx->stream_max_mem);
+ if (APR_STATUS_IS_EAGAIN(status) && APR_BLOCK_READ == block) {
+ status = h2_util_wait_on_pipe(conn_ctx->pipe_in[H2_PIPE_OUT]);
+ if (APR_SUCCESS == status) {
+ goto receive;
+ }
+ }
+ }
+ else {
+ status = h2_beam_receive(conn_ctx->beam_in, f->c, fctx->bb, block,
+ conn_ctx->mplx->stream_max_mem);
+ }
+ }
+ else {
+ status = APR_EOF;
+ }
+
+ if (APLOGctrace3(f->c)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, f->c,
+ "h2_c2_in(%s-%d): read returned",
+ conn_ctx->id, conn_ctx->stream_id);
+ }
+ if (APR_STATUS_IS_EAGAIN(status)
+ && (mode == AP_MODE_GETLINE || block == APR_BLOCK_READ)) {
+ /* chunked input handling does not seem to like it if we
+ * return with APR_EAGAIN from a GETLINE read...
+ * upload 100k test on test-ser.example.org hangs */
+ status = APR_SUCCESS;
+ }
+ else if (APR_STATUS_IS_EOF(status)) {
+ break;
+ }
+ else if (status != APR_SUCCESS) {
+ conn_ctx->last_err = status;
+ return status;
+ }
+
+ if (APLOGctrace3(f->c)) {
+ h2_util_bb_log(f->c, conn_ctx->stream_id, APLOG_TRACE3,
+ "c2 input recv raw", fctx->bb);
+ }
+ if (h2_c_logio_add_bytes_in) {
+ apr_brigade_length(bb, 0, &bblen);
+ h2_c_logio_add_bytes_in(f->c, bblen);
+ }
+ }
+
+ /* Nothing there, no more data to get. Return. */
+ if (status == APR_EOF && APR_BRIGADE_EMPTY(fctx->bb)) {
+ return status;
+ }
+
+ if (APLOGctrace3(f->c)) {
+ h2_util_bb_log(f->c, conn_ctx->stream_id, APLOG_TRACE3,
+ "c2 input.bb", fctx->bb);
+ }
+
+ if (APR_BRIGADE_EMPTY(fctx->bb)) {
+ if (APLOGctrace3(f->c)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, f->c,
+ "h2_c2_in(%s-%d): no data",
+ conn_ctx->id, conn_ctx->stream_id);
+ }
+ return (block == APR_NONBLOCK_READ)? APR_EAGAIN : APR_EOF;
+ }
+
+ if (mode == AP_MODE_EXHAUSTIVE) {
+ /* return all we have */
+ APR_BRIGADE_CONCAT(bb, fctx->bb);
+ }
+ else if (mode == AP_MODE_READBYTES) {
+ status = h2_brigade_concat_length(bb, fctx->bb, rmax);
+ }
+ else if (mode == AP_MODE_SPECULATIVE) {
+ status = h2_brigade_copy_length(bb, fctx->bb, rmax);
+ }
+ else if (mode == AP_MODE_GETLINE) {
+ /* we are reading a single LF line, e.g. the HTTP headers.
+ * this has the nasty side effect to split the bucket, even
+ * though it ends with CRLF and creates a 0 length bucket */
+ status = apr_brigade_split_line(bb, fctx->bb, block,
+ HUGE_STRING_LEN);
+ if (APLOGctrace3(f->c)) {
+ char buffer[1024];
+ apr_size_t len = sizeof(buffer)-1;
+ apr_brigade_flatten(bb, buffer, &len);
+ buffer[len] = 0;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, f->c,
+ "h2_c2_in(%s-%d): getline: %s",
+ conn_ctx->id, conn_ctx->stream_id, buffer);
+ }
+ }
+ else {
+ /* Hmm, well. There is mode AP_MODE_EATCRLF, but we chose not
+ * to support it. Seems to work. */
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOTIMPL, f->c,
+ APLOGNO(03472)
+ "h2_c2_in(%s-%d), unsupported READ mode %d",
+ conn_ctx->id, conn_ctx->stream_id, mode);
+ status = APR_ENOTIMPL;
+ }
+
+ if (APLOGctrace3(f->c)) {
+ apr_brigade_length(bb, 0, &bblen);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, f->c,
+ "h2_c2_in(%s-%d): %ld data bytes",
+ conn_ctx->id, conn_ctx->stream_id, (long)bblen);
+ }
+ return status;
+}
+
+static apr_status_t beam_out(conn_rec *c2, h2_conn_ctx_t *conn_ctx, apr_bucket_brigade* bb)
+{
+ apr_off_t written, header_len = 0;
+ apr_status_t rv;
+
+ if (h2_c_logio_add_bytes_out) {
+ /* mod_logio wants to report the number of bytes written in a
+ * response, including header and footer fields. Since h2 converts
+ * those during c1 processing into the HPACKed h2 HEADER frames,
+ * we need to give mod_logio something here and count just the
+ * raw lengths of all headers in the buckets. */
+ apr_bucket *b;
+ for (b = APR_BRIGADE_FIRST(bb);
+ b != APR_BRIGADE_SENTINEL(bb);
+ b = APR_BUCKET_NEXT(b)) {
+#if AP_HAS_RESPONSE_BUCKETS
+ if (AP_BUCKET_IS_RESPONSE(b)) {
+ header_len += (apr_off_t)response_length_estimate(b->data);
+ }
+ if (AP_BUCKET_IS_HEADERS(b)) {
+ header_len += (apr_off_t)headers_length_estimate(b->data);
+ }
+#else
+ if (H2_BUCKET_IS_HEADERS(b)) {
+ header_len += (apr_off_t)h2_bucket_headers_headers_length(b);
+ }
+#endif /* AP_HAS_RESPONSE_BUCKETS */
+ }
+ }
+
+ rv = h2_beam_send(conn_ctx->beam_out, c2, bb, APR_BLOCK_READ, &written);
+
+ if (APR_STATUS_IS_EAGAIN(rv)) {
+ rv = APR_SUCCESS;
+ }
+ if (written && h2_c_logio_add_bytes_out) {
+ h2_c_logio_add_bytes_out(c2, written + header_len);
+ }
+ return rv;
+}
+
+static apr_status_t h2_c2_filter_out(ap_filter_t* f, apr_bucket_brigade* bb)
+{
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(f->c);
+ apr_status_t rv;
+
+ ap_assert(conn_ctx);
+#if AP_HAS_RESPONSE_BUCKETS
+ if (!conn_ctx->has_final_response) {
+ apr_bucket *e;
+
+ for (e = APR_BRIGADE_FIRST(bb);
+ e != APR_BRIGADE_SENTINEL(bb);
+ e = APR_BUCKET_NEXT(e))
+ {
+ if (AP_BUCKET_IS_RESPONSE(e)) {
+ ap_bucket_response *resp = e->data;
+ if (resp->status >= 200) {
+ conn_ctx->has_final_response = 1;
+ break;
+ }
+ }
+ if (APR_BUCKET_IS_EOS(e)) {
+ break;
+ }
+ }
+ }
+#endif /* AP_HAS_RESPONSE_BUCKETS */
+ rv = beam_out(f->c, conn_ctx, bb);
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, f->c,
+ "h2_c2(%s-%d): output leave",
+ conn_ctx->id, conn_ctx->stream_id);
+ if (APR_SUCCESS != rv) {
+ h2_c2_abort(f->c, f->c);
+ }
+ return rv;
+}
+
+static void check_push(request_rec *r, const char *tag)
+{
+ apr_array_header_t *push_list = h2_config_push_list(r);
+
+ if (!r->expecting_100 && push_list && push_list->nelts > 0) {
+ int i, old_status;
+ const char *old_line;
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "%s, early announcing %d resources for push",
+ tag, push_list->nelts);
+ for (i = 0; i < push_list->nelts; ++i) {
+ h2_push_res *push = &APR_ARRAY_IDX(push_list, i, h2_push_res);
+ apr_table_add(r->headers_out, "Link",
+ apr_psprintf(r->pool, "<%s>; rel=preload%s",
+ push->uri_ref, push->critical? "; critical" : ""));
+ }
+ old_status = r->status;
+ old_line = r->status_line;
+ r->status = 103;
+ r->status_line = "103 Early Hints";
+ ap_send_interim_response(r, 1);
+ r->status = old_status;
+ r->status_line = old_line;
+ }
+}
+
+static int c2_hook_fixups(request_rec *r)
+{
+ conn_rec *c2 = r->connection;
+ h2_conn_ctx_t *conn_ctx;
+
+ if (!c2->master || !(conn_ctx = h2_conn_ctx_get(c2)) || !conn_ctx->stream_id) {
+ return DECLINED;
+ }
+
+ check_push(r, "late_fixup");
+
+ return DECLINED;
+}
+
+#if AP_HAS_RESPONSE_BUCKETS
+
+static void c2_pre_read_request(request_rec *r, conn_rec *c2)
+{
+ h2_conn_ctx_t *conn_ctx;
+
+ if (!c2->master || !(conn_ctx = h2_conn_ctx_get(c2)) || !conn_ctx->stream_id) {
+ return;
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
+ "h2_c2(%s-%d): adding request filters",
+ conn_ctx->id, conn_ctx->stream_id);
+ ap_add_input_filter_handle(c2_request_in_filter_handle, NULL, r, r->connection);
+ ap_add_output_filter_handle(c2_notes_out_filter_handle, NULL, r, r->connection);
+}
+
+static int c2_post_read_request(request_rec *r)
+{
+ h2_conn_ctx_t *conn_ctx;
+ conn_rec *c2 = r->connection;
+ apr_time_t timeout;
+
+ if (!c2->master || !(conn_ctx = h2_conn_ctx_get(c2)) || !conn_ctx->stream_id) {
+ return DECLINED;
+ }
+ /* Now that the request_rec is fully initialized, set relevant params */
+ conn_ctx->server = r->server;
+ timeout = h2_config_geti64(r, r->server, H2_CONF_STREAM_TIMEOUT);
+ if (timeout <= 0) {
+ timeout = r->server->timeout;
+ }
+ h2_conn_ctx_set_timeout(conn_ctx, timeout);
+ /* We only handle this one request on the connection and tell everyone
+ * that there is no need to keep it "clean" if something fails. Also,
+ * this prevents mod_reqtimeout from doing funny business with monitoring
+ * keepalive timeouts.
+ */
+ r->connection->keepalive = AP_CONN_CLOSE;
+
+ if (conn_ctx->beam_in && !apr_table_get(r->headers_in, "Content-Length")) {
+ r->body_indeterminate = 1;
+ }
+
+ if (h2_config_sgeti(conn_ctx->server, H2_CONF_COPY_FILES)) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "h2_mplx(%s-%d): copy_files in output",
+ conn_ctx->id, conn_ctx->stream_id);
+ h2_beam_set_copy_files(conn_ctx->beam_out, 1);
+ }
+
+ /* Add the raw bytes of the request (e.g. header frame lengths to
+ * the logio for this request. */
+ if (conn_ctx->request->raw_bytes && h2_c_logio_add_bytes_in) {
+ h2_c_logio_add_bytes_in(c2, conn_ctx->request->raw_bytes);
+ }
+ return OK;
+}
+
+static int c2_hook_pre_connection(conn_rec *c2, void *csd)
+{
+ h2_conn_ctx_t *conn_ctx;
+
+ if (!c2->master || !(conn_ctx = h2_conn_ctx_get(c2)) || !conn_ctx->stream_id) {
+ return DECLINED;
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c2,
+ "h2_c2(%s-%d), adding filters",
+ conn_ctx->id, conn_ctx->stream_id);
+ ap_add_input_filter_handle(c2_net_in_filter_handle, NULL, NULL, c2);
+ ap_add_output_filter_handle(c2_net_out_filter_handle, NULL, NULL, c2);
+ if (c2->keepalives == 0) {
+ /* Simulate that we had already a request on this connection. Some
+ * hooks trigger special behaviour when keepalives is 0.
+ * (Not necessarily in pre_connection, but later. Set it here, so it
+ * is in place.) */
+ c2->keepalives = 1;
+ /* We signal that this connection will be closed after the request.
+ * Which is true in that sense that we throw away all traffic data
+ * on this c2 connection after each requests. Although we might
+ * reuse internal structures like memory pools.
+ * The wanted effect of this is that httpd does not try to clean up
+ * any dangling data on this connection when a request is done. Which
+ * is unnecessary on a h2 stream.
+ */
+ c2->keepalive = AP_CONN_CLOSE;
+ }
+ return OK;
+}
+
+void h2_c2_register_hooks(void)
+{
+ /* When the connection processing actually starts, we might
+ * take over, if the connection is for a h2 stream.
+ */
+ ap_hook_pre_connection(c2_hook_pre_connection,
+ NULL, NULL, APR_HOOK_MIDDLE);
+
+ /* We need to manipulate the standard HTTP/1.1 protocol filters and
+ * install our own. This needs to be done very early. */
+ ap_hook_pre_read_request(c2_pre_read_request, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_post_read_request(c2_post_read_request, NULL, NULL, APR_HOOK_REALLY_FIRST);
+ ap_hook_fixups(c2_hook_fixups, NULL, NULL, APR_HOOK_LAST);
+
+ c2_net_in_filter_handle =
+ ap_register_input_filter("H2_C2_NET_IN", h2_c2_filter_in,
+ NULL, AP_FTYPE_NETWORK);
+ c2_net_out_filter_handle =
+ ap_register_output_filter("H2_C2_NET_OUT", h2_c2_filter_out,
+ NULL, AP_FTYPE_NETWORK);
+ c2_request_in_filter_handle =
+ ap_register_input_filter("H2_C2_REQUEST_IN", h2_c2_filter_request_in,
+ NULL, AP_FTYPE_PROTOCOL);
+ c2_notes_out_filter_handle =
+ ap_register_output_filter("H2_C2_NOTES_OUT", h2_c2_filter_notes_out,
+ NULL, AP_FTYPE_PROTOCOL);
+}
+
+#else /* AP_HAS_RESPONSE_BUCKETS */
+
+static apr_status_t c2_run_pre_connection(conn_rec *c2, apr_socket_t *csd)
+{
+ if (c2->keepalives == 0) {
+ /* Simulate that we had already a request on this connection. Some
+ * hooks trigger special behaviour when keepalives is 0.
+ * (Not necessarily in pre_connection, but later. Set it here, so it
+ * is in place.) */
+ c2->keepalives = 1;
+ /* We signal that this connection will be closed after the request.
+ * Which is true in that sense that we throw away all traffic data
+ * on this c2 connection after each requests. Although we might
+ * reuse internal structures like memory pools.
+ * The wanted effect of this is that httpd does not try to clean up
+ * any dangling data on this connection when a request is done. Which
+ * is unnecessary on a h2 stream.
+ */
+ c2->keepalive = AP_CONN_CLOSE;
+ return ap_run_pre_connection(c2, csd);
+ }
+ ap_assert(c2->output_filters);
+ return APR_SUCCESS;
+}
+
+apr_status_t h2_c2_process(conn_rec *c2, apr_thread_t *thread, int worker_id)
+{
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c2);
+
+ ap_assert(conn_ctx);
+ ap_assert(conn_ctx->mplx);
+
+ /* See the discussion at <https://github.com/icing/mod_h2/issues/195>
+ *
+ * Each conn_rec->id is supposed to be unique at a point in time. Since
+ * some modules (and maybe external code) uses this id as an identifier
+ * for the request_rec they handle, it needs to be unique for secondary
+ * connections also.
+ *
+ * The MPM module assigns the connection ids and mod_unique_id is using
+ * that one to generate identifier for requests. While the implementation
+ * works for HTTP/1.x, the parallel execution of several requests per
+ * connection will generate duplicate identifiers on load.
+ *
+ * The original implementation for secondary connection identifiers used
+ * to shift the master connection id up and assign the stream id to the
+ * lower bits. This was cramped on 32 bit systems, but on 64bit there was
+ * enough space.
+ *
+ * As issue 195 showed, mod_unique_id only uses the lower 32 bit of the
+ * connection id, even on 64bit systems. Therefore collisions in request ids.
+ *
+ * The way master connection ids are generated, there is some space "at the
+ * top" of the lower 32 bits on allmost all systems. If you have a setup
+ * with 64k threads per child and 255 child processes, you live on the edge.
+ *
+ * The new implementation shifts 8 bits and XORs in the worker
+ * id. This will experience collisions with > 256 h2 workers and heavy
+ * load still. There seems to be no way to solve this in all possible
+ * configurations by mod_h2 alone.
+ */
+ c2->id = (c2->master->id << 8)^worker_id;
+
+ if (!conn_ctx->pre_conn_done) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c2,
+ "h2_c2(%s-%d), adding filters",
+ conn_ctx->id, conn_ctx->stream_id);
+ ap_add_input_filter("H2_C2_NET_IN", NULL, NULL, c2);
+ ap_add_output_filter("H2_C2_NET_CATCH_H1", NULL, NULL, c2);
+ ap_add_output_filter("H2_C2_NET_OUT", NULL, NULL, c2);
+
+ c2_run_pre_connection(c2, ap_get_conn_socket(c2));
+ conn_ctx->pre_conn_done = 1;
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2,
+ "h2_c2(%s-%d): process connection",
+ conn_ctx->id, conn_ctx->stream_id);
+
+ c2->current_thread = thread;
+ ap_run_process_connection(c2);
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2,
+ "h2_c2(%s-%d): processing done",
+ conn_ctx->id, conn_ctx->stream_id);
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t c2_process(h2_conn_ctx_t *conn_ctx, conn_rec *c)
+{
+ const h2_request *req = conn_ctx->request;
+ conn_state_t *cs = c->cs;
+ request_rec *r;
+ const char *tenc;
+ apr_time_t timeout;
+
+ r = h2_create_request_rec(conn_ctx->request, c, conn_ctx->beam_in == NULL);
+ if (!r) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "h2_c2(%s-%d): create request_rec failed, r=NULL",
+ conn_ctx->id, conn_ctx->stream_id);
+ goto cleanup;
+ }
+ if (r->status != HTTP_OK) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "h2_c2(%s-%d): create request_rec failed, r->status=%d",
+ conn_ctx->id, conn_ctx->stream_id, r->status);
+ goto cleanup;
+ }
+
+ tenc = apr_table_get(r->headers_in, "Transfer-Encoding");
+ conn_ctx->input_chunked = tenc && ap_is_chunked(r->pool, tenc);
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "h2_c2(%s-%d): created request_rec for %s",
+ conn_ctx->id, conn_ctx->stream_id, r->the_request);
+ conn_ctx->server = r->server;
+ timeout = h2_config_geti64(r, r->server, H2_CONF_STREAM_TIMEOUT);
+ if (timeout <= 0) {
+ timeout = r->server->timeout;
+ }
+ h2_conn_ctx_set_timeout(conn_ctx, timeout);
+
+ if (h2_config_sgeti(conn_ctx->server, H2_CONF_COPY_FILES)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "h2_mplx(%s-%d): copy_files in output",
+ conn_ctx->id, conn_ctx->stream_id);
+ h2_beam_set_copy_files(conn_ctx->beam_out, 1);
+ }
+
+ ap_update_child_status(c->sbh, SERVER_BUSY_WRITE, r);
+ if (cs) {
+ cs->state = CONN_STATE_HANDLER;
+ }
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "h2_c2(%s-%d): start process_request",
+ conn_ctx->id, conn_ctx->stream_id);
+
+ /* Add the raw bytes of the request (e.g. header frame lengths to
+ * the logio for this request. */
+ if (req->raw_bytes && h2_c_logio_add_bytes_in) {
+ h2_c_logio_add_bytes_in(c, req->raw_bytes);
+ }
+
+ ap_process_request(r);
+ /* After the call to ap_process_request, the
+ * request pool may have been deleted. */
+ r = NULL;
+ if (conn_ctx->beam_out) {
+ h2_beam_close(conn_ctx->beam_out, c);
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "h2_c2(%s-%d): process_request done",
+ conn_ctx->id, conn_ctx->stream_id);
+ if (cs)
+ cs->state = CONN_STATE_WRITE_COMPLETION;
+
+cleanup:
+ return APR_SUCCESS;
+}
+
+conn_rec *h2_c2_create(conn_rec *c1, apr_pool_t *parent,
+ apr_bucket_alloc_t *buckt_alloc)
+{
+ apr_pool_t *pool;
+ conn_rec *c2;
+ void *cfg;
+
+ ap_assert(c1);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c1,
+ "h2_c2: create for c1(%ld)", c1->id);
+
+ /* We create a pool with its own allocator to be used for
+ * processing a request. This is the only way to have the processing
+ * independent of its parent pool in the sense that it can work in
+ * another thread.
+ */
+ apr_pool_create(&pool, parent);
+ apr_pool_tag(pool, "h2_c2_conn");
+
+ c2 = (conn_rec *) apr_palloc(pool, sizeof(conn_rec));
+ memcpy(c2, c1, sizeof(conn_rec));
+
+ c2->master = c1;
+ c2->pool = pool;
+ c2->conn_config = ap_create_conn_config(pool);
+ c2->notes = apr_table_make(pool, 5);
+ c2->input_filters = NULL;
+ c2->output_filters = NULL;
+ c2->keepalives = 0;
+#if AP_MODULE_MAGIC_AT_LEAST(20180903, 1)
+ c2->filter_conn_ctx = NULL;
+#endif
+ c2->bucket_alloc = apr_bucket_alloc_create(pool);
+#if !AP_MODULE_MAGIC_AT_LEAST(20180720, 1)
+ c2->data_in_input_filters = 0;
+ c2->data_in_output_filters = 0;
+#endif
+ /* prevent mpm_event from making wrong assumptions about this connection,
+ * like e.g. using its socket for an async read check. */
+ c2->clogging_input_filters = 1;
+ c2->log = NULL;
+ c2->aborted = 0;
+ /* We cannot install the master connection socket on the secondary, as
+ * modules mess with timeouts/blocking of the socket, with
+ * unwanted side effects to the master connection processing.
+ * Fortunately, since we never use the secondary socket, we can just install
+ * a single, process-wide dummy and everyone is happy.
+ */
+ ap_set_module_config(c2->conn_config, &core_module, dummy_socket);
+ /* TODO: these should be unique to this thread */
+ c2->sbh = NULL; /*c1->sbh;*/
+ /* TODO: not all mpm modules have learned about secondary connections yet.
+ * copy their config from master to secondary.
+ */
+ if (mpm_module) {
+ cfg = ap_get_module_config(c1->conn_config, mpm_module);
+ ap_set_module_config(c2->conn_config, mpm_module, cfg);
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c2,
+ "h2_c2(%s): created", c2->log_id);
+ return c2;
+}
+
+static int h2_c2_hook_post_read_request(request_rec *r)
+{
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(r->connection);
+
+ if (conn_ctx && conn_ctx->stream_id) {
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
+ "h2_c2(%s-%d): adding request filters",
+ conn_ctx->id, conn_ctx->stream_id);
+
+ /* setup the correct filters to process the request for h2 */
+ ap_add_input_filter("H2_C2_REQUEST_IN", NULL, r, r->connection);
+
+ /* replace the core http filter that formats response headers
+ * in HTTP/1 with our own that collects status and headers */
+ ap_remove_output_filter_byhandle(r->output_filters, "HTTP_HEADER");
+
+ ap_add_output_filter("H2_C2_RESPONSE_OUT", NULL, r, r->connection);
+ ap_add_output_filter("H2_C2_TRAILERS_OUT", NULL, r, r->connection);
+ }
+ return DECLINED;
+}
+
+static int h2_c2_hook_process(conn_rec* c)
+{
+ h2_conn_ctx_t *ctx;
+
+ if (!c->master) {
+ return DECLINED;
+ }
+
+ ctx = h2_conn_ctx_get(c);
+ if (ctx->stream_id) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "h2_h2, processing request directly");
+ c2_process(ctx, c);
+ return DONE;
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "secondary_conn(%ld): no h2 stream assing?", c->id);
+ }
+ return DECLINED;
+}
+
+void h2_c2_register_hooks(void)
+{
+ /* When the connection processing actually starts, we might
+ * take over, if the connection is for a h2 stream.
+ */
+ ap_hook_process_connection(h2_c2_hook_process,
+ NULL, NULL, APR_HOOK_FIRST);
+ /* We need to manipulate the standard HTTP/1.1 protocol filters and
+ * install our own. This needs to be done very early. */
+ ap_hook_post_read_request(h2_c2_hook_post_read_request, NULL, NULL, APR_HOOK_REALLY_FIRST);
+ ap_hook_fixups(c2_hook_fixups, NULL, NULL, APR_HOOK_LAST);
+
+ ap_register_input_filter("H2_C2_NET_IN", h2_c2_filter_in,
+ NULL, AP_FTYPE_NETWORK);
+ ap_register_output_filter("H2_C2_NET_OUT", h2_c2_filter_out,
+ NULL, AP_FTYPE_NETWORK);
+ ap_register_output_filter("H2_C2_NET_CATCH_H1", h2_c2_filter_catch_h1_out,
+ NULL, AP_FTYPE_NETWORK);
+
+ ap_register_input_filter("H2_C2_REQUEST_IN", h2_c2_filter_request_in,
+ NULL, AP_FTYPE_PROTOCOL);
+ ap_register_output_filter("H2_C2_RESPONSE_OUT", h2_c2_filter_response_out,
+ NULL, AP_FTYPE_PROTOCOL);
+ ap_register_output_filter("H2_C2_TRAILERS_OUT", h2_c2_filter_trailers_out,
+ NULL, AP_FTYPE_PROTOCOL);
+}
+
+#endif /* else AP_HAS_RESPONSE_BUCKETS */
diff --git a/modules/http2/h2_c2.h b/modules/http2/h2_c2.h
new file mode 100644
index 0000000..f278382
--- /dev/null
+++ b/modules/http2/h2_c2.h
@@ -0,0 +1,57 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __mod_h2__h2_c2__
+#define __mod_h2__h2_c2__
+
+#include <http_core.h>
+
+#include "h2.h"
+
+const char *h2_conn_mpm_name(void);
+int h2_mpm_supported(void);
+
+/* Initialize this child process for h2 secondary connection work,
+ * to be called once during child init before multi processing
+ * starts.
+ */
+apr_status_t h2_c2_child_init(apr_pool_t *pool, server_rec *s);
+
+#if !AP_HAS_RESPONSE_BUCKETS
+
+conn_rec *h2_c2_create(conn_rec *c1, apr_pool_t *parent,
+ apr_bucket_alloc_t *buckt_alloc);
+
+/**
+ * Process a secondary connection for a HTTP/2 stream request.
+ */
+apr_status_t h2_c2_process(conn_rec *c, apr_thread_t *thread, int worker_id);
+
+#endif /* !AP_HAS_RESPONSE_BUCKETS */
+
+void h2_c2_destroy(conn_rec *c2);
+
+/**
+ * Abort the I/O processing of a secondary connection. And
+ * in-/output beams will return errors and c2->aborted is set.
+ * @param c2 the secondary connection to abort
+ * @param from the connection this is invoked from
+ */
+void h2_c2_abort(conn_rec *c2, conn_rec *from);
+
+void h2_c2_register_hooks(void);
+
+#endif /* defined(__mod_h2__h2_c2__) */
diff --git a/modules/http2/h2_c2_filter.c b/modules/http2/h2_c2_filter.c
new file mode 100644
index 0000000..37254fc
--- /dev/null
+++ b/modules/http2/h2_c2_filter.c
@@ -0,0 +1,1034 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+
+#include <apr_date.h>
+#include <apr_lib.h>
+#include <apr_strings.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_log.h>
+#include <http_connection.h>
+#include <http_protocol.h>
+#include <http_request.h>
+#include <util_time.h>
+
+#include "h2_private.h"
+#include "h2.h"
+#include "h2_config.h"
+#include "h2_conn_ctx.h"
+#include "h2_headers.h"
+#include "h2_c1.h"
+#include "h2_c2_filter.h"
+#include "h2_c2.h"
+#include "h2_mplx.h"
+#include "h2_request.h"
+#include "h2_util.h"
+
+
+#if AP_HAS_RESPONSE_BUCKETS
+
+apr_status_t h2_c2_filter_notes_out(ap_filter_t *f, apr_bucket_brigade *bb)
+{
+ apr_bucket *b;
+ request_rec *r_prev;
+ ap_bucket_response *resp;
+ const char *err;
+
+ if (!f->r) {
+ goto pass;
+ }
+
+ for (b = APR_BRIGADE_FIRST(bb);
+ b != APR_BRIGADE_SENTINEL(bb);
+ b = APR_BUCKET_NEXT(b))
+ {
+ if (AP_BUCKET_IS_RESPONSE(b)) {
+ resp = b->data;
+ if (resp->status >= 400 && f->r->prev) {
+ /* Error responses are commonly handled via internal
+ * redirects to error documents. That creates a new
+ * request_rec with 'prev' set to the original.
+ * Each of these has its onw 'notes'.
+ * We'd like to copy interesting ones into the current 'r->notes'
+ * as we reset HTTP/2 stream with H2 specific error codes then.
+ */
+ for (r_prev = f->r; r_prev != NULL; r_prev = r_prev->prev) {
+ if ((err = apr_table_get(r_prev->notes, "ssl-renegotiate-forbidden"))) {
+ if (r_prev != f->r) {
+ apr_table_setn(resp->notes, "ssl-renegotiate-forbidden", err);
+ }
+ break;
+ }
+ }
+ }
+ else if (h2_config_rgeti(f->r, H2_CONF_PUSH) == 0
+ && h2_config_sgeti(f->r->server, H2_CONF_PUSH) != 0) {
+ /* location configuration turns off H2 PUSH handling */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, f->c,
+ "h2_c2_filter_notes_out, turning PUSH off");
+ apr_table_setn(resp->notes, H2_PUSH_MODE_NOTE, "0");
+ }
+ }
+ }
+pass:
+ return ap_pass_brigade(f->next, bb);
+}
+
+apr_status_t h2_c2_filter_request_in(ap_filter_t *f,
+ apr_bucket_brigade *bb,
+ ap_input_mode_t mode,
+ apr_read_type_e block,
+ apr_off_t readbytes)
+{
+ h2_conn_ctx_t *conn_ctx;
+ apr_bucket *b;
+
+ /* just get out of the way for things we don't want to handle. */
+ if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE) {
+ return ap_get_brigade(f->next, bb, mode, block, readbytes);
+ }
+
+ /* This filter is a one-time wonder */
+ ap_remove_input_filter(f);
+
+ if (f->c->master && (conn_ctx = h2_conn_ctx_get(f->c)) && conn_ctx->stream_id) {
+ if (conn_ctx->request->http_status != H2_HTTP_STATUS_UNSET) {
+ /* error was encountered preparing this request */
+ b = ap_bucket_error_create(conn_ctx->request->http_status, NULL, f->r->pool,
+ f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ return APR_SUCCESS;
+ }
+ b = h2_request_create_bucket(conn_ctx->request, f->r);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ if (!conn_ctx->beam_in) {
+ b = apr_bucket_eos_create(f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ }
+ return APR_SUCCESS;
+ }
+
+ return ap_get_brigade(f->next, bb, mode, block, readbytes);
+}
+
+#else /* AP_HAS_RESPONSE_BUCKETS */
+
+#define H2_FILTER_LOG(name, c, level, rv, msg, bb) \
+ do { \
+ if (APLOG_C_IS_LEVEL((c),(level))) { \
+ char buffer[4 * 1024]; \
+ apr_size_t len, bmax = sizeof(buffer)/sizeof(buffer[0]); \
+ len = h2_util_bb_print(buffer, bmax, "", "", (bb)); \
+ ap_log_cerror(APLOG_MARK, (level), rv, (c), \
+ "FILTER[%s]: %s %s", \
+ (name), (msg), len? buffer : ""); \
+ } \
+ } while (0)
+
+
+/* This routine is called by apr_table_do and merges all instances of
+ * the passed field values into a single array that will be further
+ * processed by some later routine. Originally intended to help split
+ * and recombine multiple Vary fields, though it is generic to any field
+ * consisting of comma/space-separated tokens.
+ */
+static int uniq_field_values(void *d, const char *key, const char *val)
+{
+ apr_array_header_t *values;
+ char *start;
+ char *e;
+ char **strpp;
+ int i;
+
+ (void)key;
+ values = (apr_array_header_t *)d;
+
+ e = apr_pstrdup(values->pool, val);
+
+ do {
+ /* Find a non-empty fieldname */
+
+ while (*e == ',' || apr_isspace(*e)) {
+ ++e;
+ }
+ if (*e == '\0') {
+ break;
+ }
+ start = e;
+ while (*e != '\0' && *e != ',' && !apr_isspace(*e)) {
+ ++e;
+ }
+ if (*e != '\0') {
+ *e++ = '\0';
+ }
+
+ /* Now add it to values if it isn't already represented.
+ * Could be replaced by a ap_array_strcasecmp() if we had one.
+ */
+ for (i = 0, strpp = (char **) values->elts; i < values->nelts;
+ ++i, ++strpp) {
+ if (*strpp && apr_strnatcasecmp(*strpp, start) == 0) {
+ break;
+ }
+ }
+ if (i == values->nelts) { /* if not found */
+ *(char **)apr_array_push(values) = start;
+ }
+ } while (*e != '\0');
+
+ return 1;
+}
+
+/*
+ * Since some clients choke violently on multiple Vary fields, or
+ * Vary fields with duplicate tokens, combine any multiples and remove
+ * any duplicates.
+ */
+static void fix_vary(request_rec *r)
+{
+ apr_array_header_t *varies;
+
+ varies = apr_array_make(r->pool, 5, sizeof(char *));
+
+ /* Extract all Vary fields from the headers_out, separate each into
+ * its comma-separated fieldname values, and then add them to varies
+ * if not already present in the array.
+ */
+ apr_table_do(uniq_field_values, varies, r->headers_out, "Vary", NULL);
+
+ /* If we found any, replace old Vary fields with unique-ified value */
+
+ if (varies->nelts > 0) {
+ apr_table_setn(r->headers_out, "Vary",
+ apr_array_pstrcat(r->pool, varies, ','));
+ }
+}
+
+static h2_headers *create_response(request_rec *r)
+{
+ const char *clheader;
+ const char *ctype;
+
+ /*
+ * Now that we are ready to send a response, we need to combine the two
+ * header field tables into a single table. If we don't do this, our
+ * later attempts to set or unset a given fieldname might be bypassed.
+ */
+ if (!apr_is_empty_table(r->err_headers_out)) {
+ r->headers_out = apr_table_overlay(r->pool, r->err_headers_out,
+ r->headers_out);
+ apr_table_clear(r->err_headers_out);
+ }
+
+ /*
+ * Remove the 'Vary' header field if the client can't handle it.
+ * Since this will have nasty effects on HTTP/1.1 caches, force
+ * the response into HTTP/1.0 mode.
+ */
+ if (apr_table_get(r->subprocess_env, "force-no-vary") != NULL) {
+ apr_table_unset(r->headers_out, "Vary");
+ r->proto_num = HTTP_VERSION(1,0);
+ apr_table_setn(r->subprocess_env, "force-response-1.0", "1");
+ }
+ else {
+ fix_vary(r);
+ }
+
+ /*
+ * Now remove any ETag response header field if earlier processing
+ * says so (such as a 'FileETag None' directive).
+ */
+ if (apr_table_get(r->notes, "no-etag") != NULL) {
+ apr_table_unset(r->headers_out, "ETag");
+ }
+
+ /* determine the protocol and whether we should use keepalives. */
+ ap_set_keepalive(r);
+
+ if (AP_STATUS_IS_HEADER_ONLY(r->status)) {
+ apr_table_unset(r->headers_out, "Transfer-Encoding");
+ apr_table_unset(r->headers_out, "Content-Length");
+ r->content_type = r->content_encoding = NULL;
+ r->content_languages = NULL;
+ r->clength = r->chunked = 0;
+ }
+ else if (r->chunked) {
+ apr_table_mergen(r->headers_out, "Transfer-Encoding", "chunked");
+ apr_table_unset(r->headers_out, "Content-Length");
+ }
+
+ ctype = ap_make_content_type(r, r->content_type);
+ if (ctype) {
+ apr_table_setn(r->headers_out, "Content-Type", ctype);
+ }
+
+ if (r->content_encoding) {
+ apr_table_setn(r->headers_out, "Content-Encoding",
+ r->content_encoding);
+ }
+
+ if (!apr_is_empty_array(r->content_languages)) {
+ int i;
+ char *token;
+ char **languages = (char **)(r->content_languages->elts);
+ const char *field = apr_table_get(r->headers_out, "Content-Language");
+
+ while (field && (token = ap_get_list_item(r->pool, &field)) != NULL) {
+ for (i = 0; i < r->content_languages->nelts; ++i) {
+ if (!apr_strnatcasecmp(token, languages[i]))
+ break;
+ }
+ if (i == r->content_languages->nelts) {
+ *((char **) apr_array_push(r->content_languages)) = token;
+ }
+ }
+
+ field = apr_array_pstrcat(r->pool, r->content_languages, ',');
+ apr_table_setn(r->headers_out, "Content-Language", field);
+ }
+
+ /*
+ * Control cachability for non-cachable responses if not already set by
+ * some other part of the server configuration.
+ */
+ if (r->no_cache && !apr_table_get(r->headers_out, "Expires")) {
+ char *date = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
+ ap_recent_rfc822_date(date, r->request_time);
+ apr_table_add(r->headers_out, "Expires", date);
+ }
+
+ /* This is a hack, but I can't find anyway around it. The idea is that
+ * we don't want to send out 0 Content-Lengths if it is a head request.
+ * This happens when modules try to outsmart the server, and return
+ * if they see a HEAD request. Apache 1.3 handlers were supposed to
+ * just return in that situation, and the core handled the HEAD. In
+ * 2.0, if a handler returns, then the core sends an EOS bucket down
+ * the filter stack, and the content-length filter computes a C-L of
+ * zero and that gets put in the headers, and we end up sending a
+ * zero C-L to the client. We can't just remove the C-L filter,
+ * because well behaved 2.0 handlers will send their data down the stack,
+ * and we will compute a real C-L for the head request. RBB
+ */
+ if (r->header_only
+ && (clheader = apr_table_get(r->headers_out, "Content-Length"))
+ && !strcmp(clheader, "0")) {
+ apr_table_unset(r->headers_out, "Content-Length");
+ }
+
+ /*
+ * keep the set-by-proxy server and date headers, otherwise
+ * generate a new server header / date header
+ */
+ if (r->proxyreq == PROXYREQ_NONE
+ || !apr_table_get(r->headers_out, "Date")) {
+ char *date = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
+ ap_recent_rfc822_date(date, r->request_time);
+ apr_table_setn(r->headers_out, "Date", date );
+ }
+ if (r->proxyreq == PROXYREQ_NONE
+ || !apr_table_get(r->headers_out, "Server")) {
+ const char *us = ap_get_server_banner();
+ if (us && *us) {
+ apr_table_setn(r->headers_out, "Server", us);
+ }
+ }
+
+ return h2_headers_rcreate(r, r->status, r->headers_out, r->pool);
+}
+
+typedef enum {
+ H2_RP_STATUS_LINE,
+ H2_RP_HEADER_LINE,
+ H2_RP_DONE
+} h2_rp_state_t;
+
+typedef struct h2_response_parser h2_response_parser;
+struct h2_response_parser {
+ const char *id;
+ h2_rp_state_t state;
+ conn_rec *c;
+ apr_pool_t *pool;
+ int http_status;
+ apr_array_header_t *hlines;
+ apr_bucket_brigade *tmp;
+ apr_bucket_brigade *saveto;
+};
+
+static apr_status_t parse_header(h2_response_parser *parser, char *line) {
+ const char *hline;
+ if (line[0] == ' ' || line[0] == '\t') {
+ char **plast;
+ /* continuation line from the header before this */
+ while (line[0] == ' ' || line[0] == '\t') {
+ ++line;
+ }
+
+ plast = apr_array_pop(parser->hlines);
+ if (plast == NULL) {
+ /* not well formed */
+ return APR_EINVAL;
+ }
+ hline = apr_psprintf(parser->pool, "%s %s", *plast, line);
+ }
+ else {
+ /* new header line */
+ hline = apr_pstrdup(parser->pool, line);
+ }
+ APR_ARRAY_PUSH(parser->hlines, const char*) = hline;
+ return APR_SUCCESS;
+}
+
+static apr_status_t get_line(h2_response_parser *parser, apr_bucket_brigade *bb,
+ char *line, apr_size_t len)
+{
+ apr_status_t status;
+
+ if (!parser->tmp) {
+ parser->tmp = apr_brigade_create(parser->pool, parser->c->bucket_alloc);
+ }
+ status = apr_brigade_split_line(parser->tmp, bb, APR_BLOCK_READ,
+ len);
+ if (status == APR_SUCCESS) {
+ --len;
+ status = apr_brigade_flatten(parser->tmp, line, &len);
+ if (status == APR_SUCCESS) {
+ /* we assume a non-0 containing line and remove trailing crlf. */
+ line[len] = '\0';
+ /*
+ * XXX: What to do if there is an LF but no CRLF?
+ * Should we error out?
+ */
+ if (len >= 2 && !strcmp(H2_CRLF, line + len - 2)) {
+ len -= 2;
+ line[len] = '\0';
+ apr_brigade_cleanup(parser->tmp);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, parser->c,
+ "h2_c2(%s): read response line: %s",
+ parser->id, line);
+ }
+ else {
+ apr_off_t brigade_length;
+
+ /*
+ * If the brigade parser->tmp becomes longer than our buffer
+ * for flattening we never have a chance to get a complete
+ * line. This can happen if we are called multiple times after
+ * previous calls did not find a H2_CRLF and we returned
+ * APR_EAGAIN. In this case parser->tmp (correctly) grows
+ * with each call to apr_brigade_split_line.
+ *
+ * XXX: Currently a stack based buffer of HUGE_STRING_LEN is
+ * used. This means we cannot cope with lines larger than
+ * HUGE_STRING_LEN which might be an issue.
+ */
+ status = apr_brigade_length(parser->tmp, 0, &brigade_length);
+ if ((status != APR_SUCCESS) || (brigade_length > (apr_off_t)len)) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, parser->c, APLOGNO(10257)
+ "h2_c2(%s): read response, line too long",
+ parser->id);
+ return APR_ENOSPC;
+ }
+ /* this does not look like a complete line yet */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, parser->c,
+ "h2_c2(%s): read response, incomplete line: %s",
+ parser->id, line);
+ if (!parser->saveto) {
+ parser->saveto = apr_brigade_create(parser->pool,
+ parser->c->bucket_alloc);
+ }
+ /*
+ * Be on the save side and save the parser->tmp brigade
+ * as it could contain transient buckets which could be
+ * invalid next time we are here.
+ *
+ * NULL for the filter parameter is ok since we
+ * provide our own brigade as second parameter
+ * and ap_save_brigade does not need to create one.
+ */
+ ap_save_brigade(NULL, &(parser->saveto), &(parser->tmp),
+ parser->tmp->p);
+ APR_BRIGADE_CONCAT(parser->tmp, parser->saveto);
+ return APR_EAGAIN;
+ }
+ }
+ }
+ apr_brigade_cleanup(parser->tmp);
+ return status;
+}
+
+static apr_table_t *make_table(h2_response_parser *parser)
+{
+ apr_array_header_t *hlines = parser->hlines;
+ if (hlines) {
+ apr_table_t *headers = apr_table_make(parser->pool, hlines->nelts);
+ int i;
+
+ for (i = 0; i < hlines->nelts; ++i) {
+ char *hline = ((char **)hlines->elts)[i];
+ char *sep = ap_strchr(hline, ':');
+ if (!sep) {
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, parser->c,
+ APLOGNO(02955) "h2_c2(%s): invalid header[%d] '%s'",
+ parser->id, i, (char*)hline);
+ /* not valid format, abort */
+ return NULL;
+ }
+ (*sep++) = '\0';
+ while (*sep == ' ' || *sep == '\t') {
+ ++sep;
+ }
+
+ if (!h2_util_ignore_resp_header(hline)) {
+ apr_table_merge(headers, hline, sep);
+ }
+ }
+ return headers;
+ }
+ else {
+ return apr_table_make(parser->pool, 0);
+ }
+}
+
+static apr_status_t pass_response(h2_conn_ctx_t *conn_ctx, ap_filter_t *f,
+ h2_response_parser *parser)
+{
+ apr_bucket *b;
+ apr_status_t status;
+
+ h2_headers *response = h2_headers_create(parser->http_status,
+ make_table(parser),
+ NULL, 0, parser->pool);
+ apr_brigade_cleanup(parser->tmp);
+ b = h2_bucket_headers_create(parser->c->bucket_alloc, response);
+ APR_BRIGADE_INSERT_TAIL(parser->tmp, b);
+ b = apr_bucket_flush_create(parser->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(parser->tmp, b);
+ status = ap_pass_brigade(f->next, parser->tmp);
+ apr_brigade_cleanup(parser->tmp);
+
+ /* reset parser for possible next response */
+ parser->state = H2_RP_STATUS_LINE;
+ apr_array_clear(parser->hlines);
+
+ if (response->status >= 200) {
+ conn_ctx->has_final_response = 1;
+ }
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, parser->c,
+ APLOGNO(03197) "h2_c2(%s): passed response %d",
+ parser->id, response->status);
+ return status;
+}
+
+static apr_status_t parse_status(h2_response_parser *parser, char *line)
+{
+ int sindex = (apr_date_checkmask(line, "HTTP/#.# ###*")? 9 :
+ (apr_date_checkmask(line, "HTTP/# ###*")? 7 : 0));
+ if (sindex > 0) {
+ int k = sindex + 3;
+ char keepchar = line[k];
+ line[k] = '\0';
+ parser->http_status = atoi(&line[sindex]);
+ line[k] = keepchar;
+ parser->state = H2_RP_HEADER_LINE;
+
+ return APR_SUCCESS;
+ }
+ /* Seems like there is garbage on the connection. May be a leftover
+ * from a previous proxy request.
+ * This should only happen if the H2_RESPONSE filter is not yet in
+ * place (post_read_request has not been reached and the handler wants
+ * to write something. Probably just the interim response we are
+ * waiting for. But if there is other data hanging around before
+ * that, this needs to fail. */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, parser->c, APLOGNO(03467)
+ "h2_c2(%s): unable to parse status line: %s",
+ parser->id, line);
+ return APR_EINVAL;
+}
+
+static apr_status_t parse_response(h2_response_parser *parser,
+ h2_conn_ctx_t *conn_ctx,
+ ap_filter_t* f, apr_bucket_brigade *bb)
+{
+ char line[HUGE_STRING_LEN];
+ apr_status_t status = APR_SUCCESS;
+
+ while (!APR_BRIGADE_EMPTY(bb) && status == APR_SUCCESS) {
+ switch (parser->state) {
+ case H2_RP_STATUS_LINE:
+ case H2_RP_HEADER_LINE:
+ status = get_line(parser, bb, line, sizeof(line));
+ if (status == APR_EAGAIN) {
+ /* need more data */
+ return APR_SUCCESS;
+ }
+ else if (status != APR_SUCCESS) {
+ return status;
+ }
+ if (parser->state == H2_RP_STATUS_LINE) {
+ /* instead of parsing, just take it directly */
+ status = parse_status(parser, line);
+ }
+ else if (line[0] == '\0') {
+ /* end of headers, pass response onward */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, parser->c,
+ "h2_c2(%s): end of response", parser->id);
+ return pass_response(conn_ctx, f, parser);
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, parser->c,
+ "h2_c2(%s): response header %s", parser->id, line);
+ status = parse_header(parser, line);
+ }
+ break;
+
+ default:
+ return status;
+ }
+ }
+ return status;
+}
+
+apr_status_t h2_c2_filter_catch_h1_out(ap_filter_t* f, apr_bucket_brigade* bb)
+{
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(f->c);
+ h2_response_parser *parser = f->ctx;
+ apr_status_t rv;
+
+ ap_assert(conn_ctx);
+ H2_FILTER_LOG("c2_catch_h1_out", f->c, APLOG_TRACE2, 0, "check", bb);
+
+ if (!f->c->aborted && !conn_ctx->has_final_response) {
+ if (!parser) {
+ parser = apr_pcalloc(f->c->pool, sizeof(*parser));
+ parser->id = apr_psprintf(f->c->pool, "%s-%d", conn_ctx->id, conn_ctx->stream_id);
+ parser->pool = f->c->pool;
+ parser->c = f->c;
+ parser->state = H2_RP_STATUS_LINE;
+ parser->hlines = apr_array_make(parser->pool, 10, sizeof(char *));
+ f->ctx = parser;
+ }
+
+ if (!APR_BRIGADE_EMPTY(bb)) {
+ apr_bucket *b = APR_BRIGADE_FIRST(bb);
+ if (AP_BUCKET_IS_EOR(b)) {
+ /* TODO: Yikes, this happens when errors are encountered on input
+ * before anything from the repsonse has been processed. The
+ * ap_die_r() call will do nothing in certain conditions.
+ */
+ int result = ap_map_http_request_error(conn_ctx->last_err,
+ HTTP_INTERNAL_SERVER_ERROR);
+ request_rec *r = h2_create_request_rec(conn_ctx->request, f->c, 1);
+ ap_die((result >= 400)? result : HTTP_INTERNAL_SERVER_ERROR, r);
+ b = ap_bucket_eor_create(f->c->bucket_alloc, r);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ }
+ }
+ /* There are cases where we need to parse a serialized http/1.1 response.
+ * One example is a 100-continue answer via a mod_proxy setup. */
+ while (bb && !f->c->aborted && !conn_ctx->has_final_response) {
+ rv = parse_response(parser, conn_ctx, f, bb);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, f->c,
+ "h2_c2(%s): parsed response", parser->id);
+ if (APR_BRIGADE_EMPTY(bb) || APR_SUCCESS != rv) {
+ return rv;
+ }
+ }
+ }
+
+ return ap_pass_brigade(f->next, bb);
+}
+
+apr_status_t h2_c2_filter_response_out(ap_filter_t *f, apr_bucket_brigade *bb)
+{
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(f->c);
+ request_rec *r = f->r;
+ apr_bucket *b, *bresp, *body_bucket = NULL, *next;
+ ap_bucket_error *eb = NULL;
+ h2_headers *response = NULL;
+ int headers_passing = 0;
+
+ H2_FILTER_LOG("c2_response_out", f->c, APLOG_TRACE1, 0, "called with", bb);
+
+ if (f->c->aborted || !conn_ctx || conn_ctx->has_final_response) {
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ if (!conn_ctx->has_final_response) {
+ /* check, if we need to send the response now. Until we actually
+ * see a DATA bucket or some EOS/EOR, we do not do so. */
+ for (b = APR_BRIGADE_FIRST(bb);
+ b != APR_BRIGADE_SENTINEL(bb);
+ b = APR_BUCKET_NEXT(b))
+ {
+ if (AP_BUCKET_IS_ERROR(b) && !eb) {
+ eb = b->data;
+ }
+ else if (AP_BUCKET_IS_EOC(b)) {
+ /* If we see an EOC bucket it is a signal that we should get out
+ * of the way doing nothing.
+ */
+ ap_remove_output_filter(f);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, f->c,
+ "h2_c2(%s): eoc bucket passed", conn_ctx->id);
+ return ap_pass_brigade(f->next, bb);
+ }
+ else if (H2_BUCKET_IS_HEADERS(b)) {
+ headers_passing = 1;
+ }
+ else if (!APR_BUCKET_IS_FLUSH(b)) {
+ body_bucket = b;
+ break;
+ }
+ }
+
+ if (eb) {
+ int st = eb->status;
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, APLOGNO(03047)
+ "h2_c2(%s): err bucket status=%d",
+ conn_ctx->id, st);
+ /* throw everything away and replace it with the error response
+ * generated by ap_die() */
+ apr_brigade_cleanup(bb);
+ ap_die(st, r);
+ return AP_FILTER_ERROR;
+ }
+
+ if (body_bucket || !headers_passing) {
+ /* time to insert the response bucket before the body or if
+ * no h2_headers is passed, e.g. the response is empty */
+ response = create_response(r);
+ if (response == NULL) {
+ ap_log_cerror(APLOG_MARK, APLOG_NOTICE, 0, f->c, APLOGNO(03048)
+ "h2_c2(%s): unable to create response", conn_ctx->id);
+ return APR_ENOMEM;
+ }
+
+ bresp = h2_bucket_headers_create(f->c->bucket_alloc, response);
+ if (body_bucket) {
+ APR_BUCKET_INSERT_BEFORE(body_bucket, bresp);
+ }
+ else {
+ APR_BRIGADE_INSERT_HEAD(bb, bresp);
+ }
+ conn_ctx->has_final_response = 1;
+ r->sent_bodyct = 1;
+ ap_remove_output_filter_byhandle(f->r->output_filters, "H2_C2_NET_CATCH_H1");
+ }
+ }
+
+ if (r->header_only || AP_STATUS_IS_HEADER_ONLY(r->status)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
+ "h2_c2(%s): headers only, cleanup output brigade", conn_ctx->id);
+ b = body_bucket? body_bucket : APR_BRIGADE_FIRST(bb);
+ while (b != APR_BRIGADE_SENTINEL(bb)) {
+ next = APR_BUCKET_NEXT(b);
+ if (APR_BUCKET_IS_EOS(b) || AP_BUCKET_IS_EOR(b)) {
+ break;
+ }
+ if (!H2_BUCKET_IS_HEADERS(b)) {
+ APR_BUCKET_REMOVE(b);
+ apr_bucket_destroy(b);
+ }
+ b = next;
+ }
+ }
+ if (conn_ctx->has_final_response) {
+ /* lets get out of the way, our task is done */
+ ap_remove_output_filter(f);
+ }
+ return ap_pass_brigade(f->next, bb);
+}
+
+
+struct h2_chunk_filter_t {
+ const char *id;
+ int eos_chunk_added;
+ apr_bucket_brigade *bbchunk;
+ apr_off_t chunked_total;
+};
+typedef struct h2_chunk_filter_t h2_chunk_filter_t;
+
+
+static void make_chunk(conn_rec *c, h2_chunk_filter_t *fctx, apr_bucket_brigade *bb,
+ apr_bucket *first, apr_off_t chunk_len,
+ apr_bucket *tail)
+{
+ /* Surround the buckets [first, tail[ with new buckets carrying the
+ * HTTP/1.1 chunked encoding format. If tail is NULL, the chunk extends
+ * to the end of the brigade. */
+ char buffer[128];
+ apr_bucket *b;
+ apr_size_t len;
+
+ len = (apr_size_t)apr_snprintf(buffer, H2_ALEN(buffer),
+ "%"APR_UINT64_T_HEX_FMT"\r\n", (apr_uint64_t)chunk_len);
+ b = apr_bucket_heap_create(buffer, len, NULL, bb->bucket_alloc);
+ APR_BUCKET_INSERT_BEFORE(first, b);
+ b = apr_bucket_immortal_create("\r\n", 2, bb->bucket_alloc);
+ if (tail) {
+ APR_BUCKET_INSERT_BEFORE(tail, b);
+ }
+ else {
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ }
+ fctx->chunked_total += chunk_len;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
+ "h2_c2(%s): added chunk %ld, total %ld",
+ fctx->id, (long)chunk_len, (long)fctx->chunked_total);
+}
+
+static int ser_header(void *ctx, const char *name, const char *value)
+{
+ apr_bucket_brigade *bb = ctx;
+ apr_brigade_printf(bb, NULL, NULL, "%s: %s\r\n", name, value);
+ return 1;
+}
+
+static apr_status_t read_and_chunk(ap_filter_t *f, h2_conn_ctx_t *conn_ctx,
+ apr_read_type_e block) {
+ h2_chunk_filter_t *fctx = f->ctx;
+ request_rec *r = f->r;
+ apr_status_t status = APR_SUCCESS;
+
+ if (!fctx->bbchunk) {
+ fctx->bbchunk = apr_brigade_create(r->pool, f->c->bucket_alloc);
+ }
+
+ if (APR_BRIGADE_EMPTY(fctx->bbchunk)) {
+ apr_bucket *b, *next, *first_data = NULL;
+ apr_bucket_brigade *tmp;
+ apr_off_t bblen = 0;
+
+ /* get more data from the lower layer filters. Always do this
+ * in larger pieces, since we handle the read modes ourself. */
+ status = ap_get_brigade(f->next, fctx->bbchunk,
+ AP_MODE_READBYTES, block, conn_ctx->mplx->stream_max_mem);
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+
+ for (b = APR_BRIGADE_FIRST(fctx->bbchunk);
+ b != APR_BRIGADE_SENTINEL(fctx->bbchunk);
+ b = next) {
+ next = APR_BUCKET_NEXT(b);
+ if (APR_BUCKET_IS_METADATA(b)) {
+ if (first_data) {
+ make_chunk(f->c, fctx, fctx->bbchunk, first_data, bblen, b);
+ first_data = NULL;
+ }
+
+ if (H2_BUCKET_IS_HEADERS(b)) {
+ h2_headers *headers = h2_bucket_headers_get(b);
+
+ ap_assert(headers);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+ "h2_c2(%s-%d): receiving trailers",
+ conn_ctx->id, conn_ctx->stream_id);
+ tmp = apr_brigade_split_ex(fctx->bbchunk, b, NULL);
+ if (!apr_is_empty_table(headers->headers)) {
+ status = apr_brigade_puts(fctx->bbchunk, NULL, NULL, "0\r\n");
+ apr_table_do(ser_header, fctx->bbchunk, headers->headers, NULL);
+ status = apr_brigade_puts(fctx->bbchunk, NULL, NULL, "\r\n");
+ }
+ else {
+ status = apr_brigade_puts(fctx->bbchunk, NULL, NULL, "0\r\n\r\n");
+ }
+ r->trailers_in = apr_table_clone(r->pool, headers->headers);
+ APR_BUCKET_REMOVE(b);
+ apr_bucket_destroy(b);
+ APR_BRIGADE_CONCAT(fctx->bbchunk, tmp);
+ apr_brigade_destroy(tmp);
+ fctx->eos_chunk_added = 1;
+ }
+ else if (APR_BUCKET_IS_EOS(b)) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+ "h2_c2(%s-%d): receiving eos",
+ conn_ctx->id, conn_ctx->stream_id);
+ if (!fctx->eos_chunk_added) {
+ tmp = apr_brigade_split_ex(fctx->bbchunk, b, NULL);
+ status = apr_brigade_puts(fctx->bbchunk, NULL, NULL, "0\r\n\r\n");
+ APR_BRIGADE_CONCAT(fctx->bbchunk, tmp);
+ apr_brigade_destroy(tmp);
+ }
+ fctx->eos_chunk_added = 0;
+ }
+ }
+ else if (b->length == 0) {
+ APR_BUCKET_REMOVE(b);
+ apr_bucket_destroy(b);
+ }
+ else {
+ if (!first_data) {
+ first_data = b;
+ bblen = 0;
+ }
+ bblen += b->length;
+ }
+ }
+
+ if (first_data) {
+ make_chunk(f->c, fctx, fctx->bbchunk, first_data, bblen, NULL);
+ }
+ }
+ return status;
+}
+
+apr_status_t h2_c2_filter_request_in(ap_filter_t* f,
+ apr_bucket_brigade* bb,
+ ap_input_mode_t mode,
+ apr_read_type_e block,
+ apr_off_t readbytes)
+{
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(f->c);
+ h2_chunk_filter_t *fctx = f->ctx;
+ request_rec *r = f->r;
+ apr_status_t status = APR_SUCCESS;
+ apr_bucket *b, *next;
+ core_server_config *conf =
+ (core_server_config *) ap_get_module_config(r->server->module_config,
+ &core_module);
+ ap_assert(conn_ctx);
+
+ if (!fctx) {
+ fctx = apr_pcalloc(r->pool, sizeof(*fctx));
+ fctx->id = apr_psprintf(r->pool, "%s-%d", conn_ctx->id, conn_ctx->stream_id);
+ f->ctx = fctx;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, f->r,
+ "h2_c2(%s-%d): request input, mode=%d, block=%d, "
+ "readbytes=%ld, exp=%d",
+ conn_ctx->id, conn_ctx->stream_id, mode, block,
+ (long)readbytes, r->expecting_100);
+ if (!conn_ctx->input_chunked) {
+ status = ap_get_brigade(f->next, bb, mode, block, readbytes);
+ /* pipe data through, just take care of trailers */
+ for (b = APR_BRIGADE_FIRST(bb);
+ b != APR_BRIGADE_SENTINEL(bb); b = next) {
+ next = APR_BUCKET_NEXT(b);
+ if (H2_BUCKET_IS_HEADERS(b)) {
+ h2_headers *headers = h2_bucket_headers_get(b);
+ ap_assert(headers);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+ "h2_c2(%s-%d): receiving trailers",
+ conn_ctx->id, conn_ctx->stream_id);
+ r->trailers_in = headers->headers;
+ if (conf && conf->merge_trailers == AP_MERGE_TRAILERS_ENABLE) {
+ r->headers_in = apr_table_overlay(r->pool, r->headers_in,
+ r->trailers_in);
+ }
+ APR_BUCKET_REMOVE(b);
+ apr_bucket_destroy(b);
+ ap_remove_input_filter(f);
+
+ if (headers->raw_bytes && h2_c_logio_add_bytes_in) {
+ h2_c_logio_add_bytes_in(f->c, headers->raw_bytes);
+ }
+ break;
+ }
+ }
+ return status;
+ }
+
+ /* Things are more complicated. The standard HTTP input filter, which
+ * does a lot what we do not want to duplicate, also cares about chunked
+ * transfer encoding and trailers.
+ * We need to simulate chunked encoding for it to be happy.
+ */
+ if ((status = read_and_chunk(f, conn_ctx, block)) != APR_SUCCESS) {
+ return status;
+ }
+
+ if (mode == AP_MODE_EXHAUSTIVE) {
+ /* return all we have */
+ APR_BRIGADE_CONCAT(bb, fctx->bbchunk);
+ }
+ else if (mode == AP_MODE_READBYTES) {
+ status = h2_brigade_concat_length(bb, fctx->bbchunk, readbytes);
+ }
+ else if (mode == AP_MODE_SPECULATIVE) {
+ status = h2_brigade_copy_length(bb, fctx->bbchunk, readbytes);
+ }
+ else if (mode == AP_MODE_GETLINE) {
+ /* we are reading a single LF line, e.g. the HTTP headers.
+ * this has the nasty side effect to split the bucket, even
+ * though it ends with CRLF and creates a 0 length bucket */
+ status = apr_brigade_split_line(bb, fctx->bbchunk, block, HUGE_STRING_LEN);
+ if (APLOGctrace1(f->c)) {
+ char buffer[1024];
+ apr_size_t len = sizeof(buffer)-1;
+ apr_brigade_flatten(bb, buffer, &len);
+ buffer[len] = 0;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
+ "h2_c2(%s-%d): getline: %s",
+ conn_ctx->id, conn_ctx->stream_id, buffer);
+ }
+ }
+ else {
+ /* Hmm, well. There is mode AP_MODE_EATCRLF, but we chose not
+ * to support it. Seems to work. */
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOTIMPL, f->c,
+ APLOGNO(02942)
+ "h2_c2, unsupported READ mode %d", mode);
+ status = APR_ENOTIMPL;
+ }
+
+ h2_util_bb_log(f->c, conn_ctx->stream_id, APLOG_TRACE2, "returning input", bb);
+ return status;
+}
+
+apr_status_t h2_c2_filter_trailers_out(ap_filter_t *f, apr_bucket_brigade *bb)
+{
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(f->c);
+ request_rec *r = f->r;
+ apr_bucket *b, *e;
+
+ if (conn_ctx && r) {
+ /* Detect the EOS/EOR bucket and forward any trailers that may have
+ * been set to our h2_headers.
+ */
+ for (b = APR_BRIGADE_FIRST(bb);
+ b != APR_BRIGADE_SENTINEL(bb);
+ b = APR_BUCKET_NEXT(b))
+ {
+ if ((APR_BUCKET_IS_EOS(b) || AP_BUCKET_IS_EOR(b))
+ && r->trailers_out && !apr_is_empty_table(r->trailers_out)) {
+ h2_headers *headers;
+ apr_table_t *trailers;
+
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, APLOGNO(03049)
+ "h2_c2(%s-%d): sending trailers",
+ conn_ctx->id, conn_ctx->stream_id);
+ trailers = apr_table_clone(r->pool, r->trailers_out);
+ headers = h2_headers_rcreate(r, HTTP_OK, trailers, r->pool);
+ e = h2_bucket_headers_create(bb->bucket_alloc, headers);
+ APR_BUCKET_INSERT_BEFORE(b, e);
+ apr_table_clear(r->trailers_out);
+ ap_remove_output_filter(f);
+ break;
+ }
+ }
+ }
+
+ return ap_pass_brigade(f->next, bb);
+}
+
+#endif /* else #if AP_HAS_RESPONSE_BUCKETS */
diff --git a/modules/http2/h2_c2_filter.h b/modules/http2/h2_c2_filter.h
new file mode 100644
index 0000000..c6f50dd
--- /dev/null
+++ b/modules/http2/h2_c2_filter.h
@@ -0,0 +1,68 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __mod_h2__h2_c2_filter__
+#define __mod_h2__h2_c2_filter__
+
+#include "h2.h"
+
+/**
+ * Input filter on secondary connections that insert the REQUEST bucket
+ * with the request to perform and then removes itself.
+ */
+apr_status_t h2_c2_filter_request_in(ap_filter_t *f,
+ apr_bucket_brigade *bb,
+ ap_input_mode_t mode,
+ apr_read_type_e block,
+ apr_off_t readbytes);
+
+#if AP_HAS_RESPONSE_BUCKETS
+
+/**
+ * Output filter that inspects the request_rec->notes of the request
+ * itself and possible internal redirects to detect conditions that
+ * merit specific HTTP/2 response codes, such as 421.
+ */
+apr_status_t h2_c2_filter_notes_out(ap_filter_t *f, apr_bucket_brigade *bb);
+
+#else /* AP_HAS_RESPONSE_BUCKETS */
+
+/**
+ * h2_from_h1 parses a HTTP/1.1 response into
+ * - response status
+ * - a list of header values
+ * - a series of bytes that represent the response body alone, without
+ * any meta data, such as inserted by chunked transfer encoding.
+ *
+ * All data is allocated from the stream memory pool.
+ *
+ * Again, see comments in h2_request: ideally we would take the headers
+ * and status from the httpd structures instead of parsing them here, but
+ * we need to have all handlers and filters involved in request/response
+ * processing, so this seems to be the way for now.
+ */
+struct h2_headers;
+struct h2_response_parser;
+
+apr_status_t h2_c2_filter_catch_h1_out(ap_filter_t* f, apr_bucket_brigade* bb);
+
+apr_status_t h2_c2_filter_response_out(ap_filter_t *f, apr_bucket_brigade *bb);
+
+apr_status_t h2_c2_filter_trailers_out(ap_filter_t *f, apr_bucket_brigade *bb);
+
+#endif /* else AP_HAS_RESPONSE_BUCKETS */
+
+#endif /* defined(__mod_h2__h2_c2_filter__) */
diff --git a/modules/http2/h2_config.c b/modules/http2/h2_config.c
new file mode 100644
index 0000000..eea4be2
--- /dev/null
+++ b/modules/http2/h2_config.c
@@ -0,0 +1,943 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+
+#include <apr_hash.h>
+#include <apr_lib.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_config.h>
+#include <http_log.h>
+#include <http_vhost.h>
+
+#include <ap_mpm.h>
+
+#include <apr_strings.h>
+
+#include "h2.h"
+#include "h2_conn_ctx.h"
+#include "h2_c1.h"
+#include "h2_config.h"
+#include "h2_protocol.h"
+#include "h2_private.h"
+
+#define DEF_VAL (-1)
+
+#define H2_CONFIG_GET(a, b, n) \
+ (((a)->n == DEF_VAL)? (b) : (a))->n
+
+#define H2_CONFIG_SET(a, n, v) \
+ ((a)->n = v)
+
+#define CONFIG_CMD_SET(cmd,dir,var,val) \
+ h2_config_seti(((cmd)->path? (dir) : NULL), h2_config_sget((cmd)->server), var, val)
+
+#define CONFIG_CMD_SET64(cmd,dir,var,val) \
+ h2_config_seti64(((cmd)->path? (dir) : NULL), h2_config_sget((cmd)->server), var, val)
+
+/* Apache httpd module configuration for h2. */
+typedef struct h2_config {
+ const char *name;
+ int h2_max_streams; /* max concurrent # streams (http2) */
+ int h2_window_size; /* stream window size (http2) */
+ int min_workers; /* min # of worker threads/child */
+ int max_workers; /* max # of worker threads/child */
+ apr_interval_time_t idle_limit; /* max duration for idle workers */
+ int stream_max_mem_size; /* max # bytes held in memory/stream */
+ int h2_direct; /* if mod_h2 is active directly */
+ int modern_tls_only; /* Accept only modern TLS in HTTP/2 connections */
+ int h2_upgrade; /* Allow HTTP/1 upgrade to h2/h2c */
+ apr_int64_t tls_warmup_size; /* Amount of TLS data to send before going full write size */
+ int tls_cooldown_secs; /* Seconds of idle time before going back to small TLS records */
+ int h2_push; /* if HTTP/2 server push is enabled */
+ struct apr_hash_t *priorities; /* map of content-type to h2_priority records */
+
+ int push_diary_size; /* # of entries in push diary */
+ int copy_files; /* if files shall be copied vs setaside on output */
+ apr_array_header_t *push_list; /* list of h2_push_res configurations */
+ int early_hints; /* support status code 103 */
+ int padding_bits;
+ int padding_always;
+ int output_buffered;
+ apr_interval_time_t stream_timeout;/* beam timeout */
+} h2_config;
+
+typedef struct h2_dir_config {
+ const char *name;
+ int h2_upgrade; /* Allow HTTP/1 upgrade to h2/h2c */
+ int h2_push; /* if HTTP/2 server push is enabled */
+ apr_array_header_t *push_list; /* list of h2_push_res configurations */
+ int early_hints; /* support status code 103 */
+ apr_interval_time_t stream_timeout;/* beam timeout */
+} h2_dir_config;
+
+
+static h2_config defconf = {
+ "default",
+ 100, /* max_streams */
+ H2_INITIAL_WINDOW_SIZE, /* window_size */
+ -1, /* min workers */
+ -1, /* max workers */
+ apr_time_from_sec(10 * 60), /* workers idle limit */
+ 32 * 1024, /* stream max mem size */
+ -1, /* h2 direct mode */
+ 1, /* modern TLS only */
+ -1, /* HTTP/1 Upgrade support */
+ 1024*1024, /* TLS warmup size */
+ 1, /* TLS cooldown secs */
+ 1, /* HTTP/2 server push enabled */
+ NULL, /* map of content-type to priorities */
+ 256, /* push diary size */
+ 0, /* copy files across threads */
+ NULL, /* push list */
+ 0, /* early hints, http status 103 */
+ 0, /* padding bits */
+ 1, /* padding always */
+ 1, /* stream output buffered */
+ -1, /* beam timeout */
+};
+
+static h2_dir_config defdconf = {
+ "default",
+ -1, /* HTTP/1 Upgrade support */
+ -1, /* HTTP/2 server push enabled */
+ NULL, /* push list */
+ -1, /* early hints, http status 103 */
+ -1, /* beam timeout */
+};
+
+void h2_config_init(apr_pool_t *pool)
+{
+ (void)pool;
+}
+
+void *h2_config_create_svr(apr_pool_t *pool, server_rec *s)
+{
+ h2_config *conf = (h2_config *)apr_pcalloc(pool, sizeof(h2_config));
+ char *name = apr_pstrcat(pool, "srv[", s->defn_name, "]", NULL);
+
+ conf->name = name;
+ conf->h2_max_streams = DEF_VAL;
+ conf->h2_window_size = DEF_VAL;
+ conf->min_workers = DEF_VAL;
+ conf->max_workers = DEF_VAL;
+ conf->idle_limit = DEF_VAL;
+ conf->stream_max_mem_size = DEF_VAL;
+ conf->h2_direct = DEF_VAL;
+ conf->modern_tls_only = DEF_VAL;
+ conf->h2_upgrade = DEF_VAL;
+ conf->tls_warmup_size = DEF_VAL;
+ conf->tls_cooldown_secs = DEF_VAL;
+ conf->h2_push = DEF_VAL;
+ conf->priorities = NULL;
+ conf->push_diary_size = DEF_VAL;
+ conf->copy_files = DEF_VAL;
+ conf->push_list = NULL;
+ conf->early_hints = DEF_VAL;
+ conf->padding_bits = DEF_VAL;
+ conf->padding_always = DEF_VAL;
+ conf->output_buffered = DEF_VAL;
+ conf->stream_timeout = DEF_VAL;
+ return conf;
+}
+
+static void *h2_config_merge(apr_pool_t *pool, void *basev, void *addv)
+{
+ h2_config *base = (h2_config *)basev;
+ h2_config *add = (h2_config *)addv;
+ h2_config *n = (h2_config *)apr_pcalloc(pool, sizeof(h2_config));
+ char *name = apr_pstrcat(pool, "merged[", add->name, ", ", base->name, "]", NULL);
+ n->name = name;
+
+ n->h2_max_streams = H2_CONFIG_GET(add, base, h2_max_streams);
+ n->h2_window_size = H2_CONFIG_GET(add, base, h2_window_size);
+ n->min_workers = H2_CONFIG_GET(add, base, min_workers);
+ n->max_workers = H2_CONFIG_GET(add, base, max_workers);
+ n->idle_limit = H2_CONFIG_GET(add, base, idle_limit);
+ n->stream_max_mem_size = H2_CONFIG_GET(add, base, stream_max_mem_size);
+ n->h2_direct = H2_CONFIG_GET(add, base, h2_direct);
+ n->modern_tls_only = H2_CONFIG_GET(add, base, modern_tls_only);
+ n->h2_upgrade = H2_CONFIG_GET(add, base, h2_upgrade);
+ n->tls_warmup_size = H2_CONFIG_GET(add, base, tls_warmup_size);
+ n->tls_cooldown_secs = H2_CONFIG_GET(add, base, tls_cooldown_secs);
+ n->h2_push = H2_CONFIG_GET(add, base, h2_push);
+ if (add->priorities && base->priorities) {
+ n->priorities = apr_hash_overlay(pool, add->priorities, base->priorities);
+ }
+ else {
+ n->priorities = add->priorities? add->priorities : base->priorities;
+ }
+ n->push_diary_size = H2_CONFIG_GET(add, base, push_diary_size);
+ n->copy_files = H2_CONFIG_GET(add, base, copy_files);
+ n->output_buffered = H2_CONFIG_GET(add, base, output_buffered);
+ if (add->push_list && base->push_list) {
+ n->push_list = apr_array_append(pool, base->push_list, add->push_list);
+ }
+ else {
+ n->push_list = add->push_list? add->push_list : base->push_list;
+ }
+ n->early_hints = H2_CONFIG_GET(add, base, early_hints);
+ n->padding_bits = H2_CONFIG_GET(add, base, padding_bits);
+ n->padding_always = H2_CONFIG_GET(add, base, padding_always);
+ n->stream_timeout = H2_CONFIG_GET(add, base, stream_timeout);
+ return n;
+}
+
+void *h2_config_merge_svr(apr_pool_t *pool, void *basev, void *addv)
+{
+ return h2_config_merge(pool, basev, addv);
+}
+
+void *h2_config_create_dir(apr_pool_t *pool, char *x)
+{
+ h2_dir_config *conf = (h2_dir_config *)apr_pcalloc(pool, sizeof(h2_dir_config));
+ const char *s = x? x : "unknown";
+ char *name = apr_pstrcat(pool, "dir[", s, "]", NULL);
+
+ conf->name = name;
+ conf->h2_upgrade = DEF_VAL;
+ conf->h2_push = DEF_VAL;
+ conf->early_hints = DEF_VAL;
+ conf->stream_timeout = DEF_VAL;
+ return conf;
+}
+
+void *h2_config_merge_dir(apr_pool_t *pool, void *basev, void *addv)
+{
+ h2_dir_config *base = (h2_dir_config *)basev;
+ h2_dir_config *add = (h2_dir_config *)addv;
+ h2_dir_config *n = (h2_dir_config *)apr_pcalloc(pool, sizeof(h2_dir_config));
+
+ n->name = apr_pstrcat(pool, "merged[", add->name, ", ", base->name, "]", NULL);
+ n->h2_upgrade = H2_CONFIG_GET(add, base, h2_upgrade);
+ n->h2_push = H2_CONFIG_GET(add, base, h2_push);
+ if (add->push_list && base->push_list) {
+ n->push_list = apr_array_append(pool, base->push_list, add->push_list);
+ }
+ else {
+ n->push_list = add->push_list? add->push_list : base->push_list;
+ }
+ n->early_hints = H2_CONFIG_GET(add, base, early_hints);
+ n->stream_timeout = H2_CONFIG_GET(add, base, stream_timeout);
+ return n;
+}
+
+static apr_int64_t h2_srv_config_geti64(const h2_config *conf, h2_config_var_t var)
+{
+ switch(var) {
+ case H2_CONF_MAX_STREAMS:
+ return H2_CONFIG_GET(conf, &defconf, h2_max_streams);
+ case H2_CONF_WIN_SIZE:
+ return H2_CONFIG_GET(conf, &defconf, h2_window_size);
+ case H2_CONF_MIN_WORKERS:
+ return H2_CONFIG_GET(conf, &defconf, min_workers);
+ case H2_CONF_MAX_WORKERS:
+ return H2_CONFIG_GET(conf, &defconf, max_workers);
+ case H2_CONF_MAX_WORKER_IDLE_LIMIT:
+ return H2_CONFIG_GET(conf, &defconf, idle_limit);
+ case H2_CONF_STREAM_MAX_MEM:
+ return H2_CONFIG_GET(conf, &defconf, stream_max_mem_size);
+ case H2_CONF_MODERN_TLS_ONLY:
+ return H2_CONFIG_GET(conf, &defconf, modern_tls_only);
+ case H2_CONF_UPGRADE:
+ return H2_CONFIG_GET(conf, &defconf, h2_upgrade);
+ case H2_CONF_DIRECT:
+ return H2_CONFIG_GET(conf, &defconf, h2_direct);
+ case H2_CONF_TLS_WARMUP_SIZE:
+ return H2_CONFIG_GET(conf, &defconf, tls_warmup_size);
+ case H2_CONF_TLS_COOLDOWN_SECS:
+ return H2_CONFIG_GET(conf, &defconf, tls_cooldown_secs);
+ case H2_CONF_PUSH:
+ return H2_CONFIG_GET(conf, &defconf, h2_push);
+ case H2_CONF_PUSH_DIARY_SIZE:
+ return H2_CONFIG_GET(conf, &defconf, push_diary_size);
+ case H2_CONF_COPY_FILES:
+ return H2_CONFIG_GET(conf, &defconf, copy_files);
+ case H2_CONF_EARLY_HINTS:
+ return H2_CONFIG_GET(conf, &defconf, early_hints);
+ case H2_CONF_PADDING_BITS:
+ return H2_CONFIG_GET(conf, &defconf, padding_bits);
+ case H2_CONF_PADDING_ALWAYS:
+ return H2_CONFIG_GET(conf, &defconf, padding_always);
+ case H2_CONF_OUTPUT_BUFFER:
+ return H2_CONFIG_GET(conf, &defconf, output_buffered);
+ case H2_CONF_STREAM_TIMEOUT:
+ return H2_CONFIG_GET(conf, &defconf, stream_timeout);
+ default:
+ return DEF_VAL;
+ }
+}
+
+static void h2_srv_config_seti(h2_config *conf, h2_config_var_t var, int val)
+{
+ switch(var) {
+ case H2_CONF_MAX_STREAMS:
+ H2_CONFIG_SET(conf, h2_max_streams, val);
+ break;
+ case H2_CONF_WIN_SIZE:
+ H2_CONFIG_SET(conf, h2_window_size, val);
+ break;
+ case H2_CONF_MIN_WORKERS:
+ H2_CONFIG_SET(conf, min_workers, val);
+ break;
+ case H2_CONF_MAX_WORKERS:
+ H2_CONFIG_SET(conf, max_workers, val);
+ break;
+ case H2_CONF_STREAM_MAX_MEM:
+ H2_CONFIG_SET(conf, stream_max_mem_size, val);
+ break;
+ case H2_CONF_MODERN_TLS_ONLY:
+ H2_CONFIG_SET(conf, modern_tls_only, val);
+ break;
+ case H2_CONF_UPGRADE:
+ H2_CONFIG_SET(conf, h2_upgrade, val);
+ break;
+ case H2_CONF_DIRECT:
+ H2_CONFIG_SET(conf, h2_direct, val);
+ break;
+ case H2_CONF_TLS_WARMUP_SIZE:
+ H2_CONFIG_SET(conf, tls_warmup_size, val);
+ break;
+ case H2_CONF_TLS_COOLDOWN_SECS:
+ H2_CONFIG_SET(conf, tls_cooldown_secs, val);
+ break;
+ case H2_CONF_PUSH:
+ H2_CONFIG_SET(conf, h2_push, val);
+ break;
+ case H2_CONF_PUSH_DIARY_SIZE:
+ H2_CONFIG_SET(conf, push_diary_size, val);
+ break;
+ case H2_CONF_COPY_FILES:
+ H2_CONFIG_SET(conf, copy_files, val);
+ break;
+ case H2_CONF_EARLY_HINTS:
+ H2_CONFIG_SET(conf, early_hints, val);
+ break;
+ case H2_CONF_PADDING_BITS:
+ H2_CONFIG_SET(conf, padding_bits, val);
+ break;
+ case H2_CONF_PADDING_ALWAYS:
+ H2_CONFIG_SET(conf, padding_always, val);
+ break;
+ case H2_CONF_OUTPUT_BUFFER:
+ H2_CONFIG_SET(conf, output_buffered, val);
+ break;
+ default:
+ break;
+ }
+}
+
+static void h2_srv_config_seti64(h2_config *conf, h2_config_var_t var, apr_int64_t val)
+{
+ switch(var) {
+ case H2_CONF_TLS_WARMUP_SIZE:
+ H2_CONFIG_SET(conf, tls_warmup_size, val);
+ break;
+ case H2_CONF_STREAM_TIMEOUT:
+ H2_CONFIG_SET(conf, stream_timeout, val);
+ break;
+ case H2_CONF_MAX_WORKER_IDLE_LIMIT:
+ H2_CONFIG_SET(conf, idle_limit, val);
+ break;
+ default:
+ h2_srv_config_seti(conf, var, (int)val);
+ break;
+ }
+}
+
+static h2_config *h2_config_sget(server_rec *s)
+{
+ h2_config *cfg = (h2_config *)ap_get_module_config(s->module_config,
+ &http2_module);
+ ap_assert(cfg);
+ return cfg;
+}
+
+static const h2_dir_config *h2_config_rget(request_rec *r)
+{
+ h2_dir_config *cfg = (h2_dir_config *)ap_get_module_config(r->per_dir_config,
+ &http2_module);
+ ap_assert(cfg);
+ return cfg;
+}
+
+static apr_int64_t h2_dir_config_geti64(const h2_dir_config *conf, h2_config_var_t var)
+{
+ switch(var) {
+ case H2_CONF_UPGRADE:
+ return H2_CONFIG_GET(conf, &defdconf, h2_upgrade);
+ case H2_CONF_PUSH:
+ return H2_CONFIG_GET(conf, &defdconf, h2_push);
+ case H2_CONF_EARLY_HINTS:
+ return H2_CONFIG_GET(conf, &defdconf, early_hints);
+ case H2_CONF_STREAM_TIMEOUT:
+ return H2_CONFIG_GET(conf, &defdconf, stream_timeout);
+
+ default:
+ return DEF_VAL;
+ }
+}
+
+static void h2_config_seti(h2_dir_config *dconf, h2_config *conf, h2_config_var_t var, int val)
+{
+ int set_srv = !dconf;
+ if (dconf) {
+ switch(var) {
+ case H2_CONF_UPGRADE:
+ H2_CONFIG_SET(dconf, h2_upgrade, val);
+ break;
+ case H2_CONF_PUSH:
+ H2_CONFIG_SET(dconf, h2_push, val);
+ break;
+ case H2_CONF_EARLY_HINTS:
+ H2_CONFIG_SET(dconf, early_hints, val);
+ break;
+ default:
+ /* not handled in dir_conf */
+ set_srv = 1;
+ break;
+ }
+ }
+
+ if (set_srv) {
+ h2_srv_config_seti(conf, var, val);
+ }
+}
+
+static void h2_config_seti64(h2_dir_config *dconf, h2_config *conf, h2_config_var_t var, apr_int64_t val)
+{
+ int set_srv = !dconf;
+ if (dconf) {
+ switch(var) {
+ case H2_CONF_STREAM_TIMEOUT:
+ H2_CONFIG_SET(dconf, stream_timeout, val);
+ break;
+ default:
+ /* not handled in dir_conf */
+ set_srv = 1;
+ break;
+ }
+ }
+
+ if (set_srv) {
+ h2_srv_config_seti64(conf, var, val);
+ }
+}
+
+static const h2_config *h2_config_get(conn_rec *c)
+{
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
+
+ if (conn_ctx && conn_ctx->server) {
+ return h2_config_sget(conn_ctx->server);
+ }
+ return h2_config_sget(c->base_server);
+}
+
+int h2_config_cgeti(conn_rec *c, h2_config_var_t var)
+{
+ return (int)h2_srv_config_geti64(h2_config_get(c), var);
+}
+
+apr_int64_t h2_config_cgeti64(conn_rec *c, h2_config_var_t var)
+{
+ return h2_srv_config_geti64(h2_config_get(c), var);
+}
+
+int h2_config_sgeti(server_rec *s, h2_config_var_t var)
+{
+ return (int)h2_srv_config_geti64(h2_config_sget(s), var);
+}
+
+apr_int64_t h2_config_sgeti64(server_rec *s, h2_config_var_t var)
+{
+ return h2_srv_config_geti64(h2_config_sget(s), var);
+}
+
+int h2_config_geti(request_rec *r, server_rec *s, h2_config_var_t var)
+{
+ return (int)h2_config_geti64(r, s, var);
+}
+
+apr_int64_t h2_config_geti64(request_rec *r, server_rec *s, h2_config_var_t var)
+{
+ apr_int64_t mode = r? (int)h2_dir_config_geti64(h2_config_rget(r), var) : DEF_VAL;
+ return (mode != DEF_VAL)? mode : h2_config_sgeti64(s, var);
+}
+
+int h2_config_rgeti(request_rec *r, h2_config_var_t var)
+{
+ return h2_config_geti(r, r->server, var);
+}
+
+apr_int64_t h2_config_rgeti64(request_rec *r, h2_config_var_t var)
+{
+ return h2_config_geti64(r, r->server, var);
+}
+
+apr_array_header_t *h2_config_push_list(request_rec *r)
+{
+ const h2_config *sconf;
+ const h2_dir_config *conf = h2_config_rget(r);
+
+ if (conf && conf->push_list) {
+ return conf->push_list;
+ }
+ sconf = h2_config_sget(r->server);
+ return sconf? sconf->push_list : NULL;
+}
+
+const struct h2_priority *h2_cconfig_get_priority(conn_rec *c, const char *content_type)
+{
+ const h2_config *conf = h2_config_get(c);
+ if (content_type && conf->priorities) {
+ apr_ssize_t len = (apr_ssize_t)strcspn(content_type, "; \t");
+ h2_priority *prio = apr_hash_get(conf->priorities, content_type, len);
+ return prio? prio : apr_hash_get(conf->priorities, "*", 1);
+ }
+ return NULL;
+}
+
+static const char *h2_conf_set_max_streams(cmd_parms *cmd,
+ void *dirconf, const char *value)
+{
+ apr_int64_t ival = (int)apr_atoi64(value);
+ if (ival < 1) {
+ return "value must be > 0";
+ }
+ CONFIG_CMD_SET64(cmd, dirconf, H2_CONF_MAX_STREAMS, ival);
+ return NULL;
+}
+
+static const char *h2_conf_set_window_size(cmd_parms *cmd,
+ void *dirconf, const char *value)
+{
+ int val = (int)apr_atoi64(value);
+ if (val < 1024) {
+ return "value must be >= 1024";
+ }
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_WIN_SIZE, val);
+ return NULL;
+}
+
+static const char *h2_conf_set_min_workers(cmd_parms *cmd,
+ void *dirconf, const char *value)
+{
+ int val = (int)apr_atoi64(value);
+ if (val < 1) {
+ return "value must be > 0";
+ }
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_MIN_WORKERS, val);
+ return NULL;
+}
+
+static const char *h2_conf_set_max_workers(cmd_parms *cmd,
+ void *dirconf, const char *value)
+{
+ int val = (int)apr_atoi64(value);
+ if (val < 1) {
+ return "value must be > 0";
+ }
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_MAX_WORKERS, val);
+ return NULL;
+}
+
+static const char *h2_conf_set_max_worker_idle_limit(cmd_parms *cmd,
+ void *dirconf, const char *value)
+{
+ apr_interval_time_t timeout;
+ apr_status_t rv = ap_timeout_parameter_parse(value, &timeout, "s");
+ if (rv != APR_SUCCESS) {
+ return "Invalid idle limit value";
+ }
+ if (timeout <= 0) {
+ timeout = DEF_VAL;
+ }
+ CONFIG_CMD_SET64(cmd, dirconf, H2_CONF_MAX_WORKER_IDLE_LIMIT, timeout);
+ return NULL;
+}
+
+static const char *h2_conf_set_stream_max_mem_size(cmd_parms *cmd,
+ void *dirconf, const char *value)
+{
+ int val = (int)apr_atoi64(value);
+ if (val < 1024) {
+ return "value must be >= 1024";
+ }
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_STREAM_MAX_MEM, val);
+ return NULL;
+}
+
+static const char *h2_conf_set_session_extra_files(cmd_parms *cmd,
+ void *dirconf, const char *value)
+{
+ /* deprecated, ignore */
+ (void)dirconf;
+ (void)value;
+ ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, cmd->pool, /* NO LOGNO */
+ "H2SessionExtraFiles is obsolete and will be ignored");
+ return NULL;
+}
+
+static const char *h2_conf_set_serialize_headers(cmd_parms *parms,
+ void *dirconf, const char *value)
+{
+ if (!strcasecmp(value, "On")) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, parms->server, APLOGNO(10307)
+ "%s: this feature has been disabled and the directive "
+ "to enable it is ignored.", parms->cmd->name);
+ }
+ return NULL;
+}
+
+static const char *h2_conf_set_direct(cmd_parms *cmd,
+ void *dirconf, const char *value)
+{
+ if (!strcasecmp(value, "On")) {
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_DIRECT, 1);
+ return NULL;
+ }
+ else if (!strcasecmp(value, "Off")) {
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_DIRECT, 0);
+ return NULL;
+ }
+ return "value must be On or Off";
+}
+
+static const char *h2_conf_set_push(cmd_parms *cmd, void *dirconf, const char *value)
+{
+ if (!strcasecmp(value, "On")) {
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_PUSH, 1);
+ return NULL;
+ }
+ else if (!strcasecmp(value, "Off")) {
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_PUSH, 0);
+ return NULL;
+ }
+ return "value must be On or Off";
+}
+
+static const char *h2_conf_add_push_priority(cmd_parms *cmd, void *_cfg,
+ const char *ctype, const char *sdependency,
+ const char *sweight)
+{
+ h2_config *cfg = (h2_config *)h2_config_sget(cmd->server);
+ const char *sdefweight = "16"; /* default AFTER weight */
+ h2_dependency dependency;
+ h2_priority *priority;
+ int weight;
+
+ (void)_cfg;
+ if (!*ctype) {
+ return "1st argument must be a mime-type, like 'text/css' or '*'";
+ }
+
+ if (!sweight) {
+ /* 2 args only, but which one? */
+ if (apr_isdigit(sdependency[0])) {
+ sweight = sdependency;
+ sdependency = "AFTER"; /* default dependency */
+ }
+ }
+
+ if (!strcasecmp("AFTER", sdependency)) {
+ dependency = H2_DEPENDANT_AFTER;
+ }
+ else if (!strcasecmp("BEFORE", sdependency)) {
+ dependency = H2_DEPENDANT_BEFORE;
+ if (sweight) {
+ return "dependency 'Before' does not allow a weight";
+ }
+ }
+ else if (!strcasecmp("INTERLEAVED", sdependency)) {
+ dependency = H2_DEPENDANT_INTERLEAVED;
+ sdefweight = "256"; /* default INTERLEAVED weight */
+ }
+ else {
+ return "dependency must be one of 'After', 'Before' or 'Interleaved'";
+ }
+
+ weight = (int)apr_atoi64(sweight? sweight : sdefweight);
+ if (weight < NGHTTP2_MIN_WEIGHT) {
+ return apr_psprintf(cmd->pool, "weight must be a number >= %d",
+ NGHTTP2_MIN_WEIGHT);
+ }
+
+ priority = apr_pcalloc(cmd->pool, sizeof(*priority));
+ priority->dependency = dependency;
+ priority->weight = weight;
+
+ if (!cfg->priorities) {
+ cfg->priorities = apr_hash_make(cmd->pool);
+ }
+ apr_hash_set(cfg->priorities, ctype, (apr_ssize_t)strlen(ctype), priority);
+ return NULL;
+}
+
+static const char *h2_conf_set_modern_tls_only(cmd_parms *cmd,
+ void *dirconf, const char *value)
+{
+ if (!strcasecmp(value, "On")) {
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_MODERN_TLS_ONLY, 1);
+ return NULL;
+ }
+ else if (!strcasecmp(value, "Off")) {
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_MODERN_TLS_ONLY, 0);
+ return NULL;
+ }
+ return "value must be On or Off";
+}
+
+static const char *h2_conf_set_upgrade(cmd_parms *cmd,
+ void *dirconf, const char *value)
+{
+ if (!strcasecmp(value, "On")) {
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_UPGRADE, 1);
+ return NULL;
+ }
+ else if (!strcasecmp(value, "Off")) {
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_UPGRADE, 0);
+ return NULL;
+ }
+ return "value must be On or Off";
+}
+
+static const char *h2_conf_set_tls_warmup_size(cmd_parms *cmd,
+ void *dirconf, const char *value)
+{
+ apr_int64_t val = apr_atoi64(value);
+ CONFIG_CMD_SET64(cmd, dirconf, H2_CONF_TLS_WARMUP_SIZE, val);
+ return NULL;
+}
+
+static const char *h2_conf_set_tls_cooldown_secs(cmd_parms *cmd,
+ void *dirconf, const char *value)
+{
+ apr_int64_t val = (int)apr_atoi64(value);
+ CONFIG_CMD_SET64(cmd, dirconf, H2_CONF_TLS_COOLDOWN_SECS, val);
+ return NULL;
+}
+
+static const char *h2_conf_set_push_diary_size(cmd_parms *cmd,
+ void *dirconf, const char *value)
+{
+ int val = (int)apr_atoi64(value);
+ if (val < 0) {
+ return "value must be >= 0";
+ }
+ if (val > 0 && (val & (val-1))) {
+ return "value must a power of 2";
+ }
+ if (val > (1 << 15)) {
+ return "value must <= 65536";
+ }
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_PUSH_DIARY_SIZE, val);
+ return NULL;
+}
+
+static const char *h2_conf_set_copy_files(cmd_parms *cmd,
+ void *dirconf, const char *value)
+{
+ if (!strcasecmp(value, "On")) {
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_COPY_FILES, 1);
+ return NULL;
+ }
+ else if (!strcasecmp(value, "Off")) {
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_COPY_FILES, 0);
+ return NULL;
+ }
+ return "value must be On or Off";
+}
+
+static void add_push(apr_array_header_t **plist, apr_pool_t *pool, h2_push_res *push)
+{
+ h2_push_res *new;
+ if (!*plist) {
+ *plist = apr_array_make(pool, 10, sizeof(*push));
+ }
+ new = apr_array_push(*plist);
+ new->uri_ref = push->uri_ref;
+ new->critical = push->critical;
+}
+
+static const char *h2_conf_add_push_res(cmd_parms *cmd, void *dirconf,
+ const char *arg1, const char *arg2,
+ const char *arg3)
+{
+ h2_push_res push;
+ const char *last = arg3;
+
+ memset(&push, 0, sizeof(push));
+ if (!strcasecmp("add", arg1)) {
+ push.uri_ref = arg2;
+ }
+ else {
+ push.uri_ref = arg1;
+ last = arg2;
+ if (arg3) {
+ return "too many parameter";
+ }
+ }
+
+ if (last) {
+ if (!strcasecmp("critical", last)) {
+ push.critical = 1;
+ }
+ else {
+ return "unknown last parameter";
+ }
+ }
+
+ if (cmd->path) {
+ add_push(&(((h2_dir_config*)dirconf)->push_list), cmd->pool, &push);
+ }
+ else {
+ add_push(&(h2_config_sget(cmd->server)->push_list), cmd->pool, &push);
+ }
+ return NULL;
+}
+
+static const char *h2_conf_set_early_hints(cmd_parms *cmd,
+ void *dirconf, const char *value)
+{
+ int val;
+
+ if (!strcasecmp(value, "On")) val = 1;
+ else if (!strcasecmp(value, "Off")) val = 0;
+ else return "value must be On or Off";
+
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_EARLY_HINTS, val);
+ if (cmd->path) {
+ ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, cmd->pool,
+ "H2EarlyHints = %d on path %s", val, cmd->path);
+ }
+ return NULL;
+}
+
+static const char *h2_conf_set_padding(cmd_parms *cmd, void *dirconf, const char *value)
+{
+ int val;
+
+ val = (int)apr_atoi64(value);
+ if (val < 0) {
+ return "number of bits must be >= 0";
+ }
+ if (val > 8) {
+ return "number of bits must be <= 8";
+ }
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_PADDING_BITS, val);
+ return NULL;
+}
+
+static const char *h2_conf_set_output_buffer(cmd_parms *cmd,
+ void *dirconf, const char *value)
+{
+ if (!strcasecmp(value, "On")) {
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_OUTPUT_BUFFER, 1);
+ return NULL;
+ }
+ else if (!strcasecmp(value, "Off")) {
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_OUTPUT_BUFFER, 0);
+ return NULL;
+ }
+ return "value must be On or Off";
+}
+
+static const char *h2_conf_set_stream_timeout(cmd_parms *cmd,
+ void *dirconf, const char *value)
+{
+ apr_status_t rv;
+ apr_interval_time_t timeout;
+
+ rv = ap_timeout_parameter_parse(value, &timeout, "s");
+ if (rv != APR_SUCCESS) {
+ return "Invalid timeout value";
+ }
+ CONFIG_CMD_SET64(cmd, dirconf, H2_CONF_STREAM_TIMEOUT, timeout);
+ return NULL;
+}
+
+void h2_get_workers_config(server_rec *s, int *pminw, int *pmaxw,
+ apr_time_t *pidle_limit)
+{
+ int threads_per_child = 0;
+
+ *pminw = h2_config_sgeti(s, H2_CONF_MIN_WORKERS);
+ *pmaxw = h2_config_sgeti(s, H2_CONF_MAX_WORKERS);
+
+ ap_mpm_query(AP_MPMQ_MAX_THREADS, &threads_per_child);
+ if (*pminw <= 0) {
+ *pminw = threads_per_child;
+ }
+ if (*pmaxw <= 0) {
+ *pmaxw = H2MAX(4, 3 * (*pminw) / 2);
+ }
+ *pidle_limit = h2_config_sgeti64(s, H2_CONF_MAX_WORKER_IDLE_LIMIT);
+}
+
+#define AP_END_CMD AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL)
+
+const command_rec h2_cmds[] = {
+ AP_INIT_TAKE1("H2MaxSessionStreams", h2_conf_set_max_streams, NULL,
+ RSRC_CONF, "maximum number of open streams per session"),
+ AP_INIT_TAKE1("H2WindowSize", h2_conf_set_window_size, NULL,
+ RSRC_CONF, "window size on client DATA"),
+ AP_INIT_TAKE1("H2MinWorkers", h2_conf_set_min_workers, NULL,
+ RSRC_CONF, "minimum number of worker threads per child"),
+ AP_INIT_TAKE1("H2MaxWorkers", h2_conf_set_max_workers, NULL,
+ RSRC_CONF, "maximum number of worker threads per child"),
+ AP_INIT_TAKE1("H2MaxWorkerIdleSeconds", h2_conf_set_max_worker_idle_limit, NULL,
+ RSRC_CONF, "maximum number of idle seconds before a worker shuts down"),
+ AP_INIT_TAKE1("H2StreamMaxMemSize", h2_conf_set_stream_max_mem_size, NULL,
+ RSRC_CONF, "maximum number of bytes buffered in memory for a stream"),
+ AP_INIT_TAKE1("H2SerializeHeaders", h2_conf_set_serialize_headers, NULL,
+ RSRC_CONF, "disabled, this directive has no longer an effect."),
+ AP_INIT_TAKE1("H2ModernTLSOnly", h2_conf_set_modern_tls_only, NULL,
+ RSRC_CONF, "off to not impose RFC 7540 restrictions on TLS"),
+ AP_INIT_TAKE1("H2Upgrade", h2_conf_set_upgrade, NULL,
+ RSRC_CONF|OR_AUTHCFG, "on to allow HTTP/1 Upgrades to h2/h2c"),
+ AP_INIT_TAKE1("H2Direct", h2_conf_set_direct, NULL,
+ RSRC_CONF, "on to enable direct HTTP/2 mode"),
+ AP_INIT_TAKE1("H2SessionExtraFiles", h2_conf_set_session_extra_files, NULL,
+ RSRC_CONF, "number of extra file a session might keep open (obsolete)"),
+ AP_INIT_TAKE1("H2TLSWarmUpSize", h2_conf_set_tls_warmup_size, NULL,
+ RSRC_CONF, "number of bytes on TLS connection before doing max writes"),
+ AP_INIT_TAKE1("H2TLSCoolDownSecs", h2_conf_set_tls_cooldown_secs, NULL,
+ RSRC_CONF, "seconds of idle time on TLS before shrinking writes"),
+ AP_INIT_TAKE1("H2Push", h2_conf_set_push, NULL,
+ RSRC_CONF|OR_AUTHCFG, "off to disable HTTP/2 server push"),
+ AP_INIT_TAKE23("H2PushPriority", h2_conf_add_push_priority, NULL,
+ RSRC_CONF, "define priority of PUSHed resources per content type"),
+ AP_INIT_TAKE1("H2PushDiarySize", h2_conf_set_push_diary_size, NULL,
+ RSRC_CONF, "size of push diary"),
+ AP_INIT_TAKE1("H2CopyFiles", h2_conf_set_copy_files, NULL,
+ OR_FILEINFO, "on to perform copy of file data"),
+ AP_INIT_TAKE123("H2PushResource", h2_conf_add_push_res, NULL,
+ OR_FILEINFO|OR_AUTHCFG, "add a resource to be pushed in this location/on this server."),
+ AP_INIT_TAKE1("H2EarlyHints", h2_conf_set_early_hints, NULL,
+ RSRC_CONF, "on to enable interim status 103 responses"),
+ AP_INIT_TAKE1("H2Padding", h2_conf_set_padding, NULL,
+ RSRC_CONF, "set payload padding"),
+ AP_INIT_TAKE1("H2OutputBuffering", h2_conf_set_output_buffer, NULL,
+ RSRC_CONF, "set stream output buffer on/off"),
+ AP_INIT_TAKE1("H2StreamTimeout", h2_conf_set_stream_timeout, NULL,
+ RSRC_CONF, "set stream timeout"),
+ AP_END_CMD
+};
+
+
diff --git a/modules/http2/h2_config.h b/modules/http2/h2_config.h
new file mode 100644
index 0000000..6d2e65f
--- /dev/null
+++ b/modules/http2/h2_config.h
@@ -0,0 +1,98 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __mod_h2__h2_config_h__
+#define __mod_h2__h2_config_h__
+
+#undef PACKAGE_VERSION
+#undef PACKAGE_TARNAME
+#undef PACKAGE_STRING
+#undef PACKAGE_NAME
+#undef PACKAGE_BUGREPORT
+
+typedef enum {
+ H2_CONF_MAX_STREAMS,
+ H2_CONF_WIN_SIZE,
+ H2_CONF_MIN_WORKERS,
+ H2_CONF_MAX_WORKERS,
+ H2_CONF_MAX_WORKER_IDLE_LIMIT,
+ H2_CONF_STREAM_MAX_MEM,
+ H2_CONF_DIRECT,
+ H2_CONF_MODERN_TLS_ONLY,
+ H2_CONF_UPGRADE,
+ H2_CONF_TLS_WARMUP_SIZE,
+ H2_CONF_TLS_COOLDOWN_SECS,
+ H2_CONF_PUSH,
+ H2_CONF_PUSH_DIARY_SIZE,
+ H2_CONF_COPY_FILES,
+ H2_CONF_EARLY_HINTS,
+ H2_CONF_PADDING_BITS,
+ H2_CONF_PADDING_ALWAYS,
+ H2_CONF_OUTPUT_BUFFER,
+ H2_CONF_STREAM_TIMEOUT,
+} h2_config_var_t;
+
+struct apr_hash_t;
+struct h2_priority;
+struct h2_push_res;
+
+typedef struct h2_push_res {
+ const char *uri_ref;
+ int critical;
+} h2_push_res;
+
+
+void *h2_config_create_dir(apr_pool_t *pool, char *x);
+void *h2_config_merge_dir(apr_pool_t *pool, void *basev, void *addv);
+void *h2_config_create_svr(apr_pool_t *pool, server_rec *s);
+void *h2_config_merge_svr(apr_pool_t *pool, void *basev, void *addv);
+
+extern const command_rec h2_cmds[];
+
+int h2_config_geti(request_rec *r, server_rec *s, h2_config_var_t var);
+apr_int64_t h2_config_geti64(request_rec *r, server_rec *s, h2_config_var_t var);
+
+/**
+ * Get the configured value for variable <var> at the given connection.
+ */
+int h2_config_cgeti(conn_rec *c, h2_config_var_t var);
+apr_int64_t h2_config_cgeti64(conn_rec *c, h2_config_var_t var);
+
+/**
+ * Get the configured value for variable <var> at the given server.
+ */
+int h2_config_sgeti(server_rec *s, h2_config_var_t var);
+apr_int64_t h2_config_sgeti64(server_rec *s, h2_config_var_t var);
+
+/**
+ * Get the configured value for variable <var> at the given request,
+ * if configured for the request location.
+ * Fallback to request server config otherwise.
+ */
+int h2_config_rgeti(request_rec *r, h2_config_var_t var);
+apr_int64_t h2_config_rgeti64(request_rec *r, h2_config_var_t var);
+
+apr_array_header_t *h2_config_push_list(request_rec *r);
+
+
+void h2_get_workers_config(server_rec *s, int *pminw, int *pmaxw,
+ apr_time_t *pidle_limit);
+void h2_config_init(apr_pool_t *pool);
+
+const struct h2_priority *h2_cconfig_get_priority(conn_rec *c, const char *content_type);
+
+#endif /* __mod_h2__h2_config_h__ */
+
diff --git a/modules/http2/h2_conn_ctx.c b/modules/http2/h2_conn_ctx.c
new file mode 100644
index 0000000..b8a0fb3
--- /dev/null
+++ b/modules/http2/h2_conn_ctx.c
@@ -0,0 +1,123 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <apr_strings.h>
+#include <apr_atomic.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_config.h>
+#include <http_log.h>
+#include <http_protocol.h>
+
+#include "h2_private.h"
+#include "h2_session.h"
+#include "h2_bucket_beam.h"
+#include "h2_c2.h"
+#include "h2_mplx.h"
+#include "h2_stream.h"
+#include "h2_util.h"
+#include "h2_conn_ctx.h"
+
+
+void h2_conn_ctx_detach(conn_rec *c)
+{
+ ap_set_module_config(c->conn_config, &http2_module, NULL);
+}
+
+static h2_conn_ctx_t *ctx_create(conn_rec *c, const char *id)
+{
+ h2_conn_ctx_t *conn_ctx = apr_pcalloc(c->pool, sizeof(*conn_ctx));
+ conn_ctx->id = id;
+ conn_ctx->server = c->base_server;
+ apr_atomic_set32(&conn_ctx->started, 1);
+ conn_ctx->started_at = apr_time_now();
+
+ ap_set_module_config(c->conn_config, &http2_module, conn_ctx);
+ return conn_ctx;
+}
+
+h2_conn_ctx_t *h2_conn_ctx_create_for_c1(conn_rec *c1, server_rec *s, const char *protocol)
+{
+ h2_conn_ctx_t *ctx;
+
+ ctx = ctx_create(c1, apr_psprintf(c1->pool, "%ld", c1->id));
+ ctx->server = s;
+ ctx->protocol = apr_pstrdup(c1->pool, protocol);
+
+ ctx->pfd.desc_type = APR_POLL_SOCKET;
+ ctx->pfd.desc.s = ap_get_conn_socket(c1);
+ ctx->pfd.reqevents = APR_POLLIN | APR_POLLERR | APR_POLLHUP;
+ ctx->pfd.client_data = ctx;
+ apr_socket_opt_set(ctx->pfd.desc.s, APR_SO_NONBLOCK, 1);
+
+ return ctx;
+}
+
+void h2_conn_ctx_assign_session(h2_conn_ctx_t *ctx, struct h2_session *session)
+{
+ ctx->session = session;
+ ctx->id = apr_psprintf(session->pool, "%d-%lu", session->child_num, (unsigned long)session->id);
+}
+
+apr_status_t h2_conn_ctx_init_for_c2(h2_conn_ctx_t **pctx, conn_rec *c2,
+ struct h2_mplx *mplx, struct h2_stream *stream,
+ struct h2_c2_transit *transit)
+{
+ h2_conn_ctx_t *conn_ctx;
+ apr_status_t rv = APR_SUCCESS;
+
+ ap_assert(c2->master);
+ conn_ctx = h2_conn_ctx_get(c2);
+ if (!conn_ctx) {
+ h2_conn_ctx_t *c1_ctx;
+
+ c1_ctx = h2_conn_ctx_get(c2->master);
+ ap_assert(c1_ctx);
+ ap_assert(c1_ctx->session);
+
+ conn_ctx = ctx_create(c2, c1_ctx->id);
+ conn_ctx->server = c2->master->base_server;
+ }
+
+ conn_ctx->mplx = mplx;
+ conn_ctx->transit = transit;
+ conn_ctx->stream_id = stream->id;
+ apr_pool_create(&conn_ctx->req_pool, c2->pool);
+ apr_pool_tag(conn_ctx->req_pool, "H2_C2_REQ");
+ conn_ctx->request = stream->request;
+ apr_atomic_set32(&conn_ctx->started, 1);
+ conn_ctx->started_at = apr_time_now();
+ conn_ctx->done = 0;
+ conn_ctx->done_at = 0;
+
+ *pctx = conn_ctx;
+ return rv;
+}
+
+void h2_conn_ctx_set_timeout(h2_conn_ctx_t *conn_ctx, apr_interval_time_t timeout)
+{
+ if (conn_ctx->beam_out) {
+ h2_beam_timeout_set(conn_ctx->beam_out, timeout);
+ }
+ if (conn_ctx->beam_in) {
+ h2_beam_timeout_set(conn_ctx->beam_in, timeout);
+ }
+ if (conn_ctx->pipe_in[H2_PIPE_OUT]) {
+ apr_file_pipe_timeout_set(conn_ctx->pipe_in[H2_PIPE_OUT], timeout);
+ }
+}
diff --git a/modules/http2/h2_conn_ctx.h b/modules/http2/h2_conn_ctx.h
new file mode 100644
index 0000000..35987bc
--- /dev/null
+++ b/modules/http2/h2_conn_ctx.h
@@ -0,0 +1,98 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __mod_h2__h2_conn_ctx__
+#define __mod_h2__h2_conn_ctx__
+
+#include "h2.h"
+
+struct h2_session;
+struct h2_stream;
+struct h2_mplx;
+struct h2_bucket_beam;
+struct h2_response_parser;
+struct h2_c2_transit;
+
+#define H2_PIPE_OUT 0
+#define H2_PIPE_IN 1
+
+/**
+ * The h2 module context associated with a connection.
+ *
+ * It keeps track of the different types of connections:
+ * - those from clients that use HTTP/2 protocol
+ * - those from clients that do not use HTTP/2
+ * - those created by ourself to perform work on HTTP/2 streams
+ */
+struct h2_conn_ctx_t {
+ const char *id; /* c*: our identifier of this connection */
+ server_rec *server; /* c*: httpd server selected. */
+ const char *protocol; /* c1: the protocol negotiated */
+ struct h2_session *session; /* c1: the h2 session established */
+ struct h2_mplx *mplx; /* c2: the multiplexer */
+ struct h2_c2_transit *transit; /* c2: transit pool and bucket_alloc */
+
+#if !AP_HAS_RESPONSE_BUCKETS
+ int pre_conn_done; /* has pre_connection setup run? */
+#endif
+ int stream_id; /* c1: 0, c2: stream id processed */
+ apr_pool_t *req_pool; /* c2: a c2 child pool for a request */
+ const struct h2_request *request; /* c2: the request to process */
+ struct h2_bucket_beam *beam_out; /* c2: data out, created from req_pool */
+ struct h2_bucket_beam *beam_in; /* c2: data in or NULL, borrowed from request stream */
+ unsigned int input_chunked; /* c2: if input needs HTTP/1.1 chunking applied */
+
+ apr_file_t *pipe_in[2]; /* c2: input produced notification pipe */
+ apr_pollfd_t pfd; /* c1: poll socket input, c2: NUL */
+
+ int has_final_response; /* final HTTP response passed on out */
+ apr_status_t last_err; /* APR_SUCCES or last error encountered in filters */
+
+ /* atomic */ apr_uint32_t started; /* c2: processing was started */
+ apr_time_t started_at; /* c2: when processing started */
+ /* atomic */ apr_uint32_t done; /* c2: processing has finished */
+ apr_time_t done_at; /* c2: when processing was done */
+};
+typedef struct h2_conn_ctx_t h2_conn_ctx_t;
+
+/**
+ * Get the h2 connection context.
+ * @param c the connection to look at
+ * @return h2 context of this connection
+ */
+#define h2_conn_ctx_get(c) \
+ ((c)? (h2_conn_ctx_t*)ap_get_module_config((c)->conn_config, &http2_module) : NULL)
+
+/**
+ * Create the h2 connection context.
+ * @param c the connection to create it at
+ * @param s the server in use
+ * @param protocol the protocol selected
+ * @return created h2 context of this connection
+ */
+h2_conn_ctx_t *h2_conn_ctx_create_for_c1(conn_rec *c, server_rec *s, const char *protocol);
+
+void h2_conn_ctx_assign_session(h2_conn_ctx_t *ctx, struct h2_session *session);
+
+apr_status_t h2_conn_ctx_init_for_c2(h2_conn_ctx_t **pctx, conn_rec *c,
+ struct h2_mplx *mplx, struct h2_stream *stream,
+ struct h2_c2_transit *transit);
+
+void h2_conn_ctx_detach(conn_rec *c);
+
+void h2_conn_ctx_set_timeout(h2_conn_ctx_t *conn_ctx, apr_interval_time_t timeout);
+
+#endif /* defined(__mod_h2__h2_conn_ctx__) */
diff --git a/modules/http2/h2_headers.c b/modules/http2/h2_headers.c
new file mode 100644
index 0000000..cbc7b01
--- /dev/null
+++ b/modules/http2/h2_headers.c
@@ -0,0 +1,207 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+
+#include <apr_strings.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_log.h>
+#include <util_time.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "h2_private.h"
+#include "h2_protocol.h"
+#include "h2_config.h"
+#include "h2_util.h"
+#include "h2_request.h"
+#include "h2_headers.h"
+
+#if !AP_HAS_RESPONSE_BUCKETS
+
+static int is_unsafe(server_rec *s)
+{
+ core_server_config *conf = ap_get_core_module_config(s->module_config);
+ return (conf->http_conformance == AP_HTTP_CONFORMANCE_UNSAFE);
+}
+
+typedef struct {
+ apr_bucket_refcount refcount;
+ h2_headers *headers;
+} h2_bucket_headers;
+
+static apr_status_t bucket_read(apr_bucket *b, const char **str,
+ apr_size_t *len, apr_read_type_e block)
+{
+ (void)b;
+ (void)block;
+ *str = NULL;
+ *len = 0;
+ return APR_SUCCESS;
+}
+
+apr_bucket * h2_bucket_headers_make(apr_bucket *b, h2_headers *r)
+{
+ h2_bucket_headers *br;
+
+ br = apr_bucket_alloc(sizeof(*br), b->list);
+ br->headers = r;
+
+ b = apr_bucket_shared_make(b, br, 0, 0);
+ b->type = &h2_bucket_type_headers;
+ b->length = 0;
+
+ return b;
+}
+
+apr_bucket * h2_bucket_headers_create(apr_bucket_alloc_t *list,
+ h2_headers *r)
+{
+ apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
+
+ APR_BUCKET_INIT(b);
+ b->free = apr_bucket_free;
+ b->list = list;
+ b = h2_bucket_headers_make(b, r);
+ return b;
+}
+
+h2_headers *h2_bucket_headers_get(apr_bucket *b)
+{
+ if (H2_BUCKET_IS_HEADERS(b)) {
+ return ((h2_bucket_headers *)b->data)->headers;
+ }
+ return NULL;
+}
+
+const apr_bucket_type_t h2_bucket_type_headers = {
+ "H2HEADERS", 5, APR_BUCKET_METADATA,
+ apr_bucket_destroy_noop,
+ bucket_read,
+ apr_bucket_setaside_noop,
+ apr_bucket_split_notimpl,
+ apr_bucket_shared_copy
+};
+
+apr_bucket *h2_bucket_headers_clone(apr_bucket *b, apr_pool_t *pool,
+ apr_bucket_alloc_t *list)
+{
+ h2_headers *hdrs = ((h2_bucket_headers *)b->data)->headers;
+ return h2_bucket_headers_create(list, h2_headers_clone(pool, hdrs));
+}
+
+
+h2_headers *h2_headers_create(int status, const apr_table_t *headers_in,
+ const apr_table_t *notes, apr_off_t raw_bytes,
+ apr_pool_t *pool)
+{
+ h2_headers *headers = apr_pcalloc(pool, sizeof(h2_headers));
+ headers->status = status;
+ headers->headers = (headers_in? apr_table_clone(pool, headers_in)
+ : apr_table_make(pool, 5));
+ headers->notes = (notes? apr_table_clone(pool, notes)
+ : apr_table_make(pool, 5));
+ return headers;
+}
+
+static int add_header_lengths(void *ctx, const char *name, const char *value)
+{
+ apr_size_t *plen = ctx;
+ *plen += strlen(name) + strlen(value);
+ return 1;
+}
+
+apr_size_t h2_headers_length(h2_headers *headers)
+{
+ apr_size_t len = 0;
+ apr_table_do(add_header_lengths, &len, headers->headers, NULL);
+ return len;
+}
+
+apr_size_t h2_bucket_headers_headers_length(apr_bucket *b)
+{
+ h2_headers *h = h2_bucket_headers_get(b);
+ return h? h2_headers_length(h) : 0;
+}
+
+h2_headers *h2_headers_rcreate(request_rec *r, int status,
+ const apr_table_t *header, apr_pool_t *pool)
+{
+ h2_headers *headers = h2_headers_create(status, header, r->notes, 0, pool);
+ if (headers->status == HTTP_FORBIDDEN) {
+ request_rec *r_prev;
+ for (r_prev = r; r_prev != NULL; r_prev = r_prev->prev) {
+ const char *cause = apr_table_get(r_prev->notes, "ssl-renegotiate-forbidden");
+ if (cause) {
+ /* This request triggered a TLS renegotiation that is not allowed
+ * in HTTP/2. Tell the client that it should use HTTP/1.1 for this.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, headers->status, r,
+ APLOGNO(10399)
+ "h2_headers(%ld): renegotiate forbidden, cause: %s",
+ (long)r->connection->id, cause);
+ headers->status = H2_ERR_HTTP_1_1_REQUIRED;
+ break;
+ }
+ }
+ }
+ if (is_unsafe(r->server)) {
+ apr_table_setn(headers->notes, H2_HDR_CONFORMANCE, H2_HDR_CONFORMANCE_UNSAFE);
+ }
+ if (h2_config_rgeti(r, H2_CONF_PUSH) == 0 && h2_config_sgeti(r->server, H2_CONF_PUSH) != 0) {
+ apr_table_setn(headers->notes, H2_PUSH_MODE_NOTE, "0");
+ }
+ return headers;
+}
+
+h2_headers *h2_headers_copy(apr_pool_t *pool, h2_headers *h)
+{
+ return h2_headers_create(h->status, h->headers, h->notes, h->raw_bytes, pool);
+}
+
+h2_headers *h2_headers_clone(apr_pool_t *pool, h2_headers *h)
+{
+ return h2_headers_create(h->status, h->headers, h->notes, h->raw_bytes, pool);
+}
+
+h2_headers *h2_headers_die(apr_status_t type,
+ const h2_request *req, apr_pool_t *pool)
+{
+ h2_headers *headers;
+ char *date;
+
+ headers = apr_pcalloc(pool, sizeof(h2_headers));
+ headers->status = (type >= 200 && type < 600)? type : 500;
+ headers->headers = apr_table_make(pool, 5);
+ headers->notes = apr_table_make(pool, 5);
+
+ date = apr_palloc(pool, APR_RFC822_DATE_LEN);
+ ap_recent_rfc822_date(date, req? req->request_time : apr_time_now());
+ apr_table_setn(headers->headers, "Date", date);
+ apr_table_setn(headers->headers, "Server", ap_get_server_banner());
+
+ return headers;
+}
+
+int h2_headers_are_final_response(h2_headers *headers)
+{
+ return headers->status >= 200;
+}
+
+#endif /* !AP_HAS_RESPONSE_BUCKETS */
diff --git a/modules/http2/h2_headers.h b/modules/http2/h2_headers.h
new file mode 100644
index 0000000..3d78dc3
--- /dev/null
+++ b/modules/http2/h2_headers.h
@@ -0,0 +1,107 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __mod_h2__h2_headers__
+#define __mod_h2__h2_headers__
+
+#include "h2.h"
+
+#if !AP_HAS_RESPONSE_BUCKETS
+
+struct h2_bucket_beam;
+
+typedef struct h2_headers h2_headers;
+struct h2_headers {
+ int status;
+ apr_table_t *headers;
+ apr_table_t *notes;
+ apr_off_t raw_bytes; /* RAW network bytes that generated this request - if known. */
+};
+
+
+extern const apr_bucket_type_t h2_bucket_type_headers;
+
+#define H2_BUCKET_IS_HEADERS(e) (e->type == &h2_bucket_type_headers)
+
+apr_bucket * h2_bucket_headers_make(apr_bucket *b, h2_headers *r);
+
+apr_bucket * h2_bucket_headers_create(apr_bucket_alloc_t *list,
+ h2_headers *r);
+
+h2_headers *h2_bucket_headers_get(apr_bucket *b);
+
+/**
+ * Create the headers from the given status and headers
+ * @param status the headers status
+ * @param header the headers of the headers
+ * @param notes the notes carried by the headers
+ * @param raw_bytes the raw network bytes (if known) used to transmit these
+ * @param pool the memory pool to use
+ */
+h2_headers *h2_headers_create(int status, const apr_table_t *header,
+ const apr_table_t *notes, apr_off_t raw_bytes,
+ apr_pool_t *pool);
+
+/**
+ * Create the headers from the given request_rec.
+ * @param r the request record which was processed
+ * @param status the headers status
+ * @param header the headers of the headers
+ * @param pool the memory pool to use
+ */
+h2_headers *h2_headers_rcreate(request_rec *r, int status,
+ const apr_table_t *header, apr_pool_t *pool);
+
+/**
+ * Copy the headers into another pool. This will not copy any
+ * header strings.
+ */
+h2_headers *h2_headers_copy(apr_pool_t *pool, h2_headers *h);
+
+/**
+ * Clone the headers into another pool. This will also clone any
+ * header strings.
+ */
+h2_headers *h2_headers_clone(apr_pool_t *pool, h2_headers *h);
+
+/**
+ * Create the headers for the given error.
+ * @param type the error code
+ * @param req the original h2_request
+ * @param pool the memory pool to use
+ */
+h2_headers *h2_headers_die(apr_status_t type,
+ const struct h2_request *req, apr_pool_t *pool);
+
+int h2_headers_are_final_response(h2_headers *headers);
+
+/**
+ * Give the number of bytes of all contained header strings.
+ */
+apr_size_t h2_headers_length(h2_headers *headers);
+
+/**
+ * For H2HEADER buckets, return the length of all contained header strings.
+ * For all other buckets, return 0.
+ */
+apr_size_t h2_bucket_headers_headers_length(apr_bucket *b);
+
+apr_bucket *h2_bucket_headers_clone(apr_bucket *b, apr_pool_t *pool,
+ apr_bucket_alloc_t *list);
+
+#endif /* !AP_HAS_RESPONSE_BUCKETS */
+
+#endif /* defined(__mod_h2__h2_headers__) */
diff --git a/modules/http2/h2_mplx.c b/modules/http2/h2_mplx.c
new file mode 100644
index 0000000..99c47ea
--- /dev/null
+++ b/modules/http2/h2_mplx.c
@@ -0,0 +1,1191 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+#include <apr_atomic.h>
+#include <apr_thread_mutex.h>
+#include <apr_thread_cond.h>
+#include <apr_strings.h>
+#include <apr_time.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_connection.h>
+#include <http_log.h>
+#include <http_protocol.h>
+
+#include <mpm_common.h>
+
+#include "mod_http2.h"
+
+#include "h2.h"
+#include "h2_private.h"
+#include "h2_bucket_beam.h"
+#include "h2_config.h"
+#include "h2_c1.h"
+#include "h2_conn_ctx.h"
+#include "h2_protocol.h"
+#include "h2_mplx.h"
+#include "h2_request.h"
+#include "h2_stream.h"
+#include "h2_session.h"
+#include "h2_c2.h"
+#include "h2_workers.h"
+#include "h2_util.h"
+
+
+/* utility for iterating over ihash stream sets */
+typedef struct {
+ h2_mplx *m;
+ h2_stream *stream;
+ apr_time_t now;
+ apr_size_t count;
+} stream_iter_ctx;
+
+static conn_rec *c2_prod_next(void *baton, int *phas_more);
+static void c2_prod_done(void *baton, conn_rec *c2);
+static void workers_shutdown(void *baton, int graceful);
+
+static void s_mplx_be_happy(h2_mplx *m, conn_rec *c, h2_conn_ctx_t *conn_ctx);
+static void m_be_annoyed(h2_mplx *m);
+
+static apr_status_t mplx_pollset_create(h2_mplx *m);
+static apr_status_t mplx_pollset_poll(h2_mplx *m, apr_interval_time_t timeout,
+ stream_ev_callback *on_stream_input,
+ stream_ev_callback *on_stream_output,
+ void *on_ctx);
+
+static apr_pool_t *pchild;
+
+/* APR callback invoked if allocation fails. */
+static int abort_on_oom(int retcode)
+{
+ ap_abort_on_oom();
+ return retcode; /* unreachable, hopefully. */
+}
+
+apr_status_t h2_mplx_c1_child_init(apr_pool_t *pool, server_rec *s)
+{
+ pchild = pool;
+ return APR_SUCCESS;
+}
+
+#define H2_MPLX_ENTER(m) \
+ do { apr_status_t rv_lock; if ((rv_lock = apr_thread_mutex_lock(m->lock)) != APR_SUCCESS) {\
+ return rv_lock;\
+ } } while(0)
+
+#define H2_MPLX_LEAVE(m) \
+ apr_thread_mutex_unlock(m->lock)
+
+#define H2_MPLX_ENTER_ALWAYS(m) \
+ apr_thread_mutex_lock(m->lock)
+
+#define H2_MPLX_ENTER_MAYBE(m, dolock) \
+ if (dolock) apr_thread_mutex_lock(m->lock)
+
+#define H2_MPLX_LEAVE_MAYBE(m, dolock) \
+ if (dolock) apr_thread_mutex_unlock(m->lock)
+
+static void c1_input_consumed(void *ctx, h2_bucket_beam *beam, apr_off_t length)
+{
+ h2_stream_in_consumed(ctx, length);
+}
+
+static int stream_is_running(h2_stream *stream)
+{
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(stream->c2);
+ return conn_ctx && apr_atomic_read32(&conn_ctx->started) != 0
+ && apr_atomic_read32(&conn_ctx->done) == 0;
+}
+
+int h2_mplx_c1_stream_is_running(h2_mplx *m, h2_stream *stream)
+{
+ int rv;
+
+ H2_MPLX_ENTER(m);
+ rv = stream_is_running(stream);
+ H2_MPLX_LEAVE(m);
+ return rv;
+}
+
+static void c1c2_stream_joined(h2_mplx *m, h2_stream *stream)
+{
+ ap_assert(!stream_is_running(stream));
+
+ h2_ihash_remove(m->shold, stream->id);
+ APR_ARRAY_PUSH(m->spurge, h2_stream *) = stream;
+}
+
+static void m_stream_cleanup(h2_mplx *m, h2_stream *stream)
+{
+ h2_conn_ctx_t *c2_ctx = h2_conn_ctx_get(stream->c2);
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
+ H2_STRM_MSG(stream, "cleanup, unsubscribing from beam events"));
+ if (c2_ctx) {
+ if (c2_ctx->beam_out) {
+ h2_beam_on_was_empty(c2_ctx->beam_out, NULL, NULL);
+ }
+ if (c2_ctx->beam_in) {
+ h2_beam_on_send(c2_ctx->beam_in, NULL, NULL);
+ h2_beam_on_received(c2_ctx->beam_in, NULL, NULL);
+ h2_beam_on_consumed(c2_ctx->beam_in, NULL, NULL);
+ }
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
+ H2_STRM_MSG(stream, "cleanup, removing from registries"));
+ ap_assert(stream->state == H2_SS_CLEANUP);
+ h2_stream_cleanup(stream);
+ h2_ihash_remove(m->streams, stream->id);
+ h2_iq_remove(m->q, stream->id);
+
+ if (c2_ctx) {
+ if (!stream_is_running(stream)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
+ H2_STRM_MSG(stream, "cleanup, c2 is done, move to spurge"));
+ /* processing has finished */
+ APR_ARRAY_PUSH(m->spurge, h2_stream *) = stream;
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
+ H2_STRM_MSG(stream, "cleanup, c2 is running, abort"));
+ /* c2 is still running */
+ h2_c2_abort(stream->c2, m->c1);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
+ H2_STRM_MSG(stream, "cleanup, c2 is done, move to shold"));
+ h2_ihash_add(m->shold, stream);
+ }
+ }
+ else {
+ /* never started */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
+ H2_STRM_MSG(stream, "cleanup, never started, move to spurge"));
+ APR_ARRAY_PUSH(m->spurge, h2_stream *) = stream;
+ }
+}
+
+static h2_c2_transit *c2_transit_create(h2_mplx *m)
+{
+ apr_allocator_t *allocator;
+ apr_pool_t *ptrans;
+ h2_c2_transit *transit;
+ apr_status_t rv;
+
+ /* We create a pool with its own allocator to be used for
+ * processing a request. This is the only way to have the processing
+ * independent of its parent pool in the sense that it can work in
+ * another thread.
+ */
+
+ rv = apr_allocator_create(&allocator);
+ if (rv == APR_SUCCESS) {
+ apr_allocator_max_free_set(allocator, ap_max_mem_free);
+ rv = apr_pool_create_ex(&ptrans, m->pool, NULL, allocator);
+ }
+ if (rv != APR_SUCCESS) {
+ /* maybe the log goes through, maybe not. */
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, m->c1,
+ APLOGNO(10004) "h2_mplx: create transit pool");
+ ap_abort_on_oom();
+ return NULL; /* should never be reached. */
+ }
+
+ apr_allocator_owner_set(allocator, ptrans);
+ apr_pool_abort_set(abort_on_oom, ptrans);
+ apr_pool_tag(ptrans, "h2_c2_transit");
+
+ transit = apr_pcalloc(ptrans, sizeof(*transit));
+ transit->pool = ptrans;
+ transit->bucket_alloc = apr_bucket_alloc_create(ptrans);
+ return transit;
+}
+
+static void c2_transit_destroy(h2_c2_transit *transit)
+{
+ apr_pool_destroy(transit->pool);
+}
+
+static h2_c2_transit *c2_transit_get(h2_mplx *m)
+{
+ h2_c2_transit **ptransit = apr_array_pop(m->c2_transits);
+ if (ptransit) {
+ return *ptransit;
+ }
+ return c2_transit_create(m);
+}
+
+static void c2_transit_recycle(h2_mplx *m, h2_c2_transit *transit)
+{
+ if (m->c2_transits->nelts >= APR_INT32_MAX ||
+ (apr_uint32_t)m->c2_transits->nelts >= m->max_spare_transits) {
+ c2_transit_destroy(transit);
+ }
+ else {
+ APR_ARRAY_PUSH(m->c2_transits, h2_c2_transit*) = transit;
+ }
+}
+
+/**
+ * A h2_mplx needs to be thread-safe *and* if will be called by
+ * the h2_session thread *and* the h2_worker threads. Therefore:
+ * - calls are protected by a mutex lock, m->lock
+ * - the pool needs its own allocator, since apr_allocator_t are
+ * not re-entrant. The separate allocator works without a
+ * separate lock since we already protect h2_mplx itself.
+ * Since HTTP/2 connections can be expected to live longer than
+ * their HTTP/1 cousins, the separate allocator seems to work better
+ * than protecting a shared h2_session one with an own lock.
+ */
+h2_mplx *h2_mplx_c1_create(int child_num, apr_uint32_t id, h2_stream *stream0,
+ server_rec *s, apr_pool_t *parent,
+ h2_workers *workers)
+{
+ h2_conn_ctx_t *conn_ctx;
+ apr_status_t status = APR_SUCCESS;
+ apr_allocator_t *allocator;
+ apr_thread_mutex_t *mutex = NULL;
+ h2_mplx *m = NULL;
+
+ m = apr_pcalloc(parent, sizeof(h2_mplx));
+ m->stream0 = stream0;
+ m->c1 = stream0->c2;
+ m->s = s;
+ m->child_num = child_num;
+ m->id = id;
+
+ /* We create a pool with its own allocator to be used for
+ * processing secondary connections. This is the only way to have the
+ * processing independent of its parent pool in the sense that it
+ * can work in another thread. Also, the new allocator needs its own
+ * mutex to synchronize sub-pools.
+ */
+ status = apr_allocator_create(&allocator);
+ if (status != APR_SUCCESS) {
+ allocator = NULL;
+ goto failure;
+ }
+
+ apr_allocator_max_free_set(allocator, ap_max_mem_free);
+ apr_pool_create_ex(&m->pool, parent, NULL, allocator);
+ if (!m->pool) goto failure;
+
+ apr_pool_tag(m->pool, "h2_mplx");
+ apr_allocator_owner_set(allocator, m->pool);
+
+ status = apr_thread_mutex_create(&mutex, APR_THREAD_MUTEX_DEFAULT,
+ m->pool);
+ if (APR_SUCCESS != status) goto failure;
+ apr_allocator_mutex_set(allocator, mutex);
+
+ status = apr_thread_mutex_create(&m->lock, APR_THREAD_MUTEX_DEFAULT,
+ m->pool);
+ if (APR_SUCCESS != status) goto failure;
+
+ m->max_streams = h2_config_sgeti(s, H2_CONF_MAX_STREAMS);
+ m->stream_max_mem = h2_config_sgeti(s, H2_CONF_STREAM_MAX_MEM);
+
+ m->streams = h2_ihash_create(m->pool, offsetof(h2_stream,id));
+ m->shold = h2_ihash_create(m->pool, offsetof(h2_stream,id));
+ m->spurge = apr_array_make(m->pool, 10, sizeof(h2_stream*));
+ m->q = h2_iq_create(m->pool, m->max_streams);
+
+ m->workers = workers;
+ m->processing_max = H2MIN(h2_workers_get_max_workers(workers), m->max_streams);
+ m->processing_limit = 6; /* the original h1 max parallel connections */
+ m->last_mood_change = apr_time_now();
+ m->mood_update_interval = apr_time_from_msec(100);
+
+ status = mplx_pollset_create(m);
+ if (APR_SUCCESS != status) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, status, m->c1, APLOGNO(10308)
+ "nghttp2: could not create pollset");
+ goto failure;
+ }
+ m->streams_ev_in = apr_array_make(m->pool, 10, sizeof(h2_stream*));
+ m->streams_ev_out = apr_array_make(m->pool, 10, sizeof(h2_stream*));
+
+ m->streams_input_read = h2_iq_create(m->pool, 10);
+ m->streams_output_written = h2_iq_create(m->pool, 10);
+ status = apr_thread_mutex_create(&m->poll_lock, APR_THREAD_MUTEX_DEFAULT,
+ m->pool);
+ if (APR_SUCCESS != status) goto failure;
+
+ conn_ctx = h2_conn_ctx_get(m->c1);
+ if (conn_ctx->pfd.reqevents) {
+ apr_pollset_add(m->pollset, &conn_ctx->pfd);
+ }
+
+ m->scratch_r = apr_pcalloc(m->pool, sizeof(*m->scratch_r));
+ m->max_spare_transits = 3;
+ m->c2_transits = apr_array_make(m->pool, (int)m->max_spare_transits,
+ sizeof(h2_c2_transit*));
+
+ m->producer = h2_workers_register(workers, m->pool,
+ apr_psprintf(m->pool, "h2-%u",
+ (unsigned int)m->id),
+ c2_prod_next, c2_prod_done,
+ workers_shutdown, m);
+ return m;
+
+failure:
+ if (m->pool) {
+ apr_pool_destroy(m->pool);
+ }
+ else if (allocator) {
+ apr_allocator_destroy(allocator);
+ }
+ return NULL;
+}
+
+int h2_mplx_c1_shutdown(h2_mplx *m)
+{
+ int max_stream_id_started = 0;
+
+ H2_MPLX_ENTER(m);
+
+ max_stream_id_started = m->max_stream_id_started;
+ /* Clear schedule queue, disabling existing streams from starting */
+ h2_iq_clear(m->q);
+
+ H2_MPLX_LEAVE(m);
+ return max_stream_id_started;
+}
+
+typedef struct {
+ h2_mplx_stream_cb *cb;
+ void *ctx;
+} stream_iter_ctx_t;
+
+static int m_stream_iter_wrap(void *ctx, void *stream)
+{
+ stream_iter_ctx_t *x = ctx;
+ return x->cb(stream, x->ctx);
+}
+
+apr_status_t h2_mplx_c1_streams_do(h2_mplx *m, h2_mplx_stream_cb *cb, void *ctx)
+{
+ stream_iter_ctx_t x;
+
+ H2_MPLX_ENTER(m);
+
+ x.cb = cb;
+ x.ctx = ctx;
+ h2_ihash_iter(m->streams, m_stream_iter_wrap, &x);
+
+ H2_MPLX_LEAVE(m);
+ return APR_SUCCESS;
+}
+
+static int m_report_stream_iter(void *ctx, void *val) {
+ h2_mplx *m = ctx;
+ h2_stream *stream = val;
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(stream->c2);
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, m->c1,
+ H2_STRM_MSG(stream, "started=%d, scheduled=%d, ready=%d, out_buffer=%ld"),
+ !!stream->c2, stream->scheduled, h2_stream_is_ready(stream),
+ (long)(stream->output? h2_beam_get_buffered(stream->output) : -1));
+ if (conn_ctx) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c1, /* NO APLOGNO */
+ H2_STRM_MSG(stream, "->03198: %s %s %s"
+ "[started=%u/done=%u]"),
+ conn_ctx->request->method, conn_ctx->request->authority,
+ conn_ctx->request->path,
+ apr_atomic_read32(&conn_ctx->started),
+ apr_atomic_read32(&conn_ctx->done));
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c1, /* NO APLOGNO */
+ H2_STRM_MSG(stream, "->03198: not started"));
+ }
+ return 1;
+}
+
+static int m_unexpected_stream_iter(void *ctx, void *val) {
+ h2_mplx *m = ctx;
+ h2_stream *stream = val;
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, m->c1, /* NO APLOGNO */
+ H2_STRM_MSG(stream, "unexpected, started=%d, scheduled=%d, ready=%d"),
+ !!stream->c2, stream->scheduled, h2_stream_is_ready(stream));
+ return 1;
+}
+
+static int m_stream_cancel_iter(void *ctx, void *val) {
+ h2_mplx *m = ctx;
+ h2_stream *stream = val;
+
+ /* take over event monitoring */
+ h2_stream_set_monitor(stream, NULL);
+ /* Reset, should transit to CLOSED state */
+ h2_stream_rst(stream, H2_ERR_NO_ERROR);
+ /* All connection data has been sent, simulate cleanup */
+ h2_stream_dispatch(stream, H2_SEV_EOS_SENT);
+ m_stream_cleanup(m, stream);
+ return 0;
+}
+
+void h2_mplx_c1_destroy(h2_mplx *m)
+{
+ apr_status_t status;
+ unsigned int i, wait_secs = 60;
+ int old_aborted;
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
+ H2_MPLX_MSG(m, "start release"));
+ /* How to shut down a h2 connection:
+ * 0. abort and tell the workers that no more work will come from us */
+ m->shutdown = m->aborted = 1;
+
+ H2_MPLX_ENTER_ALWAYS(m);
+
+ /* While really terminating any c2 connections, treat the master
+ * connection as aborted. It's not as if we could send any more data
+ * at this point. */
+ old_aborted = m->c1->aborted;
+ m->c1->aborted = 1;
+
+ /* How to shut down a h2 connection:
+ * 1. cancel all streams still active */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c1,
+ H2_MPLX_MSG(m, "release, %u/%u/%d streams (total/hold/purge), %d streams"),
+ h2_ihash_count(m->streams),
+ h2_ihash_count(m->shold),
+ m->spurge->nelts, m->processing_count);
+ while (!h2_ihash_iter(m->streams, m_stream_cancel_iter, m)) {
+ /* until empty */
+ }
+
+ /* 2. no more streams should be scheduled or in the active set */
+ ap_assert(h2_ihash_empty(m->streams));
+ ap_assert(h2_iq_empty(m->q));
+
+ /* 3. while workers are busy on this connection, meaning they
+ * are processing streams from this connection, wait on them finishing
+ * in order to wake us and let us check again.
+ * Eventually, this has to succeed. */
+ if (!m->join_wait) {
+ apr_thread_cond_create(&m->join_wait, m->pool);
+ }
+
+ for (i = 0; h2_ihash_count(m->shold) > 0; ++i) {
+ status = apr_thread_cond_timedwait(m->join_wait, m->lock, apr_time_from_sec(wait_secs));
+
+ if (APR_STATUS_IS_TIMEUP(status)) {
+ /* This can happen if we have very long running requests
+ * that do not time out on IO. */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c1, APLOGNO(03198)
+ H2_MPLX_MSG(m, "waited %u sec for %u streams"),
+ i*wait_secs, h2_ihash_count(m->shold));
+ h2_ihash_iter(m->shold, m_report_stream_iter, m);
+ }
+ }
+
+ H2_MPLX_LEAVE(m);
+ h2_workers_join(m->workers, m->producer);
+ H2_MPLX_ENTER_ALWAYS(m);
+
+ /* 4. With all workers done, all streams should be in spurge */
+ ap_assert(m->processing_count == 0);
+ if (!h2_ihash_empty(m->shold)) {
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, m->c1, APLOGNO(03516)
+ H2_MPLX_MSG(m, "unexpected %u streams in hold"),
+ h2_ihash_count(m->shold));
+ h2_ihash_iter(m->shold, m_unexpected_stream_iter, m);
+ }
+
+ m->c1->aborted = old_aborted;
+ H2_MPLX_LEAVE(m);
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c1,
+ H2_MPLX_MSG(m, "released"));
+}
+
+apr_status_t h2_mplx_c1_stream_cleanup(h2_mplx *m, h2_stream *stream,
+ unsigned int *pstream_count)
+{
+ H2_MPLX_ENTER(m);
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
+ H2_STRM_MSG(stream, "cleanup"));
+ m_stream_cleanup(m, stream);
+ *pstream_count = h2_ihash_count(m->streams);
+ H2_MPLX_LEAVE(m);
+ return APR_SUCCESS;
+}
+
+const h2_stream *h2_mplx_c2_stream_get(h2_mplx *m, int stream_id)
+{
+ h2_stream *s = NULL;
+
+ H2_MPLX_ENTER_ALWAYS(m);
+ s = h2_ihash_get(m->streams, stream_id);
+ H2_MPLX_LEAVE(m);
+
+ return s;
+}
+
+
+static void c1_update_scoreboard(h2_mplx *m, h2_stream *stream)
+{
+ if (stream->c2) {
+ m->scratch_r->connection = stream->c2;
+ m->scratch_r->bytes_sent = stream->out_frame_octets;
+ ap_increment_counts(m->c1->sbh, m->scratch_r);
+ m->scratch_r->connection = NULL;
+ }
+}
+
+static void c1_purge_streams(h2_mplx *m)
+{
+ h2_stream *stream;
+ int i;
+
+ for (i = 0; i < m->spurge->nelts; ++i) {
+ stream = APR_ARRAY_IDX(m->spurge, i, h2_stream*);
+ ap_assert(stream->state == H2_SS_CLEANUP);
+
+ c1_update_scoreboard(m, stream);
+
+ if (stream->input) {
+ h2_beam_destroy(stream->input, m->c1);
+ stream->input = NULL;
+ }
+ if (stream->c2) {
+ conn_rec *c2 = stream->c2;
+ h2_conn_ctx_t *c2_ctx = h2_conn_ctx_get(c2);
+ h2_c2_transit *transit;
+
+ stream->c2 = NULL;
+ ap_assert(c2_ctx);
+ transit = c2_ctx->transit;
+ h2_c2_destroy(c2); /* c2_ctx is gone as well */
+ if (transit) {
+ c2_transit_recycle(m, transit);
+ }
+ }
+ h2_stream_destroy(stream);
+ }
+ apr_array_clear(m->spurge);
+}
+
+apr_status_t h2_mplx_c1_poll(h2_mplx *m, apr_interval_time_t timeout,
+ stream_ev_callback *on_stream_input,
+ stream_ev_callback *on_stream_output,
+ void *on_ctx)
+{
+ apr_status_t rv;
+
+ H2_MPLX_ENTER(m);
+
+ if (m->aborted) {
+ rv = APR_ECONNABORTED;
+ goto cleanup;
+ }
+ /* Purge (destroy) streams outside of pollset processing.
+ * Streams that are registered in the pollset, will be removed
+ * when they are destroyed, but the pollset works on copies
+ * of these registrations. So, if we destroy streams while
+ * processing pollset events, we might access freed memory.
+ */
+ if (m->spurge->nelts) {
+ c1_purge_streams(m);
+ }
+ rv = mplx_pollset_poll(m, timeout, on_stream_input, on_stream_output, on_ctx);
+
+cleanup:
+ H2_MPLX_LEAVE(m);
+ return rv;
+}
+
+apr_status_t h2_mplx_c1_reprioritize(h2_mplx *m, h2_stream_pri_cmp_fn *cmp,
+ h2_session *session)
+{
+ apr_status_t status;
+
+ H2_MPLX_ENTER(m);
+
+ if (m->aborted) {
+ status = APR_ECONNABORTED;
+ }
+ else {
+ h2_iq_sort(m->q, cmp, session);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c1,
+ H2_MPLX_MSG(m, "reprioritize streams"));
+ status = APR_SUCCESS;
+ }
+
+ H2_MPLX_LEAVE(m);
+ return status;
+}
+
+static apr_status_t c1_process_stream(h2_mplx *m,
+ h2_stream *stream,
+ h2_stream_pri_cmp_fn *cmp,
+ h2_session *session)
+{
+ apr_status_t rv = APR_SUCCESS;
+
+ if (m->aborted) {
+ rv = APR_ECONNABORTED;
+ goto cleanup;
+ }
+ if (!stream->request) {
+ rv = APR_EINVAL;
+ goto cleanup;
+ }
+ if (APLOGctrace1(m->c1)) {
+ const h2_request *r = stream->request;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c1,
+ H2_STRM_MSG(stream, "process %s %s://%s%s"),
+ r->method, r->scheme, r->authority, r->path);
+ }
+
+ stream->scheduled = 1;
+ h2_ihash_add(m->streams, stream);
+ if (h2_stream_is_ready(stream)) {
+ /* already have a response */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c1,
+ H2_STRM_MSG(stream, "process, ready already"));
+ }
+ else {
+ /* last chance to set anything up before stream is processed
+ * by worker threads. */
+ rv = h2_stream_prepare_processing(stream);
+ if (APR_SUCCESS != rv) goto cleanup;
+ h2_iq_add(m->q, stream->id, cmp, session);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c1,
+ H2_STRM_MSG(stream, "process, added to q"));
+ }
+
+cleanup:
+ return rv;
+}
+
+void h2_mplx_c1_process(h2_mplx *m,
+ h2_iqueue *ready_to_process,
+ h2_stream_get_fn *get_stream,
+ h2_stream_pri_cmp_fn *stream_pri_cmp,
+ h2_session *session,
+ unsigned int *pstream_count)
+{
+ apr_status_t rv;
+ int sid;
+
+ H2_MPLX_ENTER_ALWAYS(m);
+
+ while ((sid = h2_iq_shift(ready_to_process)) > 0) {
+ h2_stream *stream = get_stream(session, sid);
+ if (stream) {
+ ap_assert(!stream->scheduled);
+ rv = c1_process_stream(session->mplx, stream, stream_pri_cmp, session);
+ if (APR_SUCCESS != rv) {
+ h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
+ }
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c1,
+ H2_MPLX_MSG(m, "stream %d not found to process"), sid);
+ }
+ }
+ if ((m->processing_count < m->processing_limit) && !h2_iq_empty(m->q)) {
+ H2_MPLX_LEAVE(m);
+ rv = h2_workers_activate(m->workers, m->producer);
+ H2_MPLX_ENTER_ALWAYS(m);
+ if (rv != APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, m->c1, APLOGNO(10021)
+ H2_MPLX_MSG(m, "activate at workers"));
+ }
+ }
+ *pstream_count = h2_ihash_count(m->streams);
+
+#if APR_POOL_DEBUG
+ do {
+ apr_size_t mem_g, mem_m, mem_s, mem_c1;
+
+ mem_g = pchild? apr_pool_num_bytes(pchild, 1) : 0;
+ mem_m = apr_pool_num_bytes(m->pool, 1);
+ mem_s = apr_pool_num_bytes(session->pool, 1);
+ mem_c1 = apr_pool_num_bytes(m->c1->pool, 1);
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, m->c1,
+ H2_MPLX_MSG(m, "child mem=%ld, mplx mem=%ld, session mem=%ld, c1=%ld"),
+ (long)mem_g, (long)mem_m, (long)mem_s, (long)mem_c1);
+
+ } while (0);
+#endif
+
+ H2_MPLX_LEAVE(m);
+}
+
+static void c2_beam_input_write_notify(void *ctx, h2_bucket_beam *beam)
+{
+ conn_rec *c = ctx;
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
+
+ (void)beam;
+ if (conn_ctx && conn_ctx->stream_id && conn_ctx->pipe_in[H2_PIPE_IN]) {
+ apr_file_putc(1, conn_ctx->pipe_in[H2_PIPE_IN]);
+ }
+}
+
+static void add_stream_poll_event(h2_mplx *m, int stream_id, h2_iqueue *q)
+{
+ apr_thread_mutex_lock(m->poll_lock);
+ if (h2_iq_append(q, stream_id) && h2_iq_count(q) == 1) {
+ /* newly added first */
+ apr_pollset_wakeup(m->pollset);
+ }
+ apr_thread_mutex_unlock(m->poll_lock);
+}
+
+static void c2_beam_input_read_notify(void *ctx, h2_bucket_beam *beam)
+{
+ conn_rec *c = ctx;
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
+
+ if (conn_ctx && conn_ctx->stream_id) {
+ add_stream_poll_event(conn_ctx->mplx, conn_ctx->stream_id,
+ conn_ctx->mplx->streams_input_read);
+ }
+}
+
+static void c2_beam_output_write_notify(void *ctx, h2_bucket_beam *beam)
+{
+ conn_rec *c = ctx;
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
+
+ if (conn_ctx && conn_ctx->stream_id) {
+ add_stream_poll_event(conn_ctx->mplx, conn_ctx->stream_id,
+ conn_ctx->mplx->streams_output_written);
+ }
+}
+
+static apr_status_t c2_setup_io(h2_mplx *m, conn_rec *c2, h2_stream *stream, h2_c2_transit *transit)
+{
+ h2_conn_ctx_t *conn_ctx;
+ apr_status_t rv = APR_SUCCESS;
+ const char *action = "init";
+
+ rv = h2_conn_ctx_init_for_c2(&conn_ctx, c2, m, stream, transit);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ if (!conn_ctx->beam_out) {
+ action = "create output beam";
+ rv = h2_beam_create(&conn_ctx->beam_out, c2, conn_ctx->req_pool,
+ stream->id, "output", 0, c2->base_server->timeout);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ h2_beam_buffer_size_set(conn_ctx->beam_out, m->stream_max_mem);
+ h2_beam_on_was_empty(conn_ctx->beam_out, c2_beam_output_write_notify, c2);
+ }
+
+ memset(&conn_ctx->pipe_in, 0, sizeof(conn_ctx->pipe_in));
+ if (stream->input) {
+ conn_ctx->beam_in = stream->input;
+ h2_beam_on_send(stream->input, c2_beam_input_write_notify, c2);
+ h2_beam_on_received(stream->input, c2_beam_input_read_notify, c2);
+ h2_beam_on_consumed(stream->input, c1_input_consumed, stream);
+#if H2_USE_PIPES
+ action = "create input write pipe";
+ rv = apr_file_pipe_create_pools(&conn_ctx->pipe_in[H2_PIPE_OUT],
+ &conn_ctx->pipe_in[H2_PIPE_IN],
+ APR_READ_BLOCK,
+ c2->pool, c2->pool);
+ if (APR_SUCCESS != rv) goto cleanup;
+#endif
+ }
+
+cleanup:
+ stream->output = (APR_SUCCESS == rv)? conn_ctx->beam_out : NULL;
+ if (APR_SUCCESS != rv) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c2,
+ H2_STRM_LOG(APLOGNO(10309), stream,
+ "error %s"), action);
+ }
+ return rv;
+}
+
+static conn_rec *s_next_c2(h2_mplx *m)
+{
+ h2_stream *stream = NULL;
+ apr_status_t rv = APR_SUCCESS;
+ apr_uint32_t sid;
+ conn_rec *c2 = NULL;
+ h2_c2_transit *transit = NULL;
+
+ while (!m->aborted && !stream && (m->processing_count < m->processing_limit)
+ && (sid = h2_iq_shift(m->q)) > 0) {
+ stream = h2_ihash_get(m->streams, sid);
+ }
+
+ if (!stream) {
+ if (m->processing_count >= m->processing_limit && !h2_iq_empty(m->q)) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c1,
+ H2_MPLX_MSG(m, "delaying request processing. "
+ "Current limit is %d and %d workers are in use."),
+ m->processing_limit, m->processing_count);
+ }
+ goto cleanup;
+ }
+
+ if (sid > m->max_stream_id_started) {
+ m->max_stream_id_started = sid;
+ }
+
+ transit = c2_transit_get(m);
+#if AP_HAS_RESPONSE_BUCKETS
+ c2 = ap_create_secondary_connection(transit->pool, m->c1, transit->bucket_alloc);
+#else
+ c2 = h2_c2_create(m->c1, transit->pool, transit->bucket_alloc);
+#endif
+ if (!c2) goto cleanup;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, m->c1,
+ H2_STRM_MSG(stream, "created new c2"));
+
+ rv = c2_setup_io(m, c2, stream, transit);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ stream->c2 = c2;
+ ++m->processing_count;
+
+cleanup:
+ if (APR_SUCCESS != rv && c2) {
+ h2_c2_destroy(c2);
+ c2 = NULL;
+ }
+ if (transit && !c2) {
+ c2_transit_recycle(m, transit);
+ }
+ return c2;
+}
+
+static conn_rec *c2_prod_next(void *baton, int *phas_more)
+{
+ h2_mplx *m = baton;
+ conn_rec *c = NULL;
+
+ H2_MPLX_ENTER_ALWAYS(m);
+ if (!m->aborted) {
+ c = s_next_c2(m);
+ *phas_more = (c != NULL && !h2_iq_empty(m->q));
+ }
+ H2_MPLX_LEAVE(m);
+ return c;
+}
+
+static void s_c2_done(h2_mplx *m, conn_rec *c2, h2_conn_ctx_t *conn_ctx)
+{
+ h2_stream *stream;
+
+ ap_assert(conn_ctx);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2,
+ "h2_mplx(%s-%d): c2 done", conn_ctx->id, conn_ctx->stream_id);
+
+ AP_DEBUG_ASSERT(apr_atomic_read32(&conn_ctx->done) == 0);
+ apr_atomic_set32(&conn_ctx->done, 1);
+ conn_ctx->done_at = apr_time_now();
+ ++c2->keepalives;
+ /* From here on, the final handling of c2 is done by c1 processing.
+ * Which means we can give it c1's scoreboard handle for updates. */
+ c2->sbh = m->c1->sbh;
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c2,
+ "h2_mplx(%s-%d): request done, %f ms elapsed",
+ conn_ctx->id, conn_ctx->stream_id,
+ (conn_ctx->done_at - conn_ctx->started_at) / 1000.0);
+
+ if (!conn_ctx->has_final_response) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, conn_ctx->last_err, c2,
+ "h2_c2(%s-%d): processing finished without final response",
+ conn_ctx->id, conn_ctx->stream_id);
+ c2->aborted = 1;
+ }
+ else if (!c2->aborted) {
+ s_mplx_be_happy(m, c2, conn_ctx);
+ }
+
+ stream = h2_ihash_get(m->streams, conn_ctx->stream_id);
+ if (stream) {
+ /* stream not done yet. trigger a potential polling on the output
+ * since nothing more will happening here. */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c2,
+ H2_STRM_MSG(stream, "c2_done, stream open"));
+ c2_beam_output_write_notify(c2, NULL);
+ }
+ else if ((stream = h2_ihash_get(m->shold, conn_ctx->stream_id)) != NULL) {
+ /* stream is done, was just waiting for this. */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c2,
+ H2_STRM_MSG(stream, "c2_done, in hold"));
+ c1c2_stream_joined(m, stream);
+ }
+ else {
+ int i;
+
+ for (i = 0; i < m->spurge->nelts; ++i) {
+ if (stream == APR_ARRAY_IDX(m->spurge, i, h2_stream*)) {
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c2,
+ H2_STRM_LOG(APLOGNO(03517), stream, "already in spurge"));
+ ap_assert("stream should not be in spurge" == NULL);
+ return;
+ }
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c2, APLOGNO(03518)
+ "h2_mplx(%s-%d): c2_done, stream not found",
+ conn_ctx->id, conn_ctx->stream_id);
+ ap_assert("stream should still be available" == NULL);
+ }
+}
+
+static void c2_prod_done(void *baton, conn_rec *c2)
+{
+ h2_mplx *m = baton;
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c2);
+
+ AP_DEBUG_ASSERT(conn_ctx);
+ H2_MPLX_ENTER_ALWAYS(m);
+
+ --m->processing_count;
+ s_c2_done(m, c2, conn_ctx);
+ if (m->join_wait) apr_thread_cond_signal(m->join_wait);
+
+ H2_MPLX_LEAVE(m);
+}
+
+static void workers_shutdown(void *baton, int graceful)
+{
+ h2_mplx *m = baton;
+
+ apr_thread_mutex_lock(m->poll_lock);
+ /* time to wakeup and assess what to do */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
+ H2_MPLX_MSG(m, "workers shutdown, waking pollset"));
+ m->shutdown = 1;
+ if (!graceful) {
+ m->aborted = 1;
+ }
+ apr_pollset_wakeup(m->pollset);
+ apr_thread_mutex_unlock(m->poll_lock);
+}
+
+/*******************************************************************************
+ * h2_mplx DoS protection
+ ******************************************************************************/
+
+static void s_mplx_be_happy(h2_mplx *m, conn_rec *c, h2_conn_ctx_t *conn_ctx)
+{
+ apr_time_t now;
+
+ if (m->processing_limit < m->processing_max
+ && conn_ctx->started_at > m->last_mood_change) {
+ --m->irritations_since;
+ if (m->processing_limit < m->processing_max
+ && ((now = apr_time_now()) - m->last_mood_change >= m->mood_update_interval
+ || m->irritations_since < -m->processing_limit)) {
+ m->processing_limit = H2MIN(m->processing_limit * 2, m->processing_max);
+ m->last_mood_change = now;
+ m->irritations_since = 0;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ H2_MPLX_MSG(m, "mood update, increasing worker limit to %d"),
+ m->processing_limit);
+ }
+ }
+}
+
+static void m_be_annoyed(h2_mplx *m)
+{
+ apr_time_t now;
+
+ if (m->processing_limit > 2) {
+ ++m->irritations_since;
+ if (((now = apr_time_now()) - m->last_mood_change >= m->mood_update_interval)
+ || (m->irritations_since >= m->processing_limit)) {
+
+ if (m->processing_limit > 16) {
+ m->processing_limit = 16;
+ }
+ else if (m->processing_limit > 8) {
+ m->processing_limit = 8;
+ }
+ else if (m->processing_limit > 4) {
+ m->processing_limit = 4;
+ }
+ else if (m->processing_limit > 2) {
+ m->processing_limit = 2;
+ }
+ m->last_mood_change = now;
+ m->irritations_since = 0;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c1,
+ H2_MPLX_MSG(m, "mood update, decreasing worker limit to %d"),
+ m->processing_limit);
+ }
+ }
+}
+
+/*******************************************************************************
+ * mplx master events dispatching
+ ******************************************************************************/
+
+static int reset_is_acceptable(h2_stream *stream)
+{
+ /* client may terminate a stream via H2 RST_STREAM message at any time.
+ * This is annyoing when we have committed resources (e.g. worker threads)
+ * to it, so our mood (e.g. willingness to commit resources on this
+ * connection in the future) goes down.
+ *
+ * This is a DoS protection. We do not want to make it too easy for
+ * a client to eat up server resources.
+ *
+ * However: there are cases where a RST_STREAM is the only way to end
+ * a request. This includes websockets and server-side-event streams (SSEs).
+ * The responses to such requests continue forever otherwise.
+ *
+ */
+ if (!stream_is_running(stream)) return 1;
+ if (!(stream->id & 0x01)) return 1; /* stream initiated by us. acceptable. */
+ if (!stream->response) return 0; /* no response headers produced yet. bad. */
+ if (!stream->out_data_frames) return 0; /* no response body data sent yet. bad. */
+ return 1; /* otherwise, be forgiving */
+}
+
+apr_status_t h2_mplx_c1_client_rst(h2_mplx *m, int stream_id)
+{
+ h2_stream *stream;
+ apr_status_t status = APR_SUCCESS;
+
+ H2_MPLX_ENTER_ALWAYS(m);
+ stream = h2_ihash_get(m->streams, stream_id);
+ if (stream && !reset_is_acceptable(stream)) {
+ m_be_annoyed(m);
+ }
+ H2_MPLX_LEAVE(m);
+ return status;
+}
+
+static apr_status_t mplx_pollset_create(h2_mplx *m)
+{
+ /* stream0 output only */
+ return apr_pollset_create(&m->pollset, 1, m->pool,
+ APR_POLLSET_WAKEABLE);
+}
+
+static apr_status_t mplx_pollset_poll(h2_mplx *m, apr_interval_time_t timeout,
+ stream_ev_callback *on_stream_input,
+ stream_ev_callback *on_stream_output,
+ void *on_ctx)
+{
+ apr_status_t rv;
+ const apr_pollfd_t *results, *pfd;
+ apr_int32_t nresults, i;
+ h2_conn_ctx_t *conn_ctx;
+ h2_stream *stream;
+
+ /* Make sure we are not called recursively. */
+ ap_assert(!m->polling);
+ m->polling = 1;
+ do {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
+ H2_MPLX_MSG(m, "enter polling timeout=%d"),
+ (int)apr_time_sec(timeout));
+
+ apr_array_clear(m->streams_ev_in);
+ apr_array_clear(m->streams_ev_out);
+
+ do {
+ /* add streams we started processing in the meantime */
+ apr_thread_mutex_lock(m->poll_lock);
+ if (!h2_iq_empty(m->streams_input_read)
+ || !h2_iq_empty(m->streams_output_written)) {
+ while ((i = h2_iq_shift(m->streams_input_read))) {
+ stream = h2_ihash_get(m->streams, i);
+ if (stream) {
+ APR_ARRAY_PUSH(m->streams_ev_in, h2_stream*) = stream;
+ }
+ }
+ while ((i = h2_iq_shift(m->streams_output_written))) {
+ stream = h2_ihash_get(m->streams, i);
+ if (stream) {
+ APR_ARRAY_PUSH(m->streams_ev_out, h2_stream*) = stream;
+ }
+ }
+ nresults = 0;
+ rv = APR_SUCCESS;
+ apr_thread_mutex_unlock(m->poll_lock);
+ break;
+ }
+ apr_thread_mutex_unlock(m->poll_lock);
+
+ H2_MPLX_LEAVE(m);
+ rv = apr_pollset_poll(m->pollset, timeout >= 0? timeout : -1, &nresults, &results);
+ H2_MPLX_ENTER_ALWAYS(m);
+ if (APR_STATUS_IS_EINTR(rv) && m->shutdown) {
+ if (!m->aborted) {
+ rv = APR_SUCCESS;
+ }
+ goto cleanup;
+ }
+ } while (APR_STATUS_IS_EINTR(rv));
+
+ if (APR_SUCCESS != rv) {
+ if (APR_STATUS_IS_TIMEUP(rv)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c1,
+ H2_MPLX_MSG(m, "polling timed out "));
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, m->c1, APLOGNO(10310) \
+ H2_MPLX_MSG(m, "polling failed"));
+ }
+ goto cleanup;
+ }
+
+ for (i = 0; i < nresults; i++) {
+ pfd = &results[i];
+ conn_ctx = pfd->client_data;
+
+ AP_DEBUG_ASSERT(conn_ctx);
+ if (conn_ctx->stream_id == 0) {
+ if (on_stream_input) {
+ APR_ARRAY_PUSH(m->streams_ev_in, h2_stream*) = m->stream0;
+ }
+ continue;
+ }
+ }
+
+ if (on_stream_input && m->streams_ev_in->nelts) {
+ H2_MPLX_LEAVE(m);
+ for (i = 0; i < m->streams_ev_in->nelts; ++i) {
+ on_stream_input(on_ctx, APR_ARRAY_IDX(m->streams_ev_in, i, h2_stream*));
+ }
+ H2_MPLX_ENTER_ALWAYS(m);
+ }
+ if (on_stream_output && m->streams_ev_out->nelts) {
+ H2_MPLX_LEAVE(m);
+ for (i = 0; i < m->streams_ev_out->nelts; ++i) {
+ on_stream_output(on_ctx, APR_ARRAY_IDX(m->streams_ev_out, i, h2_stream*));
+ }
+ H2_MPLX_ENTER_ALWAYS(m);
+ }
+ break;
+ } while(1);
+
+cleanup:
+ m->polling = 0;
+ return rv;
+}
+
diff --git a/modules/http2/h2_mplx.h b/modules/http2/h2_mplx.h
new file mode 100644
index 0000000..1f79aa8
--- /dev/null
+++ b/modules/http2/h2_mplx.h
@@ -0,0 +1,218 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __mod_h2__h2_mplx__
+#define __mod_h2__h2_mplx__
+
+/**
+ * The stream multiplexer. It performs communication between the
+ * primary HTTP/2 connection (c1) to the secondary connections (c2)
+ * that process the requests, aka. HTTP/2 streams.
+ *
+ * There is one h2_mplx instance for each h2_session.
+ *
+ * Naming Convention:
+ * "h2_mplx_c1_" are methods only to be called by the primary connection
+ * "h2_mplx_c2_" are methods only to be called by a secondary connection
+ * "h2_mplx_worker_" are methods only to be called by a h2 worker thread
+ */
+
+struct apr_pool_t;
+struct apr_thread_mutex_t;
+struct apr_thread_cond_t;
+struct h2_bucket_beam;
+struct h2_config;
+struct h2_ihash_t;
+struct h2_stream;
+struct h2_request;
+struct apr_thread_cond_t;
+struct h2_workers;
+struct h2_iqueue;
+
+#include <apr_queue.h>
+
+#include "h2_workers.h"
+
+typedef struct h2_c2_transit h2_c2_transit;
+
+struct h2_c2_transit {
+ apr_pool_t *pool;
+ apr_bucket_alloc_t *bucket_alloc;
+};
+
+typedef struct h2_mplx h2_mplx;
+
+struct h2_mplx {
+ int child_num; /* child this runs in */
+ apr_uint32_t id; /* id unique per child */
+ conn_rec *c1; /* the main connection */
+ apr_pool_t *pool;
+ struct h2_stream *stream0; /* HTTP/2's stream 0 */
+ server_rec *s; /* server for master conn */
+
+ int shutdown; /* we are shutting down */
+ int aborted; /* we need to get out of here asap */
+ int polling; /* is waiting/processing pollset events */
+ ap_conn_producer_t *producer; /* registered producer at h2_workers */
+
+ struct h2_ihash_t *streams; /* all streams active */
+ struct h2_ihash_t *shold; /* all streams done with c2 processing ongoing */
+ apr_array_header_t *spurge; /* all streams done, ready for destroy */
+
+ struct h2_iqueue *q; /* all stream ids that need to be started */
+
+ apr_size_t stream_max_mem; /* max memory to buffer for a stream */
+ apr_uint32_t max_streams; /* max # of concurrent streams */
+ apr_uint32_t max_stream_id_started; /* highest stream id that started processing */
+
+ apr_uint32_t processing_count; /* # of c2 working for this mplx */
+ apr_uint32_t processing_limit; /* current limit on processing c2s, dynamic */
+ apr_uint32_t processing_max; /* max, hard limit of processing c2s */
+
+ apr_time_t last_mood_change; /* last time, processing limit changed */
+ apr_interval_time_t mood_update_interval; /* how frequent we update at most */
+ apr_uint32_t irritations_since; /* irritations (>0) or happy events (<0) since last mood change */
+
+ apr_thread_mutex_t *lock;
+ struct apr_thread_cond_t *join_wait;
+
+ apr_pollset_t *pollset; /* pollset for c1/c2 IO events */
+ apr_array_header_t *streams_ev_in;
+ apr_array_header_t *streams_ev_out;
+
+ apr_thread_mutex_t *poll_lock; /* protect modifications of queues below */
+ struct h2_iqueue *streams_input_read; /* streams whose input has been read from */
+ struct h2_iqueue *streams_output_written; /* streams whose output has been written to */
+
+ struct h2_workers *workers; /* h2 workers process wide instance */
+
+ request_rec *scratch_r; /* pseudo request_rec for scoreboard reporting */
+
+ apr_uint32_t max_spare_transits; /* max number of transit pools idling */
+ apr_array_header_t *c2_transits; /* base pools for running c2 connections */
+};
+
+apr_status_t h2_mplx_c1_child_init(apr_pool_t *pool, server_rec *s);
+
+/**
+ * Create the multiplexer for the given HTTP2 session.
+ * Implicitly has reference count 1.
+ */
+h2_mplx *h2_mplx_c1_create(int child_id, apr_uint32_t id,
+ struct h2_stream *stream0,
+ server_rec *s, apr_pool_t *master,
+ struct h2_workers *workers);
+
+/**
+ * Destroy the mplx, shutting down all ongoing processing.
+ * @param m the mplx destroyed
+ * @param wait condition var to wait on for ref counter == 0
+ */
+void h2_mplx_c1_destroy(h2_mplx *m);
+
+/**
+ * Shut down the multiplexer gracefully. Will no longer schedule new streams
+ * but let the ongoing ones finish normally.
+ * @return the highest stream id being/been processed
+ */
+int h2_mplx_c1_shutdown(h2_mplx *m);
+
+/**
+ * Notifies mplx that a stream has been completely handled on the main
+ * connection and is ready for cleanup.
+ *
+ * @param m the mplx itself
+ * @param stream the stream ready for cleanup
+ * @param pstream_count return the number of streams active
+ */
+apr_status_t h2_mplx_c1_stream_cleanup(h2_mplx *m, struct h2_stream *stream,
+ unsigned int *pstream_count);
+
+int h2_mplx_c1_stream_is_running(h2_mplx *m, struct h2_stream *stream);
+
+/**
+ * Process a stream request.
+ *
+ * @param m the multiplexer
+ * @param read_to_process
+ * @param input_pending
+ * @param cmp the stream priority compare function
+ * @param pstream_count on return the number of streams active in mplx
+ */
+void h2_mplx_c1_process(h2_mplx *m,
+ struct h2_iqueue *read_to_process,
+ h2_stream_get_fn *get_stream,
+ h2_stream_pri_cmp_fn *cmp,
+ struct h2_session *session,
+ unsigned int *pstream_count);
+
+/**
+ * Stream priorities have changed, reschedule pending requests.
+ *
+ * @param m the multiplexer
+ * @param cmp the stream priority compare function
+ * @param ctx context data for the compare function
+ */
+apr_status_t h2_mplx_c1_reprioritize(h2_mplx *m, h2_stream_pri_cmp_fn *cmp,
+ struct h2_session *session);
+
+typedef void stream_ev_callback(void *ctx, struct h2_stream *stream);
+
+/**
+ * Poll the primary connection for input and the active streams for output.
+ * Invoke the callback for any stream where an event happened.
+ */
+apr_status_t h2_mplx_c1_poll(h2_mplx *m, apr_interval_time_t timeout,
+ stream_ev_callback *on_stream_input,
+ stream_ev_callback *on_stream_output,
+ void *on_ctx);
+
+void h2_mplx_c2_input_read(h2_mplx *m, conn_rec *c2);
+void h2_mplx_c2_output_written(h2_mplx *m, conn_rec *c2);
+
+typedef int h2_mplx_stream_cb(struct h2_stream *s, void *userdata);
+
+/**
+ * Iterate over all streams known to mplx from the primary connection.
+ * @param m the mplx
+ * @param cb the callback to invoke on each stream
+ * @param ctx userdata passed to the callback
+ */
+apr_status_t h2_mplx_c1_streams_do(h2_mplx *m, h2_mplx_stream_cb *cb, void *ctx);
+
+/**
+ * A stream has been RST_STREAM by the client. Abort
+ * any processing going on and remove from processing
+ * queue.
+ */
+apr_status_t h2_mplx_c1_client_rst(h2_mplx *m, int stream_id);
+
+/**
+ * Get readonly access to a stream for a secondary connection.
+ */
+const struct h2_stream *h2_mplx_c2_stream_get(h2_mplx *m, int stream_id);
+
+/**
+ * A h2 worker asks for a secondary connection to process.
+ * @param out_c2 non-NULL, a pointer where to reveive the next
+ * secondary connection to process.
+ */
+apr_status_t h2_mplx_worker_pop_c2(h2_mplx *m, conn_rec **out_c2);
+
+#define H2_MPLX_MSG(m, msg) \
+ "h2_mplx(%d-%lu): "msg, m->child_num, (unsigned long)m->id
+
+#endif /* defined(__mod_h2__h2_mplx__) */
diff --git a/modules/http2/h2_private.h b/modules/http2/h2_private.h
new file mode 100644
index 0000000..516be13
--- /dev/null
+++ b/modules/http2/h2_private.h
@@ -0,0 +1,28 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mod_h2_h2_private_h
+#define mod_h2_h2_private_h
+
+#include <apr_time.h>
+
+#include <nghttp2/nghttp2.h>
+
+extern module AP_MODULE_DECLARE_DATA http2_module;
+
+APLOG_USE_MODULE(http2);
+
+#endif
diff --git a/modules/http2/h2_protocol.c b/modules/http2/h2_protocol.c
new file mode 100644
index 0000000..874753e
--- /dev/null
+++ b/modules/http2/h2_protocol.c
@@ -0,0 +1,485 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+
+#include <apr_strings.h>
+#include <apr_optional.h>
+#include <apr_optional_hooks.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_config.h>
+#include <http_connection.h>
+#include <http_protocol.h>
+#include <http_request.h>
+#include <http_ssl.h>
+#include <http_log.h>
+
+#include "mod_http2.h"
+#include "h2_private.h"
+
+#include "h2_bucket_beam.h"
+#include "h2_stream.h"
+#include "h2_c2.h"
+#include "h2_config.h"
+#include "h2_conn_ctx.h"
+#include "h2_c1.h"
+#include "h2_request.h"
+#include "h2_headers.h"
+#include "h2_session.h"
+#include "h2_util.h"
+#include "h2_protocol.h"
+#include "mod_http2.h"
+
+const char *h2_protocol_ids_tls[] = {
+ "h2", NULL
+};
+
+const char *h2_protocol_ids_clear[] = {
+ "h2c", NULL
+};
+
+const char *H2_MAGIC_TOKEN = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
+
+/*******************************************************************************
+ * HTTP/2 error stuff
+ */
+static const char *h2_err_descr[] = {
+ "no error", /* 0x0 */
+ "protocol error",
+ "internal error",
+ "flow control error",
+ "settings timeout",
+ "stream closed", /* 0x5 */
+ "frame size error",
+ "refused stream",
+ "cancel",
+ "compression error",
+ "connect error", /* 0xa */
+ "enhance your calm",
+ "inadequate security",
+ "http/1.1 required",
+};
+
+const char *h2_protocol_err_description(unsigned int h2_error)
+{
+ if (h2_error < (sizeof(h2_err_descr)/sizeof(h2_err_descr[0]))) {
+ return h2_err_descr[h2_error];
+ }
+ return "unknown http/2 error code";
+}
+
+/*******************************************************************************
+ * Check connection security requirements of RFC 7540
+ */
+
+/*
+ * Black Listed Ciphers from RFC 7549 Appendix A
+ *
+ */
+static const char *RFC7540_names[] = {
+ /* ciphers with NULL encrpytion */
+ "NULL-MD5", /* TLS_NULL_WITH_NULL_NULL */
+ /* same */ /* TLS_RSA_WITH_NULL_MD5 */
+ "NULL-SHA", /* TLS_RSA_WITH_NULL_SHA */
+ "NULL-SHA256", /* TLS_RSA_WITH_NULL_SHA256 */
+ "PSK-NULL-SHA", /* TLS_PSK_WITH_NULL_SHA */
+ "DHE-PSK-NULL-SHA", /* TLS_DHE_PSK_WITH_NULL_SHA */
+ "RSA-PSK-NULL-SHA", /* TLS_RSA_PSK_WITH_NULL_SHA */
+ "PSK-NULL-SHA256", /* TLS_PSK_WITH_NULL_SHA256 */
+ "PSK-NULL-SHA384", /* TLS_PSK_WITH_NULL_SHA384 */
+ "DHE-PSK-NULL-SHA256", /* TLS_DHE_PSK_WITH_NULL_SHA256 */
+ "DHE-PSK-NULL-SHA384", /* TLS_DHE_PSK_WITH_NULL_SHA384 */
+ "RSA-PSK-NULL-SHA256", /* TLS_RSA_PSK_WITH_NULL_SHA256 */
+ "RSA-PSK-NULL-SHA384", /* TLS_RSA_PSK_WITH_NULL_SHA384 */
+ "ECDH-ECDSA-NULL-SHA", /* TLS_ECDH_ECDSA_WITH_NULL_SHA */
+ "ECDHE-ECDSA-NULL-SHA", /* TLS_ECDHE_ECDSA_WITH_NULL_SHA */
+ "ECDH-RSA-NULL-SHA", /* TLS_ECDH_RSA_WITH_NULL_SHA */
+ "ECDHE-RSA-NULL-SHA", /* TLS_ECDHE_RSA_WITH_NULL_SHA */
+ "AECDH-NULL-SHA", /* TLS_ECDH_anon_WITH_NULL_SHA */
+ "ECDHE-PSK-NULL-SHA", /* TLS_ECDHE_PSK_WITH_NULL_SHA */
+ "ECDHE-PSK-NULL-SHA256", /* TLS_ECDHE_PSK_WITH_NULL_SHA256 */
+ "ECDHE-PSK-NULL-SHA384", /* TLS_ECDHE_PSK_WITH_NULL_SHA384 */
+
+ /* DES/3DES ciphers */
+ "PSK-3DES-EDE-CBC-SHA", /* TLS_PSK_WITH_3DES_EDE_CBC_SHA */
+ "DHE-PSK-3DES-EDE-CBC-SHA", /* TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA */
+ "RSA-PSK-3DES-EDE-CBC-SHA", /* TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA */
+ "ECDH-ECDSA-DES-CBC3-SHA", /* TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA */
+ "ECDHE-ECDSA-DES-CBC3-SHA", /* TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA */
+ "ECDH-RSA-DES-CBC3-SHA", /* TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA */
+ "ECDHE-RSA-DES-CBC3-SHA", /* TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA */
+ "AECDH-DES-CBC3-SHA", /* TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA */
+ "SRP-3DES-EDE-CBC-SHA", /* TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA */
+ "SRP-RSA-3DES-EDE-CBC-SHA", /* TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA */
+ "SRP-DSS-3DES-EDE-CBC-SHA", /* TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA */
+ "ECDHE-PSK-3DES-EDE-CBC-SHA", /* TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA */
+ "DES-CBC-SHA", /* TLS_RSA_WITH_DES_CBC_SHA */
+ "DES-CBC3-SHA", /* TLS_RSA_WITH_3DES_EDE_CBC_SHA */
+ "DHE-DSS-DES-CBC3-SHA", /* TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA */
+ "DHE-RSA-DES-CBC-SHA", /* TLS_DHE_RSA_WITH_DES_CBC_SHA */
+ "DHE-RSA-DES-CBC3-SHA", /* TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA */
+ "ADH-DES-CBC-SHA", /* TLS_DH_anon_WITH_DES_CBC_SHA */
+ "ADH-DES-CBC3-SHA", /* TLS_DH_anon_WITH_3DES_EDE_CBC_SHA */
+ "EXP-DH-DSS-DES-CBC-SHA", /* TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA */
+ "DH-DSS-DES-CBC-SHA", /* TLS_DH_DSS_WITH_DES_CBC_SHA */
+ "DH-DSS-DES-CBC3-SHA", /* TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA */
+ "EXP-DH-RSA-DES-CBC-SHA", /* TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA */
+ "DH-RSA-DES-CBC-SHA", /* TLS_DH_RSA_WITH_DES_CBC_SHA */
+ "DH-RSA-DES-CBC3-SHA", /* TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA */
+
+ /* blacklisted EXPORT ciphers */
+ "EXP-RC4-MD5", /* TLS_RSA_EXPORT_WITH_RC4_40_MD5 */
+ "EXP-RC2-CBC-MD5", /* TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 */
+ "EXP-DES-CBC-SHA", /* TLS_RSA_EXPORT_WITH_DES40_CBC_SHA */
+ "EXP-DHE-DSS-DES-CBC-SHA", /* TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA */
+ "EXP-DHE-RSA-DES-CBC-SHA", /* TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA */
+ "EXP-ADH-DES-CBC-SHA", /* TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA */
+ "EXP-ADH-RC4-MD5", /* TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 */
+
+ /* blacklisted RC4 encryption */
+ "RC4-MD5", /* TLS_RSA_WITH_RC4_128_MD5 */
+ "RC4-SHA", /* TLS_RSA_WITH_RC4_128_SHA */
+ "ADH-RC4-MD5", /* TLS_DH_anon_WITH_RC4_128_MD5 */
+ "KRB5-RC4-SHA", /* TLS_KRB5_WITH_RC4_128_SHA */
+ "KRB5-RC4-MD5", /* TLS_KRB5_WITH_RC4_128_MD5 */
+ "EXP-KRB5-RC4-SHA", /* TLS_KRB5_EXPORT_WITH_RC4_40_SHA */
+ "EXP-KRB5-RC4-MD5", /* TLS_KRB5_EXPORT_WITH_RC4_40_MD5 */
+ "PSK-RC4-SHA", /* TLS_PSK_WITH_RC4_128_SHA */
+ "DHE-PSK-RC4-SHA", /* TLS_DHE_PSK_WITH_RC4_128_SHA */
+ "RSA-PSK-RC4-SHA", /* TLS_RSA_PSK_WITH_RC4_128_SHA */
+ "ECDH-ECDSA-RC4-SHA", /* TLS_ECDH_ECDSA_WITH_RC4_128_SHA */
+ "ECDHE-ECDSA-RC4-SHA", /* TLS_ECDHE_ECDSA_WITH_RC4_128_SHA */
+ "ECDH-RSA-RC4-SHA", /* TLS_ECDH_RSA_WITH_RC4_128_SHA */
+ "ECDHE-RSA-RC4-SHA", /* TLS_ECDHE_RSA_WITH_RC4_128_SHA */
+ "AECDH-RC4-SHA", /* TLS_ECDH_anon_WITH_RC4_128_SHA */
+ "ECDHE-PSK-RC4-SHA", /* TLS_ECDHE_PSK_WITH_RC4_128_SHA */
+
+ /* blacklisted AES128 encrpytion ciphers */
+ "AES128-SHA256", /* TLS_RSA_WITH_AES_128_CBC_SHA */
+ "DH-DSS-AES128-SHA", /* TLS_DH_DSS_WITH_AES_128_CBC_SHA */
+ "DH-RSA-AES128-SHA", /* TLS_DH_RSA_WITH_AES_128_CBC_SHA */
+ "DHE-DSS-AES128-SHA", /* TLS_DHE_DSS_WITH_AES_128_CBC_SHA */
+ "DHE-RSA-AES128-SHA", /* TLS_DHE_RSA_WITH_AES_128_CBC_SHA */
+ "ADH-AES128-SHA", /* TLS_DH_anon_WITH_AES_128_CBC_SHA */
+ "AES128-SHA256", /* TLS_RSA_WITH_AES_128_CBC_SHA256 */
+ "DH-DSS-AES128-SHA256", /* TLS_DH_DSS_WITH_AES_128_CBC_SHA256 */
+ "DH-RSA-AES128-SHA256", /* TLS_DH_RSA_WITH_AES_128_CBC_SHA256 */
+ "DHE-DSS-AES128-SHA256", /* TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 */
+ "DHE-RSA-AES128-SHA256", /* TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 */
+ "ECDH-ECDSA-AES128-SHA", /* TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA */
+ "ECDHE-ECDSA-AES128-SHA", /* TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA */
+ "ECDH-RSA-AES128-SHA", /* TLS_ECDH_RSA_WITH_AES_128_CBC_SHA */
+ "ECDHE-RSA-AES128-SHA", /* TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA */
+ "AECDH-AES128-SHA", /* TLS_ECDH_anon_WITH_AES_128_CBC_SHA */
+ "ECDHE-ECDSA-AES128-SHA256", /* TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 */
+ "ECDH-ECDSA-AES128-SHA256", /* TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 */
+ "ECDHE-RSA-AES128-SHA256", /* TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 */
+ "ECDH-RSA-AES128-SHA256", /* TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 */
+ "ADH-AES128-SHA256", /* TLS_DH_anon_WITH_AES_128_CBC_SHA256 */
+ "PSK-AES128-CBC-SHA", /* TLS_PSK_WITH_AES_128_CBC_SHA */
+ "DHE-PSK-AES128-CBC-SHA", /* TLS_DHE_PSK_WITH_AES_128_CBC_SHA */
+ "RSA-PSK-AES128-CBC-SHA", /* TLS_RSA_PSK_WITH_AES_128_CBC_SHA */
+ "PSK-AES128-CBC-SHA256", /* TLS_PSK_WITH_AES_128_CBC_SHA256 */
+ "DHE-PSK-AES128-CBC-SHA256", /* TLS_DHE_PSK_WITH_AES_128_CBC_SHA256 */
+ "RSA-PSK-AES128-CBC-SHA256", /* TLS_RSA_PSK_WITH_AES_128_CBC_SHA256 */
+ "ECDHE-PSK-AES128-CBC-SHA", /* TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA */
+ "ECDHE-PSK-AES128-CBC-SHA256", /* TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 */
+ "AES128-CCM", /* TLS_RSA_WITH_AES_128_CCM */
+ "AES128-CCM8", /* TLS_RSA_WITH_AES_128_CCM_8 */
+ "PSK-AES128-CCM", /* TLS_PSK_WITH_AES_128_CCM */
+ "PSK-AES128-CCM8", /* TLS_PSK_WITH_AES_128_CCM_8 */
+ "AES128-GCM-SHA256", /* TLS_RSA_WITH_AES_128_GCM_SHA256 */
+ "DH-RSA-AES128-GCM-SHA256", /* TLS_DH_RSA_WITH_AES_128_GCM_SHA256 */
+ "DH-DSS-AES128-GCM-SHA256", /* TLS_DH_DSS_WITH_AES_128_GCM_SHA256 */
+ "ADH-AES128-GCM-SHA256", /* TLS_DH_anon_WITH_AES_128_GCM_SHA256 */
+ "PSK-AES128-GCM-SHA256", /* TLS_PSK_WITH_AES_128_GCM_SHA256 */
+ "RSA-PSK-AES128-GCM-SHA256", /* TLS_RSA_PSK_WITH_AES_128_GCM_SHA256 */
+ "ECDH-ECDSA-AES128-GCM-SHA256", /* TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 */
+ "ECDH-RSA-AES128-GCM-SHA256", /* TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 */
+ "SRP-AES-128-CBC-SHA", /* TLS_SRP_SHA_WITH_AES_128_CBC_SHA */
+ "SRP-RSA-AES-128-CBC-SHA", /* TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA */
+ "SRP-DSS-AES-128-CBC-SHA", /* TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA */
+
+ /* blacklisted AES256 encrpytion ciphers */
+ "AES256-SHA", /* TLS_RSA_WITH_AES_256_CBC_SHA */
+ "DH-DSS-AES256-SHA", /* TLS_DH_DSS_WITH_AES_256_CBC_SHA */
+ "DH-RSA-AES256-SHA", /* TLS_DH_RSA_WITH_AES_256_CBC_SHA */
+ "DHE-DSS-AES256-SHA", /* TLS_DHE_DSS_WITH_AES_256_CBC_SHA */
+ "DHE-RSA-AES256-SHA", /* TLS_DHE_RSA_WITH_AES_256_CBC_SHA */
+ "ADH-AES256-SHA", /* TLS_DH_anon_WITH_AES_256_CBC_SHA */
+ "AES256-SHA256", /* TLS_RSA_WITH_AES_256_CBC_SHA256 */
+ "DH-DSS-AES256-SHA256", /* TLS_DH_DSS_WITH_AES_256_CBC_SHA256 */
+ "DH-RSA-AES256-SHA256", /* TLS_DH_RSA_WITH_AES_256_CBC_SHA256 */
+ "DHE-DSS-AES256-SHA256", /* TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 */
+ "DHE-RSA-AES256-SHA256", /* TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 */
+ "ADH-AES256-SHA256", /* TLS_DH_anon_WITH_AES_256_CBC_SHA256 */
+ "ECDH-ECDSA-AES256-SHA", /* TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA */
+ "ECDHE-ECDSA-AES256-SHA", /* TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA */
+ "ECDH-RSA-AES256-SHA", /* TLS_ECDH_RSA_WITH_AES_256_CBC_SHA */
+ "ECDHE-RSA-AES256-SHA", /* TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA */
+ "AECDH-AES256-SHA", /* TLS_ECDH_anon_WITH_AES_256_CBC_SHA */
+ "ECDHE-ECDSA-AES256-SHA384", /* TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 */
+ "ECDH-ECDSA-AES256-SHA384", /* TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 */
+ "ECDHE-RSA-AES256-SHA384", /* TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 */
+ "ECDH-RSA-AES256-SHA384", /* TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 */
+ "PSK-AES256-CBC-SHA", /* TLS_PSK_WITH_AES_256_CBC_SHA */
+ "DHE-PSK-AES256-CBC-SHA", /* TLS_DHE_PSK_WITH_AES_256_CBC_SHA */
+ "RSA-PSK-AES256-CBC-SHA", /* TLS_RSA_PSK_WITH_AES_256_CBC_SHA */
+ "PSK-AES256-CBC-SHA384", /* TLS_PSK_WITH_AES_256_CBC_SHA384 */
+ "DHE-PSK-AES256-CBC-SHA384", /* TLS_DHE_PSK_WITH_AES_256_CBC_SHA384 */
+ "RSA-PSK-AES256-CBC-SHA384", /* TLS_RSA_PSK_WITH_AES_256_CBC_SHA384 */
+ "ECDHE-PSK-AES256-CBC-SHA", /* TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA */
+ "ECDHE-PSK-AES256-CBC-SHA384", /* TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 */
+ "SRP-AES-256-CBC-SHA", /* TLS_SRP_SHA_WITH_AES_256_CBC_SHA */
+ "SRP-RSA-AES-256-CBC-SHA", /* TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA */
+ "SRP-DSS-AES-256-CBC-SHA", /* TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA */
+ "AES256-CCM", /* TLS_RSA_WITH_AES_256_CCM */
+ "AES256-CCM8", /* TLS_RSA_WITH_AES_256_CCM_8 */
+ "PSK-AES256-CCM", /* TLS_PSK_WITH_AES_256_CCM */
+ "PSK-AES256-CCM8", /* TLS_PSK_WITH_AES_256_CCM_8 */
+ "AES256-GCM-SHA384", /* TLS_RSA_WITH_AES_256_GCM_SHA384 */
+ "DH-RSA-AES256-GCM-SHA384", /* TLS_DH_RSA_WITH_AES_256_GCM_SHA384 */
+ "DH-DSS-AES256-GCM-SHA384", /* TLS_DH_DSS_WITH_AES_256_GCM_SHA384 */
+ "ADH-AES256-GCM-SHA384", /* TLS_DH_anon_WITH_AES_256_GCM_SHA384 */
+ "PSK-AES256-GCM-SHA384", /* TLS_PSK_WITH_AES_256_GCM_SHA384 */
+ "RSA-PSK-AES256-GCM-SHA384", /* TLS_RSA_PSK_WITH_AES_256_GCM_SHA384 */
+ "ECDH-ECDSA-AES256-GCM-SHA384", /* TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 */
+ "ECDH-RSA-AES256-GCM-SHA384", /* TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 */
+
+ /* blacklisted CAMELLIA128 encrpytion ciphers */
+ "CAMELLIA128-SHA", /* TLS_RSA_WITH_CAMELLIA_128_CBC_SHA */
+ "DH-DSS-CAMELLIA128-SHA", /* TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA */
+ "DH-RSA-CAMELLIA128-SHA", /* TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA */
+ "DHE-DSS-CAMELLIA128-SHA", /* TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA */
+ "DHE-RSA-CAMELLIA128-SHA", /* TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA */
+ "ADH-CAMELLIA128-SHA", /* TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA */
+ "ECDHE-ECDSA-CAMELLIA128-SHA256", /* TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 */
+ "ECDH-ECDSA-CAMELLIA128-SHA256", /* TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 */
+ "ECDHE-RSA-CAMELLIA128-SHA256", /* TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
+ "ECDH-RSA-CAMELLIA128-SHA256", /* TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
+ "PSK-CAMELLIA128-SHA256", /* TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256 */
+ "DHE-PSK-CAMELLIA128-SHA256", /* TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 */
+ "RSA-PSK-CAMELLIA128-SHA256", /* TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256 */
+ "ECDHE-PSK-CAMELLIA128-SHA256", /* TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 */
+ "CAMELLIA128-GCM-SHA256", /* TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256 */
+ "DH-RSA-CAMELLIA128-GCM-SHA256", /* TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256 */
+ "DH-DSS-CAMELLIA128-GCM-SHA256", /* TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256 */
+ "ADH-CAMELLIA128-GCM-SHA256", /* TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256 */
+ "ECDH-ECDSA-CAMELLIA128-GCM-SHA256",/* TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 */
+ "ECDH-RSA-CAMELLIA128-GCM-SHA256", /* TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256 */
+ "PSK-CAMELLIA128-GCM-SHA256", /* TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256 */
+ "RSA-PSK-CAMELLIA128-GCM-SHA256", /* TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256 */
+ "CAMELLIA128-SHA256", /* TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
+ "DH-DSS-CAMELLIA128-SHA256", /* TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256 */
+ "DH-RSA-CAMELLIA128-SHA256", /* TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
+ "DHE-DSS-CAMELLIA128-SHA256", /* TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256 */
+ "DHE-RSA-CAMELLIA128-SHA256", /* TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
+ "ADH-CAMELLIA128-SHA256", /* TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256 */
+
+ /* blacklisted CAMELLIA256 encrpytion ciphers */
+ "CAMELLIA256-SHA", /* TLS_RSA_WITH_CAMELLIA_256_CBC_SHA */
+ "DH-RSA-CAMELLIA256-SHA", /* TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA */
+ "DH-DSS-CAMELLIA256-SHA", /* TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA */
+ "DHE-DSS-CAMELLIA256-SHA", /* TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA */
+ "DHE-RSA-CAMELLIA256-SHA", /* TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA */
+ "ADH-CAMELLIA256-SHA", /* TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA */
+ "ECDHE-ECDSA-CAMELLIA256-SHA384", /* TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 */
+ "ECDH-ECDSA-CAMELLIA256-SHA384", /* TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 */
+ "ECDHE-RSA-CAMELLIA256-SHA384", /* TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384 */
+ "ECDH-RSA-CAMELLIA256-SHA384", /* TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384 */
+ "PSK-CAMELLIA256-SHA384", /* TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384 */
+ "DHE-PSK-CAMELLIA256-SHA384", /* TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 */
+ "RSA-PSK-CAMELLIA256-SHA384", /* TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384 */
+ "ECDHE-PSK-CAMELLIA256-SHA384", /* TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 */
+ "CAMELLIA256-SHA256", /* TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256 */
+ "DH-DSS-CAMELLIA256-SHA256", /* TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256 */
+ "DH-RSA-CAMELLIA256-SHA256", /* TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256 */
+ "DHE-DSS-CAMELLIA256-SHA256", /* TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256 */
+ "DHE-RSA-CAMELLIA256-SHA256", /* TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256 */
+ "ADH-CAMELLIA256-SHA256", /* TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256 */
+ "CAMELLIA256-GCM-SHA384", /* TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384 */
+ "DH-RSA-CAMELLIA256-GCM-SHA384", /* TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384 */
+ "DH-DSS-CAMELLIA256-GCM-SHA384", /* TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384 */
+ "ADH-CAMELLIA256-GCM-SHA384", /* TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384 */
+ "ECDH-ECDSA-CAMELLIA256-GCM-SHA384",/* TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 */
+ "ECDH-RSA-CAMELLIA256-GCM-SHA384", /* TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384 */
+ "PSK-CAMELLIA256-GCM-SHA384", /* TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384 */
+ "RSA-PSK-CAMELLIA256-GCM-SHA384", /* TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384 */
+
+ /* The blacklisted ARIA encrpytion ciphers */
+ "ARIA128-SHA256", /* TLS_RSA_WITH_ARIA_128_CBC_SHA256 */
+ "ARIA256-SHA384", /* TLS_RSA_WITH_ARIA_256_CBC_SHA384 */
+ "DH-DSS-ARIA128-SHA256", /* TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256 */
+ "DH-DSS-ARIA256-SHA384", /* TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384 */
+ "DH-RSA-ARIA128-SHA256", /* TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256 */
+ "DH-RSA-ARIA256-SHA384", /* TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384 */
+ "DHE-DSS-ARIA128-SHA256", /* TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256 */
+ "DHE-DSS-ARIA256-SHA384", /* TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384 */
+ "DHE-RSA-ARIA128-SHA256", /* TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256 */
+ "DHE-RSA-ARIA256-SHA384", /* TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384 */
+ "ADH-ARIA128-SHA256", /* TLS_DH_anon_WITH_ARIA_128_CBC_SHA256 */
+ "ADH-ARIA256-SHA384", /* TLS_DH_anon_WITH_ARIA_256_CBC_SHA384 */
+ "ECDHE-ECDSA-ARIA128-SHA256", /* TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256 */
+ "ECDHE-ECDSA-ARIA256-SHA384", /* TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384 */
+ "ECDH-ECDSA-ARIA128-SHA256", /* TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256 */
+ "ECDH-ECDSA-ARIA256-SHA384", /* TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384 */
+ "ECDHE-RSA-ARIA128-SHA256", /* TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256 */
+ "ECDHE-RSA-ARIA256-SHA384", /* TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384 */
+ "ECDH-RSA-ARIA128-SHA256", /* TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256 */
+ "ECDH-RSA-ARIA256-SHA384", /* TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384 */
+ "ARIA128-GCM-SHA256", /* TLS_RSA_WITH_ARIA_128_GCM_SHA256 */
+ "ARIA256-GCM-SHA384", /* TLS_RSA_WITH_ARIA_256_GCM_SHA384 */
+ "DH-DSS-ARIA128-GCM-SHA256", /* TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256 */
+ "DH-DSS-ARIA256-GCM-SHA384", /* TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384 */
+ "DH-RSA-ARIA128-GCM-SHA256", /* TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256 */
+ "DH-RSA-ARIA256-GCM-SHA384", /* TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384 */
+ "ADH-ARIA128-GCM-SHA256", /* TLS_DH_anon_WITH_ARIA_128_GCM_SHA256 */
+ "ADH-ARIA256-GCM-SHA384", /* TLS_DH_anon_WITH_ARIA_256_GCM_SHA384 */
+ "ECDH-ECDSA-ARIA128-GCM-SHA256", /* TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256 */
+ "ECDH-ECDSA-ARIA256-GCM-SHA384", /* TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384 */
+ "ECDH-RSA-ARIA128-GCM-SHA256", /* TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256 */
+ "ECDH-RSA-ARIA256-GCM-SHA384", /* TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384 */
+ "PSK-ARIA128-SHA256", /* TLS_PSK_WITH_ARIA_128_CBC_SHA256 */
+ "PSK-ARIA256-SHA384", /* TLS_PSK_WITH_ARIA_256_CBC_SHA384 */
+ "DHE-PSK-ARIA128-SHA256", /* TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256 */
+ "DHE-PSK-ARIA256-SHA384", /* TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384 */
+ "RSA-PSK-ARIA128-SHA256", /* TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256 */
+ "RSA-PSK-ARIA256-SHA384", /* TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384 */
+ "ARIA128-GCM-SHA256", /* TLS_PSK_WITH_ARIA_128_GCM_SHA256 */
+ "ARIA256-GCM-SHA384", /* TLS_PSK_WITH_ARIA_256_GCM_SHA384 */
+ "RSA-PSK-ARIA128-GCM-SHA256", /* TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256 */
+ "RSA-PSK-ARIA256-GCM-SHA384", /* TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384 */
+ "ECDHE-PSK-ARIA128-SHA256", /* TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256 */
+ "ECDHE-PSK-ARIA256-SHA384", /* TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384 */
+
+ /* blacklisted SEED encryptions */
+ "SEED-SHA", /*TLS_RSA_WITH_SEED_CBC_SHA */
+ "DH-DSS-SEED-SHA", /* TLS_DH_DSS_WITH_SEED_CBC_SHA */
+ "DH-RSA-SEED-SHA", /* TLS_DH_RSA_WITH_SEED_CBC_SHA */
+ "DHE-DSS-SEED-SHA", /* TLS_DHE_DSS_WITH_SEED_CBC_SHA */
+ "DHE-RSA-SEED-SHA", /* TLS_DHE_RSA_WITH_SEED_CBC_SHA */
+ "ADH-SEED-SHA", /* TLS_DH_anon_WITH_SEED_CBC_SHA */
+
+ /* blacklisted KRB5 ciphers */
+ "KRB5-DES-CBC-SHA", /* TLS_KRB5_WITH_DES_CBC_SHA */
+ "KRB5-DES-CBC3-SHA", /* TLS_KRB5_WITH_3DES_EDE_CBC_SHA */
+ "KRB5-IDEA-CBC-SHA", /* TLS_KRB5_WITH_IDEA_CBC_SHA */
+ "KRB5-DES-CBC-MD5", /* TLS_KRB5_WITH_DES_CBC_MD5 */
+ "KRB5-DES-CBC3-MD5", /* TLS_KRB5_WITH_3DES_EDE_CBC_MD5 */
+ "KRB5-IDEA-CBC-MD5", /* TLS_KRB5_WITH_IDEA_CBC_MD5 */
+ "EXP-KRB5-DES-CBC-SHA", /* TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA */
+ "EXP-KRB5-DES-CBC-MD5", /* TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5 */
+ "EXP-KRB5-RC2-CBC-SHA", /* TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA */
+ "EXP-KRB5-RC2-CBC-MD5", /* TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5 */
+
+ /* blacklisted exoticas */
+ "DHE-DSS-CBC-SHA", /* TLS_DHE_DSS_WITH_DES_CBC_SHA */
+ "IDEA-CBC-SHA", /* TLS_RSA_WITH_IDEA_CBC_SHA */
+
+ /* not really sure if the following names are correct */
+ "SSL3_CK_SCSV", /* TLS_EMPTY_RENEGOTIATION_INFO_SCSV */
+ "SSL3_CK_FALLBACK_SCSV"
+};
+static size_t RFC7540_names_LEN = sizeof(RFC7540_names)/sizeof(RFC7540_names[0]);
+
+
+static apr_hash_t *BLCNames;
+
+static void cipher_init(apr_pool_t *pool)
+{
+ apr_hash_t *hash = apr_hash_make(pool);
+ const char *source;
+ unsigned int i;
+
+ source = "rfc7540";
+ for (i = 0; i < RFC7540_names_LEN; ++i) {
+ apr_hash_set(hash, RFC7540_names[i], APR_HASH_KEY_STRING, source);
+ }
+
+ BLCNames = hash;
+}
+
+static int cipher_is_blacklisted(const char *cipher, const char **psource)
+{
+ *psource = apr_hash_get(BLCNames, cipher, APR_HASH_KEY_STRING);
+ return !!*psource;
+}
+
+apr_status_t h2_protocol_init(apr_pool_t *pool, server_rec *s)
+{
+ (void)pool;
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, "h2_h2, child_init");
+ cipher_init(pool);
+
+ return APR_SUCCESS;
+}
+
+int h2_protocol_is_acceptable_c1(conn_rec *c, request_rec *r, int require_all)
+{
+ int is_tls = ap_ssl_conn_is_ssl(c);
+
+ if (is_tls && h2_config_cgeti(c, H2_CONF_MODERN_TLS_ONLY) > 0) {
+ /* Check TLS connection for modern TLS parameters, as defined in
+ * RFC 7540 and https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility
+ */
+ apr_pool_t *pool = c->pool;
+ server_rec *s = c->base_server;
+ const char *val;
+
+ /* Need Tlsv1.2 or higher, rfc 7540, ch. 9.2
+ */
+ val = ap_ssl_var_lookup(pool, s, c, NULL, "SSL_PROTOCOL");
+ if (val && *val) {
+ if (strncmp("TLS", val, 3)
+ || !strcmp("TLSv1", val)
+ || !strcmp("TLSv1.1", val)) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03050)
+ "h2_h2(%ld): tls protocol not suitable: %s",
+ (long)c->id, val);
+ return 0;
+ }
+ }
+ else if (require_all) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03051)
+ "h2_h2(%ld): tls protocol is indetermined", (long)c->id);
+ return 0;
+ }
+
+ if (val && !strcmp("TLSv1.2", val)) {
+ /* Check TLS cipher blacklist, defined pre-TLSv1.3, so only
+ * checking for 1.2 */
+ val = ap_ssl_var_lookup(pool, s, c, NULL, "SSL_CIPHER");
+ if (val && *val) {
+ const char *source;
+ if (cipher_is_blacklisted(val, &source)) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03052)
+ "h2_h2(%ld): tls cipher %s blacklisted by %s",
+ (long)c->id, val, source);
+ return 0;
+ }
+ }
+ else if (require_all) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03053)
+ "h2_h2(%ld): tls cipher is indetermined", (long)c->id);
+ return 0;
+ }
+ }
+ }
+ return 1;
+}
+
diff --git a/modules/http2/h2_protocol.h b/modules/http2/h2_protocol.h
new file mode 100644
index 0000000..ed48e89
--- /dev/null
+++ b/modules/http2/h2_protocol.h
@@ -0,0 +1,56 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __mod_h2__h2_protocol__
+#define __mod_h2__h2_protocol__
+
+/**
+ * List of protocol identifiers that we support in cleartext
+ * negotiations. NULL terminated.
+ */
+extern const char *h2_protocol_ids_clear[];
+
+/**
+ * List of protocol identifiers that we support in TLS encrypted
+ * negotiations (ALPN). NULL terminated.
+ */
+extern const char *h2_protocol_ids_tls[];
+
+/**
+ * Provide a user readable description of the HTTP/2 error code-
+ * @param h2_error http/2 error code, as in rfc 7540, ch. 7
+ * @return textual description of code or that it is unknown.
+ */
+const char *h2_protocol_err_description(unsigned int h2_error);
+
+/*
+ * One time, post config initialization.
+ */
+apr_status_t h2_protocol_init(apr_pool_t *pool, server_rec *s);
+
+/**
+ * Check if the given primary connection fulfills the protocol
+ * requirements for HTTP/2.
+ * @param c the connection
+ * @param require_all != 0 iff any missing connection properties make
+ * the test fail. For example, a cipher might not have been selected while
+ * the handshake is still ongoing.
+ * @return != 0 iff protocol requirements are met
+ */
+int h2_protocol_is_acceptable_c1(conn_rec *c, request_rec *r, int require_all);
+
+
+#endif /* defined(__mod_h2__h2_protocol__) */
diff --git a/modules/http2/h2_proxy_session.c b/modules/http2/h2_proxy_session.c
new file mode 100644
index 0000000..c3f2ff3
--- /dev/null
+++ b/modules/http2/h2_proxy_session.c
@@ -0,0 +1,1719 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stddef.h>
+#include <apr_strings.h>
+#include <nghttp2/nghttp2.h>
+
+#include <mpm_common.h>
+#include <httpd.h>
+#include <http_protocol.h>
+#include <mod_proxy.h>
+
+#include "mod_http2.h"
+#include "h2.h"
+#include "h2_proxy_util.h"
+#include "h2_proxy_session.h"
+
+APLOG_USE_MODULE(proxy_http2);
+
+typedef struct h2_proxy_stream {
+ int id;
+ apr_pool_t *pool;
+ h2_proxy_session *session;
+
+ const char *url;
+ request_rec *r;
+ h2_proxy_request *req;
+ const char *real_server_uri;
+ const char *p_server_uri;
+ int standalone;
+
+ h2_proxy_stream_state_t state;
+ unsigned int suspended : 1;
+ unsigned int waiting_on_100 : 1;
+ unsigned int waiting_on_ping : 1;
+ unsigned int headers_ended : 1;
+ uint32_t error_code;
+
+ apr_bucket_brigade *input;
+ apr_off_t data_sent;
+ apr_bucket_brigade *output;
+ apr_off_t data_received;
+
+ apr_table_t *saves;
+} h2_proxy_stream;
+
+
+static void dispatch_event(h2_proxy_session *session, h2_proxys_event_t ev,
+ int arg, const char *msg);
+static void ping_arrived(h2_proxy_session *session);
+static apr_status_t check_suspended(h2_proxy_session *session);
+static void stream_resume(h2_proxy_stream *stream);
+static apr_status_t submit_trailers(h2_proxy_stream *stream);
+
+/*
+ * The H2_PING connection sub-state: a state independant of the H2_SESSION state
+ * of the connection:
+ * - H2_PING_ST_NONE: no interference with request handling, ProxyTimeout in effect.
+ * When entered, all suspended streams are unsuspended again.
+ * - H2_PING_ST_AWAIT_ANY: new requests are suspended, a possibly configured "ping"
+ * timeout is in effect. Any frame received transits to H2_PING_ST_NONE.
+ * - H2_PING_ST_AWAIT_PING: same as above, but only a PING frame transits
+ * to H2_PING_ST_NONE.
+ *
+ * An AWAIT state is entered on a new connection or when re-using a connection and
+ * the last frame received has been some time ago. The latter sends a PING frame
+ * and insists on an answer, the former is satisfied by any frame received from the
+ * backend.
+ *
+ * This works for new connections as there is always at least one SETTINGS frame
+ * that the backend sends. When re-using connection, we send a PING and insist on
+ * receiving one back, as there might be frames in our connection buffers from
+ * some time ago. Since some servers have protections against PING flooding, we
+ * only ever have one PING unanswered.
+ *
+ * Requests are suspended while in a PING state, as we do not want to send data
+ * before we can be reasonably sure that the connection is working (at least on
+ * the h2 protocol level). This also means that the session can do blocking reads
+ * when expecting PING answers.
+ */
+static void set_ping_timeout(h2_proxy_session *session)
+{
+ if (session->ping_timeout != -1 && session->save_timeout == -1) {
+ apr_socket_t *socket = NULL;
+
+ socket = ap_get_conn_socket(session->c);
+ if (socket) {
+ apr_socket_timeout_get(socket, &session->save_timeout);
+ apr_socket_timeout_set(socket, session->ping_timeout);
+ }
+ }
+}
+
+static void unset_ping_timeout(h2_proxy_session *session)
+{
+ if (session->save_timeout != -1) {
+ apr_socket_t *socket = NULL;
+
+ socket = ap_get_conn_socket(session->c);
+ if (socket) {
+ apr_socket_timeout_set(socket, session->save_timeout);
+ session->save_timeout = -1;
+ }
+ }
+}
+
+static void enter_ping_state(h2_proxy_session *session, h2_ping_state_t state)
+{
+ if (session->ping_state == state) return;
+ switch (session->ping_state) {
+ case H2_PING_ST_NONE:
+ /* leaving NONE, enforce timeout, send frame maybe */
+ if (H2_PING_ST_AWAIT_PING == state) {
+ unset_ping_timeout(session);
+ nghttp2_submit_ping(session->ngh2, 0, (const uint8_t *)"nevergonnagiveyouup");
+ }
+ set_ping_timeout(session);
+ session->ping_state = state;
+ break;
+ default:
+ /* no switching between the != NONE states */
+ if (H2_PING_ST_NONE == state) {
+ session->ping_state = state;
+ unset_ping_timeout(session);
+ ping_arrived(session);
+ }
+ break;
+ }
+}
+
+static void ping_new_session(h2_proxy_session *session, proxy_conn_rec *p_conn)
+{
+ session->save_timeout = -1;
+ session->ping_timeout = (p_conn->worker->s->ping_timeout_set?
+ p_conn->worker->s->ping_timeout : -1);
+ session->ping_state = H2_PING_ST_NONE;
+ enter_ping_state(session, H2_PING_ST_AWAIT_ANY);
+}
+
+static void ping_reuse_session(h2_proxy_session *session)
+{
+ if (H2_PING_ST_NONE == session->ping_state) {
+ apr_interval_time_t age = apr_time_now() - session->last_frame_received;
+ if (age > apr_time_from_sec(1)) {
+ enter_ping_state(session, H2_PING_ST_AWAIT_PING);
+ }
+ }
+}
+
+static void ping_ev_frame_received(h2_proxy_session *session, const nghttp2_frame *frame)
+{
+ session->last_frame_received = apr_time_now();
+ switch (session->ping_state) {
+ case H2_PING_ST_NONE:
+ /* nop */
+ break;
+ case H2_PING_ST_AWAIT_ANY:
+ enter_ping_state(session, H2_PING_ST_NONE);
+ break;
+ case H2_PING_ST_AWAIT_PING:
+ if (NGHTTP2_PING == frame->hd.type) {
+ enter_ping_state(session, H2_PING_ST_NONE);
+ }
+ /* we may receive many other frames while we are waiting for the
+ * PING answer. They may come all from our connection buffers and
+ * say nothing about the current state of the backend. */
+ break;
+ }
+}
+
+static apr_status_t proxy_session_pre_close(void *theconn)
+{
+ proxy_conn_rec *p_conn = (proxy_conn_rec *)theconn;
+ h2_proxy_session *session = p_conn->data;
+
+ if (session && session->ngh2) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
+ "proxy_session(%s): pool cleanup, state=%d, streams=%d",
+ session->id, session->state,
+ (int)h2_proxy_ihash_count(session->streams));
+ session->aborted = 1;
+ dispatch_event(session, H2_PROXYS_EV_PRE_CLOSE, 0, NULL);
+ nghttp2_session_del(session->ngh2);
+ session->ngh2 = NULL;
+ p_conn->data = NULL;
+ }
+ return APR_SUCCESS;
+}
+
+static int proxy_pass_brigade(apr_bucket_alloc_t *bucket_alloc,
+ proxy_conn_rec *p_conn,
+ conn_rec *origin, apr_bucket_brigade *bb,
+ int flush)
+{
+ apr_status_t status;
+ apr_off_t transferred;
+
+ if (flush) {
+ apr_bucket *e = apr_bucket_flush_create(bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ }
+ apr_brigade_length(bb, 0, &transferred);
+ if (transferred != -1)
+ p_conn->worker->s->transferred += transferred;
+ status = ap_pass_brigade(origin->output_filters, bb);
+ /* Cleanup the brigade now to avoid buckets lifetime
+ * issues in case of error returned below. */
+ apr_brigade_cleanup(bb);
+ if (status != APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, origin, APLOGNO(03357)
+ "pass output failed to %pI (%s)",
+ p_conn->addr, p_conn->hostname);
+ }
+ return status;
+}
+
+static ssize_t raw_send(nghttp2_session *ngh2, const uint8_t *data,
+ size_t length, int flags, void *user_data)
+{
+ h2_proxy_session *session = user_data;
+ apr_bucket *b;
+ apr_status_t status;
+ int flush = 1;
+
+ if (data) {
+ b = apr_bucket_transient_create((const char*)data, length,
+ session->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(session->output, b);
+ }
+
+ status = proxy_pass_brigade(session->c->bucket_alloc,
+ session->p_conn, session->c,
+ session->output, flush);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c,
+ "h2_proxy_sesssion(%s): raw_send %d bytes, flush=%d",
+ session->id, (int)length, flush);
+ if (status != APR_SUCCESS) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ return length;
+}
+
+static int on_frame_recv(nghttp2_session *ngh2, const nghttp2_frame *frame,
+ void *user_data)
+{
+ h2_proxy_session *session = user_data;
+ h2_proxy_stream *stream;
+ request_rec *r;
+ int n;
+
+ if (APLOGcdebug(session->c)) {
+ char buffer[256];
+
+ h2_proxy_util_frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03341)
+ "h2_proxy_session(%s): recv FRAME[%s]",
+ session->id, buffer);
+ }
+
+ ping_ev_frame_received(session, frame);
+ /* Action for frame types: */
+ switch (frame->hd.type) {
+ case NGHTTP2_HEADERS:
+ stream = nghttp2_session_get_stream_user_data(ngh2, frame->hd.stream_id);
+ if (!stream) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ r = stream->r;
+ if (r->status >= 100 && r->status < 200) {
+ /* By default, we will forward all interim responses when
+ * we are sitting on a HTTP/2 connection to the client */
+ int forward = session->h2_front;
+ switch(r->status) {
+ case 100:
+ if (stream->waiting_on_100) {
+ stream->waiting_on_100 = 0;
+ r->status_line = ap_get_status_line(r->status);
+ forward = 1;
+ }
+ break;
+ case 103:
+ /* workaround until we get this into http protocol base
+ * parts. without this, unknown codes are converted to
+ * 500... */
+ r->status_line = "103 Early Hints";
+ break;
+ default:
+ r->status_line = ap_get_status_line(r->status);
+ break;
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03487)
+ "h2_proxy_session(%s): got interim HEADERS, "
+ "status=%d, will forward=%d",
+ session->id, r->status, forward);
+ if (forward) {
+ ap_send_interim_response(r, 1);
+ }
+ }
+ stream_resume(stream);
+ break;
+ case NGHTTP2_PING:
+ break;
+ case NGHTTP2_PUSH_PROMISE:
+ break;
+ case NGHTTP2_SETTINGS:
+ if (frame->settings.niv > 0) {
+ n = nghttp2_session_get_remote_settings(ngh2, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
+ if (n > 0) {
+ session->remote_max_concurrent = n;
+ }
+ }
+ break;
+ case NGHTTP2_GOAWAY:
+ /* we expect the remote server to tell us the highest stream id
+ * that it has started processing. */
+ session->last_stream_id = frame->goaway.last_stream_id;
+ dispatch_event(session, H2_PROXYS_EV_REMOTE_GOAWAY, 0, NULL);
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int before_frame_send(nghttp2_session *ngh2,
+ const nghttp2_frame *frame, void *user_data)
+{
+ h2_proxy_session *session = user_data;
+ if (APLOGcdebug(session->c)) {
+ char buffer[256];
+
+ h2_proxy_util_frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03343)
+ "h2_proxy_session(%s): sent FRAME[%s]",
+ session->id, buffer);
+ }
+ return 0;
+}
+
+static int add_header(void *table, const char *n, const char *v)
+{
+ apr_table_add(table, n, v);
+ return 1;
+}
+
+static void process_proxy_header(apr_table_t *headers, h2_proxy_stream *stream,
+ const char *n, const char *v)
+{
+ static const struct {
+ const char *name;
+ ap_proxy_header_reverse_map_fn func;
+ } transform_hdrs[] = {
+ { "Location", ap_proxy_location_reverse_map },
+ { "Content-Location", ap_proxy_location_reverse_map },
+ { "URI", ap_proxy_location_reverse_map },
+ { "Destination", ap_proxy_location_reverse_map },
+ { "Set-Cookie", ap_proxy_cookie_reverse_map },
+ { NULL, NULL }
+ };
+ request_rec *r = stream->r;
+ proxy_dir_conf *dconf;
+ int i;
+
+ dconf = ap_get_module_config(r->per_dir_config, &proxy_module);
+ if (!dconf->preserve_host) {
+ for (i = 0; transform_hdrs[i].name; ++i) {
+ if (!ap_cstr_casecmp(transform_hdrs[i].name, n)) {
+ apr_table_add(headers, n, (*transform_hdrs[i].func)(r, dconf, v));
+ return;
+ }
+ }
+ if (!ap_cstr_casecmp("Link", n)) {
+ dconf = ap_get_module_config(r->per_dir_config, &proxy_module);
+ apr_table_add(headers, n, h2_proxy_link_reverse_map(r, dconf,
+ stream->real_server_uri, stream->p_server_uri, v));
+ return;
+ }
+ }
+ apr_table_add(headers, n, v);
+}
+
+static apr_status_t h2_proxy_stream_add_header_out(h2_proxy_stream *stream,
+ const char *n, apr_size_t nlen,
+ const char *v, apr_size_t vlen)
+{
+ if (n[0] == ':') {
+ if (!stream->data_received && !strncmp(":status", n, nlen)) {
+ char *s = apr_pstrndup(stream->r->pool, v, vlen);
+
+ apr_table_setn(stream->r->notes, "proxy-status", s);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c,
+ "h2_proxy_stream(%s-%d): got status %s",
+ stream->session->id, stream->id, s);
+ stream->r->status = (int)apr_atoi64(s);
+ if (stream->r->status <= 0) {
+ stream->r->status = 500;
+ return APR_EGENERAL;
+ }
+ }
+ return APR_SUCCESS;
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c,
+ "h2_proxy_stream(%s-%d): on_header %s: %s",
+ stream->session->id, stream->id, n, v);
+ if (!h2_proxy_res_ignore_header(n, nlen)) {
+ char *hname, *hvalue;
+ apr_table_t *headers = (stream->headers_ended?
+ stream->r->trailers_out : stream->r->headers_out);
+
+ hname = apr_pstrndup(stream->pool, n, nlen);
+ h2_proxy_util_camel_case_header(hname, nlen);
+ hvalue = apr_pstrndup(stream->pool, v, vlen);
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c,
+ "h2_proxy_stream(%s-%d): got header %s: %s",
+ stream->session->id, stream->id, hname, hvalue);
+ process_proxy_header(headers, stream, hname, hvalue);
+ }
+ return APR_SUCCESS;
+}
+
+static int log_header(void *ctx, const char *key, const char *value)
+{
+ h2_proxy_stream *stream = ctx;
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, stream->r,
+ "h2_proxy_stream(%s-%d), header_out %s: %s",
+ stream->session->id, stream->id, key, value);
+ return 1;
+}
+
+static void h2_proxy_stream_end_headers_out(h2_proxy_stream *stream)
+{
+ h2_proxy_session *session = stream->session;
+ request_rec *r = stream->r;
+ apr_pool_t *p = r->pool;
+ const char *buf;
+
+ /* Now, add in the cookies from the response to the ones already saved */
+ apr_table_do(add_header, stream->saves, r->headers_out, "Set-Cookie", NULL);
+
+ /* and now load 'em all in */
+ if (!apr_is_empty_table(stream->saves)) {
+ apr_table_unset(r->headers_out, "Set-Cookie");
+ r->headers_out = apr_table_overlay(p, r->headers_out, stream->saves);
+ }
+
+ if ((buf = apr_table_get(r->headers_out, "Content-Type"))) {
+ ap_set_content_type(r, apr_pstrdup(p, buf));
+ }
+
+ /* handle Via header in response */
+ if (session->conf->viaopt != via_off
+ && session->conf->viaopt != via_block) {
+ const char *server_name = ap_get_server_name(stream->r);
+ apr_port_t port = ap_get_server_port(stream->r);
+ char portstr[32];
+
+ /* If USE_CANONICAL_NAME_OFF was configured for the proxy virtual host,
+ * then the server name returned by ap_get_server_name() is the
+ * origin server name (which doesn't make sense with Via: headers)
+ * so we use the proxy vhost's name instead.
+ */
+ if (server_name == stream->r->hostname) {
+ server_name = stream->r->server->server_hostname;
+ }
+ if (ap_is_default_port(port, stream->r)) {
+ portstr[0] = '\0';
+ }
+ else {
+ apr_snprintf(portstr, sizeof(portstr), ":%d", port);
+ }
+
+ /* create a "Via:" response header entry and merge it */
+ apr_table_add(r->headers_out, "Via",
+ (session->conf->viaopt == via_full)
+ ? apr_psprintf(p, "%d.%d %s%s (%s)",
+ HTTP_VERSION_MAJOR(r->proto_num),
+ HTTP_VERSION_MINOR(r->proto_num),
+ server_name, portstr,
+ AP_SERVER_BASEVERSION)
+ : apr_psprintf(p, "%d.%d %s%s",
+ HTTP_VERSION_MAJOR(r->proto_num),
+ HTTP_VERSION_MINOR(r->proto_num),
+ server_name, portstr)
+ );
+ }
+ if (r->status >= 200) stream->headers_ended = 1;
+
+ if (APLOGrtrace2(stream->r)) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, stream->r,
+ "h2_proxy_stream(%s-%d), header_out after merging",
+ stream->session->id, stream->id);
+ apr_table_do(log_header, stream, stream->r->headers_out, NULL);
+ }
+}
+
+static int stream_response_data(nghttp2_session *ngh2, uint8_t flags,
+ int32_t stream_id, const uint8_t *data,
+ size_t len, void *user_data)
+{
+ h2_proxy_session *session = user_data;
+ h2_proxy_stream *stream;
+ apr_bucket *b;
+ apr_status_t status;
+
+ stream = nghttp2_session_get_stream_user_data(ngh2, stream_id);
+ if (!stream) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(03358)
+ "h2_proxy_session(%s): recv data chunk for "
+ "unknown stream %d, ignored",
+ session->id, stream_id);
+ return 0;
+ }
+
+ if (!stream->data_received) {
+ /* last chance to manipulate response headers.
+ * after this, only trailers */
+ h2_proxy_stream_end_headers_out(stream);
+ }
+ stream->data_received += len;
+
+ b = apr_bucket_transient_create((const char*)data, len,
+ stream->r->connection->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(stream->output, b);
+ /* always flush after a DATA frame, as we have no other indication
+ * of buffer use */
+ b = apr_bucket_flush_create(stream->r->connection->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(stream->output, b);
+
+ status = ap_pass_brigade(stream->r->output_filters, stream->output);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, stream->r, APLOGNO(03359)
+ "h2_proxy_session(%s): stream=%d, response DATA %ld, %ld"
+ " total", session->id, stream_id, (long)len,
+ (long)stream->data_received);
+ if (status != APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, APLOGNO(03344)
+ "h2_proxy_session(%s): passing output on stream %d",
+ session->id, stream->id);
+ nghttp2_submit_rst_stream(ngh2, NGHTTP2_FLAG_NONE,
+ stream_id, NGHTTP2_STREAM_CLOSED);
+ return NGHTTP2_ERR_STREAM_CLOSING;
+ }
+ return 0;
+}
+
+static int on_stream_close(nghttp2_session *ngh2, int32_t stream_id,
+ uint32_t error_code, void *user_data)
+{
+ h2_proxy_session *session = user_data;
+ h2_proxy_stream *stream;
+ if (!session->aborted) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03360)
+ "h2_proxy_session(%s): stream=%d, closed, err=%d",
+ session->id, stream_id, error_code);
+ stream = h2_proxy_ihash_get(session->streams, stream_id);
+ if (stream) {
+ stream->error_code = error_code;
+ }
+ dispatch_event(session, H2_PROXYS_EV_STREAM_DONE, stream_id, NULL);
+ }
+ return 0;
+}
+
+static int on_header(nghttp2_session *ngh2, const nghttp2_frame *frame,
+ const uint8_t *namearg, size_t nlen,
+ const uint8_t *valuearg, size_t vlen, uint8_t flags,
+ void *user_data)
+{
+ h2_proxy_session *session = user_data;
+ h2_proxy_stream *stream;
+ const char *n = (const char*)namearg;
+ const char *v = (const char*)valuearg;
+
+ (void)session;
+ if (frame->hd.type == NGHTTP2_HEADERS && nlen) {
+ stream = nghttp2_session_get_stream_user_data(ngh2, frame->hd.stream_id);
+ if (stream) {
+ if (h2_proxy_stream_add_header_out(stream, n, nlen, v, vlen)) {
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ }
+ }
+ else if (frame->hd.type == NGHTTP2_PUSH_PROMISE) {
+ }
+
+ return 0;
+}
+
+static ssize_t stream_request_data(nghttp2_session *ngh2, int32_t stream_id,
+ uint8_t *buf, size_t length,
+ uint32_t *data_flags,
+ nghttp2_data_source *source, void *user_data)
+{
+ h2_proxy_stream *stream;
+ apr_status_t status = APR_SUCCESS;
+
+ *data_flags = 0;
+ stream = nghttp2_session_get_stream_user_data(ngh2, stream_id);
+ if (!stream) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(03361)
+ "h2_proxy_stream(NULL): data_read, stream %d not found",
+ stream_id);
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+
+ if (stream->session->ping_state != H2_PING_ST_NONE) {
+ /* suspend until we hear from the other side */
+ stream->waiting_on_ping = 1;
+ status = APR_EAGAIN;
+ }
+ else if (stream->r->expecting_100) {
+ /* suspend until the answer comes */
+ stream->waiting_on_100 = 1;
+ status = APR_EAGAIN;
+ }
+ else if (APR_BRIGADE_EMPTY(stream->input)) {
+ status = ap_get_brigade(stream->r->input_filters, stream->input,
+ AP_MODE_READBYTES, APR_NONBLOCK_READ,
+ H2MAX(APR_BUCKET_BUFF_SIZE, length));
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, status, stream->r,
+ "h2_proxy_stream(%s-%d): request body read",
+ stream->session->id, stream->id);
+ }
+
+ if (status == APR_SUCCESS) {
+ size_t readlen = 0;
+ while (status == APR_SUCCESS
+ && (readlen < length)
+ && !APR_BRIGADE_EMPTY(stream->input)) {
+ apr_bucket* b = APR_BRIGADE_FIRST(stream->input);
+ if (APR_BUCKET_IS_METADATA(b)) {
+ if (APR_BUCKET_IS_EOS(b)) {
+ *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+ }
+ else {
+ /* we do nothing more regarding any meta here */
+ }
+ }
+ else {
+ const char *bdata = NULL;
+ apr_size_t blen = 0;
+ status = apr_bucket_read(b, &bdata, &blen, APR_BLOCK_READ);
+
+ if (status == APR_SUCCESS && blen > 0) {
+ size_t copylen = H2MIN(length - readlen, blen);
+ memcpy(buf, bdata, copylen);
+ buf += copylen;
+ readlen += copylen;
+ if (copylen < blen) {
+ /* We have data left in the bucket. Split it. */
+ status = apr_bucket_split(b, copylen);
+ }
+ }
+ }
+ apr_bucket_delete(b);
+ }
+
+ stream->data_sent += readlen;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, stream->r, APLOGNO(03468)
+ "h2_proxy_stream(%d): request DATA %ld, %ld"
+ " total, flags=%d", stream->id, (long)readlen, (long)stream->data_sent,
+ (int)*data_flags);
+ if ((*data_flags & NGHTTP2_DATA_FLAG_EOF) && !apr_is_empty_table(stream->r->trailers_in)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, stream->r, APLOGNO(10179)
+ "h2_proxy_stream(%d): submit trailers", stream->id);
+ *data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
+ submit_trailers(stream);
+ }
+ return readlen;
+ }
+ else if (APR_STATUS_IS_EAGAIN(status)) {
+ /* suspended stream, needs to be re-awakened */
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, status, stream->r,
+ "h2_proxy_stream(%s-%d): suspending",
+ stream->session->id, stream_id);
+ stream->suspended = 1;
+ h2_proxy_iq_add(stream->session->suspended, stream->id, NULL, NULL);
+ return NGHTTP2_ERR_DEFERRED;
+ }
+ else {
+ nghttp2_submit_rst_stream(ngh2, NGHTTP2_FLAG_NONE,
+ stream_id, NGHTTP2_STREAM_CLOSED);
+ return NGHTTP2_ERR_STREAM_CLOSING;
+ }
+}
+
+#ifdef H2_NG2_INVALID_HEADER_CB
+static int on_invalid_header_cb(nghttp2_session *ngh2,
+ const nghttp2_frame *frame,
+ const uint8_t *name, size_t namelen,
+ const uint8_t *value, size_t valuelen,
+ uint8_t flags, void *user_data)
+{
+ h2_proxy_session *session = user_data;
+ if (APLOGcdebug(session->c)) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03469)
+ "h2_proxy_session(%s-%d): denying stream with invalid header "
+ "'%s: %s'", session->id, (int)frame->hd.stream_id,
+ apr_pstrndup(session->pool, (const char *)name, namelen),
+ apr_pstrndup(session->pool, (const char *)value, valuelen));
+ }
+ return nghttp2_submit_rst_stream(session->ngh2, NGHTTP2_FLAG_NONE,
+ frame->hd.stream_id,
+ NGHTTP2_PROTOCOL_ERROR);
+}
+#endif
+
+h2_proxy_session *h2_proxy_session_setup(const char *id, proxy_conn_rec *p_conn,
+ proxy_server_conf *conf,
+ int h2_front,
+ unsigned char window_bits_connection,
+ unsigned char window_bits_stream,
+ h2_proxy_request_done *done)
+{
+ if (!p_conn->data) {
+ apr_pool_t *pool = p_conn->scpool;
+ h2_proxy_session *session;
+ nghttp2_session_callbacks *cbs;
+ nghttp2_option *option;
+
+ session = apr_pcalloc(pool, sizeof(*session));
+ apr_pool_pre_cleanup_register(pool, p_conn, proxy_session_pre_close);
+ p_conn->data = session;
+
+ session->id = apr_pstrdup(p_conn->scpool, id);
+ session->c = p_conn->connection;
+ session->p_conn = p_conn;
+ session->conf = conf;
+ session->pool = p_conn->scpool;
+ session->state = H2_PROXYS_ST_INIT;
+ session->h2_front = h2_front;
+ session->window_bits_stream = window_bits_stream;
+ session->window_bits_connection = window_bits_connection;
+ session->streams = h2_proxy_ihash_create(pool, offsetof(h2_proxy_stream, id));
+ session->suspended = h2_proxy_iq_create(pool, 5);
+ session->done = done;
+
+ session->input = apr_brigade_create(session->pool, session->c->bucket_alloc);
+ session->output = apr_brigade_create(session->pool, session->c->bucket_alloc);
+
+ nghttp2_session_callbacks_new(&cbs);
+ nghttp2_session_callbacks_set_on_frame_recv_callback(cbs, on_frame_recv);
+ nghttp2_session_callbacks_set_on_data_chunk_recv_callback(cbs, stream_response_data);
+ nghttp2_session_callbacks_set_on_stream_close_callback(cbs, on_stream_close);
+ nghttp2_session_callbacks_set_on_header_callback(cbs, on_header);
+ nghttp2_session_callbacks_set_before_frame_send_callback(cbs, before_frame_send);
+ nghttp2_session_callbacks_set_send_callback(cbs, raw_send);
+#ifdef H2_NG2_INVALID_HEADER_CB
+ nghttp2_session_callbacks_set_on_invalid_header_callback(cbs, on_invalid_header_cb);
+#endif
+ nghttp2_option_new(&option);
+ nghttp2_option_set_peer_max_concurrent_streams(option, 100);
+ nghttp2_option_set_no_auto_window_update(option, 0);
+
+ nghttp2_session_client_new2(&session->ngh2, cbs, session, option);
+
+ nghttp2_option_del(option);
+ nghttp2_session_callbacks_del(cbs);
+
+ ping_new_session(session, p_conn);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03362)
+ "setup session for %s", p_conn->hostname);
+ }
+ else {
+ h2_proxy_session *session = p_conn->data;
+ ping_reuse_session(session);
+ }
+ return p_conn->data;
+}
+
+static apr_status_t session_start(h2_proxy_session *session)
+{
+ nghttp2_settings_entry settings[2];
+ int rv, add_conn_window;
+ apr_socket_t *s;
+
+ s = ap_get_conn_socket(session->c);
+#if (!defined(WIN32) && !defined(NETWARE)) || defined(DOXYGEN)
+ if (s) {
+ ap_sock_disable_nagle(s);
+ }
+#endif
+
+ settings[0].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
+ settings[0].value = 0;
+ settings[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+ settings[1].value = (1 << session->window_bits_stream) - 1;
+
+ rv = nghttp2_submit_settings(session->ngh2, NGHTTP2_FLAG_NONE, settings,
+ H2_ALEN(settings));
+
+ /* If the connection window is larger than our default, trigger a WINDOW_UPDATE */
+ add_conn_window = ((1 << session->window_bits_connection) - 1 -
+ NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE);
+ if (!rv && add_conn_window != 0) {
+ rv = nghttp2_submit_window_update(session->ngh2, NGHTTP2_FLAG_NONE, 0, add_conn_window);
+ }
+ return rv? APR_EGENERAL : APR_SUCCESS;
+}
+
+static apr_status_t open_stream(h2_proxy_session *session, const char *url,
+ request_rec *r, int standalone,
+ h2_proxy_stream **pstream)
+{
+ h2_proxy_stream *stream;
+ apr_uri_t puri;
+ const char *authority, *scheme, *path;
+ apr_status_t status;
+ proxy_dir_conf *dconf;
+
+ stream = apr_pcalloc(r->pool, sizeof(*stream));
+
+ stream->pool = r->pool;
+ stream->url = url;
+ stream->r = r;
+ stream->standalone = standalone;
+ stream->session = session;
+ stream->state = H2_STREAM_ST_IDLE;
+
+ stream->input = apr_brigade_create(stream->pool, session->c->bucket_alloc);
+ stream->output = apr_brigade_create(stream->pool, session->c->bucket_alloc);
+
+ stream->req = h2_proxy_req_create(1, stream->pool);
+
+ status = apr_uri_parse(stream->pool, url, &puri);
+ if (status != APR_SUCCESS)
+ return status;
+
+ scheme = (strcmp(puri.scheme, "h2")? "http" : "https");
+
+ dconf = ap_get_module_config(r->per_dir_config, &proxy_module);
+ if (dconf->preserve_host) {
+ authority = apr_table_get(r->headers_in, "Host");
+ if (authority == NULL) {
+ authority = r->hostname;
+ }
+ }
+ else {
+ authority = puri.hostname;
+ if (!ap_strchr_c(authority, ':') && puri.port
+ && apr_uri_port_of_scheme(scheme) != puri.port) {
+ /* port info missing and port is not default for scheme: append */
+ authority = apr_psprintf(stream->pool, "%s:%d", authority, puri.port);
+ }
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
+ "authority=%s from uri.hostname=%s and uri.port=%d",
+ authority, puri.hostname, puri.port);
+ }
+ /* See #235, we use only :authority when available and remove Host:
+ * since differing values are not acceptable, see RFC 9113 ch. 8.3.1 */
+ if (authority && strlen(authority)) {
+ apr_table_unset(r->headers_in, "Host");
+ }
+
+ /* we need this for mapping relative uris in headers ("Link") back
+ * to local uris */
+ stream->real_server_uri = apr_psprintf(stream->pool, "%s://%s", scheme, authority);
+ stream->p_server_uri = apr_psprintf(stream->pool, "%s://%s", puri.scheme, authority);
+ path = apr_uri_unparse(stream->pool, &puri, APR_URI_UNP_OMITSITEPART);
+
+ h2_proxy_req_make(stream->req, stream->pool, r->method, scheme,
+ authority, path, r->headers_in);
+
+ if (dconf->add_forwarded_headers) {
+ if (PROXYREQ_REVERSE == r->proxyreq) {
+ const char *buf;
+
+ /* Add X-Forwarded-For: so that the upstream has a chance to
+ * determine, where the original request came from.
+ */
+ apr_table_mergen(stream->req->headers, "X-Forwarded-For",
+ r->useragent_ip);
+
+ /* Add X-Forwarded-Host: so that upstream knows what the
+ * original request hostname was.
+ */
+ if ((buf = apr_table_get(r->headers_in, "Host"))) {
+ apr_table_mergen(stream->req->headers, "X-Forwarded-Host", buf);
+ }
+
+ /* Add X-Forwarded-Server: so that upstream knows what the
+ * name of this proxy server is (if there are more than one)
+ * XXX: This duplicates Via: - do we strictly need it?
+ */
+ apr_table_mergen(stream->req->headers, "X-Forwarded-Server",
+ r->server->server_hostname);
+ }
+ }
+
+ /* Tuck away all already existing cookies */
+ stream->saves = apr_table_make(r->pool, 2);
+ apr_table_do(add_header, stream->saves, r->headers_out, "Set-Cookie", NULL);
+
+ *pstream = stream;
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t submit_stream(h2_proxy_session *session, h2_proxy_stream *stream)
+{
+ h2_proxy_ngheader *hd;
+ nghttp2_data_provider *pp = NULL;
+ nghttp2_data_provider provider;
+ int rv, may_have_request_body = 1;
+ apr_status_t status;
+
+ hd = h2_proxy_util_nghd_make_req(stream->pool, stream->req);
+
+ /* If we expect a 100-continue response, we must refrain from reading
+ any input until we get it. Reading the input will possibly trigger
+ HTTP_IN filter to generate the 100-continue itself. */
+ if (stream->waiting_on_100 || stream->waiting_on_ping) {
+ /* make a small test if we get an EOF/EOS immediately */
+ status = ap_get_brigade(stream->r->input_filters, stream->input,
+ AP_MODE_READBYTES, APR_NONBLOCK_READ,
+ APR_BUCKET_BUFF_SIZE);
+ may_have_request_body = APR_STATUS_IS_EAGAIN(status)
+ || (status == APR_SUCCESS
+ && !APR_BUCKET_IS_EOS(APR_BRIGADE_FIRST(stream->input)));
+ }
+
+ if (may_have_request_body) {
+ provider.source.fd = 0;
+ provider.source.ptr = NULL;
+ provider.read_callback = stream_request_data;
+ pp = &provider;
+ }
+
+ rv = nghttp2_submit_request(session->ngh2, NULL,
+ hd->nv, hd->nvlen, pp, stream);
+
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03363)
+ "h2_proxy_session(%s): submit %s%s -> %d",
+ session->id, stream->req->authority, stream->req->path,
+ rv);
+ if (rv > 0) {
+ stream->id = rv;
+ stream->state = H2_STREAM_ST_OPEN;
+ h2_proxy_ihash_add(session->streams, stream);
+ dispatch_event(session, H2_PROXYS_EV_STREAM_SUBMITTED, rv, NULL);
+
+ return APR_SUCCESS;
+ }
+ return APR_EGENERAL;
+}
+
+static apr_status_t submit_trailers(h2_proxy_stream *stream)
+{
+ h2_proxy_ngheader *hd;
+ int rv;
+
+ hd = h2_proxy_util_nghd_make(stream->pool, stream->r->trailers_in);
+ rv = nghttp2_submit_trailer(stream->session->ngh2, stream->id, hd->nv, hd->nvlen);
+ return rv == 0? APR_SUCCESS: APR_EGENERAL;
+}
+
+static apr_status_t feed_brigade(h2_proxy_session *session, apr_bucket_brigade *bb)
+{
+ apr_status_t status = APR_SUCCESS;
+ apr_size_t readlen = 0;
+ ssize_t n;
+
+ while (status == APR_SUCCESS && !APR_BRIGADE_EMPTY(bb)) {
+ apr_bucket* b = APR_BRIGADE_FIRST(bb);
+
+ if (APR_BUCKET_IS_METADATA(b)) {
+ /* nop */
+ }
+ else {
+ const char *bdata = NULL;
+ apr_size_t blen = 0;
+
+ status = apr_bucket_read(b, &bdata, &blen, APR_BLOCK_READ);
+ if (status == APR_SUCCESS && blen > 0) {
+ n = nghttp2_session_mem_recv(session->ngh2, (const uint8_t *)bdata, blen);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
+ "h2_proxy_session(%s): feeding %ld bytes -> %ld",
+ session->id, (long)blen, (long)n);
+ if (n < 0) {
+ if (nghttp2_is_fatal((int)n)) {
+ status = APR_EGENERAL;
+ }
+ }
+ else {
+ size_t rlen = (size_t)n;
+ readlen += rlen;
+ if (rlen < blen) {
+ apr_bucket_split(b, rlen);
+ }
+ }
+ }
+ }
+ apr_bucket_delete(b);
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c,
+ "h2_proxy_session(%s): fed %ld bytes of input to session",
+ session->id, (long)readlen);
+ if (readlen == 0 && status == APR_SUCCESS) {
+ return APR_EAGAIN;
+ }
+ return status;
+}
+
+static apr_status_t h2_proxy_session_read(h2_proxy_session *session, int block,
+ apr_interval_time_t timeout)
+{
+ apr_status_t status = APR_SUCCESS;
+
+ if (APR_BRIGADE_EMPTY(session->input)) {
+ apr_socket_t *socket = NULL;
+ apr_time_t save_timeout = -1;
+
+ if (block && timeout > 0) {
+ socket = ap_get_conn_socket(session->c);
+ if (socket) {
+ apr_socket_timeout_get(socket, &save_timeout);
+ apr_socket_timeout_set(socket, timeout);
+ }
+ else {
+ /* cannot block on timeout */
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, session->c, APLOGNO(03379)
+ "h2_proxy_session(%s): unable to get conn socket",
+ session->id);
+ return APR_ENOTIMPL;
+ }
+ }
+
+ status = ap_get_brigade(session->c->input_filters, session->input,
+ AP_MODE_READBYTES,
+ block? APR_BLOCK_READ : APR_NONBLOCK_READ,
+ 64 * 1024);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, session->c,
+ "h2_proxy_session(%s): read from conn", session->id);
+ if (socket && save_timeout != -1) {
+ apr_socket_timeout_set(socket, save_timeout);
+ }
+ }
+
+ if (status == APR_SUCCESS) {
+ status = feed_brigade(session, session->input);
+ }
+ else if (APR_STATUS_IS_TIMEUP(status)) {
+ /* nop */
+ }
+ else if (!APR_STATUS_IS_EAGAIN(status)) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, APLOGNO(03380)
+ "h2_proxy_session(%s): read error", session->id);
+ dispatch_event(session, H2_PROXYS_EV_CONN_ERROR, status, NULL);
+ }
+
+ return status;
+}
+
+apr_status_t h2_proxy_session_submit(h2_proxy_session *session,
+ const char *url, request_rec *r,
+ int standalone)
+{
+ h2_proxy_stream *stream;
+ apr_status_t status;
+
+ status = open_stream(session, url, r, standalone, &stream);
+ if (status == APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03381)
+ "process stream(%d): %s %s%s, original: %s",
+ stream->id, stream->req->method,
+ stream->req->authority, stream->req->path,
+ r->the_request);
+ status = submit_stream(session, stream);
+ }
+ return status;
+}
+
+static void stream_resume(h2_proxy_stream *stream)
+{
+ h2_proxy_session *session = stream->session;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
+ "h2_proxy_stream(%s-%d): resuming",
+ session->id, stream->id);
+ stream->suspended = 0;
+ h2_proxy_iq_remove(session->suspended, stream->id);
+ nghttp2_session_resume_data(session->ngh2, stream->id);
+ dispatch_event(session, H2_PROXYS_EV_STREAM_RESUMED, 0, NULL);
+}
+
+static int is_waiting_for_backend(h2_proxy_session *session)
+{
+ return ((session->ping_state != H2_PING_ST_NONE)
+ || ((session->suspended->nelts <= 0)
+ && !nghttp2_session_want_write(session->ngh2)
+ && nghttp2_session_want_read(session->ngh2)));
+}
+
+static apr_status_t check_suspended(h2_proxy_session *session)
+{
+ h2_proxy_stream *stream;
+ int i, stream_id;
+ apr_status_t status;
+
+ for (i = 0; i < session->suspended->nelts; ++i) {
+ stream_id = session->suspended->elts[i];
+ stream = nghttp2_session_get_stream_user_data(session->ngh2, stream_id);
+ if (stream) {
+ if (stream->waiting_on_100 || stream->waiting_on_ping) {
+ status = APR_EAGAIN;
+ }
+ else {
+ status = ap_get_brigade(stream->r->input_filters, stream->input,
+ AP_MODE_READBYTES, APR_NONBLOCK_READ,
+ APR_BUCKET_BUFF_SIZE);
+ }
+ if (status == APR_SUCCESS && !APR_BRIGADE_EMPTY(stream->input)) {
+ stream_resume(stream);
+ check_suspended(session);
+ return APR_SUCCESS;
+ }
+ else if (status != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(status)) {
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, session->c,
+ APLOGNO(03382) "h2_proxy_stream(%s-%d): check input",
+ session->id, stream_id);
+ stream_resume(stream);
+ check_suspended(session);
+ return APR_SUCCESS;
+ }
+ }
+ else {
+ /* gone? */
+ h2_proxy_iq_remove(session->suspended, stream_id);
+ check_suspended(session);
+ return APR_SUCCESS;
+ }
+ }
+ return APR_EAGAIN;
+}
+
+static apr_status_t session_shutdown(h2_proxy_session *session, int reason,
+ const char *msg)
+{
+ apr_status_t status = APR_SUCCESS;
+ const char *err = msg;
+
+ ap_assert(session);
+ if (!err && reason) {
+ err = nghttp2_strerror(reason);
+ }
+ nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE, 0,
+ reason, (uint8_t*)err, err? strlen(err):0);
+ status = nghttp2_session_send(session->ngh2);
+ dispatch_event(session, H2_PROXYS_EV_LOCAL_GOAWAY, reason, err);
+ return status;
+}
+
+
+static const char *StateNames[] = {
+ "INIT", /* H2_PROXYS_ST_INIT */
+ "DONE", /* H2_PROXYS_ST_DONE */
+ "IDLE", /* H2_PROXYS_ST_IDLE */
+ "BUSY", /* H2_PROXYS_ST_BUSY */
+ "WAIT", /* H2_PROXYS_ST_WAIT */
+ "LSHUTDOWN", /* H2_PROXYS_ST_LOCAL_SHUTDOWN */
+ "RSHUTDOWN", /* H2_PROXYS_ST_REMOTE_SHUTDOWN */
+};
+
+static const char *state_name(h2_proxys_state state)
+{
+ if (state >= (sizeof(StateNames)/sizeof(StateNames[0]))) {
+ return "unknown";
+ }
+ return StateNames[state];
+}
+
+static int is_accepting_streams(h2_proxy_session *session)
+{
+ switch (session->state) {
+ case H2_PROXYS_ST_IDLE:
+ case H2_PROXYS_ST_BUSY:
+ case H2_PROXYS_ST_WAIT:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static void transit(h2_proxy_session *session, const char *action,
+ h2_proxys_state nstate)
+{
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03345)
+ "h2_proxy_session(%s): transit [%s] -- %s --> [%s]", session->id,
+ state_name(session->state), action, state_name(nstate));
+ session->state = nstate;
+}
+
+static void ev_init(h2_proxy_session *session, int arg, const char *msg)
+{
+ switch (session->state) {
+ case H2_PROXYS_ST_INIT:
+ if (h2_proxy_ihash_empty(session->streams)) {
+ transit(session, "init", H2_PROXYS_ST_IDLE);
+ }
+ else {
+ transit(session, "init", H2_PROXYS_ST_BUSY);
+ }
+ break;
+
+ default:
+ /* nop */
+ break;
+ }
+}
+
+static void ev_local_goaway(h2_proxy_session *session, int arg, const char *msg)
+{
+ switch (session->state) {
+ case H2_PROXYS_ST_LOCAL_SHUTDOWN:
+ /* already did that? */
+ break;
+ case H2_PROXYS_ST_IDLE:
+ case H2_PROXYS_ST_REMOTE_SHUTDOWN:
+ /* all done */
+ transit(session, "local goaway", H2_PROXYS_ST_DONE);
+ break;
+ default:
+ transit(session, "local goaway", H2_PROXYS_ST_LOCAL_SHUTDOWN);
+ break;
+ }
+}
+
+static void ev_remote_goaway(h2_proxy_session *session, int arg, const char *msg)
+{
+ switch (session->state) {
+ case H2_PROXYS_ST_REMOTE_SHUTDOWN:
+ /* already received that? */
+ break;
+ case H2_PROXYS_ST_IDLE:
+ case H2_PROXYS_ST_LOCAL_SHUTDOWN:
+ /* all done */
+ transit(session, "remote goaway", H2_PROXYS_ST_DONE);
+ break;
+ default:
+ transit(session, "remote goaway", H2_PROXYS_ST_REMOTE_SHUTDOWN);
+ break;
+ }
+}
+
+static void ev_conn_error(h2_proxy_session *session, int arg, const char *msg)
+{
+ switch (session->state) {
+ case H2_PROXYS_ST_INIT:
+ case H2_PROXYS_ST_DONE:
+ case H2_PROXYS_ST_LOCAL_SHUTDOWN:
+ /* just leave */
+ transit(session, "conn error", H2_PROXYS_ST_DONE);
+ break;
+
+ default:
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, arg, session->c,
+ "h2_proxy_session(%s): conn error -> shutdown", session->id);
+ session_shutdown(session, arg, msg);
+ break;
+ }
+}
+
+static void ev_proto_error(h2_proxy_session *session, int arg, const char *msg)
+{
+ switch (session->state) {
+ case H2_PROXYS_ST_DONE:
+ case H2_PROXYS_ST_LOCAL_SHUTDOWN:
+ /* just leave */
+ transit(session, "proto error", H2_PROXYS_ST_DONE);
+ break;
+
+ default:
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
+ "h2_proxy_session(%s): proto error -> shutdown", session->id);
+ session_shutdown(session, arg, msg);
+ break;
+ }
+}
+
+static void ev_conn_timeout(h2_proxy_session *session, int arg, const char *msg)
+{
+ switch (session->state) {
+ case H2_PROXYS_ST_LOCAL_SHUTDOWN:
+ transit(session, "conn timeout", H2_PROXYS_ST_DONE);
+ break;
+ default:
+ session_shutdown(session, arg, msg);
+ transit(session, "conn timeout", H2_PROXYS_ST_DONE);
+ break;
+ }
+}
+
+static void ev_no_io(h2_proxy_session *session, int arg, const char *msg)
+{
+ switch (session->state) {
+ case H2_PROXYS_ST_BUSY:
+ case H2_PROXYS_ST_LOCAL_SHUTDOWN:
+ case H2_PROXYS_ST_REMOTE_SHUTDOWN:
+ /* nothing for input and output to do. If we remain
+ * in this state, we go into a tight loop and suck up
+ * CPU cycles. Ideally, we'd like to do a blocking read, but that
+ * is not possible if we have scheduled tasks and wait
+ * for them to produce something. */
+ if (h2_proxy_ihash_empty(session->streams)) {
+ if (!is_accepting_streams(session)) {
+ /* We are no longer accepting new streams and have
+ * finished processing existing ones. Time to leave. */
+ session_shutdown(session, arg, msg);
+ transit(session, "no io", H2_PROXYS_ST_DONE);
+ }
+ else {
+ /* When we have no streams, no task events are possible,
+ * switch to blocking reads */
+ transit(session, "no io", H2_PROXYS_ST_IDLE);
+ }
+ }
+ else {
+ /* Unable to do blocking reads, as we wait on events from
+ * task processing in other threads. Do a busy wait with
+ * backoff timer. */
+ transit(session, "no io", H2_PROXYS_ST_WAIT);
+ }
+ break;
+ default:
+ /* nop */
+ break;
+ }
+}
+
+static void ev_stream_submitted(h2_proxy_session *session, int stream_id,
+ const char *msg)
+{
+ switch (session->state) {
+ case H2_PROXYS_ST_IDLE:
+ case H2_PROXYS_ST_WAIT:
+ transit(session, "stream submitted", H2_PROXYS_ST_BUSY);
+ break;
+ default:
+ /* nop */
+ break;
+ }
+}
+
+static void ev_stream_done(h2_proxy_session *session, int stream_id,
+ const char *msg)
+{
+ h2_proxy_stream *stream;
+ apr_bucket *b;
+
+ stream = nghttp2_session_get_stream_user_data(session->ngh2, stream_id);
+ if (stream) {
+ /* if the stream's connection is aborted, do not send anything
+ * more on it. */
+ apr_status_t status = (stream->error_code == 0)? APR_SUCCESS : APR_EINVAL;
+ int touched = (stream->data_sent ||
+ stream_id <= session->last_stream_id);
+ if (!session->c->aborted) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03364)
+ "h2_proxy_sesssion(%s): stream(%d) closed "
+ "(touched=%d, error=%d)",
+ session->id, stream_id, touched, stream->error_code);
+
+ if (status != APR_SUCCESS) {
+ b = ap_bucket_error_create(HTTP_SERVICE_UNAVAILABLE, NULL, stream->r->pool,
+ stream->r->connection->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(stream->output, b);
+ b = apr_bucket_eos_create(stream->r->connection->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(stream->output, b);
+ ap_pass_brigade(stream->r->output_filters, stream->output);
+ }
+ else if (!stream->data_received) {
+ /* if the response had no body, this is the time to flush
+ * an empty brigade which will also write the response headers */
+ h2_proxy_stream_end_headers_out(stream);
+ stream->data_received = 1;
+ b = apr_bucket_flush_create(stream->r->connection->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(stream->output, b);
+ b = apr_bucket_eos_create(stream->r->connection->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(stream->output, b);
+ ap_pass_brigade(stream->r->output_filters, stream->output);
+ }
+ }
+
+ stream->state = H2_STREAM_ST_CLOSED;
+ h2_proxy_ihash_remove(session->streams, stream_id);
+ h2_proxy_iq_remove(session->suspended, stream_id);
+ if (session->done) {
+ session->done(session, stream->r, status, touched);
+ }
+ }
+
+ switch (session->state) {
+ default:
+ /* nop */
+ break;
+ }
+}
+
+static void ev_stream_resumed(h2_proxy_session *session, int arg, const char *msg)
+{
+ switch (session->state) {
+ case H2_PROXYS_ST_WAIT:
+ transit(session, "stream resumed", H2_PROXYS_ST_BUSY);
+ break;
+ default:
+ /* nop */
+ break;
+ }
+}
+
+static void ev_data_read(h2_proxy_session *session, int arg, const char *msg)
+{
+ switch (session->state) {
+ case H2_PROXYS_ST_IDLE:
+ case H2_PROXYS_ST_WAIT:
+ transit(session, "data read", H2_PROXYS_ST_BUSY);
+ break;
+ default:
+ /* nop */
+ break;
+ }
+}
+
+static void ev_ngh2_done(h2_proxy_session *session, int arg, const char *msg)
+{
+ switch (session->state) {
+ case H2_PROXYS_ST_DONE:
+ /* nop */
+ break;
+ default:
+ transit(session, "nghttp2 done", H2_PROXYS_ST_DONE);
+ break;
+ }
+}
+
+static void ev_pre_close(h2_proxy_session *session, int arg, const char *msg)
+{
+ switch (session->state) {
+ case H2_PROXYS_ST_DONE:
+ case H2_PROXYS_ST_LOCAL_SHUTDOWN:
+ /* nop */
+ break;
+ default:
+ session_shutdown(session, arg, msg);
+ break;
+ }
+}
+
+static void dispatch_event(h2_proxy_session *session, h2_proxys_event_t ev,
+ int arg, const char *msg)
+{
+ switch (ev) {
+ case H2_PROXYS_EV_INIT:
+ ev_init(session, arg, msg);
+ break;
+ case H2_PROXYS_EV_LOCAL_GOAWAY:
+ ev_local_goaway(session, arg, msg);
+ break;
+ case H2_PROXYS_EV_REMOTE_GOAWAY:
+ ev_remote_goaway(session, arg, msg);
+ break;
+ case H2_PROXYS_EV_CONN_ERROR:
+ ev_conn_error(session, arg, msg);
+ break;
+ case H2_PROXYS_EV_PROTO_ERROR:
+ ev_proto_error(session, arg, msg);
+ break;
+ case H2_PROXYS_EV_CONN_TIMEOUT:
+ ev_conn_timeout(session, arg, msg);
+ break;
+ case H2_PROXYS_EV_NO_IO:
+ ev_no_io(session, arg, msg);
+ break;
+ case H2_PROXYS_EV_STREAM_SUBMITTED:
+ ev_stream_submitted(session, arg, msg);
+ break;
+ case H2_PROXYS_EV_STREAM_DONE:
+ ev_stream_done(session, arg, msg);
+ break;
+ case H2_PROXYS_EV_STREAM_RESUMED:
+ ev_stream_resumed(session, arg, msg);
+ break;
+ case H2_PROXYS_EV_DATA_READ:
+ ev_data_read(session, arg, msg);
+ break;
+ case H2_PROXYS_EV_NGH2_DONE:
+ ev_ngh2_done(session, arg, msg);
+ break;
+ case H2_PROXYS_EV_PRE_CLOSE:
+ ev_pre_close(session, arg, msg);
+ break;
+ default:
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
+ "h2_proxy_session(%s): unknown event %d",
+ session->id, ev);
+ break;
+ }
+}
+
+static int send_loop(h2_proxy_session *session)
+{
+ while (nghttp2_session_want_write(session->ngh2)) {
+ int rv = nghttp2_session_send(session->ngh2);
+ if (rv < 0 && nghttp2_is_fatal(rv)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
+ "h2_proxy_session(%s): write, rv=%d", session->id, rv);
+ dispatch_event(session, H2_PROXYS_EV_CONN_ERROR, rv, NULL);
+ break;
+ }
+ return 1;
+ }
+ return 0;
+}
+
+apr_status_t h2_proxy_session_process(h2_proxy_session *session)
+{
+ apr_status_t status;
+ int have_written = 0, have_read = 0;
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
+ "h2_proxy_session(%s): process", session->id);
+
+run_loop:
+ switch (session->state) {
+ case H2_PROXYS_ST_INIT:
+ status = session_start(session);
+ if (status == APR_SUCCESS) {
+ dispatch_event(session, H2_PROXYS_EV_INIT, 0, NULL);
+ goto run_loop;
+ }
+ else {
+ dispatch_event(session, H2_PROXYS_EV_CONN_ERROR, status, NULL);
+ }
+ break;
+
+ case H2_PROXYS_ST_BUSY:
+ case H2_PROXYS_ST_LOCAL_SHUTDOWN:
+ case H2_PROXYS_ST_REMOTE_SHUTDOWN:
+ have_written = send_loop(session);
+
+ if (nghttp2_session_want_read(session->ngh2)) {
+ status = h2_proxy_session_read(session, 0, 0);
+ if (status == APR_SUCCESS) {
+ have_read = 1;
+ }
+ }
+
+ if (!have_written && !have_read
+ && !nghttp2_session_want_write(session->ngh2)) {
+ dispatch_event(session, H2_PROXYS_EV_NO_IO, 0, NULL);
+ goto run_loop;
+ }
+ break;
+
+ case H2_PROXYS_ST_WAIT:
+ if (is_waiting_for_backend(session)) {
+ /* we can do a blocking read with the default timeout (as
+ * configured via ProxyTimeout in our socket. There is
+ * nothing we want to send or check until we get more data
+ * from the backend. */
+ status = h2_proxy_session_read(session, 1, 0);
+ if (status == APR_SUCCESS) {
+ have_read = 1;
+ dispatch_event(session, H2_PROXYS_EV_DATA_READ, 0, NULL);
+ }
+ else {
+ dispatch_event(session, H2_PROXYS_EV_CONN_ERROR, status, NULL);
+ return status;
+ }
+ }
+ else if (check_suspended(session) == APR_EAGAIN) {
+ /* no stream has become resumed. Do a blocking read with
+ * ever increasing timeouts... */
+ if (session->wait_timeout < 25) {
+ session->wait_timeout = 25;
+ }
+ else {
+ session->wait_timeout = H2MIN(apr_time_from_msec(100),
+ 2*session->wait_timeout);
+ }
+
+ status = h2_proxy_session_read(session, 1, session->wait_timeout);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, session->c,
+ APLOGNO(03365)
+ "h2_proxy_session(%s): WAIT read, timeout=%fms",
+ session->id, session->wait_timeout/1000.0);
+ if (status == APR_SUCCESS) {
+ have_read = 1;
+ dispatch_event(session, H2_PROXYS_EV_DATA_READ, 0, NULL);
+ }
+ else if (APR_STATUS_IS_TIMEUP(status)
+ || APR_STATUS_IS_EAGAIN(status)) {
+ /* go back to checking all inputs again */
+ transit(session, "wait cycle", H2_PROXYS_ST_BUSY);
+ }
+ }
+ break;
+
+ case H2_PROXYS_ST_IDLE:
+ break;
+
+ case H2_PROXYS_ST_DONE: /* done, session terminated */
+ return APR_EOF;
+
+ default:
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, session->c,
+ APLOGNO(03346)"h2_proxy_session(%s): unknown state %d",
+ session->id, session->state);
+ dispatch_event(session, H2_PROXYS_EV_PROTO_ERROR, 0, NULL);
+ break;
+ }
+
+
+ if (have_read || have_written) {
+ session->wait_timeout = 0;
+ }
+
+ if (!nghttp2_session_want_read(session->ngh2)
+ && !nghttp2_session_want_write(session->ngh2)) {
+ dispatch_event(session, H2_PROXYS_EV_NGH2_DONE, 0, NULL);
+ }
+
+ return APR_SUCCESS; /* needs to be called again */
+}
+
+typedef struct {
+ h2_proxy_session *session;
+ h2_proxy_request_done *done;
+} cleanup_iter_ctx;
+
+static int cancel_iter(void *udata, void *val)
+{
+ cleanup_iter_ctx *ctx = udata;
+ h2_proxy_stream *stream = val;
+ nghttp2_submit_rst_stream(ctx->session->ngh2, NGHTTP2_FLAG_NONE,
+ stream->id, 0);
+ return 1;
+}
+
+void h2_proxy_session_cancel_all(h2_proxy_session *session)
+{
+ if (!h2_proxy_ihash_empty(session->streams)) {
+ cleanup_iter_ctx ctx;
+ ctx.session = session;
+ ctx.done = session->done;
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03366)
+ "h2_proxy_session(%s): cancel %d streams",
+ session->id, (int)h2_proxy_ihash_count(session->streams));
+ h2_proxy_ihash_iter(session->streams, cancel_iter, &ctx);
+ session_shutdown(session, 0, NULL);
+ }
+}
+
+static int done_iter(void *udata, void *val)
+{
+ cleanup_iter_ctx *ctx = udata;
+ h2_proxy_stream *stream = val;
+ int touched = (stream->data_sent ||
+ stream->id <= ctx->session->last_stream_id);
+ ctx->done(ctx->session, stream->r, APR_ECONNABORTED, touched);
+ return 1;
+}
+
+void h2_proxy_session_cleanup(h2_proxy_session *session,
+ h2_proxy_request_done *done)
+{
+ if (!h2_proxy_ihash_empty(session->streams)) {
+ cleanup_iter_ctx ctx;
+ ctx.session = session;
+ ctx.done = done;
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03519)
+ "h2_proxy_session(%s): terminated, %d streams unfinished",
+ session->id, (int)h2_proxy_ihash_count(session->streams));
+ h2_proxy_ihash_iter(session->streams, done_iter, &ctx);
+ h2_proxy_ihash_clear(session->streams);
+ }
+}
+
+static int ping_arrived_iter(void *udata, void *val)
+{
+ h2_proxy_stream *stream = val;
+ if (stream->waiting_on_ping) {
+ stream->waiting_on_ping = 0;
+ stream_resume(stream);
+ }
+ return 1;
+}
+
+static void ping_arrived(h2_proxy_session *session)
+{
+ if (!h2_proxy_ihash_empty(session->streams)) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03470)
+ "h2_proxy_session(%s): ping arrived, unblocking streams",
+ session->id);
+ h2_proxy_ihash_iter(session->streams, ping_arrived_iter, &session);
+ }
+}
+
+typedef struct {
+ h2_proxy_session *session;
+ conn_rec *c;
+ apr_off_t bytes;
+ int updated;
+} win_update_ctx;
+
diff --git a/modules/http2/h2_proxy_session.h b/modules/http2/h2_proxy_session.h
new file mode 100644
index 0000000..f40e5ee
--- /dev/null
+++ b/modules/http2/h2_proxy_session.h
@@ -0,0 +1,133 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef h2_proxy_session_h
+#define h2_proxy_session_h
+
+#define H2_ALEN(a) (sizeof(a)/sizeof((a)[0]))
+
+#include <nghttp2/nghttp2.h>
+
+struct h2_proxy_iqueue;
+struct h2_proxy_ihash_t;
+
+typedef enum {
+ H2_STREAM_ST_IDLE,
+ H2_STREAM_ST_OPEN,
+ H2_STREAM_ST_RESV_LOCAL,
+ H2_STREAM_ST_RESV_REMOTE,
+ H2_STREAM_ST_CLOSED_INPUT,
+ H2_STREAM_ST_CLOSED_OUTPUT,
+ H2_STREAM_ST_CLOSED,
+} h2_proxy_stream_state_t;
+
+typedef enum {
+ H2_PROXYS_ST_INIT, /* send initial SETTINGS, etc. */
+ H2_PROXYS_ST_DONE, /* finished, connection close */
+ H2_PROXYS_ST_IDLE, /* no streams to process */
+ H2_PROXYS_ST_BUSY, /* read/write without stop */
+ H2_PROXYS_ST_WAIT, /* waiting for tasks reporting back */
+ H2_PROXYS_ST_LOCAL_SHUTDOWN, /* we announced GOAWAY */
+ H2_PROXYS_ST_REMOTE_SHUTDOWN, /* client announced GOAWAY */
+} h2_proxys_state;
+
+typedef enum {
+ H2_PROXYS_EV_INIT, /* session was initialized */
+ H2_PROXYS_EV_LOCAL_GOAWAY, /* we send a GOAWAY */
+ H2_PROXYS_EV_REMOTE_GOAWAY, /* remote send us a GOAWAY */
+ H2_PROXYS_EV_CONN_ERROR, /* connection error */
+ H2_PROXYS_EV_PROTO_ERROR, /* protocol error */
+ H2_PROXYS_EV_CONN_TIMEOUT, /* connection timeout */
+ H2_PROXYS_EV_NO_IO, /* nothing has been read or written */
+ H2_PROXYS_EV_STREAM_SUBMITTED, /* stream has been submitted */
+ H2_PROXYS_EV_STREAM_DONE, /* stream has been finished */
+ H2_PROXYS_EV_STREAM_RESUMED, /* stream signalled availability of headers/data */
+ H2_PROXYS_EV_DATA_READ, /* connection data has been read */
+ H2_PROXYS_EV_NGH2_DONE, /* nghttp2 wants neither read nor write anything */
+ H2_PROXYS_EV_PRE_CLOSE, /* connection will close after this */
+} h2_proxys_event_t;
+
+typedef enum {
+ H2_PING_ST_NONE, /* normal connection mode, ProxyTimeout rules */
+ H2_PING_ST_AWAIT_ANY, /* waiting for any frame from backend */
+ H2_PING_ST_AWAIT_PING, /* waiting for PING frame from backend */
+} h2_ping_state_t;
+
+typedef struct h2_proxy_session h2_proxy_session;
+typedef void h2_proxy_request_done(h2_proxy_session *s, request_rec *r,
+ apr_status_t status, int touched);
+
+struct h2_proxy_session {
+ const char *id;
+ conn_rec *c;
+ proxy_conn_rec *p_conn;
+ proxy_server_conf *conf;
+ apr_pool_t *pool;
+ nghttp2_session *ngh2; /* the nghttp2 session itself */
+
+ unsigned int aborted : 1;
+ unsigned int h2_front : 1; /* if front-end connection is HTTP/2 */
+
+ h2_proxy_request_done *done;
+ void *user_data;
+
+ unsigned char window_bits_stream;
+ unsigned char window_bits_connection;
+
+ h2_proxys_state state;
+ apr_interval_time_t wait_timeout;
+
+ struct h2_proxy_ihash_t *streams;
+ struct h2_proxy_iqueue *suspended;
+ apr_size_t remote_max_concurrent;
+ int last_stream_id; /* last stream id processed by backend, or 0 */
+ apr_time_t last_frame_received;
+
+ apr_bucket_brigade *input;
+ apr_bucket_brigade *output;
+
+ h2_ping_state_t ping_state;
+ apr_time_t ping_timeout;
+ apr_time_t save_timeout;
+};
+
+h2_proxy_session *h2_proxy_session_setup(const char *id, proxy_conn_rec *p_conn,
+ proxy_server_conf *conf,
+ int h2_front,
+ unsigned char window_bits_connection,
+ unsigned char window_bits_stream,
+ h2_proxy_request_done *done);
+
+apr_status_t h2_proxy_session_submit(h2_proxy_session *s, const char *url,
+ request_rec *r, int standalone);
+
+/**
+ * Perform a step in processing the proxy session. Will return aftert
+ * one read/write cycle and indicate session status by status code.
+ * @param s the session to process
+ * @return APR_EAGAIN when processing needs to be invoked again
+ * APR_SUCCESS when all streams have been processed, session still live
+ * APR_EOF when the session has been terminated
+ */
+apr_status_t h2_proxy_session_process(h2_proxy_session *s);
+
+void h2_proxy_session_cancel_all(h2_proxy_session *s);
+
+void h2_proxy_session_cleanup(h2_proxy_session *s, h2_proxy_request_done *done);
+
+#define H2_PROXY_REQ_URL_NOTE "h2-proxy-req-url"
+
+#endif /* h2_proxy_session_h */
diff --git a/modules/http2/h2_proxy_util.c b/modules/http2/h2_proxy_util.c
new file mode 100644
index 0000000..dc69ec0
--- /dev/null
+++ b/modules/http2/h2_proxy_util.c
@@ -0,0 +1,1355 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <apr_lib.h>
+#include <apr_strings.h>
+#include <apr_thread_mutex.h>
+#include <apr_thread_cond.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_log.h>
+#include <http_request.h>
+#include <mod_proxy.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "h2.h"
+#include "h2_proxy_util.h"
+
+APLOG_USE_MODULE(proxy_http2);
+
+/* h2_log2(n) iff n is a power of 2 */
+unsigned char h2_proxy_log2(int n)
+{
+ int lz = 0;
+ if (!n) {
+ return 0;
+ }
+ if (!(n & 0xffff0000u)) {
+ lz += 16;
+ n = (n << 16);
+ }
+ if (!(n & 0xff000000u)) {
+ lz += 8;
+ n = (n << 8);
+ }
+ if (!(n & 0xf0000000u)) {
+ lz += 4;
+ n = (n << 4);
+ }
+ if (!(n & 0xc0000000u)) {
+ lz += 2;
+ n = (n << 2);
+ }
+ if (!(n & 0x80000000u)) {
+ lz += 1;
+ }
+
+ return 31 - lz;
+}
+
+/*******************************************************************************
+ * ihash - hash for structs with int identifier
+ ******************************************************************************/
+struct h2_proxy_ihash_t {
+ apr_hash_t *hash;
+ size_t ioff;
+};
+
+static unsigned int ihash(const char *key, apr_ssize_t *klen)
+{
+ return (unsigned int)(*((int*)key));
+}
+
+h2_proxy_ihash_t *h2_proxy_ihash_create(apr_pool_t *pool, size_t offset_of_int)
+{
+ h2_proxy_ihash_t *ih = apr_pcalloc(pool, sizeof(h2_proxy_ihash_t));
+ ih->hash = apr_hash_make_custom(pool, ihash);
+ ih->ioff = offset_of_int;
+ return ih;
+}
+
+size_t h2_proxy_ihash_count(h2_proxy_ihash_t *ih)
+{
+ return apr_hash_count(ih->hash);
+}
+
+int h2_proxy_ihash_empty(h2_proxy_ihash_t *ih)
+{
+ return apr_hash_count(ih->hash) == 0;
+}
+
+void *h2_proxy_ihash_get(h2_proxy_ihash_t *ih, int id)
+{
+ return apr_hash_get(ih->hash, &id, sizeof(id));
+}
+
+typedef struct {
+ h2_proxy_ihash_iter_t *iter;
+ void *ctx;
+} iter_ctx;
+
+static int ihash_iter(void *ctx, const void *key, apr_ssize_t klen,
+ const void *val)
+{
+ iter_ctx *ictx = ctx;
+ return ictx->iter(ictx->ctx, (void*)val); /* why is this passed const?*/
+}
+
+int h2_proxy_ihash_iter(h2_proxy_ihash_t *ih, h2_proxy_ihash_iter_t *fn, void *ctx)
+{
+ iter_ctx ictx;
+ ictx.iter = fn;
+ ictx.ctx = ctx;
+ return apr_hash_do(ihash_iter, &ictx, ih->hash);
+}
+
+void h2_proxy_ihash_add(h2_proxy_ihash_t *ih, void *val)
+{
+ apr_hash_set(ih->hash, ((char *)val + ih->ioff), sizeof(int), val);
+}
+
+void h2_proxy_ihash_remove(h2_proxy_ihash_t *ih, int id)
+{
+ apr_hash_set(ih->hash, &id, sizeof(id), NULL);
+}
+
+void h2_proxy_ihash_remove_val(h2_proxy_ihash_t *ih, void *val)
+{
+ int id = *((int*)((char *)val + ih->ioff));
+ apr_hash_set(ih->hash, &id, sizeof(id), NULL);
+}
+
+
+void h2_proxy_ihash_clear(h2_proxy_ihash_t *ih)
+{
+ apr_hash_clear(ih->hash);
+}
+
+typedef struct {
+ h2_proxy_ihash_t *ih;
+ void **buffer;
+ size_t max;
+ size_t len;
+} collect_ctx;
+
+static int collect_iter(void *x, void *val)
+{
+ collect_ctx *ctx = x;
+ if (ctx->len < ctx->max) {
+ ctx->buffer[ctx->len++] = val;
+ return 1;
+ }
+ return 0;
+}
+
+size_t h2_proxy_ihash_shift(h2_proxy_ihash_t *ih, void **buffer, size_t max)
+{
+ collect_ctx ctx;
+ size_t i;
+
+ ctx.ih = ih;
+ ctx.buffer = buffer;
+ ctx.max = max;
+ ctx.len = 0;
+ h2_proxy_ihash_iter(ih, collect_iter, &ctx);
+ for (i = 0; i < ctx.len; ++i) {
+ h2_proxy_ihash_remove_val(ih, buffer[i]);
+ }
+ return ctx.len;
+}
+
+typedef struct {
+ h2_proxy_ihash_t *ih;
+ int *buffer;
+ size_t max;
+ size_t len;
+} icollect_ctx;
+
+static int icollect_iter(void *x, void *val)
+{
+ icollect_ctx *ctx = x;
+ if (ctx->len < ctx->max) {
+ ctx->buffer[ctx->len++] = *((int*)((char *)val + ctx->ih->ioff));
+ return 1;
+ }
+ return 0;
+}
+
+size_t h2_proxy_ihash_ishift(h2_proxy_ihash_t *ih, int *buffer, size_t max)
+{
+ icollect_ctx ctx;
+ size_t i;
+
+ ctx.ih = ih;
+ ctx.buffer = buffer;
+ ctx.max = max;
+ ctx.len = 0;
+ h2_proxy_ihash_iter(ih, icollect_iter, &ctx);
+ for (i = 0; i < ctx.len; ++i) {
+ h2_proxy_ihash_remove(ih, buffer[i]);
+ }
+ return ctx.len;
+}
+
+/*******************************************************************************
+ * iqueue - sorted list of int
+ ******************************************************************************/
+
+static void iq_grow(h2_proxy_iqueue *q, int nlen);
+static void iq_swap(h2_proxy_iqueue *q, int i, int j);
+static int iq_bubble_up(h2_proxy_iqueue *q, int i, int top,
+ h2_proxy_iq_cmp *cmp, void *ctx);
+static int iq_bubble_down(h2_proxy_iqueue *q, int i, int bottom,
+ h2_proxy_iq_cmp *cmp, void *ctx);
+
+h2_proxy_iqueue *h2_proxy_iq_create(apr_pool_t *pool, int capacity)
+{
+ h2_proxy_iqueue *q = apr_pcalloc(pool, sizeof(h2_proxy_iqueue));
+ if (q) {
+ q->pool = pool;
+ iq_grow(q, capacity);
+ q->nelts = 0;
+ }
+ return q;
+}
+
+int h2_proxy_iq_empty(h2_proxy_iqueue *q)
+{
+ return q->nelts == 0;
+}
+
+int h2_proxy_iq_count(h2_proxy_iqueue *q)
+{
+ return q->nelts;
+}
+
+
+void h2_proxy_iq_add(h2_proxy_iqueue *q, int sid, h2_proxy_iq_cmp *cmp, void *ctx)
+{
+ int i;
+
+ if (q->nelts >= q->nalloc) {
+ iq_grow(q, q->nalloc * 2);
+ }
+
+ i = (q->head + q->nelts) % q->nalloc;
+ q->elts[i] = sid;
+ ++q->nelts;
+
+ if (cmp) {
+ /* bubble it to the front of the queue */
+ iq_bubble_up(q, i, q->head, cmp, ctx);
+ }
+}
+
+int h2_proxy_iq_remove(h2_proxy_iqueue *q, int sid)
+{
+ int i;
+ for (i = 0; i < q->nelts; ++i) {
+ if (sid == q->elts[(q->head + i) % q->nalloc]) {
+ break;
+ }
+ }
+
+ if (i < q->nelts) {
+ ++i;
+ for (; i < q->nelts; ++i) {
+ q->elts[(q->head+i-1)%q->nalloc] = q->elts[(q->head+i)%q->nalloc];
+ }
+ --q->nelts;
+ return 1;
+ }
+ return 0;
+}
+
+void h2_proxy_iq_clear(h2_proxy_iqueue *q)
+{
+ q->nelts = 0;
+}
+
+void h2_proxy_iq_sort(h2_proxy_iqueue *q, h2_proxy_iq_cmp *cmp, void *ctx)
+{
+ /* Assume that changes in ordering are minimal. This needs,
+ * best case, q->nelts - 1 comparisons to check that nothing
+ * changed.
+ */
+ if (q->nelts > 0) {
+ int i, ni, prev, last;
+
+ /* Start at the end of the queue and create a tail of sorted
+ * entries. Make that tail one element longer in each iteration.
+ */
+ last = i = (q->head + q->nelts - 1) % q->nalloc;
+ while (i != q->head) {
+ prev = (q->nalloc + i - 1) % q->nalloc;
+
+ ni = iq_bubble_up(q, i, prev, cmp, ctx);
+ if (ni == prev) {
+ /* i bubbled one up, bubble the new i down, which
+ * keeps all tasks below i sorted. */
+ iq_bubble_down(q, i, last, cmp, ctx);
+ }
+ i = prev;
+ };
+ }
+}
+
+
+int h2_proxy_iq_shift(h2_proxy_iqueue *q)
+{
+ int sid;
+
+ if (q->nelts <= 0) {
+ return 0;
+ }
+
+ sid = q->elts[q->head];
+ q->head = (q->head + 1) % q->nalloc;
+ q->nelts--;
+
+ return sid;
+}
+
+static void iq_grow(h2_proxy_iqueue *q, int nlen)
+{
+ if (nlen > q->nalloc) {
+ int *nq = apr_pcalloc(q->pool, sizeof(int) * nlen);
+ if (q->nelts > 0) {
+ int l = ((q->head + q->nelts) % q->nalloc) - q->head;
+
+ memmove(nq, q->elts + q->head, sizeof(int) * l);
+ if (l < q->nelts) {
+ /* elts wrapped, append elts in [0, remain] to nq */
+ int remain = q->nelts - l;
+ memmove(nq + l, q->elts, sizeof(int) * remain);
+ }
+ }
+ q->elts = nq;
+ q->nalloc = nlen;
+ q->head = 0;
+ }
+}
+
+static void iq_swap(h2_proxy_iqueue *q, int i, int j)
+{
+ int x = q->elts[i];
+ q->elts[i] = q->elts[j];
+ q->elts[j] = x;
+}
+
+static int iq_bubble_up(h2_proxy_iqueue *q, int i, int top,
+ h2_proxy_iq_cmp *cmp, void *ctx)
+{
+ int prev;
+ while (((prev = (q->nalloc + i - 1) % q->nalloc), i != top)
+ && (*cmp)(q->elts[i], q->elts[prev], ctx) < 0) {
+ iq_swap(q, prev, i);
+ i = prev;
+ }
+ return i;
+}
+
+static int iq_bubble_down(h2_proxy_iqueue *q, int i, int bottom,
+ h2_proxy_iq_cmp *cmp, void *ctx)
+{
+ int next;
+ while (((next = (q->nalloc + i + 1) % q->nalloc), i != bottom)
+ && (*cmp)(q->elts[i], q->elts[next], ctx) > 0) {
+ iq_swap(q, next, i);
+ i = next;
+ }
+ return i;
+}
+
+/*******************************************************************************
+ * h2_proxy_ngheader
+ ******************************************************************************/
+#define H2_HD_MATCH_LIT_CS(l, name) \
+ ((strlen(name) == sizeof(l) - 1) && !apr_strnatcasecmp(l, name))
+
+static int h2_util_ignore_header(const char *name)
+{
+ /* never forward, ch. 8.1.2.2 */
+ return (H2_HD_MATCH_LIT_CS("connection", name)
+ || H2_HD_MATCH_LIT_CS("proxy-connection", name)
+ || H2_HD_MATCH_LIT_CS("upgrade", name)
+ || H2_HD_MATCH_LIT_CS("keep-alive", name)
+ || H2_HD_MATCH_LIT_CS("transfer-encoding", name));
+}
+
+static int count_header(void *ctx, const char *key, const char *value)
+{
+ if (!h2_util_ignore_header(key)) {
+ (*((size_t*)ctx))++;
+ }
+ return 1;
+}
+
+#define NV_ADD_LIT_CS(nv, k, v) add_header(nv, k, sizeof(k) - 1, v, strlen(v))
+#define NV_ADD_CS_CS(nv, k, v) add_header(nv, k, strlen(k), v, strlen(v))
+
+static int add_header(h2_proxy_ngheader *ngh,
+ const char *key, size_t key_len,
+ const char *value, size_t val_len)
+{
+ nghttp2_nv *nv = &ngh->nv[ngh->nvlen++];
+
+ nv->name = (uint8_t*)key;
+ nv->namelen = key_len;
+ nv->value = (uint8_t*)value;
+ nv->valuelen = val_len;
+ return 1;
+}
+
+static int add_table_header(void *ctx, const char *key, const char *value)
+{
+ if (!h2_util_ignore_header(key)) {
+ add_header(ctx, key, strlen(key), value, strlen(value));
+ }
+ return 1;
+}
+
+h2_proxy_ngheader *h2_proxy_util_nghd_make_req(apr_pool_t *p,
+ const h2_proxy_request *req)
+{
+
+ h2_proxy_ngheader *ngh;
+ size_t n;
+
+ ap_assert(req);
+ ap_assert(req->scheme);
+ ap_assert(req->authority);
+ ap_assert(req->path);
+ ap_assert(req->method);
+
+ n = 4;
+ apr_table_do(count_header, &n, req->headers, NULL);
+
+ ngh = apr_pcalloc(p, sizeof(h2_proxy_ngheader));
+ ngh->nv = apr_pcalloc(p, n * sizeof(nghttp2_nv));
+ NV_ADD_LIT_CS(ngh, ":scheme", req->scheme);
+ NV_ADD_LIT_CS(ngh, ":authority", req->authority);
+ NV_ADD_LIT_CS(ngh, ":path", req->path);
+ NV_ADD_LIT_CS(ngh, ":method", req->method);
+ apr_table_do(add_table_header, ngh, req->headers, NULL);
+
+ return ngh;
+}
+
+h2_proxy_ngheader *h2_proxy_util_nghd_make(apr_pool_t *p, apr_table_t *headers)
+{
+
+ h2_proxy_ngheader *ngh;
+ size_t n;
+
+ n = 0;
+ apr_table_do(count_header, &n, headers, NULL);
+
+ ngh = apr_pcalloc(p, sizeof(h2_proxy_ngheader));
+ ngh->nv = apr_pcalloc(p, n * sizeof(nghttp2_nv));
+ apr_table_do(add_table_header, ngh, headers, NULL);
+
+ return ngh;
+}
+
+/*******************************************************************************
+ * header HTTP/1 <-> HTTP/2 conversions
+ ******************************************************************************/
+
+typedef struct {
+ const char *name;
+ size_t len;
+} literal;
+
+#define H2_DEF_LITERAL(n) { (n), (sizeof(n)-1) }
+#define H2_LIT_ARGS(a) (a),H2_ALEN(a)
+
+static literal IgnoredRequestHeaders[] = {
+ H2_DEF_LITERAL("upgrade"),
+ H2_DEF_LITERAL("connection"),
+ H2_DEF_LITERAL("keep-alive"),
+ H2_DEF_LITERAL("http2-settings"),
+ H2_DEF_LITERAL("proxy-connection"),
+ H2_DEF_LITERAL("transfer-encoding"),
+};
+static literal IgnoredProxyRespHds[] = {
+ H2_DEF_LITERAL("alt-svc"),
+};
+
+static int ignore_header(const literal *lits, size_t llen,
+ const char *name, size_t nlen)
+{
+ const literal *lit;
+ size_t i;
+
+ for (i = 0; i < llen; ++i) {
+ lit = &lits[i];
+ if (lit->len == nlen && !apr_strnatcasecmp(lit->name, name)) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int h2_proxy_req_ignore_header(const char *name, size_t len)
+{
+ return ignore_header(H2_LIT_ARGS(IgnoredRequestHeaders), name, len);
+}
+
+int h2_proxy_res_ignore_header(const char *name, size_t len)
+{
+ return (h2_proxy_req_ignore_header(name, len)
+ || ignore_header(H2_LIT_ARGS(IgnoredProxyRespHds), name, len));
+}
+
+void h2_proxy_util_camel_case_header(char *s, size_t len)
+{
+ size_t start = 1;
+ size_t i;
+ for (i = 0; i < len; ++i) {
+ if (start) {
+ if (s[i] >= 'a' && s[i] <= 'z') {
+ s[i] -= 'a' - 'A';
+ }
+
+ start = 0;
+ }
+ else if (s[i] == '-') {
+ start = 1;
+ }
+ }
+}
+
+/*******************************************************************************
+ * h2 request handling
+ ******************************************************************************/
+
+/** Match a header value against a string constance, case insensitive */
+#define H2_HD_MATCH_LIT(l, name, nlen) \
+ ((nlen == sizeof(l) - 1) && !apr_strnatcasecmp(l, name))
+
+static apr_status_t h2_headers_add_h1(apr_table_t *headers, apr_pool_t *pool,
+ const char *name, size_t nlen,
+ const char *value, size_t vlen)
+{
+ char *hname, *hvalue;
+
+ if (h2_proxy_req_ignore_header(name, nlen)) {
+ return APR_SUCCESS;
+ }
+ else if (H2_HD_MATCH_LIT("cookie", name, nlen)) {
+ const char *existing = apr_table_get(headers, "cookie");
+ if (existing) {
+ char *nval;
+
+ /* Cookie header come separately in HTTP/2, but need
+ * to be merged by "; " (instead of default ", ")
+ */
+ hvalue = apr_pstrndup(pool, value, vlen);
+ nval = apr_psprintf(pool, "%s; %s", existing, hvalue);
+ apr_table_setn(headers, "Cookie", nval);
+ return APR_SUCCESS;
+ }
+ }
+ else if (H2_HD_MATCH_LIT("host", name, nlen)) {
+ if (apr_table_get(headers, "Host")) {
+ return APR_SUCCESS; /* ignore duplicate */
+ }
+ }
+
+ hname = apr_pstrndup(pool, name, nlen);
+ hvalue = apr_pstrndup(pool, value, vlen);
+ h2_proxy_util_camel_case_header(hname, nlen);
+ apr_table_mergen(headers, hname, hvalue);
+
+ return APR_SUCCESS;
+}
+
+static h2_proxy_request *h2_proxy_req_createn(int id, apr_pool_t *pool, const char *method,
+ const char *scheme, const char *authority,
+ const char *path, apr_table_t *header)
+{
+ h2_proxy_request *req = apr_pcalloc(pool, sizeof(h2_proxy_request));
+
+ req->method = method;
+ req->scheme = scheme;
+ req->authority = authority;
+ req->path = path;
+ req->headers = header? header : apr_table_make(pool, 10);
+ req->request_time = apr_time_now();
+
+ return req;
+}
+
+h2_proxy_request *h2_proxy_req_create(int id, apr_pool_t *pool)
+{
+ return h2_proxy_req_createn(id, pool, NULL, NULL, NULL, NULL, NULL);
+}
+
+typedef struct {
+ apr_table_t *headers;
+ apr_pool_t *pool;
+} h1_ctx;
+
+static int set_h1_header(void *ctx, const char *key, const char *value)
+{
+ h1_ctx *x = ctx;
+ size_t klen = strlen(key);
+ if (!h2_proxy_req_ignore_header(key, klen)) {
+ h2_headers_add_h1(x->headers, x->pool, key, klen, value, strlen(value));
+ }
+ return 1;
+}
+
+apr_status_t h2_proxy_req_make(h2_proxy_request *req, apr_pool_t *pool,
+ const char *method, const char *scheme,
+ const char *authority, const char *path,
+ apr_table_t *headers)
+{
+ h1_ctx x;
+ const char *val;
+
+ req->method = method;
+ req->scheme = scheme;
+ req->authority = authority;
+ req->path = path;
+
+ ap_assert(req->scheme);
+ ap_assert(req->authority);
+ ap_assert(req->path);
+ ap_assert(req->method);
+
+ x.pool = pool;
+ x.headers = req->headers;
+ apr_table_do(set_h1_header, &x, headers, NULL);
+ if ((val = apr_table_get(headers, "TE")) && ap_find_token(pool, val, "trailers")) {
+ /* client accepts trailers, forward this information */
+ apr_table_addn(req->headers, "TE", "trailers");
+ }
+ apr_table_setn(req->headers, "te", "trailers");
+ return APR_SUCCESS;
+}
+
+/*******************************************************************************
+ * frame logging
+ ******************************************************************************/
+
+int h2_proxy_util_frame_print(const nghttp2_frame *frame, char *buffer, size_t maxlen)
+{
+ char scratch[128];
+ size_t s_len = sizeof(scratch)/sizeof(scratch[0]);
+
+ switch (frame->hd.type) {
+ case NGHTTP2_DATA: {
+ return apr_snprintf(buffer, maxlen,
+ "DATA[length=%d, flags=%d, stream=%d, padlen=%d]",
+ (int)frame->hd.length, frame->hd.flags,
+ frame->hd.stream_id, (int)frame->data.padlen);
+ }
+ case NGHTTP2_HEADERS: {
+ return apr_snprintf(buffer, maxlen,
+ "HEADERS[length=%d, hend=%d, stream=%d, eos=%d]",
+ (int)frame->hd.length,
+ !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS),
+ frame->hd.stream_id,
+ !!(frame->hd.flags & NGHTTP2_FLAG_END_STREAM));
+ }
+ case NGHTTP2_PRIORITY: {
+ return apr_snprintf(buffer, maxlen,
+ "PRIORITY[length=%d, flags=%d, stream=%d]",
+ (int)frame->hd.length,
+ frame->hd.flags, frame->hd.stream_id);
+ }
+ case NGHTTP2_RST_STREAM: {
+ return apr_snprintf(buffer, maxlen,
+ "RST_STREAM[length=%d, flags=%d, stream=%d]",
+ (int)frame->hd.length,
+ frame->hd.flags, frame->hd.stream_id);
+ }
+ case NGHTTP2_SETTINGS: {
+ if (frame->hd.flags & NGHTTP2_FLAG_ACK) {
+ return apr_snprintf(buffer, maxlen,
+ "SETTINGS[ack=1, stream=%d]",
+ frame->hd.stream_id);
+ }
+ return apr_snprintf(buffer, maxlen,
+ "SETTINGS[length=%d, stream=%d]",
+ (int)frame->hd.length, frame->hd.stream_id);
+ }
+ case NGHTTP2_PUSH_PROMISE: {
+ return apr_snprintf(buffer, maxlen,
+ "PUSH_PROMISE[length=%d, hend=%d, stream=%d]",
+ (int)frame->hd.length,
+ !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS),
+ frame->hd.stream_id);
+ }
+ case NGHTTP2_PING: {
+ return apr_snprintf(buffer, maxlen,
+ "PING[length=%d, ack=%d, stream=%d]",
+ (int)frame->hd.length,
+ frame->hd.flags&NGHTTP2_FLAG_ACK,
+ frame->hd.stream_id);
+ }
+ case NGHTTP2_GOAWAY: {
+ size_t len = (frame->goaway.opaque_data_len < s_len)?
+ frame->goaway.opaque_data_len : s_len-1;
+ memcpy(scratch, frame->goaway.opaque_data, len);
+ scratch[len] = '\0';
+ return apr_snprintf(buffer, maxlen, "GOAWAY[error=%d, reason='%s', "
+ "last_stream=%d]", frame->goaway.error_code,
+ scratch, frame->goaway.last_stream_id);
+ }
+ case NGHTTP2_WINDOW_UPDATE: {
+ return apr_snprintf(buffer, maxlen,
+ "WINDOW_UPDATE[stream=%d, incr=%d]",
+ frame->hd.stream_id,
+ frame->window_update.window_size_increment);
+ }
+ default:
+ return apr_snprintf(buffer, maxlen,
+ "type=%d[length=%d, flags=%d, stream=%d]",
+ frame->hd.type, (int)frame->hd.length,
+ frame->hd.flags, frame->hd.stream_id);
+ }
+}
+
+/*******************************************************************************
+ * link header handling
+ ******************************************************************************/
+
+typedef struct {
+ apr_pool_t *pool;
+ request_rec *r;
+ proxy_dir_conf *conf;
+ const char *s;
+ int slen;
+ int i;
+ const char *server_uri;
+ int su_len;
+ const char *real_backend_uri;
+ int rbu_len;
+ const char *p_server_uri;
+ int psu_len;
+ int link_start;
+ int link_end;
+} link_ctx;
+
+static int attr_char(char c)
+{
+ switch (c) {
+ case '!':
+ case '#':
+ case '$':
+ case '&':
+ case '+':
+ case '-':
+ case '.':
+ case '^':
+ case '_':
+ case '`':
+ case '|':
+ case '~':
+ return 1;
+ default:
+ return apr_isalnum(c);
+ }
+}
+
+static int ptoken_char(char c)
+{
+ switch (c) {
+ case '!':
+ case '#':
+ case '$':
+ case '&':
+ case '\'':
+ case '(':
+ case ')':
+ case '*':
+ case '+':
+ case '-':
+ case '.':
+ case '/':
+ case ':':
+ case '<':
+ case '=':
+ case '>':
+ case '?':
+ case '@':
+ case '[':
+ case ']':
+ case '^':
+ case '_':
+ case '`':
+ case '{':
+ case '|':
+ case '}':
+ case '~':
+ return 1;
+ default:
+ return apr_isalnum(c);
+ }
+}
+
+static int skip_ws(link_ctx *ctx)
+{
+ char c;
+ while (ctx->i < ctx->slen
+ && (((c = ctx->s[ctx->i]) == ' ') || (c == '\t'))) {
+ ++ctx->i;
+ }
+ return (ctx->i < ctx->slen);
+}
+
+static int find_chr(link_ctx *ctx, char c, int *pidx)
+{
+ int j;
+ for (j = ctx->i; j < ctx->slen; ++j) {
+ if (ctx->s[j] == c) {
+ *pidx = j;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int read_chr(link_ctx *ctx, char c)
+{
+ if (ctx->i < ctx->slen && ctx->s[ctx->i] == c) {
+ ++ctx->i;
+ return 1;
+ }
+ return 0;
+}
+
+static int skip_qstring(link_ctx *ctx)
+{
+ if (skip_ws(ctx) && read_chr(ctx, '\"')) {
+ int end;
+ if (find_chr(ctx, '\"', &end)) {
+ ctx->i = end + 1;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int skip_ptoken(link_ctx *ctx)
+{
+ if (skip_ws(ctx)) {
+ int i;
+ for (i = ctx->i; i < ctx->slen && ptoken_char(ctx->s[i]); ++i) {
+ /* nop */
+ }
+ if (i > ctx->i) {
+ ctx->i = i;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+static int read_link(link_ctx *ctx)
+{
+ ctx->link_start = ctx->link_end = 0;
+ if (skip_ws(ctx) && read_chr(ctx, '<')) {
+ int end;
+ if (find_chr(ctx, '>', &end)) {
+ ctx->link_start = ctx->i;
+ ctx->link_end = end;
+ ctx->i = end + 1;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int skip_pname(link_ctx *ctx)
+{
+ if (skip_ws(ctx)) {
+ int i;
+ for (i = ctx->i; i < ctx->slen && attr_char(ctx->s[i]); ++i) {
+ /* nop */
+ }
+ if (i > ctx->i) {
+ ctx->i = i;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int skip_pvalue(link_ctx *ctx)
+{
+ if (skip_ws(ctx) && read_chr(ctx, '=')) {
+ if (skip_qstring(ctx) || skip_ptoken(ctx)) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int skip_param(link_ctx *ctx)
+{
+ if (skip_ws(ctx) && read_chr(ctx, ';')) {
+ if (skip_pname(ctx)) {
+ skip_pvalue(ctx); /* value is optional */
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int read_sep(link_ctx *ctx)
+{
+ if (skip_ws(ctx) && read_chr(ctx, ',')) {
+ return 1;
+ }
+ return 0;
+}
+
+static size_t subst_str(link_ctx *ctx, int start, int end, const char *ns)
+{
+ int olen, nlen, plen;
+ int delta;
+ char *p;
+
+ olen = end - start;
+ nlen = (int)strlen(ns);
+ delta = nlen - olen;
+ plen = ctx->slen + delta + 1;
+ p = apr_palloc(ctx->pool, plen);
+ memcpy(p, ctx->s, start);
+ memcpy(p + start, ns, nlen);
+ strcpy(p + start + nlen, ctx->s + end);
+ ctx->s = p;
+ ctx->slen = plen - 1; /* (int)strlen(p) */
+ if (ctx->i >= end) {
+ ctx->i += delta;
+ }
+ return nlen;
+}
+
+static void map_link(link_ctx *ctx)
+{
+ if (ctx->link_start < ctx->link_end) {
+ char buffer[HUGE_STRING_LEN];
+ size_t need_len, link_len, buffer_len, prepend_p_server;
+ const char *mapped;
+
+ buffer[0] = '\0';
+ buffer_len = 0;
+ link_len = ctx->link_end - ctx->link_start;
+ need_len = link_len + 1;
+ prepend_p_server = (ctx->s[ctx->link_start] == '/');
+ if (prepend_p_server) {
+ /* common to use relative uris in link header, for mappings
+ * to work need to prefix the backend server uri */
+ need_len += ctx->psu_len;
+ apr_cpystrn(buffer, ctx->p_server_uri, sizeof(buffer));
+ buffer_len = ctx->psu_len;
+ }
+ if (need_len > sizeof(buffer)) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, ctx->r, APLOGNO(03482)
+ "link_reverse_map uri too long, skipped: %s", ctx->s);
+ return;
+ }
+ apr_cpystrn(buffer + buffer_len, ctx->s + ctx->link_start, link_len + 1);
+ if (!prepend_p_server
+ && strcmp(ctx->real_backend_uri, ctx->p_server_uri)
+ && !strncmp(buffer, ctx->real_backend_uri, ctx->rbu_len)) {
+ /* the server uri and our local proxy uri we use differ, for mapping
+ * to work, we need to use the proxy uri */
+ int path_start = ctx->link_start + ctx->rbu_len;
+ link_len -= ctx->rbu_len;
+ memcpy(buffer, ctx->p_server_uri, ctx->psu_len);
+ memcpy(buffer + ctx->psu_len, ctx->s + path_start, link_len);
+ buffer_len = ctx->psu_len + link_len;
+ buffer[buffer_len] = '\0';
+ }
+ mapped = ap_proxy_location_reverse_map(ctx->r, ctx->conf, buffer);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, ctx->r,
+ "reverse_map[%s] %s --> %s", ctx->p_server_uri, buffer, mapped);
+ if (mapped != buffer) {
+ if (prepend_p_server) {
+ if (ctx->server_uri == NULL) {
+ ctx->server_uri = ap_construct_url(ctx->pool, "", ctx->r);
+ ctx->su_len = (int)strlen(ctx->server_uri);
+ }
+ if (!strncmp(mapped, ctx->server_uri, ctx->su_len)) {
+ mapped += ctx->su_len;
+ }
+ }
+ subst_str(ctx, ctx->link_start, ctx->link_end, mapped);
+ }
+ }
+}
+
+/* RFC 5988 <https://tools.ietf.org/html/rfc5988#section-6.2.1>
+ Link = "Link" ":" #link-value
+ link-value = "<" URI-Reference ">" *( ";" link-param )
+ link-param = ( ( "rel" "=" relation-types )
+ | ( "anchor" "=" <"> URI-Reference <"> )
+ | ( "rev" "=" relation-types )
+ | ( "hreflang" "=" Language-Tag )
+ | ( "media" "=" ( MediaDesc | ( <"> MediaDesc <"> ) ) )
+ | ( "title" "=" quoted-string )
+ | ( "title*" "=" ext-value )
+ | ( "type" "=" ( media-type | quoted-mt ) )
+ | ( link-extension ) )
+ link-extension = ( parmname [ "=" ( ptoken | quoted-string ) ] )
+ | ( ext-name-star "=" ext-value )
+ ext-name-star = parmname "*" ; reserved for RFC2231-profiled
+ ; extensions. Whitespace NOT
+ ; allowed in between.
+ ptoken = 1*ptokenchar
+ ptokenchar = "!" | "#" | "$" | "%" | "&" | "'" | "("
+ | ")" | "*" | "+" | "-" | "." | "/" | DIGIT
+ | ":" | "<" | "=" | ">" | "?" | "@" | ALPHA
+ | "[" | "]" | "^" | "_" | "`" | "{" | "|"
+ | "}" | "~"
+ media-type = type-name "/" subtype-name
+ quoted-mt = <"> media-type <">
+ relation-types = relation-type
+ | <"> relation-type *( 1*SP relation-type ) <">
+ relation-type = reg-rel-type | ext-rel-type
+ reg-rel-type = LOALPHA *( LOALPHA | DIGIT | "." | "-" )
+ ext-rel-type = URI
+
+ and from <https://tools.ietf.org/html/rfc5987>
+ parmname = 1*attr-char
+ attr-char = ALPHA / DIGIT
+ / "!" / "#" / "$" / "&" / "+" / "-" / "."
+ / "^" / "_" / "`" / "|" / "~"
+ */
+
+const char *h2_proxy_link_reverse_map(request_rec *r,
+ proxy_dir_conf *conf,
+ const char *real_backend_uri,
+ const char *proxy_server_uri,
+ const char *s)
+{
+ link_ctx ctx;
+
+ if (r->proxyreq != PROXYREQ_REVERSE) {
+ return s;
+ }
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.r = r;
+ ctx.pool = r->pool;
+ ctx.conf = conf;
+ ctx.real_backend_uri = real_backend_uri;
+ ctx.rbu_len = (int)strlen(ctx.real_backend_uri);
+ ctx.p_server_uri = proxy_server_uri;
+ ctx.psu_len = (int)strlen(ctx.p_server_uri);
+ ctx.s = s;
+ ctx.slen = (int)strlen(s);
+ while (read_link(&ctx)) {
+ while (skip_param(&ctx)) {
+ /* nop */
+ }
+ map_link(&ctx);
+ if (!read_sep(&ctx)) {
+ break;
+ }
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+ "link_reverse_map %s --> %s", s, ctx.s);
+ return ctx.s;
+}
+
+/*******************************************************************************
+ * FIFO queue
+ ******************************************************************************/
+
+struct h2_proxy_fifo {
+ void **elems;
+ int nelems;
+ int set;
+ int head;
+ int count;
+ int aborted;
+ apr_thread_mutex_t *lock;
+ apr_thread_cond_t *not_empty;
+ apr_thread_cond_t *not_full;
+};
+
+static int nth_index(h2_proxy_fifo *fifo, int n)
+{
+ return (fifo->head + n) % fifo->nelems;
+}
+
+static apr_status_t fifo_destroy(void *data)
+{
+ h2_proxy_fifo *fifo = data;
+
+ apr_thread_cond_destroy(fifo->not_empty);
+ apr_thread_cond_destroy(fifo->not_full);
+ apr_thread_mutex_destroy(fifo->lock);
+
+ return APR_SUCCESS;
+}
+
+static int index_of(h2_proxy_fifo *fifo, void *elem)
+{
+ int i;
+
+ for (i = 0; i < fifo->count; ++i) {
+ if (elem == fifo->elems[nth_index(fifo, i)]) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+static apr_status_t create_int(h2_proxy_fifo **pfifo, apr_pool_t *pool,
+ int capacity, int as_set)
+{
+ apr_status_t rv;
+ h2_proxy_fifo *fifo;
+
+ fifo = apr_pcalloc(pool, sizeof(*fifo));
+ if (fifo == NULL) {
+ return APR_ENOMEM;
+ }
+
+ rv = apr_thread_mutex_create(&fifo->lock,
+ APR_THREAD_MUTEX_UNNESTED, pool);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ rv = apr_thread_cond_create(&fifo->not_empty, pool);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ rv = apr_thread_cond_create(&fifo->not_full, pool);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ fifo->elems = apr_pcalloc(pool, capacity * sizeof(void*));
+ if (fifo->elems == NULL) {
+ return APR_ENOMEM;
+ }
+ fifo->nelems = capacity;
+ fifo->set = as_set;
+
+ *pfifo = fifo;
+ apr_pool_cleanup_register(pool, fifo, fifo_destroy, apr_pool_cleanup_null);
+
+ return APR_SUCCESS;
+}
+
+apr_status_t h2_proxy_fifo_create(h2_proxy_fifo **pfifo, apr_pool_t *pool, int capacity)
+{
+ return create_int(pfifo, pool, capacity, 0);
+}
+
+apr_status_t h2_proxy_fifo_set_create(h2_proxy_fifo **pfifo, apr_pool_t *pool, int capacity)
+{
+ return create_int(pfifo, pool, capacity, 1);
+}
+
+apr_status_t h2_proxy_fifo_term(h2_proxy_fifo *fifo)
+{
+ apr_status_t rv;
+ if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
+ fifo->aborted = 1;
+ apr_thread_mutex_unlock(fifo->lock);
+ }
+ return rv;
+}
+
+apr_status_t h2_proxy_fifo_interrupt(h2_proxy_fifo *fifo)
+{
+ apr_status_t rv;
+ if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
+ apr_thread_cond_broadcast(fifo->not_empty);
+ apr_thread_cond_broadcast(fifo->not_full);
+ apr_thread_mutex_unlock(fifo->lock);
+ }
+ return rv;
+}
+
+int h2_proxy_fifo_count(h2_proxy_fifo *fifo)
+{
+ return fifo->count;
+}
+
+int h2_proxy_fifo_capacity(h2_proxy_fifo *fifo)
+{
+ return fifo->nelems;
+}
+
+static apr_status_t check_not_empty(h2_proxy_fifo *fifo, int block)
+{
+ if (fifo->count == 0) {
+ if (!block) {
+ return APR_EAGAIN;
+ }
+ while (fifo->count == 0) {
+ if (fifo->aborted) {
+ return APR_EOF;
+ }
+ apr_thread_cond_wait(fifo->not_empty, fifo->lock);
+ }
+ }
+ return APR_SUCCESS;
+}
+
+static apr_status_t fifo_push(h2_proxy_fifo *fifo, void *elem, int block)
+{
+ apr_status_t rv;
+
+ if (fifo->aborted) {
+ return APR_EOF;
+ }
+
+ if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
+ if (fifo->set && index_of(fifo, elem) >= 0) {
+ /* set mode, elem already member */
+ apr_thread_mutex_unlock(fifo->lock);
+ return APR_EEXIST;
+ }
+ else if (fifo->count == fifo->nelems) {
+ if (block) {
+ while (fifo->count == fifo->nelems) {
+ if (fifo->aborted) {
+ apr_thread_mutex_unlock(fifo->lock);
+ return APR_EOF;
+ }
+ apr_thread_cond_wait(fifo->not_full, fifo->lock);
+ }
+ }
+ else {
+ apr_thread_mutex_unlock(fifo->lock);
+ return APR_EAGAIN;
+ }
+ }
+
+ ap_assert(fifo->count < fifo->nelems);
+ fifo->elems[nth_index(fifo, fifo->count)] = elem;
+ ++fifo->count;
+ if (fifo->count == 1) {
+ apr_thread_cond_broadcast(fifo->not_empty);
+ }
+ apr_thread_mutex_unlock(fifo->lock);
+ }
+ return rv;
+}
+
+apr_status_t h2_proxy_fifo_push(h2_proxy_fifo *fifo, void *elem)
+{
+ return fifo_push(fifo, elem, 1);
+}
+
+apr_status_t h2_proxy_fifo_try_push(h2_proxy_fifo *fifo, void *elem)
+{
+ return fifo_push(fifo, elem, 0);
+}
+
+static void *pull_head(h2_proxy_fifo *fifo)
+{
+ void *elem;
+
+ ap_assert(fifo->count > 0);
+ elem = fifo->elems[fifo->head];
+ --fifo->count;
+ if (fifo->count > 0) {
+ fifo->head = nth_index(fifo, 1);
+ if (fifo->count+1 == fifo->nelems) {
+ apr_thread_cond_broadcast(fifo->not_full);
+ }
+ }
+ return elem;
+}
+
+static apr_status_t fifo_pull(h2_proxy_fifo *fifo, void **pelem, int block)
+{
+ apr_status_t rv;
+
+ if (fifo->aborted) {
+ return APR_EOF;
+ }
+
+ if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
+ if ((rv = check_not_empty(fifo, block)) != APR_SUCCESS) {
+ apr_thread_mutex_unlock(fifo->lock);
+ *pelem = NULL;
+ return rv;
+ }
+
+ ap_assert(fifo->count > 0);
+ *pelem = pull_head(fifo);
+
+ apr_thread_mutex_unlock(fifo->lock);
+ }
+ return rv;
+}
+
+apr_status_t h2_proxy_fifo_pull(h2_proxy_fifo *fifo, void **pelem)
+{
+ return fifo_pull(fifo, pelem, 1);
+}
+
+apr_status_t h2_proxy_fifo_try_pull(h2_proxy_fifo *fifo, void **pelem)
+{
+ return fifo_pull(fifo, pelem, 0);
+}
+
+apr_status_t h2_proxy_fifo_remove(h2_proxy_fifo *fifo, void *elem)
+{
+ apr_status_t rv;
+
+ if (fifo->aborted) {
+ return APR_EOF;
+ }
+
+ if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
+ int i, rc;
+ void *e;
+
+ rc = 0;
+ for (i = 0; i < fifo->count; ++i) {
+ e = fifo->elems[nth_index(fifo, i)];
+ if (e == elem) {
+ ++rc;
+ }
+ else if (rc) {
+ fifo->elems[nth_index(fifo, i-rc)] = e;
+ }
+ }
+ if (rc) {
+ fifo->count -= rc;
+ if (fifo->count + rc == fifo->nelems) {
+ apr_thread_cond_broadcast(fifo->not_full);
+ }
+ rv = APR_SUCCESS;
+ }
+ else {
+ rv = APR_EAGAIN;
+ }
+
+ apr_thread_mutex_unlock(fifo->lock);
+ }
+ return rv;
+}
diff --git a/modules/http2/h2_proxy_util.h b/modules/http2/h2_proxy_util.h
new file mode 100644
index 0000000..202363d
--- /dev/null
+++ b/modules/http2/h2_proxy_util.h
@@ -0,0 +1,257 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __mod_h2__h2_proxy_util__
+#define __mod_h2__h2_proxy_util__
+
+/*******************************************************************************
+ * some debugging/format helpers
+ ******************************************************************************/
+struct h2_proxy_request;
+struct nghttp2_frame;
+
+int h2_proxy_util_frame_print(const nghttp2_frame *frame, char *buffer, size_t maxlen);
+
+/*******************************************************************************
+ * ihash - hash for structs with int identifier
+ ******************************************************************************/
+typedef struct h2_proxy_ihash_t h2_proxy_ihash_t;
+typedef int h2_proxy_ihash_iter_t(void *ctx, void *val);
+
+/**
+ * Create a hash for structures that have an identifying int member.
+ * @param pool the pool to use
+ * @param offset_of_int the offsetof() the int member in the struct
+ */
+h2_proxy_ihash_t *h2_proxy_ihash_create(apr_pool_t *pool, size_t offset_of_int);
+
+size_t h2_proxy_ihash_count(h2_proxy_ihash_t *ih);
+int h2_proxy_ihash_empty(h2_proxy_ihash_t *ih);
+void *h2_proxy_ihash_get(h2_proxy_ihash_t *ih, int id);
+
+/**
+ * Iterate over the hash members (without defined order) and invoke
+ * fn for each member until 0 is returned.
+ * @param ih the hash to iterate over
+ * @param fn the function to invoke on each member
+ * @param ctx user supplied data passed into each iteration call
+ * @return 0 if one iteration returned 0, otherwise != 0
+ */
+int h2_proxy_ihash_iter(h2_proxy_ihash_t *ih, h2_proxy_ihash_iter_t *fn, void *ctx);
+
+void h2_proxy_ihash_add(h2_proxy_ihash_t *ih, void *val);
+void h2_proxy_ihash_remove(h2_proxy_ihash_t *ih, int id);
+void h2_proxy_ihash_remove_val(h2_proxy_ihash_t *ih, void *val);
+void h2_proxy_ihash_clear(h2_proxy_ihash_t *ih);
+
+size_t h2_proxy_ihash_shift(h2_proxy_ihash_t *ih, void **buffer, size_t max);
+size_t h2_proxy_ihash_ishift(h2_proxy_ihash_t *ih, int *buffer, size_t max);
+
+/*******************************************************************************
+ * iqueue - sorted list of int with user defined ordering
+ ******************************************************************************/
+typedef struct h2_proxy_iqueue {
+ int *elts;
+ int head;
+ int nelts;
+ int nalloc;
+ apr_pool_t *pool;
+} h2_proxy_iqueue;
+
+/**
+ * Comparator for two int to determine their order.
+ *
+ * @param i1 first int to compare
+ * @param i2 second int to compare
+ * @param ctx provided user data
+ * @return value is the same as for strcmp() and has the effect:
+ * == 0: s1 and s2 are treated equal in ordering
+ * < 0: s1 should be sorted before s2
+ * > 0: s2 should be sorted before s1
+ */
+typedef int h2_proxy_iq_cmp(int i1, int i2, void *ctx);
+
+/**
+ * Allocate a new queue from the pool and initialize.
+ * @param id the identifier of the queue
+ * @param pool the memory pool
+ */
+h2_proxy_iqueue *h2_proxy_iq_create(apr_pool_t *pool, int capacity);
+
+/**
+ * Return != 0 iff there are no tasks in the queue.
+ * @param q the queue to check
+ */
+int h2_proxy_iq_empty(h2_proxy_iqueue *q);
+
+/**
+ * Return the number of int in the queue.
+ * @param q the queue to get size on
+ */
+int h2_proxy_iq_count(h2_proxy_iqueue *q);
+
+/**
+ * Add a stream id to the queue.
+ *
+ * @param q the queue to append the task to
+ * @param sid the stream id to add
+ * @param cmp the comparator for sorting
+ * @param ctx user data for comparator
+ */
+void h2_proxy_iq_add(h2_proxy_iqueue *q, int sid, h2_proxy_iq_cmp *cmp, void *ctx);
+
+/**
+ * Remove the stream id from the queue. Return != 0 iff task
+ * was found in queue.
+ * @param q the task queue
+ * @param sid the stream id to remove
+ * @return != 0 iff task was found in queue
+ */
+int h2_proxy_iq_remove(h2_proxy_iqueue *q, int sid);
+
+/**
+ * Remove all entries in the queue.
+ */
+void h2_proxy_iq_clear(h2_proxy_iqueue *q);
+
+/**
+ * Sort the stream idqueue again. Call if the task ordering
+ * has changed.
+ *
+ * @param q the queue to sort
+ * @param cmp the comparator for sorting
+ * @param ctx user data for the comparator
+ */
+void h2_proxy_iq_sort(h2_proxy_iqueue *q, h2_proxy_iq_cmp *cmp, void *ctx);
+
+/**
+ * Get the first stream id from the queue or NULL if the queue is empty.
+ * The task will be removed.
+ *
+ * @param q the queue to get the first task from
+ * @return the first stream id of the queue, 0 if empty
+ */
+int h2_proxy_iq_shift(h2_proxy_iqueue *q);
+
+/*******************************************************************************
+ * common helpers
+ ******************************************************************************/
+/* h2_proxy_log2(n) iff n is a power of 2 */
+unsigned char h2_proxy_log2(int n);
+
+/*******************************************************************************
+ * HTTP/2 header helpers
+ ******************************************************************************/
+void h2_proxy_util_camel_case_header(char *s, size_t len);
+int h2_proxy_res_ignore_header(const char *name, size_t len);
+
+/*******************************************************************************
+ * nghttp2 helpers
+ ******************************************************************************/
+typedef struct h2_proxy_ngheader {
+ nghttp2_nv *nv;
+ apr_size_t nvlen;
+} h2_proxy_ngheader;
+h2_proxy_ngheader *h2_proxy_util_nghd_make_req(apr_pool_t *p,
+ const struct h2_proxy_request *req);
+
+h2_proxy_ngheader *h2_proxy_util_nghd_make(apr_pool_t *p, apr_table_t *headers);
+
+/*******************************************************************************
+ * h2_proxy_request helpers
+ ******************************************************************************/
+typedef struct h2_proxy_request h2_proxy_request;
+
+struct h2_proxy_request {
+ const char *method; /* pseudo header values, see ch. 8.1.2.3 */
+ const char *scheme;
+ const char *authority;
+ const char *path;
+
+ apr_table_t *headers;
+
+ apr_time_t request_time;
+
+ int chunked; /* iff request body needs to be forwarded as chunked */
+};
+
+h2_proxy_request *h2_proxy_req_create(int id, apr_pool_t *pool);
+apr_status_t h2_proxy_req_make(h2_proxy_request *req, apr_pool_t *pool,
+ const char *method, const char *scheme,
+ const char *authority, const char *path,
+ apr_table_t *headers);
+
+/*******************************************************************************
+ * reverse mapping for link headers
+ ******************************************************************************/
+const char *h2_proxy_link_reverse_map(request_rec *r,
+ proxy_dir_conf *conf,
+ const char *real_server_uri,
+ const char *proxy_server_uri,
+ const char *s);
+
+/*******************************************************************************
+ * FIFO queue
+ ******************************************************************************/
+
+/**
+ * A thread-safe FIFO queue with some extra bells and whistles, if you
+ * do not need anything special, better use 'apr_queue'.
+ */
+typedef struct h2_proxy_fifo h2_proxy_fifo;
+
+/**
+ * Create a FIFO queue that can hold up to capacity elements. Elements can
+ * appear several times.
+ */
+apr_status_t h2_proxy_fifo_create(h2_proxy_fifo **pfifo, apr_pool_t *pool, int capacity);
+
+/**
+ * Create a FIFO set that can hold up to capacity elements. Elements only
+ * appear once. Pushing an element already present does not change the
+ * queue and is successful.
+ */
+apr_status_t h2_proxy_fifo_set_create(h2_proxy_fifo **pfifo, apr_pool_t *pool, int capacity);
+
+apr_status_t h2_proxy_fifo_term(h2_proxy_fifo *fifo);
+apr_status_t h2_proxy_fifo_interrupt(h2_proxy_fifo *fifo);
+
+int h2_proxy_fifo_capacity(h2_proxy_fifo *fifo);
+int h2_proxy_fifo_count(h2_proxy_fifo *fifo);
+
+/**
+ * Push en element into the queue. Blocks if there is no capacity left.
+ *
+ * @param fifo the FIFO queue
+ * @param elem the element to push
+ * @return APR_SUCCESS on push, APR_EAGAIN on try_push on a full queue,
+ * APR_EEXIST when in set mode and elem already there.
+ */
+apr_status_t h2_proxy_fifo_push(h2_proxy_fifo *fifo, void *elem);
+apr_status_t h2_proxy_fifo_try_push(h2_proxy_fifo *fifo, void *elem);
+
+apr_status_t h2_proxy_fifo_pull(h2_proxy_fifo *fifo, void **pelem);
+apr_status_t h2_proxy_fifo_try_pull(h2_proxy_fifo *fifo, void **pelem);
+
+/**
+ * Remove the elem from the queue, will remove multiple appearances.
+ * @param elem the element to remove
+ * @return APR_SUCCESS iff > 0 elems were removed, APR_EAGAIN otherwise.
+ */
+apr_status_t h2_proxy_fifo_remove(h2_proxy_fifo *fifo, void *elem);
+
+
+#endif /* defined(__mod_h2__h2_proxy_util__) */
diff --git a/modules/http2/h2_push.c b/modules/http2/h2_push.c
new file mode 100644
index 0000000..462c470
--- /dev/null
+++ b/modules/http2/h2_push.c
@@ -0,0 +1,876 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+
+#include <apr_lib.h>
+#include <apr_strings.h>
+#include <apr_hash.h>
+#include <apr_time.h>
+
+#ifdef H2_OPENSSL
+#include <openssl/evp.h>
+#endif
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_log.h>
+#include <http_protocol.h>
+
+#include "h2_private.h"
+#include "h2_protocol.h"
+#include "h2_util.h"
+#include "h2_push.h"
+#include "h2_request.h"
+#include "h2_session.h"
+#include "h2_stream.h"
+
+/*******************************************************************************
+ * link header handling
+ ******************************************************************************/
+
+static const char *policy_str(h2_push_policy policy)
+{
+ switch (policy) {
+ case H2_PUSH_NONE:
+ return "none";
+ case H2_PUSH_FAST_LOAD:
+ return "fast-load";
+ case H2_PUSH_HEAD:
+ return "head";
+ default:
+ return "default";
+ }
+}
+
+typedef struct {
+ const h2_request *req;
+ apr_uint32_t push_policy;
+ apr_pool_t *pool;
+ apr_array_header_t *pushes;
+ const char *s;
+ size_t slen;
+ size_t i;
+
+ const char *link;
+ apr_table_t *params;
+ char b[4096];
+} link_ctx;
+
+static int attr_char(char c)
+{
+ switch (c) {
+ case '!':
+ case '#':
+ case '$':
+ case '&':
+ case '+':
+ case '-':
+ case '.':
+ case '^':
+ case '_':
+ case '`':
+ case '|':
+ case '~':
+ return 1;
+ default:
+ return apr_isalnum(c);
+ }
+}
+
+static int ptoken_char(char c)
+{
+ switch (c) {
+ case '!':
+ case '#':
+ case '$':
+ case '&':
+ case '\'':
+ case '(':
+ case ')':
+ case '*':
+ case '+':
+ case '-':
+ case '.':
+ case '/':
+ case ':':
+ case '<':
+ case '=':
+ case '>':
+ case '?':
+ case '@':
+ case '[':
+ case ']':
+ case '^':
+ case '_':
+ case '`':
+ case '{':
+ case '|':
+ case '}':
+ case '~':
+ return 1;
+ default:
+ return apr_isalnum(c);
+ }
+}
+
+static int skip_ws(link_ctx *ctx)
+{
+ char c;
+ while (ctx->i < ctx->slen
+ && (((c = ctx->s[ctx->i]) == ' ') || (c == '\t'))) {
+ ++ctx->i;
+ }
+ return (ctx->i < ctx->slen);
+}
+
+static int find_chr(link_ctx *ctx, char c, size_t *pidx)
+{
+ size_t j;
+ for (j = ctx->i; j < ctx->slen; ++j) {
+ if (ctx->s[j] == c) {
+ *pidx = j;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int read_chr(link_ctx *ctx, char c)
+{
+ if (ctx->i < ctx->slen && ctx->s[ctx->i] == c) {
+ ++ctx->i;
+ return 1;
+ }
+ return 0;
+}
+
+static char *mk_str(link_ctx *ctx, size_t end)
+{
+ if (ctx->i < end) {
+ return apr_pstrndup(ctx->pool, ctx->s + ctx->i, end - ctx->i);
+ }
+ return (char*)"";
+}
+
+static int read_qstring(link_ctx *ctx, const char **ps)
+{
+ if (skip_ws(ctx) && read_chr(ctx, '\"')) {
+ size_t end;
+ if (find_chr(ctx, '\"', &end)) {
+ *ps = mk_str(ctx, end);
+ ctx->i = end + 1;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int read_ptoken(link_ctx *ctx, const char **ps)
+{
+ if (skip_ws(ctx)) {
+ size_t i;
+ for (i = ctx->i; i < ctx->slen && ptoken_char(ctx->s[i]); ++i) {
+ /* nop */
+ }
+ if (i > ctx->i) {
+ *ps = mk_str(ctx, i);
+ ctx->i = i;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+static int read_link(link_ctx *ctx)
+{
+ if (skip_ws(ctx) && read_chr(ctx, '<')) {
+ size_t end;
+ if (find_chr(ctx, '>', &end)) {
+ ctx->link = mk_str(ctx, end);
+ ctx->i = end + 1;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int read_pname(link_ctx *ctx, const char **pname)
+{
+ if (skip_ws(ctx)) {
+ size_t i;
+ for (i = ctx->i; i < ctx->slen && attr_char(ctx->s[i]); ++i) {
+ /* nop */
+ }
+ if (i > ctx->i) {
+ *pname = mk_str(ctx, i);
+ ctx->i = i;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int read_pvalue(link_ctx *ctx, const char **pvalue)
+{
+ if (skip_ws(ctx) && read_chr(ctx, '=')) {
+ if (read_qstring(ctx, pvalue) || read_ptoken(ctx, pvalue)) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int read_param(link_ctx *ctx)
+{
+ if (skip_ws(ctx) && read_chr(ctx, ';')) {
+ const char *name, *value = "";
+ if (read_pname(ctx, &name)) {
+ read_pvalue(ctx, &value); /* value is optional */
+ apr_table_setn(ctx->params, name, value);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int read_sep(link_ctx *ctx)
+{
+ if (skip_ws(ctx) && read_chr(ctx, ',')) {
+ return 1;
+ }
+ return 0;
+}
+
+static void init_params(link_ctx *ctx)
+{
+ if (!ctx->params) {
+ ctx->params = apr_table_make(ctx->pool, 5);
+ }
+ else {
+ apr_table_clear(ctx->params);
+ }
+}
+
+static int same_authority(const h2_request *req, const apr_uri_t *uri)
+{
+ if (uri->scheme != NULL && strcmp(uri->scheme, req->scheme)) {
+ return 0;
+ }
+ if (uri->hostinfo != NULL && strcmp(uri->hostinfo, req->authority)) {
+ return 0;
+ }
+ return 1;
+}
+
+static int set_push_header(void *ctx, const char *key, const char *value)
+{
+ size_t klen = strlen(key);
+ if (H2_HD_MATCH_LIT("User-Agent", key, klen)
+ || H2_HD_MATCH_LIT("Accept", key, klen)
+ || H2_HD_MATCH_LIT("Accept-Encoding", key, klen)
+ || H2_HD_MATCH_LIT("Accept-Language", key, klen)
+ || H2_HD_MATCH_LIT("Cache-Control", key, klen)) {
+ apr_table_setn(ctx, key, value);
+ }
+ return 1;
+}
+
+static int has_param(link_ctx *ctx, const char *param)
+{
+ const char *p = apr_table_get(ctx->params, param);
+ return !!p;
+}
+
+static int has_relation(link_ctx *ctx, const char *rel)
+{
+ const char *s, *val = apr_table_get(ctx->params, "rel");
+ if (val) {
+ if (!strcmp(rel, val)) {
+ return 1;
+ }
+ s = ap_strstr_c(val, rel);
+ if (s && (s == val || s[-1] == ' ')) {
+ s += strlen(rel);
+ if (!*s || *s == ' ') {
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+static int add_push(link_ctx *ctx)
+{
+ /* so, we have read a Link header and need to decide
+ * if we transform it into a push.
+ */
+ if (has_relation(ctx, "preload") && !has_param(ctx, "nopush")) {
+ apr_uri_t uri;
+ if (apr_uri_parse(ctx->pool, ctx->link, &uri) == APR_SUCCESS) {
+ if (uri.path && same_authority(ctx->req, &uri)) {
+ char *path;
+ const char *method;
+ apr_table_t *headers;
+ h2_request *req;
+ h2_push *push;
+
+ /* We only want to generate pushes for resources in the
+ * same authority than the original request.
+ * icing: i think that is wise, otherwise we really need to
+ * check that the vhost/server is available and uses the same
+ * TLS (if any) parameters.
+ */
+ path = apr_uri_unparse(ctx->pool, &uri, APR_URI_UNP_OMITSITEPART);
+ push = apr_pcalloc(ctx->pool, sizeof(*push));
+ switch (ctx->push_policy) {
+ case H2_PUSH_HEAD:
+ method = "HEAD";
+ break;
+ default:
+ method = "GET";
+ break;
+ }
+ headers = apr_table_make(ctx->pool, 5);
+ apr_table_do(set_push_header, headers, ctx->req->headers, NULL);
+ req = h2_request_create(0, ctx->pool, method, ctx->req->scheme,
+ ctx->req->authority, path, headers);
+ /* atm, we do not push on pushes */
+ h2_request_end_headers(req, ctx->pool, 0);
+ push->req = req;
+ if (has_param(ctx, "critical")) {
+ h2_priority *prio = apr_pcalloc(ctx->pool, sizeof(*prio));
+ prio->dependency = H2_DEPENDANT_BEFORE;
+ push->priority = prio;
+ }
+ if (!ctx->pushes) {
+ ctx->pushes = apr_array_make(ctx->pool, 5, sizeof(h2_push*));
+ }
+ APR_ARRAY_PUSH(ctx->pushes, h2_push*) = push;
+ }
+ }
+ }
+ return 0;
+}
+
+static void inspect_link(link_ctx *ctx, const char *s, size_t slen)
+{
+ /* RFC 5988 <https://tools.ietf.org/html/rfc5988#section-6.2.1>
+ Link = "Link" ":" #link-value
+ link-value = "<" URI-Reference ">" *( ";" link-param )
+ link-param = ( ( "rel" "=" relation-types )
+ | ( "anchor" "=" <"> URI-Reference <"> )
+ | ( "rev" "=" relation-types )
+ | ( "hreflang" "=" Language-Tag )
+ | ( "media" "=" ( MediaDesc | ( <"> MediaDesc <"> ) ) )
+ | ( "title" "=" quoted-string )
+ | ( "title*" "=" ext-value )
+ | ( "type" "=" ( media-type | quoted-mt ) )
+ | ( link-extension ) )
+ link-extension = ( parmname [ "=" ( ptoken | quoted-string ) ] )
+ | ( ext-name-star "=" ext-value )
+ ext-name-star = parmname "*" ; reserved for RFC2231-profiled
+ ; extensions. Whitespace NOT
+ ; allowed in between.
+ ptoken = 1*ptokenchar
+ ptokenchar = "!" | "#" | "$" | "%" | "&" | "'" | "("
+ | ")" | "*" | "+" | "-" | "." | "/" | DIGIT
+ | ":" | "<" | "=" | ">" | "?" | "@" | ALPHA
+ | "[" | "]" | "^" | "_" | "`" | "{" | "|"
+ | "}" | "~"
+ media-type = type-name "/" subtype-name
+ quoted-mt = <"> media-type <">
+ relation-types = relation-type
+ | <"> relation-type *( 1*SP relation-type ) <">
+ relation-type = reg-rel-type | ext-rel-type
+ reg-rel-type = LOALPHA *( LOALPHA | DIGIT | "." | "-" )
+ ext-rel-type = URI
+
+ and from <https://tools.ietf.org/html/rfc5987>
+ parmname = 1*attr-char
+ attr-char = ALPHA / DIGIT
+ / "!" / "#" / "$" / "&" / "+" / "-" / "."
+ / "^" / "_" / "`" / "|" / "~"
+ */
+
+ ctx->s = s;
+ ctx->slen = slen;
+ ctx->i = 0;
+
+ while (read_link(ctx)) {
+ init_params(ctx);
+ while (read_param(ctx)) {
+ /* nop */
+ }
+ add_push(ctx);
+ if (!read_sep(ctx)) {
+ break;
+ }
+ }
+}
+
+static int head_iter(void *ctx, const char *key, const char *value)
+{
+ if (!apr_strnatcasecmp("link", key)) {
+ inspect_link(ctx, value, strlen(value));
+ }
+ return 1;
+}
+
+#if AP_HAS_RESPONSE_BUCKETS
+apr_array_header_t *h2_push_collect(apr_pool_t *p,
+ const struct h2_request *req,
+ apr_uint32_t push_policy,
+ const ap_bucket_response *res)
+#else
+apr_array_header_t *h2_push_collect(apr_pool_t *p,
+ const struct h2_request *req,
+ apr_uint32_t push_policy,
+ const struct h2_headers *res)
+#endif
+{
+ if (req && push_policy != H2_PUSH_NONE) {
+ /* Collect push candidates from the request/response pair.
+ *
+ * One source for pushes are "rel=preload" link headers
+ * in the response.
+ *
+ * TODO: This may be extended in the future by hooks or callbacks
+ * where other modules can provide push information directly.
+ */
+ if (res->headers) {
+ link_ctx ctx;
+
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.req = req;
+ ctx.push_policy = push_policy;
+ ctx.pool = p;
+
+ apr_table_do(head_iter, &ctx, res->headers, NULL);
+ if (ctx.pushes) {
+ apr_table_setn(res->headers, "push-policy",
+ policy_str(push_policy));
+ }
+ return ctx.pushes;
+ }
+ }
+ return NULL;
+}
+
+#define GCSLOG_LEVEL APLOG_TRACE1
+
+typedef struct h2_push_diary_entry {
+ apr_uint64_t hash;
+} h2_push_diary_entry;
+
+
+#ifdef H2_OPENSSL
+static void sha256_update(EVP_MD_CTX *ctx, const char *s)
+{
+ EVP_DigestUpdate(ctx, s, strlen(s));
+}
+
+static void calc_sha256_hash(h2_push_diary *diary, apr_uint64_t *phash, h2_push *push)
+{
+ EVP_MD_CTX *md;
+ apr_uint64_t val;
+ unsigned char hash[EVP_MAX_MD_SIZE];
+ unsigned len, i;
+
+ md = EVP_MD_CTX_create();
+ ap_assert(md != NULL);
+
+ i = EVP_DigestInit_ex(md, EVP_sha256(), NULL);
+ ap_assert(i == 1);
+ sha256_update(md, push->req->scheme);
+ sha256_update(md, "://");
+ sha256_update(md, push->req->authority);
+ sha256_update(md, push->req->path);
+ EVP_DigestFinal(md, hash, &len);
+
+ val = 0;
+ for (i = 0; i != len; ++i)
+ val = val * 256 + hash[i];
+ *phash = val >> (64 - diary->mask_bits);
+}
+#endif
+
+
+static unsigned int val_apr_hash(const char *str)
+{
+ apr_ssize_t len = (apr_ssize_t)strlen(str);
+ return apr_hashfunc_default(str, &len);
+}
+
+static void calc_apr_hash(h2_push_diary *diary, apr_uint64_t *phash, h2_push *push)
+{
+ apr_uint64_t val;
+ (void)diary;
+#if APR_UINT64_MAX > UINT_MAX
+ val = ((apr_uint64_t)(val_apr_hash(push->req->scheme)) << 32);
+ val ^= ((apr_uint64_t)(val_apr_hash(push->req->authority)) << 16);
+ val ^= val_apr_hash(push->req->path);
+#else
+ val = val_apr_hash(push->req->scheme);
+ val ^= val_apr_hash(push->req->authority);
+ val ^= val_apr_hash(push->req->path);
+#endif
+ *phash = val;
+}
+
+static apr_int32_t ceil_power_of_2(apr_int32_t n)
+{
+ if (n <= 2) return 2;
+ --n;
+ n |= n >> 1;
+ n |= n >> 2;
+ n |= n >> 4;
+ n |= n >> 8;
+ n |= n >> 16;
+ return ++n;
+}
+
+static h2_push_diary *diary_create(apr_pool_t *p, h2_push_digest_type dtype,
+ int N)
+{
+ h2_push_diary *diary = NULL;
+
+ if (N > 0) {
+ diary = apr_pcalloc(p, sizeof(*diary));
+
+ diary->NMax = ceil_power_of_2(N);
+ diary->N = diary->NMax;
+ /* the mask we use in value comparison depends on where we got
+ * the values from. If we calculate them ourselves, we can use
+ * the full 64 bits.
+ * If we set the diary via a compressed golomb set, we have less
+ * relevant bits and need to use a smaller mask. */
+ diary->mask_bits = 64;
+ /* grows by doubling, start with a power of 2 */
+ diary->entries = apr_array_make(p, 16, sizeof(h2_push_diary_entry));
+
+ switch (dtype) {
+#ifdef H2_OPENSSL
+ case H2_PUSH_DIGEST_SHA256:
+ diary->dtype = H2_PUSH_DIGEST_SHA256;
+ diary->dcalc = calc_sha256_hash;
+ break;
+#endif /* ifdef H2_OPENSSL */
+ default:
+ diary->dtype = H2_PUSH_DIGEST_APR_HASH;
+ diary->dcalc = calc_apr_hash;
+ break;
+ }
+ }
+
+ return diary;
+}
+
+h2_push_diary *h2_push_diary_create(apr_pool_t *p, int N)
+{
+ return diary_create(p, H2_PUSH_DIGEST_SHA256, N);
+}
+
+static int h2_push_diary_find(h2_push_diary *diary, apr_uint64_t hash)
+{
+ if (diary) {
+ h2_push_diary_entry *e;
+ int i;
+
+ /* search from the end, where the last accessed digests are */
+ for (i = diary->entries->nelts-1; i >= 0; --i) {
+ e = &APR_ARRAY_IDX(diary->entries, i, h2_push_diary_entry);
+ if (e->hash == hash) {
+ return i;
+ }
+ }
+ }
+ return -1;
+}
+
+static void move_to_last(h2_push_diary *diary, apr_size_t idx)
+{
+ h2_push_diary_entry *entries = (h2_push_diary_entry*)diary->entries->elts;
+ h2_push_diary_entry e;
+ apr_size_t lastidx;
+
+ /* Move an existing entry to the last place */
+ if (diary->entries->nelts <= 0)
+ return;
+
+ /* move entry[idx] to the end */
+ lastidx = diary->entries->nelts - 1;
+ if (idx < lastidx) {
+ e = entries[idx];
+ memmove(entries+idx, entries+idx+1, sizeof(h2_push_diary_entry) * (lastidx - idx));
+ entries[lastidx] = e;
+ }
+}
+
+static void remove_first(h2_push_diary *diary)
+{
+ h2_push_diary_entry *entries = (h2_push_diary_entry*)diary->entries->elts;
+ int lastidx;
+
+ /* move remaining entries to index 0 */
+ lastidx = diary->entries->nelts - 1;
+ if (lastidx > 0) {
+ --diary->entries->nelts;
+ memmove(entries, entries+1, sizeof(h2_push_diary_entry) * diary->entries->nelts);
+ }
+}
+
+static void h2_push_diary_append(h2_push_diary *diary, h2_push_diary_entry *e)
+{
+ while (diary->entries->nelts >= diary->N) {
+ remove_first(diary);
+ }
+ /* append a new diary entry at the end */
+ APR_ARRAY_PUSH(diary->entries, h2_push_diary_entry) = *e;
+ /* Intentional no APLOGNO */
+ ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, diary->entries->pool,
+ "push_diary_append: %"APR_UINT64_T_HEX_FMT, e->hash);
+}
+
+apr_array_header_t *h2_push_diary_update(h2_session *session, apr_array_header_t *pushes)
+{
+ apr_array_header_t *npushes = pushes;
+ h2_push_diary_entry e;
+ int i, idx;
+
+ if (session->push_diary && pushes) {
+ npushes = NULL;
+
+ for (i = 0; i < pushes->nelts; ++i) {
+ h2_push *push;
+
+ push = APR_ARRAY_IDX(pushes, i, h2_push*);
+ session->push_diary->dcalc(session->push_diary, &e.hash, push);
+ idx = h2_push_diary_find(session->push_diary, e.hash);
+ if (idx >= 0) {
+ /* Intentional no APLOGNO */
+ ap_log_cerror(APLOG_MARK, GCSLOG_LEVEL, 0, session->c1,
+ "push_diary_update: already there PUSH %s", push->req->path);
+ move_to_last(session->push_diary, (apr_size_t)idx);
+ }
+ else {
+ /* Intentional no APLOGNO */
+ ap_log_cerror(APLOG_MARK, GCSLOG_LEVEL, 0, session->c1,
+ "push_diary_update: adding PUSH %s", push->req->path);
+ if (!npushes) {
+ npushes = apr_array_make(pushes->pool, 5, sizeof(h2_push_diary_entry*));
+ }
+ APR_ARRAY_PUSH(npushes, h2_push*) = push;
+ h2_push_diary_append(session->push_diary, &e);
+ }
+ }
+ }
+ return npushes;
+}
+
+#if AP_HAS_RESPONSE_BUCKETS
+apr_array_header_t *h2_push_collect_update(struct h2_stream *stream,
+ const struct h2_request *req,
+ const ap_bucket_response *res)
+#else
+apr_array_header_t *h2_push_collect_update(struct h2_stream *stream,
+ const struct h2_request *req,
+ const struct h2_headers *res)
+#endif
+{
+ apr_array_header_t *pushes;
+
+ pushes = h2_push_collect(stream->pool, req, stream->push_policy, res);
+ return h2_push_diary_update(stream->session, pushes);
+}
+
+typedef struct {
+ h2_push_diary *diary;
+ unsigned char log2p;
+ int mask_bits;
+ int delta_bits;
+ int fixed_bits;
+ apr_uint64_t fixed_mask;
+ apr_pool_t *pool;
+ unsigned char *data;
+ apr_size_t datalen;
+ apr_size_t offset;
+ unsigned int bit;
+ apr_uint64_t last;
+} gset_encoder;
+
+static int cmp_puint64(const void *p1, const void *p2)
+{
+ const apr_uint64_t *pu1 = p1, *pu2 = p2;
+ return (*pu1 > *pu2)? 1 : ((*pu1 == *pu2)? 0 : -1);
+}
+
+/* in golomb bit stream encoding, bit 0 is the 8th of the first char, or
+ * more generally:
+ * char(bit/8) & cbit_mask[(bit % 8)]
+ */
+static unsigned char cbit_mask[] = {
+ 0x80u,
+ 0x40u,
+ 0x20u,
+ 0x10u,
+ 0x08u,
+ 0x04u,
+ 0x02u,
+ 0x01u,
+};
+
+static apr_status_t gset_encode_bit(gset_encoder *encoder, int bit)
+{
+ if (++encoder->bit >= 8) {
+ if (++encoder->offset >= encoder->datalen) {
+ apr_size_t nlen = encoder->datalen*2;
+ unsigned char *ndata = apr_pcalloc(encoder->pool, nlen);
+ if (!ndata) {
+ return APR_ENOMEM;
+ }
+ memcpy(ndata, encoder->data, encoder->datalen);
+ encoder->data = ndata;
+ encoder->datalen = nlen;
+ }
+ encoder->bit = 0;
+ encoder->data[encoder->offset] = 0xffu;
+ }
+ if (!bit) {
+ encoder->data[encoder->offset] &= ~cbit_mask[encoder->bit];
+ }
+ return APR_SUCCESS;
+}
+
+static apr_status_t gset_encode_next(gset_encoder *encoder, apr_uint64_t pval)
+{
+ apr_uint64_t delta, flex_bits;
+ apr_status_t status = APR_SUCCESS;
+ int i;
+
+ delta = pval - encoder->last;
+ encoder->last = pval;
+ flex_bits = (delta >> encoder->fixed_bits);
+ /* Intentional no APLOGNO */
+ ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, encoder->pool,
+ "h2_push_diary_enc: val=%"APR_UINT64_T_HEX_FMT", delta=%"
+ APR_UINT64_T_HEX_FMT" flex_bits=%"APR_UINT64_T_FMT", "
+ ", fixed_bits=%d, fixed_val=%"APR_UINT64_T_HEX_FMT,
+ pval, delta, flex_bits, encoder->fixed_bits, delta&encoder->fixed_mask);
+ for (; flex_bits != 0; --flex_bits) {
+ status = gset_encode_bit(encoder, 1);
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+ }
+ status = gset_encode_bit(encoder, 0);
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+
+ for (i = encoder->fixed_bits-1; i >= 0; --i) {
+ status = gset_encode_bit(encoder, (delta >> i) & 1);
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+ }
+ return APR_SUCCESS;
+}
+
+/**
+ * Get a cache digest as described in
+ * https://datatracker.ietf.org/doc/draft-kazuho-h2-cache-digest/
+ * from the contents of the push diary.
+ *
+ * @param diary the diary to calculdate the digest from
+ * @param p the pool to use
+ * @param pdata on successful return, the binary cache digest
+ * @param plen on successful return, the length of the binary data
+ */
+apr_status_t h2_push_diary_digest_get(h2_push_diary *diary, apr_pool_t *pool,
+ int maxP, const char *authority,
+ const char **pdata, apr_size_t *plen)
+{
+ int nelts, N;
+ unsigned char log2n, log2pmax;
+ gset_encoder encoder;
+ apr_uint64_t *hashes;
+ apr_size_t hash_count, i;
+
+ nelts = diary->entries->nelts;
+ N = ceil_power_of_2(nelts);
+ log2n = h2_log2(N);
+
+ /* Now log2p is the max number of relevant bits, so that
+ * log2p + log2n == mask_bits. We can use a lower log2p
+ * and have a shorter set encoding...
+ */
+ log2pmax = h2_log2(ceil_power_of_2(maxP));
+
+ memset(&encoder, 0, sizeof(encoder));
+ encoder.diary = diary;
+ encoder.log2p = H2MIN(diary->mask_bits - log2n, log2pmax);
+ encoder.mask_bits = log2n + encoder.log2p;
+ encoder.delta_bits = diary->mask_bits - encoder.mask_bits;
+ encoder.fixed_bits = encoder.log2p;
+ encoder.fixed_mask = 1;
+ encoder.fixed_mask = (encoder.fixed_mask << encoder.fixed_bits) - 1;
+ encoder.pool = pool;
+ encoder.datalen = 512;
+ encoder.data = apr_pcalloc(encoder.pool, encoder.datalen);
+
+ encoder.data[0] = log2n;
+ encoder.data[1] = encoder.log2p;
+ encoder.offset = 1;
+ encoder.bit = 8;
+ encoder.last = 0;
+
+ /* Intentional no APLOGNO */
+ ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, pool,
+ "h2_push_diary_digest_get: %d entries, N=%d, log2n=%d, "
+ "mask_bits=%d, enc.mask_bits=%d, delta_bits=%d, enc.log2p=%d, authority=%s",
+ (int)nelts, (int)N, (int)log2n, diary->mask_bits,
+ (int)encoder.mask_bits, (int)encoder.delta_bits,
+ (int)encoder.log2p, authority);
+
+ if (!authority || !diary->authority
+ || !strcmp("*", authority) || !strcmp(diary->authority, authority)) {
+ hash_count = diary->entries->nelts;
+ hashes = apr_pcalloc(encoder.pool, hash_count);
+ for (i = 0; i < hash_count; ++i) {
+ hashes[i] = ((&APR_ARRAY_IDX(diary->entries, i, h2_push_diary_entry))->hash
+ >> encoder.delta_bits);
+ }
+
+ qsort(hashes, hash_count, sizeof(apr_uint64_t), cmp_puint64);
+ for (i = 0; i < hash_count; ++i) {
+ if (!i || (hashes[i] != hashes[i-1])) {
+ gset_encode_next(&encoder, hashes[i]);
+ }
+ }
+ /* Intentional no APLOGNO */
+ ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, pool,
+ "h2_push_diary_digest_get: golomb compressed hashes, %d bytes",
+ (int)encoder.offset + 1);
+ }
+ *pdata = (const char *)encoder.data;
+ *plen = encoder.offset + 1;
+
+ return APR_SUCCESS;
+}
+
diff --git a/modules/http2/h2_push.h b/modules/http2/h2_push.h
new file mode 100644
index 0000000..947b73b
--- /dev/null
+++ b/modules/http2/h2_push.h
@@ -0,0 +1,158 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __mod_h2__h2_push__
+#define __mod_h2__h2_push__
+
+#include <http_protocol.h>
+
+#include "h2.h"
+#include "h2_headers.h"
+
+struct h2_request;
+struct h2_ngheader;
+struct h2_session;
+struct h2_stream;
+
+typedef struct h2_push {
+ const struct h2_request *req;
+ h2_priority *priority;
+} h2_push;
+
+typedef enum {
+ H2_PUSH_DIGEST_APR_HASH,
+ H2_PUSH_DIGEST_SHA256
+} h2_push_digest_type;
+
+/*******************************************************************************
+ * push diary
+ *
+ * - The push diary keeps track of resources already PUSHed via HTTP/2 on this
+ * connection. It records a hash value from the absolute URL of the resource
+ * pushed.
+ * - Lacking openssl,
+ * - with openssl, it uses SHA256 to calculate the hash value, otherwise it
+ * falls back to apr_hashfunc_default()
+ * - whatever the method to generate the hash, the diary keeps a maximum of 64
+ * bits per hash, limiting the memory consumption to about
+ * H2PushDiarySize * 8
+ * bytes. Entries are sorted by most recently used and oldest entries are
+ * forgotten first.
+ * - While useful by itself to avoid duplicated PUSHes on the same connection,
+ * the original idea was that clients provided a 'Cache-Digest' header with
+ * the values of *their own* cached resources. This was described in
+ * <https://datatracker.ietf.org/doc/draft-kazuho-h2-cache-digest/>
+ * and some subsequent revisions that tweaked values but kept the overall idea.
+ * - The draft was abandoned by the IETF http-wg, as support from major clients,
+ * e.g. browsers, was lacking for various reasons.
+ * - For these reasons, mod_h2 abandoned its support for client supplied values
+ * but keeps the diary. It seems to provide value for applications using PUSH,
+ * is configurable in size and defaults to a very moderate amount of memory
+ * used.
+ * - The cache digest header is a Golomb Coded Set of hash values, but it may
+ * limit the amount of bits per hash value even further. For a good description
+ * of GCS, read here:
+ * <http://giovanni.bajo.it/post/47119962313/golomb-coded-sets-smaller-than-bloom-filters>
+ ******************************************************************************/
+
+
+/*
+ * The push diary is based on the abandoned draft
+ * <https://datatracker.ietf.org/doc/draft-kazuho-h2-cache-digest/>
+ * that describes how to use golomb filters.
+ */
+
+typedef struct h2_push_diary h2_push_diary;
+
+typedef void h2_push_digest_calc(h2_push_diary *diary, apr_uint64_t *phash, h2_push *push);
+
+struct h2_push_diary {
+ apr_array_header_t *entries;
+ int NMax; /* Maximum for N, should size change be necessary */
+ int N; /* Current maximum number of entries, power of 2 */
+ apr_uint64_t mask; /* mask for relevant bits */
+ unsigned int mask_bits; /* number of relevant bits */
+ const char *authority;
+ h2_push_digest_type dtype;
+ h2_push_digest_calc *dcalc;
+};
+
+/**
+ * Determine the list of h2_push'es to send to the client on behalf of
+ * the given request/response pair.
+ *
+ * @param p the pool to use
+ * @param req the requst from the client
+ * @param res the response from the server
+ * @return array of h2_push addresses or NULL
+ */
+#if AP_HAS_RESPONSE_BUCKETS
+apr_array_header_t *h2_push_collect(apr_pool_t *p,
+ const struct h2_request *req,
+ apr_uint32_t push_policy,
+ const ap_bucket_response *res);
+#else
+apr_array_header_t *h2_push_collect(apr_pool_t *p,
+ const struct h2_request *req,
+ apr_uint32_t push_policy,
+ const struct h2_headers *res);
+#endif
+
+/**
+ * Create a new push diary for the given maximum number of entries.
+ *
+ * @param p the pool to use
+ * @param N the max number of entries, rounded up to 2^x
+ * @return the created diary, might be NULL of max_entries is 0
+ */
+h2_push_diary *h2_push_diary_create(apr_pool_t *p, int N);
+
+/**
+ * Filters the given pushes against the diary and returns only those pushes
+ * that were newly entered in the diary.
+ */
+apr_array_header_t *h2_push_diary_update(struct h2_session *session, apr_array_header_t *pushes);
+
+/**
+ * Collect pushes for the given request/response pair, enter them into the
+ * diary and return those pushes newly entered.
+ */
+#if AP_HAS_RESPONSE_BUCKETS
+apr_array_header_t *h2_push_collect_update(struct h2_stream *stream,
+ const struct h2_request *req,
+ const ap_bucket_response *res);
+#else
+apr_array_header_t *h2_push_collect_update(struct h2_stream *stream,
+ const struct h2_request *req,
+ const struct h2_headers *res);
+#endif
+
+/**
+ * Get a cache digest as described in
+ * https://datatracker.ietf.org/doc/draft-kazuho-h2-cache-digest/
+ * from the contents of the push diary.
+ *
+ * @param diary the diary to calculdate the digest from
+ * @param p the pool to use
+ * @param authority the authority to get the data for, use NULL/"*" for all
+ * @param pdata on successful return, the binary cache digest
+ * @param plen on successful return, the length of the binary data
+ */
+apr_status_t h2_push_diary_digest_get(h2_push_diary *diary, apr_pool_t *p,
+ int maxP, const char *authority,
+ const char **pdata, apr_size_t *plen);
+
+#endif /* defined(__mod_h2__h2_push__) */
diff --git a/modules/http2/h2_request.c b/modules/http2/h2_request.c
new file mode 100644
index 0000000..bddbad5
--- /dev/null
+++ b/modules/http2/h2_request.c
@@ -0,0 +1,519 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+#include "apr_strmatch.h"
+
+#include <ap_mmn.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_connection.h>
+#include <http_protocol.h>
+#include <http_request.h>
+#include <http_log.h>
+#include <http_ssl.h>
+#include <http_vhost.h>
+#include <util_filter.h>
+#include <ap_mpm.h>
+#include <mod_core.h>
+#include <scoreboard.h>
+
+#include "h2_private.h"
+#include "h2_config.h"
+#include "h2_push.h"
+#include "h2_request.h"
+#include "h2_util.h"
+
+
+h2_request *h2_request_create(int id, apr_pool_t *pool, const char *method,
+ const char *scheme, const char *authority,
+ const char *path, apr_table_t *header)
+{
+ h2_request *req = apr_pcalloc(pool, sizeof(h2_request));
+
+ req->method = method;
+ req->scheme = scheme;
+ req->authority = authority;
+ req->path = path;
+ req->headers = header? header : apr_table_make(pool, 10);
+ req->request_time = apr_time_now();
+
+ return req;
+}
+
+typedef struct {
+ apr_table_t *headers;
+ apr_pool_t *pool;
+ apr_status_t status;
+} h1_ctx;
+
+static int set_h1_header(void *ctx, const char *key, const char *value)
+{
+ h1_ctx *x = ctx;
+ int was_added;
+ h2_req_add_header(x->headers, x->pool, key, strlen(key), value, strlen(value), 0, &was_added);
+ return 1;
+}
+
+apr_status_t h2_request_rcreate(h2_request **preq, apr_pool_t *pool,
+ request_rec *r)
+{
+ h2_request *req;
+ const char *scheme, *authority, *path;
+ h1_ctx x;
+
+ *preq = NULL;
+ scheme = apr_pstrdup(pool, r->parsed_uri.scheme? r->parsed_uri.scheme
+ : ap_http_scheme(r));
+ authority = apr_pstrdup(pool, r->hostname);
+ path = apr_uri_unparse(pool, &r->parsed_uri, APR_URI_UNP_OMITSITEPART);
+
+ if (!r->method || !scheme || !r->hostname || !path) {
+ return APR_EINVAL;
+ }
+
+ /* The authority we carry in h2_request is the 'authority' part of
+ * the URL for the request. r->hostname has stripped any port info that
+ * might have been present. Do we need to add it?
+ */
+ if (!ap_strchr_c(authority, ':')) {
+ if (r->parsed_uri.port_str) {
+ /* Yes, it was there, add it again. */
+ authority = apr_pstrcat(pool, authority, ":", r->parsed_uri.port_str, NULL);
+ }
+ else if (!r->parsed_uri.hostname && r->server && r->server->port) {
+ /* If there was no hostname in the parsed URL, the URL was relative.
+ * In that case, we restore port from our server->port, if it
+ * is known and not the default port for the scheme. */
+ apr_port_t defport = apr_uri_port_of_scheme(scheme);
+ if (defport != r->server->port) {
+ /* port info missing and port is not default for scheme: append */
+ authority = apr_psprintf(pool, "%s:%d", authority,
+ (int)r->server->port);
+ }
+ }
+ }
+
+ req = apr_pcalloc(pool, sizeof(*req));
+ req->method = apr_pstrdup(pool, r->method);
+ req->scheme = scheme;
+ req->authority = authority;
+ req->path = path;
+ req->headers = apr_table_make(pool, 10);
+ req->http_status = H2_HTTP_STATUS_UNSET;
+
+ x.pool = pool;
+ x.headers = req->headers;
+ x.status = APR_SUCCESS;
+ apr_table_do(set_h1_header, &x, r->headers_in, NULL);
+
+ *preq = req;
+ return x.status;
+}
+
+apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool,
+ const char *name, size_t nlen,
+ const char *value, size_t vlen,
+ size_t max_field_len, int *pwas_added)
+{
+ apr_status_t status = APR_SUCCESS;
+
+ *pwas_added = 0;
+ if (nlen <= 0) {
+ return status;
+ }
+
+ if (name[0] == ':') {
+ /* pseudo header, see ch. 8.1.2.3, always should come first */
+ if (!apr_is_empty_table(req->headers)) {
+ ap_log_perror(APLOG_MARK, APLOG_ERR, 0, pool,
+ APLOGNO(02917)
+ "h2_request: pseudo header after request start");
+ return APR_EGENERAL;
+ }
+
+ if (H2_HEADER_METHOD_LEN == nlen
+ && !strncmp(H2_HEADER_METHOD, name, nlen)) {
+ req->method = apr_pstrndup(pool, value, vlen);
+ }
+ else if (H2_HEADER_SCHEME_LEN == nlen
+ && !strncmp(H2_HEADER_SCHEME, name, nlen)) {
+ req->scheme = apr_pstrndup(pool, value, vlen);
+ }
+ else if (H2_HEADER_PATH_LEN == nlen
+ && !strncmp(H2_HEADER_PATH, name, nlen)) {
+ req->path = apr_pstrndup(pool, value, vlen);
+ }
+ else if (H2_HEADER_AUTH_LEN == nlen
+ && !strncmp(H2_HEADER_AUTH, name, nlen)) {
+ req->authority = apr_pstrndup(pool, value, vlen);
+ }
+ else {
+ char buffer[32];
+ memset(buffer, 0, 32);
+ strncpy(buffer, name, (nlen > 31)? 31 : nlen);
+ ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, pool,
+ APLOGNO(02954)
+ "h2_request: ignoring unknown pseudo header %s",
+ buffer);
+ }
+ }
+ else {
+ /* non-pseudo header, add to table */
+ status = h2_req_add_header(req->headers, pool, name, nlen, value, vlen,
+ max_field_len, pwas_added);
+ }
+
+ return status;
+}
+
+apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool,
+ size_t raw_bytes)
+{
+ /* rfc7540, ch. 8.1.2.3: without :authority, Host: must be there */
+ if (req->authority && !strlen(req->authority)) {
+ req->authority = NULL;
+ }
+ if (!req->authority) {
+ const char *host = apr_table_get(req->headers, "Host");
+ if (!host) {
+ return APR_BADARG;
+ }
+ req->authority = host;
+ }
+ else {
+ apr_table_setn(req->headers, "Host", req->authority);
+ }
+ req->raw_bytes += raw_bytes;
+
+ return APR_SUCCESS;
+}
+
+h2_request *h2_request_clone(apr_pool_t *p, const h2_request *src)
+{
+ h2_request *dst = apr_pmemdup(p, src, sizeof(*dst));
+ dst->method = apr_pstrdup(p, src->method);
+ dst->scheme = apr_pstrdup(p, src->scheme);
+ dst->authority = apr_pstrdup(p, src->authority);
+ dst->path = apr_pstrdup(p, src->path);
+ dst->headers = apr_table_clone(p, src->headers);
+ return dst;
+}
+
+#if !AP_MODULE_MAGIC_AT_LEAST(20120211, 106)
+static request_rec *my_ap_create_request(conn_rec *c)
+{
+ apr_pool_t *p;
+ request_rec *r;
+
+ apr_pool_create(&p, c->pool);
+ apr_pool_tag(p, "request");
+ r = apr_pcalloc(p, sizeof(request_rec));
+ AP_READ_REQUEST_ENTRY((intptr_t)r, (uintptr_t)c);
+ r->pool = p;
+ r->connection = c;
+ r->server = c->base_server;
+
+ r->user = NULL;
+ r->ap_auth_type = NULL;
+
+ r->allowed_methods = ap_make_method_list(p, 2);
+
+ r->headers_in = apr_table_make(r->pool, 5);
+ r->trailers_in = apr_table_make(r->pool, 5);
+ r->subprocess_env = apr_table_make(r->pool, 25);
+ r->headers_out = apr_table_make(r->pool, 12);
+ r->err_headers_out = apr_table_make(r->pool, 5);
+ r->trailers_out = apr_table_make(r->pool, 5);
+ r->notes = apr_table_make(r->pool, 5);
+
+ r->request_config = ap_create_request_config(r->pool);
+ /* Must be set before we run create request hook */
+
+ r->proto_output_filters = c->output_filters;
+ r->output_filters = r->proto_output_filters;
+ r->proto_input_filters = c->input_filters;
+ r->input_filters = r->proto_input_filters;
+ ap_run_create_request(r);
+ r->per_dir_config = r->server->lookup_defaults;
+
+ r->sent_bodyct = 0; /* bytect isn't for body */
+
+ r->read_length = 0;
+ r->read_body = REQUEST_NO_BODY;
+
+ r->status = HTTP_OK; /* Until further notice */
+ r->header_only = 0;
+ r->the_request = NULL;
+
+ /* Begin by presuming any module can make its own path_info assumptions,
+ * until some module interjects and changes the value.
+ */
+ r->used_path_info = AP_REQ_DEFAULT_PATH_INFO;
+
+ r->useragent_addr = c->client_addr;
+ r->useragent_ip = c->client_ip;
+ return r;
+}
+#endif
+
+#if AP_HAS_RESPONSE_BUCKETS
+apr_bucket *h2_request_create_bucket(const h2_request *req, request_rec *r)
+{
+ conn_rec *c = r->connection;
+ apr_table_t *headers = apr_table_copy(r->pool, req->headers);
+ const char *uri = req->path;
+
+ AP_DEBUG_ASSERT(req->authority);
+ if (req->scheme && (ap_cstr_casecmp(req->scheme,
+ ap_ssl_conn_is_ssl(c->master? c->master : c)? "https" : "http")
+ || !ap_cstr_casecmp("CONNECT", req->method))) {
+ /* Client sent a non-matching ':scheme' pseudo header or CONNECT.
+ * In this case, we use an absolute URI.
+ */
+ uri = apr_psprintf(r->pool, "%s://%s%s",
+ req->scheme, req->authority, req->path ? req->path : "");
+ }
+
+ return ap_bucket_request_create(req->method, uri, "HTTP/2.0", headers,
+ r->pool, c->bucket_alloc);
+}
+#endif
+
+static void assign_headers(request_rec *r, const h2_request *req,
+ int no_body)
+{
+ const char *cl;
+
+ r->headers_in = apr_table_copy(r->pool, req->headers);
+ if (req->authority) {
+ /* for internal handling, we have to simulate that :authority
+ * came in as Host:, RFC 9113 ch. says that mismatches between
+ * :authority and Host: SHOULD be rejected as malformed. However,
+ * we are more lenient and just replace any Host: if we have
+ * an :authority.
+ */
+ const char *orig_host = apr_table_get(req->headers, "Host");
+ if (orig_host && strcmp(req->authority, orig_host)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(10401)
+ "overwriting 'Host: %s' with :authority: %s'",
+ orig_host, req->authority);
+ apr_table_setn(r->subprocess_env, "H2_ORIGINAL_HOST", orig_host);
+ }
+ apr_table_setn(r->headers_in, "Host", req->authority);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "set 'Host: %s' from :authority", req->authority);
+ }
+
+ cl = apr_table_get(req->headers, "Content-Length");
+ if (no_body) {
+ if (!cl && apr_table_get(req->headers, "Content-Type")) {
+ /* If we have a content-type, but already seen eos, no more
+ * data will come. Signal a zero content length explicitly.
+ */
+ apr_table_setn(req->headers, "Content-Length", "0");
+ }
+ }
+#if !AP_HAS_RESPONSE_BUCKETS
+ else if (!cl) {
+ /* there may be a body and we have internal HTTP/1.1 processing.
+ * If the Content-Length is unspecified, we MUST simulate
+ * chunked Transfer-Encoding.
+ *
+ * HTTP/2 does not need a Content-Length for framing. Ideally
+ * all clients set the EOS flag on the header frame if they
+ * do not intent to send a body. However, forwarding proxies
+ * might just no know at the time and send an empty DATA
+ * frame with EOS much later.
+ */
+ apr_table_mergen(r->headers_in, "Transfer-Encoding", "chunked");
+ }
+#endif /* else AP_HAS_RESPONSE_BUCKETS */
+}
+
+request_rec *h2_create_request_rec(const h2_request *req, conn_rec *c,
+ int no_body)
+{
+ int access_status = HTTP_OK;
+
+#if AP_MODULE_MAGIC_AT_LEAST(20120211, 106)
+ request_rec *r = ap_create_request(c);
+#else
+ request_rec *r = my_ap_create_request(c);
+#endif
+
+#if AP_MODULE_MAGIC_AT_LEAST(20120211, 107)
+ assign_headers(r, req, no_body);
+ ap_run_pre_read_request(r, c);
+
+ /* Time to populate r with the data we have. */
+ r->request_time = req->request_time;
+ AP_DEBUG_ASSERT(req->authority);
+ if (req->scheme && (ap_cstr_casecmp(req->scheme,
+ ap_ssl_conn_is_ssl(c->master? c->master : c)? "https" : "http")
+ || !ap_cstr_casecmp("CONNECT", req->method))) {
+ /* Client sent a non-matching ':scheme' pseudo header. Forward this
+ * via an absolute URI in the request line.
+ */
+ r->the_request = apr_psprintf(r->pool, "%s %s://%s%s HTTP/2.0",
+ req->method, req->scheme, req->authority,
+ req->path ? req->path : "");
+ }
+ else if (req->path) {
+ r->the_request = apr_psprintf(r->pool, "%s %s HTTP/2.0",
+ req->method, req->path);
+ }
+ else {
+ /* We should only come here on a request that is errored already.
+ * create a request line that passes parsing, we'll die anyway.
+ */
+ AP_DEBUG_ASSERT(req->http_status != H2_HTTP_STATUS_UNSET);
+ r->the_request = apr_psprintf(r->pool, "%s / HTTP/2.0", req->method);
+ }
+
+ /* Start with r->hostname = NULL, ap_check_request_header() will get it
+ * form Host: header, otherwise we get complains about port numbers.
+ */
+ r->hostname = NULL;
+
+ /* Validate HTTP/1 request and select vhost. */
+ if (!ap_parse_request_line(r) || !ap_check_request_header(r)) {
+ /* we may have switched to another server still */
+ r->per_dir_config = r->server->lookup_defaults;
+ if (req->http_status != H2_HTTP_STATUS_UNSET) {
+ access_status = req->http_status;
+ /* Be safe and close the connection */
+ c->keepalive = AP_CONN_CLOSE;
+ }
+ else {
+ access_status = r->status;
+ }
+ r->status = HTTP_OK;
+ goto die;
+ }
+#else
+ {
+ const char *s;
+
+ assign_headers(r, req, no_body);
+ ap_run_pre_read_request(r, c);
+
+ /* Time to populate r with the data we have. */
+ r->request_time = req->request_time;
+ r->method = apr_pstrdup(r->pool, req->method);
+ /* Provide quick information about the request method as soon as known */
+ r->method_number = ap_method_number_of(r->method);
+ if (r->method_number == M_GET && r->method[0] == 'H') {
+ r->header_only = 1;
+ }
+ ap_parse_uri(r, req->path ? req->path : "");
+ r->protocol = (char*)"HTTP/2.0";
+ r->proto_num = HTTP_VERSION(2, 0);
+ r->the_request = apr_psprintf(r->pool, "%s %s HTTP/2.0",
+ r->method, req->path ? req->path : "");
+
+ /* Start with r->hostname = NULL, ap_check_request_header() will get it
+ * form Host: header, otherwise we get complains about port numbers.
+ */
+ r->hostname = NULL;
+ ap_update_vhost_from_headers(r);
+
+ /* we may have switched to another server */
+ r->per_dir_config = r->server->lookup_defaults;
+
+ s = apr_table_get(r->headers_in, "Expect");
+ if (s && s[0]) {
+ if (ap_cstr_casecmp(s, "100-continue") == 0) {
+ r->expecting_100 = 1;
+ }
+ else {
+ r->status = HTTP_EXPECTATION_FAILED;
+ access_status = r->status;
+ goto die;
+ }
+ }
+ }
+#endif
+
+ /* we may have switched to another server */
+ r->per_dir_config = r->server->lookup_defaults;
+
+ if (req->http_status != H2_HTTP_STATUS_UNSET) {
+ access_status = req->http_status;
+ r->status = HTTP_OK;
+ /* Be safe and close the connection */
+ c->keepalive = AP_CONN_CLOSE;
+ goto die;
+ }
+
+ /*
+ * Add the HTTP_IN filter here to ensure that ap_discard_request_body
+ * called by ap_die and by ap_send_error_response works correctly on
+ * status codes that do not cause the connection to be dropped and
+ * in situations where the connection should be kept alive.
+ */
+ ap_add_input_filter_handle(ap_http_input_filter_handle,
+ NULL, r, r->connection);
+
+ if ((access_status = ap_post_read_request(r))) {
+ /* Request check post hooks failed. An example of this would be a
+ * request for a vhost where h2 is disabled --> 421.
+ */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03367)
+ "h2_request: access_status=%d, request_create failed",
+ access_status);
+ goto die;
+ }
+
+ AP_READ_REQUEST_SUCCESS((uintptr_t)r, (char *)r->method,
+ (char *)r->uri, (char *)r->server->defn_name,
+ r->status);
+ return r;
+
+die:
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
+ "ap_die(%d) for %s", access_status, r->the_request);
+ ap_die(access_status, r);
+
+ /* ap_die() sent the response through the output filters, we must now
+ * end the request with an EOR bucket for stream/pipeline accounting.
+ */
+ {
+ apr_bucket_brigade *eor_bb;
+#if AP_MODULE_MAGIC_AT_LEAST(20180905, 1)
+ eor_bb = ap_acquire_brigade(c);
+ APR_BRIGADE_INSERT_TAIL(eor_bb,
+ ap_bucket_eor_create(c->bucket_alloc, r));
+ ap_pass_brigade(c->output_filters, eor_bb);
+ ap_release_brigade(c, eor_bb);
+#else
+ eor_bb = apr_brigade_create(c->pool, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(eor_bb,
+ ap_bucket_eor_create(c->bucket_alloc, r));
+ ap_pass_brigade(c->output_filters, eor_bb);
+ apr_brigade_destroy(eor_bb);
+#endif
+ }
+
+ r = NULL;
+ AP_READ_REQUEST_FAILURE((uintptr_t)r);
+ return NULL;
+}
diff --git a/modules/http2/h2_request.h b/modules/http2/h2_request.h
new file mode 100644
index 0000000..7e20b69
--- /dev/null
+++ b/modules/http2/h2_request.h
@@ -0,0 +1,59 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __mod_h2__h2_request__
+#define __mod_h2__h2_request__
+
+#include "h2.h"
+
+h2_request *h2_request_create(int id, apr_pool_t *pool, const char *method,
+ const char *scheme, const char *authority,
+ const char *path, apr_table_t *header);
+
+apr_status_t h2_request_rcreate(h2_request **preq, apr_pool_t *pool,
+ request_rec *r);
+
+apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool,
+ const char *name, size_t nlen,
+ const char *value, size_t vlen,
+ size_t max_field_len, int *pwas_added);
+
+apr_status_t h2_request_add_trailer(h2_request *req, apr_pool_t *pool,
+ const char *name, size_t nlen,
+ const char *value, size_t vlen);
+
+apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool,
+ size_t raw_bytes);
+
+h2_request *h2_request_clone(apr_pool_t *p, const h2_request *src);
+
+/**
+ * Create a request_rec representing the h2_request to be
+ * processed on the given connection.
+ *
+ * @param req the h2 request to process
+ * @param conn the connection to process the request on
+ * @param no_body != 0 iff the request is known to have no body
+ * @return the request_rec representing the request
+ */
+request_rec *h2_create_request_rec(const h2_request *req, conn_rec *conn,
+ int no_body);
+
+#if AP_HAS_RESPONSE_BUCKETS
+apr_bucket *h2_request_create_bucket(const h2_request *req, request_rec *r);
+#endif
+
+#endif /* defined(__mod_h2__h2_request__) */
diff --git a/modules/http2/h2_session.c b/modules/http2/h2_session.c
new file mode 100644
index 0000000..7ba49cf
--- /dev/null
+++ b/modules/http2/h2_session.c
@@ -0,0 +1,1991 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stddef.h>
+#include <apr_thread_cond.h>
+#include <apr_atomic.h>
+#include <apr_base64.h>
+#include <apr_strings.h>
+
+#include <ap_mpm.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_config.h>
+#include <http_log.h>
+#include <http_protocol.h>
+#include <scoreboard.h>
+
+#include <mpm_common.h>
+
+#if APR_HAVE_UNISTD_H
+#include <unistd.h> /* for getpid() */
+#endif
+
+#include "h2_private.h"
+#include "h2.h"
+#include "h2_bucket_beam.h"
+#include "h2_bucket_eos.h"
+#include "h2_config.h"
+#include "h2_conn_ctx.h"
+#include "h2_protocol.h"
+#include "h2_mplx.h"
+#include "h2_push.h"
+#include "h2_request.h"
+#include "h2_headers.h"
+#include "h2_stream.h"
+#include "h2_c2.h"
+#include "h2_session.h"
+#include "h2_util.h"
+#include "h2_version.h"
+#include "h2_workers.h"
+
+
+static void transit(h2_session *session, const char *action,
+ h2_session_state nstate);
+
+static void on_stream_state_enter(void *ctx, h2_stream *stream);
+static void on_stream_state_event(void *ctx, h2_stream *stream, h2_stream_event_t ev);
+static void on_stream_event(void *ctx, h2_stream *stream, h2_stream_event_t ev);
+
+static int h2_session_status_from_apr_status(apr_status_t rv)
+{
+ if (rv == APR_SUCCESS) {
+ return NGHTTP2_NO_ERROR;
+ }
+ else if (APR_STATUS_IS_EAGAIN(rv)) {
+ return NGHTTP2_ERR_WOULDBLOCK;
+ }
+ else if (APR_STATUS_IS_EOF(rv)) {
+ return NGHTTP2_ERR_EOF;
+ }
+ return NGHTTP2_ERR_PROTO;
+}
+
+static h2_stream *get_stream(h2_session *session, int stream_id)
+{
+ return nghttp2_session_get_stream_user_data(session->ngh2, stream_id);
+}
+
+void h2_session_event(h2_session *session, h2_session_event_t ev,
+ int err, const char *msg)
+{
+ h2_session_dispatch_event(session, ev, err, msg);
+}
+
+static int rst_unprocessed_stream(h2_stream *stream, void *ctx)
+{
+ int unprocessed = (!h2_stream_is_at_or_past(stream, H2_SS_CLOSED)
+ && (H2_STREAM_CLIENT_INITIATED(stream->id)?
+ (!stream->session->local.accepting
+ && stream->id > stream->session->local.accepted_max)
+ :
+ (!stream->session->remote.accepting
+ && stream->id > stream->session->remote.accepted_max))
+ );
+ if (unprocessed) {
+ h2_stream_rst(stream, H2_ERR_NO_ERROR);
+ return 0;
+ }
+ return 1;
+}
+
+static void cleanup_unprocessed_streams(h2_session *session)
+{
+ h2_mplx_c1_streams_do(session->mplx, rst_unprocessed_stream, session);
+}
+
+static h2_stream *h2_session_open_stream(h2_session *session, int stream_id,
+ int initiated_on)
+{
+ h2_stream * stream;
+ apr_pool_t *stream_pool;
+
+ apr_pool_create(&stream_pool, session->pool);
+ apr_pool_tag(stream_pool, "h2_stream");
+
+ stream = h2_stream_create(stream_id, stream_pool, session,
+ session->monitor, initiated_on);
+ if (stream) {
+ nghttp2_session_set_stream_user_data(session->ngh2, stream_id, stream);
+ }
+ return stream;
+}
+
+/**
+ * Determine the priority order of streams.
+ * - if both stream depend on the same one, compare weights
+ * - if one stream is closer to the root, prioritize that one
+ * - if both are on the same level, use the weight of their root
+ * level ancestors
+ */
+static int spri_cmp(int sid1, nghttp2_stream *s1,
+ int sid2, nghttp2_stream *s2, h2_session *session)
+{
+ nghttp2_stream *p1, *p2;
+
+ p1 = nghttp2_stream_get_parent(s1);
+ p2 = nghttp2_stream_get_parent(s2);
+
+ if (p1 == p2) {
+ int32_t w1, w2;
+
+ w1 = nghttp2_stream_get_weight(s1);
+ w2 = nghttp2_stream_get_weight(s2);
+ return w2 - w1;
+ }
+ else if (!p1) {
+ /* stream 1 closer to root */
+ return -1;
+ }
+ else if (!p2) {
+ /* stream 2 closer to root */
+ return 1;
+ }
+ return spri_cmp(sid1, p1, sid2, p2, session);
+}
+
+static int stream_pri_cmp(int sid1, int sid2, void *ctx)
+{
+ h2_session *session = ctx;
+ nghttp2_stream *s1, *s2;
+
+ s1 = nghttp2_session_find_stream(session->ngh2, sid1);
+ s2 = nghttp2_session_find_stream(session->ngh2, sid2);
+
+ if (s1 == s2) {
+ return 0;
+ }
+ else if (!s1) {
+ return 1;
+ }
+ else if (!s2) {
+ return -1;
+ }
+ return spri_cmp(sid1, s1, sid2, s2, session);
+}
+
+/*
+ * Callback when nghttp2 wants to send bytes back to the client.
+ */
+static ssize_t send_cb(nghttp2_session *ngh2,
+ const uint8_t *data, size_t length,
+ int flags, void *userp)
+{
+ h2_session *session = (h2_session *)userp;
+ apr_status_t rv;
+ (void)ngh2;
+ (void)flags;
+
+ if (h2_c1_io_needs_flush(&session->io)) {
+ return NGHTTP2_ERR_WOULDBLOCK;
+ }
+
+ rv = h2_c1_io_add_data(&session->io, (const char *)data, length);
+ if (APR_SUCCESS == rv) {
+ return length;
+ }
+ else if (APR_STATUS_IS_EAGAIN(rv)) {
+ return NGHTTP2_ERR_WOULDBLOCK;
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, session->c1,
+ APLOGNO(03062) "h2_session: send error");
+ return h2_session_status_from_apr_status(rv);
+ }
+}
+
+static int on_invalid_frame_recv_cb(nghttp2_session *ngh2,
+ const nghttp2_frame *frame,
+ int error, void *userp)
+{
+ h2_session *session = (h2_session *)userp;
+ (void)ngh2;
+
+ if (APLOGcdebug(session->c1)) {
+ char buffer[256];
+
+ h2_util_frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+ H2_SSSN_LOG(APLOGNO(03063), session,
+ "recv invalid FRAME[%s], frames=%ld/%ld (r/s)"),
+ buffer, (long)session->frames_received,
+ (long)session->frames_sent);
+ }
+ return 0;
+}
+
+static int on_data_chunk_recv_cb(nghttp2_session *ngh2, uint8_t flags,
+ int32_t stream_id,
+ const uint8_t *data, size_t len, void *userp)
+{
+ h2_session *session = (h2_session *)userp;
+ apr_status_t status = APR_EINVAL;
+ h2_stream * stream;
+ int rv = 0;
+
+ stream = get_stream(session, stream_id);
+ if (stream) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+ H2_SSSN_STRM_MSG(session, stream_id, "write %ld bytes of DATA"),
+ (long)len);
+ status = h2_stream_recv_DATA(stream, flags, data, len);
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, APLOGNO(03064)
+ H2_SSSN_STRM_MSG(session, stream_id,
+ "on_data_chunk for unknown stream"));
+ rv = NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+
+ if (status != APR_SUCCESS) {
+ /* count this as consumed explicitly as no one will read it */
+ nghttp2_session_consume(session->ngh2, stream_id, len);
+ }
+ return rv;
+}
+
+static int on_stream_close_cb(nghttp2_session *ngh2, int32_t stream_id,
+ uint32_t error_code, void *userp)
+{
+ h2_session *session = (h2_session *)userp;
+ h2_stream *stream;
+
+ (void)ngh2;
+ stream = get_stream(session, stream_id);
+ if (stream) {
+ if (error_code) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+ H2_STRM_LOG(APLOGNO(03065), stream,
+ "closing with err=%d %s"),
+ (int)error_code, h2_protocol_err_description(error_code));
+ h2_stream_rst(stream, error_code);
+ }
+ }
+ return 0;
+}
+
+static int on_begin_headers_cb(nghttp2_session *ngh2,
+ const nghttp2_frame *frame, void *userp)
+{
+ h2_session *session = (h2_session *)userp;
+ h2_stream *s = NULL;
+
+ /* We may see HEADERs at the start of a stream or after all DATA
+ * streams to carry trailers. */
+ (void)ngh2;
+ s = get_stream(session, frame->hd.stream_id);
+ if (s) {
+ /* nop */
+ }
+ else if (session->local.accepting) {
+ s = h2_session_open_stream(userp, frame->hd.stream_id, 0);
+ }
+ return s? 0 : NGHTTP2_ERR_START_STREAM_NOT_ALLOWED;
+}
+
+static int on_header_cb(nghttp2_session *ngh2, const nghttp2_frame *frame,
+ const uint8_t *name, size_t namelen,
+ const uint8_t *value, size_t valuelen,
+ uint8_t flags,
+ void *userp)
+{
+ h2_session *session = (h2_session *)userp;
+ h2_stream * stream;
+ apr_status_t status;
+
+ (void)flags;
+ stream = get_stream(session, frame->hd.stream_id);
+ if (!stream) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, APLOGNO(02920)
+ H2_SSSN_STRM_MSG(session, frame->hd.stream_id,
+ "on_header unknown stream"));
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+
+ status = h2_stream_add_header(stream, (const char *)name, namelen,
+ (const char *)value, valuelen);
+ if (status != APR_SUCCESS
+ && (!stream->rtmp
+ || stream->rtmp->http_status == H2_HTTP_STATUS_UNSET)) {
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+
+/**
+ * nghttp2 session has received a complete frame. Most are used by nghttp2
+ * for processing of internal state. Some, like HEADER and DATA frames,
+ * we need to act on.
+ */
+static int on_frame_recv_cb(nghttp2_session *ng2s,
+ const nghttp2_frame *frame,
+ void *userp)
+{
+ h2_session *session = (h2_session *)userp;
+ h2_stream *stream;
+ apr_status_t rv = APR_SUCCESS;
+
+ stream = frame->hd.stream_id? get_stream(session, frame->hd.stream_id) : NULL;
+ if (APLOGcdebug(session->c1)) {
+ char buffer[256];
+
+ h2_util_frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
+ if (stream) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+ H2_STRM_LOG(APLOGNO(10302), stream,
+ "recv FRAME[%s], frames=%ld/%ld (r/s)"),
+ buffer, (long)session->frames_received,
+ (long)session->frames_sent);
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+ H2_SSSN_LOG(APLOGNO(03066), session,
+ "recv FRAME[%s], frames=%ld/%ld (r/s)"),
+ buffer, (long)session->frames_received,
+ (long)session->frames_sent);
+ }
+ }
+
+ ++session->frames_received;
+ switch (frame->hd.type) {
+ case NGHTTP2_HEADERS:
+ /* This can be HEADERS for a new stream, defining the request,
+ * or HEADER may come after DATA at the end of a stream as in
+ * trailers */
+ if (stream) {
+ rv = h2_stream_recv_frame(stream, NGHTTP2_HEADERS, frame->hd.flags,
+ frame->hd.length + H2_FRAME_HDR_LEN);
+ }
+ break;
+ case NGHTTP2_DATA:
+ if (stream) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+ H2_STRM_LOG(APLOGNO(02923), stream,
+ "DATA, len=%ld, flags=%d"),
+ (long)frame->hd.length, frame->hd.flags);
+ rv = h2_stream_recv_frame(stream, NGHTTP2_DATA, frame->hd.flags,
+ frame->hd.length + H2_FRAME_HDR_LEN);
+ }
+ break;
+ case NGHTTP2_PRIORITY:
+ session->reprioritize = 1;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+ H2_SSSN_STRM_MSG(session, frame->hd.stream_id, "PRIORITY frame "
+ " weight=%d, dependsOn=%d, exclusive=%d"),
+ frame->priority.pri_spec.weight,
+ frame->priority.pri_spec.stream_id,
+ frame->priority.pri_spec.exclusive);
+ break;
+ case NGHTTP2_WINDOW_UPDATE:
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+ H2_SSSN_STRM_MSG(session, frame->hd.stream_id,
+ "WINDOW_UPDATE incr=%d"),
+ frame->window_update.window_size_increment);
+ break;
+ case NGHTTP2_RST_STREAM:
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, APLOGNO(03067)
+ H2_SSSN_STRM_MSG(session, frame->hd.stream_id,
+ "RST_STREAM by client, error=%d"),
+ (int)frame->rst_stream.error_code);
+ if (stream && stream->initiated_on) {
+ /* A stream reset on a request we sent it. Normal, when the
+ * client does not want it. */
+ ++session->pushes_reset;
+ }
+ else {
+ /* A stream reset on a request it sent us. Could happen in a browser
+ * when the user navigates away or cancels loading - maybe. */
+ h2_mplx_c1_client_rst(session->mplx, frame->hd.stream_id);
+ }
+ ++session->streams_reset;
+ break;
+ case NGHTTP2_GOAWAY:
+ if (frame->goaway.error_code == 0
+ && frame->goaway.last_stream_id == ((1u << 31) - 1)) {
+ /* shutdown notice. Should not come from a client... */
+ session->remote.accepting = 0;
+ }
+ else {
+ session->remote.accepted_max = frame->goaway.last_stream_id;
+ h2_session_dispatch_event(session, H2_SESSION_EV_REMOTE_GOAWAY,
+ frame->goaway.error_code, NULL);
+ }
+ break;
+ case NGHTTP2_SETTINGS:
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+ H2_SSSN_MSG(session, "SETTINGS, len=%ld"), (long)frame->hd.length);
+ break;
+ default:
+ if (APLOGctrace2(session->c1)) {
+ char buffer[256];
+
+ h2_util_frame_print(frame, buffer,
+ sizeof(buffer)/sizeof(buffer[0]));
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+ H2_SSSN_MSG(session, "on_frame_rcv %s"), buffer);
+ }
+ break;
+ }
+
+ if (session->state == H2_SESSION_ST_IDLE) {
+ /* We received a frame, but session is in state IDLE. That means the frame
+ * did not really progress any of the (possibly) open streams. It was a meta
+ * frame, e.g. SETTINGS/WINDOW_UPDATE/unknown/etc.
+ * Remember: IDLE means we cannot send because either there are no streams open or
+ * all open streams are blocked on exhausted WINDOWs for outgoing data.
+ * The more frames we receive that do not change this, the less interested we
+ * become in serving this connection. This is expressed in increasing "idle_delays".
+ * Eventually, the connection will timeout and we'll close it. */
+ session->idle_frames = H2MIN(session->idle_frames + 1, session->frames_received);
+ ap_log_cerror( APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+ H2_SSSN_MSG(session, "session has %ld idle frames"),
+ (long)session->idle_frames);
+ if (session->idle_frames > 10) {
+ apr_size_t busy_frames = H2MAX(session->frames_received - session->idle_frames, 1);
+ int idle_ratio = (int)(session->idle_frames / busy_frames);
+ if (idle_ratio > 100) {
+ session->idle_delay = apr_time_from_msec(H2MIN(1000, idle_ratio));
+ }
+ else if (idle_ratio > 10) {
+ session->idle_delay = apr_time_from_msec(10);
+ }
+ else if (idle_ratio > 1) {
+ session->idle_delay = apr_time_from_msec(1);
+ }
+ else {
+ session->idle_delay = 0;
+ }
+ }
+ }
+
+ if (APR_SUCCESS != rv) return NGHTTP2_ERR_PROTO;
+ return 0;
+}
+
+static char immortal_zeros[H2_MAX_PADLEN];
+
+static int on_send_data_cb(nghttp2_session *ngh2,
+ nghttp2_frame *frame,
+ const uint8_t *framehd,
+ size_t length,
+ nghttp2_data_source *source,
+ void *userp)
+{
+ apr_status_t status = APR_SUCCESS;
+ h2_session *session = (h2_session *)userp;
+ int stream_id = (int)frame->hd.stream_id;
+ unsigned char padlen;
+ int eos;
+ h2_stream *stream;
+ apr_bucket *b;
+ apr_off_t len = length;
+
+ (void)ngh2;
+ (void)source;
+ ap_assert(frame->data.padlen <= (H2_MAX_PADLEN+1));
+ padlen = (unsigned char)frame->data.padlen;
+
+ stream = get_stream(session, stream_id);
+ if (!stream) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_NOTFOUND, session->c1,
+ APLOGNO(02924)
+ H2_SSSN_STRM_MSG(session, stream_id, "send_data, stream not found"));
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+ H2_STRM_MSG(stream, "send_data_cb for %ld bytes"),
+ (long)length);
+
+ status = h2_c1_io_add_data(&session->io, (const char *)framehd, H2_FRAME_HDR_LEN);
+ if (padlen && status == APR_SUCCESS) {
+ --padlen;
+ status = h2_c1_io_add_data(&session->io, (const char *)&padlen, 1);
+ }
+
+ if (status != APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c1,
+ H2_STRM_MSG(stream, "writing frame header"));
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+
+ status = h2_stream_read_to(stream, session->bbtmp, &len, &eos);
+ if (status != APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c1,
+ H2_STRM_MSG(stream, "send_data_cb, reading stream"));
+ apr_brigade_cleanup(session->bbtmp);
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ else if (len != (apr_off_t)length) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c1,
+ H2_STRM_MSG(stream, "send_data_cb, wanted %ld bytes, "
+ "got %ld from stream"), (long)length, (long)len);
+ apr_brigade_cleanup(session->bbtmp);
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+
+ if (padlen) {
+ b = apr_bucket_immortal_create(immortal_zeros, padlen,
+ session->c1->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(session->bbtmp, b);
+ }
+
+ status = h2_c1_io_append(&session->io, session->bbtmp);
+ apr_brigade_cleanup(session->bbtmp);
+
+ if (status == APR_SUCCESS) {
+ stream->out_data_frames++;
+ stream->out_data_octets += length;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+ H2_STRM_MSG(stream, "sent data length=%ld, total=%ld"),
+ (long)length, (long)stream->out_data_octets);
+ return 0;
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c1,
+ H2_STRM_LOG(APLOGNO(02925), stream, "failed send_data_cb"));
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+}
+
+static int on_frame_send_cb(nghttp2_session *ngh2,
+ const nghttp2_frame *frame,
+ void *user_data)
+{
+ h2_session *session = user_data;
+ h2_stream *stream;
+ int stream_id = frame->hd.stream_id;
+
+ ++session->frames_sent;
+ switch (frame->hd.type) {
+ case NGHTTP2_PUSH_PROMISE:
+ /* PUSH_PROMISE we report on the promised stream */
+ stream_id = frame->push_promise.promised_stream_id;
+ break;
+ default:
+ break;
+ }
+
+ stream = get_stream(session, stream_id);
+ if (APLOGcdebug(session->c1)) {
+ char buffer[256];
+
+ h2_util_frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
+ if (stream) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+ H2_STRM_LOG(APLOGNO(10303), stream,
+ "sent FRAME[%s], frames=%ld/%ld (r/s)"),
+ buffer, (long)session->frames_received,
+ (long)session->frames_sent);
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+ H2_SSSN_LOG(APLOGNO(03068), session,
+ "sent FRAME[%s], frames=%ld/%ld (r/s)"),
+ buffer, (long)session->frames_received,
+ (long)session->frames_sent);
+ }
+ }
+
+ if (stream) {
+ h2_stream_send_frame(stream, frame->hd.type, frame->hd.flags,
+ frame->hd.length + H2_FRAME_HDR_LEN);
+ }
+ return 0;
+}
+
+#ifdef H2_NG2_INVALID_HEADER_CB
+static int on_invalid_header_cb(nghttp2_session *ngh2,
+ const nghttp2_frame *frame,
+ const uint8_t *name, size_t namelen,
+ const uint8_t *value, size_t valuelen,
+ uint8_t flags, void *user_data)
+{
+ h2_session *session = user_data;
+ h2_stream *stream;
+
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, APLOGNO(03456)
+ H2_SSSN_STRM_MSG(session, frame->hd.stream_id,
+ "invalid header '%s: %s'"),
+ apr_pstrndup(session->pool, (const char *)name, namelen),
+ apr_pstrndup(session->pool, (const char *)value, valuelen));
+ stream = get_stream(session, frame->hd.stream_id);
+ if (stream) {
+ h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR);
+ }
+ return 0;
+}
+#endif
+
+static ssize_t select_padding_cb(nghttp2_session *ngh2,
+ const nghttp2_frame *frame,
+ size_t max_payloadlen, void *user_data)
+{
+ h2_session *session = user_data;
+ size_t frame_len = frame->hd.length + H2_FRAME_HDR_LEN; /* the total length without padding */
+ size_t padded_len = frame_len;
+
+ /* Determine # of padding bytes to append to frame. Unless session->padding_always
+ * the number my be capped by the ui.write_size that currently applies.
+ */
+ if (session->padding_max) {
+ int n = ap_random_pick(0, session->padding_max);
+ padded_len = H2MIN(max_payloadlen + H2_FRAME_HDR_LEN, frame_len + n);
+ }
+
+ if (padded_len != frame_len) {
+ if (!session->padding_always && session->io.write_size
+ && (padded_len > session->io.write_size)
+ && (frame_len <= session->io.write_size)) {
+ padded_len = session->io.write_size;
+ }
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+ "select padding from [%d, %d]: %d (frame length: 0x%04x, write size: %d)",
+ (int)frame_len, (int)max_payloadlen+H2_FRAME_HDR_LEN,
+ (int)(padded_len - frame_len), (int)padded_len, (int)session->io.write_size);
+ return padded_len - H2_FRAME_HDR_LEN;
+ }
+ return frame->hd.length;
+}
+
+#define NGH2_SET_CALLBACK(callbacks, name, fn)\
+nghttp2_session_callbacks_set_##name##_callback(callbacks, fn)
+
+static apr_status_t init_callbacks(conn_rec *c, nghttp2_session_callbacks **pcb)
+{
+ int rv = nghttp2_session_callbacks_new(pcb);
+ if (rv != 0) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c,
+ APLOGNO(02926) "nghttp2_session_callbacks_new: %s",
+ nghttp2_strerror(rv));
+ return APR_EGENERAL;
+ }
+
+ NGH2_SET_CALLBACK(*pcb, send, send_cb);
+ NGH2_SET_CALLBACK(*pcb, on_frame_recv, on_frame_recv_cb);
+ NGH2_SET_CALLBACK(*pcb, on_invalid_frame_recv, on_invalid_frame_recv_cb);
+ NGH2_SET_CALLBACK(*pcb, on_data_chunk_recv, on_data_chunk_recv_cb);
+ NGH2_SET_CALLBACK(*pcb, on_stream_close, on_stream_close_cb);
+ NGH2_SET_CALLBACK(*pcb, on_begin_headers, on_begin_headers_cb);
+ NGH2_SET_CALLBACK(*pcb, on_header, on_header_cb);
+ NGH2_SET_CALLBACK(*pcb, send_data, on_send_data_cb);
+ NGH2_SET_CALLBACK(*pcb, on_frame_send, on_frame_send_cb);
+#ifdef H2_NG2_INVALID_HEADER_CB
+ NGH2_SET_CALLBACK(*pcb, on_invalid_header, on_invalid_header_cb);
+#endif
+ NGH2_SET_CALLBACK(*pcb, select_padding, select_padding_cb);
+ return APR_SUCCESS;
+}
+
+static void update_child_status(h2_session *session, int status,
+ const char *msg, const h2_stream *stream)
+{
+ /* Assume that we also change code/msg when something really happened and
+ * avoid updating the scoreboard in between */
+ if (session->last_status_code != status
+ || session->last_status_msg != msg) {
+ char sbuffer[1024];
+ sbuffer[0] = '\0';
+ if (stream) {
+ apr_snprintf(sbuffer, sizeof(sbuffer),
+ ": stream %d, %s %s",
+ stream->id,
+ stream->request? stream->request->method : "",
+ stream->request? stream->request->path : "");
+ }
+ apr_snprintf(session->status, sizeof(session->status),
+ "[%d/%d] %s%s",
+ (int)(session->remote.emitted_count + session->pushes_submitted),
+ (int)session->streams_done,
+ msg? msg : "-", sbuffer);
+ ap_update_child_status_from_server(session->c1->sbh, status,
+ session->c1, session->s);
+ ap_update_child_status_descr(session->c1->sbh, status, session->status);
+ }
+}
+
+static apr_status_t h2_session_shutdown_notice(h2_session *session)
+{
+ apr_status_t status;
+
+ ap_assert(session);
+ if (!session->local.accepting) {
+ return APR_SUCCESS;
+ }
+
+ nghttp2_submit_shutdown_notice(session->ngh2);
+ session->local.accepting = 0;
+ status = nghttp2_session_send(session->ngh2);
+ if (status == APR_SUCCESS) {
+ status = h2_c1_io_assure_flushed(&session->io);
+ }
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+ H2_SSSN_LOG(APLOGNO(03457), session, "sent shutdown notice"));
+ return status;
+}
+
+static apr_status_t h2_session_shutdown(h2_session *session, int error,
+ const char *msg, int force_close)
+{
+ apr_status_t status = APR_SUCCESS;
+
+ ap_assert(session);
+ if (session->local.shutdown) {
+ return APR_SUCCESS;
+ }
+
+ if (error && !msg) {
+ if (APR_STATUS_IS_EPIPE(error)) {
+ msg = "remote close";
+ }
+ }
+
+ if (error || force_close) {
+ /* not a graceful shutdown, we want to leave...
+ * Do not start further streams that are waiting to be scheduled.
+ * Find out the max stream id that we habe been processed or
+ * are still actively working on.
+ * Remove all streams greater than this number without submitting
+ * a RST_STREAM frame, since that should be clear from the GOAWAY
+ * we send. */
+ session->local.accepted_max = h2_mplx_c1_shutdown(session->mplx);
+ session->local.error = error;
+ session->local.error_msg = msg;
+ }
+ else {
+ /* graceful shutdown. we will continue processing all streams
+ * we have, but no longer accept new ones. Report the max stream
+ * we have received and discard all new ones. */
+ }
+
+ session->local.accepting = 0;
+ session->local.shutdown = 1;
+ if (!session->c1->aborted) {
+ nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE,
+ session->local.accepted_max,
+ error, (uint8_t*)msg, msg? strlen(msg):0);
+ status = nghttp2_session_send(session->ngh2);
+ if (status == APR_SUCCESS) {
+ status = h2_c1_io_assure_flushed(&session->io);
+ }
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+ H2_SSSN_LOG(APLOGNO(03069), session,
+ "sent GOAWAY, err=%d, msg=%s"), error, msg? msg : "");
+ }
+ h2_session_dispatch_event(session, H2_SESSION_EV_LOCAL_GOAWAY, error, msg);
+ return status;
+}
+
+static apr_status_t session_cleanup(h2_session *session, const char *trigger)
+{
+ conn_rec *c = session->c1;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ H2_SSSN_MSG(session, "pool_cleanup"));
+
+ if (session->state != H2_SESSION_ST_DONE
+ && session->state != H2_SESSION_ST_INIT) {
+ /* Not good. The connection is being torn down and we have
+ * not sent a goaway. This is considered a protocol error and
+ * the client has to assume that any streams "in flight" may have
+ * been processed and are not safe to retry.
+ * As clients with idle connection may only learn about a closed
+ * connection when sending the next request, this has the effect
+ * that at least this one request will fail.
+ */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
+ H2_SSSN_LOG(APLOGNO(03199), session,
+ "connection disappeared without proper "
+ "goodbye, clients will be confused, should not happen"));
+ }
+
+ transit(session, trigger, H2_SESSION_ST_CLEANUP);
+ h2_mplx_c1_destroy(session->mplx);
+ session->mplx = NULL;
+
+ ap_assert(session->ngh2);
+ nghttp2_session_del(session->ngh2);
+ session->ngh2 = NULL;
+ h2_conn_ctx_detach(c);
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t session_pool_cleanup(void *data)
+{
+ conn_rec *c = data;
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
+ h2_session *session = conn_ctx? conn_ctx->session : NULL;
+
+ if (session) {
+ int mpm_state = 0;
+ int level;
+
+ ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state);
+ level = (AP_MPMQ_STOPPING == mpm_state)? APLOG_DEBUG : APLOG_WARNING;
+ /* if the session is still there, now is the last chance
+ * to perform cleanup. Normally, cleanup should have happened
+ * earlier in the connection pre_close.
+ * However, when the server is stopping, it may shutdown connections
+ * without running the pre_close hooks. Do not want about that. */
+ ap_log_cerror(APLOG_MARK, level, 0, c,
+ H2_SSSN_LOG(APLOGNO(10020), session,
+ "session cleanup triggered by pool cleanup. "
+ "this should have happened earlier already."));
+ return session_cleanup(session, "pool cleanup");
+ }
+ return APR_SUCCESS;
+}
+
+static /* atomic */ apr_uint32_t next_id;
+
+apr_status_t h2_session_create(h2_session **psession, conn_rec *c, request_rec *r,
+ server_rec *s, h2_workers *workers)
+{
+ nghttp2_session_callbacks *callbacks = NULL;
+ nghttp2_option *options = NULL;
+ uint32_t n;
+ int thread_num;
+ apr_pool_t *pool = NULL;
+ h2_session *session;
+ h2_stream *stream0;
+ apr_status_t status;
+ int rv;
+
+ *psession = NULL;
+ apr_pool_create(&pool, c->pool);
+ apr_pool_tag(pool, "h2_session");
+ session = apr_pcalloc(pool, sizeof(h2_session));
+ if (!session) {
+ return APR_ENOMEM;
+ }
+
+ *psession = session;
+ /* c->id does not give a unique id for the lifetime of the session.
+ * mpms like event change c->id when re-activating a keepalive
+ * connection based on the child_num+thread_num of the worker
+ * processing it.
+ * We'd like to have an id that remains constant and unique bc
+ * h2 streams can live through keepalive periods. While double id
+ * will not lead to processing failures, it will confuse log analysis.
+ */
+#if AP_MODULE_MAGIC_AT_LEAST(20211221, 8)
+ ap_sb_get_child_thread(c->sbh, &session->child_num, &thread_num);
+#else
+ (void)thread_num;
+ session->child_num = (int)getpid();
+#endif
+ session->id = apr_atomic_inc32(&next_id);
+ session->c1 = c;
+ session->r = r;
+ session->s = s;
+ session->pool = pool;
+ session->workers = workers;
+
+ session->state = H2_SESSION_ST_INIT;
+ session->local.accepting = 1;
+ session->remote.accepting = 1;
+
+ session->max_stream_count = h2_config_sgeti(s, H2_CONF_MAX_STREAMS);
+ session->max_stream_mem = h2_config_sgeti(s, H2_CONF_STREAM_MAX_MEM);
+
+ session->out_c1_blocked = h2_iq_create(session->pool, (int)session->max_stream_count);
+ session->ready_to_process = h2_iq_create(session->pool, (int)session->max_stream_count);
+
+ session->monitor = apr_pcalloc(pool, sizeof(h2_stream_monitor));
+ session->monitor->ctx = session;
+ session->monitor->on_state_enter = on_stream_state_enter;
+ session->monitor->on_state_event = on_stream_state_event;
+ session->monitor->on_event = on_stream_event;
+
+ stream0 = h2_stream_create(0, session->pool, session, NULL, 0);
+ stream0->c2 = session->c1; /* stream0's connection is the main connection */
+ session->mplx = h2_mplx_c1_create(session->child_num, session->id,
+ stream0, s, session->pool, workers);
+ if (!session->mplx) {
+ apr_pool_destroy(pool);
+ return APR_ENOTIMPL;
+ }
+
+ h2_c1_io_init(&session->io, session);
+ session->padding_max = h2_config_sgeti(s, H2_CONF_PADDING_BITS);
+ if (session->padding_max) {
+ session->padding_max = (0x01 << session->padding_max) - 1;
+ }
+ session->padding_always = h2_config_sgeti(s, H2_CONF_PADDING_ALWAYS);
+ session->bbtmp = apr_brigade_create(session->pool, c->bucket_alloc);
+
+ status = init_callbacks(c, &callbacks);
+ if (status != APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, status, c, APLOGNO(02927)
+ "nghttp2: error in init_callbacks");
+ apr_pool_destroy(pool);
+ return status;
+ }
+
+ rv = nghttp2_option_new(&options);
+ if (rv != 0) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, c,
+ APLOGNO(02928) "nghttp2_option_new: %s",
+ nghttp2_strerror(rv));
+ apr_pool_destroy(pool);
+ return status;
+ }
+ nghttp2_option_set_peer_max_concurrent_streams(options, (uint32_t)session->max_stream_count);
+ /* We need to handle window updates ourself, otherwise we
+ * get flooded by nghttp2. */
+ nghttp2_option_set_no_auto_window_update(options, 1);
+#ifdef H2_NG2_NO_CLOSED_STREAMS
+ /* We do not want nghttp2 to keep information about closed streams as
+ * that accumulates memory on long connections. This makes PRIORITY
+ * setting in relation to older streams non-working. */
+ nghttp2_option_set_no_closed_streams(options, 1);
+#endif
+#ifdef H2_NG2_RFC9113_STRICTNESS
+ /* nghttp2 v1.50.0 introduces the strictness checks on leading/trailing
+ * whitespace of RFC 9113 for fields. But, by default, it RST streams
+ * carrying such. We do not want that. We want to strip the ws and
+ * handle them, just like the HTTP/1.1 parser does. */
+ nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(options, 1);
+#endif
+ rv = nghttp2_session_server_new2(&session->ngh2, callbacks,
+ session, options);
+ nghttp2_session_callbacks_del(callbacks);
+ nghttp2_option_del(options);
+
+ if (rv != 0) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, c,
+ APLOGNO(02929) "nghttp2_session_server_new: %s",
+ nghttp2_strerror(rv));
+ apr_pool_destroy(pool);
+ return APR_ENOMEM;
+ }
+
+ n = h2_config_sgeti(s, H2_CONF_PUSH_DIARY_SIZE);
+ session->push_diary = h2_push_diary_create(session->pool, n);
+
+ if (APLOGcdebug(c)) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
+ H2_SSSN_LOG(APLOGNO(03200), session,
+ "created, max_streams=%d, stream_mem=%d, "
+ "workers_limit=%d, workers_max=%d, "
+ "push_diary(type=%d,N=%d)"),
+ (int)session->max_stream_count,
+ (int)session->max_stream_mem,
+ session->mplx->processing_limit,
+ session->mplx->processing_max,
+ session->push_diary->dtype,
+ (int)session->push_diary->N);
+ }
+
+ apr_pool_pre_cleanup_register(pool, c, session_pool_cleanup);
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t h2_session_start(h2_session *session, int *rv)
+{
+ apr_status_t status = APR_SUCCESS;
+ nghttp2_settings_entry settings[3];
+ size_t slen;
+ int win_size;
+
+ ap_assert(session);
+ /* Start the conversation by submitting our SETTINGS frame */
+ *rv = 0;
+ if (session->r) {
+ const char *s, *cs;
+ apr_size_t dlen;
+ h2_stream * stream;
+
+ /* 'h2c' mode: we should have a 'HTTP2-Settings' header with
+ * base64 encoded client settings. */
+ s = apr_table_get(session->r->headers_in, "HTTP2-Settings");
+ if (!s) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EINVAL, session->r,
+ APLOGNO(02931)
+ "HTTP2-Settings header missing in request");
+ return APR_EINVAL;
+ }
+ cs = NULL;
+ dlen = h2_util_base64url_decode(&cs, s, session->pool);
+
+ if (APLOGrdebug(session->r)) {
+ char buffer[128];
+ h2_util_hex_dump(buffer, 128, (char*)cs, dlen);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, session->r, APLOGNO(03070)
+ "upgrading h2c session with HTTP2-Settings: %s -> %s (%d)",
+ s, buffer, (int)dlen);
+ }
+
+ *rv = nghttp2_session_upgrade(session->ngh2, (uint8_t*)cs, dlen, NULL);
+ if (*rv != 0) {
+ status = APR_EINVAL;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r,
+ APLOGNO(02932) "nghttp2_session_upgrade: %s",
+ nghttp2_strerror(*rv));
+ return status;
+ }
+
+ /* Now we need to auto-open stream 1 for the request we got. */
+ stream = h2_session_open_stream(session, 1, 0);
+ if (!stream) {
+ status = APR_EGENERAL;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r,
+ APLOGNO(02933) "open stream 1: %s",
+ nghttp2_strerror(*rv));
+ return status;
+ }
+
+ status = h2_stream_set_request_rec(stream, session->r, 1);
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+ }
+
+ slen = 0;
+ settings[slen].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
+ settings[slen].value = (uint32_t)session->max_stream_count;
+ ++slen;
+ win_size = h2_config_sgeti(session->s, H2_CONF_WIN_SIZE);
+ if (win_size != H2_INITIAL_WINDOW_SIZE) {
+ settings[slen].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+ settings[slen].value = win_size;
+ ++slen;
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c1,
+ H2_SSSN_LOG(APLOGNO(03201), session,
+ "start, INITIAL_WINDOW_SIZE=%ld, MAX_CONCURRENT_STREAMS=%d"),
+ (long)win_size, (int)session->max_stream_count);
+ *rv = nghttp2_submit_settings(session->ngh2, NGHTTP2_FLAG_NONE,
+ settings, slen);
+ if (*rv != 0) {
+ status = APR_EGENERAL;
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c1,
+ H2_SSSN_LOG(APLOGNO(02935), session,
+ "nghttp2_submit_settings: %s"), nghttp2_strerror(*rv));
+ }
+ else {
+ /* use maximum possible value for connection window size. We are only
+ * interested in per stream flow control. which have the initial window
+ * size configured above.
+ * Therefore, for our use, the connection window can only get in the
+ * way. Example: if we allow 100 streams with a 32KB window each, we
+ * buffer up to 3.2 MB of data. Unless we do separate connection window
+ * interim updates, any smaller connection window will lead to blocking
+ * in DATA flow.
+ */
+ *rv = nghttp2_submit_window_update(session->ngh2, NGHTTP2_FLAG_NONE,
+ 0, NGHTTP2_MAX_WINDOW_SIZE - win_size);
+ if (*rv != 0) {
+ status = APR_EGENERAL;
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c1,
+ H2_SSSN_LOG(APLOGNO(02970), session,
+ "nghttp2_submit_window_update: %s"),
+ nghttp2_strerror(*rv));
+ }
+ }
+
+ return status;
+}
+
+struct h2_stream *h2_session_push(h2_session *session, h2_stream *is,
+ h2_push *push)
+{
+ h2_stream *stream;
+ h2_ngheader *ngh;
+ apr_status_t status;
+ int nid = 0;
+
+ status = h2_req_create_ngheader(&ngh, is->pool, push->req);
+ if (status == APR_SUCCESS) {
+ nid = nghttp2_submit_push_promise(session->ngh2, 0, is->id,
+ ngh->nv, ngh->nvlen, NULL);
+ }
+ if (status != APR_SUCCESS || nid <= 0) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c1,
+ H2_STRM_LOG(APLOGNO(03075), is,
+ "submitting push promise fail: %s"), nghttp2_strerror(nid));
+ return NULL;
+ }
+ ++session->pushes_promised;
+
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+ H2_STRM_LOG(APLOGNO(03076), is, "SERVER_PUSH %d for %s %s on %d"),
+ nid, push->req->method, push->req->path, is->id);
+
+ stream = h2_session_open_stream(session, nid, is->id);
+ if (!stream) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+ H2_STRM_LOG(APLOGNO(03077), is,
+ "failed to create stream obj %d"), nid);
+ /* kill the push_promise */
+ nghttp2_submit_rst_stream(session->ngh2, NGHTTP2_FLAG_NONE, nid,
+ NGHTTP2_INTERNAL_ERROR);
+ return NULL;
+ }
+
+ h2_session_set_prio(session, stream, push->priority);
+ h2_stream_set_request(stream, push->req);
+ return stream;
+}
+
+static int valid_weight(float f)
+{
+ int w = (int)f;
+ return (w < NGHTTP2_MIN_WEIGHT? NGHTTP2_MIN_WEIGHT :
+ (w > NGHTTP2_MAX_WEIGHT)? NGHTTP2_MAX_WEIGHT : w);
+}
+
+apr_status_t h2_session_set_prio(h2_session *session, h2_stream *stream,
+ const h2_priority *prio)
+{
+ apr_status_t status = APR_SUCCESS;
+ nghttp2_stream *s_grandpa, *s_parent, *s;
+
+ if (prio == NULL) {
+ /* we treat this as a NOP */
+ return APR_SUCCESS;
+ }
+ s = nghttp2_session_find_stream(session->ngh2, stream->id);
+ if (!s) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c1,
+ H2_STRM_MSG(stream, "lookup of nghttp2_stream failed"));
+ return APR_EINVAL;
+ }
+
+ s_parent = nghttp2_stream_get_parent(s);
+ if (s_parent) {
+ nghttp2_priority_spec ps;
+ int id_parent, id_grandpa, w_parent, w;
+ int rv = 0;
+ const char *ptype = "AFTER";
+ h2_dependency dep = prio->dependency;
+
+ id_parent = nghttp2_stream_get_stream_id(s_parent);
+ s_grandpa = nghttp2_stream_get_parent(s_parent);
+ if (s_grandpa) {
+ id_grandpa = nghttp2_stream_get_stream_id(s_grandpa);
+ }
+ else {
+ /* parent of parent does not exist,
+ * only possible if parent == root */
+ dep = H2_DEPENDANT_AFTER;
+ }
+
+ switch (dep) {
+ case H2_DEPENDANT_INTERLEAVED:
+ /* PUSHed stream is to be interleaved with initiating stream.
+ * It is made a sibling of the initiating stream and gets a
+ * proportional weight [1, MAX_WEIGHT] of the initiaing
+ * stream weight.
+ */
+ ptype = "INTERLEAVED";
+ w_parent = nghttp2_stream_get_weight(s_parent);
+ w = valid_weight(w_parent * ((float)prio->weight / NGHTTP2_MAX_WEIGHT));
+ nghttp2_priority_spec_init(&ps, id_grandpa, w, 0);
+ break;
+
+ case H2_DEPENDANT_BEFORE:
+ /* PUSHed stream os to be sent BEFORE the initiating stream.
+ * It gets the same weight as the initiating stream, replaces
+ * that stream in the dependency tree and has the initiating
+ * stream as child.
+ */
+ ptype = "BEFORE";
+ w = w_parent = nghttp2_stream_get_weight(s_parent);
+ nghttp2_priority_spec_init(&ps, stream->id, w_parent, 0);
+ id_grandpa = nghttp2_stream_get_stream_id(s_grandpa);
+ rv = nghttp2_session_change_stream_priority(session->ngh2, id_parent, &ps);
+ if (rv < 0) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, APLOGNO(03202)
+ H2_SSSN_STRM_MSG(session, id_parent,
+ "PUSH BEFORE, weight=%d, depends=%d, returned=%d"),
+ ps.weight, ps.stream_id, rv);
+ return APR_EGENERAL;
+ }
+ nghttp2_priority_spec_init(&ps, id_grandpa, w, 0);
+ break;
+
+ case H2_DEPENDANT_AFTER:
+ /* The PUSHed stream is to be sent after the initiating stream.
+ * Give if the specified weight and let it depend on the intiating
+ * stream.
+ */
+ /* fall through, it's the default */
+ default:
+ nghttp2_priority_spec_init(&ps, id_parent, valid_weight(prio->weight), 0);
+ break;
+ }
+
+
+ rv = nghttp2_session_change_stream_priority(session->ngh2, stream->id, &ps);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+ H2_STRM_LOG(APLOGNO(03203), stream,
+ "PUSH %s, weight=%d, depends=%d, returned=%d"),
+ ptype, ps.weight, ps.stream_id, rv);
+ status = (rv < 0)? APR_EGENERAL : APR_SUCCESS;
+ }
+
+ return status;
+}
+
+int h2_session_push_enabled(h2_session *session)
+{
+ /* iff we can and they can and want */
+ return (session->remote.accepting /* remote GOAWAY received */
+ && h2_config_sgeti(session->s, H2_CONF_PUSH)
+ && nghttp2_session_get_remote_settings(session->ngh2,
+ NGHTTP2_SETTINGS_ENABLE_PUSH));
+}
+
+static int h2_session_want_send(h2_session *session)
+{
+ return nghttp2_session_want_write(session->ngh2)
+ || h2_c1_io_pending(&session->io);
+}
+
+static apr_status_t h2_session_send(h2_session *session)
+{
+ int ngrv, pending = 0;
+ apr_status_t rv = APR_SUCCESS;
+
+ while (nghttp2_session_want_write(session->ngh2)) {
+ ngrv = nghttp2_session_send(session->ngh2);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+ "nghttp2_session_send: %d", (int)ngrv);
+ pending = 1;
+ if (ngrv != 0 && ngrv != NGHTTP2_ERR_WOULDBLOCK) {
+ if (nghttp2_is_fatal(ngrv)) {
+ h2_session_dispatch_event(session, H2_SESSION_EV_PROTO_ERROR,
+ ngrv, nghttp2_strerror(ngrv));
+ rv = APR_EGENERAL;
+ goto cleanup;
+ }
+ }
+ if (h2_c1_io_needs_flush(&session->io)) {
+ rv = h2_c1_io_assure_flushed(&session->io);
+ pending = 0;
+ }
+ }
+ if (pending) {
+ rv = h2_c1_io_pass(&session->io);
+ }
+cleanup:
+ if (rv != APR_SUCCESS) {
+ h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, rv, NULL);
+ }
+ return rv;
+}
+
+/**
+ * A streams input state has changed.
+ */
+static void on_stream_input(void *ctx, h2_stream *stream)
+{
+ h2_session *session = ctx;
+
+ ap_assert(stream);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+ H2_STRM_MSG(stream, "on_input change"));
+ update_child_status(session, SERVER_BUSY_READ, "read", stream);
+ if (stream->id == 0) {
+ /* input on primary connection available? read */
+ h2_c1_read(session);
+ }
+ else {
+ h2_stream_on_input_change(stream);
+ }
+}
+
+/**
+ * A streams output state has changed.
+ */
+static void on_stream_output(void *ctx, h2_stream *stream)
+{
+ h2_session *session = ctx;
+
+ ap_assert(stream);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+ H2_STRM_MSG(stream, "on_output change"));
+ if (stream->id != 0) {
+ update_child_status(session, SERVER_BUSY_WRITE, "write", stream);
+ h2_stream_on_output_change(stream);
+ }
+}
+
+
+static const char *StateNames[] = {
+ "INIT", /* H2_SESSION_ST_INIT */
+ "DONE", /* H2_SESSION_ST_DONE */
+ "IDLE", /* H2_SESSION_ST_IDLE */
+ "BUSY", /* H2_SESSION_ST_BUSY */
+ "WAIT", /* H2_SESSION_ST_WAIT */
+ "CLEANUP", /* H2_SESSION_ST_CLEANUP */
+};
+
+const char *h2_session_state_str(h2_session_state state)
+{
+ if (state >= (sizeof(StateNames)/sizeof(StateNames[0]))) {
+ return "unknown";
+ }
+ return StateNames[state];
+}
+
+static void transit(h2_session *session, const char *action, h2_session_state nstate)
+{
+ int ostate;
+
+ if (session->state != nstate) {
+ ostate = session->state;
+
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+ H2_SSSN_LOG(APLOGNO(03078), session,
+ "transit [%s] -- %s --> [%s]"),
+ h2_session_state_str(ostate), action,
+ h2_session_state_str(nstate));
+
+ switch (session->state) {
+ case H2_SESSION_ST_IDLE:
+ if (!session->remote.emitted_count) {
+ /* on fresh connections, with async mpm, do not return
+ * to mpm for a second. This gives the first request a better
+ * chance to arrive (und connection leaving IDLE state).
+ * If we return to mpm right away, this connection has the
+ * same chance of being cleaned up by the mpm as connections
+ * that already served requests - not fair. */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c1,
+ H2_SSSN_LOG("", session, "enter idle"));
+ }
+ else {
+ /* normal keepalive setup */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c1,
+ H2_SSSN_LOG("", session, "enter keepalive"));
+ }
+ session->state = nstate;
+ break;
+ case H2_SESSION_ST_DONE:
+ break;
+ default:
+ /* nop */
+ session->state = nstate;
+ break;
+ }
+ }
+}
+
+static void h2_session_ev_init(h2_session *session, int arg, const char *msg)
+{
+ switch (session->state) {
+ case H2_SESSION_ST_INIT:
+ transit(session, "init", H2_SESSION_ST_BUSY);
+ break;
+ default:
+ /* nop */
+ break;
+ }
+}
+
+static void h2_session_ev_input_pending(h2_session *session, int arg, const char *msg)
+{
+ switch (session->state) {
+ case H2_SESSION_ST_INIT:
+ case H2_SESSION_ST_IDLE:
+ case H2_SESSION_ST_WAIT:
+ transit(session, "input read", H2_SESSION_ST_BUSY);
+ break;
+ default:
+ break;
+ }
+}
+
+static void h2_session_ev_input_exhausted(h2_session *session, int arg, const char *msg)
+{
+ switch (session->state) {
+ case H2_SESSION_ST_BUSY:
+ if (!h2_session_want_send(session)) {
+ if (session->open_streams == 0) {
+ transit(session, "input exhausted, no streams", H2_SESSION_ST_IDLE);
+ }
+ else {
+ transit(session, "input exhausted", H2_SESSION_ST_WAIT);
+ }
+ }
+ break;
+ case H2_SESSION_ST_WAIT:
+ if (session->open_streams == 0) {
+ transit(session, "input exhausted, no streams", H2_SESSION_ST_IDLE);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void h2_session_ev_local_goaway(h2_session *session, int arg, const char *msg)
+{
+ cleanup_unprocessed_streams(session);
+ transit(session, "local goaway", H2_SESSION_ST_DONE);
+}
+
+static void h2_session_ev_remote_goaway(h2_session *session, int arg, const char *msg)
+{
+ if (!session->remote.shutdown) {
+ session->remote.error = arg;
+ session->remote.accepting = 0;
+ session->remote.shutdown = 1;
+ cleanup_unprocessed_streams(session);
+ transit(session, "remote goaway", H2_SESSION_ST_DONE);
+ }
+}
+
+static void h2_session_ev_conn_error(h2_session *session, int arg, const char *msg)
+{
+ switch (session->state) {
+ case H2_SESSION_ST_INIT:
+ case H2_SESSION_ST_DONE:
+ /* just leave */
+ transit(session, "conn error", H2_SESSION_ST_DONE);
+ break;
+
+ default:
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+ H2_SSSN_LOG(APLOGNO(03401), session,
+ "conn error -> shutdown"));
+ h2_session_shutdown(session, arg, msg, 0);
+ break;
+ }
+}
+
+static void h2_session_ev_proto_error(h2_session *session, int arg, const char *msg)
+{
+ if (!session->local.shutdown) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+ H2_SSSN_LOG(APLOGNO(03402), session,
+ "proto error -> shutdown"));
+ h2_session_shutdown(session, arg, msg, 0);
+ }
+}
+
+static void h2_session_ev_conn_timeout(h2_session *session, int arg, const char *msg)
+{
+ transit(session, msg, H2_SESSION_ST_DONE);
+ if (!session->local.shutdown) {
+ h2_session_shutdown(session, arg, msg, 1);
+ }
+}
+
+static void h2_session_ev_ngh2_done(h2_session *session, int arg, const char *msg)
+{
+ switch (session->state) {
+ case H2_SESSION_ST_DONE:
+ /* nop */
+ break;
+ default:
+ transit(session, "nghttp2 done", H2_SESSION_ST_DONE);
+ break;
+ }
+}
+
+static void h2_session_ev_mpm_stopping(h2_session *session, int arg, const char *msg)
+{
+ switch (session->state) {
+ case H2_SESSION_ST_DONE:
+ /* nop */
+ break;
+ default:
+ h2_session_shutdown_notice(session);
+#if !AP_MODULE_MAGIC_AT_LEAST(20120211, 110)
+ h2_workers_graceful_shutdown(session->workers);
+#endif
+ break;
+ }
+}
+
+static void h2_session_ev_pre_close(h2_session *session, int arg, const char *msg)
+{
+ h2_session_shutdown(session, arg, msg, 1);
+}
+
+static void h2_session_ev_no_more_streams(h2_session *session)
+{
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+ H2_SSSN_LOG(APLOGNO(10304), session, "no more streams"));
+ switch (session->state) {
+ case H2_SESSION_ST_BUSY:
+ case H2_SESSION_ST_WAIT:
+ if (!h2_session_want_send(session)) {
+ if (session->local.accepting) {
+ /* We wait for new frames on c1 only. */
+ transit(session, "all streams done", H2_SESSION_ST_IDLE);
+ }
+ else {
+ /* We are no longer accepting new streams.
+ * Time to leave. */
+ h2_session_shutdown(session, 0, "done", 0);
+ transit(session, "c1 done after goaway", H2_SESSION_ST_DONE);
+ }
+ }
+ else {
+ transit(session, "no more streams", H2_SESSION_ST_WAIT);
+ }
+ break;
+ default:
+ /* nop */
+ break;
+ }
+}
+
+static void ev_stream_created(h2_session *session, h2_stream *stream)
+{
+ /* nop */
+}
+
+static void ev_stream_open(h2_session *session, h2_stream *stream)
+{
+ if (H2_STREAM_CLIENT_INITIATED(stream->id)) {
+ ++session->remote.emitted_count;
+ if (stream->id > session->remote.emitted_max) {
+ session->remote.emitted_max = stream->id;
+ session->local.accepted_max = stream->id;
+ }
+ }
+ else {
+ if (stream->id > session->local.emitted_max) {
+ ++session->local.emitted_count;
+ session->remote.emitted_max = stream->id;
+ }
+ }
+ /* Stream state OPEN means we have received all request headers
+ * and can start processing the stream. */
+ h2_iq_append(session->ready_to_process, stream->id);
+ update_child_status(session, SERVER_BUSY_READ, "schedule", stream);
+}
+
+static void ev_stream_closed(h2_session *session, h2_stream *stream)
+{
+ apr_bucket *b;
+
+ if (H2_STREAM_CLIENT_INITIATED(stream->id)
+ && (stream->id > session->local.completed_max)) {
+ session->local.completed_max = stream->id;
+ }
+ /* The stream might have data in the buffers of the main connection.
+ * We can only free the allocated resources once all had been written.
+ * Send a special buckets on the connection that gets destroyed when
+ * all preceding data has been handled. On its destruction, it is safe
+ * to purge all resources of the stream. */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+ H2_STRM_MSG(stream, "adding h2_eos to c1 out"));
+ b = h2_bucket_eos_create(session->c1->bucket_alloc, stream);
+ APR_BRIGADE_INSERT_TAIL(session->bbtmp, b);
+ h2_c1_io_append(&session->io, session->bbtmp);
+ apr_brigade_cleanup(session->bbtmp);
+}
+
+static void on_stream_state_enter(void *ctx, h2_stream *stream)
+{
+ h2_session *session = ctx;
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+ H2_STRM_MSG(stream, "entered state"));
+ switch (stream->state) {
+ case H2_SS_IDLE: /* stream was created */
+ ev_stream_created(session, stream);
+ break;
+ case H2_SS_OPEN: /* stream has request headers */
+ case H2_SS_RSVD_L:
+ ev_stream_open(session, stream);
+ break;
+ case H2_SS_CLOSED_L: /* stream output was closed, but remote end is not */
+ /* If the stream is still being processed, it could still be reading
+ * its input (theoretically, http request hangling does not normally).
+ * But when processing is done, we need to cancel the stream as no
+ * one is consuming the input any longer.
+ * This happens, for example, on a large POST when the response
+ * is ready early due to the POST being denied. */
+ if (!h2_mplx_c1_stream_is_running(session->mplx, stream)) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+ H2_STRM_LOG(APLOGNO(10305), stream, "remote close missing"));
+ nghttp2_submit_rst_stream(session->ngh2, NGHTTP2_FLAG_NONE,
+ stream->id, H2_ERR_NO_ERROR);
+ }
+ break;
+ case H2_SS_CLOSED_R: /* stream input was closed */
+ break;
+ case H2_SS_CLOSED: /* stream in+out were closed */
+ ev_stream_closed(session, stream);
+ break;
+ case H2_SS_CLEANUP:
+ nghttp2_session_set_stream_user_data(session->ngh2, stream->id, NULL);
+ h2_mplx_c1_stream_cleanup(session->mplx, stream, &session->open_streams);
+ ++session->streams_done;
+ update_child_status(session, SERVER_BUSY_WRITE, "done", stream);
+ if (session->open_streams == 0) {
+ h2_session_dispatch_event(session, H2_SESSION_EV_NO_MORE_STREAMS,
+ 0, "stream done");
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void on_stream_event(void *ctx, h2_stream *stream, h2_stream_event_t ev)
+{
+ h2_session *session = ctx;
+ switch (ev) {
+ case H2_SEV_IN_DATA_PENDING:
+ session->input_flushed = 1;
+ break;
+ case H2_SEV_OUT_C1_BLOCK:
+ h2_iq_append(session->out_c1_blocked, stream->id);
+ break;
+ default:
+ /* NOP */
+ break;
+ }
+}
+
+static void on_stream_state_event(void *ctx, h2_stream *stream,
+ h2_stream_event_t ev)
+{
+ h2_session *session = ctx;
+ switch (ev) {
+ case H2_SEV_CANCELLED:
+ if (session->state != H2_SESSION_ST_DONE) {
+ nghttp2_submit_rst_stream(session->ngh2, NGHTTP2_FLAG_NONE,
+ stream->id, stream->rst_error);
+ }
+ break;
+ default:
+ /* NOP */
+ break;
+ }
+}
+
+void h2_session_dispatch_event(h2_session *session, h2_session_event_t ev,
+ apr_status_t arg, const char *msg)
+{
+ switch (ev) {
+ case H2_SESSION_EV_INIT:
+ h2_session_ev_init(session, arg, msg);
+ break;
+ case H2_SESSION_EV_INPUT_PENDING:
+ h2_session_ev_input_pending(session, arg, msg);
+ break;
+ case H2_SESSION_EV_INPUT_EXHAUSTED:
+ h2_session_ev_input_exhausted(session, arg, msg);
+ break;
+ case H2_SESSION_EV_LOCAL_GOAWAY:
+ h2_session_ev_local_goaway(session, arg, msg);
+ break;
+ case H2_SESSION_EV_REMOTE_GOAWAY:
+ h2_session_ev_remote_goaway(session, arg, msg);
+ break;
+ case H2_SESSION_EV_CONN_ERROR:
+ h2_session_ev_conn_error(session, arg, msg);
+ break;
+ case H2_SESSION_EV_PROTO_ERROR:
+ h2_session_ev_proto_error(session, arg, msg);
+ break;
+ case H2_SESSION_EV_CONN_TIMEOUT:
+ h2_session_ev_conn_timeout(session, arg, msg);
+ break;
+ case H2_SESSION_EV_NGH2_DONE:
+ h2_session_ev_ngh2_done(session, arg, msg);
+ break;
+ case H2_SESSION_EV_MPM_STOPPING:
+ h2_session_ev_mpm_stopping(session, arg, msg);
+ break;
+ case H2_SESSION_EV_PRE_CLOSE:
+ h2_session_ev_pre_close(session, arg, msg);
+ break;
+ case H2_SESSION_EV_NO_MORE_STREAMS:
+ h2_session_ev_no_more_streams(session);
+ break;
+ default:
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c1,
+ H2_SSSN_MSG(session, "unknown event %d"), ev);
+ break;
+ }
+}
+
+static void unblock_c1_out(h2_session *session) {
+ int sid;
+
+ while ((sid = h2_iq_shift(session->out_c1_blocked)) > 0) {
+ nghttp2_session_resume_data(session->ngh2, sid);
+ }
+}
+
+apr_status_t h2_session_process(h2_session *session, int async)
+{
+ apr_status_t status = APR_SUCCESS;
+ conn_rec *c = session->c1;
+ int rv, mpm_state, trace = APLOGctrace3(c);
+
+ if (trace) {
+ ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c,
+ H2_SSSN_MSG(session, "process start, async=%d"), async);
+ }
+
+ if (H2_SESSION_ST_INIT == session->state) {
+ if (!h2_protocol_is_acceptable_c1(c, session->r, 1)) {
+ const char *msg = nghttp2_strerror(NGHTTP2_INADEQUATE_SECURITY);
+ update_child_status(session, SERVER_BUSY_READ, msg, NULL);
+ h2_session_shutdown(session, APR_EINVAL, msg, 1);
+ }
+ else {
+ update_child_status(session, SERVER_BUSY_READ, "init", NULL);
+ status = h2_session_start(session, &rv);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c,
+ H2_SSSN_LOG(APLOGNO(03079), session,
+ "started on %s:%d"),
+ session->s->server_hostname,
+ c->local_addr->port);
+ if (status != APR_SUCCESS) {
+ h2_session_dispatch_event(session,
+ H2_SESSION_EV_CONN_ERROR, status, NULL);
+ }
+ else {
+ h2_session_dispatch_event(session, H2_SESSION_EV_INIT, 0, NULL);
+ }
+ }
+ }
+
+ while (session->state != H2_SESSION_ST_DONE) {
+
+ /* PR65731: we may get a new connection to process while the
+ * MPM already is stopping. For example due to having reached
+ * MaxRequestsPerChild limit.
+ * Since this is supposed to handle things gracefully, we need to:
+ * a) fully initialize the session before GOAWAYing
+ * b) give the client the chance to submit at least one request
+ */
+ if (session->state != H2_SESSION_ST_INIT /* no longer intializing */
+ && session->local.accepted_max > 0 /* have gotten at least one stream */
+ && session->local.accepting /* have not already locally shut down */
+ && !ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state)) {
+ if (mpm_state == AP_MPMQ_STOPPING) {
+ h2_session_dispatch_event(session, H2_SESSION_EV_MPM_STOPPING, 0, NULL);
+ }
+ }
+
+ session->status[0] = '\0';
+
+ if (h2_session_want_send(session)) {
+ h2_session_send(session);
+ }
+ else if (!nghttp2_session_want_read(session->ngh2)) {
+ h2_session_dispatch_event(session, H2_SESSION_EV_NGH2_DONE, 0, NULL);
+ }
+
+ if (!h2_iq_empty(session->ready_to_process)) {
+ h2_mplx_c1_process(session->mplx, session->ready_to_process,
+ get_stream, stream_pri_cmp, session,
+ &session->open_streams);
+ transit(session, "scheduled stream", H2_SESSION_ST_BUSY);
+ }
+
+ if (session->input_flushed) {
+ transit(session, "forwarded input", H2_SESSION_ST_BUSY);
+ session->input_flushed = 0;
+ }
+
+ if (!h2_iq_empty(session->out_c1_blocked)) {
+ unblock_c1_out(session);
+ transit(session, "unblocked output", H2_SESSION_ST_BUSY);
+ }
+
+ if (session->reprioritize) {
+ h2_mplx_c1_reprioritize(session->mplx, stream_pri_cmp, session);
+ session->reprioritize = 0;
+ }
+
+ if (h2_session_want_send(session)) {
+ h2_session_send(session);
+ }
+
+ status = h2_c1_io_assure_flushed(&session->io);
+ if (APR_SUCCESS != status) {
+ h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, status, NULL);
+ }
+
+ switch (session->state) {
+ case H2_SESSION_ST_INIT:
+ ap_assert(0);
+ h2_c1_read(session);
+ break;
+
+ case H2_SESSION_ST_IDLE:
+ ap_assert(session->open_streams == 0);
+ ap_assert(nghttp2_session_want_read(session->ngh2));
+ if (!h2_session_want_send(session)) {
+ /* Give any new incoming request a short grace period to
+ * arrive while we are still hot and return to the mpm
+ * connection handling when nothing really happened. */
+ h2_c1_read(session);
+ if (H2_SESSION_ST_IDLE == session->state) {
+ if (async) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c,
+ H2_SSSN_LOG(APLOGNO(10306), session,
+ "returning to mpm c1 monitoring"));
+ goto leaving;
+ }
+ else {
+ /* Not an async mpm, we must continue waiting
+ * for client data to arrive until the configured
+ * server Timeout/KeepAliveTimeout happens */
+ apr_time_t timeout = (session->open_streams == 0)?
+ session->s->keep_alive_timeout :
+ session->s->timeout;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, c,
+ H2_SSSN_MSG(session, "polling timeout=%d"),
+ (int)apr_time_sec(timeout));
+ status = h2_mplx_c1_poll(session->mplx, timeout,
+ on_stream_input,
+ on_stream_output, session);
+ if (APR_STATUS_IS_TIMEUP(status)) {
+ if (session->open_streams == 0) {
+ h2_session_dispatch_event(session,
+ H2_SESSION_EV_CONN_TIMEOUT, status, NULL);
+ break;
+ }
+ }
+ else if (APR_SUCCESS != status) {
+ h2_session_dispatch_event(session,
+ H2_SESSION_EV_CONN_ERROR, status, NULL);
+ break;
+ }
+ }
+ }
+ }
+ else {
+ transit(session, "c1 io pending", H2_SESSION_ST_BUSY);
+ }
+ break;
+
+ case H2_SESSION_ST_BUSY:
+ /* IO happening in and out. Make sure we react to c2 events
+ * inbetween send and receive. */
+ status = h2_mplx_c1_poll(session->mplx, 0,
+ on_stream_input, on_stream_output, session);
+ if (APR_SUCCESS != status && !APR_STATUS_IS_TIMEUP(status)) {
+ h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, status, NULL);
+ break;
+ }
+ h2_c1_read(session);
+ break;
+
+ case H2_SESSION_ST_WAIT:
+ status = h2_c1_io_assure_flushed(&session->io);
+ if (APR_SUCCESS != status) {
+ h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, status, NULL);
+ break;
+ }
+ if (session->open_streams == 0) {
+ h2_session_dispatch_event(session, H2_SESSION_EV_NO_MORE_STREAMS,
+ 0, "streams really done");
+ if (session->state != H2_SESSION_ST_WAIT) {
+ break;
+ }
+ }
+ /* No IO happening and input is exhausted. Make sure we have
+ * flushed any possibly pending output and then wait with
+ * the c1 connection timeout for sth to happen in our c1/c2 sockets/pipes */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, c,
+ H2_SSSN_MSG(session, "polling timeout=%d, open_streams=%d"),
+ (int)apr_time_sec(session->s->timeout), session->open_streams);
+ status = h2_mplx_c1_poll(session->mplx, session->s->timeout,
+ on_stream_input, on_stream_output, session);
+ if (APR_STATUS_IS_TIMEUP(status)) {
+ if (session->open_streams == 0) {
+ h2_session_dispatch_event(session, H2_SESSION_EV_CONN_TIMEOUT, status, NULL);
+ break;
+ }
+ }
+ else if (APR_SUCCESS != status) {
+ h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, status, NULL);
+ break;
+ }
+ break;
+
+ case H2_SESSION_ST_DONE:
+ h2_c1_read(session);
+ break;
+
+ default:
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, c,
+ H2_SSSN_LOG(APLOGNO(03080), session,
+ "unknown state"));
+ h2_session_dispatch_event(session, H2_SESSION_EV_PROTO_ERROR, APR_EGENERAL, NULL);
+ break;
+ }
+ }
+
+leaving:
+ if (trace) {
+ ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c,
+ H2_SSSN_MSG(session, "process returns"));
+ }
+
+ if (session->state == H2_SESSION_ST_DONE) {
+ if (session->local.error) {
+ char buffer[128];
+ const char *msg;
+ if (session->local.error_msg) {
+ msg = session->local.error_msg;
+ }
+ else {
+ msg = apr_strerror(session->local.error, buffer, sizeof(buffer));
+ }
+ update_child_status(session, SERVER_CLOSING, msg, NULL);
+ }
+ else {
+ update_child_status(session, SERVER_CLOSING, "done", NULL);
+ }
+ }
+ else if (APR_STATUS_IS_EOF(status)
+ || APR_STATUS_IS_ECONNRESET(status)
+ || APR_STATUS_IS_ECONNABORTED(status)) {
+ h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, status, NULL);
+ update_child_status(session, SERVER_CLOSING, "error", NULL);
+ }
+
+ return (session->state == H2_SESSION_ST_DONE)? APR_EOF : APR_SUCCESS;
+}
+
+apr_status_t h2_session_pre_close(h2_session *session, int async)
+{
+ apr_status_t status;
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c1,
+ H2_SSSN_MSG(session, "pre_close"));
+ h2_session_dispatch_event(session, H2_SESSION_EV_PRE_CLOSE, 0,
+ (session->state == H2_SESSION_ST_IDLE)? "timeout" : NULL);
+ status = session_cleanup(session, "pre_close");
+ if (status == APR_SUCCESS) {
+ /* no one should hold a reference to this session any longer and
+ * the h2_conn_ctx_twas removed from the connection.
+ * Take the pool (and thus all subpools etc. down now, instead of
+ * during cleanup of main connection pool. */
+ apr_pool_destroy(session->pool);
+ }
+ return status;
+}
diff --git a/modules/http2/h2_session.h b/modules/http2/h2_session.h
new file mode 100644
index 0000000..fbddfdd
--- /dev/null
+++ b/modules/http2/h2_session.h
@@ -0,0 +1,205 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __mod_h2__h2_session__
+#define __mod_h2__h2_session__
+
+#include "h2_c1_io.h"
+
+/**
+ * A HTTP/2 connection, a session with a specific client.
+ *
+ * h2_session sits on top of a httpd conn_rec* instance and takes complete
+ * control of the connection data. It receives protocol frames from the
+ * client. For new HTTP/2 streams it creates secondary connections
+ * to execute the requests in h2 workers.
+ */
+
+#include "h2.h"
+
+struct apr_thread_mutext_t;
+struct apr_thread_cond_t;
+struct h2_ctx;
+struct h2_config;
+struct h2_ihash_t;
+struct h2_mplx;
+struct h2_priority;
+struct h2_push;
+struct h2_push_diary;
+struct h2_session;
+struct h2_stream;
+struct h2_stream_monitor;
+struct h2_workers;
+
+struct nghttp2_session;
+
+typedef enum {
+ H2_SESSION_EV_INIT, /* session was initialized */
+ H2_SESSION_EV_INPUT_PENDING, /* c1 input may have data pending */
+ H2_SESSION_EV_INPUT_EXHAUSTED, /* c1 input exhausted */
+ H2_SESSION_EV_LOCAL_GOAWAY, /* we send a GOAWAY */
+ H2_SESSION_EV_REMOTE_GOAWAY, /* remote send us a GOAWAY */
+ H2_SESSION_EV_CONN_ERROR, /* connection error */
+ H2_SESSION_EV_PROTO_ERROR, /* protocol error */
+ H2_SESSION_EV_CONN_TIMEOUT, /* connection timeout */
+ H2_SESSION_EV_NGH2_DONE, /* nghttp2 wants neither read nor write anything */
+ H2_SESSION_EV_MPM_STOPPING, /* the process is stopping */
+ H2_SESSION_EV_PRE_CLOSE, /* connection will close after this */
+ H2_SESSION_EV_NO_MORE_STREAMS, /* no more streams to process */
+} h2_session_event_t;
+
+typedef struct h2_session {
+ int child_num; /* child number this session runs in */
+ apr_uint32_t id; /* identifier of this session, unique per child */
+ conn_rec *c1; /* the main connection this session serves */
+ request_rec *r; /* the request that started this in case
+ * of 'h2c', NULL otherwise */
+ server_rec *s; /* server/vhost we're starting on */
+ apr_pool_t *pool; /* pool to use in session */
+ struct h2_mplx *mplx; /* multiplexer for stream data */
+ struct h2_workers *workers; /* for executing streams */
+ struct h2_c1_io_in_ctx_t *cin; /* connection input filter context */
+ h2_c1_io io; /* io on httpd conn filters */
+ unsigned int padding_max; /* max number of padding bytes */
+ int padding_always; /* padding has precedence over I/O optimizations */
+ struct nghttp2_session *ngh2; /* the nghttp2 session (internal use) */
+
+ h2_session_state state; /* state session is in */
+
+ h2_session_props local; /* properties of local session */
+ h2_session_props remote; /* properites of remote session */
+
+ unsigned int reprioritize : 1; /* scheduled streams priority changed */
+ unsigned int flush : 1; /* flushing output necessary */
+ apr_interval_time_t wait_us; /* timeout during BUSY_WAIT state, micro secs */
+
+ struct h2_push_diary *push_diary; /* remember pushes, avoid duplicates */
+
+ struct h2_stream_monitor *monitor;/* monitor callbacks for streams */
+ unsigned int open_streams; /* number of streams processing */
+
+ unsigned int streams_done; /* number of http/2 streams handled */
+ unsigned int responses_submitted; /* number of http/2 responses submitted */
+ unsigned int streams_reset; /* number of http/2 streams reset by client */
+ unsigned int pushes_promised; /* number of http/2 push promises submitted */
+ unsigned int pushes_submitted; /* number of http/2 pushed responses submitted */
+ unsigned int pushes_reset; /* number of http/2 pushed reset by client */
+
+ apr_size_t frames_received; /* number of http/2 frames received */
+ apr_size_t frames_sent; /* number of http/2 frames sent */
+
+ apr_size_t max_stream_count; /* max number of open streams */
+ apr_size_t max_stream_mem; /* max buffer memory for a single stream */
+
+ apr_size_t idle_frames; /* number of rcvd frames that kept session in idle state */
+ apr_interval_time_t idle_delay; /* Time we delay processing rcvd frames in idle state */
+
+ apr_bucket_brigade *bbtmp; /* brigade for keeping temporary data */
+
+ char status[64]; /* status message for scoreboard */
+ int last_status_code; /* the one already reported */
+ const char *last_status_msg; /* the one already reported */
+
+ int input_flushed; /* stream input was flushed */
+ struct h2_iqueue *out_c1_blocked; /* all streams with output blocked on c1 buffer full */
+ struct h2_iqueue *ready_to_process; /* all streams ready for processing */
+
+} h2_session;
+
+const char *h2_session_state_str(h2_session_state state);
+
+/**
+ * Create a new h2_session for the given connection.
+ * The session will apply the configured parameter.
+ * @param psession pointer receiving the created session on success or NULL
+ * @param c the connection to work on
+ * @param r optional request when protocol was upgraded
+ * @param cfg the module config to apply
+ * @param workers the worker pool to use
+ * @return the created session
+ */
+apr_status_t h2_session_create(h2_session **psession,
+ conn_rec *c, request_rec *r, server_rec *,
+ struct h2_workers *workers);
+
+void h2_session_event(h2_session *session, h2_session_event_t ev,
+ int err, const char *msg);
+
+/**
+ * Process the given HTTP/2 session until it is ended or a fatal
+ * error occurred.
+ *
+ * @param session the sessionm to process
+ */
+apr_status_t h2_session_process(h2_session *session, int async);
+
+/**
+ * Last chance to do anything before the connection is closed.
+ */
+apr_status_t h2_session_pre_close(h2_session *session, int async);
+
+/**
+ * Called when a serious error occurred and the session needs to terminate
+ * without further connection io.
+ * @param session the session to abort
+ * @param reason the apache status that caused the abort
+ */
+void h2_session_abort(h2_session *session, apr_status_t reason);
+
+/**
+ * Returns if client settings have push enabled.
+ * @param != 0 iff push is enabled in client settings
+ */
+int h2_session_push_enabled(h2_session *session);
+
+/**
+ * Submit a push promise on the stream and schedule the new steam for
+ * processing..
+ *
+ * @param session the session to work in
+ * @param is the stream initiating the push
+ * @param push the push to promise
+ * @return the new promised stream or NULL
+ */
+struct h2_stream *h2_session_push(h2_session *session,
+ struct h2_stream *is, struct h2_push *push);
+
+apr_status_t h2_session_set_prio(h2_session *session,
+ struct h2_stream *stream,
+ const struct h2_priority *prio);
+
+/**
+ * Dispatch a event happending during session processing.
+ * @param session the sessiont
+ * @param ev the event that happened
+ * @param arg integer argument (event type dependant)
+ * @param msg destriptive message
+ */
+void h2_session_dispatch_event(h2_session *session, h2_session_event_t ev,
+ int arg, const char *msg);
+
+
+#define H2_SSSN_MSG(s, msg) \
+ "h2_session(%d-%lu,%s,%d): "msg, s->child_num, (unsigned long)s->id, \
+ h2_session_state_str(s->state), \
+ s->open_streams
+
+#define H2_SSSN_LOG(aplogno, s, msg) aplogno H2_SSSN_MSG(s, msg)
+
+#define H2_SSSN_STRM_MSG(s, stream_id, msg) \
+ "h2_stream(%d-%lu-%d): "msg, s->child_num, (unsigned long)s->id, stream_id
+
+#endif /* defined(__mod_h2__h2_session__) */
diff --git a/modules/http2/h2_stream.c b/modules/http2/h2_stream.c
new file mode 100644
index 0000000..cf6f798
--- /dev/null
+++ b/modules/http2/h2_stream.c
@@ -0,0 +1,1712 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stddef.h>
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+#include "apr_strmatch.h"
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_connection.h>
+#include <http_log.h>
+#include <http_protocol.h>
+#include <http_ssl.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "h2_private.h"
+#include "h2.h"
+#include "h2_bucket_beam.h"
+#include "h2_c1.h"
+#include "h2_config.h"
+#include "h2_protocol.h"
+#include "h2_mplx.h"
+#include "h2_push.h"
+#include "h2_request.h"
+#include "h2_headers.h"
+#include "h2_session.h"
+#include "h2_stream.h"
+#include "h2_c2.h"
+#include "h2_conn_ctx.h"
+#include "h2_c2.h"
+#include "h2_util.h"
+
+
+static const char *h2_ss_str(const h2_stream_state_t state)
+{
+ switch (state) {
+ case H2_SS_IDLE:
+ return "IDLE";
+ case H2_SS_RSVD_L:
+ return "RESERVED_LOCAL";
+ case H2_SS_RSVD_R:
+ return "RESERVED_REMOTE";
+ case H2_SS_OPEN:
+ return "OPEN";
+ case H2_SS_CLOSED_L:
+ return "HALF_CLOSED_LOCAL";
+ case H2_SS_CLOSED_R:
+ return "HALF_CLOSED_REMOTE";
+ case H2_SS_CLOSED:
+ return "CLOSED";
+ case H2_SS_CLEANUP:
+ return "CLEANUP";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+const char *h2_stream_state_str(const h2_stream *stream)
+{
+ return h2_ss_str(stream->state);
+}
+
+/* Abbreviations for stream transit tables */
+#define S_XXX (-2) /* Programming Error */
+#define S_ERR (-1) /* Protocol Error */
+#define S_NOP (0) /* No Change */
+#define S_IDL (H2_SS_IDL + 1)
+#define S_RS_L (H2_SS_RSVD_L + 1)
+#define S_RS_R (H2_SS_RSVD_R + 1)
+#define S_OPEN (H2_SS_OPEN + 1)
+#define S_CL_L (H2_SS_CLOSED_L + 1)
+#define S_CL_R (H2_SS_CLOSED_R + 1)
+#define S_CLS (H2_SS_CLOSED + 1)
+#define S_CLN (H2_SS_CLEANUP + 1)
+
+/* state transisitions when certain frame types are sent */
+static int trans_on_send[][H2_SS_MAX] = {
+/*S_IDLE,S_RS_R, S_RS_L, S_OPEN, S_CL_R, S_CL_L, S_CLS, S_CLN, */
+{ S_ERR, S_ERR, S_ERR, S_NOP, S_NOP, S_ERR, S_NOP, S_NOP, },/* DATA */
+{ S_ERR, S_ERR, S_CL_R, S_NOP, S_NOP, S_ERR, S_NOP, S_NOP, },/* HEADERS */
+{ S_NOP, S_NOP, S_NOP, S_NOP, S_NOP, S_NOP, S_NOP, S_NOP, },/* PRIORITY */
+{ S_CLS, S_CLS, S_CLS, S_CLS, S_CLS, S_CLS, S_NOP, S_NOP, },/* RST_STREAM */
+{ S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, },/* SETTINGS */
+{ S_RS_L,S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, },/* PUSH_PROMISE */
+{ S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, },/* PING */
+{ S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, },/* GOAWAY */
+{ S_NOP, S_NOP, S_NOP, S_NOP, S_NOP, S_NOP, S_NOP, S_NOP, },/* WINDOW_UPDATE */
+{ S_NOP, S_NOP, S_NOP, S_NOP, S_NOP, S_NOP, S_NOP, S_NOP, },/* CONT */
+};
+/* state transisitions when certain frame types are received */
+static int trans_on_recv[][H2_SS_MAX] = {
+/*S_IDLE,S_RS_R, S_RS_L, S_OPEN, S_CL_R, S_CL_L, S_CLS, S_CLN, */
+{ S_ERR, S_ERR, S_ERR, S_NOP, S_ERR, S_NOP, S_NOP, S_NOP, },/* DATA */
+{ S_OPEN,S_CL_L, S_ERR, S_NOP, S_ERR, S_NOP, S_NOP, S_NOP, },/* HEADERS */
+{ S_NOP, S_NOP, S_NOP, S_NOP, S_NOP, S_NOP, S_NOP, S_NOP, },/* PRIORITY */
+{ S_ERR, S_CLS, S_CLS, S_CLS, S_CLS, S_CLS, S_NOP, S_NOP, },/* RST_STREAM */
+{ S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, },/* SETTINGS */
+{ S_RS_R,S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, },/* PUSH_PROMISE */
+{ S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, },/* PING */
+{ S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, },/* GOAWAY */
+{ S_NOP, S_NOP, S_NOP, S_NOP, S_NOP, S_NOP, S_NOP, S_NOP, },/* WINDOW_UPDATE */
+{ S_NOP, S_NOP, S_NOP, S_NOP, S_NOP, S_NOP, S_NOP, S_NOP, },/* CONT */
+};
+/* state transisitions when certain events happen */
+static int trans_on_event[][H2_SS_MAX] = {
+/*S_IDLE,S_RS_R, S_RS_L, S_OPEN, S_CL_R, S_CL_L, S_CLS, S_CLN, */
+{ S_XXX, S_ERR, S_ERR, S_CL_L, S_CLS, S_XXX, S_XXX, S_XXX, },/* EV_CLOSED_L*/
+{ S_ERR, S_ERR, S_ERR, S_CL_R, S_ERR, S_CLS, S_NOP, S_NOP, },/* EV_CLOSED_R*/
+{ S_CLS, S_CLS, S_CLS, S_CLS, S_CLS, S_CLS, S_NOP, S_NOP, },/* EV_CANCELLED*/
+{ S_NOP, S_XXX, S_XXX, S_XXX, S_XXX, S_CLS, S_CLN, S_XXX, },/* EV_EOS_SENT*/
+{ S_NOP, S_XXX, S_CLS, S_XXX, S_XXX, S_CLS, S_XXX, S_XXX, },/* EV_IN_ERROR*/
+};
+
+static int on_map(h2_stream_state_t state, int map[H2_SS_MAX])
+{
+ int op = map[state];
+ switch (op) {
+ case S_XXX:
+ case S_ERR:
+ return op;
+ case S_NOP:
+ return state;
+ default:
+ return op-1;
+ }
+}
+
+static int on_frame(h2_stream_state_t state, int frame_type,
+ int frame_map[][H2_SS_MAX], apr_size_t maxlen)
+{
+ ap_assert(frame_type >= 0);
+ ap_assert(state >= 0);
+ if ((apr_size_t)frame_type >= maxlen) {
+ return state; /* NOP, ignore unknown frame types */
+ }
+ return on_map(state, frame_map[frame_type]);
+}
+
+static int on_frame_send(h2_stream_state_t state, int frame_type)
+{
+ return on_frame(state, frame_type, trans_on_send, H2_ALEN(trans_on_send));
+}
+
+static int on_frame_recv(h2_stream_state_t state, int frame_type)
+{
+ return on_frame(state, frame_type, trans_on_recv, H2_ALEN(trans_on_recv));
+}
+
+static int on_event(h2_stream* stream, h2_stream_event_t ev)
+{
+ if (stream->monitor && stream->monitor->on_event) {
+ stream->monitor->on_event(stream->monitor->ctx, stream, ev);
+ }
+ if (ev < H2_ALEN(trans_on_event)) {
+ return on_map(stream->state, trans_on_event[ev]);
+ }
+ return stream->state;
+}
+
+static ssize_t stream_data_cb(nghttp2_session *ng2s,
+ int32_t stream_id,
+ uint8_t *buf,
+ size_t length,
+ uint32_t *data_flags,
+ nghttp2_data_source *source,
+ void *puser);
+
+static void H2_STREAM_OUT_LOG(int lvl, h2_stream *s, const char *tag)
+{
+ if (APLOG_C_IS_LEVEL(s->session->c1, lvl)) {
+ conn_rec *c = s->session->c1;
+ char buffer[4 * 1024];
+ apr_size_t len, bmax = sizeof(buffer)/sizeof(buffer[0]);
+
+ len = h2_util_bb_print(buffer, bmax, tag, "", s->out_buffer);
+ ap_log_cerror(APLOG_MARK, lvl, 0, c,
+ H2_STRM_MSG(s, "out-buffer(%s)"), len? buffer : "empty");
+ }
+}
+
+static void stream_setup_input(h2_stream *stream)
+{
+ if (stream->input != NULL) return;
+ ap_assert(!stream->input_closed);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c1,
+ H2_STRM_MSG(stream, "setup input beam"));
+ h2_beam_create(&stream->input, stream->session->c1,
+ stream->pool, stream->id,
+ "input", 0, stream->session->s->timeout);
+}
+
+apr_status_t h2_stream_prepare_processing(h2_stream *stream)
+{
+ /* Right before processing starts, last chance to decide if
+ * there is need to an input beam. */
+ if (!stream->input_closed) {
+ stream_setup_input(stream);
+ }
+ return APR_SUCCESS;
+}
+
+static int input_buffer_is_empty(h2_stream *stream)
+{
+ return !stream->in_buffer || APR_BRIGADE_EMPTY(stream->in_buffer);
+}
+
+static apr_status_t input_flush(h2_stream *stream)
+{
+ apr_status_t status = APR_SUCCESS;
+ apr_off_t written;
+
+ if (input_buffer_is_empty(stream)) goto cleanup;
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c1,
+ H2_STRM_MSG(stream, "flush input"));
+ status = h2_beam_send(stream->input, stream->session->c1,
+ stream->in_buffer, APR_BLOCK_READ, &written);
+ stream->in_last_write = apr_time_now();
+ if (APR_SUCCESS != status && h2_stream_is_at(stream, H2_SS_CLOSED_L)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, stream->session->c1,
+ H2_STRM_MSG(stream, "send input error"));
+ h2_stream_dispatch(stream, H2_SEV_IN_ERROR);
+ }
+cleanup:
+ return status;
+}
+
+static void input_append_bucket(h2_stream *stream, apr_bucket *b)
+{
+ if (!stream->in_buffer) {
+ stream_setup_input(stream);
+ stream->in_buffer = apr_brigade_create(
+ stream->pool, stream->session->c1->bucket_alloc);
+ }
+ APR_BRIGADE_INSERT_TAIL(stream->in_buffer, b);
+}
+
+static void input_append_data(h2_stream *stream, const char *data, apr_size_t len)
+{
+ if (!stream->in_buffer) {
+ stream_setup_input(stream);
+ stream->in_buffer = apr_brigade_create(
+ stream->pool, stream->session->c1->bucket_alloc);
+ }
+ apr_brigade_write(stream->in_buffer, NULL, NULL, data, len);
+}
+
+
+static apr_status_t close_input(h2_stream *stream)
+{
+ conn_rec *c = stream->session->c1;
+ apr_status_t rv = APR_SUCCESS;
+ apr_bucket *b;
+
+ if (stream->input_closed) goto cleanup;
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c1,
+ H2_STRM_MSG(stream, "closing input"));
+ if (!stream->rst_error
+ && stream->trailers_in
+ && !apr_is_empty_table(stream->trailers_in)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c1,
+ H2_STRM_MSG(stream, "adding trailers"));
+#if AP_HAS_RESPONSE_BUCKETS
+ b = ap_bucket_headers_create(stream->trailers_in,
+ stream->pool, c->bucket_alloc);
+#else
+ b = h2_bucket_headers_create(c->bucket_alloc,
+ h2_headers_create(HTTP_OK, stream->trailers_in, NULL,
+ stream->in_trailer_octets, stream->pool));
+#endif
+ input_append_bucket(stream, b);
+ stream->trailers_in = NULL;
+ }
+
+ stream->input_closed = 1;
+ if (stream->input) {
+ b = apr_bucket_eos_create(c->bucket_alloc);
+ input_append_bucket(stream, b);
+ input_flush(stream);
+ h2_stream_dispatch(stream, H2_SEV_IN_DATA_PENDING);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c1,
+ H2_STRM_MSG(stream, "input flush + EOS"));
+ }
+
+cleanup:
+ return rv;
+}
+
+static void on_state_enter(h2_stream *stream)
+{
+ if (stream->monitor && stream->monitor->on_state_enter) {
+ stream->monitor->on_state_enter(stream->monitor->ctx, stream);
+ }
+}
+
+static void on_state_event(h2_stream *stream, h2_stream_event_t ev)
+{
+ if (stream->monitor && stream->monitor->on_state_event) {
+ stream->monitor->on_state_event(stream->monitor->ctx, stream, ev);
+ }
+}
+
+static void on_state_invalid(h2_stream *stream)
+{
+ if (stream->monitor && stream->monitor->on_state_invalid) {
+ stream->monitor->on_state_invalid(stream->monitor->ctx, stream);
+ }
+ /* stream got an event/frame invalid in its state */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c1,
+ H2_STRM_MSG(stream, "invalid state event"));
+ switch (stream->state) {
+ case H2_SS_OPEN:
+ case H2_SS_RSVD_L:
+ case H2_SS_RSVD_R:
+ case H2_SS_CLOSED_L:
+ case H2_SS_CLOSED_R:
+ h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
+ break;
+ default:
+ break;
+ }
+}
+
+static apr_status_t transit(h2_stream *stream, int new_state)
+{
+ if ((h2_stream_state_t)new_state == stream->state) {
+ return APR_SUCCESS;
+ }
+ else if (new_state < 0) {
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, stream->session->c1,
+ H2_STRM_LOG(APLOGNO(03081), stream, "invalid transition"));
+ on_state_invalid(stream);
+ return APR_EINVAL;
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c1,
+ H2_STRM_MSG(stream, "transit to [%s]"), h2_ss_str(new_state));
+ stream->state = new_state;
+ switch (new_state) {
+ case H2_SS_IDLE:
+ break;
+ case H2_SS_RSVD_L:
+ close_input(stream);
+ break;
+ case H2_SS_RSVD_R:
+ break;
+ case H2_SS_OPEN:
+ break;
+ case H2_SS_CLOSED_L:
+ break;
+ case H2_SS_CLOSED_R:
+ close_input(stream);
+ break;
+ case H2_SS_CLOSED:
+ close_input(stream);
+ if (stream->out_buffer) {
+ apr_brigade_cleanup(stream->out_buffer);
+ }
+ break;
+ case H2_SS_CLEANUP:
+ break;
+ }
+ on_state_enter(stream);
+ return APR_SUCCESS;
+}
+
+void h2_stream_set_monitor(h2_stream *stream, h2_stream_monitor *monitor)
+{
+ stream->monitor = monitor;
+}
+
+void h2_stream_dispatch(h2_stream *stream, h2_stream_event_t ev)
+{
+ int new_state;
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c1,
+ H2_STRM_MSG(stream, "dispatch event %d"), ev);
+ new_state = on_event(stream, ev);
+ if (new_state < 0) {
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, stream->session->c1,
+ H2_STRM_LOG(APLOGNO(10002), stream, "invalid event %d"), ev);
+ on_state_invalid(stream);
+ AP_DEBUG_ASSERT(new_state > S_XXX);
+ return;
+ }
+ else if ((h2_stream_state_t)new_state == stream->state) {
+ /* nop */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c1,
+ H2_STRM_MSG(stream, "non-state event %d"), ev);
+ return;
+ }
+ else {
+ on_state_event(stream, ev);
+ transit(stream, new_state);
+ }
+}
+
+static void set_policy_for(h2_stream *stream, h2_request *r)
+{
+ int enabled = h2_session_push_enabled(stream->session);
+ stream->push_policy = h2_push_policy_determine(r->headers, stream->pool, enabled);
+}
+
+apr_status_t h2_stream_send_frame(h2_stream *stream, int ftype, int flags, size_t frame_len)
+{
+ apr_status_t status = APR_SUCCESS;
+ int new_state, eos = 0;
+
+ new_state = on_frame_send(stream->state, ftype);
+ if (new_state < 0) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c1,
+ H2_STRM_MSG(stream, "invalid frame %d send"), ftype);
+ AP_DEBUG_ASSERT(new_state > S_XXX);
+ return transit(stream, new_state);
+ }
+
+ ++stream->out_frames;
+ stream->out_frame_octets += frame_len;
+ switch (ftype) {
+ case NGHTTP2_DATA:
+ eos = (flags & NGHTTP2_FLAG_END_STREAM);
+ break;
+
+ case NGHTTP2_HEADERS:
+ eos = (flags & NGHTTP2_FLAG_END_STREAM);
+ break;
+
+ case NGHTTP2_PUSH_PROMISE:
+ /* start pushed stream */
+ ap_assert(stream->request == NULL);
+ ap_assert(stream->rtmp != NULL);
+ status = h2_stream_end_headers(stream, 1, 0);
+ if (status != APR_SUCCESS) goto leave;
+ break;
+
+ default:
+ break;
+ }
+ status = transit(stream, new_state);
+ if (status == APR_SUCCESS && eos) {
+ status = transit(stream, on_event(stream, H2_SEV_CLOSED_L));
+ }
+leave:
+ return status;
+}
+
+apr_status_t h2_stream_recv_frame(h2_stream *stream, int ftype, int flags, size_t frame_len)
+{
+ apr_status_t status = APR_SUCCESS;
+ int new_state, eos = 0;
+
+ new_state = on_frame_recv(stream->state, ftype);
+ if (new_state < 0) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c1,
+ H2_STRM_MSG(stream, "invalid frame %d recv"), ftype);
+ AP_DEBUG_ASSERT(new_state > S_XXX);
+ return transit(stream, new_state);
+ }
+
+ switch (ftype) {
+ case NGHTTP2_DATA:
+ eos = (flags & NGHTTP2_FLAG_END_STREAM);
+ break;
+
+ case NGHTTP2_HEADERS:
+ eos = (flags & NGHTTP2_FLAG_END_STREAM);
+ if (h2_stream_is_at_or_past(stream, H2_SS_OPEN)) {
+ /* trailer HEADER */
+ if (!eos) {
+ h2_stream_rst(stream, H2_ERR_PROTOCOL_ERROR);
+ }
+ stream->in_trailer_octets += frame_len;
+ }
+ else {
+ /* request HEADER */
+ ap_assert(stream->request == NULL);
+ if (stream->rtmp == NULL) {
+ /* This can only happen, if the stream has received no header
+ * name/value pairs at all. The latest nghttp2 version have become
+ * pretty good at detecting this early. In any case, we have
+ * to abort the connection here, since this is clearly a protocol error */
+ return APR_EINVAL;
+ }
+ status = h2_stream_end_headers(stream, eos, frame_len);
+ if (status != APR_SUCCESS) goto leave;
+ }
+ break;
+
+ default:
+ break;
+ }
+ status = transit(stream, new_state);
+ if (status == APR_SUCCESS && eos) {
+ status = transit(stream, on_event(stream, H2_SEV_CLOSED_R));
+ }
+leave:
+ return status;
+}
+
+apr_status_t h2_stream_recv_DATA(h2_stream *stream, uint8_t flags,
+ const uint8_t *data, size_t len)
+{
+ h2_session *session = stream->session;
+ apr_status_t status = APR_SUCCESS;
+
+ stream->in_data_frames++;
+ if (len > 0) {
+ if (APLOGctrace3(session->c1)) {
+ const char *load = apr_pstrndup(stream->pool, (const char *)data, len);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, session->c1,
+ H2_STRM_MSG(stream, "recv DATA, len=%d: -->%s<--"),
+ (int)len, load);
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, session->c1,
+ H2_STRM_MSG(stream, "recv DATA, len=%d"), (int)len);
+ }
+ stream->in_data_octets += len;
+ input_append_data(stream, (const char*)data, len);
+ input_flush(stream);
+ h2_stream_dispatch(stream, H2_SEV_IN_DATA_PENDING);
+ }
+ return status;
+}
+
+h2_stream *h2_stream_create(int id, apr_pool_t *pool, h2_session *session,
+ h2_stream_monitor *monitor, int initiated_on)
+{
+ h2_stream *stream = apr_pcalloc(pool, sizeof(h2_stream));
+
+ stream->id = id;
+ stream->initiated_on = initiated_on;
+ stream->created = apr_time_now();
+ stream->state = H2_SS_IDLE;
+ stream->pool = pool;
+ stream->session = session;
+ stream->monitor = monitor;
+
+#ifdef H2_NG2_LOCAL_WIN_SIZE
+ if (id) {
+ stream->in_window_size =
+ nghttp2_session_get_stream_local_window_size(
+ stream->session->ngh2, stream->id);
+ }
+#endif
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+ H2_STRM_LOG(APLOGNO(03082), stream, "created"));
+ on_state_enter(stream);
+ return stream;
+}
+
+void h2_stream_cleanup(h2_stream *stream)
+{
+ /* Stream is done on c1. There might still be processing on a c2
+ * going on. The input/output beams get aborted and the stream's
+ * end of the in/out notifications get closed.
+ */
+ ap_assert(stream);
+ if (stream->out_buffer) {
+ apr_brigade_cleanup(stream->out_buffer);
+ }
+}
+
+void h2_stream_destroy(h2_stream *stream)
+{
+ ap_assert(stream);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, stream->session->c1,
+ H2_STRM_MSG(stream, "destroy"));
+ apr_pool_destroy(stream->pool);
+}
+
+void h2_stream_rst(h2_stream *stream, int error_code)
+{
+ stream->rst_error = error_code;
+ if (stream->c2) {
+ h2_c2_abort(stream->c2, stream->session->c1);
+ }
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c1,
+ H2_STRM_MSG(stream, "reset, error=%d"), error_code);
+ h2_stream_dispatch(stream, H2_SEV_CANCELLED);
+}
+
+apr_status_t h2_stream_set_request_rec(h2_stream *stream,
+ request_rec *r, int eos)
+{
+ h2_request *req;
+ apr_status_t status;
+
+ ap_assert(stream->request == NULL);
+ ap_assert(stream->rtmp == NULL);
+ if (stream->rst_error) {
+ return APR_ECONNRESET;
+ }
+ status = h2_request_rcreate(&req, stream->pool, r);
+ if (status == APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
+ H2_STRM_LOG(APLOGNO(03058), stream,
+ "set_request_rec %s host=%s://%s%s"),
+ req->method, req->scheme, req->authority, req->path);
+ stream->rtmp = req;
+ /* simulate the frames that led to this */
+ return h2_stream_recv_frame(stream, NGHTTP2_HEADERS,
+ NGHTTP2_FLAG_END_STREAM, 0);
+ }
+ return status;
+}
+
+void h2_stream_set_request(h2_stream *stream, const h2_request *r)
+{
+ ap_assert(stream->request == NULL);
+ ap_assert(stream->rtmp == NULL);
+ stream->rtmp = h2_request_clone(stream->pool, r);
+}
+
+static void set_error_response(h2_stream *stream, int http_status)
+{
+ if (!h2_stream_is_ready(stream) && stream->rtmp) {
+ stream->rtmp->http_status = http_status;
+ }
+}
+
+static apr_status_t add_trailer(h2_stream *stream,
+ const char *name, size_t nlen,
+ const char *value, size_t vlen,
+ size_t max_field_len, int *pwas_added)
+{
+ conn_rec *c = stream->session->c1;
+ char *hname, *hvalue;
+ const char *existing;
+
+ *pwas_added = 0;
+ if (nlen == 0 || name[0] == ':') {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_EINVAL, c,
+ H2_STRM_LOG(APLOGNO(03060), stream,
+ "pseudo header in trailer"));
+ return APR_EINVAL;
+ }
+ if (h2_ignore_req_trailer(name, nlen)) {
+ return APR_SUCCESS;
+ }
+ if (!stream->trailers_in) {
+ stream->trailers_in = apr_table_make(stream->pool, 5);
+ }
+ hname = apr_pstrndup(stream->pool, name, nlen);
+ h2_util_camel_case_header(hname, nlen);
+ existing = apr_table_get(stream->trailers_in, hname);
+ if (max_field_len
+ && ((existing? strlen(existing)+2 : 0) + vlen + nlen + 2 > max_field_len)) {
+ /* "key: (oldval, )?nval" is too long */
+ return APR_EINVAL;
+ }
+ if (!existing) *pwas_added = 1;
+ hvalue = apr_pstrndup(stream->pool, value, vlen);
+ apr_table_mergen(stream->trailers_in, hname, hvalue);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
+ H2_STRM_MSG(stream, "added trailer '%s: %s'"), hname, hvalue);
+
+ return APR_SUCCESS;
+}
+
+apr_status_t h2_stream_add_header(h2_stream *stream,
+ const char *name, size_t nlen,
+ const char *value, size_t vlen)
+{
+ h2_session *session = stream->session;
+ int error = 0, was_added = 0;
+ apr_status_t status = APR_SUCCESS;
+
+ if (stream->response) {
+ return APR_EINVAL;
+ }
+
+ if (name[0] == ':') {
+ if (vlen > APR_INT32_MAX || (int)vlen > session->s->limit_req_line) {
+ /* pseudo header: approximation of request line size check */
+ if (!h2_stream_is_ready(stream)) {
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, session->c1,
+ H2_STRM_LOG(APLOGNO(10178), stream,
+ "Request pseudo header exceeds "
+ "LimitRequestFieldSize: %s"), name);
+ }
+ error = HTTP_REQUEST_URI_TOO_LARGE;
+ goto cleanup;
+ }
+ }
+
+ if (session->s->limit_req_fields > 0
+ && stream->request_headers_added > session->s->limit_req_fields) {
+ /* already over limit, count this attempt, but do not take it in */
+ ++stream->request_headers_added;
+ }
+ else if (H2_SS_IDLE == stream->state) {
+ if (!stream->rtmp) {
+ stream->rtmp = h2_request_create(stream->id, stream->pool,
+ NULL, NULL, NULL, NULL, NULL);
+ }
+ status = h2_request_add_header(stream->rtmp, stream->pool,
+ name, nlen, value, vlen,
+ session->s->limit_req_fieldsize, &was_added);
+ if (was_added) ++stream->request_headers_added;
+ }
+ else if (H2_SS_OPEN == stream->state) {
+ status = add_trailer(stream, name, nlen, value, vlen,
+ session->s->limit_req_fieldsize, &was_added);
+ if (was_added) ++stream->request_headers_added;
+ }
+ else {
+ status = APR_EINVAL;
+ goto cleanup;
+ }
+
+ if (APR_EINVAL == status) {
+ /* header too long */
+ if (!h2_stream_is_ready(stream)) {
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, session->c1,
+ H2_STRM_LOG(APLOGNO(10180), stream,"Request header exceeds "
+ "LimitRequestFieldSize: %.*s"),
+ (int)H2MIN(nlen, 80), name);
+ }
+ error = HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE;
+ goto cleanup;
+ }
+
+ if (session->s->limit_req_fields > 0
+ && stream->request_headers_added > session->s->limit_req_fields) {
+ /* too many header lines */
+ if (stream->request_headers_added > session->s->limit_req_fields + 100) {
+ /* yeah, right, this request is way over the limit, say goodbye */
+ h2_stream_rst(stream, H2_ERR_ENHANCE_YOUR_CALM);
+ return APR_ECONNRESET;
+ }
+ if (!h2_stream_is_ready(stream)) {
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, session->c1,
+ H2_STRM_LOG(APLOGNO(10181), stream, "Number of request headers "
+ "exceeds LimitRequestFields"));
+ }
+ error = HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE;
+ goto cleanup;
+ }
+
+cleanup:
+ if (error) {
+ set_error_response(stream, error);
+ return APR_EINVAL;
+ }
+ else if (status != APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c1,
+ H2_STRM_MSG(stream, "header %s not accepted"), name);
+ h2_stream_dispatch(stream, H2_SEV_CANCELLED);
+ }
+ return status;
+}
+
+typedef struct {
+ apr_size_t maxlen;
+ const char *failed_key;
+} val_len_check_ctx;
+
+static int table_check_val_len(void *baton, const char *key, const char *value)
+{
+ val_len_check_ctx *ctx = baton;
+
+ if (strlen(value) <= ctx->maxlen) return 1;
+ ctx->failed_key = key;
+ return 0;
+}
+
+apr_status_t h2_stream_end_headers(h2_stream *stream, int eos, size_t raw_bytes)
+{
+ apr_status_t status;
+ val_len_check_ctx ctx;
+ int is_http_or_https;
+ h2_request *req = stream->rtmp;
+
+ status = h2_request_end_headers(req, stream->pool, raw_bytes);
+ if (APR_SUCCESS != status || req->http_status != H2_HTTP_STATUS_UNSET) {
+ goto cleanup;
+ }
+
+ /* keep on returning APR_SUCCESS for error responses, so that we
+ * send it and do not RST the stream.
+ */
+ set_policy_for(stream, req);
+
+ ctx.maxlen = stream->session->s->limit_req_fieldsize;
+ ctx.failed_key = NULL;
+ apr_table_do(table_check_val_len, &ctx, req->headers, NULL);
+ if (ctx.failed_key) {
+ if (!h2_stream_is_ready(stream)) {
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, stream->session->c1,
+ H2_STRM_LOG(APLOGNO(10230), stream,"Request header exceeds "
+ "LimitRequestFieldSize: %.*s"),
+ (int)H2MIN(strlen(ctx.failed_key), 80), ctx.failed_key);
+ }
+ set_error_response(stream, HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE);
+ goto cleanup;
+ }
+
+ /* http(s) scheme. rfc7540, ch. 8.1.2.3:
+ * This [:path] pseudo-header field MUST NOT be empty for "http" or "https"
+ * URIs; "http" or "https" URIs that do not contain a path component
+ * MUST include a value of '/'. The exception to this rule is an
+ * OPTIONS request for an "http" or "https" URI that does not include
+ * a path component; these MUST include a ":path" pseudo-header field
+ * with a value of '*'
+ *
+ * All HTTP/2 requests MUST include exactly one valid value for the
+ * ":method", ":scheme", and ":path" pseudo-header fields, unless it is
+ * a CONNECT request.
+ */
+ is_http_or_https = (!req->scheme
+ || !(ap_cstr_casecmpn(req->scheme, "http", 4) != 0
+ || (req->scheme[4] != '\0'
+ && (apr_tolower(req->scheme[4]) != 's'
+ || req->scheme[5] != '\0'))));
+
+ /* CONNECT. rfc7540, ch. 8.3:
+ * In HTTP/2, the CONNECT method is used to establish a tunnel over a
+ * single HTTP/2 stream to a remote host for similar purposes. The HTTP
+ * header field mapping works as defined in Section 8.1.2.3 ("Request
+ * Pseudo-Header Fields"), with a few differences. Specifically:
+ * o The ":method" pseudo-header field is set to "CONNECT".
+ * o The ":scheme" and ":path" pseudo-header fields MUST be omitted.
+ * o The ":authority" pseudo-header field contains the host and port to
+ * connect to (equivalent to the authority-form of the request-target
+ * of CONNECT requests (see [RFC7230], Section 5.3)).
+ */
+ if (!ap_cstr_casecmp(req->method, "CONNECT")) {
+ if (req->scheme || req->path) {
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, stream->session->c1,
+ H2_STRM_LOG(APLOGNO(10384), stream, "Request to CONNECT "
+ "with :scheme or :path specified, sending 400 answer"));
+ set_error_response(stream, HTTP_BAD_REQUEST);
+ goto cleanup;
+ }
+ }
+ else if (is_http_or_https) {
+ if (!req->path) {
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, stream->session->c1,
+ H2_STRM_LOG(APLOGNO(10385), stream, "Request for http(s) "
+ "resource without :path, sending 400 answer"));
+ set_error_response(stream, HTTP_BAD_REQUEST);
+ goto cleanup;
+ }
+ if (!req->scheme) {
+ req->scheme = ap_ssl_conn_is_ssl(stream->session->c1)? "https" : "http";
+ }
+ }
+
+ if (req->scheme && (req->path && req->path[0] != '/')) {
+ /* We still have a scheme, which means we need to pass an absolute URI into
+ * our HTTP protocol handling and the missing '/' at the start will prevent
+ * us from doing so (as it then confuses path and authority). */
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, stream->session->c1,
+ H2_STRM_LOG(APLOGNO(10379), stream, "Request :scheme '%s' and "
+ "path '%s' do not allow creating an absolute URL. Failing "
+ "request with 400."), req->scheme, req->path);
+ set_error_response(stream, HTTP_BAD_REQUEST);
+ goto cleanup;
+ }
+
+cleanup:
+ if (APR_SUCCESS == status) {
+ stream->request = req;
+ stream->rtmp = NULL;
+
+ if (APLOGctrace4(stream->session->c1)) {
+ int i;
+ const apr_array_header_t *t_h = apr_table_elts(req->headers);
+ const apr_table_entry_t *t_elt = (apr_table_entry_t *)t_h->elts;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, stream->session->c1,
+ H2_STRM_MSG(stream,"headers received from client:"));
+ for (i = 0; i < t_h->nelts; i++, t_elt++) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, stream->session->c1,
+ H2_STRM_MSG(stream, " %s: %s"),
+ ap_escape_logitem(stream->pool, t_elt->key),
+ ap_escape_logitem(stream->pool, t_elt->val));
+ }
+ }
+ }
+ return status;
+}
+
+static apr_bucket *get_first_response_bucket(apr_bucket_brigade *bb)
+{
+ if (bb) {
+ apr_bucket *b = APR_BRIGADE_FIRST(bb);
+ while (b != APR_BRIGADE_SENTINEL(bb)) {
+#if AP_HAS_RESPONSE_BUCKETS
+ if (AP_BUCKET_IS_RESPONSE(b)) {
+ return b;
+ }
+#else
+ if (H2_BUCKET_IS_HEADERS(b)) {
+ return b;
+ }
+#endif
+ b = APR_BUCKET_NEXT(b);
+ }
+ }
+ return NULL;
+}
+
+static void stream_do_error_bucket(h2_stream *stream, apr_bucket *b)
+{
+ int err = ((ap_bucket_error *)(b->data))->status;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c1,
+ H2_STRM_MSG(stream, "error bucket received, err=%d"), err);
+ if (err >= 500) {
+ err = NGHTTP2_INTERNAL_ERROR;
+ }
+ else if (err >= 400) {
+ err = NGHTTP2_STREAM_CLOSED;
+ }
+ else {
+ err = NGHTTP2_PROTOCOL_ERROR;
+ }
+ h2_stream_rst(stream, err);
+}
+
+static apr_status_t buffer_output_receive(h2_stream *stream)
+{
+ apr_status_t rv = APR_EAGAIN;
+ apr_off_t buf_len;
+ conn_rec *c1 = stream->session->c1;
+ apr_bucket *b, *e;
+
+ if (!stream->output) {
+ goto cleanup;
+ }
+ if (stream->rst_error) {
+ rv = APR_ECONNRESET;
+ goto cleanup;
+ }
+
+ if (!stream->out_buffer) {
+ stream->out_buffer = apr_brigade_create(stream->pool, c1->bucket_alloc);
+ buf_len = 0;
+ }
+ else {
+ /* if the brigade contains a file bucket, its normal report length
+ * might be megabytes, but the memory used is tiny. For buffering,
+ * we are only interested in the memory footprint. */
+ buf_len = h2_brigade_mem_size(stream->out_buffer);
+ }
+
+ if (buf_len > APR_INT32_MAX
+ || (apr_size_t)buf_len >= stream->session->max_stream_mem) {
+ /* we have buffered enough. No need to read more.
+ * However, we have now output pending for which we may not
+ * receive another poll event. We need to make sure that this
+ * stream is not suspended so we keep on processing output.
+ */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c1,
+ H2_STRM_MSG(stream, "out_buffer, already has %ld length"),
+ (long)buf_len);
+ rv = APR_SUCCESS;
+ goto cleanup;
+ }
+
+ if (stream->output_eos) {
+ rv = APR_BRIGADE_EMPTY(stream->out_buffer)? APR_EOF : APR_SUCCESS;
+ }
+ else {
+ H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "pre");
+ rv = h2_beam_receive(stream->output, stream->session->c1, stream->out_buffer,
+ APR_NONBLOCK_READ, stream->session->max_stream_mem - buf_len);
+ if (APR_SUCCESS != rv) {
+ if (APR_EAGAIN != rv) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c1,
+ H2_STRM_MSG(stream, "out_buffer, receive unsuccessful"));
+ }
+ }
+ }
+
+ /* get rid of buckets we have no need for */
+ if (!APR_BRIGADE_EMPTY(stream->out_buffer)) {
+ b = APR_BRIGADE_FIRST(stream->out_buffer);
+ while (b != APR_BRIGADE_SENTINEL(stream->out_buffer)) {
+ e = APR_BUCKET_NEXT(b);
+ if (APR_BUCKET_IS_METADATA(b)) {
+ if (APR_BUCKET_IS_FLUSH(b)) { /* we flush any c1 data already */
+ APR_BUCKET_REMOVE(b);
+ apr_bucket_destroy(b);
+ }
+ else if (APR_BUCKET_IS_EOS(b)) {
+ stream->output_eos = 1;
+ }
+ else if (AP_BUCKET_IS_ERROR(b)) {
+ stream_do_error_bucket(stream, b);
+ break;
+ }
+ }
+ else if (b->length == 0) { /* zero length data */
+ APR_BUCKET_REMOVE(b);
+ apr_bucket_destroy(b);
+ }
+ b = e;
+ }
+ }
+ H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "out_buffer, after receive");
+
+cleanup:
+ return rv;
+}
+
+static int bucket_pass_to_c1(apr_bucket *b)
+{
+#if AP_HAS_RESPONSE_BUCKETS
+ return !AP_BUCKET_IS_RESPONSE(b)
+ && !AP_BUCKET_IS_HEADERS(b)
+ && !APR_BUCKET_IS_EOS(b);
+#else
+ return !H2_BUCKET_IS_HEADERS(b) && !APR_BUCKET_IS_EOS(b);
+#endif
+}
+
+apr_status_t h2_stream_read_to(h2_stream *stream, apr_bucket_brigade *bb,
+ apr_off_t *plen, int *peos)
+{
+ apr_status_t rv = APR_SUCCESS;
+
+ if (stream->rst_error) {
+ return APR_ECONNRESET;
+ }
+ rv = h2_append_brigade(bb, stream->out_buffer, plen, peos, bucket_pass_to_c1);
+ if (APR_SUCCESS == rv && !*peos && !*plen) {
+ rv = APR_EAGAIN;
+ }
+ return rv;
+}
+
+static apr_status_t stream_do_trailers(h2_stream *stream)
+{
+ conn_rec *c1 = stream->session->c1;
+ int ngrv;
+ h2_ngheader *nh = NULL;
+ apr_bucket *b, *e;
+#if AP_HAS_RESPONSE_BUCKETS
+ ap_bucket_headers *headers = NULL;
+#else
+ h2_headers *headers = NULL;
+#endif
+ apr_status_t rv;
+
+ ap_assert(stream->response);
+ ap_assert(stream->out_buffer);
+
+ b = APR_BRIGADE_FIRST(stream->out_buffer);
+ while (b != APR_BRIGADE_SENTINEL(stream->out_buffer)) {
+ e = APR_BUCKET_NEXT(b);
+ if (APR_BUCKET_IS_METADATA(b)) {
+#if AP_HAS_RESPONSE_BUCKETS
+ if (AP_BUCKET_IS_HEADERS(b)) {
+ headers = b->data;
+#else /* AP_HAS_RESPONSE_BUCKETS */
+ if (H2_BUCKET_IS_HEADERS(b)) {
+ headers = h2_bucket_headers_get(b);
+#endif /* else AP_HAS_RESPONSE_BUCKETS */
+ APR_BUCKET_REMOVE(b);
+ apr_bucket_destroy(b);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c1,
+ H2_STRM_MSG(stream, "process trailers"));
+ break;
+ }
+ else if (APR_BUCKET_IS_EOS(b)) {
+ break;
+ }
+ }
+ else {
+ break;
+ }
+ b = e;
+ }
+
+ if (!headers) {
+ rv = APR_EAGAIN;
+ goto cleanup;
+ }
+
+ rv = h2_res_create_ngtrailer(&nh, stream->pool, headers);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c1,
+ H2_STRM_LOG(APLOGNO(03072), stream, "submit %d trailers"),
+ (int)nh->nvlen);
+ if (APR_SUCCESS != rv) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c1,
+ H2_STRM_LOG(APLOGNO(10024), stream, "invalid trailers"));
+ h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR);
+ goto cleanup;
+ }
+
+ ngrv = nghttp2_submit_trailer(stream->session->ngh2, stream->id, nh->nv, nh->nvlen);
+ if (nghttp2_is_fatal(ngrv)) {
+ rv = APR_EGENERAL;
+ h2_session_dispatch_event(stream->session,
+ H2_SESSION_EV_PROTO_ERROR, ngrv, nghttp2_strerror(rv));
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c1,
+ APLOGNO(02940) "submit_response: %s",
+ nghttp2_strerror(rv));
+ }
+ stream->sent_trailers = 1;
+
+cleanup:
+ return rv;
+}
+
+#if AP_HAS_RESPONSE_BUCKETS
+apr_status_t h2_stream_submit_pushes(h2_stream *stream, ap_bucket_response *response)
+#else
+apr_status_t h2_stream_submit_pushes(h2_stream *stream, h2_headers *response)
+#endif
+{
+ apr_status_t status = APR_SUCCESS;
+ apr_array_header_t *pushes;
+ int i;
+
+ pushes = h2_push_collect_update(stream, stream->request, response);
+ if (pushes && !apr_is_empty_array(pushes)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c1,
+ H2_STRM_MSG(stream, "found %d push candidates"),
+ pushes->nelts);
+ for (i = 0; i < pushes->nelts; ++i) {
+ h2_push *push = APR_ARRAY_IDX(pushes, i, h2_push*);
+ h2_stream *s = h2_session_push(stream->session, stream, push);
+ if (!s) {
+ status = APR_ECONNRESET;
+ break;
+ }
+ }
+ }
+ return status;
+}
+
+apr_table_t *h2_stream_get_trailers(h2_stream *stream)
+{
+ return NULL;
+}
+
+#if AP_HAS_RESPONSE_BUCKETS
+const h2_priority *h2_stream_get_priority(h2_stream *stream,
+ ap_bucket_response *response)
+#else
+const h2_priority *h2_stream_get_priority(h2_stream *stream,
+ h2_headers *response)
+#endif
+{
+ if (response && stream->initiated_on) {
+ const char *ctype = apr_table_get(response->headers, "content-type");
+ if (ctype) {
+ /* FIXME: Not good enough, config needs to come from request->server */
+ return h2_cconfig_get_priority(stream->session->c1, ctype);
+ }
+ }
+ return NULL;
+}
+
+int h2_stream_is_ready(h2_stream *stream)
+{
+ /* Have we sent a response or do we have the response in our buffer? */
+ if (stream->response) {
+ return 1;
+ }
+ else if (stream->out_buffer && get_first_response_bucket(stream->out_buffer)) {
+ return 1;
+ }
+ return 0;
+}
+
+int h2_stream_is_at(const h2_stream *stream, h2_stream_state_t state)
+{
+ return stream->state == state;
+}
+
+int h2_stream_is_at_or_past(const h2_stream *stream, h2_stream_state_t state)
+{
+ switch (state) {
+ case H2_SS_IDLE:
+ return 1; /* by definition */
+ case H2_SS_RSVD_R: /*fall through*/
+ case H2_SS_RSVD_L: /*fall through*/
+ case H2_SS_OPEN:
+ return stream->state == state || stream->state >= H2_SS_OPEN;
+ case H2_SS_CLOSED_R: /*fall through*/
+ case H2_SS_CLOSED_L: /*fall through*/
+ case H2_SS_CLOSED:
+ return stream->state == state || stream->state >= H2_SS_CLOSED;
+ case H2_SS_CLEANUP:
+ return stream->state == state;
+ default:
+ return 0;
+ }
+}
+
+apr_status_t h2_stream_in_consumed(h2_stream *stream, apr_off_t amount)
+{
+ h2_session *session = stream->session;
+
+ if (amount > 0) {
+ apr_off_t consumed = amount;
+
+ while (consumed > 0) {
+ int len = (consumed > INT_MAX)? INT_MAX : (int)consumed;
+ nghttp2_session_consume(session->ngh2, stream->id, len);
+ consumed -= len;
+ }
+
+#ifdef H2_NG2_LOCAL_WIN_SIZE
+ if (1) {
+ int cur_size = nghttp2_session_get_stream_local_window_size(
+ session->ngh2, stream->id);
+ int win = stream->in_window_size;
+ int thigh = win * 8/10;
+ int tlow = win * 2/10;
+ const int win_max = 2*1024*1024;
+ const int win_min = 32*1024;
+
+ /* Work in progress, probably should add directives for these
+ * values once this stabilizes somewhat. The general idea is
+ * to adapt stream window sizes if the input window changes
+ * a) very quickly (< good RTT) from full to empty
+ * b) only a little bit (> bad RTT)
+ * where in a) it grows and in b) it shrinks again.
+ */
+ if (cur_size > thigh && amount > thigh && win < win_max) {
+ /* almost empty again with one reported consumption, how
+ * long did this take? */
+ long ms = apr_time_msec(apr_time_now() - stream->in_last_write);
+ if (ms < 40) {
+ win = H2MIN(win_max, win + (64*1024));
+ }
+ }
+ else if (cur_size < tlow && amount < tlow && win > win_min) {
+ /* staying full, for how long already? */
+ long ms = apr_time_msec(apr_time_now() - stream->in_last_write);
+ if (ms > 700) {
+ win = H2MAX(win_min, win - (32*1024));
+ }
+ }
+
+ if (win != stream->in_window_size) {
+ stream->in_window_size = win;
+ nghttp2_session_set_local_window_size(session->ngh2,
+ NGHTTP2_FLAG_NONE, stream->id, win);
+ }
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c1,
+ H2_STRM_MSG(stream, "consumed %ld bytes, window now %d/%d"),
+ (long)amount, cur_size, stream->in_window_size);
+ }
+#endif /* #ifdef H2_NG2_LOCAL_WIN_SIZE */
+ }
+ return APR_SUCCESS;
+}
+
+static apr_off_t output_data_buffered(h2_stream *stream, int *peos, int *pheader_blocked)
+{
+ /* How much data do we have in our buffers that we can write? */
+ apr_off_t buf_len = 0;
+ apr_bucket *b;
+
+ *peos = *pheader_blocked = 0;
+ if (stream->out_buffer) {
+ b = APR_BRIGADE_FIRST(stream->out_buffer);
+ while (b != APR_BRIGADE_SENTINEL(stream->out_buffer)) {
+ if (APR_BUCKET_IS_METADATA(b)) {
+ if (APR_BUCKET_IS_EOS(b)) {
+ *peos = 1;
+ break;
+ }
+#if AP_HAS_RESPONSE_BUCKETS
+ else if (AP_BUCKET_IS_RESPONSE(b)) {
+ break;
+ }
+ else if (AP_BUCKET_IS_HEADERS(b)) {
+ *pheader_blocked = 1;
+ break;
+ }
+#else
+ else if (H2_BUCKET_IS_HEADERS(b)) {
+ *pheader_blocked = 1;
+ break;
+ }
+#endif
+ }
+ else {
+ buf_len += b->length;
+ }
+ b = APR_BUCKET_NEXT(b);
+ }
+ }
+ return buf_len;
+}
+
+static ssize_t stream_data_cb(nghttp2_session *ng2s,
+ int32_t stream_id,
+ uint8_t *buf,
+ size_t length,
+ uint32_t *data_flags,
+ nghttp2_data_source *source,
+ void *puser)
+{
+ h2_session *session = (h2_session *)puser;
+ conn_rec *c1 = session->c1;
+ apr_off_t buf_len;
+ int eos, header_blocked;
+ apr_status_t rv;
+ h2_stream *stream;
+
+ /* nghttp2 wants to send more DATA for the stream.
+ * we should have submitted the final response at this time
+ * after receiving output via stream_do_responses() */
+ ap_assert(session);
+ (void)ng2s;
+ (void)buf;
+ (void)source;
+ stream = nghttp2_session_get_stream_user_data(session->ngh2, stream_id);
+
+ if (!stream) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c1,
+ APLOGNO(02937)
+ H2_SSSN_STRM_MSG(session, stream_id, "data_cb, stream not found"));
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ if (!stream->output || !stream->response || !stream->out_buffer) {
+ return NGHTTP2_ERR_DEFERRED;
+ }
+ if (stream->rst_error) {
+ return NGHTTP2_ERR_DEFERRED;
+ }
+ if (h2_c1_io_needs_flush(&session->io)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c1,
+ H2_SSSN_STRM_MSG(session, stream_id, "suspending on c1 out needs flush"));
+ h2_stream_dispatch(stream, H2_SEV_OUT_C1_BLOCK);
+ return NGHTTP2_ERR_DEFERRED;
+ }
+
+ /* determine how much we'd like to send. We cannot send more than
+ * is requested. But we can reduce the size in case the master
+ * connection operates in smaller chunks. (TSL warmup) */
+ if (stream->session->io.write_size > 0) {
+ apr_size_t chunk_len = stream->session->io.write_size - H2_FRAME_HDR_LEN;
+ if (length > chunk_len) {
+ length = chunk_len;
+ }
+ }
+
+ /* How much data do we have in our buffers that we can write?
+ * if not enough, receive more. */
+ buf_len = output_data_buffered(stream, &eos, &header_blocked);
+ if (buf_len < (apr_off_t)length && !eos
+ && !header_blocked && !stream->rst_error) {
+ /* read more? */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c1,
+ H2_SSSN_STRM_MSG(session, stream_id,
+ "need more (read len=%ld, %ld in buffer)"),
+ (long)length, (long)buf_len);
+ rv = buffer_output_receive(stream);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c1,
+ H2_SSSN_STRM_MSG(session, stream_id,
+ "buffer_output_received"));
+ if (APR_STATUS_IS_EAGAIN(rv)) {
+ /* currently, no more is available */
+ }
+ else if (APR_SUCCESS == rv) {
+ /* got some, re-assess */
+ buf_len = output_data_buffered(stream, &eos, &header_blocked);
+ }
+ else if (APR_EOF == rv) {
+ if (!stream->output_eos) {
+ /* Seeing APR_EOF without an EOS bucket received before indicates
+ * that stream output is incomplete. Commonly, we expect to see
+ * an ERROR bucket to have been generated. But faulty handlers
+ * may not have generated one.
+ * We need to RST the stream bc otherwise the client thinks
+ * it is all fine. */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c1,
+ H2_SSSN_STRM_MSG(session, stream_id, "rst stream"));
+ h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c1,
+ H2_SSSN_STRM_MSG(session, stream_id,
+ "eof on receive (read len=%ld, %ld in buffer)"),
+ (long)length, (long)buf_len);
+ eos = 1;
+ rv = APR_SUCCESS;
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c1,
+ H2_STRM_LOG(APLOGNO(02938), stream, "data_cb, reading data"));
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ }
+
+ if (stream->rst_error) {
+ return NGHTTP2_ERR_DEFERRED;
+ }
+
+ if (buf_len == 0 && header_blocked) {
+ rv = stream_do_trailers(stream);
+ if (APR_SUCCESS != rv && !APR_STATUS_IS_EAGAIN(rv)) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c1,
+ H2_STRM_LOG(APLOGNO(10300), stream,
+ "data_cb, error processing trailers"));
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+ length = 0;
+ eos = 0;
+ }
+ else if (buf_len > (apr_off_t)length) {
+ eos = 0; /* Any EOS we have in the buffer does not apply yet */
+ }
+ else {
+ length = (size_t)buf_len;
+ }
+
+ if (length) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c1,
+ H2_STRM_MSG(stream, "data_cb, sending len=%ld, eos=%d"),
+ (long)length, eos);
+ *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY;
+ }
+ else if (!eos && !stream->sent_trailers) {
+ /* We have not reached the end of DATA yet, DEFER sending */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c1,
+ H2_STRM_LOG(APLOGNO(03071), stream, "data_cb, suspending"));
+ return NGHTTP2_ERR_DEFERRED;
+ }
+
+ if (eos) {
+ *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+ }
+ return length;
+}
+
+static apr_status_t stream_do_response(h2_stream *stream)
+{
+ conn_rec *c1 = stream->session->c1;
+ apr_status_t rv = APR_EAGAIN;
+ int ngrv, is_empty = 0;
+ h2_ngheader *nh = NULL;
+ apr_bucket *b, *e;
+#if AP_HAS_RESPONSE_BUCKETS
+ ap_bucket_response *resp = NULL;
+#else
+ h2_headers *resp = NULL;
+#endif
+ nghttp2_data_provider provider, *pprovider = NULL;
+
+ ap_assert(!stream->response);
+ ap_assert(stream->out_buffer);
+
+ b = APR_BRIGADE_FIRST(stream->out_buffer);
+ while (b != APR_BRIGADE_SENTINEL(stream->out_buffer)) {
+ e = APR_BUCKET_NEXT(b);
+ if (APR_BUCKET_IS_METADATA(b)) {
+#if AP_HAS_RESPONSE_BUCKETS
+ if (AP_BUCKET_IS_RESPONSE(b)) {
+ resp = b->data;
+#else /* AP_HAS_RESPONSE_BUCKETS */
+ if (H2_BUCKET_IS_HEADERS(b)) {
+ resp = h2_bucket_headers_get(b);
+#endif /* else AP_HAS_RESPONSE_BUCKETS */
+ APR_BUCKET_REMOVE(b);
+ apr_bucket_destroy(b);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c1,
+ H2_STRM_MSG(stream, "process response %d"),
+ resp->status);
+ is_empty = (e != APR_BRIGADE_SENTINEL(stream->out_buffer)
+ && APR_BUCKET_IS_EOS(e));
+ break;
+ }
+ else if (APR_BUCKET_IS_EOS(b)) {
+ h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
+ rv = APR_EINVAL;
+ goto cleanup;
+ }
+ else if (AP_BUCKET_IS_ERROR(b)) {
+ stream_do_error_bucket(stream, b);
+ rv = APR_EINVAL;
+ goto cleanup;
+ }
+ }
+ else {
+ /* data buckets before response headers, an error */
+ h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
+ rv = APR_EINVAL;
+ goto cleanup;
+ }
+ b = e;
+ }
+
+ if (!resp) {
+ rv = APR_EAGAIN;
+ goto cleanup;
+ }
+
+ if (resp->status < 100) {
+ h2_stream_rst(stream, resp->status);
+ goto cleanup;
+ }
+
+ if (resp->status == HTTP_FORBIDDEN && resp->notes) {
+ const char *cause = apr_table_get(resp->notes, "ssl-renegotiate-forbidden");
+ if (cause) {
+ /* This request triggered a TLS renegotiation that is not allowed
+ * in HTTP/2. Tell the client that it should use HTTP/1.1 for this.
+ */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, resp->status, c1,
+ H2_STRM_LOG(APLOGNO(03061), stream,
+ "renegotiate forbidden, cause: %s"), cause);
+ h2_stream_rst(stream, H2_ERR_HTTP_1_1_REQUIRED);
+ goto cleanup;
+ }
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c1,
+ H2_STRM_LOG(APLOGNO(03073), stream,
+ "submit response %d"), resp->status);
+
+ /* If this stream is not a pushed one itself,
+ * and HTTP/2 server push is enabled here,
+ * and the response HTTP status is not sth >= 400,
+ * and the remote side has pushing enabled,
+ * -> find and perform any pushes on this stream
+ * *before* we submit the stream response itself.
+ * This helps clients avoid opening new streams on Link
+ * resp that get pushed right afterwards.
+ *
+ * *) the response code is relevant, as we do not want to
+ * make pushes on 401 or 403 codes and friends.
+ * And if we see a 304, we do not push either
+ * as the client, having this resource in its cache, might
+ * also have the pushed ones as well.
+ */
+ if (!stream->initiated_on
+ && !stream->response
+ && stream->request && stream->request->method
+ && !strcmp("GET", stream->request->method)
+ && (resp->status < 400)
+ && (resp->status != 304)
+ && h2_session_push_enabled(stream->session)) {
+ /* PUSH is possible and enabled on server, unless the request
+ * denies it, submit resources to push */
+ const char *s = apr_table_get(resp->notes, H2_PUSH_MODE_NOTE);
+ if (!s || strcmp(s, "0")) {
+ h2_stream_submit_pushes(stream, resp);
+ }
+ }
+
+ if (!stream->pref_priority) {
+ stream->pref_priority = h2_stream_get_priority(stream, resp);
+ }
+ h2_session_set_prio(stream->session, stream, stream->pref_priority);
+
+ if (resp->status == 103
+ && !h2_config_sgeti(stream->session->s, H2_CONF_EARLY_HINTS)) {
+ /* suppress sending this to the client, it might have triggered
+ * pushes and served its purpose nevertheless */
+ rv = APR_SUCCESS;
+ goto cleanup;
+ }
+ if (resp->status >= 200) {
+ stream->response = resp;
+ }
+
+ if (!is_empty) {
+ memset(&provider, 0, sizeof(provider));
+ provider.source.fd = stream->id;
+ provider.read_callback = stream_data_cb;
+ pprovider = &provider;
+ }
+
+ rv = h2_res_create_ngheader(&nh, stream->pool, resp);
+ if (APR_SUCCESS != rv) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c1,
+ H2_STRM_LOG(APLOGNO(10025), stream, "invalid response"));
+ h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR);
+ goto cleanup;
+ }
+
+ ngrv = nghttp2_submit_response(stream->session->ngh2, stream->id,
+ nh->nv, nh->nvlen, pprovider);
+ if (nghttp2_is_fatal(ngrv)) {
+ rv = APR_EGENERAL;
+ h2_session_dispatch_event(stream->session,
+ H2_SESSION_EV_PROTO_ERROR, ngrv, nghttp2_strerror(rv));
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c1,
+ APLOGNO(10402) "submit_response: %s",
+ nghttp2_strerror(rv));
+ goto cleanup;
+ }
+
+ if (stream->initiated_on) {
+ ++stream->session->pushes_submitted;
+ }
+ else {
+ ++stream->session->responses_submitted;
+ }
+
+cleanup:
+ return rv;
+}
+
+static void stream_do_responses(h2_stream *stream)
+{
+ h2_session *session = stream->session;
+ conn_rec *c1 = session->c1;
+ apr_status_t rv;
+
+ ap_assert(!stream->response);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c1,
+ H2_STRM_MSG(stream, "do_response"));
+ rv = buffer_output_receive(stream);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c1,
+ H2_SSSN_STRM_MSG(session, stream->id,
+ "buffer_output_received2"));
+ if (APR_SUCCESS != rv && APR_EAGAIN != rv) {
+ h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR);
+ }
+ else {
+ /* process all headers sitting at the buffer head. */
+ do {
+ rv = stream_do_response(stream);
+ } while (APR_SUCCESS == rv
+ && !stream->rst_error
+ && !stream->response);
+ }
+}
+
+void h2_stream_on_output_change(h2_stream *stream)
+{
+ conn_rec *c1 = stream->session->c1;
+ apr_status_t rv = APR_EAGAIN;
+
+ /* stream->pout_recv_write signalled a change. Check what has happend, read
+ * from it and act on seeing a response/data. */
+ if (!stream->output) {
+ /* c2 has not assigned the output beam to the stream (yet). */
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c1,
+ H2_STRM_MSG(stream, "read_output, no output beam registered"));
+ }
+ else if (h2_stream_is_at_or_past(stream, H2_SS_CLOSED)) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c1,
+ H2_STRM_LOG(APLOGNO(10301), stream, "already closed"));
+ }
+ else if (h2_stream_is_at(stream, H2_SS_CLOSED_L)) {
+ /* We have delivered a response to a stream that was not closed
+ * by the client. This could be a POST with body that we negate
+ * and we need to RST_STREAM to end if. */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c1,
+ H2_STRM_LOG(APLOGNO(10313), stream, "remote close missing"));
+ h2_stream_rst(stream, H2_ERR_NO_ERROR);
+ }
+ else {
+ /* stream is not closed, a change in output happened. There are
+ * two modes of operation here:
+ * 1) the final response has been submitted. nghttp2 is invoking
+ * stream_data_cb() to progress the stream. This handles DATA,
+ * trailers, EOS and ERRORs.
+ * When stream_data_cb() runs out of things to send, it returns
+ * NGHTTP2_ERR_DEFERRED and nghttp2 *suspends* further processing
+ * until we tell it to resume.
+ * 2) We have not seen the *final* response yet. The stream can not
+ * send any response DATA. The nghttp2 stream_data_cb() is not
+ * invoked. We need to receive output, expecting not DATA but
+ * RESPONSEs (intermediate may arrive) and submit those. On
+ * the final response, nghttp2 will start calling stream_data_cb().
+ */
+ if (stream->response) {
+ nghttp2_session_resume_data(stream->session->ngh2, stream->id);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c1,
+ H2_STRM_MSG(stream, "resumed"));
+ }
+ else {
+ stream_do_responses(stream);
+ if (!stream->rst_error) {
+ nghttp2_session_resume_data(stream->session->ngh2, stream->id);
+ }
+ }
+ }
+}
+
+void h2_stream_on_input_change(h2_stream *stream)
+{
+ ap_assert(stream->input);
+ h2_beam_report_consumption(stream->input);
+ if (h2_stream_is_at(stream, H2_SS_CLOSED_L)
+ && !h2_mplx_c1_stream_is_running(stream->session->mplx, stream)) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->session->c1,
+ H2_STRM_LOG(APLOGNO(10026), stream, "remote close missing"));
+ h2_stream_rst(stream, H2_ERR_NO_ERROR);
+ }
+}
diff --git a/modules/http2/h2_stream.h b/modules/http2/h2_stream.h
new file mode 100644
index 0000000..695d56a
--- /dev/null
+++ b/modules/http2/h2_stream.h
@@ -0,0 +1,326 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __mod_h2__h2_stream__
+#define __mod_h2__h2_stream__
+
+#include <http_protocol.h>
+
+#include "h2.h"
+#include "h2_headers.h"
+
+/**
+ * A HTTP/2 stream, e.g. a client request+response in HTTP/1.1 terms.
+ *
+ * A stream always belongs to a h2_session, the one managing the
+ * connection to the client. The h2_session writes to the h2_stream,
+ * adding HEADERS and DATA and finally an EOS. When headers are done,
+ * h2_stream is scheduled for handling, which is expected to produce
+ * h2_headers/RESPONSE buckets.
+ *
+ * The h2_headers may be followed by more h2_headers (interim responses) and
+ * by DATA frames read from the h2_stream until EOS is reached. Trailers
+ * are send when a last h2_headers is received. This always closes the stream
+ * output.
+ */
+
+struct h2_mplx;
+struct h2_priority;
+struct h2_request;
+struct h2_session;
+struct h2_bucket_beam;
+
+typedef struct h2_stream h2_stream;
+
+typedef void h2_stream_state_cb(void *ctx, h2_stream *stream);
+typedef void h2_stream_event_cb(void *ctx, h2_stream *stream,
+ h2_stream_event_t ev);
+
+/**
+ * Callback structure for events and stream state transisitions
+ */
+typedef struct h2_stream_monitor {
+ void *ctx;
+ h2_stream_state_cb *on_state_enter; /* called when a state is entered */
+ h2_stream_state_cb *on_state_invalid; /* called when an invalid state change
+ was detected */
+ h2_stream_event_cb *on_state_event; /* called right before the given event
+ result in a new stream state */
+ h2_stream_event_cb *on_event; /* called for events that do not
+ trigger a state change */
+} h2_stream_monitor;
+
+struct h2_stream {
+ int id; /* http2 stream identifier */
+ int initiated_on; /* initiating stream id (PUSH) or 0 */
+ apr_pool_t *pool; /* the memory pool for this stream */
+ struct h2_session *session; /* the session this stream belongs to */
+ h2_stream_state_t state; /* state of this stream */
+
+ apr_time_t created; /* when stream was created */
+
+ const struct h2_request *request; /* the request made in this stream */
+ struct h2_request *rtmp; /* request being assembled */
+ apr_table_t *trailers_in; /* optional, incoming trailers */
+ int request_headers_added; /* number of request headers added */
+
+#if AP_HAS_RESPONSE_BUCKETS
+ ap_bucket_response *response; /* the final, non-interim response or NULL */
+#else
+ struct h2_headers *response; /* the final, non-interim response or NULL */
+#endif
+
+ struct h2_bucket_beam *input;
+ apr_bucket_brigade *in_buffer;
+ int in_window_size;
+ apr_time_t in_last_write;
+
+ struct h2_bucket_beam *output;
+ apr_bucket_brigade *out_buffer;
+
+ int rst_error; /* stream error for RST_STREAM */
+ unsigned int aborted : 1; /* was aborted */
+ unsigned int scheduled : 1; /* stream has been scheduled */
+ unsigned int input_closed : 1; /* no more request data/trailers coming */
+ unsigned int push_policy; /* which push policy to use for this request */
+ unsigned int sent_trailers : 1; /* trailers have been submitted */
+ unsigned int output_eos : 1; /* output EOS in buffer/sent */
+
+ conn_rec *c2; /* connection processing stream */
+
+ const h2_priority *pref_priority; /* preferred priority for this stream */
+ apr_off_t out_frames; /* # of frames sent out */
+ apr_off_t out_frame_octets; /* # of RAW frame octets sent out */
+ apr_off_t out_data_frames; /* # of DATA frames sent */
+ apr_off_t out_data_octets; /* # of DATA octets (payload) sent */
+ apr_off_t in_data_frames; /* # of DATA frames received */
+ apr_off_t in_data_octets; /* # of DATA octets (payload) received */
+ apr_off_t in_trailer_octets; /* # of HEADER octets (payload) received in trailers */
+
+ h2_stream_monitor *monitor; /* optional monitor for stream states */
+};
+
+
+#define H2_STREAM_RST(s, def) (s->rst_error? s->rst_error : (def))
+
+/**
+ * Create a stream in H2_SS_IDLE state.
+ * @param id the stream identifier
+ * @param pool the memory pool to use for this stream
+ * @param session the session this stream belongs to
+ * @param monitor an optional monitor to be called for events and
+ * state transisitions
+ * @param initiated_on the id of the stream this one was initiated on (PUSH)
+ *
+ * @return the newly opened stream
+ */
+h2_stream *h2_stream_create(int id, apr_pool_t *pool,
+ struct h2_session *session,
+ h2_stream_monitor *monitor,
+ int initiated_on);
+
+/**
+ * Destroy memory pool if still owned by the stream.
+ */
+void h2_stream_destroy(h2_stream *stream);
+
+/**
+ * Perform any late initialization before stream starts processing.
+ */
+apr_status_t h2_stream_prepare_processing(h2_stream *stream);
+
+/*
+ * Set a new monitor for this stream, replacing any existing one. Can
+ * be called with NULL to have no monitor installed.
+ */
+void h2_stream_set_monitor(h2_stream *stream, h2_stream_monitor *monitor);
+
+/**
+ * Dispatch (handle) an event on the given stream.
+ * @param stream the streama the event happened on
+ * @param ev the type of event
+ */
+void h2_stream_dispatch(h2_stream *stream, h2_stream_event_t ev);
+
+/**
+ * Determine if stream is at given state.
+ * @param stream the stream to check
+ * @param state the state to look for
+ * @return != 0 iff stream is at given state.
+ */
+int h2_stream_is_at(const h2_stream *stream, h2_stream_state_t state);
+
+/**
+ * Determine if stream is reached given state or is past this state.
+ * @param stream the stream to check
+ * @param state the state to look for
+ * @return != 0 iff stream is at or past given state.
+ */
+int h2_stream_is_at_or_past(const h2_stream *stream, h2_stream_state_t state);
+
+/**
+ * Cleanup references into requst processing.
+ *
+ * @param stream the stream to cleanup
+ */
+void h2_stream_cleanup(h2_stream *stream);
+
+/**
+ * Notify the stream that amount bytes have been consumed of its input
+ * since the last invocation of this method (delta amount).
+ */
+apr_status_t h2_stream_in_consumed(h2_stream *stream, apr_off_t amount);
+
+/**
+ * Set complete stream headers from given h2_request.
+ *
+ * @param stream stream to write request to
+ * @param r the request with all the meta data
+ * @param eos != 0 iff stream input is closed
+ */
+void h2_stream_set_request(h2_stream *stream, const h2_request *r);
+
+/**
+ * Set complete stream header from given request_rec.
+ *
+ * @param stream stream to write request to
+ * @param r the request with all the meta data
+ * @param eos != 0 iff stream input is closed
+ */
+apr_status_t h2_stream_set_request_rec(h2_stream *stream,
+ request_rec *r, int eos);
+
+/*
+ * Add a HTTP/2 header (including pseudo headers) or trailer
+ * to the given stream, depending on stream state.
+ *
+ * @param stream stream to write the header to
+ * @param name the name of the HTTP/2 header
+ * @param nlen the number of characters in name
+ * @param value the header value
+ * @param vlen the number of characters in value
+ */
+apr_status_t h2_stream_add_header(h2_stream *stream,
+ const char *name, size_t nlen,
+ const char *value, size_t vlen);
+
+/* End the construction of request headers */
+apr_status_t h2_stream_end_headers(h2_stream *stream, int eos, size_t raw_bytes);
+
+
+apr_status_t h2_stream_send_frame(h2_stream *stream, int frame_type, int flags, size_t frame_len);
+apr_status_t h2_stream_recv_frame(h2_stream *stream, int frame_type, int flags, size_t frame_len);
+
+/*
+ * Process a frame of received DATA.
+ *
+ * @param stream stream to write the data to
+ * @param flags the frame flags
+ * @param data the beginning of the bytes to write
+ * @param len the number of bytes to write
+ */
+apr_status_t h2_stream_recv_DATA(h2_stream *stream, uint8_t flags,
+ const uint8_t *data, size_t len);
+
+/**
+ * Reset the stream. Stream write/reads will return errors afterwards.
+ *
+ * @param stream the stream to reset
+ * @param error_code the HTTP/2 error code
+ */
+void h2_stream_rst(h2_stream *stream, int error_code);
+
+/**
+ * Stream input signals change. Take necessary actions.
+ * @param stream the stream to read output for
+ */
+void h2_stream_on_input_change(h2_stream *stream);
+
+/**
+ * Stream output signals change. Take necessary actions.
+ * @param stream the stream to read output for
+ */
+void h2_stream_on_output_change(h2_stream *stream);
+
+/**
+ * Read a maximum number of bytes into the bucket brigade.
+ *
+ * @param stream the stream to read from
+ * @param bb the brigade to append output to
+ * @param plen (in-/out) max. number of bytes to append and on return actual
+ * number of bytes appended to brigade
+ * @param peos (out) != 0 iff end of stream has been reached while reading
+ * @return APR_SUCCESS if out information was computed successfully.
+ * APR_EAGAIN if not data is available and end of stream has not been
+ * reached yet.
+ */
+apr_status_t h2_stream_read_to(h2_stream *stream, apr_bucket_brigade *bb,
+ apr_off_t *plen, int *peos);
+
+/**
+ * Get optional trailers for this stream, may be NULL. Meaningful
+ * results can only be expected when the end of the response body has
+ * been reached.
+ *
+ * @param stream to ask for trailers
+ * @return trailers for NULL
+ */
+apr_table_t *h2_stream_get_trailers(h2_stream *stream);
+
+/**
+ * Submit any server push promises on this stream and schedule
+ * the streams for these.
+ *
+ * @param stream the stream for which to submit
+ */
+#if AP_HAS_RESPONSE_BUCKETS
+apr_status_t h2_stream_submit_pushes(h2_stream *stream,
+ ap_bucket_response *response);
+#else
+apr_status_t h2_stream_submit_pushes(h2_stream *stream,
+ struct h2_headers *response);
+#endif
+
+/**
+ * Get priority information set for this stream.
+ */
+#if AP_HAS_RESPONSE_BUCKETS
+const struct h2_priority *h2_stream_get_priority(h2_stream *stream,
+ ap_bucket_response *response);
+#else
+const struct h2_priority *h2_stream_get_priority(h2_stream *stream,
+ struct h2_headers *response);
+#endif
+
+/**
+ * Return a textual representation of the stream state as in RFC 7540
+ * nomenclator, all caps, underscores.
+ */
+const char *h2_stream_state_str(const h2_stream *stream);
+
+/**
+ * Determine if stream is ready for submitting a response or a RST
+ * @param stream the stream to check
+ */
+int h2_stream_is_ready(h2_stream *stream);
+
+#define H2_STRM_MSG(s, msg) \
+ "h2_stream(%d-%lu-%d,%s): "msg, s->session->child_num, \
+ (unsigned long)s->session->id, s->id, h2_stream_state_str(s)
+
+#define H2_STRM_LOG(aplogno, s, msg) aplogno H2_STRM_MSG(s, msg)
+
+#endif /* defined(__mod_h2__h2_stream__) */
diff --git a/modules/http2/h2_switch.c b/modules/http2/h2_switch.c
new file mode 100644
index 0000000..a30f27c
--- /dev/null
+++ b/modules/http2/h2_switch.c
@@ -0,0 +1,232 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+
+#include <apr_strings.h>
+#include <apr_optional.h>
+#include <apr_optional_hooks.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_config.h>
+#include <http_connection.h>
+#include <http_protocol.h>
+#include <http_ssl.h>
+#include <http_log.h>
+
+#include "h2_private.h"
+#include "h2.h"
+
+#include "h2_config.h"
+#include "h2_conn_ctx.h"
+#include "h2_c1.h"
+#include "h2_c2.h"
+#include "h2_protocol.h"
+#include "h2_switch.h"
+
+/*******************************************************************************
+ * Once per lifetime init, retrieve optional functions
+ */
+apr_status_t h2_switch_init(apr_pool_t *pool, server_rec *s)
+{
+ (void)pool;
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, "h2_switch init");
+
+ return APR_SUCCESS;
+}
+
+static int h2_protocol_propose(conn_rec *c, request_rec *r,
+ server_rec *s,
+ const apr_array_header_t *offers,
+ apr_array_header_t *proposals)
+{
+ int proposed = 0;
+ int is_tls = ap_ssl_conn_is_ssl(c);
+ const char **protos = is_tls? h2_protocol_ids_tls : h2_protocol_ids_clear;
+
+ if (!h2_mpm_supported()) {
+ return DECLINED;
+ }
+
+ if (strcmp(AP_PROTOCOL_HTTP1, ap_get_protocol(c))) {
+ /* We do not know how to switch from anything else but http/1.1.
+ */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03083)
+ "protocol switch: current proto != http/1.1, declined");
+ return DECLINED;
+ }
+
+ if (!h2_protocol_is_acceptable_c1(c, r, 0)) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03084)
+ "protocol propose: connection requirements not met");
+ return DECLINED;
+ }
+
+ if (r) {
+ /* So far, this indicates an HTTP/1 Upgrade header initiated
+ * protocol switch. For that, the HTTP2-Settings header needs
+ * to be present and valid for the connection.
+ */
+ const char *p;
+
+ if (!h2_c1_can_upgrade(r)) {
+ return DECLINED;
+ }
+
+ p = apr_table_get(r->headers_in, "HTTP2-Settings");
+ if (!p) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03085)
+ "upgrade without HTTP2-Settings declined");
+ return DECLINED;
+ }
+
+ p = apr_table_get(r->headers_in, "Connection");
+ if (!ap_find_token(r->pool, p, "http2-settings")) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03086)
+ "upgrade without HTTP2-Settings declined");
+ return DECLINED;
+ }
+
+ /* We also allow switching only for requests that have no body.
+ */
+ p = apr_table_get(r->headers_in, "Content-Length");
+ if (p && strcmp(p, "0")) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03087)
+ "upgrade with content-length: %s, declined", p);
+ return DECLINED;
+ }
+ }
+
+ while (*protos) {
+ /* Add all protocols we know (tls or clear) and that
+ * are part of the offerings (if there have been any).
+ */
+ if (!offers || ap_array_str_contains(offers, *protos)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "proposing protocol '%s'", *protos);
+ APR_ARRAY_PUSH(proposals, const char*) = *protos;
+ proposed = 1;
+ }
+ ++protos;
+ }
+ return proposed? DECLINED : OK;
+}
+
+#if AP_HAS_RESPONSE_BUCKETS
+static void remove_output_filters_below(ap_filter_t *f, ap_filter_type ftype)
+{
+ ap_filter_t *fnext;
+
+ while (f && f->frec->ftype < ftype) {
+ fnext = f->next;
+ ap_remove_output_filter(f);
+ f = fnext;
+ }
+}
+
+static void remove_input_filters_below(ap_filter_t *f, ap_filter_type ftype)
+{
+ ap_filter_t *fnext;
+
+ while (f && f->frec->ftype < ftype) {
+ fnext = f->next;
+ ap_remove_input_filter(f);
+ f = fnext;
+ }
+}
+#endif
+
+static int h2_protocol_switch(conn_rec *c, request_rec *r, server_rec *s,
+ const char *protocol)
+{
+ int found = 0;
+ const char **protos = ap_ssl_conn_is_ssl(c)? h2_protocol_ids_tls : h2_protocol_ids_clear;
+ const char **p = protos;
+
+ (void)s;
+ if (!h2_mpm_supported()) {
+ return DECLINED;
+ }
+
+ while (*p) {
+ if (!strcmp(*p, protocol)) {
+ found = 1;
+ break;
+ }
+ p++;
+ }
+
+ if (found) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "switching protocol to '%s'", protocol);
+ h2_conn_ctx_create_for_c1(c, s, protocol);
+
+ if (r != NULL) {
+ apr_status_t status;
+#if AP_HAS_RESPONSE_BUCKETS
+ /* Switching in the middle of a request means that
+ * we have to send out the response to this one in h2
+ * format. So we need to take over the connection
+ * and remove all old filters with type up to the
+ * CONNEDCTION/NETWORK ones.
+ */
+ remove_input_filters_below(r->input_filters, AP_FTYPE_CONNECTION);
+ remove_output_filters_below(r->output_filters, AP_FTYPE_CONNECTION);
+#else
+ /* Switching in the middle of a request means that
+ * we have to send out the response to this one in h2
+ * format. So we need to take over the connection
+ * right away.
+ */
+ ap_remove_input_filter_byhandle(r->input_filters, "http_in");
+ ap_remove_output_filter_byhandle(r->output_filters, "HTTP_HEADER");
+#endif
+ /* Ok, start an h2_conn on this one. */
+ status = h2_c1_setup(c, r, s);
+
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO(03088)
+ "session setup");
+ h2_conn_ctx_detach(c);
+ return !OK;
+ }
+
+ h2_c1_run(c);
+ }
+ return OK;
+ }
+
+ return DECLINED;
+}
+
+static const char *h2_protocol_get(const conn_rec *c)
+{
+ h2_conn_ctx_t *ctx;
+
+ if (c->master) {
+ c = c->master;
+ }
+ ctx = h2_conn_ctx_get(c);
+ return ctx? ctx->protocol : NULL;
+}
+
+void h2_switch_register_hooks(void)
+{
+ ap_hook_protocol_propose(h2_protocol_propose, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_protocol_switch(h2_protocol_switch, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_protocol_get(h2_protocol_get, NULL, NULL, APR_HOOK_MIDDLE);
+}
diff --git a/modules/http2/h2_switch.h b/modules/http2/h2_switch.h
new file mode 100644
index 0000000..7be8a23
--- /dev/null
+++ b/modules/http2/h2_switch.h
@@ -0,0 +1,30 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __mod_h2__h2_switch__
+#define __mod_h2__h2_switch__
+
+/*
+ * One time, post config initialization.
+ */
+apr_status_t h2_switch_init(apr_pool_t *pool, server_rec *s);
+
+/* Register apache hooks for protocol switching
+ */
+void h2_switch_register_hooks(void);
+
+
+#endif /* defined(__mod_h2__h2_switch__) */
diff --git a/modules/http2/h2_util.c b/modules/http2/h2_util.c
new file mode 100644
index 0000000..728cee9
--- /dev/null
+++ b/modules/http2/h2_util.c
@@ -0,0 +1,1929 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <apr_strings.h>
+#include <apr_thread_mutex.h>
+#include <apr_thread_cond.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_log.h>
+#include <http_protocol.h>
+#include <http_request.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "h2.h"
+#include "h2_headers.h"
+#include "h2_util.h"
+
+/* h2_log2(n) iff n is a power of 2 */
+unsigned char h2_log2(int n)
+{
+ int lz = 0;
+ if (!n) {
+ return 0;
+ }
+ if (!(n & 0xffff0000u)) {
+ lz += 16;
+ n = (n << 16);
+ }
+ if (!(n & 0xff000000u)) {
+ lz += 8;
+ n = (n << 8);
+ }
+ if (!(n & 0xf0000000u)) {
+ lz += 4;
+ n = (n << 4);
+ }
+ if (!(n & 0xc0000000u)) {
+ lz += 2;
+ n = (n << 2);
+ }
+ if (!(n & 0x80000000u)) {
+ lz += 1;
+ }
+
+ return 31 - lz;
+}
+
+size_t h2_util_hex_dump(char *buffer, size_t maxlen,
+ const char *data, size_t datalen)
+{
+ size_t offset = 0;
+ size_t maxoffset = (maxlen-4);
+ size_t i;
+ for (i = 0; i < datalen && offset < maxoffset; ++i) {
+ const char *sep = (i && i % 16 == 0)? "\n" : " ";
+ int n = apr_snprintf(buffer+offset, maxoffset-offset,
+ "%2x%s", ((unsigned int)data[i]&0xff), sep);
+ offset += n;
+ }
+ strcpy(buffer+offset, (i<datalen)? "..." : "");
+ return strlen(buffer);
+}
+
+void h2_util_camel_case_header(char *s, size_t len)
+{
+ size_t start = 1;
+ size_t i;
+ for (i = 0; i < len; ++i) {
+ if (start) {
+ if (s[i] >= 'a' && s[i] <= 'z') {
+ s[i] -= 'a' - 'A';
+ }
+
+ start = 0;
+ }
+ else if (s[i] == '-') {
+ start = 1;
+ }
+ }
+}
+
+/* base64 url encoding */
+
+#define N6 (unsigned int)-1
+
+static const unsigned int BASE64URL_UINT6[] = {
+/* 0 1 2 3 4 5 6 7 8 9 a b c d e f */
+ N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* 0 */
+ N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* 1 */
+ N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, 62, N6, N6, /* 2 */
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, N6, N6, N6, N6, N6, N6, /* 3 */
+ N6, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /* 4 */
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, N6, N6, N6, N6, 63, /* 5 */
+ N6, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* 6 */
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, N6, N6, N6, N6, N6, /* 7 */
+ N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* 8 */
+ N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* 9 */
+ N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* a */
+ N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* b */
+ N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* c */
+ N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* d */
+ N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* e */
+ N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6 /* f */
+};
+static const unsigned char BASE64URL_CHARS[] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', /* 0 - 9 */
+ 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', /* 10 - 19 */
+ 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', /* 20 - 29 */
+ 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', /* 30 - 39 */
+ 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', /* 40 - 49 */
+ 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', /* 50 - 59 */
+ '8', '9', '-', '_', ' ', ' ', ' ', ' ', ' ', ' ', /* 60 - 69 */
+};
+
+#define BASE64URL_CHAR(x) BASE64URL_CHARS[ (unsigned int)(x) & 0x3fu ]
+
+apr_size_t h2_util_base64url_decode(const char **decoded, const char *encoded,
+ apr_pool_t *pool)
+{
+ const unsigned char *e = (const unsigned char *)encoded;
+ const unsigned char *p = e;
+ unsigned char *d;
+ unsigned int n;
+ long len, mlen, remain, i;
+
+ while (*p && BASE64URL_UINT6[ *p ] != N6) {
+ ++p;
+ }
+ len = (int)(p - e);
+ mlen = (len/4)*4;
+ *decoded = apr_pcalloc(pool, (apr_size_t)len + 1);
+
+ i = 0;
+ d = (unsigned char*)*decoded;
+ for (; i < mlen; i += 4) {
+ n = ((BASE64URL_UINT6[ e[i+0] ] << 18) +
+ (BASE64URL_UINT6[ e[i+1] ] << 12) +
+ (BASE64URL_UINT6[ e[i+2] ] << 6) +
+ (BASE64URL_UINT6[ e[i+3] ]));
+ *d++ = (unsigned char)(n >> 16);
+ *d++ = (unsigned char)(n >> 8 & 0xffu);
+ *d++ = (unsigned char)(n & 0xffu);
+ }
+ remain = len - mlen;
+ switch (remain) {
+ case 2:
+ n = ((BASE64URL_UINT6[ e[mlen+0] ] << 18) +
+ (BASE64URL_UINT6[ e[mlen+1] ] << 12));
+ *d++ = (unsigned char)(n >> 16);
+ remain = 1;
+ break;
+ case 3:
+ n = ((BASE64URL_UINT6[ e[mlen+0] ] << 18) +
+ (BASE64URL_UINT6[ e[mlen+1] ] << 12) +
+ (BASE64URL_UINT6[ e[mlen+2] ] << 6));
+ *d++ = (unsigned char)(n >> 16);
+ *d++ = (unsigned char)(n >> 8 & 0xffu);
+ remain = 2;
+ break;
+ default: /* do nothing */
+ break;
+ }
+ return (apr_size_t)(mlen/4*3 + remain);
+}
+
+const char *h2_util_base64url_encode(const char *data,
+ apr_size_t dlen, apr_pool_t *pool)
+{
+ int i, len = (int)dlen;
+ apr_size_t slen = ((dlen+2)/3)*4 + 1; /* 0 terminated */
+ const unsigned char *udata = (const unsigned char*)data;
+ unsigned char *enc, *p = apr_pcalloc(pool, slen);
+
+ enc = p;
+ for (i = 0; i < len-2; i+= 3) {
+ *p++ = BASE64URL_CHAR( (udata[i] >> 2) );
+ *p++ = BASE64URL_CHAR( (udata[i] << 4) + (udata[i+1] >> 4) );
+ *p++ = BASE64URL_CHAR( (udata[i+1] << 2) + (udata[i+2] >> 6) );
+ *p++ = BASE64URL_CHAR( (udata[i+2]) );
+ }
+
+ if (i < len) {
+ *p++ = BASE64URL_CHAR( (udata[i] >> 2) );
+ if (i == (len - 1)) {
+ *p++ = BASE64URL_CHARS[ ((unsigned int)udata[i] << 4) & 0x3fu ];
+ }
+ else {
+ *p++ = BASE64URL_CHAR( (udata[i] << 4) + (udata[i+1] >> 4) );
+ *p++ = BASE64URL_CHAR( (udata[i+1] << 2) );
+ }
+ }
+ *p++ = '\0';
+ return (char *)enc;
+}
+
+/*******************************************************************************
+ * ihash - hash for structs with int identifier
+ ******************************************************************************/
+struct h2_ihash_t {
+ apr_hash_t *hash;
+ size_t ioff;
+};
+
+static unsigned int ihash(const char *key, apr_ssize_t *klen)
+{
+ return (unsigned int)(*((int*)key));
+}
+
+h2_ihash_t *h2_ihash_create(apr_pool_t *pool, size_t offset_of_int)
+{
+ h2_ihash_t *ih = apr_pcalloc(pool, sizeof(h2_ihash_t));
+ ih->hash = apr_hash_make_custom(pool, ihash);
+ ih->ioff = offset_of_int;
+ return ih;
+}
+
+unsigned int h2_ihash_count(h2_ihash_t *ih)
+{
+ return apr_hash_count(ih->hash);
+}
+
+int h2_ihash_empty(h2_ihash_t *ih)
+{
+ return apr_hash_count(ih->hash) == 0;
+}
+
+void *h2_ihash_get(h2_ihash_t *ih, int id)
+{
+ return apr_hash_get(ih->hash, &id, sizeof(id));
+}
+
+typedef struct {
+ h2_ihash_iter_t *iter;
+ void *ctx;
+} iter_ctx;
+
+static int ihash_iter(void *ctx, const void *key, apr_ssize_t klen,
+ const void *val)
+{
+ iter_ctx *ictx = ctx;
+ return ictx->iter(ictx->ctx, (void*)val); /* why is this passed const?*/
+}
+
+int h2_ihash_iter(h2_ihash_t *ih, h2_ihash_iter_t *fn, void *ctx)
+{
+ iter_ctx ictx;
+ ictx.iter = fn;
+ ictx.ctx = ctx;
+ return apr_hash_do(ihash_iter, &ictx, ih->hash);
+}
+
+void h2_ihash_add(h2_ihash_t *ih, void *val)
+{
+ apr_hash_set(ih->hash, ((char *)val + ih->ioff), sizeof(int), val);
+}
+
+void h2_ihash_remove(h2_ihash_t *ih, int id)
+{
+ apr_hash_set(ih->hash, &id, sizeof(id), NULL);
+}
+
+void h2_ihash_remove_val(h2_ihash_t *ih, void *val)
+{
+ int id = *((int*)((char *)val + ih->ioff));
+ apr_hash_set(ih->hash, &id, sizeof(id), NULL);
+}
+
+
+void h2_ihash_clear(h2_ihash_t *ih)
+{
+ apr_hash_clear(ih->hash);
+}
+
+typedef struct {
+ h2_ihash_t *ih;
+ void **buffer;
+ size_t max;
+ size_t len;
+} collect_ctx;
+
+static int collect_iter(void *x, void *val)
+{
+ collect_ctx *ctx = x;
+ if (ctx->len < ctx->max) {
+ ctx->buffer[ctx->len++] = val;
+ return 1;
+ }
+ return 0;
+}
+
+size_t h2_ihash_shift(h2_ihash_t *ih, void **buffer, size_t max)
+{
+ collect_ctx ctx;
+ size_t i;
+
+ ctx.ih = ih;
+ ctx.buffer = buffer;
+ ctx.max = max;
+ ctx.len = 0;
+ h2_ihash_iter(ih, collect_iter, &ctx);
+ for (i = 0; i < ctx.len; ++i) {
+ h2_ihash_remove_val(ih, buffer[i]);
+ }
+ return ctx.len;
+}
+
+/*******************************************************************************
+ * iqueue - sorted list of int
+ ******************************************************************************/
+
+static void iq_grow(h2_iqueue *q, int nlen);
+static void iq_swap(h2_iqueue *q, int i, int j);
+static int iq_bubble_up(h2_iqueue *q, int i, int top,
+ h2_iq_cmp *cmp, void *ctx);
+static int iq_bubble_down(h2_iqueue *q, int i, int bottom,
+ h2_iq_cmp *cmp, void *ctx);
+
+h2_iqueue *h2_iq_create(apr_pool_t *pool, int capacity)
+{
+ h2_iqueue *q = apr_pcalloc(pool, sizeof(h2_iqueue));
+ q->pool = pool;
+ iq_grow(q, capacity);
+ q->nelts = 0;
+ return q;
+}
+
+int h2_iq_empty(h2_iqueue *q)
+{
+ return q->nelts == 0;
+}
+
+int h2_iq_count(h2_iqueue *q)
+{
+ return q->nelts;
+}
+
+
+int h2_iq_add(h2_iqueue *q, int sid, h2_iq_cmp *cmp, void *ctx)
+{
+ int i;
+
+ if (h2_iq_contains(q, sid)) {
+ return 0;
+ }
+ if (q->nelts >= q->nalloc) {
+ iq_grow(q, q->nalloc * 2);
+ }
+ i = (q->head + q->nelts) % q->nalloc;
+ q->elts[i] = sid;
+ ++q->nelts;
+
+ if (cmp) {
+ /* bubble it to the front of the queue */
+ iq_bubble_up(q, i, q->head, cmp, ctx);
+ }
+ return 1;
+}
+
+int h2_iq_append(h2_iqueue *q, int sid)
+{
+ return h2_iq_add(q, sid, NULL, NULL);
+}
+
+int h2_iq_remove(h2_iqueue *q, int sid)
+{
+ int i;
+ for (i = 0; i < q->nelts; ++i) {
+ if (sid == q->elts[(q->head + i) % q->nalloc]) {
+ break;
+ }
+ }
+
+ if (i < q->nelts) {
+ ++i;
+ for (; i < q->nelts; ++i) {
+ q->elts[(q->head+i-1)%q->nalloc] = q->elts[(q->head+i)%q->nalloc];
+ }
+ --q->nelts;
+ return 1;
+ }
+ return 0;
+}
+
+void h2_iq_clear(h2_iqueue *q)
+{
+ q->nelts = 0;
+}
+
+void h2_iq_sort(h2_iqueue *q, h2_iq_cmp *cmp, void *ctx)
+{
+ /* Assume that changes in ordering are minimal. This needs,
+ * best case, q->nelts - 1 comparisons to check that nothing
+ * changed.
+ */
+ if (q->nelts > 0) {
+ int i, ni, prev, last;
+
+ /* Start at the end of the queue and create a tail of sorted
+ * entries. Make that tail one element longer in each iteration.
+ */
+ last = i = (q->head + q->nelts - 1) % q->nalloc;
+ while (i != q->head) {
+ prev = (q->nalloc + i - 1) % q->nalloc;
+
+ ni = iq_bubble_up(q, i, prev, cmp, ctx);
+ if (ni == prev) {
+ /* i bubbled one up, bubble the new i down, which
+ * keeps all ints below i sorted. */
+ iq_bubble_down(q, i, last, cmp, ctx);
+ }
+ i = prev;
+ };
+ }
+}
+
+
+int h2_iq_shift(h2_iqueue *q)
+{
+ int sid;
+
+ if (q->nelts <= 0) {
+ return 0;
+ }
+
+ sid = q->elts[q->head];
+ q->head = (q->head + 1) % q->nalloc;
+ q->nelts--;
+
+ return sid;
+}
+
+size_t h2_iq_mshift(h2_iqueue *q, int *pint, size_t max)
+{
+ size_t i;
+ for (i = 0; i < max; ++i) {
+ pint[i] = h2_iq_shift(q);
+ if (pint[i] == 0) {
+ break;
+ }
+ }
+ return i;
+}
+
+static void iq_grow(h2_iqueue *q, int nlen)
+{
+ if (nlen > q->nalloc) {
+ int *nq = apr_pcalloc(q->pool, sizeof(int) * nlen);
+ if (q->nelts > 0) {
+ int l = ((q->head + q->nelts) % q->nalloc) - q->head;
+
+ memmove(nq, q->elts + q->head, sizeof(int) * l);
+ if (l < q->nelts) {
+ /* elts wrapped, append elts in [0, remain] to nq */
+ int remain = q->nelts - l;
+ memmove(nq + l, q->elts, sizeof(int) * remain);
+ }
+ }
+ q->elts = nq;
+ q->nalloc = nlen;
+ q->head = 0;
+ }
+}
+
+static void iq_swap(h2_iqueue *q, int i, int j)
+{
+ int x = q->elts[i];
+ q->elts[i] = q->elts[j];
+ q->elts[j] = x;
+}
+
+static int iq_bubble_up(h2_iqueue *q, int i, int top,
+ h2_iq_cmp *cmp, void *ctx)
+{
+ int prev;
+ while (((prev = (q->nalloc + i - 1) % q->nalloc), i != top)
+ && (*cmp)(q->elts[i], q->elts[prev], ctx) < 0) {
+ iq_swap(q, prev, i);
+ i = prev;
+ }
+ return i;
+}
+
+static int iq_bubble_down(h2_iqueue *q, int i, int bottom,
+ h2_iq_cmp *cmp, void *ctx)
+{
+ int next;
+ while (((next = (q->nalloc + i + 1) % q->nalloc), i != bottom)
+ && (*cmp)(q->elts[i], q->elts[next], ctx) > 0) {
+ iq_swap(q, next, i);
+ i = next;
+ }
+ return i;
+}
+
+int h2_iq_contains(h2_iqueue *q, int sid)
+{
+ int i;
+ for (i = 0; i < q->nelts; ++i) {
+ if (sid == q->elts[(q->head + i) % q->nalloc]) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/*******************************************************************************
+ * FIFO queue
+ ******************************************************************************/
+
+struct h2_fifo {
+ void **elems;
+ int capacity;
+ int set;
+ int in;
+ int out;
+ int count;
+ int aborted;
+ apr_thread_mutex_t *lock;
+ apr_thread_cond_t *not_empty;
+ apr_thread_cond_t *not_full;
+};
+
+static apr_status_t fifo_destroy(void *data)
+{
+ h2_fifo *fifo = data;
+
+ apr_thread_cond_destroy(fifo->not_empty);
+ apr_thread_cond_destroy(fifo->not_full);
+ apr_thread_mutex_destroy(fifo->lock);
+
+ return APR_SUCCESS;
+}
+
+static int index_of(h2_fifo *fifo, void *elem)
+{
+ int i;
+
+ for (i = fifo->out; i != fifo->in; i = (i + 1) % fifo->capacity) {
+ if (elem == fifo->elems[i]) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+static apr_status_t create_int(h2_fifo **pfifo, apr_pool_t *pool,
+ int capacity, int as_set)
+{
+ apr_status_t rv;
+ h2_fifo *fifo;
+
+ fifo = apr_pcalloc(pool, sizeof(*fifo));
+ if (fifo == NULL) {
+ return APR_ENOMEM;
+ }
+
+ rv = apr_thread_mutex_create(&fifo->lock,
+ APR_THREAD_MUTEX_UNNESTED, pool);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ rv = apr_thread_cond_create(&fifo->not_empty, pool);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ rv = apr_thread_cond_create(&fifo->not_full, pool);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ fifo->elems = apr_pcalloc(pool, capacity * sizeof(void*));
+ if (fifo->elems == NULL) {
+ return APR_ENOMEM;
+ }
+ fifo->capacity = capacity;
+ fifo->set = as_set;
+
+ *pfifo = fifo;
+ apr_pool_cleanup_register(pool, fifo, fifo_destroy, apr_pool_cleanup_null);
+
+ return APR_SUCCESS;
+}
+
+apr_status_t h2_fifo_create(h2_fifo **pfifo, apr_pool_t *pool, int capacity)
+{
+ return create_int(pfifo, pool, capacity, 0);
+}
+
+apr_status_t h2_fifo_set_create(h2_fifo **pfifo, apr_pool_t *pool, int capacity)
+{
+ return create_int(pfifo, pool, capacity, 1);
+}
+
+apr_status_t h2_fifo_term(h2_fifo *fifo)
+{
+ apr_status_t rv;
+ if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
+ fifo->aborted = 1;
+ apr_thread_cond_broadcast(fifo->not_empty);
+ apr_thread_cond_broadcast(fifo->not_full);
+ apr_thread_mutex_unlock(fifo->lock);
+ }
+ return rv;
+}
+
+int h2_fifo_count(h2_fifo *fifo)
+{
+ int n;
+
+ apr_thread_mutex_lock(fifo->lock);
+ n = fifo->count;
+ apr_thread_mutex_unlock(fifo->lock);
+ return n;
+}
+
+static apr_status_t check_not_empty(h2_fifo *fifo, int block)
+{
+ while (fifo->count == 0) {
+ if (!block) {
+ return APR_EAGAIN;
+ }
+ if (fifo->aborted) {
+ return APR_EOF;
+ }
+ apr_thread_cond_wait(fifo->not_empty, fifo->lock);
+ }
+ return APR_SUCCESS;
+}
+
+static apr_status_t fifo_push_int(h2_fifo *fifo, void *elem, int block)
+{
+ if (fifo->aborted) {
+ return APR_EOF;
+ }
+
+ if (fifo->set && index_of(fifo, elem) >= 0) {
+ /* set mode, elem already member */
+ return APR_EEXIST;
+ }
+ else if (fifo->count == fifo->capacity) {
+ if (block) {
+ while (fifo->count == fifo->capacity) {
+ if (fifo->aborted) {
+ return APR_EOF;
+ }
+ apr_thread_cond_wait(fifo->not_full, fifo->lock);
+ }
+ }
+ else {
+ return APR_EAGAIN;
+ }
+ }
+
+ fifo->elems[fifo->in++] = elem;
+ if (fifo->in >= fifo->capacity) {
+ fifo->in -= fifo->capacity;
+ }
+ ++fifo->count;
+ if (fifo->count == 1) {
+ apr_thread_cond_signal(fifo->not_empty);
+ }
+ return APR_SUCCESS;
+}
+
+static apr_status_t fifo_push(h2_fifo *fifo, void *elem, int block)
+{
+ apr_status_t rv;
+
+ if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
+ rv = fifo_push_int(fifo, elem, block);
+ apr_thread_mutex_unlock(fifo->lock);
+ }
+ return rv;
+}
+
+apr_status_t h2_fifo_push(h2_fifo *fifo, void *elem)
+{
+ return fifo_push(fifo, elem, 1);
+}
+
+apr_status_t h2_fifo_try_push(h2_fifo *fifo, void *elem)
+{
+ return fifo_push(fifo, elem, 0);
+}
+
+static apr_status_t pull_head(h2_fifo *fifo, void **pelem, int block)
+{
+ apr_status_t rv;
+ int was_full;
+
+ if ((rv = check_not_empty(fifo, block)) != APR_SUCCESS) {
+ *pelem = NULL;
+ return rv;
+ }
+ *pelem = fifo->elems[fifo->out++];
+ if (fifo->out >= fifo->capacity) {
+ fifo->out -= fifo->capacity;
+ }
+ was_full = (fifo->count == fifo->capacity);
+ --fifo->count;
+ if (was_full) {
+ apr_thread_cond_broadcast(fifo->not_full);
+ }
+ return APR_SUCCESS;
+}
+
+static apr_status_t fifo_pull(h2_fifo *fifo, void **pelem, int block)
+{
+ apr_status_t rv;
+
+ if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
+ rv = pull_head(fifo, pelem, block);
+ apr_thread_mutex_unlock(fifo->lock);
+ }
+ return rv;
+}
+
+apr_status_t h2_fifo_pull(h2_fifo *fifo, void **pelem)
+{
+ return fifo_pull(fifo, pelem, 1);
+}
+
+apr_status_t h2_fifo_try_pull(h2_fifo *fifo, void **pelem)
+{
+ return fifo_pull(fifo, pelem, 0);
+}
+
+static apr_status_t fifo_peek(h2_fifo *fifo, h2_fifo_peek_fn *fn, void *ctx, int block)
+{
+ apr_status_t rv;
+ void *elem;
+
+ if (fifo->aborted) {
+ return APR_EOF;
+ }
+
+ if (APR_SUCCESS == (rv = apr_thread_mutex_lock(fifo->lock))) {
+ if (APR_SUCCESS == (rv = pull_head(fifo, &elem, block))) {
+ switch (fn(elem, ctx)) {
+ case H2_FIFO_OP_PULL:
+ break;
+ case H2_FIFO_OP_REPUSH:
+ rv = fifo_push_int(fifo, elem, block);
+ break;
+ }
+ }
+ apr_thread_mutex_unlock(fifo->lock);
+ }
+ return rv;
+}
+
+apr_status_t h2_fifo_peek(h2_fifo *fifo, h2_fifo_peek_fn *fn, void *ctx)
+{
+ return fifo_peek(fifo, fn, ctx, 1);
+}
+
+apr_status_t h2_fifo_try_peek(h2_fifo *fifo, h2_fifo_peek_fn *fn, void *ctx)
+{
+ return fifo_peek(fifo, fn, ctx, 0);
+}
+
+apr_status_t h2_fifo_remove(h2_fifo *fifo, void *elem)
+{
+ apr_status_t rv;
+
+ if (fifo->aborted) {
+ return APR_EOF;
+ }
+
+ if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
+ int i, last_count = fifo->count;
+
+ for (i = fifo->out; i != fifo->in; i = (i + 1) % fifo->capacity) {
+ if (fifo->elems[i] == elem) {
+ --fifo->count;
+ if (fifo->count == 0) {
+ fifo->out = fifo->in = 0;
+ }
+ else if (i == fifo->out) {
+ /* first element */
+ ++fifo->out;
+ if (fifo->out >= fifo->capacity) {
+ fifo->out -= fifo->capacity;
+ }
+ }
+ else if (((i + 1) % fifo->capacity) == fifo->in) {
+ /* last element */
+ --fifo->in;
+ if (fifo->in < 0) {
+ fifo->in += fifo->capacity;
+ }
+ }
+ else if (i > fifo->out) {
+ /* between out and in/capacity, move elements below up */
+ memmove(&fifo->elems[fifo->out+1], &fifo->elems[fifo->out],
+ (i - fifo->out) * sizeof(void*));
+ ++fifo->out;
+ if (fifo->out >= fifo->capacity) {
+ fifo->out -= fifo->capacity;
+ }
+ }
+ else {
+ /* we wrapped around, move elements above down */
+ AP_DEBUG_ASSERT((fifo->in - i - 1) > 0);
+ AP_DEBUG_ASSERT((fifo->in - i - 1) < fifo->capacity);
+ memmove(&fifo->elems[i], &fifo->elems[i + 1],
+ (fifo->in - i - 1) * sizeof(void*));
+ --fifo->in;
+ if (fifo->in < 0) {
+ fifo->in += fifo->capacity;
+ }
+ }
+ }
+ }
+ if (fifo->count != last_count) {
+ if (last_count == fifo->capacity) {
+ apr_thread_cond_broadcast(fifo->not_full);
+ }
+ rv = APR_SUCCESS;
+ }
+ else {
+ rv = APR_EAGAIN;
+ }
+
+ apr_thread_mutex_unlock(fifo->lock);
+ }
+ return rv;
+}
+
+/*******************************************************************************
+ * FIFO int queue
+ ******************************************************************************/
+
+struct h2_ififo {
+ int *elems;
+ int capacity;
+ int set;
+ int head;
+ int count;
+ int aborted;
+ apr_thread_mutex_t *lock;
+ apr_thread_cond_t *not_empty;
+ apr_thread_cond_t *not_full;
+};
+
+static int inth_index(h2_ififo *fifo, int n)
+{
+ return (fifo->head + n) % fifo->capacity;
+}
+
+static apr_status_t ififo_destroy(void *data)
+{
+ h2_ififo *fifo = data;
+
+ apr_thread_cond_destroy(fifo->not_empty);
+ apr_thread_cond_destroy(fifo->not_full);
+ apr_thread_mutex_destroy(fifo->lock);
+
+ return APR_SUCCESS;
+}
+
+static int iindex_of(h2_ififo *fifo, int id)
+{
+ int i;
+
+ for (i = 0; i < fifo->count; ++i) {
+ if (id == fifo->elems[inth_index(fifo, i)]) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+static apr_status_t icreate_int(h2_ififo **pfifo, apr_pool_t *pool,
+ int capacity, int as_set)
+{
+ apr_status_t rv;
+ h2_ififo *fifo;
+
+ fifo = apr_pcalloc(pool, sizeof(*fifo));
+ if (fifo == NULL) {
+ return APR_ENOMEM;
+ }
+
+ rv = apr_thread_mutex_create(&fifo->lock,
+ APR_THREAD_MUTEX_UNNESTED, pool);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ rv = apr_thread_cond_create(&fifo->not_empty, pool);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ rv = apr_thread_cond_create(&fifo->not_full, pool);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ fifo->elems = apr_pcalloc(pool, capacity * sizeof(int));
+ if (fifo->elems == NULL) {
+ return APR_ENOMEM;
+ }
+ fifo->capacity = capacity;
+ fifo->set = as_set;
+
+ *pfifo = fifo;
+ apr_pool_cleanup_register(pool, fifo, ififo_destroy, apr_pool_cleanup_null);
+
+ return APR_SUCCESS;
+}
+
+apr_status_t h2_ififo_create(h2_ififo **pfifo, apr_pool_t *pool, int capacity)
+{
+ return icreate_int(pfifo, pool, capacity, 0);
+}
+
+apr_status_t h2_ififo_set_create(h2_ififo **pfifo, apr_pool_t *pool, int capacity)
+{
+ return icreate_int(pfifo, pool, capacity, 1);
+}
+
+apr_status_t h2_ififo_term(h2_ififo *fifo)
+{
+ apr_status_t rv;
+ if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
+ fifo->aborted = 1;
+ apr_thread_cond_broadcast(fifo->not_empty);
+ apr_thread_cond_broadcast(fifo->not_full);
+ apr_thread_mutex_unlock(fifo->lock);
+ }
+ return rv;
+}
+
+int h2_ififo_count(h2_ififo *fifo)
+{
+ return fifo->count;
+}
+
+static apr_status_t icheck_not_empty(h2_ififo *fifo, int block)
+{
+ while (fifo->count == 0) {
+ if (!block) {
+ return APR_EAGAIN;
+ }
+ if (fifo->aborted) {
+ return APR_EOF;
+ }
+ apr_thread_cond_wait(fifo->not_empty, fifo->lock);
+ }
+ return APR_SUCCESS;
+}
+
+static apr_status_t ififo_push_int(h2_ififo *fifo, int id, int block)
+{
+ if (fifo->aborted) {
+ return APR_EOF;
+ }
+
+ if (fifo->set && iindex_of(fifo, id) >= 0) {
+ /* set mode, elem already member */
+ return APR_EEXIST;
+ }
+ else if (fifo->count == fifo->capacity) {
+ if (block) {
+ while (fifo->count == fifo->capacity) {
+ if (fifo->aborted) {
+ return APR_EOF;
+ }
+ apr_thread_cond_wait(fifo->not_full, fifo->lock);
+ }
+ }
+ else {
+ return APR_EAGAIN;
+ }
+ }
+
+ ap_assert(fifo->count < fifo->capacity);
+ fifo->elems[inth_index(fifo, fifo->count)] = id;
+ ++fifo->count;
+ if (fifo->count == 1) {
+ apr_thread_cond_broadcast(fifo->not_empty);
+ }
+ return APR_SUCCESS;
+}
+
+static apr_status_t ififo_push(h2_ififo *fifo, int id, int block)
+{
+ apr_status_t rv;
+
+ if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
+ rv = ififo_push_int(fifo, id, block);
+ apr_thread_mutex_unlock(fifo->lock);
+ }
+ return rv;
+}
+
+apr_status_t h2_ififo_push(h2_ififo *fifo, int id)
+{
+ return ififo_push(fifo, id, 1);
+}
+
+apr_status_t h2_ififo_try_push(h2_ififo *fifo, int id)
+{
+ return ififo_push(fifo, id, 0);
+}
+
+static apr_status_t ipull_head(h2_ififo *fifo, int *pi, int block)
+{
+ apr_status_t rv;
+
+ if ((rv = icheck_not_empty(fifo, block)) != APR_SUCCESS) {
+ *pi = 0;
+ return rv;
+ }
+ *pi = fifo->elems[fifo->head];
+ --fifo->count;
+ if (fifo->count > 0) {
+ fifo->head = inth_index(fifo, 1);
+ if (fifo->count+1 == fifo->capacity) {
+ apr_thread_cond_broadcast(fifo->not_full);
+ }
+ }
+ return APR_SUCCESS;
+}
+
+static apr_status_t ififo_pull(h2_ififo *fifo, int *pi, int block)
+{
+ apr_status_t rv;
+
+ if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
+ rv = ipull_head(fifo, pi, block);
+ apr_thread_mutex_unlock(fifo->lock);
+ }
+ return rv;
+}
+
+apr_status_t h2_ififo_pull(h2_ififo *fifo, int *pi)
+{
+ return ififo_pull(fifo, pi, 1);
+}
+
+apr_status_t h2_ififo_try_pull(h2_ififo *fifo, int *pi)
+{
+ return ififo_pull(fifo, pi, 0);
+}
+
+static apr_status_t ififo_peek(h2_ififo *fifo, h2_ififo_peek_fn *fn, void *ctx, int block)
+{
+ apr_status_t rv;
+ int id;
+
+ if (APR_SUCCESS == (rv = apr_thread_mutex_lock(fifo->lock))) {
+ if (APR_SUCCESS == (rv = ipull_head(fifo, &id, block))) {
+ switch (fn(id, ctx)) {
+ case H2_FIFO_OP_PULL:
+ break;
+ case H2_FIFO_OP_REPUSH:
+ rv = ififo_push_int(fifo, id, block);
+ break;
+ }
+ }
+ apr_thread_mutex_unlock(fifo->lock);
+ }
+ return rv;
+}
+
+apr_status_t h2_ififo_peek(h2_ififo *fifo, h2_ififo_peek_fn *fn, void *ctx)
+{
+ return ififo_peek(fifo, fn, ctx, 1);
+}
+
+apr_status_t h2_ififo_try_peek(h2_ififo *fifo, h2_ififo_peek_fn *fn, void *ctx)
+{
+ return ififo_peek(fifo, fn, ctx, 0);
+}
+
+static apr_status_t ififo_remove(h2_ififo *fifo, int id)
+{
+ int rc, i;
+
+ if (fifo->aborted) {
+ return APR_EOF;
+ }
+
+ rc = 0;
+ for (i = 0; i < fifo->count; ++i) {
+ int e = fifo->elems[inth_index(fifo, i)];
+ if (e == id) {
+ ++rc;
+ }
+ else if (rc) {
+ fifo->elems[inth_index(fifo, i-rc)] = e;
+ }
+ }
+ if (!rc) {
+ return APR_EAGAIN;
+ }
+ fifo->count -= rc;
+ if (fifo->count + rc == fifo->capacity) {
+ apr_thread_cond_broadcast(fifo->not_full);
+ }
+ return APR_SUCCESS;
+}
+
+apr_status_t h2_ififo_remove(h2_ififo *fifo, int id)
+{
+ apr_status_t rv;
+
+ if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
+ rv = ififo_remove(fifo, id);
+ apr_thread_mutex_unlock(fifo->lock);
+ }
+ return rv;
+}
+
+/*******************************************************************************
+ * h2_util for apt_table_t
+ ******************************************************************************/
+
+typedef struct {
+ apr_size_t bytes;
+ apr_size_t pair_extra;
+} table_bytes_ctx;
+
+static int count_bytes(void *x, const char *key, const char *value)
+{
+ table_bytes_ctx *ctx = x;
+ if (key) {
+ ctx->bytes += strlen(key);
+ }
+ if (value) {
+ ctx->bytes += strlen(value);
+ }
+ ctx->bytes += ctx->pair_extra;
+ return 1;
+}
+
+apr_size_t h2_util_table_bytes(apr_table_t *t, apr_size_t pair_extra)
+{
+ table_bytes_ctx ctx;
+
+ ctx.bytes = 0;
+ ctx.pair_extra = pair_extra;
+ apr_table_do(count_bytes, &ctx, t, NULL);
+ return ctx.bytes;
+}
+
+
+/*******************************************************************************
+ * h2_util for bucket brigades
+ ******************************************************************************/
+
+static void fit_bucket_into(apr_bucket *b, apr_off_t *plen)
+{
+ /* signed apr_off_t is at least as large as unsigned apr_size_t.
+ * Problems may arise when they are both the same size. Then
+ * the bucket length *may* be larger than a value we can hold
+ * in apr_off_t. Before casting b->length to apr_off_t we must
+ * check the limitations.
+ * After we resized the bucket, it is safe to cast and substract.
+ */
+ if ((sizeof(apr_off_t) == sizeof(apr_int64_t)
+ && b->length > APR_INT64_MAX)
+ || (sizeof(apr_off_t) == sizeof(apr_int32_t)
+ && b->length > APR_INT32_MAX)
+ || *plen < (apr_off_t)b->length) {
+ /* bucket is longer the *plen */
+ apr_bucket_split(b, *plen);
+ }
+ *plen -= (apr_off_t)b->length;
+}
+
+apr_status_t h2_brigade_concat_length(apr_bucket_brigade *dest,
+ apr_bucket_brigade *src,
+ apr_off_t length)
+{
+ apr_bucket *b;
+ apr_off_t remain = length;
+ apr_status_t status = APR_SUCCESS;
+
+ while (!APR_BRIGADE_EMPTY(src)) {
+ b = APR_BRIGADE_FIRST(src);
+
+ if (APR_BUCKET_IS_METADATA(b)) {
+ APR_BUCKET_REMOVE(b);
+ APR_BRIGADE_INSERT_TAIL(dest, b);
+ }
+ else {
+ if (remain <= 0) {
+ return status;
+ }
+ if (b->length == ((apr_size_t)-1)) {
+ const char *ign;
+ apr_size_t ilen;
+ status = apr_bucket_read(b, &ign, &ilen, APR_BLOCK_READ);
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+ }
+ fit_bucket_into(b, &remain);
+ APR_BUCKET_REMOVE(b);
+ APR_BRIGADE_INSERT_TAIL(dest, b);
+ }
+ }
+ return status;
+}
+
+apr_status_t h2_brigade_copy_length(apr_bucket_brigade *dest,
+ apr_bucket_brigade *src,
+ apr_off_t length)
+{
+ apr_bucket *b, *next;
+ apr_off_t remain = length;
+ apr_status_t status = APR_SUCCESS;
+
+ for (b = APR_BRIGADE_FIRST(src);
+ b != APR_BRIGADE_SENTINEL(src);
+ b = next) {
+ next = APR_BUCKET_NEXT(b);
+
+ if (APR_BUCKET_IS_METADATA(b)) {
+ /* fall through */
+ }
+ else {
+ if (remain <= 0) {
+ return status;
+ }
+ if (b->length == ((apr_size_t)-1)) {
+ const char *ign;
+ apr_size_t ilen;
+ status = apr_bucket_read(b, &ign, &ilen, APR_BLOCK_READ);
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+ }
+ fit_bucket_into(b, &remain);
+ }
+ status = apr_bucket_copy(b, &b);
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+ APR_BRIGADE_INSERT_TAIL(dest, b);
+ }
+ return status;
+}
+
+apr_size_t h2_util_bucket_print(char *buffer, apr_size_t bmax,
+ apr_bucket *b, const char *sep)
+{
+ apr_size_t off = 0;
+ if (sep && *sep) {
+ off += apr_snprintf(buffer+off, bmax-off, "%s", sep);
+ }
+
+ if (bmax <= off) {
+ return off;
+ }
+ else if (APR_BUCKET_IS_METADATA(b)) {
+ off += apr_snprintf(buffer+off, bmax-off, "%s", b->type->name);
+ }
+ else if (bmax > off) {
+ off += apr_snprintf(buffer+off, bmax-off, "%s[%ld]",
+ b->type->name,
+ (long)(b->length == ((apr_size_t)-1)?
+ -1 : b->length));
+ }
+ return off;
+}
+
+apr_size_t h2_util_bb_print(char *buffer, apr_size_t bmax,
+ const char *tag, const char *sep,
+ apr_bucket_brigade *bb)
+{
+ apr_size_t off = 0;
+ const char *sp = "";
+ apr_bucket *b;
+
+ if (bmax > 1) {
+ if (bb) {
+ memset(buffer, 0, bmax--);
+ off += apr_snprintf(buffer+off, bmax-off, "%s(", tag);
+ for (b = APR_BRIGADE_FIRST(bb);
+ (bmax > off) && (b != APR_BRIGADE_SENTINEL(bb));
+ b = APR_BUCKET_NEXT(b)) {
+
+ off += h2_util_bucket_print(buffer+off, bmax-off, b, sp);
+ sp = " ";
+ }
+ if (bmax > off) {
+ off += apr_snprintf(buffer+off, bmax-off, ")%s", sep);
+ }
+ }
+ else {
+ off += apr_snprintf(buffer+off, bmax-off, "%s(null)%s", tag, sep);
+ }
+ }
+ return off;
+}
+
+apr_status_t h2_append_brigade(apr_bucket_brigade *to,
+ apr_bucket_brigade *from,
+ apr_off_t *plen,
+ int *peos,
+ h2_bucket_gate *should_append)
+{
+ apr_bucket *e;
+ apr_off_t start, remain;
+ apr_status_t rv;
+
+ *peos = 0;
+ start = remain = *plen;
+
+ while (!APR_BRIGADE_EMPTY(from)) {
+ e = APR_BRIGADE_FIRST(from);
+
+ if (!should_append(e)) {
+ goto leave;
+ }
+ else if (APR_BUCKET_IS_METADATA(e)) {
+ if (APR_BUCKET_IS_EOS(e)) {
+ *peos = 1;
+ apr_bucket_delete(e);
+ continue;
+ }
+ }
+ else {
+ if (remain <= 0) {
+ goto leave;
+ }
+ if (e->length == ((apr_size_t)-1)) {
+ const char *ign;
+ apr_size_t ilen;
+ rv = apr_bucket_read(e, &ign, &ilen, APR_BLOCK_READ);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+ fit_bucket_into(e, &remain);
+ }
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(to, e);
+ }
+leave:
+ *plen = start - remain;
+ return APR_SUCCESS;
+}
+
+apr_off_t h2_brigade_mem_size(apr_bucket_brigade *bb)
+{
+ apr_bucket *b;
+ apr_off_t total = 0;
+
+ for (b = APR_BRIGADE_FIRST(bb);
+ b != APR_BRIGADE_SENTINEL(bb);
+ b = APR_BUCKET_NEXT(b))
+ {
+ total += sizeof(*b);
+ if (b->length > 0) {
+ if (APR_BUCKET_IS_HEAP(b)
+ || APR_BUCKET_IS_POOL(b)) {
+ total += b->length;
+ }
+ }
+ }
+ return total;
+}
+
+
+/*******************************************************************************
+ * h2_ngheader
+ ******************************************************************************/
+
+static int count_header(void *ctx, const char *key, const char *value)
+{
+ if (!h2_util_ignore_resp_header(key)) {
+ (*((size_t*)ctx))++;
+ }
+ return 1;
+}
+
+static const char *inv_field_name_chr(const char *token)
+{
+ const char *p = ap_scan_http_token(token);
+ if (p == token && *p == ':') {
+ p = ap_scan_http_token(++p);
+ }
+ return (p && *p)? p : NULL;
+}
+
+static const char *inv_field_value_chr(const char *token)
+{
+ const char *p = ap_scan_http_field_content(token);
+ return (p && *p)? p : NULL;
+}
+
+static void strip_field_value_ws(nghttp2_nv *nv)
+{
+ while(nv->valuelen && (nv->value[0] == ' ' || nv->value[0] == '\t')) {
+ nv->value++; nv->valuelen--;
+ }
+ while(nv->valuelen && (nv->value[nv->valuelen-1] == ' '
+ || nv->value[nv->valuelen-1] == '\t')) {
+ nv->valuelen--;
+ }
+}
+
+typedef struct ngh_ctx {
+ apr_pool_t *p;
+ int unsafe;
+ h2_ngheader *ngh;
+ apr_status_t status;
+} ngh_ctx;
+
+static int add_header(ngh_ctx *ctx, const char *key, const char *value)
+{
+ nghttp2_nv *nv = &(ctx->ngh)->nv[(ctx->ngh)->nvlen++];
+ const char *p;
+
+ if (!ctx->unsafe) {
+ if ((p = inv_field_name_chr(key))) {
+ ap_log_perror(APLOG_MARK, APLOG_TRACE1, APR_EINVAL, ctx->p,
+ "h2_request: head field '%s: %s' has invalid char %s",
+ key, value, p);
+ ctx->status = APR_EINVAL;
+ return 0;
+ }
+ if ((p = inv_field_value_chr(value))) {
+ ap_log_perror(APLOG_MARK, APLOG_TRACE1, APR_EINVAL, ctx->p,
+ "h2_request: head field '%s: %s' has invalid char %s",
+ key, value, p);
+ ctx->status = APR_EINVAL;
+ return 0;
+ }
+ }
+ nv->name = (uint8_t*)key;
+ nv->namelen = strlen(key);
+ nv->value = (uint8_t*)value;
+ nv->valuelen = strlen(value);
+ strip_field_value_ws(nv);
+
+ return 1;
+}
+
+static int add_table_header(void *ctx, const char *key, const char *value)
+{
+ if (!h2_util_ignore_resp_header(key)) {
+ add_header(ctx, key, value);
+ }
+ return 1;
+}
+
+static apr_status_t ngheader_create(h2_ngheader **ph, apr_pool_t *p,
+ int unsafe, size_t key_count,
+ const char *keys[], const char *values[],
+ apr_table_t *headers)
+{
+ ngh_ctx ctx;
+ size_t n, i;
+
+ ctx.p = p;
+ ctx.unsafe = unsafe;
+
+ n = key_count;
+ apr_table_do(count_header, &n, headers, NULL);
+
+ *ph = ctx.ngh = apr_pcalloc(p, sizeof(h2_ngheader));
+ if (!ctx.ngh) {
+ return APR_ENOMEM;
+ }
+
+ ctx.ngh->nv = apr_pcalloc(p, n * sizeof(nghttp2_nv));
+ if (!ctx.ngh->nv) {
+ return APR_ENOMEM;
+ }
+
+ ctx.status = APR_SUCCESS;
+ for (i = 0; i < key_count; ++i) {
+ if (!add_header(&ctx, keys[i], values[i])) {
+ return ctx.status;
+ }
+ }
+
+ apr_table_do(add_table_header, &ctx, headers, NULL);
+
+ return ctx.status;
+}
+
+#if AP_HAS_RESPONSE_BUCKETS
+
+static int is_unsafe(ap_bucket_response *h)
+{
+ const char *v = h->notes? apr_table_get(h->notes, H2_HDR_CONFORMANCE) : NULL;
+ return (v && !strcmp(v, H2_HDR_CONFORMANCE_UNSAFE));
+}
+
+apr_status_t h2_res_create_ngtrailer(h2_ngheader **ph, apr_pool_t *p,
+ ap_bucket_headers *headers)
+{
+ return ngheader_create(ph, p, 0,
+ 0, NULL, NULL, headers->headers);
+}
+
+apr_status_t h2_res_create_ngheader(h2_ngheader **ph, apr_pool_t *p,
+ ap_bucket_response *response)
+{
+ const char *keys[] = {
+ ":status"
+ };
+ const char *values[] = {
+ apr_psprintf(p, "%d", response->status)
+ };
+ return ngheader_create(ph, p, is_unsafe(response),
+ H2_ALEN(keys), keys, values, response->headers);
+}
+
+#else /* AP_HAS_RESPONSE_BUCKETS */
+
+static int is_unsafe(h2_headers *h)
+{
+ const char *v = h->notes? apr_table_get(h->notes, H2_HDR_CONFORMANCE) : NULL;
+ return (v && !strcmp(v, H2_HDR_CONFORMANCE_UNSAFE));
+}
+
+apr_status_t h2_res_create_ngtrailer(h2_ngheader **ph, apr_pool_t *p,
+ h2_headers *headers)
+{
+ return ngheader_create(ph, p, is_unsafe(headers),
+ 0, NULL, NULL, headers->headers);
+}
+
+apr_status_t h2_res_create_ngheader(h2_ngheader **ph, apr_pool_t *p,
+ h2_headers *headers)
+{
+ const char *keys[] = {
+ ":status"
+ };
+ const char *values[] = {
+ apr_psprintf(p, "%d", headers->status)
+ };
+ return ngheader_create(ph, p, is_unsafe(headers),
+ H2_ALEN(keys), keys, values, headers->headers);
+}
+
+#endif /* else AP_HAS_RESPONSE_BUCKETS */
+
+apr_status_t h2_req_create_ngheader(h2_ngheader **ph, apr_pool_t *p,
+ const struct h2_request *req)
+{
+
+ const char *keys[] = {
+ ":scheme",
+ ":authority",
+ ":path",
+ ":method",
+ };
+ const char *values[] = {
+ req->scheme,
+ req->authority,
+ req->path,
+ req->method,
+ };
+
+ ap_assert(req->scheme);
+ ap_assert(req->authority);
+ ap_assert(req->path);
+ ap_assert(req->method);
+
+ return ngheader_create(ph, p, 0, H2_ALEN(keys), keys, values, req->headers);
+}
+
+/*******************************************************************************
+ * header HTTP/1 <-> HTTP/2 conversions
+ ******************************************************************************/
+
+
+typedef struct {
+ const char *name;
+ size_t len;
+} literal;
+
+#define H2_DEF_LITERAL(n) { (n), (sizeof(n)-1) }
+#define H2_LIT_ARGS(a) (a),H2_ALEN(a)
+
+static literal IgnoredRequestHeaders[] = {
+ H2_DEF_LITERAL("upgrade"),
+ H2_DEF_LITERAL("connection"),
+ H2_DEF_LITERAL("keep-alive"),
+ H2_DEF_LITERAL("http2-settings"),
+ H2_DEF_LITERAL("proxy-connection"),
+ H2_DEF_LITERAL("transfer-encoding"),
+};
+static literal IgnoredRequestTrailers[] = { /* Ignore, see rfc7230, ch. 4.1.2 */
+ H2_DEF_LITERAL("te"),
+ H2_DEF_LITERAL("host"),
+ H2_DEF_LITERAL("range"),
+ H2_DEF_LITERAL("cookie"),
+ H2_DEF_LITERAL("expect"),
+ H2_DEF_LITERAL("pragma"),
+ H2_DEF_LITERAL("max-forwards"),
+ H2_DEF_LITERAL("cache-control"),
+ H2_DEF_LITERAL("authorization"),
+ H2_DEF_LITERAL("content-length"),
+ H2_DEF_LITERAL("proxy-authorization"),
+};
+static literal IgnoredResponseHeaders[] = {
+ H2_DEF_LITERAL("upgrade"),
+ H2_DEF_LITERAL("connection"),
+ H2_DEF_LITERAL("keep-alive"),
+ H2_DEF_LITERAL("transfer-encoding"),
+};
+static literal IgnoredResponseTrailers[] = {
+ H2_DEF_LITERAL("age"),
+ H2_DEF_LITERAL("date"),
+ H2_DEF_LITERAL("vary"),
+ H2_DEF_LITERAL("cookie"),
+ H2_DEF_LITERAL("expires"),
+ H2_DEF_LITERAL("warning"),
+ H2_DEF_LITERAL("location"),
+ H2_DEF_LITERAL("retry-after"),
+ H2_DEF_LITERAL("cache-control"),
+ H2_DEF_LITERAL("www-authenticate"),
+ H2_DEF_LITERAL("proxy-authenticate"),
+};
+
+static int contains_name(const literal *lits, size_t llen, nghttp2_nv *nv)
+{
+ const literal *lit;
+ size_t i;
+
+ for (i = 0; i < llen; ++i) {
+ lit = &lits[i];
+ if (lit->len == nv->namelen
+ && !apr_strnatcasecmp(lit->name, (const char *)nv->name)) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+int h2_util_ignore_resp_header(const char *name)
+{
+ nghttp2_nv nv;
+
+ nv.name = (uint8_t*)name;
+ nv.namelen = strlen(name);
+ return contains_name(H2_LIT_ARGS(IgnoredResponseHeaders), &nv);
+}
+
+
+static int h2_req_ignore_header(nghttp2_nv *nv)
+{
+ return contains_name(H2_LIT_ARGS(IgnoredRequestHeaders), nv);
+}
+
+int h2_ignore_req_trailer(const char *name, size_t len)
+{
+ nghttp2_nv nv;
+
+ nv.name = (uint8_t*)name;
+ nv.namelen = strlen(name);
+ return (h2_req_ignore_header(&nv)
+ || contains_name(H2_LIT_ARGS(IgnoredRequestTrailers), &nv));
+}
+
+int h2_ignore_resp_trailer(const char *name, size_t len)
+{
+ nghttp2_nv nv;
+
+ nv.name = (uint8_t*)name;
+ nv.namelen = strlen(name);
+ return (contains_name(H2_LIT_ARGS(IgnoredResponseHeaders), &nv)
+ || contains_name(H2_LIT_ARGS(IgnoredResponseTrailers), &nv));
+}
+
+static apr_status_t req_add_header(apr_table_t *headers, apr_pool_t *pool,
+ nghttp2_nv *nv, size_t max_field_len,
+ int *pwas_added)
+{
+ char *hname, *hvalue;
+ const char *existing;
+
+ *pwas_added = 0;
+ strip_field_value_ws(nv);
+
+ if (h2_req_ignore_header(nv)) {
+ return APR_SUCCESS;
+ }
+ else if (nv->namelen == sizeof("cookie")-1
+ && !apr_strnatcasecmp("cookie", (const char *)nv->name)) {
+ existing = apr_table_get(headers, "cookie");
+ if (existing) {
+ /* Cookie header come separately in HTTP/2, but need
+ * to be merged by "; " (instead of default ", ")
+ */
+ if (max_field_len
+ && strlen(existing) + nv->valuelen + nv->namelen + 4
+ > max_field_len) {
+ /* "key: oldval, nval" is too long */
+ return APR_EINVAL;
+ }
+ hvalue = apr_pstrndup(pool, (const char*)nv->value, nv->valuelen);
+ apr_table_setn(headers, "Cookie",
+ apr_psprintf(pool, "%s; %s", existing, hvalue));
+ return APR_SUCCESS;
+ }
+ }
+ else if (nv->namelen == sizeof("host")-1
+ && !apr_strnatcasecmp("host", (const char *)nv->name)) {
+ if (apr_table_get(headers, "Host")) {
+ return APR_SUCCESS; /* ignore duplicate */
+ }
+ }
+
+ hname = apr_pstrndup(pool, (const char*)nv->name, nv->namelen);
+ h2_util_camel_case_header(hname, nv->namelen);
+ existing = apr_table_get(headers, hname);
+ if (max_field_len) {
+ if ((existing? strlen(existing)+2 : 0) + nv->valuelen + nv->namelen + 2
+ > max_field_len) {
+ /* "key: (oldval, )?nval" is too long */
+ return APR_EINVAL;
+ }
+ }
+ if (!existing) *pwas_added = 1;
+ hvalue = apr_pstrndup(pool, (const char*)nv->value, nv->valuelen);
+ apr_table_mergen(headers, hname, hvalue);
+
+ return APR_SUCCESS;
+}
+
+apr_status_t h2_req_add_header(apr_table_t *headers, apr_pool_t *pool,
+ const char *name, size_t nlen,
+ const char *value, size_t vlen,
+ size_t max_field_len, int *pwas_added)
+{
+ nghttp2_nv nv;
+
+ nv.name = (uint8_t*)name;
+ nv.namelen = nlen;
+ nv.value = (uint8_t*)value;
+ nv.valuelen = vlen;
+ return req_add_header(headers, pool, &nv, max_field_len, pwas_added);
+}
+
+/*******************************************************************************
+ * frame logging
+ ******************************************************************************/
+
+int h2_util_frame_print(const nghttp2_frame *frame, char *buffer, size_t maxlen)
+{
+ char scratch[128];
+ size_t s_len = sizeof(scratch)/sizeof(scratch[0]);
+
+ switch (frame->hd.type) {
+ case NGHTTP2_DATA: {
+ return apr_snprintf(buffer, maxlen,
+ "DATA[length=%d, flags=%d, stream=%d, padlen=%d]",
+ (int)frame->hd.length, frame->hd.flags,
+ frame->hd.stream_id, (int)frame->data.padlen);
+ }
+ case NGHTTP2_HEADERS: {
+ return apr_snprintf(buffer, maxlen,
+ "HEADERS[length=%d, hend=%d, stream=%d, eos=%d]",
+ (int)frame->hd.length,
+ !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS),
+ frame->hd.stream_id,
+ !!(frame->hd.flags & NGHTTP2_FLAG_END_STREAM));
+ }
+ case NGHTTP2_PRIORITY: {
+ return apr_snprintf(buffer, maxlen,
+ "PRIORITY[length=%d, flags=%d, stream=%d]",
+ (int)frame->hd.length,
+ frame->hd.flags, frame->hd.stream_id);
+ }
+ case NGHTTP2_RST_STREAM: {
+ return apr_snprintf(buffer, maxlen,
+ "RST_STREAM[length=%d, flags=%d, stream=%d]",
+ (int)frame->hd.length,
+ frame->hd.flags, frame->hd.stream_id);
+ }
+ case NGHTTP2_SETTINGS: {
+ if (frame->hd.flags & NGHTTP2_FLAG_ACK) {
+ return apr_snprintf(buffer, maxlen,
+ "SETTINGS[ack=1, stream=%d]",
+ frame->hd.stream_id);
+ }
+ return apr_snprintf(buffer, maxlen,
+ "SETTINGS[length=%d, stream=%d]",
+ (int)frame->hd.length, frame->hd.stream_id);
+ }
+ case NGHTTP2_PUSH_PROMISE: {
+ return apr_snprintf(buffer, maxlen,
+ "PUSH_PROMISE[length=%d, hend=%d, stream=%d]",
+ (int)frame->hd.length,
+ !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS),
+ frame->hd.stream_id);
+ }
+ case NGHTTP2_PING: {
+ return apr_snprintf(buffer, maxlen,
+ "PING[length=%d, ack=%d, stream=%d]",
+ (int)frame->hd.length,
+ frame->hd.flags&NGHTTP2_FLAG_ACK,
+ frame->hd.stream_id);
+ }
+ case NGHTTP2_GOAWAY: {
+ size_t len = (frame->goaway.opaque_data_len < s_len)?
+ frame->goaway.opaque_data_len : s_len-1;
+ if (len)
+ memcpy(scratch, frame->goaway.opaque_data, len);
+ scratch[len] = '\0';
+ return apr_snprintf(buffer, maxlen, "GOAWAY[error=%d, reason='%s', "
+ "last_stream=%d]", frame->goaway.error_code,
+ scratch, frame->goaway.last_stream_id);
+ }
+ case NGHTTP2_WINDOW_UPDATE: {
+ return apr_snprintf(buffer, maxlen,
+ "WINDOW_UPDATE[stream=%d, incr=%d]",
+ frame->hd.stream_id,
+ frame->window_update.window_size_increment);
+ }
+ default:
+ return apr_snprintf(buffer, maxlen,
+ "type=%d[length=%d, flags=%d, stream=%d]",
+ frame->hd.type, (int)frame->hd.length,
+ frame->hd.flags, frame->hd.stream_id);
+ }
+}
+
+/*******************************************************************************
+ * push policy
+ ******************************************************************************/
+int h2_push_policy_determine(apr_table_t *headers, apr_pool_t *p, int push_enabled)
+{
+ h2_push_policy policy = H2_PUSH_NONE;
+ if (push_enabled) {
+ const char *val = apr_table_get(headers, "accept-push-policy");
+ if (val) {
+ if (ap_find_token(p, val, "fast-load")) {
+ policy = H2_PUSH_FAST_LOAD;
+ }
+ else if (ap_find_token(p, val, "head")) {
+ policy = H2_PUSH_HEAD;
+ }
+ else if (ap_find_token(p, val, "default")) {
+ policy = H2_PUSH_DEFAULT;
+ }
+ else if (ap_find_token(p, val, "none")) {
+ policy = H2_PUSH_NONE;
+ }
+ else {
+ /* nothing known found in this header, go by default */
+ policy = H2_PUSH_DEFAULT;
+ }
+ }
+ else {
+ policy = H2_PUSH_DEFAULT;
+ }
+ }
+ return policy;
+}
+
+void h2_util_drain_pipe(apr_file_t *pipe)
+{
+ char rb[512];
+ apr_size_t nr = sizeof(rb);
+
+ while (apr_file_read(pipe, rb, &nr) == APR_SUCCESS) {
+ /* Although we write just one byte to the other end of the pipe
+ * during wakeup, multiple threads could call the wakeup.
+ * So simply drain out from the input side of the pipe all
+ * the data.
+ */
+ if (nr != sizeof(rb))
+ break;
+ }
+}
+
+apr_status_t h2_util_wait_on_pipe(apr_file_t *pipe)
+{
+ char rb[512];
+ apr_size_t nr = sizeof(rb);
+
+ return apr_file_read(pipe, rb, &nr);
+}
+
+#if AP_HAS_RESPONSE_BUCKETS
+
+static int add_header_lengths(void *ctx, const char *name, const char *value)
+{
+ apr_size_t *plen = ctx;
+ *plen += strlen(name) + strlen(value);
+ return 1;
+}
+
+apr_size_t headers_length_estimate(ap_bucket_headers *hdrs)
+{
+ apr_size_t len = 0;
+ apr_table_do(add_header_lengths, &len, hdrs->headers, NULL);
+ return len;
+}
+
+apr_size_t response_length_estimate(ap_bucket_response *resp)
+{
+ apr_size_t len = 3 + 1 + 8 + (resp->reason? strlen(resp->reason) : 10);
+ apr_table_do(add_header_lengths, &len, resp->headers, NULL);
+ return len;
+}
+
+#endif /* AP_HAS_RESPONSE_BUCKETS */
diff --git a/modules/http2/h2_util.h b/modules/http2/h2_util.h
new file mode 100644
index 0000000..d2e6548
--- /dev/null
+++ b/modules/http2/h2_util.h
@@ -0,0 +1,519 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __mod_h2__h2_util__
+#define __mod_h2__h2_util__
+
+#include <nghttp2/nghttp2.h>
+#include <http_protocol.h>
+
+#include "h2.h"
+#include "h2_headers.h"
+
+/*******************************************************************************
+ * some debugging/format helpers
+ ******************************************************************************/
+struct h2_request;
+struct nghttp2_frame;
+
+size_t h2_util_hex_dump(char *buffer, size_t maxlen,
+ const char *data, size_t datalen);
+
+void h2_util_camel_case_header(char *s, size_t len);
+
+int h2_util_frame_print(const nghttp2_frame *frame, char *buffer, size_t maxlen);
+
+/*******************************************************************************
+ * ihash - hash for structs with int identifier
+ ******************************************************************************/
+typedef struct h2_ihash_t h2_ihash_t;
+typedef int h2_ihash_iter_t(void *ctx, void *val);
+
+/**
+ * Create a hash for structures that have an identifying int member.
+ * @param pool the pool to use
+ * @param offset_of_int the offsetof() the int member in the struct
+ */
+h2_ihash_t *h2_ihash_create(apr_pool_t *pool, size_t offset_of_int);
+
+unsigned int h2_ihash_count(h2_ihash_t *ih);
+int h2_ihash_empty(h2_ihash_t *ih);
+void *h2_ihash_get(h2_ihash_t *ih, int id);
+
+/**
+ * Iterate over the hash members (without defined order) and invoke
+ * fn for each member until 0 is returned.
+ * @param ih the hash to iterate over
+ * @param fn the function to invoke on each member
+ * @param ctx user supplied data passed into each iteration call
+ * @return 0 if one iteration returned 0, otherwise != 0
+ */
+int h2_ihash_iter(h2_ihash_t *ih, h2_ihash_iter_t *fn, void *ctx);
+
+void h2_ihash_add(h2_ihash_t *ih, void *val);
+void h2_ihash_remove(h2_ihash_t *ih, int id);
+void h2_ihash_remove_val(h2_ihash_t *ih, void *val);
+void h2_ihash_clear(h2_ihash_t *ih);
+
+size_t h2_ihash_shift(h2_ihash_t *ih, void **buffer, size_t max);
+
+/*******************************************************************************
+ * iqueue - sorted list of int with user defined ordering
+ ******************************************************************************/
+typedef struct h2_iqueue {
+ int *elts;
+ int head;
+ int nelts;
+ int nalloc;
+ apr_pool_t *pool;
+} h2_iqueue;
+
+/**
+ * Comparator for two int to determine their order.
+ *
+ * @param i1 first int to compare
+ * @param i2 second int to compare
+ * @param ctx provided user data
+ * @return value is the same as for strcmp() and has the effect:
+ * == 0: s1 and s2 are treated equal in ordering
+ * < 0: s1 should be sorted before s2
+ * > 0: s2 should be sorted before s1
+ */
+typedef int h2_iq_cmp(int i1, int i2, void *ctx);
+
+/**
+ * Allocate a new queue from the pool and initialize.
+ * @param pool the memory pool
+ * @param capacity the initial capacity of the queue
+ */
+h2_iqueue *h2_iq_create(apr_pool_t *pool, int capacity);
+
+/**
+ * Return != 0 iff there are no ints in the queue.
+ * @param q the queue to check
+ */
+int h2_iq_empty(h2_iqueue *q);
+
+/**
+ * Return the number of int in the queue.
+ * @param q the queue to get size on
+ */
+int h2_iq_count(h2_iqueue *q);
+
+/**
+ * Add a stream id to the queue.
+ *
+ * @param q the queue to append the id to
+ * @param sid the stream id to add
+ * @param cmp the comparator for sorting
+ * @param ctx user data for comparator
+ * @return != 0 iff id was not already there
+ */
+int h2_iq_add(h2_iqueue *q, int sid, h2_iq_cmp *cmp, void *ctx);
+
+/**
+ * Append the id to the queue if not already present.
+ *
+ * @param q the queue to append the id to
+ * @param sid the id to append
+ * @return != 0 iff id was not already there
+ */
+int h2_iq_append(h2_iqueue *q, int sid);
+
+/**
+ * Remove the int from the queue. Return != 0 iff it was found.
+ * @param q the queue
+ * @param sid the stream id to remove
+ * @return != 0 iff int was found in queue
+ */
+int h2_iq_remove(h2_iqueue *q, int sid);
+
+/**
+ * Remove all entries in the queue.
+ */
+void h2_iq_clear(h2_iqueue *q);
+
+/**
+ * Sort the stream idqueue again. Call if the int ordering
+ * has changed.
+ *
+ * @param q the queue to sort
+ * @param cmp the comparator for sorting
+ * @param ctx user data for the comparator
+ */
+void h2_iq_sort(h2_iqueue *q, h2_iq_cmp *cmp, void *ctx);
+
+/**
+ * Get the first id from the queue or 0 if the queue is empty.
+ * The id is being removed.
+ *
+ * @param q the queue to get the first id from
+ * @return the first id of the queue, 0 if empty
+ */
+int h2_iq_shift(h2_iqueue *q);
+
+/**
+ * Get the first max ids from the queue. All these ids will be removed.
+ *
+ * @param q the queue to get the first ids from
+ * @param pint the int array to receive the values
+ * @param max the maximum number of ids to shift
+ * @return the actual number of ids shifted
+ */
+size_t h2_iq_mshift(h2_iqueue *q, int *pint, size_t max);
+
+/**
+ * Determine if int is in the queue already
+ *
+ * @param q the queue
+ * @param sid the integer id to check for
+ * @return != 0 iff sid is already in the queue
+ */
+int h2_iq_contains(h2_iqueue *q, int sid);
+
+/*******************************************************************************
+ * FIFO queue (void* elements)
+ ******************************************************************************/
+
+/**
+ * A thread-safe FIFO queue with some extra bells and whistles, if you
+ * do not need anything special, better use 'apr_queue'.
+ */
+typedef struct h2_fifo h2_fifo;
+
+/**
+ * Create a FIFO queue that can hold up to capacity elements. Elements can
+ * appear several times.
+ */
+apr_status_t h2_fifo_create(h2_fifo **pfifo, apr_pool_t *pool, int capacity);
+
+/**
+ * Create a FIFO set that can hold up to capacity elements. Elements only
+ * appear once. Pushing an element already present does not change the
+ * queue and is successful.
+ */
+apr_status_t h2_fifo_set_create(h2_fifo **pfifo, apr_pool_t *pool, int capacity);
+
+apr_status_t h2_fifo_term(h2_fifo *fifo);
+
+int h2_fifo_count(h2_fifo *fifo);
+
+/**
+ * Push en element into the queue. Blocks if there is no capacity left.
+ *
+ * @param fifo the FIFO queue
+ * @param elem the element to push
+ * @return APR_SUCCESS on push, APR_EAGAIN on try_push on a full queue,
+ * APR_EEXIST when in set mode and elem already there.
+ */
+apr_status_t h2_fifo_push(h2_fifo *fifo, void *elem);
+apr_status_t h2_fifo_try_push(h2_fifo *fifo, void *elem);
+
+apr_status_t h2_fifo_pull(h2_fifo *fifo, void **pelem);
+apr_status_t h2_fifo_try_pull(h2_fifo *fifo, void **pelem);
+
+typedef enum {
+ H2_FIFO_OP_PULL, /* pull the element from the queue, ie discard it */
+ H2_FIFO_OP_REPUSH, /* pull and immediately re-push it */
+} h2_fifo_op_t;
+
+typedef h2_fifo_op_t h2_fifo_peek_fn(void *head, void *ctx);
+
+/**
+ * Call given function on the head of the queue, once it exists, and
+ * perform the returned operation on it. The queue will hold its lock during
+ * this time, so no other operations on the queue are possible.
+ * @param fifo the queue to peek at
+ * @param fn the function to call on the head, once available
+ * @param ctx context to pass in call to function
+ */
+apr_status_t h2_fifo_peek(h2_fifo *fifo, h2_fifo_peek_fn *fn, void *ctx);
+
+/**
+ * Non-blocking version of h2_fifo_peek.
+ */
+apr_status_t h2_fifo_try_peek(h2_fifo *fifo, h2_fifo_peek_fn *fn, void *ctx);
+
+/**
+ * Remove the elem from the queue, will remove multiple appearances.
+ * @param elem the element to remove
+ * @return APR_SUCCESS iff > 0 elems were removed, APR_EAGAIN otherwise.
+ */
+apr_status_t h2_fifo_remove(h2_fifo *fifo, void *elem);
+
+/*******************************************************************************
+ * iFIFO queue (int elements)
+ ******************************************************************************/
+
+/**
+ * A thread-safe FIFO queue with some extra bells and whistles, if you
+ * do not need anything special, better use 'apr_queue'.
+ */
+typedef struct h2_ififo h2_ififo;
+
+/**
+ * Create a FIFO queue that can hold up to capacity int. ints can
+ * appear several times.
+ */
+apr_status_t h2_ififo_create(h2_ififo **pfifo, apr_pool_t *pool, int capacity);
+
+/**
+ * Create a FIFO set that can hold up to capacity integers. Ints only
+ * appear once. Pushing an int already present does not change the
+ * queue and is successful.
+ */
+apr_status_t h2_ififo_set_create(h2_ififo **pfifo, apr_pool_t *pool, int capacity);
+
+apr_status_t h2_ififo_term(h2_ififo *fifo);
+
+int h2_ififo_count(h2_ififo *fifo);
+
+/**
+ * Push an int into the queue. Blocks if there is no capacity left.
+ *
+ * @param fifo the FIFO queue
+ * @param id the int to push
+ * @return APR_SUCCESS on push, APR_EAGAIN on try_push on a full queue,
+ * APR_EEXIST when in set mode and elem already there.
+ */
+apr_status_t h2_ififo_push(h2_ififo *fifo, int id);
+apr_status_t h2_ififo_try_push(h2_ififo *fifo, int id);
+
+apr_status_t h2_ififo_pull(h2_ififo *fifo, int *pi);
+apr_status_t h2_ififo_try_pull(h2_ififo *fifo, int *pi);
+
+typedef h2_fifo_op_t h2_ififo_peek_fn(int head, void *ctx);
+
+/**
+ * Call given function on the head of the queue, once it exists, and
+ * perform the returned operation on it. The queue will hold its lock during
+ * this time, so no other operations on the queue are possible.
+ * @param fifo the queue to peek at
+ * @param fn the function to call on the head, once available
+ * @param ctx context to pass in call to function
+ */
+apr_status_t h2_ififo_peek(h2_ififo *fifo, h2_ififo_peek_fn *fn, void *ctx);
+
+/**
+ * Non-blocking version of h2_fifo_peek.
+ */
+apr_status_t h2_ififo_try_peek(h2_ififo *fifo, h2_ififo_peek_fn *fn, void *ctx);
+
+/**
+ * Remove the integer from the queue, will remove multiple appearances.
+ * @param id the integer to remove
+ * @return APR_SUCCESS iff > 0 ints were removed, APR_EAGAIN otherwise.
+ */
+apr_status_t h2_ififo_remove(h2_ififo *fifo, int id);
+
+/*******************************************************************************
+ * common helpers
+ ******************************************************************************/
+/* h2_log2(n) iff n is a power of 2 */
+unsigned char h2_log2(int n);
+
+/**
+ * Count the bytes that all key/value pairs in a table have
+ * in length (exlucding terminating 0s), plus additional extra per pair.
+ *
+ * @param t the table to inspect
+ * @param pair_extra the extra amount to add per pair
+ * @return the number of bytes all key/value pairs have
+ */
+apr_size_t h2_util_table_bytes(apr_table_t *t, apr_size_t pair_extra);
+
+/** Match a header value against a string constance, case insensitive */
+#define H2_HD_MATCH_LIT(l, name, nlen) \
+ ((nlen == sizeof(l) - 1) && !apr_strnatcasecmp(l, name))
+
+/*******************************************************************************
+ * HTTP/2 header helpers
+ ******************************************************************************/
+int h2_ignore_req_trailer(const char *name, size_t len);
+int h2_ignore_resp_trailer(const char *name, size_t len);
+
+/**
+ * Set the push policy for the given request. Takes request headers into
+ * account, see draft https://tools.ietf.org/html/draft-ruellan-http-accept-push-policy-00
+ * for details.
+ *
+ * @param headers the http headers to inspect
+ * @param p the pool to use
+ * @param push_enabled if HTTP/2 server push is generally enabled for this request
+ * @return the push policy desired
+ */
+int h2_push_policy_determine(apr_table_t *headers, apr_pool_t *p, int push_enabled);
+
+/*******************************************************************************
+ * base64 url encoding, different table from normal base64
+ ******************************************************************************/
+/**
+ * I always wanted to write my own base64url decoder...not. See
+ * https://tools.ietf.org/html/rfc4648#section-5 for description.
+ */
+apr_size_t h2_util_base64url_decode(const char **decoded,
+ const char *encoded,
+ apr_pool_t *pool);
+const char *h2_util_base64url_encode(const char *data,
+ apr_size_t len, apr_pool_t *pool);
+
+/*******************************************************************************
+ * nghttp2 helpers
+ ******************************************************************************/
+
+int h2_util_ignore_resp_header(const char *name);
+
+typedef struct h2_ngheader {
+ nghttp2_nv *nv;
+ apr_size_t nvlen;
+} h2_ngheader;
+
+#if AP_HAS_RESPONSE_BUCKETS
+apr_status_t h2_res_create_ngtrailer(h2_ngheader **ph, apr_pool_t *p,
+ ap_bucket_headers *headers);
+apr_status_t h2_res_create_ngheader(h2_ngheader **ph, apr_pool_t *p,
+ ap_bucket_response *response);
+apr_status_t h2_req_create_ngheader(h2_ngheader **ph, apr_pool_t *p,
+ const struct h2_request *req);
+#else
+apr_status_t h2_res_create_ngtrailer(h2_ngheader **ph, apr_pool_t *p,
+ struct h2_headers *headers);
+apr_status_t h2_res_create_ngheader(h2_ngheader **ph, apr_pool_t *p,
+ struct h2_headers *headers);
+apr_status_t h2_req_create_ngheader(h2_ngheader **ph, apr_pool_t *p,
+ const struct h2_request *req);
+#endif
+
+/**
+ * Add a HTTP/2 header and return the table key if it really was added
+ * and not ignored.
+ */
+apr_status_t h2_req_add_header(apr_table_t *headers, apr_pool_t *pool,
+ const char *name, size_t nlen,
+ const char *value, size_t vlen,
+ size_t max_field_len, int *pwas_added);
+
+/*******************************************************************************
+ * apr brigade helpers
+ ******************************************************************************/
+
+/**
+ * Concatenate at most length bytes from src to dest brigade, splitting
+ * buckets if necessary and reading buckets of indeterminate length.
+ */
+apr_status_t h2_brigade_concat_length(apr_bucket_brigade *dest,
+ apr_bucket_brigade *src,
+ apr_off_t length);
+
+/**
+ * Copy at most length bytes from src to dest brigade, splitting
+ * buckets if necessary and reading buckets of indeterminate length.
+ */
+apr_status_t h2_brigade_copy_length(apr_bucket_brigade *dest,
+ apr_bucket_brigade *src,
+ apr_off_t length);
+
+typedef apr_status_t h2_util_pass_cb(void *ctx,
+ const char *data, apr_off_t len);
+
+/**
+ * Print a bucket's meta data (type and length) to the buffer.
+ * @return number of characters printed
+ */
+apr_size_t h2_util_bucket_print(char *buffer, apr_size_t bmax,
+ apr_bucket *b, const char *sep);
+
+/**
+ * Prints the brigade bucket types and lengths into the given buffer
+ * up to bmax.
+ * @return number of characters printed
+ */
+apr_size_t h2_util_bb_print(char *buffer, apr_size_t bmax,
+ const char *tag, const char *sep,
+ apr_bucket_brigade *bb);
+/**
+ * Logs the bucket brigade (which bucket types with what length)
+ * to the log at the given level.
+ * @param c the connection to log for
+ * @param sid the stream identifier this brigade belongs to
+ * @param level the log level (as in APLOG_*)
+ * @param tag a short message text about the context
+ * @param bb the brigade to log
+ */
+#define h2_util_bb_log(c, sid, level, tag, bb) \
+if (APLOG_C_IS_LEVEL(c, level)) { \
+ do { \
+ char buffer[4 * 1024]; \
+ const char *line = "(null)"; \
+ apr_size_t len, bmax = sizeof(buffer)/sizeof(buffer[0]); \
+ len = h2_util_bb_print(buffer, bmax, (tag), "", (bb)); \
+ ap_log_cerror(APLOG_MARK, level, 0, (c), "bb_dump(%ld): %s", \
+ ((c)->master? (c)->master->id : (c)->id), (len? buffer : line)); \
+ } while(0); \
+}
+
+
+typedef int h2_bucket_gate(apr_bucket *b);
+/**
+ * Transfer buckets from one brigade to another with a limit on the
+ * maximum amount of bytes transferred. Does no setaside magic, lifetime
+ * of brigades must fit.
+ * @param to brigade to transfer buckets to
+ * @param from brigades to remove buckets from
+ * @param plen maximum bytes to transfer, actual bytes transferred
+ * @param peos if an EOS bucket was transferred
+ */
+apr_status_t h2_append_brigade(apr_bucket_brigade *to,
+ apr_bucket_brigade *from,
+ apr_off_t *plen,
+ int *peos,
+ h2_bucket_gate *should_append);
+
+/**
+ * Get an approximnation of the memory footprint of the given
+ * brigade. This varies from apr_brigade_length as
+ * - no buckets are ever read
+ * - only buckets known to allocate memory (HEAP+POOL) are counted
+ * - the bucket struct itself is counted
+ */
+apr_off_t h2_brigade_mem_size(apr_bucket_brigade *bb);
+
+/**
+ * Drain a pipe used for notification.
+ */
+void h2_util_drain_pipe(apr_file_t *pipe);
+
+/**
+ * Wait on data arriving on a pipe.
+ */
+apr_status_t h2_util_wait_on_pipe(apr_file_t *pipe);
+
+
+#if AP_HAS_RESPONSE_BUCKETS
+/**
+ * Give an estimate of the length of the header fields,
+ * without compression or other formatting decorations.
+ */
+apr_size_t headers_length_estimate(ap_bucket_headers *hdrs);
+
+/**
+ * Give an estimate of the length of the response meta data size,
+ * without compression or other formatting decorations.
+ */
+apr_size_t response_length_estimate(ap_bucket_response *resp);
+#endif /* AP_HAS_RESPONSE_BUCKETS */
+
+#endif /* defined(__mod_h2__h2_util__) */
diff --git a/modules/http2/h2_version.h b/modules/http2/h2_version.h
new file mode 100644
index 0000000..c961089
--- /dev/null
+++ b/modules/http2/h2_version.h
@@ -0,0 +1,41 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mod_h2_h2_version_h
+#define mod_h2_h2_version_h
+
+#undef PACKAGE_VERSION
+#undef PACKAGE_TARNAME
+#undef PACKAGE_STRING
+#undef PACKAGE_NAME
+#undef PACKAGE_BUGREPORT
+
+/**
+ * @macro
+ * Version number of the http2 module as c string
+ */
+#define MOD_HTTP2_VERSION "2.0.11"
+
+/**
+ * @macro
+ * Numerical representation of the version number of the http2 module
+ * release. This is a 24 bit number with 8 bits for major number, 8 bits
+ * for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203.
+ */
+#define MOD_HTTP2_VERSION_NUM 0x02000b
+
+
+#endif /* mod_h2_h2_version_h */
diff --git a/modules/http2/h2_workers.c b/modules/http2/h2_workers.c
new file mode 100644
index 0000000..e7e2039
--- /dev/null
+++ b/modules/http2/h2_workers.c
@@ -0,0 +1,626 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <apr_ring.h>
+#include <apr_thread_mutex.h>
+#include <apr_thread_cond.h>
+
+#include <mpm_common.h>
+#include <httpd.h>
+#include <http_connection.h>
+#include <http_core.h>
+#include <http_log.h>
+#include <http_protocol.h>
+
+#include "h2.h"
+#include "h2_private.h"
+#include "h2_mplx.h"
+#include "h2_c2.h"
+#include "h2_workers.h"
+#include "h2_util.h"
+
+typedef enum {
+ PROD_IDLE,
+ PROD_ACTIVE,
+ PROD_JOINED,
+} prod_state_t;
+
+struct ap_conn_producer_t {
+ APR_RING_ENTRY(ap_conn_producer_t) link;
+ const char *name;
+ void *baton;
+ ap_conn_producer_next *fn_next;
+ ap_conn_producer_done *fn_done;
+ ap_conn_producer_shutdown *fn_shutdown;
+ volatile prod_state_t state;
+ volatile int conns_active;
+};
+
+
+typedef enum {
+ H2_SLOT_FREE,
+ H2_SLOT_RUN,
+ H2_SLOT_ZOMBIE,
+} h2_slot_state_t;
+
+typedef struct h2_slot h2_slot;
+struct h2_slot {
+ APR_RING_ENTRY(h2_slot) link;
+ apr_uint32_t id;
+ apr_pool_t *pool;
+ h2_slot_state_t state;
+ volatile int should_shutdown;
+ volatile int is_idle;
+ h2_workers *workers;
+ ap_conn_producer_t *prod;
+ apr_thread_t *thread;
+ struct apr_thread_cond_t *more_work;
+ int activations;
+};
+
+struct h2_workers {
+ server_rec *s;
+ apr_pool_t *pool;
+
+ apr_uint32_t max_slots;
+ apr_uint32_t min_active;
+ volatile apr_time_t idle_limit;
+ volatile int aborted;
+ volatile int shutdown;
+ int dynamic;
+
+ volatile apr_uint32_t active_slots;
+ volatile apr_uint32_t idle_slots;
+
+ apr_threadattr_t *thread_attr;
+ h2_slot *slots;
+
+ APR_RING_HEAD(h2_slots_free, h2_slot) free;
+ APR_RING_HEAD(h2_slots_idle, h2_slot) idle;
+ APR_RING_HEAD(h2_slots_busy, h2_slot) busy;
+ APR_RING_HEAD(h2_slots_zombie, h2_slot) zombie;
+
+ APR_RING_HEAD(ap_conn_producer_active, ap_conn_producer_t) prod_active;
+ APR_RING_HEAD(ap_conn_producer_idle, ap_conn_producer_t) prod_idle;
+
+ struct apr_thread_mutex_t *lock;
+ struct apr_thread_cond_t *prod_done;
+ struct apr_thread_cond_t *all_done;
+};
+
+
+static void* APR_THREAD_FUNC slot_run(apr_thread_t *thread, void *wctx);
+
+static apr_status_t activate_slot(h2_workers *workers)
+{
+ h2_slot *slot;
+ apr_pool_t *pool;
+ apr_status_t rv;
+
+ if (APR_RING_EMPTY(&workers->free, h2_slot, link)) {
+ return APR_EAGAIN;
+ }
+ slot = APR_RING_FIRST(&workers->free);
+ ap_assert(slot->state == H2_SLOT_FREE);
+ APR_RING_REMOVE(slot, link);
+
+ ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, workers->s,
+ "h2_workers: activate slot %d", slot->id);
+
+ slot->state = H2_SLOT_RUN;
+ slot->should_shutdown = 0;
+ slot->is_idle = 0;
+ slot->pool = NULL;
+ ++workers->active_slots;
+ rv = apr_pool_create(&pool, workers->pool);
+ if (APR_SUCCESS != rv) goto cleanup;
+ apr_pool_tag(pool, "h2_worker_slot");
+ slot->pool = pool;
+
+ rv = ap_thread_create(&slot->thread, workers->thread_attr,
+ slot_run, slot, slot->pool);
+
+cleanup:
+ if (rv != APR_SUCCESS) {
+ AP_DEBUG_ASSERT(0);
+ slot->state = H2_SLOT_FREE;
+ if (slot->pool) {
+ apr_pool_destroy(slot->pool);
+ slot->pool = NULL;
+ }
+ APR_RING_INSERT_TAIL(&workers->free, slot, h2_slot, link);
+ --workers->active_slots;
+ }
+ return rv;
+}
+
+static void join_zombies(h2_workers *workers)
+{
+ h2_slot *slot;
+ apr_status_t status;
+
+ while (!APR_RING_EMPTY(&workers->zombie, h2_slot, link)) {
+ slot = APR_RING_FIRST(&workers->zombie);
+ APR_RING_REMOVE(slot, link);
+ ap_assert(slot->state == H2_SLOT_ZOMBIE);
+ ap_assert(slot->thread != NULL);
+
+ apr_thread_mutex_unlock(workers->lock);
+ apr_thread_join(&status, slot->thread);
+ apr_thread_mutex_lock(workers->lock);
+
+ slot->thread = NULL;
+ slot->state = H2_SLOT_FREE;
+ if (slot->pool) {
+ apr_pool_destroy(slot->pool);
+ slot->pool = NULL;
+ }
+ APR_RING_INSERT_TAIL(&workers->free, slot, h2_slot, link);
+ }
+}
+
+static void wake_idle_worker(h2_workers *workers, ap_conn_producer_t *prod)
+{
+ if (!APR_RING_EMPTY(&workers->idle, h2_slot, link)) {
+ h2_slot *slot;
+ for (slot = APR_RING_FIRST(&workers->idle);
+ slot != APR_RING_SENTINEL(&workers->idle, h2_slot, link);
+ slot = APR_RING_NEXT(slot, link)) {
+ if (slot->is_idle && !slot->should_shutdown) {
+ apr_thread_cond_signal(slot->more_work);
+ slot->is_idle = 0;
+ return;
+ }
+ }
+ }
+ if (workers->dynamic && !workers->shutdown
+ && (workers->active_slots < workers->max_slots)) {
+ activate_slot(workers);
+ }
+}
+
+/**
+ * Get the next connection to work on.
+ */
+static conn_rec *get_next(h2_slot *slot)
+{
+ h2_workers *workers = slot->workers;
+ conn_rec *c = NULL;
+ ap_conn_producer_t *prod;
+ int has_more;
+
+ slot->prod = NULL;
+ if (!APR_RING_EMPTY(&workers->prod_active, ap_conn_producer_t, link)) {
+ slot->prod = prod = APR_RING_FIRST(&workers->prod_active);
+ APR_RING_REMOVE(prod, link);
+ AP_DEBUG_ASSERT(PROD_ACTIVE == prod->state);
+
+ c = prod->fn_next(prod->baton, &has_more);
+ if (c && has_more) {
+ APR_RING_INSERT_TAIL(&workers->prod_active, prod, ap_conn_producer_t, link);
+ wake_idle_worker(workers, slot->prod);
+ }
+ else {
+ prod->state = PROD_IDLE;
+ APR_RING_INSERT_TAIL(&workers->prod_idle, prod, ap_conn_producer_t, link);
+ }
+ if (c) {
+ ++prod->conns_active;
+ }
+ }
+
+ return c;
+}
+
+static void* APR_THREAD_FUNC slot_run(apr_thread_t *thread, void *wctx)
+{
+ h2_slot *slot = wctx;
+ h2_workers *workers = slot->workers;
+ conn_rec *c;
+ apr_status_t rv;
+
+ apr_thread_mutex_lock(workers->lock);
+ slot->state = H2_SLOT_RUN;
+ ++slot->activations;
+ APR_RING_ELEM_INIT(slot, link);
+ for(;;) {
+ if (APR_RING_NEXT(slot, link) != slot) {
+ /* slot is part of the idle ring from the last loop */
+ APR_RING_REMOVE(slot, link);
+ --workers->idle_slots;
+ }
+ slot->is_idle = 0;
+
+ if (!workers->aborted && !slot->should_shutdown) {
+ APR_RING_INSERT_TAIL(&workers->busy, slot, h2_slot, link);
+ do {
+ c = get_next(slot);
+ if (!c) {
+ break;
+ }
+ apr_thread_mutex_unlock(workers->lock);
+ /* See the discussion at <https://github.com/icing/mod_h2/issues/195>
+ *
+ * Each conn_rec->id is supposed to be unique at a point in time. Since
+ * some modules (and maybe external code) uses this id as an identifier
+ * for the request_rec they handle, it needs to be unique for secondary
+ * connections also.
+ *
+ * The MPM module assigns the connection ids and mod_unique_id is using
+ * that one to generate identifier for requests. While the implementation
+ * works for HTTP/1.x, the parallel execution of several requests per
+ * connection will generate duplicate identifiers on load.
+ *
+ * The original implementation for secondary connection identifiers used
+ * to shift the master connection id up and assign the stream id to the
+ * lower bits. This was cramped on 32 bit systems, but on 64bit there was
+ * enough space.
+ *
+ * As issue 195 showed, mod_unique_id only uses the lower 32 bit of the
+ * connection id, even on 64bit systems. Therefore collisions in request ids.
+ *
+ * The way master connection ids are generated, there is some space "at the
+ * top" of the lower 32 bits on allmost all systems. If you have a setup
+ * with 64k threads per child and 255 child processes, you live on the edge.
+ *
+ * The new implementation shifts 8 bits and XORs in the worker
+ * id. This will experience collisions with > 256 h2 workers and heavy
+ * load still. There seems to be no way to solve this in all possible
+ * configurations by mod_h2 alone.
+ */
+ if (c->master) {
+ c->id = (c->master->id << 8)^slot->id;
+ }
+ c->current_thread = thread;
+ AP_DEBUG_ASSERT(slot->prod);
+
+#if AP_HAS_RESPONSE_BUCKETS
+ ap_process_connection(c, ap_get_conn_socket(c));
+#else
+ h2_c2_process(c, thread, slot->id);
+#endif
+ slot->prod->fn_done(slot->prod->baton, c);
+
+ apr_thread_mutex_lock(workers->lock);
+ if (--slot->prod->conns_active <= 0) {
+ apr_thread_cond_broadcast(workers->prod_done);
+ }
+ if (slot->prod->state == PROD_IDLE) {
+ APR_RING_REMOVE(slot->prod, link);
+ slot->prod->state = PROD_ACTIVE;
+ APR_RING_INSERT_TAIL(&workers->prod_active, slot->prod, ap_conn_producer_t, link);
+ }
+
+ } while (!workers->aborted && !slot->should_shutdown);
+ APR_RING_REMOVE(slot, link); /* no longer busy */
+ }
+
+ if (workers->aborted || slot->should_shutdown) {
+ break;
+ }
+
+ join_zombies(workers);
+
+ /* we are idle */
+ APR_RING_INSERT_TAIL(&workers->idle, slot, h2_slot, link);
+ ++workers->idle_slots;
+ slot->is_idle = 1;
+ if (slot->id >= workers->min_active && workers->idle_limit > 0) {
+ rv = apr_thread_cond_timedwait(slot->more_work, workers->lock,
+ workers->idle_limit);
+ if (APR_TIMEUP == rv) {
+ APR_RING_REMOVE(slot, link);
+ --workers->idle_slots;
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, workers->s,
+ "h2_workers: idle timeout slot %d in state %d (%d activations)",
+ slot->id, slot->state, slot->activations);
+ break;
+ }
+ }
+ else {
+ apr_thread_cond_wait(slot->more_work, workers->lock);
+ }
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, workers->s,
+ "h2_workers: terminate slot %d in state %d (%d activations)",
+ slot->id, slot->state, slot->activations);
+ slot->is_idle = 0;
+ slot->state = H2_SLOT_ZOMBIE;
+ slot->should_shutdown = 0;
+ APR_RING_INSERT_TAIL(&workers->zombie, slot, h2_slot, link);
+ --workers->active_slots;
+ if (workers->active_slots <= 0) {
+ apr_thread_cond_broadcast(workers->all_done);
+ }
+ apr_thread_mutex_unlock(workers->lock);
+
+ apr_thread_exit(thread, APR_SUCCESS);
+ return NULL;
+}
+
+static void wake_all_idles(h2_workers *workers)
+{
+ h2_slot *slot;
+ for (slot = APR_RING_FIRST(&workers->idle);
+ slot != APR_RING_SENTINEL(&workers->idle, h2_slot, link);
+ slot = APR_RING_NEXT(slot, link))
+ {
+ apr_thread_cond_signal(slot->more_work);
+ }
+}
+
+static apr_status_t workers_pool_cleanup(void *data)
+{
+ h2_workers *workers = data;
+ apr_time_t end, timeout = apr_time_from_sec(1);
+ apr_status_t rv;
+ int n = 0, wait_sec = 5;
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, workers->s,
+ "h2_workers: cleanup %d workers (%d idle)",
+ workers->active_slots, workers->idle_slots);
+ apr_thread_mutex_lock(workers->lock);
+ workers->shutdown = 1;
+ workers->aborted = 1;
+ wake_all_idles(workers);
+ apr_thread_mutex_unlock(workers->lock);
+
+ /* wait for all the workers to become zombies and join them.
+ * this gets called after the mpm shuts down and all connections
+ * have either been handled (graceful) or we are forced exiting
+ * (ungrateful). Either way, we show limited patience. */
+ end = apr_time_now() + apr_time_from_sec(wait_sec);
+ while (apr_time_now() < end) {
+ apr_thread_mutex_lock(workers->lock);
+ if (!(n = workers->active_slots)) {
+ apr_thread_mutex_unlock(workers->lock);
+ break;
+ }
+ wake_all_idles(workers);
+ rv = apr_thread_cond_timedwait(workers->all_done, workers->lock, timeout);
+ apr_thread_mutex_unlock(workers->lock);
+
+ if (APR_TIMEUP == rv) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, workers->s,
+ APLOGNO(10290) "h2_workers: waiting for workers to close, "
+ "still seeing %d workers (%d idle) living",
+ workers->active_slots, workers->idle_slots);
+ }
+ }
+ if (n) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, workers->s,
+ APLOGNO(10291) "h2_workers: cleanup, %d workers (%d idle) "
+ "did not exit after %d seconds.",
+ n, workers->idle_slots, wait_sec);
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, workers->s,
+ "h2_workers: cleanup all workers terminated");
+ apr_thread_mutex_lock(workers->lock);
+ join_zombies(workers);
+ apr_thread_mutex_unlock(workers->lock);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, workers->s,
+ "h2_workers: cleanup zombie workers joined");
+
+ return APR_SUCCESS;
+}
+
+h2_workers *h2_workers_create(server_rec *s, apr_pool_t *pchild,
+ int max_slots, int min_active,
+ apr_time_t idle_limit)
+{
+ apr_status_t rv;
+ h2_workers *workers;
+ apr_pool_t *pool;
+ apr_allocator_t *allocator;
+ int locked = 0;
+ apr_uint32_t i;
+
+ ap_assert(s);
+ ap_assert(pchild);
+ ap_assert(idle_limit > 0);
+
+ /* let's have our own pool that will be parent to all h2_worker
+ * instances we create. This happens in various threads, but always
+ * guarded by our lock. Without this pool, all subpool creations would
+ * happen on the pool handed to us, which we do not guard.
+ */
+ rv = apr_allocator_create(&allocator);
+ if (rv != APR_SUCCESS) {
+ goto cleanup;
+ }
+ rv = apr_pool_create_ex(&pool, pchild, NULL, allocator);
+ if (rv != APR_SUCCESS) {
+ apr_allocator_destroy(allocator);
+ goto cleanup;
+ }
+ apr_allocator_owner_set(allocator, pool);
+ apr_pool_tag(pool, "h2_workers");
+ workers = apr_pcalloc(pool, sizeof(h2_workers));
+ if (!workers) {
+ return NULL;
+ }
+
+ workers->s = s;
+ workers->pool = pool;
+ workers->min_active = min_active;
+ workers->max_slots = max_slots;
+ workers->idle_limit = idle_limit;
+ workers->dynamic = (workers->min_active < workers->max_slots);
+
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
+ "h2_workers: created with min=%d max=%d idle_ms=%d",
+ workers->min_active, workers->max_slots,
+ (int)apr_time_as_msec(idle_limit));
+
+ APR_RING_INIT(&workers->idle, h2_slot, link);
+ APR_RING_INIT(&workers->busy, h2_slot, link);
+ APR_RING_INIT(&workers->free, h2_slot, link);
+ APR_RING_INIT(&workers->zombie, h2_slot, link);
+
+ APR_RING_INIT(&workers->prod_active, ap_conn_producer_t, link);
+ APR_RING_INIT(&workers->prod_idle, ap_conn_producer_t, link);
+
+ rv = apr_threadattr_create(&workers->thread_attr, workers->pool);
+ if (rv != APR_SUCCESS) goto cleanup;
+
+ if (ap_thread_stacksize != 0) {
+ apr_threadattr_stacksize_set(workers->thread_attr,
+ ap_thread_stacksize);
+ ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s,
+ "h2_workers: using stacksize=%ld",
+ (long)ap_thread_stacksize);
+ }
+
+ rv = apr_thread_mutex_create(&workers->lock,
+ APR_THREAD_MUTEX_DEFAULT,
+ workers->pool);
+ if (rv != APR_SUCCESS) goto cleanup;
+ rv = apr_thread_cond_create(&workers->all_done, workers->pool);
+ if (rv != APR_SUCCESS) goto cleanup;
+ rv = apr_thread_cond_create(&workers->prod_done, workers->pool);
+ if (rv != APR_SUCCESS) goto cleanup;
+
+ apr_thread_mutex_lock(workers->lock);
+ locked = 1;
+
+ /* create the slots and put them on the free list */
+ workers->slots = apr_pcalloc(workers->pool, workers->max_slots * sizeof(h2_slot));
+
+ for (i = 0; i < workers->max_slots; ++i) {
+ workers->slots[i].id = i;
+ workers->slots[i].state = H2_SLOT_FREE;
+ workers->slots[i].workers = workers;
+ APR_RING_ELEM_INIT(&workers->slots[i], link);
+ APR_RING_INSERT_TAIL(&workers->free, &workers->slots[i], h2_slot, link);
+ rv = apr_thread_cond_create(&workers->slots[i].more_work, workers->pool);
+ if (rv != APR_SUCCESS) goto cleanup;
+ }
+
+ /* activate the min amount of workers */
+ for (i = 0; i < workers->min_active; ++i) {
+ rv = activate_slot(workers);
+ if (rv != APR_SUCCESS) goto cleanup;
+ }
+
+cleanup:
+ if (locked) {
+ apr_thread_mutex_unlock(workers->lock);
+ }
+ if (rv == APR_SUCCESS) {
+ /* Stop/join the workers threads when the MPM child exits (pchild is
+ * destroyed), and as a pre_cleanup of pchild thus before the threads
+ * pools (children of workers->pool) so that they are not destroyed
+ * before/under us.
+ */
+ apr_pool_pre_cleanup_register(pchild, workers, workers_pool_cleanup);
+ return workers;
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s,
+ "h2_workers: errors initializing");
+ return NULL;
+}
+
+apr_uint32_t h2_workers_get_max_workers(h2_workers *workers)
+{
+ return workers->max_slots;
+}
+
+void h2_workers_shutdown(h2_workers *workers, int graceful)
+{
+ ap_conn_producer_t *prod;
+
+ apr_thread_mutex_lock(workers->lock);
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, workers->s,
+ "h2_workers: shutdown graceful=%d", graceful);
+ workers->shutdown = 1;
+ workers->idle_limit = apr_time_from_sec(1);
+ wake_all_idles(workers);
+ for (prod = APR_RING_FIRST(&workers->prod_idle);
+ prod != APR_RING_SENTINEL(&workers->prod_idle, ap_conn_producer_t, link);
+ prod = APR_RING_NEXT(prod, link)) {
+ if (prod->fn_shutdown) {
+ prod->fn_shutdown(prod->baton, graceful);
+ }
+ }
+ apr_thread_mutex_unlock(workers->lock);
+}
+
+ap_conn_producer_t *h2_workers_register(h2_workers *workers,
+ apr_pool_t *producer_pool,
+ const char *name,
+ ap_conn_producer_next *fn_next,
+ ap_conn_producer_done *fn_done,
+ ap_conn_producer_shutdown *fn_shutdown,
+ void *baton)
+{
+ ap_conn_producer_t *prod;
+
+ prod = apr_pcalloc(producer_pool, sizeof(*prod));
+ APR_RING_ELEM_INIT(prod, link);
+ prod->name = name;
+ prod->fn_next = fn_next;
+ prod->fn_done = fn_done;
+ prod->fn_shutdown = fn_shutdown;
+ prod->baton = baton;
+
+ apr_thread_mutex_lock(workers->lock);
+ prod->state = PROD_IDLE;
+ APR_RING_INSERT_TAIL(&workers->prod_idle, prod, ap_conn_producer_t, link);
+ apr_thread_mutex_unlock(workers->lock);
+
+ return prod;
+}
+
+apr_status_t h2_workers_join(h2_workers *workers, ap_conn_producer_t *prod)
+{
+ apr_status_t rv = APR_SUCCESS;
+
+ apr_thread_mutex_lock(workers->lock);
+ if (PROD_JOINED == prod->state) {
+ AP_DEBUG_ASSERT(APR_RING_NEXT(prod, link) == prod); /* should be in no ring */
+ rv = APR_EINVAL;
+ }
+ else {
+ AP_DEBUG_ASSERT(PROD_ACTIVE == prod->state || PROD_IDLE == prod->state);
+ APR_RING_REMOVE(prod, link);
+ prod->state = PROD_JOINED; /* prevent further activations */
+ while (prod->conns_active > 0) {
+ apr_thread_cond_wait(workers->prod_done, workers->lock);
+ }
+ APR_RING_ELEM_INIT(prod, link); /* make it link to itself */
+ }
+ apr_thread_mutex_unlock(workers->lock);
+ return rv;
+}
+
+apr_status_t h2_workers_activate(h2_workers *workers, ap_conn_producer_t *prod)
+{
+ apr_status_t rv = APR_SUCCESS;
+ apr_thread_mutex_lock(workers->lock);
+ if (PROD_IDLE == prod->state) {
+ APR_RING_REMOVE(prod, link);
+ prod->state = PROD_ACTIVE;
+ APR_RING_INSERT_TAIL(&workers->prod_active, prod, ap_conn_producer_t, link);
+ wake_idle_worker(workers, prod);
+ }
+ else if (PROD_JOINED == prod->state) {
+ rv = APR_EINVAL;
+ }
+ apr_thread_mutex_unlock(workers->lock);
+ return rv;
+}
diff --git a/modules/http2/h2_workers.h b/modules/http2/h2_workers.h
new file mode 100644
index 0000000..c219304
--- /dev/null
+++ b/modules/http2/h2_workers.h
@@ -0,0 +1,129 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __mod_h2__h2_workers__
+#define __mod_h2__h2_workers__
+
+/* Thread pool specific to executing secondary connections.
+ * Has a minimum and maximum number of workers it creates.
+ * Starts with minimum workers and adds some on load,
+ * reduces the number again when idle.
+ */
+struct apr_thread_mutex_t;
+struct apr_thread_cond_t;
+struct h2_mplx;
+struct h2_request;
+struct h2_fifo;
+
+typedef struct h2_workers h2_workers;
+
+
+/**
+ * Create a worker set with a maximum number of 'slots', e.g. worker
+ * threads to run. Always keep `min_active` workers running. Shutdown
+ * any additional workers after `idle_secs` seconds of doing nothing.
+ *
+ * @oaram s the base server
+ * @param pool for allocations
+ * @param min_active minimum number of workers to run
+ * @param max_slots maximum number of worker slots
+ * @param idle_limit upper duration of idle after a non-minimal slots shuts down
+ */
+h2_workers *h2_workers_create(server_rec *s, apr_pool_t *pool,
+ int max_slots, int min_active, apr_time_t idle_limit);
+
+/**
+ * Shut down processing.
+ */
+void h2_workers_shutdown(h2_workers *workers, int graceful);
+
+/**
+ * Get the maximum number of workers.
+ */
+apr_uint32_t h2_workers_get_max_workers(h2_workers *workers);
+
+/**
+ * ap_conn_producer_t is the source of connections (conn_rec*) to run.
+ *
+ * Active producers are queried by idle workers for connections.
+ * If they do not hand one back, they become inactive and are not
+ * queried further. `h2_workers_activate()` places them on the active
+ * list again.
+ *
+ * A producer finishing MUST call `h2_workers_join()` which removes
+ * it completely from workers processing and waits for all ongoing
+ * work for this producer to be done.
+ */
+typedef struct ap_conn_producer_t ap_conn_producer_t;
+
+/**
+ * Ask a producer for the next connection to process.
+ * @param baton value from producer registration
+ * @param pconn holds the connection to process on return
+ * @param pmore if the producer has more connections that may be retrieved
+ * @return APR_SUCCESS for a connection to process, APR_EAGAIN for no
+ * connection being available at the time.
+ */
+typedef conn_rec *ap_conn_producer_next(void *baton, int *pmore);
+
+/**
+ * Tell the producer that processing the connection is done.
+ * @param baton value from producer registration
+ * @param conn the connection that has been processed.
+ */
+typedef void ap_conn_producer_done(void *baton, conn_rec *conn);
+
+/**
+ * Tell the producer that the workers are shutting down.
+ * @param baton value from producer registration
+ * @param graceful != 0 iff shutdown is graceful
+ */
+typedef void ap_conn_producer_shutdown(void *baton, int graceful);
+
+/**
+ * Register a new producer with the given `baton` and callback functions.
+ * Will allocate internal structures from the given pool (but make no use
+ * of the pool after registration).
+ * Producers are inactive on registration. See `h2_workers_activate()`.
+ * @param producer_pool to allocate the producer from
+ * @param name descriptive name of the producer, must not be unique
+ * @param fn_next callback for retrieving connections to process
+ * @param fn_done callback for processed connections
+ * @param baton provided value passed on in callbacks
+ * @return the producer instance created
+ */
+ap_conn_producer_t *h2_workers_register(h2_workers *workers,
+ apr_pool_t *producer_pool,
+ const char *name,
+ ap_conn_producer_next *fn_next,
+ ap_conn_producer_done *fn_done,
+ ap_conn_producer_shutdown *fn_shutdown,
+ void *baton);
+
+/**
+ * Stop retrieving more connection from the producer and wait
+ * for all ongoing for from that producer to be done.
+ */
+apr_status_t h2_workers_join(h2_workers *workers, ap_conn_producer_t *producer);
+
+/**
+ * Activate a producer. A worker will query the producer for a connection
+ * to process, once a worker is available.
+ * This may be called, irregardless of the producers active/inactive.
+ */
+apr_status_t h2_workers_activate(h2_workers *workers, ap_conn_producer_t *producer);
+
+#endif /* defined(__mod_h2__h2_workers__) */
diff --git a/modules/http2/mod_http2.c b/modules/http2/mod_http2.c
new file mode 100644
index 0000000..8a1ee3f
--- /dev/null
+++ b/modules/http2/mod_http2.c
@@ -0,0 +1,349 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <apr_optional.h>
+#include <apr_optional_hooks.h>
+#include <apr_strings.h>
+#include <apr_time.h>
+#include <apr_want.h>
+
+#include <httpd.h>
+#include <http_protocol.h>
+#include <http_request.h>
+#include <http_log.h>
+#include <mpm_common.h>
+
+#include "mod_http2.h"
+
+#include <nghttp2/nghttp2.h>
+#include "h2_stream.h"
+#include "h2_c1.h"
+#include "h2_c2.h"
+#include "h2_session.h"
+#include "h2_config.h"
+#include "h2_conn_ctx.h"
+#include "h2_protocol.h"
+#include "h2_mplx.h"
+#include "h2_push.h"
+#include "h2_request.h"
+#include "h2_switch.h"
+#include "h2_version.h"
+#include "h2_bucket_beam.h"
+
+
+static void h2_hooks(apr_pool_t *pool);
+
+AP_DECLARE_MODULE(http2) = {
+ STANDARD20_MODULE_STUFF,
+ h2_config_create_dir, /* func to create per dir config */
+ h2_config_merge_dir, /* func to merge per dir config */
+ h2_config_create_svr, /* func to create per server config */
+ h2_config_merge_svr, /* func to merge per server config */
+ h2_cmds, /* command handlers */
+ h2_hooks,
+#if defined(AP_MODULE_FLAG_NONE)
+ AP_MODULE_FLAG_ALWAYS_MERGE
+#endif
+};
+
+static int h2_h2_fixups(request_rec *r);
+
+typedef struct {
+ unsigned int change_prio : 1;
+ unsigned int sha256 : 1;
+ unsigned int inv_headers : 1;
+ unsigned int dyn_windows : 1;
+} features;
+
+static features myfeats;
+static int mpm_warned;
+
+/* The module initialization. Called once as apache hook, before any multi
+ * processing (threaded or not) happens. It is typically at least called twice,
+ * see
+ * http://wiki.apache.org/httpd/ModuleLife
+ * Since the first run is just a "practise" run, we want to initialize for real
+ * only on the second try. This defeats the purpose of the first dry run a bit,
+ * since apache wants to verify that a new configuration actually will work.
+ * So if we have trouble with the configuration, this will only be detected
+ * when the server has already switched.
+ * On the other hand, when we initialize lib nghttp2, all possible crazy things
+ * might happen and this might even eat threads. So, better init on the real
+ * invocation, for now at least.
+ */
+static int h2_post_config(apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ void *data = NULL;
+ const char *mod_h2_init_key = "mod_http2_init_counter";
+ nghttp2_info *ngh2;
+ apr_status_t status;
+
+ (void)plog;(void)ptemp;
+#ifdef H2_NG2_CHANGE_PRIO
+ myfeats.change_prio = 1;
+#endif
+#ifdef H2_OPENSSL
+ myfeats.sha256 = 1;
+#endif
+#ifdef H2_NG2_INVALID_HEADER_CB
+ myfeats.inv_headers = 1;
+#endif
+#ifdef H2_NG2_LOCAL_WIN_SIZE
+ myfeats.dyn_windows = 1;
+#endif
+
+ apr_pool_userdata_get(&data, mod_h2_init_key, s->process->pool);
+ if ( data == NULL ) {
+ ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03089)
+ "initializing post config dry run");
+ apr_pool_userdata_set((const void *)1, mod_h2_init_key,
+ apr_pool_cleanup_null, s->process->pool);
+ return APR_SUCCESS;
+ }
+
+ ngh2 = nghttp2_version(0);
+ ap_log_error( APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(03090)
+ "mod_http2 (v%s, feats=%s%s%s%s, nghttp2 %s), initializing...",
+ MOD_HTTP2_VERSION,
+ myfeats.change_prio? "CHPRIO" : "",
+ myfeats.sha256? "+SHA256" : "",
+ myfeats.inv_headers? "+INVHD" : "",
+ myfeats.dyn_windows? "+DWINS" : "",
+ ngh2? ngh2->version_str : "unknown");
+
+ if (!h2_mpm_supported() && !mpm_warned) {
+ mpm_warned = 1;
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(10034)
+ "The mpm module (%s) is not supported by mod_http2. The mpm determines "
+ "how things are processed in your server. HTTP/2 has more demands in "
+ "this regard and the currently selected mpm will just not do. "
+ "This is an advisory warning. Your server will continue to work, but "
+ "the HTTP/2 protocol will be inactive.",
+ h2_conn_mpm_name());
+ }
+
+ status = h2_protocol_init(p, s);
+ if (status == APR_SUCCESS) {
+ status = h2_switch_init(p, s);
+ }
+
+ return status;
+}
+
+static char *http2_var_lookup(apr_pool_t *, server_rec *,
+ conn_rec *, request_rec *, char *name);
+static int http2_is_h2(conn_rec *);
+
+static void http2_get_num_workers(server_rec *s, int *minw, int *maxw)
+{
+ apr_time_t tdummy;
+
+ h2_get_workers_config(s, minw, maxw, &tdummy);
+}
+
+/* Runs once per created child process. Perform any process
+ * related initionalization here.
+ */
+static void h2_child_init(apr_pool_t *pchild, server_rec *s)
+{
+ apr_status_t rv;
+
+ /* Set up our connection processing */
+ rv = h2_c1_child_init(pchild, s);
+ if (APR_SUCCESS == rv) {
+ rv = h2_c2_child_init(pchild, s);
+ }
+ if (APR_SUCCESS != rv) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+ APLOGNO(02949) "initializing connection handling");
+ }
+}
+
+/* Install this module into the apache2 infrastructure.
+ */
+static void h2_hooks(apr_pool_t *pool)
+{
+ static const char *const mod_ssl[] = { "mod_ssl.c", NULL};
+
+ APR_REGISTER_OPTIONAL_FN(http2_is_h2);
+ APR_REGISTER_OPTIONAL_FN(http2_var_lookup);
+ APR_REGISTER_OPTIONAL_FN(http2_get_num_workers);
+
+ ap_log_perror(APLOG_MARK, APLOG_TRACE1, 0, pool, "installing hooks");
+
+ /* Run once after configuration is set, but before mpm children initialize.
+ */
+ ap_hook_post_config(h2_post_config, mod_ssl, NULL, APR_HOOK_MIDDLE);
+
+ /* Run once after a child process has been created.
+ */
+ ap_hook_child_init(h2_child_init, NULL, NULL, APR_HOOK_MIDDLE);
+#if AP_MODULE_MAGIC_AT_LEAST(20120211, 110)
+ ap_hook_child_stopping(h2_c1_child_stopping, NULL, NULL, APR_HOOK_MIDDLE);
+#endif
+
+ h2_c1_register_hooks();
+ h2_switch_register_hooks();
+ h2_c2_register_hooks();
+
+ /* Setup subprocess env for certain variables
+ */
+ ap_hook_fixups(h2_h2_fixups, NULL,NULL, APR_HOOK_MIDDLE);
+}
+
+static const char *val_HTTP2(apr_pool_t *p, server_rec *s,
+ conn_rec *c, request_rec *r, h2_conn_ctx_t *ctx)
+{
+ return ctx? "on" : "off";
+}
+
+static const char *val_H2_PUSH(apr_pool_t *p, server_rec *s,
+ conn_rec *c, request_rec *r,
+ h2_conn_ctx_t *conn_ctx)
+{
+ if (conn_ctx) {
+ if (r) {
+ if (conn_ctx->stream_id) {
+ const h2_stream *stream = h2_mplx_c2_stream_get(conn_ctx->mplx, conn_ctx->stream_id);
+ if (stream && stream->push_policy != H2_PUSH_NONE) {
+ return "on";
+ }
+ }
+ }
+ else if (c && h2_session_push_enabled(conn_ctx->session)) {
+ return "on";
+ }
+ }
+ else if (s) {
+ if (h2_config_geti(r, s, H2_CONF_PUSH)) {
+ return "on";
+ }
+ }
+ return "off";
+}
+
+static const char *val_H2_PUSHED(apr_pool_t *p, server_rec *s,
+ conn_rec *c, request_rec *r,
+ h2_conn_ctx_t *conn_ctx)
+{
+ if (conn_ctx) {
+ if (conn_ctx->stream_id && !H2_STREAM_CLIENT_INITIATED(conn_ctx->stream_id)) {
+ return "PUSHED";
+ }
+ }
+ return "";
+}
+
+static const char *val_H2_PUSHED_ON(apr_pool_t *p, server_rec *s,
+ conn_rec *c, request_rec *r,
+ h2_conn_ctx_t *conn_ctx)
+{
+ if (conn_ctx) {
+ if (conn_ctx->stream_id && !H2_STREAM_CLIENT_INITIATED(conn_ctx->stream_id)) {
+ const h2_stream *stream = h2_mplx_c2_stream_get(conn_ctx->mplx, conn_ctx->stream_id);
+ if (stream) {
+ return apr_itoa(p, stream->initiated_on);
+ }
+ }
+ }
+ return "";
+}
+
+static const char *val_H2_STREAM_TAG(apr_pool_t *p, server_rec *s,
+ conn_rec *c, request_rec *r, h2_conn_ctx_t *ctx)
+{
+ if (c) {
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
+ if (conn_ctx) {
+ return conn_ctx->stream_id == 0? conn_ctx->id
+ : apr_psprintf(p, "%s-%d", conn_ctx->id, conn_ctx->stream_id);
+ }
+ }
+ return "";
+}
+
+static const char *val_H2_STREAM_ID(apr_pool_t *p, server_rec *s,
+ conn_rec *c, request_rec *r, h2_conn_ctx_t *ctx)
+{
+ const char *cp = val_H2_STREAM_TAG(p, s, c, r, ctx);
+ if (cp && (cp = ap_strrchr_c(cp, '-'))) {
+ return ++cp;
+ }
+ return NULL;
+}
+
+typedef const char *h2_var_lookup(apr_pool_t *p, server_rec *s,
+ conn_rec *c, request_rec *r, h2_conn_ctx_t *ctx);
+typedef struct h2_var_def {
+ const char *name;
+ h2_var_lookup *lookup;
+ unsigned int subprocess : 1; /* should be set in r->subprocess_env */
+} h2_var_def;
+
+static h2_var_def H2_VARS[] = {
+ { "HTTP2", val_HTTP2, 1 },
+ { "H2PUSH", val_H2_PUSH, 1 },
+ { "H2_PUSH", val_H2_PUSH, 1 },
+ { "H2_PUSHED", val_H2_PUSHED, 1 },
+ { "H2_PUSHED_ON", val_H2_PUSHED_ON, 1 },
+ { "H2_STREAM_ID", val_H2_STREAM_ID, 1 },
+ { "H2_STREAM_TAG", val_H2_STREAM_TAG, 1 },
+};
+
+#ifndef H2_ALEN
+#define H2_ALEN(a) (sizeof(a)/sizeof((a)[0]))
+#endif
+
+
+static int http2_is_h2(conn_rec *c)
+{
+ return h2_conn_ctx_get(c->master? c->master : c) != NULL;
+}
+
+static char *http2_var_lookup(apr_pool_t *p, server_rec *s,
+ conn_rec *c, request_rec *r, char *name)
+{
+ unsigned int i;
+ /* If the # of vars grow, we need to put definitions in a hash */
+ for (i = 0; i < H2_ALEN(H2_VARS); ++i) {
+ h2_var_def *vdef = &H2_VARS[i];
+ if (!strcmp(vdef->name, name)) {
+ h2_conn_ctx_t *ctx = (r? h2_conn_ctx_get(c) :
+ h2_conn_ctx_get(c->master? c->master : c));
+ return (char *)vdef->lookup(p, s, c, r, ctx);
+ }
+ }
+ return (char*)"";
+}
+
+static int h2_h2_fixups(request_rec *r)
+{
+ if (r->connection->master) {
+ h2_conn_ctx_t *ctx = h2_conn_ctx_get(r->connection);
+ unsigned int i;
+
+ for (i = 0; ctx && i < H2_ALEN(H2_VARS); ++i) {
+ h2_var_def *vdef = &H2_VARS[i];
+ if (vdef->subprocess) {
+ apr_table_setn(r->subprocess_env, vdef->name,
+ vdef->lookup(r->pool, r->server, r->connection,
+ r, ctx));
+ }
+ }
+ }
+ return DECLINED;
+}
diff --git a/modules/http2/mod_http2.dep b/modules/http2/mod_http2.dep
new file mode 100644
index 0000000..25c0ede
--- /dev/null
+++ b/modules/http2/mod_http2.dep
@@ -0,0 +1,1431 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_http2.mak
+
+./h2_alt_svc.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\h2_alt_svc.h"\
+ ".\h2_config.h"\
+ ".\h2_ctx.h"\
+ ".\h2_h2.h"\
+ ".\h2_private.h"\
+ ".\h2_util.h"\
+
+
+./h2_bucket_eos.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_queue.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\h2.h"\
+ ".\h2_bucket_eos.h"\
+ ".\h2_mplx.h"\
+ ".\h2_private.h"\
+ ".\h2_stream.h"\
+
+
+./h2_config.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_mpm.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\h2.h"\
+ ".\h2_alt_svc.h"\
+ ".\h2_config.h"\
+ ".\h2_conn.h"\
+ ".\h2_ctx.h"\
+ ".\h2_h2.h"\
+ ".\h2_private.h"\
+
+
+./h2_conn.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_mpm.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_queue.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\h2.h"\
+ ".\h2_config.h"\
+ ".\h2_conn.h"\
+ ".\h2_conn_io.h"\
+ ".\h2_ctx.h"\
+ ".\h2_filter.h"\
+ ".\h2_h2.h"\
+ ".\h2_mplx.h"\
+ ".\h2_private.h"\
+ ".\h2_session.h"\
+ ".\h2_stream.h"\
+ ".\h2_task.h"\
+ ".\h2_version.h"\
+ ".\h2_workers.h"\
+
+
+./h2_conn_io.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_mpm.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\h2.h"\
+ ".\h2_bucket_eos.h"\
+ ".\h2_config.h"\
+ ".\h2_conn_io.h"\
+ ".\h2_h2.h"\
+ ".\h2_private.h"\
+ ".\h2_session.h"\
+ ".\h2_util.h"\
+
+
+./h2_ctx.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\h2.h"\
+ ".\h2_conn_io.h"\
+ ".\h2_ctx.h"\
+ ".\h2_private.h"\
+ ".\h2_session.h"\
+ ".\h2_task.h"\
+
+
+./h2_filter.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_queue.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\h2.h"\
+ ".\h2_conn_io.h"\
+ ".\h2_ctx.h"\
+ ".\h2_filter.h"\
+ ".\h2_mplx.h"\
+ ".\h2_private.h"\
+ ".\h2_push.h"\
+ ".\h2_request.h"\
+ ".\h2_session.h"\
+ ".\h2_stream.h"\
+ ".\h2_task.h"\
+ ".\h2_util.h"\
+ ".\h2_version.h"\
+
+
+./h2_from_h1.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_time.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\h2.h"\
+ ".\h2_from_h1.h"\
+ ".\h2_private.h"\
+ ".\h2_task.h"\
+ ".\h2_util.h"\
+
+
+./h2_h2.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ "..\ssl\mod_ssl.h"\
+ ".\h2.h"\
+ ".\h2_config.h"\
+ ".\h2_conn.h"\
+ ".\h2_conn_io.h"\
+ ".\h2_ctx.h"\
+ ".\h2_h2.h"\
+ ".\h2_private.h"\
+ ".\h2_request.h"\
+ ".\h2_session.h"\
+ ".\h2_stream.h"\
+ ".\h2_task.h"\
+ ".\h2_util.h"\
+ ".\mod_http2.h"\
+
+
+./h2_int_queue.c : \
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
+
+./h2_io.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_queue.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_cond.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\h2.h"\
+ ".\h2_h2.h"\
+ ".\h2_mplx.h"\
+ ".\h2_private.h"\
+ ".\h2_request.h"\
+ ".\h2_task.h"\
+ ".\h2_util.h"\
+
+
+./h2_io_set.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\h2_io_set.h"\
+ ".\h2_private.h"\
+
+
+./h2_mplx.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_queue.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_cond.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\h2.h"\
+ ".\h2_config.h"\
+ ".\h2_conn.h"\
+ ".\h2_ctx.h"\
+ ".\h2_h2.h"\
+ ".\h2_mplx.h"\
+ ".\h2_private.h"\
+ ".\h2_request.h"\
+ ".\h2_stream.h"\
+ ".\h2_task.h"\
+ ".\h2_util.h"\
+ ".\h2_workers.h"\
+ ".\mod_http2.h"\
+
+
+./h2_ngn_shed.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_queue.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_cond.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\h2.h"\
+ ".\h2_config.h"\
+ ".\h2_conn.h"\
+ ".\h2_ctx.h"\
+ ".\h2_h2.h"\
+ ".\h2_mplx.h"\
+ ".\h2_private.h"\
+ ".\h2_request.h"\
+ ".\h2_task.h"\
+ ".\h2_util.h"\
+ ".\mod_http2.h"\
+
+
+./h2_push.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\h2.h"\
+ ".\h2_conn_io.h"\
+ ".\h2_h2.h"\
+ ".\h2_private.h"\
+ ".\h2_push.h"\
+ ".\h2_request.h"\
+ ".\h2_session.h"\
+ ".\h2_stream.h"\
+ ".\h2_util.h"\
+
+
+./h2_request.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_mpm.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_core.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\h2.h"\
+ ".\h2_private.h"\
+ ".\h2_push.h"\
+ ".\h2_request.h"\
+ ".\h2_util.h"\
+
+
+./h2_session.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_mpm.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_base64.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_queue.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_cond.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\h2.h"\
+ ".\h2_bucket_eos.h"\
+ ".\h2_config.h"\
+ ".\h2_conn_io.h"\
+ ".\h2_ctx.h"\
+ ".\h2_filter.h"\
+ ".\h2_from_h1.h"\
+ ".\h2_h2.h"\
+ ".\h2_mplx.h"\
+ ".\h2_private.h"\
+ ".\h2_push.h"\
+ ".\h2_request.h"\
+ ".\h2_session.h"\
+ ".\h2_stream.h"\
+ ".\h2_task.h"\
+ ".\h2_util.h"\
+ ".\h2_version.h"\
+ ".\h2_workers.h"\
+
+
+./h2_stream.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_queue.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\h2.h"\
+ ".\h2_config.h"\
+ ".\h2_conn.h"\
+ ".\h2_conn_io.h"\
+ ".\h2_ctx.h"\
+ ".\h2_filter.h"\
+ ".\h2_h2.h"\
+ ".\h2_mplx.h"\
+ ".\h2_private.h"\
+ ".\h2_push.h"\
+ ".\h2_request.h"\
+ ".\h2_session.h"\
+ ".\h2_stream.h"\
+ ".\h2_task.h"\
+ ".\h2_util.h"\
+
+
+./h2_switch.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\h2_config.h"\
+ ".\h2_conn.h"\
+ ".\h2_ctx.h"\
+ ".\h2_h2.h"\
+ ".\h2_private.h"\
+ ".\h2_switch.h"\
+
+
+./h2_task.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_mpm.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_core.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_queue.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_atomic.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_cond.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\h2.h"\
+ ".\h2_config.h"\
+ ".\h2_conn.h"\
+ ".\h2_conn_io.h"\
+ ".\h2_ctx.h"\
+ ".\h2_from_h1.h"\
+ ".\h2_h2.h"\
+ ".\h2_mplx.h"\
+ ".\h2_private.h"\
+ ".\h2_request.h"\
+ ".\h2_session.h"\
+ ".\h2_stream.h"\
+ ".\h2_task.h"\
+
+
+./h2_task_input.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_queue.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\h2.h"\
+ ".\h2_conn.h"\
+ ".\h2_conn_io.h"\
+ ".\h2_mplx.h"\
+ ".\h2_private.h"\
+ ".\h2_request.h"\
+ ".\h2_session.h"\
+ ".\h2_stream.h"\
+ ".\h2_task.h"\
+ ".\h2_util.h"\
+
+
+./h2_task_output.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_queue.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_cond.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\h2.h"\
+ ".\h2_conn.h"\
+ ".\h2_conn_io.h"\
+ ".\h2_from_h1.h"\
+ ".\h2_mplx.h"\
+ ".\h2_private.h"\
+ ".\h2_request.h"\
+ ".\h2_session.h"\
+ ".\h2_stream.h"\
+ ".\h2_task.h"\
+ ".\h2_util.h"\
+
+
+./h2_util.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\h2.h"\
+ ".\h2_private.h"\
+ ".\h2_request.h"\
+ ".\h2_util.h"\
+
+
+./h2_workers.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_mpm.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mpm_common.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_queue.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_atomic.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_cond.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\h2.h"\
+ ".\h2_mplx.h"\
+ ".\h2_private.h"\
+ ".\h2_task.h"\
+ ".\h2_workers.h"\
+
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+./mod_http2.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_queue.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\h2.h"\
+ ".\h2_alt_svc.h"\
+ ".\h2_config.h"\
+ ".\h2_conn.h"\
+ ".\h2_conn_io.h"\
+ ".\h2_ctx.h"\
+ ".\h2_filter.h"\
+ ".\h2_h2.h"\
+ ".\h2_mplx.h"\
+ ".\h2_push.h"\
+ ".\h2_request.h"\
+ ".\h2_session.h"\
+ ".\h2_stream.h"\
+ ".\h2_switch.h"\
+ ".\h2_task.h"\
+ ".\h2_version.h"\
+ ".\mod_http2.h"\
+
diff --git a/modules/http2/mod_http2.dsp b/modules/http2/mod_http2.dsp
new file mode 100644
index 0000000..d9ff222
--- /dev/null
+++ b/modules/http2/mod_http2.dsp
@@ -0,0 +1,187 @@
+# Microsoft Developer Studio Project File - Name="mod_http2" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_http2 - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_http2.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_http2.mak" CFG="mod_http2 - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_http2 - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_http2 - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_http2 - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "ssize_t=long" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../ssl" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/nghttp2/lib/includes" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "ssize_t=long" /Fd"Release\mod_http2_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_http2.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_http2.so" /d LONG_NAME="http2_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib nghttp2.lib /nologo /subsystem:windows /dll /libpath:"..\..\srclib\nghttp2\lib\MSVC_obj" /out:".\Release\mod_http2.so" /base:@..\..\os\win32\BaseAddr.ref,mod_http2.so
+# ADD LINK32 kernel32.lib nghttp2.lib /nologo /subsystem:windows /dll /libpath:"..\..\srclib\nghttp2\lib\MSVC_obj" /incremental:no /debug /out:".\Release\mod_http2.so" /base:@..\..\os\win32\BaseAddr.ref,mod_http2.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_http2.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_http2 - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "ssize_t=long" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../ssl" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/nghttp2/lib/includes" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "ssize_t=long" /Fd"Debug\mod_http2_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_http2.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_http2.so" /d LONG_NAME="http2_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib nghttp2d.lib /nologo /subsystem:windows /dll /libpath:"..\..\srclib\nghttp2\lib\MSVC_obj" /incremental:no /debug /out:".\Debug\mod_http2.so" /base:@..\..\os\win32\BaseAddr.ref,mod_http2.so
+# ADD LINK32 kernel32.lib nghttp2d.lib /nologo /subsystem:windows /dll /libpath:"..\..\srclib\nghttp2\lib\MSVC_obj" /incremental:no /debug /out:".\Debug\mod_http2.so" /base:@..\..\os\win32\BaseAddr.ref,mod_http2.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_http2.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_http2 - Win32 Release"
+# Name "mod_http2 - Win32 Debug"
+# Begin Source File
+
+SOURCE=./h2_bucket_beam.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_bucket_eos.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_c1.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_c1_io.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_c2.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_c2_filter.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_config.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_conn_ctx.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_headers.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_mplx.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_protocol.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_push.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_request.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_session.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_stream.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_switch.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_util.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_workers.c
+# End Source File
+# Begin Source File
+
+SOURCE=./mod_http2.c
+# End Source File
+# Begin Source File
+
+SOURCE=./mod_http2.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/http2/mod_http2.h b/modules/http2/mod_http2.h
new file mode 100644
index 0000000..f68edcd
--- /dev/null
+++ b/modules/http2/mod_http2.h
@@ -0,0 +1,79 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __MOD_HTTP2_H__
+#define __MOD_HTTP2_H__
+
+/** The http2_var_lookup() optional function retrieves HTTP2 environment
+ * variables. */
+APR_DECLARE_OPTIONAL_FN(char *,
+ http2_var_lookup, (apr_pool_t *, server_rec *,
+ conn_rec *, request_rec *, char *));
+
+/** An optional function which returns non-zero if the given connection
+ * or its master connection is using HTTP/2. */
+APR_DECLARE_OPTIONAL_FN(int,
+ http2_is_h2, (conn_rec *));
+
+APR_DECLARE_OPTIONAL_FN(void,
+ http2_get_num_workers, (server_rec *s,
+ int *minw, int *max));
+
+/*******************************************************************************
+ * START HTTP/2 request engines (DEPRECATED)
+ ******************************************************************************/
+
+/* The following functions were introduced for the experimental mod_proxy_http2
+ * support, but have been abandoned since.
+ * They are still declared here for backward compatibility, in case someone
+ * tries to build an old mod_proxy_http2 against it, but will disappear
+ * completely sometime in the future.
+ */
+
+struct apr_thread_cond_t;
+typedef struct h2_req_engine h2_req_engine;
+typedef void http2_output_consumed(void *ctx, conn_rec *c, apr_off_t consumed);
+
+typedef apr_status_t http2_req_engine_init(h2_req_engine *engine,
+ const char *id,
+ const char *type,
+ apr_pool_t *pool,
+ apr_size_t req_buffer_size,
+ request_rec *r,
+ http2_output_consumed **pconsumed,
+ void **pbaton);
+
+APR_DECLARE_OPTIONAL_FN(apr_status_t,
+ http2_req_engine_push, (const char *engine_type,
+ request_rec *r,
+ http2_req_engine_init *einit));
+
+APR_DECLARE_OPTIONAL_FN(apr_status_t,
+ http2_req_engine_pull, (h2_req_engine *engine,
+ apr_read_type_e block,
+ int capacity,
+ request_rec **pr));
+APR_DECLARE_OPTIONAL_FN(void,
+ http2_req_engine_done, (h2_req_engine *engine,
+ conn_rec *rconn,
+ apr_status_t status));
+
+
+/*******************************************************************************
+ * END HTTP/2 request engines (DEPRECATED)
+ ******************************************************************************/
+
+#endif
diff --git a/modules/http2/mod_http2.mak b/modules/http2/mod_http2.mak
new file mode 100644
index 0000000..26611c7
--- /dev/null
+++ b/modules/http2/mod_http2.mak
@@ -0,0 +1,533 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_http2.dsp
+!IF "$(CFG)" == ""
+CFG=mod_http2 - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_http2 - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_http2 - Win32 Release" && "$(CFG)" != "mod_http2 - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_http2.mak" CFG="mod_http2 - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_http2 - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_http2 - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_http2 - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_http2.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_http2.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\h2_alt_svc.obj"
+ -@erase "$(INTDIR)\h2_bucket_beam.obj"
+ -@erase "$(INTDIR)\h2_bucket_eos.obj"
+ -@erase "$(INTDIR)\h2_config.obj"
+ -@erase "$(INTDIR)\h2_conn.obj"
+ -@erase "$(INTDIR)\h2_conn_io.obj"
+ -@erase "$(INTDIR)\h2_ctx.obj"
+ -@erase "$(INTDIR)\h2_filter.obj"
+ -@erase "$(INTDIR)\h2_from_h1.obj"
+ -@erase "$(INTDIR)\h2_h2.obj"
+ -@erase "$(INTDIR)\h2_headers.obj"
+ -@erase "$(INTDIR)\h2_mplx.obj"
+ -@erase "$(INTDIR)\h2_push.obj"
+ -@erase "$(INTDIR)\h2_request.obj"
+ -@erase "$(INTDIR)\h2_session.obj"
+ -@erase "$(INTDIR)\h2_stream.obj"
+ -@erase "$(INTDIR)\h2_switch.obj"
+ -@erase "$(INTDIR)\h2_task.obj"
+ -@erase "$(INTDIR)\h2_util.obj"
+ -@erase "$(INTDIR)\h2_workers.obj"
+ -@erase "$(INTDIR)\mod_http2.obj"
+ -@erase "$(INTDIR)\mod_http2.res"
+ -@erase "$(INTDIR)\mod_http2_src.idb"
+ -@erase "$(INTDIR)\mod_http2_src.pdb"
+ -@erase "$(OUTDIR)\mod_http2.exp"
+ -@erase "$(OUTDIR)\mod_http2.lib"
+ -@erase "$(OUTDIR)\mod_http2.pdb"
+ -@erase "$(OUTDIR)\mod_http2.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../ssl" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/nghttp2/lib/includes" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D ssize_t=long /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_http2_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_http2.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_http2.so" /d LONG_NAME="http2_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_http2.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib nghttp2.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_http2.pdb" /debug /out:"$(OUTDIR)\mod_http2.so" /implib:"$(OUTDIR)\mod_http2.lib" /libpath:"..\..\srclib\nghttp2\lib\MSVC_obj" /base:@..\..\os\win32\BaseAddr.ref,mod_http2.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\h2_alt_svc.obj" \
+ "$(INTDIR)\h2_bucket_beam.obj" \
+ "$(INTDIR)\h2_bucket_eos.obj" \
+ "$(INTDIR)\h2_config.obj" \
+ "$(INTDIR)\h2_conn.obj" \
+ "$(INTDIR)\h2_conn_io.obj" \
+ "$(INTDIR)\h2_ctx.obj" \
+ "$(INTDIR)\h2_filter.obj" \
+ "$(INTDIR)\h2_from_h1.obj" \
+ "$(INTDIR)\h2_h2.obj" \
+ "$(INTDIR)\h2_headers.obj" \
+ "$(INTDIR)\h2_mplx.obj" \
+ "$(INTDIR)\h2_push.obj" \
+ "$(INTDIR)\h2_request.obj" \
+ "$(INTDIR)\h2_session.obj" \
+ "$(INTDIR)\h2_stream.obj" \
+ "$(INTDIR)\h2_switch.obj" \
+ "$(INTDIR)\h2_task.obj" \
+ "$(INTDIR)\h2_util.obj" \
+ "$(INTDIR)\h2_workers.obj" \
+ "$(INTDIR)\mod_http2.obj" \
+ "$(INTDIR)\mod_http2.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_http2.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_http2.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_http2.so"
+ if exist .\Release\mod_http2.so.manifest mt.exe -manifest .\Release\mod_http2.so.manifest -outputresource:.\Release\mod_http2.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_http2 - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_http2.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_http2.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\h2_alt_svc.obj"
+ -@erase "$(INTDIR)\h2_bucket_beam.obj"
+ -@erase "$(INTDIR)\h2_bucket_eos.obj"
+ -@erase "$(INTDIR)\h2_config.obj"
+ -@erase "$(INTDIR)\h2_conn.obj"
+ -@erase "$(INTDIR)\h2_conn_io.obj"
+ -@erase "$(INTDIR)\h2_ctx.obj"
+ -@erase "$(INTDIR)\h2_filter.obj"
+ -@erase "$(INTDIR)\h2_from_h1.obj"
+ -@erase "$(INTDIR)\h2_h2.obj"
+ -@erase "$(INTDIR)\h2_headers.obj"
+ -@erase "$(INTDIR)\h2_mplx.obj"
+ -@erase "$(INTDIR)\h2_push.obj"
+ -@erase "$(INTDIR)\h2_request.obj"
+ -@erase "$(INTDIR)\h2_session.obj"
+ -@erase "$(INTDIR)\h2_stream.obj"
+ -@erase "$(INTDIR)\h2_switch.obj"
+ -@erase "$(INTDIR)\h2_task.obj"
+ -@erase "$(INTDIR)\h2_util.obj"
+ -@erase "$(INTDIR)\h2_workers.obj"
+ -@erase "$(INTDIR)\mod_http2.obj"
+ -@erase "$(INTDIR)\mod_http2.res"
+ -@erase "$(INTDIR)\mod_http2_src.idb"
+ -@erase "$(INTDIR)\mod_http2_src.pdb"
+ -@erase "$(OUTDIR)\mod_http2.exp"
+ -@erase "$(OUTDIR)\mod_http2.lib"
+ -@erase "$(OUTDIR)\mod_http2.pdb"
+ -@erase "$(OUTDIR)\mod_http2.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../ssl" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/nghttp2/lib/includes" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D ssize_t=long /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_http2_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_http2.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_http2.so" /d LONG_NAME="http2_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_http2.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib nghttp2d.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_http2.pdb" /debug /out:"$(OUTDIR)\mod_http2.so" /implib:"$(OUTDIR)\mod_http2.lib" /libpath:"..\..\srclib\nghttp2\lib\MSVC_obj" /base:@..\..\os\win32\BaseAddr.ref,mod_http2.so
+LINK32_OBJS= \
+ "$(INTDIR)\h2_alt_svc.obj" \
+ "$(INTDIR)\h2_bucket_beam.obj" \
+ "$(INTDIR)\h2_bucket_eos.obj" \
+ "$(INTDIR)\h2_config.obj" \
+ "$(INTDIR)\h2_conn.obj" \
+ "$(INTDIR)\h2_conn_io.obj" \
+ "$(INTDIR)\h2_ctx.obj" \
+ "$(INTDIR)\h2_filter.obj" \
+ "$(INTDIR)\h2_from_h1.obj" \
+ "$(INTDIR)\h2_h2.obj" \
+ "$(INTDIR)\h2_headers.obj" \
+ "$(INTDIR)\h2_mplx.obj" \
+ "$(INTDIR)\h2_push.obj" \
+ "$(INTDIR)\h2_request.obj" \
+ "$(INTDIR)\h2_session.obj" \
+ "$(INTDIR)\h2_stream.obj" \
+ "$(INTDIR)\h2_switch.obj" \
+ "$(INTDIR)\h2_task.obj" \
+ "$(INTDIR)\h2_util.obj" \
+ "$(INTDIR)\h2_workers.obj" \
+ "$(INTDIR)\mod_http2.obj" \
+ "$(INTDIR)\mod_http2.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_http2.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_http2.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_http2.so"
+ if exist .\Debug\mod_http2.so.manifest mt.exe -manifest .\Debug\mod_http2.so.manifest -outputresource:.\Debug\mod_http2.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_http2.dep")
+!INCLUDE "mod_http2.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_http2.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_http2 - Win32 Release" || "$(CFG)" == "mod_http2 - Win32 Debug"
+
+!IF "$(CFG)" == "mod_http2 - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\http2"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\http2"
+
+!ELSEIF "$(CFG)" == "mod_http2 - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\http2"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\http2"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_http2 - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\http2"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\http2"
+
+!ELSEIF "$(CFG)" == "mod_http2 - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\http2"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\http2"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_http2 - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\http2"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\http2"
+
+!ELSEIF "$(CFG)" == "mod_http2 - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\http2"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\http2"
+
+!ENDIF
+
+SOURCE=./h2_alt_svc.c
+
+"$(INTDIR)\h2_alt_svc.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./h2_bucket_beam.c
+
+"$(INTDIR)/h2_bucket_beam.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./h2_bucket_eos.c
+
+"$(INTDIR)\h2_bucket_eos.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./h2_config.c
+
+"$(INTDIR)\h2_config.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./h2_conn.c
+
+"$(INTDIR)\h2_conn.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./h2_conn_io.c
+
+"$(INTDIR)\h2_conn_io.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./h2_ctx.c
+
+"$(INTDIR)\h2_ctx.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./h2_filter.c
+
+"$(INTDIR)\h2_filter.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./h2_from_h1.c
+
+"$(INTDIR)\h2_from_h1.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./h2_h2.c
+
+"$(INTDIR)\h2_h2.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./h2_headers.c
+
+"$(INTDIR)\h2_headers.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./h2_mplx.c
+
+"$(INTDIR)\h2_mplx.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./h2_push.c
+
+"$(INTDIR)\h2_push.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./h2_request.c
+
+"$(INTDIR)\h2_request.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./h2_session.c
+
+"$(INTDIR)\h2_session.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./h2_stream.c
+
+"$(INTDIR)\h2_stream.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./h2_switch.c
+
+"$(INTDIR)\h2_switch.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./h2_task.c
+
+"$(INTDIR)\h2_task.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./h2_util.c
+
+"$(INTDIR)\h2_util.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./h2_workers.c
+
+"$(INTDIR)\h2_workers.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_http2 - Win32 Release"
+
+
+"$(INTDIR)\mod_http2.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_http2.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_http2.so" /d LONG_NAME="http2_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_http2 - Win32 Debug"
+
+
+"$(INTDIR)\mod_http2.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_http2.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_http2.so" /d LONG_NAME="http2_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=./mod_http2.c
+
+"$(INTDIR)\mod_http2.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/http2/mod_proxy_http2.c b/modules/http2/mod_proxy_http2.c
new file mode 100644
index 0000000..aa299b9
--- /dev/null
+++ b/modules/http2/mod_proxy_http2.c
@@ -0,0 +1,458 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <nghttp2/nghttp2.h>
+
+#include <ap_mmn.h>
+#include <httpd.h>
+#include <mod_proxy.h>
+#include "mod_http2.h"
+
+
+#include "mod_proxy_http2.h"
+#include "h2.h"
+#include "h2_proxy_util.h"
+#include "h2_version.h"
+#include "h2_proxy_session.h"
+
+#define H2MIN(x,y) ((x) < (y) ? (x) : (y))
+
+static void register_hook(apr_pool_t *p);
+
+AP_DECLARE_MODULE(proxy_http2) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ NULL, /* command apr_table_t */
+ register_hook, /* register hooks */
+#if defined(AP_MODULE_FLAG_NONE)
+ AP_MODULE_FLAG_ALWAYS_MERGE
+#endif
+};
+
+/* Optional functions from mod_http2 */
+static int (*is_h2)(conn_rec *c);
+
+typedef struct h2_proxy_ctx {
+ const char *id;
+ conn_rec *master;
+ conn_rec *owner;
+ apr_pool_t *pool;
+ server_rec *server;
+ const char *proxy_func;
+ char server_portstr[32];
+ proxy_conn_rec *p_conn;
+ proxy_worker *worker;
+ proxy_server_conf *conf;
+
+ apr_size_t req_buffer_size;
+ int capacity;
+
+ unsigned is_ssl : 1;
+
+ request_rec *r; /* the request processed in this ctx */
+ apr_status_t r_status; /* status of request work */
+ int r_done; /* request was processed, not necessarily successfully */
+ int r_may_retry; /* request may be retried */
+ h2_proxy_session *session; /* current http2 session against backend */
+} h2_proxy_ctx;
+
+static int h2_proxy_post_config(apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ void *data = NULL;
+ const char *init_key = "mod_proxy_http2_init_counter";
+ nghttp2_info *ngh2;
+ apr_status_t status = APR_SUCCESS;
+ (void)plog;(void)ptemp;
+
+ apr_pool_userdata_get(&data, init_key, s->process->pool);
+ if ( data == NULL ) {
+ apr_pool_userdata_set((const void *)1, init_key,
+ apr_pool_cleanup_null, s->process->pool);
+ return APR_SUCCESS;
+ }
+
+ ngh2 = nghttp2_version(0);
+ ap_log_error( APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(03349)
+ "mod_proxy_http2 (v%s, nghttp2 %s), initializing...",
+ MOD_HTTP2_VERSION, ngh2? ngh2->version_str : "unknown");
+
+ is_h2 = APR_RETRIEVE_OPTIONAL_FN(http2_is_h2);
+
+ return status;
+}
+
+/**
+ * canonicalize the url into the request, if it is meant for us.
+ * slightly modified copy from mod_http
+ */
+static int proxy_http2_canon(request_rec *r, char *url)
+{
+ char *host, *path, sport[7];
+ char *search = NULL;
+ const char *err;
+ const char *scheme;
+ const char *http_scheme;
+ apr_port_t port, def_port;
+
+ /* ap_port_of_scheme() */
+ if (ap_cstr_casecmpn(url, "h2c:", 4) == 0) {
+ url += 4;
+ scheme = "h2c";
+ http_scheme = "http";
+ }
+ else if (ap_cstr_casecmpn(url, "h2:", 3) == 0) {
+ url += 3;
+ scheme = "h2";
+ http_scheme = "https";
+ }
+ else {
+ return DECLINED;
+ }
+ port = def_port = ap_proxy_port_of_scheme(http_scheme);
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "HTTP2: canonicalising URL %s", url);
+
+ /* do syntatic check.
+ * We break the URL into host, port, path, search
+ */
+ err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(03350)
+ "error parsing URL %s: %s", url, err);
+ return HTTP_BAD_REQUEST;
+ }
+
+ /*
+ * now parse path/search args, according to rfc1738:
+ * process the path.
+ *
+ * In a reverse proxy, our URL has been processed, so canonicalise
+ * unless proxy-nocanon is set to say it's raw
+ * In a forward proxy, we have and MUST NOT MANGLE the original.
+ */
+ switch (r->proxyreq) {
+ default: /* wtf are we doing here? */
+ case PROXYREQ_REVERSE:
+ if (apr_table_get(r->notes, "proxy-nocanon")) {
+ path = url; /* this is the raw path */
+ }
+ else {
+ path = ap_proxy_canonenc(r->pool, url, (int)strlen(url),
+ enc_path, 0, r->proxyreq);
+ search = r->args;
+ if (search && *(ap_scan_vchar_obstext(search))) {
+ /*
+ * We have a raw control character or a ' ' in r->args.
+ * Correct encoding was missed.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO()
+ "To be forwarded query string contains control "
+ "characters or spaces");
+ return HTTP_FORBIDDEN;
+ }
+ }
+ break;
+ case PROXYREQ_PROXY:
+ path = url;
+ break;
+ }
+
+ if (path == NULL) {
+ return HTTP_BAD_REQUEST;
+ }
+
+ if (port != def_port) {
+ apr_snprintf(sport, sizeof(sport), ":%d", port);
+ }
+ else {
+ sport[0] = '\0';
+ }
+
+ if (ap_strchr_c(host, ':')) { /* if literal IPv6 address */
+ host = apr_pstrcat(r->pool, "[", host, "]", NULL);
+ }
+ r->filename = apr_pstrcat(r->pool, "proxy:", scheme, "://", host, sport,
+ "/", path, (search) ? "?" : "", (search) ? search : "", NULL);
+ return OK;
+}
+
+static apr_status_t add_request(h2_proxy_session *session, request_rec *r)
+{
+ h2_proxy_ctx *ctx = session->user_data;
+ const char *url;
+ apr_status_t status;
+
+ url = apr_table_get(r->notes, H2_PROXY_REQ_URL_NOTE);
+ apr_table_setn(r->notes, "proxy-source-port", apr_psprintf(r->pool, "%hu",
+ ctx->p_conn->connection->local_addr->port));
+ status = h2_proxy_session_submit(session, url, r, 1);
+ if (status != APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, status, r->connection, APLOGNO(03351)
+ "pass request body failed to %pI (%s) from %s (%s)",
+ ctx->p_conn->addr, ctx->p_conn->hostname ?
+ ctx->p_conn->hostname: "", session->c->client_ip,
+ session->c->remote_host ? session->c->remote_host: "");
+ }
+ return status;
+}
+
+static void request_done(h2_proxy_ctx *ctx, request_rec *r,
+ apr_status_t status, int touched)
+{
+ if (r == ctx->r) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, r->connection,
+ "h2_proxy_session(%s): request done, touched=%d",
+ ctx->id, touched);
+ ctx->r_done = 1;
+ if (touched) ctx->r_may_retry = 0;
+ ctx->r_status = ((status == APR_SUCCESS)? APR_SUCCESS
+ : HTTP_SERVICE_UNAVAILABLE);
+ }
+}
+
+static void session_req_done(h2_proxy_session *session, request_rec *r,
+ apr_status_t status, int touched)
+{
+ request_done(session->user_data, r, status, touched);
+}
+
+static apr_status_t ctx_run(h2_proxy_ctx *ctx) {
+ apr_status_t status = OK;
+ int h2_front;
+
+ /* Step Four: Send the Request in a new HTTP/2 stream and
+ * loop until we got the response or encounter errors.
+ */
+ h2_front = is_h2? is_h2(ctx->owner) : 0;
+ ctx->session = h2_proxy_session_setup(ctx->id, ctx->p_conn, ctx->conf,
+ h2_front, 30,
+ h2_proxy_log2((int)ctx->req_buffer_size),
+ session_req_done);
+ if (!ctx->session) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->owner,
+ APLOGNO(03372) "session unavailable");
+ return HTTP_SERVICE_UNAVAILABLE;
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->owner, APLOGNO(03373)
+ "eng(%s): run session %s", ctx->id, ctx->session->id);
+ ctx->session->user_data = ctx;
+
+ ctx->r_done = 0;
+ add_request(ctx->session, ctx->r);
+
+ while (!ctx->owner->aborted && !ctx->r_done) {
+
+ status = h2_proxy_session_process(ctx->session);
+ if (status != APR_SUCCESS) {
+ /* Encountered an error during session processing */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->owner,
+ APLOGNO(03375) "eng(%s): end of session %s",
+ ctx->id, ctx->session->id);
+ /* Any open stream of that session needs to
+ * a) be reopened on the new session iff safe to do so
+ * b) reported as done (failed) otherwise
+ */
+ h2_proxy_session_cleanup(ctx->session, session_req_done);
+ goto out;
+ }
+ }
+
+out:
+ if (ctx->owner->aborted) {
+ /* master connection gone */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->owner,
+ APLOGNO(03374) "eng(%s): master connection gone", ctx->id);
+ /* cancel all ongoing requests */
+ h2_proxy_session_cancel_all(ctx->session);
+ h2_proxy_session_process(ctx->session);
+ }
+
+ ctx->session->user_data = NULL;
+ ctx->session = NULL;
+ return status;
+}
+
+static int proxy_http2_handler(request_rec *r,
+ proxy_worker *worker,
+ proxy_server_conf *conf,
+ char *url,
+ const char *proxyname,
+ apr_port_t proxyport)
+{
+ const char *proxy_func;
+ char *locurl = url, *u;
+ apr_size_t slen;
+ int is_ssl = 0;
+ apr_status_t status;
+ h2_proxy_ctx *ctx;
+ apr_uri_t uri;
+ int reconnects = 0;
+
+ /* find the scheme */
+ if ((url[0] != 'h' && url[0] != 'H') || url[1] != '2') {
+ return DECLINED;
+ }
+ u = strchr(url, ':');
+ if (u == NULL || u[1] != '/' || u[2] != '/' || u[3] == '\0') {
+ return DECLINED;
+ }
+ slen = (u - url);
+ switch(slen) {
+ case 2:
+ proxy_func = "H2";
+ is_ssl = 1;
+ break;
+ case 3:
+ if (url[2] != 'c' && url[2] != 'C') {
+ return DECLINED;
+ }
+ proxy_func = "H2C";
+ break;
+ default:
+ return DECLINED;
+ }
+
+ ctx = apr_pcalloc(r->pool, sizeof(*ctx));
+ ctx->master = r->connection->master? r->connection->master : r->connection;
+ ctx->id = apr_psprintf(r->pool, "%ld", (long)ctx->master->id);
+ ctx->owner = r->connection;
+ ctx->pool = r->pool;
+ ctx->server = r->server;
+ ctx->proxy_func = proxy_func;
+ ctx->is_ssl = is_ssl;
+ ctx->worker = worker;
+ ctx->conf = conf;
+ ctx->req_buffer_size = (32*1024);
+ ctx->r = r;
+ ctx->r_status = status = HTTP_SERVICE_UNAVAILABLE;
+ ctx->r_done = 0;
+ ctx->r_may_retry = 1;
+
+ ap_set_module_config(ctx->owner->conn_config, &proxy_http2_module, ctx);
+
+ /* scheme says, this is for us. */
+ apr_table_setn(ctx->r->notes, H2_PROXY_REQ_URL_NOTE, url);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, ctx->r,
+ "H2: serving URL %s", url);
+
+run_connect:
+ if (ctx->owner->aborted) goto cleanup;
+
+ /* Get a proxy_conn_rec from the worker, might be a new one, might
+ * be one still open from another request, or it might fail if the
+ * worker is stopped or in error. */
+ if ((status = ap_proxy_acquire_connection(ctx->proxy_func, &ctx->p_conn,
+ ctx->worker, ctx->server)) != OK) {
+ goto cleanup;
+ }
+
+ ctx->p_conn->is_ssl = ctx->is_ssl;
+
+ /* Step One: Determine the URL to connect to (might be a proxy),
+ * initialize the backend accordingly and determine the server
+ * port string we can expect in responses. */
+ if ((status = ap_proxy_determine_connection(ctx->pool, ctx->r, conf, worker,
+ ctx->p_conn, &uri, &locurl,
+ proxyname, proxyport,
+ ctx->server_portstr,
+ sizeof(ctx->server_portstr))) != OK) {
+ goto cleanup;
+ }
+
+ /* Step Two: Make the Connection (or check that an already existing
+ * socket is still usable). On success, we have a socket connected to
+ * backend->hostname. */
+ if (ap_proxy_connect_backend(ctx->proxy_func, ctx->p_conn, ctx->worker,
+ ctx->server)) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->owner, APLOGNO(03352)
+ "H2: failed to make connection to backend: %s",
+ ctx->p_conn->hostname);
+ goto cleanup;
+ }
+
+ /* Step Three: Create conn_rec for the socket we have open now. */
+ status = ap_proxy_connection_create_ex(ctx->proxy_func, ctx->p_conn, ctx->r);
+ if (status != OK) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->owner, APLOGNO(03353)
+ "setup new connection: is_ssl=%d %s %s %s",
+ ctx->p_conn->is_ssl, ctx->p_conn->ssl_hostname,
+ locurl, ctx->p_conn->hostname);
+ ctx->r_status = status;
+ goto cleanup;
+ }
+
+ if (!ctx->p_conn->data && ctx->is_ssl) {
+ /* New SSL connection: set a note on the connection about what
+ * protocol we need. */
+ apr_table_setn(ctx->p_conn->connection->notes,
+ "proxy-request-alpn-protos", "h2");
+ }
+
+ if (ctx->owner->aborted) goto cleanup;
+ status = ctx_run(ctx);
+
+ if (ctx->r_status != APR_SUCCESS && ctx->r_may_retry && !ctx->owner->aborted) {
+ /* Not successfully processed, but may retry, tear down old conn and start over */
+ if (ctx->p_conn) {
+ ctx->p_conn->close = 1;
+#if AP_MODULE_MAGIC_AT_LEAST(20140207, 2)
+ proxy_run_detach_backend(r, ctx->p_conn);
+#endif
+ ap_proxy_release_connection(ctx->proxy_func, ctx->p_conn, ctx->server);
+ ctx->p_conn = NULL;
+ }
+ ++reconnects;
+ if (reconnects < 2) {
+ goto run_connect;
+ }
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->owner, APLOGNO(10023)
+ "giving up after %d reconnects, request-done=%d",
+ reconnects, ctx->r_done);
+ }
+
+cleanup:
+ if (ctx->p_conn) {
+ if (status != APR_SUCCESS) {
+ /* close socket when errors happened or session shut down (EOF) */
+ ctx->p_conn->close = 1;
+ }
+#if AP_MODULE_MAGIC_AT_LEAST(20140207, 2)
+ proxy_run_detach_backend(ctx->r, ctx->p_conn);
+#endif
+ ap_proxy_release_connection(ctx->proxy_func, ctx->p_conn, ctx->server);
+ ctx->p_conn = NULL;
+ }
+
+ ap_set_module_config(ctx->owner->conn_config, &proxy_http2_module, NULL);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->owner,
+ APLOGNO(03377) "leaving handler");
+ return ctx->r_status;
+}
+
+static void register_hook(apr_pool_t *p)
+{
+ ap_hook_post_config(h2_proxy_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+
+ proxy_hook_scheme_handler(proxy_http2_handler, NULL, NULL, APR_HOOK_FIRST);
+ proxy_hook_canon_handler(proxy_http2_canon, NULL, NULL, APR_HOOK_FIRST);
+}
+
diff --git a/modules/http2/mod_proxy_http2.dep b/modules/http2/mod_proxy_http2.dep
new file mode 100644
index 0000000..641fca6
--- /dev/null
+++ b/modules/http2/mod_proxy_http2.dep
@@ -0,0 +1,208 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_proxy_http2.mak
+
+./h2_proxy_session.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_mpm.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_slotmem.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_proxy.h"\
+ "..\..\include\mpm_common.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_date.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_reslist.h"\
+ "..\..\srclib\apr-util\include\apr_strmatch.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ "..\..\srclib\nghttp2\lib\includes\nghttp2\nghttp2.h"\
+ "..\..\srclib\nghttp2\lib\includes\nghttp2\nghttp2ver.h"\
+ ".\h2.h"\
+ ".\h2_proxy_session.h"\
+ ".\h2_proxy_util.h"\
+ ".\mod_http2.h"\
+
+
+./h2_proxy_util.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ "..\..\srclib\nghttp2\lib\includes\nghttp2\nghttp2.h"\
+ "..\..\srclib\nghttp2\lib\includes\nghttp2\nghttp2ver.h"\
+ ".\h2.h"\
+ ".\h2_proxy_util.h"\
+
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+./mod_proxy_http2.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_slotmem.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_proxy.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_date.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_reslist.h"\
+ "..\..\srclib\apr-util\include\apr_strmatch.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ "..\..\srclib\nghttp2\lib\includes\nghttp2\nghttp2.h"\
+ "..\..\srclib\nghttp2\lib\includes\nghttp2\nghttp2ver.h"\
+ ".\h2.h"\
+ ".\h2_proxy_session.h"\
+ ".\h2_request.h"\
+ ".\h2_proxy_util.h"\
+ ".\h2_version.h"\
+ ".\mod_http2.h"\
+ ".\mod_proxy_http2.h"\
+
diff --git a/modules/http2/mod_proxy_http2.dsp b/modules/http2/mod_proxy_http2.dsp
new file mode 100644
index 0000000..5d6305f
--- /dev/null
+++ b/modules/http2/mod_proxy_http2.dsp
@@ -0,0 +1,119 @@
+# Microsoft Developer Studio Project File - Name="mod_proxy_http2" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_proxy_http2 - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_http2.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_http2.mak" CFG="mod_proxy_http2 - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy_http2 - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy_http2 - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_proxy_http2 - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "ssize_t=long" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../ssl" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/nghttp2/lib/includes" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "ssize_t=long" /Fd"Release\mod_proxy_http2_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_proxy_http2.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_proxy_http2.so" /d LONG_NAME="http2_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib nghttp2.lib /nologo /subsystem:windows /dll /libpath:"..\..\srclib\nghttp2\lib\MSVC_obj" /out:".\Release\mod_proxy_http2.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_http2.so
+# ADD LINK32 kernel32.lib nghttp2.lib /nologo /subsystem:windows /dll /libpath:"..\..\srclib\nghttp2\lib\MSVC_obj" /incremental:no /debug /out:".\Release\mod_proxy_http2.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_http2.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_proxy_http2.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_proxy_http2 - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "ssize_t=long" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../ssl" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/nghttp2/lib/includes" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "ssize_t=long" /Fd"Debug\mod_proxy_http2_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_proxy_http2.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_proxy_http2.so" /d LONG_NAME="http2_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib nghttp2d.lib /nologo /subsystem:windows /dll /libpath:"..\..\srclib\nghttp2\lib\MSVC_obj" /incremental:no /debug /out:".\Debug\mod_proxy_http2.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_http2.so
+# ADD LINK32 kernel32.lib nghttp2d.lib /nologo /subsystem:windows /dll /libpath:"..\..\srclib\nghttp2\lib\MSVC_obj" /incremental:no /debug /out:".\Debug\mod_proxy_http2.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_http2.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_proxy_http2.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_proxy_http2 - Win32 Release"
+# Name "mod_proxy_http2 - Win32 Debug"
+# Begin Source File
+
+SOURCE=./h2_proxy_session.c
+# End Source File
+# Begin Source File
+
+SOURCE=./h2_proxy_util.c
+# End Source File
+# Begin Source File
+
+SOURCE=./mod_proxy_http2.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/http2/mod_proxy_http2.h b/modules/http2/mod_proxy_http2.h
new file mode 100644
index 0000000..0048ed9
--- /dev/null
+++ b/modules/http2/mod_proxy_http2.h
@@ -0,0 +1,21 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __MOD_PROXY_HTTP2_H__
+#define __MOD_PROXY_HTTP2_H__
+
+
+#endif
diff --git a/modules/http2/mod_proxy_http2.mak b/modules/http2/mod_proxy_http2.mak
new file mode 100644
index 0000000..e8e0624
--- /dev/null
+++ b/modules/http2/mod_proxy_http2.mak
@@ -0,0 +1,427 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_proxy_http2.dsp
+!IF "$(CFG)" == ""
+CFG=mod_proxy_http2 - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_proxy_http2 - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_proxy_http2 - Win32 Release" && "$(CFG)" != "mod_proxy_http2 - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_http2.mak" CFG="mod_proxy_http2 - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy_http2 - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy_http2 - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_http2 - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_proxy_http2.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_proxy - Win32 Release" "mod_http2 - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_proxy_http2.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_http2 - Win32 ReleaseCLEAN" "mod_proxy - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\h2_proxy_session.obj"
+ -@erase "$(INTDIR)\h2_proxy_util.obj"
+ -@erase "$(INTDIR)\mod_proxy_http2.obj"
+ -@erase "$(INTDIR)\mod_proxy_http2.res"
+ -@erase "$(INTDIR)\mod_proxy_http2_src.idb"
+ -@erase "$(INTDIR)\mod_proxy_http2_src.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_http2.exp"
+ -@erase "$(OUTDIR)\mod_proxy_http2.lib"
+ -@erase "$(OUTDIR)\mod_proxy_http2.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_http2.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../ssl" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/nghttp2/lib/includes" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D ssize_t=long /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_proxy_http2_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_proxy_http2.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_proxy_http2.so" /d LONG_NAME="http2_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_proxy_http2.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib nghttp2.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_proxy_http2.pdb" /debug /out:"$(OUTDIR)\mod_proxy_http2.so" /implib:"$(OUTDIR)\mod_proxy_http2.lib" /libpath:"..\..\srclib\nghttp2\lib\MSVC_obj" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_http2.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\h2_proxy_session.obj" \
+ "$(INTDIR)\h2_proxy_util.obj" \
+ "$(INTDIR)\mod_proxy_http2.obj" \
+ "$(INTDIR)\mod_proxy_http2.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib" \
+ "$(OUTDIR)\mod_http2.lib" \
+ "..\proxy\Release\mod_proxy.lib"
+
+"$(OUTDIR)\mod_proxy_http2.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_proxy_http2.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_proxy_http2.so"
+ if exist .\Release\mod_proxy_http2.so.manifest mt.exe -manifest .\Release\mod_proxy_http2.so.manifest -outputresource:.\Release\mod_proxy_http2.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_proxy_http2 - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_proxy_http2.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_proxy - Win32 Debug" "mod_http2 - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_proxy_http2.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_http2 - Win32 DebugCLEAN" "mod_proxy - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\h2_proxy_session.obj"
+ -@erase "$(INTDIR)\h2_proxy_util.obj"
+ -@erase "$(INTDIR)\mod_proxy_http2.obj"
+ -@erase "$(INTDIR)\mod_proxy_http2.res"
+ -@erase "$(INTDIR)\mod_proxy_http2_src.idb"
+ -@erase "$(INTDIR)\mod_proxy_http2_src.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_http2.exp"
+ -@erase "$(OUTDIR)\mod_proxy_http2.lib"
+ -@erase "$(OUTDIR)\mod_proxy_http2.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_http2.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../ssl" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/nghttp2/lib/includes" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D ssize_t=long /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_proxy_http2_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_proxy_http2.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_proxy_http2.so" /d LONG_NAME="http2_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_proxy_http2.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib nghttp2d.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_proxy_http2.pdb" /debug /out:"$(OUTDIR)\mod_proxy_http2.so" /implib:"$(OUTDIR)\mod_proxy_http2.lib" /libpath:"..\..\srclib\nghttp2\lib\MSVC_obj" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_http2.so
+LINK32_OBJS= \
+ "$(INTDIR)\h2_proxy_session.obj" \
+ "$(INTDIR)\h2_proxy_util.obj" \
+ "$(INTDIR)\mod_proxy_http2.obj" \
+ "$(INTDIR)\mod_proxy_http2.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib" \
+ "$(OUTDIR)\mod_http2.lib" \
+ "..\proxy\Debug\mod_proxy.lib"
+
+"$(OUTDIR)\mod_proxy_http2.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_proxy_http2.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_proxy_http2.so"
+ if exist .\Debug\mod_proxy_http2.so.manifest mt.exe -manifest .\Debug\mod_proxy_http2.so.manifest -outputresource:.\Debug\mod_proxy_http2.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_proxy_http2.dep")
+!INCLUDE "mod_proxy_http2.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_proxy_http2.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_proxy_http2 - Win32 Release" || "$(CFG)" == "mod_proxy_http2 - Win32 Debug"
+
+!IF "$(CFG)" == "mod_proxy_http2 - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\http2"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\http2"
+
+!ELSEIF "$(CFG)" == "mod_proxy_http2 - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\http2"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\http2"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_http2 - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\http2"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\http2"
+
+!ELSEIF "$(CFG)" == "mod_proxy_http2 - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\http2"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\http2"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_http2 - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\http2"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\http2"
+
+!ELSEIF "$(CFG)" == "mod_proxy_http2 - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\http2"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\http2"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_http2 - Win32 Release"
+
+"mod_http2 - Win32 Release" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_http2.mak" CFG="mod_http2 - Win32 Release"
+ cd "."
+
+"mod_http2 - Win32 ReleaseCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_http2.mak" CFG="mod_http2 - Win32 Release" RECURSE=1 CLEAN
+ cd "."
+
+!ELSEIF "$(CFG)" == "mod_proxy_http2 - Win32 Debug"
+
+"mod_http2 - Win32 Debug" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_http2.mak" CFG="mod_http2 - Win32 Debug"
+ cd "."
+
+"mod_http2 - Win32 DebugCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_http2.mak" CFG="mod_http2 - Win32 Debug" RECURSE=1 CLEAN
+ cd "."
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_http2 - Win32 Release"
+
+"mod_proxy - Win32 Release" :
+ cd ".\..\proxy"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Release"
+ cd "..\http2"
+
+"mod_proxy - Win32 ReleaseCLEAN" :
+ cd ".\..\proxy"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Release" RECURSE=1 CLEAN
+ cd "..\http2"
+
+!ELSEIF "$(CFG)" == "mod_proxy_http2 - Win32 Debug"
+
+"mod_proxy - Win32 Debug" :
+ cd ".\..\proxy"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Debug"
+ cd "..\http2"
+
+"mod_proxy - Win32 DebugCLEAN" :
+ cd ".\..\proxy"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\http2"
+
+!ENDIF
+
+SOURCE=./h2_proxy_session.c
+
+"$(INTDIR)\h2_proxy_session.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./h2_proxy_util.c
+
+"$(INTDIR)\h2_proxy_util.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_proxy_http2 - Win32 Release"
+
+
+"$(INTDIR)\mod_proxy_http2.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_proxy_http2.res" /i "../../include" /i "../../srclib/apr/include" /i "\Build11\httpd-2.4.21-dev-mph2\build\win32" /d "NDEBUG" /d BIN_NAME="mod_proxy_http2.so" /d LONG_NAME="http2_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_proxy_http2 - Win32 Debug"
+
+
+"$(INTDIR)\mod_proxy_http2.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_proxy_http2.res" /i "../../include" /i "../../srclib/apr/include" /i "\Build11\httpd-2.4.21-dev-mph2\build\win32" /d "_DEBUG" /d BIN_NAME="mod_proxy_http2.so" /d LONG_NAME="http2_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=./mod_proxy_http2.c
+
+"$(INTDIR)\mod_proxy_http2.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/ldap/Makefile.in b/modules/ldap/Makefile.in
new file mode 100644
index 0000000..7c5c149
--- /dev/null
+++ b/modules/ldap/Makefile.in
@@ -0,0 +1,3 @@
+# a modules Makefile has no explicit targets -- they will be defined by
+# whatever modules are enabled. just grab special.mk to deal with this.
+include $(top_srcdir)/build/special.mk
diff --git a/modules/ldap/NWGNUmakefile b/modules/ldap/NWGNUmakefile
new file mode 100644
index 0000000..ef83025
--- /dev/null
+++ b/modules/ldap/NWGNUmakefile
@@ -0,0 +1,264 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(LDAPSDK)/inc \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+#LDAP client requires the use of Winsock
+#
+ifdef USE_STDSOCKETS
+XDEFINES += -DUSE_WINSOCK \
+ $(EOLIST)
+endif
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = utilldap
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) LDAP Authentication Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = UtilLDAP Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/utilldap.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/util_ldap.o \
+ $(OBJDIR)/util_ldap_cache.o \
+ $(OBJDIR)/util_ldap_cache_mgr.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ lldapsdk \
+ lldapssl \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ @lldapsdk.imp \
+ @lldapssl.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ ldap_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/ldap/README.ldap b/modules/ldap/README.ldap
new file mode 100644
index 0000000..116707e
--- /dev/null
+++ b/modules/ldap/README.ldap
@@ -0,0 +1,47 @@
+Quick installation instructions (UNIX):
+
+- Building on generic Unix:
+
+ Add generic ldap support and the TWO ldap modules to the build, like this:
+
+ ./configure --with-ldap --enable-ldap --enable-authnz-ldap
+
+ The --with-ldap switches on LDAP library linking in apr-util. Make
+ sure that you have an LDAP client library available such as those
+ from Netscape/iPlanet/Sun One or the OpenLDAP project.
+
+ The --enable-ldap option switches on the LDAP caching module. This
+ module is a support module for other LDAP modules, and is not useful
+ on its own. This module is required, but caching can be disabled
+ via the configuration directive LDAPCacheEntries.
+
+ The --enable-auth-ldap option switches on the LDAP authentication
+ module.
+
+- Building on AIX:
+
+ The following ./configure line is reported to work for AIX:
+
+ CC=cc_r; export CC
+ CPPFLAGS=-qcpluscmt;export CPPFLAGS
+ ./configure --with-mpm=worker --prefix=/usr/local/apache \
+ --enable-dav=static --enable-dav_fs=static --enable-ssl=static
+ --with-ldap=yes --with-ldap-include=/usr/local/include
+ --with-ldap-lib=/usr/local/lib --enable-ldap=static
+ --enable-authnz-ldap=static
+
+
+Quick installation instructions (win32):
+
+1. copy the file srclib\apr-util\include\apr_ldap.hw to apr_ldap.h
+2. the netscape/iplanet ldap libraries are installed in srclib\ldap
+3. Compile the two modules util_ldap and mod_authnz_ldap using the dsp files
+4. You get a mod_authnz_ldap.so and a mod_ldap.so module
+5. Put them in the modules directory, don't forget to copy the
+ nsldap32v50.dll somewhere where httpd.exe will find it
+6. Load the two modules in your httpd.conf, like below:
+ LoadModule ldap_module modules/mod_ldap.so
+ LoadModule authnz_ldap_module modules/mod_authnz_ldap.so
+7. Configure the directories as described in the docus.
+
+
diff --git a/modules/ldap/config.m4 b/modules/ldap/config.m4
new file mode 100644
index 0000000..3345bcd
--- /dev/null
+++ b/modules/ldap/config.m4
@@ -0,0 +1,25 @@
+
+dnl APACHE_MODULE(name, helptext[, objects[, structname[, default[, config]]]])
+
+APACHE_MODPATH_INIT(ldap)
+
+ldap_objects="util_ldap.lo util_ldap_cache.lo util_ldap_cache_mgr.lo"
+APACHE_MODULE(ldap, LDAP caching and connection pooling services, $ldap_objects, , most , [
+ APACHE_CHECK_APR_HAS_LDAP
+ if test "$ac_cv_APR_HAS_LDAP" = "yes" ; then
+ if test -z "$apu_config" ; then
+ LDAP_LIBS="`$apr_config --ldap-libs`"
+ else
+ LDAP_LIBS="`$apu_config --ldap-libs`"
+ fi
+ APR_ADDTO(MOD_LDAP_LDADD, [$LDAP_LIBS])
+ AC_SUBST(MOD_LDAP_LDADD)
+ else
+ AC_MSG_WARN([apr/apr-util is compiled without ldap support])
+ enable_ldap=no
+ fi
+])
+
+APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/$modpath_current])
+
+APACHE_MODPATH_FINISH
diff --git a/modules/ldap/mod_ldap.dep b/modules/ldap/mod_ldap.dep
new file mode 100644
index 0000000..37dfd9e
--- /dev/null
+++ b/modules/ldap/mod_ldap.dep
@@ -0,0 +1,192 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_ldap.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\util_ldap.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_ldap.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\srclib\apr-util\include\apr_anylock.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_ldap.h"\
+ "..\..\srclib\apr-util\include\apr_ldap_init.h"\
+ "..\..\srclib\apr-util\include\apr_ldap_option.h"\
+ "..\..\srclib\apr-util\include\apr_ldap_rebind.h"\
+ "..\..\srclib\apr-util\include\apr_ldap_url.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_rmm.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_thread_rwlock.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_version.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\util_ldap_cache.h"\
+
+
+.\util_ldap_cache.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_ldap.h"\
+ "..\..\srclib\apr-util\include\apr_anylock.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_ldap.h"\
+ "..\..\srclib\apr-util\include\apr_ldap_init.h"\
+ "..\..\srclib\apr-util\include\apr_ldap_option.h"\
+ "..\..\srclib\apr-util\include\apr_ldap_rebind.h"\
+ "..\..\srclib\apr-util\include\apr_ldap_url.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_rmm.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_thread_rwlock.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_version.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\util_ldap_cache.h"\
+
+
+.\util_ldap_cache_mgr.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_ldap.h"\
+ "..\..\srclib\apr-util\include\apr_anylock.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_ldap.h"\
+ "..\..\srclib\apr-util\include\apr_ldap_init.h"\
+ "..\..\srclib\apr-util\include\apr_ldap_option.h"\
+ "..\..\srclib\apr-util\include\apr_ldap_rebind.h"\
+ "..\..\srclib\apr-util\include\apr_ldap_url.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_rmm.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_thread_rwlock.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_version.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\util_ldap_cache.h"\
+
diff --git a/modules/ldap/mod_ldap.dsp b/modules/ldap/mod_ldap.dsp
new file mode 100644
index 0000000..db08757
--- /dev/null
+++ b/modules/ldap/mod_ldap.dsp
@@ -0,0 +1,127 @@
+# Microsoft Developer Studio Project File - Name="mod_ldap" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_ldap - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_ldap.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_ldap.mak" CFG="mod_ldap - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_ldap - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_ldap - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_ldap - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "LDAP_DECLARE_EXPORT" /Fd"Release\mod_ldap_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_ldap.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_ldap.so" /d LONG_NAME="ldap_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_ldap.so" /base:@..\..\os\win32\BaseAddr.ref,mod_ldap.so
+# ADD LINK32 kernel32.lib wldap32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_ldap.so" /base:@..\..\os\win32\BaseAddr.ref,mod_ldap.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_ldap.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_ldap - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "LDAP_DECLARE_EXPORT" /Fd"Debug\mod_ldap_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_ldap.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_ldap.so" /d LONG_NAME="ldap_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_ldap.so" /base:@..\..\os\win32\BaseAddr.ref,mod_ldap.so
+# ADD LINK32 kernel32.lib wldap32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_ldap.so" /base:@..\..\os\win32\BaseAddr.ref,mod_ldap.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_ldap.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_ldap - Win32 Release"
+# Name "mod_ldap - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\util_ldap.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\util_ldap.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\util_ldap_cache.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\util_ldap_cache.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\util_ldap_cache_mgr.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/ldap/mod_ldap.mak b/modules/ldap/mod_ldap.mak
new file mode 100644
index 0000000..23ab7fe
--- /dev/null
+++ b/modules/ldap/mod_ldap.mak
@@ -0,0 +1,371 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_ldap.dsp
+!IF "$(CFG)" == ""
+CFG=mod_ldap - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_ldap - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_ldap - Win32 Release" && "$(CFG)" != "mod_ldap - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_ldap.mak" CFG="mod_ldap - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_ldap - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_ldap - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_ldap - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_ldap.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_ldap.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_ldap.res"
+ -@erase "$(INTDIR)\mod_ldap_src.idb"
+ -@erase "$(INTDIR)\mod_ldap_src.pdb"
+ -@erase "$(INTDIR)\util_ldap.obj"
+ -@erase "$(INTDIR)\util_ldap_cache.obj"
+ -@erase "$(INTDIR)\util_ldap_cache_mgr.obj"
+ -@erase "$(OUTDIR)\mod_ldap.exp"
+ -@erase "$(OUTDIR)\mod_ldap.lib"
+ -@erase "$(OUTDIR)\mod_ldap.pdb"
+ -@erase "$(OUTDIR)\mod_ldap.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "LDAP_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_ldap_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_ldap.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_ldap.so" /d LONG_NAME="ldap_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_ldap.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib wldap32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_ldap.pdb" /debug /out:"$(OUTDIR)\mod_ldap.so" /implib:"$(OUTDIR)\mod_ldap.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_ldap.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\util_ldap.obj" \
+ "$(INTDIR)\util_ldap_cache.obj" \
+ "$(INTDIR)\util_ldap_cache_mgr.obj" \
+ "$(INTDIR)\mod_ldap.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_ldap.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_ldap.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_ldap.so"
+ if exist .\Release\mod_ldap.so.manifest mt.exe -manifest .\Release\mod_ldap.so.manifest -outputresource:.\Release\mod_ldap.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_ldap - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_ldap.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_ldap.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_ldap.res"
+ -@erase "$(INTDIR)\mod_ldap_src.idb"
+ -@erase "$(INTDIR)\mod_ldap_src.pdb"
+ -@erase "$(INTDIR)\util_ldap.obj"
+ -@erase "$(INTDIR)\util_ldap_cache.obj"
+ -@erase "$(INTDIR)\util_ldap_cache_mgr.obj"
+ -@erase "$(OUTDIR)\mod_ldap.exp"
+ -@erase "$(OUTDIR)\mod_ldap.lib"
+ -@erase "$(OUTDIR)\mod_ldap.pdb"
+ -@erase "$(OUTDIR)\mod_ldap.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "LDAP_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_ldap_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_ldap.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_ldap.so" /d LONG_NAME="ldap_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_ldap.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib wldap32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_ldap.pdb" /debug /out:"$(OUTDIR)\mod_ldap.so" /implib:"$(OUTDIR)\mod_ldap.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_ldap.so
+LINK32_OBJS= \
+ "$(INTDIR)\util_ldap.obj" \
+ "$(INTDIR)\util_ldap_cache.obj" \
+ "$(INTDIR)\util_ldap_cache_mgr.obj" \
+ "$(INTDIR)\mod_ldap.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_ldap.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_ldap.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_ldap.so"
+ if exist .\Debug\mod_ldap.so.manifest mt.exe -manifest .\Debug\mod_ldap.so.manifest -outputresource:.\Debug\mod_ldap.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_ldap.dep")
+!INCLUDE "mod_ldap.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_ldap.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_ldap - Win32 Release" || "$(CFG)" == "mod_ldap - Win32 Debug"
+
+!IF "$(CFG)" == "mod_ldap - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\ldap"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\ldap"
+
+!ELSEIF "$(CFG)" == "mod_ldap - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\ldap"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\ldap"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_ldap - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\ldap"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\ldap"
+
+!ELSEIF "$(CFG)" == "mod_ldap - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\ldap"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\ldap"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_ldap - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\ldap"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\ldap"
+
+!ELSEIF "$(CFG)" == "mod_ldap - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\ldap"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\ldap"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_ldap - Win32 Release"
+
+
+"$(INTDIR)\mod_ldap.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_ldap.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_ldap.so" /d LONG_NAME="ldap_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_ldap - Win32 Debug"
+
+
+"$(INTDIR)\mod_ldap.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_ldap.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_ldap.so" /d LONG_NAME="ldap_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\util_ldap.c
+
+"$(INTDIR)\util_ldap.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\util_ldap_cache.c
+
+"$(INTDIR)\util_ldap_cache.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\util_ldap_cache_mgr.c
+
+"$(INTDIR)\util_ldap_cache_mgr.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/ldap/util_ldap.c b/modules/ldap/util_ldap.c
new file mode 100644
index 0000000..14b774a
--- /dev/null
+++ b/modules/ldap/util_ldap.c
@@ -0,0 +1,3240 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * util_ldap.c: LDAP things
+ *
+ * Original code from auth_ldap module for Apache v1.3:
+ * Copyright 1998, 1999 Enbridge Pipelines Inc.
+ * Copyright 1999-2001 Dave Carrigan
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "util_mutex.h"
+#include "util_ldap.h"
+#include "util_ldap_cache.h"
+
+#include <apr_strings.h>
+
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#if !APR_HAS_LDAP
+#error mod_ldap requires APR-util to have LDAP support built in
+#endif
+
+/* Default define for ldap functions that need a SIZELIMIT but
+ * do not have the define
+ * XXX This should be removed once a supporting #define is
+ * released through APR-Util.
+ */
+#ifndef APR_LDAP_SIZELIMIT
+#define APR_LDAP_SIZELIMIT -1
+#endif
+
+#ifdef LDAP_OPT_DEBUG_LEVEL
+#define AP_LDAP_OPT_DEBUG LDAP_OPT_DEBUG_LEVEL
+#else
+#ifdef LDAP_OPT_DEBUG
+#define AP_LDAP_OPT_DEBUG LDAP_OPT_DEBUG
+#endif
+#endif
+
+#define AP_LDAP_HOPLIMIT_UNSET -1
+#define AP_LDAP_CHASEREFERRALS_SDKDEFAULT -1
+#define AP_LDAP_CHASEREFERRALS_OFF 0
+#define AP_LDAP_CHASEREFERRALS_ON 1
+
+#define AP_LDAP_CONNPOOL_DEFAULT -1
+#define AP_LDAP_CONNPOOL_INFINITE -2
+
+#if !defined(LDAP_OPT_NETWORK_TIMEOUT) && defined(LDAP_OPT_CONNECT_TIMEOUT)
+#define LDAP_OPT_NETWORK_TIMEOUT LDAP_OPT_CONNECT_TIMEOUT
+#endif
+
+module AP_MODULE_DECLARE_DATA ldap_module;
+static const char *ldap_cache_mutex_type = "ldap-cache";
+static apr_status_t uldap_connection_unbind(void *param);
+
+
+static APR_INLINE apr_status_t ldap_cache_lock(util_ldap_state_t *st, request_rec *r) {
+ apr_status_t rv = APR_SUCCESS;
+ if (st->util_ldap_cache_lock) {
+ apr_status_t rv = apr_global_mutex_lock(st->util_ldap_cache_lock);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_CRIT, rv, r, APLOGNO(10134) "LDAP cache lock failed");
+ ap_assert(0);
+ }
+ }
+ return rv;
+}
+static APR_INLINE apr_status_t ldap_cache_unlock(util_ldap_state_t *st, request_rec *r) {
+ apr_status_t rv = APR_SUCCESS;
+ if (st->util_ldap_cache_lock) {
+ apr_status_t rv = apr_global_mutex_unlock(st->util_ldap_cache_lock);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_CRIT, rv, r, APLOGNO(10135) "LDAP cache lock failed");
+ ap_assert(0);
+ }
+ }
+ return rv;
+}
+
+static void util_ldap_strdup (char **str, const char *newstr)
+{
+ if (*str) {
+ free(*str);
+ *str = NULL;
+ }
+
+ if (newstr) {
+ *str = strdup(newstr);
+ }
+}
+
+/*
+ * Status Handler
+ * --------------
+ *
+ * This handler generates a status page about the current performance of
+ * the LDAP cache. It is enabled as follows:
+ *
+ * <Location /ldap-status>
+ * SetHandler ldap-status
+ * </Location>
+ *
+ */
+static int util_ldap_handler(request_rec *r)
+{
+ util_ldap_state_t *st;
+
+ r->allowed |= (1 << M_GET);
+ if (r->method_number != M_GET) {
+ return DECLINED;
+ }
+
+ if (strcmp(r->handler, "ldap-status")) {
+ return DECLINED;
+ }
+
+ st = (util_ldap_state_t *) ap_get_module_config(r->server->module_config,
+ &ldap_module);
+
+ ap_set_content_type(r, "text/html; charset=ISO-8859-1");
+
+ if (r->header_only)
+ return OK;
+
+ ap_rputs(DOCTYPE_HTML_3_2
+ "<html><head><title>LDAP Cache Information</title></head>\n", r);
+ ap_rputs("<body bgcolor='#ffffff'><h1 align=center>LDAP Cache Information"
+ "</h1>\n", r);
+
+ util_ald_cache_display(r, st);
+
+ return OK;
+}
+
+
+
+/* ------------------------------------------------------------------ */
+/*
+ * Closes an LDAP connection by unlocking it. The next time
+ * uldap_connection_find() is called this connection will be
+ * available for reuse.
+ */
+static void uldap_connection_close(util_ldap_connection_t *ldc)
+{
+
+ /* We leave bound LDAP connections floating around in our pool,
+ * but always check/fix the binddn/bindpw when we take them out
+ * of the pool
+ */
+ if (!ldc->keep) {
+ uldap_connection_unbind(ldc);
+ ldc->r = NULL;
+ }
+ else {
+ /* mark our connection as available for reuse */
+ ldc->freed = apr_time_now();
+ ldc->r = NULL;
+ }
+
+#if APR_HAS_THREADS
+ apr_thread_mutex_unlock(ldc->lock);
+#endif
+}
+
+
+/*
+ * Destroys an LDAP connection by unbinding and closing the connection to
+ * the LDAP server. It is used to bring the connection back to a known
+ * state after an error.
+ */
+static apr_status_t uldap_connection_unbind(void *param)
+{
+ util_ldap_connection_t *ldc = param;
+
+ if (ldc) {
+ if (ldc->ldap) {
+ if (ldc->r) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, ldc->r, "LDC %pp unbind", ldc);
+ }
+ ldap_unbind_s(ldc->ldap);
+ ldc->ldap = NULL;
+ }
+ ldc->bound = 0;
+
+ /* forget the rebind info for this conn */
+ if (ldc->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) {
+ apr_ldap_rebind_remove(ldc->ldap);
+ apr_pool_clear(ldc->rebind_pool);
+ }
+ }
+
+ return APR_SUCCESS;
+}
+
+/* not presently used, not part of the API */
+#if 0
+/*
+ * util_ldap_connection_remove frees all storage associated with the LDAP
+ * connection and removes it completely from the per-virtualhost list of
+ * connections
+ *
+ * The caller should hold the lock for this connection
+ */
+static apr_status_t util_ldap_connection_remove (void *param)
+{
+ util_ldap_connection_t *ldc = param, *l = NULL, *prev = NULL;
+ util_ldap_state_t *st;
+
+ if (!ldc) return APR_SUCCESS;
+
+ st = ldc->st;
+
+ uldap_connection_unbind(ldc);
+
+#if APR_HAS_THREADS
+ apr_thread_mutex_lock(st->mutex);
+#endif
+
+ /* Remove ldc from the list */
+ for (l=st->connections; l; l=l->next) {
+ if (l == ldc) {
+ if (prev) {
+ prev->next = l->next;
+ }
+ else {
+ st->connections = l->next;
+ }
+ break;
+ }
+ prev = l;
+ }
+
+ if (ldc->bindpw) {
+ free((void*)ldc->bindpw);
+ }
+ if (ldc->binddn) {
+ free((void*)ldc->binddn);
+ }
+
+#if APR_HAS_THREADS
+ apr_thread_mutex_unlock(ldc->lock);
+ apr_thread_mutex_unlock(st->mutex);
+#endif
+
+ /* Destroy the pool associated with this connection */
+
+ apr_pool_destroy(ldc->pool);
+
+ return APR_SUCCESS;
+}
+#endif
+
+static int uldap_connection_init(request_rec *r,
+ util_ldap_connection_t *ldc)
+{
+ int rc = 0, ldap_option = 0;
+ int version = LDAP_VERSION3;
+ apr_ldap_err_t *result = NULL;
+#ifdef LDAP_OPT_NETWORK_TIMEOUT
+ struct timeval connectionTimeout = {0};
+#endif
+ util_ldap_state_t *st =
+ (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
+ &ldap_module);
+ int have_client_certs = !apr_is_empty_array(ldc->client_certs);
+#if !APR_HAS_SOLARIS_LDAPSDK
+ /*
+ * Normally we enable SSL/TLS with apr_ldap_set_option(), except
+ * with Solaris LDAP, where this is broken.
+ */
+ int secure = APR_LDAP_NONE;
+#else
+ /*
+ * With Solaris LDAP, we enable TSL via the secure argument
+ * to apr_ldap_init(). This requires a fix from apr-util >= 1.4.0.
+ *
+ * Just in case client certificates ever get supported, we
+ * handle those as with the other LDAP SDKs.
+ */
+ int secure = have_client_certs ? APR_LDAP_NONE : ldc->secure;
+#endif
+
+ /* Since the host will include a port if the default port is not used,
+ * always specify the default ports for the port parameter. This will
+ * allow a host string that contains multiple hosts the ability to mix
+ * some hosts with ports and some without. All hosts which do not
+ * specify a port will use the default port.
+ */
+ apr_ldap_init(r->pool, &(ldc->ldap),
+ ldc->host,
+ APR_LDAP_SSL == ldc->secure ? LDAPS_PORT : LDAP_PORT,
+ secure, &(result));
+
+ if (NULL == result) {
+ /* something really bad happened */
+ ldc->bound = 0;
+ if (NULL == ldc->reason) {
+ ldc->reason = "LDAP: ldap initialization failed";
+ }
+ return(APR_EGENERAL);
+ }
+
+ if (result->rc) {
+ ldc->reason = result->reason;
+ ldc->bound = 0;
+ return result->rc;
+ }
+
+ if (NULL == ldc->ldap)
+ {
+ ldc->bound = 0;
+ if (NULL == ldc->reason) {
+ ldc->reason = "LDAP: ldap initialization failed";
+ }
+ else {
+ ldc->reason = result->reason;
+ }
+ return(result->rc);
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "LDC %pp init", ldc);
+
+ if (ldc->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) {
+ /* Now that we have an ldap struct, add it to the referral list for rebinds. */
+ rc = apr_ldap_rebind_add(ldc->rebind_pool, ldc->ldap, ldc->binddn, ldc->bindpw);
+ if (rc != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rc, r->server, APLOGNO(01277)
+ "LDAP: Unable to add rebind cross reference entry. Out of memory?");
+ uldap_connection_unbind(ldc);
+ ldc->reason = "LDAP: Unable to add rebind cross reference entry.";
+ return(rc);
+ }
+ }
+
+ /* always default to LDAP V3 */
+ ldap_set_option(ldc->ldap, LDAP_OPT_PROTOCOL_VERSION, &version);
+
+ /* set client certificates */
+ if (have_client_certs) {
+ apr_ldap_set_option(r->pool, ldc->ldap, APR_LDAP_OPT_TLS_CERT,
+ ldc->client_certs, &(result));
+ if (LDAP_SUCCESS != result->rc) {
+ uldap_connection_unbind( ldc );
+ ldc->reason = result->reason;
+ return(result->rc);
+ }
+ }
+
+ /* switch on SSL/TLS */
+ if (APR_LDAP_NONE != ldc->secure
+#if APR_HAS_SOLARIS_LDAPSDK
+ /* See comments near apr_ldap_init() above */
+ && have_client_certs
+#endif
+ ) {
+ apr_ldap_set_option(r->pool, ldc->ldap,
+ APR_LDAP_OPT_TLS, &ldc->secure, &(result));
+ if (LDAP_SUCCESS != result->rc) {
+ uldap_connection_unbind( ldc );
+ ldc->reason = result->reason;
+ return(result->rc);
+ }
+ }
+
+ /* Set the alias dereferencing option */
+ ldap_option = ldc->deref;
+ ldap_set_option(ldc->ldap, LDAP_OPT_DEREF, &ldap_option);
+
+ if (ldc->ChaseReferrals != AP_LDAP_CHASEREFERRALS_SDKDEFAULT) {
+ /* Set options for rebind and referrals. */
+ ap_log_error(APLOG_MARK, APLOG_TRACE4, 0, r->server, APLOGNO(01278)
+ "LDAP: Setting referrals to %s.",
+ ((ldc->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) ? "On" : "Off"));
+ apr_ldap_set_option(r->pool, ldc->ldap,
+ APR_LDAP_OPT_REFERRALS,
+ (void *)((ldc->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) ?
+ LDAP_OPT_ON : LDAP_OPT_OFF),
+ &(result));
+ if (result->rc != LDAP_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, APLOGNO(01279)
+ "Unable to set LDAP_OPT_REFERRALS option to %s: %d.",
+ ((ldc->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) ? "On" : "Off"),
+ result->rc);
+ result->reason = "Unable to set LDAP_OPT_REFERRALS.";
+ ldc->reason = result->reason;
+ uldap_connection_unbind(ldc);
+ return(result->rc);
+ }
+ }
+
+ if (ldc->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) {
+ if ((ldc->ReferralHopLimit != AP_LDAP_HOPLIMIT_UNSET) && ldc->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) {
+ /* Referral hop limit - only if referrals are enabled and a hop limit is explicitly requested */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, APLOGNO(01280)
+ "Setting referral hop limit to %d.",
+ ldc->ReferralHopLimit);
+ apr_ldap_set_option(r->pool, ldc->ldap,
+ APR_LDAP_OPT_REFHOPLIMIT,
+ (void *)&ldc->ReferralHopLimit,
+ &(result));
+ if (result->rc != LDAP_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, APLOGNO(01281)
+ "Unable to set LDAP_OPT_REFHOPLIMIT option to %d: %d.",
+ ldc->ReferralHopLimit,
+ result->rc);
+ result->reason = "Unable to set LDAP_OPT_REFHOPLIMIT.";
+ ldc->reason = result->reason;
+ uldap_connection_unbind(ldc);
+ return(result->rc);
+ }
+ }
+ }
+
+/*XXX All of the #ifdef's need to be removed once apr-util 1.2 is released */
+#ifdef APR_LDAP_OPT_VERIFY_CERT
+ apr_ldap_set_option(r->pool, ldc->ldap, APR_LDAP_OPT_VERIFY_CERT,
+ &(st->verify_svr_cert), &(result));
+#else
+#if defined(LDAPSSL_VERIFY_SERVER)
+ if (st->verify_svr_cert) {
+ result->rc = ldapssl_set_verify_mode(LDAPSSL_VERIFY_SERVER);
+ }
+ else {
+ result->rc = ldapssl_set_verify_mode(LDAPSSL_VERIFY_NONE);
+ }
+#elif defined(LDAP_OPT_X_TLS_REQUIRE_CERT)
+ /* This is not a per-connection setting so just pass NULL for the
+ Ldap connection handle */
+ if (st->verify_svr_cert) {
+ int i = LDAP_OPT_X_TLS_DEMAND;
+ result->rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &i);
+ }
+ else {
+ int i = LDAP_OPT_X_TLS_NEVER;
+ result->rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &i);
+ }
+#endif
+#endif
+
+#ifdef LDAP_OPT_NETWORK_TIMEOUT
+ if (st->connectionTimeout > 0) {
+ connectionTimeout.tv_sec = st->connectionTimeout;
+ }
+
+ if (connectionTimeout.tv_sec > 0) {
+ rc = apr_ldap_set_option(r->pool, ldc->ldap, LDAP_OPT_NETWORK_TIMEOUT,
+ (void *)&connectionTimeout, &(result));
+ if (APR_SUCCESS != rc) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, APLOGNO(01282)
+ "LDAP: Could not set the connection timeout");
+ }
+ }
+#endif
+
+#ifdef LDAP_OPT_TIMEOUT
+ /*
+ * LDAP_OPT_TIMEOUT is not portable, but it influences all synchronous ldap
+ * function calls and not just ldap_search_ext_s(), which accepts a timeout
+ * parameter.
+ * XXX: It would be possible to simulate LDAP_OPT_TIMEOUT by replacing all
+ * XXX: synchronous ldap function calls with asynchronous calls and using
+ * XXX: ldap_result() with a timeout.
+ */
+ if (st->opTimeout) {
+ rc = apr_ldap_set_option(r->pool, ldc->ldap, LDAP_OPT_TIMEOUT,
+ st->opTimeout, &(result));
+ if (APR_SUCCESS != rc) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, APLOGNO(01283)
+ "LDAP: Could not set LDAP_OPT_TIMEOUT");
+ }
+ }
+#endif
+
+ return(rc);
+}
+
+static int uldap_ld_errno(util_ldap_connection_t *ldc)
+{
+ int ldaprc;
+#ifdef LDAP_OPT_ERROR_NUMBER
+ if (LDAP_SUCCESS == ldap_get_option(ldc->ldap, LDAP_OPT_ERROR_NUMBER, &ldaprc)) return ldaprc;
+#endif
+#ifdef LDAP_OPT_RESULT_CODE
+ if (LDAP_SUCCESS == ldap_get_option(ldc->ldap, LDAP_OPT_RESULT_CODE, &ldaprc)) return ldaprc;
+#endif
+ return LDAP_OTHER;
+}
+
+/*
+ * Replacement function for ldap_simple_bind_s() with a timeout.
+ * To do this in a portable way, we have to use ldap_simple_bind() and
+ * ldap_result().
+ *
+ * Returns LDAP_SUCCESS on success; and an error code on failure
+ */
+static int uldap_simple_bind(util_ldap_connection_t *ldc, char *binddn,
+ char* bindpw, struct timeval *timeout)
+{
+ LDAPMessage *result;
+ int rc;
+ int msgid = ldap_simple_bind(ldc->ldap, binddn, bindpw);
+ if (msgid == -1) {
+ ldc->reason = "LDAP: ldap_simple_bind() failed";
+ return uldap_ld_errno(ldc);
+ }
+ rc = ldap_result(ldc->ldap, msgid, 0, timeout, &result);
+ if (rc == -1) {
+ ldc->reason = "LDAP: ldap_simple_bind() result retrieval failed";
+ /* -1 is LDAP_SERVER_DOWN in openldap, use something else */
+ return uldap_ld_errno(ldc);
+ }
+ else if (rc == 0) {
+ ldc->reason = "LDAP: ldap_simple_bind() timed out";
+ rc = LDAP_TIMEOUT;
+ } else if (ldap_parse_result(ldc->ldap, result, &rc, NULL, NULL, NULL,
+ NULL, 1) == -1) {
+ ldc->reason = "LDAP: ldap_simple_bind() parse result failed";
+ return uldap_ld_errno(ldc);
+ }
+ else {
+ ldc->last_backend_conn = ldc->r->request_time;
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, ldc->r, "LDC %pp bind", ldc);
+ }
+ return rc;
+}
+
+/*
+ * Connect to the LDAP server and binds. Does not connect if already
+ * connected (i.e. ldc->ldap is non-NULL.) Does not bind if already bound.
+ *
+ * Returns LDAP_SUCCESS on success; and an error code on failure
+ */
+static int uldap_connection_open(request_rec *r,
+ util_ldap_connection_t *ldc)
+{
+ int rc = 0;
+ int failures = 0;
+ int new_connection = 0;
+ util_ldap_state_t *st;
+
+ /* sanity check for NULL */
+ if (!ldc) {
+ return -1;
+ }
+
+ /* If the connection is already bound, return
+ */
+ if (ldc->bound && !ldc->must_rebind)
+ {
+ ldc->reason = "LDAP: connection open successful (already bound)";
+ return LDAP_SUCCESS;
+ }
+
+ /* create the ldap session handle
+ */
+ if (NULL == ldc->ldap)
+ {
+ new_connection = 1;
+ rc = uldap_connection_init( r, ldc );
+ if (LDAP_SUCCESS != rc)
+ {
+ return rc;
+ }
+ }
+
+
+ st = (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
+ &ldap_module);
+
+ /* loop trying to bind up to st->retries times if LDAP_SERVER_DOWN or LDAP_TIMEOUT
+ * are returned. Close the connection before the first retry, and then on every
+ * other retry.
+ *
+ * On Success or any other error, break out of the loop.
+ *
+ * NOTE: Looping is probably not a great idea. If the server isn't
+ * responding the chances it will respond after a few tries are poor.
+ * However, the original code looped and it only happens on
+ * the error condition.
+ */
+
+ while (failures <= st->retries) {
+ if (failures > 0 && st->retry_delay > 0) {
+ apr_sleep(st->retry_delay);
+ }
+ rc = uldap_simple_bind(ldc, (char *)ldc->binddn, (char *)ldc->bindpw,
+ st->opTimeout);
+
+ if (rc == LDAP_SUCCESS) break;
+
+ failures++;
+
+ if (AP_LDAP_IS_SERVER_DOWN(rc)) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+ "ldap_simple_bind() failed with server down "
+ "(try %d)", failures);
+ }
+ else if (rc == LDAP_TIMEOUT) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01284)
+ "ldap_simple_bind() timed out on %s "
+ "connection, dropped by firewall?",
+ new_connection ? "new" : "reused");
+ }
+ else {
+ /* Other errors not retryable */
+ break;
+ }
+
+ if (!(failures % 2)) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+ "attempt to re-init the connection");
+ uldap_connection_unbind(ldc);
+ if (LDAP_SUCCESS != uldap_connection_init(r, ldc)) {
+ /* leave rc as the initial bind return code */
+ break;
+ }
+ }
+ }
+
+ /* free the handle if there was an error
+ */
+ if (LDAP_SUCCESS != rc)
+ {
+ uldap_connection_unbind(ldc);
+ ldc->reason = "LDAP: ldap_simple_bind() failed";
+ }
+ else {
+ ldc->bound = 1;
+ ldc->must_rebind = 0;
+ ldc->reason = "LDAP: connection open successful";
+ }
+
+ return(rc);
+}
+
+
+/*
+ * Compare client certificate arrays.
+ *
+ * Returns 1 on compare failure, 0 otherwise.
+ */
+static int compare_client_certs(apr_array_header_t *srcs,
+ apr_array_header_t *dests)
+{
+ int i = 0;
+ struct apr_ldap_opt_tls_cert_t *src, *dest;
+
+ /* arrays both NULL? if so, then equal */
+ if (srcs == NULL && dests == NULL) {
+ return 0;
+ }
+
+ /* arrays different length or either NULL? If so, then not equal */
+ if (srcs == NULL || dests == NULL || srcs->nelts != dests->nelts) {
+ return 1;
+ }
+
+ /* run an actual comparison */
+ src = (struct apr_ldap_opt_tls_cert_t *)srcs->elts;
+ dest = (struct apr_ldap_opt_tls_cert_t *)dests->elts;
+ for (i = 0; i < srcs->nelts; i++) {
+ if ((strcmp(src[i].path, dest[i].path)) ||
+ (src[i].type != dest[i].type) ||
+ /* One is passwordless? If so, then not equal */
+ ((src[i].password == NULL) ^ (dest[i].password == NULL)) ||
+ (src[i].password != NULL && dest[i].password != NULL &&
+ strcmp(src[i].password, dest[i].password))) {
+ return 1;
+ }
+ }
+
+ /* if we got here, the cert arrays were identical */
+ return 0;
+
+}
+
+
+/*
+ * Find an existing ldap connection struct that matches the
+ * provided ldap connection parameters.
+ *
+ * If not found in the cache, a new ldc structure will be allocated
+ * from st->pool and returned to the caller. If found in the cache,
+ * a pointer to the existing ldc structure will be returned.
+ */
+static util_ldap_connection_t *
+ uldap_connection_find(request_rec *r,
+ const char *host, int port,
+ const char *binddn, const char *bindpw,
+ deref_options deref, int secure)
+{
+ struct util_ldap_connection_t *l, *p; /* To traverse the linked list */
+ int secureflag = secure;
+ apr_time_t now = apr_time_now();
+
+ util_ldap_state_t *st =
+ (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
+ &ldap_module);
+ util_ldap_config_t *dc =
+ (util_ldap_config_t *) ap_get_module_config(r->per_dir_config, &ldap_module);
+
+#if APR_HAS_THREADS
+ /* mutex lock this function */
+ apr_thread_mutex_lock(st->mutex);
+#endif
+
+ if (secure < APR_LDAP_NONE) {
+ secureflag = st->secure;
+ }
+
+ /* Search for an exact connection match in the list that is not
+ * being used.
+ */
+ for (l=st->connections,p=NULL; l; l=l->next) {
+#if APR_HAS_THREADS
+ if (APR_SUCCESS == apr_thread_mutex_trylock(l->lock)) {
+#endif
+ if ( (l->port == port) && (strcmp(l->host, host) == 0)
+ && ((!l->binddn && !binddn) || (l->binddn && binddn
+ && !strcmp(l->binddn, binddn)))
+ && ((!l->bindpw && !bindpw) || (l->bindpw && bindpw
+ && !strcmp(l->bindpw, bindpw)))
+ && (l->deref == deref) && (l->secure == secureflag)
+ && !compare_client_certs(dc->client_certs, l->client_certs))
+ {
+ if (st->connection_pool_ttl > 0) {
+ if (l->bound && (now - l->last_backend_conn) > st->connection_pool_ttl) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "Removing LDAP connection last used %" APR_TIME_T_FMT " seconds ago",
+ (now - l->last_backend_conn) / APR_USEC_PER_SEC);
+ l->r = r;
+ uldap_connection_unbind(l);
+ /* Go ahead (by falling through) and use it, so we don't create more just to unbind some other old ones */
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r,
+ "Reuse %s LDC %pp",
+ l->bound ? "bound" : "unbound", l);
+ }
+ break;
+ }
+#if APR_HAS_THREADS
+ /* If this connection didn't match the criteria, then we
+ * need to unlock the mutex so it is available to be reused.
+ */
+ apr_thread_mutex_unlock(l->lock);
+ }
+#endif
+ p = l;
+ }
+
+ /* If nothing found, search again, but we don't care about the
+ * binddn and bindpw this time.
+ */
+ if (!l) {
+ for (l=st->connections,p=NULL; l; l=l->next) {
+#if APR_HAS_THREADS
+ if (APR_SUCCESS == apr_thread_mutex_trylock(l->lock)) {
+
+#endif
+ if ((l->port == port) && (strcmp(l->host, host) == 0) &&
+ (l->deref == deref) && (l->secure == secureflag) &&
+ !compare_client_certs(dc->client_certs, l->client_certs))
+ {
+ if (st->connection_pool_ttl > 0) {
+ if (l->bound && (now - l->last_backend_conn) > st->connection_pool_ttl) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "Removing LDAP connection last used %" APR_TIME_T_FMT " seconds ago",
+ (now - l->last_backend_conn) / APR_USEC_PER_SEC);
+ l->r = r;
+ uldap_connection_unbind(l);
+ /* Go ahead (by falling through) and use it, so we don't create more just to unbind some other old ones */
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r,
+ "Reuse %s LDC %pp (will rebind)",
+ l->bound ? "bound" : "unbound", l);
+ }
+
+ /* the bind credentials have changed */
+ l->must_rebind = 1;
+ util_ldap_strdup((char**)&(l->binddn), binddn);
+ util_ldap_strdup((char**)&(l->bindpw), bindpw);
+
+ break;
+ }
+#if APR_HAS_THREADS
+ /* If this connection didn't match the criteria, then we
+ * need to unlock the mutex so it is available to be reused.
+ */
+ apr_thread_mutex_unlock(l->lock);
+ }
+#endif
+ p = l;
+ }
+ }
+
+/* artificially disable cache */
+/* l = NULL; */
+
+ /* If no connection was found after the second search, we
+ * must create one.
+ */
+ if (!l) {
+ apr_pool_t *newpool;
+ if (apr_pool_create(&newpool, NULL) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, APLOGNO(01285)
+ "util_ldap: Failed to create memory pool");
+#if APR_HAS_THREADS
+ apr_thread_mutex_unlock(st->mutex);
+#endif
+ return NULL;
+ }
+ apr_pool_tag(newpool, "util_ldap_connection");
+
+ /*
+ * Add the new connection entry to the linked list. Note that we
+ * don't actually establish an LDAP connection yet; that happens
+ * the first time authentication is requested.
+ */
+
+ /* create the details of this connection in the new pool */
+ l = apr_pcalloc(newpool, sizeof(util_ldap_connection_t));
+ l->pool = newpool;
+ l->st = st;
+
+#if APR_HAS_THREADS
+ apr_thread_mutex_create(&l->lock, APR_THREAD_MUTEX_DEFAULT, l->pool);
+ apr_thread_mutex_lock(l->lock);
+#endif
+ l->bound = 0;
+ l->host = apr_pstrdup(l->pool, host);
+ l->port = port;
+ l->deref = deref;
+ util_ldap_strdup((char**)&(l->binddn), binddn);
+ util_ldap_strdup((char**)&(l->bindpw), bindpw);
+ l->ChaseReferrals = dc->ChaseReferrals;
+ l->ReferralHopLimit = dc->ReferralHopLimit;
+
+ /* The security mode after parsing the URL will always be either
+ * APR_LDAP_NONE (ldap://) or APR_LDAP_SSL (ldaps://).
+ * If the security setting is NONE, override it to the security
+ * setting optionally supplied by the admin using LDAPTrustedMode
+ */
+ l->secure = secureflag;
+
+ /* save away a copy of the client cert list that is presently valid */
+ l->client_certs = apr_array_copy_hdr(l->pool, dc->client_certs);
+
+ /* whether or not to keep this connection in the pool when it's returned */
+ l->keep = (st->connection_pool_ttl == 0) ? 0 : 1;
+
+ if (l->ChaseReferrals == AP_LDAP_CHASEREFERRALS_ON) {
+ if (apr_pool_create(&(l->rebind_pool), l->pool) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, APLOGNO(01286)
+ "util_ldap: Failed to create memory pool");
+#if APR_HAS_THREADS
+ apr_thread_mutex_unlock(st->mutex);
+#endif
+ return NULL;
+ }
+ apr_pool_tag(l->rebind_pool, "util_ldap_rebind");
+ }
+
+ if (p) {
+ p->next = l;
+ }
+ else {
+ st->connections = l;
+ }
+ }
+
+#if APR_HAS_THREADS
+ apr_thread_mutex_unlock(st->mutex);
+#endif
+ l->r = r;
+ return l;
+}
+
+/* ------------------------------------------------------------------ */
+
+/*
+ * Compares two DNs to see if they're equal. The only way to do this correctly
+ * is to search for the dn and then do ldap_get_dn() on the result. This should
+ * match the initial dn, since it would have been also retrieved with
+ * ldap_get_dn(). This is expensive, so if the configuration value
+ * compare_dn_on_server is false, just does an ordinary strcmp.
+ *
+ * The lock for the ldap cache should already be acquired.
+ */
+static int uldap_cache_comparedn(request_rec *r, util_ldap_connection_t *ldc,
+ const char *url, const char *dn,
+ const char *reqdn, int compare_dn_on_server)
+{
+ int result = 0;
+ util_url_node_t *curl;
+ util_url_node_t curnode;
+ util_dn_compare_node_t *node;
+ util_dn_compare_node_t newnode;
+ int failures = 0;
+ LDAPMessage *res, *entry;
+ char *searchdn;
+
+ util_ldap_state_t *st = (util_ldap_state_t *)
+ ap_get_module_config(r->server->module_config,
+ &ldap_module);
+
+ /* get cache entry (or create one) */
+ ldap_cache_lock(st, r);
+
+ curnode.url = url;
+ curl = util_ald_cache_fetch(st->util_ldap_cache, &curnode);
+ if (curl == NULL) {
+ curl = util_ald_create_caches(st, url);
+ }
+ ldap_cache_unlock(st, r);
+
+ /* a simple compare? */
+ if (!compare_dn_on_server) {
+ /* unlock this read lock */
+ if (strcmp(dn, reqdn)) {
+ ldc->reason = "DN Comparison FALSE (direct strcmp())";
+ return LDAP_COMPARE_FALSE;
+ }
+ else {
+ ldc->reason = "DN Comparison TRUE (direct strcmp())";
+ return LDAP_COMPARE_TRUE;
+ }
+ }
+
+ if (curl) {
+ /* no - it's a server side compare */
+ ldap_cache_lock(st, r);
+
+ /* is it in the compare cache? */
+ newnode.reqdn = (char *)reqdn;
+ node = util_ald_cache_fetch(curl->dn_compare_cache, &newnode);
+ if (node != NULL) {
+ /* If it's in the cache, it's good */
+ /* unlock this read lock */
+ ldap_cache_unlock(st, r);
+ ldc->reason = "DN Comparison TRUE (cached)";
+ return LDAP_COMPARE_TRUE;
+ }
+
+ /* unlock this read lock */
+ ldap_cache_unlock(st, r);
+ }
+
+start_over:
+ if (failures > st->retries) {
+ return result;
+ }
+
+ if (failures > 0 && st->retry_delay > 0) {
+ apr_sleep(st->retry_delay);
+ }
+
+ /* make a server connection */
+ if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
+ /* connect to server failed */
+ return result;
+ }
+
+ /* search for reqdn */
+ result = ldap_search_ext_s(ldc->ldap, (char *)reqdn, LDAP_SCOPE_BASE,
+ "(objectclass=*)", NULL, 1,
+ NULL, NULL, st->opTimeout, APR_LDAP_SIZELIMIT, &res);
+ if (AP_LDAP_IS_SERVER_DOWN(result))
+ {
+ ldc->reason = "DN Comparison ldap_search_ext_s() "
+ "failed with server down";
+ uldap_connection_unbind(ldc);
+ failures++;
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "%s (attempt %d)", ldc->reason, failures);
+ goto start_over;
+ }
+ if (result == LDAP_TIMEOUT && failures == 0) {
+ /*
+ * we are reusing a connection that doesn't seem to be active anymore
+ * (firewall state drop?), let's try a new connection.
+ */
+ ldc->reason = "DN Comparison ldap_search_ext_s() "
+ "failed with timeout";
+ uldap_connection_unbind(ldc);
+ failures++;
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "%s (attempt %d)", ldc->reason, failures);
+ goto start_over;
+ }
+ if (result != LDAP_SUCCESS) {
+ /* search for reqdn failed - no match */
+ ldc->reason = "DN Comparison ldap_search_ext_s() failed";
+ return result;
+ }
+
+ ldc->last_backend_conn = r->request_time;
+ entry = ldap_first_entry(ldc->ldap, res);
+ searchdn = ldap_get_dn(ldc->ldap, entry);
+
+ ldap_msgfree(res);
+ if (strcmp(dn, searchdn) != 0) {
+ /* compare unsuccessful */
+ ldc->reason = "DN Comparison FALSE (checked on server)";
+ result = LDAP_COMPARE_FALSE;
+ }
+ else {
+ if (curl) {
+ /* compare successful - add to the compare cache */
+ ldap_cache_lock(st, r);
+ newnode.reqdn = (char *)reqdn;
+ newnode.dn = (char *)dn;
+
+ node = util_ald_cache_fetch(curl->dn_compare_cache, &newnode);
+ if ( (node == NULL)
+ || (strcmp(reqdn, node->reqdn) != 0)
+ || (strcmp(dn, node->dn) != 0))
+ {
+ util_ald_cache_insert(curl->dn_compare_cache, &newnode);
+ }
+ ldap_cache_unlock(st, r);
+ }
+ ldc->reason = "DN Comparison TRUE (checked on server)";
+ result = LDAP_COMPARE_TRUE;
+ }
+ ldap_memfree(searchdn);
+ return result;
+
+}
+
+/*
+ * Does an generic ldap_compare operation. It accepts a cache that it will use
+ * to lookup the compare in the cache. We cache two kinds of compares
+ * (require group compares) and (require user compares). Each compare has a
+ * different cache node: require group includes the DN; require user does not
+ * because the require user cache is owned by the
+ *
+ */
+static int uldap_cache_compare(request_rec *r, util_ldap_connection_t *ldc,
+ const char *url, const char *dn,
+ const char *attrib, const char *value)
+{
+ int result = 0;
+ util_url_node_t *curl;
+ util_url_node_t curnode;
+ util_compare_node_t *compare_nodep;
+ util_compare_node_t the_compare_node;
+ apr_time_t curtime = 0; /* silence gcc -Wall */
+ int failures = 0;
+
+ util_ldap_state_t *st = (util_ldap_state_t *)
+ ap_get_module_config(r->server->module_config,
+ &ldap_module);
+
+ /* get cache entry (or create one) */
+ ldap_cache_lock(st, r);
+ curnode.url = url;
+ curl = util_ald_cache_fetch(st->util_ldap_cache, &curnode);
+ if (curl == NULL) {
+ curl = util_ald_create_caches(st, url);
+ }
+ ldap_cache_unlock(st, r);
+
+ if (curl) {
+ /* make a comparison to the cache */
+ ldap_cache_lock(st, r);
+ curtime = apr_time_now();
+
+ the_compare_node.dn = (char *)dn;
+ the_compare_node.attrib = (char *)attrib;
+ the_compare_node.value = (char *)value;
+ the_compare_node.result = 0;
+ the_compare_node.sgl_processed = 0;
+ the_compare_node.subgroupList = NULL;
+
+ compare_nodep = util_ald_cache_fetch(curl->compare_cache,
+ &the_compare_node);
+
+ if (compare_nodep != NULL) {
+ /* found it... */
+ if (curtime - compare_nodep->lastcompare > st->compare_cache_ttl) {
+ /* ...but it is too old */
+ util_ald_cache_remove(curl->compare_cache, compare_nodep);
+ }
+ else {
+ /* ...and it is good */
+ if (LDAP_COMPARE_TRUE == compare_nodep->result) {
+ ldc->reason = "Comparison true (cached)";
+ }
+ else if (LDAP_COMPARE_FALSE == compare_nodep->result) {
+ ldc->reason = "Comparison false (cached)";
+ }
+ else if (LDAP_NO_SUCH_ATTRIBUTE == compare_nodep->result) {
+ ldc->reason = "Comparison no such attribute (cached)";
+ }
+ else {
+ ldc->reason = apr_psprintf(r->pool,
+ "Comparison undefined: (%d): %s (adding to cache)",
+ result, ldap_err2string(result));
+ }
+
+ /* record the result code to return with the reason... */
+ result = compare_nodep->result;
+ /* and unlock this read lock */
+ ldap_cache_unlock(st, r);
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r,
+ "ldap_compare_s(%pp, %s, %s, %s) = %s (cached)",
+ ldc->ldap, dn, attrib, value, ldap_err2string(result));
+ return result;
+ }
+ }
+ /* unlock this read lock */
+ ldap_cache_unlock(st, r);
+ }
+
+start_over:
+ if (failures > st->retries) {
+ return result;
+ }
+
+ if (failures > 0 && st->retry_delay > 0) {
+ apr_sleep(st->retry_delay);
+ }
+
+ if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
+ /* connect failed */
+ return result;
+ }
+
+ result = ldap_compare_s(ldc->ldap,
+ (char *)dn,
+ (char *)attrib,
+ (char *)value);
+ if (AP_LDAP_IS_SERVER_DOWN(result)) {
+ /* connection failed - try again */
+ ldc->reason = "ldap_compare_s() failed with server down";
+ uldap_connection_unbind(ldc);
+ failures++;
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "%s (attempt %d)", ldc->reason, failures);
+ goto start_over;
+ }
+ if (result == LDAP_TIMEOUT && failures == 0) {
+ /*
+ * we are reusing a connection that doesn't seem to be active anymore
+ * (firewall state drop?), let's try a new connection.
+ */
+ ldc->reason = "ldap_compare_s() failed with timeout";
+ uldap_connection_unbind(ldc);
+ failures++;
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "%s (attempt %d)", ldc->reason, failures);
+ goto start_over;
+ }
+
+ ldc->last_backend_conn = r->request_time;
+ ldc->reason = "Comparison complete";
+ if ((LDAP_COMPARE_TRUE == result) ||
+ (LDAP_COMPARE_FALSE == result) ||
+ (LDAP_NO_SUCH_ATTRIBUTE == result)) {
+ if (curl) {
+ /* compare completed; caching result */
+ ldap_cache_lock(st, r);
+ the_compare_node.lastcompare = curtime;
+ the_compare_node.result = result;
+ the_compare_node.sgl_processed = 0;
+ the_compare_node.subgroupList = NULL;
+
+ /* If the node doesn't exist then insert it, otherwise just update
+ * it with the last results
+ */
+ compare_nodep = util_ald_cache_fetch(curl->compare_cache,
+ &the_compare_node);
+ if ( (compare_nodep == NULL)
+ || (strcmp(the_compare_node.dn, compare_nodep->dn) != 0)
+ || (strcmp(the_compare_node.attrib,compare_nodep->attrib) != 0)
+ || (strcmp(the_compare_node.value, compare_nodep->value) != 0))
+ {
+ void *junk;
+
+ junk = util_ald_cache_insert(curl->compare_cache,
+ &the_compare_node);
+ if (junk == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01287)
+ "cache_compare: Cache insertion failure.");
+ }
+ }
+ else {
+ compare_nodep->lastcompare = curtime;
+ compare_nodep->result = result;
+ }
+ ldap_cache_unlock(st, r);
+ }
+
+ if (LDAP_COMPARE_TRUE == result) {
+ ldc->reason = "Comparison true (adding to cache)";
+ }
+ else if (LDAP_COMPARE_FALSE == result) {
+ ldc->reason = "Comparison false (adding to cache)";
+ }
+ else if (LDAP_NO_SUCH_ATTRIBUTE == result) {
+ ldc->reason = "Comparison no such attribute (adding to cache)";
+ }
+ else {
+ ldc->reason = apr_psprintf(r->pool,
+ "Comparison undefined: (%d): %s (adding to cache)",
+ result, ldap_err2string(result));
+ }
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r,
+ "ldap_compare_s(%pp, %s, %s, %s) = %s",
+ ldc->ldap, dn, attrib, value, ldap_err2string(result));
+ return result;
+}
+
+
+static util_compare_subgroup_t* uldap_get_subgroups(request_rec *r,
+ util_ldap_connection_t *ldc,
+ const char *url,
+ const char *dn,
+ char **subgroupAttrs,
+ apr_array_header_t *subgroupclasses)
+{
+ int failures = 0;
+ int result = LDAP_COMPARE_FALSE;
+ util_compare_subgroup_t *res = NULL;
+ LDAPMessage *sga_res, *entry;
+ struct mod_auth_ldap_groupattr_entry_t *sgc_ents;
+ apr_array_header_t *subgroups = apr_array_make(r->pool, 20, sizeof(char *));
+ util_ldap_state_t *st = (util_ldap_state_t *)
+ ap_get_module_config(r->server->module_config,
+ &ldap_module);
+
+ sgc_ents = (struct mod_auth_ldap_groupattr_entry_t *) subgroupclasses->elts;
+
+ if (!subgroupAttrs) {
+ return res;
+ }
+
+start_over:
+ /*
+ * 3.B. The cache didn't have any subgrouplist yet. Go check for subgroups.
+ */
+ if (failures > st->retries) {
+ return res;
+ }
+
+ if (failures > 0 && st->retry_delay > 0) {
+ apr_sleep(st->retry_delay);
+ }
+
+
+ if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
+ /* connect failed */
+ return res;
+ }
+
+ /* try to do the search */
+ result = ldap_search_ext_s(ldc->ldap, (char *)dn, LDAP_SCOPE_BASE,
+ NULL, subgroupAttrs, 0,
+ NULL, NULL, NULL, APR_LDAP_SIZELIMIT, &sga_res);
+ if (AP_LDAP_IS_SERVER_DOWN(result)) {
+ ldc->reason = "ldap_search_ext_s() for subgroups failed with server"
+ " down";
+ uldap_connection_unbind(ldc);
+ failures++;
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "%s (attempt %d)", ldc->reason, failures);
+ goto start_over;
+ }
+ if (result == LDAP_TIMEOUT && failures == 0) {
+ /*
+ * we are reusing a connection that doesn't seem to be active anymore
+ * (firewall state drop?), let's try a new connection.
+ */
+ ldc->reason = "ldap_search_ext_s() for subgroups failed with timeout";
+ uldap_connection_unbind(ldc);
+ failures++;
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "%s (attempt %d)", ldc->reason, failures);
+ goto start_over;
+ }
+
+ /* if there is an error (including LDAP_NO_SUCH_OBJECT) return now */
+ if (result != LDAP_SUCCESS) {
+ ldc->reason = "ldap_search_ext_s() for subgroups failed";
+ return res;
+ }
+
+ ldc->last_backend_conn = r->request_time;
+ entry = ldap_first_entry(ldc->ldap, sga_res);
+
+ /*
+ * Get values for the provided sub-group attributes.
+ */
+ if (subgroupAttrs) {
+ int indx = 0, tmp_sgcIndex;
+
+ while (subgroupAttrs[indx]) {
+ char **values;
+ int val_index = 0;
+
+ /* Get *all* matching "member" values from this group. */
+ values = ldap_get_values(ldc->ldap, entry, subgroupAttrs[indx]);
+
+ if (values) {
+ val_index = 0;
+ /*
+ * Now we are going to pare the subgroup members of this group
+ * to *just* the subgroups, add them to the compare_nodep, and
+ * then proceed to check the new level of subgroups.
+ */
+ while (values[val_index]) {
+ /* Check if this entry really is a group. */
+ tmp_sgcIndex = 0;
+ result = LDAP_COMPARE_FALSE;
+ while ((tmp_sgcIndex < subgroupclasses->nelts)
+ && (result != LDAP_COMPARE_TRUE)) {
+ result = uldap_cache_compare(r, ldc, url,
+ values[val_index],
+ "objectClass",
+ sgc_ents[tmp_sgcIndex].name
+ );
+
+ if (result != LDAP_COMPARE_TRUE) {
+ tmp_sgcIndex++;
+ }
+ }
+ /* It's a group, so add it to the array. */
+ if (result == LDAP_COMPARE_TRUE) {
+ char **newgrp = (char **) apr_array_push(subgroups);
+ *newgrp = apr_pstrdup(r->pool, values[val_index]);
+ }
+ val_index++;
+ }
+ ldap_value_free(values);
+ }
+ indx++;
+ }
+ }
+
+ ldap_msgfree(sga_res);
+
+ if (subgroups->nelts > 0) {
+ /* We need to fill in tmp_local_subgroups using the data from LDAP */
+ int sgindex;
+ char **group;
+ res = apr_pcalloc(r->pool, sizeof(util_compare_subgroup_t));
+ res->subgroupDNs = apr_palloc(r->pool,
+ sizeof(char *) * (subgroups->nelts));
+ for (sgindex = 0; (group = apr_array_pop(subgroups)); sgindex++) {
+ res->subgroupDNs[sgindex] = apr_pstrdup(r->pool, *group);
+ }
+ res->len = sgindex;
+ }
+
+ return res;
+}
+
+
+/*
+ * Does a recursive lookup operation to try to find a user within (cached)
+ * nested groups. It accepts a cache that it will use to lookup previous
+ * compare attempts. We cache two kinds of compares (require group compares)
+ * and (require user compares). Each compare has a different cache node:
+ * require group includes the DN; require user does not because the require
+ * user cache is owned by the
+ *
+ * DON'T CALL THIS UNLESS YOU CALLED uldap_cache_compare FIRST!!!!!
+ *
+ *
+ * 1. Call uldap_cache_compare for each subgroupclass value to check the
+ * generic, user-agnostic, cached group entry. This will create a new generic
+ * cache entry if there
+ * wasn't one. If nothing returns LDAP_COMPARE_TRUE skip to step 5 since we
+ * have no groups.
+ * 2. Lock The cache and get the generic cache entry.
+ * 3. Check if there is already a subgrouplist in this generic group's cache
+ * entry.
+ * A. If there is, go to step 4.
+ * B. If there isn't:
+ * i) Use ldap_search to get the full list
+ * of subgroup "members" (which may include non-group "members").
+ * ii) Use uldap_cache_compare to strip the list down to just groups.
+ * iii) Lock and add this stripped down list to the cache of the generic
+ * group.
+ * 4. Loop through the sgl and call uldap_cache_compare (using the user info)
+ * for each
+ * subgroup to see if the subgroup contains the user and to get the subgroups
+ * added to the
+ * cache (with user-afinity, if they aren't already there).
+ * A. If the user is in the subgroup, then we'll be returning
+ * LDAP_COMPARE_TRUE.
+ * B. if the user isn't in the subgroup (LDAP_COMPARE_FALSE via
+ * uldap_cache_compare) then recursively call this function to get the
+ * sub-subgroups added...
+ * 5. Cleanup local allocations.
+ * 6. Return the final result.
+ */
+
+static int uldap_cache_check_subgroups(request_rec *r,
+ util_ldap_connection_t *ldc,
+ const char *url, const char *dn,
+ const char *attrib, const char *value,
+ char **subgroupAttrs,
+ apr_array_header_t *subgroupclasses,
+ int cur_subgroup_depth,
+ int max_subgroup_depth)
+{
+ int result = LDAP_COMPARE_FALSE;
+ util_url_node_t *curl;
+ util_url_node_t curnode;
+ util_compare_node_t *compare_nodep;
+ util_compare_node_t the_compare_node;
+ util_compare_subgroup_t *tmp_local_sgl = NULL;
+ int sgl_cached_empty = 0, sgindex = 0, base_sgcIndex = 0;
+ struct mod_auth_ldap_groupattr_entry_t *sgc_ents =
+ (struct mod_auth_ldap_groupattr_entry_t *) subgroupclasses->elts;
+ util_ldap_state_t *st = (util_ldap_state_t *)
+ ap_get_module_config(r->server->module_config,
+ &ldap_module);
+
+ /*
+ * Stop looking at deeper levels of nested groups if we have reached the
+ * max. Since we already checked the top-level group in uldap_cache_compare,
+ * we don't need to check it again here - so if max_subgroup_depth is set
+ * to 0, we won't check it (i.e. that is why we check < rather than <=).
+ * We'll be calling uldap_cache_compare from here to check if the user is
+ * in the next level before we recurse into that next level looking for
+ * more subgroups.
+ */
+ if (cur_subgroup_depth >= max_subgroup_depth) {
+ return LDAP_COMPARE_FALSE;
+ }
+
+ /*
+ * 1. Check the "groupiness" of the specified basedn. Stopping at the first
+ * TRUE return.
+ */
+ while ((base_sgcIndex < subgroupclasses->nelts)
+ && (result != LDAP_COMPARE_TRUE)) {
+ result = uldap_cache_compare(r, ldc, url, dn, "objectClass",
+ sgc_ents[base_sgcIndex].name);
+ if (result != LDAP_COMPARE_TRUE) {
+ base_sgcIndex++;
+ }
+ }
+
+ if (result != LDAP_COMPARE_TRUE) {
+ ldc->reason = "DN failed group verification.";
+ return result;
+ }
+
+ /*
+ * 2. Find previously created cache entry and check if there is already a
+ * subgrouplist.
+ */
+ ldap_cache_lock(st, r);
+ curnode.url = url;
+ curl = util_ald_cache_fetch(st->util_ldap_cache, &curnode);
+ ldap_cache_unlock(st, r);
+
+ if (curl && curl->compare_cache) {
+ /* make a comparison to the cache */
+ ldap_cache_lock(st, r);
+
+ the_compare_node.dn = (char *)dn;
+ the_compare_node.attrib = (char *)"objectClass";
+ the_compare_node.value = (char *)sgc_ents[base_sgcIndex].name;
+ the_compare_node.result = 0;
+ the_compare_node.sgl_processed = 0;
+ the_compare_node.subgroupList = NULL;
+
+ compare_nodep = util_ald_cache_fetch(curl->compare_cache,
+ &the_compare_node);
+
+ if (compare_nodep != NULL) {
+ /*
+ * Found the generic group entry... but the user isn't in this
+ * group or we wouldn't be here.
+ */
+ if (compare_nodep->sgl_processed) {
+ if (compare_nodep->subgroupList) {
+ /* Make a local copy of the subgroup list */
+ int i;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01288)
+ "Making local copy of SGL for "
+ "group (%s)(objectClass=%s) ",
+ dn, (char *)sgc_ents[base_sgcIndex].name);
+ tmp_local_sgl = apr_pcalloc(r->pool,
+ sizeof(util_compare_subgroup_t));
+ tmp_local_sgl->len = compare_nodep->subgroupList->len;
+ tmp_local_sgl->subgroupDNs =
+ apr_palloc(r->pool,
+ sizeof(char *) * compare_nodep->subgroupList->len);
+ for (i = 0; i < compare_nodep->subgroupList->len; i++) {
+ tmp_local_sgl->subgroupDNs[i] =
+ apr_pstrdup(r->pool,
+ compare_nodep->subgroupList->subgroupDNs[i]);
+ }
+ }
+ else {
+ sgl_cached_empty = 1;
+ }
+ }
+ }
+ ldap_cache_unlock(st, r);
+ }
+
+ if (!tmp_local_sgl && !sgl_cached_empty) {
+ /* No Cached SGL, retrieve from LDAP */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01289)
+ "no cached SGL for %s, retrieving from LDAP", dn);
+ tmp_local_sgl = uldap_get_subgroups(r, ldc, url, dn, subgroupAttrs,
+ subgroupclasses);
+ if (!tmp_local_sgl) {
+ /* No SGL aailable via LDAP either */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01290) "no subgroups for %s",
+ dn);
+ }
+
+ if (curl && curl->compare_cache) {
+ /*
+ * Find the generic group cache entry and add the sgl we just retrieved.
+ */
+ ldap_cache_lock(st, r);
+
+ the_compare_node.dn = (char *)dn;
+ the_compare_node.attrib = (char *)"objectClass";
+ the_compare_node.value = (char *)sgc_ents[base_sgcIndex].name;
+ the_compare_node.result = 0;
+ the_compare_node.sgl_processed = 0;
+ the_compare_node.subgroupList = NULL;
+
+ compare_nodep = util_ald_cache_fetch(curl->compare_cache,
+ &the_compare_node);
+
+ if (compare_nodep == NULL) {
+ /*
+ * The group entry we want to attach our SGL to doesn't exist.
+ * We only got here if we verified this DN was actually a group
+ * based on the objectClass, but we can't call the compare function
+ * while we already hold the cache lock -- only the insert.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01291)
+ "Cache entry for %s doesn't exist", dn);
+ the_compare_node.result = LDAP_COMPARE_TRUE;
+ util_ald_cache_insert(curl->compare_cache, &the_compare_node);
+ compare_nodep = util_ald_cache_fetch(curl->compare_cache,
+ &the_compare_node);
+ if (compare_nodep == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01292)
+ "util_ldap: Couldn't retrieve group entry "
+ "for %s from cache",
+ dn);
+ }
+ }
+
+ /*
+ * We have a valid cache entry and a locally generated SGL.
+ * Attach the SGL to the cache entry
+ */
+ if (compare_nodep && !compare_nodep->sgl_processed) {
+ if (!tmp_local_sgl) {
+ /* We looked up an SGL for a group and found it to be empty */
+ if (compare_nodep->subgroupList == NULL) {
+ compare_nodep->sgl_processed = 1;
+ }
+ }
+ else {
+ util_compare_subgroup_t *sgl_copy =
+ util_ald_sgl_dup(curl->compare_cache, tmp_local_sgl);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, APLOGNO(01293)
+ "Copying local SGL of len %d for group %s into cache",
+ tmp_local_sgl->len, dn);
+ if (sgl_copy) {
+ if (compare_nodep->subgroupList) {
+ util_ald_sgl_free(curl->compare_cache,
+ &(compare_nodep->subgroupList));
+ }
+ compare_nodep->subgroupList = sgl_copy;
+ compare_nodep->sgl_processed = 1;
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, APLOGNO(01294)
+ "Copy of SGL failed to obtain shared memory, "
+ "couldn't update cache");
+ }
+ }
+ }
+ ldap_cache_unlock(st, r);
+ }
+ }
+
+ /*
+ * tmp_local_sgl has either been created, or copied out of the cache
+ * If tmp_local_sgl is NULL, there are no subgroups to process and we'll
+ * return false
+ */
+ result = LDAP_COMPARE_FALSE;
+ if (!tmp_local_sgl) {
+ return result;
+ }
+
+ while ((result != LDAP_COMPARE_TRUE) && (sgindex < tmp_local_sgl->len)) {
+ const char *group = NULL;
+ group = tmp_local_sgl->subgroupDNs[sgindex];
+ /*
+ * 4. Now loop through the subgroupList and call uldap_cache_compare
+ * to check for the user.
+ */
+ result = uldap_cache_compare(r, ldc, url, group, attrib, value);
+ if (result == LDAP_COMPARE_TRUE) {
+ /*
+ * 4.A. We found the user in the subgroup. Return
+ * LDAP_COMPARE_TRUE.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01295)
+ "Found user %s in a subgroup (%s) at level %d of %d.",
+ r->user, group, cur_subgroup_depth+1,
+ max_subgroup_depth);
+ }
+ else {
+ /*
+ * 4.B. We didn't find the user in this subgroup, so recurse into
+ * it and keep looking.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01296)
+ "User %s not found in subgroup (%s) at level %d of "
+ "%d.", r->user, group, cur_subgroup_depth+1,
+ max_subgroup_depth);
+ result = uldap_cache_check_subgroups(r, ldc, url, group, attrib,
+ value, subgroupAttrs,
+ subgroupclasses,
+ cur_subgroup_depth+1,
+ max_subgroup_depth);
+ }
+ sgindex++;
+ }
+
+ return result;
+}
+
+
+static int uldap_cache_checkuserid(request_rec *r, util_ldap_connection_t *ldc,
+ const char *url, const char *basedn,
+ int scope, char **attrs, const char *filter,
+ const char *bindpw, const char **binddn,
+ const char ***retvals)
+{
+ const char **vals = NULL;
+ int numvals = 0;
+ int result = 0;
+ LDAPMessage *res, *entry;
+ char *dn;
+ int count;
+ int failures = 0;
+ util_url_node_t *curl; /* Cached URL node */
+ util_url_node_t curnode;
+ util_search_node_t *search_nodep; /* Cached search node */
+ util_search_node_t the_search_node;
+ apr_time_t curtime;
+
+ util_ldap_state_t *st =
+ (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
+ &ldap_module);
+
+ /* Get the cache node for this url */
+ ldap_cache_lock(st, r);
+ curnode.url = url;
+ curl = (util_url_node_t *)util_ald_cache_fetch(st->util_ldap_cache,
+ &curnode);
+ if (curl == NULL) {
+ curl = util_ald_create_caches(st, url);
+ }
+ ldap_cache_unlock(st, r);
+
+ if (curl) {
+ ldap_cache_lock(st, r);
+ the_search_node.username = filter;
+ search_nodep = util_ald_cache_fetch(curl->search_cache,
+ &the_search_node);
+ if (search_nodep != NULL) {
+
+ /* found entry in search cache... */
+ curtime = apr_time_now();
+
+ /*
+ * Remove this item from the cache if its expired. If the sent
+ * password doesn't match the storepassword, the entry will
+ * be removed and readded later if the credentials pass
+ * authentication.
+ */
+ if ((curtime - search_nodep->lastbind) > st->search_cache_ttl) {
+ /* ...but entry is too old */
+ util_ald_cache_remove(curl->search_cache, search_nodep);
+ }
+ else if ( (search_nodep->bindpw)
+ && (search_nodep->bindpw[0] != '\0')
+ && (strcmp(search_nodep->bindpw, bindpw) == 0))
+ {
+ /* ...and entry is valid */
+ *binddn = apr_pstrdup(r->pool, search_nodep->dn);
+ if (attrs) {
+ int i;
+ *retvals = apr_palloc(r->pool, sizeof(char *) * search_nodep->numvals);
+ for (i = 0; i < search_nodep->numvals; i++) {
+ (*retvals)[i] = apr_pstrdup(r->pool, search_nodep->vals[i]);
+ }
+ }
+ ldap_cache_unlock(st, r);
+ ldc->reason = "Authentication successful (cached)";
+ return LDAP_SUCCESS;
+ }
+ }
+ /* unlock this read lock */
+ ldap_cache_unlock(st, r);
+ }
+
+ /*
+ * At this point, there is no valid cached search, so lets do the search.
+ */
+
+ /*
+ * If LDAP operation fails due to LDAP_SERVER_DOWN, control returns here.
+ */
+start_over:
+ if (failures > st->retries) {
+ return result;
+ }
+
+ if (failures > 0 && st->retry_delay > 0) {
+ apr_sleep(st->retry_delay);
+ }
+
+ if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
+ return result;
+ }
+
+ /* try do the search */
+ result = ldap_search_ext_s(ldc->ldap,
+ (char *)basedn, scope,
+ (char *)filter, attrs, 0,
+ NULL, NULL, st->opTimeout, APR_LDAP_SIZELIMIT, &res);
+ if (AP_LDAP_IS_SERVER_DOWN(result))
+ {
+ ldc->reason = "ldap_search_ext_s() for user failed with server down";
+ uldap_connection_unbind(ldc);
+ failures++;
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "%s (attempt %d)", ldc->reason, failures);
+ goto start_over;
+ }
+
+ if (result == LDAP_TIMEOUT) {
+ ldc->reason = "ldap_search_ext_s() for user failed with timeout";
+ uldap_connection_unbind(ldc);
+ failures++;
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "%s (attempt %d)", ldc->reason, failures);
+ goto start_over;
+ }
+
+
+ /* if there is an error (including LDAP_NO_SUCH_OBJECT) return now */
+ if (result != LDAP_SUCCESS) {
+ ldc->reason = "ldap_search_ext_s() for user failed";
+ return result;
+ }
+
+ /*
+ * We should have found exactly one entry; to find a different
+ * number is an error.
+ */
+ ldc->last_backend_conn = r->request_time;
+ count = ldap_count_entries(ldc->ldap, res);
+ if (count != 1)
+ {
+ if (count == 0 )
+ ldc->reason = "User not found";
+ else
+ ldc->reason = "User is not unique (search found two "
+ "or more matches)";
+ ldap_msgfree(res);
+ return LDAP_NO_SUCH_OBJECT;
+ }
+
+ entry = ldap_first_entry(ldc->ldap, res);
+
+ /* Grab the dn, copy it into the pool, and free it again */
+ dn = ldap_get_dn(ldc->ldap, entry);
+ *binddn = apr_pstrdup(r->pool, dn);
+ ldap_memfree(dn);
+
+ /*
+ * A bind to the server with an empty password always succeeds, so
+ * we check to ensure that the password is not empty. This implies
+ * that users who actually do have empty passwords will never be
+ * able to authenticate with this module. I don't see this as a big
+ * problem.
+ */
+ if (!bindpw || strlen(bindpw) <= 0) {
+ ldap_msgfree(res);
+ ldc->reason = "Empty password not allowed";
+ return LDAP_INVALID_CREDENTIALS;
+ }
+
+ /*
+ * Attempt to bind with the retrieved dn and the password. If the bind
+ * fails, it means that the password is wrong (the dn obviously
+ * exists, since we just retrieved it)
+ */
+ result = uldap_simple_bind(ldc, (char *)*binddn, (char *)bindpw,
+ st->opTimeout);
+ if (AP_LDAP_IS_SERVER_DOWN(result) ||
+ (result == LDAP_TIMEOUT && failures == 0)) {
+ if (AP_LDAP_IS_SERVER_DOWN(result))
+ ldc->reason = "ldap_simple_bind() to check user credentials "
+ "failed with server down";
+ else
+ ldc->reason = "ldap_simple_bind() to check user credentials "
+ "timed out";
+ ldap_msgfree(res);
+ uldap_connection_unbind(ldc);
+ failures++;
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "%s (attempt %d)", ldc->reason, failures);
+ goto start_over;
+ }
+
+ /* failure? if so - return */
+ if (result != LDAP_SUCCESS) {
+ ldc->reason = "ldap_simple_bind() to check user credentials failed";
+ ldap_msgfree(res);
+ uldap_connection_unbind(ldc);
+ return result;
+ }
+ else {
+ /*
+ * We have just bound the connection to a different user and password
+ * combination, which might be reused unintentionally next time this
+ * connection is used from the connection pool.
+ */
+ ldc->must_rebind = 1;
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "LDC %pp used for authn, must be rebound", ldc);
+ }
+
+ /*
+ * Get values for the provided attributes.
+ */
+ if (attrs) {
+ int k = 0;
+ int i = 0;
+ while (attrs[k++]);
+ vals = apr_pcalloc(r->pool, sizeof(char *) * (k+1));
+ numvals = k;
+ while (attrs[i]) {
+ char **values;
+ int j = 0;
+ char *str = NULL;
+ /* get values */
+ values = ldap_get_values(ldc->ldap, entry, attrs[i]);
+ while (values && values[j]) {
+ str = str ? apr_pstrcat(r->pool, str, "; ", values[j], NULL)
+ : apr_pstrdup(r->pool, values[j]);
+ j++;
+ }
+ ldap_value_free(values);
+ vals[i] = str;
+ i++;
+ }
+ *retvals = vals;
+ }
+
+ /*
+ * Add the new username to the search cache.
+ */
+ if (curl) {
+ ldap_cache_lock(st, r);
+ the_search_node.username = filter;
+ the_search_node.dn = *binddn;
+ the_search_node.bindpw = bindpw;
+ the_search_node.lastbind = apr_time_now();
+ the_search_node.vals = vals;
+ the_search_node.numvals = numvals;
+
+ /* Search again to make sure that another thread didn't ready insert
+ * this node into the cache before we got here. If it does exist then
+ * update the lastbind
+ */
+ search_nodep = util_ald_cache_fetch(curl->search_cache,
+ &the_search_node);
+ if ((search_nodep == NULL) ||
+ (strcmp(*binddn, search_nodep->dn) != 0)) {
+
+ /* Nothing in cache, insert new entry */
+ util_ald_cache_insert(curl->search_cache, &the_search_node);
+ }
+ else if ((!search_nodep->bindpw) ||
+ (strcmp(bindpw, search_nodep->bindpw) != 0)) {
+
+ /* Entry in cache is invalid, remove it and insert new one */
+ util_ald_cache_remove(curl->search_cache, search_nodep);
+ util_ald_cache_insert(curl->search_cache, &the_search_node);
+ }
+ else {
+ /* Cache entry is valid, update lastbind */
+ search_nodep->lastbind = the_search_node.lastbind;
+ }
+ ldap_cache_unlock(st, r);
+ }
+ ldap_msgfree(res);
+
+ ldc->reason = "Authentication successful";
+ return LDAP_SUCCESS;
+}
+
+/*
+ * This function will return the DN of the entry matching userid.
+ * It is used to get the DN in case some other module than mod_auth_ldap
+ * has authenticated the user.
+ * The function is basically a copy of uldap_cache_checkuserid
+ * with password checking removed.
+ */
+static int uldap_cache_getuserdn(request_rec *r, util_ldap_connection_t *ldc,
+ const char *url, const char *basedn,
+ int scope, char **attrs, const char *filter,
+ const char **binddn, const char ***retvals)
+{
+ const char **vals = NULL;
+ int numvals = 0;
+ int result = 0;
+ LDAPMessage *res, *entry;
+ char *dn;
+ int count;
+ int failures = 0;
+ util_url_node_t *curl; /* Cached URL node */
+ util_url_node_t curnode;
+ util_search_node_t *search_nodep; /* Cached search node */
+ util_search_node_t the_search_node;
+ apr_time_t curtime;
+
+ util_ldap_state_t *st =
+ (util_ldap_state_t *)ap_get_module_config(r->server->module_config,
+ &ldap_module);
+
+ /* Get the cache node for this url */
+ ldap_cache_lock(st, r);
+ curnode.url = url;
+ curl = (util_url_node_t *)util_ald_cache_fetch(st->util_ldap_cache,
+ &curnode);
+ if (curl == NULL) {
+ curl = util_ald_create_caches(st, url);
+ }
+ ldap_cache_unlock(st, r);
+
+ if (curl) {
+ ldap_cache_lock(st, r);
+ the_search_node.username = filter;
+ search_nodep = util_ald_cache_fetch(curl->search_cache,
+ &the_search_node);
+ if (search_nodep != NULL) {
+
+ /* found entry in search cache... */
+ curtime = apr_time_now();
+
+ /*
+ * Remove this item from the cache if its expired.
+ */
+ if ((curtime - search_nodep->lastbind) > st->search_cache_ttl) {
+ /* ...but entry is too old */
+ util_ald_cache_remove(curl->search_cache, search_nodep);
+ }
+ else {
+ /* ...and entry is valid */
+ *binddn = apr_pstrdup(r->pool, search_nodep->dn);
+ if (attrs) {
+ int i;
+ *retvals = apr_palloc(r->pool, sizeof(char *) * search_nodep->numvals);
+ for (i = 0; i < search_nodep->numvals; i++) {
+ (*retvals)[i] = apr_pstrdup(r->pool, search_nodep->vals[i]);
+ }
+ }
+ ldap_cache_unlock(st, r);
+ ldc->reason = "Search successful (cached)";
+ return LDAP_SUCCESS;
+ }
+ }
+ /* unlock this read lock */
+ ldap_cache_unlock(st, r);
+ }
+
+ /*
+ * At this point, there is no valid cached search, so lets do the search.
+ */
+
+ /*
+ * If LDAP operation fails due to LDAP_SERVER_DOWN, control returns here.
+ */
+start_over:
+ if (failures > st->retries) {
+ return result;
+ }
+
+ if (failures > 0 && st->retry_delay > 0) {
+ apr_sleep(st->retry_delay);
+ }
+
+ if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) {
+ return result;
+ }
+
+ /* try do the search */
+ result = ldap_search_ext_s(ldc->ldap,
+ (char *)basedn, scope,
+ (char *)filter, attrs, 0,
+ NULL, NULL, st->opTimeout, APR_LDAP_SIZELIMIT, &res);
+ if (AP_LDAP_IS_SERVER_DOWN(result))
+ {
+ ldc->reason = "ldap_search_ext_s() for user failed with server down";
+ uldap_connection_unbind(ldc);
+ failures++;
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "%s (attempt %d)", ldc->reason, failures);
+ goto start_over;
+ }
+
+ /* if there is an error (including LDAP_NO_SUCH_OBJECT) return now */
+ if (result != LDAP_SUCCESS) {
+ ldc->reason = "ldap_search_ext_s() for user failed";
+ return result;
+ }
+
+ /*
+ * We should have found exactly one entry; to find a different
+ * number is an error.
+ */
+ ldc->last_backend_conn = r->request_time;
+ count = ldap_count_entries(ldc->ldap, res);
+ if (count != 1)
+ {
+ if (count == 0 )
+ ldc->reason = "User not found";
+ else
+ ldc->reason = "User is not unique (search found two "
+ "or more matches)";
+ ldap_msgfree(res);
+ return LDAP_NO_SUCH_OBJECT;
+ }
+
+ entry = ldap_first_entry(ldc->ldap, res);
+
+ /* Grab the dn, copy it into the pool, and free it again */
+ dn = ldap_get_dn(ldc->ldap, entry);
+ *binddn = apr_pstrdup(r->pool, dn);
+ ldap_memfree(dn);
+
+ /*
+ * Get values for the provided attributes.
+ */
+ if (attrs) {
+ int k = 0;
+ int i = 0;
+ while (attrs[k++]);
+ vals = apr_pcalloc(r->pool, sizeof(char *) * (k+1));
+ numvals = k;
+ while (attrs[i]) {
+ char **values;
+ int j = 0;
+ char *str = NULL;
+ /* get values */
+ values = ldap_get_values(ldc->ldap, entry, attrs[i]);
+ while (values && values[j]) {
+ str = str ? apr_pstrcat(r->pool, str, "; ", values[j], NULL)
+ : apr_pstrdup(r->pool, values[j]);
+ j++;
+ }
+ ldap_value_free(values);
+ vals[i] = str;
+ i++;
+ }
+ *retvals = vals;
+ }
+
+ /*
+ * Add the new username to the search cache.
+ */
+ if (curl) {
+ ldap_cache_lock(st, r);
+ the_search_node.username = filter;
+ the_search_node.dn = *binddn;
+ the_search_node.bindpw = NULL;
+ the_search_node.lastbind = apr_time_now();
+ the_search_node.vals = vals;
+ the_search_node.numvals = numvals;
+
+ /* Search again to make sure that another thread didn't ready insert
+ * this node into the cache before we got here. If it does exist then
+ * update the lastbind
+ */
+ search_nodep = util_ald_cache_fetch(curl->search_cache,
+ &the_search_node);
+ if ((search_nodep == NULL) ||
+ (strcmp(*binddn, search_nodep->dn) != 0)) {
+
+ /* Nothing in cache, insert new entry */
+ util_ald_cache_insert(curl->search_cache, &the_search_node);
+ }
+ /*
+ * Don't update lastbind on entries with bindpw because
+ * we haven't verified that password. It's OK to update
+ * the entry if there is no password in it.
+ */
+ else if (!search_nodep->bindpw) {
+ /* Cache entry is valid, update lastbind */
+ search_nodep->lastbind = the_search_node.lastbind;
+ }
+ ldap_cache_unlock(st, r);
+ }
+
+ ldap_msgfree(res);
+
+ ldc->reason = "Search successful";
+ return LDAP_SUCCESS;
+}
+
+/*
+ * Reports if ssl support is enabled
+ *
+ * 1 = enabled, 0 = not enabled
+ */
+static int uldap_ssl_supported(request_rec *r)
+{
+ util_ldap_state_t *st = (util_ldap_state_t *)ap_get_module_config(
+ r->server->module_config, &ldap_module);
+
+ return(st->ssl_supported);
+}
+
+
+/* ---------------------------------------- */
+/* config directives */
+
+
+static const char *util_ldap_set_cache_bytes(cmd_parms *cmd, void *dummy,
+ const char *bytes)
+{
+ util_ldap_state_t *st =
+ (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
+ &ldap_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ st->cache_bytes = atol(bytes);
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01297)
+ "ldap cache: Setting shared memory cache size to "
+ "%" APR_SIZE_T_FMT " bytes.",
+ st->cache_bytes);
+
+ return NULL;
+}
+
+static const char *util_ldap_set_cache_file(cmd_parms *cmd, void *dummy,
+ const char *file)
+{
+ util_ldap_state_t *st =
+ (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
+ &ldap_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ if (file) {
+ st->cache_file = ap_server_root_relative(st->pool, file);
+ }
+ else {
+ st->cache_file = NULL;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01298)
+ "LDAP cache: Setting shared memory cache file to %s.",
+ st->cache_file);
+
+ return NULL;
+}
+
+static const char *util_ldap_set_cache_ttl(cmd_parms *cmd, void *dummy,
+ const char *ttl)
+{
+ util_ldap_state_t *st =
+ (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
+ &ldap_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ st->search_cache_ttl = atol(ttl) * 1000000;
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01299)
+ "ldap cache: Setting cache TTL to %ld microseconds.",
+ st->search_cache_ttl);
+
+ return NULL;
+}
+
+static const char *util_ldap_set_cache_entries(cmd_parms *cmd, void *dummy,
+ const char *size)
+{
+ util_ldap_state_t *st =
+ (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
+ &ldap_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ st->search_cache_size = atol(size);
+ if (st->search_cache_size < 0) {
+ st->search_cache_size = 0;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01300)
+ "ldap cache: Setting search cache size to %ld entries.",
+ st->search_cache_size);
+
+ return NULL;
+}
+
+static const char *util_ldap_set_opcache_ttl(cmd_parms *cmd, void *dummy,
+ const char *ttl)
+{
+ util_ldap_state_t *st =
+ (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
+ &ldap_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ st->compare_cache_ttl = atol(ttl) * APR_USEC_PER_SEC;
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01301)
+ "ldap cache: Setting operation cache TTL to %ld microseconds.",
+ st->compare_cache_ttl);
+
+ return NULL;
+}
+
+static const char *util_ldap_set_opcache_entries(cmd_parms *cmd, void *dummy,
+ const char *size)
+{
+ util_ldap_state_t *st =
+ (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
+ &ldap_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ st->compare_cache_size = atol(size);
+ if (st->compare_cache_size < 0) {
+ st->compare_cache_size = 0;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01302)
+ "ldap cache: Setting operation cache size to %ld entries.",
+ st->compare_cache_size);
+
+ return NULL;
+}
+
+
+/**
+ * Parse the certificate type.
+ *
+ * The type can be one of the following:
+ * CA_DER, CA_BASE64, CA_CERT7_DB, CA_SECMOD, CERT_DER, CERT_BASE64,
+ * CERT_KEY3_DB, CERT_NICKNAME, KEY_DER, KEY_BASE64
+ *
+ * If no matches are found, APR_LDAP_CA_TYPE_UNKNOWN is returned.
+ */
+static int util_ldap_parse_cert_type(const char *type)
+{
+ /* Authority file in binary DER format */
+ if (0 == strcasecmp("CA_DER", type)) {
+ return APR_LDAP_CA_TYPE_DER;
+ }
+
+ /* Authority file in Base64 format */
+ else if (0 == strcasecmp("CA_BASE64", type)) {
+ return APR_LDAP_CA_TYPE_BASE64;
+ }
+
+ /* Netscape certificate database file/directory */
+ else if (0 == strcasecmp("CA_CERT7_DB", type)) {
+ return APR_LDAP_CA_TYPE_CERT7_DB;
+ }
+
+ /* Netscape secmod file/directory */
+ else if (0 == strcasecmp("CA_SECMOD", type)) {
+ return APR_LDAP_CA_TYPE_SECMOD;
+ }
+
+ /* Client cert file in DER format */
+ else if (0 == strcasecmp("CERT_DER", type)) {
+ return APR_LDAP_CERT_TYPE_DER;
+ }
+
+ /* Client cert file in Base64 format */
+ else if (0 == strcasecmp("CERT_BASE64", type)) {
+ return APR_LDAP_CERT_TYPE_BASE64;
+ }
+
+ /* Client cert file in PKCS#12 format */
+ else if (0 == strcasecmp("CERT_PFX", type)) {
+ return APR_LDAP_CERT_TYPE_PFX;
+ }
+
+ /* Netscape client cert database file/directory */
+ else if (0 == strcasecmp("CERT_KEY3_DB", type)) {
+ return APR_LDAP_CERT_TYPE_KEY3_DB;
+ }
+
+ /* Netscape client cert nickname */
+ else if (0 == strcasecmp("CERT_NICKNAME", type)) {
+ return APR_LDAP_CERT_TYPE_NICKNAME;
+ }
+
+ /* Client cert key file in DER format */
+ else if (0 == strcasecmp("KEY_DER", type)) {
+ return APR_LDAP_KEY_TYPE_DER;
+ }
+
+ /* Client cert key file in Base64 format */
+ else if (0 == strcasecmp("KEY_BASE64", type)) {
+ return APR_LDAP_KEY_TYPE_BASE64;
+ }
+
+ /* Client cert key file in PKCS#12 format */
+ else if (0 == strcasecmp("KEY_PFX", type)) {
+ return APR_LDAP_KEY_TYPE_PFX;
+ }
+
+ else {
+ return APR_LDAP_CA_TYPE_UNKNOWN;
+ }
+
+}
+
+
+/**
+ * Set LDAPTrustedGlobalCert.
+ *
+ * This directive takes either two or three arguments:
+ * - certificate type
+ * - certificate file / directory / nickname
+ * - certificate password (optional)
+ *
+ * This directive may only be used globally.
+ */
+static const char *util_ldap_set_trusted_global_cert(cmd_parms *cmd,
+ void *dummy,
+ const char *type,
+ const char *file,
+ const char *password)
+{
+ util_ldap_state_t *st =
+ (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
+ &ldap_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ apr_finfo_t finfo;
+ apr_status_t rv;
+ int cert_type = 0;
+ apr_ldap_opt_tls_cert_t *cert;
+
+ if (err != NULL) {
+ return err;
+ }
+
+ /* handle the certificate type */
+ if (type) {
+ cert_type = util_ldap_parse_cert_type(type);
+ if (APR_LDAP_CA_TYPE_UNKNOWN == cert_type) {
+ return apr_psprintf(cmd->pool, "The certificate type %s is "
+ "not recognised. It should be one "
+ "of CA_DER, CA_BASE64, CA_CERT7_DB, "
+ "CA_SECMOD, CERT_DER, CERT_BASE64, "
+ "CERT_KEY3_DB, CERT_NICKNAME, "
+ "KEY_DER, KEY_BASE64", type);
+ }
+ }
+ else {
+ return "Certificate type was not specified.";
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01303)
+ "LDAP: SSL trusted global cert - %s (type %s)",
+ file, type);
+
+ /* add the certificate to the global array */
+ cert = (apr_ldap_opt_tls_cert_t *)apr_array_push(st->global_certs);
+ cert->type = cert_type;
+ cert->path = file;
+ cert->password = password;
+
+ /* if file is a file or path, fix the path */
+ if (cert_type != APR_LDAP_CA_TYPE_UNKNOWN &&
+ cert_type != APR_LDAP_CERT_TYPE_NICKNAME) {
+
+ cert->path = ap_server_root_relative(cmd->pool, file);
+ if (cert->path &&
+ ((rv = apr_stat (&finfo, cert->path, APR_FINFO_MIN, cmd->pool))
+ != APR_SUCCESS))
+ {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, cmd->server, APLOGNO(01304)
+ "LDAP: Could not open SSL trusted certificate "
+ "authority file - %s",
+ cert->path == NULL ? file : cert->path);
+ return "Invalid global certificate file path";
+ }
+ }
+
+ return(NULL);
+}
+
+
+/**
+ * Set LDAPTrustedClientCert.
+ *
+ * This directive takes either two or three arguments:
+ * - certificate type
+ * - certificate file / directory / nickname
+ * - certificate password (optional)
+ */
+static const char *util_ldap_set_trusted_client_cert(cmd_parms *cmd,
+ void *config,
+ const char *type,
+ const char *file,
+ const char *password)
+{
+ util_ldap_config_t *dc = config;
+ apr_finfo_t finfo;
+ apr_status_t rv;
+ int cert_type = 0;
+ apr_ldap_opt_tls_cert_t *cert;
+
+ /* handle the certificate type */
+ if (type) {
+ cert_type = util_ldap_parse_cert_type(type);
+ if (APR_LDAP_CA_TYPE_UNKNOWN == cert_type) {
+ return apr_psprintf(cmd->pool, "The certificate type \"%s\" is "
+ "not recognised. It should be one "
+ "of CA_DER, CA_BASE64, "
+ "CERT_DER, CERT_BASE64, "
+ "CERT_NICKNAME, CERT_PFX, "
+ "KEY_DER, KEY_BASE64, KEY_PFX",
+ type);
+ }
+ else if ( APR_LDAP_CA_TYPE_CERT7_DB == cert_type ||
+ APR_LDAP_CA_TYPE_SECMOD == cert_type ||
+ APR_LDAP_CERT_TYPE_PFX == cert_type ||
+ APR_LDAP_CERT_TYPE_KEY3_DB == cert_type) {
+ return apr_psprintf(cmd->pool, "The certificate type \"%s\" is "
+ "only valid within a "
+ "LDAPTrustedGlobalCert directive. "
+ "Only CA_DER, CA_BASE64, "
+ "CERT_DER, CERT_BASE64, "
+ "CERT_NICKNAME, KEY_DER, and "
+ "KEY_BASE64 may be used.", type);
+ }
+ }
+ else {
+ return "Certificate type was not specified.";
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01305)
+ "LDAP: SSL trusted client cert - %s (type %s)",
+ file, type);
+
+ /* add the certificate to the client array */
+ cert = (apr_ldap_opt_tls_cert_t *)apr_array_push(dc->client_certs);
+ cert->type = cert_type;
+ cert->path = file;
+ cert->password = password;
+
+ /* if file is a file or path, fix the path */
+ if (cert_type != APR_LDAP_CA_TYPE_UNKNOWN &&
+ cert_type != APR_LDAP_CERT_TYPE_NICKNAME) {
+
+ cert->path = ap_server_root_relative(cmd->pool, file);
+ if (cert->path &&
+ ((rv = apr_stat (&finfo, cert->path, APR_FINFO_MIN, cmd->pool))
+ != APR_SUCCESS))
+ {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, cmd->server, APLOGNO(01306)
+ "LDAP: Could not open SSL client certificate "
+ "file - %s",
+ cert->path == NULL ? file : cert->path);
+ return "Invalid client certificate file path";
+ }
+
+ }
+
+ return(NULL);
+}
+
+
+/**
+ * Set LDAPTrustedMode.
+ *
+ * This directive sets what encryption mode to use on a connection:
+ * - None (No encryption)
+ * - SSL (SSL encryption)
+ * - STARTTLS (TLS encryption)
+ */
+static const char *util_ldap_set_trusted_mode(cmd_parms *cmd, void *dummy,
+ const char *mode)
+{
+ util_ldap_state_t *st =
+ (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
+ &ldap_module);
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01307)
+ "LDAP: SSL trusted mode - %s",
+ mode);
+
+ if (0 == strcasecmp("NONE", mode)) {
+ st->secure = APR_LDAP_NONE;
+ }
+ else if (0 == strcasecmp("SSL", mode)) {
+ st->secure = APR_LDAP_SSL;
+ }
+ else if ( (0 == strcasecmp("TLS", mode))
+ || (0 == strcasecmp("STARTTLS", mode))) {
+ st->secure = APR_LDAP_STARTTLS;
+ }
+ else {
+ return "Invalid LDAPTrustedMode setting: must be one of NONE, "
+ "SSL, or TLS/STARTTLS";
+ }
+
+ st->secure_set = 1;
+ return(NULL);
+}
+
+static const char *util_ldap_set_verify_srv_cert(cmd_parms *cmd,
+ void *dummy,
+ int mode)
+{
+ util_ldap_state_t *st =
+ (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
+ &ldap_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01308)
+ "LDAP: SSL verify server certificate - %s",
+ mode?"TRUE":"FALSE");
+
+ st->verify_svr_cert = mode;
+
+ return(NULL);
+}
+
+
+static const char *util_ldap_set_connection_timeout(cmd_parms *cmd,
+ void *dummy,
+ const char *ttl)
+{
+#ifdef LDAP_OPT_NETWORK_TIMEOUT
+ util_ldap_state_t *st =
+ (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
+ &ldap_module);
+#endif
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err != NULL) {
+ return err;
+ }
+
+#ifdef LDAP_OPT_NETWORK_TIMEOUT
+ st->connectionTimeout = atol(ttl);
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01309)
+ "ldap connection: Setting connection timeout to %ld seconds.",
+ st->connectionTimeout);
+#else
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, cmd->server, APLOGNO(01310)
+ "LDAP: Connection timeout option not supported by the "
+ "LDAP SDK in use." );
+#endif
+
+ return NULL;
+}
+
+
+static const char *util_ldap_set_chase_referrals(cmd_parms *cmd,
+ void *config,
+ const char *arg)
+{
+ util_ldap_config_t *dc = config;
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01311)
+ "LDAP: Setting referral chasing %s", arg);
+
+ if (0 == strcasecmp(arg, "on")) {
+ dc->ChaseReferrals = AP_LDAP_CHASEREFERRALS_ON;
+ }
+ else if (0 == strcasecmp(arg, "off")) {
+ dc->ChaseReferrals = AP_LDAP_CHASEREFERRALS_OFF;
+ }
+ else if (0 == strcasecmp(arg, "default")) {
+ dc->ChaseReferrals = AP_LDAP_CHASEREFERRALS_SDKDEFAULT;
+ }
+ else {
+ return "LDAPReferrals must be 'on', 'off', or 'default'";
+ }
+
+ return(NULL);
+}
+
+static const char *util_ldap_set_debug_level(cmd_parms *cmd,
+ void *config,
+ const char *arg) {
+#ifdef AP_LDAP_OPT_DEBUG
+ util_ldap_state_t *st =
+ (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
+ &ldap_module);
+#endif
+
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+#ifndef AP_LDAP_OPT_DEBUG
+ return "This directive is not supported with the currently linked LDAP library";
+#else
+ st->debug_level = atoi(arg);
+ return NULL;
+#endif
+}
+
+static const char *util_ldap_set_referral_hop_limit(cmd_parms *cmd,
+ void *config,
+ const char *hop_limit)
+{
+ util_ldap_config_t *dc = config;
+
+ dc->ReferralHopLimit = atol(hop_limit);
+
+ if (dc->ReferralHopLimit <= 0) {
+ return "LDAPReferralHopLimit must be greater than zero (Use 'LDAPReferrals Off' to disable referral chasing)";
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01312)
+ "LDAP: Limit chased referrals to maximum of %d hops.",
+ dc->ReferralHopLimit);
+
+ return NULL;
+}
+
+static void *util_ldap_create_dir_config(apr_pool_t *p, char *d)
+{
+ util_ldap_config_t *dc =
+ (util_ldap_config_t *) apr_pcalloc(p,sizeof(util_ldap_config_t));
+
+ /* defaults are AP_LDAP_CHASEREFERRALS_ON and AP_LDAP_DEFAULT_HOPLIMIT */
+ dc->client_certs = apr_array_make(p, 10, sizeof(apr_ldap_opt_tls_cert_t));
+ dc->ChaseReferrals = AP_LDAP_CHASEREFERRALS_ON;
+ dc->ReferralHopLimit = AP_LDAP_HOPLIMIT_UNSET;
+
+ return dc;
+}
+
+static const char *util_ldap_set_op_timeout(cmd_parms *cmd,
+ void *dummy,
+ const char *val)
+{
+ long timeout;
+ char *endptr;
+ util_ldap_state_t *st =
+ (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
+ &ldap_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ timeout = strtol(val, &endptr, 10);
+ if ((val == endptr) || (*endptr != '\0')) {
+ return "Timeout not numerical";
+ }
+ if (timeout < 0) {
+ return "Timeout must be non-negative";
+ }
+
+ if (timeout) {
+ if (!st->opTimeout) {
+ st->opTimeout = apr_pcalloc(cmd->pool, sizeof(struct timeval));
+ }
+ st->opTimeout->tv_sec = timeout;
+ }
+ else {
+ st->opTimeout = NULL;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01313)
+ "ldap connection: Setting op timeout to %ld seconds.",
+ timeout);
+
+#ifndef LDAP_OPT_TIMEOUT
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01314)
+ "LDAP: LDAP_OPT_TIMEOUT option not supported by the "
+ "LDAP library in use. Using LDAPTimeout value as search "
+ "timeout only." );
+#endif
+
+ return NULL;
+}
+
+static const char *util_ldap_set_conn_ttl(cmd_parms *cmd,
+ void *dummy,
+ const char *val)
+{
+ apr_interval_time_t timeout = -1;
+ util_ldap_state_t *st =
+ (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
+ &ldap_module);
+
+ /* Negative values mean AP_LDAP_CONNPOOL_INFINITE */
+ if (val[0] != '-' &&
+ ap_timeout_parameter_parse(val, &timeout, "s") != APR_SUCCESS) {
+ return "LDAPConnectionPoolTTL has wrong format";
+ }
+
+ if (timeout < 0) {
+ /* reserve -1 for default value */
+ timeout = AP_LDAP_CONNPOOL_INFINITE;
+ }
+ st->connection_pool_ttl = timeout;
+ return NULL;
+}
+
+static const char *util_ldap_set_retry_delay(cmd_parms *cmd,
+ void *dummy,
+ const char *val)
+{
+ apr_interval_time_t timeout;
+ util_ldap_state_t *st =
+ (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
+ &ldap_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ if (ap_timeout_parameter_parse(val, &timeout, "s") != APR_SUCCESS) {
+ return "LDAPRetryDelay has wrong format";
+ }
+
+ if (timeout < 0) {
+ return "LDAPRetryDelay must be >= 0";
+ }
+
+ st->retry_delay = timeout;
+ return NULL;
+}
+
+static const char *util_ldap_set_retries(cmd_parms *cmd,
+ void *dummy,
+ const char *val)
+{
+ util_ldap_state_t *st =
+ (util_ldap_state_t *)ap_get_module_config(cmd->server->module_config,
+ &ldap_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ st->retries = atoi(val);
+ if (st->retries < 0) {
+ return "LDAPRetries must be >= 0";
+ }
+
+ return NULL;
+}
+
+static void *util_ldap_create_config(apr_pool_t *p, server_rec *s)
+{
+ util_ldap_state_t *st =
+ (util_ldap_state_t *)apr_pcalloc(p, sizeof(util_ldap_state_t));
+
+ /* Create a per vhost pool for mod_ldap to use, serialized with
+ * st->mutex (also one per vhost). both are replicated by fork(),
+ * no shared memory managed by either.
+ */
+ apr_pool_create(&st->pool, p);
+ apr_pool_tag(st->pool, "util_ldap_state");
+#if APR_HAS_THREADS
+ apr_thread_mutex_create(&st->mutex, APR_THREAD_MUTEX_DEFAULT, st->pool);
+#endif
+
+ st->cache_bytes = 500000;
+ st->search_cache_ttl = 600 * APR_USEC_PER_SEC; /* 10 minutes */
+ st->search_cache_size = 1024;
+ st->compare_cache_ttl = 600 * APR_USEC_PER_SEC; /* 10 minutes */
+ st->compare_cache_size = 1024;
+ st->connections = NULL;
+ st->ssl_supported = 0;
+ st->global_certs = apr_array_make(p, 10, sizeof(apr_ldap_opt_tls_cert_t));
+ st->secure = APR_LDAP_NONE;
+ st->secure_set = 0;
+ st->connectionTimeout = 10;
+ st->opTimeout = apr_pcalloc(p, sizeof(struct timeval));
+ st->opTimeout->tv_sec = 60;
+ st->verify_svr_cert = 1;
+ st->connection_pool_ttl = AP_LDAP_CONNPOOL_DEFAULT; /* no limit */
+ st->retries = 3;
+ st->retry_delay = 0; /* no delay */
+
+ return st;
+}
+
+/* cache-related settings are not merged here, but in the post_config hook,
+ * since the cache has not yet sprung to life
+ */
+static void *util_ldap_merge_config(apr_pool_t *p, void *basev,
+ void *overridesv)
+{
+ util_ldap_state_t *st = apr_pcalloc(p, sizeof(util_ldap_state_t));
+ util_ldap_state_t *base = (util_ldap_state_t *) basev;
+ util_ldap_state_t *overrides = (util_ldap_state_t *) overridesv;
+
+ st->pool = overrides->pool;
+#if APR_HAS_THREADS
+ st->mutex = overrides->mutex;
+#endif
+
+ /* The cache settings can not be modified in a
+ virtual host since all server use the same
+ shared memory cache. */
+ st->cache_bytes = base->cache_bytes;
+ st->search_cache_ttl = base->search_cache_ttl;
+ st->search_cache_size = base->search_cache_size;
+ st->compare_cache_ttl = base->compare_cache_ttl;
+ st->compare_cache_size = base->compare_cache_size;
+
+ st->connections = NULL;
+ st->ssl_supported = 0; /* not known until post-config and re-merged */
+ st->global_certs = apr_array_append(p, base->global_certs,
+ overrides->global_certs);
+ st->secure = (overrides->secure_set == 0) ? base->secure
+ : overrides->secure;
+
+ /* These LDAP connection settings can not be overwritten in
+ a virtual host. Once set in the base server, they must
+ remain the same. None of the LDAP SDKs seem to be able
+ to handle setting the verify_svr_cert flag on a
+ per-connection basis. The OpenLDAP client appears to be
+ able to handle the connection timeout per-connection
+ but the Novell SDK cannot. Allowing the timeout to
+ be set by each vhost is of little value so rather than
+ trying to make special exceptions for one LDAP SDK, GLOBAL_ONLY
+ is being enforced on this setting as well. */
+ st->connectionTimeout = base->connectionTimeout;
+ st->opTimeout = base->opTimeout;
+ st->verify_svr_cert = base->verify_svr_cert;
+ st->debug_level = base->debug_level;
+
+ st->connection_pool_ttl = (overrides->connection_pool_ttl == AP_LDAP_CONNPOOL_DEFAULT) ?
+ base->connection_pool_ttl : overrides->connection_pool_ttl;
+
+ st->retries = base->retries;
+ st->retry_delay = base->retry_delay;
+
+ return st;
+}
+
+static apr_status_t util_ldap_cleanup_module(void *data)
+{
+ server_rec *s = data;
+ util_ldap_state_t *st = (util_ldap_state_t *)ap_get_module_config(
+ s->module_config, &ldap_module);
+
+ if (st->ssl_supported) {
+ apr_ldap_ssl_deinit();
+ }
+
+ return APR_SUCCESS;
+}
+
+static int util_ldap_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp)
+{
+ apr_status_t result;
+
+ result = ap_mutex_register(pconf, ldap_cache_mutex_type, NULL,
+ APR_LOCK_DEFAULT, 0);
+ if (result != APR_SUCCESS) {
+ return result;
+ }
+
+ return OK;
+}
+
+static int util_ldap_post_config(apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ apr_status_t result;
+ server_rec *s_vhost;
+ util_ldap_state_t *st_vhost;
+
+ util_ldap_state_t *st = (util_ldap_state_t *)
+ ap_get_module_config(s->module_config,
+ &ldap_module);
+
+ apr_ldap_err_t *result_err = NULL;
+ int rc;
+
+ /* util_ldap_post_config() will be called twice. Don't bother
+ * going through all of the initialization on the first call
+ * because it will just be thrown away.*/
+ if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) {
+
+#if APR_HAS_SHARED_MEMORY
+ /*
+ * If we are using shared memory caching and the cache file already
+ * exists then delete it. Otherwise we are going to run into problems
+ * creating the shared memory.
+ */
+ if (st->cache_file && st->cache_bytes > 0) {
+ char *lck_file = apr_pstrcat(ptemp, st->cache_file, ".lck",
+ NULL);
+ apr_file_remove(lck_file, ptemp);
+ }
+#endif
+ return OK;
+ }
+
+#if APR_HAS_SHARED_MEMORY
+ /*
+ * initializing cache if we don't already have a shm address
+ */
+ if (!st->cache_shm) {
+#endif
+ result = util_ldap_cache_init(p, st);
+ if (result != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, result, s, APLOGNO(01315)
+ "LDAP cache: could not create shared memory segment");
+ return DONE;
+ }
+
+ result = ap_global_mutex_create(&st->util_ldap_cache_lock, NULL,
+ ldap_cache_mutex_type, NULL, s, p, 0);
+ if (result != APR_SUCCESS) {
+ return result;
+ }
+
+ /* merge config in all vhost */
+ s_vhost = s->next;
+ while (s_vhost) {
+ st_vhost = (util_ldap_state_t *)
+ ap_get_module_config(s_vhost->module_config,
+ &ldap_module);
+ st_vhost->util_ldap_cache = st->util_ldap_cache;
+ st_vhost->util_ldap_cache_lock = st->util_ldap_cache_lock;
+#if APR_HAS_SHARED_MEMORY
+ st_vhost->cache_shm = st->cache_shm;
+ st_vhost->cache_rmm = st->cache_rmm;
+ st_vhost->cache_file = st->cache_file;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, result, s, APLOGNO(01316)
+ "LDAP merging Shared Cache conf: shm=0x%pp rmm=0x%pp "
+ "for VHOST: %s", st->cache_shm, st->cache_rmm,
+ s_vhost->server_hostname);
+#endif
+ s_vhost = s_vhost->next;
+ }
+#if APR_HAS_SHARED_MEMORY
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01317)
+ "LDAP cache: LDAPSharedCacheSize is zero, disabling "
+ "shared memory cache");
+ }
+#endif
+
+ /* log the LDAP SDK used
+ */
+ {
+ apr_ldap_err_t *result = NULL;
+ apr_ldap_info(p, &(result));
+ if (result != NULL) {
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(01318) "%s", result->reason);
+ }
+ }
+
+ apr_pool_cleanup_register(p, s, util_ldap_cleanup_module,
+ util_ldap_cleanup_module);
+
+ /*
+ * Initialize SSL support, and log the result for the benefit of the admin.
+ *
+ * If SSL is not supported it is not necessarily an error, as the
+ * application may not want to use it.
+ */
+ rc = apr_ldap_ssl_init(p,
+ NULL,
+ 0,
+ &(result_err));
+ if (APR_SUCCESS == rc) {
+ rc = apr_ldap_set_option(ptemp, NULL, APR_LDAP_OPT_TLS_CERT,
+ (void *)st->global_certs, &(result_err));
+ }
+
+ if (APR_SUCCESS == rc) {
+ st->ssl_supported = 1;
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(01319)
+ "LDAP: SSL support available" );
+ }
+ else {
+ st->ssl_supported = 0;
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(01320)
+ "LDAP: SSL support unavailable%s%s",
+ result_err ? ": " : "",
+ result_err ? result_err->reason : "");
+ }
+
+ /* ssl_supported is really a global setting */
+ s_vhost = s->next;
+ while (s_vhost) {
+ st_vhost = (util_ldap_state_t *)
+ ap_get_module_config(s_vhost->module_config,
+ &ldap_module);
+
+ st_vhost->ssl_supported = st->ssl_supported;
+ s_vhost = s_vhost->next;
+ }
+
+ /* Initialize the rebind callback's cross reference list. */
+ apr_ldap_rebind_init (p);
+
+#ifdef AP_LDAP_OPT_DEBUG
+ if (st->debug_level > 0) {
+ result = ldap_set_option(NULL, AP_LDAP_OPT_DEBUG, &st->debug_level);
+ if (result != LDAP_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(01321)
+ "LDAP: Could not set the LDAP library debug level to %d:(%d) %s",
+ st->debug_level, result, ldap_err2string(result));
+ }
+ }
+#endif
+
+ return(OK);
+}
+
+static void util_ldap_child_init(apr_pool_t *p, server_rec *s)
+{
+ apr_status_t sts;
+ util_ldap_state_t *st = ap_get_module_config(s->module_config,
+ &ldap_module);
+
+ if (!st->util_ldap_cache_lock) return;
+
+ sts = apr_global_mutex_child_init(&st->util_ldap_cache_lock,
+ apr_global_mutex_lockfile(st->util_ldap_cache_lock), p);
+ if (sts != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, sts, s, APLOGNO(01322)
+ "Failed to initialise global mutex %s in child process",
+ ldap_cache_mutex_type);
+ }
+}
+
+static const command_rec util_ldap_cmds[] = {
+ AP_INIT_TAKE1("LDAPSharedCacheSize", util_ldap_set_cache_bytes,
+ NULL, RSRC_CONF,
+ "Set the size of the shared memory cache (in bytes). Use "
+ "0 to disable the shared memory cache. (default: 500000)"),
+
+ AP_INIT_TAKE1("LDAPSharedCacheFile", util_ldap_set_cache_file,
+ NULL, RSRC_CONF,
+ "Set the file name for the shared memory cache."),
+
+ AP_INIT_TAKE1("LDAPCacheEntries", util_ldap_set_cache_entries,
+ NULL, RSRC_CONF,
+ "Set the maximum number of entries that are possible in the "
+ "LDAP search cache. Use 0 or -1 to disable the search cache "
+ "(default: 1024)"),
+
+ AP_INIT_TAKE1("LDAPCacheTTL", util_ldap_set_cache_ttl,
+ NULL, RSRC_CONF,
+ "Set the maximum time (in seconds) that an item can be "
+ "cached in the LDAP search cache. Use 0 for no limit. "
+ "(default 600)"),
+
+ AP_INIT_TAKE1("LDAPOpCacheEntries", util_ldap_set_opcache_entries,
+ NULL, RSRC_CONF,
+ "Set the maximum number of entries that are possible "
+ "in the LDAP compare cache. Use 0 or -1 to disable the compare cache "
+ "(default: 1024)"),
+
+ AP_INIT_TAKE1("LDAPOpCacheTTL", util_ldap_set_opcache_ttl,
+ NULL, RSRC_CONF,
+ "Set the maximum time (in seconds) that an item is cached "
+ "in the LDAP operation cache. Use 0 for no limit. "
+ "(default: 600)"),
+
+ AP_INIT_TAKE23("LDAPTrustedGlobalCert", util_ldap_set_trusted_global_cert,
+ NULL, RSRC_CONF,
+ "Takes three arguments; the first argument is the cert "
+ "type of the second argument, one of CA_DER, CA_BASE64, "
+ "CA_CERT7_DB, CA_SECMOD, CERT_DER, CERT_BASE64, CERT_KEY3_DB, "
+ "CERT_NICKNAME, KEY_DER, or KEY_BASE64. The second argument "
+ "specifes the file and/or directory containing the trusted CA "
+ "certificates (and global client certs for Netware) used to "
+ "validate the LDAP server. The third argument is an optional "
+ "passphrase if applicable."),
+
+ AP_INIT_TAKE23("LDAPTrustedClientCert", util_ldap_set_trusted_client_cert,
+ NULL, OR_AUTHCFG,
+ "Takes three arguments: the first argument is the certificate "
+ "type of the second argument, one of CA_DER, CA_BASE64, "
+ "CA_CERT7_DB, CA_SECMOD, CERT_DER, CERT_BASE64, CERT_KEY3_DB, "
+ "CERT_NICKNAME, KEY_DER, or KEY_BASE64. The second argument "
+ "specifies the file and/or directory containing the client "
+ "certificate, or certificate ID used to validate this LDAP "
+ "client. The third argument is an optional passphrase if "
+ "applicable."),
+
+ AP_INIT_TAKE1("LDAPTrustedMode", util_ldap_set_trusted_mode,
+ NULL, RSRC_CONF,
+ "Specify the type of security that should be applied to "
+ "an LDAP connection. One of; NONE, SSL or STARTTLS."),
+
+ AP_INIT_FLAG("LDAPVerifyServerCert", util_ldap_set_verify_srv_cert,
+ NULL, RSRC_CONF,
+ "Set to 'ON' requires that the server certificate be verified"
+ " before a secure LDAP connection can be establish. Default"
+ " 'ON'"),
+
+ AP_INIT_TAKE1("LDAPConnectionTimeout", util_ldap_set_connection_timeout,
+ NULL, RSRC_CONF,
+ "Specify the LDAP socket connection timeout in seconds "
+ "(default: 10)"),
+
+ AP_INIT_TAKE1("LDAPReferrals", util_ldap_set_chase_referrals,
+ NULL, OR_AUTHCFG,
+ "Choose whether referrals are chased ['ON'|'OFF'|'DEFAULT']. Default 'ON'"),
+
+ AP_INIT_TAKE1("LDAPReferralHopLimit", util_ldap_set_referral_hop_limit,
+ NULL, OR_AUTHCFG,
+ "Limit the number of referral hops that LDAP can follow. "
+ "(Integer value, Consult LDAP SDK documentation for applicability and defaults"),
+
+ AP_INIT_TAKE1("LDAPLibraryDebug", util_ldap_set_debug_level,
+ NULL, RSRC_CONF,
+ "Enable debugging in LDAP SDK (Default: off, values: SDK specific"),
+
+ AP_INIT_TAKE1("LDAPTimeout", util_ldap_set_op_timeout,
+ NULL, RSRC_CONF,
+ "Specify the LDAP bind/search timeout in seconds "
+ "(0 = no limit). Default: 60"),
+ AP_INIT_TAKE1("LDAPConnectionPoolTTL", util_ldap_set_conn_ttl,
+ NULL, RSRC_CONF,
+ "Specify the maximum amount of time a bound connection can sit "
+ "idle and still be considered valid for reuse"
+ "(0 = no pool, -1 = no limit, n = time in seconds). Default: -1"),
+ AP_INIT_TAKE1("LDAPRetries", util_ldap_set_retries,
+ NULL, RSRC_CONF,
+ "Specify the number of times a failed LDAP operation should be retried "
+ "(0 = no retries). Default: 3"),
+ AP_INIT_TAKE1("LDAPRetryDelay", util_ldap_set_retry_delay,
+ NULL, RSRC_CONF,
+ "Specify the delay between retries of a failed LDAP operation "
+ "(0 = no delay). Default: 0"),
+
+
+ {NULL}
+};
+
+static void util_ldap_register_hooks(apr_pool_t *p)
+{
+ APR_REGISTER_OPTIONAL_FN(uldap_connection_open);
+ APR_REGISTER_OPTIONAL_FN(uldap_connection_close);
+ APR_REGISTER_OPTIONAL_FN(uldap_connection_unbind);
+ APR_REGISTER_OPTIONAL_FN(uldap_connection_find);
+ APR_REGISTER_OPTIONAL_FN(uldap_cache_comparedn);
+ APR_REGISTER_OPTIONAL_FN(uldap_cache_compare);
+ APR_REGISTER_OPTIONAL_FN(uldap_cache_checkuserid);
+ APR_REGISTER_OPTIONAL_FN(uldap_cache_getuserdn);
+ APR_REGISTER_OPTIONAL_FN(uldap_ssl_supported);
+ APR_REGISTER_OPTIONAL_FN(uldap_cache_check_subgroups);
+
+ ap_hook_pre_config(util_ldap_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_post_config(util_ldap_post_config,NULL,NULL,APR_HOOK_MIDDLE);
+ ap_hook_handler(util_ldap_handler, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_child_init(util_ldap_child_init, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(ldap) = {
+ STANDARD20_MODULE_STUFF,
+ util_ldap_create_dir_config, /* create dir config */
+ NULL, /* merge dir config */
+ util_ldap_create_config, /* create server config */
+ util_ldap_merge_config, /* merge server config */
+ util_ldap_cmds, /* command table */
+ util_ldap_register_hooks, /* set up request processing hooks */
+};
diff --git a/modules/ldap/util_ldap_cache.c b/modules/ldap/util_ldap_cache.c
new file mode 100644
index 0000000..774a76e
--- /dev/null
+++ b/modules/ldap/util_ldap_cache.c
@@ -0,0 +1,467 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * util_ldap_cache.c: LDAP cache things
+ *
+ * Original code from auth_ldap module for Apache v1.3:
+ * Copyright 1998, 1999 Enbridge Pipelines Inc.
+ * Copyright 1999-2001 Dave Carrigan
+ */
+
+#include "httpd.h"
+#include "util_ldap.h"
+#include "util_ldap_cache.h"
+#include <apr_strings.h>
+
+#if APR_HAS_LDAP
+
+/* ------------------------------------------------------------------ */
+
+unsigned long util_ldap_url_node_hash(void *n)
+{
+ util_url_node_t *node = n;
+ return util_ald_hash_string(1, node->url);
+}
+
+int util_ldap_url_node_compare(void *a, void *b)
+{
+ util_url_node_t *na = a;
+ util_url_node_t *nb = b;
+
+ return (strcmp(na->url, nb->url) == 0);
+}
+
+void *util_ldap_url_node_copy(util_ald_cache_t *cache, void *c)
+{
+ util_url_node_t *n = c;
+ util_url_node_t *node = util_ald_alloc(cache, sizeof *node);
+
+ if (node) {
+ if (!(node->url = util_ald_strdup(cache, n->url))) {
+ util_ald_free(cache, node);
+ return NULL;
+ }
+ node->search_cache = n->search_cache;
+ node->compare_cache = n->compare_cache;
+ node->dn_compare_cache = n->dn_compare_cache;
+ return node;
+ }
+ else {
+ return NULL;
+ }
+}
+
+void util_ldap_url_node_free(util_ald_cache_t *cache, void *n)
+{
+ util_url_node_t *node = n;
+
+ util_ald_free(cache, node->url);
+ util_ald_destroy_cache(node->search_cache);
+ util_ald_destroy_cache(node->compare_cache);
+ util_ald_destroy_cache(node->dn_compare_cache);
+ util_ald_free(cache, node);
+}
+
+void util_ldap_url_node_display(request_rec *r, util_ald_cache_t *cache, void *n)
+{
+ util_url_node_t *node = n;
+ char date_str[APR_CTIME_LEN];
+ const char *type_str;
+ util_ald_cache_t *cache_node;
+ int x;
+
+ for (x=0;x<3;x++) {
+ switch (x) {
+ case 0:
+ cache_node = node->search_cache;
+ type_str = "Searches";
+ break;
+ case 1:
+ cache_node = node->compare_cache;
+ type_str = "Compares";
+ break;
+ case 2:
+ default:
+ cache_node = node->dn_compare_cache;
+ type_str = "DN Compares";
+ break;
+ }
+
+ if (cache_node->marktime) {
+ apr_ctime(date_str, cache_node->marktime);
+ }
+ else
+ date_str[0] = 0;
+
+ ap_rprintf(r,
+ "<tr valign='top'>"
+ "<td nowrap>%s (%s)</td>"
+ "<td nowrap>%ld</td>"
+ "<td nowrap>%ld</td>"
+ "<td nowrap>%ld</td>"
+ "<td nowrap>%" APR_TIME_T_FMT "</td>"
+ "<td nowrap>%ld</td>"
+ "<td nowrap>%s</td>"
+ "</tr>",
+ node->url,
+ type_str,
+ cache_node->size,
+ cache_node->maxentries,
+ cache_node->numentries,
+ apr_time_sec(cache_node->ttl),
+ cache_node->fullmark,
+ date_str);
+ }
+
+}
+
+/* ------------------------------------------------------------------ */
+
+/* Cache functions for search nodes */
+unsigned long util_ldap_search_node_hash(void *n)
+{
+ util_search_node_t *node = n;
+ return util_ald_hash_string(1, node->username);
+}
+
+int util_ldap_search_node_compare(void *a, void *b)
+{
+ util_search_node_t *na = a;
+ util_search_node_t *nb = b;
+
+ return (strcmp(na->username, nb->username) == 0);
+}
+
+void *util_ldap_search_node_copy(util_ald_cache_t *cache, void *c)
+{
+ util_search_node_t *node = c;
+ util_search_node_t *newnode = util_ald_alloc(cache, sizeof *newnode);
+
+ /* safety check */
+ if (newnode) {
+
+ /* copy vals */
+ if (node->vals) {
+ int k = node->numvals;
+ int i = 0;
+ if (!(newnode->vals = util_ald_alloc(cache, sizeof(char *) * (k+1)))) {
+ util_ldap_search_node_free(cache, newnode);
+ return NULL;
+ }
+ newnode->numvals = node->numvals;
+ for (;k;k--) {
+ if (node->vals[i]) {
+ if (!(newnode->vals[i] = util_ald_strdup(cache, node->vals[i]))) {
+ util_ldap_search_node_free(cache, newnode);
+ return NULL;
+ }
+ }
+ else
+ newnode->vals[i] = NULL;
+ i++;
+ }
+ }
+ else {
+ newnode->vals = NULL;
+ }
+ if (!(newnode->username = util_ald_strdup(cache, node->username)) ||
+ !(newnode->dn = util_ald_strdup(cache, node->dn)) ) {
+ util_ldap_search_node_free(cache, newnode);
+ return NULL;
+ }
+ if (node->bindpw) {
+ if (!(newnode->bindpw = util_ald_strdup(cache, node->bindpw))) {
+ util_ldap_search_node_free(cache, newnode);
+ return NULL;
+ }
+ } else {
+ newnode->bindpw = NULL;
+ }
+ newnode->lastbind = node->lastbind;
+
+ }
+ return (void *)newnode;
+}
+
+void util_ldap_search_node_free(util_ald_cache_t *cache, void *n)
+{
+ int i = 0;
+ util_search_node_t *node = n;
+ int k = node->numvals;
+
+ if (node->vals) {
+ for (;k;k--,i++) {
+ if (node->vals[i]) {
+ util_ald_free(cache, node->vals[i]);
+ }
+ }
+ util_ald_free(cache, node->vals);
+ }
+ util_ald_free(cache, node->username);
+ util_ald_free(cache, node->dn);
+ util_ald_free(cache, node->bindpw);
+ util_ald_free(cache, node);
+}
+
+void util_ldap_search_node_display(request_rec *r, util_ald_cache_t *cache, void *n)
+{
+ util_search_node_t *node = n;
+ char date_str[APR_CTIME_LEN];
+
+ apr_ctime(date_str, node->lastbind);
+
+ ap_rprintf(r,
+ "<tr valign='top'>"
+ "<td nowrap>%s</td>"
+ "<td nowrap>%s</td>"
+ "<td nowrap>%s</td>"
+ "</tr>",
+ node->username,
+ node->dn,
+ date_str);
+}
+
+/* ------------------------------------------------------------------ */
+
+unsigned long util_ldap_compare_node_hash(void *n)
+{
+ util_compare_node_t *node = n;
+ return util_ald_hash_string(3, node->dn, node->attrib, node->value);
+}
+
+int util_ldap_compare_node_compare(void *a, void *b)
+{
+ util_compare_node_t *na = a;
+ util_compare_node_t *nb = b;
+
+ return (strcmp(na->dn, nb->dn) == 0 &&
+ strcmp(na->attrib, nb->attrib) == 0 &&
+ strcmp(na->value, nb->value) == 0);
+}
+
+void *util_ldap_compare_node_copy(util_ald_cache_t *cache, void *c)
+{
+ util_compare_node_t *n = c;
+ util_compare_node_t *node = util_ald_alloc(cache, sizeof *node);
+
+ if (node) {
+ if (!(node->dn = util_ald_strdup(cache, n->dn)) ||
+ !(node->attrib = util_ald_strdup(cache, n->attrib)) ||
+ !(node->value = util_ald_strdup(cache, n->value)) ||
+ ((n->subgroupList) && !(node->subgroupList = util_ald_sgl_dup(cache, n->subgroupList)))) {
+ util_ldap_compare_node_free(cache, node);
+ return NULL;
+ }
+ node->lastcompare = n->lastcompare;
+ node->result = n->result;
+ node->sgl_processed = n->sgl_processed;
+ return node;
+ }
+ else {
+ return NULL;
+ }
+}
+
+void util_ldap_compare_node_free(util_ald_cache_t *cache, void *n)
+{
+ util_compare_node_t *node = n;
+
+ util_ald_sgl_free(cache, &(node->subgroupList));
+ util_ald_free(cache, node->dn);
+ util_ald_free(cache, node->attrib);
+ util_ald_free(cache, node->value);
+ util_ald_free(cache, node);
+}
+
+void util_ldap_compare_node_display(request_rec *r, util_ald_cache_t *cache, void *n)
+{
+ util_compare_node_t *node = n;
+ char date_str[APR_CTIME_LEN];
+ char *cmp_result;
+ char *sub_groups_val;
+ char *sub_groups_checked;
+
+ apr_ctime(date_str, node->lastcompare);
+
+ if (node->result == LDAP_COMPARE_TRUE) {
+ cmp_result = "LDAP_COMPARE_TRUE";
+ }
+ else if (node->result == LDAP_COMPARE_FALSE) {
+ cmp_result = "LDAP_COMPARE_FALSE";
+ }
+ else {
+ cmp_result = apr_itoa(r->pool, node->result);
+ }
+
+ if (node->subgroupList) {
+ sub_groups_val = "Yes";
+ }
+ else {
+ sub_groups_val = "No";
+ }
+
+ if (node->sgl_processed) {
+ sub_groups_checked = "Yes";
+ }
+ else {
+ sub_groups_checked = "No";
+ }
+
+ ap_rprintf(r,
+ "<tr valign='top'>"
+ "<td nowrap>%s</td>"
+ "<td nowrap>%s</td>"
+ "<td nowrap>%s</td>"
+ "<td nowrap>%s</td>"
+ "<td nowrap>%s</td>"
+ "<td nowrap>%s</td>"
+ "<td nowrap>%s</td>"
+ "</tr>",
+ node->dn,
+ node->attrib,
+ node->value,
+ date_str,
+ cmp_result,
+ sub_groups_val,
+ sub_groups_checked);
+}
+
+/* ------------------------------------------------------------------ */
+
+unsigned long util_ldap_dn_compare_node_hash(void *n)
+{
+ util_dn_compare_node_t *node = n;
+ return util_ald_hash_string(1, node->reqdn);
+}
+
+int util_ldap_dn_compare_node_compare(void *a, void *b)
+{
+ util_dn_compare_node_t *na = a;
+ util_dn_compare_node_t *nb = b;
+
+ return (strcmp(na->reqdn, nb->reqdn) == 0);
+}
+
+void *util_ldap_dn_compare_node_copy(util_ald_cache_t *cache, void *c)
+{
+ util_dn_compare_node_t *n = c;
+ util_dn_compare_node_t *node = util_ald_alloc(cache, sizeof *node);
+
+ if (node) {
+ if (!(node->reqdn = util_ald_strdup(cache, n->reqdn)) ||
+ !(node->dn = util_ald_strdup(cache, n->dn))) {
+ util_ldap_dn_compare_node_free(cache, node);
+ return NULL;
+ }
+ return node;
+ }
+ else {
+ return NULL;
+ }
+}
+
+void util_ldap_dn_compare_node_free(util_ald_cache_t *cache, void *n)
+{
+ util_dn_compare_node_t *node = n;
+ util_ald_free(cache, node->reqdn);
+ util_ald_free(cache, node->dn);
+ util_ald_free(cache, node);
+}
+
+void util_ldap_dn_compare_node_display(request_rec *r, util_ald_cache_t *cache, void *n)
+{
+ util_dn_compare_node_t *node = n;
+
+ ap_rprintf(r,
+ "<tr valign='top'>"
+ "<td nowrap>%s</td>"
+ "<td nowrap>%s</td>"
+ "</tr>",
+ node->reqdn,
+ node->dn);
+}
+
+
+/* ------------------------------------------------------------------ */
+static apr_status_t util_ldap_cache_module_kill(void *data)
+{
+ util_ldap_state_t *st = data;
+
+ util_ald_destroy_cache(st->util_ldap_cache);
+#if APR_HAS_SHARED_MEMORY
+ if (st->cache_rmm != NULL) {
+ apr_rmm_destroy (st->cache_rmm);
+ st->cache_rmm = NULL;
+ }
+ if (st->cache_shm != NULL) {
+ apr_status_t result = apr_shm_destroy(st->cache_shm);
+ st->cache_shm = NULL;
+ return result;
+ }
+#endif
+ return APR_SUCCESS;
+}
+
+apr_status_t util_ldap_cache_init(apr_pool_t *pool, util_ldap_state_t *st)
+{
+#if APR_HAS_SHARED_MEMORY
+ apr_status_t result;
+ apr_size_t size;
+
+ if (st->cache_bytes > 0) {
+ if (st->cache_file) {
+ /* Remove any existing shm segment with this name. */
+ apr_shm_remove(st->cache_file, st->pool);
+ }
+
+ size = APR_ALIGN_DEFAULT(st->cache_bytes);
+
+ result = apr_shm_create(&st->cache_shm, size, st->cache_file, st->pool);
+ if (result != APR_SUCCESS) {
+ return result;
+ }
+
+ /* Determine the usable size of the shm segment. */
+ size = apr_shm_size_get(st->cache_shm);
+
+ /* This will create a rmm "handler" to get into the shared memory area */
+ result = apr_rmm_init(&st->cache_rmm, NULL,
+ apr_shm_baseaddr_get(st->cache_shm), size,
+ st->pool);
+ if (result != APR_SUCCESS) {
+ return result;
+ }
+ }
+
+#endif
+
+ apr_pool_cleanup_register(st->pool, st , util_ldap_cache_module_kill, apr_pool_cleanup_null);
+
+ st->util_ldap_cache =
+ util_ald_create_cache(st,
+ st->search_cache_size,
+ st->search_cache_ttl,
+ util_ldap_url_node_hash,
+ util_ldap_url_node_compare,
+ util_ldap_url_node_copy,
+ util_ldap_url_node_free,
+ util_ldap_url_node_display);
+ return APR_SUCCESS;
+}
+
+
+#endif /* APR_HAS_LDAP */
diff --git a/modules/ldap/util_ldap_cache.h b/modules/ldap/util_ldap_cache.h
new file mode 100644
index 0000000..3a98454
--- /dev/null
+++ b/modules/ldap/util_ldap_cache.h
@@ -0,0 +1,206 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef APU_LDAP_CACHE_H
+#define APU_LDAP_CACHE_H
+
+/**
+ * @file util_ldap_cache.h
+ * @brief This switches LDAP support on or off.
+ */
+
+/* this whole thing disappears if LDAP is not enabled */
+#if APR_HAS_LDAP
+
+
+/*
+ * LDAP Cache Manager
+ */
+
+#include "util_ldap.h"
+
+typedef struct util_cache_node_t {
+ void *payload; /* Pointer to the payload */
+ apr_time_t add_time; /* Time node was added to cache */
+ struct util_cache_node_t *next;
+} util_cache_node_t;
+
+typedef struct util_ald_cache util_ald_cache_t;
+
+struct util_ald_cache {
+ unsigned long size; /* Size of cache array */
+ unsigned long maxentries; /* Maximum number of cache entries */
+ unsigned long numentries; /* Current number of cache entries */
+ unsigned long fullmark; /* Used to keep track of when cache becomes 3/4 full */
+ apr_time_t marktime; /* Time that the cache became 3/4 full */
+ unsigned long ttl; /* Time to live for items in cache */
+ unsigned long (*hash)(void *); /* Func to hash the payload */
+ int (*compare)(void *, void *); /* Func to compare two payloads */
+ void * (*copy)(util_ald_cache_t *cache, void *); /* Func to alloc mem and copy payload to new mem */
+ void (*free)(util_ald_cache_t *cache, void *); /* Func to free mem used by the payload */
+ void (*display)(request_rec *r, util_ald_cache_t *cache, void *); /* Func to display the payload contents */
+ util_cache_node_t **nodes;
+
+ unsigned long numpurges; /* No. of times the cache has been purged */
+ double avg_purgetime; /* Average time to purge the cache */
+ apr_time_t last_purge; /* Time of the last purge */
+ unsigned long npurged; /* Number of elements purged in last purge. This is not
+ obvious: it won't be 3/4 the size of the cache if
+ there were a lot of expired entries. */
+
+ unsigned long fetches; /* Number of fetches */
+ unsigned long hits; /* Number of cache hits */
+ unsigned long inserts; /* Number of inserts */
+ unsigned long removes; /* Number of removes */
+
+#if APR_HAS_SHARED_MEMORY
+ apr_shm_t *shm_addr;
+ apr_rmm_t *rmm_addr;
+#endif
+
+};
+
+#ifndef WIN32
+#define ALD_MM_FILE_MODE ( S_IRUSR|S_IWUSR )
+#else
+#define ALD_MM_FILE_MODE ( _S_IREAD|_S_IWRITE )
+#endif
+
+
+/*
+ * LDAP Cache
+ */
+
+/*
+ * Maintain a cache of LDAP URLs that the server handles. Each node in
+ * the cache contains the search cache for that URL, and a compare cache
+ * for the URL. The compare cash is populated when doing require group
+ * compares.
+ */
+typedef struct util_url_node_t {
+ const char *url;
+ util_ald_cache_t *search_cache;
+ util_ald_cache_t *compare_cache;
+ util_ald_cache_t *dn_compare_cache;
+} util_url_node_t;
+
+/*
+ * When a group is found, subgroups are stored in the group's cache entry.
+ */
+typedef struct util_compare_subgroup_t {
+ const char **subgroupDNs;
+ int len;
+} util_compare_subgroup_t;
+
+/*
+ * We cache every successful search and bind operation, using the username
+ * as the key. Each node in the cache contains the returned DN, plus the
+ * password used to bind.
+ */
+typedef struct util_search_node_t {
+ const char *username; /* Cache key */
+ const char *dn; /* DN returned from search */
+ const char *bindpw; /* The most recently used bind password;
+ NULL if the bind failed */
+ apr_time_t lastbind; /* Time of last successful bind */
+ const char **vals; /* Values of queried attributes */
+ int numvals; /* Number of queried attributes */
+} util_search_node_t;
+
+/*
+ * We cache every successful compare operation, using the DN, attrib, and
+ * value as the key.
+ */
+typedef struct util_compare_node_t {
+ const char *dn; /* DN, attrib and value combine to be the key */
+ const char *attrib;
+ const char *value;
+ apr_time_t lastcompare;
+ int result;
+ int sgl_processed; /* 0 if no sgl processing yet. 1 if sgl has been processed (even if SGL is NULL). Saves repeat work on leaves. */
+ struct util_compare_subgroup_t *subgroupList;
+} util_compare_node_t;
+
+/*
+ * We cache every successful compare dn operation, using the dn in the require
+ * statement and the dn fetched based on the client-provided username.
+ */
+typedef struct util_dn_compare_node_t {
+ const char *reqdn; /* The DN in the require dn statement */
+ const char *dn; /* The DN found in the search */
+} util_dn_compare_node_t;
+
+
+/*
+ * Function prototypes for LDAP cache
+ */
+
+/* util_ldap_cache.c */
+unsigned long util_ldap_url_node_hash(void *n);
+int util_ldap_url_node_compare(void *a, void *b);
+void *util_ldap_url_node_copy(util_ald_cache_t *cache, void *c);
+void util_ldap_url_node_free(util_ald_cache_t *cache, void *n);
+void util_ldap_url_node_display(request_rec *r, util_ald_cache_t *cache, void *n);
+
+unsigned long util_ldap_search_node_hash(void *n);
+int util_ldap_search_node_compare(void *a, void *b);
+void *util_ldap_search_node_copy(util_ald_cache_t *cache, void *c);
+void util_ldap_search_node_free(util_ald_cache_t *cache, void *n);
+void util_ldap_search_node_display(request_rec *r, util_ald_cache_t *cache, void *n);
+
+unsigned long util_ldap_compare_node_hash(void *n);
+int util_ldap_compare_node_compare(void *a, void *b);
+void *util_ldap_compare_node_copy(util_ald_cache_t *cache, void *c);
+void util_ldap_compare_node_free(util_ald_cache_t *cache, void *n);
+void util_ldap_compare_node_display(request_rec *r, util_ald_cache_t *cache, void *n);
+
+unsigned long util_ldap_dn_compare_node_hash(void *n);
+int util_ldap_dn_compare_node_compare(void *a, void *b);
+void *util_ldap_dn_compare_node_copy(util_ald_cache_t *cache, void *c);
+void util_ldap_dn_compare_node_free(util_ald_cache_t *cache, void *n);
+void util_ldap_dn_compare_node_display(request_rec *r, util_ald_cache_t *cache, void *n);
+
+
+/* util_ldap_cache_mgr.c */
+
+/* Cache alloc and free function, dealing or not with shm */
+void util_ald_free(util_ald_cache_t *cache, const void *ptr);
+void *util_ald_alloc(util_ald_cache_t *cache, unsigned long size);
+const char *util_ald_strdup(util_ald_cache_t *cache, const char *s);
+util_compare_subgroup_t *util_ald_sgl_dup(util_ald_cache_t *cache, util_compare_subgroup_t *sgl);
+void util_ald_sgl_free(util_ald_cache_t *cache, util_compare_subgroup_t **sgl);
+
+/* Cache managing function */
+unsigned long util_ald_hash_string(int nstr, ...);
+void util_ald_cache_purge(util_ald_cache_t *cache);
+util_url_node_t *util_ald_create_caches(util_ldap_state_t *s, const char *url);
+util_ald_cache_t *util_ald_create_cache(util_ldap_state_t *st,
+ long cache_size,
+ long cache_ttl,
+ unsigned long (*hashfunc)(void *),
+ int (*comparefunc)(void *, void *),
+ void * (*copyfunc)(util_ald_cache_t *cache, void *),
+ void (*freefunc)(util_ald_cache_t *cache, void *),
+ void (*displayfunc)(request_rec *r, util_ald_cache_t *cache, void *));
+
+void util_ald_destroy_cache(util_ald_cache_t *cache);
+void *util_ald_cache_fetch(util_ald_cache_t *cache, void *payload);
+void *util_ald_cache_insert(util_ald_cache_t *cache, void *payload);
+void util_ald_cache_remove(util_ald_cache_t *cache, void *payload);
+char *util_ald_cache_display_stats(request_rec *r, util_ald_cache_t *cache, char *name, char *id);
+
+#endif /* APR_HAS_LDAP */
+#endif /* APU_LDAP_CACHE_H */
diff --git a/modules/ldap/util_ldap_cache_mgr.c b/modules/ldap/util_ldap_cache_mgr.c
new file mode 100644
index 0000000..aa822bc
--- /dev/null
+++ b/modules/ldap/util_ldap_cache_mgr.c
@@ -0,0 +1,905 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * util_ldap_cache_mgr.c: LDAP cache manager things
+ *
+ * Original code from auth_ldap module for Apache v1.3:
+ * Copyright 1998, 1999 Enbridge Pipelines Inc.
+ * Copyright 1999-2001 Dave Carrigan
+ */
+
+#include "httpd.h"
+#include "util_ldap.h"
+#include "util_ldap_cache.h"
+#include <apr_strings.h>
+
+APLOG_USE_MODULE(ldap);
+
+#if APR_HAS_LDAP
+
+/* only here until strdup is gone */
+#include <string.h>
+
+/* here till malloc is gone */
+#include <stdlib.h>
+
+static const unsigned long primes[] =
+{
+ 11,
+ 19,
+ 37,
+ 73,
+ 109,
+ 163,
+ 251,
+ 367,
+ 557,
+ 823,
+ 1237,
+ 1861,
+ 2777,
+ 4177,
+ 6247,
+ 9371,
+ 14057,
+ 21089,
+ 31627,
+ 47431,
+ 71143,
+ 106721,
+ 160073,
+ 240101,
+ 360163,
+ 540217,
+ 810343,
+ 1215497,
+ 1823231,
+ 2734867,
+ 4102283,
+ 6153409,
+ 9230113,
+ 13845163,
+ 0
+};
+
+void util_ald_free(util_ald_cache_t *cache, const void *ptr)
+{
+#if APR_HAS_SHARED_MEMORY
+ if (cache->rmm_addr) {
+ if (ptr)
+ /* Free in shared memory */
+ apr_rmm_free(cache->rmm_addr, apr_rmm_offset_get(cache->rmm_addr, (void *)ptr));
+ }
+ else {
+ if (ptr)
+ /* Cache shm is not used */
+ free((void *)ptr);
+ }
+#else
+ if (ptr)
+ free((void *)ptr);
+#endif
+}
+
+void *util_ald_alloc(util_ald_cache_t *cache, unsigned long size)
+{
+ if (0 == size)
+ return NULL;
+#if APR_HAS_SHARED_MEMORY
+ if (cache->rmm_addr) {
+ /* allocate from shared memory */
+ apr_rmm_off_t block = apr_rmm_calloc(cache->rmm_addr, size);
+ return block ? (void *)apr_rmm_addr_get(cache->rmm_addr, block) : NULL;
+ }
+ else {
+ /* Cache shm is not used */
+ return (void *)calloc(sizeof(char), size);
+ }
+#else
+ return (void *)calloc(sizeof(char), size);
+#endif
+}
+
+const char *util_ald_strdup(util_ald_cache_t *cache, const char *s)
+{
+#if APR_HAS_SHARED_MEMORY
+ if (cache->rmm_addr) {
+ /* allocate from shared memory */
+ apr_rmm_off_t block = apr_rmm_calloc(cache->rmm_addr, strlen(s)+1);
+ char *buf = block ? (char *)apr_rmm_addr_get(cache->rmm_addr, block) : NULL;
+ if (buf) {
+ strcpy(buf, s);
+ return buf;
+ }
+ else {
+ return NULL;
+ }
+ }
+ else {
+ /* Cache shm is not used */
+ return strdup(s);
+ }
+#else
+ return strdup(s);
+#endif
+}
+
+/*
+ * Duplicate a subgroupList from one compare entry to another.
+ * Returns: ptr to a new copy of the subgroupList or NULL if allocation failed.
+ */
+util_compare_subgroup_t *util_ald_sgl_dup(util_ald_cache_t *cache, util_compare_subgroup_t *sgl_in)
+{
+ int i = 0;
+ util_compare_subgroup_t *sgl_out = NULL;
+
+ if (!sgl_in) {
+ return NULL;
+ }
+
+ sgl_out = (util_compare_subgroup_t *) util_ald_alloc(cache, sizeof(util_compare_subgroup_t));
+ if (sgl_out) {
+ sgl_out->subgroupDNs = util_ald_alloc(cache, sizeof(char *) * sgl_in->len);
+ if (sgl_out->subgroupDNs) {
+ for (i = 0; i < sgl_in->len; i++) {
+ sgl_out->subgroupDNs[i] = util_ald_strdup(cache, sgl_in->subgroupDNs[i]);
+ if (!sgl_out->subgroupDNs[i]) {
+ /* We ran out of SHM, delete the strings we allocated for the SGL */
+ for (i = (i - 1); i >= 0; i--) {
+ util_ald_free(cache, sgl_out->subgroupDNs[i]);
+ }
+ util_ald_free(cache, sgl_out->subgroupDNs);
+ util_ald_free(cache, sgl_out);
+ sgl_out = NULL;
+ break;
+ }
+ }
+ /* We were able to allocate new strings for all the subgroups */
+ if (sgl_out != NULL) {
+ sgl_out->len = sgl_in->len;
+ }
+ }
+ }
+
+ return sgl_out;
+}
+
+/*
+ * Delete an entire subgroupList.
+ */
+void util_ald_sgl_free(util_ald_cache_t *cache, util_compare_subgroup_t **sgl)
+{
+ int i = 0;
+ if (sgl == NULL || *sgl == NULL) {
+ return;
+ }
+
+ for (i = 0; i < (*sgl)->len; i++) {
+ util_ald_free(cache, (*sgl)->subgroupDNs[i]);
+ }
+ util_ald_free(cache, *sgl);
+}
+
+/*
+ * Computes the hash on a set of strings. The first argument is the number
+ * of strings to hash, the rest of the args are strings.
+ * Algorithm taken from glibc.
+ */
+unsigned long util_ald_hash_string(int nstr, ...)
+{
+ int i;
+ va_list args;
+ unsigned long h=0, g;
+ char *str, *p;
+
+ va_start(args, nstr);
+ for (i=0; i < nstr; ++i) {
+ str = va_arg(args, char *);
+ for (p = str; *p; ++p) {
+ h = ( h << 4 ) + *p;
+ if ( ( g = h & 0xf0000000 ) ) {
+ h = h ^ (g >> 24);
+ h = h ^ g;
+ }
+ }
+ }
+ va_end(args);
+
+ return h;
+}
+
+
+/*
+ Purges a cache that has gotten full. We keep track of the time that we
+ added the entry that made the cache 3/4 full, then delete all entries
+ that were added before that time. It's pretty simplistic, but time to
+ purge is only O(n), which is more important.
+*/
+void util_ald_cache_purge(util_ald_cache_t *cache)
+{
+ unsigned long i;
+ util_cache_node_t *p, *q, **pp;
+ apr_time_t now;
+
+ if (!cache)
+ return;
+
+ now = cache->last_purge = apr_time_now();
+ cache->npurged = 0;
+ cache->numpurges++;
+
+ /* If the marktime is farther back than TTL from now,
+ move the marktime forward to include additional expired entries.
+ */
+ if (now - cache->ttl > cache->marktime) {
+ cache->marktime = now - cache->ttl;
+ }
+
+ for (i=0; i < cache->size; ++i) {
+ pp = cache->nodes + i;
+ p = *pp;
+ while (p != NULL) {
+ if (p->add_time < cache->marktime) {
+ q = p->next;
+ (*cache->free)(cache, p->payload);
+ util_ald_free(cache, p);
+ cache->numentries--;
+ cache->npurged++;
+ p = *pp = q;
+ }
+ else {
+ pp = &(p->next);
+ p = *pp;
+ }
+ }
+ }
+
+ now = apr_time_now();
+ cache->avg_purgetime =
+ ((now - cache->last_purge) + (cache->avg_purgetime * (cache->numpurges-1))) /
+ cache->numpurges;
+}
+
+
+/*
+ * create caches
+ */
+util_url_node_t *util_ald_create_caches(util_ldap_state_t *st, const char *url)
+{
+ util_url_node_t curl;
+ util_ald_cache_t *search_cache;
+ util_ald_cache_t *compare_cache;
+ util_ald_cache_t *dn_compare_cache;
+
+ /* create the three caches */
+ search_cache = util_ald_create_cache(st,
+ st->search_cache_size,
+ st->search_cache_ttl,
+ util_ldap_search_node_hash,
+ util_ldap_search_node_compare,
+ util_ldap_search_node_copy,
+ util_ldap_search_node_free,
+ util_ldap_search_node_display);
+ compare_cache = util_ald_create_cache(st,
+ st->compare_cache_size,
+ st->compare_cache_ttl,
+ util_ldap_compare_node_hash,
+ util_ldap_compare_node_compare,
+ util_ldap_compare_node_copy,
+ util_ldap_compare_node_free,
+ util_ldap_compare_node_display);
+ dn_compare_cache = util_ald_create_cache(st,
+ st->compare_cache_size,
+ st->compare_cache_ttl,
+ util_ldap_dn_compare_node_hash,
+ util_ldap_dn_compare_node_compare,
+ util_ldap_dn_compare_node_copy,
+ util_ldap_dn_compare_node_free,
+ util_ldap_dn_compare_node_display);
+
+ /* check that all the caches initialised successfully */
+ if (search_cache && compare_cache && dn_compare_cache) {
+ /* The contents of this structure will be duplicated in shared
+ memory during the insert. So use stack memory rather than
+ pool memory to avoid a memory leak. */
+ memset (&curl, 0, sizeof(util_url_node_t));
+ curl.url = url;
+ curl.search_cache = search_cache;
+ curl.compare_cache = compare_cache;
+ curl.dn_compare_cache = dn_compare_cache;
+
+ return util_ald_cache_insert(st->util_ldap_cache, &curl);
+ }
+ else {
+ /* util_ald_destroy_cache is a noop for a NULL argument. */
+ util_ald_destroy_cache(search_cache);
+ util_ald_destroy_cache(compare_cache);
+ util_ald_destroy_cache(dn_compare_cache);
+
+ return NULL;
+ }
+}
+
+
+util_ald_cache_t *util_ald_create_cache(util_ldap_state_t *st,
+ long cache_size,
+ long cache_ttl,
+ unsigned long (*hashfunc)(void *),
+ int (*comparefunc)(void *, void *),
+ void * (*copyfunc)(util_ald_cache_t *cache, void *),
+ void (*freefunc)(util_ald_cache_t *cache, void *),
+ void (*displayfunc)(request_rec *r, util_ald_cache_t *cache, void *))
+{
+ util_ald_cache_t *cache;
+ unsigned long i;
+#if APR_HAS_SHARED_MEMORY
+ apr_rmm_off_t block;
+#endif
+
+ if (cache_size <= 0)
+ return NULL;
+
+#if APR_HAS_SHARED_MEMORY
+ if (!st->cache_rmm) {
+ cache = (util_ald_cache_t *)calloc(sizeof(util_ald_cache_t), 1);
+ }
+ else {
+ block = apr_rmm_calloc(st->cache_rmm, sizeof(util_ald_cache_t));
+ cache = block ? (util_ald_cache_t *)apr_rmm_addr_get(st->cache_rmm, block) : NULL;
+ }
+#else
+ cache = (util_ald_cache_t *)calloc(sizeof(util_ald_cache_t), 1);
+#endif
+ if (!cache)
+ return NULL;
+
+#if APR_HAS_SHARED_MEMORY
+ cache->rmm_addr = st->cache_rmm;
+ cache->shm_addr = st->cache_shm;
+#endif
+ cache->maxentries = cache_size;
+ cache->numentries = 0;
+ cache->size = cache_size / 3;
+ if (cache->size < 64)
+ cache->size = 64;
+ for (i = 0; primes[i] && primes[i] < cache->size; ++i)
+ ;
+ cache->size = primes[i] ? primes[i] : primes[i-1];
+
+ cache->nodes = (util_cache_node_t **)util_ald_alloc(cache, cache->size * sizeof(util_cache_node_t *));
+ if (!cache->nodes) {
+ /* This frees cache in the right way even if !APR_HAS_SHARED_MEMORY or !st->cache_rmm */
+ util_ald_free(cache, cache);
+ return NULL;
+ }
+
+ for (i=0; i < cache->size; ++i)
+ cache->nodes[i] = NULL;
+
+ cache->hash = hashfunc;
+ cache->compare = comparefunc;
+ cache->copy = copyfunc;
+ cache->free = freefunc;
+ cache->display = displayfunc;
+
+
+ cache->fullmark = cache->maxentries / 4 * 3;
+ cache->marktime = 0;
+ cache->ttl = cache_ttl;
+ cache->avg_purgetime = 0.0;
+ cache->numpurges = 0;
+ cache->last_purge = 0;
+ cache->npurged = 0;
+
+ cache->fetches = 0;
+ cache->hits = 0;
+ cache->inserts = 0;
+ cache->removes = 0;
+
+ return cache;
+}
+
+void util_ald_destroy_cache(util_ald_cache_t *cache)
+{
+ unsigned long i;
+ util_cache_node_t *p, *q;
+
+ if (cache == NULL)
+ return;
+
+ for (i = 0; i < cache->size; ++i) {
+ p = cache->nodes[i];
+ q = NULL;
+ while (p != NULL) {
+ q = p->next;
+ (*cache->free)(cache, p->payload);
+ util_ald_free(cache, p);
+ p = q;
+ }
+ }
+ util_ald_free(cache, cache->nodes);
+ util_ald_free(cache, cache);
+}
+
+void *util_ald_cache_fetch(util_ald_cache_t *cache, void *payload)
+{
+ unsigned long hashval;
+ util_cache_node_t *p;
+
+ if (cache == NULL)
+ return NULL;
+
+ cache->fetches++;
+
+ hashval = (*cache->hash)(payload) % cache->size;
+
+ for (p = cache->nodes[hashval];
+ p && !(*cache->compare)(p->payload, payload);
+ p = p->next) ;
+
+ if (p != NULL) {
+ cache->hits++;
+ return p->payload;
+ }
+ else {
+ return NULL;
+ }
+}
+
+/*
+ * Insert an item into the cache.
+ * *** Does not catch duplicates!!! ***
+ */
+void *util_ald_cache_insert(util_ald_cache_t *cache, void *payload)
+{
+ unsigned long hashval;
+ void *tmp_payload;
+ util_cache_node_t *node;
+
+ /* sanity check */
+ if (cache == NULL || payload == NULL) {
+ return NULL;
+ }
+
+ /* check if we are full - if so, try purge */
+ if (cache->numentries >= cache->maxentries) {
+ util_ald_cache_purge(cache);
+ if (cache->numentries >= cache->maxentries) {
+ /* if the purge was not effective, we leave now to avoid an overflow */
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(01323)
+ "Purge of LDAP cache failed");
+ return NULL;
+ }
+ }
+
+ node = (util_cache_node_t *)util_ald_alloc(cache,
+ sizeof(util_cache_node_t));
+ if (node == NULL) {
+ /*
+ * XXX: The cache management should be rewritten to work
+ * properly when LDAPSharedCacheSize is too small.
+ */
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, APLOGNO(01324)
+ "LDAPSharedCacheSize is too small. Increase it or "
+ "reduce LDAPCacheEntries/LDAPOpCacheEntries!");
+ if (cache->numentries < cache->fullmark) {
+ /*
+ * We have not even reached fullmark, trigger a complete purge.
+ * This is still better than not being able to add new entries
+ * at all.
+ */
+ cache->marktime = apr_time_now();
+ }
+ util_ald_cache_purge(cache);
+ node = (util_cache_node_t *)util_ald_alloc(cache,
+ sizeof(util_cache_node_t));
+ if (node == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(01325)
+ "Could not allocate memory for LDAP cache entry");
+ return NULL;
+ }
+ }
+
+ /* Take a copy of the payload before proceeding. */
+ tmp_payload = (*cache->copy)(cache, payload);
+ if (tmp_payload == NULL) {
+ /*
+ * XXX: The cache management should be rewritten to work
+ * properly when LDAPSharedCacheSize is too small.
+ */
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, APLOGNO(01326)
+ "LDAPSharedCacheSize is too small. Increase it or "
+ "reduce LDAPCacheEntries/LDAPOpCacheEntries!");
+ if (cache->numentries < cache->fullmark) {
+ /*
+ * We have not even reached fullmark, trigger a complete purge.
+ * This is still better than not being able to add new entries
+ * at all.
+ */
+ cache->marktime = apr_time_now();
+ }
+ util_ald_cache_purge(cache);
+ tmp_payload = (*cache->copy)(cache, payload);
+ if (tmp_payload == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(01327)
+ "Could not allocate memory for LDAP cache value");
+ util_ald_free(cache, node);
+ return NULL;
+ }
+ }
+ payload = tmp_payload;
+
+ /* populate the entry */
+ cache->inserts++;
+ hashval = (*cache->hash)(payload) % cache->size;
+ node->add_time = apr_time_now();
+ node->payload = payload;
+ node->next = cache->nodes[hashval];
+ cache->nodes[hashval] = node;
+
+ /* if we reach the full mark, note the time we did so
+ * for the benefit of the purge function
+ */
+ if (++cache->numentries == cache->fullmark) {
+ cache->marktime=apr_time_now();
+ }
+
+ return node->payload;
+}
+
+void util_ald_cache_remove(util_ald_cache_t *cache, void *payload)
+{
+ unsigned long hashval;
+ util_cache_node_t *p, *q;
+
+ if (cache == NULL)
+ return;
+
+ cache->removes++;
+ hashval = (*cache->hash)(payload) % cache->size;
+ for (p = cache->nodes[hashval], q=NULL;
+ p && !(*cache->compare)(p->payload, payload);
+ p = p->next) {
+ q = p;
+ }
+
+ /* If p is null, it means that we couldn't find the node, so just return */
+ if (p == NULL)
+ return;
+
+ if (q == NULL) {
+ /* We found the node, and it's the first in the list */
+ cache->nodes[hashval] = p->next;
+ }
+ else {
+ /* We found the node and it's not the first in the list */
+ q->next = p->next;
+ }
+ (*cache->free)(cache, p->payload);
+ util_ald_free(cache, p);
+ cache->numentries--;
+}
+
+char *util_ald_cache_display_stats(request_rec *r, util_ald_cache_t *cache, char *name, char *id)
+{
+ unsigned long i;
+ int totchainlen = 0;
+ int nchains = 0;
+ double chainlen;
+ util_cache_node_t *n;
+ char *buf, *buf2;
+ apr_pool_t *p = r->pool;
+
+ if (cache == NULL) {
+ return "";
+ }
+
+ for (i=0; i < cache->size; ++i) {
+ if (cache->nodes[i] != NULL) {
+ nchains++;
+ for (n = cache->nodes[i];
+ n != NULL && n != n->next;
+ n = n->next) {
+ totchainlen++;
+ }
+ }
+ }
+ chainlen = nchains? (double)totchainlen / (double)nchains : 0;
+
+ if (id) {
+ buf2 = apr_psprintf(p,
+ "<a href=\"%s?%s\">%s</a>",
+ ap_escape_html(r->pool, ap_escape_uri(r->pool, r->uri)),
+ id,
+ name);
+ }
+ else {
+ buf2 = name;
+ }
+
+ buf = apr_psprintf(p,
+ "<tr valign='top'>"
+ "<td nowrap>%s</td>"
+ "<td align='right' nowrap>%lu (%.0f%% full)</td>"
+ "<td align='right'>%.1f</td>"
+ "<td align='right'>%lu/%lu</td>"
+ "<td align='right'>%.0f%%</td>"
+ "<td align='right'>%lu/%lu</td>",
+ buf2,
+ cache->numentries,
+ (double)cache->numentries / (double)cache->maxentries * 100.0,
+ chainlen,
+ cache->hits,
+ cache->fetches,
+ (cache->fetches > 0 ? (double)(cache->hits) / (double)(cache->fetches) * 100.0 : 100.0),
+ cache->inserts,
+ cache->removes);
+
+ if (cache->numpurges) {
+ char str_ctime[APR_CTIME_LEN];
+
+ apr_ctime(str_ctime, cache->last_purge);
+ buf = apr_psprintf(p,
+ "%s"
+ "<td align='right'>%lu</td>\n"
+ "<td align='right' nowrap>%s</td>\n",
+ buf,
+ cache->numpurges,
+ str_ctime);
+ }
+ else {
+ buf = apr_psprintf(p,
+ "%s<td colspan='2' align='center'>(none)</td>\n",
+ buf);
+ }
+
+ buf = apr_psprintf(p, "%s<td align='right'>%.2gms</td>\n</tr>", buf, cache->avg_purgetime);
+
+ return buf;
+}
+
+char *util_ald_cache_display(request_rec *r, util_ldap_state_t *st)
+{
+ unsigned long i,j;
+ char *buf, *t1, *t2, *t3;
+ char *id1, *id2, *id3;
+ char *argfmt = "cache=%s&id=%d&off=%d";
+ char *scanfmt = "cache=%4s&id=%u&off=%u%1s";
+ apr_pool_t *pool = r->pool;
+ util_cache_node_t *p = NULL;
+ util_url_node_t *n = NULL;
+
+ util_ald_cache_t *util_ldap_cache = st->util_ldap_cache;
+
+
+ if (!util_ldap_cache) {
+ ap_rputs("<tr valign='top'><td nowrap colspan=7>Cache has not been enabled/initialised.</td></tr>", r);
+ return NULL;
+ }
+
+ if (r->args && strlen(r->args)) {
+ char cachetype[5], lint[2];
+ unsigned int id, off;
+ char date_str[APR_CTIME_LEN];
+
+ if ((3 == sscanf(r->args, scanfmt, cachetype, &id, &off, lint)) &&
+ (id < util_ldap_cache->size)) {
+
+ if ((p = util_ldap_cache->nodes[id]) != NULL) {
+ n = (util_url_node_t *)p->payload;
+ buf = (char*)n->url;
+ }
+ else {
+ buf = "";
+ }
+
+ ap_rprintf(r,
+ "<p>\n"
+ "<table border='0'>\n"
+ "<tr>\n"
+ "<td bgcolor='#000000'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Cache Name:</b></font></td>"
+ "<td bgcolor='#ffffff'><font size='-1' face='Arial,Helvetica' color='#000000'><b>%s (%s)</b></font></td>"
+ "</tr>\n"
+ "</table>\n</p>\n",
+ buf,
+ cachetype[0] == 'm'? "Main" :
+ (cachetype[0] == 's' ? "Search" :
+ (cachetype[0] == 'c' ? "Compares" : "DNCompares")));
+
+ switch (cachetype[0]) {
+ case 'm':
+ if (util_ldap_cache->marktime) {
+ apr_ctime(date_str, util_ldap_cache->marktime);
+ }
+ else
+ date_str[0] = 0;
+
+ ap_rprintf(r,
+ "<p>\n"
+ "<table border='0'>\n"
+ "<tr>\n"
+ "<td bgcolor='#000000'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Size:</b></font></td>"
+ "<td bgcolor='#ffffff'><font size='-1' face='Arial,Helvetica' color='#000000'><b>%ld</b></font></td>"
+ "</tr>\n"
+ "<tr>\n"
+ "<td bgcolor='#000000'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Max Entries:</b></font></td>"
+ "<td bgcolor='#ffffff'><font size='-1' face='Arial,Helvetica' color='#000000'><b>%ld</b></font></td>"
+ "</tr>\n"
+ "<tr>\n"
+ "<td bgcolor='#000000'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b># Entries:</b></font></td>"
+ "<td bgcolor='#ffffff'><font size='-1' face='Arial,Helvetica' color='#000000'><b>%ld</b></font></td>"
+ "</tr>\n"
+ "<tr>\n"
+ "<td bgcolor='#000000'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>TTL (sec):</b></font></td>"
+ "<td bgcolor='#ffffff'><font size='-1' face='Arial,Helvetica' color='#000000'><b>%" APR_TIME_T_FMT "</b></font></td>"
+ "</tr>\n"
+ "<tr>\n"
+ "<td bgcolor='#000000'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Full Mark:</b></font></td>"
+ "<td bgcolor='#ffffff'><font size='-1' face='Arial,Helvetica' color='#000000'><b>%ld</b></font></td>"
+ "</tr>\n"
+ "<tr>\n"
+ "<td bgcolor='#000000'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Full Mark Time:</b></font></td>"
+ "<td bgcolor='#ffffff'><font size='-1' face='Arial,Helvetica' color='#000000'><b>%s</b></font></td>"
+ "</tr>\n"
+ "</table>\n</p>\n",
+ util_ldap_cache->size,
+ util_ldap_cache->maxentries,
+ util_ldap_cache->numentries,
+ apr_time_sec(util_ldap_cache->ttl),
+ util_ldap_cache->fullmark,
+ date_str);
+
+ ap_rputs("<p>\n"
+ "<table border='0'>\n"
+ "<tr bgcolor='#000000'>\n"
+ "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>LDAP URL</b></font></td>"
+ "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Size</b></font></td>"
+ "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Max Entries</b></font></td>"
+ "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b># Entries</b></font></td>"
+ "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>TTL (sec)</b></font></td>"
+ "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Full Mark</b></font></td>"
+ "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Full Mark Time</b></font></td>"
+ "</tr>\n", r
+ );
+ for (i=0; i < util_ldap_cache->size; ++i) {
+ for (p = util_ldap_cache->nodes[i]; p != NULL; p = p->next) {
+
+ (*util_ldap_cache->display)(r, util_ldap_cache, p->payload);
+ }
+ }
+ ap_rputs("</table>\n</p>\n", r);
+
+
+ break;
+ case 's':
+ ap_rputs("<p>\n"
+ "<table border='0'>\n"
+ "<tr bgcolor='#000000'>\n"
+ "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>LDAP Filter</b></font></td>"
+ "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>User Name</b></font></td>"
+ "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Last Bind</b></font></td>"
+ "</tr>\n", r
+ );
+ if (n) {
+ for (i=0; i < n->search_cache->size; ++i) {
+ for (p = n->search_cache->nodes[i]; p != NULL; p = p->next) {
+
+ (*n->search_cache->display)(r, n->search_cache, p->payload);
+ }
+ }
+ }
+ ap_rputs("</table>\n</p>\n", r);
+ break;
+ case 'c':
+ ap_rputs("<p>\n"
+ "<table border='0'>\n"
+ "<tr bgcolor='#000000'>\n"
+ "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>DN</b></font></td>"
+ "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Attribute</b></font></td>"
+ "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Value</b></font></td>"
+ "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Last Compare</b></font></td>"
+ "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Result</b></font></td>"
+ "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Sub-groups?</b></font></td>"
+ "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>S-G Checked?</b></font></td>"
+ "</tr>\n", r
+ );
+ if (n) {
+ for (i=0; i < n->compare_cache->size; ++i) {
+ for (p = n->compare_cache->nodes[i]; p != NULL; p = p->next) {
+
+ (*n->compare_cache->display)(r, n->compare_cache, p->payload);
+ }
+ }
+ }
+ ap_rputs("</table>\n</p>\n", r);
+ break;
+ case 'd':
+ ap_rputs("<p>\n"
+ "<table border='0'>\n"
+ "<tr bgcolor='#000000'>\n"
+ "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Require DN</b></font></td>"
+ "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Actual DN</b></font></td>"
+ "</tr>\n", r
+ );
+ if (n) {
+ for (i=0; i < n->dn_compare_cache->size; ++i) {
+ for (p = n->dn_compare_cache->nodes[i]; p != NULL; p = p->next) {
+
+ (*n->dn_compare_cache->display)(r, n->dn_compare_cache, p->payload);
+ }
+ }
+ }
+ ap_rputs("</table>\n</p>\n", r);
+ break;
+ default:
+ break;
+ }
+
+ }
+ else {
+ buf = "";
+ }
+ }
+ else {
+ ap_rputs("<p>\n"
+ "<table border='0'>\n"
+ "<tr bgcolor='#000000'>\n"
+ "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Cache Name</b></font></td>"
+ "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Entries</b></font></td>"
+ "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Avg. Chain Len.</b></font></td>"
+ "<td colspan='2'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Hits</b></font></td>"
+ "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Ins/Rem</b></font></td>"
+ "<td colspan='2'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Purges</b></font></td>"
+ "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Avg Purge Time</b></font></td>"
+ "</tr>\n", r
+ );
+
+
+ id1 = apr_psprintf(pool, argfmt, "main", 0, 0);
+ buf = util_ald_cache_display_stats(r, st->util_ldap_cache, "LDAP URL Cache", id1);
+
+ for (i=0; i < util_ldap_cache->size; ++i) {
+ for (p = util_ldap_cache->nodes[i],j=0; p != NULL; p = p->next,j++) {
+
+ n = (util_url_node_t *)p->payload;
+
+ t1 = apr_psprintf(pool, "%s (Searches)", n->url);
+ t2 = apr_psprintf(pool, "%s (Compares)", n->url);
+ t3 = apr_psprintf(pool, "%s (DNCompares)", n->url);
+ id1 = apr_psprintf(pool, argfmt, "srch", i, j);
+ id2 = apr_psprintf(pool, argfmt, "cmpr", i, j);
+ id3 = apr_psprintf(pool, argfmt, "dncp", i, j);
+
+ buf = apr_psprintf(pool, "%s\n\n"
+ "%s\n\n"
+ "%s\n\n"
+ "%s\n\n",
+ buf,
+ util_ald_cache_display_stats(r, n->search_cache, t1, id1),
+ util_ald_cache_display_stats(r, n->compare_cache, t2, id2),
+ util_ald_cache_display_stats(r, n->dn_compare_cache, t3, id3)
+ );
+ }
+ }
+ ap_rputs(buf, r);
+ ap_rputs("</table>\n</p>\n", r);
+ }
+
+ return buf;
+}
+
+#endif /* APR_HAS_LDAP */
diff --git a/modules/loggers/.indent.pro b/modules/loggers/.indent.pro
new file mode 100644
index 0000000..a9fbe9f
--- /dev/null
+++ b/modules/loggers/.indent.pro
@@ -0,0 +1,54 @@
+-i4 -npsl -di0 -br -nce -d0 -cli0 -npcs -nfc1
+-TBUFF
+-TFILE
+-TTRANS
+-TUINT4
+-T_trans
+-Tallow_options_t
+-Tapache_sfio
+-Tarray_header
+-Tbool_int
+-Tbuf_area
+-Tbuff_struct
+-Tbuffy
+-Tcmd_how
+-Tcmd_parms
+-Tcommand_rec
+-Tcommand_struct
+-Tconn_rec
+-Tcore_dir_config
+-Tcore_server_config
+-Tdir_maker_func
+-Tevent
+-Tglobals_s
+-Thandler_func
+-Thandler_rec
+-Tjoblist_s
+-Tlisten_rec
+-Tmerger_func
+-Tmode_t
+-Tmodule
+-Tmodule_struct
+-Tmutex
+-Tn_long
+-Tother_child_rec
+-Toverrides_t
+-Tparent_score
+-Tpid_t
+-Tpiped_log
+-Tpool
+-Trequest_rec
+-Trequire_line
+-Trlim_t
+-Tscoreboard
+-Tsemaphore
+-Tserver_addr_rec
+-Tserver_rec
+-Tserver_rec_chain
+-Tshort_score
+-Ttable
+-Ttable_entry
+-Tthread
+-Tu_wide_int
+-Tvtime_t
+-Twide_int
diff --git a/modules/loggers/Makefile.in b/modules/loggers/Makefile.in
new file mode 100644
index 0000000..167b343
--- /dev/null
+++ b/modules/loggers/Makefile.in
@@ -0,0 +1,3 @@
+
+include $(top_srcdir)/build/special.mk
+
diff --git a/modules/loggers/NWGNUforensic b/modules/loggers/NWGNUforensic
new file mode 100644
index 0000000..c55f036
--- /dev/null
+++ b/modules/loggers/NWGNUforensic
@@ -0,0 +1,258 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/modules/http \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = forensic
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Forensic Logging Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Forensic Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/forensic.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_log_forensic.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ log_forensic_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/loggers/NWGNUlogdebug b/modules/loggers/NWGNUlogdebug
new file mode 100644
index 0000000..e92839a
--- /dev/null
+++ b/modules/loggers/NWGNUlogdebug
@@ -0,0 +1,258 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/modules/http \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = logdebug
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Debug Logging Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Logdbg Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_log_debug.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ log_debug_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/loggers/NWGNUmakefile b/modules/loggers/NWGNUmakefile
new file mode 100644
index 0000000..6154bf1
--- /dev/null
+++ b/modules/loggers/NWGNUmakefile
@@ -0,0 +1,247 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME =
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION =
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME =
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE =
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/modlogio.nlm \
+ $(OBJDIR)/forensic.nlm \
+ $(OBJDIR)/logdebug.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/loggers/NWGNUmodlogio b/modules/loggers/NWGNUmodlogio
new file mode 100644
index 0000000..fa376db
--- /dev/null
+++ b/modules/loggers/NWGNUmodlogio
@@ -0,0 +1,258 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/modules/http \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = logio
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) IO Logging Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Logio Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/modlogio.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_logio.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ logio_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/loggers/config.m4 b/modules/loggers/config.m4
new file mode 100644
index 0000000..762e773
--- /dev/null
+++ b/modules/loggers/config.m4
@@ -0,0 +1,20 @@
+dnl modules enabled in this directory by default
+
+dnl APACHE_MODULE(name, helptext[, objects[, structname[, default[, config]]]])
+
+APACHE_MODPATH_INIT(loggers)
+
+APACHE_MODULE(log_config, logging configuration. You won't be able to log requests to the server without this module., , , yes)
+APACHE_MODULE(log_debug, configurable debug logging, , , most)
+APACHE_MODULE(log_forensic, forensic logging)
+
+if test "x$enable_log_forensic" != "xno"; then
+ # mod_log_forensic needs test_char.h
+ APR_ADDTO(INCLUDES, [-I\$(top_builddir)/server])
+fi
+
+APACHE_MODULE(logio, input and output logging, , , most)
+
+APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/$modpath_current])
+
+APACHE_MODPATH_FINISH
diff --git a/modules/loggers/mod_log_config.c b/modules/loggers/mod_log_config.c
new file mode 100644
index 0000000..5d5b73a
--- /dev/null
+++ b/modules/loggers/mod_log_config.c
@@ -0,0 +1,1857 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Modified by djm@va.pubnix.com:
+ * If no TransferLog is given explicitly, decline to log.
+ *
+ * This is module implements the TransferLog directive (same as the
+ * common log module), and additional directives, LogFormat and CustomLog.
+ *
+ *
+ * Syntax:
+ *
+ * TransferLog fn Logs transfers to fn in standard log format, unless
+ * a custom format is set with LogFormat
+ * LogFormat format Set a log format from TransferLog files
+ * CustomLog fn format
+ * Log to file fn with format given by the format
+ * argument
+ *
+ * There can be any number of TransferLog and CustomLog
+ * commands. Each request will be logged to _ALL_ the
+ * named files, in the appropriate format.
+ *
+ * If no TransferLog or CustomLog directive appears in a VirtualHost,
+ * the request will be logged to the log file(s) defined outside
+ * the virtual host section. If a TransferLog or CustomLog directive
+ * appears in the VirtualHost section, the log files defined outside
+ * the VirtualHost will _not_ be used. This makes this module compatible
+ * with the CLF and config log modules, where the use of TransferLog
+ * inside the VirtualHost section overrides its use outside.
+ *
+ * Examples:
+ *
+ * TransferLog logs/access_log
+ * <VirtualHost>
+ * LogFormat "... custom format ..."
+ * TransferLog log/virtual_only
+ * CustomLog log/virtual_useragents "%t %{user-agent}i"
+ * </VirtualHost>
+ *
+ * This will log using CLF to access_log any requests handled by the
+ * main server, while any requests to the virtual host will be logged
+ * with the "... custom format..." to virtual_only _AND_ using
+ * the custom user-agent log to virtual_useragents.
+ *
+ * Note that the NCSA referer and user-agent logs are easily added with
+ * CustomLog:
+ * CustomLog logs/referer "%{referer}i -> %U"
+ * CustomLog logs/agent "%{user-agent}i"
+ *
+ * RefererIgnore functionality can be obtained with conditional
+ * logging (SetEnvIf and CustomLog ... env=!VAR).
+ *
+ * But using this method allows much easier modification of the
+ * log format, e.g. to log hosts along with UA:
+ * CustomLog logs/referer "%{referer}i %U %h"
+ *
+ * The argument to LogFormat and CustomLog is a string, which can include
+ * literal characters copied into the log files, and '%' directives as
+ * follows:
+ *
+ * %...B: bytes sent, excluding HTTP headers.
+ * %...b: bytes sent, excluding HTTP headers in CLF format, i.e. a '-'
+ * when no bytes where sent (rather than a '0'.
+ * %...{FOOBAR}C: The contents of the HTTP cookie FOOBAR
+ * %...{FOOBAR}e: The contents of the environment variable FOOBAR
+ * %...f: filename
+ * %...h: remote host
+ * %...a: remote IP-address
+ * %...A: local IP-address
+ * %...{Foobar}i: The contents of Foobar: header line(s) in the request
+ * sent to the client.
+ * %...k: number of keepalive requests served over this connection
+ * %...l: remote logname (from identd, if supplied)
+ * %...{Foobar}n: The contents of note "Foobar" from another module.
+ * %...{Foobar}o: The contents of Foobar: header line(s) in the reply.
+ * %...p: the canonical port for the server
+ * %...{format}p: the canonical port for the server, or the actual local
+ * or remote port
+ * %...P: the process ID of the child that serviced the request.
+ * %...{format}P: the process ID or thread ID of the child/thread that
+ * serviced the request
+ * %...r: first line of request
+ * %...s: status. For requests that got internally redirected, this
+ * is status of the *original* request --- %...>s for the last.
+ * %...t: time, in common log format time format
+ * %...{format}t: The time, in the form given by format, which should
+ * be in strftime(3) format.
+ * %...T: the time taken to serve the request, in seconds.
+ * %...{s}T: the time taken to serve the request, in seconds, same as %T.
+ * %...{us}T: the time taken to serve the request, in micro seconds, same as %D.
+ * %...{ms}T: the time taken to serve the request, in milliseconds.
+ * %...D: the time taken to serve the request, in micro seconds.
+ * %...u: remote user (from auth; may be bogus if return status (%s) is 401)
+ * %...U: the URL path requested.
+ * %...v: the configured name of the server (i.e. which virtual host?)
+ * %...V: the server name according to the UseCanonicalName setting
+ * %...m: the request method
+ * %...H: the request protocol
+ * %...q: the query string prepended by "?", or empty if no query string
+ * %...X: Status of the connection.
+ * 'X' = connection aborted before the response completed.
+ * '+' = connection may be kept alive after the response is sent.
+ * '-' = connection will be closed after the response is sent.
+ * (This directive was %...c in late versions of Apache 1.3, but
+ * this conflicted with the historical ssl %...{var}c syntax.)
+ * %...L: Log-Id of the Request (or '-' if none)
+ * %...{c}L: Log-Id of the Connection (or '-' if none)
+ *
+ * The '...' can be nothing at all (e.g. "%h %u %r %s %b"), or it can
+ * indicate conditions for inclusion of the item (which will cause it
+ * to be replaced with '-' if the condition is not met). Note that
+ * there is no escaping performed on the strings from %r, %...i and
+ * %...o; some with long memories may remember that I thought this was
+ * a bad idea, once upon a time, and I'm still not comfortable with
+ * it, but it is difficult to see how to "do the right thing" with all
+ * of '%..i', unless we URL-escape everything and break with CLF.
+ *
+ * The forms of condition are a list of HTTP status codes, which may
+ * or may not be preceded by '!'. Thus, '%400,501{User-agent}i' logs
+ * User-agent: on 400 errors and 501 errors (Bad Request, Not
+ * Implemented) only; '%!200,304,302{Referer}i' logs Referer: on all
+ * requests which did *not* return some sort of normal status.
+ *
+ * The default LogFormat reproduces CLF; see below.
+ *
+ * The way this is supposed to work with virtual hosts is as follows:
+ * a virtual host can have its own LogFormat, or its own TransferLog.
+ * If it doesn't have its own LogFormat, it inherits from the main
+ * server. If it doesn't have its own TransferLog, it writes to the
+ * same descriptor (meaning the same process for "| ...").
+ *
+ * --- rst */
+
+#include "apr_strings.h"
+#include "apr_lib.h"
+#include "apr_hash.h"
+#include "apr_optional.h"
+#include "apr_anylock.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "ap_config.h"
+#include "mod_log_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h" /* For REMOTE_NAME */
+#include "http_log.h"
+#include "http_protocol.h"
+#include "util_time.h"
+#include "ap_mpm.h"
+
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+
+#define DEFAULT_LOG_FORMAT "%h %l %u %t \"%r\" %>s %b"
+
+module AP_MODULE_DECLARE_DATA log_config_module;
+
+
+static int xfer_flags = (APR_WRITE | APR_APPEND | APR_CREATE | APR_LARGEFILE);
+static apr_fileperms_t xfer_perms = APR_OS_DEFAULT;
+static apr_hash_t *log_hash;
+static apr_status_t ap_default_log_writer(request_rec *r,
+ void *handle,
+ const char **strs,
+ int *strl,
+ int nelts,
+ apr_size_t len);
+static apr_status_t ap_buffered_log_writer(request_rec *r,
+ void *handle,
+ const char **strs,
+ int *strl,
+ int nelts,
+ apr_size_t len);
+static void *ap_default_log_writer_init(apr_pool_t *p, server_rec *s,
+ const char* name);
+static void *ap_buffered_log_writer_init(apr_pool_t *p, server_rec *s,
+ const char* name);
+
+static ap_log_writer_init *ap_log_set_writer_init(ap_log_writer_init *handle);
+static ap_log_writer *ap_log_set_writer(ap_log_writer *handle);
+static ap_log_writer *log_writer = ap_default_log_writer;
+static ap_log_writer_init *log_writer_init = ap_default_log_writer_init;
+static int buffered_logs = 0; /* default unbuffered */
+static apr_array_header_t *all_buffered_logs = NULL;
+
+/* POSIX.1 defines PIPE_BUF as the maximum number of bytes that is
+ * guaranteed to be atomic when writing a pipe. And PIPE_BUF >= 512
+ * is guaranteed. So we'll just guess 512 in the event the system
+ * doesn't have this. Now, for file writes there is actually no limit,
+ * the entire write is atomic. Whether all systems implement this
+ * correctly is another question entirely ... so we'll just use PIPE_BUF
+ * because it's probably a good guess as to what is implemented correctly
+ * everywhere.
+ */
+#ifdef PIPE_BUF
+#define LOG_BUFSIZE PIPE_BUF
+#else
+#define LOG_BUFSIZE (512)
+#endif
+
+/*
+ * multi_log_state is our per-(virtual)-server configuration. We store
+ * an array of the logs we are going to use, each of type config_log_state.
+ * If a default log format is given by LogFormat, store in default_format
+ * (backward compat. with mod_log_config). We also store for each virtual
+ * server a pointer to the logs specified for the main server, so that if this
+ * vhost has no logs defined, we can use the main server's logs instead.
+ *
+ * So, for the main server, config_logs contains a list of the log files
+ * and server_config_logs is empty. For a vhost, server_config_logs
+ * points to the same array as config_logs in the main server, and
+ * config_logs points to the array of logs defined inside this vhost,
+ * which might be empty.
+ */
+
+typedef struct {
+ const char *default_format_string;
+ apr_array_header_t *default_format;
+ apr_array_header_t *config_logs;
+ apr_array_header_t *server_config_logs;
+ apr_table_t *formats;
+} multi_log_state;
+
+/*
+ * config_log_state holds the status of a single log file. fname might
+ * be NULL, which means this module does no logging for this
+ * request. format might be NULL, in which case the default_format
+ * from the multi_log_state should be used, or if that is NULL as
+ * well, use the CLF.
+ * log_writer is NULL before the log file is opened and is
+ * set to a opaque structure (usually a fd) after it is opened.
+
+ */
+typedef struct {
+ apr_file_t *handle;
+ apr_size_t outcnt;
+ char outbuf[LOG_BUFSIZE];
+ apr_anylock_t mutex;
+} buffered_log;
+
+typedef struct {
+ const char *fname;
+ const char *format_string;
+ apr_array_header_t *format;
+ void *log_writer;
+ char *condition_var;
+ int inherit;
+ ap_expr_info_t *condition_expr;
+ /** place of definition or NULL if already checked */
+ const ap_directive_t *directive;
+} config_log_state;
+
+/*
+ * log_request_state holds request specific log data that is not
+ * part of the request_rec.
+ */
+typedef struct {
+ apr_time_t request_end_time;
+} log_request_state;
+
+/*
+ * Format items...
+ * Note that many of these could have ap_sprintfs replaced with static buffers.
+ */
+
+typedef struct {
+ ap_log_handler_fn_t *func;
+ char *arg;
+ int condition_sense;
+ int want_orig;
+ apr_array_header_t *conditions;
+} log_format_item;
+
+static char *pfmt(apr_pool_t *p, int i)
+{
+ if (i <= 0) {
+ return "-";
+ }
+ else {
+ return apr_itoa(p, i);
+ }
+}
+
+static const char *constant_item(request_rec *dummy, char *stuff)
+{
+ return stuff;
+}
+
+static const char *log_remote_host(request_rec *r, char *a)
+{
+ const char *remote_host;
+ if (a && !strcmp(a, "c")) {
+ remote_host = ap_get_remote_host(r->connection, r->per_dir_config,
+ REMOTE_NAME, NULL);
+ }
+ else {
+ remote_host = ap_get_useragent_host(r, REMOTE_NAME, NULL);
+ }
+ return ap_escape_logitem(r->pool, remote_host);
+}
+
+static const char *log_remote_address(request_rec *r, char *a)
+{
+ if (a && !strcmp(a, "c")) {
+ return r->connection->client_ip;
+ }
+ else {
+ return r->useragent_ip;
+ }
+}
+
+static const char *log_local_address(request_rec *r, char *a)
+{
+ return r->connection->local_ip;
+}
+
+static const char *log_remote_logname(request_rec *r, char *a)
+{
+ return ap_escape_logitem(r->pool, ap_get_remote_logname(r));
+}
+
+static const char *log_remote_user(request_rec *r, char *a)
+{
+ char *rvalue = r->user;
+
+ if (rvalue == NULL) {
+ rvalue = "-";
+ }
+ else if (strlen(rvalue) == 0) {
+ rvalue = "\"\"";
+ }
+ else {
+ rvalue = ap_escape_logitem(r->pool, rvalue);
+ }
+
+ return rvalue;
+}
+
+static const char *log_request_line(request_rec *r, char *a)
+{
+ /* NOTE: If the original request contained a password, we
+ * re-write the request line here to contain XXXXXX instead:
+ * (note the truncation before the protocol string for HTTP/0.9 requests)
+ * (note also that r->the_request contains the unmodified request)
+ */
+ return ap_escape_logitem(r->pool,
+ (r->parsed_uri.password)
+ ? apr_pstrcat(r->pool, r->method, " ",
+ apr_uri_unparse(r->pool,
+ &r->parsed_uri, 0),
+ r->assbackwards ? NULL : " ",
+ r->protocol, NULL)
+ : r->the_request);
+}
+
+static const char *log_request_file(request_rec *r, char *a)
+{
+ return ap_escape_logitem(r->pool, r->filename);
+}
+static const char *log_request_uri(request_rec *r, char *a)
+{
+ return ap_escape_logitem(r->pool, r->uri);
+}
+static const char *log_request_method(request_rec *r, char *a)
+{
+ return ap_escape_logitem(r->pool, r->method);
+}
+static const char *log_log_id(request_rec *r, char *a)
+{
+ if (a && !strcmp(a, "c")) {
+ return r->connection->log_id ? r->connection->log_id : "-";
+ }
+ else {
+ return r->log_id ? r->log_id : "-";
+ }
+}
+static const char *log_request_protocol(request_rec *r, char *a)
+{
+ return ap_escape_logitem(r->pool, r->protocol);
+}
+static const char *log_request_query(request_rec *r, char *a)
+{
+ return (r->args) ? apr_pstrcat(r->pool, "?",
+ ap_escape_logitem(r->pool, r->args), NULL)
+ : "";
+}
+static const char *log_status(request_rec *r, char *a)
+{
+ return pfmt(r->pool, r->status);
+}
+
+static const char *log_handler(request_rec *r, char *a)
+{
+ return ap_escape_logitem(r->pool, r->handler);
+}
+
+static const char *clf_log_bytes_sent(request_rec *r, char *a)
+{
+ if (!r->sent_bodyct || !r->bytes_sent) {
+ return "-";
+ }
+ else {
+ return apr_off_t_toa(r->pool, r->bytes_sent);
+ }
+}
+
+static const char *log_bytes_sent(request_rec *r, char *a)
+{
+ if (!r->sent_bodyct || !r->bytes_sent) {
+ return "0";
+ }
+ else {
+ return apr_off_t_toa(r->pool, r->bytes_sent);
+ }
+}
+
+
+static const char *log_header_in(request_rec *r, char *a)
+{
+ return ap_escape_logitem(r->pool, apr_table_get(r->headers_in, a));
+}
+
+static const char *log_trailer_in(request_rec *r, char *a)
+{
+ return ap_escape_logitem(r->pool, apr_table_get(r->trailers_in, a));
+}
+
+
+static APR_INLINE char *find_multiple_headers(apr_pool_t *pool,
+ const apr_table_t *table,
+ const char *key)
+{
+ const apr_array_header_t *elts;
+ const apr_table_entry_t *t_elt;
+ const apr_table_entry_t *t_end;
+ apr_size_t len;
+ struct sle {
+ struct sle *next;
+ const char *value;
+ apr_size_t len;
+ } *result_list, *rp;
+
+ elts = apr_table_elts(table);
+
+ if (!elts->nelts) {
+ return NULL;
+ }
+
+ t_elt = (const apr_table_entry_t *)elts->elts;
+ t_end = t_elt + elts->nelts;
+ len = 1; /* \0 */
+ result_list = rp = NULL;
+
+ do {
+ if (!ap_cstr_casecmp(t_elt->key, key)) {
+ if (!result_list) {
+ result_list = rp = apr_palloc(pool, sizeof(*rp));
+ }
+ else {
+ rp = rp->next = apr_palloc(pool, sizeof(*rp));
+ len += 2; /* ", " */
+ }
+
+ rp->next = NULL;
+ rp->value = t_elt->val;
+ rp->len = strlen(rp->value);
+
+ len += rp->len;
+ }
+ ++t_elt;
+ } while (t_elt < t_end);
+
+ if (result_list) {
+ char *result = apr_palloc(pool, len);
+ char *cp = result;
+
+ rp = result_list;
+ while (rp) {
+ if (rp != result_list) {
+ *cp++ = ',';
+ *cp++ = ' ';
+ }
+ memcpy(cp, rp->value, rp->len);
+ cp += rp->len;
+ rp = rp->next;
+ }
+ *cp = '\0';
+
+ return result;
+ }
+
+ return NULL;
+}
+
+static const char *log_header_out(request_rec *r, char *a)
+{
+ const char *cp = NULL;
+
+ if (!ap_cstr_casecmp(a, "Content-type") && r->content_type) {
+ cp = ap_field_noparam(r->pool, r->content_type);
+ }
+ else if (!ap_cstr_casecmp(a, "Set-Cookie")) {
+ cp = find_multiple_headers(r->pool, r->headers_out, a);
+ }
+ else {
+ cp = apr_table_get(r->headers_out, a);
+ }
+
+ return ap_escape_logitem(r->pool, cp);
+}
+
+static const char *log_trailer_out(request_rec *r, char *a)
+{
+ return ap_escape_logitem(r->pool, apr_table_get(r->trailers_out, a));
+}
+
+static const char *log_note(request_rec *r, char *a)
+{
+ return ap_escape_logitem(r->pool, apr_table_get(r->notes, a));
+}
+static const char *log_env_var(request_rec *r, char *a)
+{
+ return ap_escape_logitem(r->pool, apr_table_get(r->subprocess_env, a));
+}
+
+static const char *log_cookie(request_rec *r, char *a)
+{
+ const char *cookies_entry;
+
+ /*
+ * This supports Netscape version 0 cookies while being tolerant to
+ * some properties of RFC2109/2965 version 1 cookies:
+ * - case-insensitive match of cookie names
+ * - white space between the tokens
+ * It does not support the following version 1 features:
+ * - quoted strings as cookie values
+ * - commas to separate cookies
+ */
+
+ if ((cookies_entry = apr_table_get(r->headers_in, "Cookie"))) {
+ char *cookie, *last1, *last2;
+ char *cookies = apr_pstrdup(r->pool, cookies_entry);
+
+ while ((cookie = apr_strtok(cookies, ";", &last1))) {
+ char *name = apr_strtok(cookie, "=", &last2);
+ /* last2 points to the next char following an '=' delim,
+ or the trailing NUL char of the string */
+ char *value = last2;
+ if (name && *name && value && *value) {
+ char *last = value - 2;
+ /* Move past leading WS */
+ name += strspn(name, " \t");
+ while (last >= name && apr_isspace(*last)) {
+ *last = '\0';
+ --last;
+ }
+
+ if (!ap_cstr_casecmp(name, a)) {
+ /* last1 points to the next char following the ';' delim,
+ or the trailing NUL char of the string */
+ last = last1 - (*last1 ? 2 : 1);
+ /* Move past leading WS */
+ value += strspn(value, " \t");
+ while (last >= value && apr_isspace(*last)) {
+ *last = '\0';
+ --last;
+ }
+
+ return ap_escape_logitem(r->pool, value);
+ }
+ }
+ /* Iterate the remaining tokens using apr_strtok(NULL, ...) */
+ cookies = NULL;
+ }
+ }
+ return NULL;
+}
+
+static const char *log_request_time_custom(request_rec *r, char *a,
+ apr_time_exp_t *xt)
+{
+ apr_size_t retcode;
+ char tstr[MAX_STRING_LEN];
+ apr_strftime(tstr, &retcode, sizeof(tstr), a, xt);
+ return apr_pstrdup(r->pool, tstr);
+}
+
+#define DEFAULT_REQUEST_TIME_SIZE 32
+typedef struct {
+ unsigned t;
+ char timestr[DEFAULT_REQUEST_TIME_SIZE];
+ unsigned t_validate;
+} cached_request_time;
+
+#define TIME_FMT_CUSTOM 0
+#define TIME_FMT_CLF 1
+#define TIME_FMT_ABS_SEC 2
+#define TIME_FMT_ABS_MSEC 3
+#define TIME_FMT_ABS_USEC 4
+#define TIME_FMT_ABS_MSEC_FRAC 5
+#define TIME_FMT_ABS_USEC_FRAC 6
+
+#define TIME_CACHE_SIZE 4
+#define TIME_CACHE_MASK 3
+static cached_request_time request_time_cache[TIME_CACHE_SIZE];
+
+static apr_time_t get_request_end_time(request_rec *r)
+{
+ log_request_state *state = (log_request_state *)ap_get_module_config(r->request_config,
+ &log_config_module);
+ if (!state) {
+ state = apr_pcalloc(r->pool, sizeof(log_request_state));
+ ap_set_module_config(r->request_config, &log_config_module, state);
+ }
+ if (state->request_end_time == 0) {
+ state->request_end_time = apr_time_now();
+ }
+ return state->request_end_time;
+}
+
+
+static const char *log_request_time(request_rec *r, char *a)
+{
+ apr_time_exp_t xt;
+ apr_time_t request_time = r->request_time;
+ int fmt_type = TIME_FMT_CUSTOM;
+ char *fmt = a;
+
+ if (fmt && *fmt) {
+ if (!strncmp(fmt, "begin", 5)) {
+ fmt += 5;
+ if (!*fmt) {
+ fmt_type = TIME_FMT_CLF;
+ }
+ else if (*fmt == ':') {
+ fmt++;
+ a = fmt;
+ }
+ }
+ else if (!strncmp(fmt, "end", 3)) {
+ fmt += 3;
+ if (!*fmt) {
+ request_time = get_request_end_time(r);
+ fmt_type = TIME_FMT_CLF;
+ }
+ else if (*fmt == ':') {
+ fmt++;
+ a = fmt;
+ request_time = get_request_end_time(r);
+ }
+ }
+ if (!strncmp(fmt, "msec", 4)) {
+ fmt += 4;
+ if (!*fmt) {
+ fmt_type = TIME_FMT_ABS_MSEC;
+ }
+ else if (!strcmp(fmt, "_frac")) {
+ fmt_type = TIME_FMT_ABS_MSEC_FRAC;
+ }
+ }
+ else if (!strncmp(fmt, "usec", 4)) {
+ fmt += 4;
+ if (!*fmt) {
+ fmt_type = TIME_FMT_ABS_USEC;
+ }
+ else if (!strcmp(fmt, "_frac")) {
+ fmt_type = TIME_FMT_ABS_USEC_FRAC;
+ }
+ }
+ else if (!strcmp(fmt, "sec")) {
+ fmt_type = TIME_FMT_ABS_SEC;
+ }
+ else if (!*fmt) {
+ fmt_type = TIME_FMT_CLF;
+ }
+ }
+ else {
+ fmt_type = TIME_FMT_CLF;
+ }
+
+ if (fmt_type >= TIME_FMT_ABS_SEC) { /* Absolute (micro-/milli-)second time
+ * or msec/usec fraction
+ */
+ char* buf = apr_palloc(r->pool, 20);
+ switch (fmt_type) {
+ case TIME_FMT_ABS_SEC:
+ apr_snprintf(buf, 20, "%" APR_TIME_T_FMT, apr_time_sec(request_time));
+ break;
+ case TIME_FMT_ABS_MSEC:
+ apr_snprintf(buf, 20, "%" APR_TIME_T_FMT, apr_time_as_msec(request_time));
+ break;
+ case TIME_FMT_ABS_USEC:
+ apr_snprintf(buf, 20, "%" APR_TIME_T_FMT, request_time);
+ break;
+ case TIME_FMT_ABS_MSEC_FRAC:
+ apr_snprintf(buf, 20, "%03" APR_TIME_T_FMT, apr_time_msec(request_time));
+ break;
+ case TIME_FMT_ABS_USEC_FRAC:
+ apr_snprintf(buf, 20, "%06" APR_TIME_T_FMT, apr_time_usec(request_time));
+ break;
+ default:
+ return "-";
+ }
+ return buf;
+ }
+ else if (fmt_type == TIME_FMT_CUSTOM) { /* Custom format */
+ /* The custom time formatting uses a very large temp buffer
+ * on the stack. To avoid using so much stack space in the
+ * common case where we're not using a custom format, the code
+ * for the custom format in a separate function. (That's why
+ * log_request_time_custom is not inlined right here.)
+ */
+ ap_explode_recent_localtime(&xt, request_time);
+ return log_request_time_custom(r, a, &xt);
+ }
+ else { /* CLF format */
+ /* This code uses the same technique as ap_explode_recent_localtime():
+ * optimistic caching with logic to detect and correct race conditions.
+ * See the comments in server/util_time.c for more information.
+ */
+ cached_request_time* cached_time = apr_palloc(r->pool,
+ sizeof(*cached_time));
+ unsigned t_seconds = (unsigned)apr_time_sec(request_time);
+ unsigned i = t_seconds & TIME_CACHE_MASK;
+ *cached_time = request_time_cache[i];
+ if ((t_seconds != cached_time->t) ||
+ (t_seconds != cached_time->t_validate)) {
+
+ /* Invalid or old snapshot, so compute the proper time string
+ * and store it in the cache
+ */
+ char sign;
+ int timz;
+
+ ap_explode_recent_localtime(&xt, request_time);
+ timz = xt.tm_gmtoff;
+ if (timz < 0) {
+ timz = -timz;
+ sign = '-';
+ }
+ else {
+ sign = '+';
+ }
+ cached_time->t = t_seconds;
+ apr_snprintf(cached_time->timestr, DEFAULT_REQUEST_TIME_SIZE,
+ "[%02d/%s/%d:%02d:%02d:%02d %c%.2d%.2d]",
+ xt.tm_mday, apr_month_snames[xt.tm_mon],
+ xt.tm_year+1900, xt.tm_hour, xt.tm_min, xt.tm_sec,
+ sign, timz / (60*60), (timz % (60*60)) / 60);
+ cached_time->t_validate = t_seconds;
+ request_time_cache[i] = *cached_time;
+ }
+ return cached_time->timestr;
+ }
+}
+
+static const char *log_request_duration_microseconds(request_rec *r, char *a)
+{
+ return apr_psprintf(r->pool, "%" APR_TIME_T_FMT,
+ (get_request_end_time(r) - r->request_time));
+}
+
+static const char *log_request_duration_scaled(request_rec *r, char *a)
+{
+ apr_time_t duration = get_request_end_time(r) - r->request_time;
+ if (*a == '\0' || !strcasecmp(a, "s")) {
+ duration = apr_time_sec(duration);
+ }
+ else if (!strcasecmp(a, "ms")) {
+ duration = apr_time_as_msec(duration);
+ }
+ else if (!strcasecmp(a, "us")) {
+ }
+ else {
+ /* bogus format */
+ return a;
+ }
+ return apr_psprintf(r->pool, "%" APR_TIME_T_FMT, duration);
+}
+
+/* These next two routines use the canonical name:port so that log
+ * parsers don't need to duplicate all the vhost parsing crud.
+ */
+static const char *log_virtual_host(request_rec *r, char *a)
+{
+ return ap_escape_logitem(r->pool, r->server->server_hostname);
+}
+
+static const char *log_server_port(request_rec *r, char *a)
+{
+ apr_port_t port;
+
+ if (*a == '\0' || !strcasecmp(a, "canonical")) {
+ port = r->server->port ? r->server->port : ap_default_port(r);
+ }
+ else if (!strcasecmp(a, "remote")) {
+ port = r->useragent_addr->port;
+ }
+ else if (!strcasecmp(a, "local")) {
+ port = r->connection->local_addr->port;
+ }
+ else {
+ /* bogus format */
+ return a;
+ }
+ return apr_itoa(r->pool, (int)port);
+}
+
+/* This respects the setting of UseCanonicalName so that
+ * the dynamic mass virtual hosting trick works better.
+ */
+static const char *log_server_name(request_rec *r, char *a)
+{
+ return ap_escape_logitem(r->pool, ap_get_server_name(r));
+}
+
+static const char *log_pid_tid(request_rec *r, char *a)
+{
+ if (*a == '\0' || !strcasecmp(a, "pid")) {
+ return ap_append_pid(r->pool, "", "");
+ }
+ else if (!strcasecmp(a, "tid") || !strcasecmp(a, "hextid")) {
+#if APR_HAS_THREADS
+ apr_os_thread_t tid = apr_os_thread_current();
+#else
+ int tid = 0; /* APR will format "0" anyway but an arg is needed */
+#endif
+ return apr_psprintf(r->pool,
+ /* APR can format a thread id in hex */
+ *a == 'h' ? "%pt" : "%pT", &tid);
+ }
+ /* bogus format */
+ return a;
+}
+
+static const char *log_connection_status(request_rec *r, char *a)
+{
+ if (r->connection->aborted)
+ return "X";
+
+ if (r->connection->keepalive == AP_CONN_KEEPALIVE &&
+ (!r->server->keep_alive_max ||
+ (r->server->keep_alive_max - r->connection->keepalives) > 0)) {
+ return "+";
+ }
+ return "-";
+}
+
+static const char *log_requests_on_connection(request_rec *r, char *a)
+{
+ int num = r->connection->keepalives ? r->connection->keepalives - 1 : 0;
+ return apr_itoa(r->pool, num);
+}
+
+/*****************************************************************
+ *
+ * Parsing the log format string
+ */
+
+static char *parse_log_misc_string(apr_pool_t *p, log_format_item *it,
+ const char **sa)
+{
+ const char *s;
+ char *d;
+
+ it->func = constant_item;
+ it->conditions = NULL;
+
+ s = *sa;
+ while (*s && *s != '%') {
+ s++;
+ }
+ /*
+ * This might allocate a few chars extra if there's a backslash
+ * escape in the format string.
+ */
+ it->arg = apr_palloc(p, s - *sa + 1);
+
+ d = it->arg;
+ s = *sa;
+ while (*s && *s != '%') {
+ if (*s != '\\') {
+ *d++ = *s++;
+ }
+ else {
+ s++;
+ switch (*s) {
+ case '\\':
+ *d++ = '\\';
+ s++;
+ break;
+ case 'r':
+ *d++ = '\r';
+ s++;
+ break;
+ case 'n':
+ *d++ = '\n';
+ s++;
+ break;
+ case 't':
+ *d++ = '\t';
+ s++;
+ break;
+ default:
+ /* copy verbatim */
+ *d++ = '\\';
+ /*
+ * Allow the loop to deal with this *s in the normal
+ * fashion so that it handles end of string etc.
+ * properly.
+ */
+ break;
+ }
+ }
+ }
+ *d = '\0';
+
+ *sa = s;
+ return NULL;
+}
+
+static char *parse_log_item(apr_pool_t *p, log_format_item *it, const char **sa)
+{
+ const char *s = *sa;
+ ap_log_handler *handler = NULL;
+
+ if (*s != '%') {
+ return parse_log_misc_string(p, it, sa);
+ }
+
+ ++s;
+ it->condition_sense = 0;
+ it->conditions = NULL;
+
+ if (*s == '%') {
+ it->arg = "%";
+ it->func = constant_item;
+ *sa = ++s;
+
+ return NULL;
+ }
+
+ it->want_orig = -1;
+ it->arg = ""; /* For safety's sake... */
+
+ while (*s) {
+ int i;
+
+ switch (*s) {
+ case '!':
+ ++s;
+ it->condition_sense = !it->condition_sense;
+ break;
+
+ case '<':
+ ++s;
+ it->want_orig = 1;
+ break;
+
+ case '>':
+ ++s;
+ it->want_orig = 0;
+ break;
+
+ case ',':
+ ++s;
+ break;
+
+ case '{':
+ ++s;
+ it->arg = ap_getword(p, &s, '}');
+ break;
+
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ i = *s - '0';
+ while (apr_isdigit(*++s)) {
+ i = i * 10 + (*s) - '0';
+ }
+ if (!it->conditions) {
+ it->conditions = apr_array_make(p, 4, sizeof(int));
+ }
+ *(int *) apr_array_push(it->conditions) = i;
+ break;
+
+ default:
+ /* check for '^' + two character format first */
+ if (*s == '^' && *(s+1) && *(s+2)) {
+ handler = (ap_log_handler *)apr_hash_get(log_hash, s, 3);
+ if (handler) {
+ s += 3;
+ }
+ }
+ if (!handler) {
+ handler = (ap_log_handler *)apr_hash_get(log_hash, s++, 1);
+ }
+ if (!handler) {
+ char dummy[2];
+
+ dummy[0] = s[-1];
+ dummy[1] = '\0';
+ return apr_pstrcat(p, "Unrecognized LogFormat directive %",
+ dummy, NULL);
+ }
+ it->func = handler->func;
+ if (it->want_orig == -1) {
+ it->want_orig = handler->want_orig_default;
+ }
+ *sa = s;
+ return NULL;
+ }
+ }
+
+ return "Ran off end of LogFormat parsing args to some directive";
+}
+
+static apr_array_header_t *parse_log_string(apr_pool_t *p, const char *s, const char **err)
+{
+ apr_array_header_t *a = apr_array_make(p, 30, sizeof(log_format_item));
+ char *res;
+
+ while (*s) {
+ if ((res = parse_log_item(p, (log_format_item *) apr_array_push(a), &s))) {
+ *err = res;
+ return NULL;
+ }
+ }
+
+ s = APR_EOL_STR;
+ parse_log_item(p, (log_format_item *) apr_array_push(a), &s);
+ return a;
+}
+
+/*****************************************************************
+ *
+ * Actually logging.
+ */
+
+static const char *process_item(request_rec *r, request_rec *orig,
+ log_format_item *item)
+{
+ const char *cp;
+
+ /* First, see if we need to process this thing at all... */
+
+ if (item->conditions && item->conditions->nelts != 0) {
+ int i;
+ int *conds = (int *) item->conditions->elts;
+ int in_list = 0;
+
+ for (i = 0; i < item->conditions->nelts; ++i) {
+ if (r->status == conds[i]) {
+ in_list = 1;
+ break;
+ }
+ }
+
+ if ((item->condition_sense && in_list)
+ || (!item->condition_sense && !in_list)) {
+ return "-";
+ }
+ }
+
+ /* We do. Do it... */
+
+ cp = (*item->func) (item->want_orig ? orig : r, item->arg);
+ return cp ? cp : "-";
+}
+
+static void flush_log(buffered_log *buf)
+{
+ if (buf->outcnt && buf->handle != NULL) {
+ /* XXX: error handling */
+ apr_file_write_full(buf->handle, buf->outbuf, buf->outcnt, NULL);
+ buf->outcnt = 0;
+ }
+}
+
+
+static int config_log_transaction(request_rec *r, config_log_state *cls,
+ apr_array_header_t *default_format)
+{
+ log_format_item *items;
+ const char **strs;
+ int *strl;
+ request_rec *orig;
+ int i;
+ apr_size_t len = 0;
+ apr_array_header_t *format;
+ char *envar;
+ apr_status_t rv;
+
+ if (cls->fname == NULL) {
+ return DECLINED;
+ }
+
+ /*
+ * See if we've got any conditional envariable-controlled logging decisions
+ * to make.
+ */
+ if (cls->condition_var != NULL) {
+ envar = cls->condition_var;
+ if (*envar != '!') {
+ if (apr_table_get(r->subprocess_env, envar) == NULL) {
+ return DECLINED;
+ }
+ }
+ else {
+ if (apr_table_get(r->subprocess_env, &envar[1]) != NULL) {
+ return DECLINED;
+ }
+ }
+ }
+ else if (cls->condition_expr != NULL) {
+ const char *err;
+ int rc = ap_expr_exec(r, cls->condition_expr, &err);
+ if (rc < 0)
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(00644)
+ "Error evaluating log condition: %s", err);
+ if (rc <= 0)
+ return DECLINED;
+ }
+
+ format = cls->format ? cls->format : default_format;
+
+ strs = apr_palloc(r->pool, sizeof(char *) * (format->nelts));
+ strl = apr_palloc(r->pool, sizeof(int) * (format->nelts));
+ items = (log_format_item *) format->elts;
+
+ orig = r;
+ while (orig->prev) {
+ orig = orig->prev;
+ }
+ while (r->next) {
+ r = r->next;
+ }
+
+ for (i = 0; i < format->nelts; ++i) {
+ strs[i] = process_item(r, orig, &items[i]);
+ len += strl[i] = strlen(strs[i]);
+ }
+
+ if (!log_writer) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00645)
+ "log writer isn't correctly setup");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ rv = log_writer(r, cls->log_writer, strs, strl, format->nelts, len);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(00646)
+ "Error writing to %s", cls->fname);
+ }
+ return OK;
+}
+
+static int multi_log_transaction(request_rec *r)
+{
+ multi_log_state *mls = ap_get_module_config(r->server->module_config,
+ &log_config_module);
+ config_log_state *clsarray;
+ int i;
+
+ /*
+ * Initialize per request state
+ */
+ log_request_state *state = apr_pcalloc(r->pool, sizeof(log_request_state));
+ ap_set_module_config(r->request_config, &log_config_module, state);
+
+ /*
+ * Log this transaction..
+ */
+ if (mls->config_logs->nelts) {
+ clsarray = (config_log_state *) mls->config_logs->elts;
+ for (i = 0; i < mls->config_logs->nelts; ++i) {
+ config_log_state *cls = &clsarray[i];
+
+ config_log_transaction(r, cls, mls->default_format);
+ }
+ }
+
+ if (mls->server_config_logs) {
+ clsarray = (config_log_state *) mls->server_config_logs->elts;
+ for (i = 0; i < mls->server_config_logs->nelts; ++i) {
+ config_log_state *cls = &clsarray[i];
+
+ if (cls->inherit || !mls->config_logs->nelts) {
+ config_log_transaction(r, cls, mls->default_format);
+ }
+ }
+ }
+
+ return OK;
+}
+
+/*****************************************************************
+ *
+ * Module glue...
+ */
+
+static void *make_config_log_state(apr_pool_t *p, server_rec *s)
+{
+ multi_log_state *mls;
+
+ mls = (multi_log_state *) apr_palloc(p, sizeof(multi_log_state));
+ mls->config_logs = apr_array_make(p, 1, sizeof(config_log_state));
+ mls->default_format_string = NULL;
+ mls->default_format = NULL;
+ mls->server_config_logs = NULL;
+ mls->formats = apr_table_make(p, 4);
+ apr_table_setn(mls->formats, "CLF", DEFAULT_LOG_FORMAT);
+
+ return mls;
+}
+
+/*
+ * Use the merger to simply add a pointer from the vhost log state
+ * to the log of logs specified for the non-vhost configuration. Make sure
+ * vhosts inherit any globally-defined format names.
+ */
+
+static void *merge_config_log_state(apr_pool_t *p, void *basev, void *addv)
+{
+ multi_log_state *base = (multi_log_state *) basev;
+ multi_log_state *add = (multi_log_state *) addv;
+
+ add->server_config_logs = base->config_logs;
+ if (!add->default_format) {
+ add->default_format_string = base->default_format_string;
+ add->default_format = base->default_format;
+ }
+ add->formats = apr_table_overlay(p, base->formats, add->formats);
+
+ return add;
+}
+
+/*
+ * Set the default logfile format, or define a nickname for a format string.
+ */
+static const char *log_format(cmd_parms *cmd, void *dummy, const char *fmt,
+ const char *name)
+{
+ const char *err_string = NULL;
+ multi_log_state *mls = ap_get_module_config(cmd->server->module_config,
+ &log_config_module);
+
+ /*
+ * If we were given two arguments, the second is a name to be given to the
+ * format. This syntax just defines the nickname - it doesn't actually
+ * make the format the default.
+ */
+ if (name != NULL) {
+ parse_log_string(cmd->pool, fmt, &err_string);
+ if (err_string == NULL) {
+ apr_table_setn(mls->formats, name, fmt);
+ }
+ }
+ else {
+ mls->default_format_string = fmt;
+ mls->default_format = parse_log_string(cmd->pool, fmt, &err_string);
+ }
+ return err_string;
+}
+
+
+static const char *add_custom_log(cmd_parms *cmd, void *dummy, const char *fn,
+ const char *fmt, const char *envclause)
+{
+ const char *err_string = NULL;
+ multi_log_state *mls = ap_get_module_config(cmd->server->module_config,
+ &log_config_module);
+ config_log_state *cls;
+
+ cls = (config_log_state *) apr_array_push(mls->config_logs);
+ cls->condition_var = NULL;
+ cls->condition_expr = NULL;
+ if (envclause != NULL) {
+ if (strncasecmp(envclause, "env=", 4) == 0) {
+ if ((envclause[4] == '\0')
+ || ((envclause[4] == '!') && (envclause[5] == '\0'))) {
+ return "missing environment variable name";
+ }
+ cls->condition_var = apr_pstrdup(cmd->pool, &envclause[4]);
+ }
+ else if (strncasecmp(envclause, "expr=", 5) == 0) {
+ const char *err;
+ if ((envclause[5] == '\0'))
+ return "missing condition";
+ cls->condition_expr = ap_expr_parse_cmd(cmd, &envclause[5],
+ AP_EXPR_FLAG_DONT_VARY,
+ &err, NULL);
+ if (err)
+ return err;
+ }
+ else {
+ return "error in condition clause";
+ }
+ }
+
+ cls->fname = fn;
+ cls->format_string = fmt;
+ cls->directive = cmd->directive;
+ if (fmt == NULL) {
+ cls->format = NULL;
+ }
+ else {
+ cls->format = parse_log_string(cmd->pool, fmt, &err_string);
+ }
+ cls->log_writer = NULL;
+
+ return err_string;
+}
+
+static const char *add_global_log(cmd_parms *cmd, void *dummy, const char *fn,
+ const char *fmt, const char *envclause) {
+ multi_log_state *mls = ap_get_module_config(cmd->server->module_config,
+ &log_config_module);
+ config_log_state *clsarray;
+ config_log_state *cls;
+ const char *ret;
+
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err) {
+ return err;
+ }
+
+ /* Add a custom log through the normal channel */
+ ret = add_custom_log(cmd, dummy, fn, fmt, envclause);
+
+ /* Set the inherit flag unless there was some error */
+ if (ret == NULL) {
+ clsarray = (config_log_state*)mls->config_logs->elts;
+ cls = &clsarray[mls->config_logs->nelts-1];
+ cls->inherit = 1;
+ }
+
+ return ret;
+}
+
+static const char *set_transfer_log(cmd_parms *cmd, void *dummy,
+ const char *fn)
+{
+ return add_custom_log(cmd, dummy, fn, NULL, NULL);
+}
+
+static const char *set_buffered_logs_on(cmd_parms *parms, void *dummy, int flag)
+{
+ buffered_logs = flag;
+ if (buffered_logs) {
+ ap_log_set_writer_init(ap_buffered_log_writer_init);
+ ap_log_set_writer(ap_buffered_log_writer);
+ }
+ else {
+ ap_log_set_writer_init(ap_default_log_writer_init);
+ ap_log_set_writer(ap_default_log_writer);
+ }
+ return NULL;
+}
+static const command_rec config_log_cmds[] =
+{
+AP_INIT_TAKE23("CustomLog", add_custom_log, NULL, RSRC_CONF,
+ "a file name, a custom log format string or format name, "
+ "and an optional \"env=\" or \"expr=\" clause (see docs)"),
+AP_INIT_TAKE23("GlobalLog", add_global_log, NULL, RSRC_CONF,
+ "Same as CustomLog, but forces virtualhosts to inherit the log"),
+AP_INIT_TAKE1("TransferLog", set_transfer_log, NULL, RSRC_CONF,
+ "the filename of the access log"),
+AP_INIT_TAKE12("LogFormat", log_format, NULL, RSRC_CONF,
+ "a log format string (see docs) and an optional format name"),
+AP_INIT_FLAG("BufferedLogs", set_buffered_logs_on, NULL, RSRC_CONF,
+ "Enable Buffered Logging (experimental)"),
+ {NULL}
+};
+
+static config_log_state *open_config_log(server_rec *s, apr_pool_t *p,
+ config_log_state *cls,
+ apr_array_header_t *default_format)
+{
+ if (cls->log_writer != NULL) {
+ return cls; /* virtual config shared w/main server */
+ }
+
+ if (cls->fname == NULL) {
+ return cls; /* Leave it NULL to decline. */
+ }
+
+ cls->log_writer = log_writer_init(p, s, cls->fname);
+ if (cls->log_writer == NULL)
+ return NULL;
+
+ return cls;
+}
+
+static int open_multi_logs(server_rec *s, apr_pool_t *p)
+{
+ int i;
+ multi_log_state *mls = ap_get_module_config(s->module_config,
+ &log_config_module);
+ config_log_state *clsarray;
+ const char *dummy;
+ const char *format;
+
+ if (mls->default_format_string) {
+ format = apr_table_get(mls->formats, mls->default_format_string);
+ if (format) {
+ mls->default_format = parse_log_string(p, format, &dummy);
+ }
+ }
+
+ if (!mls->default_format) {
+ mls->default_format = parse_log_string(p, DEFAULT_LOG_FORMAT, &dummy);
+ }
+
+ if (mls->config_logs->nelts) {
+ clsarray = (config_log_state *) mls->config_logs->elts;
+ for (i = 0; i < mls->config_logs->nelts; ++i) {
+ config_log_state *cls = &clsarray[i];
+
+ if (cls->format_string) {
+ format = apr_table_get(mls->formats, cls->format_string);
+ if (format) {
+ cls->format = parse_log_string(p, format, &dummy);
+ }
+ }
+
+ if (!open_config_log(s, p, cls, mls->default_format)) {
+ /* Failure already logged by open_config_log */
+ return DONE;
+ }
+ }
+ }
+ else if (mls->server_config_logs) {
+ clsarray = (config_log_state *) mls->server_config_logs->elts;
+ for (i = 0; i < mls->server_config_logs->nelts; ++i) {
+ config_log_state *cls = &clsarray[i];
+
+ if (cls->format_string) {
+ format = apr_table_get(mls->formats, cls->format_string);
+ if (format) {
+ cls->format = parse_log_string(p, format, &dummy);
+ }
+ }
+
+ if (!open_config_log(s, p, cls, mls->default_format)) {
+ /* Failure already logged by open_config_log */
+ return DONE;
+ }
+ }
+ }
+
+ return OK;
+}
+
+
+static apr_status_t flush_all_logs(void *data)
+{
+ server_rec *s = data;
+ multi_log_state *mls;
+ apr_array_header_t *log_list;
+ config_log_state *clsarray;
+ buffered_log *buf;
+ int i;
+
+ if (!buffered_logs)
+ return APR_SUCCESS;
+
+ for (; s; s = s->next) {
+ mls = ap_get_module_config(s->module_config, &log_config_module);
+ log_list = NULL;
+ if (mls->config_logs->nelts) {
+ log_list = mls->config_logs;
+ }
+ else if (mls->server_config_logs) {
+ log_list = mls->server_config_logs;
+ }
+ if (log_list) {
+ clsarray = (config_log_state *) log_list->elts;
+ for (i = 0; i < log_list->nelts; ++i) {
+ buf = clsarray[i].log_writer;
+ flush_log(buf);
+ }
+ }
+ }
+ return APR_SUCCESS;
+}
+
+
+static int init_config_log(apr_pool_t *pc, apr_pool_t *p, apr_pool_t *pt, server_rec *s)
+{
+ int res;
+
+ /* First init the buffered logs array, which is needed when opening the logs. */
+ if (buffered_logs) {
+ all_buffered_logs = apr_array_make(p, 5, sizeof(buffered_log *));
+ }
+
+ /* Next, do "physical" server, which gets default log fd and format
+ * for the virtual servers, if they don't override...
+ */
+ res = open_multi_logs(s, p);
+
+ /* Then, virtual servers */
+
+ for (s = s->next; (res == OK) && s; s = s->next) {
+ res = open_multi_logs(s, p);
+ }
+
+ return res;
+}
+
+static void init_child(apr_pool_t *p, server_rec *s)
+{
+ int mpm_threads;
+
+ ap_mpm_query(AP_MPMQ_MAX_THREADS, &mpm_threads);
+
+ /* Now register the last buffer flush with the cleanup engine */
+ if (buffered_logs) {
+ int i;
+ buffered_log **array = (buffered_log **)all_buffered_logs->elts;
+
+ apr_pool_cleanup_register(p, s, flush_all_logs, flush_all_logs);
+
+ for (i = 0; i < all_buffered_logs->nelts; i++) {
+ buffered_log *this = array[i];
+
+#if APR_HAS_THREADS
+ if (mpm_threads > 1) {
+ apr_status_t rv;
+
+ this->mutex.type = apr_anylock_threadmutex;
+ rv = apr_thread_mutex_create(&this->mutex.lock.tm,
+ APR_THREAD_MUTEX_DEFAULT,
+ p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00647)
+ "could not initialize buffered log mutex, "
+ "transfer log may become corrupted");
+ this->mutex.type = apr_anylock_none;
+ }
+ }
+ else
+#endif
+ {
+ this->mutex.type = apr_anylock_none;
+ }
+ }
+ }
+}
+
+static void ap_register_log_handler(apr_pool_t *p, char *tag,
+ ap_log_handler_fn_t *handler, int def)
+{
+ ap_log_handler *log_struct = apr_palloc(p, sizeof(*log_struct));
+ log_struct->func = handler;
+ log_struct->want_orig_default = def;
+
+ apr_hash_set(log_hash, tag, strlen(tag), (const void *)log_struct);
+}
+static ap_log_writer_init *ap_log_set_writer_init(ap_log_writer_init *handle)
+{
+ ap_log_writer_init *old = log_writer_init;
+ log_writer_init = handle;
+
+ return old;
+
+}
+static ap_log_writer *ap_log_set_writer(ap_log_writer *handle)
+{
+ ap_log_writer *old = log_writer;
+ log_writer = handle;
+
+ return old;
+}
+
+static apr_status_t ap_default_log_writer( request_rec *r,
+ void *handle,
+ const char **strs,
+ int *strl,
+ int nelts,
+ apr_size_t len)
+
+{
+ char *str;
+ char *s;
+ int i;
+ apr_status_t rv;
+
+ /*
+ * We do this memcpy dance because write() is atomic for len < PIPE_BUF,
+ * while writev() need not be.
+ */
+ str = apr_palloc(r->pool, len + 1);
+
+ for (i = 0, s = str; i < nelts; ++i) {
+ memcpy(s, strs[i], strl[i]);
+ s += strl[i];
+ }
+
+ rv = apr_file_write((apr_file_t*)handle, str, &len);
+
+ return rv;
+}
+static void *ap_default_log_writer_init(apr_pool_t *p, server_rec *s,
+ const char* name)
+{
+ if (*name == '|') {
+ piped_log *pl;
+
+ pl = ap_open_piped_log(p, name + 1);
+ if (pl == NULL) {
+ return NULL;
+ }
+ return ap_piped_log_write_fd(pl);
+ }
+ else {
+ const char *fname = ap_server_root_relative(p, name);
+ apr_file_t *fd;
+ apr_status_t rv;
+
+ if (!fname) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, APR_EBADPATH, s, APLOGNO(00648)
+ "invalid transfer log path %s.", name);
+ return NULL;
+ }
+ rv = apr_file_open(&fd, fname, xfer_flags, xfer_perms, p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00649)
+ "could not open transfer log file %s.", fname);
+ return NULL;
+ }
+ return fd;
+ }
+}
+static void *ap_buffered_log_writer_init(apr_pool_t *p, server_rec *s,
+ const char* name)
+{
+ buffered_log *b;
+ b = apr_pcalloc(p, sizeof(buffered_log));
+ b->handle = ap_default_log_writer_init(p, s, name);
+
+ if (b->handle) {
+ *(buffered_log **)apr_array_push(all_buffered_logs) = b;
+ return b;
+ }
+ else
+ return NULL;
+}
+static apr_status_t ap_buffered_log_writer(request_rec *r,
+ void *handle,
+ const char **strs,
+ int *strl,
+ int nelts,
+ apr_size_t len)
+
+{
+ char *str;
+ char *s;
+ int i;
+ apr_status_t rv;
+ buffered_log *buf = (buffered_log*)handle;
+
+ if ((rv = APR_ANYLOCK_LOCK(&buf->mutex)) != APR_SUCCESS) {
+ return rv;
+ }
+
+ if (len + buf->outcnt > LOG_BUFSIZE) {
+ flush_log(buf);
+ }
+ if (len >= LOG_BUFSIZE) {
+ apr_size_t w;
+
+ /*
+ * We do this memcpy dance because write() is atomic for
+ * len < PIPE_BUF, while writev() need not be.
+ */
+ str = apr_palloc(r->pool, len + 1);
+ for (i = 0, s = str; i < nelts; ++i) {
+ memcpy(s, strs[i], strl[i]);
+ s += strl[i];
+ }
+ w = len;
+ rv = apr_file_write_full(buf->handle, str, w, NULL);
+
+ }
+ else {
+ for (i = 0, s = &buf->outbuf[buf->outcnt]; i < nelts; ++i) {
+ memcpy(s, strs[i], strl[i]);
+ s += strl[i];
+ }
+ buf->outcnt += len;
+ rv = APR_SUCCESS;
+ }
+
+ APR_ANYLOCK_UNLOCK(&buf->mutex);
+ return rv;
+}
+
+static int log_pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp)
+{
+ static APR_OPTIONAL_FN_TYPE(ap_register_log_handler) *log_pfn_register;
+
+ log_pfn_register = APR_RETRIEVE_OPTIONAL_FN(ap_register_log_handler);
+
+ if (log_pfn_register) {
+ log_pfn_register(p, "h", log_remote_host, 0);
+ log_pfn_register(p, "a", log_remote_address, 0 );
+ log_pfn_register(p, "A", log_local_address, 0 );
+ log_pfn_register(p, "l", log_remote_logname, 0);
+ log_pfn_register(p, "u", log_remote_user, 0);
+ log_pfn_register(p, "t", log_request_time, 0);
+ log_pfn_register(p, "f", log_request_file, 0);
+ log_pfn_register(p, "b", clf_log_bytes_sent, 0);
+ log_pfn_register(p, "B", log_bytes_sent, 0);
+ log_pfn_register(p, "i", log_header_in, 0);
+ log_pfn_register(p, "o", log_header_out, 0);
+ log_pfn_register(p, "n", log_note, 0);
+ log_pfn_register(p, "L", log_log_id, 1);
+ log_pfn_register(p, "e", log_env_var, 0);
+ log_pfn_register(p, "V", log_server_name, 0);
+ log_pfn_register(p, "v", log_virtual_host, 0);
+ log_pfn_register(p, "p", log_server_port, 0);
+ log_pfn_register(p, "P", log_pid_tid, 0);
+ log_pfn_register(p, "H", log_request_protocol, 0);
+ log_pfn_register(p, "m", log_request_method, 0);
+ log_pfn_register(p, "q", log_request_query, 0);
+ log_pfn_register(p, "X", log_connection_status, 0);
+ log_pfn_register(p, "C", log_cookie, 0);
+ log_pfn_register(p, "k", log_requests_on_connection, 0);
+ log_pfn_register(p, "r", log_request_line, 1);
+ log_pfn_register(p, "D", log_request_duration_microseconds, 1);
+ log_pfn_register(p, "T", log_request_duration_scaled, 1);
+ log_pfn_register(p, "U", log_request_uri, 1);
+ log_pfn_register(p, "s", log_status, 1);
+ log_pfn_register(p, "R", log_handler, 1);
+
+ log_pfn_register(p, "^ti", log_trailer_in, 0);
+ log_pfn_register(p, "^to", log_trailer_out, 0);
+ }
+
+ /* reset to default conditions */
+ ap_log_set_writer_init(ap_default_log_writer_init);
+ ap_log_set_writer(ap_default_log_writer);
+ buffered_logs = 0;
+
+ return OK;
+}
+
+static int check_log_dir(apr_pool_t *p, server_rec *s, config_log_state *cls)
+{
+ if (!cls->fname || cls->fname[0] == '|' || !cls->directive) {
+ return OK;
+ }
+ else {
+ char *abs = ap_server_root_relative(p, cls->fname);
+ char *dir = ap_make_dirstr_parent(p, abs);
+ apr_finfo_t finfo;
+ const ap_directive_t *directive = cls->directive;
+ apr_status_t rv = apr_stat(&finfo, dir, APR_FINFO_TYPE, p);
+ cls->directive = NULL; /* Don't check this config_log_state again */
+ if (rv == APR_SUCCESS && finfo.filetype != APR_DIR)
+ rv = APR_ENOTDIR;
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_EMERG, rv, s,
+ APLOGNO(02297)
+ "Cannot access directory '%s' for log file '%s' "
+ "defined at %s:%d", dir, cls->fname,
+ directive->filename, directive->line_num);
+ return !OK;
+ }
+ }
+ return OK;
+}
+
+static int log_check_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
+{
+ int rv = OK;
+ while (s) {
+ multi_log_state *mls = ap_get_module_config(s->module_config,
+ &log_config_module);
+ /*
+ * We don't need to check mls->server_config_logs because it just
+ * points to the parent server's mls->config_logs.
+ */
+ apr_array_header_t *log_list = mls->config_logs;
+ config_log_state *clsarray = (config_log_state *) log_list->elts;
+ int i;
+ for (i = 0; i < log_list->nelts; ++i) {
+ if (check_log_dir(ptemp, s, &clsarray[i]) != OK)
+ rv = !OK;
+ }
+
+ s = s->next;
+ }
+ return rv;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_hook_pre_config(log_pre_config,NULL,NULL,APR_HOOK_REALLY_FIRST);
+ ap_hook_check_config(log_check_config,NULL,NULL,APR_HOOK_MIDDLE);
+ ap_hook_child_init(init_child,NULL,NULL,APR_HOOK_MIDDLE);
+ ap_hook_open_logs(init_config_log,NULL,NULL,APR_HOOK_MIDDLE);
+ ap_hook_log_transaction(multi_log_transaction,NULL,NULL,APR_HOOK_MIDDLE);
+
+ /* Init log_hash before we register the optional function. It is
+ * possible for the optional function, ap_register_log_handler,
+ * to be called before any other mod_log_config hooks are called.
+ * As a policy, we should init everything required by an optional function
+ * before calling APR_REGISTER_OPTIONAL_FN.
+ */
+ log_hash = apr_hash_make(p);
+ APR_REGISTER_OPTIONAL_FN(ap_register_log_handler);
+ APR_REGISTER_OPTIONAL_FN(ap_log_set_writer_init);
+ APR_REGISTER_OPTIONAL_FN(ap_log_set_writer);
+}
+
+AP_DECLARE_MODULE(log_config) =
+{
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-dir config */
+ NULL, /* merge per-dir config */
+ make_config_log_state, /* server config */
+ merge_config_log_state, /* merge server config */
+ config_log_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
+
diff --git a/modules/loggers/mod_log_config.dep b/modules/loggers/mod_log_config.dep
new file mode 100644
index 0000000..8107bd9
--- /dev/null
+++ b/modules/loggers/mod_log_config.dep
@@ -0,0 +1,62 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_log_config.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_log_config.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_mpm.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_time.h"\
+ "..\..\srclib\apr-util\include\apr_anylock.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_thread_rwlock.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_log_config.h"\
+
diff --git a/modules/loggers/mod_log_config.dsp b/modules/loggers/mod_log_config.dsp
new file mode 100644
index 0000000..2338875
--- /dev/null
+++ b/modules/loggers/mod_log_config.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_log_config" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_log_config - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_log_config.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_log_config.mak" CFG="mod_log_config - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_log_config - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_log_config - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_log_config - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_log_config_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_log_config.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_log_config.so" /d LONG_NAME="log_config_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_log_config.so" /base:@..\..\os\win32\BaseAddr.ref,mod_log_config.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_log_config.so" /base:@..\..\os\win32\BaseAddr.ref,mod_log_config.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_log_config.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_log_config - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_log_config_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_log_config.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_log_config.so" /d LONG_NAME="log_config_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_log_config.so" /base:@..\..\os\win32\BaseAddr.ref,mod_log_config.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_log_config.so" /base:@..\..\os\win32\BaseAddr.ref,mod_log_config.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_log_config.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_log_config - Win32 Release"
+# Name "mod_log_config - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_log_config.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/loggers/mod_log_config.exp b/modules/loggers/mod_log_config.exp
new file mode 100644
index 0000000..0749e52
--- /dev/null
+++ b/modules/loggers/mod_log_config.exp
@@ -0,0 +1 @@
+log_config_module
diff --git a/modules/loggers/mod_log_config.h b/modules/loggers/mod_log_config.h
new file mode 100644
index 0000000..877a593
--- /dev/null
+++ b/modules/loggers/mod_log_config.h
@@ -0,0 +1,74 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file mod_log_config.h
+ * @brief Logging Configuration Extension Module for Apache
+ *
+ * @defgroup MOD_LOG_CONFIG mod_log_config
+ * @ingroup APACHE_MODS
+ * @{
+ */
+
+#include "apr_optional.h"
+#include "httpd.h"
+#include "scoreboard.h"
+
+#ifndef _MOD_LOG_CONFIG_H
+#define _MOD_LOG_CONFIG_H 1
+
+/**
+ * callback function prototype for a external log handler
+ */
+typedef const char *ap_log_handler_fn_t(request_rec *r, char *a);
+
+/**
+ * callback function prototype for external writer initialization.
+ */
+typedef void *ap_log_writer_init(apr_pool_t *p, server_rec *s,
+ const char *name);
+/**
+ * callback which gets called where there is a log line to write.
+ */
+typedef apr_status_t ap_log_writer(
+ request_rec *r,
+ void *handle,
+ const char **portions,
+ int *lengths,
+ int nelts,
+ apr_size_t len);
+
+typedef struct ap_log_handler {
+ ap_log_handler_fn_t *func;
+ int want_orig_default;
+} ap_log_handler;
+
+APR_DECLARE_OPTIONAL_FN(void, ap_register_log_handler,
+ (apr_pool_t *p, char *tag, ap_log_handler_fn_t *func,
+ int def));
+/**
+ * you will need to set your init handler *BEFORE* the open_logs
+ * in mod_log_config gets executed
+ */
+APR_DECLARE_OPTIONAL_FN(ap_log_writer_init*, ap_log_set_writer_init,(ap_log_writer_init *func));
+/**
+ * you should probably set the writer at the same time (ie..before open_logs)
+ */
+APR_DECLARE_OPTIONAL_FN(ap_log_writer*, ap_log_set_writer, (ap_log_writer* func));
+
+#endif /* MOD_LOG_CONFIG */
+/** @} */
+
diff --git a/modules/loggers/mod_log_config.mak b/modules/loggers/mod_log_config.mak
new file mode 100644
index 0000000..df9ddf6
--- /dev/null
+++ b/modules/loggers/mod_log_config.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_log_config.dsp
+!IF "$(CFG)" == ""
+CFG=mod_log_config - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_log_config - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_log_config - Win32 Release" && "$(CFG)" != "mod_log_config - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_log_config.mak" CFG="mod_log_config - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_log_config - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_log_config - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_log_config - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_log_config.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_log_config.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_log_config.obj"
+ -@erase "$(INTDIR)\mod_log_config.res"
+ -@erase "$(INTDIR)\mod_log_config_src.idb"
+ -@erase "$(INTDIR)\mod_log_config_src.pdb"
+ -@erase "$(OUTDIR)\mod_log_config.exp"
+ -@erase "$(OUTDIR)\mod_log_config.lib"
+ -@erase "$(OUTDIR)\mod_log_config.pdb"
+ -@erase "$(OUTDIR)\mod_log_config.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_log_config_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_log_config.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_log_config.so" /d LONG_NAME="log_config_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_log_config.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_log_config.pdb" /debug /out:"$(OUTDIR)\mod_log_config.so" /implib:"$(OUTDIR)\mod_log_config.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_log_config.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_log_config.obj" \
+ "$(INTDIR)\mod_log_config.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_log_config.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_log_config.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_log_config.so"
+ if exist .\Release\mod_log_config.so.manifest mt.exe -manifest .\Release\mod_log_config.so.manifest -outputresource:.\Release\mod_log_config.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_log_config - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_log_config.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_log_config.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_log_config.obj"
+ -@erase "$(INTDIR)\mod_log_config.res"
+ -@erase "$(INTDIR)\mod_log_config_src.idb"
+ -@erase "$(INTDIR)\mod_log_config_src.pdb"
+ -@erase "$(OUTDIR)\mod_log_config.exp"
+ -@erase "$(OUTDIR)\mod_log_config.lib"
+ -@erase "$(OUTDIR)\mod_log_config.pdb"
+ -@erase "$(OUTDIR)\mod_log_config.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_log_config_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_log_config.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_log_config.so" /d LONG_NAME="log_config_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_log_config.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_log_config.pdb" /debug /out:"$(OUTDIR)\mod_log_config.so" /implib:"$(OUTDIR)\mod_log_config.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_log_config.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_log_config.obj" \
+ "$(INTDIR)\mod_log_config.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_log_config.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_log_config.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_log_config.so"
+ if exist .\Debug\mod_log_config.so.manifest mt.exe -manifest .\Debug\mod_log_config.so.manifest -outputresource:.\Debug\mod_log_config.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_log_config.dep")
+!INCLUDE "mod_log_config.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_log_config.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_log_config - Win32 Release" || "$(CFG)" == "mod_log_config - Win32 Debug"
+
+!IF "$(CFG)" == "mod_log_config - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\loggers"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\loggers"
+
+!ELSEIF "$(CFG)" == "mod_log_config - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\loggers"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\loggers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_log_config - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\loggers"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\loggers"
+
+!ELSEIF "$(CFG)" == "mod_log_config - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\loggers"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\loggers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_log_config - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\loggers"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\loggers"
+
+!ELSEIF "$(CFG)" == "mod_log_config - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\loggers"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\loggers"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_log_config - Win32 Release"
+
+
+"$(INTDIR)\mod_log_config.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_log_config.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_log_config.so" /d LONG_NAME="log_config_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_log_config - Win32 Debug"
+
+
+"$(INTDIR)\mod_log_config.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_log_config.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_log_config.so" /d LONG_NAME="log_config_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_log_config.c
+
+"$(INTDIR)\mod_log_config.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/loggers/mod_log_debug.c b/modules/loggers/mod_log_debug.c
new file mode 100644
index 0000000..3f27a95
--- /dev/null
+++ b/modules/loggers/mod_log_debug.c
@@ -0,0 +1,295 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr_strings.h"
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "ap_expr.h"
+
+extern module AP_MODULE_DECLARE_DATA log_debug_module;
+
+typedef struct {
+ ap_expr_info_t *msg_expr;
+ ap_expr_info_t *condition;
+ const char *hook;
+} msg_entry;
+
+typedef struct {
+ apr_array_header_t *entries;
+} log_debug_dirconf;
+
+static const char *allhooks = "all";
+static const char * const hooks[] = {
+ "log_transaction", /* 0 */
+ "quick_handler", /* 1 */
+ "handler", /* 2 */
+ "translate_name", /* 3 */
+ "map_to_storage", /* 4 */
+ "fixups", /* 5 */
+ "type_checker", /* 6 */
+ "check_access", /* 7 */
+ "check_access_ex", /* 8 */
+ "check_authn", /* 9 */
+ "check_authz", /* 10 */
+ "insert_filter", /* 11 */
+ "pre_translate_name", /* 12 */
+ NULL
+};
+
+static void do_debug_log(request_rec *r, const char *hookname)
+{
+ log_debug_dirconf *dconf = ap_get_module_config(r->per_dir_config, &log_debug_module);
+ int i;
+ if (dconf->entries == NULL)
+ return;
+
+ for (i = 0; i < dconf->entries->nelts; i++) {
+ const char *msg, *err;
+ msg_entry *entry = APR_ARRAY_IDX(dconf->entries, i, msg_entry *);
+ if (entry->hook != allhooks && entry->hook != hookname)
+ continue;
+ if (entry->condition) {
+ int ret = ap_expr_exec(r, entry->condition, &err);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00640)
+ "Can't evaluate condition: %s", err);
+ continue;
+ }
+ if (!ret)
+ continue;
+ }
+ msg = ap_expr_str_exec(r, entry->msg_expr, &err);
+ if (err)
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00641)
+ "Can't evaluate message expression: %s", err);
+ if (APLOGrdebug(r))
+ /* Intentional no APLOGNO */
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
+ "%s (%s hook, %s:%d)",
+ msg, hookname, entry->msg_expr->filename,
+ entry->msg_expr->line_number);
+ else
+ /* Intentional no APLOGNO */
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
+ "%s", msg);
+ }
+}
+
+static int log_debug_log_transaction(request_rec *r)
+{
+ do_debug_log(r, hooks[0]);
+ return DECLINED;
+}
+
+static int log_debug_quick_handler(request_rec *r, int lookup_uri)
+{
+ do_debug_log(r, hooks[1]);
+ return DECLINED;
+}
+
+static int log_debug_handler(request_rec *r)
+{
+ do_debug_log(r, hooks[2]);
+ return DECLINED;
+}
+
+static int log_debug_pre_translate_name(request_rec *r)
+{
+ do_debug_log(r, hooks[12]);
+ return DECLINED;
+}
+
+static int log_debug_translate_name(request_rec *r)
+{
+ do_debug_log(r, hooks[3]);
+ return DECLINED;
+}
+
+static int log_debug_map_to_storage(request_rec *r)
+{
+ do_debug_log(r, hooks[4]);
+ return DECLINED;
+}
+
+static int log_debug_fixups(request_rec *r)
+{
+ do_debug_log(r, hooks[5]);
+ return DECLINED;
+}
+
+static int log_debug_type_checker(request_rec *r)
+{
+ do_debug_log(r, hooks[6]);
+ return DECLINED;
+}
+
+static int log_debug_check_access(request_rec *r)
+{
+ do_debug_log(r, hooks[7]);
+ return DECLINED;
+}
+
+static int log_debug_check_access_ex(request_rec *r)
+{
+ do_debug_log(r, hooks[8]);
+ return DECLINED;
+}
+
+static int log_debug_check_authn(request_rec *r)
+{
+ do_debug_log(r, hooks[9]);
+ return DECLINED;
+}
+
+static int log_debug_check_authz(request_rec *r)
+{
+ do_debug_log(r, hooks[10]);
+ return DECLINED;
+}
+
+static void log_debug_insert_filter(request_rec *r)
+{
+ do_debug_log(r, hooks[11]);
+}
+
+static void *log_debug_create_dconf(apr_pool_t *p, char *dirspec)
+{
+ log_debug_dirconf *dconf = apr_pcalloc(p, sizeof(log_debug_dirconf));
+ return dconf;
+}
+
+static void *log_debug_merge_dconf(apr_pool_t *p, void *parent_conf, void *new_conf)
+{
+ log_debug_dirconf *merged = apr_pcalloc(p, sizeof(log_debug_dirconf));
+ const log_debug_dirconf *parent = parent_conf;
+ const log_debug_dirconf *new = new_conf;
+
+ if (parent->entries == NULL)
+ merged->entries = new->entries;
+ else if (new->entries == NULL)
+ merged->entries = parent->entries;
+ else
+ /* apr_array_append actually creates a new array */
+ merged->entries = apr_array_append(p, parent->entries, new->entries);
+
+ return merged;
+}
+
+static const char *cmd_log_message(cmd_parms *cmd, void *dconf_, const char *arg1,
+ const char *arg2, const char *arg3)
+{
+ msg_entry *entry = apr_pcalloc(cmd->pool, sizeof(msg_entry));
+ log_debug_dirconf *dconf = dconf_;
+ int i, j;
+ const char *err;
+ const char *args[2];
+ args[0] = arg2;
+ args[1] = arg3;
+
+ entry->msg_expr = ap_expr_parse_cmd(cmd, arg1, AP_EXPR_FLAG_STRING_RESULT|
+ AP_EXPR_FLAG_DONT_VARY,
+ &err, NULL);
+ if (err)
+ return apr_psprintf(cmd->pool,
+ "Could not parse message expression '%s': %s",
+ arg1, err);
+
+ for (i = 0; i < 2; i++) {
+ if (args[i] == NULL)
+ break;
+
+ if (strncasecmp(args[i], "hook=", 5) == 0) {
+ const char *name = args[i] + 5;
+ j = 0;
+ while (hooks[j]) {
+ if (strcasecmp(hooks[j], name) == 0) {
+ entry->hook = hooks[j];
+ break;
+ }
+ j++;
+ }
+ if (entry->hook == NULL) {
+ if (strcmp(name, "*") == 0 || strcasecmp(name, allhooks) == 0)
+ entry->hook = allhooks;
+ else
+ return apr_psprintf(cmd->pool, "Invalid hook name: %s", name);
+ }
+ }
+ else if (strncasecmp(args[i], "expr=", 5) == 0) {
+ const char *expr = args[i] + 5;
+ entry->condition = ap_expr_parse_cmd(cmd, expr,
+ AP_EXPR_FLAG_DONT_VARY,
+ &err, NULL);
+ if (err)
+ return apr_psprintf(cmd->pool,
+ "Could not parse expression '%s': %s",
+ expr, err);
+ }
+ else {
+ return apr_psprintf(cmd->pool, "Invalid argument %s", args[i]);
+ }
+ }
+ if (entry->hook == NULL)
+ entry->hook = hooks[0];
+
+ if (!dconf->entries)
+ dconf->entries = apr_array_make(cmd->pool, 4, sizeof(msg_entry *));
+
+ APR_ARRAY_PUSH(dconf->entries, msg_entry *) = entry;
+
+ return NULL;
+}
+
+static const command_rec log_debug_cmds[] =
+{
+ AP_INIT_TAKE123("LogMessage", cmd_log_message, NULL, RSRC_CONF|ACCESS_CONF,
+ "Log a debug message to the error log if this config block is used for "
+ " a request"),
+ {NULL}
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_hook_log_transaction(log_debug_log_transaction, NULL, NULL, APR_HOOK_FIRST);
+ ap_hook_quick_handler(log_debug_quick_handler, NULL, NULL, APR_HOOK_FIRST);
+ ap_hook_handler(log_debug_handler, NULL, NULL, APR_HOOK_FIRST);
+ ap_hook_pre_translate_name(log_debug_pre_translate_name, NULL, NULL, APR_HOOK_FIRST);
+ ap_hook_translate_name(log_debug_translate_name, NULL, NULL, APR_HOOK_FIRST);
+ ap_hook_map_to_storage(log_debug_map_to_storage, NULL, NULL, APR_HOOK_FIRST);
+ ap_hook_fixups(log_debug_fixups, NULL, NULL, APR_HOOK_FIRST);
+ ap_hook_type_checker(log_debug_type_checker, NULL, NULL, APR_HOOK_FIRST);
+ ap_hook_check_access(log_debug_check_access, NULL, NULL, APR_HOOK_FIRST, AP_AUTH_INTERNAL_PER_URI);
+ ap_hook_check_access_ex(log_debug_check_access_ex, NULL, NULL, APR_HOOK_FIRST, AP_AUTH_INTERNAL_PER_URI);
+ ap_hook_check_authn(log_debug_check_authn, NULL, NULL, APR_HOOK_FIRST, AP_AUTH_INTERNAL_PER_URI);
+ ap_hook_check_authz(log_debug_check_authz, NULL, NULL, APR_HOOK_FIRST, AP_AUTH_INTERNAL_PER_URI);
+ ap_hook_insert_filter(log_debug_insert_filter, NULL, NULL, APR_HOOK_FIRST);
+}
+
+AP_DECLARE_MODULE(log_debug) =
+{
+ STANDARD20_MODULE_STUFF,
+ log_debug_create_dconf, /* create per-dir config */
+ log_debug_merge_dconf, /* merge per-dir config */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ log_debug_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
+
diff --git a/modules/loggers/mod_log_debug.dep b/modules/loggers/mod_log_debug.dep
new file mode 100644
index 0000000..eed5a6d
--- /dev/null
+++ b/modules/loggers/mod_log_debug.dep
@@ -0,0 +1,54 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_log_debug.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_log_debug.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/loggers/mod_log_debug.dsp b/modules/loggers/mod_log_debug.dsp
new file mode 100644
index 0000000..f96e2ae
--- /dev/null
+++ b/modules/loggers/mod_log_debug.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_log_debug" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_log_debug - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_log_debug.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_log_debug.mak" CFG="mod_log_debug - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_log_debug - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_log_debug - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_log_debug - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_log_debug_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_log_debug.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_log_debug.so" /d LONG_NAME="log_debug_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_log_debug.so" /base:@..\..\os\win32\BaseAddr.ref,mod_log_debug.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_log_debug.so" /base:@..\..\os\win32\BaseAddr.ref,mod_log_debug.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_log_debug.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_log_debug - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_log_debug_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_log_debug.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_log_debug.so" /d LONG_NAME="log_debug_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_log_debug.so" /base:@..\..\os\win32\BaseAddr.ref,mod_log_debug.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_log_debug.so" /base:@..\..\os\win32\BaseAddr.ref,mod_log_debug.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_log_debug.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_log_debug - Win32 Release"
+# Name "mod_log_debug - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_log_debug.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/loggers/mod_log_debug.mak b/modules/loggers/mod_log_debug.mak
new file mode 100644
index 0000000..94b0bee
--- /dev/null
+++ b/modules/loggers/mod_log_debug.mak
@@ -0,0 +1,325 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_log_debug.dsp
+!IF "$(CFG)" == ""
+CFG=mod_log_debug - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_log_debug - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_log_debug - Win32 Release" && "$(CFG)" != "mod_log_debug - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_log_debug.mak" CFG="mod_log_debug - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_log_debug - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_log_debug - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_log_debug - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_log_debug.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_log_debug.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_log_debug.obj"
+ -@erase "$(INTDIR)\mod_log_debug.res"
+ -@erase "$(INTDIR)\mod_log_debug_src.idb"
+ -@erase "$(INTDIR)\mod_log_debug_src.pdb"
+ -@erase "$(OUTDIR)\mod_log_debug.exp"
+ -@erase "$(OUTDIR)\mod_log_debug.lib"
+ -@erase "$(OUTDIR)\mod_log_debug.pdb"
+ -@erase "$(OUTDIR)\mod_log_debug.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_log_debug_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_log_debug.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_log_debug.so" /d LONG_NAME="log_debug_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_log_debug.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_log_debug.pdb" /debug /out:"$(OUTDIR)\mod_log_debug.so" /implib:"$(OUTDIR)\mod_log_debug.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_log_debug.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_log_debug.obj" \
+ "$(INTDIR)\mod_log_debug.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_log_debug.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_log_debug.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_log_debug.so"
+ if exist .\Release\mod_log_debug.so.manifest mt.exe -manifest .\Release\mod_log_debug.so.manifest -outputresource:.\Release\mod_log_debug.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_log_debug - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_log_debug.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_log_debug.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_log_debug.obj"
+ -@erase "$(INTDIR)\mod_log_debug.res"
+ -@erase "$(INTDIR)\mod_log_debug_src.idb"
+ -@erase "$(INTDIR)\mod_log_debug_src.pdb"
+ -@erase "$(OUTDIR)\mod_log_debug.exp"
+ -@erase "$(OUTDIR)\mod_log_debug.lib"
+ -@erase "$(OUTDIR)\mod_log_debug.pdb"
+ -@erase "$(OUTDIR)\mod_log_debug.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_log_debug_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_log_debug.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_log_debug.so" /d LONG_NAME="log_debug_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_log_debug.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_log_debug.pdb" /debug /out:"$(OUTDIR)\mod_log_debug.so" /implib:"$(OUTDIR)\mod_log_debug.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_log_debug.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_log_debug.obj" \
+ "$(INTDIR)\mod_log_debug.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_log_debug.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_log_debug.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_log_debug.so"
+ if exist .\Debug\mod_log_debug.so.manifest mt.exe -manifest .\Debug\mod_log_debug.so.manifest -outputresource:.\Debug\mod_log_debug.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_log_debug.dep")
+!INCLUDE "mod_log_debug.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_log_debug.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_log_debug - Win32 Release" || "$(CFG)" == "mod_log_debug - Win32 Debug"
+
+!IF "$(CFG)" == "mod_log_debug - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\loggers"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\loggers"
+
+!ELSEIF "$(CFG)" == "mod_log_debug - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\loggers"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\loggers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_log_debug - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\loggers"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\loggers"
+
+!ELSEIF "$(CFG)" == "mod_log_debug - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\loggers"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\loggers"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_log_debug - Win32 Release"
+
+
+"$(INTDIR)\mod_log_debug.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_log_debug.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_log_debug.so" /d LONG_NAME="log_debug_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_log_debug - Win32 Debug"
+
+
+"$(INTDIR)\mod_log_debug.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_log_debug.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_log_debug.so" /d LONG_NAME="log_debug_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_log_debug.c
+
+"$(INTDIR)\mod_log_debug.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/loggers/mod_log_forensic.c b/modules/loggers/mod_log_forensic.c
new file mode 100644
index 0000000..4884f25
--- /dev/null
+++ b/modules/loggers/mod_log_forensic.c
@@ -0,0 +1,289 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * See also support/check_forensic.
+ * Relate the forensic log to the transfer log by including
+ * %{forensic-id}n in the custom log format, for example:
+ * CustomLog logs/custom "%h %l %u %t \"%r\" %>s %b %{forensic-id}n"
+ *
+ * Credit is due to Tina Bird <tbird precision-guesswork.com>, whose
+ * idea this module was.
+ *
+ * Ben Laurie 29/12/2003
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "apr_strings.h"
+#include "apr_atomic.h"
+#include "http_protocol.h"
+#include "test_char.h"
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+module AP_MODULE_DECLARE_DATA log_forensic_module;
+
+typedef struct fcfg {
+ const char *logname;
+ apr_file_t *fd;
+} fcfg;
+
+static apr_uint32_t next_id;
+
+static void *make_forensic_log_scfg(apr_pool_t *p, server_rec *s)
+{
+ fcfg *cfg = apr_pcalloc(p, sizeof *cfg);
+
+ cfg->logname = NULL;
+ cfg->fd = NULL;
+
+ return cfg;
+}
+
+static void *merge_forensic_log_scfg(apr_pool_t *p, void *parent, void *new)
+{
+ fcfg *cfg = apr_pcalloc(p, sizeof *cfg);
+ fcfg *pc = parent;
+ fcfg *nc = new;
+
+ cfg->logname = apr_pstrdup(p, nc->logname ? nc->logname : pc->logname);
+ cfg->fd = NULL;
+
+ return cfg;
+}
+
+static int open_log(server_rec *s, apr_pool_t *p)
+{
+ fcfg *cfg = ap_get_module_config(s->module_config, &log_forensic_module);
+
+ if (!cfg->logname || cfg->fd)
+ return 1;
+
+ if (*cfg->logname == '|') {
+ piped_log *pl;
+ const char *pname = ap_server_root_relative(p, cfg->logname + 1);
+
+ pl = ap_open_piped_log(p, pname);
+ if (pl == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00650)
+ "couldn't spawn forensic log pipe %s", cfg->logname);
+ return 0;
+ }
+ cfg->fd = ap_piped_log_write_fd(pl);
+ }
+ else {
+ const char *fname = ap_server_root_relative(p, cfg->logname);
+ apr_status_t rv;
+
+ if ((rv = apr_file_open(&cfg->fd, fname,
+ APR_WRITE | APR_APPEND | APR_CREATE,
+ APR_OS_DEFAULT, p)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00651)
+ "could not open forensic log file %s.", fname);
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static int log_init(apr_pool_t *pc, apr_pool_t *p, apr_pool_t *pt,
+ server_rec *s)
+{
+ for ( ; s ; s = s->next) {
+ if (!open_log(s, p)) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ return OK;
+}
+
+
+/* e is the first _invalid_ location in q
+ N.B. returns the terminating NUL.
+ */
+static char *log_escape(char *q, const char *e, const char *p)
+{
+ for ( ; *p ; ++p) {
+ ap_assert(q < e);
+ if (TEST_CHAR(*p, T_ESCAPE_FORENSIC)) {
+ ap_assert(q+2 < e);
+ *q++ = '%';
+ ap_bin2hex(p, 1, q);
+ q += 2;
+ }
+ else
+ *q++ = *p;
+ }
+ ap_assert(q < e);
+ *q = '\0';
+
+ return q;
+}
+
+typedef struct hlog {
+ char *log;
+ char *pos;
+ char *end;
+ apr_pool_t *p;
+ apr_size_t count;
+} hlog;
+
+static apr_size_t count_string(const char *p)
+{
+ apr_size_t n;
+
+ for (n = 0 ; *p ; ++p, ++n)
+ if (TEST_CHAR(*p, T_ESCAPE_FORENSIC))
+ n += 2;
+ return n;
+}
+
+static int count_headers(void *h_, const char *key, const char *value)
+{
+ hlog *h = h_;
+
+ h->count += count_string(key)+count_string(value)+2;
+
+ return 1;
+}
+
+static int log_headers(void *h_, const char *key, const char *value)
+{
+ hlog *h = h_;
+
+ /* note that we don't have to check h->pos here, coz its been done
+ for us by log_escape */
+ *h->pos++ = '|';
+ h->pos = log_escape(h->pos, h->end, key);
+ *h->pos++ = ':';
+ h->pos = log_escape(h->pos, h->end, value);
+
+ return 1;
+}
+
+static int log_before(request_rec *r)
+{
+ fcfg *cfg = ap_get_module_config(r->server->module_config,
+ &log_forensic_module);
+ const char *id;
+ hlog h;
+ apr_size_t n;
+ apr_status_t rv;
+
+ if (!cfg->fd || r->prev) {
+ return DECLINED;
+ }
+
+ if (!(id = apr_table_get(r->subprocess_env, "UNIQUE_ID"))) {
+ /* we make the assumption that we can't go through all the PIDs in
+ under 1 second */
+ id = apr_psprintf(r->pool, "%" APR_PID_T_FMT ":%lx:%x", getpid(),
+ time(NULL), apr_atomic_inc32(&next_id));
+ }
+ ap_set_module_config(r->request_config, &log_forensic_module, (char *)id);
+
+ h.p = r->pool;
+ h.count = 0;
+
+ apr_table_do(count_headers, &h, r->headers_in, NULL);
+
+ h.count += 1+strlen(id)+1+count_string(r->the_request)+1+1;
+ h.log = apr_palloc(r->pool, h.count);
+ h.pos = h.log;
+ h.end = h.log+h.count;
+
+ *h.pos++ = '+';
+ strcpy(h.pos, id);
+ h.pos += strlen(h.pos);
+ *h.pos++ = '|';
+ h.pos = log_escape(h.pos, h.end, r->the_request);
+
+ apr_table_do(log_headers, &h, r->headers_in, NULL);
+
+ ap_assert(h.pos < h.end);
+ *h.pos++ = '\n';
+
+ n = h.count-1;
+ rv = apr_file_write(cfg->fd, h.log, &n);
+ ap_assert(rv == APR_SUCCESS && n == h.count-1);
+
+ apr_table_setn(r->notes, "forensic-id", id);
+
+ return OK;
+}
+
+static int log_after(request_rec *r)
+{
+ fcfg *cfg = ap_get_module_config(r->server->module_config,
+ &log_forensic_module);
+ const char *id = ap_get_module_config(r->request_config,
+ &log_forensic_module);
+ char *s;
+ apr_size_t l, n;
+ apr_status_t rv;
+
+ if (!cfg->fd || id == NULL) {
+ return DECLINED;
+ }
+
+ s = apr_pstrcat(r->pool, "-", id, "\n", NULL);
+ l = n = strlen(s);
+ rv = apr_file_write(cfg->fd, s, &n);
+ ap_assert(rv == APR_SUCCESS && n == l);
+
+ return OK;
+}
+
+static const char *set_forensic_log(cmd_parms *cmd, void *dummy, const char *fn)
+{
+ fcfg *cfg = ap_get_module_config(cmd->server->module_config,
+ &log_forensic_module);
+
+ cfg->logname = fn;
+ return NULL;
+}
+
+static const command_rec forensic_log_cmds[] =
+{
+ AP_INIT_TAKE1("ForensicLog", set_forensic_log, NULL, RSRC_CONF,
+ "the filename of the forensic log"),
+ { NULL }
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+ static const char * const pre[] = { "mod_unique_id.c", NULL };
+
+ ap_hook_open_logs(log_init,NULL,NULL,APR_HOOK_MIDDLE);
+ ap_hook_post_read_request(log_before,pre,NULL,APR_HOOK_REALLY_FIRST);
+ ap_hook_log_transaction(log_after,NULL,NULL,APR_HOOK_REALLY_LAST);
+}
+
+AP_DECLARE_MODULE(log_forensic) =
+{
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-dir config */
+ NULL, /* merge per-dir config */
+ make_forensic_log_scfg, /* server config */
+ merge_forensic_log_scfg, /* merge server config */
+ forensic_log_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/loggers/mod_log_forensic.dep b/modules/loggers/mod_log_forensic.dep
new file mode 100644
index 0000000..2337156
--- /dev/null
+++ b/modules/loggers/mod_log_forensic.dep
@@ -0,0 +1,53 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_log_forensic.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_log_forensic.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\server\test_char.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_atomic.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/loggers/mod_log_forensic.dsp b/modules/loggers/mod_log_forensic.dsp
new file mode 100644
index 0000000..cdf7c21
--- /dev/null
+++ b/modules/loggers/mod_log_forensic.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_log_forensic" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_log_forensic - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_log_forensic.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_log_forensic.mak" CFG="mod_log_forensic - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_log_forensic - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_log_forensic - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_log_forensic - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../server" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_log_forensic_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_log_forensic.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_log_forensic.so" /d LONG_NAME="log_forensic_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_log_forensic.so" /base:@..\..\os\win32\BaseAddr.ref,mod_log_forensic.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_log_forensic.so" /base:@..\..\os\win32\BaseAddr.ref,mod_log_forensic.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_log_forensic.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_log_forensic - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../server" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_log_forensic_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_log_forensic.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_log_forensic.so" /d LONG_NAME="log_forensic_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_log_forensic.so" /base:@..\..\os\win32\BaseAddr.ref,mod_log_forensic.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_log_forensic.so" /base:@..\..\os\win32\BaseAddr.ref,mod_log_forensic.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_log_forensic.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_log_forensic - Win32 Release"
+# Name "mod_log_forensic - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_log_forensic.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/loggers/mod_log_forensic.exp b/modules/loggers/mod_log_forensic.exp
new file mode 100644
index 0000000..92f5075
--- /dev/null
+++ b/modules/loggers/mod_log_forensic.exp
@@ -0,0 +1 @@
+log_forensic_module
diff --git a/modules/loggers/mod_log_forensic.mak b/modules/loggers/mod_log_forensic.mak
new file mode 100644
index 0000000..0ad2e54
--- /dev/null
+++ b/modules/loggers/mod_log_forensic.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_log_forensic.dsp
+!IF "$(CFG)" == ""
+CFG=mod_log_forensic - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_log_forensic - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_log_forensic - Win32 Release" && "$(CFG)" != "mod_log_forensic - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_log_forensic.mak" CFG="mod_log_forensic - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_log_forensic - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_log_forensic - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_log_forensic - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_log_forensic.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_log_forensic.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_log_forensic.obj"
+ -@erase "$(INTDIR)\mod_log_forensic.res"
+ -@erase "$(INTDIR)\mod_log_forensic_src.idb"
+ -@erase "$(INTDIR)\mod_log_forensic_src.pdb"
+ -@erase "$(OUTDIR)\mod_log_forensic.exp"
+ -@erase "$(OUTDIR)\mod_log_forensic.lib"
+ -@erase "$(OUTDIR)\mod_log_forensic.pdb"
+ -@erase "$(OUTDIR)\mod_log_forensic.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../server" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_log_forensic_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_log_forensic.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_log_forensic.so" /d LONG_NAME="log_forensic_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_log_forensic.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_log_forensic.pdb" /debug /out:"$(OUTDIR)\mod_log_forensic.so" /implib:"$(OUTDIR)\mod_log_forensic.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_log_forensic.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_log_forensic.obj" \
+ "$(INTDIR)\mod_log_forensic.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_log_forensic.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_log_forensic.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_log_forensic.so"
+ if exist .\Release\mod_log_forensic.so.manifest mt.exe -manifest .\Release\mod_log_forensic.so.manifest -outputresource:.\Release\mod_log_forensic.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_log_forensic - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_log_forensic.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_log_forensic.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_log_forensic.obj"
+ -@erase "$(INTDIR)\mod_log_forensic.res"
+ -@erase "$(INTDIR)\mod_log_forensic_src.idb"
+ -@erase "$(INTDIR)\mod_log_forensic_src.pdb"
+ -@erase "$(OUTDIR)\mod_log_forensic.exp"
+ -@erase "$(OUTDIR)\mod_log_forensic.lib"
+ -@erase "$(OUTDIR)\mod_log_forensic.pdb"
+ -@erase "$(OUTDIR)\mod_log_forensic.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../server" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_log_forensic_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_log_forensic.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_log_forensic.so" /d LONG_NAME="log_forensic_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_log_forensic.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_log_forensic.pdb" /debug /out:"$(OUTDIR)\mod_log_forensic.so" /implib:"$(OUTDIR)\mod_log_forensic.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_log_forensic.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_log_forensic.obj" \
+ "$(INTDIR)\mod_log_forensic.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_log_forensic.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_log_forensic.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_log_forensic.so"
+ if exist .\Debug\mod_log_forensic.so.manifest mt.exe -manifest .\Debug\mod_log_forensic.so.manifest -outputresource:.\Debug\mod_log_forensic.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_log_forensic.dep")
+!INCLUDE "mod_log_forensic.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_log_forensic.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_log_forensic - Win32 Release" || "$(CFG)" == "mod_log_forensic - Win32 Debug"
+
+!IF "$(CFG)" == "mod_log_forensic - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\loggers"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\loggers"
+
+!ELSEIF "$(CFG)" == "mod_log_forensic - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\loggers"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\loggers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_log_forensic - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\loggers"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\loggers"
+
+!ELSEIF "$(CFG)" == "mod_log_forensic - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\loggers"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\loggers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_log_forensic - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\loggers"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\loggers"
+
+!ELSEIF "$(CFG)" == "mod_log_forensic - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\loggers"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\loggers"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_log_forensic - Win32 Release"
+
+
+"$(INTDIR)\mod_log_forensic.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_log_forensic.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_log_forensic.so" /d LONG_NAME="log_forensic_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_log_forensic - Win32 Debug"
+
+
+"$(INTDIR)\mod_log_forensic.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_log_forensic.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_log_forensic.so" /d LONG_NAME="log_forensic_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_log_forensic.c
+
+"$(INTDIR)\mod_log_forensic.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/loggers/mod_logio.c b/modules/loggers/mod_logio.c
new file mode 100644
index 0000000..b609f7f
--- /dev/null
+++ b/modules/loggers/mod_logio.c
@@ -0,0 +1,284 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Written by Bojan Smojver <bojan rexursive.com>.
+ */
+
+#include "apr_strings.h"
+#include "apr_lib.h"
+#include "apr_hash.h"
+#include "apr_optional.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "ap_config.h"
+#include "mod_log_config.h"
+#include "httpd.h"
+#include "http_core.h"
+#include "http_config.h"
+#include "http_connection.h"
+#include "http_protocol.h"
+#include "http_request.h"
+
+module AP_MODULE_DECLARE_DATA logio_module;
+
+static const char logio_filter_name[] = "LOG_INPUT_OUTPUT";
+static const char logio_ttfb_filter_name[] = "LOGIO_TTFB_OUT";
+
+/*
+ * Logging of input and output config...
+ */
+
+typedef struct logio_config_t {
+ apr_off_t bytes_in;
+ apr_off_t bytes_out;
+ apr_off_t bytes_last_request;
+} logio_config_t;
+
+typedef struct logio_dirconf_t {
+ unsigned int track_ttfb:1;
+} logio_dirconf_t;
+
+typedef struct logio_req_t {
+ apr_time_t ttfb;
+} logio_req_t;
+
+
+
+/*
+ * Optional function for the core to add to bytes_out
+ */
+
+static void ap_logio_add_bytes_out(conn_rec *c, apr_off_t bytes)
+{
+ logio_config_t *cf = ap_get_module_config(c->conn_config, &logio_module);
+ cf->bytes_out += bytes;
+}
+
+/*
+ * Optional function for modules to adjust bytes_in
+ */
+
+static void ap_logio_add_bytes_in(conn_rec *c, apr_off_t bytes)
+{
+ logio_config_t *cf = ap_get_module_config(c->conn_config, &logio_module);
+
+ cf->bytes_in += bytes;
+}
+
+/*
+ * Optional function to get total byte count of last request for
+ * ap_increment_counts.
+ */
+
+static apr_off_t ap_logio_get_last_bytes(conn_rec *c)
+{
+ logio_config_t *cf = ap_get_module_config(c->conn_config, &logio_module);
+
+ return cf->bytes_last_request;
+}
+
+/*
+ * Format items...
+ */
+
+static const char *log_bytes_in(request_rec *r, char *a)
+{
+ logio_config_t *cf = ap_get_module_config(r->connection->conn_config,
+ &logio_module);
+
+ return apr_off_t_toa(r->pool, cf->bytes_in);
+}
+
+static const char *log_bytes_out(request_rec *r, char *a)
+{
+ logio_config_t *cf = ap_get_module_config(r->connection->conn_config,
+ &logio_module);
+
+ return apr_off_t_toa(r->pool, cf->bytes_out);
+}
+
+static const char *log_bytes_combined(request_rec *r, char *a)
+{
+ logio_config_t *cf = ap_get_module_config(r->connection->conn_config,
+ &logio_module);
+
+ return apr_off_t_toa(r->pool, cf->bytes_out + cf->bytes_in);
+}
+
+static const char *log_ttfb(request_rec *r, char *a)
+{
+ logio_req_t *rconf = ap_get_module_config(r->request_config,
+ &logio_module);
+
+ if (!rconf || !rconf->ttfb) {
+ return "-";
+ }
+
+ return apr_psprintf(r->pool, "%" APR_TIME_T_FMT, rconf->ttfb);
+}
+/*
+ * Reset counters after logging...
+ */
+
+static int logio_transaction(request_rec *r)
+{
+ logio_config_t *cf = ap_get_module_config(r->connection->conn_config,
+ &logio_module);
+
+ /* need to save byte count of last request for ap_increment_counts */
+ cf->bytes_last_request = cf->bytes_in + cf->bytes_out;
+ cf->bytes_in = cf->bytes_out = 0;
+
+ return OK;
+}
+
+/*
+ * Logging of input filter...
+ */
+
+static apr_status_t logio_in_filter(ap_filter_t *f,
+ apr_bucket_brigade *bb,
+ ap_input_mode_t mode,
+ apr_read_type_e block,
+ apr_off_t readbytes)
+{
+ apr_off_t length;
+ apr_status_t status;
+ logio_config_t *cf = ap_get_module_config(f->c->conn_config, &logio_module);
+
+ status = ap_get_brigade(f->next, bb, mode, block, readbytes);
+
+ apr_brigade_length (bb, 0, &length);
+
+ if (length > 0)
+ cf->bytes_in += length;
+
+ return status;
+}
+
+/*
+ * The hooks...
+ */
+
+static int logio_pre_conn(conn_rec *c, void *csd)
+{
+ logio_config_t *cf = apr_pcalloc(c->pool, sizeof(*cf));
+
+ ap_set_module_config(c->conn_config, &logio_module, cf);
+
+ ap_add_input_filter(logio_filter_name, NULL, NULL, c);
+
+ return OK;
+}
+
+static int logio_pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp)
+{
+ APR_OPTIONAL_FN_TYPE(ap_register_log_handler) *log_pfn_register;
+
+ log_pfn_register = APR_RETRIEVE_OPTIONAL_FN(ap_register_log_handler);
+
+ if (log_pfn_register) {
+ log_pfn_register(p, "I", log_bytes_in, 0);
+ log_pfn_register(p, "O", log_bytes_out, 0);
+ log_pfn_register(p, "S", log_bytes_combined, 0);
+ log_pfn_register(p, "^FB", log_ttfb, 0);
+ }
+
+ return OK;
+}
+
+static apr_status_t logio_ttfb_filter(ap_filter_t *f, apr_bucket_brigade *b)
+{
+ request_rec *r = f->r;
+ logio_dirconf_t *conf = ap_get_module_config(r->per_dir_config,
+ &logio_module);
+ if (conf && conf->track_ttfb) {
+ logio_req_t *rconf = ap_get_module_config(r->request_config,
+ &logio_module);
+ if (rconf == NULL) {
+ rconf = apr_pcalloc(r->pool, sizeof(logio_req_t));
+ rconf->ttfb = apr_time_now() - r->request_time;
+ ap_set_module_config(r->request_config, &logio_module, rconf);
+ }
+ }
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, b);
+}
+
+static void logio_insert_filter(request_rec * r)
+{
+ logio_dirconf_t *conf = ap_get_module_config(r->per_dir_config,
+ &logio_module);
+ if (conf->track_ttfb) {
+ ap_add_output_filter(logio_ttfb_filter_name, NULL, r, r->connection);
+ }
+}
+
+static const char *logio_track_ttfb(cmd_parms *cmd, void *in_dir_config, int arg)
+{
+ logio_dirconf_t *dir_config = in_dir_config;
+ dir_config->track_ttfb = arg;
+ return NULL;
+}
+
+static void *create_logio_dirconf (apr_pool_t *p, char *dummy)
+{
+ logio_dirconf_t *new =
+ (logio_dirconf_t *) apr_pcalloc(p, sizeof(logio_dirconf_t));
+ return (void *) new;
+}
+
+
+static const command_rec logio_cmds[] = {
+ AP_INIT_FLAG ("LogIOTrackTTFB", logio_track_ttfb, NULL, OR_ALL,
+ "Set to 'ON' to enable tracking time to first byte"),
+ {NULL}
+};
+
+
+static void register_hooks(apr_pool_t *p)
+{
+ static const char *pre[] = { "mod_log_config.c", NULL };
+
+ ap_hook_pre_connection(logio_pre_conn, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_pre_config(logio_pre_config, NULL, NULL, APR_HOOK_REALLY_FIRST);
+ ap_hook_log_transaction(logio_transaction, pre, NULL, APR_HOOK_MIDDLE);
+
+ ap_register_input_filter(logio_filter_name, logio_in_filter, NULL,
+ AP_FTYPE_NETWORK - 1);
+
+ ap_hook_insert_filter(logio_insert_filter, NULL, NULL, APR_HOOK_LAST);
+ ap_register_output_filter(logio_ttfb_filter_name, logio_ttfb_filter, NULL,
+ AP_FTYPE_RESOURCE);
+
+ APR_REGISTER_OPTIONAL_FN(ap_logio_add_bytes_out);
+ APR_REGISTER_OPTIONAL_FN(ap_logio_add_bytes_in);
+ APR_REGISTER_OPTIONAL_FN(ap_logio_get_last_bytes);
+}
+
+AP_DECLARE_MODULE(logio) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_logio_dirconf, /* create per-dir config */
+ NULL, /* merge per-dir config */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ logio_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/loggers/mod_logio.dep b/modules/loggers/mod_logio.dep
new file mode 100644
index 0000000..cc70d81
--- /dev/null
+++ b/modules/loggers/mod_logio.dep
@@ -0,0 +1,59 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_logio.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_logio.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_log_config.h"\
+
diff --git a/modules/loggers/mod_logio.dsp b/modules/loggers/mod_logio.dsp
new file mode 100644
index 0000000..f5a8186
--- /dev/null
+++ b/modules/loggers/mod_logio.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_logio" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_logio - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_logio.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_logio.mak" CFG="mod_logio - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_logio - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_logio - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_logio - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_logio_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_logio.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_logio.so" /d LONG_NAME="logio_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_logio.so" /base:@..\..\os\win32\BaseAddr.ref,mod_logio.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_logio.so" /base:@..\..\os\win32\BaseAddr.ref,mod_logio.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_logio.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_logio - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_logio_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_logio.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_logio.so" /d LONG_NAME="logio_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_logio.so" /base:@..\..\os\win32\BaseAddr.ref,mod_logio.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_logio.so" /base:@..\..\os\win32\BaseAddr.ref,mod_logio.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_logio.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_logio - Win32 Release"
+# Name "mod_logio - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_logio.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/loggers/mod_logio.mak b/modules/loggers/mod_logio.mak
new file mode 100644
index 0000000..363e848
--- /dev/null
+++ b/modules/loggers/mod_logio.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_logio.dsp
+!IF "$(CFG)" == ""
+CFG=mod_logio - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_logio - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_logio - Win32 Release" && "$(CFG)" != "mod_logio - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_logio.mak" CFG="mod_logio - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_logio - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_logio - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_logio - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_logio.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_logio.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_logio.obj"
+ -@erase "$(INTDIR)\mod_logio.res"
+ -@erase "$(INTDIR)\mod_logio_src.idb"
+ -@erase "$(INTDIR)\mod_logio_src.pdb"
+ -@erase "$(OUTDIR)\mod_logio.exp"
+ -@erase "$(OUTDIR)\mod_logio.lib"
+ -@erase "$(OUTDIR)\mod_logio.pdb"
+ -@erase "$(OUTDIR)\mod_logio.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_logio_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_logio.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_logio.so" /d LONG_NAME="logio_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_logio.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_logio.pdb" /debug /out:"$(OUTDIR)\mod_logio.so" /implib:"$(OUTDIR)\mod_logio.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_logio.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_logio.obj" \
+ "$(INTDIR)\mod_logio.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_logio.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_logio.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_logio.so"
+ if exist .\Release\mod_logio.so.manifest mt.exe -manifest .\Release\mod_logio.so.manifest -outputresource:.\Release\mod_logio.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_logio - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_logio.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_logio.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_logio.obj"
+ -@erase "$(INTDIR)\mod_logio.res"
+ -@erase "$(INTDIR)\mod_logio_src.idb"
+ -@erase "$(INTDIR)\mod_logio_src.pdb"
+ -@erase "$(OUTDIR)\mod_logio.exp"
+ -@erase "$(OUTDIR)\mod_logio.lib"
+ -@erase "$(OUTDIR)\mod_logio.pdb"
+ -@erase "$(OUTDIR)\mod_logio.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_logio_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_logio.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_logio.so" /d LONG_NAME="logio_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_logio.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_logio.pdb" /debug /out:"$(OUTDIR)\mod_logio.so" /implib:"$(OUTDIR)\mod_logio.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_logio.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_logio.obj" \
+ "$(INTDIR)\mod_logio.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_logio.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_logio.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_logio.so"
+ if exist .\Debug\mod_logio.so.manifest mt.exe -manifest .\Debug\mod_logio.so.manifest -outputresource:.\Debug\mod_logio.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_logio.dep")
+!INCLUDE "mod_logio.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_logio.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_logio - Win32 Release" || "$(CFG)" == "mod_logio - Win32 Debug"
+
+!IF "$(CFG)" == "mod_logio - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\loggers"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\loggers"
+
+!ELSEIF "$(CFG)" == "mod_logio - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\loggers"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\loggers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_logio - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\loggers"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\loggers"
+
+!ELSEIF "$(CFG)" == "mod_logio - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\loggers"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\loggers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_logio - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\loggers"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\loggers"
+
+!ELSEIF "$(CFG)" == "mod_logio - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\loggers"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\loggers"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_logio - Win32 Release"
+
+
+"$(INTDIR)\mod_logio.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_logio.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_logio.so" /d LONG_NAME="logio_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_logio - Win32 Debug"
+
+
+"$(INTDIR)\mod_logio.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_logio.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_logio.so" /d LONG_NAME="logio_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_logio.c
+
+"$(INTDIR)\mod_logio.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/lua/Makefile.in b/modules/lua/Makefile.in
new file mode 100644
index 0000000..7c5c149
--- /dev/null
+++ b/modules/lua/Makefile.in
@@ -0,0 +1,3 @@
+# a modules Makefile has no explicit targets -- they will be defined by
+# whatever modules are enabled. just grab special.mk to deal with this.
+include $(top_srcdir)/build/special.mk
diff --git a/modules/lua/NWGNUmakefile b/modules/lua/NWGNUmakefile
new file mode 100644
index 0000000..24f2703
--- /dev/null
+++ b/modules/lua/NWGNUmakefile
@@ -0,0 +1,287 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(SRC)/include \
+ $(STDMOD)/database \
+ $(STDMOD)/http \
+ $(STDMOD)/ssl \
+ $(NWOS) \
+ $(LUASRC)/src \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ -opt nointrinsics \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ -DLUA_COMPAT_ALL \
+ -DLUA_COMPAT_5_2 \
+ -DLUA_COMPAT_5_1 \
+ -DLUA_COMPAT_MODULE \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ -L$(OBJDIR) \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = mod_lua
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) LUA Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = LUA Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 131072
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(OBJDIR)/lua.lib \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_lua.o \
+ $(OBJDIR)/lua_apr.o \
+ $(OBJDIR)/lua_config.o \
+ $(OBJDIR)/lua_passwd.o \
+ $(OBJDIR)/lua_request.o \
+ $(OBJDIR)/lua_vmprep.o \
+ $(OBJDIR)/lua_dbd.o \
+ $(OBJDIR)/libprews.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(OBJDIR)/lua.lib \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+# Don't link with Winsock if standard sockets are being used
+ifndef USE_STDSOCKETS
+FILES_nlm_Ximports += @ws2nlm.imp \
+ $(EOLIST)
+endif
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ lua_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+UNWANTED = $(LUASRC)/src/lua.c $(LUASRC)/src/luac.c $(LUASRC)/src/print.c
+
+FILES_lib_objs = \
+ $(patsubst $(LUASRC)/src/%.c,$(OBJDIR)/%.o, $(filter-out $(UNWANTED), $(wildcard $(LUASRC)/src/*.c))) \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+#
+# Any specialized rules here
+#
+
+vpath %.c $(LUASRC)/src ../arch/netware
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/lua/README b/modules/lua/README
new file mode 100644
index 0000000..e0106e8
--- /dev/null
+++ b/modules/lua/README
@@ -0,0 +1,54 @@
+-*- mode:org -*-
+* Requirements:
+** lua 5.1, 5.2, 5.3 ( http://www.lua.org/ ) or LuaJIT 2.x ( http://www.luajit.org/ )
+** Apache HTTPD 2.4 ( http://httpd.apache.org/ ) or higher
+
+* Documentation
+ See docs/README
+
+* Building
+ For now, see docs/building-from-subversion.txt
+
+
+* Task List
+** TODO Use r->file to determine file, doing rewriting in translate_name
+** TODO Provide means to get useful output from lua errors in response body
+ Probably have to put it on the vm spec for pre-handler errors, as
+ it is pre-handler, will prolly be on the request_config somewhere,
+ but sometimes cannot put there, so... fun
+** TODO Mapping in the server_rec
+** TODO Figure out how reentrancy works regarding filter chain stuff.
+ Do we need new "threads"?
+** TODO: Flatten LuaHook* to LuaHook phase file fn ?
+** TODO: document or remove block sections
+** TODO: test per-dir behavior of block sections
+** TODO: Suppress internal details (fs path to scripts, etc) in error responses
+** TODO: Check whether we can tighten the mode flag in lua_load(),
+ luaL_loadfile() an dluaL_loadbuffer() from NULL (="bt")
+ to e.g. "t".
+
+* License
+ Apache License, Version 2.0,
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ See NOTICE file for more information
+
+* Problems and Patches:
+ Please use dev@httpd.apache.org for discussing mod_lua development
+ To subscribe send email to dev-subscribe@httpd.apache.org
+ Note that this is for development discussion, not user support :-)
+
+* Contributors Include
+** Brian McCallister
+** Paul Querna
+** Garrett Rooney
+** Martin Traverso
+** Brian Akins
+** Justin Erenkrantz
+** Philip M. Gollucci
+** Stefan Fritsch
+** Eric Covener
+** Daniel Gruno
+** Günter Knauf
+** Jim Jagielski
diff --git a/modules/lua/config.m4 b/modules/lua/config.m4
new file mode 100644
index 0000000..40ae6f0
--- /dev/null
+++ b/modules/lua/config.m4
@@ -0,0 +1,130 @@
+
+APACHE_MODPATH_INIT(lua)
+
+dnl CHECK_LUA_PATH(PREFIX, INCLUDE-PATH, LIB-PATH, LIB-NAME)
+dnl
+dnl Checks for a specific version of the Lua libraries. Use CHECK_LUA instead,
+dnl which will call this macro.
+dnl
+dnl Sets LUA_CFLAGS and LUA_LIBS, and breaks from its containing loop, if the
+dnl check succeeds.
+AC_DEFUN([CHECK_LUA_PATH], [dnl
+ AC_MSG_CHECKING([for lua.h in $1/$2])
+ if test -f $1/$2/lua.h; then
+ AC_MSG_RESULT([yes])
+ save_CFLAGS=$CFLAGS
+ save_LDFLAGS=$LDFLAGS
+ CFLAGS="$CFLAGS"
+ LDFLAGS="-L$1/$3 $LDFLAGS $lib_m"
+ AC_CHECK_LIB($4, luaL_newstate, [
+ LUA_LIBS="-L$1/$3 -l$4 $lib_m"
+ if test "x$ap_platform_runtime_link_flag" != "x"; then
+ APR_ADDTO(LUA_LIBS, [$ap_platform_runtime_link_flag$1/$3])
+ fi
+ LUA_CFLAGS="-I$1/$2"
+ ])
+ CFLAGS=$save_CFLAGS
+ LDFLAGS=$save_LDFLAGS
+
+ if test -n "${LUA_LIBS}"; then
+ break
+ fi
+ else
+ AC_MSG_RESULT([no])
+ fi
+])
+
+dnl Check for Lua Libraries
+dnl CHECK_LUA(ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND])
+dnl Sets:
+dnl LUA_CFLAGS
+dnl LUA_LIBS
+AC_DEFUN([CHECK_LUA],
+[dnl
+
+AC_ARG_WITH(
+ lua,
+ [AC_HELP_STRING([--with-lua=PATH],[Path to the Lua installation prefix])],
+ lua_path="$withval",
+ :)
+
+dnl # Determine lua lib directory
+if test -z "$lua_path"; then
+ test_paths=". /usr/local /usr"
+else
+ test_paths="${lua_path}"
+fi
+
+for pklua in lua lua5.4 lua5.3 lua5.2 lua5.1; do
+ if test -n "$PKGCONFIG" -a -z "$lua_path" \
+ && $PKGCONFIG --atleast-version=5.1 $pklua; then
+ LUA_LIBS="`$PKGCONFIG --libs $pklua`"
+ LUA_CFLAGS="`$PKGCONFIG --cflags $pklua`"
+ LUA_VERSION="`$PKGCONFIG --modversion $pklua`"
+ AC_MSG_NOTICE([using Lua $LUA_VERSION configuration from pkg-config])
+ break
+ fi
+done
+
+if test -z "$LUA_VERSION"; then
+ AC_CHECK_LIB(m, pow, lib_m="-lm")
+ AC_CHECK_LIB(m, sqrt, lib_m="-lm")
+ for x in $test_paths ; do
+ CHECK_LUA_PATH([${x}], [include/lua-5.4], [lib/lua-5.4], [lua-5.4])
+ CHECK_LUA_PATH([${x}], [include/lua5.4], [lib], [lua5.4])
+ CHECK_LUA_PATH([${x}], [include/lua54], [lib/lua54], [lua])
+
+ CHECK_LUA_PATH([${x}], [include/lua-5.3], [lib/lua-5.3], [lua-5.3])
+ CHECK_LUA_PATH([${x}], [include/lua5.3], [lib], [lua5.3])
+ CHECK_LUA_PATH([${x}], [include/lua53], [lib/lua53], [lua])
+
+ CHECK_LUA_PATH([${x}], [include], [lib], [lua])
+
+ CHECK_LUA_PATH([${x}], [include/lua-5.2], [lib/lua-5.2], [lua-5.2])
+ CHECK_LUA_PATH([${x}], [include/lua5.2], [lib], [lua5.2])
+ CHECK_LUA_PATH([${x}], [include/lua52], [lib/lua52], [lua])
+
+ CHECK_LUA_PATH([${x}], [include/lua-5.1], [lib/lua-5.1], [lua-5.1])
+ CHECK_LUA_PATH([${x}], [include/lua5.1], [lib], [lua5.1])
+ CHECK_LUA_PATH([${x}], [include/lua51], [lib/lua51], [lua])
+ done
+fi
+
+AC_SUBST(LUA_LIBS)
+AC_SUBST(LUA_CFLAGS)
+
+if test -z "${LUA_LIBS}"; then
+ AC_MSG_WARN([*** Lua 5.4 5.3 5.2 or 5.1 library not found.])
+ ifelse([$2], ,
+ enable_lua="no"
+ if test -z "${lua_path}"; then
+ AC_MSG_WARN([Lua 5.4 5.3 5.2 or 5.1 library is required])
+ else
+ AC_MSG_ERROR([Lua 5.4 5.3 5.2 or 5.1 library is required])
+ fi,
+ $2)
+else
+ AC_MSG_NOTICE([using '${LUA_LIBS}' for Lua Library])
+ AC_ARG_ENABLE(luajit,APACHE_HELP_STRING(--enable-luajit,Enable LuaJit Support),
+ [
+ if test "$enableval" = "yes"; then
+ APR_ADDTO(MOD_CPPFLAGS, ["-DAP_ENABLE_LUAJIT"])
+ fi
+ ])
+ ifelse([$1], , , $1)
+fi
+])
+
+lua_objects="lua_apr.lo lua_config.lo mod_lua.lo lua_request.lo lua_vmprep.lo lua_dbd.lo lua_passwd.lo"
+
+APACHE_MODULE(lua, Apache Lua Framework, $lua_objects, , , [
+ CHECK_LUA()
+ if test "x$enable_lua" != "xno" ; then
+ APR_ADDTO(MOD_INCLUDES, [$LUA_CFLAGS])
+ APR_ADDTO(MOD_LUA_LDADD, [$LUA_LIBS $CRYPT_LIBS])
+ fi
+])
+
+APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/$modpath_current])
+
+APACHE_MODPATH_FINISH
diff --git a/modules/lua/docs/README b/modules/lua/docs/README
new file mode 100644
index 0000000..632945a
--- /dev/null
+++ b/modules/lua/docs/README
@@ -0,0 +1,12 @@
+Index of documents:
+ building-from-subversion.txt
+ Basic build instructions
+
+ basic-configuration.txt
+ Getting mod_wombat up and running
+
+ running-developer-tests.txt
+ How to set up and run the developer and regression tests
+
+ writing-handlers.txt
+ basics on writing handlers in mod_wombat \ No newline at end of file
diff --git a/modules/lua/docs/basic-configuration.txt b/modules/lua/docs/basic-configuration.txt
new file mode 100644
index 0000000..b837698
--- /dev/null
+++ b/modules/lua/docs/basic-configuration.txt
@@ -0,0 +1,141 @@
+See sample_httpd.conf for examples
+
+The basic module loading directive is
+ LoadModule lua_module modules/mod_lua.so
+
+The handler name is "lua-script" so you can use the normal
+AddHandler directive, such as "AddHandler lua-script .lua" to
+set anything ending in .lua to use mod_lua to evaluate
+
+mod_lua exports several additional directives:
+
+ LuaRoot /path/to/a/directory
+ Specify the base path which will be used to evaluate all
+ relative paths within mod_lua. If not specified they
+ will be resolved relative to the current working directory,
+ which may not always work well for a server.
+
+ LuaScope once|request|conn|server [max|min max]
+ Specify the lifecycle scope of the Lua interpreter which will
+ be used by handlers in this "Directory." The default is "once"
+
+ once: use the interpreter once and throw it away.
+
+ request: use the interpreter to handle anything based on
+ the same file within this request, which is also
+ request scoped.
+
+ conn: Same as request but attached to the connection_rec
+
+ server: This one is different than others because the
+ server scope is quite long lived, and multiple threads
+ will have the same server_rec. To accommodate this
+ server scoped interpreter are stored in an apr
+ resource list. The min and max arguments are intended
+ to specify the pool size, but are unused at this time.
+
+ LuaMapHandler uri-pattern /path/to/lua/script.lua [function-name]
+ This directive matches a uri pattern to invoke a specific
+ handler function in a specific file. It uses PCRE regular
+ expressions to match the uri, and supports interpolating
+ match groups into both the file path and the function name
+ be careful writing your regular expressions to avoid security
+ issues.
+
+ Examples:
+ LuaMapHandler /(\w+)/(/w+) /scripts/$1.lua handle_$2
+ This would match uri's such as /photos/show?id=9
+ to the file /scripts/photos.lua and invoke the
+ handler function handle_show on the lua vm after
+ loading that file.
+
+ LuaMapHandler /bingo /scripts/wombat.lua
+ This would invoke the "handle" function, which
+ is the default if no specific function name is
+ provided.
+
+ LuaPackagePath /path/to/include/?.lua
+ Add a path to lua's module search path. Follows the same
+ conventions as lua. This just munges the package.path in the
+ lua vms.
+
+ Examples:
+ LuaPackagePath /scripts/lib/?.lua
+ LuaPackagePath /scripts/lib/?/init.lua
+
+ LuaPackageCPath /path/to/include/?.soa
+ Add a path to lua's shared library search path. Follows the same
+ conventions as lua. This just munges the package.cpath in the
+ lua vms.
+
+ Examples:
+ LuaPackagePath /scripts/lib/?.so
+
+ LuaCodeCache stat|forever|never
+ Specify the behavior of the in-memory code cache. The default
+ is stat, which stats the top level script (not any included
+ ones) each time that file is needed, and reloads it if the
+ modified time indicates it is newer than the one it has
+ already loaded. The other values cause it to keep the file
+ cached forever (don't stat and replace) or to never cache the
+ file.
+
+ In general stat or forever is good production and stat or never
+ for development.
+
+ Examples:
+ LuaCodeCache stat
+ LuaCodeCache forever
+ LuaCodeCache never
+
+ LuaHookTranslateName /path/to/lua/script.lua hook_function_name
+ Add a hook (at APR_HOOK_MIDDLE) to the translate name phase of
+ request processing. The hook function receives a single
+ argument, the request_rec, and should return a status code,
+ which is either an HTTP error code, or the constants defined
+ in the apache2 module: apache2.OK, apache2.DECLINED, or
+ apache2.DONE.
+
+ For those new to hooks, basically each hook will be invoked
+ until one of them returns apache2.OK. If your hook doesn't
+ want to do the translation it should just return
+ apache2.DECLINED. If the request should stop processing, then
+ return apache2.DONE.
+
+ Example:
+ LuaHookTranslateName /scripts/conf/hooks.lua silly_mapper
+
+ -- /scripts/conf/hooks.lua --
+ function silly_mapper(r)
+ if r.uri == "/" then
+ r.file = "/var/www/home.lua"
+ return apache2.OK
+ else
+ return apache2.DECLINED
+ end
+ end
+
+ LuaHookFixups /path/to/lua/script.lua hook_function_name
+ Just like LuaHookTranslateName, but executed at the fixups phase
+
+ LuaHookMapToStorage /path/to/lua/script.lua hook_function_name
+ ...
+
+ LuaHookCheckUserID /path/to/lua/script.lua hook_function_name
+ ...
+
+ LuaHookTypeChecker /path/to/lua/script.lua hook_function_name
+ ...
+
+ LuaHookAuthChecker /path/to/lua/script.lua hook_function_name
+ ...
+
+ LuaHookAccessChecker /path/to/lua/script.lua hook_function_name
+ ...
+
+ LuaHookAuthChecker /path/to/lua/script.lua hook_function_name
+ ...
+
+ LuaHookInsertFilter /path/to/lua/script.lua hook_function_name
+ Not Yet Implemented
+
diff --git a/modules/lua/docs/building-from-subversion.txt b/modules/lua/docs/building-from-subversion.txt
new file mode 100644
index 0000000..98da7bf
--- /dev/null
+++ b/modules/lua/docs/building-from-subversion.txt
@@ -0,0 +1,72 @@
+Install Lua 5.1
+ http://www.lua.org/download.html
+
+ Lua does not use autoconf for compiling. This means that you do not use
+ ./configure. It has good build instructions, though, so hopefully things
+ will go smoothly.
+
+ I like to change the directory Lua installs to. In order to do this you
+ need to set LUA_TOP in the configuration makefile for Lua. For these
+ instructions I have set LUA_TOP to /Users/brianm/.opt/lua-5.1.2 -- you
+ will see this directory referred to later.
+
+
+Install Apache HTTPD 2.2
+ http://httpd.apache.org/download.cgi
+
+ You can build apache pretty much any way you like, as long as you enable
+ dynamic module loading (--enable-so) so that mod_wombat can be loaded.
+
+ You may user (and I encourage you to!) the threaded MPMs -- mod_wombat
+ plays nicely with them.
+
+ I build it with these flags:
+
+ ./configure --prefix=/Users/brianm/.opt/httpd-2.2.4-worker-wombat \
+ --with-mpm=worker \
+ --enable-so
+
+
+Install libapreq2
+ http://httpd.apache.org/apreq/download.cgi
+ The download link is in the page body, NOT under the "Download!" link
+ in the left hand column.
+
+ Right now, mod_wombat requires libapreq2 for parsing entity bodies. This
+ dependency will probably be made optional in the near future, but for now
+ you need it.
+
+ I build it with these flags:
+
+ ./configure --prefix=/Users/brianm/.opt/libapreq2-2.0.8 \
+ --with-apache2-apxs=/Users/brianm/.opt/httpd-2.2.4-worker-wombat/bin/apxs
+
+
+Install mod_wombat from subversion
+ http://svn.apache.org/repos/asf/httpd/mod_wombat/trunk
+
+ The first step, when building from subversion, is to bootstrap autoconf.
+ To do this run the bootstrap script:
+
+ ./bootstrap
+
+ The bootstrap script may report an error that it cannot find
+ libtoolize or glibtoolize. That is fine as long as it
+ doesn't report that it cannot find both of them. The script
+ just sets up the autoconf magic.
+
+ After that, it is a normal configure and build:
+
+ ./configure --with-lua=/Users/brianm/.opt/lua-5.1.2/ \
+ --with-apxs=/Users/brianm/.opt/httpd-2.2.4-worker-wombat/bin/apxs \
+ --with-apreq2=/Users/brianm/.opt/libapreq2-2.0.8/
+
+ If compiling (make) reports an error that it cannot find the
+ libapreq2 header file, please tell me ( brianm@apache.org )
+ as this occurs under some configurations but we haven't
+ hammered down the weird things libapreq2 does with its
+ install. If you build libapreq2 with a --prefix configuration
+ option, it always seems to work.
+
+
+That is it. To configure mod_wombat, look at the basic-configuration.txt document. \ No newline at end of file
diff --git a/modules/lua/docs/running-developer-tests.txt b/modules/lua/docs/running-developer-tests.txt
new file mode 100644
index 0000000..59eb990
--- /dev/null
+++ b/modules/lua/docs/running-developer-tests.txt
@@ -0,0 +1,16 @@
+-*- mode:org -*-
+* Building mod_lua
+ The first step is to build mod_lua per the instructions in
+ building-from-subversion.txt.
+
+* Build and install LuaSocket
+ http://www.cs.princeton.edu/~diego/professional/luasocket/
+ FreeBSD: /usr/ports/net/luasocket
+
+* Running Tests
+ 1. Replace apache's httpd.conf with test/test_httpd.conf
+ 2. Customize the new httpd.conf to match your directories
+ 3. Finally, to run the tests, start apache and run:
+ $ cd test
+ $ lua ./test.lua
+ FreeBSD: lua-5.1 ./test.lua
diff --git a/modules/lua/docs/writing-handlers.txt b/modules/lua/docs/writing-handlers.txt
new file mode 100644
index 0000000..2f18c41
--- /dev/null
+++ b/modules/lua/docs/writing-handlers.txt
@@ -0,0 +1,49 @@
+mod_lua always looks to invoke a function for the handler, rather than
+just evaluating a script body CGI style. A handler function looks
+something like this:
+
+ -- example.lua --
+ require "string"
+
+ function handle_something(r)
+ r.content_type = "text/plain"
+ r:puts("Hello Lua World!\n")
+
+ if r.method == 'GET' then
+ for k, v in pairs( r:parseargs() ) do
+ r:puts( string.format("%s: %s", k, v) )
+ end
+ elseif r.method == 'POST' then
+ for k, v in pairs( r:parsebody() ) do
+ r:puts( string.format("%s: %s", k, v) )
+ end
+ else
+ r:puts("unknown HTTP method " .. r.method)
+ end
+ end
+
+This handler function just prints out the uri or form encoded
+arguments to a plaintext page.
+
+This means (and in fact encourages) that you can have multiple
+handlers (or hooks, or filters) in the same script.
+
+Data Structures:
+ request_rec:
+ the request_rec is mapped in as a userdata. It has a metatable
+ which lets you do useful things with it. For the most part it
+ has the same fields as the request_rec struct (see httpd.h
+ until we get better docs here) many of which are writeable as
+ well as readable, and has (at least) the following methods:
+
+ r:puts("hello", " world", "!") -- print to response body
+
+ -- logging functions
+ r:debug("This is a debug log message")
+ r:info("This is an info log message")
+ r:notice("This is an notice log message")
+ r:warn("This is an warn log message")
+ r:err("This is an err log message")
+ r:alert("This is an alert log message")
+ r:crit("This is an crit log message")
+ r:emerg("This is an emerg log message")
diff --git a/modules/lua/lua_apr.c b/modules/lua/lua_apr.c
new file mode 100644
index 0000000..9590fd6
--- /dev/null
+++ b/modules/lua/lua_apr.c
@@ -0,0 +1,110 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mod_lua.h"
+#include "lua_apr.h"
+APLOG_USE_MODULE(lua);
+
+req_table_t *ap_lua_check_apr_table(lua_State *L, int index)
+{
+ req_table_t* t;
+ luaL_checkudata(L, index, "Apr.Table");
+ t = lua_unboxpointer(L, index);
+ return t;
+}
+
+
+void ap_lua_push_apr_table(lua_State *L, req_table_t *t)
+{
+ lua_boxpointer(L, t);
+ luaL_getmetatable(L, "Apr.Table");
+ lua_setmetatable(L, -2);
+}
+
+static int lua_table_set(lua_State *L)
+{
+ req_table_t *t = ap_lua_check_apr_table(L, 1);
+ const char *key = luaL_checkstring(L, 2);
+ const char *val = luaL_optlstring(L, 3, NULL, NULL);
+
+ if (!val) {
+ apr_table_unset(t->t, key);
+ return 0;
+ }
+
+ /* Unless it's the 'notes' table, check for newline chars */
+ /* t->r will be NULL in case of the connection notes, but since
+ we aren't going to check anything called 'notes', we can safely
+ disregard checking whether t->r is defined.
+ */
+ if (strcmp(t->n, "notes") && ap_strchr_c(val, '\n')) {
+ char *badchar;
+ char *replacement = apr_pstrdup(t->r->pool, val);
+ badchar = replacement;
+ while ( (badchar = ap_strchr(badchar, '\n')) ) {
+ *badchar = ' ';
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, t->r, APLOGNO(02614)
+ "mod_lua: Value for '%s' in table '%s' contains newline!",
+ key, t->n);
+ apr_table_set(t->t, key, replacement);
+ }
+ else {
+ apr_table_set(t->t, key, val);
+ }
+ return 0;
+}
+
+static int lua_table_get(lua_State *L)
+{
+ req_table_t *t = ap_lua_check_apr_table(L, 1);
+ const char *key = luaL_checkstring(L, 2);
+ const char *val = apr_table_get(t->t, key);
+ lua_pushstring(L, val);
+ return 1;
+}
+
+static const luaL_Reg lua_table_methods[] = {
+ {"set", lua_table_set},
+ {"get", lua_table_get},
+ {0, 0}
+};
+
+
+int ap_lua_init(lua_State *L, apr_pool_t *p)
+{
+ luaL_newmetatable(L, "Apr.Table");
+#if LUA_VERSION_NUM < 502
+ luaL_register(L, "apr_table", lua_table_methods);
+#else
+ luaL_newlib(L, lua_table_methods);
+#endif
+ lua_pushstring(L, "__index");
+ lua_pushstring(L, "get");
+ lua_gettable(L, 2);
+ lua_settable(L, 1);
+
+ lua_pushstring(L, "__newindex");
+ lua_pushstring(L, "set");
+ lua_gettable(L, 2);
+ lua_settable(L, 1);
+
+ return 0;
+}
+
+
+
diff --git a/modules/lua/lua_apr.h b/modules/lua/lua_apr.h
new file mode 100644
index 0000000..c194f11
--- /dev/null
+++ b/modules/lua/lua_apr.h
@@ -0,0 +1,36 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _LUA_APR_H_
+#define _LUA_APR_H_
+
+#include "scoreboard.h"
+#include "http_main.h"
+#include "ap_mpm.h"
+#include "apr_md5.h"
+#include "apr_sha1.h"
+#include "apr_poll.h"
+#include "apr.h"
+#include "apr_tables.h"
+#include "apr_base64.h"
+
+
+int ap_lua_init(lua_State *L, apr_pool_t * p);
+req_table_t *ap_lua_check_apr_table(lua_State *L, int index);
+void ap_lua_push_apr_table(lua_State *L, req_table_t *t);
+
+#endif /* !_LUA_APR_H_ */
diff --git a/modules/lua/lua_config.c b/modules/lua/lua_config.c
new file mode 100644
index 0000000..14674a9
--- /dev/null
+++ b/modules/lua/lua_config.c
@@ -0,0 +1,277 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "lua_config.h"
+#include "lua_vmprep.h"
+
+APLOG_USE_MODULE(lua);
+
+static ap_lua_dir_cfg *check_dir_config(lua_State *L, int index)
+{
+ ap_lua_dir_cfg *cfg;
+ luaL_checkudata(L, index, "Apache2.DirConfig");
+ cfg = (ap_lua_dir_cfg *) lua_unboxpointer(L, index);
+ return cfg;
+}
+
+static cmd_parms *check_cmd_parms(lua_State *L, int index)
+{
+ cmd_parms *cmd;
+ luaL_checkudata(L, index, "Apache2.CommandParameters");
+ cmd = (cmd_parms *) lua_unboxpointer(L, index);
+ return cmd;
+}
+
+static int apl_toscope(const char *name)
+{
+ if (0 == strcmp("once", name))
+ return AP_LUA_SCOPE_ONCE;
+ if (0 == strcmp("request", name))
+ return AP_LUA_SCOPE_REQUEST;
+ if (0 == strcmp("connection", name))
+ return AP_LUA_SCOPE_CONN;
+ if (0 == strcmp("conn", name))
+ return AP_LUA_SCOPE_CONN;
+ if (0 == strcmp("thread", name))
+ return AP_LUA_SCOPE_THREAD;
+ return AP_LUA_SCOPE_ONCE;
+}
+
+apr_status_t ap_lua_map_handler(ap_lua_dir_cfg *cfg,
+ const char *file,
+ const char *function,
+ const char *pattern,
+ const char *scope)
+{
+ ap_regex_t *uri_pattern;
+ apr_status_t rv;
+ ap_lua_mapped_handler_spec *handler =
+ apr_pcalloc(cfg->pool, sizeof(ap_lua_mapped_handler_spec));
+ handler->uri_pattern = NULL;
+ handler->function_name = NULL;
+
+ uri_pattern = apr_palloc(cfg->pool, sizeof(ap_regex_t));
+ if ((rv = ap_regcomp(uri_pattern, pattern, 0)) != APR_SUCCESS) {
+ return rv;
+ }
+ handler->file_name = apr_pstrdup(cfg->pool, file);
+ handler->uri_pattern = uri_pattern;
+ handler->scope = apl_toscope(scope);
+
+ handler->function_name = apr_pstrdup(cfg->pool, function);
+ *(const ap_lua_mapped_handler_spec **) apr_array_push(cfg->mapped_handlers) =
+ handler;
+ return APR_SUCCESS;
+}
+
+/* Change to use ap_lua_map_handler */
+static int cfg_lua_map_handler(lua_State *L)
+{
+ ap_lua_dir_cfg *cfg = check_dir_config(L, 1);
+ ap_lua_mapped_handler_spec *handler =
+ apr_pcalloc(cfg->pool, sizeof(ap_lua_mapped_handler_spec));
+ handler->uri_pattern = NULL;
+ handler->function_name = NULL;
+
+ luaL_checktype(L, 2, LUA_TTABLE);
+ lua_getfield(L, 2, "file");
+ if (lua_isstring(L, -1)) {
+ const char *file = lua_tostring(L, -1);
+ handler->file_name = apr_pstrdup(cfg->pool, file);
+ }
+ lua_pop(L, 1);
+
+ lua_getfield(L, 2, "pattern");
+ if (lua_isstring(L, -1)) {
+ const char *pattern = lua_tostring(L, -1);
+
+ ap_regex_t *uri_pattern = apr_palloc(cfg->pool, sizeof(ap_regex_t));
+ if (ap_regcomp(uri_pattern, pattern, 0) != OK) {
+ return luaL_error(L, "Unable to compile regular expression, '%s'",
+ pattern);
+ }
+ handler->uri_pattern = uri_pattern;
+ }
+ lua_pop(L, 1);
+
+ lua_getfield(L, 2, "scope");
+ if (lua_isstring(L, -1)) {
+ const char *scope = lua_tostring(L, -1);
+ handler->scope = apl_toscope(scope);
+ }
+ else {
+ handler->scope = AP_LUA_SCOPE_ONCE;
+ }
+ lua_pop(L, 1);
+
+ lua_getfield(L, 2, "func");
+ if (lua_isstring(L, -1)) {
+ const char *value = lua_tostring(L, -1);
+ handler->function_name = apr_pstrdup(cfg->pool, value);
+ }
+ else {
+ handler->function_name = "handle";
+ }
+ lua_pop(L, 1);
+
+
+ *(const ap_lua_mapped_handler_spec **) apr_array_push(cfg->mapped_handlers) =
+ handler;
+ return 0;
+}
+
+static int cfg_directory(lua_State *L)
+{
+ ap_lua_dir_cfg *cfg = check_dir_config(L, 1);
+ lua_pushstring(L, cfg->dir);
+ return 1;
+}
+
+/*static int cfg_root(lua_State *L)
+{
+ ap_lua_dir_cfg *cfg = check_dir_config(L, 1);
+ lua_pushstring(L, cfg->root_path);
+ return 1;
+}*/
+
+static const struct luaL_Reg cfg_methods[] = {
+ {"match_handler", cfg_lua_map_handler},
+ {"directory", cfg_directory},
+ /* {"root", cfg_root}, */
+ {NULL, NULL}
+};
+
+/* helper function for the logging functions below */
+static int cmd_log_at(lua_State *L, int level)
+{
+ const char *msg;
+ cmd_parms *cmd = check_cmd_parms(L, 1);
+ lua_Debug dbg;
+
+ lua_getstack(L, 1, &dbg);
+ lua_getinfo(L, "Sl", &dbg);
+
+ msg = luaL_checkstring(L, 2);
+ /* Intentional no APLOGNO */
+ ap_log_error(dbg.source, dbg.currentline, APLOG_MODULE_INDEX, level, 0,
+ cmd->server, "%s", msg);
+ return 0;
+}
+
+/* r:debug(String) and friends which use apache logging */
+static int cmd_emerg(lua_State *L)
+{
+ return cmd_log_at(L, APLOG_EMERG);
+}
+static int cmd_alert(lua_State *L)
+{
+ return cmd_log_at(L, APLOG_ALERT);
+}
+static int cmd_crit(lua_State *L)
+{
+ return cmd_log_at(L, APLOG_CRIT);
+}
+static int cmd_err(lua_State *L)
+{
+ return cmd_log_at(L, APLOG_ERR);
+}
+static int cmd_warn(lua_State *L)
+{
+ return cmd_log_at(L, APLOG_WARNING);
+}
+static int cmd_notice(lua_State *L)
+{
+ return cmd_log_at(L, APLOG_NOTICE);
+}
+static int cmd_info(lua_State *L)
+{
+ return cmd_log_at(L, APLOG_INFO);
+}
+static int cmd_debug(lua_State *L)
+{
+ return cmd_log_at(L, APLOG_DEBUG);
+}
+static int cmd_trace1(lua_State *L)
+{
+ return cmd_log_at(L, APLOG_TRACE1);
+}
+static int cmd_trace2(lua_State *L)
+{
+ return cmd_log_at(L, APLOG_TRACE2);
+}
+static int cmd_trace3(lua_State *L)
+{
+ return cmd_log_at(L, APLOG_TRACE3);
+}
+static int cmd_trace4(lua_State *L)
+{
+ return cmd_log_at(L, APLOG_TRACE4);
+}
+static int cmd_trace5(lua_State *L)
+{
+ return cmd_log_at(L, APLOG_TRACE5);
+}
+static int cmd_trace6(lua_State *L)
+{
+ return cmd_log_at(L, APLOG_TRACE6);
+}
+static int cmd_trace7(lua_State *L)
+{
+ return cmd_log_at(L, APLOG_TRACE7);
+}
+static int cmd_trace8(lua_State *L)
+{
+ return cmd_log_at(L, APLOG_TRACE8);
+}
+
+static const struct luaL_Reg cmd_methods[] = {
+ {"trace8", cmd_trace8},
+ {"trace7", cmd_trace7},
+ {"trace6", cmd_trace6},
+ {"trace5", cmd_trace5},
+ {"trace4", cmd_trace4},
+ {"trace3", cmd_trace3},
+ {"trace2", cmd_trace2},
+ {"trace1", cmd_trace1},
+ {"debug", cmd_debug},
+ {"info", cmd_info},
+ {"notice", cmd_notice},
+ {"warn", cmd_warn},
+ {"err", cmd_err},
+ {"crit", cmd_crit},
+ {"alert", cmd_alert},
+ {"emerg", cmd_emerg},
+
+ {NULL, NULL}
+};
+
+void ap_lua_load_config_lmodule(lua_State *L)
+{
+ luaL_newmetatable(L, "Apache2.DirConfig"); /* [metatable] */
+ lua_pushvalue(L, -1);
+
+ lua_setfield(L, -2, "__index");
+ luaL_setfuncs_compat(L, cfg_methods); /* [metatable] */
+
+
+ luaL_newmetatable(L, "Apache2.CommandParameters");
+ lua_pushvalue(L, -1);
+
+ lua_setfield(L, -2, "__index");
+ luaL_setfuncs_compat(L, cmd_methods); /* [metatable] */
+
+}
diff --git a/modules/lua/lua_config.h b/modules/lua/lua_config.h
new file mode 100644
index 0000000..8a778ad
--- /dev/null
+++ b/modules/lua/lua_config.h
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mod_lua.h"
+
+#ifndef _APL_CONFIG_H_
+#define _APL_CONFIG_H_
+
+void ap_lua_load_config_lmodule(lua_State *L);
+
+apr_status_t ap_lua_map_handler(ap_lua_dir_cfg *cfg,
+ const char *file,
+ const char *function,
+ const char *pattern,
+ const char *scope);
+
+#endif /* !_APL_CONFIG_H_ */
diff --git a/modules/lua/lua_dbd.c b/modules/lua/lua_dbd.c
new file mode 100644
index 0000000..1b8d5b7
--- /dev/null
+++ b/modules/lua/lua_dbd.c
@@ -0,0 +1,843 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include "mod_lua.h"
+#include "lua_dbd.h"
+
+APLOG_USE_MODULE(lua);
+static APR_OPTIONAL_FN_TYPE(ap_dbd_close) *lua_ap_dbd_close = NULL;
+static APR_OPTIONAL_FN_TYPE(ap_dbd_open) *lua_ap_dbd_open = NULL;
+
+
+
+
+static request_rec *ap_lua_check_request_rec(lua_State *L, int index)
+{
+ request_rec *r;
+ luaL_checkudata(L, index, "Apache2.Request");
+ r = lua_unboxpointer(L, index);
+ return r;
+}
+
+static lua_db_handle *lua_get_db_handle(lua_State *L)
+{
+ luaL_checktype(L, 1, LUA_TTABLE);
+ lua_rawgeti(L, 1, 0);
+ luaL_checktype(L, -1, LUA_TUSERDATA);
+ return (lua_db_handle *) lua_topointer(L, -1);
+}
+
+static lua_db_result_set *lua_get_result_set(lua_State *L)
+{
+ luaL_checktype(L, 1, LUA_TTABLE);
+ lua_rawgeti(L, 1, 0);
+ luaL_checktype(L, -1, LUA_TUSERDATA);
+ return (lua_db_result_set *) lua_topointer(L, -1);
+}
+
+
+/*
+ =============================================================================
+ db:close(): Closes an open database connection.
+ =============================================================================
+ */
+int lua_db_close(lua_State *L)
+{
+ /*~~~~~~~~~~~~~~~~~~~~*/
+ lua_db_handle *db;
+ apr_status_t rc = 0;
+ /*~~~~~~~~~~~~~~~~~~~~*/
+
+ db = lua_get_db_handle(L);
+ if (db && db->alive) {
+ if (db->type == LUA_DBTYPE_APR_DBD) {
+ rc = apr_dbd_close(db->driver, db->handle);
+ if (db->pool) apr_pool_destroy(db->pool);
+ }
+ else {
+ lua_ap_dbd_close = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_close);
+ if (lua_ap_dbd_close != NULL)
+ if (db->dbdhandle) lua_ap_dbd_close(db->server, db->dbdhandle);
+ }
+
+ db->driver = NULL;
+ db->handle = NULL;
+ db->alive = 0;
+ db->pool = NULL;
+ }
+
+ lua_settop(L, 0);
+ lua_pushnumber(L, rc);
+ return 1;
+}
+
+/*
+ =============================================================================
+ db:__gc(): Garbage collecting function.
+ =============================================================================
+ */
+int lua_db_gc(lua_State *L)
+{
+ /*~~~~~~~~~~~~~~~~*/
+ lua_db_handle *db;
+ /*~~~~~~~~~~~~~~~~~~~~*/
+
+ db = lua_touserdata(L, 1);
+ if (db && db->alive) {
+ if (db->type == LUA_DBTYPE_APR_DBD) {
+ apr_dbd_close(db->driver, db->handle);
+ if (db->pool) apr_pool_destroy(db->pool);
+ }
+ else {
+ lua_ap_dbd_close = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_close);
+ if (lua_ap_dbd_close != NULL)
+ if (db->dbdhandle) lua_ap_dbd_close(db->server, db->dbdhandle);
+ }
+ db->driver = NULL;
+ db->handle = NULL;
+ db->alive = 0;
+ db->pool = NULL;
+ }
+ lua_settop(L, 0);
+ return 0;
+}
+
+/*
+ =============================================================================
+ db:active(): Returns true if the connection to the db is still active.
+ =============================================================================
+ */
+int lua_db_active(lua_State *L)
+{
+ /*~~~~~~~~~~~~~~~~~~~~*/
+ lua_db_handle *db = 0;
+ apr_status_t rc = 0;
+ /*~~~~~~~~~~~~~~~~~~~~*/
+
+ db = lua_get_db_handle(L);
+ if (db && db->alive) {
+ rc = apr_dbd_check_conn(db->driver, db->pool, db->handle);
+ if (rc == APR_SUCCESS) {
+ lua_pushboolean(L, 1);
+ return 1;
+ }
+ }
+
+ lua_pushboolean(L, 0);
+ return 1;
+}
+
+/*
+ =============================================================================
+ db:query(statement): Executes the given database query and returns the
+ number of rows affected. If an error is encountered, returns nil as the
+ first parameter and the error message as the second.
+ =============================================================================
+ */
+int lua_db_query(lua_State *L)
+{
+ /*~~~~~~~~~~~~~~~~~~~~~~~*/
+ lua_db_handle *db = 0;
+ apr_status_t rc = 0;
+ int x = 0;
+ const char *statement;
+ /*~~~~~~~~~~~~~~~~~~~~~~~*/
+ luaL_checktype(L, 3, LUA_TSTRING);
+ statement = lua_tostring(L, 3);
+ db = lua_get_db_handle(L);
+ if (db && db->alive)
+ rc = apr_dbd_query(db->driver, db->handle, &x, statement);
+ else {
+ rc = 0;
+ x = -1;
+ }
+
+ if (rc == APR_SUCCESS)
+ lua_pushnumber(L, x);
+ else {
+
+ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+ const char *err = apr_dbd_error(db->driver, db->handle, rc);
+ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+
+ lua_pushnil(L);
+ if (err) {
+ lua_pushstring(L, err);
+ return 2;
+ }
+ }
+
+ return 1;
+}
+
+/*
+ =============================================================================
+ db:escape(string): Escapes a string for safe use in the given database type.
+ =============================================================================
+ */
+int lua_db_escape(lua_State *L)
+{
+ /*~~~~~~~~~~~~~~~~~~~~~*/
+ lua_db_handle *db = 0;
+ const char *statement;
+ const char *escaped = 0;
+ request_rec *r;
+ /*~~~~~~~~~~~~~~~~~~~~~*/
+
+ r = ap_lua_check_request_rec(L, 2);
+ if (r) {
+ luaL_checktype(L, 3, LUA_TSTRING);
+ statement = lua_tostring(L, 3);
+ db = lua_get_db_handle(L);
+ if (db && db->alive) {
+ apr_dbd_init(r->pool);
+ escaped = apr_dbd_escape(db->driver, r->pool, statement,
+ db->handle);
+ if (escaped) {
+ lua_pushstring(L, escaped);
+ return 1;
+ }
+ }
+ else {
+ lua_pushnil(L);
+ }
+ return (1);
+ }
+
+ return 0;
+}
+
+/*
+ =============================================================================
+ resultset(N): Fetches one or more rows from a result set.
+ =============================================================================
+ */
+int lua_db_get_row(lua_State *L)
+{
+ int row_no,x,alpha = 0;
+ const char *entry, *rowname;
+ apr_dbd_row_t *row = 0;
+ lua_db_result_set *res = lua_get_result_set(L);
+
+ row_no = luaL_optinteger(L, 2, 0);
+ if (lua_isboolean(L, 3)) {
+ alpha = lua_toboolean(L, 3);
+ }
+ lua_settop(L,0);
+
+ /* Fetch all rows at once? */
+
+ if (row_no == 0) {
+ row_no = 1;
+ lua_newtable(L);
+ while (apr_dbd_get_row(res->driver, res->pool, res->results,
+ &row, -1) != -1)
+ {
+ lua_pushinteger(L, row_no);
+ lua_newtable(L);
+ for (x = 0; x < res->cols; x++) {
+ entry = apr_dbd_get_entry(res->driver, row, x);
+ if (entry) {
+ if (alpha == 1) {
+ rowname = apr_dbd_get_name(res->driver,
+ res->results, x);
+ lua_pushstring(L, rowname ? rowname : "(oob)");
+ }
+ else {
+ lua_pushinteger(L, x + 1);
+ }
+ lua_pushstring(L, entry);
+ lua_rawset(L, -3);
+ }
+ }
+ lua_rawset(L, -3);
+ row_no++;
+ }
+ return 1;
+ }
+
+ /* Just fetch a single row */
+ if (apr_dbd_get_row(res->driver, res->pool, res->results,
+ &row, row_no) != -1)
+ {
+
+ lua_newtable(L);
+ for (x = 0; x < res->cols; x++) {
+ entry = apr_dbd_get_entry(res->driver, row, x);
+ if (entry) {
+ if (alpha == 1) {
+ rowname = apr_dbd_get_name(res->driver,
+ res->results, x);
+ lua_pushstring(L, rowname ? rowname : "(oob)");
+ }
+ else {
+ lua_pushinteger(L, x + 1);
+ }
+ lua_pushstring(L, entry);
+ lua_rawset(L, -3);
+ }
+ }
+ return 1;
+ }
+ return 0;
+}
+
+
+/*
+ =============================================================================
+ db:select(statement): Queries the database for the given statement and
+ returns the rows/columns found as a table. If an error is encountered,
+ returns nil as the first parameter and the error message as the second.
+ =============================================================================
+ */
+int lua_db_select(lua_State *L)
+{
+ /*~~~~~~~~~~~~~~~~~~~~~~~*/
+ lua_db_handle *db = 0;
+ apr_status_t rc = 0;
+ const char *statement;
+ request_rec *r;
+ /*~~~~~~~~~~~~~~~~~~~~~~~*/
+ r = ap_lua_check_request_rec(L, 2);
+ if (r) {
+ luaL_checktype(L, 3, LUA_TSTRING);
+ statement = lua_tostring(L, 3);
+ db = lua_get_db_handle(L);
+ if (db && db->alive) {
+
+ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+ int cols;
+ apr_dbd_results_t *results = 0;
+ lua_db_result_set* resultset = NULL;
+ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+
+ rc = apr_dbd_select(db->driver, db->pool, db->handle,
+ &results, statement, 0);
+ if (rc == APR_SUCCESS) {
+
+ cols = apr_dbd_num_cols(db->driver, results);
+
+ if (cols > 0) {
+ lua_newtable(L);
+ resultset = lua_newuserdata(L, sizeof(lua_db_result_set));
+ resultset->cols = cols;
+ resultset->driver = db->driver;
+ resultset->pool = db->pool;
+ resultset->rows = apr_dbd_num_tuples(db->driver, results);
+ resultset->results = results;
+ luaL_newmetatable(L, "lua_apr.dbselect");
+ lua_pushliteral(L, "__call");
+ lua_pushcfunction(L, lua_db_get_row);
+ lua_rawset(L, -3);
+ lua_setmetatable(L, -3);
+ lua_rawseti(L, -2, 0);
+ return 1;
+ }
+ return 0;
+ }
+ else {
+
+ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+ const char *err = apr_dbd_error(db->driver, db->handle, rc);
+ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+
+ lua_pushnil(L);
+ if (err) {
+ lua_pushstring(L, err);
+ return 2;
+ }
+ }
+ }
+
+ lua_pushboolean(L, 0);
+ return 1;
+ }
+
+ return 0;
+}
+
+
+
+/*
+ =============================================================================
+ statement:select(var1, var2, var3...): Injects variables into a prepared
+ statement and returns the number of rows matching the query.
+ =============================================================================
+ */
+int lua_db_prepared_select(lua_State *L)
+{
+ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+ lua_db_prepared_statement *st = 0;
+ apr_status_t rc = 0;
+ const char **vars;
+ int x, have;
+ /*~~~~~~~~~~~~~~~~~~~~~~~*/
+
+ /* Fetch the prepared statement and the vars passed */
+ luaL_checktype(L, 1, LUA_TTABLE);
+ lua_rawgeti(L, 1, 0);
+ luaL_checktype(L, -1, LUA_TUSERDATA);
+ st = (lua_db_prepared_statement*) lua_topointer(L, -1);
+
+ /* Check if we got enough variables passed on to us.
+ * This, of course, only works for prepared statements made through lua. */
+ have = lua_gettop(L) - 2;
+ if (st->variables != -1 && have < st->variables ) {
+ lua_pushboolean(L, 0);
+ lua_pushfstring(L,
+ "Error in executing prepared statement: Expected %d arguments, got %d.",
+ st->variables, have);
+ return 2;
+ }
+ vars = apr_pcalloc(st->db->pool, have*sizeof(char *));
+ for (x = 0; x < have; x++) {
+ vars[x] = lua_tostring(L, x + 2);
+ }
+
+ /* Fire off the query */
+ if (st->db && st->db->alive) {
+
+ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+ int cols;
+ apr_dbd_results_t *results = 0;
+ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+
+ rc = apr_dbd_pselect(st->db->driver, st->db->pool, st->db->handle,
+ &results, st->statement, 0, have, vars);
+ if (rc == APR_SUCCESS) {
+
+ /*~~~~~~~~~~~~~~~~~~~~~*/
+ lua_db_result_set *resultset;
+ /*~~~~~~~~~~~~~~~~~~~~~*/
+
+ cols = apr_dbd_num_cols(st->db->driver, results);
+ lua_newtable(L);
+ resultset = lua_newuserdata(L, sizeof(lua_db_result_set));
+ resultset->cols = cols;
+ resultset->driver = st->db->driver;
+ resultset->pool = st->db->pool;
+ resultset->rows = apr_dbd_num_tuples(st->db->driver, results);
+ resultset->results = results;
+ luaL_newmetatable(L, "lua_apr.dbselect");
+ lua_pushliteral(L, "__call");
+ lua_pushcfunction(L, lua_db_get_row);
+ lua_rawset(L, -3);
+ lua_setmetatable(L, -3);
+ lua_rawseti(L, -2, 0);
+ return 1;
+
+ }
+ else {
+
+ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+ const char *err = apr_dbd_error(st->db->driver, st->db->handle, rc);
+ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+
+ lua_pushnil(L);
+ if (err) {
+ lua_pushstring(L, err);
+ return 2;
+ }
+ return 1;
+ }
+ }
+
+ lua_pushboolean(L, 0);
+ lua_pushliteral(L,
+ "Database connection seems to be closed, please reacquire it.");
+ return (2);
+}
+
+
+/*
+ =============================================================================
+ statement:query(var1, var2, var3...): Injects variables into a prepared
+ statement and returns the number of rows affected.
+ =============================================================================
+ */
+int lua_db_prepared_query(lua_State *L)
+{
+ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+ lua_db_prepared_statement *st = 0;
+ apr_status_t rc = 0;
+ const char **vars;
+ int x, have;
+ /*~~~~~~~~~~~~~~~~~~~~~~~*/
+
+ /* Fetch the prepared statement and the vars passed */
+ luaL_checktype(L, 1, LUA_TTABLE);
+ lua_rawgeti(L, 1, 0);
+ luaL_checktype(L, -1, LUA_TUSERDATA);
+ st = (lua_db_prepared_statement*) lua_topointer(L, -1);
+
+ /* Check if we got enough variables passed on to us.
+ * This, of course, only works for prepared statements made through lua. */
+ have = lua_gettop(L) - 2;
+ if (st->variables != -1 && have < st->variables ) {
+ lua_pushboolean(L, 0);
+ lua_pushfstring(L,
+ "Error in executing prepared statement: Expected %d arguments, got %d.",
+ st->variables, have);
+ return 2;
+ }
+ vars = apr_pcalloc(st->db->pool, have*sizeof(char *));
+ for (x = 0; x < have; x++) {
+ vars[x] = lua_tostring(L, x + 2);
+ }
+
+ /* Fire off the query */
+ if (st->db && st->db->alive) {
+
+ /*~~~~~~~~~~~~~~*/
+ int affected = 0;
+ /*~~~~~~~~~~~~~~*/
+
+ rc = apr_dbd_pquery(st->db->driver, st->db->pool, st->db->handle,
+ &affected, st->statement, have, vars);
+ if (rc == APR_SUCCESS) {
+ lua_pushinteger(L, affected);
+ return 1;
+ }
+ else {
+
+ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+ const char *err = apr_dbd_error(st->db->driver, st->db->handle, rc);
+ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+
+ lua_pushnil(L);
+ if (err) {
+ lua_pushstring(L, err);
+ return 2;
+ }
+ return 1;
+ }
+ }
+
+ lua_pushboolean(L, 0);
+ lua_pushliteral(L,
+ "Database connection seems to be closed, please reacquire it.");
+ return (2);
+}
+
+/*
+ =============================================================================
+ db:prepare(statement): Prepares a statement for later query/select.
+ Returns a table with a :query and :select function, same as the db funcs.
+ =============================================================================
+ */
+int lua_db_prepare(lua_State* L)
+{
+ /*~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+ lua_db_handle *db = 0;
+ apr_status_t rc = 0;
+ const char *statement, *at;
+ request_rec *r;
+ lua_db_prepared_statement* st;
+ int need = 0;
+ /*~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+
+ r = ap_lua_check_request_rec(L, 2);
+ if (r) {
+ apr_dbd_prepared_t *pstatement = NULL;
+ luaL_checktype(L, 3, LUA_TSTRING);
+ statement = lua_tostring(L, 3);
+
+ /* Count number of variables in statement */
+ at = ap_strchr_c(statement,'%');
+ while (at != NULL) {
+ if (at[1] == '%') {
+ at++;
+ }
+ else {
+ need++;
+ }
+ at = ap_strchr_c(at+1,'%');
+ }
+
+
+ db = lua_get_db_handle(L);
+ rc = apr_dbd_prepare(db->driver, r->pool, db->handle, statement,
+ NULL, &pstatement);
+ if (rc != APR_SUCCESS) {
+ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+ const char *err = apr_dbd_error(db->driver, db->handle, rc);
+ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+
+ lua_pushnil(L);
+ if (err) {
+ lua_pushstring(L, err);
+ return 2;
+ }
+ return 1;
+ }
+
+ /* Push the prepared statement table */
+ lua_newtable(L);
+ st = lua_newuserdata(L, sizeof(lua_db_prepared_statement));
+ st->statement = pstatement;
+ st->variables = need;
+ st->db = db;
+
+ lua_pushliteral(L, "select");
+ lua_pushcfunction(L, lua_db_prepared_select);
+ lua_rawset(L, -4);
+ lua_pushliteral(L, "query");
+ lua_pushcfunction(L, lua_db_prepared_query);
+ lua_rawset(L, -4);
+ lua_rawseti(L, -2, 0);
+ return 1;
+ }
+ return 0;
+}
+
+
+
+/*
+ =============================================================================
+ db:prepared(statement): Fetches a prepared statement made through
+ DBDPrepareSQL.
+ =============================================================================
+ */
+int lua_db_prepared(lua_State* L)
+{
+ /*~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+ lua_db_handle *db = 0;
+ const char *tag;
+ request_rec *r;
+ lua_db_prepared_statement* st;
+ /*~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+
+ r = ap_lua_check_request_rec(L, 2);
+ if (r) {
+ apr_dbd_prepared_t *pstatement = NULL;
+ db = lua_get_db_handle(L);
+ luaL_checktype(L, 3, LUA_TSTRING);
+ tag = lua_tostring(L, 3);
+
+ /* Look for the statement */
+ pstatement = apr_hash_get(db->dbdhandle->prepared, tag,
+ APR_HASH_KEY_STRING);
+
+ if (pstatement == NULL) {
+ lua_pushnil(L);
+ lua_pushfstring(L,
+ "Could not find any prepared statement called %s!", tag);
+ return 2;
+ }
+
+
+ /* Push the prepared statement table */
+ lua_newtable(L);
+ st = lua_newuserdata(L, sizeof(lua_db_prepared_statement));
+ st->statement = pstatement;
+ st->variables = -1; /* we don't know :( */
+ st->db = db;
+ lua_pushliteral(L, "select");
+ lua_pushcfunction(L, lua_db_prepared_select);
+ lua_rawset(L, -4);
+ lua_pushliteral(L, "query");
+ lua_pushcfunction(L, lua_db_prepared_query);
+ lua_rawset(L, -4);
+ lua_rawseti(L, -2, 0);
+ return 1;
+ }
+ return 0;
+}
+
+
+
+/* lua_push_db_handle: Creates a database table object with database functions
+ and a userdata at index 0, which will call lua_dbgc when garbage collected.
+ */
+static lua_db_handle* lua_push_db_handle(lua_State *L, request_rec* r, int type,
+ apr_pool_t* pool)
+{
+ lua_db_handle* db;
+ lua_newtable(L);
+ db = lua_newuserdata(L, sizeof(lua_db_handle));
+ db->alive = 1;
+ db->pool = pool;
+ db->type = type;
+ db->dbdhandle = 0;
+ db->server = r->server;
+ luaL_newmetatable(L, "lua_apr.dbacquire");
+ lua_pushliteral(L, "__gc");
+ lua_pushcfunction(L, lua_db_gc);
+ lua_rawset(L, -3);
+ lua_setmetatable(L, -2);
+ lua_rawseti(L, -2, 0);
+
+ lua_pushliteral(L, "escape");
+ lua_pushcfunction(L, lua_db_escape);
+ lua_rawset(L, -3);
+
+ lua_pushliteral(L, "close");
+ lua_pushcfunction(L, lua_db_close);
+ lua_rawset(L, -3);
+
+ lua_pushliteral(L, "select");
+ lua_pushcfunction(L, lua_db_select);
+ lua_rawset(L, -3);
+
+ lua_pushliteral(L, "query");
+ lua_pushcfunction(L, lua_db_query);
+ lua_rawset(L, -3);
+
+ lua_pushliteral(L, "active");
+ lua_pushcfunction(L, lua_db_active);
+ lua_rawset(L, -3);
+
+ lua_pushliteral(L, "prepare");
+ lua_pushcfunction(L, lua_db_prepare);
+ lua_rawset(L, -3);
+
+ lua_pushliteral(L, "prepared");
+ lua_pushcfunction(L, lua_db_prepared);
+ lua_rawset(L, -3);
+ return db;
+}
+
+/*
+ =============================================================================
+ dbacquire(dbType, dbString): Opens a new connection to a database of type
+ _dbType_ and with the connection parameters _dbString_. If successful,
+ returns a table with functions for using the database handle. If an error
+ occurs, returns nil as the first parameter and the error message as the
+ second. See the APR_DBD for a list of database types and connection strings
+ supported.
+ =============================================================================
+ */
+int lua_db_acquire(lua_State *L)
+{
+ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+ const char *type;
+ const char *arguments;
+ const char *error = 0;
+ request_rec *r;
+ lua_db_handle *db = 0;
+ apr_status_t rc = 0;
+ ap_dbd_t *dbdhandle = NULL;
+ apr_pool_t *pool = NULL;
+ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+
+ r = ap_lua_check_request_rec(L, 1);
+ if (r) {
+ type = luaL_optstring(L, 2, "mod_dbd"); /* Defaults to mod_dbd */
+
+ if (!strcmp(type, "mod_dbd")) {
+
+ lua_settop(L, 0);
+ lua_ap_dbd_open = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_open);
+ if (lua_ap_dbd_open)
+ dbdhandle = (ap_dbd_t *) lua_ap_dbd_open(
+ r->server->process->pool, r->server);
+
+ if (dbdhandle) {
+ db = lua_push_db_handle(L, r, LUA_DBTYPE_MOD_DBD, dbdhandle->pool);
+ db->driver = dbdhandle->driver;
+ db->handle = dbdhandle->handle;
+ db->dbdhandle = dbdhandle;
+ return 1;
+ }
+ else {
+ lua_pushnil(L);
+ if ( lua_ap_dbd_open == NULL )
+ lua_pushliteral(L,
+ "mod_dbd doesn't seem to have been loaded.");
+ else
+ lua_pushliteral(
+ L,
+ "Could not acquire connection from mod_dbd. If your database is running, this may indicate a permission problem.");
+ return 2;
+ }
+ }
+ else {
+ rc = apr_pool_create(&pool, NULL);
+ if (rc != APR_SUCCESS) {
+ lua_pushnil(L);
+ lua_pushliteral(L, "Could not allocate memory for database!");
+ return 2;
+ }
+ apr_pool_tag(pool, "lua_dbd_pool");
+ apr_dbd_init(pool);
+ dbdhandle = apr_pcalloc(pool, sizeof(ap_dbd_t));
+ rc = apr_dbd_get_driver(pool, type, &dbdhandle->driver);
+ if (rc == APR_SUCCESS) {
+ luaL_checktype(L, 3, LUA_TSTRING);
+ arguments = lua_tostring(L, 3);
+ lua_settop(L, 0);
+
+ if (*arguments) {
+ rc = apr_dbd_open_ex(dbdhandle->driver, pool,
+ arguments, &dbdhandle->handle, &error);
+ if (rc == APR_SUCCESS) {
+ db = lua_push_db_handle(L, r, LUA_DBTYPE_APR_DBD, pool);
+ db->driver = dbdhandle->driver;
+ db->handle = dbdhandle->handle;
+ db->dbdhandle = dbdhandle;
+ return 1;
+ }
+ else {
+ lua_pushnil(L);
+ if (error) {
+ lua_pushstring(L, error);
+ return 2;
+ }
+
+ return 1;
+ }
+ }
+
+ lua_pushnil(L);
+ lua_pushliteral(L,
+ "No database connection string was specified.");
+ apr_pool_destroy(pool);
+ return (2);
+ }
+ else {
+ lua_pushnil(L);
+ if (APR_STATUS_IS_ENOTIMPL(rc)) {
+ lua_pushfstring(L,
+ "driver for %s not available", type);
+ }
+ else if (APR_STATUS_IS_EDSOOPEN(rc)) {
+ lua_pushfstring(L,
+ "can't find driver for %s", type);
+ }
+ else if (APR_STATUS_IS_ESYMNOTFOUND(rc)) {
+ lua_pushfstring(L,
+ "driver for %s is invalid or corrupted",
+ type);
+ }
+ else {
+ lua_pushliteral(L,
+ "mod_lua not compatible with APR in get_driver");
+ }
+ lua_pushinteger(L, rc);
+ apr_pool_destroy(pool);
+ return 3;
+ }
+ }
+
+ lua_pushnil(L);
+ return 1;
+ }
+
+ return 0;
+}
+
diff --git a/modules/lua/lua_dbd.h b/modules/lua/lua_dbd.h
new file mode 100644
index 0000000..566204b
--- /dev/null
+++ b/modules/lua/lua_dbd.h
@@ -0,0 +1,66 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _LUA_DBD_H_
+#define _LUA_DBD_H_
+
+#include "mod_lua.h"
+#include "apr.h"
+#include "apr_dbd.h"
+#include "mod_dbd.h"
+
+#define LUA_DBTYPE_APR_DBD 0
+#define LUA_DBTYPE_MOD_DBD 1
+typedef struct
+{
+ apr_dbd_t *handle;
+ const apr_dbd_driver_t *driver;
+ int alive;
+ apr_pool_t *pool;
+ char type;
+ ap_dbd_t * dbdhandle;
+ server_rec *server;
+} lua_db_handle;
+
+typedef struct {
+ const apr_dbd_driver_t *driver;
+ int rows;
+ int cols;
+ apr_dbd_results_t *results;
+ apr_pool_t *pool;
+} lua_db_result_set;
+
+typedef struct {
+ apr_dbd_prepared_t *statement;
+ int variables;
+ lua_db_handle *db;
+} lua_db_prepared_statement;
+
+int lua_db_acquire(lua_State* L);
+int lua_db_escape(lua_State* L);
+int lua_db_close(lua_State* L);
+int lua_db_prepare(lua_State* L);
+int lua_db_prepared(lua_State* L);
+int lua_db_select(lua_State* L);
+int lua_db_query(lua_State* L);
+int lua_db_prepared_select(lua_State* L);
+int lua_db_prepared_query(lua_State* L);
+int lua_db_get_row(lua_State* L);
+int lua_db_gc(lua_State* L);
+int lua_db_active(lua_State* L);
+
+#endif /* !_LUA_DBD_H_ */
diff --git a/modules/lua/lua_passwd.c b/modules/lua/lua_passwd.c
new file mode 100644
index 0000000..ad86536
--- /dev/null
+++ b/modules/lua/lua_passwd.c
@@ -0,0 +1,178 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "lua_passwd.h"
+#include "apr_strings.h"
+#include "apr_errno.h"
+
+#if APR_HAVE_STDIO_H
+#include <stdio.h>
+#endif
+
+#include "apr_md5.h"
+#include "apr_sha1.h"
+
+#if APR_HAVE_TIME_H
+#include <time.h>
+#endif
+#if APR_HAVE_CRYPT_H
+#include <crypt.h>
+#endif
+#if APR_HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#if APR_HAVE_STRING_H
+#include <string.h>
+#endif
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if APR_HAVE_IO_H
+#include <io.h>
+#endif
+
+static int generate_salt(char *s, size_t size, const char **errstr,
+ apr_pool_t *pool)
+{
+ unsigned char rnd[32];
+ static const char itoa64[] =
+ "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+ apr_size_t n;
+ unsigned int val = 0, bits = 0;
+ apr_status_t rv;
+
+ n = (size * 6 + 7)/8;
+ if (n > sizeof(rnd)) {
+ *errstr = apr_psprintf(pool, "generate_salt(): BUG: Buffer too small");
+ return ERR_RANDOM;
+ }
+ rv = apr_generate_random_bytes(rnd, n);
+ if (rv) {
+ *errstr = apr_psprintf(pool, "Unable to generate random bytes: %pm",
+ &rv);
+ return ERR_RANDOM;
+ }
+ n = 0;
+ while (size > 0) {
+ if (bits < 6) {
+ val |= (rnd[n++] << bits);
+ bits += 8;
+ }
+ *s++ = itoa64[val & 0x3f];
+ size--;
+ val >>= 6;
+ bits -= 6;
+ }
+ *s = '\0';
+ return 0;
+}
+
+/*
+ * Make a password record from the given information. A zero return
+ * indicates success; on failure, ctx->errstr points to the error message.
+ */
+int mk_password_hash(passwd_ctx *ctx)
+{
+ char *pw;
+ char salt[16];
+ apr_status_t rv;
+ int ret = 0;
+#if CRYPT_ALGO_SUPPORTED
+ char *cbuf;
+#endif
+
+ pw = ctx->passwd;
+ switch (ctx->alg) {
+ case ALG_APSHA:
+ /* XXX out >= 28 + strlen(sha1) chars - fixed len SHA */
+ apr_sha1_base64(pw, strlen(pw), ctx->out);
+ break;
+
+ case ALG_APMD5:
+ ret = generate_salt(salt, 8, &ctx->errstr, ctx->pool);
+ if (ret != 0) {
+ ret = ERR_GENERAL;
+ break;
+ }
+ rv = apr_md5_encode(pw, salt, ctx->out, ctx->out_len);
+ if (rv != APR_SUCCESS) {
+ ctx->errstr = apr_psprintf(ctx->pool,
+ "could not encode password: %pm", &rv);
+ ret = ERR_GENERAL;
+ }
+ break;
+
+#if CRYPT_ALGO_SUPPORTED
+ case ALG_CRYPT:
+ ret = generate_salt(salt, 8, &ctx->errstr, ctx->pool);
+ if (ret != 0)
+ break;
+ cbuf = crypt(pw, salt);
+ if (cbuf == NULL) {
+ rv = APR_FROM_OS_ERROR(errno);
+ ctx->errstr = apr_psprintf(ctx->pool, "crypt() failed: %pm", &rv);
+ ret = ERR_PWMISMATCH;
+ break;
+ }
+
+ apr_cpystrn(ctx->out, cbuf, ctx->out_len - 1);
+ if (strlen(pw) > 8) {
+ char *truncpw = apr_pstrdup(ctx->pool, pw);
+ truncpw[8] = '\0';
+ if (!strcmp(ctx->out, crypt(truncpw, salt))) {
+ ctx->errstr = apr_psprintf(ctx->pool,
+ "Warning: Password truncated to 8 "
+ "characters by CRYPT algorithm.");
+ }
+ memset(truncpw, '\0', strlen(pw));
+ }
+ break;
+#endif /* CRYPT_ALGO_SUPPORTED */
+
+#if BCRYPT_ALGO_SUPPORTED
+ case ALG_BCRYPT:
+ rv = apr_generate_random_bytes((unsigned char*)salt, 16);
+ if (rv != APR_SUCCESS) {
+ ctx->errstr = apr_psprintf(ctx->pool, "Unable to generate random "
+ "bytes: %pm", &rv);
+ ret = ERR_RANDOM;
+ break;
+ }
+
+ if (ctx->cost == 0)
+ ctx->cost = BCRYPT_DEFAULT_COST;
+ rv = apr_bcrypt_encode(pw, ctx->cost, (unsigned char*)salt, 16,
+ ctx->out, ctx->out_len);
+ if (rv != APR_SUCCESS) {
+ ctx->errstr = apr_psprintf(ctx->pool, "Unable to encode with "
+ "bcrypt: %pm", &rv);
+ ret = ERR_PWMISMATCH;
+ break;
+ }
+ break;
+#endif /* BCRYPT_ALGO_SUPPORTED */
+
+ default:
+ ctx->errstr = apr_psprintf(ctx->pool,
+ "mk_password_hash(): unsupported algorithm %d",
+ ctx->alg);
+ ret = ERR_GENERAL;
+ }
+ memset(pw, '\0', strlen(pw));
+ return ret;
+}
+
+
diff --git a/modules/lua/lua_passwd.h b/modules/lua/lua_passwd.h
new file mode 100644
index 0000000..8606cc1
--- /dev/null
+++ b/modules/lua/lua_passwd.h
@@ -0,0 +1,90 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _LUA_PASSWD_H
+#define _LUA_PASSWD_H
+
+#include "apr.h"
+#include "apr_lib.h"
+#include "apr_strings.h"
+#include "apr_errno.h"
+#include "apr_file_io.h"
+#include "apr_general.h"
+#include "apr_version.h"
+#if !APR_VERSION_AT_LEAST(2,0,0)
+#include "apu_version.h"
+#endif
+
+#define MAX_PASSWD_LEN 256
+
+#define ALG_APMD5 0
+#define ALG_APSHA 1
+#define ALG_BCRYPT 2
+#define ALG_CRYPT 3
+
+#define BCRYPT_DEFAULT_COST 5
+
+#define ERR_FILEPERM 1
+#define ERR_SYNTAX 2
+#define ERR_PWMISMATCH 3
+#define ERR_INTERRUPTED 4
+#define ERR_OVERFLOW 5
+#define ERR_BADUSER 6
+#define ERR_INVALID 7
+#define ERR_RANDOM 8
+#define ERR_GENERAL 9
+#define ERR_ALG_NOT_SUPP 10
+
+#if defined(WIN32) || defined(NETWARE)
+#define CRYPT_ALGO_SUPPORTED 0
+#define PLAIN_ALGO_SUPPORTED 1
+#else
+#define CRYPT_ALGO_SUPPORTED 1
+#define PLAIN_ALGO_SUPPORTED 0
+#endif
+
+#if APR_VERSION_AT_LEAST(2,0,0) || \
+ (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 5)
+#define BCRYPT_ALGO_SUPPORTED 1
+#else
+#define BCRYPT_ALGO_SUPPORTED 0
+#endif
+
+typedef struct passwd_ctx passwd_ctx;
+
+struct passwd_ctx {
+ apr_pool_t *pool;
+ const char *errstr;
+ char *out;
+ apr_size_t out_len;
+ char *passwd;
+ int alg;
+ int cost;
+};
+
+
+/*
+ * The following functions return zero on success; otherwise, one of
+ * the ERR_* codes is returned and an error message is stored in ctx->errstr.
+ */
+
+/*
+ * Make a password record from the given information.
+ */
+int mk_password_hash(passwd_ctx *ctx);
+
+#endif /* _LUA_PASSWD_H */
+
diff --git a/modules/lua/lua_request.c b/modules/lua/lua_request.c
new file mode 100644
index 0000000..bec8580
--- /dev/null
+++ b/modules/lua/lua_request.c
@@ -0,0 +1,3043 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mod_lua.h"
+#include "lua_apr.h"
+#include "lua_dbd.h"
+#include "lua_passwd.h"
+#include "scoreboard.h"
+#include "util_md5.h"
+#include "util_script.h"
+#include "util_varbuf.h"
+#include "apr_date.h"
+#include "apr_pools.h"
+#include "apr_thread_mutex.h"
+#include "apr_tables.h"
+#include "util_cookies.h"
+
+#define APR_WANT_BYTEFUNC
+#include "apr_want.h"
+
+extern apr_global_mutex_t* lua_ivm_mutex;
+extern apr_shm_t *lua_ivm_shm;
+
+APLOG_USE_MODULE(lua);
+#define POST_MAX_VARS 500
+
+#ifndef MODLUA_MAX_REG_MATCH
+#define MODLUA_MAX_REG_MATCH 25
+#endif
+
+typedef char *(*req_field_string_f) (request_rec * r);
+typedef int (*req_field_int_f) (request_rec * r);
+typedef req_table_t *(*req_field_apr_table_f) (request_rec * r);
+
+
+void ap_lua_rstack_dump(lua_State *L, request_rec *r, const char *msg)
+{
+ int i;
+ int top = lua_gettop(L);
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01484) "Lua Stack Dump: [%s]", msg);
+ for (i = 1; i <= top; i++) {
+ int t = lua_type(L, i);
+ switch (t) {
+ case LUA_TSTRING:{
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(03001)
+ "%d: '%s'", i, lua_tostring(L, i));
+ break;
+ }
+ case LUA_TUSERDATA:{
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(03002)
+ "%d: userdata", i);
+ break;
+ }
+ case LUA_TLIGHTUSERDATA:{
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(03003)
+ "%d: lightuserdata", i);
+ break;
+ }
+ case LUA_TNIL:{
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(03004)
+ "%d: NIL", i);
+ break;
+ }
+ case LUA_TNONE:{
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(03005)
+ "%d: None", i);
+ break;
+ }
+ case LUA_TBOOLEAN:{
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(03006)
+ "%d: %s", i,
+ lua_toboolean(L, i) ? "true" : "false");
+ break;
+ }
+ case LUA_TNUMBER:{
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(03007)
+ "%d: %g", i, lua_tonumber(L, i));
+ break;
+ }
+ case LUA_TTABLE:{
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(03008)
+ "%d: <table>", i);
+ break;
+ }
+ case LUA_TFUNCTION:{
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(03009)
+ "%d: <function>", i);
+ break;
+ }
+ default:{
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(03010)
+ "%d: unknown: -[%s]-", i, lua_typename(L, i));
+ break;
+ }
+ }
+ }
+}
+
+/**
+ * Verify that the thing at index is a request_rec wrapping
+ * userdata thingamajig and return it if it is. if it is not
+ * lua will enter its error handling routine.
+ */
+static request_rec *ap_lua_check_request_rec(lua_State *L, int index)
+{
+ request_rec *r;
+ luaL_checkudata(L, index, "Apache2.Request");
+ r = (request_rec *) lua_unboxpointer(L, index);
+ return r;
+}
+
+/* ------------------ request methods -------------------- */
+/* helper callback for req_parseargs */
+static int req_aprtable2luatable_cb(void *l, const char *key,
+ const char *value)
+{
+ int t;
+ lua_State *L = (lua_State *) l; /* [table<s,t>, table<s,s>] */
+ /* rstack_dump(L, RRR, "start of cb"); */
+ /* L is [table<s,t>, table<s,s>] */
+ /* build complex */
+
+ lua_getfield(L, -1, key); /* [VALUE, table<s,t>, table<s,s>] */
+ /* rstack_dump(L, RRR, "after getfield"); */
+ t = lua_type(L, -1);
+ switch (t) {
+ case LUA_TNIL:
+ case LUA_TNONE:{
+ lua_pop(L, 1); /* [table<s,t>, table<s,s>] */
+ lua_newtable(L); /* [array, table<s,t>, table<s,s>] */
+ lua_pushnumber(L, 1); /* [1, array, table<s,t>, table<s,s>] */
+ lua_pushstring(L, value); /* [string, 1, array, table<s,t>, table<s,s>] */
+ lua_settable(L, -3); /* [array, table<s,t>, table<s,s>] */
+ lua_setfield(L, -2, key); /* [table<s,t>, table<s,s>] */
+ break;
+ }
+ case LUA_TTABLE:{
+ /* [array, table<s,t>, table<s,s>] */
+ int size = lua_rawlen(L, -1);
+ lua_pushnumber(L, size + 1); /* [#, array, table<s,t>, table<s,s>] */
+ lua_pushstring(L, value); /* [string, #, array, table<s,t>, table<s,s>] */
+ lua_settable(L, -3); /* [array, table<s,t>, table<s,s>] */
+ lua_setfield(L, -2, key); /* [table<s,t>, table<s,s>] */
+ break;
+ }
+ }
+
+ /* L is [table<s,t>, table<s,s>] */
+ /* build simple */
+ lua_getfield(L, -2, key); /* [VALUE, table<s,s>, table<s,t>] */
+ if (lua_isnoneornil(L, -1)) { /* only set if not already set */
+ lua_pop(L, 1); /* [table<s,s>, table<s,t>]] */
+ lua_pushstring(L, value); /* [string, table<s,s>, table<s,t>] */
+ lua_setfield(L, -3, key); /* [table<s,s>, table<s,t>] */
+ }
+ else {
+ lua_pop(L, 1);
+ }
+ return 1;
+}
+
+/* helper callback for req_parseargs */
+static int req_aprtable2luatable_cb_len(void *l, const char *key,
+ const char *value, size_t len)
+{
+ int t;
+ lua_State *L = (lua_State *) l; /* [table<s,t>, table<s,s>] */
+ /* rstack_dump(L, RRR, "start of cb"); */
+ /* L is [table<s,t>, table<s,s>] */
+ /* build complex */
+
+ lua_getfield(L, -1, key); /* [VALUE, table<s,t>, table<s,s>] */
+ /* rstack_dump(L, RRR, "after getfield"); */
+ t = lua_type(L, -1);
+ switch (t) {
+ case LUA_TNIL:
+ case LUA_TNONE:{
+ lua_pop(L, 1); /* [table<s,t>, table<s,s>] */
+ lua_newtable(L); /* [array, table<s,t>, table<s,s>] */
+ lua_pushnumber(L, 1); /* [1, array, table<s,t>, table<s,s>] */
+ lua_pushlstring(L, value, len); /* [string, 1, array, table<s,t>, table<s,s>] */
+ lua_settable(L, -3); /* [array, table<s,t>, table<s,s>] */
+ lua_setfield(L, -2, key); /* [table<s,t>, table<s,s>] */
+ break;
+ }
+
+ case LUA_TTABLE:{
+ /* [array, table<s,t>, table<s,s>] */
+ int size = lua_rawlen(L, -1);
+ lua_pushnumber(L, size + 1); /* [#, array, table<s,t>, table<s,s>] */
+ lua_pushlstring(L, value, len); /* [string, #, array, table<s,t>, table<s,s>] */
+ lua_settable(L, -3); /* [array, table<s,t>, table<s,s>] */
+ lua_setfield(L, -2, key); /* [table<s,t>, table<s,s>] */
+ break;
+ }
+ }
+
+ /* L is [table<s,t>, table<s,s>] */
+ /* build simple */
+ lua_getfield(L, -2, key); /* [VALUE, table<s,s>, table<s,t>] */
+ if (lua_isnoneornil(L, -1)) { /* only set if not already set */
+ lua_pop(L, 1); /* [table<s,s>, table<s,t>]] */
+ lua_pushlstring(L, value, len); /* [string, table<s,s>, table<s,t>] */
+ lua_setfield(L, -3, key); /* [table<s,s>, table<s,t>] */
+ }
+ else {
+ lua_pop(L, 1);
+ }
+ return 1;
+}
+
+
+/*
+ =======================================================================================================================
+ lua_read_body(request_rec *r, const char **rbuf, apr_off_t *size): Reads any additional form data sent in POST/PUT
+ requests. Used for multipart POST data.
+ =======================================================================================================================
+ */
+static int lua_read_body(request_rec *r, const char **rbuf, apr_off_t *size,
+ apr_off_t maxsize)
+{
+ int rc = OK;
+
+ *rbuf = NULL;
+ *size = 0;
+
+ if ((rc = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR))) {
+ return (rc);
+ }
+ if (ap_should_client_block(r)) {
+
+ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+ apr_off_t len_read = -1;
+ apr_off_t rpos = 0;
+ apr_off_t length = r->remaining;
+ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+
+ if (maxsize != 0 && length > maxsize) {
+ return APR_EINCOMPLETE; /* Only room for incomplete data chunk :( */
+ }
+ *rbuf = (const char *) apr_pcalloc(r->pool, (apr_size_t) (length) + 1);
+ while ((rpos < length)
+ && (len_read = ap_get_client_block(r, (char *) *rbuf + rpos,
+ length - rpos)) > 0) {
+ rpos += len_read;
+ }
+ if (len_read < 0) {
+ return APR_EINCOMPLETE;
+ }
+ *size = rpos;
+ }
+ else {
+ rc = DONE;
+ }
+
+ return (rc);
+}
+
+
+/*
+ * =======================================================================================================================
+ * lua_write_body: Reads any additional form data sent in POST/PUT requests
+ * and writes to a file.
+ * =======================================================================================================================
+ */
+static apr_status_t lua_write_body(request_rec *r, apr_file_t *file, apr_off_t *size)
+{
+ apr_status_t rc = OK;
+
+ *size = 0;
+
+ if ((rc = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR)))
+ return rc;
+ if (ap_should_client_block(r)) {
+ char argsbuffer[HUGE_STRING_LEN];
+ apr_off_t rsize,
+ len_read,
+ rpos = 0;
+ apr_off_t length = r->remaining;
+
+ *size = length;
+ while ((len_read =
+ ap_get_client_block(r, argsbuffer,
+ sizeof(argsbuffer))) > 0) {
+ if ((rpos + len_read) > length)
+ rsize = (apr_size_t) length - rpos;
+ else
+ rsize = len_read;
+
+ rc = apr_file_write_full(file, argsbuffer, (apr_size_t) rsize,
+ NULL);
+ if (rc != APR_SUCCESS)
+ return rc;
+ rpos += rsize;
+ }
+ }
+ else {
+ rc = DONE;
+ }
+
+ return rc;
+}
+
+/* expose apr_table as (r/o) lua table */
+static int req_aprtable2luatable(lua_State *L, apr_table_t *t)
+{
+ lua_newtable(L);
+ lua_newtable(L); /* [table, table] */
+ apr_table_do(req_aprtable2luatable_cb, L, t, NULL);
+ return 2; /* [table<string, string>, table<string, array<string>>] */
+}
+
+static int req_headers_in_table(lua_State *L)
+{
+ request_rec *r = ap_lua_check_request_rec(L, 1);
+ return req_aprtable2luatable(L, r->headers_in);
+}
+static int req_headers_out_table(lua_State *L)
+{
+ request_rec *r = ap_lua_check_request_rec(L, 1);
+ return req_aprtable2luatable(L, r->headers_out);
+}
+static int req_err_headers_out_table(lua_State *L)
+{
+ request_rec *r = ap_lua_check_request_rec(L, 1);
+ return req_aprtable2luatable(L, r->err_headers_out);
+}
+static int req_notes_table(lua_State *L)
+{
+ request_rec *r = ap_lua_check_request_rec(L, 1);
+ return req_aprtable2luatable(L, r->notes);
+}
+static int req_subprocess_env_table(lua_State *L)
+{
+ request_rec *r = ap_lua_check_request_rec(L, 1);
+ return req_aprtable2luatable(L, r->subprocess_env);
+}
+/* r:parseargs() returning a lua table */
+static int req_parseargs(lua_State *L)
+{
+ apr_table_t *form_table;
+ request_rec *r = ap_lua_check_request_rec(L, 1);
+ lua_newtable(L);
+ lua_newtable(L); /* [table, table] */
+ ap_args_to_table(r, &form_table);
+ apr_table_do(req_aprtable2luatable_cb, L, form_table, NULL);
+ return 2; /* [table<string, string>, table<string, array<string>>] */
+}
+
+/* ap_lua_binstrstr: Binary strstr function for uploaded data with NULL bytes */
+static char* ap_lua_binstrstr (const char * haystack, size_t hsize, const char* needle, size_t nsize)
+{
+ size_t p;
+ if (haystack == NULL) return NULL;
+ if (needle == NULL) return NULL;
+ if (hsize < nsize) return NULL;
+ for (p = 0; p <= (hsize - nsize); ++p) {
+ if (memcmp(haystack + p, needle, nsize) == 0) {
+ return (char*) (haystack + p);
+ }
+ }
+ return NULL;
+}
+
+/* r:parsebody(): Parses regular (url-enocded) or multipart POST data and returns two tables*/
+static int req_parsebody(lua_State *L)
+{
+ apr_array_header_t *pairs;
+ apr_off_t len;
+ int res;
+ apr_size_t size;
+ apr_size_t max_post_size;
+ char *multipart;
+ const char *contentType;
+ request_rec *r = ap_lua_check_request_rec(L, 1);
+ max_post_size = (apr_size_t) luaL_optinteger(L, 2, MAX_STRING_LEN);
+ multipart = apr_pcalloc(r->pool, 256);
+ contentType = apr_table_get(r->headers_in, "Content-Type");
+ lua_newtable(L);
+ lua_newtable(L); /* [table, table] */
+ if (contentType != NULL && (sscanf(contentType, "multipart/form-data; boundary=%250c", multipart) == 1)) {
+ char *buffer, *key, *filename;
+ char *start = 0, *end = 0, *crlf = 0;
+ const char *data;
+ int i;
+ size_t vlen = 0;
+ size_t len = 0;
+ if (lua_read_body(r, &data, (apr_off_t*) &size, max_post_size) != OK) {
+ return 2;
+ }
+ len = strlen(multipart);
+ i = 0;
+ for
+ (
+ start = strstr((char *) data, multipart);
+ start != NULL;
+ start = end
+ ) {
+ i++;
+ if (i == POST_MAX_VARS) break;
+ crlf = strstr((char *) start, "\r\n\r\n");
+ if (!crlf) break;
+ end = ap_lua_binstrstr(crlf, (size - (crlf - data)), multipart, len);
+ if (end == NULL) break;
+ key = (char *) apr_pcalloc(r->pool, 256);
+ filename = (char *) apr_pcalloc(r->pool, 256);
+ if (end - crlf <= 8) break;
+ vlen = end - crlf - 8;
+ buffer = (char *) apr_pcalloc(r->pool, vlen+1);
+ memcpy(buffer, crlf + 4, vlen);
+ sscanf(start + len + 2,
+ "Content-Disposition: form-data; name=\"%255[^\"]\"; filename=\"%255[^\"]\"",
+ key, filename);
+ if (*key) {
+ req_aprtable2luatable_cb_len(L, key, buffer, vlen);
+ }
+ }
+ }
+ else {
+ char *buffer;
+ res = ap_parse_form_data(r, NULL, &pairs, -1, max_post_size);
+ if (res == OK) {
+ while(pairs && !apr_is_empty_array(pairs)) {
+ ap_form_pair_t *pair = (ap_form_pair_t *) apr_array_pop(pairs);
+ apr_brigade_length(pair->value, 1, &len);
+ size = (apr_size_t) len;
+ buffer = apr_palloc(r->pool, size + 1);
+ apr_brigade_flatten(pair->value, buffer, &size);
+ buffer[len] = 0;
+ req_aprtable2luatable_cb(L, pair->name, buffer);
+ }
+ }
+ }
+ return 2; /* [table<string, string>, table<string, array<string>>] */
+}
+
+
+/*
+ * lua_ap_requestbody; r:requestbody([filename]) - Reads or stores the request
+ * body
+ */
+static int lua_ap_requestbody(lua_State *L)
+{
+ const char *filename;
+ request_rec *r;
+ apr_off_t maxSize;
+
+ r = ap_lua_check_request_rec(L, 1);
+ filename = luaL_optstring(L, 2, 0);
+ maxSize = (apr_off_t)luaL_optinteger(L, 3, 0);
+
+ if (r) {
+ apr_off_t size;
+ if (maxSize > 0 && r->remaining > maxSize) {
+ lua_pushnil(L);
+ lua_pushliteral(L, "Request body was larger than the permitted size.");
+ return 2;
+ }
+ if (r->method_number != M_POST && r->method_number != M_PUT)
+ return (0);
+ if (!filename) {
+ const char *data;
+
+ if (lua_read_body(r, &data, &size, maxSize) != OK)
+ return (0);
+
+ lua_pushlstring(L, data, (size_t) size);
+ lua_pushinteger(L, (lua_Integer) size);
+ return (2);
+ } else {
+ apr_status_t rc;
+ apr_file_t *file;
+
+ rc = apr_file_open(&file, filename, APR_CREATE | APR_FOPEN_WRITE,
+ APR_FPROT_OS_DEFAULT, r->pool);
+ lua_settop(L, 0);
+ if (rc == APR_SUCCESS) {
+ rc = lua_write_body(r, file, &size);
+ apr_file_close(file);
+ if (rc != OK) {
+ lua_pushboolean(L, 0);
+ return 1;
+ }
+ lua_pushinteger(L, (lua_Integer) size);
+ return (1);
+ } else
+ lua_pushboolean(L, 0);
+ return (1);
+ }
+ }
+
+ return (0);
+}
+
+/* wrap ap_rputs as r:puts(String) */
+static int req_puts(lua_State *L)
+{
+ request_rec *r = ap_lua_check_request_rec(L, 1);
+
+ int argc = lua_gettop(L);
+ int i;
+
+ for (i = 2; i <= argc; i++) {
+ ap_rputs(luaL_checkstring(L, i), r);
+ }
+ return 0;
+}
+
+/* wrap ap_rwrite as r:write(String) */
+static int req_write(lua_State *L)
+{
+ request_rec *r = ap_lua_check_request_rec(L, 1);
+ size_t n;
+ int rv;
+ const char *buf = luaL_checklstring(L, 2, &n);
+
+ rv = ap_rwrite((void *) buf, n, r);
+ lua_pushinteger(L, rv);
+ return 1;
+}
+
+/* r:add_output_filter(name) */
+static int req_add_output_filter(lua_State *L)
+{
+ request_rec *r = ap_lua_check_request_rec(L, 1);
+ const char *name = luaL_checkstring(L, 2);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01485) "adding output filter %s",
+ name);
+ ap_add_output_filter(name, L, r, r->connection);
+ return 0;
+}
+
+/* wrap ap_construct_url as r:construct_url(String) */
+static int req_construct_url(lua_State *L)
+{
+ request_rec *r = ap_lua_check_request_rec(L, 1);
+ const char *name = luaL_checkstring(L, 2);
+ lua_pushstring(L, ap_construct_url(r->pool, name, r));
+ return 1;
+}
+
+/* wrap ap_escape_html r:escape_html(String) */
+static int req_escape_html(lua_State *L)
+{
+ request_rec *r = ap_lua_check_request_rec(L, 1);
+ const char *s = luaL_checkstring(L, 2);
+ lua_pushstring(L, ap_escape_html(r->pool, s));
+ return 1;
+}
+
+/* wrap optional ssl_var_lookup as r:ssl_var_lookup(String) */
+static int req_ssl_var_lookup(lua_State *L)
+{
+ request_rec *r = ap_lua_check_request_rec(L, 1);
+ const char *s = luaL_checkstring(L, 2);
+ const char *res = ap_lua_ssl_val(r->pool, r->server, r->connection, r,
+ (char *)s);
+ lua_pushstring(L, res);
+ return 1;
+}
+
+/* BEGIN dispatch mathods for request_rec fields */
+
+/* not really a field, but we treat it like one */
+static const char *req_document_root(request_rec *r)
+{
+ return ap_document_root(r);
+}
+
+static const char *req_context_prefix(request_rec *r)
+{
+ return ap_context_prefix(r);
+}
+
+static const char *req_context_document_root(request_rec *r)
+{
+ return ap_context_document_root(r);
+}
+
+static char *req_uri_field(request_rec *r)
+{
+ return r->uri;
+}
+
+static const char *req_method_field(request_rec *r)
+{
+ return r->method;
+}
+static const char *req_handler_field(request_rec *r)
+{
+ return r->handler;
+}
+static const char *req_proxyreq_field(request_rec *r)
+{
+ switch (r->proxyreq) {
+ case PROXYREQ_NONE: return "PROXYREQ_NONE";
+ case PROXYREQ_PROXY: return "PROXYREQ_PROXY";
+ case PROXYREQ_REVERSE: return "PROXYREQ_REVERSE";
+ case PROXYREQ_RESPONSE: return "PROXYREQ_RESPONSE";
+ default: return NULL;
+ }
+}
+static const char *req_hostname_field(request_rec *r)
+{
+ return r->hostname;
+}
+
+static const char *req_args_field(request_rec *r)
+{
+ return r->args;
+}
+
+static const char *req_path_info_field(request_rec *r)
+{
+ return r->path_info;
+}
+
+static const char *req_canonical_filename_field(request_rec *r)
+{
+ return r->canonical_filename;
+}
+
+static const char *req_filename_field(request_rec *r)
+{
+ return r->filename;
+}
+
+static const char *req_user_field(request_rec *r)
+{
+ return r->user;
+}
+
+static const char *req_unparsed_uri_field(request_rec *r)
+{
+ return r->unparsed_uri;
+}
+
+static const char *req_ap_auth_type_field(request_rec *r)
+{
+ return r->ap_auth_type;
+}
+
+static const char *req_content_encoding_field(request_rec *r)
+{
+ return r->content_encoding;
+}
+
+static const char *req_content_type_field(request_rec *r)
+{
+ return r->content_type;
+}
+
+static const char *req_range_field(request_rec *r)
+{
+ return r->range;
+}
+
+static const char *req_protocol_field(request_rec *r)
+{
+ return r->protocol;
+}
+
+static const char *req_the_request_field(request_rec *r)
+{
+ return r->the_request;
+}
+
+static const char *req_log_id_field(request_rec *r)
+{
+ return r->log_id;
+}
+
+static const char *req_useragent_ip_field(request_rec *r)
+{
+ return r->useragent_ip;
+}
+
+static int req_remaining_field(request_rec *r)
+{
+ return r->remaining;
+}
+
+static int req_status_field(request_rec *r)
+{
+ return r->status;
+}
+
+static int req_assbackwards_field(request_rec *r)
+{
+ return r->assbackwards;
+}
+
+static req_table_t* req_headers_in(request_rec *r)
+{
+ req_table_t* t = apr_palloc(r->pool, sizeof(req_table_t));
+ t->r = r;
+ t->t = r->headers_in;
+ t->n = "headers_in";
+ return t;
+}
+
+static req_table_t* req_headers_out(request_rec *r)
+{
+ req_table_t* t = apr_palloc(r->pool, sizeof(req_table_t));
+ t->r = r;
+ t->t = r->headers_out;
+ t->n = "headers_out";
+ return t;
+}
+
+static req_table_t* req_err_headers_out(request_rec *r)
+{
+ req_table_t* t = apr_palloc(r->pool, sizeof(req_table_t));
+ t->r = r;
+ t->t = r->err_headers_out;
+ t->n = "err_headers_out";
+ return t;
+}
+
+static req_table_t* req_subprocess_env(request_rec *r)
+{
+ req_table_t* t = apr_palloc(r->pool, sizeof(req_table_t));
+ t->r = r;
+ t->t = r->subprocess_env;
+ t->n = "subprocess_env";
+ return t;
+}
+
+static req_table_t* req_notes(request_rec *r)
+{
+ req_table_t* t = apr_palloc(r->pool, sizeof(req_table_t));
+ t->r = r;
+ t->t = r->notes;
+ t->n = "notes";
+ return t;
+}
+
+static int req_ssl_is_https_field(request_rec *r)
+{
+ return ap_lua_ssl_is_https(r->connection);
+}
+
+static int req_ap_get_server_port(request_rec *r)
+{
+ return (int) ap_get_server_port(r);
+}
+
+static int lua_ap_rflush (lua_State *L) {
+
+ int returnValue;
+ request_rec *r;
+ luaL_checktype(L, 1, LUA_TUSERDATA);
+ r = ap_lua_check_request_rec(L, 1);
+ returnValue = ap_rflush(r);
+ lua_pushboolean(L, (returnValue == 0));
+ return 1;
+}
+
+
+static const char* lua_ap_options(request_rec* r)
+{
+ int opts;
+ opts = ap_allow_options(r);
+ return apr_psprintf(r->pool, "%s %s %s %s %s %s", (opts&OPT_INDEXES) ? "Indexes" : "", (opts&OPT_INCLUDES) ? "Includes" : "", (opts&OPT_SYM_LINKS) ? "FollowSymLinks" : "", (opts&OPT_EXECCGI) ? "ExecCGI" : "", (opts&OPT_MULTI) ? "MultiViews" : "", (opts&OPT_ALL) == OPT_ALL ? "All" : "" );
+}
+
+static const char* lua_ap_allowoverrides(request_rec* r)
+{
+ int opts;
+ opts = ap_allow_overrides(r);
+ if ( (opts & OR_ALL) == OR_ALL) {
+ return "All";
+ }
+ else if (opts == OR_NONE) {
+ return "None";
+ }
+ return apr_psprintf(r->pool, "%s %s %s %s %s", (opts & OR_LIMIT) ? "Limit" : "", (opts & OR_OPTIONS) ? "Options" : "", (opts & OR_FILEINFO) ? "FileInfo" : "", (opts & OR_AUTHCFG) ? "AuthCfg" : "", (opts & OR_INDEXES) ? "Indexes" : "" );
+
+}
+
+static int lua_ap_started(request_rec* r)
+{
+ return (int)(ap_scoreboard_image->global->restart_time / 1000000);
+}
+
+static const char* lua_ap_basic_auth_pw(request_rec* r)
+{
+ const char* pw = NULL;
+ ap_get_basic_auth_pw(r, &pw);
+ return pw ? pw : "";
+}
+
+static int lua_ap_limit_req_body(request_rec* r)
+{
+ return (int) ap_get_limit_req_body(r);
+}
+
+static int lua_ap_is_initial_req(request_rec *r)
+{
+ return ap_is_initial_req(r);
+}
+
+static int lua_ap_some_auth_required(request_rec *r)
+{
+ return ap_some_auth_required(r);
+}
+
+static int lua_ap_sendfile(lua_State *L)
+{
+
+ apr_finfo_t file_info;
+ const char *filename;
+ request_rec *r;
+
+ luaL_checktype(L, 1, LUA_TUSERDATA);
+ luaL_checktype(L, 2, LUA_TSTRING);
+ r = ap_lua_check_request_rec(L, 1);
+ filename = lua_tostring(L, 2);
+ apr_stat(&file_info, filename, APR_FINFO_MIN, r->pool);
+ if (file_info.filetype == APR_NOFILE || file_info.filetype == APR_DIR) {
+ lua_pushboolean(L, 0);
+ }
+ else {
+ apr_size_t sent;
+ apr_status_t rc;
+ apr_file_t *file;
+
+ rc = apr_file_open(&file, filename, APR_READ, APR_OS_DEFAULT,
+ r->pool);
+ if (rc == APR_SUCCESS) {
+ ap_send_fd(file, r, 0, (apr_size_t)file_info.size, &sent);
+ apr_file_close(file);
+ lua_pushinteger(L, sent);
+ }
+ else {
+ lua_pushboolean(L, 0);
+ }
+ }
+
+ return (1);
+}
+
+
+/*
+ * lua_apr_b64encode; r:encode_base64(string) - encodes a string to Base64
+ * format
+ */
+static int lua_apr_b64encode(lua_State *L)
+{
+ const char *plain;
+ char *encoded;
+ size_t plain_len, encoded_len;
+ request_rec *r;
+
+ r = ap_lua_check_request_rec(L, 1);
+ luaL_checktype(L, 2, LUA_TSTRING);
+ plain = lua_tolstring(L, 2, &plain_len);
+ encoded_len = apr_base64_encode_len(plain_len);
+ if (encoded_len) {
+ encoded = apr_palloc(r->pool, encoded_len);
+ encoded_len = apr_base64_encode(encoded, plain, plain_len);
+ if (encoded_len > 0 && encoded[encoded_len - 1] == '\0')
+ encoded_len--;
+ lua_pushlstring(L, encoded, encoded_len);
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * lua_apr_b64decode; r:decode_base64(string) - decodes a Base64 string
+ */
+static int lua_apr_b64decode(lua_State *L)
+{
+ const char *encoded;
+ char *plain;
+ size_t encoded_len, decoded_len;
+ request_rec *r;
+
+ r = ap_lua_check_request_rec(L, 1);
+ luaL_checktype(L, 2, LUA_TSTRING);
+ encoded = lua_tolstring(L, 2, &encoded_len);
+ decoded_len = apr_base64_decode_len(encoded);
+ if (decoded_len) {
+ plain = apr_palloc(r->pool, decoded_len);
+ decoded_len = apr_base64_decode(plain, encoded);
+ if (decoded_len > 0 && plain[decoded_len - 1] == '\0')
+ decoded_len--;
+ lua_pushlstring(L, plain, decoded_len);
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * lua_ap_unescape; r:unescape(string) - Unescapes an URL-encoded string
+ */
+static int lua_ap_unescape(lua_State *L)
+{
+ const char *escaped;
+ char *plain;
+ size_t x,
+ y;
+ request_rec *r;
+ r = ap_lua_check_request_rec(L, 1);
+ luaL_checktype(L, 2, LUA_TSTRING);
+ escaped = lua_tolstring(L, 2, &x);
+ plain = apr_pstrdup(r->pool, escaped);
+ y = ap_unescape_urlencoded(plain);
+ if (!y) {
+ lua_pushstring(L, plain);
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * lua_ap_escape; r:escape(string) - URL-escapes a string
+ */
+static int lua_ap_escape(lua_State *L)
+{
+ const char *plain;
+ char *escaped;
+ size_t x;
+ request_rec *r;
+ r = ap_lua_check_request_rec(L, 1);
+ luaL_checktype(L, 2, LUA_TSTRING);
+ plain = lua_tolstring(L, 2, &x);
+ escaped = ap_escape_urlencoded(r->pool, plain);
+ lua_pushstring(L, escaped);
+ return 1;
+}
+
+/*
+ * lua_apr_md5; r:md5(string) - Calculates an MD5 digest of a string
+ */
+static int lua_apr_md5(lua_State *L)
+{
+ const char *buffer;
+ char *result;
+ size_t len;
+ request_rec *r;
+
+ r = ap_lua_check_request_rec(L, 1);
+ luaL_checktype(L, 2, LUA_TSTRING);
+ buffer = lua_tolstring(L, 2, &len);
+ result = ap_md5_binary(r->pool, (const unsigned char *)buffer, len);
+ lua_pushstring(L, result);
+ return 1;
+}
+
+/*
+ * lua_apr_sha1; r:sha1(string) - Calculates the SHA1 digest of a string
+ */
+static int lua_apr_sha1(lua_State *L)
+{
+ unsigned char digest[APR_SHA1_DIGESTSIZE];
+ apr_sha1_ctx_t sha1;
+ const char *buffer;
+ char *result;
+ size_t len;
+ request_rec *r;
+
+ r = ap_lua_check_request_rec(L, 1);
+ luaL_checktype(L, 2, LUA_TSTRING);
+ result = apr_pcalloc(r->pool, sizeof(digest) * 2 + 1);
+ buffer = lua_tolstring(L, 2, &len);
+ apr_sha1_init(&sha1);
+ apr_sha1_update(&sha1, buffer, len);
+ apr_sha1_final(digest, &sha1);
+
+ ap_bin2hex(digest, sizeof(digest), result);
+ lua_pushstring(L, result);
+ return 1;
+}
+
+/*
+ * lua_apr_htpassword; r:htpassword(string [, algorithm [, cost]]) - Creates
+ * a htpassword hash from a string
+ */
+static int lua_apr_htpassword(lua_State *L)
+{
+ passwd_ctx ctx = { 0 };
+ request_rec *r;
+
+ r = ap_lua_check_request_rec(L, 1);
+ luaL_checktype(L, 2, LUA_TSTRING);
+ ctx.passwd = apr_pstrdup(r->pool, lua_tostring(L, 2));
+ ctx.alg = luaL_optinteger(L, 3, ALG_APMD5);
+ ctx.cost = luaL_optinteger(L, 4, 0);
+ ctx.pool = r->pool;
+ ctx.out = apr_pcalloc(r->pool, MAX_PASSWD_LEN);
+ ctx.out_len = MAX_PASSWD_LEN;
+ if (mk_password_hash(&ctx)) {
+ lua_pushboolean(L, 0);
+ lua_pushstring(L, ctx.errstr);
+ return 2;
+ } else {
+ lua_pushstring(L, ctx.out);
+ }
+ return 1;
+}
+
+/*
+ * lua_apr_touch; r:touch(string [, time]) - Sets mtime of a file
+ */
+static int lua_apr_touch(lua_State *L)
+{
+ request_rec *r;
+ const char *path;
+ apr_status_t status;
+ apr_time_t mtime;
+
+ r = ap_lua_check_request_rec(L, 1);
+ luaL_checktype(L, 2, LUA_TSTRING);
+ path = lua_tostring(L, 2);
+ mtime = (apr_time_t)luaL_optnumber(L, 3, (lua_Number)apr_time_now());
+ status = apr_file_mtime_set(path, mtime, r->pool);
+ lua_pushboolean(L, (status == 0));
+ return 1;
+}
+
+/*
+ * lua_apr_mkdir; r:mkdir(string [, permissions]) - Creates a directory
+ */
+static int lua_apr_mkdir(lua_State *L)
+{
+ request_rec *r;
+ const char *path;
+ apr_status_t status;
+ apr_fileperms_t perms;
+
+ r = ap_lua_check_request_rec(L, 1);
+ luaL_checktype(L, 2, LUA_TSTRING);
+ path = lua_tostring(L, 2);
+ perms = luaL_optinteger(L, 3, APR_OS_DEFAULT);
+ status = apr_dir_make(path, perms, r->pool);
+ lua_pushboolean(L, (status == 0));
+ return 1;
+}
+
+/*
+ * lua_apr_mkrdir; r:mkrdir(string [, permissions]) - Creates directories
+ * recursive
+ */
+static int lua_apr_mkrdir(lua_State *L)
+{
+ request_rec *r;
+ const char *path;
+ apr_status_t status;
+ apr_fileperms_t perms;
+
+ r = ap_lua_check_request_rec(L, 1);
+ luaL_checktype(L, 2, LUA_TSTRING);
+ path = lua_tostring(L, 2);
+ perms = luaL_optinteger(L, 3, APR_OS_DEFAULT);
+ status = apr_dir_make_recursive(path, perms, r->pool);
+ lua_pushboolean(L, (status == 0));
+ return 1;
+}
+
+/*
+ * lua_apr_rmdir; r:rmdir(string) - Removes a directory
+ */
+static int lua_apr_rmdir(lua_State *L)
+{
+ request_rec *r;
+ const char *path;
+ apr_status_t status;
+
+ r = ap_lua_check_request_rec(L, 1);
+ luaL_checktype(L, 2, LUA_TSTRING);
+ path = lua_tostring(L, 2);
+ status = apr_dir_remove(path, r->pool);
+ lua_pushboolean(L, (status == 0));
+ return 1;
+}
+
+/*
+ * lua_apr_date_parse_rfc; r.date_parse_rfc(string) - Parses a DateTime string
+ */
+static int lua_apr_date_parse_rfc(lua_State *L)
+{
+ const char *input;
+ apr_time_t result;
+
+ luaL_checktype(L, 1, LUA_TSTRING);
+ input = lua_tostring(L, 1);
+ result = apr_date_parse_rfc(input);
+ if (result == 0)
+ return 0;
+ lua_pushnumber(L, (lua_Number)(result / APR_USEC_PER_SEC));
+ return 1;
+}
+
+/*
+ * lua_ap_mpm_query; r:mpm_query(info) - Queries for MPM info
+ */
+static int lua_ap_mpm_query(lua_State *L)
+{
+ int x,
+ y;
+
+ x = lua_tointeger(L, 1);
+ ap_mpm_query(x, &y);
+ lua_pushinteger(L, y);
+ return 1;
+}
+
+/*
+ * lua_ap_expr; r:expr(string) - Evaluates an expr statement.
+ */
+static int lua_ap_expr(lua_State *L)
+{
+ request_rec *r;
+ int x = 0;
+ const char *expr,
+ *err;
+ ap_expr_info_t res;
+
+ luaL_checktype(L, 1, LUA_TUSERDATA);
+ luaL_checktype(L, 2, LUA_TSTRING);
+ r = ap_lua_check_request_rec(L, 1);
+ expr = lua_tostring(L, 2);
+
+
+ res.filename = NULL;
+ res.flags = 0;
+ res.line_number = 0;
+ res.module_index = APLOG_MODULE_INDEX;
+
+ err = ap_expr_parse(r->pool, r->pool, &res, expr, NULL);
+ if (!err) {
+ x = ap_expr_exec(r, &res, &err);
+ lua_pushboolean(L, x);
+ if (x < 0) {
+ lua_pushstring(L, err);
+ return 2;
+ }
+ return 1;
+ } else {
+ lua_pushboolean(L, 0);
+ lua_pushstring(L, err);
+ return 2;
+ }
+ lua_pushboolean(L, 0);
+ return 1;
+}
+
+
+/*
+ * lua_ap_regex; r:regex(string, pattern [, flags])
+ * - Evaluates a regex and returns captures if matched
+ */
+static int lua_ap_regex(lua_State *L)
+{
+ request_rec *r;
+ int i,
+ rv,
+ flags;
+ const char *pattern,
+ *source;
+ char *err;
+ ap_regex_t regex;
+ ap_regmatch_t matches[MODLUA_MAX_REG_MATCH+1];
+
+ luaL_checktype(L, 1, LUA_TUSERDATA);
+ luaL_checktype(L, 2, LUA_TSTRING);
+ luaL_checktype(L, 3, LUA_TSTRING);
+ r = ap_lua_check_request_rec(L, 1);
+ source = lua_tostring(L, 2);
+ pattern = lua_tostring(L, 3);
+ flags = luaL_optinteger(L, 4, 0);
+
+ rv = ap_regcomp(&regex, pattern, flags);
+ if (rv) {
+ lua_pushboolean(L, 0);
+ err = apr_palloc(r->pool, 256);
+ ap_regerror(rv, &regex, err, 256);
+ lua_pushstring(L, err);
+ return 2;
+ }
+
+ if (regex.re_nsub > MODLUA_MAX_REG_MATCH) {
+ lua_pushboolean(L, 0);
+ err = apr_palloc(r->pool, 64);
+ apr_snprintf(err, 64,
+ "regcomp found %d matches; only %d allowed.",
+ regex.re_nsub, MODLUA_MAX_REG_MATCH);
+ lua_pushstring(L, err);
+ return 2;
+ }
+
+ rv = ap_regexec(&regex, source, MODLUA_MAX_REG_MATCH, matches, 0);
+ if (rv == AP_REG_NOMATCH) {
+ lua_pushboolean(L, 0);
+ return 1;
+ }
+
+ lua_newtable(L);
+ for (i = 0; i <= regex.re_nsub; i++) {
+ lua_pushinteger(L, i);
+ if (matches[i].rm_so >= 0 && matches[i].rm_eo >= 0)
+ lua_pushstring(L,
+ apr_pstrndup(r->pool, source + matches[i].rm_so,
+ matches[i].rm_eo - matches[i].rm_so));
+ else
+ lua_pushnil(L);
+ lua_settable(L, -3);
+
+ }
+ return 1;
+}
+
+
+
+
+/*
+ * lua_ap_scoreboard_process; r:scoreboard_process(a) - returns scoreboard info
+ */
+static int lua_ap_scoreboard_process(lua_State *L)
+{
+ int i;
+ process_score *ps_record;
+
+ luaL_checktype(L, 1, LUA_TUSERDATA);
+ luaL_checktype(L, 2, LUA_TNUMBER);
+ i = lua_tointeger(L, 2);
+ ps_record = ap_get_scoreboard_process(i);
+ if (ps_record) {
+ lua_newtable(L);
+
+ lua_pushstring(L, "connections");
+ lua_pushnumber(L, ps_record->connections);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "keepalive");
+ lua_pushnumber(L, ps_record->keep_alive);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "lingering_close");
+ lua_pushnumber(L, ps_record->lingering_close);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "pid");
+ lua_pushnumber(L, ps_record->pid);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "suspended");
+ lua_pushnumber(L, ps_record->suspended);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "write_completion");
+ lua_pushnumber(L, ps_record->write_completion);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "not_accepting");
+ lua_pushnumber(L, ps_record->not_accepting);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "quiescing");
+ lua_pushnumber(L, ps_record->quiescing);
+ lua_settable(L, -3);
+
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * lua_ap_scoreboard_worker; r:scoreboard_worker(proc, thread) - Returns thread
+ * info
+ */
+static int lua_ap_scoreboard_worker(lua_State *L)
+{
+ int i, j;
+ worker_score *ws_record = NULL;
+ request_rec *r = NULL;
+
+ luaL_checktype(L, 1, LUA_TUSERDATA);
+ luaL_checktype(L, 2, LUA_TNUMBER);
+ luaL_checktype(L, 3, LUA_TNUMBER);
+
+ r = ap_lua_check_request_rec(L, 1);
+ if (!r) return 0;
+
+ i = lua_tointeger(L, 2);
+ j = lua_tointeger(L, 3);
+ ws_record = apr_palloc(r->pool, sizeof *ws_record);
+
+ ap_copy_scoreboard_worker(ws_record, i, j);
+ if (ws_record) {
+ lua_newtable(L);
+
+ lua_pushstring(L, "access_count");
+ lua_pushnumber(L, ws_record->access_count);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "bytes_served");
+ lua_pushnumber(L, (lua_Number) ws_record->bytes_served);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "client");
+ lua_pushstring(L, ws_record->client);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "client64");
+ lua_pushstring(L, ws_record->client64);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "conn_bytes");
+ lua_pushnumber(L, (lua_Number) ws_record->conn_bytes);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "conn_count");
+ lua_pushnumber(L, ws_record->conn_count);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "generation");
+ lua_pushnumber(L, ws_record->generation);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "last_used");
+ lua_pushnumber(L, (lua_Number) ws_record->last_used);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "pid");
+ lua_pushnumber(L, ws_record->pid);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "request");
+ lua_pushstring(L, ws_record->request);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "start_time");
+ lua_pushnumber(L, (lua_Number) ws_record->start_time);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "status");
+ lua_pushnumber(L, ws_record->status);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "stop_time");
+ lua_pushnumber(L, (lua_Number) ws_record->stop_time);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "tid");
+
+ lua_pushinteger(L, (lua_Integer) ws_record->tid);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "vhost");
+ lua_pushstring(L, ws_record->vhost);
+ lua_settable(L, -3);
+#ifdef HAVE_TIMES
+ lua_pushstring(L, "stimes");
+ lua_pushnumber(L, ws_record->times.tms_stime);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "utimes");
+ lua_pushnumber(L, ws_record->times.tms_utime);
+ lua_settable(L, -3);
+#endif
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * lua_ap_clock; r:clock() - Returns timestamp with microsecond precision
+ */
+static int lua_ap_clock(lua_State *L)
+{
+ apr_time_t now;
+ now = apr_time_now();
+ lua_pushnumber(L, (lua_Number) now);
+ return 1;
+}
+
+/*
+ * lua_ap_add_input_filter; r:add_input_filter(name) - Adds an input filter to
+ * the chain
+ */
+static int lua_ap_add_input_filter(lua_State *L)
+{
+ request_rec *r;
+ const char *filterName;
+ ap_filter_rec_t *filter;
+
+ luaL_checktype(L, 1, LUA_TUSERDATA);
+ luaL_checktype(L, 2, LUA_TSTRING);
+ r = ap_lua_check_request_rec(L, 1);
+ filterName = lua_tostring(L, 2);
+ filter = ap_get_input_filter_handle(filterName);
+ if (filter) {
+ ap_add_input_filter_handle(filter, NULL, r, r->connection);
+ lua_pushboolean(L, 1);
+ } else
+ lua_pushboolean(L, 0);
+ return 1;
+}
+
+
+/*
+ * lua_ap_module_info; r:module_info(mod_name) - Returns information about a
+ * loaded module
+ */
+static int lua_ap_module_info(lua_State *L)
+{
+ const char *moduleName;
+ module *mod;
+
+ luaL_checktype(L, 1, LUA_TSTRING);
+ moduleName = lua_tostring(L, 1);
+ mod = ap_find_linked_module(moduleName);
+ if (mod && mod->cmds) {
+ const command_rec *cmd;
+ lua_newtable(L);
+ lua_pushstring(L, "commands");
+ lua_newtable(L);
+ for (cmd = mod->cmds; cmd->name; ++cmd) {
+ lua_pushstring(L, cmd->name);
+ lua_pushstring(L, cmd->errmsg);
+ lua_settable(L, -3);
+ }
+ lua_settable(L, -3);
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * lua_ap_runtime_dir_relative: r:runtime_dir_relative(file): Returns the
+ * filename as relative to the runtime dir
+ */
+static int lua_ap_runtime_dir_relative(lua_State *L)
+{
+ request_rec *r;
+ const char *file;
+
+ luaL_checktype(L, 1, LUA_TUSERDATA);
+ r = ap_lua_check_request_rec(L, 1);
+ file = luaL_optstring(L, 2, ".");
+ lua_pushstring(L, ap_runtime_dir_relative(r->pool, file));
+ return 1;
+}
+
+/*
+ * lua_ap_set_document_root; r:set_document_root(path) - sets the current doc
+ * root for the request
+ */
+static int lua_ap_set_document_root(lua_State *L)
+{
+ request_rec *r;
+ const char *root;
+
+ luaL_checktype(L, 1, LUA_TUSERDATA);
+ luaL_checktype(L, 2, LUA_TSTRING);
+ r = ap_lua_check_request_rec(L, 1);
+ root = lua_tostring(L, 2);
+ ap_set_document_root(r, root);
+ return 0;
+}
+
+/*
+ * lua_ap_getdir; r:get_direntries(directory) - Gets all entries of a
+ * directory and returns the directory info as a table
+ */
+static int lua_ap_getdir(lua_State *L)
+{
+ request_rec *r;
+ apr_dir_t *thedir;
+ apr_finfo_t file_info;
+ apr_status_t status;
+ const char *directory;
+
+ luaL_checktype(L, 1, LUA_TUSERDATA);
+ luaL_checktype(L, 2, LUA_TSTRING);
+ r = ap_lua_check_request_rec(L, 1);
+ directory = lua_tostring(L, 2);
+ if (apr_dir_open(&thedir, directory, r->pool) == APR_SUCCESS) {
+ int i = 0;
+ lua_newtable(L);
+ do {
+ status = apr_dir_read(&file_info, APR_FINFO_NAME, thedir);
+ if (APR_STATUS_IS_INCOMPLETE(status)) {
+ continue; /* ignore un-stat()able files */
+ }
+ else if (status != APR_SUCCESS) {
+ break;
+ }
+ lua_pushinteger(L, ++i);
+ lua_pushstring(L, file_info.name);
+ lua_settable(L, -3);
+
+ } while (1);
+ apr_dir_close(thedir);
+ return 1;
+ }
+ else {
+ return 0;
+ }
+}
+
+/*
+ * lua_ap_stat; r:stat(filename [, wanted]) - Runs stat on a file and
+ * returns the file info as a table
+ */
+static int lua_ap_stat(lua_State *L)
+{
+ request_rec *r;
+ const char *filename;
+ apr_finfo_t file_info;
+ apr_int32_t wanted;
+
+ luaL_checktype(L, 1, LUA_TUSERDATA);
+ luaL_checktype(L, 2, LUA_TSTRING);
+ r = ap_lua_check_request_rec(L, 1);
+ filename = lua_tostring(L, 2);
+ wanted = luaL_optinteger(L, 3, APR_FINFO_MIN);
+ if (apr_stat(&file_info, filename, wanted, r->pool) == OK) {
+ lua_newtable(L);
+ if (wanted & APR_FINFO_MTIME) {
+ lua_pushstring(L, "mtime");
+ lua_pushnumber(L, (lua_Number) file_info.mtime);
+ lua_settable(L, -3);
+ }
+ if (wanted & APR_FINFO_ATIME) {
+ lua_pushstring(L, "atime");
+ lua_pushnumber(L, (lua_Number) file_info.atime);
+ lua_settable(L, -3);
+ }
+ if (wanted & APR_FINFO_CTIME) {
+ lua_pushstring(L, "ctime");
+ lua_pushnumber(L, (lua_Number) file_info.ctime);
+ lua_settable(L, -3);
+ }
+ if (wanted & APR_FINFO_SIZE) {
+ lua_pushstring(L, "size");
+ lua_pushnumber(L, (lua_Number) file_info.size);
+ lua_settable(L, -3);
+ }
+ if (wanted & APR_FINFO_TYPE) {
+ lua_pushstring(L, "filetype");
+ lua_pushinteger(L, file_info.filetype);
+ lua_settable(L, -3);
+ }
+ if (wanted & APR_FINFO_PROT) {
+ lua_pushstring(L, "protection");
+ lua_pushinteger(L, file_info.protection);
+ lua_settable(L, -3);
+ }
+ return 1;
+ }
+ else {
+ return 0;
+ }
+}
+
+/*
+ * lua_ap_loaded_modules; r:loaded_modules() - Returns a list of loaded modules
+ */
+static int lua_ap_loaded_modules(lua_State *L)
+{
+ int i;
+ lua_newtable(L);
+ for (i = 0; ap_loaded_modules[i] && ap_loaded_modules[i]->name; i++) {
+ lua_pushinteger(L, i + 1);
+ lua_pushstring(L, ap_loaded_modules[i]->name);
+ lua_settable(L, -3);
+ }
+ return 1;
+}
+
+/*
+ * lua_ap_server_info; r:server_info() - Returns server info, such as the
+ * executable filename, server root, mpm etc
+ */
+static int lua_ap_server_info(lua_State *L)
+{
+ lua_newtable(L);
+
+ lua_pushstring(L, "server_executable");
+ lua_pushstring(L, ap_server_argv0);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "server_root");
+ lua_pushstring(L, ap_server_root);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "scoreboard_fname");
+ lua_pushstring(L, ap_scoreboard_fname);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "server_mpm");
+ lua_pushstring(L, ap_show_mpm());
+ lua_settable(L, -3);
+
+ return 1;
+}
+
+
+/*
+ * === Auto-scraped functions ===
+ */
+
+
+/**
+ * ap_set_context_info: Set context_prefix and context_document_root.
+ * @param r The request
+ * @param prefix the URI prefix, without trailing slash
+ * @param document_root the corresponding directory on disk, without trailing
+ * slash
+ * @note If one of prefix of document_root is NULL, the corrsponding
+ * property will not be changed.
+ */
+static int lua_ap_set_context_info(lua_State *L)
+{
+ request_rec *r;
+ const char *prefix;
+ const char *document_root;
+ luaL_checktype(L, 1, LUA_TUSERDATA);
+ r = ap_lua_check_request_rec(L, 1);
+ luaL_checktype(L, 2, LUA_TSTRING);
+ prefix = lua_tostring(L, 2);
+ luaL_checktype(L, 3, LUA_TSTRING);
+ document_root = lua_tostring(L, 3);
+ ap_set_context_info(r, prefix, document_root);
+ return 0;
+}
+
+
+/**
+ * ap_os_escape_path (apr_pool_t *p, const char *path, int partial)
+ * convert an OS path to a URL in an OS dependant way.
+ * @param p The pool to allocate from
+ * @param path The path to convert
+ * @param partial if set, assume that the path will be appended to something
+ * with a '/' in it (and thus does not prefix "./")
+ * @return The converted URL
+ */
+static int lua_ap_os_escape_path(lua_State *L)
+{
+ char *returnValue;
+ request_rec *r;
+ const char *path;
+ int partial = 0;
+ luaL_checktype(L, 1, LUA_TUSERDATA);
+ r = ap_lua_check_request_rec(L, 1);
+ luaL_checktype(L, 2, LUA_TSTRING);
+ path = lua_tostring(L, 2);
+ if (lua_isboolean(L, 3))
+ partial = lua_toboolean(L, 3);
+ returnValue = ap_os_escape_path(r->pool, path, partial);
+ lua_pushstring(L, returnValue);
+ return 1;
+}
+
+
+/**
+ * ap_escape_logitem (apr_pool_t *p, const char *str)
+ * Escape a string for logging
+ * @param p The pool to allocate from
+ * @param str The string to escape
+ * @return The escaped string
+ */
+static int lua_ap_escape_logitem(lua_State *L)
+{
+ char *returnValue;
+ request_rec *r;
+ const char *str;
+ luaL_checktype(L, 1, LUA_TUSERDATA);
+ r = ap_lua_check_request_rec(L, 1);
+ luaL_checktype(L, 2, LUA_TSTRING);
+ str = lua_tostring(L, 2);
+ returnValue = ap_escape_logitem(r->pool, str);
+ lua_pushstring(L, returnValue);
+ return 1;
+}
+
+/**
+ * ap_strcmp_match (const char *str, const char *expected)
+ * Determine if a string matches a pattern containing the wildcards '?' or '*'
+ * @param str The string to check
+ * @param expected The pattern to match against
+ * @param ignoreCase Whether to ignore case when matching
+ * @return 1 if the two strings match, 0 otherwise
+ */
+static int lua_ap_strcmp_match(lua_State *L)
+{
+ int returnValue;
+ const char *str;
+ const char *expected;
+ int ignoreCase = 0;
+ luaL_checktype(L, 1, LUA_TSTRING);
+ str = lua_tostring(L, 1);
+ luaL_checktype(L, 2, LUA_TSTRING);
+ expected = lua_tostring(L, 2);
+ if (lua_isboolean(L, 3))
+ ignoreCase = lua_toboolean(L, 3);
+ if (!ignoreCase)
+ returnValue = ap_strcmp_match(str, expected);
+ else
+ returnValue = ap_strcasecmp_match(str, expected);
+ lua_pushboolean(L, (!returnValue));
+ return 1;
+}
+
+
+/**
+ * ap_set_keepalive (request_rec *r)
+ * Set the keepalive status for this request
+ * @param r The current request
+ * @return 1 if keepalive can be set, 0 otherwise
+ */
+static int lua_ap_set_keepalive(lua_State *L)
+{
+ int returnValue;
+ request_rec *r;
+ luaL_checktype(L, 1, LUA_TUSERDATA);
+ r = ap_lua_check_request_rec(L, 1);
+ returnValue = ap_set_keepalive(r);
+ lua_pushboolean(L, returnValue);
+ return 1;
+}
+
+/**
+ * ap_make_etag (request_rec *r, int force_weak)
+ * Construct an entity tag from the resource information. If it's a real
+ * file, build in some of the file characteristics.
+ * @param r The current request
+ * @param force_weak Force the entity tag to be weak - it could be modified
+ * again in as short an interval.
+ * @return The entity tag
+ */
+static int lua_ap_make_etag(lua_State *L)
+{
+ char *returnValue;
+ request_rec *r;
+ int force_weak;
+ luaL_checktype(L, 1, LUA_TUSERDATA);
+ r = ap_lua_check_request_rec(L, 1);
+ luaL_checktype(L, 2, LUA_TBOOLEAN);
+ force_weak = (int)luaL_optinteger(L, 2, 0);
+ returnValue = ap_make_etag(r, force_weak);
+ lua_pushstring(L, returnValue);
+ return 1;
+}
+
+
+
+/**
+ * ap_send_interim_response (request_rec *r, int send_headers)
+ * Send an interim (HTTP 1xx) response immediately.
+ * @param r The request
+ * @param send_headers Whether to send&clear headers in r->headers_out
+ */
+static int lua_ap_send_interim_response(lua_State *L)
+{
+ request_rec *r;
+ int send_headers = 0;
+ luaL_checktype(L, 1, LUA_TUSERDATA);
+ r = ap_lua_check_request_rec(L, 1);
+ if (lua_isboolean(L, 2))
+ send_headers = lua_toboolean(L, 2);
+ ap_send_interim_response(r, send_headers);
+ return 0;
+}
+
+
+/**
+ * ap_custom_response (request_rec *r, int status, const char *string)
+ * Install a custom response handler for a given status
+ * @param r The current request
+ * @param status The status for which the custom response should be used
+ * @param string The custom response. This can be a static string, a file
+ * or a URL
+ */
+static int lua_ap_custom_response(lua_State *L)
+{
+ request_rec *r;
+ int status;
+ const char *string;
+ luaL_checktype(L, 1, LUA_TUSERDATA);
+ r = ap_lua_check_request_rec(L, 1);
+ luaL_checktype(L, 2, LUA_TNUMBER);
+ status = lua_tointeger(L, 2);
+ luaL_checktype(L, 3, LUA_TSTRING);
+ string = lua_tostring(L, 3);
+ ap_custom_response(r, status, string);
+ return 0;
+}
+
+
+/**
+ * ap_exists_config_define (const char *name)
+ * Check for a definition from the server command line
+ * @param name The define to check for
+ * @return 1 if defined, 0 otherwise
+ */
+static int lua_ap_exists_config_define(lua_State *L)
+{
+ int returnValue;
+ const char *name;
+ luaL_checktype(L, 1, LUA_TSTRING);
+ name = lua_tostring(L, 1);
+ returnValue = ap_exists_config_define(name);
+ lua_pushboolean(L, returnValue);
+ return 1;
+}
+
+static int lua_ap_get_server_name_for_url(lua_State *L)
+{
+ const char *servername;
+ request_rec *r;
+ luaL_checktype(L, 1, LUA_TUSERDATA);
+ r = ap_lua_check_request_rec(L, 1);
+ servername = ap_get_server_name_for_url(r);
+ lua_pushstring(L, servername);
+ return 1;
+}
+
+/* ap_state_query (int query_code) item starts a new field */
+static int lua_ap_state_query(lua_State *L)
+{
+
+ int returnValue;
+ int query_code;
+ luaL_checktype(L, 1, LUA_TNUMBER);
+ query_code = lua_tointeger(L, 1);
+ returnValue = ap_state_query(query_code);
+ lua_pushinteger(L, returnValue);
+ return 1;
+}
+
+/*
+ * lua_ap_usleep; r:usleep(microseconds)
+ * - Sleep for the specified number of microseconds.
+ */
+static int lua_ap_usleep(lua_State *L)
+{
+ apr_interval_time_t msec;
+ luaL_checktype(L, 1, LUA_TNUMBER);
+ msec = (apr_interval_time_t)lua_tonumber(L, 1);
+ apr_sleep(msec);
+ return 0;
+}
+
+/* END dispatch methods for request_rec fields */
+
+static int req_dispatch(lua_State *L)
+{
+ apr_hash_t *dispatch;
+ req_fun_t *rft;
+ request_rec *r = ap_lua_check_request_rec(L, 1);
+ const char *name = luaL_checkstring(L, 2);
+ lua_pop(L, 2);
+
+ lua_getfield(L, LUA_REGISTRYINDEX, "Apache2.Request.dispatch");
+ dispatch = lua_touserdata(L, 1);
+ lua_pop(L, 1);
+
+ rft = apr_hash_get(dispatch, name, APR_HASH_KEY_STRING);
+ if (rft) {
+ switch (rft->type) {
+ case APL_REQ_FUNTYPE_TABLE:{
+ req_table_t *rs;
+ req_field_apr_table_f func = (req_field_apr_table_f)rft->fun;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01486)
+ "request_rec->dispatching %s -> apr table",
+ name);
+ rs = (*func)(r);
+ ap_lua_push_apr_table(L, rs);
+ return 1;
+ }
+
+ case APL_REQ_FUNTYPE_LUACFUN:{
+ lua_CFunction func = (lua_CFunction)rft->fun;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01487)
+ "request_rec->dispatching %s -> lua_CFunction",
+ name);
+ lua_pushcfunction(L, func);
+ return 1;
+ }
+ case APL_REQ_FUNTYPE_STRING:{
+ req_field_string_f func = (req_field_string_f)rft->fun;
+ char *rs;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01488)
+ "request_rec->dispatching %s -> string", name);
+ rs = (*func) (r);
+ lua_pushstring(L, rs);
+ return 1;
+ }
+ case APL_REQ_FUNTYPE_INT:{
+ req_field_int_f func = (req_field_int_f)rft->fun;
+ int rs;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01489)
+ "request_rec->dispatching %s -> int", name);
+ rs = (*func) (r);
+ lua_pushinteger(L, rs);
+ return 1;
+ }
+ case APL_REQ_FUNTYPE_BOOLEAN:{
+ req_field_int_f func = (req_field_int_f)rft->fun;
+ int rs;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01490)
+ "request_rec->dispatching %s -> boolean", name);
+ rs = (*func) (r);
+ lua_pushboolean(L, rs);
+ return 1;
+ }
+ }
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01491) "nothing for %s", name);
+ return 0;
+}
+
+/* helper function for the logging functions below */
+static int req_log_at(lua_State *L, int level)
+{
+ const char *msg;
+ request_rec *r = ap_lua_check_request_rec(L, 1);
+ lua_Debug dbg;
+
+ lua_getstack(L, 1, &dbg);
+ lua_getinfo(L, "Sl", &dbg);
+
+ msg = luaL_checkstring(L, 2);
+ /* Intentional no APLOGNO */
+ ap_log_rerror(dbg.source, dbg.currentline, APLOG_MODULE_INDEX, level, 0,
+ r, "%s", msg);
+ return 0;
+}
+
+/* r:debug(String) and friends which use apache logging */
+static int req_emerg(lua_State *L)
+{
+ return req_log_at(L, APLOG_EMERG);
+}
+static int req_alert(lua_State *L)
+{
+ return req_log_at(L, APLOG_ALERT);
+}
+static int req_crit(lua_State *L)
+{
+ return req_log_at(L, APLOG_CRIT);
+}
+static int req_err(lua_State *L)
+{
+ return req_log_at(L, APLOG_ERR);
+}
+static int req_warn(lua_State *L)
+{
+ return req_log_at(L, APLOG_WARNING);
+}
+static int req_notice(lua_State *L)
+{
+ return req_log_at(L, APLOG_NOTICE);
+}
+static int req_info(lua_State *L)
+{
+ return req_log_at(L, APLOG_INFO);
+}
+static int req_debug(lua_State *L)
+{
+ return req_log_at(L, APLOG_DEBUG);
+}
+
+static int lua_ivm_get(lua_State *L)
+{
+ const char *key, *raw_key;
+ apr_pool_t *pool;
+ lua_ivm_object *object = NULL;
+ request_rec *r = ap_lua_check_request_rec(L, 1);
+ key = luaL_checkstring(L, 2);
+ raw_key = apr_pstrcat(r->pool, "lua_ivm_", key, NULL);
+ apr_global_mutex_lock(lua_ivm_mutex);
+ pool = *((apr_pool_t**) apr_shm_baseaddr_get(lua_ivm_shm));
+ apr_pool_userdata_get((void **)&object, raw_key, pool);
+ if (object) {
+ if (object->type == LUA_TBOOLEAN) lua_pushboolean(L, (int) object->number);
+ else if (object->type == LUA_TNUMBER) lua_pushnumber(L, object->number);
+ else if (object->type == LUA_TSTRING) lua_pushlstring(L, object->vb.buf, object->size);
+ apr_global_mutex_unlock(lua_ivm_mutex);
+ return 1;
+ }
+ else {
+ apr_global_mutex_unlock(lua_ivm_mutex);
+ return 0;
+ }
+}
+
+
+static int lua_ivm_set(lua_State *L)
+{
+ const char *key, *raw_key;
+ const char *value = NULL;
+ apr_pool_t *pool;
+ size_t str_len;
+ lua_ivm_object *object = NULL;
+ request_rec *r = ap_lua_check_request_rec(L, 1);
+ key = luaL_checkstring(L, 2);
+ luaL_checkany(L, 3);
+ raw_key = apr_pstrcat(r->pool, "lua_ivm_", key, NULL);
+
+ apr_global_mutex_lock(lua_ivm_mutex);
+ pool = *((apr_pool_t**) apr_shm_baseaddr_get(lua_ivm_shm));
+ apr_pool_userdata_get((void **)&object, raw_key, pool);
+ if (!object) {
+ object = apr_pcalloc(pool, sizeof(lua_ivm_object));
+ ap_varbuf_init(pool, &object->vb, 2);
+ object->size = 1;
+ object->vb_size = 1;
+ }
+ object->type = lua_type(L, 3);
+ if (object->type == LUA_TNUMBER) object->number = lua_tonumber(L, 3);
+ else if (object->type == LUA_TBOOLEAN) object->number = lua_tonumber(L, 3);
+ else if (object->type == LUA_TSTRING) {
+ value = lua_tolstring(L, 3, &str_len);
+ str_len++; /* add trailing \0 */
+ if ( str_len > object->vb_size) {
+ ap_varbuf_grow(&object->vb, str_len);
+ object->vb_size = str_len;
+ }
+ object->size = str_len-1;
+ memset(object->vb.buf, 0, str_len);
+ memcpy(object->vb.buf, value, str_len-1);
+ }
+ apr_pool_userdata_set(object, raw_key, NULL, pool);
+ apr_global_mutex_unlock(lua_ivm_mutex);
+ return 0;
+}
+
+static int lua_get_cookie(lua_State *L)
+{
+ const char *key, *cookie;
+ request_rec *r = ap_lua_check_request_rec(L, 1);
+ key = luaL_checkstring(L, 2);
+ cookie = NULL;
+ ap_cookie_read(r, key, &cookie, 0);
+ if (cookie != NULL) {
+ lua_pushstring(L, cookie);
+ return 1;
+ }
+ return 0;
+}
+
+static int lua_set_cookie(lua_State *L)
+{
+ const char *key, *value, *out, *path = "", *domain = "";
+ const char *strexpires = "", *strdomain = "", *strpath = "";
+ int secure = 0, expires = 0, httponly = 0;
+ char cdate[APR_RFC822_DATE_LEN+1];
+ apr_status_t rv;
+ request_rec *r = ap_lua_check_request_rec(L, 1);
+
+ /* New >= 2.4.8 method: */
+ if (lua_istable(L, 2)) {
+
+ /* key */
+ lua_pushstring(L, "key");
+ lua_gettable(L, -2);
+ key = luaL_checkstring(L, -1);
+ lua_pop(L, 1);
+
+ /* value */
+ lua_pushstring(L, "value");
+ lua_gettable(L, -2);
+ value = luaL_checkstring(L, -1);
+ lua_pop(L, 1);
+
+ /* expiry */
+ lua_pushstring(L, "expires");
+ lua_gettable(L, -2);
+ expires = (int)luaL_optinteger(L, -1, 0);
+ lua_pop(L, 1);
+
+ /* secure */
+ lua_pushstring(L, "secure");
+ lua_gettable(L, -2);
+ if (lua_isboolean(L, -1)) {
+ secure = lua_toboolean(L, -1);
+ }
+ lua_pop(L, 1);
+
+ /* httponly */
+ lua_pushstring(L, "httponly");
+ lua_gettable(L, -2);
+ if (lua_isboolean(L, -1)) {
+ httponly = lua_toboolean(L, -1);
+ }
+ lua_pop(L, 1);
+
+ /* path */
+ lua_pushstring(L, "path");
+ lua_gettable(L, -2);
+ path = luaL_optstring(L, -1, "/");
+ lua_pop(L, 1);
+
+ /* domain */
+ lua_pushstring(L, "domain");
+ lua_gettable(L, -2);
+ domain = luaL_optstring(L, -1, "");
+ lua_pop(L, 1);
+ }
+ /* Old <= 2.4.7 method: */
+ else {
+ key = luaL_checkstring(L, 2);
+ value = luaL_checkstring(L, 3);
+ secure = 0;
+ if (lua_isboolean(L, 4)) {
+ secure = lua_toboolean(L, 4);
+ }
+ expires = luaL_optinteger(L, 5, 0);
+ }
+
+ /* Calculate expiry if set */
+ if (expires > 0) {
+ rv = apr_rfc822_date(cdate, apr_time_from_sec(expires));
+ if (rv == APR_SUCCESS) {
+ strexpires = apr_psprintf(r->pool, "Expires=%s;", cdate);
+ }
+ }
+
+ /* Create path segment */
+ if (path != NULL && strlen(path) > 0) {
+ strpath = apr_psprintf(r->pool, "Path=%s;", path);
+ }
+
+ /* Create domain segment */
+ if (domain != NULL && strlen(domain) > 0) {
+ /* Domain does NOT like quotes in most browsers, so let's avoid that */
+ strdomain = apr_psprintf(r->pool, "Domain=%s;", domain);
+ }
+
+ /* URL-encode key/value */
+ value = ap_escape_urlencoded(r->pool, value);
+ key = ap_escape_urlencoded(r->pool, key);
+
+ /* Create the header */
+ out = apr_psprintf(r->pool, "%s=%s; %s %s %s %s %s", key, value,
+ secure ? "Secure;" : "",
+ expires ? strexpires : "",
+ httponly ? "HttpOnly;" : "",
+ *strdomain ? strdomain : "",
+ *strpath ? strpath : "");
+
+ apr_table_add(r->err_headers_out, "Set-Cookie", out);
+ return 0;
+}
+
+static apr_uint64_t ap_ntoh64(const apr_uint64_t *input)
+{
+ apr_uint64_t rval;
+ unsigned char *data = (unsigned char *)&rval;
+ if (APR_IS_BIGENDIAN) {
+ return *input;
+ }
+
+ data[0] = *input >> 56;
+ data[1] = *input >> 48;
+ data[2] = *input >> 40;
+ data[3] = *input >> 32;
+ data[4] = *input >> 24;
+ data[5] = *input >> 16;
+ data[6] = *input >> 8;
+ data[7] = *input >> 0;
+
+ return rval;
+}
+
+static int lua_websocket_greet(lua_State *L)
+{
+ const char *key = NULL;
+ unsigned char digest[APR_SHA1_DIGESTSIZE];
+ apr_sha1_ctx_t sha1;
+ char *encoded;
+ int encoded_len;
+ request_rec *r = ap_lua_check_request_rec(L, 1);
+ key = apr_table_get(r->headers_in, "Sec-WebSocket-Key");
+ if (key != NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03011)
+ "Websocket: Got websocket key: %s", key);
+ key = apr_pstrcat(r->pool, key, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11",
+ NULL);
+ apr_sha1_init(&sha1);
+ apr_sha1_update(&sha1, key, strlen(key));
+ apr_sha1_final(digest, &sha1);
+ encoded_len = apr_base64_encode_len(APR_SHA1_DIGESTSIZE);
+ if (encoded_len) {
+ encoded = apr_palloc(r->pool, encoded_len);
+ encoded_len = apr_base64_encode(encoded, (char*) digest, APR_SHA1_DIGESTSIZE);
+ r->status = 101;
+ apr_table_setn(r->headers_out, "Upgrade", "websocket");
+ apr_table_setn(r->headers_out, "Connection", "Upgrade");
+ apr_table_setn(r->headers_out, "Sec-WebSocket-Accept", encoded);
+
+ /* Trick httpd into NOT using the chunked filter, IMPORTANT!!!111*/
+ apr_table_setn(r->headers_out, "Transfer-Encoding", "chunked");
+
+ r->clength = 0;
+ r->bytes_sent = 0;
+ r->read_chunked = 0;
+ ap_rflush(r);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03012)
+ "Websocket: Upgraded from HTTP to Websocket");
+ lua_pushboolean(L, 1);
+ return 1;
+ }
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, APLOGNO(02666)
+ "Websocket: Upgrade from HTTP to Websocket failed");
+ return 0;
+}
+
+static apr_status_t lua_websocket_readbytes(conn_rec* c,
+ apr_bucket_brigade *brigade,
+ char* buffer, apr_off_t len)
+{
+ apr_size_t delivered;
+ apr_status_t rv;
+
+ rv = ap_get_brigade(c->input_filters, brigade, AP_MODE_READBYTES,
+ APR_BLOCK_READ, len);
+ if (rv == APR_SUCCESS) {
+ delivered = len;
+ rv = apr_brigade_flatten(brigade, buffer, &delivered);
+ if ((rv == APR_SUCCESS) && (delivered < len)) {
+ rv = APR_INCOMPLETE;
+ }
+ }
+ apr_brigade_cleanup(brigade);
+ return rv;
+}
+
+static int lua_websocket_peek(lua_State *L)
+{
+ apr_status_t rv;
+ apr_bucket_brigade *brigade;
+
+ request_rec *r = ap_lua_check_request_rec(L, 1);
+
+ brigade = apr_brigade_create(r->connection->pool,
+ r->connection->bucket_alloc);
+ rv = ap_get_brigade(r->connection->input_filters, brigade,
+ AP_MODE_READBYTES, APR_NONBLOCK_READ, 1);
+ if (rv == APR_SUCCESS) {
+ lua_pushboolean(L, 1);
+ }
+ else {
+ lua_pushboolean(L, 0);
+ }
+ apr_brigade_cleanup(brigade);
+ return 1;
+}
+
+static int lua_websocket_read(lua_State *L)
+{
+ apr_status_t rv;
+ int do_read = 1;
+ int n = 0;
+ apr_size_t plen = 0;
+ unsigned short payload_short = 0;
+ apr_uint64_t payload_long = 0;
+ unsigned char *mask_bytes;
+ char byte;
+ apr_bucket_brigade *brigade;
+ conn_rec* c;
+
+ request_rec *r = ap_lua_check_request_rec(L, 1);
+ c = r->connection;
+
+ mask_bytes = apr_pcalloc(r->pool, 4);
+
+ brigade = apr_brigade_create(r->pool, c->bucket_alloc);
+
+ while (do_read) {
+ do_read = 0;
+ /* Get opcode and FIN bit */
+ rv = lua_websocket_readbytes(c, brigade, &byte, 1);
+ if (rv == APR_SUCCESS) {
+ unsigned char ubyte, fin, opcode, mask, payload;
+ ubyte = (unsigned char)byte;
+ /* fin bit is the first bit */
+ fin = ubyte >> (CHAR_BIT - 1);
+ /* opcode is the last four bits (there's 3 reserved bits we don't care about) */
+ opcode = ubyte & 0xf;
+
+ /* Get the payload length and mask bit */
+ rv = lua_websocket_readbytes(c, brigade, &byte, 1);
+ if (rv == APR_SUCCESS) {
+ ubyte = (unsigned char)byte;
+ /* Mask is the first bit */
+ mask = ubyte >> (CHAR_BIT - 1);
+ /* Payload is the last 7 bits */
+ payload = ubyte & 0x7f;
+ plen = payload;
+
+ /* Extended payload? */
+ if (payload == 126) {
+ rv = lua_websocket_readbytes(c, brigade,
+ (char*) &payload_short, 2);
+
+ if (rv != APR_SUCCESS) {
+ return 0;
+ }
+
+ plen = ntohs(payload_short);
+ }
+ /* Super duper extended payload? */
+ if (payload == 127) {
+ rv = lua_websocket_readbytes(c, brigade,
+ (char*) &payload_long, 8);
+
+ if (rv != APR_SUCCESS) {
+ return 0;
+ }
+
+ plen = ap_ntoh64(&payload_long);
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03210)
+ "Websocket: Reading %" APR_SIZE_T_FMT " (%s) bytes, masking is %s. %s",
+ plen,
+ (payload >= 126) ? "extra payload" : "no extra payload",
+ mask ? "on" : "off",
+ fin ? "This is a final frame" : "more to follow");
+ if (mask) {
+ rv = lua_websocket_readbytes(c, brigade,
+ (char*) mask_bytes, 4);
+
+ if (rv != APR_SUCCESS) {
+ return 0;
+ }
+ }
+ if (plen < (HUGE_STRING_LEN*1024) && plen > 0) {
+ apr_size_t remaining = plen;
+ char *buffer = apr_palloc(r->pool, plen+1);
+ buffer[plen] = 0;
+
+ rv = lua_websocket_readbytes(c, brigade, buffer, remaining);
+
+ if (rv != APR_SUCCESS) {
+ return 0;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "Websocket: Frame contained %" APR_SIZE_T_FMT \
+ " bytes, pushed to Lua stack", remaining);
+ if (mask) {
+ for (n = 0; n < plen; n++) {
+ buffer[n] ^= mask_bytes[n%4];
+ }
+ }
+
+ lua_pushlstring(L, buffer, (size_t) plen); /* push to stack */
+ lua_pushboolean(L, fin); /* push FIN bit to stack as boolean */
+ return 2;
+ }
+
+ /* Decide if we need to react to the opcode or not */
+ if (opcode == 0x09) { /* ping */
+ char frame[2];
+ apr_bucket *b;
+
+ frame[0] = 0x8A;
+ frame[1] = 0;
+
+ /* Pong! */
+ b = apr_bucket_transient_create(frame, 2, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(brigade, b);
+
+ rv = ap_pass_brigade(c->output_filters, brigade);
+ apr_brigade_cleanup(brigade);
+
+ if (rv != APR_SUCCESS) {
+ return 0;
+ }
+
+ do_read = 1;
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+
+static int lua_websocket_write(lua_State *L)
+{
+ const char *string;
+ apr_status_t rv;
+ size_t len;
+ int raw = 0;
+ char prelude;
+ request_rec *r = ap_lua_check_request_rec(L, 1);
+
+ if (lua_isboolean(L, 3)) {
+ raw = lua_toboolean(L, 3);
+ }
+ string = lua_tolstring(L, 2, &len);
+
+ if (raw != 1) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03013)
+ "Websocket: Writing framed message to client");
+
+ prelude = 0x81; /* text frame, FIN */
+ ap_rputc(prelude, r);
+ if (len < 126) {
+ ap_rputc(len, r);
+ }
+ else if (len < 65535) {
+ apr_uint16_t slen = len;
+ ap_rputc(126, r);
+ slen = htons(slen);
+ ap_rwrite((char*) &slen, 2, r);
+ }
+ else {
+ apr_uint64_t llen = len;
+ ap_rputc(127, r);
+ llen = ap_ntoh64(&llen); /* ntoh doubles as hton */
+ ap_rwrite((char*) &llen, 8, r);
+ }
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03014)
+ "Websocket: Writing raw message to client");
+ }
+ ap_rwrite(string, len, r);
+ rv = ap_rflush(r);
+ if (rv == APR_SUCCESS) {
+ lua_pushboolean(L, 1);
+ }
+ else {
+ lua_pushboolean(L, 0);
+ }
+ return 1;
+}
+
+
+static int lua_websocket_close(lua_State *L)
+{
+ apr_socket_t *sock;
+ char prelude[2];
+ request_rec *r = ap_lua_check_request_rec(L, 1);
+
+ sock = ap_get_conn_socket(r->connection);
+
+ /* Send a header that says: socket is closing. */
+ prelude[0] = 0x88; /* closing socket opcode */
+ prelude[1] = 0; /* zero length frame */
+ ap_rwrite(prelude, 2, r);
+
+ /* Close up tell the MPM and filters to back off */
+ apr_socket_close(sock);
+ r->output_filters = NULL;
+ r->connection->keepalive = AP_CONN_CLOSE;
+ return 0;
+}
+
+static int lua_websocket_ping(lua_State *L)
+{
+ apr_socket_t *sock;
+ apr_size_t plen;
+ char prelude[2];
+ apr_status_t rv;
+ request_rec *r = ap_lua_check_request_rec(L, 1);
+ sock = ap_get_conn_socket(r->connection);
+
+ /* Send a header that says: PING. */
+ prelude[0] = 0x89; /* ping opcode */
+ prelude[1] = 0;
+ plen = 2;
+ apr_socket_send(sock, prelude, &plen);
+
+
+ /* Get opcode and FIN bit from pong */
+ plen = 2;
+ rv = apr_socket_recv(sock, prelude, &plen);
+ if (rv == APR_SUCCESS) {
+ unsigned char opcode = prelude[0];
+ unsigned char len = prelude[1];
+ unsigned char mask = len >> 7;
+ if (mask) len -= 128;
+ plen = len;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03015)
+ "Websocket: Got PONG opcode: %x", opcode);
+ if (opcode == 0x8A) {
+ lua_pushboolean(L, 1);
+ }
+ else {
+ lua_pushboolean(L, 0);
+ }
+ if (plen > 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "Websocket: Reading %" APR_SIZE_T_FMT " bytes of PONG", plen);
+ return 1;
+ }
+ if (mask) {
+ plen = 2;
+ apr_socket_recv(sock, prelude, &plen);
+ plen = 2;
+ apr_socket_recv(sock, prelude, &plen);
+ }
+ }
+ else {
+ lua_pushboolean(L, 0);
+ }
+ return 1;
+}
+
+
+#define APLUA_REQ_TRACE(lev) static int req_trace##lev(lua_State *L) \
+{ \
+ return req_log_at(L, APLOG_TRACE##lev); \
+}
+
+APLUA_REQ_TRACE(1)
+APLUA_REQ_TRACE(2)
+APLUA_REQ_TRACE(3)
+APLUA_REQ_TRACE(4)
+APLUA_REQ_TRACE(5)
+APLUA_REQ_TRACE(6)
+APLUA_REQ_TRACE(7)
+APLUA_REQ_TRACE(8)
+
+/* handle r.status = 201 */
+static int req_newindex(lua_State *L)
+{
+ const char *key;
+ /* request_rec* r = lua_touserdata(L, lua_upvalueindex(1)); */
+ /* const char* key = luaL_checkstring(L, -2); */
+ request_rec *r = ap_lua_check_request_rec(L, 1);
+ key = luaL_checkstring(L, 2);
+
+ if (0 == strcmp("args", key)) {
+ const char *value = luaL_checkstring(L, 3);
+ r->args = apr_pstrdup(r->pool, value);
+ return 0;
+ }
+
+ if (0 == strcmp("content_type", key)) {
+ const char *value = luaL_checkstring(L, 3);
+ ap_set_content_type(r, apr_pstrdup(r->pool, value));
+ return 0;
+ }
+
+ if (0 == strcmp("filename", key)) {
+ const char *value = luaL_checkstring(L, 3);
+ r->filename = apr_pstrdup(r->pool, value);
+ return 0;
+ }
+
+ if (0 == strcmp("handler", key)) {
+ const char *value = luaL_checkstring(L, 3);
+ r->handler = apr_pstrdup(r->pool, value);
+ return 0;
+ }
+
+ if (0 == strcmp("proxyreq", key)) {
+ int value = luaL_checkinteger(L, 3);
+ r->proxyreq = value;
+ return 0;
+ }
+
+ if (0 == strcmp("status", key)) {
+ int code = luaL_checkinteger(L, 3);
+ r->status = code;
+ return 0;
+ }
+
+ if (0 == strcmp("uri", key)) {
+ const char *value = luaL_checkstring(L, 3);
+ r->uri = apr_pstrdup(r->pool, value);
+ return 0;
+ }
+
+ if (0 == strcmp("user", key)) {
+ const char *value = luaL_checkstring(L, 3);
+ r->user = apr_pstrdup(r->pool, value);
+ return 0;
+ }
+
+ lua_pushstring(L,
+ apr_psprintf(r->pool,
+ "Property [%s] may not be set on a request_rec",
+ key));
+ lua_error(L);
+ return 0;
+}
+
+
+
+/* helper function for walking config trees */
+static void read_cfg_tree(lua_State *L, request_rec *r, ap_directive_t *rcfg) {
+ int x = 0;
+ const char* value;
+ ap_directive_t *cfg;
+ lua_newtable(L);
+
+ for (cfg = rcfg; cfg; cfg = cfg->next) {
+ x++;
+ lua_pushnumber(L, x);
+ lua_newtable(L);
+ value = apr_psprintf(r->pool, "%s %s", cfg->directive, cfg->args);
+ lua_pushstring(L, "directive");
+ lua_pushstring(L, value);
+ lua_settable(L, -3);
+ lua_pushstring(L, "file");
+ lua_pushstring(L, cfg->filename);
+ lua_settable(L, -3);
+ lua_pushstring(L, "line");
+ lua_pushnumber(L, cfg->line_num);
+ lua_settable(L, -3);
+ if (cfg->first_child) {
+ lua_pushstring(L, "children");
+ read_cfg_tree(L, r, cfg->first_child);
+ lua_settable(L, -3);
+ }
+ lua_settable(L, -3);
+ }
+}
+
+static int lua_ap_get_config(lua_State *L) {
+ request_rec *r = ap_lua_check_request_rec(L, 1);
+ read_cfg_tree(L, r, ap_conftree);
+
+ return 1;
+}
+
+
+/* Hack, hack, hack...! TODO: Make this actually work properly */
+static int lua_ap_get_active_config(lua_State *L) {
+ ap_directive_t *subdir;
+ ap_directive_t *dir = ap_conftree;
+ request_rec *r = ap_lua_check_request_rec(L, 1);
+
+ for (dir = ap_conftree; dir; dir = dir->next) {
+ if (ap_strcasestr(dir->directive, "<virtualhost") && dir->first_child) {
+ for (subdir = dir->first_child; subdir; subdir = subdir->next) {
+ if (ap_strcasecmp_match(subdir->directive, "servername") &&
+ !ap_strcasecmp_match(r->hostname, subdir->args)) {
+ read_cfg_tree(L, r, dir->first_child);
+ return 1;
+ }
+ if (ap_strcasecmp_match(subdir->directive, "serveralias") &&
+ !ap_strcasecmp_match(r->hostname, subdir->args)) {
+ read_cfg_tree(L, r, dir->first_child);
+ return 1;
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+
+
+static const struct luaL_Reg request_methods[] = {
+ {"__index", req_dispatch},
+ {"__newindex", req_newindex},
+ /* {"__newindex", req_set_field}, */
+ {NULL, NULL}
+};
+
+
+static const struct luaL_Reg connection_methods[] = {
+ {NULL, NULL}
+};
+
+static const char* lua_ap_auth_name(request_rec* r)
+{
+ const char *name;
+ name = ap_auth_name(r);
+ return name ? name : "";
+}
+
+static const char* lua_ap_get_server_name(request_rec* r)
+{
+ const char *name;
+ name = ap_get_server_name(r);
+ return name ? name : "localhost";
+}
+
+
+
+
+static const struct luaL_Reg server_methods[] = {
+ {NULL, NULL}
+};
+
+
+static req_fun_t *makefun(const void *fun, int type, apr_pool_t *pool)
+{
+ req_fun_t *rft = apr_palloc(pool, sizeof(req_fun_t));
+ rft->fun = fun;
+ rft->type = type;
+ return rft;
+}
+
+void ap_lua_load_request_lmodule(lua_State *L, apr_pool_t *p)
+{
+
+ apr_hash_t *dispatch = apr_hash_make(p);
+
+ apr_hash_set(dispatch, "puts", APR_HASH_KEY_STRING,
+ makefun(&req_puts, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "write", APR_HASH_KEY_STRING,
+ makefun(&req_write, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "document_root", APR_HASH_KEY_STRING,
+ makefun(&req_document_root, APL_REQ_FUNTYPE_STRING, p));
+ apr_hash_set(dispatch, "context_prefix", APR_HASH_KEY_STRING,
+ makefun(&req_context_prefix, APL_REQ_FUNTYPE_STRING, p));
+ apr_hash_set(dispatch, "context_document_root", APR_HASH_KEY_STRING,
+ makefun(&req_context_document_root, APL_REQ_FUNTYPE_STRING, p));
+ apr_hash_set(dispatch, "parseargs", APR_HASH_KEY_STRING,
+ makefun(&req_parseargs, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "parsebody", APR_HASH_KEY_STRING,
+ makefun(&req_parsebody, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "debug", APR_HASH_KEY_STRING,
+ makefun(&req_debug, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "info", APR_HASH_KEY_STRING,
+ makefun(&req_info, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "notice", APR_HASH_KEY_STRING,
+ makefun(&req_notice, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "warn", APR_HASH_KEY_STRING,
+ makefun(&req_warn, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "err", APR_HASH_KEY_STRING,
+ makefun(&req_err, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "crit", APR_HASH_KEY_STRING,
+ makefun(&req_crit, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "alert", APR_HASH_KEY_STRING,
+ makefun(&req_alert, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "emerg", APR_HASH_KEY_STRING,
+ makefun(&req_emerg, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "trace1", APR_HASH_KEY_STRING,
+ makefun(&req_trace1, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "trace2", APR_HASH_KEY_STRING,
+ makefun(&req_trace2, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "trace3", APR_HASH_KEY_STRING,
+ makefun(&req_trace3, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "trace4", APR_HASH_KEY_STRING,
+ makefun(&req_trace4, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "trace5", APR_HASH_KEY_STRING,
+ makefun(&req_trace5, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "trace6", APR_HASH_KEY_STRING,
+ makefun(&req_trace6, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "trace7", APR_HASH_KEY_STRING,
+ makefun(&req_trace7, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "trace8", APR_HASH_KEY_STRING,
+ makefun(&req_trace8, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "add_output_filter", APR_HASH_KEY_STRING,
+ makefun(&req_add_output_filter, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "construct_url", APR_HASH_KEY_STRING,
+ makefun(&req_construct_url, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "escape_html", APR_HASH_KEY_STRING,
+ makefun(&req_escape_html, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "ssl_var_lookup", APR_HASH_KEY_STRING,
+ makefun(&req_ssl_var_lookup, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "is_https", APR_HASH_KEY_STRING,
+ makefun(&req_ssl_is_https_field, APL_REQ_FUNTYPE_BOOLEAN, p));
+ apr_hash_set(dispatch, "assbackwards", APR_HASH_KEY_STRING,
+ makefun(&req_assbackwards_field, APL_REQ_FUNTYPE_BOOLEAN, p));
+ apr_hash_set(dispatch, "status", APR_HASH_KEY_STRING,
+ makefun(&req_status_field, APL_REQ_FUNTYPE_INT, p));
+ apr_hash_set(dispatch, "protocol", APR_HASH_KEY_STRING,
+ makefun(&req_protocol_field, APL_REQ_FUNTYPE_STRING, p));
+ apr_hash_set(dispatch, "range", APR_HASH_KEY_STRING,
+ makefun(&req_range_field, APL_REQ_FUNTYPE_STRING, p));
+ apr_hash_set(dispatch, "content_type", APR_HASH_KEY_STRING,
+ makefun(&req_content_type_field, APL_REQ_FUNTYPE_STRING, p));
+ apr_hash_set(dispatch, "content_encoding", APR_HASH_KEY_STRING,
+ makefun(&req_content_encoding_field, APL_REQ_FUNTYPE_STRING,
+ p));
+ apr_hash_set(dispatch, "ap_auth_type", APR_HASH_KEY_STRING,
+ makefun(&req_ap_auth_type_field, APL_REQ_FUNTYPE_STRING, p));
+ apr_hash_set(dispatch, "unparsed_uri", APR_HASH_KEY_STRING,
+ makefun(&req_unparsed_uri_field, APL_REQ_FUNTYPE_STRING, p));
+ apr_hash_set(dispatch, "user", APR_HASH_KEY_STRING,
+ makefun(&req_user_field, APL_REQ_FUNTYPE_STRING, p));
+ apr_hash_set(dispatch, "filename", APR_HASH_KEY_STRING,
+ makefun(&req_filename_field, APL_REQ_FUNTYPE_STRING, p));
+ apr_hash_set(dispatch, "canonical_filename", APR_HASH_KEY_STRING,
+ makefun(&req_canonical_filename_field,
+ APL_REQ_FUNTYPE_STRING, p));
+ apr_hash_set(dispatch, "path_info", APR_HASH_KEY_STRING,
+ makefun(&req_path_info_field, APL_REQ_FUNTYPE_STRING, p));
+ apr_hash_set(dispatch, "args", APR_HASH_KEY_STRING,
+ makefun(&req_args_field, APL_REQ_FUNTYPE_STRING, p));
+ apr_hash_set(dispatch, "handler", APR_HASH_KEY_STRING,
+ makefun(&req_handler_field, APL_REQ_FUNTYPE_STRING, p));
+ apr_hash_set(dispatch, "hostname", APR_HASH_KEY_STRING,
+ makefun(&req_hostname_field, APL_REQ_FUNTYPE_STRING, p));
+ apr_hash_set(dispatch, "uri", APR_HASH_KEY_STRING,
+ makefun(&req_uri_field, APL_REQ_FUNTYPE_STRING, p));
+ apr_hash_set(dispatch, "the_request", APR_HASH_KEY_STRING,
+ makefun(&req_the_request_field, APL_REQ_FUNTYPE_STRING, p));
+ apr_hash_set(dispatch, "log_id", APR_HASH_KEY_STRING,
+ makefun(&req_log_id_field, APL_REQ_FUNTYPE_STRING, p));
+ apr_hash_set(dispatch, "useragent_ip", APR_HASH_KEY_STRING,
+ makefun(&req_useragent_ip_field, APL_REQ_FUNTYPE_STRING, p));
+ apr_hash_set(dispatch, "method", APR_HASH_KEY_STRING,
+ makefun(&req_method_field, APL_REQ_FUNTYPE_STRING, p));
+ apr_hash_set(dispatch, "proxyreq", APR_HASH_KEY_STRING,
+ makefun(&req_proxyreq_field, APL_REQ_FUNTYPE_STRING, p));
+ apr_hash_set(dispatch, "headers_in", APR_HASH_KEY_STRING,
+ makefun(&req_headers_in, APL_REQ_FUNTYPE_TABLE, p));
+ apr_hash_set(dispatch, "headers_in_table", APR_HASH_KEY_STRING,
+ makefun(&req_headers_in_table, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "headers_out", APR_HASH_KEY_STRING,
+ makefun(&req_headers_out, APL_REQ_FUNTYPE_TABLE, p));
+ apr_hash_set(dispatch, "headers_out_table", APR_HASH_KEY_STRING,
+ makefun(&req_headers_out_table, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "err_headers_out", APR_HASH_KEY_STRING,
+ makefun(&req_err_headers_out, APL_REQ_FUNTYPE_TABLE, p));
+ apr_hash_set(dispatch, "err_headers_out_table", APR_HASH_KEY_STRING,
+ makefun(&req_err_headers_out_table, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "notes", APR_HASH_KEY_STRING,
+ makefun(&req_notes, APL_REQ_FUNTYPE_TABLE, p));
+ apr_hash_set(dispatch, "notes_table", APR_HASH_KEY_STRING,
+ makefun(&req_notes_table, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "subprocess_env", APR_HASH_KEY_STRING,
+ makefun(&req_subprocess_env, APL_REQ_FUNTYPE_TABLE, p));
+ apr_hash_set(dispatch, "subprocess_env_table", APR_HASH_KEY_STRING,
+ makefun(&req_subprocess_env_table, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "flush", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_rflush, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "port", APR_HASH_KEY_STRING,
+ makefun(&req_ap_get_server_port, APL_REQ_FUNTYPE_INT, p));
+ apr_hash_set(dispatch, "banner", APR_HASH_KEY_STRING,
+ makefun(&ap_get_server_banner, APL_REQ_FUNTYPE_STRING, p));
+ apr_hash_set(dispatch, "options", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_options, APL_REQ_FUNTYPE_STRING, p));
+ apr_hash_set(dispatch, "allowoverrides", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_allowoverrides, APL_REQ_FUNTYPE_STRING, p));
+ apr_hash_set(dispatch, "started", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_started, APL_REQ_FUNTYPE_INT, p));
+ apr_hash_set(dispatch, "basic_auth_pw", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_basic_auth_pw, APL_REQ_FUNTYPE_STRING, p));
+ apr_hash_set(dispatch, "limit_req_body", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_limit_req_body, APL_REQ_FUNTYPE_INT, p));
+ apr_hash_set(dispatch, "server_built", APR_HASH_KEY_STRING,
+ makefun(&ap_get_server_built, APL_REQ_FUNTYPE_STRING, p));
+ apr_hash_set(dispatch, "is_initial_req", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_is_initial_req, APL_REQ_FUNTYPE_BOOLEAN, p));
+ apr_hash_set(dispatch, "remaining", APR_HASH_KEY_STRING,
+ makefun(&req_remaining_field, APL_REQ_FUNTYPE_INT, p));
+ apr_hash_set(dispatch, "some_auth_required", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_some_auth_required, APL_REQ_FUNTYPE_BOOLEAN, p));
+ apr_hash_set(dispatch, "server_name", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_get_server_name, APL_REQ_FUNTYPE_STRING, p));
+ apr_hash_set(dispatch, "auth_name", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_auth_name, APL_REQ_FUNTYPE_STRING, p));
+ apr_hash_set(dispatch, "sendfile", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_sendfile, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "dbacquire", APR_HASH_KEY_STRING,
+ makefun(&lua_db_acquire, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "stat", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_stat, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "get_direntries", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_getdir, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "regex", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_regex, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "usleep", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_usleep, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "base64_encode", APR_HASH_KEY_STRING,
+ makefun(&lua_apr_b64encode, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "base64_decode", APR_HASH_KEY_STRING,
+ makefun(&lua_apr_b64decode, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "md5", APR_HASH_KEY_STRING,
+ makefun(&lua_apr_md5, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "sha1", APR_HASH_KEY_STRING,
+ makefun(&lua_apr_sha1, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "htpassword", APR_HASH_KEY_STRING,
+ makefun(&lua_apr_htpassword, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "touch", APR_HASH_KEY_STRING,
+ makefun(&lua_apr_touch, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "mkdir", APR_HASH_KEY_STRING,
+ makefun(&lua_apr_mkdir, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "mkrdir", APR_HASH_KEY_STRING,
+ makefun(&lua_apr_mkrdir, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "rmdir", APR_HASH_KEY_STRING,
+ makefun(&lua_apr_rmdir, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "date_parse_rfc", APR_HASH_KEY_STRING,
+ makefun(&lua_apr_date_parse_rfc, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "escape", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_escape, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "unescape", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_unescape, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "mpm_query", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_mpm_query, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "expr", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_expr, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "scoreboard_process", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_scoreboard_process, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "scoreboard_worker", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_scoreboard_worker, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "clock", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_clock, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "requestbody", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_requestbody, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "add_input_filter", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_add_input_filter, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "module_info", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_module_info, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "loaded_modules", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_loaded_modules, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "runtime_dir_relative", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_runtime_dir_relative, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "server_info", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_server_info, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "set_document_root", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_set_document_root, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "set_context_info", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_set_context_info, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "os_escape_path", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_os_escape_path, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "escape_logitem", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_escape_logitem, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "strcmp_match", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_strcmp_match, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "set_keepalive", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_set_keepalive, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "make_etag", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_make_etag, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "send_interim_response", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_send_interim_response, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "custom_response", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_custom_response, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "exists_config_define", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_exists_config_define, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "state_query", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_state_query, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "get_server_name_for_url", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_get_server_name_for_url, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "ivm_get", APR_HASH_KEY_STRING,
+ makefun(&lua_ivm_get, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "ivm_set", APR_HASH_KEY_STRING,
+ makefun(&lua_ivm_set, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "getcookie", APR_HASH_KEY_STRING,
+ makefun(&lua_get_cookie, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "setcookie", APR_HASH_KEY_STRING,
+ makefun(&lua_set_cookie, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "wsupgrade", APR_HASH_KEY_STRING,
+ makefun(&lua_websocket_greet, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "wsread", APR_HASH_KEY_STRING,
+ makefun(&lua_websocket_read, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "wspeek", APR_HASH_KEY_STRING,
+ makefun(&lua_websocket_peek, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "wswrite", APR_HASH_KEY_STRING,
+ makefun(&lua_websocket_write, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "wsclose", APR_HASH_KEY_STRING,
+ makefun(&lua_websocket_close, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "wsping", APR_HASH_KEY_STRING,
+ makefun(&lua_websocket_ping, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "config", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_get_config, APL_REQ_FUNTYPE_LUACFUN, p));
+ apr_hash_set(dispatch, "activeconfig", APR_HASH_KEY_STRING,
+ makefun(&lua_ap_get_active_config, APL_REQ_FUNTYPE_LUACFUN, p));
+ lua_pushlightuserdata(L, dispatch);
+ lua_setfield(L, LUA_REGISTRYINDEX, "Apache2.Request.dispatch");
+
+ luaL_newmetatable(L, "Apache2.Request"); /* [metatable] */
+ lua_pushvalue(L, -1);
+
+ lua_setfield(L, -2, "__index");
+ luaL_setfuncs_compat(L, request_methods); /* [metatable] */
+
+ lua_pop(L, 2);
+
+ luaL_newmetatable(L, "Apache2.Connection"); /* [metatable] */
+ lua_pushvalue(L, -1);
+
+ lua_setfield(L, -2, "__index");
+ luaL_setfuncs_compat(L, connection_methods); /* [metatable] */
+
+ lua_pop(L, 2);
+
+ luaL_newmetatable(L, "Apache2.Server"); /* [metatable] */
+ lua_pushvalue(L, -1);
+
+ lua_setfield(L, -2, "__index");
+ luaL_setfuncs_compat(L, server_methods); /* [metatable] */
+
+ lua_pop(L, 2);
+
+}
+
+void ap_lua_push_connection(lua_State *L, conn_rec *c)
+{
+ req_table_t* t;
+ lua_boxpointer(L, c);
+ luaL_getmetatable(L, "Apache2.Connection");
+ lua_setmetatable(L, -2);
+ luaL_getmetatable(L, "Apache2.Connection");
+
+ t = apr_pcalloc(c->pool, sizeof(req_table_t));
+ t->t = c->notes;
+ t->r = NULL;
+ t->n = "notes";
+ ap_lua_push_apr_table(L, t);
+ lua_setfield(L, -2, "notes");
+
+ lua_pushstring(L, c->client_ip);
+ lua_setfield(L, -2, "client_ip");
+
+ lua_pop(L, 1);
+}
+
+
+void ap_lua_push_server(lua_State *L, server_rec *s)
+{
+ lua_boxpointer(L, s);
+ luaL_getmetatable(L, "Apache2.Server");
+ lua_setmetatable(L, -2);
+ luaL_getmetatable(L, "Apache2.Server");
+
+ lua_pushstring(L, s->server_hostname);
+ lua_setfield(L, -2, "server_hostname");
+
+ lua_pop(L, 1);
+}
+
+void ap_lua_push_request(lua_State *L, request_rec *r)
+{
+ lua_boxpointer(L, r);
+ luaL_getmetatable(L, "Apache2.Request");
+ lua_setmetatable(L, -2);
+}
diff --git a/modules/lua/lua_request.h b/modules/lua/lua_request.h
new file mode 100644
index 0000000..a39f4b9
--- /dev/null
+++ b/modules/lua/lua_request.h
@@ -0,0 +1,58 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _LUA_REQUEST_H_
+#define _LUA_REQUEST_H_
+
+#include "mod_lua.h"
+#include "util_varbuf.h"
+
+void ap_lua_load_request_lmodule(lua_State *L, apr_pool_t *p);
+void ap_lua_push_connection(lua_State *L, conn_rec *r);
+void ap_lua_push_server(lua_State *L, server_rec *r);
+void ap_lua_push_request(lua_State *L, request_rec *r);
+
+#define APL_REQ_FUNTYPE_STRING 1
+#define APL_REQ_FUNTYPE_INT 2
+#define APL_REQ_FUNTYPE_TABLE 3
+#define APL_REQ_FUNTYPE_LUACFUN 4
+#define APL_REQ_FUNTYPE_BOOLEAN 5
+
+typedef struct
+{
+ const void *fun;
+ int type;
+} req_fun_t;
+
+
+/* Struct to use as userdata for request_rec tables */
+typedef struct
+{
+ request_rec *r; /* Request_rec */
+ apr_table_t *t; /* apr_table_t* */
+ const char *n; /* name of table */
+} req_table_t;
+
+typedef struct {
+ int type;
+ size_t size;
+ size_t vb_size;
+ lua_Number number;
+ struct ap_varbuf vb;
+} lua_ivm_object;
+
+#endif /* !_LUA_REQUEST_H_ */
diff --git a/modules/lua/lua_vmprep.c b/modules/lua/lua_vmprep.c
new file mode 100644
index 0000000..001897a
--- /dev/null
+++ b/modules/lua/lua_vmprep.c
@@ -0,0 +1,552 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "mod_lua.h"
+#include "http_log.h"
+#include "apr_uuid.h"
+#include "lua_config.h"
+#include "apr_file_info.h"
+#include "mod_auth.h"
+
+APLOG_USE_MODULE(lua);
+
+#ifndef AP_LUA_MODULE_EXT
+#if defined(NETWARE)
+#define AP_LUA_MODULE_EXT ".nlm"
+#elif defined(WIN32)
+#define AP_LUA_MODULE_EXT ".dll"
+#elif (defined(__hpux__) || defined(__hpux)) && !defined(__ia64)
+#define AP_LUA_MODULE_EXT ".sl"
+#else
+#define AP_LUA_MODULE_EXT ".so"
+#endif
+#endif
+
+#if APR_HAS_THREADS
+ apr_thread_mutex_t *ap_lua_mutex;
+#endif
+extern apr_global_mutex_t *lua_ivm_mutex;
+
+void ap_lua_init_mutex(apr_pool_t *pool, server_rec *s)
+{
+ apr_status_t rv;
+
+ /* global IVM mutex */
+ rv = apr_global_mutex_child_init(&lua_ivm_mutex,
+ apr_global_mutex_lockfile(lua_ivm_mutex),
+ pool);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(03016)
+ "mod_lua: Failed to reopen mutex lua-ivm-shm in child");
+ exit(1); /* bah :( */
+ }
+
+ /* Server pool mutex */
+#if APR_HAS_THREADS
+ apr_thread_mutex_create(&ap_lua_mutex, APR_THREAD_MUTEX_DEFAULT, pool);
+#endif
+}
+
+/* forward dec'l from this file */
+
+#if 0
+static void pstack_dump(lua_State *L, apr_pool_t *r, int level,
+ const char *msg)
+{
+ int i;
+ int top = lua_gettop(L);
+
+ ap_log_perror(APLOG_MARK, level, 0, r, APLOGNO(03211)
+ "Lua Stack Dump: [%s]", msg);
+
+ for (i = 1; i <= top; i++) {
+ int t = lua_type(L, i);
+ switch (t) {
+ case LUA_TSTRING:{
+ ap_log_perror(APLOG_MARK, level, 0, r, APLOGNO(03212)
+ "%d: '%s'", i, lua_tostring(L, i));
+ break;
+ }
+ case LUA_TUSERDATA:{
+ ap_log_perror(APLOG_MARK, level, 0, r, APLOGNO(03213)
+ "%d: userdata", i);
+ break;
+ }
+ case LUA_TLIGHTUSERDATA:{
+ ap_log_perror(APLOG_MARK, level, 0, r, APLOGNO(03214)
+ "%d: lightuserdata", i);
+ break;
+ }
+ case LUA_TNIL:{
+ ap_log_perror(APLOG_MARK, level, 0, r, APLOGNO(03215)
+ "%d: NIL", i);
+ break;
+ }
+ case LUA_TNONE:{
+ ap_log_perror(APLOG_MARK, level, 0, r, APLOGNO(03216)
+ "%d: None", i);
+ break;
+ }
+ case LUA_TBOOLEAN:{
+ ap_log_perror(APLOG_MARK, level, 0, r, APLOGNO(03217)
+ "%d: %s",
+ i, lua_toboolean(L, i) ? "true" : "false");
+ break;
+ }
+ case LUA_TNUMBER:{
+ ap_log_perror(APLOG_MARK, level, 0, r, APLOGNO(03218)
+ "%d: %g", i, lua_tonumber(L, i));
+ break;
+ }
+ case LUA_TTABLE:{
+ ap_log_perror(APLOG_MARK, level, 0, r, APLOGNO(03219)
+ "%d: <table>", i);
+ break;
+ }
+ case LUA_TTHREAD:{
+ ap_log_perror(APLOG_MARK, level, 0, r, APLOGNO(03220)
+ "%d: <thread>", i);
+ break;
+ }
+ case LUA_TFUNCTION:{
+ ap_log_perror(APLOG_MARK, level, 0, r, APLOGNO(03221)
+ "%d: <function>", i);
+ break;
+ }
+ default:{
+ ap_log_perror(APLOG_MARK, level, 0, r, APLOGNO(03222)
+ "%d: unknown: [%s]", i, lua_typename(L, i));
+ break;
+ }
+ }
+ }
+}
+#endif
+
+/* BEGIN modules*/
+
+/* BEGIN apache lmodule */
+
+#define makeintegerfield(L, n) lua_pushinteger(L, n); lua_setfield(L, -2, #n)
+
+void ap_lua_load_apache2_lmodule(lua_State *L)
+{
+ lua_getglobal(L, "package");
+ lua_getfield(L, -1, "loaded");
+ lua_newtable(L);
+ lua_setfield(L, -2, "apache2");
+ lua_setglobal(L, "apache2");
+ lua_pop(L, 1); /* empty stack */
+
+ lua_getglobal(L, "apache2");
+
+ lua_pushstring(L, ap_get_server_banner());
+ lua_setfield(L, -2, "version");
+
+ makeintegerfield(L, OK);
+ makeintegerfield(L, DECLINED);
+ makeintegerfield(L, DONE);
+ makeintegerfield(L, HTTP_MOVED_TEMPORARILY);
+ makeintegerfield(L, PROXYREQ_NONE);
+ makeintegerfield(L, PROXYREQ_PROXY);
+ makeintegerfield(L, PROXYREQ_REVERSE);
+ makeintegerfield(L, PROXYREQ_RESPONSE);
+ makeintegerfield(L, PROXYREQ_RESPONSE);
+ makeintegerfield(L, AUTHZ_DENIED);
+ makeintegerfield(L, AUTHZ_GRANTED);
+ makeintegerfield(L, AUTHZ_NEUTRAL);
+ makeintegerfield(L, AUTHZ_GENERAL_ERROR);
+ makeintegerfield(L, AUTHZ_DENIED_NO_USER);
+
+ /*
+ makeintegerfield(L, HTTP_CONTINUE);
+ makeintegerfield(L, HTTP_SWITCHING_PROTOCOLS);
+ makeintegerfield(L, HTTP_PROCESSING);
+ makeintegerfield(L, HTTP_OK);
+ makeintegerfield(L, HTTP_CREATED);
+ makeintegerfield(L, HTTP_ACCEPTED);
+ makeintegerfield(L, HTTP_NON_AUTHORITATIVE);
+ makeintegerfield(L, HTTP_NO_CONTENT);
+ makeintegerfield(L, HTTP_RESET_CONTENT);
+ makeintegerfield(L, HTTP_PARTIAL_CONTENT);
+ makeintegerfield(L, HTTP_MULTI_STATUS);
+ makeintegerfield(L, HTTP_ALREADY_REPORTED);
+ makeintegerfield(L, HTTP_IM_USED);
+ makeintegerfield(L, HTTP_MULTIPLE_CHOICES);
+ makeintegerfield(L, HTTP_MOVED_PERMANENTLY);
+ makeintegerfield(L, HTTP_MOVED_TEMPORARILY);
+ makeintegerfield(L, HTTP_SEE_OTHER);
+ makeintegerfield(L, HTTP_NOT_MODIFIED);
+ makeintegerfield(L, HTTP_USE_PROXY);
+ makeintegerfield(L, HTTP_TEMPORARY_REDIRECT);
+ makeintegerfield(L, HTTP_PERMANENT_REDIRECT);
+ makeintegerfield(L, HTTP_BAD_REQUEST);
+ makeintegerfield(L, HTTP_UNAUTHORIZED);
+ makeintegerfield(L, HTTP_PAYMENT_REQUIRED);
+ makeintegerfield(L, HTTP_FORBIDDEN);
+ makeintegerfield(L, HTTP_NOT_FOUND);
+ makeintegerfield(L, HTTP_METHOD_NOT_ALLOWED);
+ makeintegerfield(L, HTTP_NOT_ACCEPTABLE);
+ makeintegerfield(L, HTTP_PROXY_AUTHENTICATION_REQUIRED);
+ makeintegerfield(L, HTTP_REQUEST_TIME_OUT);
+ makeintegerfield(L, HTTP_CONFLICT);
+ makeintegerfield(L, HTTP_GONE);
+ makeintegerfield(L, HTTP_LENGTH_REQUIRED);
+ makeintegerfield(L, HTTP_PRECONDITION_FAILED);
+ makeintegerfield(L, HTTP_REQUEST_ENTITY_TOO_LARGE);
+ makeintegerfield(L, HTTP_REQUEST_URI_TOO_LARGE);
+ makeintegerfield(L, HTTP_UNSUPPORTED_MEDIA_TYPE);
+ makeintegerfield(L, HTTP_RANGE_NOT_SATISFIABLE);
+ makeintegerfield(L, HTTP_EXPECTATION_FAILED);
+ makeintegerfield(L, HTTP_UNPROCESSABLE_ENTITY);
+ makeintegerfield(L, HTTP_LOCKED);
+ makeintegerfield(L, HTTP_FAILED_DEPENDENCY);
+ makeintegerfield(L, HTTP_UPGRADE_REQUIRED);
+ makeintegerfield(L, HTTP_PRECONDITION_REQUIRED);
+ makeintegerfield(L, HTTP_TOO_MANY_REQUESTS);
+ makeintegerfield(L, HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE);
+ makeintegerfield(L, HTTP_INTERNAL_SERVER_ERROR);
+ makeintegerfield(L, HTTP_NOT_IMPLEMENTED);
+ makeintegerfield(L, HTTP_BAD_GATEWAY);
+ makeintegerfield(L, HTTP_SERVICE_UNAVAILABLE);
+ makeintegerfield(L, HTTP_GATEWAY_TIME_OUT);
+ makeintegerfield(L, HTTP_VERSION_NOT_SUPPORTED);
+ makeintegerfield(L, HTTP_VARIANT_ALSO_VARIES);
+ makeintegerfield(L, HTTP_INSUFFICIENT_STORAGE);
+ makeintegerfield(L, HTTP_LOOP_DETECTED);
+ makeintegerfield(L, HTTP_NOT_EXTENDED);
+ makeintegerfield(L, HTTP_NETWORK_AUTHENTICATION_REQUIRED);
+ */
+}
+
+/* END apache2 lmodule */
+
+/* END library functions */
+
+/* callback for cleaning up a lua vm when pool is closed */
+static apr_status_t cleanup_lua(void *l)
+{
+ AP_DEBUG_ASSERT(l != NULL);
+ lua_close((lua_State *) l);
+ return APR_SUCCESS;
+}
+
+static apr_status_t server_cleanup_lua(void *resource, void *params, apr_pool_t *pool)
+{
+ ap_lua_server_spec* spec = (ap_lua_server_spec*) resource;
+ AP_DEBUG_ASSERT(spec != NULL);
+ if (spec->L != NULL) {
+ lua_close((lua_State *) spec->L);
+ }
+ return APR_SUCCESS;
+}
+
+/*
+ munge_path(L,
+ "path",
+ "?.lua",
+ "./?.lua",
+ lifecycle_pool,
+ spec->package_paths,
+ spec->file);
+*/
+/**
+ * field -> "path" or "cpath"
+ * sub_pat -> "?.lua"
+ * rep_pat -> "./?.lua"
+ * pool -> lifecycle pool for allocations
+ * paths -> things to add
+ * file -> ???
+ */
+static void munge_path(lua_State *L,
+ const char *field,
+ const char *sub_pat,
+ const char *rep_pat,
+ apr_pool_t *pool,
+ apr_array_header_t *paths,
+ const char *file)
+{
+ const char *current;
+ const char *parent_dir;
+ const char *pattern;
+ const char *modified;
+ char *part;
+
+ lua_getglobal(L, "package");
+ lua_getfield(L, -1, field);
+
+ current = lua_tostring(L, -1);
+
+ parent_dir = ap_make_dirstr_parent(pool, file);
+
+ pattern = apr_pstrcat(pool, parent_dir, sub_pat, NULL);
+
+ luaL_gsub(L, current, rep_pat, pattern);
+ lua_setfield(L, -3, field);
+ lua_getfield(L, -2, field);
+ modified = lua_tostring(L, -1);
+
+
+ lua_pop(L, 2);
+
+ part = apr_pstrcat(pool, modified, ";", apr_array_pstrcat(pool, paths, ';'),
+ NULL);
+
+ lua_pushstring(L, part);
+ lua_setfield(L, -2, field);
+ lua_pop(L, 1); /* pop "package" off the stack */
+}
+
+#ifdef AP_ENABLE_LUAJIT
+static int loadjitmodule(lua_State *L, apr_pool_t *lifecycle_pool)
+{
+ lua_getglobal(L, "require");
+ lua_pushliteral(L, "jit.");
+ lua_pushvalue(L, -3);
+ lua_concat(L, 2);
+ if (lua_pcall(L, 1, 1, 0)) {
+ const char *msg = lua_tostring(L, -1);
+ ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, lifecycle_pool, APLOGNO(01480)
+ "Failed to init LuaJIT: %s", msg);
+ return 1;
+ }
+ lua_getfield(L, -1, "start");
+ lua_remove(L, -2); /* drop module table */
+ return 0;
+}
+
+#endif
+
+static apr_status_t vm_construct(lua_State **vm, void *params, apr_pool_t *lifecycle_pool)
+{
+ lua_State* L;
+
+ ap_lua_vm_spec *spec = params;
+
+ L = luaL_newstate();
+#ifdef AP_ENABLE_LUAJIT
+ luaopen_jit(L);
+#endif
+ luaL_openlibs(L);
+ if (spec->package_paths) {
+ munge_path(L,
+ "path", "?.lua", "./?.lua",
+ lifecycle_pool,
+ spec->package_paths,
+ spec->file);
+ }
+ if (spec->package_cpaths) {
+ munge_path(L,
+ "cpath", "?" AP_LUA_MODULE_EXT, "./?" AP_LUA_MODULE_EXT,
+ lifecycle_pool,
+ spec->package_cpaths,
+ spec->file);
+ }
+
+ if (spec->cb) {
+ spec->cb(L, lifecycle_pool, spec->cb_arg);
+ }
+
+
+ if (spec->bytecode && spec->bytecode_len > 0) {
+ luaL_loadbuffer(L, spec->bytecode, spec->bytecode_len, spec->file);
+ lua_pcall(L, 0, LUA_MULTRET, 0);
+ }
+ else {
+ int rc;
+ ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, lifecycle_pool, APLOGNO(01481)
+ "loading lua file %s", spec->file);
+ rc = luaL_loadfile(L, spec->file);
+ if (rc != 0) {
+ ap_log_perror(APLOG_MARK, APLOG_ERR, 0, lifecycle_pool, APLOGNO(01482)
+ "Error loading %s: %s", spec->file,
+ rc == LUA_ERRMEM ? "memory allocation error"
+ : lua_tostring(L, 0));
+ return APR_EBADF;
+ }
+ if ( lua_pcall(L, 0, LUA_MULTRET, 0) == LUA_ERRRUN ) {
+ ap_log_perror(APLOG_MARK, APLOG_ERR, 0, lifecycle_pool, APLOGNO(02613)
+ "Error loading %s: %s", spec->file,
+ lua_tostring(L, -1));
+ return APR_EBADF;
+ }
+ }
+
+#ifdef AP_ENABLE_LUAJIT
+ loadjitmodule(L, lifecycle_pool);
+#endif
+ lua_pushlightuserdata(L, lifecycle_pool);
+ lua_setfield(L, LUA_REGISTRYINDEX, "Apache2.Wombat.pool");
+ *vm = L;
+
+ return APR_SUCCESS;
+}
+
+static ap_lua_vm_spec* copy_vm_spec(apr_pool_t* pool, ap_lua_vm_spec* spec)
+{
+ ap_lua_vm_spec* copied_spec = apr_pcalloc(pool, sizeof(ap_lua_vm_spec));
+ copied_spec->bytecode_len = spec->bytecode_len;
+ copied_spec->bytecode = apr_pstrdup(pool, spec->bytecode);
+ copied_spec->cb = spec->cb;
+ copied_spec->cb_arg = NULL;
+ copied_spec->file = apr_pstrdup(pool, spec->file);
+ copied_spec->package_cpaths = apr_array_copy(pool, spec->package_cpaths);
+ copied_spec->package_paths = apr_array_copy(pool, spec->package_paths);
+ copied_spec->pool = pool;
+ copied_spec->scope = AP_LUA_SCOPE_SERVER;
+ copied_spec->codecache = spec->codecache;
+ return copied_spec;
+}
+
+static apr_status_t server_vm_construct(lua_State **resource, void *params, apr_pool_t *pool)
+{
+ lua_State* L;
+ ap_lua_server_spec* spec = apr_pcalloc(pool, sizeof(ap_lua_server_spec));
+ *resource = NULL;
+ if (vm_construct(&L, params, pool) == APR_SUCCESS) {
+ spec->finfo = apr_pcalloc(pool, sizeof(ap_lua_finfo));
+ if (L != NULL) {
+ spec->L = L;
+ *resource = (void*) spec;
+ lua_pushlightuserdata(L, spec);
+ lua_setfield(L, LUA_REGISTRYINDEX, "Apache2.Lua.server_spec");
+ return APR_SUCCESS;
+ }
+ }
+ return APR_EGENERAL;
+}
+
+/**
+ * Function used to create a lua_State instance bound into the web
+ * server in the appropriate scope.
+ */
+lua_State *ap_lua_get_lua_state(apr_pool_t *lifecycle_pool,
+ ap_lua_vm_spec *spec, request_rec* r)
+{
+ lua_State *L = NULL;
+ ap_lua_finfo *cache_info = NULL;
+ int tryCache = 0;
+
+ if (spec->scope == AP_LUA_SCOPE_SERVER) {
+ char *hash;
+ apr_reslist_t* reslist = NULL;
+ ap_lua_server_spec* sspec = NULL;
+ hash = apr_psprintf(r->pool, "reslist:%s", spec->file);
+#if APR_HAS_THREADS
+ apr_thread_mutex_lock(ap_lua_mutex);
+#endif
+ if (apr_pool_userdata_get((void **)&reslist, hash,
+ r->server->process->pool) == APR_SUCCESS) {
+ if (reslist != NULL) {
+ if (apr_reslist_acquire(reslist, (void**) &sspec) == APR_SUCCESS) {
+ L = sspec->L;
+ cache_info = sspec->finfo;
+ }
+ }
+ }
+ if (L == NULL) {
+ ap_lua_vm_spec* server_spec = copy_vm_spec(r->server->process->pool, spec);
+ if (
+ apr_reslist_create(&reslist, spec->vm_min, spec->vm_max, spec->vm_max, 0,
+ (apr_reslist_constructor) server_vm_construct,
+ (apr_reslist_destructor) server_cleanup_lua,
+ server_spec, r->server->process->pool)
+ == APR_SUCCESS && reslist != NULL) {
+ apr_pool_userdata_set(reslist, hash, NULL,
+ r->server->process->pool);
+ if (apr_reslist_acquire(reslist, (void**) &sspec) == APR_SUCCESS) {
+ L = sspec->L;
+ cache_info = sspec->finfo;
+ }
+ else {
+#if APR_HAS_THREADS
+ apr_thread_mutex_unlock(ap_lua_mutex);
+#endif
+ return NULL;
+ }
+ }
+ }
+#if APR_HAS_THREADS
+ apr_thread_mutex_unlock(ap_lua_mutex);
+#endif
+ }
+ else {
+ if (apr_pool_userdata_get((void **)&L, spec->file,
+ lifecycle_pool) != APR_SUCCESS) {
+ L = NULL;
+ }
+ }
+ if (L == NULL) {
+ ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, lifecycle_pool, APLOGNO(01483)
+ "creating lua_State with file %s", spec->file);
+ /* not available, so create */
+
+ if (!vm_construct(&L, spec, lifecycle_pool)) {
+ AP_DEBUG_ASSERT(L != NULL);
+ apr_pool_userdata_set(L, spec->file, cleanup_lua, lifecycle_pool);
+ }
+ }
+
+ if (spec->codecache == AP_LUA_CACHE_FOREVER || (spec->bytecode && spec->bytecode_len > 0)) {
+ tryCache = 1;
+ }
+ else {
+ char* mkey;
+ if (spec->scope != AP_LUA_SCOPE_SERVER) {
+ mkey = apr_psprintf(r->pool, "ap_lua_modified:%s", spec->file);
+ apr_pool_userdata_get((void **)&cache_info, mkey, lifecycle_pool);
+ if (cache_info == NULL) {
+ cache_info = apr_pcalloc(lifecycle_pool, sizeof(ap_lua_finfo));
+ apr_pool_userdata_set((void*) cache_info, mkey, NULL, lifecycle_pool);
+ }
+ }
+ if (spec->codecache == AP_LUA_CACHE_STAT) {
+ apr_finfo_t lua_finfo;
+ apr_stat(&lua_finfo, spec->file, APR_FINFO_MTIME|APR_FINFO_SIZE, lifecycle_pool);
+
+ /* On first visit, modified will be zero, but that's fine - The file is
+ loaded in the vm_construct function.
+ */
+ if ((cache_info->modified == lua_finfo.mtime && cache_info->size == lua_finfo.size)
+ || cache_info->modified == 0) {
+ tryCache = 1;
+ }
+ cache_info->modified = lua_finfo.mtime;
+ cache_info->size = lua_finfo.size;
+ }
+ else if (spec->codecache == AP_LUA_CACHE_NEVER) {
+ if (cache_info->runs == 0)
+ tryCache = 1;
+ }
+ cache_info->runs++;
+ }
+ if (tryCache == 0 && spec->scope != AP_LUA_SCOPE_ONCE) {
+ int rc;
+ ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, lifecycle_pool, APLOGNO(02332)
+ "(re)loading lua file %s", spec->file);
+ rc = luaL_loadfile(L, spec->file);
+ if (rc != 0) {
+ ap_log_perror(APLOG_MARK, APLOG_ERR, 0, lifecycle_pool, APLOGNO(02333)
+ "Error loading %s: %s", spec->file,
+ rc == LUA_ERRMEM ? "memory allocation error"
+ : lua_tostring(L, 0));
+ return 0;
+ }
+ lua_pcall(L, 0, LUA_MULTRET, 0);
+ }
+
+ return L;
+}
diff --git a/modules/lua/lua_vmprep.h b/modules/lua/lua_vmprep.h
new file mode 100644
index 0000000..e46ac9b
--- /dev/null
+++ b/modules/lua/lua_vmprep.h
@@ -0,0 +1,147 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "lua.h"
+#include "lauxlib.h"
+#include "lualib.h"
+
+#include "httpd.h"
+
+#include "apr_thread_rwlock.h"
+#include "apr_strings.h"
+#include "apr_tables.h"
+#include "apr_hash.h"
+#include "apr_buckets.h"
+#include "apr_file_info.h"
+#include "apr_time.h"
+#include "apr_pools.h"
+#include "apr_reslist.h"
+
+
+#ifndef VMPREP_H
+#define VMPREP_H
+
+#define AP_LUA_SCOPE_UNSET 0
+#define AP_LUA_SCOPE_ONCE 1
+#define AP_LUA_SCOPE_REQUEST 2
+#define AP_LUA_SCOPE_CONN 3
+#define AP_LUA_SCOPE_THREAD 4
+#define AP_LUA_SCOPE_SERVER 5
+
+#define AP_LUA_CACHE_UNSET 0
+#define AP_LUA_CACHE_NEVER 1
+#define AP_LUA_CACHE_STAT 2
+#define AP_LUA_CACHE_FOREVER 3
+
+#define AP_LUA_FILTER_INPUT 1
+#define AP_LUA_FILTER_OUTPUT 2
+
+typedef void (*ap_lua_state_open_callback) (lua_State *L, apr_pool_t *p,
+ void *ctx);
+/**
+ * Specification for a lua virtual machine
+ */
+typedef struct
+{
+ /* NEED TO ADD ADDITIONAL PACKAGE PATHS AS PART OF SPEC INSTEAD OF DIR CONFIG */
+ apr_array_header_t *package_paths;
+ apr_array_header_t *package_cpaths;
+
+ /* name of base file to load in the vm */
+ const char *file;
+
+ /* APL_SCOPE_ONCE | APL_SCOPE_REQUEST | APL_SCOPE_CONN | APL_SCOPE_THREAD | APL_SCOPE_SERVER */
+ int scope;
+ unsigned int vm_min;
+ unsigned int vm_max;
+
+ ap_lua_state_open_callback cb;
+ void* cb_arg;
+
+ /* pool to use for lifecycle if APL_SCOPE_ONCE is set, otherwise unused */
+ apr_pool_t *pool;
+
+ /* Pre-compiled Lua Byte code to load directly. If bytecode_len is >0,
+ * the file part of this structure is ignored for loading purposes, but
+ * it is used for error messages.
+ */
+ const char *bytecode;
+ apr_size_t bytecode_len;
+
+ int codecache;
+} ap_lua_vm_spec;
+
+typedef struct
+{
+ const char *function_name;
+ const char *file_name;
+ int scope;
+ ap_regex_t *uri_pattern;
+ const char *bytecode;
+ apr_size_t bytecode_len;
+ int codecache;
+} ap_lua_mapped_handler_spec;
+
+typedef struct
+{
+ const char *function_name;
+ const char *file_name;
+ const char* filter_name;
+ int direction; /* AP_LUA_FILTER_INPUT | AP_LUA_FILTER_OUTPUT */
+} ap_lua_filter_handler_spec;
+
+typedef struct {
+ apr_size_t runs;
+ apr_time_t modified;
+ apr_off_t size;
+} ap_lua_finfo;
+
+typedef struct {
+ lua_State* L;
+ ap_lua_finfo* finfo;
+} ap_lua_server_spec;
+
+/**
+ * Fake out addition of the "apache2" module
+ */
+void ap_lua_load_apache2_lmodule(lua_State *L);
+
+/*
+ * alternate means of getting lua_State (preferred eventually)
+ * Obtain a lua_State which has loaded file and is associated with lifecycle_pool
+ * If one exists, will return extant one, otherwise will create, attach, and return
+ * This does no locking around the lua_State, so if the pool is shared between
+ * threads, locking is up the client.
+ *
+ * @lifecycle_pool -> pool whose lifeycle controls the lua_State
+ * @file file to be opened, also used as a key for uniquing lua_States
+ * @cb callback for vm initialization called *before* the file is opened
+ * @ctx a baton passed to cb
+ */
+lua_State *ap_lua_get_lua_state(apr_pool_t *lifecycle_pool,
+ ap_lua_vm_spec *spec, request_rec* r);
+
+#if APR_HAS_THREADS || defined(DOXYGEN)
+/*
+ * Initialize mod_lua mutex.
+ * @pool pool for mutex
+ * @s server_rec for logging
+ */
+void ap_lua_init_mutex(apr_pool_t *pool, server_rec *s);
+#endif
+
+#endif
diff --git a/modules/lua/mod_lua.c b/modules/lua/mod_lua.c
new file mode 100644
index 0000000..303890e
--- /dev/null
+++ b/modules/lua/mod_lua.c
@@ -0,0 +1,2197 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mod_lua.h"
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <apr_thread_mutex.h>
+#include <apr_pools.h>
+#include "lua_apr.h"
+#include "lua_config.h"
+#include "apr_optional.h"
+#include "mod_auth.h"
+#include "util_mutex.h"
+
+
+#ifdef APR_HAS_THREADS
+#include "apr_thread_proc.h"
+#endif
+
+/* getpid for *NIX */
+#if APR_HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+/* getpid for Windows */
+#if APR_HAVE_PROCESS_H
+#include <process.h>
+#endif
+
+APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(ap_lua, AP_LUA, int, lua_open,
+ (lua_State *L, apr_pool_t *p),
+ (L, p), OK, DECLINED)
+
+APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(ap_lua, AP_LUA, int, lua_request,
+ (lua_State *L, request_rec *r),
+ (L, r), OK, DECLINED)
+
+module AP_MODULE_DECLARE_DATA lua_module;
+
+#define AP_LUA_HOOK_FIRST (APR_HOOK_FIRST - 1)
+#define AP_LUA_HOOK_LAST (APR_HOOK_LAST + 1)
+
+typedef struct {
+ const char *name;
+ const char *file_name;
+ const char *function_name;
+ ap_lua_vm_spec *spec;
+} lua_authz_provider_spec;
+
+typedef struct {
+ lua_authz_provider_spec *spec;
+ apr_array_header_t *args;
+} lua_authz_provider_func;
+
+apr_hash_t *lua_authz_providers;
+
+typedef struct
+{
+ apr_bucket_brigade *tmpBucket;
+ lua_State *L;
+ ap_lua_vm_spec *spec;
+ int broken;
+} lua_filter_ctx;
+
+#define DEFAULT_LUA_SHMFILE "lua_ivm_shm"
+
+apr_global_mutex_t *lua_ivm_mutex;
+apr_shm_t *lua_ivm_shm;
+char *lua_ivm_shmfile;
+
+static apr_status_t shm_cleanup_wrapper(void *unused)
+{
+ if (lua_ivm_shm) {
+ return apr_shm_destroy(lua_ivm_shm);
+ }
+ return OK;
+}
+
+/**
+ * error reporting if lua has an error.
+ * Extracts the error from lua stack and prints
+ */
+static void report_lua_error(lua_State *L, request_rec *r)
+{
+ const char *lua_response;
+ r->status = HTTP_INTERNAL_SERVER_ERROR;
+ r->content_type = "text/html";
+ ap_rputs("<h3>Error!</h3>\n", r);
+ ap_rputs("<pre>", r);
+ lua_response = lua_tostring(L, -1);
+ ap_rputs(ap_escape_html(r->pool, lua_response), r);
+ ap_rputs("</pre>\n", r);
+
+ ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, r->pool, APLOGNO(01471) "Lua error: %s",
+ lua_response);
+}
+
+static void lua_open_callback(lua_State *L, apr_pool_t *p, void *ctx)
+{
+ ap_lua_init(L, p);
+ ap_lua_load_apache2_lmodule(L);
+ ap_lua_load_request_lmodule(L, p);
+ ap_lua_load_config_lmodule(L);
+}
+
+static int lua_open_hook(lua_State *L, apr_pool_t *p)
+{
+ lua_open_callback(L, p, NULL);
+ return OK;
+}
+
+static const char *scope_to_string(unsigned int scope)
+{
+ switch (scope) {
+ case AP_LUA_SCOPE_ONCE:
+ case AP_LUA_SCOPE_UNSET:
+ return "once";
+ case AP_LUA_SCOPE_REQUEST:
+ return "request";
+ case AP_LUA_SCOPE_CONN:
+ return "conn";
+#if APR_HAS_THREADS
+ case AP_LUA_SCOPE_THREAD:
+ return "thread";
+ case AP_LUA_SCOPE_SERVER:
+ return "server";
+#endif
+ default:
+ ap_assert(0);
+ return 0;
+ }
+}
+
+static void ap_lua_release_state(lua_State* L, ap_lua_vm_spec* spec, request_rec* r)
+{
+ char *hash;
+ apr_reslist_t* reslist = NULL;
+
+ if (spec->scope == AP_LUA_SCOPE_SERVER) {
+ ap_lua_server_spec* sspec = NULL;
+ lua_settop(L, 0);
+ lua_getfield(L, LUA_REGISTRYINDEX, "Apache2.Lua.server_spec");
+ sspec = (ap_lua_server_spec*) lua_touserdata(L, 1);
+ hash = apr_psprintf(r->pool, "reslist:%s", spec->file);
+ if (apr_pool_userdata_get((void **)&reslist, hash,
+ r->server->process->pool) == APR_SUCCESS) {
+ AP_DEBUG_ASSERT(sspec != NULL);
+ if (reslist != NULL) {
+ apr_reslist_release(reslist, sspec);
+ }
+ }
+ }
+}
+
+static ap_lua_vm_spec *create_vm_spec(apr_pool_t **lifecycle_pool,
+ request_rec *r,
+ const ap_lua_dir_cfg *cfg,
+ const ap_lua_server_cfg *server_cfg,
+ const char *filename,
+ const char *bytecode,
+ apr_size_t bytecode_len,
+ const char *function,
+ const char *what)
+{
+ apr_pool_t *pool;
+ ap_lua_vm_spec *spec = apr_pcalloc(r->pool, sizeof(ap_lua_vm_spec));
+
+ spec->scope = cfg->vm_scope;
+ spec->pool = r->pool;
+ spec->package_paths = cfg->package_paths;
+ spec->package_cpaths = cfg->package_cpaths;
+ spec->cb = &lua_open_callback;
+ spec->cb_arg = NULL;
+ spec->bytecode = bytecode;
+ spec->bytecode_len = bytecode_len;
+ spec->codecache = (cfg->codecache == AP_LUA_CACHE_UNSET) ? AP_LUA_CACHE_STAT : cfg->codecache;
+ spec->vm_min = cfg->vm_min ? cfg->vm_min : 1;
+ spec->vm_max = cfg->vm_max ? cfg->vm_max : 1;
+
+ if (filename) {
+ char *file;
+ apr_filepath_merge(&file, server_cfg->root_path,
+ filename, APR_FILEPATH_NOTRELATIVE, r->pool);
+ spec->file = file;
+ }
+ else {
+ spec->file = r->filename;
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, APLOGNO(02313)
+ "%s details: scope: %s, file: %s, func: %s",
+ what, scope_to_string(spec->scope), spec->file,
+ function ? function : "-");
+
+ switch (spec->scope) {
+ case AP_LUA_SCOPE_ONCE:
+ case AP_LUA_SCOPE_UNSET:
+ apr_pool_create(&pool, r->pool);
+ apr_pool_tag(pool, "mod_lua-vm");
+ break;
+ case AP_LUA_SCOPE_REQUEST:
+ pool = r->pool;
+ break;
+ case AP_LUA_SCOPE_CONN:
+ pool = r->connection->pool;
+ break;
+#if APR_HAS_THREADS
+ case AP_LUA_SCOPE_THREAD:
+ pool = apr_thread_pool_get(r->connection->current_thread);
+ break;
+ case AP_LUA_SCOPE_SERVER:
+ pool = r->server->process->pool;
+ break;
+#endif
+ default:
+ ap_assert(0);
+ }
+
+ *lifecycle_pool = pool;
+ return spec;
+}
+
+static const char* ap_lua_interpolate_string(apr_pool_t* pool, const char* string, const char** values)
+{
+ char *stringBetween;
+ const char* ret;
+ int srclen,x,y;
+ srclen = strlen(string);
+ ret = "";
+ y = 0;
+ for (x=0; x < srclen; x++) {
+ if (string[x] == '$' && x != srclen-1 && string[x+1] >= '0' && string[x+1] <= '9') {
+ int v = *(string+x+1) - '0';
+ if (x-y > 0) {
+ stringBetween = apr_pstrndup(pool, string+y, x-y);
+ }
+ else {
+ stringBetween = "";
+ }
+ ret = apr_pstrcat(pool, ret, stringBetween, values[v], NULL);
+ y = ++x+1;
+ }
+ }
+
+ if (x-y > 0 && y > 0) {
+ stringBetween = apr_pstrndup(pool, string+y, x-y);
+ ret = apr_pstrcat(pool, ret, stringBetween, NULL);
+ }
+ /* If no replacement was made, just return the original string */
+ else if (y == 0) {
+ return string;
+ }
+ return ret;
+}
+
+
+
+/**
+ * "main"
+ */
+static int lua_handler(request_rec *r)
+{
+ int rc = OK;
+ if (strcmp(r->handler, "lua-script")) {
+ return DECLINED;
+ }
+ /* Decline the request if the script does not exist (or is a directory),
+ * rather than just returning internal server error */
+ if (
+ (r->finfo.filetype == APR_NOFILE)
+ || (r->finfo.filetype & APR_DIR)
+ ) {
+ return DECLINED;
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(01472)
+ "handling [%s] in mod_lua", r->filename);
+
+ /* XXX: This seems wrong because it may generate wrong headers for HEAD requests */
+ if (!r->header_only) {
+ lua_State *L;
+ apr_pool_t *pool;
+ const ap_lua_dir_cfg *cfg = ap_get_module_config(r->per_dir_config,
+ &lua_module);
+ ap_lua_vm_spec *spec = create_vm_spec(&pool, r, cfg, NULL, NULL, NULL,
+ 0, "handle", "request handler");
+
+ L = ap_lua_get_lua_state(pool, spec, r);
+ if (!L) {
+ /* TODO annotate spec with failure reason */
+ r->status = HTTP_INTERNAL_SERVER_ERROR;
+ ap_rputs("Unable to compile VM, see logs", r);
+ ap_lua_release_state(L, spec, r);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, APLOGNO(01474) "got a vm!");
+ lua_getglobal(L, "handle");
+ if (!lua_isfunction(L, -1)) {
+ ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, APLOGNO(01475)
+ "lua: Unable to find entry function '%s' in %s (not a valid function)",
+ "handle",
+ spec->file);
+ ap_lua_release_state(L, spec, r);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ ap_lua_run_lua_request(L, r);
+ if (lua_pcall(L, 1, 1, 0)) {
+ report_lua_error(L, r);
+ }
+ if (lua_isnumber(L, -1)) {
+ rc = lua_tointeger(L, -1);
+ }
+ ap_lua_release_state(L, spec, r);
+ }
+ return rc;
+}
+
+
+/* ------------------- Input/output content filters ------------------- */
+
+
+static apr_status_t lua_setup_filter_ctx(ap_filter_t* f, request_rec* r, lua_filter_ctx** c)
+{
+ apr_pool_t *pool;
+ ap_lua_vm_spec *spec;
+ int n, rc, nres;
+ lua_State *L;
+ lua_filter_ctx *ctx;
+ ap_lua_server_cfg *server_cfg = ap_get_module_config(r->server->module_config,
+ &lua_module);
+ const ap_lua_dir_cfg *cfg = ap_get_module_config(r->per_dir_config,
+ &lua_module);
+
+ ctx = apr_pcalloc(r->pool, sizeof(lua_filter_ctx));
+ ctx->broken = 0;
+ *c = ctx;
+ /* Find the filter that was called.
+ * XXX: If we were wired with mod_filter, the filter (mod_filters name)
+ * and the provider (our underlying filters name) need to have matched.
+ */
+ for (n = 0; n < cfg->mapped_filters->nelts; n++) {
+ ap_lua_filter_handler_spec *hook_spec =
+ ((ap_lua_filter_handler_spec **) cfg->mapped_filters->elts)[n];
+
+ if (hook_spec == NULL) {
+ continue;
+ }
+ if (!strcasecmp(hook_spec->filter_name, f->frec->name)) {
+ spec = create_vm_spec(&pool, r, cfg, server_cfg,
+ hook_spec->file_name,
+ NULL,
+ 0,
+ hook_spec->function_name,
+ "filter");
+ L = ap_lua_get_lua_state(pool, spec, r);
+ if (L) {
+ L = lua_newthread(L);
+ }
+
+ if (!L) {
+ ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, APLOGNO(02328)
+ "lua: Failed to obtain lua interpreter for %s %s",
+ hook_spec->function_name, hook_spec->file_name);
+ ap_lua_release_state(L, spec, r);
+ return APR_EGENERAL;
+ }
+ if (hook_spec->function_name != NULL) {
+ lua_getglobal(L, hook_spec->function_name);
+ if (!lua_isfunction(L, -1)) {
+ ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, APLOGNO(02329)
+ "lua: Unable to find entry function '%s' in %s (not a valid function)",
+ hook_spec->function_name,
+ hook_spec->file_name);
+ ap_lua_release_state(L, spec, r);
+ return APR_EGENERAL;
+ }
+
+ ap_lua_run_lua_request(L, r);
+ }
+ else {
+ int t;
+ ap_lua_run_lua_request(L, r);
+
+ t = lua_gettop(L);
+ lua_setglobal(L, "r");
+ lua_settop(L, t);
+ }
+ ctx->L = L;
+ ctx->spec = spec;
+
+ /* If a Lua filter is interested in filtering a request, it must first do a yield,
+ * otherwise we'll assume that it's not interested and pretend we didn't find it.
+ */
+ rc = lua_resume(L, 1, &nres);
+ if (rc == LUA_YIELD) {
+ if (f->frec->providers == NULL) {
+ /* Not wired by mod_filter */
+ apr_table_unset(r->headers_out, "Content-Length");
+ apr_table_unset(r->headers_out, "Content-MD5");
+ apr_table_unset(r->headers_out, "ETAG");
+ }
+ return OK;
+ }
+ else {
+ ap_lua_release_state(L, spec, r);
+ return APR_ENOENT;
+ }
+ }
+ }
+ return APR_ENOENT;
+}
+
+static apr_status_t lua_output_filter_handle(ap_filter_t *f, apr_bucket_brigade *pbbIn)
+{
+ request_rec *r = f->r;
+ int rc, nres;
+ lua_State *L;
+ lua_filter_ctx* ctx;
+ conn_rec *c = r->connection;
+ apr_bucket *pbktIn;
+ apr_status_t rv;
+
+ /* Set up the initial filter context and acquire the function.
+ * The corresponding Lua function should yield here.
+ */
+ if (!f->ctx) {
+ rc = lua_setup_filter_ctx(f,r,&ctx);
+ if (rc == APR_EGENERAL) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ if (rc == APR_ENOENT) {
+ /* No filter entry found (or the script declined to filter), just pass on the buckets */
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next,pbbIn);
+ }
+ else {
+ /* We've got a willing lua filter, setup and check for a prefix */
+ size_t olen;
+ apr_bucket *pbktOut;
+ const char* output = lua_tolstring(ctx->L, 1, &olen);
+
+ f->ctx = ctx;
+ ctx->tmpBucket = apr_brigade_create(r->pool, c->bucket_alloc);
+
+ if (olen > 0) {
+ pbktOut = apr_bucket_heap_create(output, olen, NULL, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(ctx->tmpBucket, pbktOut);
+ rv = ap_pass_brigade(f->next, ctx->tmpBucket);
+ apr_brigade_cleanup(ctx->tmpBucket);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+ }
+ }
+ ctx = (lua_filter_ctx*) f->ctx;
+ L = ctx->L;
+ /* While the Lua function is still yielding, pass in buckets to the coroutine */
+ if (!ctx->broken) {
+ for (pbktIn = APR_BRIGADE_FIRST(pbbIn);
+ pbktIn != APR_BRIGADE_SENTINEL(pbbIn);
+ pbktIn = APR_BUCKET_NEXT(pbktIn))
+ {
+ const char *data;
+ apr_size_t len;
+ apr_bucket *pbktOut;
+
+ /* read the bucket */
+ apr_bucket_read(pbktIn,&data,&len,APR_BLOCK_READ);
+
+ /* Push the bucket onto the Lua stack as a global var */
+ lua_pushlstring(L, data, len);
+ lua_setglobal(L, "bucket");
+
+ /* If Lua yielded, it means we have something to pass on */
+ if (lua_resume(L, 0, &nres) == LUA_YIELD && nres == 1) {
+ size_t olen;
+ const char* output = lua_tolstring(L, 1, &olen);
+ if (olen > 0) {
+ pbktOut = apr_bucket_heap_create(output, olen, NULL,
+ c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(ctx->tmpBucket, pbktOut);
+ rv = ap_pass_brigade(f->next, ctx->tmpBucket);
+ apr_brigade_cleanup(ctx->tmpBucket);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+ }
+ else {
+ ctx->broken = 1;
+ ap_lua_release_state(L, ctx->spec, r);
+ ap_remove_output_filter(f);
+ apr_brigade_cleanup(pbbIn);
+ apr_brigade_cleanup(ctx->tmpBucket);
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02663)
+ "lua: Error while executing filter: %s",
+ lua_tostring(L, -1));
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+ /* If we've safely reached the end, do a final call to Lua to allow for any
+ finishing moves by the script, such as appending a tail. */
+ if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(pbbIn))) {
+ apr_bucket *pbktEOS;
+ lua_pushnil(L);
+ lua_setglobal(L, "bucket");
+ if (lua_resume(L, 0, &nres) == LUA_YIELD && nres == 1) {
+ apr_bucket *pbktOut;
+ size_t olen;
+ const char* output = lua_tolstring(L, 1, &olen);
+ if (olen > 0) {
+ pbktOut = apr_bucket_heap_create(output, olen, NULL,
+ c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(ctx->tmpBucket, pbktOut);
+ }
+ }
+ pbktEOS = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(ctx->tmpBucket, pbktEOS);
+ ap_lua_release_state(L, ctx->spec, r);
+ rv = ap_pass_brigade(f->next, ctx->tmpBucket);
+ apr_brigade_cleanup(ctx->tmpBucket);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+ }
+ /* Clean up */
+ apr_brigade_cleanup(pbbIn);
+ return APR_SUCCESS;
+}
+
+
+
+static apr_status_t lua_input_filter_handle(ap_filter_t *f,
+ apr_bucket_brigade *pbbOut,
+ ap_input_mode_t eMode,
+ apr_read_type_e eBlock,
+ apr_off_t nBytes)
+{
+ request_rec *r = f->r;
+ int rc, lastCall = 0, nres;
+ lua_State *L;
+ lua_filter_ctx* ctx;
+ conn_rec *c = r->connection;
+ apr_status_t ret;
+
+ /* Set up the initial filter context and acquire the function.
+ * The corresponding Lua function should yield here.
+ */
+ if (!f->ctx) {
+ rc = lua_setup_filter_ctx(f,r,&ctx);
+ f->ctx = ctx;
+ if (rc == APR_EGENERAL) {
+ ctx->broken = 1;
+ ap_remove_input_filter(f);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ if (rc == APR_ENOENT ) {
+ ap_remove_input_filter(f);
+ ctx->broken = 1;
+ }
+ if (rc == APR_SUCCESS) {
+ ctx->tmpBucket = apr_brigade_create(r->pool, c->bucket_alloc);
+ }
+ }
+ ctx = (lua_filter_ctx*) f->ctx;
+ L = ctx->L;
+ /* If the Lua script broke or denied serving the request, just pass the buckets through */
+ if (ctx->broken) {
+ return ap_get_brigade(f->next, pbbOut, eMode, eBlock, nBytes);
+ }
+
+ if (APR_BRIGADE_EMPTY(ctx->tmpBucket)) {
+ ret = ap_get_brigade(f->next, ctx->tmpBucket, eMode, eBlock, nBytes);
+ if (eMode == AP_MODE_EATCRLF || ret != APR_SUCCESS)
+ return ret;
+ }
+
+ /* While the Lua function is still yielding, pass buckets to the coroutine */
+ if (!ctx->broken) {
+ lastCall = 0;
+ while (!APR_BRIGADE_EMPTY(ctx->tmpBucket)) {
+ apr_bucket *pbktIn = APR_BRIGADE_FIRST(ctx->tmpBucket);
+ apr_bucket *pbktOut;
+ const char *data;
+ apr_size_t len;
+
+ if (APR_BUCKET_IS_EOS(pbktIn)) {
+ APR_BUCKET_REMOVE(pbktIn);
+ break;
+ }
+
+ /* read the bucket */
+ ret = apr_bucket_read(pbktIn, &data, &len, eBlock);
+ if (ret != APR_SUCCESS)
+ return ret;
+
+ /* Push the bucket onto the Lua stack as a global var */
+ lastCall++;
+ lua_pushlstring(L, data, len);
+ lua_setglobal(L, "bucket");
+
+ /* If Lua yielded, it means we have something to pass on */
+ if (lua_resume(L, 0, &nres) == LUA_YIELD && nres == 1) {
+ size_t olen;
+ const char* output = lua_tolstring(L, 1, &olen);
+ pbktOut = apr_bucket_heap_create(output, olen, 0, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(pbbOut, pbktOut);
+ apr_bucket_delete(pbktIn);
+ return APR_SUCCESS;
+ }
+ else {
+ ctx->broken = 1;
+ ap_lua_release_state(L, ctx->spec, r);
+ ap_remove_input_filter(f);
+ apr_bucket_delete(pbktIn);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+ /* If we've safely reached the end, do a final call to Lua to allow for any
+ finishing moves by the script, such as appending a tail. */
+ if (lastCall == 0) {
+ apr_bucket *pbktEOS = apr_bucket_eos_create(c->bucket_alloc);
+ lua_pushnil(L);
+ lua_setglobal(L, "bucket");
+ if (lua_resume(L, 0, &nres) == LUA_YIELD && nres == 1) {
+ apr_bucket *pbktOut;
+ size_t olen;
+ const char* output = lua_tolstring(L, 1, &olen);
+ pbktOut = apr_bucket_heap_create(output, olen, 0, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(pbbOut, pbktOut);
+ }
+ APR_BRIGADE_INSERT_TAIL(pbbOut,pbktEOS);
+ ap_lua_release_state(L, ctx->spec, r);
+ }
+ }
+ return APR_SUCCESS;
+}
+
+
+/* ---------------- Configury stuff --------------- */
+
+/** harnesses for magic hooks **/
+
+static int lua_request_rec_hook_harness(request_rec *r, const char *name, int apr_hook_when)
+{
+ int rc;
+ apr_pool_t *pool;
+ lua_State *L;
+ ap_lua_vm_spec *spec;
+ ap_lua_server_cfg *server_cfg = ap_get_module_config(r->server->module_config,
+ &lua_module);
+ const ap_lua_dir_cfg *cfg = ap_get_module_config(r->per_dir_config,
+ &lua_module);
+ const char *key = apr_psprintf(r->pool, "%s_%d", name, apr_hook_when);
+ apr_array_header_t *hook_specs = apr_hash_get(cfg->hooks, key,
+ APR_HASH_KEY_STRING);
+ if (hook_specs) {
+ int i;
+ for (i = 0; i < hook_specs->nelts; i++) {
+ ap_lua_mapped_handler_spec *hook_spec =
+ ((ap_lua_mapped_handler_spec **) hook_specs->elts)[i];
+
+ if (hook_spec == NULL) {
+ continue;
+ }
+ spec = create_vm_spec(&pool, r, cfg, server_cfg,
+ hook_spec->file_name,
+ hook_spec->bytecode,
+ hook_spec->bytecode_len,
+ hook_spec->function_name,
+ "request hook");
+
+ L = ap_lua_get_lua_state(pool, spec, r);
+
+ if (!L) {
+ ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, APLOGNO(01477)
+ "lua: Failed to obtain lua interpreter for entry function '%s' in %s",
+ hook_spec->function_name, hook_spec->file_name);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (hook_spec->function_name != NULL) {
+ lua_getglobal(L, hook_spec->function_name);
+ if (!lua_isfunction(L, -1)) {
+ ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, APLOGNO(01478)
+ "lua: Unable to find entry function '%s' in %s (not a valid function)",
+ hook_spec->function_name,
+ hook_spec->file_name);
+ ap_lua_release_state(L, spec, r);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ap_lua_run_lua_request(L, r);
+ }
+ else {
+ int t;
+ ap_lua_run_lua_request(L, r);
+
+ t = lua_gettop(L);
+ lua_setglobal(L, "r");
+ lua_settop(L, t);
+ }
+
+ if (lua_pcall(L, 1, 1, 0)) {
+ report_lua_error(L, r);
+ ap_lua_release_state(L, spec, r);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ rc = DECLINED;
+ if (lua_isnumber(L, -1)) {
+ rc = lua_tointeger(L, -1);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r, "Lua hook %s:%s for phase %s returned %d",
+ hook_spec->file_name, hook_spec->function_name, name, rc);
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, APLOGNO(03017)
+ "Lua hook %s:%s for phase %s did not return a numeric value",
+ hook_spec->file_name, hook_spec->function_name, name);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ if (rc != DECLINED) {
+ ap_lua_release_state(L, spec, r);
+ return rc;
+ }
+ ap_lua_release_state(L, spec, r);
+ }
+ }
+ return DECLINED;
+}
+
+
+/* Fix for making sure that LuaMapHandler works when FallbackResource is set */
+static int lua_map_handler_fixups(request_rec *r)
+{
+ /* If there is no handler set yet, this might be a LuaMapHandler request */
+ if (r->handler == NULL) {
+ int n = 0;
+ ap_regmatch_t match[10];
+ const ap_lua_dir_cfg *cfg = ap_get_module_config(r->per_dir_config,
+ &lua_module);
+ for (n = 0; n < cfg->mapped_handlers->nelts; n++) {
+ ap_lua_mapped_handler_spec *hook_spec =
+ ((ap_lua_mapped_handler_spec **) cfg->mapped_handlers->elts)[n];
+
+ if (hook_spec == NULL) {
+ continue;
+ }
+ if (!ap_regexec(hook_spec->uri_pattern, r->uri, 10, match, 0)) {
+ r->handler = apr_pstrdup(r->pool, "lua-map-handler");
+ return OK;
+ }
+ }
+ }
+ return DECLINED;
+}
+
+
+static int lua_map_handler(request_rec *r)
+{
+ int rc, n = 0;
+ apr_pool_t *pool;
+ lua_State *L;
+ const char *filename, *function_name;
+ const char *values[10];
+ ap_lua_vm_spec *spec;
+ ap_regmatch_t match[10];
+ ap_lua_server_cfg *server_cfg = ap_get_module_config(r->server->module_config,
+ &lua_module);
+ const ap_lua_dir_cfg *cfg = ap_get_module_config(r->per_dir_config,
+ &lua_module);
+ for (n = 0; n < cfg->mapped_handlers->nelts; n++) {
+ ap_lua_mapped_handler_spec *hook_spec =
+ ((ap_lua_mapped_handler_spec **) cfg->mapped_handlers->elts)[n];
+
+ if (hook_spec == NULL) {
+ continue;
+ }
+ if (!ap_regexec(hook_spec->uri_pattern, r->uri, 10, match, 0)) {
+ int i;
+ for (i=0 ; i < 10; i++) {
+ if (match[i].rm_eo >= 0) {
+ values[i] = apr_pstrndup(r->pool, r->uri+match[i].rm_so, match[i].rm_eo - match[i].rm_so);
+ }
+ else values[i] = "";
+ }
+ filename = ap_lua_interpolate_string(r->pool, hook_spec->file_name, values);
+ function_name = ap_lua_interpolate_string(r->pool, hook_spec->function_name, values);
+ spec = create_vm_spec(&pool, r, cfg, server_cfg,
+ filename,
+ hook_spec->bytecode,
+ hook_spec->bytecode_len,
+ function_name,
+ "mapped handler");
+ L = ap_lua_get_lua_state(pool, spec, r);
+
+ if (!L) {
+ ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, APLOGNO(02330)
+ "lua: Failed to obtain Lua interpreter for entry function '%s' in %s",
+ function_name, filename);
+ ap_lua_release_state(L, spec, r);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (function_name != NULL) {
+ lua_getglobal(L, function_name);
+ if (!lua_isfunction(L, -1)) {
+ ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, APLOGNO(02331)
+ "lua: Unable to find entry function '%s' in %s (not a valid function)",
+ function_name,
+ filename);
+ ap_lua_release_state(L, spec, r);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ap_lua_run_lua_request(L, r);
+ }
+ else {
+ int t;
+ ap_lua_run_lua_request(L, r);
+
+ t = lua_gettop(L);
+ lua_setglobal(L, "r");
+ lua_settop(L, t);
+ }
+
+ if (lua_pcall(L, 1, 1, 0)) {
+ report_lua_error(L, r);
+ ap_lua_release_state(L, spec, r);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ rc = DECLINED;
+ if (lua_isnumber(L, -1)) {
+ rc = lua_tointeger(L, -1);
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02483)
+ "lua: Lua handler %s in %s did not return a value, assuming apache2.OK",
+ function_name,
+ filename);
+ rc = OK;
+ }
+ ap_lua_release_state(L, spec, r);
+ if (rc != DECLINED) {
+ return rc;
+ }
+ }
+ }
+ return DECLINED;
+}
+
+
+static apr_size_t config_getstr(ap_configfile_t *cfg, char *buf,
+ size_t bufsiz)
+{
+ apr_size_t i = 0;
+
+ if (cfg->getstr) {
+ apr_status_t rc = (cfg->getstr) (buf, bufsiz, cfg->param);
+ if (rc == APR_SUCCESS) {
+ i = strlen(buf);
+ if (i && buf[i - 1] == '\n')
+ ++cfg->line_number;
+ }
+ else {
+ buf[0] = '\0';
+ i = 0;
+ }
+ }
+ else {
+ while (i < bufsiz) {
+ char ch;
+ apr_status_t rc = (cfg->getch) (&ch, cfg->param);
+ if (rc != APR_SUCCESS)
+ break;
+ buf[i++] = ch;
+ if (ch == '\n') {
+ ++cfg->line_number;
+ break;
+ }
+ }
+ }
+ return i;
+}
+
+typedef struct cr_ctx
+{
+ cmd_parms *cmd;
+ ap_configfile_t *cfp;
+ size_t startline;
+ const char *endstr;
+ char buf[HUGE_STRING_LEN];
+} cr_ctx;
+
+
+/* Okay, this deserves a little explanation -- in order for the errors that lua
+ * generates to be 'accuarate', including line numbers, we basically inject
+ * N line number new lines into the 'top' of the chunk reader.....
+ *
+ * be happy. this is cool.
+ *
+ */
+static const char *lf =
+ "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n";
+#define N_LF 32
+
+static const char *direct_chunkreader(lua_State *lvm, void *udata,
+ size_t *plen)
+{
+ const char *p;
+ struct cr_ctx *ctx = udata;
+
+ if (ctx->startline) {
+ *plen = ctx->startline > N_LF ? N_LF : ctx->startline;
+ ctx->startline -= *plen;
+ return lf;
+ }
+ *plen = config_getstr(ctx->cfp, ctx->buf, HUGE_STRING_LEN);
+
+ for (p = ctx->buf; isspace(*p); ++p);
+ if (p[0] == '<' && p[1] == '/') {
+ apr_size_t i = 0;
+ while (i < strlen(ctx->endstr)) {
+ if (tolower(p[i + 2]) != ctx->endstr[i])
+ return ctx->buf;
+ ++i;
+ }
+ *plen = 0;
+ return NULL;
+ }
+ /*fprintf(stderr, "buf read: %s\n", ctx->buf); */
+ return ctx->buf;
+}
+
+static int ldump_writer(lua_State *L, const void *b, size_t size, void *B)
+{
+ (void) L;
+ luaL_addlstring((luaL_Buffer *) B, (const char *) b, size);
+ return 0;
+}
+
+typedef struct hack_section_baton
+{
+ const char *name;
+ ap_lua_mapped_handler_spec *spec;
+ int apr_hook_when;
+} hack_section_baton;
+
+/* You can be unhappy now.
+ *
+ * This is uncool.
+ *
+ * When you create a <Section handler in httpd, the only 'easy' way to create
+ * a directory context is to parse the section, and convert it into a 'normal'
+ * Configureation option, and then collapse the entire section, in memory,
+ * back into the parent section -- from which you can then get the new directive
+ * invoked.... anyways. evil. Rici taught me how to do this hack :-)
+ */
+static const char *hack_section_handler(cmd_parms *cmd, void *_cfg,
+ const char *arg)
+{
+ ap_lua_dir_cfg *cfg = (ap_lua_dir_cfg *) _cfg;
+ ap_directive_t *directive = cmd->directive;
+ hack_section_baton *baton = directive->data;
+ const char *key = apr_psprintf(cmd->pool, "%s_%d", baton->name, baton->apr_hook_when);
+
+ apr_array_header_t *hook_specs = apr_hash_get(cfg->hooks, key,
+ APR_HASH_KEY_STRING);
+ if (!hook_specs) {
+ hook_specs = apr_array_make(cmd->pool, 2,
+ sizeof(ap_lua_mapped_handler_spec *));
+ apr_hash_set(cfg->hooks, key,
+ APR_HASH_KEY_STRING, hook_specs);
+ }
+
+ baton->spec->scope = cfg->vm_scope;
+
+ *(ap_lua_mapped_handler_spec **) apr_array_push(hook_specs) = baton->spec;
+
+ return NULL;
+}
+
+static const char *register_named_block_function_hook(const char *name,
+ cmd_parms *cmd,
+ void *mconfig,
+ const char *line)
+{
+ const char *function = NULL;
+ ap_lua_mapped_handler_spec *spec;
+ int when = APR_HOOK_MIDDLE;
+ const char *endp = ap_strrchr_c(line, '>');
+
+ if (endp == NULL) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ "> directive missing closing '>'", NULL);
+ }
+
+ line = apr_pstrndup(cmd->temp_pool, line, endp - line);
+
+ if (line[0]) {
+ const char *word;
+ word = ap_getword_conf(cmd->temp_pool, &line);
+ if (*word) {
+ function = apr_pstrdup(cmd->pool, word);
+ }
+ word = ap_getword_conf(cmd->temp_pool, &line);
+ if (*word) {
+ if (!strcasecmp("early", word)) {
+ when = AP_LUA_HOOK_FIRST;
+ }
+ else if (!strcasecmp("late", word)) {
+ when = AP_LUA_HOOK_LAST;
+ }
+ else {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ "> 2nd argument must be 'early' or 'late'", NULL);
+ }
+ }
+ }
+
+ spec = apr_pcalloc(cmd->pool, sizeof(ap_lua_mapped_handler_spec));
+
+ {
+ cr_ctx ctx;
+ lua_State *lvm;
+ char *tmp;
+ int rv;
+ ap_directive_t **current;
+ hack_section_baton *baton;
+
+ spec->file_name = apr_psprintf(cmd->pool, "%s:%u",
+ cmd->config_file->name,
+ cmd->config_file->line_number);
+ if (function) {
+ spec->function_name = (char *) function;
+ }
+ else {
+ function = NULL;
+ }
+
+ ctx.cmd = cmd;
+ tmp = apr_pstrdup(cmd->pool, cmd->err_directive->directive + 1);
+ ap_str_tolower(tmp);
+ ctx.endstr = tmp;
+ ctx.cfp = cmd->config_file;
+ ctx.startline = cmd->config_file->line_number;
+
+ /* This lua State is used only to compile the input strings -> bytecode, so we don't need anything extra. */
+ lvm = luaL_newstate();
+
+ lua_settop(lvm, 0);
+
+ rv = lua_load(lvm, direct_chunkreader, &ctx, spec->file_name);
+
+ if (rv != 0) {
+ const char *errstr = apr_pstrcat(cmd->pool, "Lua Error:",
+ lua_tostring(lvm, -1), NULL);
+ lua_close(lvm);
+ return errstr;
+ }
+ else {
+ luaL_Buffer b;
+ luaL_buffinit(lvm, &b);
+ lua_dump(lvm, ldump_writer, &b);
+ luaL_pushresult(&b);
+ spec->bytecode_len = lua_rawlen(lvm, -1);
+ spec->bytecode = apr_pstrmemdup(cmd->pool, lua_tostring(lvm, -1),
+ spec->bytecode_len);
+ lua_close(lvm);
+ }
+
+ current = mconfig;
+
+ /* Here, we have to replace our current config node for the next pass */
+ if (!*current) {
+ *current = apr_pcalloc(cmd->pool, sizeof(**current));
+ }
+
+ baton = apr_pcalloc(cmd->pool, sizeof(hack_section_baton));
+ baton->name = name;
+ baton->spec = spec;
+ baton->apr_hook_when = when;
+
+ (*current)->filename = cmd->config_file->name;
+ (*current)->line_num = cmd->config_file->line_number;
+ (*current)->directive = apr_pstrdup(cmd->pool, "Lua_____ByteCodeHack");
+ (*current)->args = NULL;
+ (*current)->data = baton;
+ }
+
+ return NULL;
+}
+
+static const char *register_named_file_function_hook(const char *name,
+ cmd_parms *cmd,
+ void *_cfg,
+ const char *file,
+ const char *function,
+ int apr_hook_when)
+{
+ ap_lua_mapped_handler_spec *spec;
+ ap_lua_dir_cfg *cfg = (ap_lua_dir_cfg *) _cfg;
+ const char *key = apr_psprintf(cmd->pool, "%s_%d", name, apr_hook_when);
+ apr_array_header_t *hook_specs = apr_hash_get(cfg->hooks, key,
+ APR_HASH_KEY_STRING);
+
+ if (!hook_specs) {
+ hook_specs = apr_array_make(cmd->pool, 2,
+ sizeof(ap_lua_mapped_handler_spec *));
+ apr_hash_set(cfg->hooks, key, APR_HASH_KEY_STRING, hook_specs);
+ }
+
+ spec = apr_pcalloc(cmd->pool, sizeof(ap_lua_mapped_handler_spec));
+ spec->file_name = apr_pstrdup(cmd->pool, file);
+ spec->function_name = apr_pstrdup(cmd->pool, function);
+ spec->scope = cfg->vm_scope;
+
+ *(ap_lua_mapped_handler_spec **) apr_array_push(hook_specs) = spec;
+ return NULL;
+}
+static const char *register_mapped_file_function_hook(const char *pattern,
+ cmd_parms *cmd,
+ void *_cfg,
+ const char *file,
+ const char *function)
+{
+ ap_lua_mapped_handler_spec *spec;
+ ap_lua_dir_cfg *cfg = (ap_lua_dir_cfg *) _cfg;
+ ap_regex_t *regex = apr_pcalloc(cmd->pool, sizeof(ap_regex_t));
+ if (ap_regcomp(regex, pattern,0)) {
+ return "Invalid regex pattern!";
+ }
+
+ spec = apr_pcalloc(cmd->pool, sizeof(ap_lua_mapped_handler_spec));
+ spec->file_name = apr_pstrdup(cmd->pool, file);
+ spec->function_name = apr_pstrdup(cmd->pool, function);
+ spec->scope = cfg->vm_scope;
+ spec->uri_pattern = regex;
+
+ *(ap_lua_mapped_handler_spec **) apr_array_push(cfg->mapped_handlers) = spec;
+ return NULL;
+}
+static const char *register_filter_function_hook(const char *filter,
+ cmd_parms *cmd,
+ void *_cfg,
+ const char *file,
+ const char *function,
+ int direction)
+{
+ ap_lua_filter_handler_spec *spec;
+ ap_lua_dir_cfg *cfg = (ap_lua_dir_cfg *) _cfg;
+
+ spec = apr_pcalloc(cmd->pool, sizeof(ap_lua_filter_handler_spec));
+ spec->file_name = apr_pstrdup(cmd->pool, file);
+ spec->function_name = apr_pstrdup(cmd->pool, function);
+ spec->filter_name = filter;
+
+ *(ap_lua_filter_handler_spec **) apr_array_push(cfg->mapped_filters) = spec;
+ /* TODO: Make it work on other types than just AP_FTYPE_RESOURCE? */
+ if (direction == AP_LUA_FILTER_OUTPUT) {
+ spec->direction = AP_LUA_FILTER_OUTPUT;
+ ap_register_output_filter_protocol(filter, lua_output_filter_handle, NULL, AP_FTYPE_RESOURCE,
+ AP_FILTER_PROTO_CHANGE|AP_FILTER_PROTO_CHANGE_LENGTH);
+ }
+ else {
+ spec->direction = AP_LUA_FILTER_INPUT;
+ ap_register_input_filter(filter, lua_input_filter_handle, NULL, AP_FTYPE_RESOURCE);
+ }
+ return NULL;
+}
+/* disabled (see reference below)
+static int lua_check_user_id_harness_first(request_rec *r)
+{
+ return lua_request_rec_hook_harness(r, "check_user_id", AP_LUA_HOOK_FIRST);
+}
+*/
+static int lua_check_user_id_harness(request_rec *r)
+{
+ return lua_request_rec_hook_harness(r, "check_user_id", APR_HOOK_MIDDLE);
+}
+/* disabled (see reference below)
+static int lua_check_user_id_harness_last(request_rec *r)
+{
+ return lua_request_rec_hook_harness(r, "check_user_id", AP_LUA_HOOK_LAST);
+}
+*/
+
+static int lua_pre_trans_name_harness(request_rec *r)
+{
+ return lua_request_rec_hook_harness(r, "pre_translate_name", APR_HOOK_MIDDLE);
+}
+
+static int lua_translate_name_harness_first(request_rec *r)
+{
+ return lua_request_rec_hook_harness(r, "translate_name", AP_LUA_HOOK_FIRST);
+}
+static int lua_translate_name_harness(request_rec *r)
+{
+ return lua_request_rec_hook_harness(r, "translate_name", APR_HOOK_MIDDLE);
+}
+static int lua_translate_name_harness_last(request_rec *r)
+{
+ return lua_request_rec_hook_harness(r, "translate_name", AP_LUA_HOOK_LAST);
+}
+
+static int lua_fixup_harness(request_rec *r)
+{
+ return lua_request_rec_hook_harness(r, "fixups", APR_HOOK_MIDDLE);
+}
+
+static int lua_map_to_storage_harness(request_rec *r)
+{
+ return lua_request_rec_hook_harness(r, "map_to_storage", APR_HOOK_MIDDLE);
+}
+
+static int lua_type_checker_harness(request_rec *r)
+{
+ return lua_request_rec_hook_harness(r, "type_checker", APR_HOOK_MIDDLE);
+}
+
+static int lua_access_checker_harness_first(request_rec *r)
+{
+ return lua_request_rec_hook_harness(r, "access_checker", AP_LUA_HOOK_FIRST);
+}
+static int lua_access_checker_harness(request_rec *r)
+{
+ return lua_request_rec_hook_harness(r, "access_checker", APR_HOOK_MIDDLE);
+}
+static int lua_access_checker_harness_last(request_rec *r)
+{
+ return lua_request_rec_hook_harness(r, "access_checker", AP_LUA_HOOK_LAST);
+}
+
+static int lua_auth_checker_harness_first(request_rec *r)
+{
+ return lua_request_rec_hook_harness(r, "auth_checker", AP_LUA_HOOK_FIRST);
+}
+static int lua_auth_checker_harness(request_rec *r)
+{
+ return lua_request_rec_hook_harness(r, "auth_checker", APR_HOOK_MIDDLE);
+}
+static int lua_auth_checker_harness_last(request_rec *r)
+{
+ return lua_request_rec_hook_harness(r, "auth_checker", AP_LUA_HOOK_LAST);
+}
+static void lua_insert_filter_harness(request_rec *r)
+{
+ /* ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(03223)
+ * "LuaHookInsertFilter not yet implemented"); */
+}
+
+static int lua_log_transaction_harness(request_rec *r)
+{
+ return lua_request_rec_hook_harness(r, "log_transaction", APR_HOOK_FIRST);
+}
+
+static int lua_quick_harness(request_rec *r, int lookup)
+{
+ if (lookup) {
+ return DECLINED;
+ }
+ return lua_request_rec_hook_harness(r, "quick", APR_HOOK_MIDDLE);
+}
+
+static const char *register_pre_trans_name_hook(cmd_parms *cmd, void *_cfg,
+ const char *file,
+ const char *function)
+{
+ return register_named_file_function_hook("pre_translate_name", cmd, _cfg, file,
+ function, APR_HOOK_MIDDLE);
+}
+
+static const char *register_pre_trans_name_block(cmd_parms *cmd, void *_cfg,
+ const char *line)
+{
+ return register_named_block_function_hook("pre_translate_name", cmd, _cfg,
+ line);
+}
+
+static const char *register_translate_name_hook(cmd_parms *cmd, void *_cfg,
+ const char *file,
+ const char *function,
+ const char *when)
+{
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_DIRECTORY|NOT_IN_FILES|
+ NOT_IN_HTACCESS);
+ int apr_hook_when = APR_HOOK_MIDDLE;
+ if (err) {
+ return err;
+ }
+
+ if (when) {
+ if (!strcasecmp(when, "early")) {
+ apr_hook_when = AP_LUA_HOOK_FIRST;
+ }
+ else if (!strcasecmp(when, "late")) {
+ apr_hook_when = AP_LUA_HOOK_LAST;
+ }
+ else {
+ return "Third argument must be 'early' or 'late'";
+ }
+ }
+
+ return register_named_file_function_hook("translate_name", cmd, _cfg,
+ file, function, apr_hook_when);
+}
+
+static const char *register_translate_name_block(cmd_parms *cmd, void *_cfg,
+ const char *line)
+{
+ return register_named_block_function_hook("translate_name", cmd, _cfg,
+ line);
+}
+
+
+static const char *register_fixups_hook(cmd_parms *cmd, void *_cfg,
+ const char *file,
+ const char *function)
+{
+ return register_named_file_function_hook("fixups", cmd, _cfg, file,
+ function, APR_HOOK_MIDDLE);
+}
+static const char *register_fixups_block(cmd_parms *cmd, void *_cfg,
+ const char *line)
+{
+ return register_named_block_function_hook("fixups", cmd, _cfg, line);
+}
+
+static const char *register_map_to_storage_hook(cmd_parms *cmd, void *_cfg,
+ const char *file,
+ const char *function)
+{
+ return register_named_file_function_hook("map_to_storage", cmd, _cfg,
+ file, function, APR_HOOK_MIDDLE);
+}
+
+static const char *register_log_transaction_hook(cmd_parms *cmd, void *_cfg,
+ const char *file,
+ const char *function)
+{
+ return register_named_file_function_hook("log_transaction", cmd, _cfg,
+ file, function, APR_HOOK_FIRST);
+}
+
+static const char *register_map_to_storage_block(cmd_parms *cmd, void *_cfg,
+ const char *line)
+{
+ return register_named_block_function_hook("map_to_storage", cmd, _cfg,
+ line);
+}
+
+
+static const char *register_check_user_id_hook(cmd_parms *cmd, void *_cfg,
+ const char *file,
+ const char *function,
+ const char *when)
+{
+ int apr_hook_when = APR_HOOK_MIDDLE;
+/* XXX: This does not currently work!!
+ if (when) {
+ if (!strcasecmp(when, "early")) {
+ apr_hook_when = AP_LUA_HOOK_FIRST;
+ }
+ else if (!strcasecmp(when, "late")) {
+ apr_hook_when = AP_LUA_HOOK_LAST;
+ }
+ else {
+ return "Third argument must be 'early' or 'late'";
+ }
+ }
+*/
+ return register_named_file_function_hook("check_user_id", cmd, _cfg, file,
+ function, apr_hook_when);
+}
+static const char *register_check_user_id_block(cmd_parms *cmd, void *_cfg,
+ const char *line)
+{
+ return register_named_block_function_hook("check_user_id", cmd, _cfg,
+ line);
+}
+
+static const char *register_type_checker_hook(cmd_parms *cmd, void *_cfg,
+ const char *file,
+ const char *function)
+{
+ return register_named_file_function_hook("type_checker", cmd, _cfg, file,
+ function, APR_HOOK_MIDDLE);
+}
+static const char *register_type_checker_block(cmd_parms *cmd, void *_cfg,
+ const char *line)
+{
+ return register_named_block_function_hook("type_checker", cmd, _cfg,
+ line);
+}
+
+static const char *register_access_checker_hook(cmd_parms *cmd, void *_cfg,
+ const char *file,
+ const char *function,
+ const char *when)
+{
+ int apr_hook_when = APR_HOOK_MIDDLE;
+
+ if (when) {
+ if (!strcasecmp(when, "early")) {
+ apr_hook_when = AP_LUA_HOOK_FIRST;
+ }
+ else if (!strcasecmp(when, "late")) {
+ apr_hook_when = AP_LUA_HOOK_LAST;
+ }
+ else {
+ return "Third argument must be 'early' or 'late'";
+ }
+ }
+
+ return register_named_file_function_hook("access_checker", cmd, _cfg,
+ file, function, apr_hook_when);
+}
+static const char *register_access_checker_block(cmd_parms *cmd, void *_cfg,
+ const char *line)
+{
+
+ return register_named_block_function_hook("access_checker", cmd, _cfg,
+ line);
+}
+
+static const char *register_auth_checker_hook(cmd_parms *cmd, void *_cfg,
+ const char *file,
+ const char *function,
+ const char *when)
+{
+ int apr_hook_when = APR_HOOK_MIDDLE;
+
+ if (when) {
+ if (!strcasecmp(when, "early")) {
+ apr_hook_when = AP_LUA_HOOK_FIRST;
+ }
+ else if (!strcasecmp(when, "late")) {
+ apr_hook_when = AP_LUA_HOOK_LAST;
+ }
+ else {
+ return "Third argument must be 'early' or 'late'";
+ }
+ }
+
+ return register_named_file_function_hook("auth_checker", cmd, _cfg, file,
+ function, apr_hook_when);
+}
+static const char *register_auth_checker_block(cmd_parms *cmd, void *_cfg,
+ const char *line)
+{
+ return register_named_block_function_hook("auth_checker", cmd, _cfg,
+ line);
+}
+
+static const char *register_insert_filter_hook(cmd_parms *cmd, void *_cfg,
+ const char *file,
+ const char *function)
+{
+ return "LuaHookInsertFilter not yet implemented";
+}
+
+static const char *register_quick_hook(cmd_parms *cmd, void *_cfg,
+ const char *file, const char *function)
+{
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_DIRECTORY|NOT_IN_FILES|
+ NOT_IN_HTACCESS);
+ if (err) {
+ return err;
+ }
+ return register_named_file_function_hook("quick", cmd, _cfg, file,
+ function, APR_HOOK_MIDDLE);
+}
+static const char *register_map_handler(cmd_parms *cmd, void *_cfg,
+ const char* match, const char *file, const char *function)
+{
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_DIRECTORY|NOT_IN_FILES|
+ NOT_IN_HTACCESS);
+ if (err) {
+ return err;
+ }
+ if (!function) function = "handle";
+ return register_mapped_file_function_hook(match, cmd, _cfg, file,
+ function);
+}
+static const char *register_output_filter(cmd_parms *cmd, void *_cfg,
+ const char* filter, const char *file, const char *function)
+{
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_DIRECTORY|NOT_IN_FILES|
+ NOT_IN_HTACCESS);
+ if (err) {
+ return err;
+ }
+ if (!function) function = "handle";
+ return register_filter_function_hook(filter, cmd, _cfg, file,
+ function, AP_LUA_FILTER_OUTPUT);
+}
+static const char *register_input_filter(cmd_parms *cmd, void *_cfg,
+ const char* filter, const char *file, const char *function)
+{
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_DIRECTORY|NOT_IN_FILES|
+ NOT_IN_HTACCESS);
+ if (err) {
+ return err;
+ }
+ if (!function) function = "handle";
+ return register_filter_function_hook(filter, cmd, _cfg, file,
+ function, AP_LUA_FILTER_INPUT);
+}
+static const char *register_quick_block(cmd_parms *cmd, void *_cfg,
+ const char *line)
+{
+ return register_named_block_function_hook("quick", cmd, _cfg,
+ line);
+}
+
+
+
+static const char *register_package_helper(cmd_parms *cmd,
+ const char *arg,
+ apr_array_header_t *dir_array)
+{
+ apr_status_t rv;
+
+ ap_lua_server_cfg *server_cfg =
+ ap_get_module_config(cmd->server->module_config, &lua_module);
+
+ char *fixed_filename;
+ rv = apr_filepath_merge(&fixed_filename,
+ server_cfg->root_path,
+ arg,
+ APR_FILEPATH_NOTRELATIVE,
+ cmd->pool);
+
+ if (rv != APR_SUCCESS) {
+ return apr_psprintf(cmd->pool,
+ "Unable to build full path to file, %s", arg);
+ }
+
+ *(const char **) apr_array_push(dir_array) = fixed_filename;
+ return NULL;
+}
+
+
+/**
+ * Called for config directive which looks like
+ * LuaPackagePath /lua/package/path/mapped/thing/like/this/?.lua
+ */
+static const char *register_package_dir(cmd_parms *cmd, void *_cfg,
+ const char *arg)
+{
+ ap_lua_dir_cfg *cfg = (ap_lua_dir_cfg *) _cfg;
+
+ return register_package_helper(cmd, arg, cfg->package_paths);
+}
+
+/**
+ * Called for config directive which looks like
+ * LuaPackageCPath /lua/package/path/mapped/thing/like/this/?.so
+ */
+static const char *register_package_cdir(cmd_parms *cmd,
+ void *_cfg,
+ const char *arg)
+{
+ ap_lua_dir_cfg *cfg = (ap_lua_dir_cfg *) _cfg;
+
+ return register_package_helper(cmd, arg, cfg->package_cpaths);
+}
+
+static const char *register_lua_inherit(cmd_parms *cmd,
+ void *_cfg,
+ const char *arg)
+{
+ ap_lua_dir_cfg *cfg = (ap_lua_dir_cfg *) _cfg;
+
+ if (strcasecmp("none", arg) == 0) {
+ cfg->inherit = AP_LUA_INHERIT_NONE;
+ }
+ else if (strcasecmp("parent-first", arg) == 0) {
+ cfg->inherit = AP_LUA_INHERIT_PARENT_FIRST;
+ }
+ else if (strcasecmp("parent-last", arg) == 0) {
+ cfg->inherit = AP_LUA_INHERIT_PARENT_LAST;
+ }
+ else {
+ return apr_psprintf(cmd->pool,
+ "LuaInherit type of '%s' not recognized, valid "
+ "options are 'none', 'parent-first', and 'parent-last'",
+ arg);
+ }
+ return NULL;
+}
+static const char *register_lua_codecache(cmd_parms *cmd,
+ void *_cfg,
+ const char *arg)
+{
+ ap_lua_dir_cfg *cfg = (ap_lua_dir_cfg *) _cfg;
+
+ if (strcasecmp("never", arg) == 0) {
+ cfg->codecache = AP_LUA_CACHE_NEVER;
+ }
+ else if (strcasecmp("stat", arg) == 0) {
+ cfg->codecache = AP_LUA_CACHE_STAT;
+ }
+ else if (strcasecmp("forever", arg) == 0) {
+ cfg->codecache = AP_LUA_CACHE_FOREVER;
+ }
+ else {
+ return apr_psprintf(cmd->pool,
+ "LuaCodeCache type of '%s' not recognized, valid "
+ "options are 'never', 'stat', and 'forever'",
+ arg);
+ }
+ return NULL;
+}
+static const char *register_lua_scope(cmd_parms *cmd,
+ void *_cfg,
+ const char *scope,
+ const char *min,
+ const char *max)
+{
+ ap_lua_dir_cfg *cfg = (ap_lua_dir_cfg *) _cfg;
+ if (strcmp("once", scope) == 0) {
+ cfg->vm_scope = AP_LUA_SCOPE_ONCE;
+ }
+ else if (strcmp("request", scope) == 0) {
+ cfg->vm_scope = AP_LUA_SCOPE_REQUEST;
+ }
+ else if (strcmp("conn", scope) == 0) {
+ cfg->vm_scope = AP_LUA_SCOPE_CONN;
+ }
+ else if (strcmp("thread", scope) == 0) {
+#if !APR_HAS_THREADS
+ return apr_psprintf(cmd->pool,
+ "Scope type of '%s' cannot be used because this "
+ "server does not have threading support "
+ "(APR_HAS_THREADS)",
+ scope);
+#endif
+ cfg->vm_scope = AP_LUA_SCOPE_THREAD;
+ }
+ else if (strcmp("server", scope) == 0) {
+ unsigned int vmin, vmax;
+#if !APR_HAS_THREADS
+ return apr_psprintf(cmd->pool,
+ "Scope type of '%s' cannot be used because this "
+ "server does not have threading support "
+ "(APR_HAS_THREADS)",
+ scope);
+#endif
+ cfg->vm_scope = AP_LUA_SCOPE_SERVER;
+ vmin = min ? atoi(min) : 1;
+ vmax = max ? atoi(max) : 1;
+ if (vmin == 0) {
+ vmin = 1;
+ }
+ if (vmax < vmin) {
+ vmax = vmin;
+ }
+ cfg->vm_min = vmin;
+ cfg->vm_max = vmax;
+ }
+ else {
+ return apr_psprintf(cmd->pool,
+ "Invalid value for LuaScope, '%s', acceptable "
+ "values are: 'once', 'request', 'conn'"
+#if APR_HAS_THREADS
+ ", 'thread', 'server'"
+#endif
+ ,scope);
+ }
+
+ return NULL;
+}
+
+
+
+static const char *register_lua_root(cmd_parms *cmd, void *_cfg,
+ const char *root)
+{
+ /* ap_lua_dir_cfg* cfg = (ap_lua_dir_cfg*)_cfg; */
+ ap_lua_server_cfg *cfg = ap_get_module_config(cmd->server->module_config,
+ &lua_module);
+
+ cfg->root_path = root;
+ return NULL;
+}
+
+const char *ap_lua_ssl_val(apr_pool_t *p, server_rec *s, conn_rec *c,
+ request_rec *r, const char *var)
+{
+ return ap_ssl_var_lookup(p, s, c, r, var);
+}
+
+int ap_lua_ssl_is_https(conn_rec *c)
+{
+ return ap_ssl_conn_is_ssl(c);
+}
+
+/*******************************/
+
+static const char *lua_authz_parse(cmd_parms *cmd, const char *require_line,
+ const void **parsed_require_line)
+{
+ const char *provider_name;
+ lua_authz_provider_spec *spec;
+ lua_authz_provider_func *func = apr_pcalloc(cmd->pool, sizeof(lua_authz_provider_func));
+
+ apr_pool_userdata_get((void**)&provider_name, AUTHZ_PROVIDER_NAME_NOTE,
+ cmd->temp_pool);
+ ap_assert(provider_name != NULL);
+
+ spec = apr_hash_get(lua_authz_providers, provider_name, APR_HASH_KEY_STRING);
+ ap_assert(spec != NULL);
+ func->spec = spec;
+
+ if (require_line && *require_line) {
+ const char *arg;
+ func->args = apr_array_make(cmd->pool, 2, sizeof(const char *));
+ while ((arg = ap_getword_conf(cmd->pool, &require_line)) && *arg) {
+ APR_ARRAY_PUSH(func->args, const char *) = arg;
+ }
+ }
+
+ *parsed_require_line = func;
+ return NULL;
+}
+
+static authz_status lua_authz_check(request_rec *r, const char *require_line,
+ const void *parsed_require_line)
+{
+ apr_pool_t *pool;
+ ap_lua_vm_spec *spec;
+ lua_State *L;
+ ap_lua_server_cfg *server_cfg = ap_get_module_config(r->server->module_config,
+ &lua_module);
+ const ap_lua_dir_cfg *cfg = ap_get_module_config(r->per_dir_config,
+ &lua_module);
+ const lua_authz_provider_func *prov_func = parsed_require_line;
+ const lua_authz_provider_spec *prov_spec = prov_func->spec;
+ int result;
+ int nargs = 0;
+
+ spec = create_vm_spec(&pool, r, cfg, server_cfg, prov_spec->file_name,
+ NULL, 0, prov_spec->function_name, "authz provider");
+
+ L = ap_lua_get_lua_state(pool, spec, r);
+ if (L == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02314)
+ "Unable to compile VM for authz provider %s", prov_spec->name);
+ return AUTHZ_GENERAL_ERROR;
+ }
+ lua_getglobal(L, prov_spec->function_name);
+ if (!lua_isfunction(L, -1)) {
+ ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, APLOGNO(02319)
+ "Unable to find entry function '%s' in %s (not a valid function)",
+ prov_spec->function_name, prov_spec->file_name);
+ ap_lua_release_state(L, spec, r);
+ return AUTHZ_GENERAL_ERROR;
+ }
+ ap_lua_run_lua_request(L, r);
+ if (prov_func->args) {
+ int i;
+ if (!lua_checkstack(L, prov_func->args->nelts)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02315)
+ "Error: authz provider %s: too many arguments", prov_spec->name);
+ ap_lua_release_state(L, spec, r);
+ return AUTHZ_GENERAL_ERROR;
+ }
+ for (i = 0; i < prov_func->args->nelts; i++) {
+ const char *arg = APR_ARRAY_IDX(prov_func->args, i, const char *);
+ lua_pushstring(L, arg);
+ }
+ nargs = prov_func->args->nelts;
+ }
+ if (lua_pcall(L, 1 + nargs, 1, 0)) {
+ const char *err = lua_tostring(L, -1);
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02316)
+ "Error executing authz provider %s: %s", prov_spec->name, err);
+ ap_lua_release_state(L, spec, r);
+ return AUTHZ_GENERAL_ERROR;
+ }
+ if (!lua_isnumber(L, -1)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02317)
+ "Error: authz provider %s did not return integer", prov_spec->name);
+ ap_lua_release_state(L, spec, r);
+ return AUTHZ_GENERAL_ERROR;
+ }
+ result = lua_tointeger(L, -1);
+ ap_lua_release_state(L, spec, r);
+ switch (result) {
+ case AUTHZ_DENIED:
+ case AUTHZ_GRANTED:
+ case AUTHZ_NEUTRAL:
+ case AUTHZ_GENERAL_ERROR:
+ case AUTHZ_DENIED_NO_USER:
+ return result;
+ default:
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02318)
+ "Error: authz provider %s: invalid return value %d",
+ prov_spec->name, result);
+ }
+ return AUTHZ_GENERAL_ERROR;
+}
+
+static const authz_provider lua_authz_provider =
+{
+ &lua_authz_check,
+ &lua_authz_parse,
+};
+
+static const char *register_authz_provider(cmd_parms *cmd, void *_cfg,
+ const char *name, const char *file,
+ const char *function)
+{
+ lua_authz_provider_spec *spec;
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err)
+ return err;
+
+ spec = apr_pcalloc(cmd->pool, sizeof(*spec));
+ spec->name = name;
+ spec->file_name = file;
+ spec->function_name = function;
+
+ apr_hash_set(lua_authz_providers, name, APR_HASH_KEY_STRING, spec);
+ ap_register_auth_provider(cmd->pool, AUTHZ_PROVIDER_GROUP, name,
+ AUTHZ_PROVIDER_VERSION,
+ &lua_authz_provider,
+ AP_AUTH_INTERNAL_PER_CONF);
+ return NULL;
+}
+
+
+static const command_rec lua_commands[] = {
+
+ AP_INIT_TAKE1("LuaRoot", register_lua_root, NULL, OR_ALL,
+ "Specify the base path for resolving relative paths for mod_lua directives"),
+
+ AP_INIT_TAKE1("LuaPackagePath", register_package_dir, NULL, OR_ALL,
+ "Add a directory to lua's package.path"),
+
+ AP_INIT_TAKE1("LuaPackageCPath", register_package_cdir, NULL, OR_ALL,
+ "Add a directory to lua's package.cpath"),
+
+ AP_INIT_TAKE3("LuaAuthzProvider", register_authz_provider, NULL, RSRC_CONF|EXEC_ON_READ,
+ "Provide an authorization provider"),
+
+ AP_INIT_TAKE2("LuaHookPreTranslateName", register_pre_trans_name_hook, NULL,
+ OR_ALL,
+ "Provide a hook for the pre_translate name phase of request processing"),
+
+ AP_INIT_RAW_ARGS("<LuaHookPreTranslateName", register_pre_trans_name_block, NULL,
+ EXEC_ON_READ | OR_ALL,
+ "Provide a hook for the pre_translate name phase of request processing"),
+
+ AP_INIT_TAKE23("LuaHookTranslateName", register_translate_name_hook, NULL,
+ OR_ALL,
+ "Provide a hook for the translate name phase of request processing"),
+
+ AP_INIT_RAW_ARGS("<LuaHookTranslateName", register_translate_name_block,
+ NULL,
+ EXEC_ON_READ | OR_ALL,
+ "Provide a hook for the translate name phase of request processing"),
+
+ AP_INIT_TAKE2("LuaHookFixups", register_fixups_hook, NULL, OR_ALL,
+ "Provide a hook for the fixups phase of request processing"),
+ AP_INIT_RAW_ARGS("<LuaHookFixups", register_fixups_block, NULL,
+ EXEC_ON_READ | OR_ALL,
+ "Provide a inline hook for the fixups phase of request processing"),
+
+ /* todo: test */
+ AP_INIT_TAKE2("LuaHookMapToStorage", register_map_to_storage_hook, NULL,
+ OR_ALL,
+ "Provide a hook for the map_to_storage phase of request processing"),
+ AP_INIT_RAW_ARGS("<LuaHookMapToStorage", register_map_to_storage_block,
+ NULL,
+ EXEC_ON_READ | OR_ALL,
+ "Provide a hook for the map_to_storage phase of request processing"),
+
+ /* todo: test */
+ AP_INIT_TAKE23("LuaHookCheckUserID", register_check_user_id_hook, NULL,
+ OR_ALL,
+ "Provide a hook for the check_user_id phase of request processing"),
+ AP_INIT_RAW_ARGS("<LuaHookCheckUserID", register_check_user_id_block,
+ NULL,
+ EXEC_ON_READ | OR_ALL,
+ "Provide a hook for the check_user_id phase of request processing"),
+
+ /* todo: test */
+ AP_INIT_TAKE2("LuaHookTypeChecker", register_type_checker_hook, NULL,
+ OR_ALL,
+ "Provide a hook for the type_checker phase of request processing"),
+ AP_INIT_RAW_ARGS("<LuaHookTypeChecker", register_type_checker_block, NULL,
+ EXEC_ON_READ | OR_ALL,
+ "Provide a hook for the type_checker phase of request processing"),
+
+ /* todo: test */
+ AP_INIT_TAKE23("LuaHookAccessChecker", register_access_checker_hook, NULL,
+ OR_ALL,
+ "Provide a hook for the access_checker phase of request processing"),
+ AP_INIT_RAW_ARGS("<LuaHookAccessChecker", register_access_checker_block,
+ NULL,
+ EXEC_ON_READ | OR_ALL,
+ "Provide a hook for the access_checker phase of request processing"),
+
+ /* todo: test */
+ AP_INIT_TAKE23("LuaHookAuthChecker", register_auth_checker_hook, NULL,
+ OR_ALL,
+ "Provide a hook for the auth_checker phase of request processing"),
+ AP_INIT_RAW_ARGS("<LuaHookAuthChecker", register_auth_checker_block, NULL,
+ EXEC_ON_READ | OR_ALL,
+ "Provide a hook for the auth_checker phase of request processing"),
+
+ /* todo: test */
+ AP_INIT_TAKE2("LuaHookInsertFilter", register_insert_filter_hook, NULL,
+ OR_ALL,
+ "Provide a hook for the insert_filter phase of request processing"),
+
+ AP_INIT_TAKE2("LuaHookLog", register_log_transaction_hook, NULL,
+ OR_ALL,
+ "Provide a hook for the logging phase of request processing"),
+
+ AP_INIT_TAKE123("LuaScope", register_lua_scope, NULL, OR_ALL,
+ "One of once, request, conn, server -- default is once"),
+
+ AP_INIT_TAKE1("LuaInherit", register_lua_inherit, NULL, OR_ALL,
+ "Controls how Lua scripts in parent contexts are merged with the current "
+ " context: none|parent-last|parent-first (default: parent-first) "),
+
+ AP_INIT_TAKE1("LuaCodeCache", register_lua_codecache, NULL, OR_ALL,
+ "Controls the behavior of the in-memory code cache "
+ " context: stat|forever|never (default: stat) "),
+
+ AP_INIT_TAKE2("LuaQuickHandler", register_quick_hook, NULL, OR_ALL,
+ "Provide a hook for the quick handler of request processing"),
+ AP_INIT_RAW_ARGS("<LuaQuickHandler", register_quick_block, NULL,
+ EXEC_ON_READ | OR_ALL,
+ "Provide a hook for the quick handler of request processing"),
+ AP_INIT_RAW_ARGS("Lua_____ByteCodeHack", hack_section_handler, NULL,
+ OR_ALL,
+ "(internal) Byte code handler"),
+ AP_INIT_TAKE23("LuaMapHandler", register_map_handler, NULL, OR_ALL,
+ "Maps a path to a lua handler"),
+ AP_INIT_TAKE3("LuaOutputFilter", register_output_filter, NULL, OR_ALL,
+ "Registers a Lua function as an output filter"),
+ AP_INIT_TAKE3("LuaInputFilter", register_input_filter, NULL, OR_ALL,
+ "Registers a Lua function as an input filter"),
+ {NULL}
+};
+
+
+static void *create_dir_config(apr_pool_t *p, char *dir)
+{
+ ap_lua_dir_cfg *cfg = apr_pcalloc(p, sizeof(ap_lua_dir_cfg));
+ cfg->package_paths = apr_array_make(p, 2, sizeof(char *));
+ cfg->package_cpaths = apr_array_make(p, 2, sizeof(char *));
+ cfg->mapped_handlers =
+ apr_array_make(p, 1, sizeof(ap_lua_mapped_handler_spec *));
+ cfg->mapped_filters =
+ apr_array_make(p, 1, sizeof(ap_lua_filter_handler_spec *));
+ cfg->pool = p;
+ cfg->hooks = apr_hash_make(p);
+ cfg->dir = apr_pstrdup(p, dir);
+ cfg->vm_scope = AP_LUA_SCOPE_UNSET;
+ cfg->codecache = AP_LUA_CACHE_UNSET;
+ cfg->vm_min = 0;
+ cfg->vm_max = 0;
+ cfg->inherit = AP_LUA_INHERIT_UNSET;
+
+ return cfg;
+}
+
+static int create_request_config(request_rec *r)
+{
+ ap_lua_request_cfg *cfg = apr_palloc(r->pool, sizeof(ap_lua_request_cfg));
+ cfg->mapped_request_details = NULL;
+ cfg->request_scoped_vms = apr_hash_make(r->pool);
+ ap_set_module_config(r->request_config, &lua_module, cfg);
+ return OK;
+}
+
+static void *create_server_config(apr_pool_t *p, server_rec *s)
+{
+
+ ap_lua_server_cfg *cfg = apr_pcalloc(p, sizeof(ap_lua_server_cfg));
+ cfg->root_path = NULL;
+
+ return cfg;
+}
+
+static int lua_request_hook(lua_State *L, request_rec *r)
+{
+ ap_lua_push_request(L, r);
+ return OK;
+}
+
+static int lua_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp)
+{
+ ap_mutex_register(pconf, "lua-ivm-shm", NULL, APR_LOCK_DEFAULT, 0);
+ return OK;
+}
+
+static int lua_post_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ apr_pool_t **pool;
+ apr_status_t rs;
+
+ if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG)
+ return OK;
+
+ /* Create ivm mutex */
+ rs = ap_global_mutex_create(&lua_ivm_mutex, NULL, "lua-ivm-shm", NULL,
+ s, pconf, 0);
+ if (APR_SUCCESS != rs) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* Create shared memory space, anonymous first if possible. */
+ rs = apr_shm_create(&lua_ivm_shm, sizeof pool, NULL, pconf);
+ if (APR_STATUS_IS_ENOTIMPL(rs)) {
+ /* Fall back to filename-based; nuke any left-over first. */
+ lua_ivm_shmfile = ap_runtime_dir_relative(pconf, DEFAULT_LUA_SHMFILE);
+
+ apr_shm_remove(lua_ivm_shmfile, pconf);
+
+ rs = apr_shm_create(&lua_ivm_shm, sizeof pool, lua_ivm_shmfile, pconf);
+ }
+ if (rs != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rs, s, APLOGNO(02665)
+ "mod_lua: Failed to create shared memory segment on file %s",
+ lua_ivm_shmfile ? lua_ivm_shmfile : "(anonymous)");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ pool = (apr_pool_t **)apr_shm_baseaddr_get(lua_ivm_shm);
+ apr_pool_create(pool, pconf);
+ apr_pool_tag(*pool, "mod_lua-shared");
+ apr_pool_cleanup_register(pconf, NULL, shm_cleanup_wrapper,
+ apr_pool_cleanup_null);
+ return OK;
+}
+static void *overlay_hook_specs(apr_pool_t *p,
+ const void *key,
+ apr_ssize_t klen,
+ const void *overlay_val,
+ const void *base_val,
+ const void *data)
+{
+ const apr_array_header_t *overlay_info = (const apr_array_header_t*)overlay_val;
+ const apr_array_header_t *base_info = (const apr_array_header_t*)base_val;
+ return apr_array_append(p, base_info, overlay_info);
+}
+
+static void *merge_dir_config(apr_pool_t *p, void *basev, void *overridesv)
+{
+ ap_lua_dir_cfg *a, *base, *overrides;
+
+ a = (ap_lua_dir_cfg *)apr_pcalloc(p, sizeof(ap_lua_dir_cfg));
+ base = (ap_lua_dir_cfg*)basev;
+ overrides = (ap_lua_dir_cfg*)overridesv;
+
+ a->pool = overrides->pool;
+ a->dir = apr_pstrdup(p, overrides->dir);
+
+ a->vm_scope = (overrides->vm_scope == AP_LUA_SCOPE_UNSET) ? base->vm_scope: overrides->vm_scope;
+ a->inherit = (overrides->inherit == AP_LUA_INHERIT_UNSET) ? base->inherit : overrides->inherit;
+ a->codecache = (overrides->codecache == AP_LUA_CACHE_UNSET) ? base->codecache : overrides->codecache;
+
+ a->vm_min = (overrides->vm_min == 0) ? base->vm_min : overrides->vm_min;
+ a->vm_max = (overrides->vm_max == 0) ? base->vm_max : overrides->vm_max;
+
+ if (a->inherit == AP_LUA_INHERIT_UNSET || a->inherit == AP_LUA_INHERIT_PARENT_FIRST) {
+ a->package_paths = apr_array_append(p, base->package_paths, overrides->package_paths);
+ a->package_cpaths = apr_array_append(p, base->package_cpaths, overrides->package_cpaths);
+ a->mapped_handlers = apr_array_append(p, base->mapped_handlers, overrides->mapped_handlers);
+ a->mapped_filters = apr_array_append(p, base->mapped_filters, overrides->mapped_filters);
+ a->hooks = apr_hash_merge(p, overrides->hooks, base->hooks, overlay_hook_specs, NULL);
+ }
+ else if (a->inherit == AP_LUA_INHERIT_PARENT_LAST) {
+ a->package_paths = apr_array_append(p, overrides->package_paths, base->package_paths);
+ a->package_cpaths = apr_array_append(p, overrides->package_cpaths, base->package_cpaths);
+ a->mapped_handlers = apr_array_append(p, overrides->mapped_handlers, base->mapped_handlers);
+ a->mapped_filters = apr_array_append(p, overrides->mapped_filters, base->mapped_filters);
+ a->hooks = apr_hash_merge(p, base->hooks, overrides->hooks, overlay_hook_specs, NULL);
+ }
+ else {
+ a->package_paths = overrides->package_paths;
+ a->package_cpaths = overrides->package_cpaths;
+ a->mapped_handlers= overrides->mapped_handlers;
+ a->mapped_filters= overrides->mapped_filters;
+ a->hooks= overrides->hooks;
+ }
+
+ return a;
+}
+
+static void lua_register_hooks(apr_pool_t *p)
+{
+ /* ap_register_output_filter("luahood", luahood, NULL, AP_FTYPE_RESOURCE); */
+ ap_hook_handler(lua_handler, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_create_request(create_request_config, NULL, NULL,
+ APR_HOOK_MIDDLE);
+
+ /* http_request.h hooks */
+ ap_hook_pre_translate_name(lua_pre_trans_name_harness, NULL, NULL,
+ APR_HOOK_MIDDLE);
+
+ ap_hook_translate_name(lua_translate_name_harness_first, NULL, NULL,
+ AP_LUA_HOOK_FIRST);
+ ap_hook_translate_name(lua_translate_name_harness, NULL, NULL,
+ APR_HOOK_MIDDLE);
+ ap_hook_translate_name(lua_translate_name_harness_last, NULL, NULL,
+ AP_LUA_HOOK_LAST);
+
+ ap_hook_fixups(lua_fixup_harness, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_map_to_storage(lua_map_to_storage_harness, NULL, NULL,
+ APR_HOOK_MIDDLE);
+
+/* XXX: Does not work :(
+ * ap_hook_check_user_id(lua_check_user_id_harness_first, NULL, NULL,
+ AP_LUA_HOOK_FIRST);
+ */
+ ap_hook_check_user_id(lua_check_user_id_harness, NULL, NULL,
+ APR_HOOK_MIDDLE);
+/* XXX: Does not work :(
+ * ap_hook_check_user_id(lua_check_user_id_harness_last, NULL, NULL,
+ AP_LUA_HOOK_LAST);
+*/
+ ap_hook_type_checker(lua_type_checker_harness, NULL, NULL,
+ APR_HOOK_MIDDLE);
+
+ ap_hook_access_checker(lua_access_checker_harness_first, NULL, NULL,
+ AP_LUA_HOOK_FIRST);
+ ap_hook_access_checker(lua_access_checker_harness, NULL, NULL,
+ APR_HOOK_MIDDLE);
+ ap_hook_access_checker(lua_access_checker_harness_last, NULL, NULL,
+ AP_LUA_HOOK_LAST);
+ ap_hook_auth_checker(lua_auth_checker_harness_first, NULL, NULL,
+ AP_LUA_HOOK_FIRST);
+ ap_hook_auth_checker(lua_auth_checker_harness, NULL, NULL,
+ APR_HOOK_MIDDLE);
+ ap_hook_auth_checker(lua_auth_checker_harness_last, NULL, NULL,
+ AP_LUA_HOOK_LAST);
+
+ ap_hook_insert_filter(lua_insert_filter_harness, NULL, NULL,
+ APR_HOOK_MIDDLE);
+ ap_hook_quick_handler(lua_quick_harness, NULL, NULL, APR_HOOK_FIRST);
+
+ ap_hook_post_config(lua_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_pre_config(lua_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
+
+ APR_OPTIONAL_HOOK(ap_lua, lua_open, lua_open_hook, NULL, NULL,
+ APR_HOOK_REALLY_FIRST);
+
+ APR_OPTIONAL_HOOK(ap_lua, lua_request, lua_request_hook, NULL, NULL,
+ APR_HOOK_REALLY_FIRST);
+ ap_hook_handler(lua_map_handler, NULL, NULL, AP_LUA_HOOK_FIRST);
+
+ /* Hook this right before FallbackResource kicks in */
+ ap_hook_fixups(lua_map_handler_fixups, NULL, NULL, AP_LUA_HOOK_LAST-2);
+ ap_hook_child_init(ap_lua_init_mutex, NULL, NULL, APR_HOOK_MIDDLE);
+
+ /* providers */
+ lua_authz_providers = apr_hash_make(p);
+
+ /* Logging catcher */
+ ap_hook_log_transaction(lua_log_transaction_harness,NULL,NULL,
+ APR_HOOK_FIRST);
+}
+
+AP_DECLARE_MODULE(lua) = {
+ STANDARD20_MODULE_STUFF,
+ create_dir_config, /* create per-dir config structures */
+ merge_dir_config, /* merge per-dir config structures */
+ create_server_config, /* create per-server config structures */
+ NULL, /* merge per-server config structures */
+ lua_commands, /* table of config file commands */
+ lua_register_hooks /* register hooks */
+};
diff --git a/modules/lua/mod_lua.dep b/modules/lua/mod_lua.dep
new file mode 100644
index 0000000..126b7ce
--- /dev/null
+++ b/modules/lua/mod_lua.dep
@@ -0,0 +1,418 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_lua.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\lua_apr.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_mpm.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_varbuf.h"\
+ "..\..\srclib\apr-util\include\apr_base64.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_reslist.h"\
+ "..\..\srclib\apr-util\include\apr_sha1.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_thread_rwlock.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\lua_apr.h"\
+ ".\lua_request.h"\
+ ".\lua_vmprep.h"\
+ ".\mod_lua.h"\
+
+
+.\lua_config.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_varbuf.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_reslist.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_thread_rwlock.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\lua_config.h"\
+ ".\lua_request.h"\
+ ".\lua_vmprep.h"\
+ ".\mod_lua.h"\
+
+
+.\lua_dbd.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_varbuf.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_dbd.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_reslist.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_thread_rwlock.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ "..\database\mod_dbd.h"\
+ ".\lua_dbd.h"\
+ ".\lua_request.h"\
+ ".\lua_vmprep.h"\
+ ".\mod_lua.h"\
+
+
+.\lua_passwd.c : \
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_sha1.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr-util\include\apu_version.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_version.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\lua_passwd.h"\
+
+
+.\lua_request.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_mpm.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_cookies.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_md5.h"\
+ "..\..\include\util_script.h"\
+ "..\..\include\util_varbuf.h"\
+ "..\..\srclib\apr-util\include\apr_base64.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_date.h"\
+ "..\..\srclib\apr-util\include\apr_dbd.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_reslist.h"\
+ "..\..\srclib\apr-util\include\apr_sha1.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr-util\include\apu_version.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_thread_rwlock.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_version.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ "..\database\mod_dbd.h"\
+ ".\lua_apr.h"\
+ ".\lua_dbd.h"\
+ ".\lua_passwd.h"\
+ ".\lua_request.h"\
+ ".\lua_vmprep.h"\
+ ".\mod_lua.h"\
+
+
+.\lua_vmprep.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_varbuf.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_reslist.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_thread_rwlock.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\lua_config.h"\
+ ".\lua_request.h"\
+ ".\lua_vmprep.h"\
+ ".\mod_lua.h"\
+
+
+.\mod_lua.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_mpm.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\include\util_varbuf.h"\
+ "..\..\srclib\apr-util\include\apr_base64.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_reslist.h"\
+ "..\..\srclib\apr-util\include\apr_sha1.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_thread_rwlock.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ "..\ssl\mod_ssl.h"\
+ ".\lua_apr.h"\
+ ".\lua_config.h"\
+ ".\lua_request.h"\
+ ".\lua_vmprep.h"\
+ ".\mod_lua.h"\
+
diff --git a/modules/lua/mod_lua.dsp b/modules/lua/mod_lua.dsp
new file mode 100644
index 0000000..ef3294e
--- /dev/null
+++ b/modules/lua/mod_lua.dsp
@@ -0,0 +1,163 @@
+# Microsoft Developer Studio Project File - Name="mod_lua" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_lua - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_lua.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_lua.mak" CFG="mod_lua - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_lua - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_lua - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_lua - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/lua/src" /I "../ssl" /I "../database" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "AP_LUA_DECLARE_EXPORT" /Fd"Release\mod_lua_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_lua.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_lua.so" /d LONG_NAME="lua_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_lua.so" /base:@..\..\os\win32\BaseAddr.ref,mod_lua.so
+# ADD LINK32 kernel32.lib ws2_32.lib lua51.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_lua.so" /base:@..\..\os\win32\BaseAddr.ref,mod_lua.so /opt:ref /libpath:"../../srclib/lua/src"
+# Begin Special Build Tool
+TargetPath=.\Release\mod_lua.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_lua - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/lua/src" /I "../ssl" /I "../database" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "AP_LUA_DECLARE_EXPORT" /Fd"Debug\mod_lua_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_lua.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_lua.so" /d LONG_NAME="lua_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_lua.so" /base:@..\..\os\win32\BaseAddr.ref,mod_lua.so
+# ADD LINK32 kernel32.lib ws2_32.lib lua51.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_lua.so" /base:@..\..\os\win32\BaseAddr.ref,mod_lua.so /libpath:"../../srclib/lua/src"
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_lua.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_lua - Win32 Release"
+# Name "mod_lua - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\lua_apr.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\lua_apr.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\lua_config.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\lua_config.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\lua_passwd.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\lua_passwd.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\lua_request.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\lua_request.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\lua_vmprep.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\lua_vmprep.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\mod_lua.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\mod_lua.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\lua_dbd.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\lua_dbd.h
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/lua/mod_lua.h b/modules/lua/mod_lua.h
new file mode 100644
index 0000000..33807fb
--- /dev/null
+++ b/modules/lua/mod_lua.h
@@ -0,0 +1,187 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MOD_LUA_H_
+#define _MOD_LUA_H_
+
+#include <stdio.h>
+
+#include "httpd.h"
+#include "http_core.h"
+#include "http_config.h"
+#include "http_request.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "http_ssl.h"
+#include "ap_regex.h"
+
+#include "ap_config.h"
+#include "util_filter.h"
+
+#include "apr_thread_rwlock.h"
+#include "apr_strings.h"
+#include "apr_tables.h"
+#include "apr_hash.h"
+#include "apr_buckets.h"
+#include "apr_file_info.h"
+#include "apr_time.h"
+#include "apr_hooks.h"
+#include "apr_reslist.h"
+
+#include "lua.h"
+#include "lauxlib.h"
+#include "lualib.h"
+
+#if LUA_VERSION_NUM > 501
+/* Load mode for lua_load() */
+#define lua_load(a,b,c,d) lua_load(a,b,c,d,NULL)
+
+#if LUA_VERSION_NUM > 503
+#define lua_resume(a,b,c) lua_resume(a, NULL, b, c)
+#else
+/* ### For version < 5.4, assume that exactly one stack item is on the
+ * stack, which is what the code did before but seems dubious. */
+#define lua_resume(a,b,c) (*(c) = 1, lua_resume(a, NULL, b))
+#endif
+
+#define luaL_setfuncs_compat(a,b) luaL_setfuncs(a,b,0)
+#else
+#define lua_rawlen(L,i) lua_objlen(L, (i))
+#define luaL_setfuncs_compat(a,b) luaL_register(a,NULL,b)
+#define lua_resume(a,b,c) (*(c) = 1, lua_resume(a, b))
+#endif
+#if LUA_VERSION_NUM > 502
+#define lua_dump(a,b,c) lua_dump(a,b,c,0)
+#endif
+
+/* Create a set of AP_LUA_DECLARE(type), AP_LUA_DECLARE_NONSTD(type) and
+ * AP_LUA_DECLARE_DATA with appropriate export and import tags for the platform
+ */
+#if !defined(WIN32)
+#define AP_LUA_DECLARE(type) type
+#define AP_LUA_DECLARE_NONSTD(type) type
+#define AP_LUA_DECLARE_DATA
+#elif defined(AP_LUA_DECLARE_STATIC)
+#define AP_LUA_DECLARE(type) type __stdcall
+#define AP_LUA_DECLARE_NONSTD(type) type
+#define AP_LUA_DECLARE_DATA
+#elif defined(AP_LUA_DECLARE_EXPORT)
+#define AP_LUA_DECLARE(type) __declspec(dllexport) type __stdcall
+#define AP_LUA_DECLARE_NONSTD(type) __declspec(dllexport) type
+#define AP_LUA_DECLARE_DATA __declspec(dllexport)
+#else
+#define AP_LUA_DECLARE(type) __declspec(dllimport) type __stdcall
+#define AP_LUA_DECLARE_NONSTD(type) __declspec(dllimport) type
+#define AP_LUA_DECLARE_DATA __declspec(dllimport)
+#endif
+
+
+#include "lua_request.h"
+#include "lua_vmprep.h"
+
+typedef enum {
+ AP_LUA_INHERIT_UNSET = -1,
+ AP_LUA_INHERIT_NONE = 0,
+ AP_LUA_INHERIT_PARENT_FIRST = 1,
+ AP_LUA_INHERIT_PARENT_LAST = 2
+} ap_lua_inherit_t;
+
+/**
+ * make a userdata out of a C pointer, and vice versa
+ * instead of using lightuserdata
+ */
+#ifndef lua_boxpointer
+#define lua_boxpointer(L,u) (*(void **)(lua_newuserdata(L, sizeof(void *))) = (u))
+#define lua_unboxpointer(L,i) (*(void **)(lua_touserdata(L, i)))
+#endif
+
+void ap_lua_rstack_dump(lua_State *L, request_rec *r, const char *msg);
+
+typedef struct
+{
+ apr_array_header_t *package_paths;
+ apr_array_header_t *package_cpaths;
+
+ /**
+ * mapped handlers/filters
+ */
+ apr_array_header_t *mapped_handlers;
+ apr_array_header_t *mapped_filters;
+
+ apr_pool_t *pool;
+
+ /**
+ * AP_LUA_SCOPE_ONCE | AP_LUA_SCOPE_REQUEST | AP_LUA_SCOPE_CONN | AP_LUA_SCOPE_SERVER
+ */
+ unsigned int vm_scope;
+ unsigned int vm_min;
+ unsigned int vm_max;
+
+ /* info for the hook harnesses */
+ apr_hash_t *hooks; /* <wombat_hook_info> */
+
+ /* the actual directory being configured */
+ const char *dir;
+
+ /* Whether Lua scripts in a sub-dir are run before parents */
+ ap_lua_inherit_t inherit;
+
+ /**
+ * AP_LUA_CACHE_NEVER | AP_LUA_CACHE_STAT | AP_LUA_CACHE_FOREVER
+ */
+ unsigned int codecache;
+
+} ap_lua_dir_cfg;
+
+typedef struct
+{
+ /* value of the LuaRoot directive */
+ const char *root_path;
+} ap_lua_server_cfg;
+
+typedef struct
+{
+ const char *function_name;
+ ap_lua_vm_spec *spec;
+} mapped_request_details;
+
+typedef struct
+{
+ mapped_request_details *mapped_request_details;
+ apr_hash_t *request_scoped_vms;
+} ap_lua_request_cfg;
+
+typedef struct
+{
+ lua_State *L;
+ const char *function;
+} ap_lua_filter_ctx;
+
+extern module AP_MODULE_DECLARE_DATA lua_module;
+
+APR_DECLARE_EXTERNAL_HOOK(ap_lua, AP_LUA, int, lua_open,
+ (lua_State *L, apr_pool_t *p))
+
+APR_DECLARE_EXTERNAL_HOOK(ap_lua, AP_LUA, int, lua_request,
+ (lua_State *L, request_rec *r))
+
+const char *ap_lua_ssl_val(apr_pool_t *p, server_rec *s, conn_rec *c,
+ request_rec *r, const char *var);
+
+int ap_lua_ssl_is_https(conn_rec *c);
+
+#endif /* !_MOD_LUA_H_ */
diff --git a/modules/lua/mod_lua.mak b/modules/lua/mod_lua.mak
new file mode 100644
index 0000000..114d6bd
--- /dev/null
+++ b/modules/lua/mod_lua.mak
@@ -0,0 +1,407 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_lua.dsp
+!IF "$(CFG)" == ""
+CFG=mod_lua - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_lua - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_lua - Win32 Release" && "$(CFG)" != "mod_lua - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_lua.mak" CFG="mod_lua - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_lua - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_lua - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_lua - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_lua.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_lua.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\lua_apr.obj"
+ -@erase "$(INTDIR)\lua_config.obj"
+ -@erase "$(INTDIR)\lua_dbd.obj"
+ -@erase "$(INTDIR)\lua_passwd.obj"
+ -@erase "$(INTDIR)\lua_request.obj"
+ -@erase "$(INTDIR)\lua_vmprep.obj"
+ -@erase "$(INTDIR)\mod_lua.obj"
+ -@erase "$(INTDIR)\mod_lua.res"
+ -@erase "$(INTDIR)\mod_lua_src.idb"
+ -@erase "$(INTDIR)\mod_lua_src.pdb"
+ -@erase "$(OUTDIR)\mod_lua.exp"
+ -@erase "$(OUTDIR)\mod_lua.lib"
+ -@erase "$(OUTDIR)\mod_lua.pdb"
+ -@erase "$(OUTDIR)\mod_lua.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/lua/src" /I "../ssl" /I "../database" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "AP_LUA_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_lua_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_lua.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_lua.so" /d LONG_NAME="lua_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_lua.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib lua51.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_lua.pdb" /debug /out:"$(OUTDIR)\mod_lua.so" /implib:"$(OUTDIR)\mod_lua.lib" /libpath:"../../srclib/lua/src" /base:@..\..\os\win32\BaseAddr.ref,mod_lua.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\lua_apr.obj" \
+ "$(INTDIR)\lua_config.obj" \
+ "$(INTDIR)\lua_passwd.obj" \
+ "$(INTDIR)\lua_request.obj" \
+ "$(INTDIR)\lua_vmprep.obj" \
+ "$(INTDIR)\mod_lua.obj" \
+ "$(INTDIR)\lua_dbd.obj" \
+ "$(INTDIR)\mod_lua.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_lua.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_lua.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_lua.so"
+ if exist .\Release\mod_lua.so.manifest mt.exe -manifest .\Release\mod_lua.so.manifest -outputresource:.\Release\mod_lua.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_lua - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_lua.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_lua.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\lua_apr.obj"
+ -@erase "$(INTDIR)\lua_config.obj"
+ -@erase "$(INTDIR)\lua_dbd.obj"
+ -@erase "$(INTDIR)\lua_passwd.obj"
+ -@erase "$(INTDIR)\lua_request.obj"
+ -@erase "$(INTDIR)\lua_vmprep.obj"
+ -@erase "$(INTDIR)\mod_lua.obj"
+ -@erase "$(INTDIR)\mod_lua.res"
+ -@erase "$(INTDIR)\mod_lua_src.idb"
+ -@erase "$(INTDIR)\mod_lua_src.pdb"
+ -@erase "$(OUTDIR)\mod_lua.exp"
+ -@erase "$(OUTDIR)\mod_lua.lib"
+ -@erase "$(OUTDIR)\mod_lua.pdb"
+ -@erase "$(OUTDIR)\mod_lua.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/lua/src" /I "../ssl" /I "../database" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "AP_LUA_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_lua_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_lua.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_lua.so" /d LONG_NAME="lua_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_lua.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib lua51.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_lua.pdb" /debug /out:"$(OUTDIR)\mod_lua.so" /implib:"$(OUTDIR)\mod_lua.lib" /libpath:"../../srclib/lua/src" /base:@..\..\os\win32\BaseAddr.ref,mod_lua.so
+LINK32_OBJS= \
+ "$(INTDIR)\lua_apr.obj" \
+ "$(INTDIR)\lua_config.obj" \
+ "$(INTDIR)\lua_passwd.obj" \
+ "$(INTDIR)\lua_request.obj" \
+ "$(INTDIR)\lua_vmprep.obj" \
+ "$(INTDIR)\mod_lua.obj" \
+ "$(INTDIR)\lua_dbd.obj" \
+ "$(INTDIR)\mod_lua.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_lua.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_lua.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_lua.so"
+ if exist .\Debug\mod_lua.so.manifest mt.exe -manifest .\Debug\mod_lua.so.manifest -outputresource:.\Debug\mod_lua.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_lua.dep")
+!INCLUDE "mod_lua.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_lua.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_lua - Win32 Release" || "$(CFG)" == "mod_lua - Win32 Debug"
+
+!IF "$(CFG)" == "mod_lua - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\lua"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\lua"
+
+!ELSEIF "$(CFG)" == "mod_lua - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\lua"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\lua"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_lua - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\lua"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\lua"
+
+!ELSEIF "$(CFG)" == "mod_lua - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\lua"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\lua"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_lua - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\lua"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\lua"
+
+!ELSEIF "$(CFG)" == "mod_lua - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\lua"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\lua"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_lua - Win32 Release"
+
+
+"$(INTDIR)\mod_lua.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_lua.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_lua.so" /d LONG_NAME="lua_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_lua - Win32 Debug"
+
+
+"$(INTDIR)\mod_lua.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_lua.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_lua.so" /d LONG_NAME="lua_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\lua_apr.c
+
+"$(INTDIR)\lua_apr.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\lua_config.c
+
+"$(INTDIR)\lua_config.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\lua_dbd.c
+
+"$(INTDIR)\lua_dbd.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\lua_passwd.c
+
+"$(INTDIR)\lua_passwd.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\lua_request.c
+
+"$(INTDIR)\lua_request.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\lua_vmprep.c
+
+"$(INTDIR)\lua_vmprep.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\mod_lua.c
+
+"$(INTDIR)\mod_lua.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/lua/test/helpers.lua b/modules/lua/test/helpers.lua
new file mode 100644
index 0000000..79bd269
--- /dev/null
+++ b/modules/lua/test/helpers.lua
@@ -0,0 +1,36 @@
+module("helpers", package.seeall)
+
+local io = require("io")
+local http = require("socket.http")
+local string = require("string")
+
+base_url = "http://localhost"
+
+function get(uri)
+ return http.request(base_url .. uri)
+end
+
+function post(uri, body)
+ local function do_it(body)
+ local flat
+ if (type(body) == "table") then
+ i = 1
+ for k, v in pairs(body) do
+ if i == 1 then
+ flat = k .. "=" ..v
+ else
+ flat = flat .. "&" .. k .. "=" .. v
+ end
+ i = i + 1
+ end
+ else
+ flat = body;
+ end
+ return http.request(base_url .. uri, flat)
+ end
+ if body then
+ return do_it(body)
+ else
+ return do_it
+ end
+end \ No newline at end of file
diff --git a/modules/lua/test/htdocs/config_tests.lua b/modules/lua/test/htdocs/config_tests.lua
new file mode 100644
index 0000000..698bedf
--- /dev/null
+++ b/modules/lua/test/htdocs/config_tests.lua
@@ -0,0 +1,37 @@
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements. See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License. You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+require 'string'
+
+local count = 0
+
+function handle(r)
+ r:puts("success in handle " .. count)
+end
+
+function handle_server_vm(r)
+ r:puts("hello from server scope " .. count)
+ count = count + 1
+end
+
+function handle_request_vm(r)
+ r:puts("hello from request scope " .. count)
+ count = count + 1
+end
+
+function handle_conn_vm(r)
+ r:puts("hello from request scope " .. count)
+ count = count + 1
+end \ No newline at end of file
diff --git a/modules/lua/test/htdocs/filters.lua b/modules/lua/test/htdocs/filters.lua
new file mode 100644
index 0000000..cad0ca8
--- /dev/null
+++ b/modules/lua/test/htdocs/filters.lua
@@ -0,0 +1,7 @@
+
+local s = require 'string'
+
+function handle_simple(r)
+ -- r:addoutputfilter("wombathood")
+ r:puts("added wombathood")
+end \ No newline at end of file
diff --git a/modules/lua/test/htdocs/find_me.txt b/modules/lua/test/htdocs/find_me.txt
new file mode 100644
index 0000000..cb96e9e
--- /dev/null
+++ b/modules/lua/test/htdocs/find_me.txt
@@ -0,0 +1 @@
+please find me \ No newline at end of file
diff --git a/modules/lua/test/htdocs/headers.lua b/modules/lua/test/htdocs/headers.lua
new file mode 100644
index 0000000..35938ea
--- /dev/null
+++ b/modules/lua/test/htdocs/headers.lua
@@ -0,0 +1,6 @@
+function handle(r)
+ local host = r.headers_in['host']
+ r:debug(host)
+ r:puts(host)
+ r.headers_out['wombat'] = 'lua'
+end
diff --git a/modules/lua/test/htdocs/hooks.lua b/modules/lua/test/htdocs/hooks.lua
new file mode 100644
index 0000000..b8a8248
--- /dev/null
+++ b/modules/lua/test/htdocs/hooks.lua
@@ -0,0 +1,29 @@
+require 'string'
+require 'apache2'
+
+function translate_name(r)
+ if r.uri == "/translate-name" then
+ r.uri = "/find_me.txt"
+ return apache2.DECLINED
+ end
+ return apache2.DECLINED
+end
+
+function translate_name2(r)
+ if r.uri == "/translate-name2" then
+ r.uri = "/find_me.txt"
+ return apache2.DECLINED
+ end
+ return apache2.DECLINED
+end
+
+function fixups_test(r)
+ -- r:err("KABAZ")
+ if r.uri == "/test_fixupstest" then
+ -- r:err("KABIZ")
+ r.status = 201
+ return apache2.OK
+ end
+ -- r:err("ZIBAK")
+ return apache2.DECLINED
+end \ No newline at end of file
diff --git a/modules/lua/test/htdocs/other.lua b/modules/lua/test/htdocs/other.lua
new file mode 100644
index 0000000..90c6ed2
--- /dev/null
+++ b/modules/lua/test/htdocs/other.lua
@@ -0,0 +1,21 @@
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements. See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License. You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+module("other")
+
+function doit(r)
+ r:debug("doing it...")
+ r:puts("Do It!\n")
+end
diff --git a/modules/lua/test/htdocs/simple.lua b/modules/lua/test/htdocs/simple.lua
new file mode 100644
index 0000000..a3f8861
--- /dev/null
+++ b/modules/lua/test/htdocs/simple.lua
@@ -0,0 +1,4 @@
+function handle(r)
+ r.content_type = "text/plain"
+ r:puts("Hi there!")
+end
diff --git a/modules/lua/test/htdocs/test.lua b/modules/lua/test/htdocs/test.lua
new file mode 100644
index 0000000..c0b96ab
--- /dev/null
+++ b/modules/lua/test/htdocs/test.lua
@@ -0,0 +1,129 @@
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements. See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License. You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+require 'string'
+
+function print_args(r, simple, complex)
+ local s = " %s: %s\n"
+ r:puts(" simple:\n")
+ for k, v in pairs(simple) do
+ r:puts(s:format(k, v))
+ end
+
+ s = " %s: "
+ r:puts(" complex:\n")
+ for k, ary in pairs(complex) do
+ r:puts(s:format(k))
+ for i=1, #ary do
+ r:puts(ary[i])
+ if i < #ary then r:puts(", ") end
+ end
+ r:puts("\n")
+ end
+end
+
+function debug_stuff(r)
+ r:debug("This is a debug log message")
+ -- r:info("This is an info log message")
+ -- r:notice("This is an notice log message")
+ -- r:warn("This is an warn log message")
+ -- r:err("This is an err log message")
+ -- r:alert("This is an alert log message")
+ -- r:crit("This is an crit log message")
+ -- r:emerg("This is an emerg log message")
+end
+
+function handle(r)
+ r:puts("hello Lua world\n")
+ r:puts("Query args:\n")
+
+ print_args(r, r:parseargs());
+
+ debug_stuff(r)
+
+ r:puts("HTTP Method:\n " .. r.method .. "\n")
+
+ if r.method == 'POST' then
+ print_args(r, r:parsebody())
+ end
+
+ require("other")
+ r:puts("loaded relative to script:\n ")
+ other.doit(r)
+
+ r:puts("loaded from LuaPackagePath:\n")
+ require("kangaroo");
+ kangaroo.hop(r);
+end
+
+function handle_foo(r)
+ r:puts("Handler FOO!\n")
+ r.status = 201
+ r:debug("set status to 201")
+end
+
+
+function handle_attributes(r)
+ local function pf(name)
+ r:puts(("%s: %s\n"):format(name, tostring(r[name])))
+ end
+
+ pf("status")
+ r.status = 201
+ pf("status")
+ r:puts("\n")
+
+ pf("content_type")
+ r.content_type = "text/plain?charset=ascii"
+ pf("content_type")
+ r:puts("\n")
+
+ pf("method")
+ pf("protocol")
+ pf("assbackwards")
+ pf("the_request")
+ pf("range")
+ pf("content_encoding")
+ pf("user")
+ pf("unparsed_uri")
+ pf("ap_auth_type")
+ pf("uri")
+ pf("filename")
+ pf("canonical_filename")
+ pf("path_info")
+ pf("args")
+
+ r:puts("\n")
+end
+
+function test_headers(r)
+ r:puts("test getting and setting headers here\n")
+end
+
+function handle_quietly(r)
+ r:puts("hello!")
+end
+
+function handle_regex(r)
+ r:puts("matched in handle_regex")
+end
+
+function handle_serverversion(r)
+ r:puts(apache2.version)
+end
+
+function handle_fixupstest(r)
+ r:puts("status is " .. r.status)
+end \ No newline at end of file
diff --git a/modules/lua/test/lib/kangaroo.lua b/modules/lua/test/lib/kangaroo.lua
new file mode 100644
index 0000000..00ba3e5
--- /dev/null
+++ b/modules/lua/test/lib/kangaroo.lua
@@ -0,0 +1,19 @@
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements. See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License. You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+module("kangaroo")
+
+function hop(r)
+ r:puts(" hop hop!\n")
+end
diff --git a/modules/lua/test/moonunit.lua b/modules/lua/test/moonunit.lua
new file mode 100644
index 0000000..8537a1e
--- /dev/null
+++ b/modules/lua/test/moonunit.lua
@@ -0,0 +1,52 @@
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements. See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License. You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+module("moonunit", package.seeall)
+
+TestCase = {}
+
+function TestCase:new(it)
+ it = it or {}
+ setmetatable(it, self)
+ self.__index = self
+ return it
+end
+
+function TestCase:run(args)
+ args = args or arg
+ local function run_test(t, name)
+ local status, err = pcall(t, self)
+ if status then
+ print(("%-39s \27[32mpass\27[39m"):format("[" .. name .. "]"))
+ else
+ print(("%-39s \27[31mFAIL\27[39m %s"):format("[" .. name .. "]", err))
+ end
+ end
+
+ if (args and #args > 0) then
+ for _, v in ipairs(args) do
+ if type(self[v]) == "function" then
+ run_test(self[v], v)
+ else
+ print(("%-39s FAIL %s"):format("[" .. v .. "]",
+ "'" .. v .. "' doesn't appear to be a test function"))
+ end
+ end
+ else
+ for k, v in pairs(self) do
+ run_test(v, k)
+ end
+ end
+end
diff --git a/modules/lua/test/test.lua b/modules/lua/test/test.lua
new file mode 100755
index 0000000..25da4d9
--- /dev/null
+++ b/modules/lua/test/test.lua
@@ -0,0 +1,126 @@
+#!/usr/bin/env lua
+
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements. See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License. You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+local mu = require "moonunit"
+local http = require "helpers"
+
+http.base_url = "http://localhost:8008"
+
+local test = mu.TestCase:new{}
+
+function test:document_root()
+ local b, c = http.get "/document_root.lua"
+ assert(200 == c, "expected status code 200, got " .. c)
+ assert(b:find("test"), "test not found in document root")
+end
+
+function test:basic_get()
+ local b, c = http.get "/basic"
+ assert(200 == c, "expected status code 200, got " .. c)
+ assert(b:find("hello Lua world"), "'hello Lua world' not found in response")
+end
+
+function test:quietly()
+ local b, c = http.get "/test_quietly"
+ assert(200 == c, "unexpected response code " .. c)
+ assert(b == 'hello!', "unexpected response body [" .. b .. "]")
+end
+
+function test.basic_post()
+ local b, c = http.post "/basic" "hello=7&hello=1"
+ assert(200 == c, "expected status code 200, got " .. c)
+ assert(b:find("complex:%s+hello: 7, 1\n"), "didn't find complex post parsing")
+ assert(b:find("simple:%s+hello: 7\n"), "didn't find simple post parsing")
+end
+
+function test.basic_post_alt()
+ local b, c = http.post("/test_foo", "hello=7&hello=1")
+ assert(201 == c, "expected status code 200, got " .. c)
+ assert(b:find("Handler FOO!"), "unexpected output!")
+end
+
+function test.post_with_table()
+ local b, c = http.post "/basic" { hello = "7" }
+ assert(200 == c, "expected status code 200, got " .. c)
+ assert(b:find("hello: 7"), "didn't get expected post data [" .. b .."]")
+
+ b, c = http.post "/basic" { hello = "7", goodbye = "8" }
+
+ assert(200 == c, "expected status code 200, got " .. c)
+ assert(b:find("hello: 7"), "didn't get expected post data [" .. b .."]")
+ assert(b:find("goodbye: 8"), "didn't get expected post data [" .. b .."]")
+end
+
+function test:simple_filter()
+ local b, c = http.get "/filter/simple"
+ assert(200 == c, "expected status code 200, got " .. c)
+end
+
+function test:request_attributes()
+ local r, c = http.get "/test_attributes?yes=no"
+ assert(201 == c, "expected status code 201, got " .. c)
+
+ assert(r:find("status: 200\nstatus: 201"), "changing status code failed")
+ assert(r:find("method: GET"), "method wasn't reported correctly")
+ assert(r:find("protocol: HTTP/1.1"), "protocol reported incorrectly")
+ assert(r:find("assbackwards: false"), "assbackwards reported incorrectly")
+ assert(r:find("args: yes=no"), "args not reported correctly")
+end
+
+function test:map_regex()
+ local r, c = http.get "/test_regex"
+ assert(200 == c, "expected status code 200, got " .. c)
+ assert(r:find("matched in handle_regex"), "didn't find 'matched in handle_regex'")
+end
+
+function test:map_regex2()
+ local r, c = http.get "/test_regex?a=8"
+ assert(200 == c, "expected status code 200, got " .. c)
+ assert(r:find("matched in handle_regex"), "didn't find 'matched in handle_regex'")
+end
+
+function test:translate_name_hook()
+ local r, c = http.get "/translate-name"
+ assert(200 == c, "expected 200 got " .. c)
+ assert(r:find("please find me"), "didn't get expected static file :-(, instead got " .. r)
+end
+
+function test:translate_name_hook2()
+ local r, c = http.get "/translate-name2"
+ assert(200 == c, "expected 200 got " .. c)
+ assert(r:find("please find me"), "didn't get expected static file :-(, instead got " .. r)
+end
+
+function test:server_version()
+ local r, c = http.get "/test_serverversion"
+ assert(200 == c)
+ assert(r:find("Apache/2"), "version isn't Apache/2, but is " .. r)
+end
+
+function test:fixups_hook()
+ local r, c = http.get "/test_fixupstest"
+ assert(201 == c, "incorrect status code returned, expected 201 got " .. c)
+ assert(r:find("status is 201"), "handler sees incorrect status")
+end
+
+function test:simple()
+ local r, c = http.get "/simple.lua"
+ assert(200 == c, "incorrect status code returned, expected 200 got " .. c)
+ assert(r:find("Hi"), "Didn't find 'Hi'")
+end
+
+test:run()
diff --git a/modules/lua/test/test_httpd.conf b/modules/lua/test/test_httpd.conf
new file mode 100644
index 0000000..27e3a50
--- /dev/null
+++ b/modules/lua/test/test_httpd.conf
@@ -0,0 +1,31 @@
+# Customize these two values for your Apache2 install
+ServerRoot "/Users/brianm/.opt/httpd-2.2.3-worker-for-lua"
+DocumentRoot "/Users/brianm/src/wombat/test/htdocs"
+
+# Customize this value to point to the top of mod_wombat's test dir
+LuaRoot /Users/brianm/src/wombat/test
+
+Listen 8000
+
+LoadModule lua_module modules/mod_lua.so
+
+AddHandler lua-script .lua
+
+#LuaConfig httpd_config.lua configure
+
+LuaMapHandler /basic /Users/brianm/src/wombat/test/htdocs/test.lua
+LuaMapHandler /filter/simple /Users/brianm/src/wombat/test/htdocs/filters.lua handle_simple
+LuaMapHandler ^/(\w+)_(\w+)$ /Users/brianm/src/wombat/test/htdocs/$1.lua handle_$2
+
+LuaHookTranslateName htdocs/hooks.lua translate_name
+LuaHookTranslateName htdocs/hooks.lua translate_name2
+
+LuaHookFixups htdocs/hooks.lua fixups_test
+
+LuaPackagePath lib/?.lua
+
+# stat | forever | never
+LuaCodeCache stat
+
+ErrorLog logs/error_log
+LogLevel debug
diff --git a/modules/mappers/.indent.pro b/modules/mappers/.indent.pro
new file mode 100644
index 0000000..a9fbe9f
--- /dev/null
+++ b/modules/mappers/.indent.pro
@@ -0,0 +1,54 @@
+-i4 -npsl -di0 -br -nce -d0 -cli0 -npcs -nfc1
+-TBUFF
+-TFILE
+-TTRANS
+-TUINT4
+-T_trans
+-Tallow_options_t
+-Tapache_sfio
+-Tarray_header
+-Tbool_int
+-Tbuf_area
+-Tbuff_struct
+-Tbuffy
+-Tcmd_how
+-Tcmd_parms
+-Tcommand_rec
+-Tcommand_struct
+-Tconn_rec
+-Tcore_dir_config
+-Tcore_server_config
+-Tdir_maker_func
+-Tevent
+-Tglobals_s
+-Thandler_func
+-Thandler_rec
+-Tjoblist_s
+-Tlisten_rec
+-Tmerger_func
+-Tmode_t
+-Tmodule
+-Tmodule_struct
+-Tmutex
+-Tn_long
+-Tother_child_rec
+-Toverrides_t
+-Tparent_score
+-Tpid_t
+-Tpiped_log
+-Tpool
+-Trequest_rec
+-Trequire_line
+-Trlim_t
+-Tscoreboard
+-Tsemaphore
+-Tserver_addr_rec
+-Tserver_rec
+-Tserver_rec_chain
+-Tshort_score
+-Ttable
+-Ttable_entry
+-Tthread
+-Tu_wide_int
+-Tvtime_t
+-Twide_int
diff --git a/modules/mappers/Makefile.in b/modules/mappers/Makefile.in
new file mode 100644
index 0000000..167b343
--- /dev/null
+++ b/modules/mappers/Makefile.in
@@ -0,0 +1,3 @@
+
+include $(top_srcdir)/build/special.mk
+
diff --git a/modules/mappers/NWGNUactions b/modules/mappers/NWGNUactions
new file mode 100644
index 0000000..3a94773
--- /dev/null
+++ b/modules/mappers/NWGNUactions
@@ -0,0 +1,248 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = actions
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Actions Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Actions Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/actions.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_actions.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ actions_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/mappers/NWGNUimagemap b/modules/mappers/NWGNUimagemap
new file mode 100644
index 0000000..7f66086
--- /dev/null
+++ b/modules/mappers/NWGNUimagemap
@@ -0,0 +1,249 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/modules/http \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = imagemap
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Image Map Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Image Map Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/imagemap.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_imagemap.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ imagemap_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/mappers/NWGNUmakefile b/modules/mappers/NWGNUmakefile
new file mode 100644
index 0000000..d7abda5
--- /dev/null
+++ b/modules/mappers/NWGNUmakefile
@@ -0,0 +1,250 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME =
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION =
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME =
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE =
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/actions.nlm \
+ $(OBJDIR)/imagemap.nlm \
+ $(OBJDIR)/rewrite.nlm \
+ $(OBJDIR)/speling.nlm \
+ $(OBJDIR)/userdir.nlm \
+ $(OBJDIR)/vhost.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/mappers/NWGNUrewrite b/modules/mappers/NWGNUrewrite
new file mode 100644
index 0000000..d16dd8e
--- /dev/null
+++ b/modules/mappers/NWGNUrewrite
@@ -0,0 +1,250 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/modules/database \
+ $(AP_WORK)/modules/ssl \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = rewrite
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Rewrite Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Rewrite Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/rewrite.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_rewrite.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ rewrite_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/mappers/NWGNUspeling b/modules/mappers/NWGNUspeling
new file mode 100644
index 0000000..6982988
--- /dev/null
+++ b/modules/mappers/NWGNUspeling
@@ -0,0 +1,248 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = speling
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Speling Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Speling Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/speling.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_speling.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ speling_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/mappers/NWGNUuserdir b/modules/mappers/NWGNUuserdir
new file mode 100644
index 0000000..b0fc7a0
--- /dev/null
+++ b/modules/mappers/NWGNUuserdir
@@ -0,0 +1,249 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/modules/http \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = userdir
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) User Dir Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = UserDir Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/userdir.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_userdir.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ userdir_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/mappers/NWGNUvhost b/modules/mappers/NWGNUvhost
new file mode 100644
index 0000000..d3b655c
--- /dev/null
+++ b/modules/mappers/NWGNUvhost
@@ -0,0 +1,249 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/modules/http \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = vhost
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Vhost Alias Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Vhost Alias Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/vhost.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_vhost_alias.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ vhost_alias_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/mappers/config9.m4 b/modules/mappers/config9.m4
new file mode 100644
index 0000000..55a97ab
--- /dev/null
+++ b/modules/mappers/config9.m4
@@ -0,0 +1,19 @@
+dnl modules enabled in this directory by default
+
+dnl APACHE_MODULE(name, helptext[, objects[, structname[, default[, config]]]])
+
+APACHE_MODPATH_INIT(mappers)
+
+APACHE_MODULE(vhost_alias, mass virtual hosting module, , , most)
+APACHE_MODULE(negotiation, content negotiation, , , most)
+APACHE_MODULE(dir, directory request handling, , , yes)
+APACHE_MODULE(imagemap, server-side imagemaps, , , no)
+APACHE_MODULE(actions, Action triggering on requests, , , most)
+APACHE_MODULE(speling, correct common URL misspellings, , , most)
+APACHE_MODULE(userdir, mapping of requests to user-specific directories, , , most)
+APACHE_MODULE(alias, mapping of requests to different filesystem parts, , , yes)
+APACHE_MODULE(rewrite, rule based URL manipulation, , , most)
+
+APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/$modpath_current])
+
+APACHE_MODPATH_FINISH
diff --git a/modules/mappers/mod_actions.c b/modules/mappers/mod_actions.c
new file mode 100644
index 0000000..ac9c3b7
--- /dev/null
+++ b/modules/mappers/mod_actions.c
@@ -0,0 +1,230 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * mod_actions.c: executes scripts based on MIME type or HTTP method
+ *
+ * by Alexei Kosut; based on mod_cgi.c, mod_mime.c and mod_includes.c,
+ * adapted by rst from original NCSA code by Rob McCool
+ *
+ * Usage instructions:
+ *
+ * Action mime/type /cgi-bin/script
+ *
+ * will activate /cgi-bin/script when a file of content type mime/type is
+ * requested. It sends the URL and file path of the requested document using
+ * the standard CGI PATH_INFO and PATH_TRANSLATED environment variables.
+ *
+ * Script PUT /cgi-bin/script
+ *
+ * will activate /cgi-bin/script when a request is received with the
+ * HTTP method "PUT". The available method names are defined in httpd.h.
+ * If the method is GET, the script will only be activated if the requested
+ * URI includes query information (stuff after a ?-mark).
+ */
+
+#include "apr_strings.h"
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_request.h"
+#include "http_core.h"
+#include "http_protocol.h"
+#include "http_main.h"
+#include "http_log.h"
+#include "util_script.h"
+
+typedef struct {
+ apr_table_t *action_types; /* Added with Action... */
+ const char *scripted[METHODS]; /* Added with Script... */
+ int configured; /* True if Action or Script has been
+ * called at least once
+ */
+} action_dir_config;
+
+module AP_MODULE_DECLARE_DATA actions_module;
+
+static void *create_action_dir_config(apr_pool_t *p, char *dummy)
+{
+ action_dir_config *new =
+ (action_dir_config *) apr_pcalloc(p, sizeof(action_dir_config));
+
+ new->action_types = apr_table_make(p, 4);
+
+ return new;
+}
+
+static void *merge_action_dir_configs(apr_pool_t *p, void *basev, void *addv)
+{
+ action_dir_config *base = (action_dir_config *) basev;
+ action_dir_config *add = (action_dir_config *) addv;
+ action_dir_config *new = (action_dir_config *) apr_palloc(p,
+ sizeof(action_dir_config));
+ int i;
+
+ new->action_types = apr_table_overlay(p, add->action_types,
+ base->action_types);
+
+ for (i = 0; i < METHODS; ++i) {
+ new->scripted[i] = add->scripted[i] ? add->scripted[i]
+ : base->scripted[i];
+ }
+
+ new->configured = (base->configured || add->configured);
+ return new;
+}
+
+static const char *add_action(cmd_parms *cmd, void *m_v,
+ const char *type, const char *script,
+ const char *option)
+{
+ action_dir_config *m = (action_dir_config *)m_v;
+
+ if (option && strcasecmp(option, "virtual")) {
+ return apr_pstrcat(cmd->pool,
+ "unrecognized option '", option, "'", NULL);
+ }
+
+ apr_table_setn(m->action_types, type,
+ apr_pstrcat(cmd->pool, option ? "1" : "0", script, NULL));
+ m->configured = 1;
+
+ return NULL;
+}
+
+static const char *set_script(cmd_parms *cmd, void *m_v,
+ const char *method, const char *script)
+{
+ action_dir_config *m = (action_dir_config *)m_v;
+ int methnum;
+ if (cmd->pool == cmd->temp_pool) {
+ /* In .htaccess, we can't globally register new methods. */
+ methnum = ap_method_number_of(method);
+ }
+ else {
+ /* ap_method_register recognizes already registered methods,
+ * so don't bother to check its previous existence explicitly.
+ */
+ methnum = ap_method_register(cmd->pool, method);
+ }
+
+ if (methnum == M_TRACE) {
+ return "TRACE not allowed for Script";
+ }
+ else if (methnum == M_INVALID) {
+ return apr_pstrcat(cmd->pool, "Could not register method '", method,
+ "' for Script", NULL);
+ }
+
+ m->scripted[methnum] = script;
+ m->configured = 1;
+
+ return NULL;
+}
+
+static const command_rec action_cmds[] =
+{
+ AP_INIT_TAKE23("Action", add_action, NULL, OR_FILEINFO,
+ "a media type followed by a script name"),
+ AP_INIT_TAKE2("Script", set_script, NULL, ACCESS_CONF | RSRC_CONF,
+ "a method followed by a script name"),
+ {NULL}
+};
+
+static int action_handler(request_rec *r)
+{
+ action_dir_config *conf = (action_dir_config *)
+ ap_get_module_config(r->per_dir_config, &actions_module);
+ const char *t, *action;
+ const char *script;
+ int i;
+
+ if (!conf->configured) {
+ return DECLINED;
+ }
+
+ /* Note that this handler handles _all_ types, so handler is unchecked */
+
+ /* Set allowed stuff */
+ for (i = 0; i < METHODS; ++i) {
+ if (conf->scripted[i])
+ r->allowed |= (AP_METHOD_BIT << i);
+ }
+
+ /* First, check for the method-handling scripts */
+ if (r->method_number == M_GET) {
+ if (r->args)
+ script = conf->scripted[M_GET];
+ else
+ script = NULL;
+ }
+ else {
+ script = conf->scripted[r->method_number];
+ }
+
+ /* Check for looping, which can happen if the CGI script isn't */
+ if (script && r->prev && r->prev->prev)
+ return DECLINED;
+
+ /* Second, check for actions (which override the method scripts) */
+ action = r->handler ? r->handler :
+ ap_field_noparam(r->pool, r->content_type);
+
+ if (action && (t = apr_table_get(conf->action_types, action))) {
+ int virtual = (*t++ == '0' ? 0 : 1);
+ if (!virtual && r->finfo.filetype == APR_NOFILE) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00652)
+ "File does not exist: %s", r->filename);
+ return HTTP_NOT_FOUND;
+ }
+
+ script = t;
+ /* propagate the handler name to the script
+ * (will be REDIRECT_HANDLER there)
+ */
+ apr_table_setn(r->subprocess_env, "HANDLER", action);
+ if (virtual) {
+ apr_table_setn(r->notes, "virtual_script", "1");
+ }
+ }
+
+ if (script == NULL)
+ return DECLINED;
+
+ ap_internal_redirect_handler(apr_pstrcat(r->pool, script,
+ ap_escape_uri(r->pool, r->uri),
+ r->args ? "?" : NULL,
+ r->args, NULL), r);
+ return OK;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_hook_handler(action_handler,NULL,NULL,APR_HOOK_LAST);
+}
+
+AP_DECLARE_MODULE(actions) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_action_dir_config, /* dir config creater */
+ merge_action_dir_configs, /* dir merger --- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ action_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/mappers/mod_actions.dep b/modules/mappers/mod_actions.dep
new file mode 100644
index 0000000..9bfdaf1
--- /dev/null
+++ b/modules/mappers/mod_actions.dep
@@ -0,0 +1,58 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_actions.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_actions.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/mappers/mod_actions.dsp b/modules/mappers/mod_actions.dsp
new file mode 100644
index 0000000..736937c
--- /dev/null
+++ b/modules/mappers/mod_actions.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_actions" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_actions - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_actions.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_actions.mak" CFG="mod_actions - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_actions - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_actions - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_actions - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_actions_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_actions.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_actions.so" /d LONG_NAME="actions_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_actions.so" /base:@..\..\os\win32\BaseAddr.ref,mod_actions.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_actions.so" /base:@..\..\os\win32\BaseAddr.ref,mod_actions.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_actions.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_actions - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_actions_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_actions.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_actions.so" /d LONG_NAME="actions_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_actions.so" /base:@..\..\os\win32\BaseAddr.ref,mod_actions.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_actions.so" /base:@..\..\os\win32\BaseAddr.ref,mod_actions.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_actions.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_actions - Win32 Release"
+# Name "mod_actions - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_actions.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/mappers/mod_actions.exp b/modules/mappers/mod_actions.exp
new file mode 100644
index 0000000..8cc6cdb
--- /dev/null
+++ b/modules/mappers/mod_actions.exp
@@ -0,0 +1 @@
+actions_module
diff --git a/modules/mappers/mod_actions.mak b/modules/mappers/mod_actions.mak
new file mode 100644
index 0000000..28ec1e0
--- /dev/null
+++ b/modules/mappers/mod_actions.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_actions.dsp
+!IF "$(CFG)" == ""
+CFG=mod_actions - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_actions - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_actions - Win32 Release" && "$(CFG)" != "mod_actions - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_actions.mak" CFG="mod_actions - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_actions - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_actions - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_actions - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_actions.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_actions.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_actions.obj"
+ -@erase "$(INTDIR)\mod_actions.res"
+ -@erase "$(INTDIR)\mod_actions_src.idb"
+ -@erase "$(INTDIR)\mod_actions_src.pdb"
+ -@erase "$(OUTDIR)\mod_actions.exp"
+ -@erase "$(OUTDIR)\mod_actions.lib"
+ -@erase "$(OUTDIR)\mod_actions.pdb"
+ -@erase "$(OUTDIR)\mod_actions.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_actions_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_actions.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_actions.so" /d LONG_NAME="actions_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_actions.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_actions.pdb" /debug /out:"$(OUTDIR)\mod_actions.so" /implib:"$(OUTDIR)\mod_actions.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_actions.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_actions.obj" \
+ "$(INTDIR)\mod_actions.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_actions.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_actions.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_actions.so"
+ if exist .\Release\mod_actions.so.manifest mt.exe -manifest .\Release\mod_actions.so.manifest -outputresource:.\Release\mod_actions.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_actions - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_actions.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_actions.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_actions.obj"
+ -@erase "$(INTDIR)\mod_actions.res"
+ -@erase "$(INTDIR)\mod_actions_src.idb"
+ -@erase "$(INTDIR)\mod_actions_src.pdb"
+ -@erase "$(OUTDIR)\mod_actions.exp"
+ -@erase "$(OUTDIR)\mod_actions.lib"
+ -@erase "$(OUTDIR)\mod_actions.pdb"
+ -@erase "$(OUTDIR)\mod_actions.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_actions_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_actions.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_actions.so" /d LONG_NAME="actions_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_actions.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_actions.pdb" /debug /out:"$(OUTDIR)\mod_actions.so" /implib:"$(OUTDIR)\mod_actions.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_actions.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_actions.obj" \
+ "$(INTDIR)\mod_actions.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_actions.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_actions.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_actions.so"
+ if exist .\Debug\mod_actions.so.manifest mt.exe -manifest .\Debug\mod_actions.so.manifest -outputresource:.\Debug\mod_actions.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_actions.dep")
+!INCLUDE "mod_actions.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_actions.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_actions - Win32 Release" || "$(CFG)" == "mod_actions - Win32 Debug"
+
+!IF "$(CFG)" == "mod_actions - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\mappers"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ELSEIF "$(CFG)" == "mod_actions - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\mappers"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_actions - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\mappers"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ELSEIF "$(CFG)" == "mod_actions - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\mappers"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_actions - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\mappers"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\mappers"
+
+!ELSEIF "$(CFG)" == "mod_actions - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\mappers"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\mappers"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_actions - Win32 Release"
+
+
+"$(INTDIR)\mod_actions.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_actions.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_actions.so" /d LONG_NAME="actions_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_actions - Win32 Debug"
+
+
+"$(INTDIR)\mod_actions.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_actions.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_actions.so" /d LONG_NAME="actions_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_actions.c
+
+"$(INTDIR)\mod_actions.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/mappers/mod_alias.c b/modules/mappers/mod_alias.c
new file mode 100644
index 0000000..5ff937b
--- /dev/null
+++ b/modules/mappers/mod_alias.c
@@ -0,0 +1,727 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * http_alias.c: Stuff for dealing with directory aliases
+ *
+ * Original by Rob McCool, rewritten in succession by David Robinson
+ * and rst.
+ *
+ */
+
+#include "apr_strings.h"
+#include "apr_lib.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_core.h"
+#include "http_config.h"
+#include "http_request.h"
+#include "http_log.h"
+#include "ap_expr.h"
+
+
+typedef struct {
+ const char *real;
+ const char *fake;
+ char *handler;
+ ap_regex_t *regexp;
+ int redir_status; /* 301, 302, 303, 410, etc */
+} alias_entry;
+
+typedef struct {
+ apr_array_header_t *aliases;
+ apr_array_header_t *redirects;
+} alias_server_conf;
+
+typedef struct {
+ unsigned int alias_set:1;
+ unsigned int redirect_set:1;
+ apr_array_header_t *redirects;
+ const ap_expr_info_t *alias;
+ char *handler;
+ const ap_expr_info_t *redirect;
+ int redirect_status; /* 301, 302, 303, 410, etc */
+} alias_dir_conf;
+
+module AP_MODULE_DECLARE_DATA alias_module;
+
+static char magic_error_value;
+#define PREGSUB_ERROR (&magic_error_value)
+
+static void *create_alias_config(apr_pool_t *p, server_rec *s)
+{
+ alias_server_conf *a =
+ (alias_server_conf *) apr_pcalloc(p, sizeof(alias_server_conf));
+
+ a->aliases = apr_array_make(p, 20, sizeof(alias_entry));
+ a->redirects = apr_array_make(p, 20, sizeof(alias_entry));
+ return a;
+}
+
+static void *create_alias_dir_config(apr_pool_t *p, char *d)
+{
+ alias_dir_conf *a =
+ (alias_dir_conf *) apr_pcalloc(p, sizeof(alias_dir_conf));
+ a->redirects = apr_array_make(p, 2, sizeof(alias_entry));
+ return a;
+}
+
+static void *merge_alias_config(apr_pool_t *p, void *basev, void *overridesv)
+{
+ alias_server_conf *a =
+ (alias_server_conf *) apr_pcalloc(p, sizeof(alias_server_conf));
+ alias_server_conf *base = (alias_server_conf *) basev;
+ alias_server_conf *overrides = (alias_server_conf *) overridesv;
+
+ a->aliases = apr_array_append(p, overrides->aliases, base->aliases);
+ a->redirects = apr_array_append(p, overrides->redirects, base->redirects);
+ return a;
+}
+
+static void *merge_alias_dir_config(apr_pool_t *p, void *basev, void *overridesv)
+{
+ alias_dir_conf *a =
+ (alias_dir_conf *) apr_pcalloc(p, sizeof(alias_dir_conf));
+ alias_dir_conf *base = (alias_dir_conf *) basev;
+ alias_dir_conf *overrides = (alias_dir_conf *) overridesv;
+
+ a->redirects = apr_array_append(p, overrides->redirects, base->redirects);
+
+ a->alias = (overrides->alias_set == 0) ? base->alias : overrides->alias;
+ a->handler = (overrides->alias_set == 0) ? base->handler : overrides->handler;
+ a->alias_set = overrides->alias_set || base->alias_set;
+
+ a->redirect = (overrides->redirect_set == 0) ? base->redirect : overrides->redirect;
+ a->redirect_status = (overrides->redirect_set == 0) ? base->redirect_status : overrides->redirect_status;
+ a->redirect_set = overrides->redirect_set || base->redirect_set;
+
+ return a;
+}
+
+/* need prototype for overlap check */
+static int alias_matches(const char *uri, const char *alias_fakename);
+
+static const char *add_alias_internal(cmd_parms *cmd, void *dummy,
+ const char *fake, const char *real,
+ int use_regex)
+{
+ server_rec *s = cmd->server;
+ alias_server_conf *conf = ap_get_module_config(s->module_config,
+ &alias_module);
+ alias_entry *new = apr_array_push(conf->aliases);
+ alias_entry *entries = (alias_entry *)conf->aliases->elts;
+ int i;
+
+ /* XXX: real can NOT be relative to DocumentRoot here... compat bug. */
+
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_CONTEXT);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ if (use_regex) {
+ new->regexp = ap_pregcomp(cmd->pool, fake, AP_REG_EXTENDED);
+ if (new->regexp == NULL)
+ return "Regular expression could not be compiled.";
+ new->real = real;
+ }
+ else {
+ /* XXX This may be optimized, but we must know that new->real
+ * exists. If so, we can dir merge later, trusing new->real
+ * and just canonicalizing the remainder. Not till I finish
+ * cleaning out the old ap_canonical stuff first.
+ */
+ new->real = real;
+ }
+ new->fake = fake;
+ new->handler = cmd->info;
+
+ /* check for overlapping (Script)Alias directives
+ * and throw a warning if found one
+ */
+ if (!use_regex) {
+ for (i = 0; i < conf->aliases->nelts - 1; ++i) {
+ alias_entry *alias = &entries[i];
+
+ if ( (!alias->regexp && alias_matches(fake, alias->fake) > 0)
+ || (alias->regexp && !ap_regexec(alias->regexp, fake, 0, NULL, 0))) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00671)
+ "The %s directive in %s at line %d will probably "
+ "never match because it overlaps an earlier "
+ "%sAlias%s.",
+ cmd->cmd->name, cmd->directive->filename,
+ cmd->directive->line_num,
+ alias->handler ? "Script" : "",
+ alias->regexp ? "Match" : "");
+
+ break; /* one warning per alias should be sufficient */
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static const char *add_alias(cmd_parms *cmd, void *dummy, const char *fake,
+ const char *real)
+{
+ if (real) {
+
+ return add_alias_internal(cmd, dummy, fake, real, 0);
+
+ }
+ else {
+ alias_dir_conf *dirconf = (alias_dir_conf *) dummy;
+
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_DIRECTORY|NOT_IN_FILES);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ if (!cmd->path) {
+ return "Alias must have two arguments when used globally";
+ }
+
+ dirconf->alias =
+ ap_expr_parse_cmd(cmd, fake, AP_EXPR_FLAG_STRING_RESULT,
+ &err, NULL);
+ if (err) {
+ return apr_pstrcat(cmd->temp_pool,
+ "Cannot parse alias expression '", fake, "': ", err,
+ NULL);
+ }
+
+ dirconf->handler = cmd->info;
+ dirconf->alias_set = 1;
+
+ return NULL;
+
+ }
+}
+
+static const char *add_alias_regex(cmd_parms *cmd, void *dummy,
+ const char *fake, const char *real)
+{
+ return add_alias_internal(cmd, dummy, fake, real, 1);
+}
+
+static const char *add_redirect_internal(cmd_parms *cmd,
+ alias_dir_conf *dirconf,
+ const char *arg1, const char *arg2,
+ const char *arg3, int use_regex)
+{
+ alias_entry *new;
+ server_rec *s = cmd->server;
+ alias_server_conf *serverconf = ap_get_module_config(s->module_config,
+ &alias_module);
+ int status = (int) (long) cmd->info;
+ int grokarg1 = 1;
+ ap_regex_t *regex = NULL;
+ const char *fake = arg2;
+ const char *url = arg3;
+
+ /*
+ * Logic flow:
+ * Go ahead and try to grok the 1st arg, in case it is a
+ * Redirect status. Now if we have 3 args, we expect that
+ * we were able to understand that 1st argument (it's something
+ * we expected, so if not, then we bail
+ */
+ if (!strcasecmp(arg1, "permanent"))
+ status = HTTP_MOVED_PERMANENTLY;
+ else if (!strcasecmp(arg1, "temp"))
+ status = HTTP_MOVED_TEMPORARILY;
+ else if (!strcasecmp(arg1, "seeother"))
+ status = HTTP_SEE_OTHER;
+ else if (!strcasecmp(arg1, "gone")) {
+ status = HTTP_GONE;
+ grokarg1 = -1;
+ }
+ else if (apr_isdigit(*arg1)) {
+ status = atoi(arg1);
+ if (!ap_is_HTTP_REDIRECT(status)) {
+ grokarg1 = -1;
+ }
+ }
+ else {
+ grokarg1 = 0;
+ }
+
+ if (arg3 && !grokarg1)
+ return "Redirect: invalid first argument (of three)";
+
+ /*
+ * if we have the 2nd arg and we understand the 1st one as a redirect
+ * status (3xx, but not things like 404 /robots.txt), or if we have the
+ * 1st arg but don't understand it, we use the expression syntax assuming
+ * a path from the location.
+ *
+ * if we understand the first arg but have no second arg, we are dealing
+ * with a status like "GONE" or a non-redirect status (e.g. 404, 503).
+ */
+ if (!cmd->path) {
+ /* <Location> context only for now */
+ ;
+ }
+ else if ((grokarg1 > 0 && arg2 && !arg3) || (!grokarg1 && !arg2)) {
+ const char *expr_err = NULL;
+
+ url = grokarg1 ? arg2 : arg1;
+ dirconf->redirect =
+ ap_expr_parse_cmd(cmd, url, AP_EXPR_FLAG_STRING_RESULT,
+ &expr_err, NULL);
+ if (expr_err) {
+ return apr_pstrcat(cmd->temp_pool,
+ "Cannot parse redirect expression '", url, "': ", expr_err,
+ NULL);
+ }
+
+ dirconf->redirect_status = status;
+ dirconf->redirect_set = 1;
+
+ return NULL;
+
+ }
+ else if (grokarg1 < 0 && !arg2) {
+
+ dirconf->redirect_status = status;
+ dirconf->redirect_set = 1;
+
+ return NULL;
+
+ }
+
+ /*
+ * if we don't have the 3rd arg and we didn't understand the 1st
+ * one, then assume URL-path URL. This also handles case, eg, GONE
+ * we even though we don't have a 3rd arg, we did understand the 1st
+ * one, so we don't want to re-arrange
+ */
+ if (!arg3 && !grokarg1) {
+ fake = arg1;
+ url = arg2;
+ }
+
+ if (use_regex) {
+ regex = ap_pregcomp(cmd->pool, fake, AP_REG_EXTENDED);
+ if (regex == NULL)
+ return "Regular expression could not be compiled.";
+ }
+
+ if (ap_is_HTTP_REDIRECT(status)) {
+ if (!url)
+ return "URL to redirect to is missing";
+ /* PR#35314: we can allow path components here;
+ * they get correctly resolved to full URLs.
+ */
+ if (!use_regex && !ap_is_url(url) && (url[0] != '/'))
+ return "Redirect to non-URL";
+ }
+ else {
+ if (url)
+ return "Redirect URL not valid for this status";
+ }
+
+ if (cmd->path)
+ new = apr_array_push(dirconf->redirects);
+ else
+ new = apr_array_push(serverconf->redirects);
+
+ new->fake = fake;
+ new->real = url;
+ new->regexp = regex;
+ new->redir_status = status;
+ return NULL;
+}
+
+static const char *add_redirect(cmd_parms *cmd, void *dirconf,
+ const char *arg1, const char *arg2,
+ const char *arg3)
+{
+ return add_redirect_internal(cmd, dirconf, arg1, arg2, arg3, 0);
+}
+
+static const char *add_redirect2(cmd_parms *cmd, void *dirconf,
+ const char *arg1, const char *arg2)
+{
+ return add_redirect_internal(cmd, dirconf, arg1, arg2, NULL, 0);
+}
+
+static const char *add_redirect_regex(cmd_parms *cmd, void *dirconf,
+ const char *arg1, const char *arg2,
+ const char *arg3)
+{
+ return add_redirect_internal(cmd, dirconf, arg1, arg2, arg3, 1);
+}
+
+static int alias_matches(const char *uri, const char *alias_fakename)
+{
+ const char *aliasp = alias_fakename, *urip = uri;
+
+ while (*aliasp) {
+ if (*aliasp == '/') {
+ /* any number of '/' in the alias matches any number in
+ * the supplied URI, but there must be at least one...
+ */
+ if (*urip != '/')
+ return 0;
+
+ do {
+ ++aliasp;
+ } while (*aliasp == '/');
+ do {
+ ++urip;
+ } while (*urip == '/');
+ }
+ else {
+ /* Other characters are compared literally */
+ if (*urip++ != *aliasp++)
+ return 0;
+ }
+ }
+
+ /* Check last alias path component matched all the way */
+
+ if (aliasp[-1] != '/' && *urip != '\0' && *urip != '/')
+ return 0;
+
+ /* Return number of characters from URI which matched (may be
+ * greater than length of alias, since we may have matched
+ * doubled slashes)
+ */
+
+ return urip - uri;
+}
+
+static char *try_alias(request_rec *r)
+{
+ alias_dir_conf *dirconf =
+ (alias_dir_conf *) ap_get_module_config(r->per_dir_config, &alias_module);
+
+ if (dirconf->alias) {
+ const char *err = NULL;
+
+ char *found = apr_pstrdup(r->pool,
+ ap_expr_str_exec(r, dirconf->alias, &err));
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02825)
+ "Can't evaluate alias expression: %s", err);
+ return PREGSUB_ERROR;
+ }
+
+ if (dirconf->handler) { /* Set handler, and leave a note for mod_cgi */
+ r->handler = dirconf->handler;
+ apr_table_setn(r->notes, "alias-forced-type", r->handler);
+ }
+ /* XXX This is as SLOW as can be, next step, we optimize
+ * and merge to whatever part of the found path was already
+ * canonicalized. After I finish eliminating os canonical.
+ * Better fail test for ap_server_root_relative needed here.
+ */
+ found = ap_server_root_relative(r->pool, found);
+ return found;
+
+ }
+
+ return NULL;
+}
+
+static char *try_redirect(request_rec *r, int *status)
+{
+ alias_dir_conf *dirconf =
+ (alias_dir_conf *) ap_get_module_config(r->per_dir_config, &alias_module);
+
+ if (dirconf->redirect_set) {
+ apr_uri_t uri;
+ const char *err = NULL;
+ char *found = "";
+
+ if (dirconf->redirect) {
+
+ found = apr_pstrdup(r->pool,
+ ap_expr_str_exec(r, dirconf->redirect, &err));
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02826)
+ "Can't evaluate redirect expression: %s", err);
+ return PREGSUB_ERROR;
+ }
+
+ apr_uri_parse(r->pool, found, &uri);
+ /* Do not escape the query string or fragment. */
+ found = apr_uri_unparse(r->pool, &uri, APR_URI_UNP_OMITQUERY);
+ found = ap_escape_uri(r->pool, found);
+ if (uri.query) {
+ found = apr_pstrcat(r->pool, found, "?", uri.query, NULL);
+ }
+ if (uri.fragment) {
+ found = apr_pstrcat(r->pool, found, "#", uri.fragment, NULL);
+ }
+
+ }
+
+ *status = dirconf->redirect_status;
+ return found;
+
+ }
+
+ return NULL;
+}
+
+static char *try_alias_list(request_rec *r, apr_array_header_t *aliases,
+ int is_redir, int *status)
+{
+ alias_entry *entries = (alias_entry *) aliases->elts;
+ ap_regmatch_t regm[AP_MAX_REG_MATCH];
+ char *found = NULL;
+ int i;
+
+ for (i = 0; i < aliases->nelts; ++i) {
+ alias_entry *alias = &entries[i];
+ int l;
+
+ if (alias->regexp) {
+ if (!ap_regexec(alias->regexp, r->uri, AP_MAX_REG_MATCH, regm, 0)) {
+ if (alias->real) {
+ found = ap_pregsub(r->pool, alias->real, r->uri,
+ AP_MAX_REG_MATCH, regm);
+ if (found) {
+ if (is_redir) {
+ apr_uri_t uri;
+ apr_uri_parse(r->pool, found, &uri);
+ /* Do not escape the query string or fragment. */
+ found = apr_uri_unparse(r->pool, &uri,
+ APR_URI_UNP_OMITQUERY);
+ found = ap_escape_uri(r->pool, found);
+ if (uri.query) {
+ found = apr_pstrcat(r->pool, found, "?",
+ uri.query, NULL);
+ }
+ if (uri.fragment) {
+ found = apr_pstrcat(r->pool, found, "#",
+ uri.fragment, NULL);
+ }
+ }
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00672)
+ "Regex substitution in '%s' failed. "
+ "Replacement too long?", alias->real);
+ return PREGSUB_ERROR;
+ }
+ }
+ else {
+ /* need something non-null */
+ found = "";
+ }
+ }
+ }
+ else {
+ l = alias_matches(r->uri, alias->fake);
+
+ if (l > 0) {
+ ap_set_context_info(r, alias->fake, alias->real);
+ if (is_redir) {
+ char *escurl;
+ escurl = ap_os_escape_path(r->pool, r->uri + l, 1);
+
+ found = apr_pstrcat(r->pool, alias->real, escurl, NULL);
+ }
+ else
+ found = apr_pstrcat(r->pool, alias->real, r->uri + l, NULL);
+ }
+ }
+
+ if (found) {
+ if (alias->handler) { /* Set handler, and leave a note for mod_cgi */
+ r->handler = alias->handler;
+ apr_table_setn(r->notes, "alias-forced-type", r->handler);
+ }
+ /* XXX This is as SLOW as can be, next step, we optimize
+ * and merge to whatever part of the found path was already
+ * canonicalized. After I finish eliminating os canonical.
+ * Better fail test for ap_server_root_relative needed here.
+ */
+ if (!is_redir) {
+ found = ap_server_root_relative(r->pool, found);
+ }
+ if (found) {
+ *status = alias->redir_status;
+ }
+ return found;
+ }
+
+ }
+
+ return NULL;
+}
+
+static int translate_alias_redir(request_rec *r)
+{
+ ap_conf_vector_t *sconf = r->server->module_config;
+ alias_server_conf *serverconf = ap_get_module_config(sconf, &alias_module);
+ char *ret;
+ int status;
+
+ if (r->uri[0] != '/' && r->uri[0] != '\0') {
+ return DECLINED;
+ }
+
+ if ((ret = try_redirect(r, &status)) != NULL
+ || (ret = try_alias_list(r, serverconf->redirects, 1, &status))
+ != NULL) {
+ if (ret == PREGSUB_ERROR)
+ return HTTP_INTERNAL_SERVER_ERROR;
+ if (ap_is_HTTP_REDIRECT(status)) {
+ if (ret[0] == '/') {
+ char *orig_target = ret;
+
+ ret = ap_construct_url(r->pool, ret, r);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00673)
+ "incomplete redirection target of '%s' for "
+ "URI '%s' modified to '%s'",
+ orig_target, r->uri, ret);
+ }
+ if (!ap_is_url(ret)) {
+ status = HTTP_INTERNAL_SERVER_ERROR;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00674)
+ "cannot redirect '%s' to '%s'; "
+ "target is not a valid absoluteURI or abs_path",
+ r->uri, ret);
+ }
+ else {
+ /* append requested query only, if the config didn't
+ * supply its own.
+ */
+ if (r->args && !ap_strchr(ret, '?')) {
+ ret = apr_pstrcat(r->pool, ret, "?", r->args, NULL);
+ }
+ apr_table_setn(r->headers_out, "Location", ret);
+ }
+ }
+ return status;
+ }
+
+ if ((ret = try_alias(r)) != NULL
+ || (ret = try_alias_list(r, serverconf->aliases, 0, &status))
+ != NULL) {
+ r->filename = ret;
+ return OK;
+ }
+
+ return DECLINED;
+}
+
+static int fixup_redir(request_rec *r)
+{
+ void *dconf = r->per_dir_config;
+ alias_dir_conf *dirconf =
+ (alias_dir_conf *) ap_get_module_config(dconf, &alias_module);
+ char *ret;
+ int status;
+
+ /* It may have changed since last time, so try again */
+
+ if ((ret = try_redirect(r, &status)) != NULL
+ || (ret = try_alias_list(r, dirconf->redirects, 1, &status))
+ != NULL) {
+ if (ret == PREGSUB_ERROR)
+ return HTTP_INTERNAL_SERVER_ERROR;
+ if (ap_is_HTTP_REDIRECT(status)) {
+ if (ret[0] == '/') {
+ char *orig_target = ret;
+
+ ret = ap_construct_url(r->pool, ret, r);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00675)
+ "incomplete redirection target of '%s' for "
+ "URI '%s' modified to '%s'",
+ orig_target, r->uri, ret);
+ }
+ if (!ap_is_url(ret)) {
+ status = HTTP_INTERNAL_SERVER_ERROR;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00676)
+ "cannot redirect '%s' to '%s'; "
+ "target is not a valid absoluteURI or abs_path",
+ r->uri, ret);
+ }
+ else {
+ /* append requested query only, if the config didn't
+ * supply its own.
+ */
+ if (r->args && !ap_strchr(ret, '?')) {
+ ret = apr_pstrcat(r->pool, ret, "?", r->args, NULL);
+ }
+ apr_table_setn(r->headers_out, "Location", ret);
+ }
+ }
+ return status;
+ }
+
+ return DECLINED;
+}
+
+static const command_rec alias_cmds[] =
+{
+ AP_INIT_TAKE12("Alias", add_alias, NULL, RSRC_CONF | ACCESS_CONF,
+ "a fakename and a realname, or a realname in a Location"),
+ AP_INIT_TAKE12("ScriptAlias", add_alias, "cgi-script", RSRC_CONF | ACCESS_CONF,
+ "a fakename and a realname, or a realname in a Location"),
+ AP_INIT_TAKE123("Redirect", add_redirect, (void *) HTTP_MOVED_TEMPORARILY,
+ OR_FILEINFO,
+ "an optional status, then document to be redirected and "
+ "destination URL"),
+ AP_INIT_TAKE2("AliasMatch", add_alias_regex, NULL, RSRC_CONF,
+ "a regular expression and a filename"),
+ AP_INIT_TAKE2("ScriptAliasMatch", add_alias_regex, "cgi-script", RSRC_CONF,
+ "a regular expression and a filename"),
+ AP_INIT_TAKE23("RedirectMatch", add_redirect_regex,
+ (void *) HTTP_MOVED_TEMPORARILY, OR_FILEINFO,
+ "an optional status, then a regular expression and "
+ "destination URL"),
+ AP_INIT_TAKE2("RedirectTemp", add_redirect2,
+ (void *) HTTP_MOVED_TEMPORARILY, OR_FILEINFO,
+ "a document to be redirected, then the destination URL"),
+ AP_INIT_TAKE2("RedirectPermanent", add_redirect2,
+ (void *) HTTP_MOVED_PERMANENTLY, OR_FILEINFO,
+ "a document to be redirected, then the destination URL"),
+ {NULL}
+};
+
+
+static void register_hooks(apr_pool_t *p)
+{
+ static const char * const aszSucc[]={ "mod_userdir.c",
+ "mod_vhost_alias.c",NULL };
+
+ ap_hook_translate_name(translate_alias_redir,NULL,aszSucc,APR_HOOK_MIDDLE);
+ ap_hook_fixups(fixup_redir,NULL,NULL,APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(alias) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_alias_dir_config, /* dir config creater */
+ merge_alias_dir_config, /* dir merger --- default is to override */
+ create_alias_config, /* server config */
+ merge_alias_config, /* merge server configs */
+ alias_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/mappers/mod_alias.dep b/modules/mappers/mod_alias.dep
new file mode 100644
index 0000000..fab6805
--- /dev/null
+++ b/modules/mappers/mod_alias.dep
@@ -0,0 +1,51 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_alias.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_alias.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/mappers/mod_alias.dsp b/modules/mappers/mod_alias.dsp
new file mode 100644
index 0000000..4c8cf84
--- /dev/null
+++ b/modules/mappers/mod_alias.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_alias" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_alias - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_alias.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_alias.mak" CFG="mod_alias - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_alias - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_alias - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_alias - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_alias_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_alias.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_alias.so" /d LONG_NAME="alias_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_alias.so" /base:@..\..\os\win32\BaseAddr.ref,mod_alias.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_alias.so" /base:@..\..\os\win32\BaseAddr.ref,mod_alias.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_alias.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_alias - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_alias_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_alias.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_alias.so" /d LONG_NAME="alias_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_alias.so" /base:@..\..\os\win32\BaseAddr.ref,mod_alias.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_alias.so" /base:@..\..\os\win32\BaseAddr.ref,mod_alias.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_alias.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_alias - Win32 Release"
+# Name "mod_alias - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_alias.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/mappers/mod_alias.exp b/modules/mappers/mod_alias.exp
new file mode 100644
index 0000000..ac386ec
--- /dev/null
+++ b/modules/mappers/mod_alias.exp
@@ -0,0 +1 @@
+alias_module
diff --git a/modules/mappers/mod_alias.mak b/modules/mappers/mod_alias.mak
new file mode 100644
index 0000000..17ad406
--- /dev/null
+++ b/modules/mappers/mod_alias.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_alias.dsp
+!IF "$(CFG)" == ""
+CFG=mod_alias - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_alias - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_alias - Win32 Release" && "$(CFG)" != "mod_alias - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_alias.mak" CFG="mod_alias - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_alias - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_alias - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_alias - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_alias.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_alias.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_alias.obj"
+ -@erase "$(INTDIR)\mod_alias.res"
+ -@erase "$(INTDIR)\mod_alias_src.idb"
+ -@erase "$(INTDIR)\mod_alias_src.pdb"
+ -@erase "$(OUTDIR)\mod_alias.exp"
+ -@erase "$(OUTDIR)\mod_alias.lib"
+ -@erase "$(OUTDIR)\mod_alias.pdb"
+ -@erase "$(OUTDIR)\mod_alias.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_alias_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_alias.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_alias.so" /d LONG_NAME="alias_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_alias.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_alias.pdb" /debug /out:"$(OUTDIR)\mod_alias.so" /implib:"$(OUTDIR)\mod_alias.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_alias.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_alias.obj" \
+ "$(INTDIR)\mod_alias.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_alias.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_alias.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_alias.so"
+ if exist .\Release\mod_alias.so.manifest mt.exe -manifest .\Release\mod_alias.so.manifest -outputresource:.\Release\mod_alias.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_alias - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_alias.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_alias.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_alias.obj"
+ -@erase "$(INTDIR)\mod_alias.res"
+ -@erase "$(INTDIR)\mod_alias_src.idb"
+ -@erase "$(INTDIR)\mod_alias_src.pdb"
+ -@erase "$(OUTDIR)\mod_alias.exp"
+ -@erase "$(OUTDIR)\mod_alias.lib"
+ -@erase "$(OUTDIR)\mod_alias.pdb"
+ -@erase "$(OUTDIR)\mod_alias.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_alias_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_alias.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_alias.so" /d LONG_NAME="alias_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_alias.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_alias.pdb" /debug /out:"$(OUTDIR)\mod_alias.so" /implib:"$(OUTDIR)\mod_alias.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_alias.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_alias.obj" \
+ "$(INTDIR)\mod_alias.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_alias.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_alias.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_alias.so"
+ if exist .\Debug\mod_alias.so.manifest mt.exe -manifest .\Debug\mod_alias.so.manifest -outputresource:.\Debug\mod_alias.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_alias.dep")
+!INCLUDE "mod_alias.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_alias.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_alias - Win32 Release" || "$(CFG)" == "mod_alias - Win32 Debug"
+
+!IF "$(CFG)" == "mod_alias - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\mappers"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ELSEIF "$(CFG)" == "mod_alias - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\mappers"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_alias - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\mappers"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ELSEIF "$(CFG)" == "mod_alias - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\mappers"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_alias - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\mappers"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\mappers"
+
+!ELSEIF "$(CFG)" == "mod_alias - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\mappers"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\mappers"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_alias - Win32 Release"
+
+
+"$(INTDIR)\mod_alias.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_alias.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_alias.so" /d LONG_NAME="alias_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_alias - Win32 Debug"
+
+
+"$(INTDIR)\mod_alias.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_alias.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_alias.so" /d LONG_NAME="alias_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_alias.c
+
+"$(INTDIR)\mod_alias.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/mappers/mod_dir.c b/modules/mappers/mod_dir.c
new file mode 100644
index 0000000..3e30198
--- /dev/null
+++ b/modules/mappers/mod_dir.c
@@ -0,0 +1,417 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * mod_dir.c: handle default index files, and trailing-/ redirects
+ */
+
+#include "apr_strings.h"
+#include "apr_lib.h"
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_request.h"
+#include "http_protocol.h"
+#include "http_log.h"
+#include "http_main.h"
+#include "util_script.h"
+#include "mod_rewrite.h"
+
+module AP_MODULE_DECLARE_DATA dir_module;
+
+typedef enum {
+ MODDIR_OFF = 0,
+ MODDIR_ON,
+ MODDIR_UNSET
+} moddir_cfg;
+
+#define REDIRECT_OFF 0
+#define REDIRECT_UNSET 1
+
+typedef struct dir_config_struct {
+ apr_array_header_t *index_names;
+ moddir_cfg do_slash;
+ moddir_cfg checkhandler;
+ int redirect_index;
+ const char *dflt;
+} dir_config_rec;
+
+#define DIR_CMD_PERMS OR_INDEXES
+
+static const char *add_index(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ dir_config_rec *d = dummy;
+ const char *t, *w;
+ int count = 0;
+
+ if (!d->index_names) {
+ d->index_names = apr_array_make(cmd->pool, 2, sizeof(char *));
+ }
+
+ t = arg;
+ while ((w = ap_getword_conf(cmd->pool, &t)) && w[0]) {
+ if (count == 0 && !strcasecmp(w, "disabled")) {
+ /* peek to see if "disabled" is first in a series of arguments */
+ const char *tt = t;
+ const char *ww = ap_getword_conf(cmd->temp_pool, &tt);
+ if (ww[0] == '\0') {
+ /* "disabled" is first, and alone */
+ apr_array_clear(d->index_names);
+ break;
+ }
+ }
+ *(const char **)apr_array_push(d->index_names) = w;
+ count++;
+ }
+
+ return NULL;
+}
+
+static const char *configure_slash(cmd_parms *cmd, void *d_, int arg)
+{
+ dir_config_rec *d = d_;
+
+ d->do_slash = arg ? MODDIR_ON : MODDIR_OFF;
+ return NULL;
+}
+static const char *configure_checkhandler(cmd_parms *cmd, void *d_, int arg)
+{
+ dir_config_rec *d = d_;
+
+ d->checkhandler = arg ? MODDIR_ON : MODDIR_OFF;
+ return NULL;
+}
+static const char *configure_redirect(cmd_parms *cmd, void *d_, const char *arg1)
+{
+ dir_config_rec *d = d_;
+ int status;
+
+ if (!strcasecmp(arg1, "ON"))
+ status = HTTP_MOVED_TEMPORARILY;
+ else if (!strcasecmp(arg1, "OFF"))
+ status = REDIRECT_OFF;
+ else if (!strcasecmp(arg1, "permanent"))
+ status = HTTP_MOVED_PERMANENTLY;
+ else if (!strcasecmp(arg1, "temp"))
+ status = HTTP_MOVED_TEMPORARILY;
+ else if (!strcasecmp(arg1, "seeother"))
+ status = HTTP_SEE_OTHER;
+ else if (apr_isdigit(*arg1)) {
+ status = atoi(arg1);
+ if (!ap_is_HTTP_REDIRECT(status)) {
+ return "DirectoryIndexRedirect only accepts values between 300 and 399";
+ }
+ }
+ else {
+ return "DirectoryIndexRedirect ON|OFF|permanent|temp|seeother|3xx";
+ }
+
+ d->redirect_index = status;
+ return NULL;
+}
+static const command_rec dir_cmds[] =
+{
+ AP_INIT_TAKE1("FallbackResource", ap_set_string_slot,
+ (void*)APR_OFFSETOF(dir_config_rec, dflt),
+ DIR_CMD_PERMS, "Set a default handler"),
+ AP_INIT_RAW_ARGS("DirectoryIndex", add_index, NULL, DIR_CMD_PERMS,
+ "a list of file names"),
+ AP_INIT_FLAG("DirectorySlash", configure_slash, NULL, DIR_CMD_PERMS,
+ "On or Off"),
+ AP_INIT_FLAG("DirectoryCheckHandler", configure_checkhandler, NULL, DIR_CMD_PERMS,
+ "On or Off"),
+ AP_INIT_TAKE1("DirectoryIndexRedirect", configure_redirect,
+ NULL, DIR_CMD_PERMS, "On, Off, or a 3xx status code."),
+
+ {NULL}
+};
+
+static void *create_dir_config(apr_pool_t *p, char *dummy)
+{
+ dir_config_rec *new = apr_pcalloc(p, sizeof(dir_config_rec));
+
+ new->index_names = NULL;
+ new->do_slash = MODDIR_UNSET;
+ new->checkhandler = MODDIR_UNSET;
+ new->redirect_index = REDIRECT_UNSET;
+ return (void *) new;
+}
+
+static void *merge_dir_configs(apr_pool_t *p, void *basev, void *addv)
+{
+ dir_config_rec *new = apr_pcalloc(p, sizeof(dir_config_rec));
+ dir_config_rec *base = (dir_config_rec *)basev;
+ dir_config_rec *add = (dir_config_rec *)addv;
+
+ new->index_names = add->index_names ? add->index_names : base->index_names;
+ new->do_slash =
+ (add->do_slash == MODDIR_UNSET) ? base->do_slash : add->do_slash;
+ new->checkhandler =
+ (add->checkhandler == MODDIR_UNSET) ? base->checkhandler : add->checkhandler;
+ new->redirect_index=
+ (add->redirect_index == REDIRECT_UNSET) ? base->redirect_index : add->redirect_index;
+ new->dflt = add->dflt ? add->dflt : base->dflt;
+ return new;
+}
+
+static int fixup_dflt(request_rec *r)
+{
+ dir_config_rec *d = ap_get_module_config(r->per_dir_config, &dir_module);
+ const char *name_ptr;
+ request_rec *rr;
+ int error_notfound = 0;
+
+ name_ptr = d->dflt;
+ if ((name_ptr == NULL) || !(strcasecmp(name_ptr,"disabled"))){
+ return DECLINED;
+ }
+ /* XXX: if FallbackResource points to something that doesn't exist,
+ * this may recurse until it hits the limit for internal redirects
+ * before returning an Internal Server Error.
+ */
+
+ /* The logic of this function is basically cloned and simplified
+ * from fixup_dir below. See the comments there.
+ */
+ if (r->args != NULL) {
+ name_ptr = apr_pstrcat(r->pool, name_ptr, "?", r->args, NULL);
+ }
+ rr = ap_sub_req_lookup_uri(name_ptr, r, r->output_filters);
+ if (rr->status == HTTP_OK
+ && ( (rr->handler && !strcmp(rr->handler, "proxy-server"))
+ || rr->finfo.filetype == APR_REG)) {
+ ap_internal_fast_redirect(rr, r);
+ return OK;
+ }
+ else if (ap_is_HTTP_REDIRECT(rr->status)) {
+
+ apr_pool_join(r->pool, rr->pool);
+ r->notes = apr_table_overlay(r->pool, r->notes, rr->notes);
+ r->headers_out = apr_table_overlay(r->pool, r->headers_out,
+ rr->headers_out);
+ r->err_headers_out = apr_table_overlay(r->pool, r->err_headers_out,
+ rr->err_headers_out);
+ error_notfound = rr->status;
+ }
+ else if (rr->status && rr->status != HTTP_NOT_FOUND
+ && rr->status != HTTP_OK) {
+ error_notfound = rr->status;
+ }
+
+ ap_destroy_sub_req(rr);
+ if (error_notfound) {
+ return error_notfound;
+ }
+
+ /* nothing for us to do, pass on through */
+ return DECLINED;
+}
+
+static int fixup_dir(request_rec *r)
+{
+ dir_config_rec *d;
+ char *dummy_ptr[1];
+ char **names_ptr;
+ int num_names;
+ int error_notfound = 0;
+
+ /* In case mod_mime wasn't present, and no handler was assigned. */
+ if (!r->handler) {
+ r->handler = DIR_MAGIC_TYPE;
+ }
+
+ /* Never tolerate path_info on dir requests */
+ if (r->path_info && *r->path_info) {
+ return DECLINED;
+ }
+
+ d = (dir_config_rec *)ap_get_module_config(r->per_dir_config,
+ &dir_module);
+
+ /* Redirect requests that are not '/' terminated */
+ if (r->uri[0] == '\0' || r->uri[strlen(r->uri) - 1] != '/')
+ {
+ char *ifile;
+
+ if (!d->do_slash) {
+ return DECLINED;
+ }
+
+ /* Only redirect non-get requests if we have no note to warn
+ * that this browser cannot handle redirs on non-GET requests
+ * (such as Microsoft's WebFolders).
+ */
+ if ((r->method_number != M_GET)
+ && apr_table_get(r->subprocess_env, "redirect-carefully")) {
+ return DECLINED;
+ }
+
+ if (r->args != NULL) {
+ ifile = apr_pstrcat(r->pool, ap_escape_uri(r->pool, r->uri),
+ "/?", r->args, NULL);
+ }
+ else {
+ ifile = apr_pstrcat(r->pool, ap_escape_uri(r->pool, r->uri),
+ "/", NULL);
+ }
+
+ apr_table_setn(r->headers_out, "Location",
+ ap_construct_url(r->pool, ifile, r));
+ return HTTP_MOVED_PERMANENTLY;
+ }
+
+ /* we're running between mod_rewrites fixup and its internal redirect handler, step aside */
+ if (!strcmp(r->handler, REWRITE_REDIRECT_HANDLER_NAME)) {
+ /* Prevent DIR_MAGIC_TYPE from leaking out when someone has taken over */
+ if (!strcmp(r->content_type, DIR_MAGIC_TYPE)) {
+ r->content_type = NULL;
+ }
+ return DECLINED;
+ }
+
+ if (d->checkhandler == MODDIR_ON && strcmp(r->handler, DIR_MAGIC_TYPE)) {
+ /* Prevent DIR_MAGIC_TYPE from leaking out when someone has taken over */
+ if (!strcmp(r->content_type, DIR_MAGIC_TYPE)) {
+ r->content_type = NULL;
+ }
+ return DECLINED;
+ }
+
+ if (d->index_names) {
+ names_ptr = (char **)d->index_names->elts;
+ num_names = d->index_names->nelts;
+ }
+ else {
+ dummy_ptr[0] = AP_DEFAULT_INDEX;
+ names_ptr = dummy_ptr;
+ num_names = 1;
+ }
+
+ for (; num_names; ++names_ptr, --num_names) {
+ /* XXX: Is this name_ptr considered escaped yet, or not??? */
+ char *name_ptr = *names_ptr;
+ request_rec *rr;
+
+ /* Once upon a time args were handled _after_ the successful redirect.
+ * But that redirect might then _refuse_ the given r->args, creating
+ * a nasty tangle. It seems safer to consider the r->args while we
+ * determine if name_ptr is our viable index, and therefore set them
+ * up correctly on redirect.
+ */
+ if (r->args != NULL) {
+ name_ptr = apr_pstrcat(r->pool, name_ptr, "?", r->args, NULL);
+ }
+
+ rr = ap_sub_req_lookup_uri(name_ptr, r, r->output_filters);
+
+ /* The sub request lookup is very liberal, and the core map_to_storage
+ * handler will almost always result in HTTP_OK as /foo/index.html
+ * may be /foo with PATH_INFO="/index.html", or even / with
+ * PATH_INFO="/foo/index.html". To get around this we insist that the
+ * the index be a regular filetype.
+ *
+ * Another reason is that the core handler also makes the assumption
+ * that if r->finfo is still NULL by the time it gets called, the
+ * file does not exist.
+ */
+ if (rr->status == HTTP_OK
+ && ( (rr->handler && !strcmp(rr->handler, "proxy-server"))
+ || rr->finfo.filetype == APR_REG)) {
+
+ if (ap_is_HTTP_REDIRECT(d->redirect_index)) {
+ apr_table_setn(r->headers_out, "Location", ap_construct_url(r->pool, rr->uri, r));
+ return d->redirect_index;
+ }
+
+ ap_internal_fast_redirect(rr, r);
+ return OK;
+ }
+
+ /* If the request returned a redirect, propagate it to the client */
+
+ if (ap_is_HTTP_REDIRECT(rr->status)
+ || (rr->status == HTTP_NOT_ACCEPTABLE && num_names == 1)
+ || (rr->status == HTTP_UNAUTHORIZED && num_names == 1)) {
+
+ apr_pool_join(r->pool, rr->pool);
+ error_notfound = rr->status;
+ r->notes = apr_table_overlay(r->pool, r->notes, rr->notes);
+ r->headers_out = apr_table_overlay(r->pool, r->headers_out,
+ rr->headers_out);
+ r->err_headers_out = apr_table_overlay(r->pool, r->err_headers_out,
+ rr->err_headers_out);
+ return error_notfound;
+ }
+
+ /* If the request returned something other than 404 (or 200),
+ * it means the module encountered some sort of problem. To be
+ * secure, we should return the error, rather than allow autoindex
+ * to create a (possibly unsafe) directory index.
+ *
+ * So we store the error, and if none of the listed files
+ * exist, we return the last error response we got, instead
+ * of a directory listing.
+ */
+ if (rr->status && rr->status != HTTP_NOT_FOUND
+ && rr->status != HTTP_OK) {
+ error_notfound = rr->status;
+ }
+
+ ap_destroy_sub_req(rr);
+ }
+
+ if (error_notfound) {
+ return error_notfound;
+ }
+
+ /* record what we tried, mostly for the benefit of mod_autoindex */
+ apr_table_setn(r->notes, "dir-index-names",
+ d->index_names ?
+ apr_array_pstrcat(r->pool, d->index_names, ',') :
+ AP_DEFAULT_INDEX);
+
+ /* nothing for us to do, pass on through */
+ return DECLINED;
+}
+
+static int dir_fixups(request_rec *r)
+{
+ if (r->finfo.filetype == APR_DIR) {
+ /* serve up a directory */
+ return fixup_dir(r);
+ }
+ else if ((r->finfo.filetype == APR_NOFILE) && (r->handler == NULL)) {
+ /* No handler and nothing in the filesystem - use fallback */
+ return fixup_dflt(r);
+ }
+ return DECLINED;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_hook_fixups(dir_fixups,NULL,NULL,APR_HOOK_LAST);
+}
+
+AP_DECLARE_MODULE(dir) = {
+ STANDARD20_MODULE_STUFF,
+ create_dir_config, /* create per-directory config structure */
+ merge_dir_configs, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ dir_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/mappers/mod_dir.dep b/modules/mappers/mod_dir.dep
new file mode 100644
index 0000000..e862f70
--- /dev/null
+++ b/modules/mappers/mod_dir.dep
@@ -0,0 +1,60 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_dir.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_dir.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_rewrite.h"\
+
diff --git a/modules/mappers/mod_dir.dsp b/modules/mappers/mod_dir.dsp
new file mode 100644
index 0000000..cc9ebdb
--- /dev/null
+++ b/modules/mappers/mod_dir.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_dir" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_dir - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_dir.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_dir.mak" CFG="mod_dir - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_dir - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_dir - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_dir - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_dir_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_dir.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_dir.so" /d LONG_NAME="dir_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_dir.so" /base:@..\..\os\win32\BaseAddr.ref,mod_dir.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_dir.so" /base:@..\..\os\win32\BaseAddr.ref,mod_dir.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_dir.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_dir - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_dir_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_dir.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_dir.so" /d LONG_NAME="dir_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_dir.so" /base:@..\..\os\win32\BaseAddr.ref,mod_dir.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_dir.so" /base:@..\..\os\win32\BaseAddr.ref,mod_dir.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_dir.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_dir - Win32 Release"
+# Name "mod_dir - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_dir.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/mappers/mod_dir.exp b/modules/mappers/mod_dir.exp
new file mode 100644
index 0000000..5fbf772
--- /dev/null
+++ b/modules/mappers/mod_dir.exp
@@ -0,0 +1 @@
+dir_module
diff --git a/modules/mappers/mod_dir.mak b/modules/mappers/mod_dir.mak
new file mode 100644
index 0000000..d490010
--- /dev/null
+++ b/modules/mappers/mod_dir.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_dir.dsp
+!IF "$(CFG)" == ""
+CFG=mod_dir - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_dir - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_dir - Win32 Release" && "$(CFG)" != "mod_dir - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_dir.mak" CFG="mod_dir - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_dir - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_dir - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_dir - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_dir.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_dir.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_dir.obj"
+ -@erase "$(INTDIR)\mod_dir.res"
+ -@erase "$(INTDIR)\mod_dir_src.idb"
+ -@erase "$(INTDIR)\mod_dir_src.pdb"
+ -@erase "$(OUTDIR)\mod_dir.exp"
+ -@erase "$(OUTDIR)\mod_dir.lib"
+ -@erase "$(OUTDIR)\mod_dir.pdb"
+ -@erase "$(OUTDIR)\mod_dir.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_dir_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_dir.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_dir.so" /d LONG_NAME="dir_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_dir.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_dir.pdb" /debug /out:"$(OUTDIR)\mod_dir.so" /implib:"$(OUTDIR)\mod_dir.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_dir.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_dir.obj" \
+ "$(INTDIR)\mod_dir.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_dir.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_dir.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_dir.so"
+ if exist .\Release\mod_dir.so.manifest mt.exe -manifest .\Release\mod_dir.so.manifest -outputresource:.\Release\mod_dir.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_dir - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_dir.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_dir.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_dir.obj"
+ -@erase "$(INTDIR)\mod_dir.res"
+ -@erase "$(INTDIR)\mod_dir_src.idb"
+ -@erase "$(INTDIR)\mod_dir_src.pdb"
+ -@erase "$(OUTDIR)\mod_dir.exp"
+ -@erase "$(OUTDIR)\mod_dir.lib"
+ -@erase "$(OUTDIR)\mod_dir.pdb"
+ -@erase "$(OUTDIR)\mod_dir.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_dir_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_dir.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_dir.so" /d LONG_NAME="dir_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_dir.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_dir.pdb" /debug /out:"$(OUTDIR)\mod_dir.so" /implib:"$(OUTDIR)\mod_dir.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_dir.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_dir.obj" \
+ "$(INTDIR)\mod_dir.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_dir.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_dir.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_dir.so"
+ if exist .\Debug\mod_dir.so.manifest mt.exe -manifest .\Debug\mod_dir.so.manifest -outputresource:.\Debug\mod_dir.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_dir.dep")
+!INCLUDE "mod_dir.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_dir.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_dir - Win32 Release" || "$(CFG)" == "mod_dir - Win32 Debug"
+
+!IF "$(CFG)" == "mod_dir - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\mappers"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ELSEIF "$(CFG)" == "mod_dir - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\mappers"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_dir - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\mappers"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ELSEIF "$(CFG)" == "mod_dir - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\mappers"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_dir - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\mappers"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\mappers"
+
+!ELSEIF "$(CFG)" == "mod_dir - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\mappers"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\mappers"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_dir - Win32 Release"
+
+
+"$(INTDIR)\mod_dir.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_dir.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_dir.so" /d LONG_NAME="dir_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_dir - Win32 Debug"
+
+
+"$(INTDIR)\mod_dir.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_dir.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_dir.so" /d LONG_NAME="dir_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_dir.c
+
+"$(INTDIR)\mod_dir.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/mappers/mod_imagemap.c b/modules/mappers/mod_imagemap.c
new file mode 100644
index 0000000..206c0b6
--- /dev/null
+++ b/modules/mappers/mod_imagemap.c
@@ -0,0 +1,897 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * This imagemap module started as a port of the original imagemap.c
+ * written by Rob McCool (11/13/93 robm@ncsa.uiuc.edu).
+ * This version includes the mapping algorithms found in version 1.3
+ * of imagemap.c.
+ *
+ * Contributors to this code include:
+ *
+ * Kevin Hughes, kevinh@pulua.hcc.hawaii.edu
+ *
+ * Eric Haines, erich@eye.com
+ * "macmartinized" polygon code copyright 1992 by Eric Haines, erich@eye.com
+ *
+ * Randy Terbush, randy@zyzzyva.com
+ * port to Apache module format, "base_uri" and support for relative URLs
+ *
+ * James H. Cloos, Jr., cloos@jhcloos.com
+ * Added point datatype, using code in NCSA's version 1.8 imagemap.c
+ * program, as distributed with version 1.4.1 of their server.
+ * The point code is originally added by Craig Milo Rogers, Rogers@ISI.Edu
+ *
+ * Nathan Kurz, nate@tripod.com
+ * Rewrite/reorganization. New handling of default, base and relative URLs.
+ * New Configuration directives:
+ * ImapMenu {none, formatted, semiformatted, unformatted}
+ * ImapDefault {error, nocontent, referer, menu, URL}
+ * ImapBase {map, referer, URL}
+ * Support for creating non-graphical menu added. (backwards compatible):
+ * Old: directive URL [x,y ...]
+ * New: directive URL "Menu text" [x,y ...]
+ * or: directive URL x,y ... "Menu text"
+ * Map format and menu concept courtesy Joshua Bell, jsbell@acs.ucalgary.ca.
+ *
+ * Mark Cox, mark@ukweb.com, Allow relative URLs even when no base specified
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+
+#define APR_WANT_STDIO /* for sscanf() */
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_request.h"
+#include "http_core.h"
+#include "http_protocol.h"
+#include "http_main.h"
+#include "http_log.h"
+#include "util_script.h"
+#include "mod_core.h"
+
+
+#define IMAP_MAGIC_TYPE "application/x-httpd-imap"
+#define MAXVERTS 100
+#define X 0
+#define Y 1
+
+#define IMAP_MENU_DEFAULT "formatted"
+#define IMAP_DEFAULT_DEFAULT "nocontent"
+#define IMAP_BASE_DEFAULT "map"
+
+module AP_MODULE_DECLARE_DATA imagemap_module;
+
+typedef struct {
+ char *imap_menu;
+ char *imap_default;
+ char *imap_base;
+} imap_conf_rec;
+
+static void *create_imap_dir_config(apr_pool_t *p, char *dummy)
+{
+ imap_conf_rec *icr =
+ (imap_conf_rec *) apr_palloc(p, sizeof(imap_conf_rec));
+
+ icr->imap_menu = NULL;
+ icr->imap_default = NULL;
+ icr->imap_base = NULL;
+
+ return icr;
+}
+
+static void *merge_imap_dir_configs(apr_pool_t *p, void *basev, void *addv)
+{
+ imap_conf_rec *new = (imap_conf_rec *) apr_palloc(p, sizeof(imap_conf_rec));
+ imap_conf_rec *base = (imap_conf_rec *) basev;
+ imap_conf_rec *add = (imap_conf_rec *) addv;
+
+ new->imap_menu = add->imap_menu ? add->imap_menu : base->imap_menu;
+ new->imap_default = add->imap_default ? add->imap_default
+ : base->imap_default;
+ new->imap_base = add->imap_base ? add->imap_base : base->imap_base;
+
+ return new;
+}
+
+
+static const command_rec imap_cmds[] =
+{
+ AP_INIT_TAKE1("ImapMenu", ap_set_string_slot,
+ (void *)APR_OFFSETOF(imap_conf_rec, imap_menu), OR_INDEXES,
+ "the type of menu generated: none, formatted, semiformatted, "
+ "unformatted"),
+ AP_INIT_TAKE1("ImapDefault", ap_set_string_slot,
+ (void *)APR_OFFSETOF(imap_conf_rec, imap_default), OR_INDEXES,
+ "the action taken if no match: error, nocontent, referer, "
+ "menu, URL"),
+ AP_INIT_TAKE1("ImapBase", ap_set_string_slot,
+ (void *)APR_OFFSETOF(imap_conf_rec, imap_base), OR_INDEXES,
+ "the base for all URL's: map, referer, URL (or start of)"),
+ {NULL}
+};
+
+static int pointinrect(const double point[2], double coords[MAXVERTS][2])
+{
+ double max[2], min[2];
+ if (coords[0][X] > coords[1][X]) {
+ max[0] = coords[0][X];
+ min[0] = coords[1][X];
+ }
+ else {
+ max[0] = coords[1][X];
+ min[0] = coords[0][X];
+ }
+
+ if (coords[0][Y] > coords[1][Y]) {
+ max[1] = coords[0][Y];
+ min[1] = coords[1][Y];
+ }
+ else {
+ max[1] = coords[1][Y];
+ min[1] = coords[0][Y];
+ }
+
+ return ((point[X] >= min[0] && point[X] <= max[0]) &&
+ (point[Y] >= min[1] && point[Y] <= max[1]));
+}
+
+static int pointincircle(const double point[2], double coords[MAXVERTS][2])
+{
+ double radius1, radius2;
+
+ radius1 = ((coords[0][Y] - coords[1][Y]) * (coords[0][Y] - coords[1][Y]))
+ + ((coords[0][X] - coords[1][X]) * (coords[0][X] - coords[1][X]));
+
+ radius2 = ((coords[0][Y] - point[Y]) * (coords[0][Y] - point[Y]))
+ + ((coords[0][X] - point[X]) * (coords[0][X] - point[X]));
+
+ return (radius2 <= radius1);
+}
+
+#define fmin(a,b) (((a)>(b))?(b):(a))
+#define fmax(a,b) (((a)>(b))?(a):(b))
+
+static int pointinpoly(const double point[2], double pgon[MAXVERTS][2])
+{
+ int i, numverts, crossings = 0;
+ double x = point[X], y = point[Y];
+
+ for (numverts = 0; numverts < MAXVERTS && pgon[numverts][X] != -1;
+ numverts++) {
+ /* just counting the vertexes */
+ }
+
+ for (i = 0; i < numverts; i++) {
+ double x1=pgon[i][X];
+ double y1=pgon[i][Y];
+ double x2=pgon[(i + 1) % numverts][X];
+ double y2=pgon[(i + 1) % numverts][Y];
+ double d=(y - y1) * (x2 - x1) - (x - x1) * (y2 - y1);
+
+ if ((y1 >= y) != (y2 >= y)) {
+ crossings +=y2 - y1 >= 0 ? d >= 0 : d <= 0;
+ }
+ if (!d && fmin(x1,x2) <= x && x <= fmax(x1,x2)
+ && fmin(y1,y2) <= y && y <= fmax(y1,y2)) {
+ return 1;
+ }
+ }
+ return crossings & 0x01;
+}
+
+
+static int is_closer(const double point[2], double coords[MAXVERTS][2],
+ double *closest)
+{
+ double dist_squared = ((point[X] - coords[0][X])
+ * (point[X] - coords[0][X]))
+ + ((point[Y] - coords[0][Y])
+ * (point[Y] - coords[0][Y]));
+
+ if (point[X] < 0 || point[Y] < 0) {
+ return (0); /* don't mess around with negative coordinates */
+ }
+
+ if (*closest < 0 || dist_squared < *closest) {
+ *closest = dist_squared;
+ return (1); /* if this is the first point or is the closest yet
+ set 'closest' equal to this distance^2 */
+ }
+
+ return (0); /* if it's not the first or closest */
+
+}
+
+static double get_x_coord(const char *args)
+{
+ char *endptr; /* we want it non-null */
+ double x_coord = -1; /* -1 is returned if no coordinate is given */
+
+ if (args == NULL) {
+ return (-1); /* in case we aren't passed anything */
+ }
+
+ while (*args && !apr_isdigit(*args) && *args != ',') {
+ args++; /* jump to the first digit, but not past
+ a comma or end */
+ }
+
+ x_coord = strtod(args, &endptr);
+
+ if (endptr > args) { /* if a conversion was made */
+ return (x_coord);
+ }
+
+ return (-1); /* else if no conversion was made,
+ or if no args was given */
+}
+
+static double get_y_coord(const char *args)
+{
+ char *endptr; /* we want it non-null */
+ const char *start_of_y = NULL;
+ double y_coord = -1; /* -1 is returned on error */
+
+ if (args == NULL) {
+ return (-1); /* in case we aren't passed anything */
+ }
+
+ start_of_y = ap_strchr_c(args, ','); /* the comma */
+
+ if (start_of_y) {
+
+ start_of_y++; /* start looking at the character after
+ the comma */
+
+ while (*start_of_y && !apr_isdigit(*start_of_y)) {
+ start_of_y++; /* jump to the first digit, but not
+ past the end */
+ }
+
+ y_coord = strtod(start_of_y, &endptr);
+
+ if (endptr > start_of_y) {
+ return (y_coord);
+ }
+ }
+
+ return (-1); /* if no conversion was made, or
+ no comma was found in args */
+}
+
+
+/* See if string has a "quoted part", and if so set *quoted_part to
+ * the first character of the quoted part, then hammer a \0 onto the
+ * trailing quote, and set *string to point at the first character
+ * past the second quote.
+ *
+ * Otherwise set *quoted_part to NULL, and leave *string alone.
+ */
+static void read_quoted(char **string, char **quoted_part)
+{
+ char *strp = *string;
+
+ /* assume there's no quoted part */
+ *quoted_part = NULL;
+
+ while (apr_isspace(*strp)) {
+ strp++; /* go along string until non-whitespace */
+ }
+
+ if (*strp == '"') { /* if that character is a double quote */
+ strp++; /* step over it */
+ *quoted_part = strp; /* note where the quoted part begins */
+
+ while (*strp && *strp != '"') {
+ ++strp; /* skip the quoted portion */
+ }
+
+ *strp = '\0'; /* end the string with a NUL */
+
+ strp++; /* step over the last double quote */
+ *string = strp;
+ }
+}
+
+/*
+ * returns the mapped URL or NULL.
+ */
+static const char *imap_url(request_rec *r, const char *base, const char *value)
+{
+/* translates a value into a URL. */
+ apr_size_t slen, clen;
+ char *string_pos = NULL;
+ const char *string_pos_const = NULL;
+ char *directory = NULL;
+ const char *referer = NULL;
+ char *my_base;
+
+ if (!strcasecmp(value, "map") || !strcasecmp(value, "menu")) {
+ return ap_construct_url(r->pool, r->uri, r);
+ }
+
+ if (!strcasecmp(value, "nocontent") || !strcasecmp(value, "error")) {
+ return apr_pstrdup(r->pool, value); /* these are handled elsewhere,
+ so just copy them */
+ }
+
+ if (!strcasecmp(value, "referer")) {
+ referer = apr_table_get(r->headers_in, "Referer");
+ if (referer && *referer) {
+ return referer;
+ }
+ else {
+ /* XXX: This used to do *value = '\0'; ... which is totally bogus
+ * because it hammers the passed in value, which can be a string
+ * constant, or part of a config, or whatever. Total garbage.
+ * This works around that without changing the rest of this
+ * code much
+ */
+ value = ""; /* if 'referer' but no referring page,
+ null the value */
+ }
+ }
+
+ string_pos_const = value;
+ while (apr_isalpha(*string_pos_const)) {
+ string_pos_const++; /* go along the URL from the map
+ until a non-letter */
+ }
+ if (*string_pos_const == ':') {
+ /* if letters and then a colon (like http:) */
+ /* it's an absolute URL, so use it! */
+ return apr_pstrdup(r->pool, value);
+ }
+
+ if (!base || !*base) {
+ if (value && *value) {
+ return apr_pstrdup(r->pool, value); /* no base: use what is given */
+ }
+ /* no base, no value: pick a simple default */
+ return ap_construct_url(r->pool, "/", r);
+ }
+
+ /* must be a relative URL to be combined with base */
+ if (ap_strchr_c(base, '/') == NULL && (!strncmp(value, "../", 3)
+ || !strcmp(value, ".."))) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00677)
+ "invalid base directive in map file: %s", r->uri);
+ return NULL;
+ }
+ my_base = apr_pstrdup(r->pool, base);
+ string_pos = my_base;
+ while (*string_pos) {
+ if (*string_pos == '/' && *(string_pos + 1) == '/') {
+ string_pos += 2; /* if there are two slashes, jump over them */
+ continue;
+ }
+ if (*string_pos == '/') { /* the first single slash */
+ if (value[0] == '/') {
+ *string_pos = '\0';
+ } /* if the URL from the map starts from root,
+ end the base URL string at the first single
+ slash */
+ else {
+ directory = string_pos; /* save the start of
+ the directory portion */
+
+ string_pos = strrchr(string_pos, '/'); /* now reuse
+ string_pos */
+ string_pos++; /* step over that last slash */
+ *string_pos = '\0';
+ } /* but if the map url is relative, leave the
+ slash on the base (if there is one) */
+ break;
+ }
+ string_pos++; /* until we get to the end of my_base without
+ finding a slash by itself */
+ }
+
+ while (!strncmp(value, "../", 3) || !strcmp(value, "..")) {
+
+ if (directory && (slen = strlen(directory))) {
+
+ /* for each '..', knock a directory off the end
+ by ending the string right at the last slash.
+ But only consider the directory portion: don't eat
+ into the server name. And only try if a directory
+ portion was found */
+
+ clen = slen - 1;
+
+ while ((slen - clen) == 1) {
+
+ if ((string_pos = strrchr(directory, '/'))) {
+ *string_pos = '\0';
+ }
+ clen = strlen(directory);
+ if (clen == 0) {
+ break;
+ }
+ }
+
+ value += 2; /* jump over the '..' that we found in the
+ value */
+ }
+ else if (directory) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00678)
+ "invalid directory name in map file: %s", r->uri);
+ return NULL;
+ }
+
+ if (!strncmp(value, "/../", 4) || !strcmp(value, "/..")) {
+ value++; /* step over the '/' if there are more '..'
+ to do. This way, we leave the starting
+ '/' on value after the last '..', but get
+ rid of it otherwise */
+ }
+
+ } /* by this point, value does not start
+ with '..' */
+
+ if (value && *value) {
+ return apr_pstrcat(r->pool, my_base, value, NULL);
+ }
+ return my_base;
+}
+
+static int imap_reply(request_rec *r, const char *redirect)
+{
+ if (!strcasecmp(redirect, "error")) {
+ /* they actually requested an error! */
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ if (!strcasecmp(redirect, "nocontent")) {
+ /* tell the client to keep the page it has */
+ return HTTP_NO_CONTENT;
+ }
+ if (redirect && *redirect) {
+ /* must be a URL, so redirect to it */
+ apr_table_setn(r->headers_out, "Location", redirect);
+ return HTTP_MOVED_TEMPORARILY;
+ }
+ return HTTP_INTERNAL_SERVER_ERROR;
+}
+
+static void menu_header(request_rec *r, char *menu)
+{
+ ap_set_content_type(r, "text/html; charset=ISO-8859-1");
+
+ ap_rvputs(r, DOCTYPE_HTML_3_2, "<html><head>\n<title>Menu for ",
+ ap_escape_html(r->pool, r->uri),
+ "</title>\n</head><body>\n", NULL);
+
+ if (!strcasecmp(menu, "formatted")) {
+ ap_rvputs(r, "<h1>Menu for ",
+ ap_escape_html(r->pool, r->uri),
+ "</h1>\n<hr />\n\n", NULL);
+ }
+}
+
+static void menu_blank(request_rec *r, char *menu)
+{
+ if (!strcasecmp(menu, "formatted")) {
+ ap_rputs("\n", r);
+ }
+ else if (!strcasecmp(menu, "semiformatted")) {
+ ap_rputs("<br />\n", r);
+ }
+ else if (!strcasecmp(menu, "unformatted")) {
+ ap_rputs("\n", r);
+ }
+}
+
+static void menu_comment(request_rec *r, char *menu, char *comment)
+{
+ /* comments are ignored in the 'formatted' form */
+ if (!strcasecmp(menu, "formatted")) {
+ ap_rputs("\n", r); /* print just a newline if 'formatted' */
+ }
+ else if (!strcasecmp(menu, "semiformatted") && *comment) {
+ ap_rvputs(r, comment, "\n", NULL);
+ }
+ else if (!strcasecmp(menu, "unformatted") && *comment) {
+ ap_rvputs(r, comment, "\n", NULL);
+ }
+}
+
+static void menu_default(request_rec *r, const char *menu, const char *href, const char *text)
+{
+ char *ehref, *etext;
+ if (!strcasecmp(href, "error") || !strcasecmp(href, "nocontent")) {
+ return; /* don't print such lines, these aren't
+ really href's */
+ }
+
+ ehref = ap_escape_uri(r->pool, href);
+ etext = ap_escape_html(r->pool, text);
+
+ if (!strcasecmp(menu, "formatted")) {
+ ap_rvputs(r, "<pre>(Default) <a href=\"", ehref, "\">", etext,
+ "</a></pre>\n", NULL);
+ }
+ else if (!strcasecmp(menu, "semiformatted")) {
+ ap_rvputs(r, "<pre>(Default) <a href=\"", ehref, "\">", etext,
+ "</a></pre>\n", NULL);
+ }
+ else if (!strcasecmp(menu, "unformatted")) {
+ ap_rvputs(r, "<a href=\"", ehref, "\">", etext, "</a>", NULL);
+ }
+}
+
+static void menu_directive(request_rec *r, const char *menu, const char *href, const char *text)
+{
+ char *ehref, *etext;
+ if (!strcasecmp(href, "error") || !strcasecmp(href, "nocontent")) {
+ return; /* don't print such lines, as this isn't
+ really an href */
+ }
+
+ ehref = ap_escape_uri(r->pool, href);
+ etext = ap_escape_html(r->pool, text);
+
+ if (!strcasecmp(menu, "formatted")) {
+ ap_rvputs(r, "<pre> <a href=\"", ehref, "\">", etext,
+ "</a></pre>\n", NULL);
+ }
+ else if (!strcasecmp(menu, "semiformatted")) {
+ ap_rvputs(r, "<pre> <a href=\"", ehref, "\">", etext,
+ "</a></pre>\n", NULL);
+ }
+ else if (!strcasecmp(menu, "unformatted")) {
+ ap_rvputs(r, "<a href=\"", ehref, "\">", etext, "</a>", NULL);
+ }
+}
+
+static void menu_footer(request_rec *r)
+{
+ ap_rputs("\n\n</body>\n</html>\n", r); /* finish the menu */
+}
+
+static int imap_handler_internal(request_rec *r)
+{
+ char input[MAX_STRING_LEN];
+ char *directive;
+ char *value;
+ char *href_text;
+ const char *base;
+ const char *redirect;
+ const char *mapdflt;
+ char *closest = NULL;
+ double closest_yet = -1;
+ apr_status_t status;
+
+ double testpoint[2];
+ double pointarray[MAXVERTS + 1][2];
+ int vertex;
+
+ char *string_pos;
+ int showmenu = 0;
+
+ imap_conf_rec *icr;
+
+ char *imap_menu;
+ char *imap_default;
+ char *imap_base;
+
+ ap_configfile_t *imap;
+
+ icr = ap_get_module_config(r->per_dir_config, &imagemap_module);
+
+ imap_menu = icr->imap_menu ? icr->imap_menu : IMAP_MENU_DEFAULT;
+ imap_default = icr->imap_default
+ ? icr->imap_default : IMAP_DEFAULT_DEFAULT;
+ imap_base = icr->imap_base ? icr->imap_base : IMAP_BASE_DEFAULT;
+
+ status = ap_pcfg_openfile(&imap, r->pool, r->filename);
+
+ if (status != APR_SUCCESS) {
+ return HTTP_NOT_FOUND;
+ }
+
+ base = imap_url(r, NULL, imap_base); /* set base according
+ to default */
+ if (!base) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ mapdflt = imap_url(r, NULL, imap_default); /* and default to
+ global default */
+ if (!mapdflt) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ testpoint[X] = get_x_coord(r->args);
+ testpoint[Y] = get_y_coord(r->args);
+
+ if ((testpoint[X] == -1 || testpoint[Y] == -1) ||
+ (testpoint[X] == 0 && testpoint[Y] == 0)) {
+ /* if either is -1 or if both are zero (new Lynx) */
+ /* we don't have valid coordinates */
+ testpoint[X] = -1;
+ testpoint[Y] = -1;
+ if (strncasecmp(imap_menu, "none", 2)) {
+ showmenu = 1; /* show the menu _unless_ ImapMenu is
+ 'none' or 'no' */
+ }
+ }
+
+ if (showmenu) { /* send start of imagemap menu if
+ we're going to */
+ menu_header(r, imap_menu);
+ }
+
+ while (!ap_cfg_getline(input, sizeof(input), imap)) {
+ if (!input[0]) {
+ if (showmenu) {
+ menu_blank(r, imap_menu);
+ }
+ continue;
+ }
+
+ if (input[0] == '#') {
+ if (showmenu) {
+ menu_comment(r, imap_menu, input + 1);
+ }
+ continue;
+ } /* blank lines and comments are ignored
+ if we aren't printing a menu */
+
+ /* find the first two space delimited fields, recall that
+ * ap_cfg_getline has removed leading/trailing whitespace.
+ *
+ * note that we're tokenizing as we go... if we were to use the
+ * ap_getword() class of functions we would end up allocating extra
+ * memory for every line of the map file
+ */
+ string_pos = input;
+ if (!*string_pos) { /* need at least two fields */
+ goto need_2_fields;
+ }
+
+ directive = string_pos;
+ while (*string_pos && !apr_isspace(*string_pos)) { /* past directive */
+ ++string_pos;
+ }
+ if (!*string_pos) { /* need at least two fields */
+ goto need_2_fields;
+ }
+ *string_pos++ = '\0';
+
+ if (!*string_pos) { /* need at least two fields */
+ goto need_2_fields;
+ }
+ while (apr_isspace(*string_pos)) { /* past whitespace */
+ ++string_pos;
+ }
+
+ value = string_pos;
+ while (*string_pos && !apr_isspace(*string_pos)) { /* past value */
+ ++string_pos;
+ }
+ if (apr_isspace(*string_pos)) {
+ *string_pos++ = '\0';
+ }
+ else {
+ /* end of input, don't advance past it */
+ *string_pos = '\0';
+ }
+
+ if (!strncasecmp(directive, "base", 4)) { /* base, base_uri */
+ base = imap_url(r, NULL, value);
+ if (!base) {
+ goto menu_bail;
+ }
+ continue; /* base is never printed to a menu */
+ }
+
+ read_quoted(&string_pos, &href_text);
+
+ if (!strcasecmp(directive, "default")) { /* default */
+ mapdflt = imap_url(r, NULL, value);
+ if (!mapdflt) {
+ goto menu_bail;
+ }
+ if (showmenu) { /* print the default if there's a menu */
+ redirect = imap_url(r, base, mapdflt);
+ if (!redirect) {
+ goto menu_bail;
+ }
+ menu_default(r, imap_menu, redirect,
+ href_text ? href_text : mapdflt);
+ }
+ continue;
+ }
+
+ vertex = 0;
+ while (vertex < MAXVERTS &&
+ sscanf(string_pos, "%lf%*[, ]%lf",
+ &pointarray[vertex][X], &pointarray[vertex][Y]) == 2) {
+ /* Now skip what we just read... we can't use ANSIism %n */
+ while (apr_isspace(*string_pos)) { /* past whitespace */
+ string_pos++;
+ }
+ while (apr_isdigit(*string_pos)) { /* and the 1st number */
+ string_pos++;
+ }
+ string_pos++; /* skip the ',' */
+ while (apr_isspace(*string_pos)) { /* past any more whitespace */
+ string_pos++;
+ }
+ while (apr_isdigit(*string_pos)) { /* 2nd number */
+ string_pos++;
+ }
+ vertex++;
+ } /* so long as there are more vertices to
+ read, and we have room, read them in.
+ We start where we left off of the last
+ sscanf, not at the beginning. */
+
+ pointarray[vertex][X] = -1; /* signals the end of vertices */
+
+ if (showmenu) {
+ if (!href_text) {
+ read_quoted(&string_pos, &href_text); /* href text could
+ be here instead */
+ }
+ redirect = imap_url(r, base, value);
+ if (!redirect) {
+ goto menu_bail;
+ }
+ menu_directive(r, imap_menu, redirect,
+ href_text ? href_text : value);
+ continue;
+ }
+ /* note that we don't make it past here if we are making a menu */
+
+ if (testpoint[X] == -1 || pointarray[0][X] == -1) {
+ continue; /* don't try the following tests if testpoints
+ are invalid, or if there are no
+ coordinates */
+ }
+
+ if (!strcasecmp(directive, "poly")) { /* poly */
+
+ if (pointinpoly(testpoint, pointarray)) {
+ ap_cfg_closefile(imap);
+ redirect = imap_url(r, base, value);
+ if (!redirect) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ return (imap_reply(r, redirect));
+ }
+ continue;
+ }
+
+ if (!strcasecmp(directive, "circle")) { /* circle */
+
+ if (pointincircle(testpoint, pointarray)) {
+ ap_cfg_closefile(imap);
+ redirect = imap_url(r, base, value);
+ if (!redirect) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ return (imap_reply(r, redirect));
+ }
+ continue;
+ }
+
+ if (!strcasecmp(directive, "rect")) { /* rect */
+
+ if (pointinrect(testpoint, pointarray)) {
+ ap_cfg_closefile(imap);
+ redirect = imap_url(r, base, value);
+ if (!redirect) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ return (imap_reply(r, redirect));
+ }
+ continue;
+ }
+
+ if (!strcasecmp(directive, "point")) { /* point */
+
+ if (is_closer(testpoint, pointarray, &closest_yet)) {
+ closest = apr_pstrdup(r->pool, value);
+ }
+
+ continue;
+ } /* move on to next line whether it's
+ closest or not */
+
+ } /* nothing matched, so we get another line! */
+
+ ap_cfg_closefile(imap); /* we are done with the map file; close it */
+
+ if (showmenu) {
+ menu_footer(r); /* finish the menu and we are done */
+ return OK;
+ }
+
+ if (closest) { /* if a 'point' directive has been seen */
+ redirect = imap_url(r, base, closest);
+ if (!redirect) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ return (imap_reply(r, redirect));
+ }
+
+ if (mapdflt) { /* a default should be defined, even if
+ only 'nocontent' */
+ redirect = imap_url(r, base, mapdflt);
+ if (!redirect) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ return (imap_reply(r, redirect));
+ }
+
+ return HTTP_INTERNAL_SERVER_ERROR; /* If we make it this far,
+ we failed. They lose! */
+
+need_2_fields:
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00679)
+ "map file %s, line %d syntax error: requires at "
+ "least two fields", r->uri, imap->line_number);
+ /* fall through */
+menu_bail:
+ ap_cfg_closefile(imap);
+ if (showmenu) {
+ /* There's not much else we can do ... we've already sent the headers
+ * to the client.
+ */
+ ap_rputs("\n\n[an internal server error occured]\n", r);
+ menu_footer(r);
+ return OK;
+ }
+ return HTTP_INTERNAL_SERVER_ERROR;
+}
+
+static int imap_handler(request_rec *r)
+{
+ /* Optimization: skip the allocation of large local variables on the
+ * stack (in imap_handler_internal()) on requests that aren't using
+ * imagemaps
+ */
+ if (r->method_number != M_GET || (strcmp(r->handler,IMAP_MAGIC_TYPE)
+ && strcmp(r->handler, "imap-file"))) {
+ return DECLINED;
+ }
+ else {
+ return imap_handler_internal(r);
+ }
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_hook_handler(imap_handler,NULL,NULL,APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(imagemap) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_imap_dir_config, /* dir config creater */
+ merge_imap_dir_configs, /* dir merger --- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ imap_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/mappers/mod_imagemap.dep b/modules/mappers/mod_imagemap.dep
new file mode 100644
index 0000000..357895e
--- /dev/null
+++ b/modules/mappers/mod_imagemap.dep
@@ -0,0 +1,60 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_imagemap.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_imagemap.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_core.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/mappers/mod_imagemap.dsp b/modules/mappers/mod_imagemap.dsp
new file mode 100644
index 0000000..88a85c8
--- /dev/null
+++ b/modules/mappers/mod_imagemap.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_imagemap" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_imagemap - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_imagemap.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_imagemap.mak" CFG="mod_imagemap - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_imagemap - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_imagemap - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_imagemap - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_imagemap_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_imagemap.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_imagemap.so" /d LONG_NAME="imagemap_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_imagemap.so" /base:@..\..\os\win32\BaseAddr.ref,mod_imagemap.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_imagemap.so" /base:@..\..\os\win32\BaseAddr.ref,mod_imagemap.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_imagemap.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_imagemap - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_imagemap_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_imagemap.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_imagemap.so" /d LONG_NAME="imagemap_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_imagemap.so" /base:@..\..\os\win32\BaseAddr.ref,mod_imagemap.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_imagemap.so" /base:@..\..\os\win32\BaseAddr.ref,mod_imagemap.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_imagemap.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_imagemap - Win32 Release"
+# Name "mod_imagemap - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_imagemap.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/mappers/mod_imagemap.exp b/modules/mappers/mod_imagemap.exp
new file mode 100644
index 0000000..1e0e0b8
--- /dev/null
+++ b/modules/mappers/mod_imagemap.exp
@@ -0,0 +1 @@
+imap_module
diff --git a/modules/mappers/mod_imagemap.mak b/modules/mappers/mod_imagemap.mak
new file mode 100644
index 0000000..da50a61
--- /dev/null
+++ b/modules/mappers/mod_imagemap.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_imagemap.dsp
+!IF "$(CFG)" == ""
+CFG=mod_imagemap - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_imagemap - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_imagemap - Win32 Release" && "$(CFG)" != "mod_imagemap - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_imagemap.mak" CFG="mod_imagemap - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_imagemap - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_imagemap - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_imagemap - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_imagemap.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_imagemap.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_imagemap.obj"
+ -@erase "$(INTDIR)\mod_imagemap.res"
+ -@erase "$(INTDIR)\mod_imagemap_src.idb"
+ -@erase "$(INTDIR)\mod_imagemap_src.pdb"
+ -@erase "$(OUTDIR)\mod_imagemap.exp"
+ -@erase "$(OUTDIR)\mod_imagemap.lib"
+ -@erase "$(OUTDIR)\mod_imagemap.pdb"
+ -@erase "$(OUTDIR)\mod_imagemap.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_imagemap_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_imagemap.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_imagemap.so" /d LONG_NAME="imagemap_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_imagemap.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_imagemap.pdb" /debug /out:"$(OUTDIR)\mod_imagemap.so" /implib:"$(OUTDIR)\mod_imagemap.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_imagemap.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_imagemap.obj" \
+ "$(INTDIR)\mod_imagemap.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_imagemap.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_imagemap.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_imagemap.so"
+ if exist .\Release\mod_imagemap.so.manifest mt.exe -manifest .\Release\mod_imagemap.so.manifest -outputresource:.\Release\mod_imagemap.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_imagemap - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_imagemap.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_imagemap.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_imagemap.obj"
+ -@erase "$(INTDIR)\mod_imagemap.res"
+ -@erase "$(INTDIR)\mod_imagemap_src.idb"
+ -@erase "$(INTDIR)\mod_imagemap_src.pdb"
+ -@erase "$(OUTDIR)\mod_imagemap.exp"
+ -@erase "$(OUTDIR)\mod_imagemap.lib"
+ -@erase "$(OUTDIR)\mod_imagemap.pdb"
+ -@erase "$(OUTDIR)\mod_imagemap.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_imagemap_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_imagemap.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_imagemap.so" /d LONG_NAME="imagemap_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_imagemap.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_imagemap.pdb" /debug /out:"$(OUTDIR)\mod_imagemap.so" /implib:"$(OUTDIR)\mod_imagemap.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_imagemap.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_imagemap.obj" \
+ "$(INTDIR)\mod_imagemap.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_imagemap.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_imagemap.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_imagemap.so"
+ if exist .\Debug\mod_imagemap.so.manifest mt.exe -manifest .\Debug\mod_imagemap.so.manifest -outputresource:.\Debug\mod_imagemap.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_imagemap.dep")
+!INCLUDE "mod_imagemap.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_imagemap.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_imagemap - Win32 Release" || "$(CFG)" == "mod_imagemap - Win32 Debug"
+
+!IF "$(CFG)" == "mod_imagemap - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\mappers"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ELSEIF "$(CFG)" == "mod_imagemap - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\mappers"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_imagemap - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\mappers"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ELSEIF "$(CFG)" == "mod_imagemap - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\mappers"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_imagemap - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\mappers"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\mappers"
+
+!ELSEIF "$(CFG)" == "mod_imagemap - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\mappers"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\mappers"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_imagemap - Win32 Release"
+
+
+"$(INTDIR)\mod_imagemap.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_imagemap.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_imagemap.so" /d LONG_NAME="imagemap_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_imagemap - Win32 Debug"
+
+
+"$(INTDIR)\mod_imagemap.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_imagemap.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_imagemap.so" /d LONG_NAME="imagemap_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_imagemap.c
+
+"$(INTDIR)\mod_imagemap.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/mappers/mod_negotiation.c b/modules/mappers/mod_negotiation.c
new file mode 100644
index 0000000..c056b28
--- /dev/null
+++ b/modules/mappers/mod_negotiation.c
@@ -0,0 +1,3223 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * mod_negotiation.c: keeps track of MIME types the client is willing to
+ * accept, and contains code to handle type arbitration.
+ *
+ * rst
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_file_io.h"
+#include "apr_lib.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_request.h"
+#include "http_protocol.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "util_script.h"
+
+
+#define MAP_FILE_MAGIC_TYPE "application/x-type-map"
+
+/* Commands --- configuring document caching on a per (virtual?)
+ * server basis...
+ */
+
+typedef struct {
+ int forcelangpriority;
+ apr_array_header_t *language_priority;
+} neg_dir_config;
+
+/* forcelangpriority flags
+ */
+#define FLP_UNDEF 0 /* Same as FLP_DEFAULT, but base overrides */
+#define FLP_NONE 1 /* Return 406, HTTP_NOT_ACCEPTABLE */
+#define FLP_PREFER 2 /* Use language_priority rather than MC */
+#define FLP_FALLBACK 4 /* Use language_priority rather than NA */
+
+#define FLP_DEFAULT FLP_PREFER
+
+/* env evaluation
+ */
+#define DISCARD_ALL_ENCODINGS 1 /* no-gzip */
+#define DISCARD_ALL_BUT_HTML 2 /* gzip-only-text/html */
+
+module AP_MODULE_DECLARE_DATA negotiation_module;
+
+static void *create_neg_dir_config(apr_pool_t *p, char *dummy)
+{
+ neg_dir_config *new = (neg_dir_config *) apr_palloc(p,
+ sizeof(neg_dir_config));
+
+ new->forcelangpriority = FLP_UNDEF;
+ new->language_priority = NULL;
+ return new;
+}
+
+static void *merge_neg_dir_configs(apr_pool_t *p, void *basev, void *addv)
+{
+ neg_dir_config *base = (neg_dir_config *) basev;
+ neg_dir_config *add = (neg_dir_config *) addv;
+ neg_dir_config *new = (neg_dir_config *) apr_palloc(p,
+ sizeof(neg_dir_config));
+
+ /* give priority to the config in the subdirectory */
+ new->forcelangpriority = (add->forcelangpriority != FLP_UNDEF)
+ ? add->forcelangpriority
+ : base->forcelangpriority;
+ new->language_priority = add->language_priority
+ ? add->language_priority
+ : base->language_priority;
+ return new;
+}
+
+static const char *set_language_priority(cmd_parms *cmd, void *n_,
+ const char *lang)
+{
+ neg_dir_config *n = n_;
+ const char **langp;
+
+ if (!n->language_priority)
+ n->language_priority = apr_array_make(cmd->pool, 4, sizeof(char *));
+
+ langp = (const char **) apr_array_push(n->language_priority);
+ *langp = lang;
+ return NULL;
+}
+
+static const char *set_force_priority(cmd_parms *cmd, void *n_, const char *w)
+{
+ neg_dir_config *n = n_;
+
+ if (!strcasecmp(w, "None")) {
+ if (n->forcelangpriority & ~FLP_NONE) {
+ return "Cannot combine ForceLanguagePriority options with None";
+ }
+ n->forcelangpriority = FLP_NONE;
+ }
+ else if (!strcasecmp(w, "Prefer")) {
+ if (n->forcelangpriority & FLP_NONE) {
+ return "Cannot combine ForceLanguagePriority options None and "
+ "Prefer";
+ }
+ n->forcelangpriority |= FLP_PREFER;
+ }
+ else if (!strcasecmp(w, "Fallback")) {
+ if (n->forcelangpriority & FLP_NONE) {
+ return "Cannot combine ForceLanguagePriority options None and "
+ "Fallback";
+ }
+ n->forcelangpriority |= FLP_FALLBACK;
+ }
+ else {
+ return apr_pstrcat(cmd->pool, "Invalid ForceLanguagePriority option ",
+ w, NULL);
+ }
+
+ return NULL;
+}
+
+static const char *cache_negotiated_docs(cmd_parms *cmd, void *dummy,
+ int arg)
+{
+ ap_set_module_config(cmd->server->module_config, &negotiation_module,
+ (arg ? "Cache" : NULL));
+ return NULL;
+}
+
+static int do_cache_negotiated_docs(server_rec *s)
+{
+ return (ap_get_module_config(s->module_config,
+ &negotiation_module) != NULL);
+}
+
+static const command_rec negotiation_cmds[] =
+{
+ AP_INIT_FLAG("CacheNegotiatedDocs", cache_negotiated_docs, NULL, RSRC_CONF,
+ "Either 'on' or 'off' (default)"),
+ AP_INIT_ITERATE("LanguagePriority", set_language_priority, NULL,
+ OR_FILEINFO,
+ "space-delimited list of MIME language abbreviations"),
+ AP_INIT_ITERATE("ForceLanguagePriority", set_force_priority, NULL,
+ OR_FILEINFO,
+ "Force LanguagePriority elections, either None, or "
+ "Fallback and/or Prefer"),
+ {NULL}
+};
+
+/*
+ * Record of available info on a media type specified by the client
+ * (we also use 'em for encodings and languages)
+ */
+
+typedef struct accept_rec {
+ char *name; /* MUST be lowercase */
+ float quality;
+ float level;
+ char *charset; /* for content-type only */
+} accept_rec;
+
+/*
+ * Record of available info on a particular variant
+ *
+ * Note that a few of these fields are updated by the actual negotiation
+ * code. These are:
+ *
+ * level_matched --- initialized to zero. Set to the value of level
+ * if the client actually accepts this media type at that
+ * level (and *not* if it got in on a wildcard). See level_cmp
+ * below.
+ * mime_stars -- initialized to zero. Set to the number of stars
+ * present in the best matching Accept header element.
+ * 1 for star/star, 2 for type/star and 3 for
+ * type/subtype.
+ *
+ * definite -- initialized to 1. Set to 0 if there is a match which
+ * makes the variant non-definite according to the rules
+ * in rfc2296.
+ */
+
+typedef struct var_rec {
+ request_rec *sub_req; /* May be NULL (is, for map files) */
+ const char *mime_type; /* MUST be lowercase */
+ const char *file_name; /* Set to 'this' (for map file body content) */
+ apr_off_t body; /* Only for map file body content */
+ const char *content_encoding;
+ apr_array_header_t *content_languages; /* list of lang. for this variant */
+ const char *content_charset;
+ const char *description;
+
+ /* The next five items give the quality values for the dimensions
+ * of negotiation for this variant. They are obtained from the
+ * appropriate header lines, except for source_quality, which
+ * is obtained from the variant itself (the 'qs' parameter value
+ * from the variant's mime-type). Apart from source_quality,
+ * these values are set when we find the quality for each variant
+ * (see best_match()). source_quality is set from the 'qs' parameter
+ * of the variant description or mime type: see set_mime_fields().
+ */
+ float lang_quality; /* quality of this variant's language */
+ float encoding_quality; /* ditto encoding */
+ float charset_quality; /* ditto charset */
+ float mime_type_quality; /* ditto media type */
+ float source_quality; /* source quality for this variant */
+
+ /* Now some special values */
+ float level; /* Auxiliary to content-type... */
+ apr_off_t bytes; /* content length, if known */
+ int lang_index; /* Index into LanguagePriority list */
+ int is_pseudo_html; /* text/html, *or* the INCLUDES_MAGIC_TYPEs */
+
+ /* Above are all written-once properties of the variant. The
+ * three fields below are changed during negotiation:
+ */
+
+ float level_matched;
+ int mime_stars;
+ int definite;
+} var_rec;
+
+/* Something to carry around the state of negotiation (and to keep
+ * all of this thread-safe)...
+ */
+
+typedef struct {
+ apr_pool_t *pool;
+ request_rec *r;
+ neg_dir_config *conf;
+ char *dir_name;
+ int accept_q; /* 1 if an Accept item has a q= param */
+ float default_lang_quality; /* fiddle lang q for variants with no lang */
+
+ /* the array pointers below are NULL if the corresponding accept
+ * headers are not present
+ */
+ apr_array_header_t *accepts; /* accept_recs */
+ apr_array_header_t *accept_encodings; /* accept_recs */
+ apr_array_header_t *accept_charsets; /* accept_recs */
+ apr_array_header_t *accept_langs; /* accept_recs */
+
+ apr_array_header_t *avail_vars; /* available variants */
+
+ int count_multiviews_variants; /* number of variants found on disk */
+
+ int is_transparent; /* 1 if this resource is trans. negotiable */
+
+ int dont_fiddle_headers; /* 1 if we may not fiddle with accept hdrs */
+ int ua_supports_trans; /* 1 if ua supports trans negotiation */
+ int send_alternates; /* 1 if we want to send an Alternates header */
+ int may_choose; /* 1 if we may choose a variant for the client */
+ int use_rvsa; /* 1 if we must use RVSA/1.0 negotiation algo */
+} negotiation_state;
+
+/* A few functions to manipulate var_recs.
+ * Cleaning out the fields...
+ */
+
+static void clean_var_rec(var_rec *mime_info)
+{
+ mime_info->sub_req = NULL;
+ mime_info->mime_type = "";
+ mime_info->file_name = "";
+ mime_info->body = 0;
+ mime_info->content_encoding = NULL;
+ mime_info->content_languages = NULL;
+ mime_info->content_charset = "";
+ mime_info->description = "";
+
+ mime_info->is_pseudo_html = 0;
+ mime_info->level = 0.0f;
+ mime_info->level_matched = 0.0f;
+ mime_info->bytes = -1;
+ mime_info->lang_index = -1;
+ mime_info->mime_stars = 0;
+ mime_info->definite = 1;
+
+ mime_info->charset_quality = 1.0f;
+ mime_info->encoding_quality = 1.0f;
+ mime_info->lang_quality = 1.0f;
+ mime_info->mime_type_quality = 1.0f;
+ mime_info->source_quality = 0.0f;
+}
+
+/* Initializing the relevant fields of a variant record from the
+ * accept_info read out of its content-type, one way or another.
+ */
+
+static void set_mime_fields(var_rec *var, accept_rec *mime_info)
+{
+ var->mime_type = mime_info->name;
+ var->source_quality = mime_info->quality;
+ var->level = mime_info->level;
+ var->content_charset = mime_info->charset;
+
+ var->is_pseudo_html = (!strcmp(var->mime_type, "text/html")
+ || !strcmp(var->mime_type, INCLUDES_MAGIC_TYPE)
+ || !strcmp(var->mime_type, INCLUDES_MAGIC_TYPE3));
+}
+
+/* Create a variant list validator in r using info from vlistr. */
+
+static void set_vlist_validator(request_rec *r, request_rec *vlistr)
+{
+ /* Calculating the variant list validator is similar to
+ * calculating an etag for the source of the variant list
+ * information, so we use ap_make_etag(). Note that this
+ * validator can be 'weak' in extreme case.
+ */
+ ap_update_mtime(vlistr, vlistr->finfo.mtime);
+ r->vlist_validator = ap_make_etag(vlistr, 0);
+
+ /* ap_set_etag will later take r->vlist_validator into account
+ * when creating the etag header
+ */
+}
+
+
+/*****************************************************************
+ *
+ * Parsing (lists of) media types and their parameters, as seen in
+ * HTTPD header lines and elsewhere.
+ */
+
+/*
+ * parse quality value. atof(3) is not well-usable here, because it
+ * depends on the locale (argh).
+ *
+ * However, RFC 2616 states:
+ * 3.9 Quality Values
+ *
+ * [...] HTTP/1.1 applications MUST NOT generate more than three digits
+ * after the decimal point. User configuration of these values SHOULD also
+ * be limited in this fashion.
+ *
+ * qvalue = ( "0" [ "." 0*3DIGIT ] )
+ * | ( "1" [ "." 0*3("0") ] )
+ *
+ * This is quite easy. If the supplied string doesn't match the above
+ * definition (loosely), we simply return 1 (same as if there's no qvalue)
+ */
+
+static float atoq(const char *string)
+{
+ if (!string || !*string) {
+ return 1.0f;
+ }
+
+ while (apr_isspace(*string)) {
+ ++string;
+ }
+
+ /* be tolerant and accept qvalues without leading zero
+ * (also for backwards compat, where atof() was in use)
+ */
+ if (*string != '.' && *string++ != '0') {
+ return 1.0f;
+ }
+
+ if (*string == '.') {
+ /* better only one division later, than dealing with fscking
+ * IEEE format 0.1 factors ...
+ */
+ int i = 0;
+
+ if (*++string >= '0' && *string <= '9') {
+ i += (*string - '0') * 100;
+
+ if (*++string >= '0' && *string <= '9') {
+ i += (*string - '0') * 10;
+
+ if (*++string > '0' && *string <= '9') {
+ i += (*string - '0');
+ }
+ }
+ }
+
+ return (float)i / 1000.0f;
+ }
+
+ return 0.0f;
+}
+
+/*
+ * Get a single mime type entry --- one media type and parameters;
+ * enter the values we recognize into the argument accept_rec
+ */
+
+static const char *get_entry(apr_pool_t *p, accept_rec *result,
+ const char *accept_line)
+{
+ result->quality = 1.0f;
+ result->level = 0.0f;
+ result->charset = "";
+
+ /*
+ * Note that this handles what I gather is the "old format",
+ *
+ * Accept: text/html text/plain moo/zot
+ *
+ * without any compatibility kludges --- if the token after the
+ * MIME type begins with a semicolon, we know we're looking at parms,
+ * otherwise, we know we aren't. (So why all the pissing and moaning
+ * in the CERN server code? I must be missing something).
+ */
+
+ result->name = ap_get_token(p, &accept_line, 0);
+ ap_str_tolower(result->name); /* You want case insensitive,
+ * you'll *get* case insensitive.
+ */
+
+ /* KLUDGE!!! Default HTML to level 2.0 unless the browser
+ * *explicitly* says something else.
+ */
+
+ if (!strcmp(result->name, "text/html") && (result->level == 0.0)) {
+ result->level = 2.0f;
+ }
+ else if (!strcmp(result->name, INCLUDES_MAGIC_TYPE)) {
+ result->level = 2.0f;
+ }
+ else if (!strcmp(result->name, INCLUDES_MAGIC_TYPE3)) {
+ result->level = 3.0f;
+ }
+
+ while (*accept_line == ';') {
+ /* Parameters ... */
+
+ char *parm;
+ char *cp;
+ char *end;
+
+ ++accept_line;
+ parm = ap_get_token(p, &accept_line, 1);
+
+ /* Look for 'var = value' --- and make sure the var is in lcase. */
+
+ for (cp = parm; (*cp && !apr_isspace(*cp) && *cp != '='); ++cp) {
+ *cp = apr_tolower(*cp);
+ }
+
+ if (!*cp) {
+ continue; /* No '='; just ignore it. */
+ }
+
+ *cp++ = '\0'; /* Delimit var */
+ while (apr_isspace(*cp) || *cp == '=') {
+ ++cp;
+ }
+
+ if (*cp == '"') {
+ ++cp;
+ for (end = cp;
+ (*end && *end != '\n' && *end != '\r' && *end != '\"');
+ end++);
+ }
+ else {
+ for (end = cp; (*end && !apr_isspace(*end)); end++);
+ }
+ if (*end) {
+ *end = '\0'; /* strip ending quote or return */
+ }
+ ap_str_tolower(cp);
+
+ if (parm[0] == 'q'
+ && (parm[1] == '\0' || (parm[1] == 's' && parm[2] == '\0'))) {
+ result->quality = atoq(cp);
+ }
+ else if (parm[0] == 'l' && !strcmp(&parm[1], "evel")) {
+ result->level = (float)atoi(cp);
+ }
+ else if (!strcmp(parm, "charset")) {
+ result->charset = cp;
+ }
+ }
+
+ if (*accept_line == ',') {
+ ++accept_line;
+ }
+
+ return accept_line;
+}
+
+/*****************************************************************
+ *
+ * Dealing with header lines ...
+ *
+ * Accept, Accept-Charset, Accept-Language and Accept-Encoding
+ * are handled by do_header_line() - they all have the same
+ * basic structure of a list of items of the format
+ * name; q=N; charset=TEXT
+ *
+ * where charset is only valid in Accept.
+ */
+
+static apr_array_header_t *do_header_line(apr_pool_t *p,
+ const char *accept_line)
+{
+ apr_array_header_t *accept_recs;
+
+ if (!accept_line) {
+ return NULL;
+ }
+
+ accept_recs = apr_array_make(p, 40, sizeof(accept_rec));
+
+ while (*accept_line) {
+ accept_rec *new = (accept_rec *) apr_array_push(accept_recs);
+ accept_line = get_entry(p, new, accept_line);
+ }
+
+ return accept_recs;
+}
+
+/* Given the text of the Content-Languages: line from the var map file,
+ * return an array containing the languages of this variant
+ */
+
+static apr_array_header_t *do_languages_line(apr_pool_t *p,
+ const char **lang_line)
+{
+ apr_array_header_t *lang_recs = apr_array_make(p, 2, sizeof(char *));
+
+ if (!lang_line) {
+ return lang_recs;
+ }
+
+ while (**lang_line) {
+ char **new = (char **) apr_array_push(lang_recs);
+ *new = ap_get_token(p, lang_line, 0);
+ ap_str_tolower(*new);
+ if (**lang_line == ',' || **lang_line == ';') {
+ ++(*lang_line);
+ }
+ }
+
+ return lang_recs;
+}
+
+/*****************************************************************
+ *
+ * Handling header lines from clients...
+ */
+
+static negotiation_state *parse_accept_headers(request_rec *r)
+{
+ negotiation_state *new =
+ (negotiation_state *) apr_pcalloc(r->pool, sizeof(negotiation_state));
+ accept_rec *elts;
+ apr_table_t *hdrs = r->headers_in;
+ int i;
+
+ new->pool = r->pool;
+ new->r = r;
+ new->conf = (neg_dir_config *)ap_get_module_config(r->per_dir_config,
+ &negotiation_module);
+
+ new->dir_name = ap_make_dirstr_parent(r->pool, r->filename);
+
+ new->accepts = do_header_line(r->pool, apr_table_get(hdrs, "Accept"));
+
+ /* calculate new->accept_q value */
+ if (new->accepts) {
+ elts = (accept_rec *) new->accepts->elts;
+
+ for (i = 0; i < new->accepts->nelts; ++i) {
+ if (elts[i].quality < 1.0) {
+ new->accept_q = 1;
+ }
+ }
+ }
+
+ new->accept_encodings =
+ do_header_line(r->pool, apr_table_get(hdrs, "Accept-Encoding"));
+ new->accept_langs =
+ do_header_line(r->pool, apr_table_get(hdrs, "Accept-Language"));
+ new->accept_charsets =
+ do_header_line(r->pool, apr_table_get(hdrs, "Accept-Charset"));
+
+ /* This is possibly overkill for some servers, heck, we have
+ * only 33 index.html variants in docs/docroot (today).
+ * Make this configurable?
+ */
+ new->avail_vars = apr_array_make(r->pool, 40, sizeof(var_rec));
+
+ return new;
+}
+
+
+static void parse_negotiate_header(request_rec *r, negotiation_state *neg)
+{
+ const char *negotiate = apr_table_get(r->headers_in, "Negotiate");
+ char *tok;
+
+ /* First, default to no TCN, no Alternates, and the original Apache
+ * negotiation algorithm with fiddles for broken browser configs.
+ *
+ * To save network bandwidth, we do not configure to send an
+ * Alternates header to the user agent by default. User
+ * agents that want an Alternates header for agent-driven
+ * negotiation will have to request it by sending an
+ * appropriate Negotiate header.
+ */
+ neg->ua_supports_trans = 0;
+ neg->send_alternates = 0;
+ neg->may_choose = 1;
+ neg->use_rvsa = 0;
+ neg->dont_fiddle_headers = 0;
+
+ if (!negotiate)
+ return;
+
+ if (strcmp(negotiate, "trans") == 0) {
+ /* Lynx 2.7 and 2.8 send 'negotiate: trans' even though they
+ * do not support transparent content negotiation, so for Lynx we
+ * ignore the negotiate header when its contents are exactly "trans".
+ * If future versions of Lynx ever need to say 'negotiate: trans',
+ * they can send the equivalent 'negotiate: trans, trans' instead
+ * to avoid triggering the workaround below.
+ */
+ const char *ua = apr_table_get(r->headers_in, "User-Agent");
+
+ if (ua && (strncmp(ua, "Lynx", 4) == 0))
+ return;
+ }
+
+ neg->may_choose = 0; /* An empty Negotiate would require 300 response */
+
+ while ((tok = ap_get_list_item(neg->pool, &negotiate)) != NULL) {
+
+ if (strcmp(tok, "trans") == 0 ||
+ strcmp(tok, "vlist") == 0 ||
+ strcmp(tok, "guess-small") == 0 ||
+ apr_isdigit(tok[0]) ||
+ strcmp(tok, "*") == 0) {
+
+ /* The user agent supports transparent negotiation */
+ neg->ua_supports_trans = 1;
+
+ /* Send-alternates could be configurable, but note
+ * that it must be 1 if we have 'vlist' in the
+ * negotiate header.
+ */
+ neg->send_alternates = 1;
+
+ if (strcmp(tok, "1.0") == 0) {
+ /* we may use the RVSA/1.0 algorithm, configure for it */
+ neg->may_choose = 1;
+ neg->use_rvsa = 1;
+ neg->dont_fiddle_headers = 1;
+ }
+ else if (tok[0] == '*') {
+ /* we may use any variant selection algorithm, configure
+ * to use the Apache algorithm
+ */
+ neg->may_choose = 1;
+
+ /* We disable header fiddles on the assumption that a
+ * client sending Negotiate knows how to send correct
+ * headers which don't need fiddling.
+ */
+ neg->dont_fiddle_headers = 1;
+ }
+ }
+ }
+
+#ifdef NEG_DEBUG
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00680)
+ "dont_fiddle_headers=%d use_rvsa=%d ua_supports_trans=%d "
+ "send_alternates=%d, may_choose=%d",
+ neg->dont_fiddle_headers, neg->use_rvsa,
+ neg->ua_supports_trans, neg->send_alternates, neg->may_choose);
+#endif
+
+}
+
+/* Sometimes clients will give us no Accept info at all; this routine sets
+ * up the standard default for that case, and also arranges for us to be
+ * willing to run a CGI script if we find one. (In fact, we set up to
+ * dramatically prefer CGI scripts in cases where that's appropriate,
+ * e.g., POST or when URI includes query args or extra path info).
+ */
+static void maybe_add_default_accepts(negotiation_state *neg,
+ int prefer_scripts)
+{
+ accept_rec *new_accept;
+
+ if (!neg->accepts) {
+ neg->accepts = apr_array_make(neg->pool, 4, sizeof(accept_rec));
+
+ new_accept = (accept_rec *) apr_array_push(neg->accepts);
+
+ new_accept->name = "*/*";
+ new_accept->quality = 1.0f;
+ new_accept->level = 0.0f;
+ }
+
+ new_accept = (accept_rec *) apr_array_push(neg->accepts);
+
+ new_accept->name = CGI_MAGIC_TYPE;
+ if (neg->use_rvsa) {
+ new_accept->quality = 0;
+ }
+ else {
+ new_accept->quality = prefer_scripts ? 2.0f : 0.001f;
+ }
+ new_accept->level = 0.0f;
+}
+
+/*****************************************************************
+ *
+ * Parsing type-map files, in Roy's meta/http format augmented with
+ * #-comments.
+ */
+
+/* Reading RFC822-style header lines, ignoring #-comments and
+ * handling continuations.
+ */
+
+enum header_state {
+ header_eof, header_seen, header_sep
+};
+
+static enum header_state get_header_line(char *buffer, int len, apr_file_t *map)
+{
+ char *buf_end = buffer + len;
+ char *cp;
+ char c;
+
+ /* Get a noncommented line */
+
+ do {
+ if (apr_file_gets(buffer, MAX_STRING_LEN, map) != APR_SUCCESS) {
+ return header_eof;
+ }
+ } while (buffer[0] == '#');
+
+ /* If blank, just return it --- this ends information on this variant */
+
+ for (cp = buffer; apr_isspace(*cp); ++cp) {
+ continue;
+ }
+
+ if (*cp == '\0') {
+ return header_sep;
+ }
+
+ /* If non-blank, go looking for header lines, but note that we still
+ * have to treat comments specially...
+ */
+
+ cp += strlen(cp);
+
+ /* We need to shortcut the rest of this block following the Body:
+ * tag - we will not look for continutation after this line.
+ */
+ if (!ap_cstr_casecmpn(buffer, "Body:", 5))
+ return header_seen;
+
+ while (apr_file_getc(&c, map) != APR_EOF) {
+ if (c == '#') {
+ /* Comment line */
+ while (apr_file_getc(&c, map) != APR_EOF && c != '\n') {
+ continue;
+ }
+ }
+ else if (apr_isspace(c)) {
+ /* Leading whitespace. POSSIBLE continuation line
+ * Also, possibly blank --- if so, we ungetc() the final newline
+ * so that we will pick up the blank line the next time 'round.
+ */
+
+ while (c != '\n' && apr_isspace(c)) {
+ if (apr_file_getc(&c, map) != APR_SUCCESS) {
+ break;
+ }
+ }
+
+ apr_file_ungetc(c, map);
+
+ if (c == '\n') {
+ return header_seen; /* Blank line */
+ }
+
+ /* Continuation */
+
+ while ( cp < buf_end - 2
+ && (apr_file_getc(&c, map)) != APR_EOF
+ && c != '\n') {
+ *cp++ = c;
+ }
+
+ *cp++ = '\n';
+ *cp = '\0';
+ }
+ else {
+
+ /* Line beginning with something other than whitespace */
+
+ apr_file_ungetc(c, map);
+ return header_seen;
+ }
+ }
+
+ return header_seen;
+}
+
+static apr_off_t get_body(char *buffer, apr_size_t *len, const char *tag,
+ apr_file_t *map)
+{
+ char *endbody;
+ apr_size_t bodylen;
+ apr_off_t pos;
+
+
+ /* We are at the first character following a body:tag\n entry
+ * Suck in the body, then backspace to the first char after the
+ * closing tag entry. If we fail to read, find the tag or back
+ * up then we have a hosed file, so give up already
+ */
+ --*len; /* Reserve space for '\0' */
+ if (apr_file_read(map, buffer, len) != APR_SUCCESS) {
+ return -1;
+ }
+ buffer[*len] = '\0';
+
+ endbody = ap_strstr(buffer, tag);
+ if (!endbody) {
+ return -1;
+ }
+ bodylen = endbody - buffer;
+ endbody += strlen(tag);
+ /* Skip all the trailing cruft after the end tag to the next line */
+ while (*endbody) {
+ if (*endbody == '\n') {
+ ++endbody;
+ break;
+ }
+ ++endbody;
+ }
+
+ pos = -(apr_off_t)(*len - (endbody - buffer));
+ if (apr_file_seek(map, APR_CUR, &pos) != APR_SUCCESS) {
+ return -1;
+ }
+
+ /* Give the caller back the actual body's file offset and length */
+ *len = bodylen;
+ return pos - (endbody - buffer);
+}
+
+
+/* Stripping out RFC822 comments */
+
+static void strip_paren_comments(char *hdr)
+{
+ /* Hmmm... is this correct? In Roy's latest draft, (comments) can nest! */
+ /* Nope, it isn't correct. Fails to handle backslash escape as well. */
+
+ while (*hdr) {
+ if (*hdr == '"') {
+ hdr = strchr(hdr, '"');
+ if (hdr == NULL) {
+ return;
+ }
+ ++hdr;
+ }
+ else if (*hdr == '(') {
+ while (*hdr && *hdr != ')') {
+ *hdr++ = ' ';
+ }
+
+ if (*hdr) {
+ *hdr++ = ' ';
+ }
+ }
+ else {
+ ++hdr;
+ }
+ }
+}
+
+/* Getting to a header body from the header */
+
+static char *lcase_header_name_return_body(char *header, request_rec *r)
+{
+ char *cp = header;
+
+ for ( ; *cp && *cp != ':' ; ++cp) {
+ *cp = apr_tolower(*cp);
+ }
+
+ if (!*cp) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00681)
+ "Syntax error in type map, no ':' in %s for header %s",
+ r->filename, header);
+ return NULL;
+ }
+
+ do {
+ ++cp;
+ } while (apr_isspace(*cp));
+
+ if (!*cp) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00682)
+ "Syntax error in type map --- no header body: %s for %s",
+ r->filename, header);
+ return NULL;
+ }
+
+ return cp;
+}
+
+static int read_type_map(apr_file_t **map, negotiation_state *neg,
+ request_rec *rr)
+{
+ request_rec *r = neg->r;
+ apr_file_t *map_ = NULL;
+ apr_status_t status;
+ char buffer[MAX_STRING_LEN];
+ enum header_state hstate;
+ struct var_rec mime_info;
+ int has_content;
+
+ if (!map)
+ map = &map_;
+
+ /* We are not using multiviews */
+ neg->count_multiviews_variants = 0;
+
+ if ((status = apr_file_open(map, rr->filename, APR_READ | APR_BUFFERED,
+ APR_OS_DEFAULT, neg->pool)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(00683)
+ "cannot access type map file: %s", rr->filename);
+ if (APR_STATUS_IS_ENOTDIR(status) || APR_STATUS_IS_ENOENT(status)) {
+ return HTTP_NOT_FOUND;
+ }
+ else {
+ return HTTP_FORBIDDEN;
+ }
+ }
+
+ clean_var_rec(&mime_info);
+ has_content = 0;
+
+ do {
+ hstate = get_header_line(buffer, MAX_STRING_LEN, *map);
+
+ if (hstate == header_seen) {
+ char *body1 = lcase_header_name_return_body(buffer, neg->r);
+ const char *body;
+
+ if (body1 == NULL) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ strip_paren_comments(body1);
+ body = body1;
+
+ if (!strncmp(buffer, "uri:", 4)) {
+ mime_info.file_name = ap_get_token(neg->pool, &body, 0);
+ }
+ else if (!strncmp(buffer, "content-type:", 13)) {
+ struct accept_rec accept_info;
+
+ get_entry(neg->pool, &accept_info, body);
+ set_mime_fields(&mime_info, &accept_info);
+ has_content = 1;
+ }
+ else if (!strncmp(buffer, "content-length:", 15)) {
+ apr_off_t clen;
+
+ body1 = ap_get_token(neg->pool, &body, 0);
+ if (!ap_parse_strict_length(&clen, body1)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00684)
+ "Parse error in type map, Content-Length: "
+ "'%s' in %s is invalid.",
+ body1, r->filename);
+ break;
+ }
+ mime_info.bytes = clen;
+ has_content = 1;
+ }
+ else if (!strncmp(buffer, "content-language:", 17)) {
+ mime_info.content_languages = do_languages_line(neg->pool,
+ &body);
+ has_content = 1;
+ }
+ else if (!strncmp(buffer, "content-encoding:", 17)) {
+ mime_info.content_encoding = ap_get_token(neg->pool, &body, 0);
+ has_content = 1;
+ }
+ else if (!strncmp(buffer, "description:", 12)) {
+ char *desc = apr_pstrdup(neg->pool, body);
+ char *cp;
+
+ for (cp = desc; *cp; ++cp) {
+ if (*cp=='\n') *cp=' ';
+ }
+ if (cp>desc) *(cp-1)=0;
+ mime_info.description = desc;
+ }
+ else if (!strncmp(buffer, "body:", 5)) {
+ char *tag = apr_pstrdup(neg->pool, body);
+ char *eol = strchr(tag, '\0');
+ apr_size_t len = MAX_STRING_LEN;
+ while (--eol >= tag && apr_isspace(*eol))
+ *eol = '\0';
+ if ((mime_info.body = get_body(buffer, &len, tag, *map)) < 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00685)
+ "Syntax error in type map, no end tag '%s' "
+ "found in %s for Body: content.",
+ tag, r->filename);
+ break;
+ }
+ mime_info.bytes = len;
+ mime_info.file_name = apr_filepath_name_get(rr->filename);
+ }
+ }
+ else {
+ if (*mime_info.file_name && has_content) {
+ void *new_var = apr_array_push(neg->avail_vars);
+
+ memcpy(new_var, (void *) &mime_info, sizeof(var_rec));
+ }
+
+ clean_var_rec(&mime_info);
+ has_content = 0;
+ }
+ } while (hstate != header_eof);
+
+ if (map_)
+ apr_file_close(map_);
+
+ set_vlist_validator(r, rr);
+
+ return OK;
+}
+
+/* Sort function used by read_types_multi. */
+static int variantsortf(var_rec *a, var_rec *b)
+{
+ /* First key is the source quality, sort in descending order. */
+
+ /* XXX: note that we currently implement no method of setting the
+ * source quality for multiviews variants, so we are always comparing
+ * 1.0 to 1.0 for now
+ */
+ if (a->source_quality < b->source_quality)
+ return 1;
+ if (a->source_quality > b->source_quality)
+ return -1;
+
+ /* Second key is the variant name */
+ return strcmp(a->file_name, b->file_name);
+}
+
+/*****************************************************************
+ *
+ * Same as read_type_map, except we use a filtered directory listing
+ * as the map...
+ */
+
+static int read_types_multi(negotiation_state *neg)
+{
+ request_rec *r = neg->r;
+
+ char *filp;
+ int prefix_len;
+ apr_dir_t *dirp;
+ apr_finfo_t dirent;
+ apr_status_t status;
+ struct var_rec mime_info;
+ struct accept_rec accept_info;
+ void *new_var;
+ int anymatch = 0;
+
+ clean_var_rec(&mime_info);
+
+ if (r->proxyreq || !r->filename
+ || !ap_os_is_path_absolute(neg->pool, r->filename)) {
+ return DECLINED;
+ }
+
+ /* Only absolute paths here */
+ if (!(filp = strrchr(r->filename, '/'))) {
+ return DECLINED;
+ }
+ ++filp;
+ prefix_len = strlen(filp);
+
+ if ((status = apr_dir_open(&dirp, neg->dir_name,
+ neg->pool)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(00686)
+ "cannot read directory for multi: %s", neg->dir_name);
+ return HTTP_FORBIDDEN;
+ }
+
+ while (apr_dir_read(&dirent, APR_FINFO_DIRENT, dirp) == APR_SUCCESS) {
+ apr_array_header_t *exception_list;
+ request_rec *sub_req;
+
+ /* Do we have a match? */
+#ifdef CASE_BLIND_FILESYSTEM
+ if (strncasecmp(dirent.name, filp, prefix_len)) {
+#else
+ if (strncmp(dirent.name, filp, prefix_len)) {
+#endif
+ continue;
+ }
+ if (dirent.name[prefix_len] != '.') {
+ continue;
+ }
+
+ /* Don't negotiate directories and other unusual files
+ * Really shouldn't see anything but DIR/LNK/REG here,
+ * and we aught to discover if the LNK was interesting.
+ *
+ * Of course, this only helps platforms that capture the
+ * the filetype in apr_dir_read(), which most can once
+ * they are optimized with some magic [it's known to the
+ * dirent, not associated to the inode, on most FS's.]
+ */
+ if ((dirent.valid & APR_FINFO_TYPE) && (dirent.filetype == APR_DIR))
+ continue;
+
+ /* Ok, something's here. Maybe nothing useful. Remember that
+ * we tried, if we completely fail, so we can reject the request!
+ */
+ anymatch = 1;
+
+ /* See if it's something which we have access to, and which
+ * has a known type and encoding.
+ */
+ sub_req = ap_sub_req_lookup_dirent(&dirent, r, AP_SUBREQ_MERGE_ARGS,
+ NULL);
+
+ /* Double check, we still don't multi-resolve non-ordinary files
+ */
+ if (sub_req->finfo.filetype != APR_REG) {
+ /* XXX sub req not destroyed -- may be a bug/unintentional ? */
+ continue;
+ }
+
+ /* If it has a handler, we'll pretend it's a CGI script,
+ * since that's a good indication of the sort of thing it
+ * might be doing.
+ */
+ if (sub_req->handler && !sub_req->content_type) {
+ ap_set_content_type(sub_req, CGI_MAGIC_TYPE);
+ }
+
+ /*
+ * mod_mime will _always_ provide us the base name in the
+ * ap-mime-exception-list, if it processed anything. If
+ * this list is empty, give up immediately, there was
+ * nothing interesting. For example, looking at the files
+ * readme.txt and readme.foo, we will throw away .foo if
+ * it's an insignificant file (e.g. did not identify a
+ * language, charset, encoding, content type or handler,)
+ */
+ exception_list =
+ (apr_array_header_t *)apr_table_get(sub_req->notes,
+ "ap-mime-exceptions-list");
+
+ if (!exception_list) {
+ ap_destroy_sub_req(sub_req);
+ continue;
+ }
+
+ /* Each unregonized bit better match our base name, in sequence.
+ * A test of index.html.foo will match index.foo or index.html.foo,
+ * but it will never transpose the segments and allow index.foo.html
+ * because that would introduce too much CPU consumption. Better that
+ * we don't attempt a many-to-many match here.
+ */
+ {
+ int nexcept = exception_list->nelts;
+ char **cur_except = (char**)exception_list->elts;
+ char *segstart = filp, *segend, saveend;
+
+ while (*segstart && nexcept) {
+ if (!(segend = strchr(segstart, '.')))
+ segend = strchr(segstart, '\0');
+ saveend = *segend;
+ *segend = '\0';
+
+#ifdef CASE_BLIND_FILESYSTEM
+ if (strcasecmp(segstart, *cur_except) == 0) {
+#else
+ if (strcmp(segstart, *cur_except) == 0) {
+#endif
+ --nexcept;
+ ++cur_except;
+ }
+
+ if (!saveend)
+ break;
+
+ *segend = saveend;
+ segstart = segend + 1;
+ }
+
+ if (nexcept) {
+ /* Something you don't know is, something you don't know...
+ */
+ ap_destroy_sub_req(sub_req);
+ continue;
+ }
+ }
+
+ /*
+ * If we failed the subrequest, or don't
+ * know what we are serving, then continue.
+ */
+ if (sub_req->status != HTTP_OK || (!sub_req->content_type)) {
+ ap_destroy_sub_req(sub_req);
+ continue;
+ }
+
+ /* If it's a map file, we use that instead of the map
+ * we're building...
+ */
+ if (((sub_req->content_type) &&
+ !strcmp(sub_req->content_type, MAP_FILE_MAGIC_TYPE)) ||
+ ((sub_req->handler) &&
+ !strcmp(sub_req->handler, "type-map"))) {
+
+ apr_dir_close(dirp);
+ neg->avail_vars->nelts = 0;
+ if (sub_req->status != HTTP_OK) {
+ return sub_req->status;
+ }
+ return read_type_map(NULL, neg, sub_req);
+ }
+
+ /* Have reasonable variant --- gather notes. */
+
+ mime_info.sub_req = sub_req;
+ mime_info.file_name = apr_pstrdup(neg->pool, dirent.name);
+ if (sub_req->content_encoding) {
+ mime_info.content_encoding = sub_req->content_encoding;
+ }
+ if (sub_req->content_languages) {
+ mime_info.content_languages = sub_req->content_languages;
+ }
+
+ get_entry(neg->pool, &accept_info, sub_req->content_type);
+ set_mime_fields(&mime_info, &accept_info);
+
+ new_var = apr_array_push(neg->avail_vars);
+ memcpy(new_var, (void *) &mime_info, sizeof(var_rec));
+
+ neg->count_multiviews_variants++;
+
+ clean_var_rec(&mime_info);
+ }
+
+ apr_dir_close(dirp);
+
+ /* We found some file names that matched. None could be served.
+ * Rather than fall out to autoindex or some other mapper, this
+ * request must die.
+ */
+ if (anymatch && !neg->avail_vars->nelts) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00687)
+ "Negotiation: discovered file(s) matching request: %s"
+ " (None could be negotiated).",
+ r->filename);
+ return HTTP_NOT_FOUND;
+ }
+
+ set_vlist_validator(r, r);
+
+ /* Sort the variants into a canonical order. The negotiation
+ * result sometimes depends on the order of the variants. By
+ * sorting the variants into a canonical order, rather than using
+ * the order in which readdir() happens to return them, we ensure
+ * that the negotiation result will be consistent over filesystem
+ * backup/restores and over all mirror sites.
+ */
+
+ qsort((void *) neg->avail_vars->elts, neg->avail_vars->nelts,
+ sizeof(var_rec), (int (*)(const void *, const void *)) variantsortf);
+
+ return OK;
+}
+
+
+/*****************************************************************
+ * And now for the code you've been waiting for... actually
+ * finding a match to the client's requirements.
+ */
+
+/* Matching MIME types ... the star/star and foo/star commenting conventions
+ * are implemented here. (You know what I mean by star/star, but just
+ * try mentioning those three characters in a C comment). Using strcmp()
+ * is legit, because everything has already been smashed to lowercase.
+ *
+ * Note also that if we get an exact match on the media type, we update
+ * level_matched for use in level_cmp below...
+ *
+ * We also give a value for mime_stars, which is used later. It should
+ * be 1 for star/star, 2 for type/star and 3 for type/subtype.
+ */
+
+static int mime_match(accept_rec *accept_r, var_rec *avail)
+{
+ const char *accept_type = accept_r->name;
+ const char *avail_type = avail->mime_type;
+ int len = strlen(accept_type);
+
+ if ((len == 1 && accept_type[0] == '*')
+ || (len == 3 && !strncmp(accept_type, "*/*", 3))) {
+ /* Anything matches star or star/star */
+ if (avail->mime_stars < 1) {
+ avail->mime_stars = 1;
+ }
+ return 1;
+ }
+ else if (len > 2 && accept_type[len - 2] == '/'
+ && accept_type[len - 1] == '*'
+ && !strncmp(accept_type, avail_type, len - 2)
+ && avail_type[len - 2] == '/') {
+ /* Any subtype matches for type/star */
+ if (avail->mime_stars < 2) {
+ avail->mime_stars = 2;
+ }
+ return 1;
+ }
+ else if (!strcmp(accept_type, avail_type)
+ || (!strcmp(accept_type, "text/html")
+ && (!strcmp(avail_type, INCLUDES_MAGIC_TYPE)
+ || !strcmp(avail_type, INCLUDES_MAGIC_TYPE3)))) {
+ if (accept_r->level >= avail->level) {
+ avail->level_matched = avail->level;
+ avail->mime_stars = 3;
+ return 1;
+ }
+ }
+
+ return OK;
+}
+
+/* This code implements a piece of the tie-breaking algorithm between
+ * variants of equal quality. This piece is the treatment of variants
+ * of the same base media type, but different levels. What we want to
+ * return is the variant at the highest level that the client explicitly
+ * claimed to accept.
+ *
+ * If all the variants available are at a higher level than that, or if
+ * the client didn't say anything specific about this media type at all
+ * and these variants just got in on a wildcard, we prefer the lowest
+ * level, on grounds that that's the one that the client is least likely
+ * to choke on.
+ *
+ * (This is all motivated by treatment of levels in HTML --- we only
+ * want to give level 3 to browsers that explicitly ask for it; browsers
+ * that don't, including HTTP/0.9 browsers that only get the implicit
+ * "Accept: * / *" [space added to avoid confusing cpp --- no, that
+ * syntax doesn't really work] should get HTML2 if available).
+ *
+ * (Note that this code only comes into play when we are choosing among
+ * variants of equal quality, where the draft standard gives us a fair
+ * bit of leeway about what to do. It ain't specified by the standard;
+ * rather, it is a choice made by this server about what to do in cases
+ * where the standard does not specify a unique course of action).
+ */
+
+static int level_cmp(var_rec *var1, var_rec *var2)
+{
+ /* Levels are only comparable between matching media types */
+
+ if (var1->is_pseudo_html && !var2->is_pseudo_html) {
+ return 0;
+ }
+
+ if (!var1->is_pseudo_html && strcmp(var1->mime_type, var2->mime_type)) {
+ return 0;
+ }
+ /* The result of the above if statements is that, if we get to
+ * here, both variants have the same mime_type or both are
+ * pseudo-html.
+ */
+
+ /* Take highest level that matched, if either did match. */
+
+ if (var1->level_matched > var2->level_matched) {
+ return 1;
+ }
+ if (var1->level_matched < var2->level_matched) {
+ return -1;
+ }
+
+ /* Neither matched. Take lowest level, if there's a difference. */
+
+ if (var1->level < var2->level) {
+ return 1;
+ }
+ if (var1->level > var2->level) {
+ return -1;
+ }
+
+ /* Tied */
+
+ return 0;
+}
+
+/* Finding languages. The main entry point is set_language_quality()
+ * which is called for each variant. It sets two elements in the
+ * variant record:
+ * language_quality - the 'q' value of the 'best' matching language
+ * from Accept-Language: header (HTTP/1.1)
+ * lang_index - Non-negotiated language priority, using
+ * position of language on the Accept-Language:
+ * header, if present, else LanguagePriority
+ * directive order.
+ *
+ * When we do the variant checking for best variant, we use language
+ * quality first, and if a tie, language_index next (this only applies
+ * when _not_ using the RVSA/1.0 algorithm). If using the RVSA/1.0
+ * algorithm, lang_index is never used.
+ *
+ * set_language_quality() calls find_lang_index() and find_default_index()
+ * to set lang_index.
+ */
+
+static int find_lang_index(apr_array_header_t *accept_langs, char *lang)
+{
+ const char **alang;
+ int i;
+
+ if (!lang || !accept_langs) {
+ return -1;
+ }
+
+ alang = (const char **) accept_langs->elts;
+
+ for (i = 0; i < accept_langs->nelts; ++i) {
+ if (!ap_cstr_casecmpn(lang, *alang, strlen(*alang))) {
+ return i;
+ }
+ alang += (accept_langs->elt_size / sizeof(char*));
+ }
+
+ return -1;
+}
+
+/* set_default_lang_quality() sets the quality we apply to variants
+ * which have no language assigned to them. If none of the variants
+ * have a language, we are not negotiating on language, so all are
+ * acceptable, and we set the default q value to 1.0. However if
+ * some of the variants have languages, we set this default to 0.0001.
+ * The value of this default will be applied to all variants with
+ * no explicit language -- which will have the effect of making them
+ * acceptable, but only if no variants with an explicit language
+ * are acceptable. The default q value set here is assigned to variants
+ * with no language type in set_language_quality().
+ *
+ * Note that if using the RVSA/1.0 algorithm, we don't use this
+ * fiddle.
+ */
+
+static void set_default_lang_quality(negotiation_state *neg)
+{
+ var_rec *avail_recs = (var_rec *) neg->avail_vars->elts;
+ int j;
+
+ if (!neg->dont_fiddle_headers) {
+ for (j = 0; j < neg->avail_vars->nelts; ++j) {
+ var_rec *variant = &avail_recs[j];
+ if (variant->content_languages &&
+ variant->content_languages->nelts) {
+ neg->default_lang_quality = 0.0001f;
+ return;
+ }
+ }
+ }
+
+ neg->default_lang_quality = 1.0f;
+}
+
+/* Set the language_quality value in the variant record. Also
+ * assigns lang_index for ForceLanguagePriority.
+ *
+ * To find the language_quality value, we look for the 'q' value
+ * of the 'best' matching language on the Accept-Language
+ * header. The 'best' match is the language on Accept-Language
+ * header which matches the language of this variant either fully,
+ * or as far as the prefix marker (-). If two or more languages
+ * match, use the longest string from the Accept-Language header
+ * (see HTTP/1.1 [14.4])
+ *
+ * When a variant has multiple languages, we find the 'best'
+ * match for each variant language tag as above, then select the
+ * one with the highest q value. Because both the accept-header
+ * and variant can have multiple languages, we now have a hairy
+ * loop-within-a-loop here.
+ *
+ * If the variant has no language and we have no Accept-Language
+ * items, leave the quality at 1.0 and return.
+ *
+ * If the variant has no language, we use the default as set by
+ * set_default_lang_quality() (1.0 if we are not negotiating on
+ * language, 0.001 if we are).
+ *
+ * Following the setting of the language quality, we drop through to
+ * set the old 'lang_index'. This is set based on either the order
+ * of the languages on the Accept-Language header, or the
+ * order on the LanguagePriority directive. This is only used
+ * in the negotiation if the language qualities tie.
+ */
+
+static void set_language_quality(negotiation_state *neg, var_rec *variant)
+{
+ int forcepriority = neg->conf->forcelangpriority;
+ if (forcepriority == FLP_UNDEF) {
+ forcepriority = FLP_DEFAULT;
+ }
+
+ if (!variant->content_languages || !variant->content_languages->nelts) {
+ /* This variant has no content-language, so use the default
+ * quality factor for variants with no content-language
+ * (previously set by set_default_lang_quality()).
+ * Leave the factor alone (it remains at 1.0) when we may not fiddle
+ * with the headers.
+ */
+ if (!neg->dont_fiddle_headers) {
+ variant->lang_quality = neg->default_lang_quality;
+ }
+ return;
+ }
+ else {
+ /* Variant has one (or more) languages. Look for the best
+ * match. We do this by going through each language on the
+ * variant description looking for a match on the
+ * Accept-Language header. The best match is the longest
+ * matching language on the header. The final result is the
+ * best q value from all the languages on the variant
+ * description.
+ */
+
+ if (!neg->accept_langs) {
+ /* no accept-language header makes the variant indefinite */
+ variant->definite = 0;
+ }
+ else { /* There is an accept-language with 0 or more items */
+ accept_rec *accs = (accept_rec *) neg->accept_langs->elts;
+ accept_rec *best = NULL, *star = NULL;
+ accept_rec *bestthistag;
+ char *lang, *p;
+ float fiddle_q = 0.0f;
+ int any_match_on_star = 0;
+ int i, j;
+ apr_size_t alen, longest_lang_range_len;
+
+ for (j = 0; j < variant->content_languages->nelts; ++j) {
+ p = NULL;
+ bestthistag = NULL;
+ longest_lang_range_len = 0;
+
+ /* lang is the variant's language-tag, which is the one
+ * we are allowed to use the prefix of in HTTP/1.1
+ */
+ lang = ((char **) (variant->content_languages->elts))[j];
+
+ /* now find the best (i.e. longest) matching
+ * Accept-Language header language. We put the best match
+ * for this tag in bestthistag. We cannot update the
+ * overall best (based on q value) because the best match
+ * for this tag is the longest language item on the accept
+ * header, not necessarily the highest q.
+ */
+ for (i = 0; i < neg->accept_langs->nelts; ++i) {
+ if (!strcmp(accs[i].name, "*")) {
+ if (!star) {
+ star = &accs[i];
+ }
+ continue;
+ }
+ /* Find language. We match if either the variant
+ * language tag exactly matches the language range
+ * from the accept header, or a prefix of the variant
+ * language tag up to a '-' character matches the
+ * whole of the language range in the Accept-Language
+ * header. Note that HTTP/1.x allows any number of
+ * '-' characters in a tag or range, currently only
+ * tags with zero or one '-' characters are defined
+ * for general use (see rfc1766).
+ *
+ * We only use language range in the Accept-Language
+ * header the best match for the variant language tag
+ * if it is longer than the previous best match.
+ */
+
+ alen = strlen(accs[i].name);
+
+ if ((strlen(lang) >= alen) &&
+ !strncmp(lang, accs[i].name, alen) &&
+ ((lang[alen] == 0) || (lang[alen] == '-')) ) {
+
+ if (alen > longest_lang_range_len) {
+ longest_lang_range_len = alen;
+ bestthistag = &accs[i];
+ }
+ }
+
+ if (!bestthistag && !neg->dont_fiddle_headers) {
+ /* The next bit is a fiddle. Some browsers might
+ * be configured to send more specific language
+ * ranges than desirable. For example, an
+ * Accept-Language of en-US should never match
+ * variants with languages en or en-GB. But US
+ * English speakers might pick en-US as their
+ * language choice. So this fiddle checks if the
+ * language range has a prefix, and if so, it
+ * matches variants which match that prefix with a
+ * priority of 0.001. So a request for en-US would
+ * match variants of types en and en-GB, but at
+ * much lower priority than matches of en-US
+ * directly, or of any other language listed on
+ * the Accept-Language header. Note that this
+ * fiddle does not handle multi-level prefixes.
+ */
+ if ((p = strchr(accs[i].name, '-'))) {
+ int plen = p - accs[i].name;
+
+ if (!strncmp(lang, accs[i].name, plen)) {
+ fiddle_q = 0.001f;
+ }
+ }
+ }
+ }
+ /* Finished looking at Accept-Language headers, the best
+ * (longest) match is in bestthistag, or NULL if no match
+ */
+ if (!best ||
+ (bestthistag && bestthistag->quality > best->quality)) {
+ best = bestthistag;
+ }
+
+ /* See if the tag matches on a * in the Accept-Language
+ * header. If so, record this fact for later use
+ */
+ if (!bestthistag && star) {
+ any_match_on_star = 1;
+ }
+ }
+
+ /* If one of the language tags of the variant matched on *, we
+ * need to see if its q is better than that of any non-* match
+ * on any other tag of the variant. If so the * match takes
+ * precedence and the overall match is not definite.
+ */
+ if ( any_match_on_star &&
+ ((best && star->quality > best->quality) ||
+ (!best)) ) {
+ best = star;
+ variant->definite = 0;
+ }
+
+ variant->lang_quality = best ? best->quality : fiddle_q;
+ }
+ }
+
+ /* Handle the ForceDefaultLanguage overrides, based on the best match
+ * to LanguagePriority order. The best match is the lowest index of
+ * any LanguagePriority match.
+ */
+ if (((forcepriority & FLP_PREFER)
+ && (variant->lang_index < 0))
+ || ((forcepriority & FLP_FALLBACK)
+ && !variant->lang_quality))
+ {
+ int bestidx = -1;
+ int j;
+
+ for (j = 0; j < variant->content_languages->nelts; ++j)
+ {
+ /* lang is the variant's language-tag, which is the one
+ * we are allowed to use the prefix of in HTTP/1.1
+ */
+ char *lang = ((char **) (variant->content_languages->elts))[j];
+ int idx;
+
+ /* If we wish to fallback or
+ * we use our own LanguagePriority index.
+ */
+ idx = find_lang_index(neg->conf->language_priority, lang);
+ if ((idx >= 0) && ((bestidx == -1) || (idx < bestidx))) {
+ bestidx = idx;
+ }
+ }
+
+ if (bestidx >= 0) {
+ if (variant->lang_quality) {
+ if (forcepriority & FLP_PREFER) {
+ variant->lang_index = bestidx;
+ }
+ }
+ else {
+ if (forcepriority & FLP_FALLBACK) {
+ variant->lang_index = bestidx;
+ variant->lang_quality = .0001f;
+ variant->definite = 0;
+ }
+ }
+ }
+ }
+}
+
+/* Determining the content length --- if the map didn't tell us,
+ * we have to do a stat() and remember for next time.
+ */
+
+static apr_off_t find_content_length(negotiation_state *neg, var_rec *variant)
+{
+ apr_finfo_t statb;
+
+ if (variant->bytes < 0) {
+ if ( variant->sub_req
+ && (variant->sub_req->finfo.valid & APR_FINFO_SIZE)) {
+ variant->bytes = variant->sub_req->finfo.size;
+ }
+ else {
+ char *fullname = ap_make_full_path(neg->pool, neg->dir_name,
+ variant->file_name);
+
+ if (apr_stat(&statb, fullname,
+ APR_FINFO_SIZE, neg->pool) == APR_SUCCESS) {
+ variant->bytes = statb.size;
+ }
+ }
+ }
+
+ return variant->bytes;
+}
+
+/* For a given variant, find the best matching Accept: header
+ * and assign the Accept: header's quality value to the
+ * mime_type_quality field of the variant, for later use in
+ * determining the best matching variant.
+ */
+
+static void set_accept_quality(negotiation_state *neg, var_rec *variant)
+{
+ int i;
+ accept_rec *accept_recs;
+ float q = 0.0f;
+ int q_definite = 1;
+
+ /* if no Accept: header, leave quality alone (will
+ * remain at the default value of 1)
+ *
+ * XXX: This if is currently never true because of the effect of
+ * maybe_add_default_accepts().
+ */
+ if (!neg->accepts) {
+ if (variant->mime_type && *variant->mime_type)
+ variant->definite = 0;
+ return;
+ }
+
+ accept_recs = (accept_rec *) neg->accepts->elts;
+
+ /*
+ * Go through each of the ranges on the Accept: header,
+ * looking for the 'best' match with this variant's
+ * content-type. We use the best match's quality
+ * value (from the Accept: header) for this variant's
+ * mime_type_quality field.
+ *
+ * The best match is determined like this:
+ * type/type is better than type/ * is better than * / *
+ * if match is type/type, use the level mime param if available
+ */
+ for (i = 0; i < neg->accepts->nelts; ++i) {
+
+ accept_rec *type = &accept_recs[i];
+ int prev_mime_stars;
+
+ prev_mime_stars = variant->mime_stars;
+
+ if (!mime_match(type, variant)) {
+ continue; /* didn't match the content type at all */
+ }
+ else {
+ /* did match - see if there were less or more stars than
+ * in previous match
+ */
+ if (prev_mime_stars == variant->mime_stars) {
+ continue; /* more stars => not as good a match */
+ }
+ }
+
+ /* If we are allowed to mess with the q-values
+ * and have no explicit q= parameters in the accept header,
+ * make wildcards very low, so we have a low chance
+ * of ending up with them if there's something better.
+ */
+
+ if (!neg->dont_fiddle_headers && !neg->accept_q &&
+ variant->mime_stars == 1) {
+ q = 0.01f;
+ }
+ else if (!neg->dont_fiddle_headers && !neg->accept_q &&
+ variant->mime_stars == 2) {
+ q = 0.02f;
+ }
+ else {
+ q = type->quality;
+ }
+
+ q_definite = (variant->mime_stars == 3);
+ }
+ variant->mime_type_quality = q;
+ variant->definite = variant->definite && q_definite;
+
+}
+
+/* For a given variant, find the 'q' value of the charset given
+ * on the Accept-Charset line. If no charsets are listed,
+ * assume value of '1'.
+ */
+static void set_charset_quality(negotiation_state *neg, var_rec *variant)
+{
+ int i;
+ accept_rec *accept_recs;
+ const char *charset = variant->content_charset;
+ accept_rec *star = NULL;
+
+ /* if no Accept-Charset: header, leave quality alone (will
+ * remain at the default value of 1)
+ */
+ if (!neg->accept_charsets) {
+ if (charset && *charset)
+ variant->definite = 0;
+ return;
+ }
+
+ accept_recs = (accept_rec *) neg->accept_charsets->elts;
+
+ if (charset == NULL || !*charset) {
+ /* Charset of variant not known */
+
+ /* if not a text / * type, leave quality alone */
+ if (!(!strncmp(variant->mime_type, "text/", 5)
+ || !strcmp(variant->mime_type, INCLUDES_MAGIC_TYPE)
+ || !strcmp(variant->mime_type, INCLUDES_MAGIC_TYPE3)
+ ))
+ return;
+
+ /* Don't go guessing if we are in strict header mode,
+ * e.g. when running the rvsa, as any guess won't be reflected
+ * in the variant list or content-location headers.
+ */
+ if (neg->dont_fiddle_headers)
+ return;
+
+ charset = "iso-8859-1"; /* The default charset for HTTP text types */
+ }
+
+ /*
+ * Go through each of the items on the Accept-Charset header,
+ * looking for a match with this variant's charset. If none
+ * match, charset is unacceptable, so set quality to 0.
+ */
+ for (i = 0; i < neg->accept_charsets->nelts; ++i) {
+
+ accept_rec *type = &accept_recs[i];
+
+ if (!strcmp(type->name, charset)) {
+ variant->charset_quality = type->quality;
+ return;
+ }
+ else if (strcmp(type->name, "*") == 0) {
+ star = type;
+ }
+ }
+ /* No explicit match */
+ if (star) {
+ variant->charset_quality = star->quality;
+ variant->definite = 0;
+ return;
+ }
+ /* If this variant is in charset iso-8859-1, the default is 1.0 */
+ if (strcmp(charset, "iso-8859-1") == 0) {
+ variant->charset_quality = 1.0f;
+ }
+ else {
+ variant->charset_quality = 0.0f;
+ }
+}
+
+
+/* is_identity_encoding is included for back-compat, but does anyone
+ * use 7bit, 8bin or binary in their var files??
+ */
+
+static int is_identity_encoding(const char *enc)
+{
+ return (!enc || !enc[0] || !strcmp(enc, "7bit") || !strcmp(enc, "8bit")
+ || !strcmp(enc, "binary"));
+}
+
+/*
+ * set_encoding_quality determines whether the encoding for a particular
+ * variant is acceptable for the user-agent.
+ *
+ * The rules for encoding are that if the user-agent does not supply
+ * any Accept-Encoding header, then all encodings are allowed but a
+ * variant with no encoding should be preferred.
+ * If there is an empty Accept-Encoding header, then no encodings are
+ * acceptable. If there is a non-empty Accept-Encoding header, then
+ * any of the listed encodings are acceptable, as well as no encoding
+ * unless the "identity" encoding is specifically excluded.
+ */
+static void set_encoding_quality(negotiation_state *neg, var_rec *variant)
+{
+ accept_rec *accept_recs;
+ const char *enc = variant->content_encoding;
+ accept_rec *star = NULL;
+ float value_if_not_found = 0.0f;
+ int i;
+
+ if (!neg->accept_encodings) {
+ /* We had no Accept-Encoding header, assume that all
+ * encodings are acceptable with a low quality,
+ * but we prefer no encoding if available.
+ */
+ if (!enc || is_identity_encoding(enc))
+ variant->encoding_quality = 1.0f;
+ else
+ variant->encoding_quality = 0.5f;
+
+ return;
+ }
+
+ if (!enc || is_identity_encoding(enc)) {
+ enc = "identity";
+ value_if_not_found = 0.0001f;
+ }
+
+ accept_recs = (accept_rec *) neg->accept_encodings->elts;
+
+ /* Go through each of the encodings on the Accept-Encoding: header,
+ * looking for a match with our encoding. x- prefixes are ignored.
+ */
+ if (enc[0] == 'x' && enc[1] == '-') {
+ enc += 2;
+ }
+ for (i = 0; i < neg->accept_encodings->nelts; ++i) {
+
+ char *name = accept_recs[i].name;
+
+ if (name[0] == 'x' && name[1] == '-') {
+ name += 2;
+ }
+
+ if (!strcmp(name, enc)) {
+ variant->encoding_quality = accept_recs[i].quality;
+ return;
+ }
+
+ if (strcmp(name, "*") == 0) {
+ star = &accept_recs[i];
+ }
+
+ }
+ /* No explicit match */
+ if (star) {
+ variant->encoding_quality = star->quality;
+ return;
+ }
+
+ /* Encoding not found on Accept-Encoding: header, so it is
+ * _not_ acceptable unless it is the identity (no encoding)
+ */
+ variant->encoding_quality = value_if_not_found;
+}
+
+/*************************************************************
+ * Possible results of the variant selection algorithm
+ */
+enum algorithm_results {
+ alg_choice = 1, /* choose variant */
+ alg_list /* list variants */
+};
+
+/* Below is the 'best_match' function. It returns an int, which has
+ * one of the two values alg_choice or alg_list, which give the result
+ * of the variant selection algorithm. alg_list means that no best
+ * variant was found by the algorithm, alg_choice means that a best
+ * variant was found and should be returned. The list/choice
+ * terminology comes from TCN (rfc2295), but is used in a more generic
+ * way here. The best variant is returned in *pbest. best_match has
+ * two possible algorithms for determining the best variant: the
+ * RVSA/1.0 algorithm (from RFC2296), and the standard Apache
+ * algorithm. These are split out into separate functions
+ * (is_variant_better_rvsa() and is_variant_better()). Selection of
+ * one is through the neg->use_rvsa flag.
+ *
+ * The call to best_match also creates full information, including
+ * language, charset, etc quality for _every_ variant. This is needed
+ * for generating a correct Vary header, and can be used for the
+ * Alternates header, the human-readable list responses and 406 errors.
+ */
+
+/* Firstly, the RVSA/1.0 (HTTP Remote Variant Selection Algorithm
+ * v1.0) from rfc2296. This is the algorithm that goes together with
+ * transparent content negotiation (TCN).
+ */
+static int is_variant_better_rvsa(negotiation_state *neg, var_rec *variant,
+ var_rec *best, float *p_bestq)
+{
+ float bestq = *p_bestq, q;
+
+ /* TCN does not cover negotiation on content-encoding. For now,
+ * we ignore the encoding unless it was explicitly excluded.
+ */
+ if (variant->encoding_quality == 0.0f)
+ return 0;
+
+ q = variant->mime_type_quality *
+ variant->source_quality *
+ variant->charset_quality *
+ variant->lang_quality;
+
+ /* RFC 2296 calls for the result to be rounded to 5 decimal places,
+ * but we don't do that because it serves no useful purpose other
+ * than to ensure that a remote algorithm operates on the same
+ * precision as ours. That is silly, since what we obviously want
+ * is for the algorithm to operate on the best available precision
+ * regardless of who runs it. Since the above calculation may
+ * result in significant variance at 1e-12, rounding would be bogus.
+ */
+
+#ifdef NEG_DEBUG
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00688)
+ "Variant: file=%s type=%s lang=%s sourceq=%1.3f "
+ "mimeq=%1.3f langq=%1.3f charq=%1.3f encq=%1.3f "
+ "q=%1.5f definite=%d",
+ (variant->file_name ? variant->file_name : ""),
+ (variant->mime_type ? variant->mime_type : ""),
+ (variant->content_languages
+ ? apr_array_pstrcat(neg->pool, variant->content_languages, ',')
+ : ""),
+ variant->source_quality,
+ variant->mime_type_quality,
+ variant->lang_quality,
+ variant->charset_quality,
+ variant->encoding_quality,
+ q,
+ variant->definite);
+#endif
+
+ if (q <= 0.0f) {
+ return 0;
+ }
+ if (q > bestq) {
+ *p_bestq = q;
+ return 1;
+ }
+ if (q == bestq) {
+ /* If the best variant's encoding is of lesser quality than
+ * this variant, then we prefer this variant
+ */
+ if (variant->encoding_quality > best->encoding_quality) {
+ *p_bestq = q;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* Negotiation algorithm as used by previous versions of Apache
+ * (just about).
+ */
+
+static int is_variant_better(negotiation_state *neg, var_rec *variant,
+ var_rec *best, float *p_bestq)
+{
+ float bestq = *p_bestq, q;
+ int levcmp;
+
+ /* For non-transparent negotiation, server can choose how
+ * to handle the negotiation. We'll use the following in
+ * order: content-type, language, content-type level, charset,
+ * content encoding, content length.
+ *
+ * For each check, we have three possible outcomes:
+ * This variant is worse than current best: return 0
+ * This variant is better than the current best:
+ * assign this variant's q to *p_bestq, and return 1
+ * This variant is just as desirable as the current best:
+ * drop through to the next test.
+ *
+ * This code is written in this long-winded way to allow future
+ * customisation, either by the addition of additional
+ * checks, or to allow the order of the checks to be determined
+ * by configuration options (e.g. we might prefer to check
+ * language quality _before_ content type).
+ */
+
+ /* First though, eliminate this variant if it is not
+ * acceptable by type, charset, encoding or language.
+ */
+
+#ifdef NEG_DEBUG
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00689)
+ "Variant: file=%s type=%s lang=%s sourceq=%1.3f "
+ "mimeq=%1.3f langq=%1.3f langidx=%d charq=%1.3f encq=%1.3f ",
+ (variant->file_name ? variant->file_name : ""),
+ (variant->mime_type ? variant->mime_type : ""),
+ (variant->content_languages
+ ? apr_array_pstrcat(neg->pool, variant->content_languages, ',')
+ : ""),
+ variant->source_quality,
+ variant->mime_type_quality,
+ variant->lang_quality,
+ variant->lang_index,
+ variant->charset_quality,
+ variant->encoding_quality);
+#endif
+
+ if (variant->encoding_quality == 0.0f ||
+ variant->lang_quality == 0.0f ||
+ variant->source_quality == 0.0f ||
+ variant->charset_quality == 0.0f ||
+ variant->mime_type_quality == 0.0f) {
+ return 0; /* don't consider unacceptables */
+ }
+
+ q = variant->mime_type_quality * variant->source_quality;
+ if (q == 0.0 || q < bestq) {
+ return 0;
+ }
+ if (q > bestq || !best) {
+ *p_bestq = q;
+ return 1;
+ }
+
+ /* language */
+ if (variant->lang_quality < best->lang_quality) {
+ return 0;
+ }
+ if (variant->lang_quality > best->lang_quality) {
+ *p_bestq = q;
+ return 1;
+ }
+
+ /* if language qualities were equal, try the LanguagePriority stuff */
+ if (best->lang_index != -1 &&
+ (variant->lang_index == -1 || variant->lang_index > best->lang_index)) {
+ return 0;
+ }
+ if (variant->lang_index != -1 &&
+ (best->lang_index == -1 || variant->lang_index < best->lang_index)) {
+ *p_bestq = q;
+ return 1;
+ }
+
+ /* content-type level (sometimes used with text/html, though we
+ * support it on other types too)
+ */
+ levcmp = level_cmp(variant, best);
+ if (levcmp == -1) {
+ return 0;
+ }
+ if (levcmp == 1) {
+ *p_bestq = q;
+ return 1;
+ }
+
+ /* charset */
+ if (variant->charset_quality < best->charset_quality) {
+ return 0;
+ }
+ /* If the best variant's charset is ISO-8859-1 and this variant has
+ * the same charset quality, then we prefer this variant
+ */
+
+ if (variant->charset_quality > best->charset_quality ||
+ ((variant->content_charset != NULL &&
+ *variant->content_charset != '\0' &&
+ strcmp(variant->content_charset, "iso-8859-1") != 0) &&
+ (best->content_charset == NULL ||
+ *best->content_charset == '\0' ||
+ strcmp(best->content_charset, "iso-8859-1") == 0))) {
+ *p_bestq = q;
+ return 1;
+ }
+
+ /* Prefer the highest value for encoding_quality.
+ */
+ if (variant->encoding_quality < best->encoding_quality) {
+ return 0;
+ }
+ if (variant->encoding_quality > best->encoding_quality) {
+ *p_bestq = q;
+ return 1;
+ }
+
+ /* content length if all else equal */
+ if (find_content_length(neg, variant) >= find_content_length(neg, best)) {
+ return 0;
+ }
+
+ /* ok, to get here means every thing turned out equal, except
+ * we have a shorter content length, so use this variant
+ */
+ *p_bestq = q;
+ return 1;
+}
+
+/* figure out, whether a variant is in a specific language
+ * it returns also false, if the variant has no language.
+ */
+static int variant_has_language(var_rec *variant, const char *lang)
+{
+ /* fast exit */
+ if ( !lang
+ || !variant->content_languages) {
+ return 0;
+ }
+
+ if (ap_array_str_contains(variant->content_languages, lang)) {
+ return 1;
+ }
+
+ return 0;
+}
+
+/* check for environment variables 'no-gzip' and
+ * 'gzip-only-text/html' to get a behaviour similar
+ * to mod_deflate
+ */
+static int discard_variant_by_env(var_rec *variant, int discard)
+{
+ if ( is_identity_encoding(variant->content_encoding)
+ || !strcmp(variant->content_encoding, "identity")) {
+ return 0;
+ }
+
+ return ( (discard == DISCARD_ALL_ENCODINGS)
+ || (discard == DISCARD_ALL_BUT_HTML
+ && (!variant->mime_type
+ || strncmp(variant->mime_type, "text/html", 9))));
+}
+
+static int best_match(negotiation_state *neg, var_rec **pbest)
+{
+ int j;
+ var_rec *best;
+ float bestq = 0.0f;
+ enum algorithm_results algorithm_result;
+ int may_discard = 0;
+
+ var_rec *avail_recs = (var_rec *) neg->avail_vars->elts;
+
+ /* fetch request dependent variables
+ * prefer-language: prefer a certain language.
+ */
+ const char *preferred_language = apr_table_get(neg->r->subprocess_env,
+ "prefer-language");
+
+ /* no-gzip: do not send encoded documents */
+ if (apr_table_get(neg->r->subprocess_env, "no-gzip")) {
+ may_discard = DISCARD_ALL_ENCODINGS;
+ }
+
+ /* gzip-only-text/html: send encoded documents only
+ * if they are text/html. (no-gzip has a higher priority).
+ */
+ else {
+ const char *env_value = apr_table_get(neg->r->subprocess_env,
+ "gzip-only-text/html");
+
+ if (env_value && !strcmp(env_value, "1")) {
+ may_discard = DISCARD_ALL_BUT_HTML;
+ }
+ }
+
+ set_default_lang_quality(neg);
+
+ /*
+ * Find the 'best' variant
+ * We run the loop possibly twice: if "prefer-language"
+ * environment variable is set but we did not find an appropriate
+ * best variant. In that case forget the preferred language and
+ * negotiate over all variants.
+ */
+
+ do {
+ best = NULL;
+
+ for (j = 0; j < neg->avail_vars->nelts; ++j) {
+ var_rec *variant = &avail_recs[j];
+
+ /* if this variant is encoded somehow and there are special
+ * variables set, we do not negotiate it. see above.
+ */
+ if ( may_discard
+ && discard_variant_by_env(variant, may_discard)) {
+ continue;
+ }
+
+ /* if a language is preferred, but the current variant
+ * is not in that language, then drop it for now
+ */
+ if ( preferred_language
+ && !variant_has_language(variant, preferred_language)) {
+ continue;
+ }
+
+ /* Find all the relevant 'quality' values from the
+ * Accept... headers, and store in the variant. This also
+ * prepares for sending an Alternates header etc so we need to
+ * do it even if we do not actually plan to find a best
+ * variant.
+ */
+ set_accept_quality(neg, variant);
+ /* accept the preferred language, even when it's not listed within
+ * the Accept-Language header
+ */
+ if (preferred_language) {
+ variant->lang_quality = 1.0f;
+ variant->definite = 1;
+ }
+ else {
+ set_language_quality(neg, variant);
+ }
+ set_encoding_quality(neg, variant);
+ set_charset_quality(neg, variant);
+
+ /* Only do variant selection if we may actually choose a
+ * variant for the client
+ */
+ if (neg->may_choose) {
+
+ /* Now find out if this variant is better than the current
+ * best, either using the RVSA/1.0 algorithm, or Apache's
+ * internal server-driven algorithm. Presumably other
+ * server-driven algorithms are possible, and could be
+ * implemented here.
+ */
+
+ if (neg->use_rvsa) {
+ if (is_variant_better_rvsa(neg, variant, best, &bestq)) {
+ best = variant;
+ }
+ }
+ else {
+ if (is_variant_better(neg, variant, best, &bestq)) {
+ best = variant;
+ }
+ }
+ }
+ }
+
+ /* We now either have a best variant, or no best variant */
+
+ if (neg->use_rvsa) {
+ /* calculate result for RVSA/1.0 algorithm:
+ * only a choice response if the best variant has q>0
+ * and is definite
+ */
+ algorithm_result = (best && best->definite) && (bestq > 0) ?
+ alg_choice : alg_list;
+ }
+ else {
+ /* calculate result for Apache negotiation algorithm */
+ algorithm_result = bestq > 0 ? alg_choice : alg_list;
+ }
+
+ /* run the loop again, if the "prefer-language" got no clear result */
+ if (preferred_language && (!best || algorithm_result != alg_choice)) {
+ preferred_language = NULL;
+ continue;
+ }
+
+ break;
+ } while (1);
+
+ /* Returning a choice response with a non-neighboring variant is a
+ * protocol security error in TCN (see rfc2295). We do *not*
+ * verify here that the variant and URI are neighbors, even though
+ * we may return alg_choice. We depend on the environment (the
+ * caller) to only declare the resource transparently negotiable if
+ * all variants are neighbors.
+ */
+ *pbest = best;
+ return algorithm_result;
+}
+
+/* Sets response headers for a negotiated response.
+ * neg->is_transparent determines whether a transparently negotiated
+ * response or a plain `server driven negotiation' response is
+ * created. Applicable headers are Alternates, Vary, and TCN.
+ *
+ * The Vary header we create is sometimes longer than is required for
+ * the correct caching of negotiated results by HTTP/1.1 caches. For
+ * example if we have 3 variants x.html, x.ps.en and x.ps.nl, and if
+ * the Accept: header assigns a 0 quality to .ps, then the results of
+ * the two server-side negotiation algorithms we currently implement
+ * will never depend on Accept-Language so we could return `Vary:
+ * negotiate, accept' instead of the longer 'Vary: negotiate, accept,
+ * accept-language' which the code below will return. A routine for
+ * computing the exact minimal Vary header would be a huge pain to code
+ * and maintain though, especially because we need to take all possible
+ * twiddles in the server-side negotiation algorithms into account.
+ */
+static void set_neg_headers(request_rec *r, negotiation_state *neg,
+ int alg_result)
+{
+ apr_table_t *hdrs;
+ var_rec *avail_recs = (var_rec *) neg->avail_vars->elts;
+ const char *sample_type = NULL;
+ const char *sample_language = NULL;
+ const char *sample_encoding = NULL;
+ const char *sample_charset = NULL;
+ char *lang;
+ char *qstr;
+ apr_off_t len;
+ apr_array_header_t *arr;
+ int max_vlist_array = (neg->avail_vars->nelts * 21);
+ int first_variant = 1;
+ int vary_by_type = 0;
+ int vary_by_language = 0;
+ int vary_by_charset = 0;
+ int vary_by_encoding = 0;
+ int j;
+
+ /* In order to avoid O(n^2) memory copies in building Alternates,
+ * we preallocate a apr_table_t with the maximum substrings possible,
+ * fill it with the variant list, and then concatenate the entire array.
+ * Note that if you change the number of substrings pushed, you also
+ * need to change the calculation of max_vlist_array above.
+ */
+ if (neg->send_alternates && neg->avail_vars->nelts)
+ arr = apr_array_make(r->pool, max_vlist_array, sizeof(char *));
+ else
+ arr = NULL;
+
+ /* Put headers into err_headers_out, since send_http_header()
+ * outputs both headers_out and err_headers_out.
+ */
+ hdrs = r->err_headers_out;
+
+ for (j = 0; j < neg->avail_vars->nelts; ++j) {
+ var_rec *variant = &avail_recs[j];
+
+ if (variant->content_languages && variant->content_languages->nelts) {
+ lang = apr_array_pstrcat(r->pool, variant->content_languages, ',');
+ }
+ else {
+ lang = NULL;
+ }
+
+ /* Calculate Vary by looking for any difference between variants */
+
+ if (first_variant) {
+ sample_type = variant->mime_type;
+ sample_charset = variant->content_charset;
+ sample_language = lang;
+ sample_encoding = variant->content_encoding;
+ }
+ else {
+ if (!vary_by_type &&
+ strcmp(sample_type ? sample_type : "",
+ variant->mime_type ? variant->mime_type : "")) {
+ vary_by_type = 1;
+ }
+ if (!vary_by_charset &&
+ strcmp(sample_charset ? sample_charset : "",
+ variant->content_charset ?
+ variant->content_charset : "")) {
+ vary_by_charset = 1;
+ }
+ if (!vary_by_language &&
+ strcmp(sample_language ? sample_language : "",
+ lang ? lang : "")) {
+ vary_by_language = 1;
+ }
+ if (!vary_by_encoding &&
+ strcmp(sample_encoding ? sample_encoding : "",
+ variant->content_encoding ?
+ variant->content_encoding : "")) {
+ vary_by_encoding = 1;
+ }
+ }
+ first_variant = 0;
+
+ if (!neg->send_alternates)
+ continue;
+
+ /* Generate the string components for this Alternates entry */
+
+ *((const char **) apr_array_push(arr)) = "{\"";
+ *((const char **) apr_array_push(arr)) = ap_escape_path_segment(r->pool, variant->file_name);
+ *((const char **) apr_array_push(arr)) = "\" ";
+
+ qstr = (char *) apr_palloc(r->pool, 6);
+ apr_snprintf(qstr, 6, "%1.3f", variant->source_quality);
+
+ /* Strip trailing zeros (saves those valuable network bytes) */
+ if (qstr[4] == '0') {
+ qstr[4] = '\0';
+ if (qstr[3] == '0') {
+ qstr[3] = '\0';
+ if (qstr[2] == '0') {
+ qstr[1] = '\0';
+ }
+ }
+ }
+ *((const char **) apr_array_push(arr)) = qstr;
+
+ if (variant->mime_type && *variant->mime_type) {
+ *((const char **) apr_array_push(arr)) = " {type ";
+ *((const char **) apr_array_push(arr)) = variant->mime_type;
+ *((const char **) apr_array_push(arr)) = "}";
+ }
+ if (variant->content_charset && *variant->content_charset) {
+ *((const char **) apr_array_push(arr)) = " {charset ";
+ *((const char **) apr_array_push(arr)) = variant->content_charset;
+ *((const char **) apr_array_push(arr)) = "}";
+ }
+ if (lang) {
+ *((const char **) apr_array_push(arr)) = " {language ";
+ *((const char **) apr_array_push(arr)) = lang;
+ *((const char **) apr_array_push(arr)) = "}";
+ }
+ if (variant->content_encoding && *variant->content_encoding) {
+ /* Strictly speaking, this is non-standard, but so is TCN */
+
+ *((const char **) apr_array_push(arr)) = " {encoding ";
+ *((const char **) apr_array_push(arr)) = variant->content_encoding;
+ *((const char **) apr_array_push(arr)) = "}";
+ }
+
+ /* Note that the Alternates specification (in rfc2295) does
+ * not require that we include {length x}, so we could omit it
+ * if determining the length is too expensive. We currently
+ * always include it though.
+ *
+ * If the variant is a CGI script, find_content_length would
+ * return the length of the script, not the output it
+ * produces, so we check for the presence of a handler and if
+ * there is one we don't add a length.
+ *
+ * XXX: TODO: This check does not detect a CGI script if we
+ * get the variant from a type map. This needs to be fixed
+ * (without breaking things if the type map specifies a
+ * content-length, which currently leads to the correct result).
+ */
+ if (!(variant->sub_req && variant->sub_req->handler)
+ && (len = find_content_length(neg, variant)) >= 0) {
+
+ *((const char **) apr_array_push(arr)) = " {length ";
+ *((const char **) apr_array_push(arr)) = apr_off_t_toa(r->pool,
+ len);
+ *((const char **) apr_array_push(arr)) = "}";
+ }
+
+ *((const char **) apr_array_push(arr)) = "}";
+ *((const char **) apr_array_push(arr)) = ", "; /* trimmed below */
+ }
+
+ if (neg->send_alternates && neg->avail_vars->nelts) {
+ arr->nelts--; /* remove last comma */
+ apr_table_mergen(hdrs, "Alternates",
+ apr_array_pstrcat(r->pool, arr, '\0'));
+ }
+
+ if (neg->is_transparent || vary_by_type || vary_by_language ||
+ vary_by_charset || vary_by_encoding) {
+
+ apr_table_mergen(hdrs, "Vary", 2 + apr_pstrcat(r->pool,
+ neg->is_transparent ? ", negotiate" : "",
+ vary_by_type ? ", accept" : "",
+ vary_by_language ? ", accept-language" : "",
+ vary_by_charset ? ", accept-charset" : "",
+ vary_by_encoding ? ", accept-encoding" : "", NULL));
+ }
+
+ if (neg->is_transparent) { /* Create TCN response header */
+ apr_table_setn(hdrs, "TCN",
+ alg_result == alg_list ? "list" : "choice");
+ }
+}
+
+/**********************************************************************
+ *
+ * Return an HTML list of variants. This is output as part of the
+ * choice response or 406 status body.
+ */
+
+static char *make_variant_list(request_rec *r, negotiation_state *neg)
+{
+ apr_array_header_t *arr;
+ int i;
+ int max_vlist_array = (neg->avail_vars->nelts * 15) + 2;
+
+ /* In order to avoid O(n^2) memory copies in building the list,
+ * we preallocate a apr_table_t with the maximum substrings possible,
+ * fill it with the variant list, and then concatenate the entire array.
+ */
+ arr = apr_array_make(r->pool, max_vlist_array, sizeof(char *));
+
+ *((const char **) apr_array_push(arr)) = "Available variants:\n<ul>\n";
+
+ for (i = 0; i < neg->avail_vars->nelts; ++i) {
+ var_rec *variant = &((var_rec *) neg->avail_vars->elts)[i];
+ const char *filename = variant->file_name ? variant->file_name : "";
+ apr_array_header_t *languages = variant->content_languages;
+ const char *description = variant->description
+ ? variant->description
+ : "";
+
+ /* The format isn't very neat, and it would be nice to make
+ * the tags human readable (eg replace 'language en' with 'English').
+ * Note that if you change the number of substrings pushed, you also
+ * need to change the calculation of max_vlist_array above.
+ */
+ *((const char **) apr_array_push(arr)) = "<li><a href=\"";
+ *((const char **) apr_array_push(arr)) = ap_escape_path_segment(r->pool, filename);
+ *((const char **) apr_array_push(arr)) = "\">";
+ *((const char **) apr_array_push(arr)) = ap_escape_html(r->pool, filename);
+ *((const char **) apr_array_push(arr)) = "</a> ";
+ *((const char **) apr_array_push(arr)) = description;
+
+ if (variant->mime_type && *variant->mime_type) {
+ *((const char **) apr_array_push(arr)) = ", type ";
+ *((const char **) apr_array_push(arr)) = variant->mime_type;
+ }
+ if (languages && languages->nelts) {
+ *((const char **) apr_array_push(arr)) = ", language ";
+ *((const char **) apr_array_push(arr)) = apr_array_pstrcat(r->pool,
+ languages, ',');
+ }
+ if (variant->content_charset && *variant->content_charset) {
+ *((const char **) apr_array_push(arr)) = ", charset ";
+ *((const char **) apr_array_push(arr)) = variant->content_charset;
+ }
+ if (variant->content_encoding) {
+ *((const char **) apr_array_push(arr)) = ", encoding ";
+ *((const char **) apr_array_push(arr)) = variant->content_encoding;
+ }
+ *((const char **) apr_array_push(arr)) = "</li>\n";
+ }
+ *((const char **) apr_array_push(arr)) = "</ul>\n";
+
+ return apr_array_pstrcat(r->pool, arr, '\0');
+}
+
+static void store_variant_list(request_rec *r, negotiation_state *neg)
+{
+ if (r->main == NULL) {
+ apr_table_setn(r->notes, "variant-list", make_variant_list(r, neg));
+ }
+ else {
+ apr_table_setn(r->main->notes, "variant-list",
+ make_variant_list(r->main, neg));
+ }
+}
+
+/* Called if we got a "Choice" response from the variant selection algorithm.
+ * It checks the result of the chosen variant to see if it
+ * is itself negotiated (if so, return error HTTP_VARIANT_ALSO_VARIES).
+ * Otherwise, add the appropriate headers to the current response.
+ */
+
+static int setup_choice_response(request_rec *r, negotiation_state *neg,
+ var_rec *variant)
+{
+ request_rec *sub_req;
+ const char *sub_vary;
+
+ if (!variant->sub_req) {
+ int status;
+
+ sub_req = ap_sub_req_lookup_file(variant->file_name, r, r->output_filters);
+ status = sub_req->status;
+
+ if (status != HTTP_OK &&
+ !apr_table_get(sub_req->err_headers_out, "TCN")) {
+ ap_destroy_sub_req(sub_req);
+ return status;
+ }
+ variant->sub_req = sub_req;
+ }
+ else {
+ sub_req = variant->sub_req;
+ }
+
+ /* The variant selection algorithm told us to return a "Choice"
+ * response. This is the normal variant response, with
+ * some extra headers. First, ensure that the chosen
+ * variant did or will not itself engage in transparent negotiation.
+ * If not, set the appropriate headers, and fall through to
+ * the normal variant handling
+ */
+
+ /* This catches the error that a transparent type map selects a
+ * transparent multiviews resource as the best variant.
+ *
+ * XXX: We do not signal an error if a transparent type map
+ * selects a _non_transparent multiviews resource as the best
+ * variant, because we can generate a legal negotiation response
+ * in this case. In this case, the vlist_validator of the
+ * nontransparent subrequest will be lost however. This could
+ * lead to cases in which a change in the set of variants or the
+ * negotiation algorithm of the nontransparent resource is never
+ * propagated up to a HTTP/1.1 cache which interprets Vary. To be
+ * completely on the safe side we should return HTTP_VARIANT_ALSO_VARIES
+ * for this type of recursive negotiation too.
+ */
+ if (neg->is_transparent &&
+ apr_table_get(sub_req->err_headers_out, "TCN")) {
+ return HTTP_VARIANT_ALSO_VARIES;
+ }
+
+ /* This catches the error that a transparent type map recursively
+ * selects, as the best variant, another type map which itself
+ * causes transparent negotiation to be done.
+ *
+ * XXX: Actually, we catch this error by catching all cases of
+ * type map recursion. There are some borderline recursive type
+ * map arrangements which would not produce transparent
+ * negotiation protocol errors or lack of cache propagation
+ * problems, but such arrangements are very hard to detect at this
+ * point in the control flow, so we do not bother to single them
+ * out.
+ *
+ * Recursive type maps imply a recursive arrangement of negotiated
+ * resources which is visible to outside clients, and this is not
+ * supported by the transparent negotiation caching protocols, so
+ * if we are to have generic support for recursive type maps, we
+ * have to create some configuration setting which makes all type
+ * maps non-transparent when recursion is enabled. Also, if we
+ * want recursive type map support which ensures propagation of
+ * type map changes into HTTP/1.1 caches that handle Vary, we
+ * would have to extend the current mechanism for generating
+ * variant list validators.
+ */
+ if (sub_req->handler && strcmp(sub_req->handler, "type-map") == 0) {
+ return HTTP_VARIANT_ALSO_VARIES;
+ }
+
+ /* This adds an appropriate Variant-Vary header if the subrequest
+ * is a multiviews resource.
+ *
+ * XXX: TODO: Note that this does _not_ handle any Vary header
+ * returned by a CGI if sub_req is a CGI script, because we don't
+ * see that Vary header yet at this point in the control flow.
+ * This won't cause any cache consistency problems _unless_ the
+ * CGI script also returns a Cache-Control header marking the
+ * response as cacheable. This needs to be fixed, also there are
+ * problems if a CGI returns an Etag header which also need to be
+ * fixed.
+ */
+ if ((sub_vary = apr_table_get(sub_req->err_headers_out, "Vary")) != NULL) {
+ apr_table_setn(r->err_headers_out, "Variant-Vary", sub_vary);
+
+ /* Move the subreq Vary header into the main request to
+ * prevent having two Vary headers in the response, which
+ * would be legal but strange.
+ */
+ apr_table_setn(r->err_headers_out, "Vary", sub_vary);
+ apr_table_unset(sub_req->err_headers_out, "Vary");
+ }
+
+ apr_table_setn(r->err_headers_out, "Content-Location",
+ ap_escape_path_segment(r->pool, variant->file_name));
+
+ set_neg_headers(r, neg, alg_choice); /* add Alternates and Vary */
+
+ /* Still to do by caller: add Expires */
+
+ return 0;
+}
+
+/****************************************************************
+ *
+ * Executive...
+ */
+
+static int do_negotiation(request_rec *r, negotiation_state *neg,
+ var_rec **bestp, int prefer_scripts)
+{
+ var_rec *avail_recs = (var_rec *) neg->avail_vars->elts;
+ int alg_result; /* result of variant selection algorithm */
+ int res;
+ int j;
+
+ /* Decide if resource is transparently negotiable */
+
+ /* GET or HEAD? (HEAD has same method number as GET) */
+ if (r->method_number == M_GET) {
+
+ /* maybe this should be configurable, see also the comment
+ * about recursive type maps in setup_choice_response()
+ */
+ neg->is_transparent = 1;
+
+ /* We can't be transparent if we are a map file in the middle
+ * of the request URI.
+ */
+ if (r->path_info && *r->path_info)
+ neg->is_transparent = 0;
+
+ for (j = 0; j < neg->avail_vars->nelts; ++j) {
+ var_rec *variant = &avail_recs[j];
+
+ /* We can't be transparent, because of internal
+ * assumptions in best_match(), if there is a
+ * non-neighboring variant. We can have a non-neighboring
+ * variant when processing a type map.
+ */
+ if (ap_strchr_c(variant->file_name, '/'))
+ neg->is_transparent = 0;
+
+ /* We can't be transparent, because of the behavior
+ * of variant typemap bodies.
+ */
+ if (variant->body) {
+ neg->is_transparent = 0;
+ }
+ }
+ }
+
+ if (neg->is_transparent) {
+ parse_negotiate_header(r, neg);
+ }
+ else { /* configure negotiation on non-transparent resource */
+ neg->may_choose = 1;
+ }
+
+ maybe_add_default_accepts(neg, prefer_scripts);
+
+ alg_result = best_match(neg, bestp);
+
+ /* alg_result is one of
+ * alg_choice: a best variant is chosen
+ * alg_list: no best variant is chosen
+ */
+
+ if (alg_result == alg_list) {
+ /* send a list response or HTTP_NOT_ACCEPTABLE error response */
+
+ neg->send_alternates = 1; /* always include Alternates header */
+ set_neg_headers(r, neg, alg_result);
+ store_variant_list(r, neg);
+
+ if (neg->is_transparent && neg->ua_supports_trans) {
+ /* XXX todo: expires? cachability? */
+
+ /* Some HTTP/1.0 clients are known to choke when they get
+ * a 300 (multiple choices) response without a Location
+ * header. However the 300 code response we are about
+ * to generate will only reach 1.0 clients which support
+ * transparent negotiation, and they should be OK. The
+ * response should never reach older 1.0 clients, even if
+ * we have CacheNegotiatedDocs enabled, because no 1.0
+ * proxy cache (we know of) will cache and return 300
+ * responses (they certainly won't if they conform to the
+ * HTTP/1.0 specification).
+ */
+ return HTTP_MULTIPLE_CHOICES;
+ }
+
+ if (!*bestp) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00690)
+ "no acceptable variant: %s", r->filename);
+ return HTTP_NOT_ACCEPTABLE;
+ }
+ }
+
+ /* Variant selection chose a variant */
+
+ /* XXX todo: merge the two cases in the if statement below */
+ if (neg->is_transparent) {
+
+ if ((res = setup_choice_response(r, neg, *bestp)) != 0) {
+ return res; /* return if error */
+ }
+ }
+ else {
+ set_neg_headers(r, neg, alg_result);
+ }
+
+ /* Make sure caching works - Vary should handle HTTP/1.1, but for
+ * HTTP/1.0, we can't allow caching at all.
+ */
+
+ /* XXX: Note that we only set r->no_cache to 1, which causes
+ * Expires: <now> to be added, when responding to a HTTP/1.0
+ * client. If we return the response to a 1.1 client, we do not
+ * add Expires <now>, because doing so would degrade 1.1 cache
+ * performance by preventing re-use of the response without prior
+ * revalidation. On the other hand, if the 1.1 client is a proxy
+ * which was itself contacted by a 1.0 client, or a proxy cache
+ * which can be contacted later by 1.0 clients, then we currently
+ * rely on this 1.1 proxy to add the Expires: <now> when it
+ * forwards the response.
+ *
+ * XXX: TODO: Find out if the 1.1 spec requires proxies and
+ * tunnels to add Expires: <now> when forwarding the response to
+ * 1.0 clients. I (kh) recall it is rather vague on this point.
+ * Testing actual 1.1 proxy implementations would also be nice. If
+ * Expires: <now> is not added by proxies then we need to always
+ * include Expires: <now> ourselves to ensure correct caching, but
+ * this would degrade HTTP/1.1 cache efficiency unless we also add
+ * Cache-Control: max-age=N, which we currently don't.
+ *
+ * Roy: No, we are not going to screw over HTTP future just to
+ * ensure that people who can't be bothered to upgrade their
+ * clients will always receive perfect server-side negotiation.
+ * Hell, those clients are sending bogus accept headers anyway.
+ *
+ * Manual setting of cache-control/expires always overrides this
+ * automated kluge, on purpose.
+ */
+
+ if ((!do_cache_negotiated_docs(r->server)
+ && (r->proto_num < HTTP_VERSION(1,1)))
+ && neg->count_multiviews_variants != 1) {
+ r->no_cache = 1;
+ }
+
+ return OK;
+}
+
+static int handle_map_file(request_rec *r)
+{
+ negotiation_state *neg;
+ apr_file_t *map;
+ var_rec *best;
+ int res;
+ char *udir;
+ const char *new_req;
+
+ if (strcmp(r->handler, MAP_FILE_MAGIC_TYPE) && strcmp(r->handler, "type-map")) {
+ return DECLINED;
+ }
+
+ neg = parse_accept_headers(r);
+ if ((res = read_type_map(&map, neg, r))) {
+ return res;
+ }
+
+ res = do_negotiation(r, neg, &best, 0);
+ if (res != 0) {
+ return res;
+ }
+
+ if (best->body)
+ {
+ conn_rec *c = r->connection;
+ apr_bucket_brigade *bb;
+ apr_bucket *e;
+
+ ap_allow_standard_methods(r, REPLACE_ALLOW, M_GET, M_OPTIONS,
+ M_POST, -1);
+ /* XXX: ?
+ * if (r->method_number == M_OPTIONS) {
+ * return ap_send_http_options(r);
+ *}
+ */
+ if (r->method_number != M_GET && r->method_number != M_POST) {
+ return HTTP_METHOD_NOT_ALLOWED;
+ }
+
+ /* ### These may be implemented by adding some 'extra' info
+ * of the file offset onto the etag
+ * ap_update_mtime(r, r->finfo.mtime);
+ * ap_set_last_modified(r);
+ * ap_set_etag(r);
+ */
+ ap_set_accept_ranges(r);
+ ap_set_content_length(r, best->bytes);
+
+ /* set MIME type and charset as negotiated */
+ if (best->mime_type && *best->mime_type) {
+ if (best->content_charset && *best->content_charset) {
+ ap_set_content_type(r, apr_pstrcat(r->pool,
+ best->mime_type,
+ "; charset=",
+ best->content_charset,
+ NULL));
+ }
+ else {
+ ap_set_content_type(r, apr_pstrdup(r->pool, best->mime_type));
+ }
+ }
+
+ /* set Content-language(s) as negotiated */
+ if (best->content_languages && best->content_languages->nelts) {
+ r->content_languages = apr_array_copy(r->pool,
+ best->content_languages);
+ }
+
+ /* set Content-Encoding as negotiated */
+ if (best->content_encoding && *best->content_encoding) {
+ r->content_encoding = apr_pstrdup(r->pool,
+ best->content_encoding);
+ }
+
+ if ((res = ap_meets_conditions(r)) != OK) {
+ return res;
+ }
+
+ if ((res = ap_discard_request_body(r)) != OK) {
+ return res;
+ }
+ bb = apr_brigade_create(r->pool, c->bucket_alloc);
+
+ apr_brigade_insert_file(bb, map, best->body, best->bytes, r->pool);
+
+ e = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+
+ return ap_pass_brigade_fchk(r, bb, NULL);
+ }
+
+ if (r->path_info && *r->path_info) {
+ /* remove any path_info from the end of the uri before trying
+ * to change the filename. r->path_info from the original
+ * request is passed along on the redirect.
+ */
+ r->uri[ap_find_path_info(r->uri, r->path_info)] = '\0';
+ }
+ udir = ap_make_dirstr_parent(r->pool, r->uri);
+ udir = ap_escape_uri(r->pool, udir);
+ if (r->args) {
+ if (r->path_info) {
+ new_req = apr_pstrcat(r->pool, udir, best->file_name,
+ r->path_info, "?", r->args, NULL);
+ }
+ else {
+ new_req = apr_pstrcat(r->pool, udir, best->file_name,
+ "?", r->args, NULL);
+ }
+ }
+ else {
+ new_req = apr_pstrcat(r->pool, udir, best->file_name,
+ r->path_info, NULL);
+ }
+ ap_internal_redirect(new_req, r);
+ return OK;
+}
+
+static int handle_multi(request_rec *r)
+{
+ negotiation_state *neg;
+ var_rec *best, *avail_recs;
+ request_rec *sub_req;
+ int res;
+ int j;
+
+ if (r->finfo.filetype != APR_NOFILE
+ || !(ap_allow_options(r) & OPT_MULTI)) {
+ return DECLINED;
+ }
+
+ neg = parse_accept_headers(r);
+
+ if ((res = read_types_multi(neg))) {
+ return_from_multi:
+ /* free all allocated memory from subrequests */
+ avail_recs = (var_rec *) neg->avail_vars->elts;
+ for (j = 0; j < neg->avail_vars->nelts; ++j) {
+ var_rec *variant = &avail_recs[j];
+ if (variant->sub_req) {
+ ap_destroy_sub_req(variant->sub_req);
+ }
+ }
+ return res;
+ }
+ if (neg->avail_vars->nelts == 0) {
+ return DECLINED;
+ }
+
+ res = do_negotiation(r, neg, &best,
+ (r->method_number != M_GET) || r->args ||
+ (r->path_info && *r->path_info));
+ if (res != 0)
+ goto return_from_multi;
+
+ if (!(sub_req = best->sub_req)) {
+ /* We got this out of a map file, so we don't actually have
+ * a sub_req structure yet. Get one now.
+ */
+
+ sub_req = ap_sub_req_lookup_file(best->file_name, r, r->output_filters);
+ if (sub_req->status != HTTP_OK) {
+ res = sub_req->status;
+ ap_destroy_sub_req(sub_req);
+ goto return_from_multi;
+ }
+ }
+ if (sub_req->args == NULL) {
+ sub_req->args = r->args;
+ }
+
+ /* now do a "fast redirect" ... promotes the sub_req into the main req */
+ ap_internal_fast_redirect(sub_req, r);
+
+ /* give no advise for time on this subrequest. Perhaps we
+ * should tally the last mtime among all variants, and date
+ * the most recent, but that could confuse the proxies.
+ */
+ r->mtime = 0;
+
+ /* clean up all but our favorite variant, since that sub_req
+ * is now merged into the main request!
+ */
+ avail_recs = (var_rec *) neg->avail_vars->elts;
+ for (j = 0; j < neg->avail_vars->nelts; ++j) {
+ var_rec *variant = &avail_recs[j];
+ if (variant != best && variant->sub_req) {
+ ap_destroy_sub_req(variant->sub_req);
+ }
+ }
+ return OK;
+}
+
+/**********************************************************************
+ * There is a problem with content-encoding, as some clients send and
+ * expect an x- token (e.g. x-gzip) while others expect the plain token
+ * (i.e. gzip). To try and deal with this as best as possible we do
+ * the following: if the client sent an Accept-Encoding header and it
+ * contains a plain token corresponding to the content encoding of the
+ * response, then set content encoding using the plain token. Else if
+ * the A-E header contains the x- token use the x- token in the C-E
+ * header. Else don't do anything.
+ *
+ * Note that if no A-E header was sent, or it does not contain a token
+ * compatible with the final content encoding, then the token in the
+ * C-E header will be whatever was specified in the AddEncoding
+ * directive.
+ */
+static int fix_encoding(request_rec *r)
+{
+ const char *enc = r->content_encoding;
+ char *x_enc = NULL;
+ apr_array_header_t *accept_encodings;
+ accept_rec *accept_recs;
+ int i;
+
+ if (!enc || !*enc) {
+ return DECLINED;
+ }
+
+ if (enc[0] == 'x' && enc[1] == '-') {
+ enc += 2;
+ }
+
+ if ((accept_encodings = do_header_line(r->pool,
+ apr_table_get(r->headers_in, "Accept-Encoding"))) == NULL) {
+ return DECLINED;
+ }
+
+ accept_recs = (accept_rec *) accept_encodings->elts;
+
+ for (i = 0; i < accept_encodings->nelts; ++i) {
+ char *name = accept_recs[i].name;
+
+ if (!strcmp(name, enc)) {
+ r->content_encoding = name;
+ return OK;
+ }
+
+ if (name[0] == 'x' && name[1] == '-' && !strcmp(name+2, enc)) {
+ x_enc = name;
+ }
+ }
+
+ if (x_enc) {
+ r->content_encoding = x_enc;
+ return OK;
+ }
+
+ return DECLINED;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_hook_fixups(fix_encoding,NULL,NULL,APR_HOOK_MIDDLE);
+ ap_hook_type_checker(handle_multi,NULL,NULL,APR_HOOK_FIRST);
+ ap_hook_handler(handle_map_file,NULL,NULL,APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(negotiation) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_neg_dir_config, /* dir config creator */
+ merge_neg_dir_configs, /* dir merger --- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ negotiation_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/mappers/mod_negotiation.dep b/modules/mappers/mod_negotiation.dep
new file mode 100644
index 0000000..93a58df
--- /dev/null
+++ b/modules/mappers/mod_negotiation.dep
@@ -0,0 +1,58 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_negotiation.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_negotiation.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/mappers/mod_negotiation.dsp b/modules/mappers/mod_negotiation.dsp
new file mode 100644
index 0000000..4f24316
--- /dev/null
+++ b/modules/mappers/mod_negotiation.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_negotiation" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_negotiation - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_negotiation.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_negotiation.mak" CFG="mod_negotiation - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_negotiation - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_negotiation - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_negotiation - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_negotiation_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_negotiation.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_negotiation.so" /d LONG_NAME="negotiation_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_negotiation.so" /base:@..\..\os\win32\BaseAddr.ref,mod_negotiation.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_negotiation.so" /base:@..\..\os\win32\BaseAddr.ref,mod_negotiation.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_negotiation.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_negotiation - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_negotiation_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_negotiation.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_negotiation.so" /d LONG_NAME="negotiation_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_negotiation.so" /base:@..\..\os\win32\BaseAddr.ref,mod_negotiation.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_negotiation.so" /base:@..\..\os\win32\BaseAddr.ref,mod_negotiation.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_negotiation.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_negotiation - Win32 Release"
+# Name "mod_negotiation - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_negotiation.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/mappers/mod_negotiation.exp b/modules/mappers/mod_negotiation.exp
new file mode 100644
index 0000000..a7c18da
--- /dev/null
+++ b/modules/mappers/mod_negotiation.exp
@@ -0,0 +1 @@
+negotiation_module
diff --git a/modules/mappers/mod_negotiation.mak b/modules/mappers/mod_negotiation.mak
new file mode 100644
index 0000000..6de4c00
--- /dev/null
+++ b/modules/mappers/mod_negotiation.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_negotiation.dsp
+!IF "$(CFG)" == ""
+CFG=mod_negotiation - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_negotiation - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_negotiation - Win32 Release" && "$(CFG)" != "mod_negotiation - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_negotiation.mak" CFG="mod_negotiation - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_negotiation - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_negotiation - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_negotiation - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_negotiation.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_negotiation.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_negotiation.obj"
+ -@erase "$(INTDIR)\mod_negotiation.res"
+ -@erase "$(INTDIR)\mod_negotiation_src.idb"
+ -@erase "$(INTDIR)\mod_negotiation_src.pdb"
+ -@erase "$(OUTDIR)\mod_negotiation.exp"
+ -@erase "$(OUTDIR)\mod_negotiation.lib"
+ -@erase "$(OUTDIR)\mod_negotiation.pdb"
+ -@erase "$(OUTDIR)\mod_negotiation.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_negotiation_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_negotiation.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_negotiation.so" /d LONG_NAME="negotiation_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_negotiation.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_negotiation.pdb" /debug /out:"$(OUTDIR)\mod_negotiation.so" /implib:"$(OUTDIR)\mod_negotiation.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_negotiation.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_negotiation.obj" \
+ "$(INTDIR)\mod_negotiation.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_negotiation.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_negotiation.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_negotiation.so"
+ if exist .\Release\mod_negotiation.so.manifest mt.exe -manifest .\Release\mod_negotiation.so.manifest -outputresource:.\Release\mod_negotiation.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_negotiation - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_negotiation.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_negotiation.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_negotiation.obj"
+ -@erase "$(INTDIR)\mod_negotiation.res"
+ -@erase "$(INTDIR)\mod_negotiation_src.idb"
+ -@erase "$(INTDIR)\mod_negotiation_src.pdb"
+ -@erase "$(OUTDIR)\mod_negotiation.exp"
+ -@erase "$(OUTDIR)\mod_negotiation.lib"
+ -@erase "$(OUTDIR)\mod_negotiation.pdb"
+ -@erase "$(OUTDIR)\mod_negotiation.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_negotiation_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_negotiation.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_negotiation.so" /d LONG_NAME="negotiation_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_negotiation.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_negotiation.pdb" /debug /out:"$(OUTDIR)\mod_negotiation.so" /implib:"$(OUTDIR)\mod_negotiation.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_negotiation.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_negotiation.obj" \
+ "$(INTDIR)\mod_negotiation.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_negotiation.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_negotiation.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_negotiation.so"
+ if exist .\Debug\mod_negotiation.so.manifest mt.exe -manifest .\Debug\mod_negotiation.so.manifest -outputresource:.\Debug\mod_negotiation.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_negotiation.dep")
+!INCLUDE "mod_negotiation.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_negotiation.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_negotiation - Win32 Release" || "$(CFG)" == "mod_negotiation - Win32 Debug"
+
+!IF "$(CFG)" == "mod_negotiation - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\mappers"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ELSEIF "$(CFG)" == "mod_negotiation - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\mappers"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_negotiation - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\mappers"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ELSEIF "$(CFG)" == "mod_negotiation - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\mappers"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_negotiation - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\mappers"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\mappers"
+
+!ELSEIF "$(CFG)" == "mod_negotiation - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\mappers"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\mappers"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_negotiation - Win32 Release"
+
+
+"$(INTDIR)\mod_negotiation.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_negotiation.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_negotiation.so" /d LONG_NAME="negotiation_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_negotiation - Win32 Debug"
+
+
+"$(INTDIR)\mod_negotiation.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_negotiation.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_negotiation.so" /d LONG_NAME="negotiation_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_negotiation.c
+
+"$(INTDIR)\mod_negotiation.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/mappers/mod_rewrite.c b/modules/mappers/mod_rewrite.c
new file mode 100644
index 0000000..5195cee
--- /dev/null
+++ b/modules/mappers/mod_rewrite.c
@@ -0,0 +1,5406 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* _ _ _
+ * _ __ ___ ___ __| | _ __ _____ ___ __(_) |_ ___
+ * | '_ ` _ \ / _ \ / _` | | '__/ _ \ \ /\ / / '__| | __/ _ \
+ * | | | | | | (_) | (_| | | | | __/\ V V /| | | | || __/
+ * |_| |_| |_|\___/ \__,_|___|_| \___| \_/\_/ |_| |_|\__\___|
+ * |_____|
+ *
+ * URL Rewriting Module
+ *
+ * This module uses a rule-based rewriting engine (based on a
+ * regular-expression parser) to rewrite requested URLs on the fly.
+ *
+ * It supports an unlimited number of additional rule conditions (which can
+ * operate on a lot of variables, even on HTTP headers) for granular
+ * matching and even external database lookups (either via plain text
+ * tables, DBM hash files or even external processes) for advanced URL
+ * substitution.
+ *
+ * It operates on the full URLs (including the PATH_INFO part) both in
+ * per-server context (httpd.conf) and per-dir context (.htaccess) and even
+ * can generate QUERY_STRING parts on result. The rewriting result finally
+ * can lead to internal subprocessing, external request redirection or even
+ * to internal proxy throughput.
+ *
+ * This module was originally written in April 1996 and
+ * gifted exclusively to the The Apache Software Foundation in July 1997 by
+ *
+ * Ralf S. Engelschall
+ * rse engelschall.com
+ * www.engelschall.com
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_hash.h"
+#include "apr_user.h"
+#include "apr_lib.h"
+#include "apr_signal.h"
+#include "apr_global_mutex.h"
+#include "apr_dbm.h"
+#include "apr_dbd.h"
+
+#include "apr_version.h"
+#if !APR_VERSION_AT_LEAST(2,0,0)
+#include "apu_version.h"
+#endif
+
+#include "mod_dbd.h"
+
+#if APR_HAS_THREADS
+#include "apr_thread_mutex.h"
+#endif
+
+#define APR_WANT_MEMFUNC
+#define APR_WANT_STRFUNC
+#define APR_WANT_IOVEC
+#include "apr_want.h"
+
+/* XXX: Do we really need these headers? */
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if APR_HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#if APR_HAVE_STDARG_H
+#include <stdarg.h>
+#endif
+#if APR_HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#if APR_HAVE_CTYPE_H
+#include <ctype.h>
+#endif
+#if APR_HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_request.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "http_ssl.h"
+#include "http_vhost.h"
+#include "util_mutex.h"
+
+#include "mod_rewrite.h"
+#include "ap_expr.h"
+
+static ap_dbd_t *(*dbd_acquire)(request_rec*) = NULL;
+static void (*dbd_prepare)(server_rec*, const char*, const char*) = NULL;
+static const char* really_last_key = "rewrite_really_last";
+
+/*
+ * in order to improve performance on running production systems, you
+ * may strip all rewritelog code entirely from mod_rewrite by using the
+ * -DREWRITELOG_DISABLED compiler option.
+ *
+ * DO NOT USE THIS OPTION FOR PUBLIC BINARY RELEASES. Otherwise YOU are
+ * responsible for answering all the mod_rewrite questions out there.
+ */
+/* If logging is limited to APLOG_DEBUG or lower, disable rewrite log, too */
+#ifdef APLOG_MAX_LOGLEVEL
+#if APLOG_MAX_LOGLEVEL < APLOG_TRACE1
+#ifndef REWRITELOG_DISABLED
+#define REWRITELOG_DISABLED
+#endif
+#endif
+#endif
+
+#ifndef REWRITELOG_DISABLED
+
+#define rewritelog(x) do_rewritelog x
+#define REWRITELOG_MODE ( APR_UREAD | APR_UWRITE | APR_GREAD | APR_WREAD )
+#define REWRITELOG_FLAGS ( APR_WRITE | APR_APPEND | APR_CREATE )
+
+#else /* !REWRITELOG_DISABLED */
+
+#define rewritelog(x)
+
+#endif /* REWRITELOG_DISABLED */
+
+/* remembered mime-type for [T=...] */
+#define REWRITE_FORCED_MIMETYPE_NOTEVAR "rewrite-forced-mimetype"
+#define REWRITE_FORCED_HANDLER_NOTEVAR "rewrite-forced-handler"
+
+#define ENVVAR_SCRIPT_URL "SCRIPT_URL"
+#define REDIRECT_ENVVAR_SCRIPT_URL "REDIRECT_" ENVVAR_SCRIPT_URL
+#define ENVVAR_SCRIPT_URI "SCRIPT_URI"
+
+#define CONDFLAG_NONE (1<<0)
+#define CONDFLAG_NOCASE (1<<1)
+#define CONDFLAG_NOTMATCH (1<<2)
+#define CONDFLAG_ORNEXT (1<<3)
+#define CONDFLAG_NOVARY (1<<4)
+
+#define RULEFLAG_NONE (1<<0)
+#define RULEFLAG_FORCEREDIRECT (1<<1)
+#define RULEFLAG_LASTRULE (1<<2)
+#define RULEFLAG_NEWROUND (1<<3)
+#define RULEFLAG_CHAIN (1<<4)
+#define RULEFLAG_IGNOREONSUBREQ (1<<5)
+#define RULEFLAG_NOTMATCH (1<<6)
+#define RULEFLAG_PROXY (1<<7)
+#define RULEFLAG_PASSTHROUGH (1<<8)
+#define RULEFLAG_QSAPPEND (1<<9)
+#define RULEFLAG_NOCASE (1<<10)
+#define RULEFLAG_NOESCAPE (1<<11)
+#define RULEFLAG_NOSUB (1<<12)
+#define RULEFLAG_STATUS (1<<13)
+#define RULEFLAG_ESCAPEBACKREF (1<<14)
+#define RULEFLAG_DISCARDPATHINFO (1<<15)
+#define RULEFLAG_QSDISCARD (1<<16)
+#define RULEFLAG_END (1<<17)
+#define RULEFLAG_ESCAPENOPLUS (1<<18)
+#define RULEFLAG_QSLAST (1<<19)
+#define RULEFLAG_QSNONE (1<<20) /* programattic only */
+
+/* return code of the rewrite rule
+ * the result may be escaped - or not
+ */
+#define ACTION_NORMAL (1<<0)
+#define ACTION_NOESCAPE (1<<1)
+#define ACTION_STATUS (1<<2)
+
+
+#define MAPTYPE_TXT (1<<0)
+#define MAPTYPE_DBM (1<<1)
+#define MAPTYPE_PRG (1<<2)
+#define MAPTYPE_INT (1<<3)
+#define MAPTYPE_RND (1<<4)
+#define MAPTYPE_DBD (1<<5)
+#define MAPTYPE_DBD_CACHE (1<<6)
+
+#define ENGINE_DISABLED (1<<0)
+#define ENGINE_ENABLED (1<<1)
+
+#define OPTION_NONE (1<<0)
+#define OPTION_INHERIT (1<<1)
+#define OPTION_INHERIT_BEFORE (1<<2)
+#define OPTION_NOSLASH (1<<3)
+#define OPTION_ANYURI (1<<4)
+#define OPTION_MERGEBASE (1<<5)
+#define OPTION_INHERIT_DOWN (1<<6)
+#define OPTION_INHERIT_DOWN_BEFORE (1<<7)
+#define OPTION_IGNORE_INHERIT (1<<8)
+#define OPTION_IGNORE_CONTEXT_INFO (1<<9)
+#define OPTION_LEGACY_PREFIX_DOCROOT (1<<10)
+
+#ifndef RAND_MAX
+#define RAND_MAX 32767
+#endif
+
+/* max cookie size in rfc 2109 */
+/* XXX: not used at all. We should do a check somewhere and/or cut the cookie */
+#define MAX_COOKIE_LEN 4096
+
+/* max line length (incl.\n) in text rewrite maps */
+#ifndef REWRITE_MAX_TXT_MAP_LINE
+#define REWRITE_MAX_TXT_MAP_LINE 1024
+#endif
+
+/* buffer length for prg rewrite maps */
+#ifndef REWRITE_PRG_MAP_BUF
+#define REWRITE_PRG_MAP_BUF 1024
+#endif
+
+/* for better readbility */
+#define LEFT_CURLY '{'
+#define RIGHT_CURLY '}'
+
+/*
+ * expansion result items on the stack to save some cycles
+ *
+ * (5 == about 2 variables like "foo%{var}bar%{var}baz")
+ */
+#define SMALL_EXPANSION 5
+
+/*
+ * check that a subrequest won't cause infinite recursion
+ *
+ * either not in a subrequest, or in a subrequest
+ * and URIs aren't NULL and sub/main URIs differ
+ */
+#define subreq_ok(r) (!r->main || \
+ (r->main->uri && r->uri && strcmp(r->main->uri, r->uri)))
+
+#ifndef REWRITE_MAX_ROUNDS
+#define REWRITE_MAX_ROUNDS 32000
+#endif
+
+/*
+ * +-------------------------------------------------------+
+ * | |
+ * | Types and Structures
+ * | |
+ * +-------------------------------------------------------+
+ */
+
+typedef struct {
+ const char *datafile; /* filename for map data files */
+ const char *dbmtype; /* dbm type for dbm map data files */
+ const char *checkfile; /* filename to check for map existence */
+ const char *cachename; /* for cached maps (txt/rnd/dbm) */
+ int type; /* the type of the map */
+ apr_file_t *fpin; /* in file pointer for program maps */
+ apr_file_t *fpout; /* out file pointer for program maps */
+ apr_file_t *fperr; /* err file pointer for program maps */
+ char *(*func)(request_rec *, /* function pointer for internal maps */
+ char *);
+ char **argv; /* argv of the external rewrite map */
+ const char *dbdq; /* SQL SELECT statement for rewritemap */
+ const char *checkfile2; /* filename to check for map existence
+ NULL if only one file */
+ const char *user; /* run RewriteMap program as this user */
+ const char *group; /* run RewriteMap program as this group */
+} rewritemap_entry;
+
+/* special pattern types for RewriteCond */
+typedef enum {
+ CONDPAT_REGEX = 0,
+ CONDPAT_FILE_EXISTS,
+ CONDPAT_FILE_SIZE,
+ CONDPAT_FILE_LINK,
+ CONDPAT_FILE_DIR,
+ CONDPAT_FILE_XBIT,
+ CONDPAT_LU_URL,
+ CONDPAT_LU_FILE,
+ CONDPAT_STR_LT,
+ CONDPAT_STR_LE,
+ CONDPAT_STR_EQ,
+ CONDPAT_STR_GT,
+ CONDPAT_STR_GE,
+ CONDPAT_INT_LT,
+ CONDPAT_INT_LE,
+ CONDPAT_INT_EQ,
+ CONDPAT_INT_GT,
+ CONDPAT_INT_GE,
+ CONDPAT_AP_EXPR
+} pattern_type;
+
+typedef struct {
+ char *input; /* Input string of RewriteCond */
+ char *pattern; /* the RegExp pattern string */
+ ap_regex_t *regexp; /* the precompiled regexp */
+ ap_expr_info_t *expr; /* the compiled ap_expr */
+ int flags; /* Flags which control the match */
+ pattern_type ptype; /* pattern type */
+ int pskip; /* back-index to display pattern */
+} rewritecond_entry;
+
+/* single linked list for env vars and cookies */
+typedef struct data_item {
+ struct data_item *next;
+ char *data;
+} data_item;
+
+typedef struct {
+ apr_array_header_t *rewriteconds;/* the corresponding RewriteCond entries */
+ char *pattern; /* the RegExp pattern string */
+ ap_regex_t *regexp; /* the RegExp pattern compilation */
+ char *output; /* the Substitution string */
+ int flags; /* Flags which control the substitution */
+ char *forced_mimetype; /* forced MIME type of substitution */
+ char *forced_handler; /* forced content handler of subst. */
+ int forced_responsecode; /* forced HTTP response status */
+ data_item *env; /* added environment variables */
+ data_item *cookie; /* added cookies */
+ int skip; /* number of next rules to skip */
+ int maxrounds; /* limit on number of loops with N flag */
+ char *escapes; /* specific backref escapes */
+} rewriterule_entry;
+
+typedef struct {
+ int state; /* the RewriteEngine state */
+ int options; /* the RewriteOption state */
+ apr_hash_t *rewritemaps; /* the RewriteMap entries */
+ apr_array_header_t *rewriteconds; /* the RewriteCond entries (temp.) */
+ apr_array_header_t *rewriterules; /* the RewriteRule entries */
+ server_rec *server; /* the corresponding server indicator */
+ unsigned int state_set:1;
+ unsigned int options_set:1;
+} rewrite_server_conf;
+
+typedef struct {
+ int state; /* the RewriteEngine state */
+ int options; /* the RewriteOption state */
+ apr_array_header_t *rewriteconds; /* the RewriteCond entries (temp.) */
+ apr_array_header_t *rewriterules; /* the RewriteRule entries */
+ char *directory; /* the directory where it applies */
+ const char *baseurl; /* the base-URL where it applies */
+ unsigned int state_set:1;
+ unsigned int options_set:1;
+ unsigned int baseurl_set:1;
+} rewrite_perdir_conf;
+
+/* the (per-child) cache structures.
+ */
+typedef struct cache {
+ apr_pool_t *pool;
+ apr_hash_t *maps;
+#if APR_HAS_THREADS
+ apr_thread_mutex_t *lock;
+#endif
+} cache;
+
+/* cached maps contain an mtime for the whole map and live in a subpool
+ * of the cachep->pool. That makes it easy to forget them if necessary.
+ */
+typedef struct {
+ apr_time_t mtime;
+ apr_pool_t *pool;
+ apr_hash_t *entries;
+} cachedmap;
+
+/* the regex structure for the
+ * substitution of backreferences
+ */
+typedef struct backrefinfo {
+ const char *source;
+ ap_regmatch_t regmatch[AP_MAX_REG_MATCH];
+} backrefinfo;
+
+/* single linked list used for
+ * variable expansion
+ */
+typedef struct result_list {
+ struct result_list *next;
+ apr_size_t len;
+ const char *string;
+} result_list;
+
+/* context structure for variable lookup and expansion
+ */
+typedef struct {
+ request_rec *r;
+ const char *uri;
+ const char *vary_this;
+ const char *vary;
+ char *perdir;
+ backrefinfo briRR;
+ backrefinfo briRC;
+} rewrite_ctx;
+
+/*
+ * +-------------------------------------------------------+
+ * | |
+ * | static module data
+ * | |
+ * +-------------------------------------------------------+
+ */
+
+/* the global module structure */
+module AP_MODULE_DECLARE_DATA rewrite_module;
+
+/* rewritemap int: handler function registry */
+static apr_hash_t *mapfunc_hash;
+
+/* the cache */
+static cache *cachep;
+
+/* whether proxy module is available or not */
+static int proxy_available;
+
+/* Locks/Mutexes */
+static int rewrite_lock_needed = 0;
+static apr_global_mutex_t *rewrite_mapr_lock_acquire = NULL;
+static const char *rewritemap_mutex_type = "rewrite-map";
+
+/* Optional functions imported from mod_ssl when loaded: */
+static char *escape_backref(apr_pool_t *p, const char *path, const char *escapeme, int noplus);
+
+/*
+ * +-------------------------------------------------------+
+ * | |
+ * | rewriting logfile support
+ * | |
+ * +-------------------------------------------------------+
+ */
+
+#ifndef REWRITELOG_DISABLED
+static void do_rewritelog(request_rec *r, int level, char *perdir,
+ const char *fmt, ...)
+ __attribute__((format(printf,4,5)));
+
+static void do_rewritelog(request_rec *r, int level, char *perdir,
+ const char *fmt, ...)
+{
+ char *logline, *text;
+ const char *rhost, *rname;
+ int redir;
+ request_rec *req;
+ va_list ap;
+
+ if (!APLOG_R_IS_LEVEL(r, APLOG_DEBUG + level))
+ return;
+
+ rhost = ap_get_useragent_host(r, REMOTE_NOLOOKUP, NULL);
+ rname = ap_get_remote_logname(r);
+
+ for (redir=0, req=r; req->prev; req = req->prev) {
+ ++redir;
+ }
+
+ va_start(ap, fmt);
+ text = apr_pvsprintf(r->pool, fmt, ap);
+ va_end(ap);
+
+ logline = apr_psprintf(r->pool, "%s %s %s [%s/sid#%pp][rid#%pp/%s%s%s] "
+ "%s%s%s%s",
+ rhost ? rhost : "UNKNOWN-HOST",
+ rname ? rname : "-",
+ r->user ? (*r->user ? r->user : "\"\"") : "-",
+ ap_get_server_name(r),
+ (void *)(r->server),
+ (void *)r,
+ r->main ? "subreq" : "initial",
+ redir ? "/redir#" : "",
+ redir ? apr_itoa(r->pool, redir) : "",
+ perdir ? "[perdir " : "",
+ perdir ? perdir : "",
+ perdir ? "] ": "",
+ text);
+
+ AP_REWRITE_LOG((uintptr_t)r, level, r->main ? 0 : 1, (char *)ap_get_server_name(r), logline);
+
+ /* Intentional no APLOGNO */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG + level, 0, r, "%s", logline);
+
+ return;
+}
+#endif /* !REWRITELOG_DISABLED */
+
+
+/*
+ * +-------------------------------------------------------+
+ * | |
+ * | URI and path functions
+ * | |
+ * +-------------------------------------------------------+
+ */
+
+/* return number of chars of the scheme (incl. '://')
+ * if the URI is absolute (includes a scheme etc.)
+ * otherwise 0.
+ * If supportqs is not NULL, we return a whether or not
+ * the scheme supports a query string or not.
+ *
+ * NOTE: If you add new schemes here, please have a
+ * look at escape_absolute_uri and splitout_queryargs.
+ * Not every scheme takes query strings and some schemes
+ * may be handled in a special way.
+ *
+ * XXX: we may consider a scheme registry, perhaps with
+ * appropriate escape callbacks to allow other modules
+ * to extend mod_rewrite at runtime.
+ */
+static unsigned is_absolute_uri(char *uri, int *supportsqs)
+{
+ int dummy, *sqs;
+
+ sqs = (supportsqs ? supportsqs : &dummy);
+ *sqs = 0;
+ /* fast exit */
+ if (*uri == '/' || strlen(uri) <= 5) {
+ return 0;
+ }
+
+ switch (*uri++) {
+ case 'a':
+ case 'A':
+ if (!ap_cstr_casecmpn(uri, "jp://", 5)) { /* ajp:// */
+ *sqs = 1;
+ return 6;
+ }
+ break;
+
+ case 'b':
+ case 'B':
+ if (!ap_cstr_casecmpn(uri, "alancer://", 10)) { /* balancer:// */
+ *sqs = 1;
+ return 11;
+ }
+ break;
+
+ case 'f':
+ case 'F':
+ if (!ap_cstr_casecmpn(uri, "tp://", 5)) { /* ftp:// */
+ return 6;
+ }
+ if (!ap_cstr_casecmpn(uri, "cgi://", 6)) { /* fcgi:// */
+ *sqs = 1;
+ return 7;
+ }
+ break;
+
+ case 'g':
+ case 'G':
+ if (!ap_cstr_casecmpn(uri, "opher://", 8)) { /* gopher:// */
+ return 9;
+ }
+ break;
+
+ case 'h':
+ case 'H':
+ if (!ap_cstr_casecmpn(uri, "ttp://", 6)) { /* http:// */
+ *sqs = 1;
+ return 7;
+ }
+ else if (!ap_cstr_casecmpn(uri, "ttps://", 7)) { /* https:// */
+ *sqs = 1;
+ return 8;
+ }
+ else if (!ap_cstr_casecmpn(uri, "2://", 4)) { /* h2:// */
+ *sqs = 1;
+ return 5;
+ }
+ else if (!ap_cstr_casecmpn(uri, "2c://", 5)) { /* h2c:// */
+ *sqs = 1;
+ return 6;
+ }
+ break;
+
+ case 'l':
+ case 'L':
+ if (!ap_cstr_casecmpn(uri, "dap://", 6)) { /* ldap:// */
+ return 7;
+ }
+ break;
+
+ case 'm':
+ case 'M':
+ if (!ap_cstr_casecmpn(uri, "ailto:", 6)) { /* mailto: */
+ *sqs = 1;
+ return 7;
+ }
+ break;
+
+ case 'n':
+ case 'N':
+ if (!ap_cstr_casecmpn(uri, "ews:", 4)) { /* news: */
+ return 5;
+ }
+ else if (!ap_cstr_casecmpn(uri, "ntp://", 6)) { /* nntp:// */
+ return 7;
+ }
+ break;
+
+ case 's':
+ case 'S':
+ if (!ap_cstr_casecmpn(uri, "cgi://", 6)) { /* scgi:// */
+ *sqs = 1;
+ return 7;
+ }
+ break;
+
+ case 'w':
+ case 'W':
+ if (!ap_cstr_casecmpn(uri, "s://", 4)) { /* ws:// */
+ *sqs = 1;
+ return 5;
+ }
+ else if (!ap_cstr_casecmpn(uri, "ss://", 5)) { /* wss:// */
+ *sqs = 1;
+ return 6;
+ }
+ break;
+
+ case 'u':
+ case 'U':
+ if (!ap_cstr_casecmpn(uri, "nix:", 4)) { /* unix: */
+ *sqs = 1;
+ return (uri[4] == '/' && uri[5] == '/') ? 7 : 5;
+ }
+ }
+
+ return 0;
+}
+
+static const char c2x_table[] = "0123456789abcdef";
+
+static APR_INLINE unsigned char *c2x(unsigned what, unsigned char prefix,
+ unsigned char *where)
+{
+#if APR_CHARSET_EBCDIC
+ what = apr_xlate_conv_byte(ap_hdrs_to_ascii, (unsigned char)what);
+#endif /*APR_CHARSET_EBCDIC*/
+ *where++ = prefix;
+ *where++ = c2x_table[what >> 4];
+ *where++ = c2x_table[what & 0xf];
+ return where;
+}
+
+/*
+ * Escapes a backreference in a similar way as php's urlencode does.
+ * Based on ap_os_escape_path in server/util.c
+ */
+static char *escape_backref(apr_pool_t *p, const char *path, const char *escapeme, int noplus) {
+ char *copy = apr_palloc(p, 3 * strlen(path) + 3);
+ const unsigned char *s = (const unsigned char *)path;
+ unsigned char *d = (unsigned char *)copy;
+ unsigned c;
+
+ while ((c = *s)) {
+ if (!escapeme) {
+ if (apr_isalnum(c) || c == '_') {
+ *d++ = c;
+ }
+ else if (c == ' ' && !noplus) {
+ *d++ = '+';
+ }
+ else {
+ d = c2x(c, '%', d);
+ }
+ }
+ else {
+ const char *esc = escapeme;
+ while (*esc) {
+ if (c == *esc) {
+ if (c == ' ' && !noplus) {
+ *d++ = '+';
+ }
+ else {
+ d = c2x(c, '%', d);
+ }
+ break;
+ }
+ ++esc;
+ }
+ if (!*esc) {
+ *d++ = c;
+ }
+ }
+ ++s;
+ }
+ *d = '\0';
+ return copy;
+}
+
+/*
+ * escape absolute uri, which may or may not be path oriented.
+ * So let's handle them differently.
+ */
+static char *escape_absolute_uri(apr_pool_t *p, char *uri, unsigned scheme)
+{
+ char *cp;
+
+ /* be safe.
+ * NULL should indicate elsewhere, that something's wrong
+ */
+ if (!scheme || strlen(uri) < scheme) {
+ return NULL;
+ }
+
+ cp = uri + scheme;
+
+ /* scheme with authority part? */
+ if (cp[-1] == '/') {
+ /* skip host part */
+ while (*cp && *cp != '/') {
+ ++cp;
+ }
+
+ /* nothing after the hostpart. ready! */
+ if (!*cp || !*++cp) {
+ return apr_pstrdup(p, uri);
+ }
+
+ /* remember the hostname stuff */
+ scheme = cp - uri;
+
+ /* special thing for ldap.
+ * The parts are separated by question marks. From RFC 2255:
+ * ldapurl = scheme "://" [hostport] ["/"
+ * [dn ["?" [attributes] ["?" [scope]
+ * ["?" [filter] ["?" extensions]]]]]]
+ */
+ if (!ap_cstr_casecmpn(uri, "ldap", 4)) {
+ char *token[5];
+ int c = 0;
+
+ token[0] = cp = apr_pstrdup(p, cp);
+ while (*cp && c < 4) {
+ if (*cp == '?') {
+ token[++c] = cp + 1;
+ *cp = '\0';
+ }
+ ++cp;
+ }
+
+ return apr_pstrcat(p, apr_pstrndup(p, uri, scheme),
+ ap_escape_uri(p, token[0]),
+ (c >= 1) ? "?" : NULL,
+ (c >= 1) ? ap_escape_uri(p, token[1]) : NULL,
+ (c >= 2) ? "?" : NULL,
+ (c >= 2) ? ap_escape_uri(p, token[2]) : NULL,
+ (c >= 3) ? "?" : NULL,
+ (c >= 3) ? ap_escape_uri(p, token[3]) : NULL,
+ (c >= 4) ? "?" : NULL,
+ (c >= 4) ? ap_escape_uri(p, token[4]) : NULL,
+ NULL);
+ }
+ }
+
+ /* Nothing special here. Apply normal escaping. */
+ return apr_pstrcat(p, apr_pstrndup(p, uri, scheme),
+ ap_escape_uri(p, cp), NULL);
+}
+
+/*
+ * split out a QUERY_STRING part from
+ * the current URI string
+ */
+static void splitout_queryargs(request_rec *r, int flags)
+{
+ char *q;
+ int split, skip;
+ int qsappend = flags & RULEFLAG_QSAPPEND;
+ int qsdiscard = flags & RULEFLAG_QSDISCARD;
+ int qslast = flags & RULEFLAG_QSLAST;
+
+ if (flags & RULEFLAG_QSNONE) {
+ rewritelog((r, 2, NULL, "discarding query string, no parse from substitution"));
+ r->args = NULL;
+ return;
+ }
+
+ /* don't touch, unless it's a scheme for which a query string makes sense.
+ * See RFC 1738 and RFC 2368.
+ */
+ if ((skip = is_absolute_uri(r->filename, &split))
+ && !split) {
+ r->args = NULL; /* forget the query that's still flying around */
+ return;
+ }
+
+ if (qsdiscard) {
+ r->args = NULL; /* Discard query string */
+ rewritelog((r, 2, NULL, "discarding query string"));
+ }
+
+ q = qslast ? ap_strrchr(r->filename + skip, '?') : ap_strchr(r->filename + skip, '?');
+
+ if (q != NULL) {
+ char *olduri;
+ apr_size_t len;
+
+ olduri = apr_pstrdup(r->pool, r->filename);
+ *q++ = '\0';
+ if (qsappend) {
+ if (*q) {
+ r->args = apr_pstrcat(r->pool, q, "&" , r->args, NULL);
+ }
+ }
+ else {
+ r->args = apr_pstrdup(r->pool, q);
+ }
+
+ if (r->args) {
+ len = strlen(r->args);
+
+ if (!len) {
+ r->args = NULL;
+ }
+ else if (r->args[len-1] == '&') {
+ r->args[len-1] = '\0';
+ }
+ }
+
+ rewritelog((r, 3, NULL, "split uri=%s -> uri=%s, args=%s", olduri,
+ r->filename, r->args ? r->args : "<none>"));
+ }
+}
+
+/*
+ * strip 'http[s]://ourhost/' from URI
+ */
+static void reduce_uri(request_rec *r)
+{
+ char *cp;
+ apr_size_t l;
+
+ cp = (char *)ap_http_scheme(r);
+ l = strlen(cp);
+ if ( strlen(r->filename) > l+3
+ && ap_cstr_casecmpn(r->filename, cp, l) == 0
+ && r->filename[l] == ':'
+ && r->filename[l+1] == '/'
+ && r->filename[l+2] == '/' ) {
+
+ unsigned short port;
+ char *portp, *host, *url, *scratch;
+
+ scratch = apr_pstrdup(r->pool, r->filename); /* our scratchpad */
+
+ /* cut the hostname and port out of the URI */
+ cp = host = scratch + l + 3; /* 3 == strlen("://") */
+ while (*cp && *cp != '/' && *cp != ':') {
+ ++cp;
+ }
+
+ if (*cp == ':') { /* additional port given */
+ *cp++ = '\0';
+ portp = cp;
+ while (*cp && *cp != '/') {
+ ++cp;
+ }
+ *cp = '\0';
+
+ port = atoi(portp);
+ url = r->filename + (cp - scratch);
+ if (!*url) {
+ url = "/";
+ }
+ }
+ else if (*cp == '/') { /* default port */
+ *cp = '\0';
+
+ port = ap_default_port(r);
+ url = r->filename + (cp - scratch);
+ }
+ else {
+ port = ap_default_port(r);
+ url = "/";
+ }
+
+ /* now check whether we could reduce it to a local path... */
+ if (ap_matches_request_vhost(r, host, port)) {
+ rewrite_server_conf *conf =
+ ap_get_module_config(r->server->module_config, &rewrite_module);
+ rewritelog((r, 3, NULL, "reduce %s -> %s", r->filename, url));
+ r->filename = apr_pstrdup(r->pool, url);
+
+ /* remember that the uri was reduced */
+ if(!(conf->options & OPTION_LEGACY_PREFIX_DOCROOT)) {
+ apr_table_setn(r->notes, "mod_rewrite_uri_reduced", "true");
+ }
+ }
+ }
+
+ return;
+}
+
+/*
+ * add 'http[s]://ourhost[:ourport]/' to URI
+ * if URI is still not fully qualified
+ */
+static void fully_qualify_uri(request_rec *r)
+{
+ if (r->method_number == M_CONNECT) {
+ return;
+ }
+ else if (!is_absolute_uri(r->filename, NULL)) {
+ const char *thisserver;
+ char *thisport;
+ int port;
+
+ thisserver = ap_get_server_name_for_url(r);
+ port = ap_get_server_port(r);
+ thisport = ap_is_default_port(port, r)
+ ? ""
+ : apr_psprintf(r->pool, ":%u", port);
+
+ r->filename = apr_psprintf(r->pool, "%s://%s%s%s%s",
+ ap_http_scheme(r), thisserver, thisport,
+ (*r->filename == '/') ? "" : "/",
+ r->filename);
+ }
+
+ return;
+}
+
+/*
+ * stat() only the first segment of a path
+ */
+static int prefix_stat(const char *path, apr_pool_t *pool)
+{
+ const char *curpath = path;
+ const char *root;
+ const char *slash;
+ char *statpath;
+ apr_status_t rv;
+
+ rv = apr_filepath_root(&root, &curpath, APR_FILEPATH_TRUENAME, pool);
+
+ if (rv != APR_SUCCESS) {
+ return 0;
+ }
+
+ /* let's recognize slashes only, the mod_rewrite semantics are opaque
+ * enough.
+ */
+ if ((slash = ap_strchr_c(curpath, '/')) != NULL) {
+ rv = apr_filepath_merge(&statpath, root,
+ apr_pstrndup(pool, curpath,
+ (apr_size_t)(slash - curpath)),
+ APR_FILEPATH_NOTABOVEROOT |
+ APR_FILEPATH_NOTRELATIVE, pool);
+ }
+ else {
+ rv = apr_filepath_merge(&statpath, root, curpath,
+ APR_FILEPATH_NOTABOVEROOT |
+ APR_FILEPATH_NOTRELATIVE, pool);
+ }
+
+ if (rv == APR_SUCCESS) {
+ apr_finfo_t sb;
+
+ if (apr_stat(&sb, statpath, APR_FINFO_MIN, pool) == APR_SUCCESS) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * substitute the prefix path 'match' in 'input' with 'subst' (RewriteBase)
+ */
+static char *subst_prefix_path(request_rec *r, char *input, const char *match,
+ const char *subst)
+{
+ apr_size_t len = strlen(match);
+
+ if (len && match[len - 1] == '/') {
+ --len;
+ }
+
+ if (!strncmp(input, match, len) && input[len++] == '/') {
+ apr_size_t slen, outlen;
+ char *output;
+
+ rewritelog((r, 5, NULL, "strip matching prefix: %s -> %s", input,
+ input+len));
+
+ slen = strlen(subst);
+ if (slen && subst[slen - 1] != '/') {
+ ++slen;
+ }
+
+ outlen = strlen(input) + slen - len;
+ output = apr_palloc(r->pool, outlen + 1); /* don't forget the \0 */
+
+ memcpy(output, subst, slen);
+ if (slen && !output[slen-1]) {
+ output[slen-1] = '/';
+ }
+ memcpy(output+slen, input+len, outlen - slen);
+ output[outlen] = '\0';
+
+ rewritelog((r, 4, NULL, "add subst prefix: %s -> %s", input+len,
+ output));
+
+ return output;
+ }
+
+ /* prefix didn't match */
+ return input;
+}
+
+
+/*
+ * +-------------------------------------------------------+
+ * | |
+ * | caching support
+ * | |
+ * +-------------------------------------------------------+
+ */
+
+static void set_cache_value(const char *name, apr_time_t t, char *key,
+ char *val)
+{
+ cachedmap *map;
+
+ if (cachep) {
+#if APR_HAS_THREADS
+ apr_thread_mutex_lock(cachep->lock);
+#endif
+ map = apr_hash_get(cachep->maps, name, APR_HASH_KEY_STRING);
+
+ if (!map) {
+ apr_pool_t *p;
+
+ if (apr_pool_create(&p, cachep->pool) != APR_SUCCESS) {
+#if APR_HAS_THREADS
+ apr_thread_mutex_unlock(cachep->lock);
+#endif
+ return;
+ }
+ apr_pool_tag(p, "rewrite_cachedmap");
+
+ map = apr_palloc(cachep->pool, sizeof(cachedmap));
+ map->pool = p;
+ map->entries = apr_hash_make(map->pool);
+ map->mtime = t;
+
+ apr_hash_set(cachep->maps, name, APR_HASH_KEY_STRING, map);
+ }
+ else if (map->mtime != t) {
+ apr_pool_clear(map->pool);
+ map->entries = apr_hash_make(map->pool);
+ map->mtime = t;
+ }
+
+ /* Now we should have a valid map->entries hash, where we
+ * can store our value.
+ *
+ * We need to copy the key and the value into OUR pool,
+ * so that we don't leave it during the r->pool cleanup.
+ */
+ apr_hash_set(map->entries,
+ apr_pstrdup(map->pool, key), APR_HASH_KEY_STRING,
+ apr_pstrdup(map->pool, val));
+
+#if APR_HAS_THREADS
+ apr_thread_mutex_unlock(cachep->lock);
+#endif
+ }
+
+ return;
+}
+
+static char *get_cache_value(const char *name, apr_time_t t, char *key,
+ apr_pool_t *p)
+{
+ cachedmap *map;
+ char *val = NULL;
+
+ if (cachep) {
+#if APR_HAS_THREADS
+ apr_thread_mutex_lock(cachep->lock);
+#endif
+ map = apr_hash_get(cachep->maps, name, APR_HASH_KEY_STRING);
+
+ if (map) {
+ /* if this map is outdated, forget it. */
+ if (map->mtime != t) {
+ apr_pool_clear(map->pool);
+ map->entries = apr_hash_make(map->pool);
+ map->mtime = t;
+ }
+ else {
+ val = apr_hash_get(map->entries, key, APR_HASH_KEY_STRING);
+ if (val) {
+ /* copy the cached value into the supplied pool,
+ * where it belongs (r->pool usually)
+ */
+ val = apr_pstrdup(p, val);
+ }
+ }
+ }
+
+#if APR_HAS_THREADS
+ apr_thread_mutex_unlock(cachep->lock);
+#endif
+ }
+
+ return val;
+}
+
+static int init_cache(apr_pool_t *p)
+{
+ cachep = apr_palloc(p, sizeof(cache));
+ if (apr_pool_create(&cachep->pool, p) != APR_SUCCESS) {
+ cachep = NULL; /* turns off cache */
+ return 0;
+ }
+ apr_pool_tag(cachep->pool, "rewrite_cachep");
+
+ cachep->maps = apr_hash_make(cachep->pool);
+#if APR_HAS_THREADS
+ (void)apr_thread_mutex_create(&(cachep->lock), APR_THREAD_MUTEX_DEFAULT, p);
+#endif
+
+ return 1;
+}
+
+
+/*
+ * +-------------------------------------------------------+
+ * | |
+ * | Map Functions
+ * | |
+ * +-------------------------------------------------------+
+ */
+
+/*
+ * General Note: key is already a fresh string, created (expanded) just
+ * for the purpose to be passed in here. So one can modify key itself.
+ */
+
+static char *rewrite_mapfunc_toupper(request_rec *r, char *key)
+{
+ ap_str_toupper(key);
+
+ return key;
+}
+
+static char *rewrite_mapfunc_tolower(request_rec *r, char *key)
+{
+ ap_str_tolower(key);
+
+ return key;
+}
+
+static char *rewrite_mapfunc_escape(request_rec *r, char *key)
+{
+ return ap_escape_uri(r->pool, key);
+}
+
+static char *rewrite_mapfunc_unescape(request_rec *r, char *key)
+{
+ ap_unescape_url(key);
+
+ return key;
+}
+
+static char *select_random_value_part(request_rec *r, char *value)
+{
+ char *p = value;
+ unsigned n = 1;
+
+ /* count number of distinct values */
+ while ((p = ap_strchr(p, '|')) != NULL) {
+ ++n;
+ ++p;
+ }
+
+ if (n > 1) {
+ n = ap_random_pick(1, n);
+
+ /* extract it from the whole string */
+ while (--n && (value = ap_strchr(value, '|')) != NULL) {
+ ++value;
+ }
+
+ if (value) { /* should not be NULL, but ... */
+ p = ap_strchr(value, '|');
+ if (p) {
+ *p = '\0';
+ }
+ }
+ }
+
+ return value;
+}
+
+/* child process code */
+static void rewrite_child_errfn(apr_pool_t *p, apr_status_t err,
+ const char *desc)
+{
+ ap_log_error(APLOG_MARK, APLOG_ERR, err, NULL, APLOGNO(00653) "%s", desc);
+}
+
+static apr_status_t rewritemap_program_child(apr_pool_t *p,
+ const char *progname, char **argv,
+ const char *user, const char *group,
+ apr_file_t **fpout,
+ apr_file_t **fpin)
+{
+ apr_status_t rc;
+ apr_procattr_t *procattr;
+ apr_proc_t *procnew;
+
+ if ( APR_SUCCESS == (rc=apr_procattr_create(&procattr, p))
+ && APR_SUCCESS == (rc=apr_procattr_io_set(procattr, APR_FULL_BLOCK,
+ APR_FULL_BLOCK, APR_NO_PIPE))
+ && APR_SUCCESS == (rc=apr_procattr_dir_set(procattr,
+ ap_make_dirstr_parent(p, argv[0])))
+ && (!user || APR_SUCCESS == (rc=apr_procattr_user_set(procattr, user, "")))
+ && (!group || APR_SUCCESS == (rc=apr_procattr_group_set(procattr, group)))
+ && APR_SUCCESS == (rc=apr_procattr_cmdtype_set(procattr, APR_PROGRAM))
+ && APR_SUCCESS == (rc=apr_procattr_child_errfn_set(procattr,
+ rewrite_child_errfn))
+ && APR_SUCCESS == (rc=apr_procattr_error_check_set(procattr, 1))) {
+
+ procnew = apr_pcalloc(p, sizeof(*procnew));
+ rc = apr_proc_create(procnew, argv[0], (const char **)argv, NULL,
+ procattr, p);
+
+ if (rc == APR_SUCCESS) {
+ apr_pool_note_subprocess(p, procnew, APR_KILL_AFTER_TIMEOUT);
+
+ if (fpin) {
+ (*fpin) = procnew->in;
+ }
+
+ if (fpout) {
+ (*fpout) = procnew->out;
+ }
+ }
+ }
+
+ return (rc);
+}
+
+static apr_status_t run_rewritemap_programs(server_rec *s, apr_pool_t *p)
+{
+ rewrite_server_conf *conf;
+ apr_hash_index_t *hi;
+ apr_status_t rc;
+
+ conf = ap_get_module_config(s->module_config, &rewrite_module);
+
+ /* If the engine isn't turned on,
+ * don't even try to do anything.
+ */
+ if (conf->state == ENGINE_DISABLED) {
+ return APR_SUCCESS;
+ }
+
+ for (hi = apr_hash_first(p, conf->rewritemaps); hi; hi = apr_hash_next(hi)){
+ apr_file_t *fpin = NULL;
+ apr_file_t *fpout = NULL;
+ rewritemap_entry *map;
+ void *val;
+
+ apr_hash_this(hi, NULL, NULL, &val);
+ map = val;
+
+ if (map->type != MAPTYPE_PRG) {
+ continue;
+ }
+ if (!(map->argv[0]) || !*(map->argv[0]) || map->fpin || map->fpout) {
+ continue;
+ }
+
+ rc = rewritemap_program_child(p, map->argv[0], map->argv,
+ map->user, map->group,
+ &fpout, &fpin);
+ if (rc != APR_SUCCESS || fpin == NULL || fpout == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rc, s, APLOGNO(00654)
+ "mod_rewrite: could not start RewriteMap "
+ "program %s", map->checkfile);
+ return rc;
+ }
+ map->fpin = fpin;
+ map->fpout = fpout;
+ }
+
+ return APR_SUCCESS;
+}
+
+
+/*
+ * +-------------------------------------------------------+
+ * | |
+ * | Lookup functions
+ * | |
+ * +-------------------------------------------------------+
+ */
+
+static char *lookup_map_txtfile(request_rec *r, const char *file, char *key)
+{
+ apr_file_t *fp = NULL;
+ char line[REWRITE_MAX_TXT_MAP_LINE + 1]; /* +1 for \0 */
+ char *value, *keylast;
+ apr_status_t rv;
+
+ if ((rv = apr_file_open(&fp, file, APR_READ|APR_BUFFERED, APR_OS_DEFAULT,
+ r->pool)) != APR_SUCCESS)
+ {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00655)
+ "mod_rewrite: can't open text RewriteMap file %s", file);
+ return NULL;
+ }
+
+ keylast = key + strlen(key);
+ value = NULL;
+ while (apr_file_gets(line, sizeof(line), fp) == APR_SUCCESS) {
+ char *p, *c;
+
+ /* ignore comments and lines starting with whitespaces */
+ if (*line == '#' || apr_isspace(*line)) {
+ continue;
+ }
+
+ p = line;
+ c = key;
+ while (c < keylast && *p == *c && !apr_isspace(*p)) {
+ ++p;
+ ++c;
+ }
+
+ /* key doesn't match - ignore. */
+ if (c != keylast || !apr_isspace(*p)) {
+ continue;
+ }
+
+ /* jump to the value */
+ while (apr_isspace(*p)) {
+ ++p;
+ }
+
+ /* no value? ignore */
+ if (!*p) {
+ continue;
+ }
+
+ /* extract the value and return. */
+ c = p;
+ while (*p && !apr_isspace(*p)) {
+ ++p;
+ }
+ value = apr_pstrmemdup(r->pool, c, p - c);
+ break;
+ }
+ apr_file_close(fp);
+
+ return value;
+}
+
+static char *lookup_map_dbmfile(request_rec *r, const char *file,
+ const char *dbmtype, char *key)
+{
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ const apr_dbm_driver_t *driver;
+ const apu_err_t *err;
+#endif
+ apr_dbm_t *dbmfp = NULL;
+ apr_datum_t dbmkey;
+ apr_datum_t dbmval;
+ char *value;
+ apr_status_t rv;
+
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ if ((rv = apr_dbm_get_driver(&driver, dbmtype, &err,
+ r->pool)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10287)
+ "mod_rewrite: can't load DBM library '%s': %s",
+ err->reason, err->msg);
+ return NULL;
+ }
+ if ((rv = apr_dbm_open2(&dbmfp, driver, file, APR_DBM_READONLY,
+ APR_OS_DEFAULT, r->pool)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00656)
+ "mod_rewrite: can't open DBM RewriteMap %s", file);
+ return NULL;
+ }
+#else
+ if ((rv = apr_dbm_open_ex(&dbmfp, dbmtype, file, APR_DBM_READONLY,
+ APR_OS_DEFAULT, r->pool)) != APR_SUCCESS)
+ {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00656)
+ "mod_rewrite: can't open DBM RewriteMap %s", file);
+ return NULL;
+ }
+#endif
+
+ dbmkey.dptr = key;
+ dbmkey.dsize = strlen(key);
+
+ if (apr_dbm_fetch(dbmfp, dbmkey, &dbmval) == APR_SUCCESS && dbmval.dptr) {
+ value = apr_pstrmemdup(r->pool, dbmval.dptr, dbmval.dsize);
+ }
+ else {
+ value = NULL;
+ }
+
+ apr_dbm_close(dbmfp);
+
+ return value;
+}
+static char *lookup_map_dbd(request_rec *r, char *key, const char *label)
+{
+ apr_status_t rv;
+ apr_dbd_prepared_t *stmt;
+ const char *errmsg;
+ apr_dbd_results_t *res = NULL;
+ apr_dbd_row_t *row = NULL;
+ char *ret = NULL;
+ int n = 0;
+ ap_dbd_t *db = dbd_acquire(r);
+
+ if (db == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02963)
+ "rewritemap: No db handle available! "
+ "Check your database access");
+ return NULL;
+ }
+
+ stmt = apr_hash_get(db->prepared, label, APR_HASH_KEY_STRING);
+
+ rv = apr_dbd_pvselect(db->driver, r->pool, db->handle, &res,
+ stmt, 0, key, NULL);
+ if (rv != 0) {
+ errmsg = apr_dbd_error(db->driver, db->handle, rv);
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00657)
+ "rewritemap: error %s querying for %s", errmsg, key);
+ return NULL;
+ }
+ while ((rv = apr_dbd_get_row(db->driver, r->pool, res, &row, -1)) == 0) {
+ ++n;
+ if (ret == NULL) {
+ ret = apr_pstrdup(r->pool,
+ apr_dbd_get_entry(db->driver, row, 0));
+ }
+ else {
+ /* randomise crudely amongst multiple results */
+ if ((double)rand() < (double)RAND_MAX/(double)n) {
+ ret = apr_pstrdup(r->pool,
+ apr_dbd_get_entry(db->driver, row, 0));
+ }
+ }
+ }
+ if (rv != -1) {
+ errmsg = apr_dbd_error(db->driver, db->handle, rv);
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00658)
+ "rewritemap: error %s looking up %s", errmsg, key);
+ }
+ switch (n) {
+ case 0:
+ return NULL;
+ case 1:
+ return ret;
+ default:
+ /* what's a fair rewritelog level for this? */
+ rewritelog((r, 3, NULL, "Multiple values found for %s", key));
+ return ret;
+ }
+}
+
+static char *lookup_map_program(request_rec *r, apr_file_t *fpin,
+ apr_file_t *fpout, char *key)
+{
+ char *buf;
+ char c;
+ apr_size_t i, nbytes, combined_len = 0;
+ apr_status_t rv;
+ const char *eol = APR_EOL_STR;
+ apr_size_t eolc = 0;
+ int found_nl = 0;
+ result_list *buflist = NULL, *curbuf = NULL;
+
+#ifndef NO_WRITEV
+ struct iovec iova[2];
+ apr_size_t niov;
+#endif
+
+ /* when `RewriteEngine off' was used in the per-server
+ * context then the rewritemap-programs were not spawned.
+ * In this case using such a map (usually in per-dir context)
+ * is useless because it is not available.
+ *
+ * newlines in the key leave bytes in the pipe and cause
+ * bad things to happen (next map lookup will use the chars
+ * after the \n instead of the new key etc etc - in other words,
+ * the Rewritemap falls out of sync with the requests).
+ */
+ if (fpin == NULL || fpout == NULL || ap_strchr(key, '\n')) {
+ return NULL;
+ }
+
+ /* take the lock */
+ if (rewrite_mapr_lock_acquire) {
+ rv = apr_global_mutex_lock(rewrite_mapr_lock_acquire);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00659)
+ "apr_global_mutex_lock(rewrite_mapr_lock_acquire) "
+ "failed");
+ return NULL; /* Maybe this should be fatal? */
+ }
+ }
+
+ /* write out the request key */
+#ifdef NO_WRITEV
+ nbytes = strlen(key);
+ /* XXX: error handling */
+ apr_file_write_full(fpin, key, nbytes, NULL);
+ nbytes = 1;
+ apr_file_write_full(fpin, "\n", nbytes, NULL);
+#else
+ iova[0].iov_base = key;
+ iova[0].iov_len = strlen(key);
+ iova[1].iov_base = "\n";
+ iova[1].iov_len = 1;
+
+ niov = 2;
+ /* XXX: error handling */
+ apr_file_writev_full(fpin, iova, niov, &nbytes);
+#endif
+
+ buf = apr_palloc(r->pool, REWRITE_PRG_MAP_BUF + 1);
+
+ /* read in the response value */
+ nbytes = 1;
+ apr_file_read(fpout, &c, &nbytes);
+ do {
+ i = 0;
+ while (nbytes == 1 && (i < REWRITE_PRG_MAP_BUF)) {
+ if (c == eol[eolc]) {
+ if (!eol[++eolc]) {
+ /* remove eol from the buffer */
+ --eolc;
+ if (i < eolc) {
+ curbuf->len -= eolc-i;
+ i = 0;
+ }
+ else {
+ i -= eolc;
+ }
+ ++found_nl;
+ break;
+ }
+ }
+
+ /* only partial (invalid) eol sequence -> reset the counter */
+ else if (eolc) {
+ eolc = 0;
+ }
+
+ /* catch binary mode, e.g. on Win32 */
+ else if (c == '\n') {
+ ++found_nl;
+ break;
+ }
+
+ buf[i++] = c;
+ apr_file_read(fpout, &c, &nbytes);
+ }
+
+ /* well, if there wasn't a newline yet, we need to read further */
+ if (buflist || (nbytes == 1 && !found_nl)) {
+ if (!buflist) {
+ curbuf = buflist = apr_palloc(r->pool, sizeof(*buflist));
+ }
+ else if (i) {
+ curbuf->next = apr_palloc(r->pool, sizeof(*buflist));
+ curbuf = curbuf->next;
+
+ }
+ curbuf->next = NULL;
+
+ if (i) {
+ curbuf->string = buf;
+ curbuf->len = i;
+ combined_len += i;
+ buf = apr_palloc(r->pool, REWRITE_PRG_MAP_BUF);
+ }
+
+ if (nbytes == 1 && !found_nl) {
+ continue;
+ }
+ }
+
+ break;
+ } while (1);
+
+ /* concat the stuff */
+ if (buflist) {
+ char *p;
+
+ p = buf = apr_palloc(r->pool, combined_len + 1); /* \0 */
+ while (buflist) {
+ if (buflist->len) {
+ memcpy(p, buflist->string, buflist->len);
+ p += buflist->len;
+ }
+ buflist = buflist->next;
+ }
+ *p = '\0';
+ i = combined_len;
+ }
+ else {
+ buf[i] = '\0';
+ }
+
+ /* give the lock back */
+ if (rewrite_mapr_lock_acquire) {
+ rv = apr_global_mutex_unlock(rewrite_mapr_lock_acquire);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00660)
+ "apr_global_mutex_unlock(rewrite_mapr_lock_acquire) "
+ "failed");
+ return NULL; /* Maybe this should be fatal? */
+ }
+ }
+
+ /* catch the "failed" case */
+ if (i == 4 && !strcasecmp(buf, "NULL")) {
+ return NULL;
+ }
+
+ return buf;
+}
+
+/*
+ * generic map lookup
+ */
+static char *lookup_map(request_rec *r, char *name, char *key)
+{
+ rewrite_server_conf *conf;
+ rewritemap_entry *s;
+ char *value;
+ apr_finfo_t st;
+ apr_status_t rv;
+
+ /* get map configuration */
+ conf = ap_get_module_config(r->server->module_config, &rewrite_module);
+ s = apr_hash_get(conf->rewritemaps, name, APR_HASH_KEY_STRING);
+
+ /* map doesn't exist */
+ if (!s) {
+ return NULL;
+ }
+
+ switch (s->type) {
+ /*
+ * Text file map (perhaps random)
+ */
+ case MAPTYPE_RND:
+ case MAPTYPE_TXT:
+ rv = apr_stat(&st, s->checkfile, APR_FINFO_MIN, r->pool);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00661)
+ "mod_rewrite: can't access text RewriteMap file %s",
+ s->checkfile);
+ return NULL;
+ }
+
+ value = get_cache_value(s->cachename, st.mtime, key, r->pool);
+ if (!value) {
+ rewritelog((r, 6, NULL,
+ "cache lookup FAILED, forcing new map lookup"));
+
+ value = lookup_map_txtfile(r, s->datafile, key);
+ if (!value) {
+ rewritelog((r, 5, NULL, "map lookup FAILED: map=%s[txt] key=%s",
+ name, key));
+ set_cache_value(s->cachename, st.mtime, key, "");
+ return NULL;
+ }
+
+ rewritelog((r, 5, NULL,"map lookup OK: map=%s[txt] key=%s -> val=%s",
+ name, key, value));
+ set_cache_value(s->cachename, st.mtime, key, value);
+ }
+ else {
+ rewritelog((r,5,NULL,"cache lookup OK: map=%s[txt] key=%s -> val=%s",
+ name, key, value));
+ }
+
+ if (s->type == MAPTYPE_RND && *value) {
+ value = select_random_value_part(r, value);
+ rewritelog((r, 5, NULL, "randomly chosen the subvalue `%s'",value));
+ }
+
+ return *value ? value : NULL;
+
+ /*
+ * DBM file map
+ */
+ case MAPTYPE_DBM:
+ rv = apr_stat(&st, s->checkfile, APR_FINFO_MIN, r->pool);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00662)
+ "mod_rewrite: can't access DBM RewriteMap file %s",
+ s->checkfile);
+ }
+ else if(s->checkfile2 != NULL) {
+ apr_finfo_t st2;
+
+ rv = apr_stat(&st2, s->checkfile2, APR_FINFO_MIN, r->pool);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00663)
+ "mod_rewrite: can't access DBM RewriteMap "
+ "file %s", s->checkfile2);
+ }
+ else if(st2.mtime > st.mtime) {
+ st.mtime = st2.mtime;
+ }
+ }
+ if(rv != APR_SUCCESS) {
+ return NULL;
+ }
+
+ value = get_cache_value(s->cachename, st.mtime, key, r->pool);
+ if (!value) {
+ rewritelog((r, 6, NULL,
+ "cache lookup FAILED, forcing new map lookup"));
+
+ value = lookup_map_dbmfile(r, s->datafile, s->dbmtype, key);
+ if (!value) {
+ rewritelog((r, 5, NULL, "map lookup FAILED: map=%s[dbm] key=%s",
+ name, key));
+ set_cache_value(s->cachename, st.mtime, key, "");
+ return NULL;
+ }
+
+ rewritelog((r, 5, NULL, "map lookup OK: map=%s[dbm] key=%s -> "
+ "val=%s", name, key, value));
+
+ set_cache_value(s->cachename, st.mtime, key, value);
+ return value;
+ }
+
+ rewritelog((r, 5, NULL, "cache lookup OK: map=%s[dbm] key=%s -> val=%s",
+ name, key, value));
+ return *value ? value : NULL;
+
+ /*
+ * SQL map without cache
+ */
+ case MAPTYPE_DBD:
+ value = lookup_map_dbd(r, key, s->dbdq);
+ if (!value) {
+ rewritelog((r, 5, NULL, "SQL map lookup FAILED: map %s key=%s",
+ name, key));
+ return NULL;
+ }
+
+ rewritelog((r, 5, NULL, "SQL map lookup OK: map %s key=%s, val=%s",
+ name, key, value));
+
+ return value;
+
+ /*
+ * SQL map with cache
+ */
+ case MAPTYPE_DBD_CACHE:
+ value = get_cache_value(s->cachename, 0, key, r->pool);
+ if (!value) {
+ rewritelog((r, 6, NULL,
+ "cache lookup FAILED, forcing new map lookup"));
+
+ value = lookup_map_dbd(r, key, s->dbdq);
+ if (!value) {
+ rewritelog((r, 5, NULL, "SQL map lookup FAILED: map %s key=%s",
+ name, key));
+ set_cache_value(s->cachename, 0, key, "");
+ return NULL;
+ }
+
+ rewritelog((r, 5, NULL, "SQL map lookup OK: map %s key=%s, val=%s",
+ name, key, value));
+
+ set_cache_value(s->cachename, 0, key, value);
+ return value;
+ }
+
+ rewritelog((r, 5, NULL, "cache lookup OK: map=%s[SQL] key=%s, val=%s",
+ name, key, value));
+ return *value ? value : NULL;
+
+ /*
+ * Program file map
+ */
+ case MAPTYPE_PRG:
+ value = lookup_map_program(r, s->fpin, s->fpout, key);
+ if (!value) {
+ rewritelog((r, 5,NULL,"map lookup FAILED: map=%s key=%s", name,
+ key));
+ return NULL;
+ }
+
+ rewritelog((r, 5, NULL, "map lookup OK: map=%s key=%s -> val=%s",
+ name, key, value));
+ return value;
+
+ /*
+ * Internal Map
+ */
+ case MAPTYPE_INT:
+ value = s->func(r, key);
+ if (!value) {
+ rewritelog((r, 5,NULL,"map lookup FAILED: map=%s key=%s", name,
+ key));
+ return NULL;
+ }
+
+ rewritelog((r, 5, NULL, "map lookup OK: map=%s key=%s -> val=%s",
+ name, key, value));
+ return value;
+ }
+
+ return NULL;
+}
+
+/*
+ * lookup a HTTP header and set VARY note
+ */
+static const char *lookup_header(const char *name, rewrite_ctx *ctx)
+{
+ const char *val = apr_table_get(ctx->r->headers_in, name);
+
+ /* Skip the 'Vary: Host' header combination
+ * as indicated in rfc7231 section-7.1.4
+ */
+ if (val && strcasecmp(name, "Host") != 0) {
+ ctx->vary_this = ctx->vary_this
+ ? apr_pstrcat(ctx->r->pool, ctx->vary_this, ", ",
+ name, NULL)
+ : apr_pstrdup(ctx->r->pool, name);
+ }
+
+ return val;
+}
+
+/*
+ * lookahead helper function
+ * Determine the correct URI path in perdir context
+ */
+static APR_INLINE const char *la_u(rewrite_ctx *ctx)
+{
+ rewrite_perdir_conf *conf;
+
+ if (*ctx->uri == '/') {
+ return ctx->uri;
+ }
+
+ conf = ap_get_module_config(ctx->r->per_dir_config, &rewrite_module);
+
+ return apr_pstrcat(ctx->r->pool, conf->baseurl
+ ? conf->baseurl : conf->directory,
+ ctx->uri, NULL);
+}
+
+/*
+ * generic variable lookup
+ */
+static char *lookup_variable(char *var, rewrite_ctx *ctx)
+{
+ const char *result;
+ request_rec *r = ctx->r;
+ apr_size_t varlen = strlen(var);
+
+ /* fast exit */
+ if (varlen < 4) {
+ return "";
+ }
+
+ result = NULL;
+
+ /* fast tests for variable length variables (sic) first */
+ if (var[3] == ':') {
+ if (var[4] && !strncasecmp(var, "ENV", 3)) {
+ var += 4;
+ result = apr_table_get(r->notes, var);
+
+ if (!result) {
+ result = apr_table_get(r->subprocess_env, var);
+ }
+ if (!result) {
+ result = getenv(var);
+ }
+ }
+ else if (var[4] && !strncasecmp(var, "SSL", 3)) {
+ result = ap_ssl_var_lookup(r->pool, r->server, r->connection, r,
+ var + 4);
+ }
+ }
+ else if (var[4] == ':') {
+ if (var[5]) {
+ request_rec *rr;
+ const char *path;
+
+ if (!strncasecmp(var, "HTTP", 4)) {
+ result = lookup_header(var+5, ctx);
+ }
+ else if (!strncasecmp(var, "LA-U", 4)) {
+ if (ctx->uri && subreq_ok(r)) {
+ path = ctx->perdir ? la_u(ctx) : ctx->uri;
+ rr = ap_sub_req_lookup_uri(path, r, NULL);
+ ctx->r = rr;
+ result = apr_pstrdup(r->pool, lookup_variable(var+5, ctx));
+ ctx->r = r;
+ ap_destroy_sub_req(rr);
+
+ rewritelog((r, 5, ctx->perdir, "lookahead: path=%s var=%s "
+ "-> val=%s", path, var+5, result));
+
+ return (char *)result;
+ }
+ }
+ else if (!strncasecmp(var, "LA-F", 4)) {
+ if (ctx->uri && subreq_ok(r)) {
+ path = ctx->uri;
+ if (ctx->perdir && *path == '/') {
+ /* sigh, the user wants a file based subrequest, but
+ * we can't do one, since we don't know what the file
+ * path is! In this case behave like LA-U.
+ */
+ rr = ap_sub_req_lookup_uri(path, r, NULL);
+ }
+ else {
+ if (ctx->perdir) {
+ rewrite_perdir_conf *conf;
+
+ conf = ap_get_module_config(r->per_dir_config,
+ &rewrite_module);
+
+ path = apr_pstrcat(r->pool, conf->directory, path,
+ NULL);
+ }
+
+ rr = ap_sub_req_lookup_file(path, r, NULL);
+ }
+
+ ctx->r = rr;
+ result = apr_pstrdup(r->pool, lookup_variable(var+5, ctx));
+ ctx->r = r;
+ ap_destroy_sub_req(rr);
+
+ rewritelog((r, 5, ctx->perdir, "lookahead: path=%s var=%s "
+ "-> val=%s", path, var+5, result));
+
+ return (char *)result;
+ }
+ }
+ }
+ }
+
+ /* well, do it the hard way */
+ else {
+ apr_time_exp_t tm;
+
+ /* can't do this above, because of the getenv call */
+ ap_str_toupper(var);
+
+ switch (varlen) {
+ case 4:
+ if (!strcmp(var, "TIME")) {
+ apr_time_exp_lt(&tm, apr_time_now());
+ result = apr_psprintf(r->pool, "%04d%02d%02d%02d%02d%02d",
+ tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
+ tm.tm_hour, tm.tm_min, tm.tm_sec);
+ rewritelog((r, 1, ctx->perdir, "RESULT='%s'", result));
+ return (char *)result;
+ }
+ else if (!strcmp(var, "IPV6")) {
+ int flag = FALSE;
+#if APR_HAVE_IPV6
+ apr_sockaddr_t *addr = r->useragent_addr;
+ flag = (addr->family == AF_INET6 &&
+ !IN6_IS_ADDR_V4MAPPED((struct in6_addr *)addr->ipaddr_ptr));
+ rewritelog((r, 1, ctx->perdir, "IPV6='%s'", flag ? "on" : "off"));
+#else
+ rewritelog((r, 1, ctx->perdir, "IPV6='off' (IPv6 is not enabled)"));
+#endif
+ result = (flag ? "on" : "off");
+ }
+ break;
+
+ case 5:
+ if (!strcmp(var, "HTTPS")) {
+ int flag = ap_ssl_conn_is_ssl(r->connection);
+ return apr_pstrdup(r->pool, flag ? "on" : "off");
+ }
+ break;
+
+ case 8:
+ switch (var[6]) {
+ case 'A':
+ if (!strcmp(var, "TIME_DAY")) {
+ apr_time_exp_lt(&tm, apr_time_now());
+ return apr_psprintf(r->pool, "%02d", tm.tm_mday);
+ }
+ break;
+
+ case 'E':
+ if (!strcmp(var, "TIME_SEC")) {
+ apr_time_exp_lt(&tm, apr_time_now());
+ return apr_psprintf(r->pool, "%02d", tm.tm_sec);
+ }
+ break;
+
+ case 'I':
+ if (!strcmp(var, "TIME_MIN")) {
+ apr_time_exp_lt(&tm, apr_time_now());
+ return apr_psprintf(r->pool, "%02d", tm.tm_min);
+ }
+ break;
+
+ case 'O':
+ if (!strcmp(var, "TIME_MON")) {
+ apr_time_exp_lt(&tm, apr_time_now());
+ return apr_psprintf(r->pool, "%02d", tm.tm_mon+1);
+ }
+ break;
+ }
+ break;
+
+ case 9:
+ switch (var[7]) {
+ case 'A':
+ if (var[8] == 'Y' && !strcmp(var, "TIME_WDAY")) {
+ apr_time_exp_lt(&tm, apr_time_now());
+ return apr_psprintf(r->pool, "%d", tm.tm_wday);
+ }
+ else if (!strcmp(var, "TIME_YEAR")) {
+ apr_time_exp_lt(&tm, apr_time_now());
+ return apr_psprintf(r->pool, "%04d", tm.tm_year+1900);
+ }
+ break;
+
+ case 'E':
+ if (!strcmp(var, "IS_SUBREQ")) {
+ result = (r->main ? "true" : "false");
+ }
+ break;
+
+ case 'F':
+ if (!strcmp(var, "PATH_INFO")) {
+ result = r->path_info;
+ }
+ break;
+
+ case 'P':
+ if (!strcmp(var, "AUTH_TYPE")) {
+ result = r->ap_auth_type;
+ }
+ break;
+
+ case 'S':
+ if (!strcmp(var, "HTTP_HOST")) {
+ result = lookup_header("Host", ctx);
+ }
+ break;
+
+ case 'U':
+ if (!strcmp(var, "TIME_HOUR")) {
+ apr_time_exp_lt(&tm, apr_time_now());
+ return apr_psprintf(r->pool, "%02d", tm.tm_hour);
+ }
+ break;
+ }
+ break;
+
+ case 11:
+ switch (var[8]) {
+ case 'A':
+ if (!strcmp(var, "SERVER_NAME")) {
+ result = ap_get_server_name_for_url(r);
+ }
+ break;
+
+ case 'D':
+ if (*var == 'R' && !strcmp(var, "REMOTE_ADDR")) {
+ result = r->useragent_ip;
+ }
+ else if (!strcmp(var, "SERVER_ADDR")) {
+ result = r->connection->local_ip;
+ }
+ break;
+
+ case 'E':
+ if (*var == 'H' && !strcmp(var, "HTTP_ACCEPT")) {
+ result = lookup_header("Accept", ctx);
+ }
+ else if (!strcmp(var, "THE_REQUEST")) {
+ result = r->the_request;
+ }
+ break;
+
+ case 'I':
+ if (!strcmp(var, "API_VERSION")) {
+ return apr_psprintf(r->pool, "%d:%d",
+ MODULE_MAGIC_NUMBER_MAJOR,
+ MODULE_MAGIC_NUMBER_MINOR);
+ }
+ break;
+
+ case 'K':
+ if (!strcmp(var, "HTTP_COOKIE")) {
+ result = lookup_header("Cookie", ctx);
+ }
+ break;
+
+ case 'O':
+ if (*var == 'S' && !strcmp(var, "SERVER_PORT")) {
+ return apr_psprintf(r->pool, "%u", ap_get_server_port(r));
+ }
+ else if (var[7] == 'H' && !strcmp(var, "REMOTE_HOST")) {
+ result = ap_get_remote_host(r->connection,r->per_dir_config,
+ REMOTE_NAME, NULL);
+ }
+ else if (!strcmp(var, "REMOTE_PORT")) {
+ return apr_itoa(r->pool, r->useragent_addr->port);
+ }
+ break;
+
+ case 'S':
+ if (*var == 'R' && !strcmp(var, "REMOTE_USER")) {
+ result = r->user;
+ }
+ else if (!strcmp(var, "SCRIPT_USER")) {
+ result = "<unknown>";
+ if (r->finfo.valid & APR_FINFO_USER) {
+ apr_uid_name_get((char **)&result, r->finfo.user,
+ r->pool);
+ }
+ }
+ break;
+
+ case 'U':
+ if (!strcmp(var, "REQUEST_URI")) {
+ result = r->uri;
+ }
+ break;
+ }
+ break;
+
+ case 12:
+ switch (var[3]) {
+ case 'I':
+ if (!strcmp(var, "SCRIPT_GROUP")) {
+ result = "<unknown>";
+ if (r->finfo.valid & APR_FINFO_GROUP) {
+ apr_gid_name_get((char **)&result, r->finfo.group,
+ r->pool);
+ }
+ }
+ break;
+
+ case 'O':
+ if (!strcmp(var, "REMOTE_IDENT")) {
+ result = ap_get_remote_logname(r);
+ }
+ break;
+
+ case 'P':
+ if (!strcmp(var, "HTTP_REFERER")) {
+ result = lookup_header("Referer", ctx);
+ }
+ break;
+
+ case 'R':
+ if (!strcmp(var, "QUERY_STRING")) {
+ result = r->args;
+ }
+ break;
+
+ case 'V':
+ if (!strcmp(var, "SERVER_ADMIN")) {
+ result = r->server->server_admin;
+ }
+ break;
+ }
+ break;
+
+ case 13:
+ if (!strcmp(var, "DOCUMENT_ROOT")) {
+ result = ap_document_root(r);
+ }
+ break;
+
+ case 14:
+ if (*var == 'H' && !strcmp(var, "HTTP_FORWARDED")) {
+ result = lookup_header("Forwarded", ctx);
+ }
+ else if (*var == 'C' && !strcmp(var, "CONTEXT_PREFIX")) {
+ result = ap_context_prefix(r);
+ }
+ else if (var[8] == 'M' && !strcmp(var, "REQUEST_METHOD")) {
+ result = r->method;
+ }
+ else if (!strcmp(var, "REQUEST_SCHEME")) {
+ result = ap_http_scheme(r);
+ }
+ break;
+
+ case 15:
+ switch (var[7]) {
+ case 'E':
+ if (!strcmp(var, "HTTP_USER_AGENT")) {
+ result = lookup_header("User-Agent", ctx);
+ }
+ break;
+
+ case 'F':
+ if (!strcmp(var, "SCRIPT_FILENAME")) {
+ result = r->filename; /* same as request_filename (16) */
+ }
+ break;
+
+ case 'P':
+ if (!strcmp(var, "SERVER_PROTOCOL")) {
+ result = r->protocol;
+ }
+ break;
+
+ case 'S':
+ if (!strcmp(var, "SERVER_SOFTWARE")) {
+ result = ap_get_server_banner();
+ }
+ break;
+ }
+ break;
+
+ case 16:
+ if (*var == 'C' && !strcmp(var, "CONN_REMOTE_ADDR")) {
+ result = r->connection->client_ip;
+ }
+ else if (!strcmp(var, "REQUEST_FILENAME")) {
+ result = r->filename; /* same as script_filename (15) */
+ }
+ break;
+
+ case 21:
+ if (!strcmp(var, "HTTP_PROXY_CONNECTION")) {
+ result = lookup_header("Proxy-Connection", ctx);
+ }
+ else if (!strcmp(var, "CONTEXT_DOCUMENT_ROOT")) {
+ result = ap_context_document_root(r);
+ }
+ break;
+ }
+ }
+
+ return apr_pstrdup(r->pool, result ? result : "");
+}
+
+
+/*
+ * +-------------------------------------------------------+
+ * | |
+ * | Expansion functions
+ * | |
+ * +-------------------------------------------------------+
+ */
+
+/*
+ * Bracketed expression handling
+ * s points after the opening bracket
+ */
+static APR_INLINE char *find_closing_curly(char *s)
+{
+ unsigned depth;
+
+ for (depth = 1; *s; ++s) {
+ if (*s == RIGHT_CURLY && --depth == 0) {
+ return s;
+ }
+ else if (*s == LEFT_CURLY) {
+ ++depth;
+ }
+ }
+
+ return NULL;
+}
+
+static APR_INLINE char *find_char_in_curlies(char *s, int c)
+{
+ unsigned depth;
+
+ for (depth = 1; *s; ++s) {
+ if (*s == c && depth == 1) {
+ return s;
+ }
+ else if (*s == RIGHT_CURLY && --depth == 0) {
+ return NULL;
+ }
+ else if (*s == LEFT_CURLY) {
+ ++depth;
+ }
+ }
+
+ return NULL;
+}
+
+/* perform all the expansions on the input string
+ * putting the result into a new string
+ *
+ * for security reasons this expansion must be performed in a
+ * single pass, otherwise an attacker can arrange for the result
+ * of an earlier expansion to include expansion specifiers that
+ * are interpreted by a later expansion, producing results that
+ * were not intended by the administrator.
+ */
+static char *do_expand(char *input, rewrite_ctx *ctx, rewriterule_entry *entry)
+{
+ result_list *result, *current;
+ result_list sresult[SMALL_EXPANSION];
+ unsigned spc = 0;
+ apr_size_t span, inputlen, outlen;
+ char *p, *c;
+ apr_pool_t *pool = ctx->r->pool;
+
+ span = strcspn(input, "\\$%");
+ inputlen = strlen(input);
+
+ /* fast exit */
+ if (inputlen == span) {
+ return apr_pstrmemdup(pool, input, inputlen);
+ }
+
+ /* well, actually something to do */
+ result = current = &(sresult[spc++]);
+
+ p = input + span;
+ current->next = NULL;
+ current->string = input;
+ current->len = span;
+ outlen = span;
+
+ /* loop for specials */
+ do {
+ /* prepare next entry */
+ if (current->len) {
+ current->next = (spc < SMALL_EXPANSION)
+ ? &(sresult[spc++])
+ : (result_list *)apr_palloc(pool,
+ sizeof(result_list));
+ current = current->next;
+ current->next = NULL;
+ current->len = 0;
+ }
+
+ /* escaped character */
+ if (*p == '\\') {
+ current->len = 1;
+ ++outlen;
+ if (!p[1]) {
+ current->string = p;
+ break;
+ }
+ else {
+ current->string = ++p;
+ ++p;
+ }
+ }
+
+ /* variable or map lookup */
+ else if (p[1] == '{') {
+ char *endp;
+
+ endp = find_closing_curly(p+2);
+ if (!endp) {
+ current->len = 2;
+ current->string = p;
+ outlen += 2;
+ p += 2;
+ }
+
+ /* variable lookup */
+ else if (*p == '%') {
+ p = lookup_variable(apr_pstrmemdup(pool, p+2, endp-p-2), ctx);
+
+ span = strlen(p);
+ current->len = span;
+ current->string = p;
+ outlen += span;
+ p = endp + 1;
+ }
+
+ /* map lookup */
+ else { /* *p == '$' */
+ char *key;
+
+ /*
+ * To make rewrite maps useful, the lookup key and
+ * default values must be expanded, so we make
+ * recursive calls to do the work. For security
+ * reasons we must never expand a string that includes
+ * verbatim data from the network. The recursion here
+ * isn't a problem because the result of expansion is
+ * only passed to lookup_map() so it cannot be
+ * re-expanded, only re-looked-up. Another way of
+ * looking at it is that the recursion is entirely
+ * driven by the syntax of the nested curly brackets.
+ */
+
+ key = find_char_in_curlies(p+2, ':');
+ if (!key) {
+ current->len = 2;
+ current->string = p;
+ outlen += 2;
+ p += 2;
+ }
+ else {
+ char *map, *dflt;
+
+ map = apr_pstrmemdup(pool, p+2, endp-p-2);
+ key = map + (key-p-2);
+ *key++ = '\0';
+ dflt = find_char_in_curlies(key, '|');
+ if (dflt) {
+ *dflt++ = '\0';
+ }
+
+ /* reuse of key variable as result */
+ key = lookup_map(ctx->r, map, do_expand(key, ctx, entry));
+
+ if (!key && dflt && *dflt) {
+ key = do_expand(dflt, ctx, entry);
+ }
+
+ if (key) {
+ span = strlen(key);
+ current->len = span;
+ current->string = key;
+ outlen += span;
+ }
+
+ p = endp + 1;
+ }
+ }
+ }
+
+ /* backreference */
+ else if (apr_isdigit(p[1])) {
+ int n = p[1] - '0';
+ backrefinfo *bri = (*p == '$') ? &ctx->briRR : &ctx->briRC;
+
+ /* see ap_pregsub() in server/util.c */
+ if (bri->source && n < AP_MAX_REG_MATCH
+ && bri->regmatch[n].rm_eo > bri->regmatch[n].rm_so) {
+ span = bri->regmatch[n].rm_eo - bri->regmatch[n].rm_so;
+ if (entry && (entry->flags & RULEFLAG_ESCAPEBACKREF)) {
+ /* escape the backreference */
+ char *tmp2, *tmp;
+ tmp = apr_pstrmemdup(pool, bri->source + bri->regmatch[n].rm_so, span);
+ tmp2 = escape_backref(pool, tmp, entry->escapes, entry->flags & RULEFLAG_ESCAPENOPLUS);
+ rewritelog((ctx->r, 5, ctx->perdir, "escaping backreference '%s' to '%s'",
+ tmp, tmp2));
+
+ current->len = span = strlen(tmp2);
+ current->string = tmp2;
+ } else {
+ current->len = span;
+ current->string = bri->source + bri->regmatch[n].rm_so;
+ }
+
+ outlen += span;
+ }
+
+ p += 2;
+ }
+
+ /* not for us, just copy it */
+ else {
+ current->len = 1;
+ current->string = p++;
+ ++outlen;
+ }
+
+ /* check the remainder */
+ if (*p && (span = strcspn(p, "\\$%")) > 0) {
+ if (current->len) {
+ current->next = (spc < SMALL_EXPANSION)
+ ? &(sresult[spc++])
+ : (result_list *)apr_palloc(pool,
+ sizeof(result_list));
+ current = current->next;
+ current->next = NULL;
+ }
+
+ current->len = span;
+ current->string = p;
+ p += span;
+ outlen += span;
+ }
+
+ } while (p < input+inputlen);
+
+ /* assemble result */
+ c = p = apr_palloc(pool, outlen + 1); /* don't forget the \0 */
+ do {
+ if (result->len) {
+ ap_assert(c+result->len <= p+outlen); /* XXX: can be removed after
+ * extensive testing and
+ * review
+ */
+ memcpy(c, result->string, result->len);
+ c += result->len;
+ }
+ result = result->next;
+ } while (result);
+
+ p[outlen] = '\0';
+
+ return p;
+}
+
+/*
+ * perform all the expansions on the environment variables
+ */
+static void do_expand_env(data_item *env, rewrite_ctx *ctx)
+{
+ char *name, *val;
+
+ while (env) {
+ name = do_expand(env->data, ctx, NULL);
+ if (*name == '!') {
+ name++;
+ apr_table_unset(ctx->r->subprocess_env, name);
+ rewritelog((ctx->r, 5, NULL, "unsetting env variable '%s'", name));
+ }
+ else {
+ if ((val = ap_strchr(name, ':')) != NULL) {
+ *val++ = '\0';
+ } else {
+ val = "";
+ }
+
+ apr_table_set(ctx->r->subprocess_env, name, val);
+ rewritelog((ctx->r, 5, NULL, "setting env variable '%s' to '%s'",
+ name, val));
+ }
+
+ env = env->next;
+ }
+
+ return;
+}
+
+/*
+ * perform all the expansions on the cookies
+ *
+ * TODO: use cached time similar to how logging does it
+ */
+static void add_cookie(request_rec *r, char *s)
+{
+ char *var;
+ char *val;
+ char *domain;
+ char *expires;
+ char *path;
+ char *secure;
+ char *httponly;
+ char *samesite;
+
+ char *tok_cntx;
+ char *cookie;
+ /* long-standing default, but can't use ':' in a cookie */
+ const char *sep = ":";
+
+ /* opt-in to ; separator if first character is a ; */
+ if (s && *s == ';') {
+ sep = ";";
+ s++;
+ }
+
+ var = apr_strtok(s, sep, &tok_cntx);
+ val = apr_strtok(NULL, sep, &tok_cntx);
+ domain = apr_strtok(NULL, sep, &tok_cntx);
+
+ if (var && val && domain) {
+ request_rec *rmain = r;
+ char *notename;
+ void *data;
+
+ while (rmain->main) {
+ rmain = rmain->main;
+ }
+
+ notename = apr_pstrcat(rmain->pool, var, "_rewrite", NULL);
+ apr_pool_userdata_get(&data, notename, rmain->pool);
+ if (!data) {
+ char *exp_time = NULL;
+
+ expires = apr_strtok(NULL, sep, &tok_cntx);
+ path = expires ? apr_strtok(NULL, sep, &tok_cntx) : NULL;
+ secure = path ? apr_strtok(NULL, sep, &tok_cntx) : NULL;
+ httponly = secure ? apr_strtok(NULL, sep, &tok_cntx) : NULL;
+ samesite = httponly ? apr_strtok(NULL, sep, &tok_cntx) : NULL;
+
+ if (expires) {
+ apr_time_exp_t tms;
+ long exp_min;
+
+ exp_min = atol(expires);
+ if (exp_min) {
+ apr_time_exp_gmt(&tms, r->request_time
+ + apr_time_from_sec((60 * exp_min)));
+ exp_time = apr_psprintf(r->pool, "%s, %.2d-%s-%.4d "
+ "%.2d:%.2d:%.2d GMT",
+ apr_day_snames[tms.tm_wday],
+ tms.tm_mday,
+ apr_month_snames[tms.tm_mon],
+ tms.tm_year+1900,
+ tms.tm_hour, tms.tm_min, tms.tm_sec);
+ }
+ }
+
+ cookie = apr_pstrcat(rmain->pool,
+ var, "=", val,
+ "; path=", path ? path : "/",
+ "; domain=", domain,
+ expires ? (exp_time ? "; expires=" : "")
+ : NULL,
+ expires ? (exp_time ? exp_time : "")
+ : NULL,
+ (secure && (!ap_cstr_casecmp(secure, "true")
+ || !strcmp(secure, "1")
+ || !ap_cstr_casecmp(secure,
+ "secure"))) ?
+ "; secure" : NULL,
+ (httponly && (!ap_cstr_casecmp(httponly, "true")
+ || !strcmp(httponly, "1")
+ || !ap_cstr_casecmp(httponly,
+ "HttpOnly"))) ?
+ "; HttpOnly" : NULL,
+ NULL);
+
+ if (samesite && strcmp(samesite, "0") && ap_cstr_casecmp(samesite,"false")) {
+ cookie = apr_pstrcat(rmain->pool, cookie, "; SameSite=",
+ samesite, NULL);
+ }
+
+ apr_table_addn(rmain->err_headers_out, "Set-Cookie", cookie);
+ apr_pool_userdata_set("set", notename, NULL, rmain->pool);
+ rewritelog((rmain, 5, NULL, "setting cookie '%s'", cookie));
+ }
+ else {
+ rewritelog((rmain, 5, NULL, "skipping already set cookie '%s'",
+ var));
+ }
+ }
+
+ return;
+}
+
+static void do_expand_cookie(data_item *cookie, rewrite_ctx *ctx)
+{
+ while (cookie) {
+ add_cookie(ctx->r, do_expand(cookie->data, ctx, NULL));
+ cookie = cookie->next;
+ }
+
+ return;
+}
+
+#if APR_HAS_USER
+/*
+ * Expand tilde-paths (/~user) through Unix /etc/passwd
+ * database information (or other OS-specific database)
+ */
+static char *expand_tildepaths(request_rec *r, char *uri)
+{
+ if (uri && *uri == '/' && uri[1] == '~') {
+ char *p, *user;
+
+ p = user = uri + 2;
+ while (*p && *p != '/') {
+ ++p;
+ }
+
+ if (p > user) {
+ char *homedir;
+
+ user = apr_pstrmemdup(r->pool, user, p-user);
+ if (apr_uid_homepath_get(&homedir, user, r->pool) == APR_SUCCESS) {
+ if (*p) {
+ /* reuse of user variable */
+ user = homedir + strlen(homedir) - 1;
+ if (user >= homedir && *user == '/') {
+ *user = '\0';
+ }
+
+ return apr_pstrcat(r->pool, homedir, p, NULL);
+ }
+ else {
+ return homedir;
+ }
+ }
+ }
+ }
+
+ return uri;
+}
+#endif /* if APR_HAS_USER */
+
+
+/*
+ * +-------------------------------------------------------+
+ * | |
+ * | rewriting lockfile support
+ * | |
+ * +-------------------------------------------------------+
+ */
+
+static apr_status_t rewritelock_create(server_rec *s, apr_pool_t *p)
+{
+ apr_status_t rc;
+
+ /* create the lockfile */
+ rc = ap_global_mutex_create(&rewrite_mapr_lock_acquire, NULL,
+ rewritemap_mutex_type, NULL, s, p, 0);
+ if (rc != APR_SUCCESS) {
+ return rc;
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t rewritelock_remove(void *data)
+{
+ /* destroy the rewritelock */
+ if (rewrite_mapr_lock_acquire) {
+ apr_global_mutex_destroy(rewrite_mapr_lock_acquire);
+ rewrite_mapr_lock_acquire = NULL;
+ }
+ return APR_SUCCESS;
+}
+
+
+/*
+ * +-------------------------------------------------------+
+ * | |
+ * | configuration directive handling
+ * | |
+ * +-------------------------------------------------------+
+ */
+
+/*
+ * own command line parser for RewriteRule and RewriteCond,
+ * which doesn't have the '\\' problem.
+ * (returns true on error)
+ *
+ * XXX: what an inclined parser. Seems we have to leave it so
+ * for backwards compat. *sigh*
+ */
+static int parseargline(char *str, char **a1, char **a2, char **a2_end, char **a3)
+{
+ char quote;
+
+ while (apr_isspace(*str)) {
+ ++str;
+ }
+
+ /*
+ * determine first argument
+ */
+ quote = (*str == '"' || *str == '\'') ? *str++ : '\0';
+ *a1 = str;
+
+ for (; *str; ++str) {
+ if ((apr_isspace(*str) && !quote) || (*str == quote)) {
+ break;
+ }
+ if (*str == '\\' && apr_isspace(str[1])) {
+ ++str;
+ continue;
+ }
+ }
+
+ if (!*str) {
+ return 1;
+ }
+ *str++ = '\0';
+
+ while (apr_isspace(*str)) {
+ ++str;
+ }
+
+ /*
+ * determine second argument
+ */
+ quote = (*str == '"' || *str == '\'') ? *str++ : '\0';
+ *a2 = str;
+
+ for (; *str; ++str) {
+ if ((apr_isspace(*str) && !quote) || (*str == quote)) {
+ break;
+ }
+ if (*str == '\\' && apr_isspace(str[1])) {
+ ++str;
+ continue;
+ }
+ }
+
+ if (!*str) {
+ *a3 = NULL; /* 3rd argument is optional */
+ *a2_end = str;
+ return 0;
+ }
+ *a2_end = str;
+ *str++ = '\0';
+
+ while (apr_isspace(*str)) {
+ ++str;
+ }
+
+ if (!*str) {
+ *a3 = NULL; /* 3rd argument is still optional */
+ return 0;
+ }
+
+ /*
+ * determine third argument
+ */
+ quote = (*str == '"' || *str == '\'') ? *str++ : '\0';
+ *a3 = str;
+ for (; *str; ++str) {
+ if ((apr_isspace(*str) && !quote) || (*str == quote)) {
+ break;
+ }
+ if (*str == '\\' && apr_isspace(str[1])) {
+ ++str;
+ continue;
+ }
+ }
+ *str = '\0';
+
+ return 0;
+}
+
+static void *config_server_create(apr_pool_t *p, server_rec *s)
+{
+ rewrite_server_conf *a;
+
+ a = (rewrite_server_conf *)apr_pcalloc(p, sizeof(rewrite_server_conf));
+
+ a->state = ENGINE_DISABLED;
+ a->options = OPTION_NONE;
+ a->rewritemaps = apr_hash_make(p);
+ a->rewriteconds = apr_array_make(p, 2, sizeof(rewritecond_entry));
+ a->rewriterules = apr_array_make(p, 2, sizeof(rewriterule_entry));
+ a->server = s;
+
+ return (void *)a;
+}
+
+static void *config_server_merge(apr_pool_t *p, void *basev, void *overridesv)
+{
+ rewrite_server_conf *a, *base, *overrides;
+
+ a = (rewrite_server_conf *)apr_pcalloc(p,
+ sizeof(rewrite_server_conf));
+ base = (rewrite_server_conf *)basev;
+ overrides = (rewrite_server_conf *)overridesv;
+
+ a->state = (overrides->state_set == 0) ? base->state : overrides->state;
+ a->state_set = overrides->state_set || base->state_set;
+ a->options = (overrides->options_set == 0) ? base->options : overrides->options;
+ a->options_set = overrides->options_set || base->options_set;
+
+ a->server = overrides->server;
+
+ if (a->options & OPTION_INHERIT ||
+ (base->options & OPTION_INHERIT_DOWN &&
+ !(a->options & OPTION_IGNORE_INHERIT))) {
+ /*
+ * local directives override
+ * and anything else is inherited
+ */
+ a->rewritemaps = apr_hash_overlay(p, overrides->rewritemaps,
+ base->rewritemaps);
+ a->rewriteconds = apr_array_append(p, overrides->rewriteconds,
+ base->rewriteconds);
+ a->rewriterules = apr_array_append(p, overrides->rewriterules,
+ base->rewriterules);
+ }
+ else if (a->options & OPTION_INHERIT_BEFORE ||
+ (base->options & OPTION_INHERIT_DOWN_BEFORE &&
+ !(a->options & OPTION_IGNORE_INHERIT))) {
+ /*
+ * local directives override
+ * and anything else is inherited (preserving order)
+ */
+ a->rewritemaps = apr_hash_overlay(p, base->rewritemaps,
+ overrides->rewritemaps);
+ a->rewriteconds = apr_array_append(p, base->rewriteconds,
+ overrides->rewriteconds);
+ a->rewriterules = apr_array_append(p, base->rewriterules,
+ overrides->rewriterules);
+ }
+ else {
+ /*
+ * local directives override
+ * and anything else gets defaults
+ */
+ a->rewritemaps = overrides->rewritemaps;
+ a->rewriteconds = overrides->rewriteconds;
+ a->rewriterules = overrides->rewriterules;
+ }
+
+ return (void *)a;
+}
+
+static void *config_perdir_create(apr_pool_t *p, char *path)
+{
+ rewrite_perdir_conf *a;
+
+ a = (rewrite_perdir_conf *)apr_pcalloc(p, sizeof(rewrite_perdir_conf));
+
+ a->state = ENGINE_DISABLED;
+ a->options = OPTION_NONE;
+ a->baseurl = NULL;
+ a->rewriteconds = apr_array_make(p, 2, sizeof(rewritecond_entry));
+ a->rewriterules = apr_array_make(p, 2, sizeof(rewriterule_entry));
+
+ if (path == NULL) {
+ a->directory = NULL;
+ }
+ else {
+ /* make sure it has a trailing slash */
+ if (path[strlen(path)-1] == '/') {
+ a->directory = apr_pstrdup(p, path);
+ }
+ else {
+ a->directory = apr_pstrcat(p, path, "/", NULL);
+ }
+ }
+
+ return (void *)a;
+}
+
+static void *config_perdir_merge(apr_pool_t *p, void *basev, void *overridesv)
+{
+ rewrite_perdir_conf *a, *base, *overrides;
+
+ a = (rewrite_perdir_conf *)apr_pcalloc(p,
+ sizeof(rewrite_perdir_conf));
+ base = (rewrite_perdir_conf *)basev;
+ overrides = (rewrite_perdir_conf *)overridesv;
+
+ a->state = (overrides->state_set == 0) ? base->state : overrides->state;
+ a->state_set = overrides->state_set || base->state_set;
+ a->options = (overrides->options_set == 0) ? base->options : overrides->options;
+ a->options_set = overrides->options_set || base->options_set;
+
+ if (a->options & OPTION_MERGEBASE) {
+ a->baseurl = (overrides->baseurl_set == 0) ? base->baseurl : overrides->baseurl;
+ a->baseurl_set = overrides->baseurl_set || base->baseurl_set;
+ }
+ else {
+ a->baseurl = overrides->baseurl;
+ }
+
+ a->directory = overrides->directory;
+
+ if (a->options & OPTION_INHERIT ||
+ (base->options & OPTION_INHERIT_DOWN &&
+ !(a->options & OPTION_IGNORE_INHERIT))) {
+ a->rewriteconds = apr_array_append(p, overrides->rewriteconds,
+ base->rewriteconds);
+ a->rewriterules = apr_array_append(p, overrides->rewriterules,
+ base->rewriterules);
+ }
+ else if (a->options & OPTION_INHERIT_BEFORE ||
+ (base->options & OPTION_INHERIT_DOWN_BEFORE &&
+ !(a->options & OPTION_IGNORE_INHERIT))) {
+ a->rewriteconds = apr_array_append(p, base->rewriteconds,
+ overrides->rewriteconds);
+ a->rewriterules = apr_array_append(p, base->rewriterules,
+ overrides->rewriterules);
+ }
+ else {
+ a->rewriteconds = overrides->rewriteconds;
+ a->rewriterules = overrides->rewriterules;
+ }
+
+ return (void *)a;
+}
+
+static const char *cmd_rewriteengine(cmd_parms *cmd,
+ void *in_dconf, int flag)
+{
+ rewrite_perdir_conf *dconf = in_dconf;
+ rewrite_server_conf *sconf;
+
+ sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module);
+
+ /* server command? set both global scope and base directory scope */
+ if (cmd->path == NULL) {
+ sconf->state = (flag ? ENGINE_ENABLED : ENGINE_DISABLED);
+ sconf->state_set = 1;
+ dconf->state = sconf->state;
+ dconf->state_set = 1;
+ }
+ /* directory command? set directory scope only */
+ else {
+ dconf->state = (flag ? ENGINE_ENABLED : ENGINE_DISABLED);
+ dconf->state_set = 1;
+ }
+
+ return NULL;
+}
+
+static const char *cmd_rewriteoptions(cmd_parms *cmd,
+ void *in_dconf, const char *option)
+{
+ int options = 0;
+
+ while (*option) {
+ char *w = ap_getword_conf(cmd->temp_pool, &option);
+
+ if (!strcasecmp(w, "inherit")) {
+ options |= OPTION_INHERIT;
+ }
+ else if (!strcasecmp(w, "inheritbefore")) {
+ options |= OPTION_INHERIT_BEFORE;
+ }
+ else if (!strcasecmp(w, "inheritdown")) {
+ options |= OPTION_INHERIT_DOWN;
+ }
+ else if(!strcasecmp(w, "inheritdownbefore")) {
+ options |= OPTION_INHERIT_DOWN_BEFORE;
+ }
+ else if (!strcasecmp(w, "ignoreinherit")) {
+ options |= OPTION_IGNORE_INHERIT;
+ }
+ else if (!strcasecmp(w, "allownoslash")) {
+ options |= OPTION_NOSLASH;
+ }
+ else if (!strncasecmp(w, "MaxRedirects=", 13)) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00664)
+ "RewriteOptions: MaxRedirects option has been "
+ "removed in favor of the global "
+ "LimitInternalRecursion directive and will be "
+ "ignored.");
+ }
+ else if (!strcasecmp(w, "allowanyuri")) {
+ options |= OPTION_ANYURI;
+ }
+ else if (!strcasecmp(w, "mergebase")) {
+ options |= OPTION_MERGEBASE;
+ }
+ else if (!strcasecmp(w, "ignorecontextinfo")) {
+ options |= OPTION_IGNORE_CONTEXT_INFO;
+ }
+ else if (!strcasecmp(w, "legacyprefixdocroot")) {
+ options |= OPTION_LEGACY_PREFIX_DOCROOT;
+ }
+ else {
+ return apr_pstrcat(cmd->pool, "RewriteOptions: unknown option '",
+ w, "'", NULL);
+ }
+ }
+
+ /* server command? set both global scope and base directory scope */
+ if (cmd->path == NULL) { /* is server command */
+ rewrite_perdir_conf *dconf = in_dconf;
+ rewrite_server_conf *sconf =
+ ap_get_module_config(cmd->server->module_config,
+ &rewrite_module);
+
+ sconf->options |= options;
+ sconf->options_set = 1;
+ dconf->options |= options;
+ dconf->options_set = 1;
+ }
+ /* directory command? set directory scope only */
+ else { /* is per-directory command */
+ rewrite_perdir_conf *dconf = in_dconf;
+
+ dconf->options |= options;
+ dconf->options_set = 1;
+ }
+
+ return NULL;
+}
+
+static const char *cmd_rewritemap(cmd_parms *cmd, void *dconf, const char *a1,
+ const char *a2, const char *a3)
+{
+ rewrite_server_conf *sconf;
+ rewritemap_entry *newmap;
+ apr_finfo_t st;
+ const char *fname;
+
+ sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module);
+
+ newmap = apr_pcalloc(cmd->pool, sizeof(rewritemap_entry));
+
+ if (strncasecmp(a2, "txt:", 4) == 0) {
+ if ((fname = ap_server_root_relative(cmd->pool, a2+4)) == NULL) {
+ return apr_pstrcat(cmd->pool, "RewriteMap: bad path to txt map: ",
+ a2+4, NULL);
+ }
+
+ newmap->type = MAPTYPE_TXT;
+ newmap->datafile = fname;
+ newmap->checkfile = fname;
+ newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s",
+ (void *)cmd->server, a1);
+ }
+ else if (strncasecmp(a2, "rnd:", 4) == 0) {
+ if ((fname = ap_server_root_relative(cmd->pool, a2+4)) == NULL) {
+ return apr_pstrcat(cmd->pool, "RewriteMap: bad path to rnd map: ",
+ a2+4, NULL);
+ }
+
+ newmap->type = MAPTYPE_RND;
+ newmap->datafile = fname;
+ newmap->checkfile = fname;
+ newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s",
+ (void *)cmd->server, a1);
+ }
+ else if (strncasecmp(a2, "dbm", 3) == 0) {
+ apr_status_t rv;
+
+ newmap->type = MAPTYPE_DBM;
+ fname = NULL;
+ newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s",
+ (void *)cmd->server, a1);
+
+ if (a2[3] == ':') {
+ newmap->dbmtype = "default";
+ fname = a2+4;
+ }
+ else if (a2[3] == '=') {
+ const char *colon = ap_strchr_c(a2 + 4, ':');
+
+ if (colon) {
+ newmap->dbmtype = apr_pstrndup(cmd->pool, a2 + 4,
+ colon - (a2 + 3) - 1);
+ fname = colon + 1;
+ }
+ }
+
+ if (!fname) {
+ return apr_pstrcat(cmd->pool, "RewriteMap: bad map:",
+ a2, NULL);
+ }
+
+ if ((newmap->datafile = ap_server_root_relative(cmd->pool,
+ fname)) == NULL) {
+ return apr_pstrcat(cmd->pool, "RewriteMap: bad path to dbm map: ",
+ fname, NULL);
+ }
+
+ rv = apr_dbm_get_usednames_ex(cmd->pool, newmap->dbmtype,
+ newmap->datafile, &newmap->checkfile,
+ &newmap->checkfile2);
+ if (rv != APR_SUCCESS) {
+ return apr_pstrcat(cmd->pool, "RewriteMap: dbm type ",
+ newmap->dbmtype, " is invalid", NULL);
+ }
+ }
+ else if ((strncasecmp(a2, "dbd:", 4) == 0)
+ || (strncasecmp(a2, "fastdbd:", 8) == 0)) {
+ if (dbd_prepare == NULL) {
+ return "RewriteMap types dbd and fastdbd require mod_dbd!";
+ }
+ if ((a2[0] == 'd') || (a2[0] == 'D')) {
+ newmap->type = MAPTYPE_DBD;
+ fname = a2+4;
+ }
+ else {
+ newmap->type = MAPTYPE_DBD_CACHE;
+ fname = a2+8;
+ newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s",
+ (void *)cmd->server, a1);
+ }
+ newmap->dbdq = a1;
+ dbd_prepare(cmd->server, fname, newmap->dbdq);
+ }
+ else if (strncasecmp(a2, "prg:", 4) == 0) {
+ apr_tokenize_to_argv(a2 + 4, &newmap->argv, cmd->pool);
+
+ fname = newmap->argv[0];
+ if ((newmap->argv[0] = ap_server_root_relative(cmd->pool,
+ fname)) == NULL) {
+ return apr_pstrcat(cmd->pool, "RewriteMap: bad path to prg map: ",
+ fname, NULL);
+ }
+
+ newmap->type = MAPTYPE_PRG;
+ newmap->checkfile = newmap->argv[0];
+ rewrite_lock_needed = 1;
+
+ if (a3) {
+ char *tok_cntx;
+ newmap->user = apr_strtok(apr_pstrdup(cmd->pool, a3), ":", &tok_cntx);
+ newmap->group = apr_strtok(NULL, ":", &tok_cntx);
+ }
+ }
+ else if (strncasecmp(a2, "int:", 4) == 0) {
+ newmap->type = MAPTYPE_INT;
+ newmap->func = (char *(*)(request_rec *,char *))
+ apr_hash_get(mapfunc_hash, a2+4, strlen(a2+4));
+ if (newmap->func == NULL) {
+ return apr_pstrcat(cmd->pool, "RewriteMap: internal map not found:",
+ a2+4, NULL);
+ }
+ }
+ else {
+ if ((fname = ap_server_root_relative(cmd->pool, a2)) == NULL) {
+ return apr_pstrcat(cmd->pool, "RewriteMap: bad path to txt map: ",
+ a2, NULL);
+ }
+
+ newmap->type = MAPTYPE_TXT;
+ newmap->datafile = fname;
+ newmap->checkfile = fname;
+ newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s",
+ (void *)cmd->server, a1);
+ }
+
+ if (newmap->checkfile
+ && (apr_stat(&st, newmap->checkfile, APR_FINFO_MIN,
+ cmd->pool) != APR_SUCCESS)) {
+ return apr_pstrcat(cmd->pool,
+ "RewriteMap: file for map ", a1,
+ " not found:", newmap->checkfile, NULL);
+ }
+
+ apr_hash_set(sconf->rewritemaps, a1, APR_HASH_KEY_STRING, newmap);
+
+ return NULL;
+}
+
+static const char *cmd_rewritebase(cmd_parms *cmd, void *in_dconf,
+ const char *a1)
+{
+ rewrite_perdir_conf *dconf = in_dconf;
+
+ if (cmd->path == NULL || dconf == NULL) {
+ return "RewriteBase: only valid in per-directory config files";
+ }
+ if (a1[0] == '\0') {
+ return "RewriteBase: empty URL not allowed";
+ }
+ if (a1[0] != '/') {
+ return "RewriteBase: argument is not a valid URL";
+ }
+
+ dconf->baseurl = a1;
+ dconf->baseurl_set = 1;
+
+ return NULL;
+}
+
+/*
+ * generic lexer for RewriteRule and RewriteCond flags.
+ * The parser will be passed in as a function pointer
+ * and called if a flag was found
+ */
+static const char *cmd_parseflagfield(apr_pool_t *p, void *cfg, char *key,
+ const char *(*parse)(apr_pool_t *,
+ void *,
+ char *, char *))
+{
+ char *val, *nextp, *endp;
+ const char *err;
+
+ endp = key + strlen(key) - 1;
+ if (*key != '[' || *endp != ']') {
+ return "bad flag delimiters";
+ }
+
+ *endp = ','; /* for simpler parsing */
+ ++key;
+
+ while (*key) {
+ /* skip leading spaces */
+ while (apr_isspace(*key)) {
+ ++key;
+ }
+
+ if (!*key || (nextp = ap_strchr(key, ',')) == NULL) { /* NULL should not
+ * happen, but ...
+ */
+ break;
+ }
+
+ /* strip trailing spaces */
+ endp = nextp - 1;
+ while (apr_isspace(*endp)) {
+ --endp;
+ }
+ *++endp = '\0';
+
+ /* split key and val */
+ val = ap_strchr(key, '=');
+ if (val) {
+ *val++ = '\0';
+ }
+ else {
+ val = endp;
+ }
+
+ err = parse(p, cfg, key, val);
+ if (err) {
+ return err;
+ }
+
+ key = nextp + 1;
+ }
+
+ return NULL;
+}
+
+static const char *cmd_rewritecond_setflag(apr_pool_t *p, void *_cfg,
+ char *key, char *val)
+{
+ rewritecond_entry *cfg = _cfg;
+
+ if ( strcasecmp(key, "nocase") == 0
+ || strcasecmp(key, "NC") == 0 ) {
+ cfg->flags |= CONDFLAG_NOCASE;
+ }
+ else if ( strcasecmp(key, "ornext") == 0
+ || strcasecmp(key, "OR") == 0 ) {
+ cfg->flags |= CONDFLAG_ORNEXT;
+ }
+ else if ( strcasecmp(key, "novary") == 0
+ || strcasecmp(key, "NV") == 0 ) {
+ cfg->flags |= CONDFLAG_NOVARY;
+ }
+ else {
+ return apr_pstrcat(p, "unknown flag '", key, "'", NULL);
+ }
+ return NULL;
+}
+
+static const char *cmd_rewritecond(cmd_parms *cmd, void *in_dconf,
+ const char *in_str)
+{
+ rewrite_perdir_conf *dconf = in_dconf;
+ char *str = apr_pstrdup(cmd->pool, in_str);
+ rewrite_server_conf *sconf;
+ rewritecond_entry *newcond;
+ ap_regex_t *regexp;
+ char *a1 = NULL, *a2 = NULL, *a2_end, *a3 = NULL;
+ const char *err;
+
+ sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module);
+
+ /* make a new entry in the internal temporary rewrite rule list */
+ if (cmd->path == NULL) { /* is server command */
+ newcond = apr_array_push(sconf->rewriteconds);
+ }
+ else { /* is per-directory command */
+ newcond = apr_array_push(dconf->rewriteconds);
+ }
+
+ /* parse the argument line ourself
+ * a1 .. a3 are substrings of str, which is a fresh copy
+ * of the argument line. So we can use a1 .. a3 without
+ * copying them again.
+ */
+ if (parseargline(str, &a1, &a2, &a2_end, &a3)) {
+ return apr_pstrcat(cmd->pool, "RewriteCond: bad argument line '", str,
+ "'", NULL);
+ }
+
+ /* arg1: the input string */
+ newcond->input = a1;
+
+ /* arg3: optional flags field
+ * (this has to be parsed first, because we need to
+ * know if the regex should be compiled with ICASE!)
+ */
+ newcond->flags = CONDFLAG_NONE;
+ if (a3 != NULL) {
+ if ((err = cmd_parseflagfield(cmd->pool, newcond, a3,
+ cmd_rewritecond_setflag)) != NULL) {
+ return apr_pstrcat(cmd->pool, "RewriteCond: ", err, NULL);
+ }
+ }
+
+ /* arg2: the pattern */
+ newcond->pattern = a2;
+ if (*a2 == '!') {
+ newcond->flags |= CONDFLAG_NOTMATCH;
+ ++a2;
+ }
+
+ /* determine the pattern type */
+ newcond->ptype = CONDPAT_REGEX;
+ if (strcasecmp(a1, "expr") == 0) {
+ newcond->ptype = CONDPAT_AP_EXPR;
+ }
+ else if (*a2 && a2[1]) {
+ if (*a2 == '-') {
+ if (!a2[2]) {
+ switch (a2[1]) {
+ case 'f': newcond->ptype = CONDPAT_FILE_EXISTS; break;
+ case 's': newcond->ptype = CONDPAT_FILE_SIZE; break;
+ case 'd': newcond->ptype = CONDPAT_FILE_DIR; break;
+ case 'x': newcond->ptype = CONDPAT_FILE_XBIT; break;
+ case 'h': newcond->ptype = CONDPAT_FILE_LINK; break;
+ case 'L': newcond->ptype = CONDPAT_FILE_LINK; break;
+ case 'l': newcond->ptype = CONDPAT_FILE_LINK; break;
+ case 'U': newcond->ptype = CONDPAT_LU_URL; break;
+ case 'F': newcond->ptype = CONDPAT_LU_FILE; break;
+ }
+ }
+ else if (a2[3]) {
+ switch (a2[1]) {
+ case 'l':
+ if (a2[2] == 't') {
+ a2 += 3;
+ newcond->ptype = CONDPAT_INT_LT;
+ }
+ else if (a2[2] == 'e') {
+ a2 += 3;
+ newcond->ptype = CONDPAT_INT_LE;
+ }
+ break;
+
+ case 'g':
+ if (a2[2] == 't') {
+ a2 += 3;
+ newcond->ptype = CONDPAT_INT_GT;
+ }
+ else if (a2[2] == 'e') {
+ a2 += 3;
+ newcond->ptype = CONDPAT_INT_GE;
+ }
+ break;
+
+ case 'e':
+ if (a2[2] == 'q') {
+ a2 += 3;
+ newcond->ptype = CONDPAT_INT_EQ;
+ }
+ break;
+
+ case 'n':
+ if (a2[2] == 'e') {
+ /* Inversion, ensure !-ne == -eq */
+ a2 += 3;
+ newcond->ptype = CONDPAT_INT_EQ;
+ newcond->flags ^= CONDFLAG_NOTMATCH;
+ }
+ break;
+ }
+ }
+ }
+ else {
+ switch (*a2) {
+ case '>': if (*++a2 == '=')
+ ++a2, newcond->ptype = CONDPAT_STR_GE;
+ else
+ newcond->ptype = CONDPAT_STR_GT;
+ break;
+
+ case '<': if (*++a2 == '=')
+ ++a2, newcond->ptype = CONDPAT_STR_LE;
+ else
+ newcond->ptype = CONDPAT_STR_LT;
+ break;
+
+ case '=': newcond->ptype = CONDPAT_STR_EQ;
+ /* "" represents an empty string */
+ if (*++a2 == '"' && a2[1] == '"' && !a2[2])
+ a2 += 2;
+ break;
+ }
+ }
+ }
+
+ if ((newcond->ptype != CONDPAT_REGEX) &&
+ (newcond->ptype < CONDPAT_STR_LT || newcond->ptype > CONDPAT_STR_GE) &&
+ (newcond->flags & CONDFLAG_NOCASE)) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00665)
+ "RewriteCond: NoCase option for non-regex pattern '%s' "
+ "is not supported and will be ignored. (%s:%d)", a2,
+ cmd->directive->filename, cmd->directive->line_num);
+ newcond->flags &= ~CONDFLAG_NOCASE;
+ }
+
+ newcond->pskip = a2 - newcond->pattern;
+ newcond->pattern += newcond->pskip;
+
+ if (newcond->ptype == CONDPAT_REGEX) {
+ regexp = ap_pregcomp(cmd->pool, a2,
+ AP_REG_EXTENDED | ((newcond->flags & CONDFLAG_NOCASE)
+ ? AP_REG_ICASE : 0));
+ if (!regexp) {
+ return apr_pstrcat(cmd->pool, "RewriteCond: cannot compile regular "
+ "expression '", a2, "'", NULL);
+ }
+
+ newcond->regexp = regexp;
+ }
+ else if (newcond->ptype == CONDPAT_AP_EXPR) {
+ unsigned int flags = newcond->flags & CONDFLAG_NOVARY ?
+ AP_EXPR_FLAG_DONT_VARY : 0;
+ newcond->expr = ap_expr_parse_cmd(cmd, a2, flags, &err, NULL);
+ if (err)
+ return apr_psprintf(cmd->pool, "RewriteCond: cannot compile "
+ "expression \"%s\": %s", a2, err);
+ }
+
+ return NULL;
+}
+
+static const char *cmd_rewriterule_setflag(apr_pool_t *p, void *_cfg,
+ char *key, char *val)
+{
+ rewriterule_entry *cfg = _cfg;
+ int error = 0;
+
+ switch (*key++) {
+ case 'b':
+ case 'B':
+ if (!*key || !strcasecmp(key, "ackrefescaping")) {
+ cfg->flags |= RULEFLAG_ESCAPEBACKREF;
+ if (val && *val) {
+ cfg->escapes = val;
+ }
+ }
+ else if (!strcasecmp(key, "NP") || !strcasecmp(key, "ackrefernoplus")) {
+ cfg->flags |= RULEFLAG_ESCAPENOPLUS;
+ }
+ else {
+ ++error;
+ }
+ break;
+ case 'c':
+ case 'C':
+ if (!*key || !strcasecmp(key, "hain")) { /* chain */
+ cfg->flags |= RULEFLAG_CHAIN;
+ }
+ else if (((*key == 'O' || *key == 'o') && !key[1])
+ || !strcasecmp(key, "ookie")) { /* cookie */
+ data_item *cp = cfg->cookie;
+
+ if (!cp) {
+ cp = cfg->cookie = apr_palloc(p, sizeof(*cp));
+ }
+ else {
+ while (cp->next) {
+ cp = cp->next;
+ }
+ cp->next = apr_palloc(p, sizeof(*cp));
+ cp = cp->next;
+ }
+
+ cp->next = NULL;
+ cp->data = val;
+ }
+ else {
+ ++error;
+ }
+ break;
+ case 'd':
+ case 'D':
+ if (!*key || !strcasecmp(key, "PI") || !strcasecmp(key,"iscardpath")) {
+ cfg->flags |= (RULEFLAG_DISCARDPATHINFO);
+ }
+ break;
+ case 'e':
+ case 'E':
+ if (!*key || !strcasecmp(key, "nv")) { /* env */
+ data_item *cp = cfg->env;
+
+ if (!cp) {
+ cp = cfg->env = apr_palloc(p, sizeof(*cp));
+ }
+ else {
+ while (cp->next) {
+ cp = cp->next;
+ }
+ cp->next = apr_palloc(p, sizeof(*cp));
+ cp = cp->next;
+ }
+
+ cp->next = NULL;
+ cp->data = val;
+ }
+ else if (!strcasecmp(key, "nd")) { /* end */
+ cfg->flags |= RULEFLAG_END;
+ }
+ else {
+ ++error;
+ }
+ break;
+
+ case 'f':
+ case 'F':
+ if (!*key || !strcasecmp(key, "orbidden")) { /* forbidden */
+ cfg->flags |= (RULEFLAG_STATUS | RULEFLAG_NOSUB);
+ cfg->forced_responsecode = HTTP_FORBIDDEN;
+ }
+ else {
+ ++error;
+ }
+ break;
+
+ case 'g':
+ case 'G':
+ if (!*key || !strcasecmp(key, "one")) { /* gone */
+ cfg->flags |= (RULEFLAG_STATUS | RULEFLAG_NOSUB);
+ cfg->forced_responsecode = HTTP_GONE;
+ }
+ else {
+ ++error;
+ }
+ break;
+
+ case 'h':
+ case 'H':
+ if (!*key || !strcasecmp(key, "andler")) { /* handler */
+ cfg->forced_handler = val;
+ }
+ else {
+ ++error;
+ }
+ break;
+ case 'l':
+ case 'L':
+ if (!*key || !strcasecmp(key, "ast")) { /* last */
+ cfg->flags |= RULEFLAG_LASTRULE;
+ }
+ else {
+ ++error;
+ }
+ break;
+
+ case 'n':
+ case 'N':
+ if (((*key == 'E' || *key == 'e') && !key[1])
+ || !strcasecmp(key, "oescape")) { /* noescape */
+ cfg->flags |= RULEFLAG_NOESCAPE;
+ }
+ else if (!*key || !strcasecmp(key, "ext")) { /* next */
+ cfg->flags |= RULEFLAG_NEWROUND;
+ if (val && *val) {
+ cfg->maxrounds = atoi(val);
+ }
+
+ }
+ else if (((*key == 'S' || *key == 's') && !key[1])
+ || !strcasecmp(key, "osubreq")) { /* nosubreq */
+ cfg->flags |= RULEFLAG_IGNOREONSUBREQ;
+ }
+ else if (((*key == 'C' || *key == 'c') && !key[1])
+ || !strcasecmp(key, "ocase")) { /* nocase */
+ cfg->flags |= RULEFLAG_NOCASE;
+ }
+ else {
+ ++error;
+ }
+ break;
+
+ case 'p':
+ case 'P':
+ if (!*key || !strcasecmp(key, "roxy")) { /* proxy */
+ cfg->flags |= RULEFLAG_PROXY;
+ }
+ else if (((*key == 'T' || *key == 't') && !key[1])
+ || !strcasecmp(key, "assthrough")) { /* passthrough */
+ cfg->flags |= RULEFLAG_PASSTHROUGH;
+ }
+ else {
+ ++error;
+ }
+ break;
+
+ case 'q':
+ case 'Q':
+ if ( !strcasecmp(key, "SA")
+ || !strcasecmp(key, "sappend")) { /* qsappend */
+ cfg->flags |= RULEFLAG_QSAPPEND;
+ } else if ( !strcasecmp(key, "SD")
+ || !strcasecmp(key, "sdiscard") ) { /* qsdiscard */
+ cfg->flags |= RULEFLAG_QSDISCARD;
+ } else if ( !strcasecmp(key, "SL")
+ || !strcasecmp(key, "slast") ) { /* qslast */
+ cfg->flags |= RULEFLAG_QSLAST;
+ }
+ else {
+ ++error;
+ }
+ break;
+
+ case 'r':
+ case 'R':
+ if (!*key || !strcasecmp(key, "edirect")) { /* redirect */
+ int status = 0;
+
+ cfg->flags |= RULEFLAG_FORCEREDIRECT;
+ if (*val) {
+ if (strcasecmp(val, "permanent") == 0) {
+ status = HTTP_MOVED_PERMANENTLY;
+ }
+ else if (strcasecmp(val, "temp") == 0) {
+ status = HTTP_MOVED_TEMPORARILY;
+ }
+ else if (strcasecmp(val, "seeother") == 0) {
+ status = HTTP_SEE_OTHER;
+ }
+ else if (apr_isdigit(*val)) {
+ status = atoi(val);
+ if (status != HTTP_INTERNAL_SERVER_ERROR) {
+ int idx =
+ ap_index_of_response(HTTP_INTERNAL_SERVER_ERROR);
+
+ if (ap_index_of_response(status) == idx) {
+ return apr_psprintf(p, "invalid HTTP "
+ "response code '%s' for "
+ "flag 'R'",
+ val);
+ }
+ }
+ if (!ap_is_HTTP_REDIRECT(status)) {
+ cfg->flags |= (RULEFLAG_STATUS | RULEFLAG_NOSUB);
+ }
+ }
+ cfg->forced_responsecode = status;
+ }
+ }
+ else {
+ ++error;
+ }
+ break;
+
+ case 's':
+ case 'S':
+ if (!*key || !strcasecmp(key, "kip")) { /* skip */
+ cfg->skip = atoi(val);
+ }
+ else {
+ ++error;
+ }
+ break;
+
+ case 't':
+ case 'T':
+ if (!*key || !strcasecmp(key, "ype")) { /* type */
+ cfg->forced_mimetype = val;
+ }
+ else {
+ ++error;
+ }
+ break;
+ default:
+ ++error;
+ break;
+ }
+
+ if (error) {
+ return apr_pstrcat(p, "unknown flag '", --key, "'", NULL);
+ }
+
+ return NULL;
+}
+
+static const char *cmd_rewriterule(cmd_parms *cmd, void *in_dconf,
+ const char *in_str)
+{
+ rewrite_perdir_conf *dconf = in_dconf;
+ char *str = apr_pstrdup(cmd->pool, in_str);
+ rewrite_server_conf *sconf;
+ rewriterule_entry *newrule;
+ ap_regex_t *regexp;
+ char *a1 = NULL, *a2 = NULL, *a2_end, *a3 = NULL;
+ const char *err;
+
+ sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module);
+
+ /* make a new entry in the internal rewrite rule list */
+ if (cmd->path == NULL) { /* is server command */
+ newrule = apr_array_push(sconf->rewriterules);
+ }
+ else { /* is per-directory command */
+ newrule = apr_array_push(dconf->rewriterules);
+ }
+
+ /* parse the argument line ourself */
+ if (parseargline(str, &a1, &a2, &a2_end, &a3)) {
+ return apr_pstrcat(cmd->pool, "RewriteRule: bad argument line '", str,
+ "'", NULL);
+ }
+
+ /* arg3: optional flags field */
+ newrule->forced_mimetype = NULL;
+ newrule->forced_handler = NULL;
+ newrule->forced_responsecode = HTTP_MOVED_TEMPORARILY;
+ newrule->flags = RULEFLAG_NONE;
+ newrule->env = NULL;
+ newrule->cookie = NULL;
+ newrule->skip = 0;
+ newrule->maxrounds = REWRITE_MAX_ROUNDS;
+ if (a3 != NULL) {
+ if ((err = cmd_parseflagfield(cmd->pool, newrule, a3,
+ cmd_rewriterule_setflag)) != NULL) {
+ return apr_pstrcat(cmd->pool, "RewriteRule: ", err, NULL);
+ }
+ }
+
+ /* arg1: the pattern
+ * try to compile the regexp to test if is ok
+ */
+ if (*a1 == '!') {
+ newrule->flags |= RULEFLAG_NOTMATCH;
+ ++a1;
+ }
+
+ regexp = ap_pregcomp(cmd->pool, a1, AP_REG_EXTENDED |
+ ((newrule->flags & RULEFLAG_NOCASE)
+ ? AP_REG_ICASE : 0));
+ if (!regexp) {
+ return apr_pstrcat(cmd->pool,
+ "RewriteRule: cannot compile regular expression '",
+ a1, "'", NULL);
+ }
+
+ newrule->pattern = a1;
+ newrule->regexp = regexp;
+
+ /* arg2: the output string */
+ newrule->output = a2;
+ if (*a2 == '-' && !a2[1]) {
+ newrule->flags |= RULEFLAG_NOSUB;
+ }
+
+ if (*(a2_end-1) == '?') {
+ /* a literal ? at the end of the unsubstituted rewrite rule */
+ newrule->flags |= RULEFLAG_QSNONE;
+ }
+ else if (newrule->flags & RULEFLAG_QSDISCARD) {
+ if (NULL == ap_strchr(newrule->output, '?')) {
+ newrule->flags |= RULEFLAG_QSNONE;
+ }
+ }
+
+ /* now, if the server or per-dir config holds an
+ * array of RewriteCond entries, we take it for us
+ * and clear the array
+ */
+ if (cmd->path == NULL) { /* is server command */
+ newrule->rewriteconds = sconf->rewriteconds;
+ sconf->rewriteconds = apr_array_make(cmd->pool, 2,
+ sizeof(rewritecond_entry));
+ }
+ else { /* is per-directory command */
+ newrule->rewriteconds = dconf->rewriteconds;
+ dconf->rewriteconds = apr_array_make(cmd->pool, 2,
+ sizeof(rewritecond_entry));
+ }
+
+ return NULL;
+}
+
+
+/*
+ * +-------------------------------------------------------+
+ * | |
+ * | the rewriting engine
+ * | |
+ * +-------------------------------------------------------+
+ */
+
+/* Lexicographic Compare */
+static APR_INLINE int compare_lexicography(char *a, char *b)
+{
+ apr_size_t i, lena, lenb;
+
+ lena = strlen(a);
+ lenb = strlen(b);
+
+ if (lena == lenb) {
+ for (i = 0; i < lena; ++i) {
+ if (a[i] != b[i]) {
+ return ((unsigned char)a[i] > (unsigned char)b[i]) ? 1 : -1;
+ }
+ }
+
+ return 0;
+ }
+
+ return ((lena > lenb) ? 1 : -1);
+}
+
+/*
+ * Apply a single rewriteCond
+ */
+static int apply_rewrite_cond(rewritecond_entry *p, rewrite_ctx *ctx)
+{
+ char *input = NULL;
+ apr_finfo_t sb;
+ request_rec *rsub, *r = ctx->r;
+ ap_regmatch_t regmatch[AP_MAX_REG_MATCH];
+ int rc = 0;
+ int basis;
+
+ if (p->ptype != CONDPAT_AP_EXPR)
+ input = do_expand(p->input, ctx, NULL);
+
+ switch (p->ptype) {
+ case CONDPAT_FILE_EXISTS:
+ if ( apr_stat(&sb, input, APR_FINFO_MIN, r->pool) == APR_SUCCESS
+ && sb.filetype == APR_REG) {
+ rc = 1;
+ }
+ break;
+
+ case CONDPAT_FILE_SIZE:
+ if ( apr_stat(&sb, input, APR_FINFO_MIN, r->pool) == APR_SUCCESS
+ && sb.filetype == APR_REG && sb.size > 0) {
+ rc = 1;
+ }
+ break;
+
+ case CONDPAT_FILE_LINK:
+#if !defined(OS2)
+ if ( apr_stat(&sb, input, APR_FINFO_MIN | APR_FINFO_LINK,
+ r->pool) == APR_SUCCESS
+ && sb.filetype == APR_LNK) {
+ rc = 1;
+ }
+#endif
+ break;
+
+ case CONDPAT_FILE_DIR:
+ if ( apr_stat(&sb, input, APR_FINFO_MIN, r->pool) == APR_SUCCESS
+ && sb.filetype == APR_DIR) {
+ rc = 1;
+ }
+ break;
+
+ case CONDPAT_FILE_XBIT:
+ if ( apr_stat(&sb, input, APR_FINFO_PROT, r->pool) == APR_SUCCESS
+ && (sb.protection & (APR_UEXECUTE | APR_GEXECUTE | APR_WEXECUTE))) {
+ rc = 1;
+ }
+ break;
+
+ case CONDPAT_LU_URL:
+ if (*input && subreq_ok(r)) {
+ rsub = ap_sub_req_lookup_uri(input, r, NULL);
+ if (rsub->status < 400) {
+ rc = 1;
+ }
+ rewritelog((r, 5, NULL, "RewriteCond URI (-U) check: "
+ "path=%s -> status=%d", input, rsub->status));
+ ap_destroy_sub_req(rsub);
+ }
+ break;
+
+ case CONDPAT_LU_FILE:
+ if (*input && subreq_ok(r)) {
+ rsub = ap_sub_req_lookup_file(input, r, NULL);
+ if (rsub->status < 300 &&
+ /* double-check that file exists since default result is 200 */
+ apr_stat(&sb, rsub->filename, APR_FINFO_MIN,
+ r->pool) == APR_SUCCESS) {
+ rc = 1;
+ }
+ rewritelog((r, 5, NULL, "RewriteCond file (-F) check: path=%s "
+ "-> file=%s status=%d", input, rsub->filename,
+ rsub->status));
+ ap_destroy_sub_req(rsub);
+ }
+ break;
+
+ case CONDPAT_STR_GE:
+ basis = 0;
+ goto test_str_g;
+ case CONDPAT_STR_GT:
+ basis = 1;
+test_str_g:
+ if (p->flags & CONDFLAG_NOCASE) {
+ rc = (strcasecmp(input, p->pattern) >= basis) ? 1 : 0;
+ }
+ else {
+ rc = (compare_lexicography(input, p->pattern) >= basis) ? 1 : 0;
+ }
+ break;
+
+ case CONDPAT_STR_LE:
+ basis = 0;
+ goto test_str_l;
+ case CONDPAT_STR_LT:
+ basis = -1;
+test_str_l:
+ if (p->flags & CONDFLAG_NOCASE) {
+ rc = (strcasecmp(input, p->pattern) <= basis) ? 1 : 0;
+ }
+ else {
+ rc = (compare_lexicography(input, p->pattern) <= basis) ? 1 : 0;
+ }
+ break;
+
+ case CONDPAT_STR_EQ:
+ /* Note: the only type where the operator is dropped from p->pattern */
+ if (p->flags & CONDFLAG_NOCASE) {
+ rc = !strcasecmp(input, p->pattern);
+ }
+ else {
+ rc = !strcmp(input, p->pattern);
+ }
+ break;
+
+ case CONDPAT_INT_GE: rc = (atoi(input) >= atoi(p->pattern)); break;
+ case CONDPAT_INT_GT: rc = (atoi(input) > atoi(p->pattern)); break;
+
+ case CONDPAT_INT_LE: rc = (atoi(input) <= atoi(p->pattern)); break;
+ case CONDPAT_INT_LT: rc = (atoi(input) < atoi(p->pattern)); break;
+
+ case CONDPAT_INT_EQ: rc = (atoi(input) == atoi(p->pattern)); break;
+
+ case CONDPAT_AP_EXPR:
+ {
+ const char *err, *source;
+ rc = ap_expr_exec_re(r, p->expr, AP_MAX_REG_MATCH, regmatch,
+ &source, &err);
+ if (rc < 0 || err) {
+ rewritelog((r, 1, ctx->perdir,
+ "RewriteCond: expr='%s' evaluation failed: %s",
+ p->pattern - p->pskip, err));
+ rc = 0;
+ }
+ /* update briRC backref info */
+ if (rc && !(p->flags & CONDFLAG_NOTMATCH)) {
+ ctx->briRC.source = source;
+ memcpy(ctx->briRC.regmatch, regmatch, sizeof(regmatch));
+ }
+ }
+ break;
+ default:
+ /* it is really a regexp pattern, so apply it */
+ rc = !ap_regexec(p->regexp, input, AP_MAX_REG_MATCH, regmatch, 0);
+
+ /* update briRC backref info */
+ if (rc && !(p->flags & CONDFLAG_NOTMATCH)) {
+ ctx->briRC.source = input;
+ memcpy(ctx->briRC.regmatch, regmatch, sizeof(regmatch));
+ }
+ break;
+ }
+
+ if (p->flags & CONDFLAG_NOTMATCH) {
+ rc = !rc;
+ }
+
+ rewritelog((r, 4, ctx->perdir, "RewriteCond: input='%s' pattern='%s'%s "
+ "=> %s", input, p->pattern - p->pskip,
+ (p->flags & CONDFLAG_NOCASE) ? " [NC]" : "",
+ rc ? "matched" : "not-matched"));
+
+ return rc;
+}
+
+/* check for forced type and handler */
+static APR_INLINE void force_type_handler(rewriterule_entry *p,
+ rewrite_ctx *ctx)
+{
+ char *expanded;
+
+ if (p->forced_mimetype) {
+ expanded = do_expand(p->forced_mimetype, ctx, p);
+
+ if (*expanded) {
+ ap_str_tolower(expanded);
+
+ rewritelog((ctx->r, 2, ctx->perdir, "remember %s to have MIME-type "
+ "'%s'", ctx->r->filename, expanded));
+
+ apr_table_setn(ctx->r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR,
+ expanded);
+ }
+ }
+
+ if (p->forced_handler) {
+ expanded = do_expand(p->forced_handler, ctx, p);
+
+ if (*expanded) {
+ ap_str_tolower(expanded);
+
+ rewritelog((ctx->r, 2, ctx->perdir, "remember %s to have "
+ "Content-handler '%s'", ctx->r->filename, expanded));
+
+ apr_table_setn(ctx->r->notes, REWRITE_FORCED_HANDLER_NOTEVAR,
+ expanded);
+ }
+ }
+}
+
+/*
+ * Apply a single RewriteRule
+ */
+static int apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx)
+{
+ ap_regmatch_t regmatch[AP_MAX_REG_MATCH];
+ apr_array_header_t *rewriteconds;
+ rewritecond_entry *conds;
+ int i, rc;
+ char *newuri = NULL;
+ request_rec *r = ctx->r;
+ int is_proxyreq = 0;
+
+ ctx->uri = r->filename;
+
+ if (ctx->perdir) {
+ apr_size_t dirlen = strlen(ctx->perdir);
+
+ /*
+ * Proxy request?
+ */
+ is_proxyreq = ( r->proxyreq && r->filename
+ && !strncmp(r->filename, "proxy:", 6));
+
+ /* Since we want to match against the (so called) full URL, we have
+ * to re-add the PATH_INFO postfix
+ */
+ if (r->path_info && *r->path_info) {
+ rewritelog((r, 3, ctx->perdir, "add path info postfix: %s -> %s%s",
+ ctx->uri, ctx->uri, r->path_info));
+ ctx->uri = apr_pstrcat(r->pool, ctx->uri, r->path_info, NULL);
+ }
+
+ /* Additionally we strip the physical path from the url to match
+ * it independent from the underlying filesystem.
+ */
+ if (!is_proxyreq && strlen(ctx->uri) >= dirlen &&
+ !strncmp(ctx->uri, ctx->perdir, dirlen)) {
+
+ rewritelog((r, 3, ctx->perdir, "strip per-dir prefix: %s -> %s",
+ ctx->uri, ctx->uri + dirlen));
+ ctx->uri = ctx->uri + dirlen;
+ }
+ }
+
+ /* Try to match the URI against the RewriteRule pattern
+ * and exit immediately if it didn't apply.
+ */
+ rewritelog((r, 3, ctx->perdir, "applying pattern '%s' to uri '%s'",
+ p->pattern, ctx->uri));
+
+ rc = !ap_regexec(p->regexp, ctx->uri, AP_MAX_REG_MATCH, regmatch, 0);
+ if (! (( rc && !(p->flags & RULEFLAG_NOTMATCH)) ||
+ (!rc && (p->flags & RULEFLAG_NOTMATCH)) ) ) {
+ return 0;
+ }
+
+ /* It matched, wow! Now it's time to prepare the context structure for
+ * further processing
+ */
+ ctx->vary_this = NULL;
+ ctx->briRC.source = NULL;
+
+ if (p->flags & RULEFLAG_NOTMATCH) {
+ ctx->briRR.source = NULL;
+ }
+ else {
+ ctx->briRR.source = apr_pstrdup(r->pool, ctx->uri);
+ memcpy(ctx->briRR.regmatch, regmatch, sizeof(regmatch));
+ }
+
+ /* Ok, we already know the pattern has matched, but we now
+ * additionally have to check for all existing preconditions
+ * (RewriteCond) which have to be also true. We do this at
+ * this very late stage to avoid unnecessary checks which
+ * would slow down the rewriting engine.
+ */
+ rewriteconds = p->rewriteconds;
+ conds = (rewritecond_entry *)rewriteconds->elts;
+
+ for (i = 0; i < rewriteconds->nelts; ++i) {
+ rewritecond_entry *c = &conds[i];
+
+ rc = apply_rewrite_cond(c, ctx);
+ /*
+ * Reset vary_this if the novary flag is set for this condition.
+ */
+ if (c->flags & CONDFLAG_NOVARY) {
+ ctx->vary_this = NULL;
+ }
+ if (c->flags & CONDFLAG_ORNEXT) {
+ if (!rc) {
+ /* One condition is false, but another can be still true. */
+ ctx->vary_this = NULL;
+ continue;
+ }
+ else {
+ /* skip the rest of the chained OR conditions */
+ while ( i < rewriteconds->nelts
+ && c->flags & CONDFLAG_ORNEXT) {
+ c = &conds[++i];
+ }
+ }
+ }
+ else if (!rc) {
+ return 0;
+ }
+
+ /* If some HTTP header was involved in the condition, remember it
+ * for later use
+ */
+ if (ctx->vary_this) {
+ ctx->vary = ctx->vary
+ ? apr_pstrcat(r->pool, ctx->vary, ", ", ctx->vary_this,
+ NULL)
+ : ctx->vary_this;
+ ctx->vary_this = NULL;
+ }
+ }
+
+ /* expand the result */
+ if (!(p->flags & RULEFLAG_NOSUB)) {
+ newuri = do_expand(p->output, ctx, p);
+ rewritelog((r, 2, ctx->perdir, "rewrite '%s' -> '%s'", ctx->uri,
+ newuri));
+ }
+
+ /* expand [E=var:val] and [CO=<cookie>] */
+ do_expand_env(p->env, ctx);
+ do_expand_cookie(p->cookie, ctx);
+
+ /* non-substitution rules ('RewriteRule <pat> -') end here. */
+ if (p->flags & RULEFLAG_NOSUB) {
+ force_type_handler(p, ctx);
+
+ if (p->flags & RULEFLAG_STATUS) {
+ rewritelog((r, 2, ctx->perdir, "forcing responsecode %d for %s",
+ p->forced_responsecode, r->filename));
+
+ r->status = p->forced_responsecode;
+ }
+
+ return 2;
+ }
+
+ /* Now adjust API's knowledge about r->filename and r->args */
+ r->filename = newuri;
+
+ if (ctx->perdir && (p->flags & RULEFLAG_DISCARDPATHINFO)) {
+ r->path_info = NULL;
+ }
+
+ splitout_queryargs(r, p->flags);
+
+ /* Add the previously stripped per-directory location prefix, unless
+ * (1) it's an absolute URL path and
+ * (2) it's a full qualified URL
+ */
+ if ( ctx->perdir && !is_proxyreq && *r->filename != '/'
+ && !is_absolute_uri(r->filename, NULL)) {
+ rewritelog((r, 3, ctx->perdir, "add per-dir prefix: %s -> %s%s",
+ r->filename, ctx->perdir, r->filename));
+
+ r->filename = apr_pstrcat(r->pool, ctx->perdir, r->filename, NULL);
+ }
+
+ /* If this rule is forced for proxy throughput
+ * (`RewriteRule ... ... [P]') then emulate mod_proxy's
+ * URL-to-filename handler to be sure mod_proxy is triggered
+ * for this URL later in the Apache API. But make sure it is
+ * a fully-qualified URL. (If not it is qualified with
+ * ourself).
+ */
+ if (p->flags & RULEFLAG_PROXY) {
+ /* For rules evaluated in server context, the mod_proxy fixup
+ * hook can be relied upon to escape the URI as and when
+ * necessary, since it occurs later. If in directory context,
+ * the ordering of the fixup hooks is forced such that
+ * mod_proxy comes first, so the URI must be escaped here
+ * instead. See PR 39746, 46428, and other headaches. */
+ if (ctx->perdir && (p->flags & RULEFLAG_NOESCAPE) == 0) {
+ char *old_filename = r->filename;
+
+ r->filename = ap_escape_uri(r->pool, r->filename);
+ rewritelog((r, 2, ctx->perdir, "escaped URI in per-dir context "
+ "for proxy, %s -> %s", old_filename, r->filename));
+ }
+
+ fully_qualify_uri(r);
+
+ rewritelog((r, 2, ctx->perdir, "forcing proxy-throughput with %s",
+ r->filename));
+
+ r->filename = apr_pstrcat(r->pool, "proxy:", r->filename, NULL);
+ return 1;
+ }
+
+ /* If this rule is explicitly forced for HTTP redirection
+ * (`RewriteRule .. .. [R]') then force an external HTTP
+ * redirect. But make sure it is a fully-qualified URL. (If
+ * not it is qualified with ourself).
+ */
+ if (p->flags & RULEFLAG_FORCEREDIRECT) {
+ fully_qualify_uri(r);
+
+ rewritelog((r, 2, ctx->perdir, "explicitly forcing redirect with %s",
+ r->filename));
+
+ r->status = p->forced_responsecode;
+ return 1;
+ }
+
+ /* Special Rewriting Feature: Self-Reduction
+ * We reduce the URL by stripping a possible
+ * http[s]://<ourhost>[:<port>] prefix, i.e. a prefix which
+ * corresponds to ourself. This is to simplify rewrite maps
+ * and to avoid recursion, etc. When this prefix is not a
+ * coincidence then the user has to use [R] explicitly (see
+ * above).
+ */
+ reduce_uri(r);
+
+ /* If this rule is still implicitly forced for HTTP
+ * redirection (`RewriteRule .. <scheme>://...') then
+ * directly force an external HTTP redirect.
+ */
+ if (is_absolute_uri(r->filename, NULL)) {
+ rewritelog((r, 2, ctx->perdir, "implicitly forcing redirect (rc=%d) "
+ "with %s", p->forced_responsecode, r->filename));
+
+ r->status = p->forced_responsecode;
+ return 1;
+ }
+
+ /* Finally remember the forced mime-type */
+ force_type_handler(p, ctx);
+
+ /* Puuhhhhhhhh... WHAT COMPLICATED STUFF ;_)
+ * But now we're done for this particular rule.
+ */
+ return 1;
+}
+
+/*
+ * Apply a complete rule set,
+ * i.e. a list of rewrite rules
+ */
+static int apply_rewrite_list(request_rec *r, apr_array_header_t *rewriterules,
+ char *perdir)
+{
+ rewriterule_entry *entries;
+ rewriterule_entry *p;
+ int i;
+ int changed;
+ int rc;
+ int s;
+ rewrite_ctx *ctx;
+ int round = 1;
+
+ ctx = apr_palloc(r->pool, sizeof(*ctx));
+ ctx->perdir = perdir;
+ ctx->r = r;
+
+ /*
+ * Iterate over all existing rules
+ */
+ entries = (rewriterule_entry *)rewriterules->elts;
+ changed = 0;
+ loop:
+ for (i = 0; i < rewriterules->nelts; i++) {
+ p = &entries[i];
+
+ /*
+ * Ignore this rule on subrequests if we are explicitly
+ * asked to do so or this is a proxy-throughput or a
+ * forced redirect rule.
+ */
+ if (r->main != NULL &&
+ (p->flags & RULEFLAG_IGNOREONSUBREQ ||
+ p->flags & RULEFLAG_FORCEREDIRECT )) {
+ continue;
+ }
+
+ /*
+ * Apply the current rule.
+ */
+ ctx->vary = NULL;
+ rc = apply_rewrite_rule(p, ctx);
+
+ if (rc) {
+
+ /* Catch looping rules with pathinfo growing unbounded */
+ if ( strlen( r->filename ) > 2*r->server->limit_req_line ) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "RewriteRule '%s' and URI '%s' "
+ "exceeded maximum length (%d)",
+ p->pattern, r->uri, 2*r->server->limit_req_line );
+ r->status = HTTP_INTERNAL_SERVER_ERROR;
+ return ACTION_STATUS;
+ }
+
+ /* Regardless of what we do next, we've found a match. Check to see
+ * if any of the request header fields were involved, and add them
+ * to the Vary field of the response.
+ */
+ if (ctx->vary) {
+ apr_table_merge(r->headers_out, "Vary", ctx->vary);
+ }
+
+ /*
+ * The rule sets the response code (implies match-only)
+ */
+ if (p->flags & RULEFLAG_STATUS) {
+ return ACTION_STATUS;
+ }
+
+ /*
+ * Indicate a change if this was not a match-only rule.
+ */
+ if (rc != 2) {
+ changed = ((p->flags & RULEFLAG_NOESCAPE)
+ ? ACTION_NOESCAPE : ACTION_NORMAL);
+ }
+
+ /*
+ * Pass-Through Feature (`RewriteRule .. .. [PT]'):
+ * Because the Apache 1.x API is very limited we
+ * need this hack to pass the rewritten URL to other
+ * modules like mod_alias, mod_userdir, etc.
+ */
+ if (p->flags & RULEFLAG_PASSTHROUGH) {
+ rewritelog((r, 2, perdir, "forcing '%s' to get passed through "
+ "to next API URI-to-filename handler", r->filename));
+ r->filename = apr_pstrcat(r->pool, "passthrough:",
+ r->filename, NULL);
+ changed = ACTION_NORMAL;
+ break;
+ }
+
+ if (p->flags & RULEFLAG_END) {
+ rewritelog((r, 8, perdir, "Rule has END flag, no further rewriting for this request"));
+ apr_pool_userdata_set("1", really_last_key, apr_pool_cleanup_null, r->pool);
+ break;
+ }
+ /*
+ * Stop processing also on proxy pass-through and
+ * last-rule and new-round flags.
+ */
+ if (p->flags & (RULEFLAG_PROXY | RULEFLAG_LASTRULE)) {
+ break;
+ }
+
+ /*
+ * On "new-round" flag we just start from the top of
+ * the rewriting ruleset again.
+ */
+ if (p->flags & RULEFLAG_NEWROUND) {
+ if (++round >= p->maxrounds) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02596)
+ "RewriteRule '%s' and URI '%s' exceeded "
+ "maximum number of rounds (%d) via the [N] flag",
+ p->pattern, r->uri, p->maxrounds);
+
+ r->status = HTTP_INTERNAL_SERVER_ERROR;
+ return ACTION_STATUS;
+ }
+ goto loop;
+ }
+
+ /*
+ * If we are forced to skip N next rules, do it now.
+ */
+ if (p->skip > 0) {
+ s = p->skip;
+ while ( i < rewriterules->nelts
+ && s > 0) {
+ i++;
+ s--;
+ }
+ }
+ }
+ else {
+ /*
+ * If current rule is chained with next rule(s),
+ * skip all this next rule(s)
+ */
+ while ( i < rewriterules->nelts
+ && p->flags & RULEFLAG_CHAIN) {
+ i++;
+ p = &entries[i];
+ }
+ }
+ }
+ return changed;
+}
+
+
+/*
+ * +-------------------------------------------------------+
+ * | |
+ * | Module Initialization Hooks
+ * | |
+ * +-------------------------------------------------------+
+ */
+
+static int pre_config(apr_pool_t *pconf,
+ apr_pool_t *plog,
+ apr_pool_t *ptemp)
+{
+ APR_OPTIONAL_FN_TYPE(ap_register_rewrite_mapfunc) *map_pfn_register;
+
+ rewrite_lock_needed = 0;
+ ap_mutex_register(pconf, rewritemap_mutex_type, NULL, APR_LOCK_DEFAULT, 0);
+
+ /* register int: rewritemap handlers */
+ map_pfn_register = APR_RETRIEVE_OPTIONAL_FN(ap_register_rewrite_mapfunc);
+ if (map_pfn_register) {
+ map_pfn_register("tolower", rewrite_mapfunc_tolower);
+ map_pfn_register("toupper", rewrite_mapfunc_toupper);
+ map_pfn_register("escape", rewrite_mapfunc_escape);
+ map_pfn_register("unescape", rewrite_mapfunc_unescape);
+ }
+ dbd_acquire = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_acquire);
+ dbd_prepare = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_prepare);
+ return OK;
+}
+
+static int post_config(apr_pool_t *p,
+ apr_pool_t *plog,
+ apr_pool_t *ptemp,
+ server_rec *s)
+{
+ apr_status_t rv;
+
+ /* check if proxy module is available */
+ proxy_available = (ap_find_linked_module("mod_proxy.c") != NULL);
+
+ if (rewrite_lock_needed) {
+ rv = rewritelock_create(s, p);
+ if (rv != APR_SUCCESS) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ apr_pool_cleanup_register(p, (void *)s, rewritelock_remove,
+ apr_pool_cleanup_null);
+ }
+
+ /* if we are not doing the initial config, step through the servers and
+ * open the RewriteMap prg:xxx programs,
+ */
+ if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_CONFIG) {
+ for (; s; s = s->next) {
+ if (run_rewritemap_programs(s, p) != APR_SUCCESS) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+ }
+
+ return OK;
+}
+
+static void init_child(apr_pool_t *p, server_rec *s)
+{
+ apr_status_t rv = 0; /* get a rid of gcc warning (REWRITELOG_DISABLED) */
+
+ if (rewrite_mapr_lock_acquire) {
+ rv = apr_global_mutex_child_init(&rewrite_mapr_lock_acquire,
+ apr_global_mutex_lockfile(rewrite_mapr_lock_acquire), p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00666)
+ "mod_rewrite: could not init rewrite_mapr_lock_acquire"
+ " in child");
+ }
+ }
+
+ /* create the lookup cache */
+ if (!init_cache(p)) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00667)
+ "mod_rewrite: could not init map cache in child");
+ }
+}
+
+
+/*
+ * +-------------------------------------------------------+
+ * | |
+ * | runtime hooks
+ * | |
+ * +-------------------------------------------------------+
+ */
+
+/*
+ * URI-to-filename hook
+ * [deals with RewriteRules in server context]
+ */
+static int hook_uri2file(request_rec *r)
+{
+ rewrite_perdir_conf *dconf;
+ rewrite_server_conf *conf;
+ const char *saved_rulestatus;
+ const char *var;
+ const char *thisserver;
+ char *thisport;
+ const char *thisurl;
+ unsigned int port;
+ int rulestatus;
+ void *skipdata;
+ const char *oargs;
+
+ /*
+ * retrieve the config structures
+ */
+ conf = ap_get_module_config(r->server->module_config, &rewrite_module);
+
+ dconf = (rewrite_perdir_conf *)ap_get_module_config(r->per_dir_config,
+ &rewrite_module);
+
+ /*
+ * only do something under runtime if the engine is really enabled,
+ * else return immediately!
+ */
+ if (!dconf || dconf->state == ENGINE_DISABLED) {
+ return DECLINED;
+ }
+
+ /*
+ * check for the ugly API case of a virtual host section where no
+ * mod_rewrite directives exists. In this situation we became no chance
+ * by the API to setup our default per-server config so we have to
+ * on-the-fly assume we have the default config. But because the default
+ * config has a disabled rewriting engine we are lucky because can
+ * just stop operating now.
+ */
+ if (conf->server != r->server) {
+ return DECLINED;
+ }
+
+ /* END flag was used as a RewriteRule flag on this request */
+ apr_pool_userdata_get(&skipdata, really_last_key, r->pool);
+ if (skipdata != NULL) {
+ rewritelog((r, 8, NULL, "Declining, no further rewriting due to END flag"));
+ return DECLINED;
+ }
+
+ /* Unless the anyuri option is set, ensure that the input to the
+ * first rule really is a URL-path, avoiding security issues with
+ * poorly configured rules. See CVE-2011-3368, CVE-2011-4317. */
+ if ((dconf->options & OPTION_ANYURI) == 0
+ && ((r->unparsed_uri[0] == '*' && r->unparsed_uri[1] == '\0')
+ || !r->uri || r->uri[0] != '/')) {
+ rewritelog((r, 8, NULL, "Declining, request-URI '%s' is not a URL-path. "
+ "Consult the manual entry for the RewriteOptions directive "
+ "for options and caveats about matching other strings.",
+ r->uri));
+ return DECLINED;
+ }
+
+ /*
+ * remember the original query string for later check, since we don't
+ * want to apply URL-escaping when no substitution has changed it.
+ */
+ oargs = r->args;
+
+ /*
+ * add the SCRIPT_URL variable to the env. this is a bit complicated
+ * due to the fact that apache uses subrequests and internal redirects
+ */
+
+ if (r->main == NULL) {
+ var = apr_table_get(r->subprocess_env, REDIRECT_ENVVAR_SCRIPT_URL);
+ if (var == NULL) {
+ apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, r->uri);
+ }
+ else {
+ apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, var);
+ }
+ }
+ else {
+ var = apr_table_get(r->main->subprocess_env, ENVVAR_SCRIPT_URL);
+ apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, var);
+ }
+
+ /*
+ * create the SCRIPT_URI variable for the env
+ */
+
+ /* add the canonical URI of this URL */
+ thisserver = ap_get_server_name_for_url(r);
+ port = ap_get_server_port(r);
+ if (ap_is_default_port(port, r)) {
+ thisport = "";
+ }
+ else {
+ thisport = apr_psprintf(r->pool, ":%u", port);
+ }
+ thisurl = apr_table_get(r->subprocess_env, ENVVAR_SCRIPT_URL);
+
+ /* set the variable */
+ var = apr_pstrcat(r->pool, ap_http_scheme(r), "://", thisserver, thisport,
+ thisurl, NULL);
+ apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URI, var);
+
+ if (!(saved_rulestatus = apr_table_get(r->notes,"mod_rewrite_rewritten"))) {
+ /* if filename was not initially set,
+ * we start with the requested URI
+ */
+ if (r->filename == NULL) {
+ r->filename = apr_pstrdup(r->pool, r->uri);
+ rewritelog((r, 2, NULL, "init rewrite engine with requested uri %s",
+ r->filename));
+ }
+ else {
+ rewritelog((r, 2, NULL, "init rewrite engine with passed filename "
+ "%s. Original uri = %s", r->filename, r->uri));
+ }
+
+ /*
+ * now apply the rules ...
+ */
+ rulestatus = apply_rewrite_list(r, conf->rewriterules, NULL);
+ apr_table_setn(r->notes, "mod_rewrite_rewritten",
+ apr_psprintf(r->pool,"%d",rulestatus));
+ }
+ else {
+ rewritelog((r, 2, NULL, "uri already rewritten. Status %s, Uri %s, "
+ "r->filename %s", saved_rulestatus, r->uri, r->filename));
+
+ rulestatus = atoi(saved_rulestatus);
+ }
+
+ if (rulestatus) {
+ unsigned skip;
+ apr_size_t flen;
+
+ if (r->args && *(ap_scan_vchar_obstext(r->args))) {
+ /*
+ * We have a raw control character or a ' ' in r->args.
+ * Correct encoding was missed.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10410)
+ "Rewritten query string contains control "
+ "characters or spaces");
+ return HTTP_FORBIDDEN;
+ }
+
+ if (ACTION_STATUS == rulestatus) {
+ int n = r->status;
+
+ r->status = HTTP_OK;
+ return n;
+ }
+
+ flen = r->filename ? strlen(r->filename) : 0;
+ if (flen > 6 && strncmp(r->filename, "proxy:", 6) == 0) {
+ /* it should be go on as an internal proxy request */
+
+ /* check if the proxy module is enabled, so
+ * we can actually use it!
+ */
+ if (!proxy_available) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00669)
+ "attempt to make remote request from mod_rewrite "
+ "without proxy enabled: %s", r->filename);
+ return HTTP_FORBIDDEN;
+ }
+
+ if (rulestatus == ACTION_NOESCAPE) {
+ apr_table_setn(r->notes, "proxy-nocanon", "1");
+ }
+
+ /* make sure the QUERY_STRING and
+ * PATH_INFO parts get incorporated
+ */
+ if (r->path_info != NULL) {
+ r->filename = apr_pstrcat(r->pool, r->filename,
+ r->path_info, NULL);
+ }
+ if ((r->args != NULL)
+ && ((r->proxyreq == PROXYREQ_PROXY)
+ || (rulestatus == ACTION_NOESCAPE))) {
+ /* see proxy_http:proxy_http_canon() */
+ r->filename = apr_pstrcat(r->pool, r->filename,
+ "?", r->args, NULL);
+ }
+
+ /* now make sure the request gets handled by the proxy handler */
+ if (PROXYREQ_NONE == r->proxyreq) {
+ r->proxyreq = PROXYREQ_REVERSE;
+ }
+ r->handler = "proxy-server";
+
+ rewritelog((r, 1, NULL, "go-ahead with proxy request %s [OK]",
+ r->filename));
+ return OK;
+ }
+ else if ((skip = is_absolute_uri(r->filename, NULL)) > 0) {
+ int n;
+
+ /* it was finally rewritten to a remote URL */
+
+ if (rulestatus != ACTION_NOESCAPE) {
+ rewritelog((r, 1, NULL, "escaping %s for redirect",
+ r->filename));
+ r->filename = escape_absolute_uri(r->pool, r->filename, skip);
+ }
+
+ /* append the QUERY_STRING part */
+ if (r->args) {
+ char *escaped_args = NULL;
+ int noescape = (rulestatus == ACTION_NOESCAPE ||
+ (oargs && !strcmp(r->args, oargs)));
+
+ r->filename = apr_pstrcat(r->pool, r->filename, "?",
+ noescape
+ ? r->args
+ : (escaped_args =
+ ap_escape_uri(r->pool, r->args)),
+ NULL);
+
+ rewritelog((r, 1, NULL, "%s %s to query string for redirect %s",
+ noescape ? "copying" : "escaping",
+ r->args ,
+ noescape ? "" : escaped_args));
+ }
+
+ /* determine HTTP redirect response code */
+ if (ap_is_HTTP_REDIRECT(r->status)) {
+ n = r->status;
+ r->status = HTTP_OK; /* make Apache kernel happy */
+ }
+ else {
+ n = HTTP_MOVED_TEMPORARILY;
+ }
+
+ /* now do the redirection */
+ apr_table_setn(r->headers_out, "Location", r->filename);
+ rewritelog((r, 1, NULL, "redirect to %s [REDIRECT/%d]", r->filename,
+ n));
+
+ return n;
+ }
+ else if (flen > 12 && strncmp(r->filename, "passthrough:", 12) == 0) {
+ /*
+ * Hack because of underpowered API: passing the current
+ * rewritten filename through to other URL-to-filename handlers
+ * just as it were the requested URL. This is to enable
+ * post-processing by mod_alias, etc. which always act on
+ * r->uri! The difference here is: We do not try to
+ * add the document root
+ */
+ r->uri = apr_pstrdup(r->pool, r->filename+12);
+ return DECLINED;
+ }
+ else {
+ /* it was finally rewritten to a local path */
+ const char *uri_reduced = NULL;
+
+ /* expand "/~user" prefix */
+#if APR_HAS_USER
+ r->filename = expand_tildepaths(r, r->filename);
+#endif
+ rewritelog((r, 2, NULL, "local path result: %s", r->filename));
+
+ /* the filename must be either an absolute local path or an
+ * absolute local URL.
+ */
+ if ( *r->filename != '/'
+ && !ap_os_is_path_absolute(r->pool, r->filename)) {
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* if there is no valid prefix, we call
+ * the translator from the core and
+ * prefix the filename with document_root
+ *
+ * NOTICE:
+ * We cannot leave out the prefix_stat because
+ * - when we always prefix with document_root
+ * then no absolute path can be created, e.g. via
+ * emulating a ScriptAlias directive, etc.
+ * - when we always NOT prefix with document_root
+ * then the files under document_root have to
+ * be references directly and document_root
+ * gets never used and will be a dummy parameter -
+ * this is also bad
+ *
+ * BUT:
+ * Under real Unix systems this is no problem,
+ * because we only do stat() on the first directory
+ * and this gets cached by the kernel for along time!
+ */
+
+ if(!(conf->options & OPTION_LEGACY_PREFIX_DOCROOT)) {
+ uri_reduced = apr_table_get(r->notes, "mod_rewrite_uri_reduced");
+ }
+
+ if (!prefix_stat(r->filename, r->pool) || uri_reduced != NULL) {
+ int res;
+ char *tmp = r->uri;
+
+ r->uri = r->filename;
+ res = ap_core_translate(r);
+ r->uri = tmp;
+
+ if (res != OK) {
+ rewritelog((r, 1, NULL, "prefixing with document_root of %s"
+ " FAILED", r->filename));
+
+ return res;
+ }
+
+ rewritelog((r, 2, NULL, "prefixed with document_root to %s",
+ r->filename));
+ }
+
+ rewritelog((r, 1, NULL, "go-ahead with %s [OK]", r->filename));
+ return OK;
+ }
+ }
+ else {
+ rewritelog((r, 1, NULL, "pass through %s", r->filename));
+ return DECLINED;
+ }
+}
+
+/*
+ * Fixup hook
+ * [RewriteRules in directory context]
+ */
+static int hook_fixup(request_rec *r)
+{
+ rewrite_perdir_conf *dconf;
+ char *cp;
+ char *cp2;
+ const char *ccp;
+ apr_size_t l;
+ int rulestatus;
+ int n;
+ char *ofilename, *oargs;
+ int is_proxyreq;
+ void *skipdata;
+
+ dconf = (rewrite_perdir_conf *)ap_get_module_config(r->per_dir_config,
+ &rewrite_module);
+
+ /* if there is no per-dir config we return immediately */
+ if (dconf == NULL) {
+ return DECLINED;
+ }
+
+ /*
+ * only do something under runtime if the engine is really enabled,
+ * for this directory, else return immediately!
+ */
+ if (dconf->state == ENGINE_DISABLED) {
+ return DECLINED;
+ }
+
+ /* if there are no real (i.e. no RewriteRule directives!)
+ per-dir config of us, we return also immediately */
+ if (dconf->directory == NULL) {
+ return DECLINED;
+ }
+
+ /*
+ * Proxy request?
+ */
+ is_proxyreq = ( r->proxyreq && r->filename
+ && !strncmp(r->filename, "proxy:", 6));
+
+ /*
+ * .htaccess file is called before really entering the directory, i.e.:
+ * URL: http://localhost/foo and .htaccess is located in foo directory
+ * Ignore such attempts, allowing mod_dir to direct the client to the
+ * canonical URL. This can be controlled with the AllowNoSlash option.
+ */
+ if (!is_proxyreq && !(dconf->options & OPTION_NOSLASH)) {
+ l = strlen(dconf->directory) - 1;
+ if (r->filename && strlen(r->filename) == l &&
+ (dconf->directory)[l] == '/' &&
+ !strncmp(r->filename, dconf->directory, l)) {
+ return DECLINED;
+ }
+ }
+
+ /* END flag was used as a RewriteRule flag on this request */
+ apr_pool_userdata_get(&skipdata, really_last_key, r->pool);
+ if (skipdata != NULL) {
+ rewritelog((r, 8, dconf->directory, "Declining, no further rewriting due to END flag"));
+ return DECLINED;
+ }
+
+ /*
+ * Do the Options check after engine check, so
+ * the user is able to explicitly turn RewriteEngine Off.
+ */
+ if (!(ap_allow_options(r) & (OPT_SYM_LINKS | OPT_SYM_OWNER))) {
+ /* FollowSymLinks is mandatory! */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00670)
+ "Options FollowSymLinks and SymLinksIfOwnerMatch are both off, "
+ "so the RewriteRule directive is also forbidden "
+ "due to its similar ability to circumvent directory restrictions : "
+ "%s", r->filename);
+ return HTTP_FORBIDDEN;
+ }
+
+ /*
+ * remember the current filename before rewriting for later check
+ * to prevent deadlooping because of internal redirects
+ * on final URL/filename which can be equal to the initial one.
+ * also, we'll restore original r->filename if we decline this
+ * request
+ */
+ ofilename = r->filename;
+ oargs = r->args;
+
+ if (r->filename == NULL) {
+ r->filename = apr_pstrdup(r->pool, r->uri);
+ rewritelog((r, 2, dconf->directory, "init rewrite engine with"
+ " requested uri %s", r->filename));
+ }
+
+ /*
+ * now apply the rules ...
+ */
+ rulestatus = apply_rewrite_list(r, dconf->rewriterules, dconf->directory);
+ if (rulestatus) {
+ unsigned skip;
+
+ if (r->args && *(ap_scan_vchar_obstext(r->args))) {
+ /*
+ * We have a raw control character or a ' ' in r->args.
+ * Correct encoding was missed.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10411)
+ "Rewritten query string contains control "
+ "characters or spaces");
+ return HTTP_FORBIDDEN;
+ }
+
+ if (ACTION_STATUS == rulestatus) {
+ int n = r->status;
+
+ r->status = HTTP_OK;
+ return n;
+ }
+
+ l = strlen(r->filename);
+ if (l > 6 && strncmp(r->filename, "proxy:", 6) == 0) {
+ /* it should go on as an internal proxy request */
+
+ /* make sure the QUERY_STRING and
+ * PATH_INFO parts get incorporated
+ * (r->path_info was already appended by the
+ * rewriting engine because of the per-dir context!)
+ */
+ if (r->args != NULL) {
+ /* see proxy_http:proxy_http_canon() */
+ r->filename = apr_pstrcat(r->pool, r->filename,
+ "?", r->args, NULL);
+ }
+
+ /* now make sure the request gets handled by the proxy handler */
+ if (PROXYREQ_NONE == r->proxyreq) {
+ r->proxyreq = PROXYREQ_REVERSE;
+ }
+ r->handler = "proxy-server";
+
+ rewritelog((r, 1, dconf->directory, "go-ahead with proxy request "
+ "%s [OK]", r->filename));
+ return OK;
+ }
+ else if ((skip = is_absolute_uri(r->filename, NULL)) > 0) {
+ /* it was finally rewritten to a remote URL */
+
+ /* because we are in a per-dir context
+ * first try to replace the directory with its base-URL
+ * if there is a base-URL available
+ */
+ if (dconf->baseurl != NULL) {
+ /* skip 'scheme://' */
+ cp = r->filename + skip;
+
+ if ((cp = ap_strchr(cp, '/')) != NULL && *(++cp)) {
+ rewritelog((r, 2, dconf->directory,
+ "trying to replace prefix %s with %s",
+ dconf->directory, dconf->baseurl));
+
+ /* I think, that hack needs an explanation:
+ * well, here is it:
+ * mod_rewrite was written for unix systems, were
+ * absolute file-system paths start with a slash.
+ * URL-paths _also_ start with slashes, so they
+ * can be easily compared with system paths.
+ *
+ * the following assumes, that the actual url-path
+ * may be prefixed by the current directory path and
+ * tries to replace the system path with the RewriteBase
+ * URL.
+ * That assumption is true if we use a RewriteRule like
+ *
+ * RewriteRule ^foo bar [R]
+ *
+ * (see apply_rewrite_rule function)
+ * However on systems that don't have a / as system
+ * root this will never match, so we skip the / after the
+ * hostname and compare/substitute only the stuff after it.
+ *
+ * (note that cp was already increased to the right value)
+ */
+ cp2 = subst_prefix_path(r, cp, (*dconf->directory == '/')
+ ? dconf->directory + 1
+ : dconf->directory,
+ dconf->baseurl + 1);
+ if (strcmp(cp2, cp) != 0) {
+ *cp = '\0';
+ r->filename = apr_pstrcat(r->pool, r->filename,
+ cp2, NULL);
+ }
+ }
+ }
+
+ /* now prepare the redirect... */
+ if (rulestatus != ACTION_NOESCAPE) {
+ rewritelog((r, 1, dconf->directory, "escaping %s for redirect",
+ r->filename));
+ r->filename = escape_absolute_uri(r->pool, r->filename, skip);
+ }
+
+ /* append the QUERY_STRING part */
+ if (r->args) {
+ char *escaped_args = NULL;
+ int noescape = (rulestatus == ACTION_NOESCAPE ||
+ (oargs && !strcmp(r->args, oargs)));
+
+ r->filename = apr_pstrcat(r->pool, r->filename, "?",
+ noescape
+ ? r->args
+ : (escaped_args = ap_escape_uri(r->pool, r->args)),
+ NULL);
+
+ rewritelog((r, 1, dconf->directory, "%s %s to query string for redirect %s",
+ noescape ? "copying" : "escaping",
+ r->args ,
+ noescape ? "" : escaped_args));
+ }
+
+ /* determine HTTP redirect response code */
+ if (ap_is_HTTP_REDIRECT(r->status)) {
+ n = r->status;
+ r->status = HTTP_OK; /* make Apache kernel happy */
+ }
+ else {
+ n = HTTP_MOVED_TEMPORARILY;
+ }
+
+ /* now do the redirection */
+ apr_table_setn(r->headers_out, "Location", r->filename);
+ rewritelog((r, 1, dconf->directory, "redirect to %s [REDIRECT/%d]",
+ r->filename, n));
+ return n;
+ }
+ else {
+ const char *tmpfilename = NULL;
+ /* it was finally rewritten to a local path */
+
+ /* if someone used the PASSTHROUGH flag in per-dir
+ * context we just ignore it. It is only useful
+ * in per-server context
+ */
+ if (l > 12 && strncmp(r->filename, "passthrough:", 12) == 0) {
+ r->filename = apr_pstrdup(r->pool, r->filename+12);
+ }
+
+ /* the filename must be either an absolute local path or an
+ * absolute local URL.
+ */
+ if ( *r->filename != '/'
+ && !ap_os_is_path_absolute(r->pool, r->filename)) {
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* Check for deadlooping:
+ * At this point we KNOW that at least one rewriting
+ * rule was applied, but when the resulting URL is
+ * the same as the initial URL, we are not allowed to
+ * use the following internal redirection stuff because
+ * this would lead to a deadloop.
+ */
+ if (ofilename != NULL && strcmp(r->filename, ofilename) == 0) {
+ rewritelog((r, 1, dconf->directory, "initial URL equal rewritten"
+ " URL: %s [IGNORING REWRITE]", r->filename));
+ return OK;
+ }
+
+ tmpfilename = r->filename;
+
+ /* if there is a valid base-URL then substitute
+ * the per-dir prefix with this base-URL if the
+ * current filename still is inside this per-dir
+ * context. If not then treat the result as a
+ * plain URL
+ */
+ if (dconf->baseurl != NULL) {
+ rewritelog((r, 2, dconf->directory, "trying to replace prefix "
+ "%s with %s", dconf->directory, dconf->baseurl));
+
+ r->filename = subst_prefix_path(r, r->filename,
+ dconf->directory,
+ dconf->baseurl);
+ }
+ else {
+ /* if no explicit base-URL exists we assume
+ * that the directory prefix is also a valid URL
+ * for this webserver and only try to remove the
+ * document_root if it is prefix
+ */
+ if ((ccp = ap_document_root(r)) != NULL) {
+ /* strip trailing slash */
+ l = strlen(ccp);
+ if (ccp[l-1] == '/') {
+ --l;
+ }
+ if (!strncmp(r->filename, ccp, l) &&
+ r->filename[l] == '/') {
+ rewritelog((r, 2,dconf->directory, "strip document_root"
+ " prefix: %s -> %s", r->filename,
+ r->filename+l));
+
+ r->filename = apr_pstrdup(r->pool, r->filename+l);
+ }
+ }
+ }
+
+ /* No base URL, or r->filename wasn't still under dconf->directory
+ * or, r->filename wasn't still under the document root.
+ * If there's a context document root AND a context prefix, and
+ * the context document root is a prefix of r->filename, replace.
+ * This allows a relative substitution on a path found by mod_userdir
+ * or mod_alias without baking in a RewriteBase.
+ */
+ if (tmpfilename == r->filename &&
+ !(dconf->options & OPTION_IGNORE_CONTEXT_INFO)) {
+ if ((ccp = ap_context_document_root(r)) != NULL) {
+ const char *prefix = ap_context_prefix(r);
+ if (prefix != NULL) {
+ rewritelog((r, 2, dconf->directory, "trying to replace "
+ "context docroot %s with context prefix %s",
+ ccp, prefix));
+ r->filename = subst_prefix_path(r, r->filename,
+ ccp, prefix);
+ }
+ }
+ }
+
+ apr_table_setn(r->notes, "redirect-keeps-vary", "");
+
+ /* now initiate the internal redirect */
+ rewritelog((r, 1, dconf->directory, "internal redirect with %s "
+ "[INTERNAL REDIRECT]", r->filename));
+ r->filename = apr_pstrcat(r->pool, "redirect:", r->filename, NULL);
+ r->handler = REWRITE_REDIRECT_HANDLER_NAME;
+ return OK;
+ }
+ }
+ else {
+ rewritelog((r, 1, dconf->directory, "pass through %s", r->filename));
+ r->filename = ofilename;
+ return DECLINED;
+ }
+}
+
+/*
+ * MIME-type hook
+ * [T=...,H=...] execution
+ */
+static int hook_mimetype(request_rec *r)
+{
+ const char *t;
+
+ /* type */
+ t = apr_table_get(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR);
+ if (t && *t) {
+ rewritelog((r, 1, NULL, "force filename %s to have MIME-type '%s'",
+ r->filename, t));
+
+ ap_set_content_type(r, t);
+ }
+
+ /* handler */
+ t = apr_table_get(r->notes, REWRITE_FORCED_HANDLER_NOTEVAR);
+ if (t && *t) {
+ rewritelog((r, 1, NULL, "force filename %s to have the "
+ "Content-handler '%s'", r->filename, t));
+
+ r->handler = t;
+ }
+
+ return OK;
+}
+
+
+/*
+ * "content" handler for internal redirects
+ */
+static int handler_redirect(request_rec *r)
+{
+ if (strcmp(r->handler, REWRITE_REDIRECT_HANDLER_NAME)) {
+ return DECLINED;
+ }
+
+ /* just make sure that we are really meant! */
+ if (strncmp(r->filename, "redirect:", 9) != 0) {
+ return DECLINED;
+ }
+
+ /* now do the internal redirect */
+ ap_internal_redirect(apr_pstrcat(r->pool, r->filename+9,
+ r->args ? "?" : NULL, r->args, NULL), r);
+
+ /* and return gracefully */
+ return OK;
+}
+
+
+/*
+ * +-------------------------------------------------------+
+ * | |
+ * | Module paraphernalia
+ * | |
+ * +-------------------------------------------------------+
+ */
+
+static const command_rec command_table[] = {
+ AP_INIT_FLAG( "RewriteEngine", cmd_rewriteengine, NULL, OR_FILEINFO,
+ "On or Off to enable or disable (default) the whole "
+ "rewriting engine"),
+ AP_INIT_ITERATE( "RewriteOptions", cmd_rewriteoptions, NULL, OR_FILEINFO,
+ "List of option strings to set"),
+ AP_INIT_TAKE1( "RewriteBase", cmd_rewritebase, NULL, OR_FILEINFO,
+ "the base URL of the per-directory context"),
+ AP_INIT_RAW_ARGS("RewriteCond", cmd_rewritecond, NULL, OR_FILEINFO,
+ "an input string and a to be applied regexp-pattern"),
+ AP_INIT_RAW_ARGS("RewriteRule", cmd_rewriterule, NULL, OR_FILEINFO,
+ "an URL-applied regexp-pattern and a substitution URL"),
+ AP_INIT_TAKE23( "RewriteMap", cmd_rewritemap, NULL, RSRC_CONF,
+ "a mapname and a filename and options"),
+ { NULL }
+};
+
+static void ap_register_rewrite_mapfunc(char *name, rewrite_mapfunc_t *func)
+{
+ apr_hash_set(mapfunc_hash, name, strlen(name), (const void *)func);
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ /* fixup after mod_proxy, so that the proxied url will not
+ * escaped accidentally by mod_proxy's fixup.
+ */
+ static const char * const aszPre[]={ "mod_proxy.c", NULL };
+
+ /* make the hashtable before registering the function, so that
+ * other modules are prevented from accessing uninitialized memory.
+ */
+ mapfunc_hash = apr_hash_make(p);
+ APR_REGISTER_OPTIONAL_FN(ap_register_rewrite_mapfunc);
+
+ ap_hook_handler(handler_redirect, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_pre_config(pre_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_post_config(post_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_child_init(init_child, NULL, NULL, APR_HOOK_MIDDLE);
+
+ ap_hook_fixups(hook_fixup, aszPre, NULL, APR_HOOK_FIRST);
+ ap_hook_fixups(hook_mimetype, NULL, NULL, APR_HOOK_LAST);
+ ap_hook_translate_name(hook_uri2file, NULL, NULL, APR_HOOK_FIRST);
+}
+
+ /* the main config structure */
+AP_DECLARE_MODULE(rewrite) = {
+ STANDARD20_MODULE_STUFF,
+ config_perdir_create, /* create per-dir config structures */
+ config_perdir_merge, /* merge per-dir config structures */
+ config_server_create, /* create per-server config structures */
+ config_server_merge, /* merge per-server config structures */
+ command_table, /* table of config file commands */
+ register_hooks /* register hooks */
+};
+
+/*EOF*/
diff --git a/modules/mappers/mod_rewrite.dep b/modules/mappers/mod_rewrite.dep
new file mode 100644
index 0000000..b6956b1
--- /dev/null
+++ b/modules/mappers/mod_rewrite.dep
@@ -0,0 +1,65 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_rewrite.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_rewrite.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_dbd.h"\
+ "..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_signal.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ "..\database\mod_dbd.h"\
+ "..\ssl\mod_ssl.h"\
+ ".\mod_rewrite.h"\
+
diff --git a/modules/mappers/mod_rewrite.dsp b/modules/mappers/mod_rewrite.dsp
new file mode 100644
index 0000000..adde8ee
--- /dev/null
+++ b/modules/mappers/mod_rewrite.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_rewrite" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_rewrite - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_rewrite.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_rewrite.mak" CFG="mod_rewrite - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_rewrite - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_rewrite - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_rewrite - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../database" /I "../ssl" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_rewrite_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_rewrite.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_rewrite.so" /d LONG_NAME="rewrite_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_rewrite.so" /base:@..\..\os\win32\BaseAddr.ref,mod_rewrite.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_rewrite.so" /base:@..\..\os\win32\BaseAddr.ref,mod_rewrite.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_rewrite.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_rewrite - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../database" /I "../ssl" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_rewrite_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_rewrite.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_rewrite.so" /d LONG_NAME="rewrite_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_rewrite.so" /base:@..\..\os\win32\BaseAddr.ref,mod_rewrite.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_rewrite.so" /base:@..\..\os\win32\BaseAddr.ref,mod_rewrite.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_rewrite.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_rewrite - Win32 Release"
+# Name "mod_rewrite - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_rewrite.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/mappers/mod_rewrite.exp b/modules/mappers/mod_rewrite.exp
new file mode 100644
index 0000000..8f2165b
--- /dev/null
+++ b/modules/mappers/mod_rewrite.exp
@@ -0,0 +1 @@
+rewrite_module
diff --git a/modules/mappers/mod_rewrite.h b/modules/mappers/mod_rewrite.h
new file mode 100644
index 0000000..8fa158e
--- /dev/null
+++ b/modules/mappers/mod_rewrite.h
@@ -0,0 +1,42 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file mod_rewrite.h
+ * @brief Rewrite Extension module for Apache
+ *
+ * @defgroup MOD_REWRITE mod_rewrite
+ * @ingroup APACHE_MODS
+ * @{
+ */
+
+#ifndef MOD_REWRITE_H
+#define MOD_REWRITE_H 1
+
+#include "apr_optional.h"
+#include "httpd.h"
+
+#define REWRITE_REDIRECT_HANDLER_NAME "redirect-handler"
+
+/* rewrite map function prototype */
+typedef char *(rewrite_mapfunc_t)(request_rec *r, char *key);
+
+/* optional function declaration */
+APR_DECLARE_OPTIONAL_FN(void, ap_register_rewrite_mapfunc,
+ (char *name, rewrite_mapfunc_t *func));
+
+#endif /* MOD_REWRITE_H */
+/** @} */
diff --git a/modules/mappers/mod_rewrite.mak b/modules/mappers/mod_rewrite.mak
new file mode 100644
index 0000000..3b08cab
--- /dev/null
+++ b/modules/mappers/mod_rewrite.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_rewrite.dsp
+!IF "$(CFG)" == ""
+CFG=mod_rewrite - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_rewrite - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_rewrite - Win32 Release" && "$(CFG)" != "mod_rewrite - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_rewrite.mak" CFG="mod_rewrite - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_rewrite - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_rewrite - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_rewrite - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_rewrite.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_rewrite.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_rewrite.obj"
+ -@erase "$(INTDIR)\mod_rewrite.res"
+ -@erase "$(INTDIR)\mod_rewrite_src.idb"
+ -@erase "$(INTDIR)\mod_rewrite_src.pdb"
+ -@erase "$(OUTDIR)\mod_rewrite.exp"
+ -@erase "$(OUTDIR)\mod_rewrite.lib"
+ -@erase "$(OUTDIR)\mod_rewrite.pdb"
+ -@erase "$(OUTDIR)\mod_rewrite.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../database" /I "../ssl" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_rewrite_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_rewrite.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_rewrite.so" /d LONG_NAME="rewrite_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_rewrite.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_rewrite.pdb" /debug /out:"$(OUTDIR)\mod_rewrite.so" /implib:"$(OUTDIR)\mod_rewrite.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_rewrite.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_rewrite.obj" \
+ "$(INTDIR)\mod_rewrite.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_rewrite.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_rewrite.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_rewrite.so"
+ if exist .\Release\mod_rewrite.so.manifest mt.exe -manifest .\Release\mod_rewrite.so.manifest -outputresource:.\Release\mod_rewrite.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_rewrite - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_rewrite.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_rewrite.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_rewrite.obj"
+ -@erase "$(INTDIR)\mod_rewrite.res"
+ -@erase "$(INTDIR)\mod_rewrite_src.idb"
+ -@erase "$(INTDIR)\mod_rewrite_src.pdb"
+ -@erase "$(OUTDIR)\mod_rewrite.exp"
+ -@erase "$(OUTDIR)\mod_rewrite.lib"
+ -@erase "$(OUTDIR)\mod_rewrite.pdb"
+ -@erase "$(OUTDIR)\mod_rewrite.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../database" /I "../ssl" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_rewrite_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_rewrite.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_rewrite.so" /d LONG_NAME="rewrite_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_rewrite.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_rewrite.pdb" /debug /out:"$(OUTDIR)\mod_rewrite.so" /implib:"$(OUTDIR)\mod_rewrite.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_rewrite.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_rewrite.obj" \
+ "$(INTDIR)\mod_rewrite.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_rewrite.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_rewrite.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_rewrite.so"
+ if exist .\Debug\mod_rewrite.so.manifest mt.exe -manifest .\Debug\mod_rewrite.so.manifest -outputresource:.\Debug\mod_rewrite.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_rewrite.dep")
+!INCLUDE "mod_rewrite.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_rewrite.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_rewrite - Win32 Release" || "$(CFG)" == "mod_rewrite - Win32 Debug"
+
+!IF "$(CFG)" == "mod_rewrite - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\mappers"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ELSEIF "$(CFG)" == "mod_rewrite - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\mappers"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_rewrite - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\mappers"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ELSEIF "$(CFG)" == "mod_rewrite - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\mappers"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_rewrite - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\mappers"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\mappers"
+
+!ELSEIF "$(CFG)" == "mod_rewrite - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\mappers"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\mappers"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_rewrite - Win32 Release"
+
+
+"$(INTDIR)\mod_rewrite.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_rewrite.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_rewrite.so" /d LONG_NAME="rewrite_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_rewrite - Win32 Debug"
+
+
+"$(INTDIR)\mod_rewrite.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_rewrite.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_rewrite.so" /d LONG_NAME="rewrite_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_rewrite.c
+
+"$(INTDIR)\mod_rewrite.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/mappers/mod_speling.c b/modules/mappers/mod_speling.c
new file mode 100644
index 0000000..2ed65eb
--- /dev/null
+++ b/modules/mappers/mod_speling.c
@@ -0,0 +1,528 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr.h"
+#include "apr_file_io.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "httpd.h"
+#include "http_core.h"
+#include "http_config.h"
+#include "http_request.h"
+#include "http_log.h"
+
+/* mod_speling.c - by Alexei Kosut <akosut@organic.com> June, 1996
+ *
+ * This module is transparent, and simple. It attempts to correct
+ * misspellings of URLs that users might have entered, namely by checking
+ * capitalizations. If it finds a match, it sends a redirect.
+ *
+ * Sep-1999 Hugo Haas <hugo@w3.org>
+ * o Added a CheckCaseOnly option to check only miscapitalized words.
+ *
+ * 08-Aug-1997 <Martin.Kraemer@Mch.SNI.De>
+ * o Upgraded module interface to apache_1.3a2-dev API (more NULL's in
+ * speling_module).
+ * o Integrated tcsh's "spelling correction" routine which allows one
+ * misspelling (character insertion/omission/typo/transposition).
+ * Rewrote it to ignore case as well. This ought to catch the majority
+ * of misspelled requests.
+ * o Commented out the second pass where files' suffixes are stripped.
+ * Given the better hit rate of the first pass, this rather ugly
+ * (request index.html, receive index.db ?!?!) solution can be
+ * omitted.
+ * o wrote a "kind of" html page for mod_speling
+ *
+ * Activate it with "CheckSpelling On"
+ */
+
+module AP_MODULE_DECLARE_DATA speling_module;
+
+typedef struct {
+ int enabled;
+ int check_case_only;
+ int check_basename_match;
+} spconfig;
+
+/*
+ * Create a configuration specific to this module for a server or directory
+ * location, and fill it with the default settings.
+ *
+ * The API says that in the absence of a merge function, the record for the
+ * closest ancestor is used exclusively. That's what we want, so we don't
+ * bother to have such a function.
+ */
+
+static void *mkconfig(apr_pool_t *p)
+{
+ spconfig *cfg = apr_pcalloc(p, sizeof(spconfig));
+
+ cfg->enabled = 0;
+ cfg->check_case_only = 0;
+ cfg->check_basename_match = 1;
+ return cfg;
+}
+
+/*
+ * Respond to a callback to create configuration record for a server or
+ * vhost environment.
+ */
+static void *create_mconfig_for_server(apr_pool_t *p, server_rec *s)
+{
+ return mkconfig(p);
+}
+
+/*
+ * Respond to a callback to create a config record for a specific directory.
+ */
+static void *create_mconfig_for_directory(apr_pool_t *p, char *dir)
+{
+ return mkconfig(p);
+}
+
+/*
+ * Define the directives specific to this module. This structure is referenced
+ * later by the 'module' structure.
+ */
+static const command_rec speling_cmds[] =
+{
+ AP_INIT_FLAG("CheckSpelling", ap_set_flag_slot,
+ (void*)APR_OFFSETOF(spconfig, enabled), OR_OPTIONS,
+ "whether or not to fix miscapitalized/misspelled requests"),
+ AP_INIT_FLAG("CheckCaseOnly", ap_set_flag_slot,
+ (void*)APR_OFFSETOF(spconfig, check_case_only), OR_OPTIONS,
+ "whether or not to fix only miscapitalized requests"),
+ AP_INIT_FLAG("CheckBasenameMatch", ap_set_flag_slot,
+ (void*)APR_OFFSETOF(spconfig, check_basename_match), OR_OPTIONS,
+ "whether or not to fix files with the same base name"),
+ { NULL }
+};
+
+typedef enum {
+ SP_IDENTICAL = 0,
+ SP_MISCAPITALIZED = 1,
+ SP_TRANSPOSITION = 2,
+ SP_MISSINGCHAR = 3,
+ SP_EXTRACHAR = 4,
+ SP_SIMPLETYPO = 5,
+ SP_VERYDIFFERENT = 6
+} sp_reason;
+
+static const char *sp_reason_str[] =
+{
+ "identical",
+ "miscapitalized",
+ "transposed characters",
+ "character missing",
+ "extra character",
+ "mistyped character",
+ "common basename",
+};
+
+typedef struct {
+ const char *name;
+ sp_reason quality;
+} misspelled_file;
+
+/*
+ * spdist() is taken from Kernighan & Pike,
+ * _The_UNIX_Programming_Environment_
+ * and adapted somewhat to correspond better to psychological reality.
+ * (Note the changes to the return values)
+ *
+ * According to Pollock and Zamora, CACM April 1984 (V. 27, No. 4),
+ * page 363, the correct order for this is:
+ * OMISSION = TRANSPOSITION > INSERTION > SUBSTITUTION
+ * thus, it was exactly backwards in the old version. -- PWP
+ *
+ * This routine was taken out of tcsh's spelling correction code
+ * (tcsh-6.07.04) and re-converted to apache data types ("char" type
+ * instead of tcsh's NLS'ed "Char"). Plus it now ignores the case
+ * during comparisons, so is a "approximate strcasecmp()".
+ * NOTE that is still allows only _one_ real "typo",
+ * it does NOT try to correct multiple errors.
+ */
+
+static sp_reason spdist(const char *s, const char *t)
+{
+ for (; apr_tolower(*s) == apr_tolower(*t); t++, s++) {
+ if (*t == '\0') {
+ return SP_MISCAPITALIZED; /* exact match (sans case) */
+ }
+ }
+ if (*s) {
+ if (*t) {
+ if (s[1] && t[1] && apr_tolower(*s) == apr_tolower(t[1])
+ && apr_tolower(*t) == apr_tolower(s[1])
+ && strcasecmp(s + 2, t + 2) == 0) {
+ return SP_TRANSPOSITION; /* transposition */
+ }
+ if (strcasecmp(s + 1, t + 1) == 0) {
+ return SP_SIMPLETYPO; /* 1 char mismatch */
+ }
+ }
+ if (strcasecmp(s + 1, t) == 0) {
+ return SP_EXTRACHAR; /* extra character */
+ }
+ }
+ if (*t && strcasecmp(s, t + 1) == 0) {
+ return SP_MISSINGCHAR; /* missing character */
+ }
+ return SP_VERYDIFFERENT; /* distance too large to fix. */
+}
+
+static int sort_by_quality(const void *left, const void *rite)
+{
+ return (int) (((misspelled_file *) left)->quality)
+ - (int) (((misspelled_file *) rite)->quality);
+}
+
+static int check_speling(request_rec *r)
+{
+ spconfig *cfg;
+ char *good, *bad, *postgood, *url;
+ apr_finfo_t dirent;
+ int filoc, dotloc, urlen, pglen;
+ apr_array_header_t *candidates = NULL;
+ apr_dir_t *dir;
+
+ cfg = ap_get_module_config(r->per_dir_config, &speling_module);
+ if (!cfg->enabled) {
+ return DECLINED;
+ }
+
+ /* We only want to worry about GETs */
+ if (r->method_number != M_GET) {
+ return DECLINED;
+ }
+
+ /* We've already got a file of some kind or another */
+ if (r->finfo.filetype != APR_NOFILE) {
+ return DECLINED;
+ }
+
+ /* Not a file request */
+ if (r->proxyreq || !r->filename) {
+ return DECLINED;
+ }
+
+ /* This is a sub request - don't mess with it */
+ if (r->main) {
+ return DECLINED;
+ }
+
+ /*
+ * The request should end up looking like this:
+ * r->uri: /correct-url/mispelling/more
+ * r->filename: /correct-file/mispelling r->path_info: /more
+ *
+ * So we do this in steps. First break r->filename into two pieces
+ */
+
+ filoc = ap_rind(r->filename, '/');
+ /*
+ * Don't do anything if the request doesn't contain a slash, or
+ * requests "/"
+ */
+ if (filoc == -1 || strcmp(r->uri, "/") == 0) {
+ return DECLINED;
+ }
+
+ /* good = /correct-file */
+ good = apr_pstrndup(r->pool, r->filename, filoc);
+ /* bad = mispelling */
+ bad = apr_pstrdup(r->pool, r->filename + filoc + 1);
+ /* postgood = mispelling/more */
+ postgood = apr_pstrcat(r->pool, bad, r->path_info, NULL);
+
+ urlen = strlen(r->uri);
+ pglen = strlen(postgood);
+
+ /* Check to see if the URL pieces add up */
+ if (strcmp(postgood, r->uri + (urlen - pglen))) {
+ return DECLINED;
+ }
+
+ /* url = /correct-url */
+ url = apr_pstrndup(r->pool, r->uri, (urlen - pglen));
+
+ /* Now open the directory and do ourselves a check... */
+ if (apr_dir_open(&dir, good, r->pool) != APR_SUCCESS) {
+ /* Oops, not a directory... */
+ return DECLINED;
+ }
+
+ candidates = apr_array_make(r->pool, 2, sizeof(misspelled_file));
+
+ dotloc = ap_ind(bad, '.');
+ if (dotloc == -1) {
+ dotloc = strlen(bad);
+ }
+
+ while (apr_dir_read(&dirent, APR_FINFO_DIRENT, dir) == APR_SUCCESS) {
+ sp_reason q;
+
+ /*
+ * If we end up with a "fixed" URL which is identical to the
+ * requested one, we must have found a broken symlink or some such.
+ * Do _not_ try to redirect this, it causes a loop!
+ */
+ if (strcmp(bad, dirent.name) == 0) {
+ apr_dir_close(dir);
+ return OK;
+ }
+
+ /*
+ * miscapitalization errors are checked first (like, e.g., lower case
+ * file, upper case request)
+ */
+ else if (strcasecmp(bad, dirent.name) == 0) {
+ misspelled_file *sp_new;
+
+ sp_new = (misspelled_file *) apr_array_push(candidates);
+ sp_new->name = apr_pstrdup(r->pool, dirent.name);
+ sp_new->quality = SP_MISCAPITALIZED;
+ }
+
+ /*
+ * simple typing errors are checked next (like, e.g.,
+ * missing/extra/transposed char)
+ */
+ else if ((cfg->check_case_only == 0)
+ && ((q = spdist(bad, dirent.name)) != SP_VERYDIFFERENT)) {
+ misspelled_file *sp_new;
+
+ sp_new = (misspelled_file *) apr_array_push(candidates);
+ sp_new->name = apr_pstrdup(r->pool, dirent.name);
+ sp_new->quality = q;
+ }
+
+ /*
+ * The spdist() should have found the majority of the misspelled
+ * requests. It is of questionable use to continue looking for
+ * files with the same base name, but potentially of totally wrong
+ * type (index.html <-> index.db).
+ *
+ * If you're using MultiViews, and have a file named foobar.html,
+ * which you refer to as "foobar", and someone tried to access
+ * "Foobar", without CheckBasenameMatch, mod_speling won't find it,
+ * because it won't find anything matching that spelling.
+ * With the extension-munging, it would locate "foobar.html".
+ */
+ else if (cfg->check_basename_match == 1) {
+ /*
+ * Okay... we didn't find anything. Now we take out the hard-core
+ * power tools. There are several cases here. Someone might have
+ * entered a wrong extension (.htm instead of .html or vice
+ * versa) or the document could be negotiated. At any rate, now
+ * we just compare stuff before the first dot. If it matches, we
+ * figure we got us a match. This can result in wrong things if
+ * there are files of different content types but the same prefix
+ * (e.g. foo.gif and foo.html) This code will pick the first one
+ * it finds. Better than a Not Found, though.
+ */
+ int entloc = ap_ind(dirent.name, '.');
+ if (entloc == -1) {
+ entloc = strlen(dirent.name);
+ }
+
+ if ((dotloc == entloc)
+ && !strncasecmp(bad, dirent.name, dotloc)) {
+ misspelled_file *sp_new;
+
+ sp_new = (misspelled_file *) apr_array_push(candidates);
+ sp_new->name = apr_pstrdup(r->pool, dirent.name);
+ sp_new->quality = SP_VERYDIFFERENT;
+ }
+ }
+ }
+ apr_dir_close(dir);
+
+ if (candidates->nelts != 0) {
+ /* Wow... we found us a mispelling. Construct a fixed url */
+ char *nuri;
+ const char *ref;
+ misspelled_file *variant = (misspelled_file *) candidates->elts;
+ int i;
+
+ ref = apr_table_get(r->headers_in, "Referer");
+
+ qsort((void *) candidates->elts, candidates->nelts,
+ sizeof(misspelled_file), sort_by_quality);
+
+ /*
+ * Conditions for immediate redirection:
+ * a) the first candidate was not found by stripping the suffix
+ * AND b) there exists only one candidate OR the best match is not
+ * ambiguous
+ * then return a redirection right away.
+ */
+ if (variant[0].quality != SP_VERYDIFFERENT
+ && (candidates->nelts == 1
+ || variant[0].quality != variant[1].quality)) {
+
+ nuri = ap_escape_uri(r->pool, apr_pstrcat(r->pool, url,
+ variant[0].name,
+ r->path_info, NULL));
+ if (r->parsed_uri.query)
+ nuri = apr_pstrcat(r->pool, nuri, "?", r->parsed_uri.query, NULL);
+
+ apr_table_setn(r->headers_out, "Location",
+ ap_construct_url(r->pool, nuri, r));
+
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, APR_SUCCESS,
+ r,
+ ref ? APLOGNO(03224) "Fixed spelling: %s to %s from %s"
+ : APLOGNO(03225) "Fixed spelling: %s to %s%s",
+ r->uri, nuri,
+ (ref ? ref : ""));
+
+ return HTTP_MOVED_PERMANENTLY;
+ }
+ /*
+ * Otherwise, a "[300] Multiple Choices" list with the variants is
+ * returned.
+ */
+ else {
+ apr_pool_t *p;
+ apr_table_t *notes;
+ apr_pool_t *sub_pool;
+ apr_array_header_t *t;
+ apr_array_header_t *v;
+
+
+ if (r->main == NULL) {
+ p = r->pool;
+ notes = r->notes;
+ }
+ else {
+ p = r->main->pool;
+ notes = r->main->notes;
+ }
+
+ if (apr_pool_create(&sub_pool, p) != APR_SUCCESS)
+ return DECLINED;
+ apr_pool_tag(sub_pool, "speling_sub");
+
+ t = apr_array_make(sub_pool, candidates->nelts * 8 + 8,
+ sizeof(char *));
+ v = apr_array_make(sub_pool, candidates->nelts * 5,
+ sizeof(char *));
+
+ /* Generate the response text. */
+
+ *(const char **)apr_array_push(t) =
+ "The document name you requested (<code>";
+ *(const char **)apr_array_push(t) = ap_escape_html(sub_pool, r->uri);
+ *(const char **)apr_array_push(t) =
+ "</code>) could not be found on this server.\n"
+ "However, we found documents with names similar "
+ "to the one you requested.<p>"
+ "Available documents:\n<ul>\n";
+
+ for (i = 0; i < candidates->nelts; ++i) {
+ char *vuri;
+ const char *reason;
+
+ reason = sp_reason_str[(int) (variant[i].quality)];
+ /* The format isn't very neat... */
+ vuri = apr_pstrcat(sub_pool, url, variant[i].name, r->path_info,
+ (r->parsed_uri.query != NULL) ? "?" : "",
+ (r->parsed_uri.query != NULL)
+ ? r->parsed_uri.query : "",
+ NULL);
+ *(const char **)apr_array_push(v) = "\"";
+ *(const char **)apr_array_push(v) = ap_escape_uri(sub_pool, vuri);
+ *(const char **)apr_array_push(v) = "\";\"";
+ *(const char **)apr_array_push(v) = reason;
+ *(const char **)apr_array_push(v) = "\"";
+
+ *(const char **)apr_array_push(t) = "<li><a href=\"";
+ *(const char **)apr_array_push(t) = ap_escape_uri(sub_pool, vuri);
+ *(const char **)apr_array_push(t) = "\">";
+ *(const char **)apr_array_push(t) = ap_escape_html(sub_pool, vuri);
+ *(const char **)apr_array_push(t) = "</a> (";
+ *(const char **)apr_array_push(t) = reason;
+ *(const char **)apr_array_push(t) = ")\n";
+
+ /*
+ * when we have printed the "close matches" and there are
+ * more "distant matches" (matched by stripping the suffix),
+ * then we insert an additional separator text to suggest
+ * that the user LOOK CLOSELY whether these are really the
+ * files she wanted.
+ */
+ if (i > 0 && i < candidates->nelts - 1
+ && variant[i].quality != SP_VERYDIFFERENT
+ && variant[i + 1].quality == SP_VERYDIFFERENT) {
+ *(const char **)apr_array_push(t) =
+ "</ul>\nFurthermore, the following related "
+ "documents were found:\n<ul>\n";
+ }
+ }
+ *(const char **)apr_array_push(t) = "</ul>\n";
+
+ /* If we know there was a referring page, add a note: */
+ if (ref != NULL) {
+ *(const char **)apr_array_push(t) =
+ "Please consider informing the owner of the "
+ "referring page <tt>";
+ *(const char **)apr_array_push(t) = ap_escape_html(sub_pool, ref);
+ *(const char **)apr_array_push(t) =
+ "</tt> about the broken link.\n";
+ }
+
+
+ /* Pass our apr_table_t to http_protocol.c (see mod_negotiation): */
+ apr_table_setn(notes, "variant-list", apr_array_pstrcat(p, t, 0));
+
+ apr_table_mergen(r->subprocess_env, "VARIANTS",
+ apr_array_pstrcat(p, v, ','));
+
+ apr_pool_destroy(sub_pool);
+
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
+ ref ? APLOGNO(03226) "Spelling fix: %s: %d candidates from %s"
+ : APLOGNO(03227) "Spelling fix: %s: %d candidates%s",
+ r->uri, candidates->nelts,
+ (ref ? ref : ""));
+
+ return HTTP_MULTIPLE_CHOICES;
+ }
+ }
+
+ return OK;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_hook_fixups(check_speling,NULL,NULL,APR_HOOK_LAST);
+}
+
+AP_DECLARE_MODULE(speling) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_mconfig_for_directory, /* create per-dir config */
+ NULL, /* merge per-dir config */
+ create_mconfig_for_server, /* server config */
+ NULL, /* merge server config */
+ speling_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/mappers/mod_speling.dep b/modules/mappers/mod_speling.dep
new file mode 100644
index 0000000..6709aa8
--- /dev/null
+++ b/modules/mappers/mod_speling.dep
@@ -0,0 +1,51 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_speling.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_speling.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/mappers/mod_speling.dsp b/modules/mappers/mod_speling.dsp
new file mode 100644
index 0000000..d8840b8
--- /dev/null
+++ b/modules/mappers/mod_speling.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_speling" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_speling - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_speling.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_speling.mak" CFG="mod_speling - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_speling - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_speling - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_speling - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_speling_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_speling.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_speling.so" /d LONG_NAME="speling_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_speling.so" /base:@..\..\os\win32\BaseAddr.ref,mod_speling.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_speling.so" /base:@..\..\os\win32\BaseAddr.ref,mod_speling.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_speling.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_speling - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_speling_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_speling.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_speling.so" /d LONG_NAME="speling_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_speling.so" /base:@..\..\os\win32\BaseAddr.ref,mod_speling.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_speling.so" /base:@..\..\os\win32\BaseAddr.ref,mod_speling.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_speling.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_speling - Win32 Release"
+# Name "mod_speling - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_speling.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/mappers/mod_speling.exp b/modules/mappers/mod_speling.exp
new file mode 100644
index 0000000..a6ee8b5
--- /dev/null
+++ b/modules/mappers/mod_speling.exp
@@ -0,0 +1 @@
+speling_module
diff --git a/modules/mappers/mod_speling.mak b/modules/mappers/mod_speling.mak
new file mode 100644
index 0000000..b49233f
--- /dev/null
+++ b/modules/mappers/mod_speling.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_speling.dsp
+!IF "$(CFG)" == ""
+CFG=mod_speling - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_speling - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_speling - Win32 Release" && "$(CFG)" != "mod_speling - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_speling.mak" CFG="mod_speling - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_speling - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_speling - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_speling - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_speling.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_speling.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_speling.obj"
+ -@erase "$(INTDIR)\mod_speling.res"
+ -@erase "$(INTDIR)\mod_speling_src.idb"
+ -@erase "$(INTDIR)\mod_speling_src.pdb"
+ -@erase "$(OUTDIR)\mod_speling.exp"
+ -@erase "$(OUTDIR)\mod_speling.lib"
+ -@erase "$(OUTDIR)\mod_speling.pdb"
+ -@erase "$(OUTDIR)\mod_speling.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_speling_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_speling.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_speling.so" /d LONG_NAME="speling_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_speling.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_speling.pdb" /debug /out:"$(OUTDIR)\mod_speling.so" /implib:"$(OUTDIR)\mod_speling.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_speling.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_speling.obj" \
+ "$(INTDIR)\mod_speling.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_speling.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_speling.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_speling.so"
+ if exist .\Release\mod_speling.so.manifest mt.exe -manifest .\Release\mod_speling.so.manifest -outputresource:.\Release\mod_speling.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_speling - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_speling.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_speling.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_speling.obj"
+ -@erase "$(INTDIR)\mod_speling.res"
+ -@erase "$(INTDIR)\mod_speling_src.idb"
+ -@erase "$(INTDIR)\mod_speling_src.pdb"
+ -@erase "$(OUTDIR)\mod_speling.exp"
+ -@erase "$(OUTDIR)\mod_speling.lib"
+ -@erase "$(OUTDIR)\mod_speling.pdb"
+ -@erase "$(OUTDIR)\mod_speling.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_speling_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_speling.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_speling.so" /d LONG_NAME="speling_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_speling.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_speling.pdb" /debug /out:"$(OUTDIR)\mod_speling.so" /implib:"$(OUTDIR)\mod_speling.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_speling.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_speling.obj" \
+ "$(INTDIR)\mod_speling.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_speling.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_speling.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_speling.so"
+ if exist .\Debug\mod_speling.so.manifest mt.exe -manifest .\Debug\mod_speling.so.manifest -outputresource:.\Debug\mod_speling.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_speling.dep")
+!INCLUDE "mod_speling.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_speling.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_speling - Win32 Release" || "$(CFG)" == "mod_speling - Win32 Debug"
+
+!IF "$(CFG)" == "mod_speling - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\mappers"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ELSEIF "$(CFG)" == "mod_speling - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\mappers"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_speling - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\mappers"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ELSEIF "$(CFG)" == "mod_speling - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\mappers"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_speling - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\mappers"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\mappers"
+
+!ELSEIF "$(CFG)" == "mod_speling - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\mappers"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\mappers"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_speling - Win32 Release"
+
+
+"$(INTDIR)\mod_speling.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_speling.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_speling.so" /d LONG_NAME="speling_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_speling - Win32 Debug"
+
+
+"$(INTDIR)\mod_speling.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_speling.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_speling.so" /d LONG_NAME="speling_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_speling.c
+
+"$(INTDIR)\mod_speling.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/mappers/mod_userdir.c b/modules/mappers/mod_userdir.c
new file mode 100644
index 0000000..1ec0e90
--- /dev/null
+++ b/modules/mappers/mod_userdir.c
@@ -0,0 +1,390 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * mod_userdir... implement the UserDir command. Broken away from the
+ * Alias stuff for a couple of good and not-so-good reasons:
+ *
+ * 1) It shows a real minimal working example of how to do something like
+ * this.
+ * 2) I know people who are actually interested in changing this *particular*
+ * aspect of server functionality without changing the rest of it. That's
+ * what this whole modular arrangement is supposed to be good at...
+ *
+ * Modified by Alexei Kosut to support the following constructs
+ * (server running at www.foo.com, request for /~bar/one/two.html)
+ *
+ * UserDir public_html -> ~bar/public_html/one/two.html
+ * UserDir /usr/web -> /usr/web/bar/one/two.html
+ * UserDir /home/ * /www -> /home/bar/www/one/two.html
+ * NOTE: theses ^ ^ space only added allow it to work in a comment, ignore
+ * UserDir http://x/users -> (302) http://x/users/bar/one/two.html
+ * UserDir http://x/ * /y -> (302) http://x/bar/y/one/two.html
+ * NOTE: here also ^ ^
+ *
+ * In addition, you can use multiple entries, to specify alternate
+ * user directories (a la Directory Index). For example:
+ *
+ * UserDir public_html /usr/web http://www.xyz.com/users
+ *
+ * Modified by Ken Coar to provide for the following:
+ *
+ * UserDir disable[d] username ...
+ * UserDir enable[d] username ...
+ *
+ * If "disabled" has no other arguments, *all* ~<username> references are
+ * disabled, except those explicitly turned on with the "enabled" keyword.
+ */
+
+#include "apr_strings.h"
+#include "apr_user.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_request.h"
+
+#if !defined(WIN32) && !defined(OS2) && !defined(NETWARE)
+#define HAVE_UNIX_SUEXEC
+#endif
+
+#ifdef HAVE_UNIX_SUEXEC
+#include "unixd.h" /* Contains the suexec_identity hook used on Unix */
+#endif
+
+
+/*
+ * The default directory in user's home dir
+ * In the default install, the module is disabled
+ */
+#ifndef DEFAULT_USER_DIR
+#define DEFAULT_USER_DIR NULL
+#endif
+
+#define O_DEFAULT 0
+#define O_ENABLE 1
+#define O_DISABLE 2
+
+module AP_MODULE_DECLARE_DATA userdir_module;
+
+typedef struct {
+ int globally_disabled;
+ const char *userdir;
+ apr_table_t *enabled_users;
+ apr_table_t *disabled_users;
+} userdir_config;
+
+/*
+ * Server config for this module: global disablement flag, a list of usernames
+ * ineligible for UserDir access, a list of those immune to global (but not
+ * explicit) disablement, and the replacement string for all others.
+ */
+
+static void *create_userdir_config(apr_pool_t *p, server_rec *s)
+{
+ userdir_config *newcfg = apr_pcalloc(p, sizeof(*newcfg));
+
+ newcfg->globally_disabled = O_DEFAULT;
+ newcfg->userdir = DEFAULT_USER_DIR;
+ newcfg->enabled_users = apr_table_make(p, 4);
+ newcfg->disabled_users = apr_table_make(p, 4);
+
+ return newcfg;
+}
+
+static void *merge_userdir_config(apr_pool_t *p, void *basev, void *overridesv)
+{
+ userdir_config *cfg = apr_pcalloc(p, sizeof(userdir_config));
+ userdir_config *base = basev, *overrides = overridesv;
+
+ cfg->globally_disabled = (overrides->globally_disabled != O_DEFAULT) ?
+ overrides->globally_disabled :
+ base->globally_disabled;
+ cfg->userdir = (overrides->userdir != DEFAULT_USER_DIR) ?
+ overrides->userdir : base->userdir;
+
+ /* not merged */
+ cfg->enabled_users = overrides->enabled_users;
+ cfg->disabled_users = overrides->disabled_users;
+
+ return cfg;
+}
+
+
+static const char *set_user_dir(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ userdir_config *s_cfg = ap_get_module_config(cmd->server->module_config,
+ &userdir_module);
+ char *username;
+ const char *usernames = arg;
+ char *kw = ap_getword_conf(cmd->temp_pool, &usernames);
+ apr_table_t *usertable;
+
+ /* Since we are a raw argument, it is possible for us to be called with
+ * zero arguments. So that we aren't ambiguous, flat out reject this.
+ */
+ if (*kw == '\0') {
+ return "UserDir requires an argument.";
+ }
+
+ /*
+ * Let's do the comparisons once.
+ */
+ if ((!strcasecmp(kw, "disable")) || (!strcasecmp(kw, "disabled"))) {
+ /*
+ * If there are no usernames specified, this is a global disable - we
+ * need do no more at this point than record the fact.
+ */
+ if (!*usernames) {
+ s_cfg->globally_disabled = O_DISABLE;
+ return NULL;
+ }
+ usertable = s_cfg->disabled_users;
+ }
+ else if ((!strcasecmp(kw, "enable")) || (!strcasecmp(kw, "enabled"))) {
+ if (!*usernames) {
+ s_cfg->globally_disabled = O_ENABLE;
+ return NULL;
+ }
+ usertable = s_cfg->enabled_users;
+ }
+ else {
+ /*
+ * If the first (only?) value isn't one of our keywords, just copy
+ * the string to the userdir string.
+ */
+ s_cfg->userdir = arg;
+ return NULL;
+ }
+ /*
+ * Now we just take each word in turn from the command line and add it to
+ * the appropriate table.
+ */
+ while (*usernames) {
+ username = ap_getword_conf(cmd->pool, &usernames);
+ apr_table_setn(usertable, username, "1");
+ }
+ return NULL;
+}
+
+static const command_rec userdir_cmds[] = {
+ AP_INIT_RAW_ARGS("UserDir", set_user_dir, NULL, RSRC_CONF,
+ "the public subdirectory in users' home directories, or "
+ "'disabled', or 'disabled username username...', or "
+ "'enabled username username...'"),
+ {NULL}
+};
+
+static int translate_userdir(request_rec *r)
+{
+ ap_conf_vector_t *server_conf;
+ const userdir_config *s_cfg;
+ const char *userdirs;
+ const char *user, *dname;
+ char *redirect;
+ apr_finfo_t statbuf;
+
+ /*
+ * If the URI doesn't match our basic pattern, we've nothing to do with
+ * it.
+ */
+ if (r->uri[0] != '/' || r->uri[1] != '~') {
+ return DECLINED;
+ }
+ server_conf = r->server->module_config;
+ s_cfg = ap_get_module_config(server_conf, &userdir_module);
+ userdirs = s_cfg->userdir;
+ if (userdirs == NULL) {
+ return DECLINED;
+ }
+
+ dname = r->uri + 2;
+ user = ap_getword(r->pool, &dname, '/');
+
+ /*
+ * The 'dname' funny business involves backing it up to capture the '/'
+ * delimiting the "/~user" part from the rest of the URL, in case there
+ * was one (the case where there wasn't being just "GET /~user HTTP/1.0",
+ * for which we don't want to tack on a '/' onto the filename).
+ */
+
+ if (dname[-1] == '/') {
+ --dname;
+ }
+
+ /*
+ * If there's no username, it's not for us. Ignore . and .. as well.
+ */
+ if (user[0] == '\0' ||
+ (user[1] == '.' && (user[2] == '\0' ||
+ (user[2] == '.' && user[3] == '\0')))) {
+ return DECLINED;
+ }
+ /*
+ * Nor if there's an username but it's in the disabled list.
+ */
+ if (apr_table_get(s_cfg->disabled_users, user) != NULL) {
+ return DECLINED;
+ }
+ /*
+ * If there's a global interdiction on UserDirs, check to see if this
+ * name is one of the Blessed.
+ */
+ if (s_cfg->globally_disabled == O_DISABLE
+ && apr_table_get(s_cfg->enabled_users, user) == NULL) {
+ return DECLINED;
+ }
+
+ /*
+ * Special cases all checked, onward to normal substitution processing.
+ */
+
+ while (*userdirs) {
+ const char *userdir = ap_getword_conf(r->pool, &userdirs);
+ char *filename = NULL, *prefix = NULL;
+ apr_status_t rv;
+ int is_absolute = ap_os_is_path_absolute(r->pool, userdir);
+
+ if (ap_strchr_c(userdir, '*'))
+ prefix = ap_getword(r->pool, &userdir, '*');
+
+ if (userdir[0] == '\0' || is_absolute) {
+ if (prefix) {
+#ifdef HAVE_DRIVE_LETTERS
+ /*
+ * Crummy hack. Need to figure out whether we have been
+ * redirected to a URL or to a file on some drive. Since I
+ * know of no protocols that are a single letter, ignore
+ * a : as the first or second character, and assume a file
+ * was specified
+ */
+ if (strchr(prefix + 2, ':'))
+#else
+ if (strchr(prefix, ':') && !is_absolute)
+#endif /* HAVE_DRIVE_LETTERS */
+ {
+ redirect = apr_pstrcat(r->pool, prefix, user, userdir,
+ dname, NULL);
+ apr_table_setn(r->headers_out, "Location", redirect);
+ return HTTP_MOVED_TEMPORARILY;
+ }
+ else
+ filename = apr_pstrcat(r->pool, prefix, user, userdir,
+ NULL);
+ }
+ else
+ filename = apr_pstrcat(r->pool, userdir, "/", user, NULL);
+ }
+ else if (prefix && ap_strchr_c(prefix, ':')) {
+ redirect = apr_pstrcat(r->pool, prefix, user, dname, NULL);
+ apr_table_setn(r->headers_out, "Location", redirect);
+ return HTTP_MOVED_TEMPORARILY;
+ }
+ else {
+#if APR_HAS_USER
+ char *homedir;
+
+ if (apr_uid_homepath_get(&homedir, user, r->pool) == APR_SUCCESS) {
+ filename = apr_pstrcat(r->pool, homedir, "/", userdir, NULL);
+ }
+#else
+ return DECLINED;
+#endif
+ }
+
+ /*
+ * Now see if it exists, or we're at the last entry. If we are at the
+ * last entry, then use the filename generated (if there is one)
+ * anyway, in the hope that some handler might handle it. This can be
+ * used, for example, to run a CGI script for the user.
+ */
+ if (filename && (!*userdirs
+ || ((rv = apr_stat(&statbuf, filename, APR_FINFO_MIN,
+ r->pool)) == APR_SUCCESS
+ || rv == APR_INCOMPLETE))) {
+ r->filename = apr_pstrcat(r->pool, filename, dname, NULL);
+ ap_set_context_info(r, apr_pstrmemdup(r->pool, r->uri,
+ dname - r->uri),
+ filename);
+ /* XXX: Does this walk us around FollowSymLink rules?
+ * When statbuf contains info on r->filename we can save a syscall
+ * by copying it to r->finfo
+ */
+ if (*userdirs && dname[0] == 0)
+ r->finfo = statbuf;
+
+ /* For use in the get_suexec_identity phase */
+ apr_table_setn(r->notes, "mod_userdir_user", user);
+
+ return OK;
+ }
+ }
+
+ return DECLINED;
+}
+
+#ifdef HAVE_UNIX_SUEXEC
+static ap_unix_identity_t *get_suexec_id_doer(const request_rec *r)
+{
+ ap_unix_identity_t *ugid = NULL;
+#if APR_HAS_USER
+ const char *username = apr_table_get(r->notes, "mod_userdir_user");
+
+ if (username == NULL) {
+ return NULL;
+ }
+
+ if ((ugid = apr_palloc(r->pool, sizeof(*ugid))) == NULL) {
+ return NULL;
+ }
+
+ if (apr_uid_get(&ugid->uid, &ugid->gid, username, r->pool) != APR_SUCCESS) {
+ return NULL;
+ }
+
+ ugid->userdir = 1;
+#endif
+ return ugid;
+}
+#endif /* HAVE_UNIX_SUEXEC */
+
+static void register_hooks(apr_pool_t *p)
+{
+ static const char * const aszPre[]={ "mod_alias.c",NULL };
+ static const char * const aszSucc[]={ "mod_vhost_alias.c",NULL };
+
+ ap_hook_translate_name(translate_userdir,aszPre,aszSucc,APR_HOOK_MIDDLE);
+#ifdef HAVE_UNIX_SUEXEC
+ ap_hook_get_suexec_identity(get_suexec_id_doer,NULL,NULL,APR_HOOK_FIRST);
+#endif
+}
+
+AP_DECLARE_MODULE(userdir) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* dir config creater */
+ NULL, /* dir merger --- default is to override */
+ create_userdir_config, /* server config */
+ merge_userdir_config, /* merge server config */
+ userdir_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/mappers/mod_userdir.dep b/modules/mappers/mod_userdir.dep
new file mode 100644
index 0000000..2aff834
--- /dev/null
+++ b/modules/mappers/mod_userdir.dep
@@ -0,0 +1,46 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_userdir.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_userdir.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/mappers/mod_userdir.dsp b/modules/mappers/mod_userdir.dsp
new file mode 100644
index 0000000..e40367f
--- /dev/null
+++ b/modules/mappers/mod_userdir.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_userdir" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_userdir - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_userdir.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_userdir.mak" CFG="mod_userdir - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_userdir - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_userdir - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_userdir - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_userdir_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_userdir.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_userdir.so" /d LONG_NAME="userdir_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_userdir.so" /base:@..\..\os\win32\BaseAddr.ref,mod_userdir.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_userdir.so" /base:@..\..\os\win32\BaseAddr.ref,mod_userdir.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_userdir.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_userdir - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_userdir_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_userdir.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_userdir.so" /d LONG_NAME="userdir_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_userdir.so" /base:@..\..\os\win32\BaseAddr.ref,mod_userdir.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_userdir.so" /base:@..\..\os\win32\BaseAddr.ref,mod_userdir.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_userdir.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_userdir - Win32 Release"
+# Name "mod_userdir - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_userdir.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/mappers/mod_userdir.exp b/modules/mappers/mod_userdir.exp
new file mode 100644
index 0000000..6b8b81d
--- /dev/null
+++ b/modules/mappers/mod_userdir.exp
@@ -0,0 +1 @@
+userdir_module
diff --git a/modules/mappers/mod_userdir.mak b/modules/mappers/mod_userdir.mak
new file mode 100644
index 0000000..8087ce1
--- /dev/null
+++ b/modules/mappers/mod_userdir.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_userdir.dsp
+!IF "$(CFG)" == ""
+CFG=mod_userdir - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_userdir - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_userdir - Win32 Release" && "$(CFG)" != "mod_userdir - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_userdir.mak" CFG="mod_userdir - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_userdir - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_userdir - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_userdir - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_userdir.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_userdir.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_userdir.obj"
+ -@erase "$(INTDIR)\mod_userdir.res"
+ -@erase "$(INTDIR)\mod_userdir_src.idb"
+ -@erase "$(INTDIR)\mod_userdir_src.pdb"
+ -@erase "$(OUTDIR)\mod_userdir.exp"
+ -@erase "$(OUTDIR)\mod_userdir.lib"
+ -@erase "$(OUTDIR)\mod_userdir.pdb"
+ -@erase "$(OUTDIR)\mod_userdir.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_userdir_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_userdir.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_userdir.so" /d LONG_NAME="userdir_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_userdir.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_userdir.pdb" /debug /out:"$(OUTDIR)\mod_userdir.so" /implib:"$(OUTDIR)\mod_userdir.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_userdir.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_userdir.obj" \
+ "$(INTDIR)\mod_userdir.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_userdir.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_userdir.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_userdir.so"
+ if exist .\Release\mod_userdir.so.manifest mt.exe -manifest .\Release\mod_userdir.so.manifest -outputresource:.\Release\mod_userdir.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_userdir - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_userdir.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_userdir.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_userdir.obj"
+ -@erase "$(INTDIR)\mod_userdir.res"
+ -@erase "$(INTDIR)\mod_userdir_src.idb"
+ -@erase "$(INTDIR)\mod_userdir_src.pdb"
+ -@erase "$(OUTDIR)\mod_userdir.exp"
+ -@erase "$(OUTDIR)\mod_userdir.lib"
+ -@erase "$(OUTDIR)\mod_userdir.pdb"
+ -@erase "$(OUTDIR)\mod_userdir.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_userdir_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_userdir.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_userdir.so" /d LONG_NAME="userdir_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_userdir.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_userdir.pdb" /debug /out:"$(OUTDIR)\mod_userdir.so" /implib:"$(OUTDIR)\mod_userdir.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_userdir.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_userdir.obj" \
+ "$(INTDIR)\mod_userdir.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_userdir.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_userdir.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_userdir.so"
+ if exist .\Debug\mod_userdir.so.manifest mt.exe -manifest .\Debug\mod_userdir.so.manifest -outputresource:.\Debug\mod_userdir.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_userdir.dep")
+!INCLUDE "mod_userdir.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_userdir.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_userdir - Win32 Release" || "$(CFG)" == "mod_userdir - Win32 Debug"
+
+!IF "$(CFG)" == "mod_userdir - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\mappers"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ELSEIF "$(CFG)" == "mod_userdir - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\mappers"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_userdir - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\mappers"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ELSEIF "$(CFG)" == "mod_userdir - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\mappers"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_userdir - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\mappers"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\mappers"
+
+!ELSEIF "$(CFG)" == "mod_userdir - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\mappers"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\mappers"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_userdir - Win32 Release"
+
+
+"$(INTDIR)\mod_userdir.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_userdir.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_userdir.so" /d LONG_NAME="userdir_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_userdir - Win32 Debug"
+
+
+"$(INTDIR)\mod_userdir.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_userdir.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_userdir.so" /d LONG_NAME="userdir_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_userdir.c
+
+"$(INTDIR)\mod_userdir.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/mappers/mod_vhost_alias.c b/modules/mappers/mod_vhost_alias.c
new file mode 100644
index 0000000..b1e5bfb
--- /dev/null
+++ b/modules/mappers/mod_vhost_alias.c
@@ -0,0 +1,457 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * mod_vhost_alias.c: support for dynamically configured mass virtual hosting
+ *
+ * Copyright (c) 1998-1999 Demon Internet Ltd.
+ *
+ * This software was submitted by Demon Internet to the Apache Software Foundation
+ * in May 1999. Future revisions and derivatives of this source code
+ * must acknowledge Demon Internet as the original contributor of
+ * this module. All other licensing and usage conditions are those
+ * of the Apache Software Foundation.
+ *
+ * Originally written by Tony Finch <fanf@demon.net> <dot@dotat.at>.
+ *
+ * Implementation ideas were taken from mod_alias.c. The overall
+ * concept is derived from the OVERRIDE_DOC_ROOT/OVERRIDE_CGIDIR
+ * patch to Apache 1.3b3 and a similar feature in Demon's thttpd,
+ * both written by James Grinter <jrg@blodwen.demon.co.uk>.
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "ap_hooks.h"
+#include "apr_lib.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_request.h" /* for ap_hook_translate_name */
+
+
+module AP_MODULE_DECLARE_DATA vhost_alias_module;
+
+
+/*
+ * basic configuration things
+ * we abbreviate "mod_vhost_alias" to "mva" for shorter names
+ */
+
+typedef enum {
+ VHOST_ALIAS_UNSET, VHOST_ALIAS_NONE, VHOST_ALIAS_NAME, VHOST_ALIAS_IP
+} mva_mode_e;
+
+/*
+ * Per-server module config record.
+ */
+typedef struct mva_sconf_t {
+ const char *doc_root;
+ const char *cgi_root;
+ mva_mode_e doc_root_mode;
+ mva_mode_e cgi_root_mode;
+} mva_sconf_t;
+
+static void *mva_create_server_config(apr_pool_t *p, server_rec *s)
+{
+ mva_sconf_t *conf;
+
+ conf = (mva_sconf_t *) apr_pcalloc(p, sizeof(mva_sconf_t));
+ conf->doc_root = NULL;
+ conf->cgi_root = NULL;
+ conf->doc_root_mode = VHOST_ALIAS_UNSET;
+ conf->cgi_root_mode = VHOST_ALIAS_UNSET;
+ return conf;
+}
+
+static void *mva_merge_server_config(apr_pool_t *p, void *parentv, void *childv)
+{
+ mva_sconf_t *parent = (mva_sconf_t *) parentv;
+ mva_sconf_t *child = (mva_sconf_t *) childv;
+ mva_sconf_t *conf;
+
+ conf = (mva_sconf_t *) apr_pcalloc(p, sizeof(*conf));
+ if (child->doc_root_mode == VHOST_ALIAS_UNSET) {
+ conf->doc_root_mode = parent->doc_root_mode;
+ conf->doc_root = parent->doc_root;
+ }
+ else {
+ conf->doc_root_mode = child->doc_root_mode;
+ conf->doc_root = child->doc_root;
+ }
+ if (child->cgi_root_mode == VHOST_ALIAS_UNSET) {
+ conf->cgi_root_mode = parent->cgi_root_mode;
+ conf->cgi_root = parent->cgi_root;
+ }
+ else {
+ conf->cgi_root_mode = child->cgi_root_mode;
+ conf->cgi_root = child->cgi_root;
+ }
+ return conf;
+}
+
+
+/*
+ * These are just here to tell us what vhost_alias_set should do.
+ * We don't put anything into them; we just use the cell addresses.
+ */
+static int vhost_alias_set_doc_root_ip,
+ vhost_alias_set_cgi_root_ip,
+ vhost_alias_set_doc_root_name,
+ vhost_alias_set_cgi_root_name;
+
+static const char *vhost_alias_set(cmd_parms *cmd, void *dummy, const char *map)
+{
+ mva_sconf_t *conf;
+ mva_mode_e mode, *pmode;
+ const char **pmap;
+ const char *p;
+
+ conf = (mva_sconf_t *) ap_get_module_config(cmd->server->module_config,
+ &vhost_alias_module);
+ /* there ought to be a better way of doing this */
+ if (&vhost_alias_set_doc_root_ip == cmd->info) {
+ mode = VHOST_ALIAS_IP;
+ pmap = &conf->doc_root;
+ pmode = &conf->doc_root_mode;
+ }
+ else if (&vhost_alias_set_cgi_root_ip == cmd->info) {
+ mode = VHOST_ALIAS_IP;
+ pmap = &conf->cgi_root;
+ pmode = &conf->cgi_root_mode;
+ }
+ else if (&vhost_alias_set_doc_root_name == cmd->info) {
+ mode = VHOST_ALIAS_NAME;
+ pmap = &conf->doc_root;
+ pmode = &conf->doc_root_mode;
+ }
+ else if (&vhost_alias_set_cgi_root_name == cmd->info) {
+ mode = VHOST_ALIAS_NAME;
+ pmap = &conf->cgi_root;
+ pmode = &conf->cgi_root_mode;
+ }
+ else {
+ return "INTERNAL ERROR: unknown command info";
+ }
+
+ if (!ap_os_is_path_absolute(cmd->pool, map)) {
+ if (ap_cstr_casecmp(map, "none")) {
+ return "format string must be an absolute path, or 'none'";
+ }
+ *pmap = NULL;
+ *pmode = VHOST_ALIAS_NONE;
+ return NULL;
+ }
+
+ /* sanity check */
+ p = map;
+ while (*p != '\0') {
+ if (*p++ != '%') {
+ continue;
+ }
+ /* we just found a '%' */
+ if (*p == 'p' || *p == '%') {
+ ++p;
+ continue;
+ }
+ /* optional dash */
+ if (*p == '-') {
+ ++p;
+ }
+ /* digit N */
+ if (apr_isdigit(*p)) {
+ ++p;
+ }
+ else {
+ return "syntax error in format string";
+ }
+ /* optional plus */
+ if (*p == '+') {
+ ++p;
+ }
+ /* do we end here? */
+ if (*p != '.') {
+ continue;
+ }
+ ++p;
+ /* optional dash */
+ if (*p == '-') {
+ ++p;
+ }
+ /* digit M */
+ if (apr_isdigit(*p)) {
+ ++p;
+ }
+ else {
+ return "syntax error in format string";
+ }
+ /* optional plus */
+ if (*p == '+') {
+ ++p;
+ }
+ }
+ *pmap = map;
+ *pmode = mode;
+ return NULL;
+}
+
+static const command_rec mva_commands[] =
+{
+ AP_INIT_TAKE1("VirtualScriptAlias", vhost_alias_set,
+ &vhost_alias_set_cgi_root_name, RSRC_CONF,
+ "how to create a ScriptAlias based on the host"),
+ AP_INIT_TAKE1("VirtualDocumentRoot", vhost_alias_set,
+ &vhost_alias_set_doc_root_name, RSRC_CONF,
+ "how to create the DocumentRoot based on the host"),
+ AP_INIT_TAKE1("VirtualScriptAliasIP", vhost_alias_set,
+ &vhost_alias_set_cgi_root_ip, RSRC_CONF,
+ "how to create a ScriptAlias based on the host"),
+ AP_INIT_TAKE1("VirtualDocumentRootIP", vhost_alias_set,
+ &vhost_alias_set_doc_root_ip, RSRC_CONF,
+ "how to create the DocumentRoot based on the host"),
+ { NULL }
+};
+
+
+/*
+ * This really wants to be a nested function
+ * but C is too feeble to support them.
+ */
+static APR_INLINE void vhost_alias_checkspace(request_rec *r, char *buf,
+ char **pdest, int size)
+{
+ /* XXX: what if size > HUGE_STRING_LEN? */
+ if (*pdest + size > buf + HUGE_STRING_LEN) {
+ **pdest = '\0';
+ if (r->filename) {
+ r->filename = apr_pstrcat(r->pool, r->filename, buf, NULL);
+ }
+ else {
+ r->filename = apr_pstrdup(r->pool, buf);
+ }
+ *pdest = buf;
+ }
+}
+
+static void vhost_alias_interpolate(request_rec *r, const char *name,
+ const char *map, const char *uri)
+{
+ /* 0..9 9..0 */
+ enum { MAXDOTS = 19 };
+ const char *dots[MAXDOTS+1];
+ int ndots;
+
+ char buf[HUGE_STRING_LEN];
+ char *dest;
+ const char *docroot;
+
+ int N, M, Np, Mp, Nd, Md;
+ const char *start, *end;
+
+ const char *p;
+
+ ndots = 0;
+ dots[ndots++] = name-1; /* slightly naughty */
+ for (p = name; *p; ++p) {
+ if (*p == '.' && ndots < MAXDOTS) {
+ dots[ndots++] = p;
+ }
+ }
+ dots[ndots] = p;
+
+ r->filename = NULL;
+
+ dest = buf;
+ while (*map) {
+ if (*map != '%') {
+ /* normal characters */
+ vhost_alias_checkspace(r, buf, &dest, 1);
+ *dest++ = *map++;
+ continue;
+ }
+ /* we are in a format specifier */
+ ++map;
+ /* %% -> % */
+ if (*map == '%') {
+ ++map;
+ vhost_alias_checkspace(r, buf, &dest, 1);
+ *dest++ = '%';
+ continue;
+ }
+ /* port number */
+ if (*map == 'p') {
+ ++map;
+ /* no. of decimal digits in a short plus one */
+ vhost_alias_checkspace(r, buf, &dest, 7);
+ dest += apr_snprintf(dest, 7, "%d", ap_get_server_port(r));
+ continue;
+ }
+ /* deal with %-N+.-M+ -- syntax is already checked */
+ M = 0; /* value */
+ Np = Mp = 0; /* is there a plus? */
+ Nd = Md = 0; /* is there a dash? */
+ if (*map == '-') ++map, Nd = 1;
+ N = *map++ - '0';
+ if (*map == '+') ++map, Np = 1;
+ if (*map == '.') {
+ ++map;
+ if (*map == '-') {
+ ++map, Md = 1;
+ }
+ M = *map++ - '0';
+ if (*map == '+') {
+ ++map, Mp = 1;
+ }
+ }
+ /* note that N and M are one-based indices, not zero-based */
+ start = dots[0]+1; /* ptr to the first character */
+ end = dots[ndots]; /* ptr to the character after the last one */
+ if (N != 0) {
+ if (N > ndots) {
+ start = "_";
+ end = start+1;
+ }
+ else if (!Nd) {
+ start = dots[N-1]+1;
+ if (!Np) {
+ end = dots[N];
+ }
+ }
+ else {
+ if (!Np) {
+ start = dots[ndots-N]+1;
+ }
+ end = dots[ndots-N+1];
+ }
+ }
+ if (M != 0) {
+ if (M > end - start) {
+ start = "_";
+ end = start+1;
+ }
+ else if (!Md) {
+ start = start+M-1;
+ if (!Mp) {
+ end = start+1;
+ }
+ }
+ else {
+ if (!Mp) {
+ start = end-M;
+ }
+ end = end-M+1;
+ }
+ }
+ vhost_alias_checkspace(r, buf, &dest, end - start);
+ for (p = start; p < end; ++p) {
+ *dest++ = apr_tolower(*p);
+ }
+ }
+ /* no double slashes */
+ if (dest - buf > 0 && dest[-1] == '/') {
+ --dest;
+ }
+ *dest = '\0';
+
+ if (r->filename)
+ docroot = apr_pstrcat(r->pool, r->filename, buf, NULL);
+ else
+ docroot = apr_pstrdup(r->pool, buf);
+ r->filename = apr_pstrcat(r->pool, docroot, uri, NULL);
+ ap_set_context_info(r, NULL, docroot);
+ ap_set_document_root(r, docroot);
+}
+
+static int mva_translate(request_rec *r)
+{
+ mva_sconf_t *conf;
+ const char *name, *map, *uri;
+ mva_mode_e mode;
+ const char *cgi;
+
+ conf = (mva_sconf_t *) ap_get_module_config(r->server->module_config,
+ &vhost_alias_module);
+ cgi = NULL;
+ if (conf->cgi_root) {
+ cgi = strstr(r->uri, "cgi-bin/");
+ if (cgi && (cgi != r->uri + strspn(r->uri, "/"))) {
+ cgi = NULL;
+ }
+ }
+ if (cgi) {
+ mode = conf->cgi_root_mode;
+ map = conf->cgi_root;
+ uri = cgi + strlen("cgi-bin");
+ }
+ else if (r->uri[0] == '/') {
+ mode = conf->doc_root_mode;
+ map = conf->doc_root;
+ uri = r->uri;
+ }
+ else {
+ return DECLINED;
+ }
+
+ if (mode == VHOST_ALIAS_NAME) {
+ name = ap_get_server_name(r);
+ }
+ else if (mode == VHOST_ALIAS_IP) {
+ name = r->connection->local_ip;
+ }
+ else {
+ return DECLINED;
+ }
+
+ /* ### There is an optimization available here to determine the
+ * absolute portion of the path from the server config phase,
+ * through the first % segment, and note that portion of the path
+ * canonical_path buffer.
+ */
+ r->canonical_filename = "";
+ vhost_alias_interpolate(r, name, map, uri);
+
+ if (cgi) {
+ /* see is_scriptaliased() in mod_cgi */
+ r->handler = "cgi-script";
+ apr_table_setn(r->notes, "alias-forced-type", r->handler);
+ ap_set_context_info(r, "/cgi-bin", NULL);
+ }
+
+ return OK;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ static const char * const aszPre[]={ "mod_alias.c","mod_userdir.c",NULL };
+
+ ap_hook_translate_name(mva_translate, aszPre, NULL, APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(vhost_alias) =
+{
+ STANDARD20_MODULE_STUFF,
+ NULL, /* dir config creater */
+ NULL, /* dir merger --- default is to override */
+ mva_create_server_config, /* server config */
+ mva_merge_server_config, /* merge server configs */
+ mva_commands, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
+
diff --git a/modules/mappers/mod_vhost_alias.dep b/modules/mappers/mod_vhost_alias.dep
new file mode 100644
index 0000000..7b520e3
--- /dev/null
+++ b/modules/mappers/mod_vhost_alias.dep
@@ -0,0 +1,50 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_vhost_alias.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_vhost_alias.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/mappers/mod_vhost_alias.dsp b/modules/mappers/mod_vhost_alias.dsp
new file mode 100644
index 0000000..0273ab4
--- /dev/null
+++ b/modules/mappers/mod_vhost_alias.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_vhost_alias" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_vhost_alias - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_vhost_alias.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_vhost_alias.mak" CFG="mod_vhost_alias - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_vhost_alias - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_vhost_alias - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_vhost_alias - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_vhost_alias_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_vhost_alias.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_vhost_alias.so" /d LONG_NAME="vhost_alias_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_vhost_alias.so" /base:@..\..\os\win32\BaseAddr.ref,mod_vhost_alias.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_vhost_alias.so" /base:@..\..\os\win32\BaseAddr.ref,mod_vhost_alias.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_vhost_alias.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_vhost_alias - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_vhost_alias_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_vhost_alias.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_vhost_alias.so" /d LONG_NAME="vhost_alias_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_vhost_alias.so" /base:@..\..\os\win32\BaseAddr.ref,mod_vhost_alias.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_vhost_alias.so" /base:@..\..\os\win32\BaseAddr.ref,mod_vhost_alias.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_vhost_alias.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_vhost_alias - Win32 Release"
+# Name "mod_vhost_alias - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_vhost_alias.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/mappers/mod_vhost_alias.exp b/modules/mappers/mod_vhost_alias.exp
new file mode 100644
index 0000000..b17666f
--- /dev/null
+++ b/modules/mappers/mod_vhost_alias.exp
@@ -0,0 +1 @@
+vhost_alias_module
diff --git a/modules/mappers/mod_vhost_alias.mak b/modules/mappers/mod_vhost_alias.mak
new file mode 100644
index 0000000..62085ab
--- /dev/null
+++ b/modules/mappers/mod_vhost_alias.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_vhost_alias.dsp
+!IF "$(CFG)" == ""
+CFG=mod_vhost_alias - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_vhost_alias - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_vhost_alias - Win32 Release" && "$(CFG)" != "mod_vhost_alias - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_vhost_alias.mak" CFG="mod_vhost_alias - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_vhost_alias - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_vhost_alias - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_vhost_alias - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_vhost_alias.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_vhost_alias.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_vhost_alias.obj"
+ -@erase "$(INTDIR)\mod_vhost_alias.res"
+ -@erase "$(INTDIR)\mod_vhost_alias_src.idb"
+ -@erase "$(INTDIR)\mod_vhost_alias_src.pdb"
+ -@erase "$(OUTDIR)\mod_vhost_alias.exp"
+ -@erase "$(OUTDIR)\mod_vhost_alias.lib"
+ -@erase "$(OUTDIR)\mod_vhost_alias.pdb"
+ -@erase "$(OUTDIR)\mod_vhost_alias.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_vhost_alias_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_vhost_alias.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_vhost_alias.so" /d LONG_NAME="vhost_alias_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_vhost_alias.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_vhost_alias.pdb" /debug /out:"$(OUTDIR)\mod_vhost_alias.so" /implib:"$(OUTDIR)\mod_vhost_alias.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_vhost_alias.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_vhost_alias.obj" \
+ "$(INTDIR)\mod_vhost_alias.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_vhost_alias.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_vhost_alias.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_vhost_alias.so"
+ if exist .\Release\mod_vhost_alias.so.manifest mt.exe -manifest .\Release\mod_vhost_alias.so.manifest -outputresource:.\Release\mod_vhost_alias.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_vhost_alias - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_vhost_alias.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_vhost_alias.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_vhost_alias.obj"
+ -@erase "$(INTDIR)\mod_vhost_alias.res"
+ -@erase "$(INTDIR)\mod_vhost_alias_src.idb"
+ -@erase "$(INTDIR)\mod_vhost_alias_src.pdb"
+ -@erase "$(OUTDIR)\mod_vhost_alias.exp"
+ -@erase "$(OUTDIR)\mod_vhost_alias.lib"
+ -@erase "$(OUTDIR)\mod_vhost_alias.pdb"
+ -@erase "$(OUTDIR)\mod_vhost_alias.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_vhost_alias_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_vhost_alias.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_vhost_alias.so" /d LONG_NAME="vhost_alias_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_vhost_alias.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_vhost_alias.pdb" /debug /out:"$(OUTDIR)\mod_vhost_alias.so" /implib:"$(OUTDIR)\mod_vhost_alias.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_vhost_alias.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_vhost_alias.obj" \
+ "$(INTDIR)\mod_vhost_alias.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_vhost_alias.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_vhost_alias.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_vhost_alias.so"
+ if exist .\Debug\mod_vhost_alias.so.manifest mt.exe -manifest .\Debug\mod_vhost_alias.so.manifest -outputresource:.\Debug\mod_vhost_alias.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_vhost_alias.dep")
+!INCLUDE "mod_vhost_alias.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_vhost_alias.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_vhost_alias - Win32 Release" || "$(CFG)" == "mod_vhost_alias - Win32 Debug"
+
+!IF "$(CFG)" == "mod_vhost_alias - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\mappers"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ELSEIF "$(CFG)" == "mod_vhost_alias - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\mappers"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_vhost_alias - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\mappers"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ELSEIF "$(CFG)" == "mod_vhost_alias - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\mappers"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\mappers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_vhost_alias - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\mappers"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\mappers"
+
+!ELSEIF "$(CFG)" == "mod_vhost_alias - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\mappers"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\mappers"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_vhost_alias - Win32 Release"
+
+
+"$(INTDIR)\mod_vhost_alias.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_vhost_alias.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_vhost_alias.so" /d LONG_NAME="vhost_alias_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_vhost_alias - Win32 Debug"
+
+
+"$(INTDIR)\mod_vhost_alias.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_vhost_alias.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_vhost_alias.so" /d LONG_NAME="vhost_alias_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_vhost_alias.c
+
+"$(INTDIR)\mod_vhost_alias.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/md/Makefile.in b/modules/md/Makefile.in
new file mode 100644
index 0000000..4395bc3
--- /dev/null
+++ b/modules/md/Makefile.in
@@ -0,0 +1,20 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# standard stuff
+#
+
+include $(top_srcdir)/build/special.mk
diff --git a/modules/md/config2.m4 b/modules/md/config2.m4
new file mode 100644
index 0000000..11d4f32
--- /dev/null
+++ b/modules/md/config2.m4
@@ -0,0 +1,311 @@
+dnl Licensed to the Apache Software Foundation (ASF) under one or more
+dnl contributor license agreements. See the NOTICE file distributed with
+dnl this work for additional information regarding copyright ownership.
+dnl The ASF licenses this file to You under the Apache License, Version 2.0
+dnl (the "License"); you may not use this file except in compliance with
+dnl the License. You may obtain a copy of the License at
+dnl
+dnl http://www.apache.org/licenses/LICENSE-2.0
+dnl
+dnl Unless required by applicable law or agreed to in writing, software
+dnl distributed under the License is distributed on an "AS IS" BASIS,
+dnl WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+dnl See the License for the specific language governing permissions and
+dnl limitations under the License.
+
+dnl
+dnl APACHE_CHECK_CURL
+dnl
+dnl Configure for libcurl, giving preference to
+dnl "--with-curl=<path>" if it was specified.
+dnl
+AC_DEFUN([APACHE_CHECK_CURL],[
+ AC_CACHE_CHECK([for curl], [ac_cv_curl], [
+ dnl initialise the variables we use
+ ac_cv_curl=no
+ ap_curl_found=""
+ ap_curl_base=""
+ ap_curl_libs=""
+
+ dnl Determine the curl base directory, if any
+ AC_MSG_CHECKING([for user-provided curl base directory])
+ AC_ARG_WITH(curl, APACHE_HELP_STRING(--with-curl=PATH, curl installation directory), [
+ dnl If --with-curl specifies a directory, we use that directory
+ if test "x$withval" != "xyes" -a "x$withval" != "x"; then
+ dnl This ensures $withval is actually a directory and that it is absolute
+ ap_curl_base="`cd $withval ; pwd`"
+ fi
+ ])
+ if test "x$ap_curl_base" = "x"; then
+ AC_MSG_RESULT(none)
+ else
+ AC_MSG_RESULT($ap_curl_base)
+ fi
+
+ dnl Run header and version checks
+ saved_CPPFLAGS="$CPPFLAGS"
+ saved_LIBS="$LIBS"
+ saved_LDFLAGS="$LDFLAGS"
+
+ dnl Before doing anything else, load in pkg-config variables
+ if test -n "$PKGCONFIG"; then
+ saved_PKG_CONFIG_PATH="$PKG_CONFIG_PATH"
+ AC_MSG_CHECKING([for pkg-config along $PKG_CONFIG_PATH])
+ if test "x$ap_curl_base" != "x" ; then
+ if test -f "${ap_curl_base}/lib/pkgconfig/libcurl.pc"; then
+ dnl Ensure that the given path is used by pkg-config too, otherwise
+ dnl the system libcurl.pc might be picked up instead.
+ PKG_CONFIG_PATH="${ap_curl_base}/lib/pkgconfig${PKG_CONFIG_PATH+:}${PKG_CONFIG_PATH}"
+ export PKG_CONFIG_PATH
+ elif test -f "${ap_curl_base}/lib64/pkgconfig/libcurl.pc"; then
+ dnl Ensure that the given path is used by pkg-config too, otherwise
+ dnl the system libcurl.pc might be picked up instead.
+ PKG_CONFIG_PATH="${ap_curl_base}/lib64/pkgconfig${PKG_CONFIG_PATH+:}${PKG_CONFIG_PATH}"
+ export PKG_CONFIG_PATH
+ fi
+ fi
+ AC_ARG_ENABLE(curl-staticlib-deps,APACHE_HELP_STRING(--enable-curl-staticlib-deps,[link mod_md with dependencies of libcurl's static libraries (as indicated by "pkg-config --static"). Must be specified in addition to --enable-md.]), [
+ if test "$enableval" = "yes"; then
+ PKGCONFIG_LIBOPTS="--static"
+ fi
+ ])
+ ap_curl_libs="`$PKGCONFIG $PKGCONFIG_LIBOPTS --libs-only-l --silence-errors libcurl`"
+ if test $? -eq 0; then
+ ap_curl_found="yes"
+ pkglookup="`$PKGCONFIG --cflags-only-I libcurl`"
+ APR_ADDTO(CPPFLAGS, [$pkglookup])
+ APR_ADDTO(MOD_CFLAGS, [$pkglookup])
+ pkglookup="`$PKGCONFIG $PKGCONFIG_LIBOPTS --libs-only-L libcurl`"
+ APR_ADDTO(LDFLAGS, [$pkglookup])
+ APR_ADDTO(MOD_LDFLAGS, [$pkglookup])
+ pkglookup="`$PKGCONFIG $PKGCONFIG_LIBOPTS --libs-only-other libcurl`"
+ APR_ADDTO(LDFLAGS, [$pkglookup])
+ APR_ADDTO(MOD_LDFLAGS, [$pkglookup])
+ fi
+ PKG_CONFIG_PATH="$saved_PKG_CONFIG_PATH"
+ fi
+
+ dnl fall back to the user-supplied directory if not found via pkg-config
+ if test "x$ap_curl_base" != "x" -a "x$ap_curl_found" = "x"; then
+ APR_ADDTO(CPPFLAGS, [-I$ap_curl_base/include])
+ APR_ADDTO(MOD_CFLAGS, [-I$ap_curl_base/include])
+ APR_ADDTO(LDFLAGS, [-L$ap_curl_base/lib])
+ APR_ADDTO(MOD_LDFLAGS, [-L$ap_curl_base/lib])
+ if test "x$ap_platform_runtime_link_flag" != "x"; then
+ APR_ADDTO(LDFLAGS, [$ap_platform_runtime_link_flag$ap_curl_base/lib])
+ APR_ADDTO(MOD_LDFLAGS, [$ap_platform_runtime_link_flag$ap_curl_base/lib])
+ fi
+ fi
+
+ AC_CHECK_HEADERS([curl/curl.h])
+
+ AC_MSG_CHECKING([for curl version >= 7.29])
+ AC_TRY_COMPILE([#include <curl/curlver.h>],[
+#if !defined(LIBCURL_VERSION_MAJOR)
+#error "Missing libcurl version"
+#endif
+#if LIBCURL_VERSION_MAJOR < 7
+#error "Unsupported libcurl version " LIBCURL_VERSION
+#endif
+#if LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR < 29
+#error "Unsupported libcurl version " LIBCURL_VERSION
+#endif],
+ [AC_MSG_RESULT(OK)
+ ac_cv_curl=yes],
+ [AC_MSG_RESULT(FAILED)])
+
+ if test "x$ac_cv_curl" = "xyes"; then
+ ap_curl_libs="${ap_curl_libs:--lcurl} `$apr_config --libs`"
+ APR_ADDTO(MOD_LDFLAGS, [$ap_curl_libs])
+ APR_ADDTO(LIBS, [$ap_curl_libs])
+ fi
+
+ dnl restore
+ CPPFLAGS="$saved_CPPFLAGS"
+ LIBS="$saved_LIBS"
+ LDFLAGS="$saved_LDFLAGS"
+ ])
+ if test "x$ac_cv_curl" = "xyes"; then
+ AC_DEFINE(HAVE_CURL, 1, [Define if curl is available])
+ fi
+])
+
+
+dnl
+dnl APACHE_CHECK_JANSSON
+dnl
+dnl Configure for libjansson, giving preference to
+dnl "--with-jansson=<path>" if it was specified.
+dnl
+AC_DEFUN([APACHE_CHECK_JANSSON],[
+ AC_CACHE_CHECK([for jansson], [ac_cv_jansson], [
+ dnl initialise the variables we use
+ ac_cv_jansson=no
+ ap_jansson_found=""
+ ap_jansson_base=""
+ ap_jansson_libs=""
+
+ dnl Determine the jansson base directory, if any
+ AC_MSG_CHECKING([for user-provided jansson base directory])
+ AC_ARG_WITH(jansson, APACHE_HELP_STRING(--with-jansson=PATH, jansson installation directory), [
+ dnl If --with-jansson specifies a directory, we use that directory
+ if test "x$withval" != "xyes" -a "x$withval" != "x"; then
+ dnl This ensures $withval is actually a directory and that it is absolute
+ ap_jansson_base="`cd $withval ; pwd`"
+ fi
+ ])
+ if test "x$ap_jansson_base" = "x"; then
+ AC_MSG_RESULT(none)
+ else
+ AC_MSG_RESULT($ap_jansson_base)
+ fi
+
+ dnl Run header and version checks
+ saved_CPPFLAGS="$CPPFLAGS"
+ saved_LIBS="$LIBS"
+ saved_LDFLAGS="$LDFLAGS"
+
+ dnl Before doing anything else, load in pkg-config variables
+ if test -n "$PKGCONFIG"; then
+ saved_PKG_CONFIG_PATH="$PKG_CONFIG_PATH"
+ AC_MSG_CHECKING([for pkg-config along $PKG_CONFIG_PATH])
+ if test "x$ap_jansson_base" != "x" ; then
+ if test -f "${ap_jansson_base}/lib/pkgconfig/libjansson.pc"; then
+ dnl Ensure that the given path is used by pkg-config too, otherwise
+ dnl the system libjansson.pc might be picked up instead.
+ PKG_CONFIG_PATH="${ap_jansson_base}/lib/pkgconfig${PKG_CONFIG_PATH+:}${PKG_CONFIG_PATH}"
+ export PKG_CONFIG_PATH
+ elif test -f "${ap_jansson_base}/lib64/pkgconfig/libjansson.pc"; then
+ dnl Ensure that the given path is used by pkg-config too, otherwise
+ dnl the system libjansson.pc might be picked up instead.
+ PKG_CONFIG_PATH="${ap_jansson_base}/lib64/pkgconfig${PKG_CONFIG_PATH+:}${PKG_CONFIG_PATH}"
+ export PKG_CONFIG_PATH
+ fi
+ fi
+ AC_ARG_ENABLE(jansson-staticlib-deps,APACHE_HELP_STRING(--enable-jansson-staticlib-deps,[link mod_md with dependencies of libjansson's static libraries (as indicated by "pkg-config --static"). Must be specified in addition to --enable-md.]), [
+ if test "$enableval" = "yes"; then
+ PKGCONFIG_LIBOPTS="--static"
+ fi
+ ])
+ ap_jansson_libs="`$PKGCONFIG $PKGCONFIG_LIBOPTS --libs-only-l --silence-errors libjansson`"
+ if test $? -eq 0; then
+ ap_jansson_found="yes"
+ pkglookup="`$PKGCONFIG --cflags-only-I libjansson`"
+ APR_ADDTO(CPPFLAGS, [$pkglookup])
+ APR_ADDTO(MOD_CFLAGS, [$pkglookup])
+ pkglookup="`$PKGCONFIG $PKGCONFIG_LIBOPTS --libs-only-L libjansson`"
+ APR_ADDTO(LDFLAGS, [$pkglookup])
+ APR_ADDTO(MOD_LDFLAGS, [$pkglookup])
+ pkglookup="`$PKGCONFIG $PKGCONFIG_LIBOPTS --libs-only-other libjansson`"
+ APR_ADDTO(LDFLAGS, [$pkglookup])
+ APR_ADDTO(MOD_LDFLAGS, [$pkglookup])
+ fi
+ PKG_CONFIG_PATH="$saved_PKG_CONFIG_PATH"
+ fi
+
+ dnl fall back to the user-supplied directory if not found via pkg-config
+ if test "x$ap_jansson_base" != "x" -a "x$ap_jansson_found" = "x"; then
+ APR_ADDTO(CPPFLAGS, [-I$ap_jansson_base/include])
+ APR_ADDTO(MOD_CFLAGS, [-I$ap_jansson_base/include])
+ APR_ADDTO(LDFLAGS, [-L$ap_jansson_base/lib])
+ APR_ADDTO(MOD_LDFLAGS, [-L$ap_jansson_base/lib])
+ if test "x$ap_platform_runtime_link_flag" != "x"; then
+ APR_ADDTO(LDFLAGS, [$ap_platform_runtime_link_flag$ap_jansson_base/lib])
+ APR_ADDTO(MOD_LDFLAGS, [$ap_platform_runtime_link_flag$ap_jansson_base/lib])
+ fi
+ fi
+
+ # attempts to include jansson.h fail me. So lets make sure we can at least
+ # include its other header file
+ AC_TRY_COMPILE([#include <jansson_config.h>],[],
+ [AC_MSG_RESULT(OK)
+ ac_cv_jansson=yes],
+ [AC_MSG_RESULT(FAILED)])
+
+ if test "x$ac_cv_jansson" = "xyes"; then
+ ap_jansson_libs="${ap_jansson_libs:--ljansson} `$apr_config --libs`"
+ APR_ADDTO(MOD_LDFLAGS, [$ap_jansson_libs])
+ APR_ADDTO(LIBS, [$ap_jansson_libs])
+ fi
+
+ dnl restore
+ CPPFLAGS="$saved_CPPFLAGS"
+ LIBS="$saved_LIBS"
+ LDFLAGS="$saved_LDFLAGS"
+ ])
+ if test "x$ac_cv_jansson" = "xyes"; then
+ AC_DEFINE(HAVE_JANSSON, 1, [Define if jansson is available])
+ fi
+])
+
+
+dnl # start of module specific part
+APACHE_MODPATH_INIT(md)
+
+dnl # list of module object files
+md_objs="dnl
+md_acme.lo dnl
+md_acme_acct.lo dnl
+md_acme_authz.lo dnl
+md_acme_drive.lo dnl
+md_acmev2_drive.lo dnl
+md_acme_order.lo dnl
+md_core.lo dnl
+md_curl.lo dnl
+md_crypt.lo dnl
+md_event.lo dnl
+md_http.lo dnl
+md_json.lo dnl
+md_jws.lo dnl
+md_log.lo dnl
+md_ocsp.lo dnl
+md_result.lo dnl
+md_reg.lo dnl
+md_status.lo dnl
+md_store.lo dnl
+md_store_fs.lo dnl
+md_tailscale.lo dnl
+md_time.lo dnl
+md_util.lo dnl
+mod_md.lo dnl
+mod_md_config.lo dnl
+mod_md_drive.lo dnl
+mod_md_ocsp.lo dnl
+mod_md_os.lo dnl
+mod_md_status.lo dnl
+"
+
+# Ensure that other modules can pick up mod_md.h
+APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/$modpath_current])
+
+dnl # hook module into the Autoconf mechanism (--enable-md)
+APACHE_MODULE(md, [Managed Domain handling], $md_objs, , most, [
+ APACHE_CHECK_OPENSSL
+ if test "x$ac_cv_openssl" = "xno" ; then
+ AC_MSG_WARN([libssl (or compatible) not found])
+ enable_md=no
+ fi
+
+ APACHE_CHECK_JANSSON
+ if test "x$ac_cv_jansson" != "xyes" ; then
+ AC_MSG_WARN([libjansson not found])
+ enable_md=no
+ fi
+
+ APACHE_CHECK_CURL
+ if test "x$ac_cv_curl" != "xyes" ; then
+ AC_MSG_WARN([libcurl not found])
+ enable_md=no
+ fi
+
+ AC_CHECK_FUNCS([arc4random_buf],
+ [APR_ADDTO(MOD_CPPFLAGS, ["-DMD_HAVE_ARC4RANDOM"])], [])
+
+ if test "x$enable_md" = "xshared"; then
+ APR_ADDTO(MOD_MD_LDADD, [-export-symbols-regex md_module])
+ fi
+])
+
+dnl # end of module specific part
+APACHE_MODPATH_FINISH
+
diff --git a/modules/md/md.h b/modules/md/md.h
new file mode 100644
index 0000000..1d75d10
--- /dev/null
+++ b/modules/md/md.h
@@ -0,0 +1,330 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mod_md_md_h
+#define mod_md_md_h
+
+#include <apr_time.h>
+
+#include "md_time.h"
+#include "md_version.h"
+
+struct apr_array_header_t;
+struct apr_hash_t;
+struct md_json_t;
+struct md_cert_t;
+struct md_job_t;
+struct md_pkey_t;
+struct md_result_t;
+struct md_store_t;
+struct md_srv_conf_t;
+struct md_pkey_spec_t;
+
+#define MD_PKEY_RSA_BITS_MIN 2048
+#define MD_PKEY_RSA_BITS_DEF 2048
+
+/* Minimum age for the HSTS header (RFC 6797), considered appropriate by Mozilla Security */
+#define MD_HSTS_HEADER "Strict-Transport-Security"
+#define MD_HSTS_MAX_AGE_DEFAULT 15768000
+
+#define PROTO_ACME_TLS_1 "acme-tls/1"
+
+#define MD_TIME_LIFE_NORM (apr_time_from_sec(100 * MD_SECS_PER_DAY))
+#define MD_TIME_RENEW_WINDOW_DEF (apr_time_from_sec(33 * MD_SECS_PER_DAY))
+#define MD_TIME_WARN_WINDOW_DEF (apr_time_from_sec(10 * MD_SECS_PER_DAY))
+#define MD_TIME_OCSP_KEEP_NORM (apr_time_from_sec(7 * MD_SECS_PER_DAY))
+
+#define MD_OTHER "other"
+
+typedef enum {
+ MD_S_UNKNOWN = 0, /* MD has not been analysed yet */
+ MD_S_INCOMPLETE = 1, /* MD is missing necessary information, cannot go live */
+ MD_S_COMPLETE = 2, /* MD has all necessary information, can go live */
+ MD_S_EXPIRED_DEPRECATED = 3, /* deprecated */
+ MD_S_ERROR = 4, /* MD data is flawed, unable to be processed as is */
+ MD_S_MISSING_INFORMATION = 5, /* User has not agreed to ToS */
+} md_state_t;
+
+typedef enum {
+ MD_REQUIRE_UNSET = -1,
+ MD_REQUIRE_OFF,
+ MD_REQUIRE_TEMPORARY,
+ MD_REQUIRE_PERMANENT,
+} md_require_t;
+
+typedef enum {
+ MD_RENEW_DEFAULT = -1, /* default value */
+ MD_RENEW_MANUAL, /* manually triggered renewal of certificate */
+ MD_RENEW_AUTO, /* automatic process performed by httpd */
+ MD_RENEW_ALWAYS, /* always renewed by httpd, even if not necessary */
+} md_renew_mode_t;
+
+typedef struct md_t md_t;
+struct md_t {
+ const char *name; /* unique name of this MD */
+ struct apr_array_header_t *domains; /* all DNS names this MD includes */
+ struct apr_array_header_t *contacts; /* list of contact uris, e.g. mailto:xxx */
+
+ int transitive; /* != 0 iff VirtualHost names/aliases are auto-added */
+ md_require_t require_https; /* Iff https: is required for this MD */
+
+ int renew_mode; /* mode of obtaining credentials */
+ struct md_pkeys_spec_t *pks; /* specification for generating private keys */
+ int must_staple; /* certificates should set the OCSP Must Staple extension */
+ md_timeslice_t *renew_window; /* time before expiration that starts renewal */
+ md_timeslice_t *warn_window; /* time before expiration that warnings are sent out */
+
+ const char *ca_proto; /* protocol used vs CA (e.g. ACME) */
+ struct apr_array_header_t *ca_urls; /* urls of CAs */
+ const char *ca_effective; /* url of CA used */
+ const char *ca_account; /* account used at CA */
+ const char *ca_agreement; /* accepted agreement uri between CA and user */
+ struct apr_array_header_t *ca_challenges; /* challenge types configured for this MD */
+ struct apr_array_header_t *cert_files; /* != NULL iff pubcerts explicitly configured */
+ struct apr_array_header_t *pkey_files; /* != NULL iff privkeys explicitly configured */
+ const char *ca_eab_kid; /* optional KEYID for external account binding */
+ const char *ca_eab_hmac; /* optional HMAC for external account binding */
+
+ md_state_t state; /* state of this MD */
+ const char *state_descr; /* description of state of NULL */
+
+ struct apr_array_header_t *acme_tls_1_domains; /* domains supporting "acme-tls/1" protocol */
+ int stapling; /* if OCSP stapling is enabled */
+ const char *dns01_cmd; /* DNS challenge command, override global command */
+
+ int watched; /* if certificate is supervised (renew or expiration warning) */
+ const struct md_srv_conf_t *sc; /* server config where it was defined or NULL */
+ const char *defn_name; /* config file this MD was defined */
+ unsigned defn_line_number; /* line number of definition */
+
+ const char *configured_name; /* name this MD was configured with, if different */
+};
+
+#define MD_KEY_ACCOUNT "account"
+#define MD_KEY_ACME_TLS_1 "acme-tls/1"
+#define MD_KEY_ACTIVATION_DELAY "activation-delay"
+#define MD_KEY_ACTIVITY "activity"
+#define MD_KEY_AGREEMENT "agreement"
+#define MD_KEY_AUTHORIZATIONS "authorizations"
+#define MD_KEY_BITS "bits"
+#define MD_KEY_CA "ca"
+#define MD_KEY_CA_URL "ca-url"
+#define MD_KEY_CERT "cert"
+#define MD_KEY_CERT_FILES "cert-files"
+#define MD_KEY_CERTIFICATE "certificate"
+#define MD_KEY_CHALLENGE "challenge"
+#define MD_KEY_CHALLENGES "challenges"
+#define MD_KEY_CMD_DNS01 "cmd-dns-01"
+#define MD_KEY_COMPLETE "complete"
+#define MD_KEY_CONTACT "contact"
+#define MD_KEY_CONTACTS "contacts"
+#define MD_KEY_CSR "csr"
+#define MD_KEY_CURVE "curve"
+#define MD_KEY_DETAIL "detail"
+#define MD_KEY_DISABLED "disabled"
+#define MD_KEY_DIR "dir"
+#define MD_KEY_DOMAIN "domain"
+#define MD_KEY_DOMAINS "domains"
+#define MD_KEY_EAB "eab"
+#define MD_KEY_EAB_REQUIRED "externalAccountRequired"
+#define MD_KEY_ENTRIES "entries"
+#define MD_KEY_ERRORED "errored"
+#define MD_KEY_ERROR "error"
+#define MD_KEY_ERRORS "errors"
+#define MD_KEY_EXPIRES "expires"
+#define MD_KEY_FINALIZE "finalize"
+#define MD_KEY_FINISHED "finished"
+#define MD_KEY_FROM "from"
+#define MD_KEY_GOOD "good"
+#define MD_KEY_HMAC "hmac"
+#define MD_KEY_HTTP "http"
+#define MD_KEY_HTTPS "https"
+#define MD_KEY_ID "id"
+#define MD_KEY_IDENTIFIER "identifier"
+#define MD_KEY_KEY "key"
+#define MD_KEY_KID "kid"
+#define MD_KEY_KEYAUTHZ "keyAuthorization"
+#define MD_KEY_LAST "last"
+#define MD_KEY_LAST_RUN "last-run"
+#define MD_KEY_LOCATION "location"
+#define MD_KEY_LOG "log"
+#define MD_KEY_MDS "managed-domains"
+#define MD_KEY_MESSAGE "message"
+#define MD_KEY_MUST_STAPLE "must-staple"
+#define MD_KEY_NAME "name"
+#define MD_KEY_NEXT_RUN "next-run"
+#define MD_KEY_NOTIFIED "notified"
+#define MD_KEY_NOTIFIED_RENEWED "notified-renewed"
+#define MD_KEY_OCSP "ocsp"
+#define MD_KEY_OCSPS "ocsps"
+#define MD_KEY_ORDERS "orders"
+#define MD_KEY_PERMANENT "permanent"
+#define MD_KEY_PKEY "privkey"
+#define MD_KEY_PKEY_FILES "pkey-files"
+#define MD_KEY_PROBLEM "problem"
+#define MD_KEY_PROTO "proto"
+#define MD_KEY_READY "ready"
+#define MD_KEY_REGISTRATION "registration"
+#define MD_KEY_RENEW "renew"
+#define MD_KEY_RENEW_AT "renew-at"
+#define MD_KEY_RENEW_MODE "renew-mode"
+#define MD_KEY_RENEWAL "renewal"
+#define MD_KEY_RENEWING "renewing"
+#define MD_KEY_RENEW_WINDOW "renew-window"
+#define MD_KEY_REQUIRE_HTTPS "require-https"
+#define MD_KEY_RESOURCE "resource"
+#define MD_KEY_RESPONSE "response"
+#define MD_KEY_REVOKED "revoked"
+#define MD_KEY_SERIAL "serial"
+#define MD_KEY_SHA256_FINGERPRINT "sha256-fingerprint"
+#define MD_KEY_STAPLING "stapling"
+#define MD_KEY_STATE "state"
+#define MD_KEY_STATE_DESCR "state-descr"
+#define MD_KEY_STATUS "status"
+#define MD_KEY_STORE "store"
+#define MD_KEY_SUBPROBLEMS "subproblems"
+#define MD_KEY_TEMPORARY "temporary"
+#define MD_KEY_TOS "termsOfService"
+#define MD_KEY_TOKEN "token"
+#define MD_KEY_TOTAL "total"
+#define MD_KEY_TRANSITIVE "transitive"
+#define MD_KEY_TYPE "type"
+#define MD_KEY_UNKNOWN "unknown"
+#define MD_KEY_UNTIL "until"
+#define MD_KEY_URL "url"
+#define MD_KEY_URLS "urls"
+#define MD_KEY_URI "uri"
+#define MD_KEY_VALID "valid"
+#define MD_KEY_VALID_FROM "valid-from"
+#define MD_KEY_VALUE "value"
+#define MD_KEY_VERSION "version"
+#define MD_KEY_WATCHED "watched"
+#define MD_KEY_WHEN "when"
+#define MD_KEY_WARN_WINDOW "warn-window"
+
+/* Check if a string member of a new MD (n) has
+ * a value and if it differs from the old MD o
+ */
+#define MD_VAL_UPDATE(n,o,s) ((n)->s != (o)->s)
+#define MD_SVAL_UPDATE(n,o,s) ((n)->s && (!(o)->s || strcmp((n)->s, (o)->s)))
+
+/**
+ * Determine if the Managed Domain contains a specific domain name.
+ */
+int md_contains(const md_t *md, const char *domain, int case_sensitive);
+
+/**
+ * Determine if the names of the two managed domains overlap.
+ */
+int md_domains_overlap(const md_t *md1, const md_t *md2);
+
+/**
+ * Determine if the domain names are equal.
+ */
+int md_equal_domains(const md_t *md1, const md_t *md2, int case_sensitive);
+
+/**
+ * Determine if the domains in md1 contain all domains of md2.
+ */
+int md_contains_domains(const md_t *md1, const md_t *md2);
+
+/**
+ * Get one common domain name of the two managed domains or NULL.
+ */
+const char *md_common_name(const md_t *md1, const md_t *md2);
+
+/**
+ * Get the number of common domains.
+ */
+apr_size_t md_common_name_count(const md_t *md1, const md_t *md2);
+
+/**
+ * Look up a managed domain by its name.
+ */
+md_t *md_get_by_name(struct apr_array_header_t *mds, const char *name);
+
+/**
+ * Look up a managed domain by a DNS name it contains.
+ */
+md_t *md_get_by_domain(struct apr_array_header_t *mds, const char *domain);
+
+/**
+ * Find a managed domain, different from the given one, that has overlaps
+ * in the domain list.
+ */
+md_t *md_get_by_dns_overlap(struct apr_array_header_t *mds, const md_t *md);
+
+/**
+ * Create and empty md record, structures initialized.
+ */
+md_t *md_create_empty(apr_pool_t *p);
+
+/**
+ * Create a managed domain, given a list of domain names.
+ */
+md_t *md_create(apr_pool_t *p, struct apr_array_header_t *domains);
+
+/**
+ * Deep copy an md record into another pool.
+ */
+md_t *md_clone(apr_pool_t *p, const md_t *src);
+
+/**
+ * Shallow copy an md record into another pool.
+ */
+md_t *md_copy(apr_pool_t *p, const md_t *src);
+
+/**
+ * Convert the managed domain into a JSON representation and vice versa.
+ *
+ * This reads and writes the following information: name, domains, ca_url, ca_proto and state.
+ */
+struct md_json_t *md_to_json(const md_t *md, apr_pool_t *p);
+md_t *md_from_json(struct md_json_t *json, apr_pool_t *p);
+
+/**
+ * Same as md_to_json(), but with sensitive fields stripped.
+ */
+struct md_json_t *md_to_public_json(const md_t *md, apr_pool_t *p);
+
+int md_is_covered_by_alt_names(const md_t *md, const struct apr_array_header_t* alt_names);
+
+/* how many certificates this domain has/will eventually have. */
+int md_cert_count(const md_t *md);
+
+const char *md_get_ca_name_from_url(apr_pool_t *p, const char *url);
+apr_status_t md_get_ca_url_from_name(const char **purl, apr_pool_t *p, const char *name);
+
+/**************************************************************************************************/
+/* notifications */
+
+typedef apr_status_t md_job_notify_cb(struct md_job_t *job, const char *reason,
+ struct md_result_t *result, apr_pool_t *p, void *baton);
+
+/**************************************************************************************************/
+/* domain credentials */
+
+typedef struct md_pubcert_t md_pubcert_t;
+struct md_pubcert_t {
+ struct apr_array_header_t *certs; /* chain of const md_cert*, leaf cert first */
+ struct apr_array_header_t *alt_names; /* alt-names of leaf cert */
+ const char *cert_file; /* file path of chain */
+ const char *key_file; /* file path of key for leaf cert */
+};
+
+#define MD_OK(c) (APR_SUCCESS == (rv = c))
+
+#endif /* mod_md_md_h */
diff --git a/modules/md/md_acme.c b/modules/md/md_acme.c
new file mode 100644
index 0000000..4366bf6
--- /dev/null
+++ b/modules/md/md_acme.c
@@ -0,0 +1,797 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include <apr_lib.h>
+#include <apr_strings.h>
+#include <apr_buckets.h>
+#include <apr_hash.h>
+#include <apr_uri.h>
+
+#include "md.h"
+#include "md_crypt.h"
+#include "md_json.h"
+#include "md_jws.h"
+#include "md_http.h"
+#include "md_log.h"
+#include "md_store.h"
+#include "md_result.h"
+#include "md_util.h"
+#include "md_version.h"
+
+#include "md_acme.h"
+#include "md_acme_acct.h"
+
+
+static const char *base_product= "-";
+
+typedef struct acme_problem_status_t acme_problem_status_t;
+
+struct acme_problem_status_t {
+ const char *type; /* the ACME error string */
+ apr_status_t rv; /* what Apache status code we give it */
+ int input_related; /* if error indicates wrong input value */
+};
+
+static acme_problem_status_t Problems[] = {
+ { "acme:error:badCSR", APR_EINVAL, 1 },
+ { "acme:error:badNonce", APR_EAGAIN, 0 },
+ { "acme:error:badSignatureAlgorithm", APR_EINVAL, 1 },
+ { "acme:error:externalAccountRequired", APR_EINVAL, 1 },
+ { "acme:error:invalidContact", APR_BADARG, 1 },
+ { "acme:error:unsupportedContact", APR_EGENERAL, 1 },
+ { "acme:error:malformed", APR_EINVAL, 1 },
+ { "acme:error:rateLimited", APR_BADARG, 0 },
+ { "acme:error:rejectedIdentifier", APR_BADARG, 1 },
+ { "acme:error:serverInternal", APR_EGENERAL, 0 },
+ { "acme:error:unauthorized", APR_EACCES, 0 },
+ { "acme:error:unsupportedIdentifier", APR_BADARG, 1 },
+ { "acme:error:userActionRequired", APR_EAGAIN, 0 },
+ { "acme:error:badRevocationReason", APR_EINVAL, 1 },
+ { "acme:error:caa", APR_EGENERAL, 0 },
+ { "acme:error:dns", APR_EGENERAL, 0 },
+ { "acme:error:connection", APR_EGENERAL, 0 },
+ { "acme:error:tls", APR_EGENERAL, 0 },
+ { "acme:error:incorrectResponse", APR_EGENERAL, 0 },
+};
+
+static apr_status_t problem_status_get(const char *type) {
+ size_t i;
+
+ if (strstr(type, "urn:ietf:params:") == type) {
+ type += strlen("urn:ietf:params:");
+ }
+ else if (strstr(type, "urn:") == type) {
+ type += strlen("urn:");
+ }
+
+ for(i = 0; i < (sizeof(Problems)/sizeof(Problems[0])); ++i) {
+ if (!apr_strnatcasecmp(type, Problems[i].type)) {
+ return Problems[i].rv;
+ }
+ }
+ return APR_EGENERAL;
+}
+
+int md_acme_problem_is_input_related(const char *problem) {
+ size_t i;
+
+ if (!problem) return 0;
+ if (strstr(problem, "urn:ietf:params:") == problem) {
+ problem += strlen("urn:ietf:params:");
+ }
+ else if (strstr(problem, "urn:") == problem) {
+ problem += strlen("urn:");
+ }
+
+ for(i = 0; i < (sizeof(Problems)/sizeof(Problems[0])); ++i) {
+ if (!apr_strnatcasecmp(problem, Problems[i].type)) {
+ return Problems[i].input_related;
+ }
+ }
+ return 0;
+}
+
+/**************************************************************************************************/
+/* acme requests */
+
+static void req_update_nonce(md_acme_t *acme, apr_table_t *hdrs)
+{
+ if (hdrs) {
+ const char *nonce = apr_table_get(hdrs, "Replay-Nonce");
+ if (nonce) {
+ acme->nonce = apr_pstrdup(acme->p, nonce);
+ }
+ }
+}
+
+static apr_status_t http_update_nonce(const md_http_response_t *res, void *data)
+{
+ req_update_nonce(data, res->headers);
+ return APR_SUCCESS;
+}
+
+static md_acme_req_t *md_acme_req_create(md_acme_t *acme, const char *method, const char *url)
+{
+ apr_pool_t *pool;
+ md_acme_req_t *req;
+ apr_status_t rv;
+
+ rv = apr_pool_create(&pool, acme->p);
+ if (rv != APR_SUCCESS) {
+ return NULL;
+ }
+ apr_pool_tag(pool, "md_acme_req");
+
+ req = apr_pcalloc(pool, sizeof(*req));
+ if (!req) {
+ apr_pool_destroy(pool);
+ return NULL;
+ }
+
+ req->acme = acme;
+ req->p = pool;
+ req->method = method;
+ req->url = url;
+ req->prot_fields = md_json_create(pool);
+ req->max_retries = acme->max_retries;
+ req->result = md_result_make(req->p, APR_SUCCESS);
+ return req;
+}
+
+static apr_status_t acmev2_new_nonce(md_acme_t *acme)
+{
+ return md_http_HEAD_perform(acme->http, acme->api.v2.new_nonce, NULL, http_update_nonce, acme);
+}
+
+
+apr_status_t md_acme_init(apr_pool_t *p, const char *base, int init_ssl)
+{
+ base_product = base;
+ return init_ssl? md_crypt_init(p) : APR_SUCCESS;
+}
+
+static apr_status_t inspect_problem(md_acme_req_t *req, const md_http_response_t *res)
+{
+ const char *ctype;
+ md_json_t *problem = NULL;
+ apr_status_t rv;
+
+ ctype = apr_table_get(req->resp_hdrs, "content-type");
+ ctype = md_util_parse_ct(res->req->pool, ctype);
+ if (ctype && !strcmp(ctype, "application/problem+json")) {
+ /* RFC 7807 */
+ rv = md_json_read_http(&problem, req->p, res);
+ if (rv == APR_SUCCESS && problem) {
+ const char *ptype, *pdetail;
+
+ req->resp_json = problem;
+ ptype = md_json_gets(problem, MD_KEY_TYPE, NULL);
+ pdetail = md_json_gets(problem, MD_KEY_DETAIL, NULL);
+ req->rv = problem_status_get(ptype);
+ md_result_problem_set(req->result, req->rv, ptype, pdetail,
+ md_json_getj(problem, MD_KEY_SUBPROBLEMS, NULL));
+
+
+
+ if (APR_STATUS_IS_EAGAIN(req->rv)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, req->rv, req->p,
+ "acme reports %s: %s", ptype, pdetail);
+ }
+ else {
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, req->rv, req->p,
+ "acme problem %s: %s", ptype, pdetail);
+ }
+ return req->rv;
+ }
+ }
+
+ switch (res->status) {
+ case 400:
+ return APR_EINVAL;
+ case 401: /* sectigo returns this instead of 403 */
+ case 403:
+ return APR_EACCES;
+ case 404:
+ return APR_ENOENT;
+ default:
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, req->p,
+ "acme problem unknown: http status %d", res->status);
+ md_result_printf(req->result, APR_EGENERAL, "unexpected http status: %d",
+ res->status);
+ return req->result->status;
+ }
+ return APR_SUCCESS;
+}
+
+/**************************************************************************************************/
+/* ACME requests with nonce handling */
+
+static apr_status_t acmev2_req_init(md_acme_req_t *req, md_json_t *jpayload)
+{
+ md_data_t payload;
+
+ md_data_null(&payload);
+ if (!req->acme->acct) {
+ return APR_EINVAL;
+ }
+ if (jpayload) {
+ payload.data = md_json_writep(jpayload, req->p, MD_JSON_FMT_COMPACT);
+ if (!payload.data) {
+ return APR_EINVAL;
+ }
+ }
+ else {
+ payload.data = "";
+ }
+
+ payload.len = strlen(payload.data);
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, req->p,
+ "acme payload(len=%" APR_SIZE_T_FMT "): %s", payload.len, payload.data);
+ return md_jws_sign(&req->req_json, req->p, &payload,
+ req->prot_fields, req->acme->acct_key, req->acme->acct->url);
+}
+
+apr_status_t md_acme_req_body_init(md_acme_req_t *req, md_json_t *payload)
+{
+ return req->acme->req_init_fn(req, payload);
+}
+
+static apr_status_t md_acme_req_done(md_acme_req_t *req, apr_status_t rv)
+{
+ if (req->result->status != APR_SUCCESS) {
+ if (req->on_err) {
+ req->on_err(req, req->result, req->baton);
+ }
+ }
+ /* An error in rv superceeds the result->status */
+ if (APR_SUCCESS != rv) req->result->status = rv;
+ rv = req->result->status;
+ /* transfer results into the acme's central result for longer life and later inspection */
+ md_result_dup(req->acme->last, req->result);
+ if (req->p) {
+ apr_pool_destroy(req->p);
+ }
+ return rv;
+}
+
+static apr_status_t on_response(const md_http_response_t *res, void *data)
+{
+ md_acme_req_t *req = data;
+ apr_status_t rv = APR_SUCCESS;
+
+ req->resp_hdrs = apr_table_clone(req->p, res->headers);
+ req_update_nonce(req->acme, res->headers);
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, req->p, "response: %d", res->status);
+ if (res->status >= 200 && res->status < 300) {
+ int processed = 0;
+
+ if (req->on_json) {
+ processed = 1;
+ rv = md_json_read_http(&req->resp_json, req->p, res);
+ if (APR_SUCCESS == rv) {
+ if (md_log_is_level(req->p, MD_LOG_TRACE2)) {
+ const char *s;
+ s = md_json_writep(req->resp_json, req->p, MD_JSON_FMT_INDENT);
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, req->p,
+ "response: %s",
+ s ? s : "<failed to serialize!>");
+ }
+ rv = req->on_json(req->acme, req->p, req->resp_hdrs, req->resp_json, req->baton);
+ }
+ else if (APR_STATUS_IS_ENOENT(rv)) {
+ /* not JSON content, fall through */
+ processed = 0;
+ }
+ else {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, req->p, "parsing JSON body");
+ }
+ }
+
+ if (!processed && req->on_res) {
+ processed = 1;
+ rv = req->on_res(req->acme, res, req->baton);
+ }
+
+ if (!processed) {
+ rv = APR_EINVAL;
+ md_result_printf(req->result, rv, "unable to process the response: "
+ "http-status=%d, content-type=%s",
+ res->status, apr_table_get(res->headers, "Content-Type"));
+ md_result_log(req->result, MD_LOG_ERR);
+ }
+ }
+ else if (APR_EAGAIN == (rv = inspect_problem(req, res))) {
+ /* leave req alive */
+ return rv;
+ }
+
+ md_acme_req_done(req, rv);
+ return rv;
+}
+
+static apr_status_t acmev2_GET_as_POST_init(md_acme_req_t *req, void *baton)
+{
+ (void)baton;
+ return md_acme_req_body_init(req, NULL);
+}
+
+static apr_status_t md_acme_req_send(md_acme_req_t *req)
+{
+ apr_status_t rv;
+ md_acme_t *acme = req->acme;
+ md_data_t *body = NULL;
+ md_result_t *result;
+
+ assert(acme->url);
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, req->p,
+ "sending req: %s %s", req->method, req->url);
+ md_result_reset(req->acme->last);
+ result = md_result_make(req->p, APR_SUCCESS);
+
+ /* Whom are we talking to? */
+ if (acme->version == MD_ACME_VERSION_UNKNOWN) {
+ rv = md_acme_setup(acme, result);
+ if (APR_SUCCESS != rv) goto leave;
+ }
+
+ if (!strcmp("GET", req->method) && !req->on_init && !req->req_json) {
+ /* See <https://ietf-wg-acme.github.io/acme/draft-ietf-acme-acme.html#rfc.section.6.3>
+ * and <https://mailarchive.ietf.org/arch/msg/acme/sotffSQ0OWV-qQJodLwWYWcEVKI>
+ * and <https://community.letsencrypt.org/t/acme-v2-scheduled-deprecation-of-unauthenticated-resource-gets/74380>
+ * We implement this change in ACMEv2 and higher as keeping the md_acme_GET() methods,
+ * but switching them to POSTs with a empty, JWS signed, body when we call
+ * our HTTP client. */
+ req->method = "POST";
+ req->on_init = acmev2_GET_as_POST_init;
+ /*req->max_retries = 0; don't do retries on these "GET"s */
+ }
+
+ /* Besides GET/HEAD, we always need a fresh nonce */
+ if (strcmp("GET", req->method) && strcmp("HEAD", req->method)) {
+ if (acme->version == MD_ACME_VERSION_UNKNOWN) {
+ rv = md_acme_setup(acme, result);
+ if (APR_SUCCESS != rv) goto leave;
+ }
+ if (!acme->nonce && (APR_SUCCESS != (rv = acme->new_nonce_fn(acme)))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, req->p,
+ "error retrieving new nonce from ACME server");
+ goto leave;
+ }
+
+ md_json_sets(acme->nonce, req->prot_fields, "nonce", NULL);
+ md_json_sets(req->url, req->prot_fields, "url", NULL);
+ acme->nonce = NULL;
+ }
+
+ rv = req->on_init? req->on_init(req, req->baton) : APR_SUCCESS;
+ if (APR_SUCCESS != rv) goto leave;
+
+ if (req->req_json) {
+ body = apr_pcalloc(req->p, sizeof(*body));
+ body->data = md_json_writep(req->req_json, req->p, MD_JSON_FMT_INDENT);
+ body->len = strlen(body->data);
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, req->p,
+ "sending JSON body: %s", body->data);
+ }
+
+ if (body && md_log_is_level(req->p, MD_LOG_TRACE4)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, req->p,
+ "req: %s %s, body:\n%s", req->method, req->url, body->data);
+ }
+ else {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, req->p,
+ "req: %s %s", req->method, req->url);
+ }
+
+ if (!strcmp("GET", req->method)) {
+ rv = md_http_GET_perform(req->acme->http, req->url, NULL, on_response, req);
+ }
+ else if (!strcmp("POST", req->method)) {
+ rv = md_http_POSTd_perform(req->acme->http, req->url, NULL, "application/jose+json",
+ body, on_response, req);
+ }
+ else if (!strcmp("HEAD", req->method)) {
+ rv = md_http_HEAD_perform(req->acme->http, req->url, NULL, on_response, req);
+ }
+ else {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, req->p,
+ "HTTP method %s against: %s", req->method, req->url);
+ rv = APR_ENOTIMPL;
+ }
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, req->p, "req sent");
+
+ if (APR_EAGAIN == rv && req->max_retries > 0) {
+ --req->max_retries;
+ rv = md_acme_req_send(req);
+ }
+ req = NULL;
+
+leave:
+ if (req) md_acme_req_done(req, rv);
+ return rv;
+}
+
+apr_status_t md_acme_POST(md_acme_t *acme, const char *url,
+ md_acme_req_init_cb *on_init,
+ md_acme_req_json_cb *on_json,
+ md_acme_req_res_cb *on_res,
+ md_acme_req_err_cb *on_err,
+ void *baton)
+{
+ md_acme_req_t *req;
+
+ assert(url);
+ assert(on_json || on_res);
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, acme->p, "add acme POST: %s", url);
+ req = md_acme_req_create(acme, "POST", url);
+ req->on_init = on_init;
+ req->on_json = on_json;
+ req->on_res = on_res;
+ req->on_err = on_err;
+ req->baton = baton;
+
+ return md_acme_req_send(req);
+}
+
+apr_status_t md_acme_GET(md_acme_t *acme, const char *url,
+ md_acme_req_init_cb *on_init,
+ md_acme_req_json_cb *on_json,
+ md_acme_req_res_cb *on_res,
+ md_acme_req_err_cb *on_err,
+ void *baton)
+{
+ md_acme_req_t *req;
+
+ assert(url);
+ assert(on_json || on_res);
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, acme->p, "add acme GET: %s", url);
+ req = md_acme_req_create(acme, "GET", url);
+ req->on_init = on_init;
+ req->on_json = on_json;
+ req->on_res = on_res;
+ req->on_err = on_err;
+ req->baton = baton;
+
+ return md_acme_req_send(req);
+}
+
+void md_acme_report_result(md_acme_t *acme, apr_status_t rv, struct md_result_t *result)
+{
+ if (acme->last->status == APR_SUCCESS) {
+ md_result_set(result, rv, NULL);
+ }
+ else {
+ md_result_problem_set(result, acme->last->status, acme->last->problem,
+ acme->last->detail, acme->last->subproblems);
+ }
+}
+
+/**************************************************************************************************/
+/* GET JSON */
+
+typedef struct {
+ apr_pool_t *pool;
+ md_json_t *json;
+} json_ctx;
+
+static apr_status_t on_got_json(md_acme_t *acme, apr_pool_t *p, const apr_table_t *headers,
+ md_json_t *jbody, void *baton)
+{
+ json_ctx *ctx = baton;
+
+ (void)acme;
+ (void)p;
+ (void)headers;
+ ctx->json = md_json_clone(ctx->pool, jbody);
+ return APR_SUCCESS;
+}
+
+apr_status_t md_acme_get_json(struct md_json_t **pjson, md_acme_t *acme,
+ const char *url, apr_pool_t *p)
+{
+ apr_status_t rv;
+ json_ctx ctx;
+
+ ctx.pool = p;
+ ctx.json = NULL;
+
+ rv = md_acme_GET(acme, url, NULL, on_got_json, NULL, NULL, &ctx);
+ *pjson = (APR_SUCCESS == rv)? ctx.json : NULL;
+ return rv;
+}
+
+/**************************************************************************************************/
+/* Generic ACME operations */
+
+void md_acme_clear_acct(md_acme_t *acme)
+{
+ acme->acct_id = NULL;
+ acme->acct = NULL;
+ acme->acct_key = NULL;
+}
+
+const char *md_acme_acct_id_get(md_acme_t *acme)
+{
+ return acme->acct_id;
+}
+
+const char *md_acme_acct_url_get(md_acme_t *acme)
+{
+ return acme->acct? acme->acct->url : NULL;
+}
+
+apr_status_t md_acme_use_acct(md_acme_t *acme, md_store_t *store,
+ apr_pool_t *p, const char *acct_id)
+{
+ md_acme_acct_t *acct;
+ md_pkey_t *pkey;
+ apr_status_t rv;
+
+ if (APR_SUCCESS == (rv = md_acme_acct_load(&acct, &pkey,
+ store, MD_SG_ACCOUNTS, acct_id, acme->p))) {
+ if (md_acme_acct_matches_url(acct, acme->url)) {
+ acme->acct_id = apr_pstrdup(p, acct_id);
+ acme->acct = acct;
+ acme->acct_key = pkey;
+ rv = md_acme_acct_validate(acme, store, p);
+ }
+ else {
+ /* account is from another server or, more likely, from another
+ * protocol endpoint on the same server */
+ rv = APR_ENOENT;
+ }
+ }
+ return rv;
+}
+
+apr_status_t md_acme_use_acct_for_md(md_acme_t *acme, struct md_store_t *store,
+ apr_pool_t *p, const char *acct_id,
+ const md_t *md)
+{
+ md_acme_acct_t *acct;
+ md_pkey_t *pkey;
+ apr_status_t rv;
+
+ if (APR_SUCCESS == (rv = md_acme_acct_load(&acct, &pkey,
+ store, MD_SG_ACCOUNTS, acct_id, acme->p))) {
+ if (md_acme_acct_matches_md(acct, md)) {
+ acme->acct_id = apr_pstrdup(p, acct_id);
+ acme->acct = acct;
+ acme->acct_key = pkey;
+ rv = md_acme_acct_validate(acme, store, p);
+ }
+ else {
+ /* account is from another server or, more likely, from another
+ * protocol endpoint on the same server */
+ rv = APR_ENOENT;
+ }
+ }
+ return rv;
+}
+
+apr_status_t md_acme_save_acct(md_acme_t *acme, apr_pool_t *p, md_store_t *store)
+{
+ return md_acme_acct_save(store, p, acme, &acme->acct_id, acme->acct, acme->acct_key);
+}
+
+static apr_status_t acmev2_POST_new_account(md_acme_t *acme,
+ md_acme_req_init_cb *on_init,
+ md_acme_req_json_cb *on_json,
+ md_acme_req_res_cb *on_res,
+ md_acme_req_err_cb *on_err,
+ void *baton)
+{
+ return md_acme_POST(acme, acme->api.v2.new_account, on_init, on_json, on_res, on_err, baton);
+}
+
+apr_status_t md_acme_POST_new_account(md_acme_t *acme,
+ md_acme_req_init_cb *on_init,
+ md_acme_req_json_cb *on_json,
+ md_acme_req_res_cb *on_res,
+ md_acme_req_err_cb *on_err,
+ void *baton)
+{
+ return acme->post_new_account_fn(acme, on_init, on_json, on_res, on_err, baton);
+}
+
+/**************************************************************************************************/
+/* ACME setup */
+
+apr_status_t md_acme_create(md_acme_t **pacme, apr_pool_t *p, const char *url,
+ const char *proxy_url, const char *ca_file)
+{
+ md_acme_t *acme;
+ const char *err = NULL;
+ apr_status_t rv;
+ apr_uri_t uri_parsed;
+ size_t len;
+
+ if (!url) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, p, "create ACME without url");
+ return APR_EINVAL;
+ }
+
+ if (APR_SUCCESS != (rv = md_util_abs_uri_check(p, url, &err))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "invalid ACME uri (%s): %s", err, url);
+ return rv;
+ }
+
+ acme = apr_pcalloc(p, sizeof(*acme));
+ acme->url = url;
+ acme->p = p;
+ acme->user_agent = apr_psprintf(p, "%s mod_md/%s",
+ base_product, MOD_MD_VERSION);
+ acme->proxy_url = proxy_url? apr_pstrdup(p, proxy_url) : NULL;
+ acme->max_retries = 99;
+ acme->ca_file = ca_file;
+
+ if (APR_SUCCESS != (rv = apr_uri_parse(p, url, &uri_parsed))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "parsing ACME uri: %s", url);
+ return APR_EINVAL;
+ }
+
+ len = strlen(uri_parsed.hostname);
+ acme->sname = (len <= 16)? uri_parsed.hostname : apr_pstrdup(p, uri_parsed.hostname + len - 16);
+ acme->version = MD_ACME_VERSION_UNKNOWN;
+ acme->last = md_result_make(acme->p, APR_SUCCESS);
+
+ *pacme = acme;
+ return rv;
+}
+
+typedef struct {
+ md_acme_t *acme;
+ md_result_t *result;
+} update_dir_ctx;
+
+static apr_status_t update_directory(const md_http_response_t *res, void *data)
+{
+ md_http_request_t *req = res->req;
+ md_acme_t *acme = ((update_dir_ctx *)data)->acme;
+ md_result_t *result = ((update_dir_ctx *)data)->result;
+ apr_status_t rv;
+ md_json_t *json;
+ const char *s;
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, req->pool, "directory lookup response: %d", res->status);
+ if (res->status == 503) {
+ md_result_printf(result, APR_EAGAIN,
+ "The ACME server at <%s> reports that Service is Unavailable (503). This "
+ "may happen during maintenance for short periods of time.", acme->url);
+ md_result_log(result, MD_LOG_INFO);
+ rv = result->status;
+ goto leave;
+ }
+ else if (res->status < 200 || res->status >= 300) {
+ md_result_printf(result, APR_EAGAIN,
+ "The ACME server at <%s> responded with HTTP status %d. This "
+ "is unusual. Please verify that the URL is correct and that you can indeed "
+ "make request from the server to it by other means, e.g. invoking curl/wget.",
+ acme->url, res->status);
+ rv = result->status;
+ goto leave;
+ }
+
+ rv = md_json_read_http(&json, req->pool, res);
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, req->pool, "reading JSON body");
+ goto leave;
+ }
+
+ if (md_log_is_level(acme->p, MD_LOG_TRACE2)) {
+ s = md_json_writep(json, req->pool, MD_JSON_FMT_INDENT);
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, req->pool,
+ "response: %s", s ? s : "<failed to serialize!>");
+ }
+
+ /* What have we got? */
+ if ((s = md_json_dups(acme->p, json, "newAccount", NULL))) {
+ acme->api.v2.new_account = s;
+ acme->api.v2.new_order = md_json_dups(acme->p, json, "newOrder", NULL);
+ acme->api.v2.revoke_cert = md_json_dups(acme->p, json, "revokeCert", NULL);
+ acme->api.v2.key_change = md_json_dups(acme->p, json, "keyChange", NULL);
+ acme->api.v2.new_nonce = md_json_dups(acme->p, json, "newNonce", NULL);
+ /* RFC 8555 only requires "directory" and "newNonce" resources.
+ * mod_md uses "newAccount" and "newOrder" so check for them.
+ * But mod_md does not use the "revokeCert" or "keyChange"
+ * resources, so tolerate the absence of those keys. In the
+ * future if mod_md implements revocation or key rollover then
+ * the use of those features should be predicated on the
+ * server's advertised capabilities. */
+ if (acme->api.v2.new_account
+ && acme->api.v2.new_order
+ && acme->api.v2.new_nonce) {
+ acme->version = MD_ACME_VERSION_2;
+ }
+ acme->ca_agreement = md_json_dups(acme->p, json, "meta", MD_KEY_TOS, NULL);
+ acme->eab_required = md_json_getb(json, "meta", MD_KEY_EAB_REQUIRED, NULL);
+ acme->new_nonce_fn = acmev2_new_nonce;
+ acme->req_init_fn = acmev2_req_init;
+ acme->post_new_account_fn = acmev2_POST_new_account;
+ }
+ else if ((s = md_json_dups(acme->p, json, "new-authz", NULL))) {
+ acme->api.v1.new_authz = s;
+ acme->api.v1.new_cert = md_json_dups(acme->p, json, "new-cert", NULL);
+ acme->api.v1.new_reg = md_json_dups(acme->p, json, "new-reg", NULL);
+ acme->api.v1.revoke_cert = md_json_dups(acme->p, json, "revoke-cert", NULL);
+ if (acme->api.v1.new_authz && acme->api.v1.new_cert
+ && acme->api.v1.new_reg && acme->api.v1.revoke_cert) {
+ acme->version = MD_ACME_VERSION_1;
+ }
+ acme->ca_agreement = md_json_dups(acme->p, json, "meta", "terms-of-service", NULL);
+ /* we init that far, but will not use the v1 api */
+ }
+
+ if (MD_ACME_VERSION_UNKNOWN == acme->version) {
+ md_result_printf(result, APR_EINVAL,
+ "Unable to understand ACME server response from <%s>. "
+ "Wrong ACME protocol version or link?", acme->url);
+ md_result_log(result, MD_LOG_WARNING);
+ rv = result->status;
+ }
+leave:
+ return rv;
+}
+
+apr_status_t md_acme_setup(md_acme_t *acme, md_result_t *result)
+{
+ apr_status_t rv;
+ update_dir_ctx ctx;
+
+ assert(acme->url);
+ acme->version = MD_ACME_VERSION_UNKNOWN;
+
+ if (!acme->http && APR_SUCCESS != (rv = md_http_create(&acme->http, acme->p,
+ acme->user_agent, acme->proxy_url))) {
+ return rv;
+ }
+ /* TODO: maybe this should be configurable. Let's take some reasonable
+ * defaults for now that protect our client */
+ md_http_set_response_limit(acme->http, 1024*1024);
+ md_http_set_timeout_default(acme->http, apr_time_from_sec(10 * 60));
+ md_http_set_connect_timeout_default(acme->http, apr_time_from_sec(30));
+ md_http_set_stalling_default(acme->http, 10, apr_time_from_sec(30));
+ md_http_set_ca_file(acme->http, acme->ca_file);
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, acme->p, "get directory from %s", acme->url);
+
+ ctx.acme = acme;
+ ctx.result = result;
+ rv = md_http_GET_perform(acme->http, acme->url, NULL, update_directory, &ctx);
+
+ if (APR_SUCCESS != rv && APR_SUCCESS == result->status) {
+ /* If the result reports no error, we never got a response from the server */
+ md_result_printf(result, rv,
+ "Unsuccessful in contacting ACME server at <%s>. If this problem persists, "
+ "please check your network connectivity from your Apache server to the "
+ "ACME server. Also, older servers might have trouble verifying the certificates "
+ "of the ACME server. You can check if you are able to contact it manually via the "
+ "curl command. Sometimes, the ACME server might be down for maintenance, "
+ "so failing to contact it is not an immediate problem. Apache will "
+ "continue retrying this.", acme->url);
+ md_result_log(result, MD_LOG_WARNING);
+ }
+ return rv;
+}
+
+
diff --git a/modules/md/md_acme.h b/modules/md/md_acme.h
new file mode 100644
index 0000000..f28f2b6
--- /dev/null
+++ b/modules/md/md_acme.h
@@ -0,0 +1,317 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mod_md_md_acme_h
+#define mod_md_md_acme_h
+
+struct apr_array_header_t;
+struct apr_bucket_brigade;
+struct md_http_response_t;
+struct apr_hash_t;
+struct md_http_t;
+struct md_json_t;
+struct md_pkey_t;
+struct md_t;
+struct md_acme_acct_t;
+struct md_acmev2_acct_t;
+struct md_store_t;
+struct md_result_t;
+
+#define MD_PROTO_ACME "ACME"
+
+#define MD_AUTHZ_CHA_HTTP_01 "http-01"
+#define MD_AUTHZ_CHA_SNI_01 "tls-sni-01"
+
+#define MD_ACME_VERSION_UNKNOWN 0x0
+#define MD_ACME_VERSION_1 0x010000
+#define MD_ACME_VERSION_2 0x020000
+
+#define MD_ACME_VERSION_MAJOR(i) (((i)&0xFF0000) >> 16)
+
+typedef enum {
+ MD_ACME_S_UNKNOWN, /* MD has not been analysed yet */
+ MD_ACME_S_REGISTERED, /* MD is registered at CA, but not more */
+ MD_ACME_S_TOS_ACCEPTED, /* Terms of Service were accepted by account holder */
+ MD_ACME_S_CHALLENGED, /* MD challenge information for all domains is known */
+ MD_ACME_S_VALIDATED, /* MD domains have been validated */
+ MD_ACME_S_CERTIFIED, /* MD has valid certificate */
+ MD_ACME_S_DENIED, /* MD domains (at least one) have been denied by CA */
+} md_acme_state_t;
+
+typedef struct md_acme_t md_acme_t;
+
+typedef struct md_acme_req_t md_acme_req_t;
+/**
+ * Request callback on a successful HTTP response (status 2xx).
+ */
+typedef apr_status_t md_acme_req_res_cb(md_acme_t *acme,
+ const struct md_http_response_t *res, void *baton);
+
+/**
+ * Request callback to initialize before sending. May be invoked more than once in
+ * case of retries.
+ */
+typedef apr_status_t md_acme_req_init_cb(md_acme_req_t *req, void *baton);
+
+/**
+ * Request callback on a successful response (HTTP response code 2xx) and content
+ * type matching application/.*json.
+ */
+typedef apr_status_t md_acme_req_json_cb(md_acme_t *acme, apr_pool_t *p,
+ const apr_table_t *headers,
+ struct md_json_t *jbody, void *baton);
+
+/**
+ * Request callback on detected errors.
+ */
+typedef apr_status_t md_acme_req_err_cb(md_acme_req_t *req,
+ const struct md_result_t *result, void *baton);
+
+
+typedef apr_status_t md_acme_new_nonce_fn(md_acme_t *acme);
+typedef apr_status_t md_acme_req_init_fn(md_acme_req_t *req, struct md_json_t *jpayload);
+
+typedef apr_status_t md_acme_post_fn(md_acme_t *acme,
+ md_acme_req_init_cb *on_init,
+ md_acme_req_json_cb *on_json,
+ md_acme_req_res_cb *on_res,
+ md_acme_req_err_cb *on_err,
+ void *baton);
+
+struct md_acme_t {
+ const char *url; /* directory url of the ACME service */
+ const char *sname; /* short name for the service, not necessarily unique */
+ apr_pool_t *p;
+ const char *user_agent;
+ const char *proxy_url;
+ const char *ca_file;
+
+ const char *acct_id; /* local storage id account was loaded from or NULL */
+ struct md_acme_acct_t *acct; /* account at ACME server to use for requests */
+ struct md_pkey_t *acct_key; /* private RSA key belonging to account */
+
+ int version; /* as detected from the server */
+ union {
+ struct { /* obsolete */
+ const char *new_authz;
+ const char *new_cert;
+ const char *new_reg;
+ const char *revoke_cert;
+
+ } v1;
+ struct {
+ const char *new_account;
+ const char *new_order;
+ const char *key_change;
+ const char *revoke_cert;
+ const char *new_nonce;
+ } v2;
+ } api;
+ const char *ca_agreement;
+ const char *acct_name;
+ int eab_required;
+
+ md_acme_new_nonce_fn *new_nonce_fn;
+ md_acme_req_init_fn *req_init_fn;
+ md_acme_post_fn *post_new_account_fn;
+
+ struct md_http_t *http;
+
+ const char *nonce;
+ int max_retries;
+ struct md_result_t *last; /* result of last request */
+};
+
+/**
+ * Global init, call once at start up.
+ */
+apr_status_t md_acme_init(apr_pool_t *pool, const char *base_version, int init_ssl);
+
+/**
+ * Create a new ACME server instance. If path is not NULL, will use that directory
+ * for persisting information. Will load any information persisted in earlier session.
+ * url needs only be specified for instances where this has never been persisted before.
+ *
+ * @param pacme will hold the ACME server instance on success
+ * @param p pool to used
+ * @param url url of the server, optional if known at path
+ * @param proxy_url optional url of a HTTP(S) proxy to use
+ */
+apr_status_t md_acme_create(md_acme_t **pacme, apr_pool_t *p, const char *url,
+ const char *proxy_url, const char *ca_file);
+
+/**
+ * Contact the ACME server and retrieve its directory information.
+ *
+ * @param acme the ACME server to contact
+ */
+apr_status_t md_acme_setup(md_acme_t *acme, struct md_result_t *result);
+
+void md_acme_report_result(md_acme_t *acme, apr_status_t rv, struct md_result_t *result);
+
+/**************************************************************************************************/
+/* account handling */
+
+/**
+ * Clear any existing account data from acme instance.
+ */
+void md_acme_clear_acct(md_acme_t *acme);
+
+apr_status_t md_acme_POST_new_account(md_acme_t *acme,
+ md_acme_req_init_cb *on_init,
+ md_acme_req_json_cb *on_json,
+ md_acme_req_res_cb *on_res,
+ md_acme_req_err_cb *on_err,
+ void *baton);
+
+/**
+ * Get the local name of the account currently used by the acme instance.
+ * Will be NULL if no account has been setup successfully.
+ */
+const char *md_acme_acct_id_get(md_acme_t *acme);
+const char *md_acme_acct_url_get(md_acme_t *acme);
+
+/**
+ * Specify the account to use by name in local store. On success, the account
+ * is the "current" one used by the acme instance.
+ * @param acme the acme instance to set the account for
+ * @param store the store to load accounts from
+ * @param p pool for allocations
+ * @param acct_id name of the account to load
+ */
+apr_status_t md_acme_use_acct(md_acme_t *acme, struct md_store_t *store,
+ apr_pool_t *p, const char *acct_id);
+
+/**
+ * Specify the account to use for a specific MD by name in local store.
+ * On success, the account is the "current" one used by the acme instance.
+ * @param acme the acme instance to set the account for
+ * @param store the store to load accounts from
+ * @param p pool for allocations
+ * @param acct_id name of the account to load
+ * @param md the MD the account shall be used for
+ */
+apr_status_t md_acme_use_acct_for_md(md_acme_t *acme, struct md_store_t *store,
+ apr_pool_t *p, const char *acct_id,
+ const md_t *md);
+
+/**
+ * Get the local name of the account currently used by the acme instance.
+ * Will be NULL if no account has been setup successfully.
+ */
+const char *md_acme_acct_id_get(md_acme_t *acme);
+
+/**
+ * Agree to the given Terms-of-Service url for the current account.
+ */
+apr_status_t md_acme_agree(md_acme_t *acme, apr_pool_t *p, const char *tos);
+
+/**
+ * Confirm with the server that the current account agrees to the Terms-of-Service
+ * given in the agreement url.
+ * If the known agreement is equal to this, nothing is done.
+ * If it differs, the account is re-validated in the hope that the server
+ * announces the Tos URL it wants. If this is equal to the agreement specified,
+ * the server is notified of this. If the server requires a ToS that the account
+ * thinks it has already given, it is resend.
+ *
+ * If an agreement is required, different from the current one, APR_INCOMPLETE is
+ * returned and the agreement url is returned in the parameter.
+ */
+apr_status_t md_acme_check_agreement(md_acme_t *acme, apr_pool_t *p,
+ const char *agreement, const char **prequired);
+
+apr_status_t md_acme_save_acct(md_acme_t *acme, apr_pool_t *p, struct md_store_t *store);
+
+/**
+ * Deactivate the current account at the ACME server..
+ */
+apr_status_t md_acme_acct_deactivate(md_acme_t *acme, apr_pool_t *p);
+
+/**************************************************************************************************/
+/* request handling */
+
+struct md_acme_req_t {
+ md_acme_t *acme; /* the ACME server to talk to */
+ apr_pool_t *p; /* pool for the request duration */
+
+ const char *url; /* url to POST the request to */
+ const char *method; /* HTTP method to use */
+ struct md_json_t *prot_fields; /* JWS protected fields */
+ struct md_json_t *req_json; /* JSON to be POSTed in request body */
+
+ apr_table_t *resp_hdrs; /* HTTP response headers */
+ struct md_json_t *resp_json; /* JSON response body received */
+
+ apr_status_t rv; /* status of request */
+
+ md_acme_req_init_cb *on_init; /* callback to initialize the request before submit */
+ md_acme_req_json_cb *on_json; /* callback on successful JSON response */
+ md_acme_req_res_cb *on_res; /* callback on generic HTTP response */
+ md_acme_req_err_cb *on_err; /* callback on encountered error */
+ int max_retries; /* how often this might be retried */
+ void *baton; /* userdata for callbacks */
+ struct md_result_t *result; /* result of this request */
+};
+
+apr_status_t md_acme_req_body_init(md_acme_req_t *req, struct md_json_t *payload);
+
+apr_status_t md_acme_GET(md_acme_t *acme, const char *url,
+ md_acme_req_init_cb *on_init,
+ md_acme_req_json_cb *on_json,
+ md_acme_req_res_cb *on_res,
+ md_acme_req_err_cb *on_err,
+ void *baton);
+/**
+ * Perform a POST against the ACME url. If a on_json callback is given and
+ * the HTTP response is JSON, only this callback is invoked. Otherwise, on HTTP status
+ * 2xx, the on_res callback is invoked. If no on_res is given, it is considered a
+ * response error, since only JSON was expected.
+ * At least one callback needs to be non-NULL.
+ *
+ * @param acme the ACME server to talk to
+ * @param url the url to send the request to
+ * @param on_init callback to initialize the request data
+ * @param on_json callback on successful JSON response
+ * @param on_res callback on successful HTTP response
+ * @param baton userdata for callbacks
+ */
+apr_status_t md_acme_POST(md_acme_t *acme, const char *url,
+ md_acme_req_init_cb *on_init,
+ md_acme_req_json_cb *on_json,
+ md_acme_req_res_cb *on_res,
+ md_acme_req_err_cb *on_err,
+ void *baton);
+
+/**
+ * Retrieve a JSON resource from the ACME server
+ */
+apr_status_t md_acme_get_json(struct md_json_t **pjson, md_acme_t *acme,
+ const char *url, apr_pool_t *p);
+
+
+apr_status_t md_acme_req_body_init(md_acme_req_t *req, struct md_json_t *jpayload);
+
+apr_status_t md_acme_protos_add(struct apr_hash_t *protos, apr_pool_t *p);
+
+/**
+ * Return != 0 iff the given problem identifier is an ACME error string
+ * indicating something is wrong with the input values, e.g. from our
+ * configuration.
+ */
+int md_acme_problem_is_input_related(const char *problem);
+
+#endif /* md_acme_h */
diff --git a/modules/md/md_acme_acct.c b/modules/md/md_acme_acct.c
new file mode 100644
index 0000000..f3e043e
--- /dev/null
+++ b/modules/md/md_acme_acct.c
@@ -0,0 +1,749 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+
+#include <apr_lib.h>
+#include <apr_file_info.h>
+#include <apr_file_io.h>
+#include <apr_fnmatch.h>
+#include <apr_hash.h>
+#include <apr_strings.h>
+#include <apr_tables.h>
+
+#include "md.h"
+#include "md_crypt.h"
+#include "md_json.h"
+#include "md_jws.h"
+#include "md_log.h"
+#include "md_result.h"
+#include "md_store.h"
+#include "md_util.h"
+#include "md_version.h"
+
+#include "md_acme.h"
+#include "md_acme_acct.h"
+
+static apr_status_t acct_make(md_acme_acct_t **pacct, apr_pool_t *p,
+ const char *ca_url, apr_array_header_t *contacts)
+{
+ md_acme_acct_t *acct;
+
+ acct = apr_pcalloc(p, sizeof(*acct));
+ acct->ca_url = ca_url;
+ if (!contacts || apr_is_empty_array(contacts)) {
+ acct->contacts = apr_array_make(p, 5, sizeof(const char *));
+ }
+ else {
+ acct->contacts = apr_array_copy(p, contacts);
+ }
+
+ *pacct = acct;
+ return APR_SUCCESS;
+}
+
+
+static const char *mk_acct_id(apr_pool_t *p, md_acme_t *acme, int i)
+{
+ return apr_psprintf(p, "ACME-%s-%04d", acme->sname, i);
+}
+
+static const char *mk_acct_pattern(apr_pool_t *p, md_acme_t *acme)
+{
+ return apr_psprintf(p, "ACME-%s-*", acme->sname);
+}
+
+/**************************************************************************************************/
+/* json load/save */
+
+static md_acme_acct_st acct_st_from_str(const char *s)
+{
+ if (s) {
+ if (!strcmp("valid", s)) {
+ return MD_ACME_ACCT_ST_VALID;
+ }
+ else if (!strcmp("deactivated", s)) {
+ return MD_ACME_ACCT_ST_DEACTIVATED;
+ }
+ else if (!strcmp("revoked", s)) {
+ return MD_ACME_ACCT_ST_REVOKED;
+ }
+ }
+ return MD_ACME_ACCT_ST_UNKNOWN;
+}
+
+md_json_t *md_acme_acct_to_json(md_acme_acct_t *acct, apr_pool_t *p)
+{
+ md_json_t *jacct;
+ const char *s;
+
+ assert(acct);
+ jacct = md_json_create(p);
+ switch (acct->status) {
+ case MD_ACME_ACCT_ST_VALID:
+ s = "valid";
+ break;
+ case MD_ACME_ACCT_ST_DEACTIVATED:
+ s = "deactivated";
+ break;
+ case MD_ACME_ACCT_ST_REVOKED:
+ s = "revoked";
+ break;
+ default:
+ s = NULL;
+ break;
+ }
+ if (s) md_json_sets(s, jacct, MD_KEY_STATUS, NULL);
+ if (acct->url) md_json_sets(acct->url, jacct, MD_KEY_URL, NULL);
+ if (acct->ca_url) md_json_sets(acct->ca_url, jacct, MD_KEY_CA_URL, NULL);
+ if (acct->contacts) md_json_setsa(acct->contacts, jacct, MD_KEY_CONTACT, NULL);
+ if (acct->registration) md_json_setj(acct->registration, jacct, MD_KEY_REGISTRATION, NULL);
+ if (acct->agreement) md_json_sets(acct->agreement, jacct, MD_KEY_AGREEMENT, NULL);
+ if (acct->orders) md_json_sets(acct->orders, jacct, MD_KEY_ORDERS, NULL);
+ if (acct->eab_kid) md_json_sets(acct->eab_kid, jacct, MD_KEY_EAB, MD_KEY_KID, NULL);
+ if (acct->eab_hmac) md_json_sets(acct->eab_hmac, jacct, MD_KEY_EAB, MD_KEY_HMAC, NULL);
+
+ return jacct;
+}
+
+apr_status_t md_acme_acct_from_json(md_acme_acct_t **pacct, md_json_t *json, apr_pool_t *p)
+{
+ apr_status_t rv = APR_EINVAL;
+ md_acme_acct_t *acct;
+ md_acme_acct_st status = MD_ACME_ACCT_ST_UNKNOWN;
+ const char *ca_url, *url;
+ apr_array_header_t *contacts;
+
+ if (md_json_has_key(json, MD_KEY_STATUS, NULL)) {
+ status = acct_st_from_str(md_json_gets(json, MD_KEY_STATUS, NULL));
+ }
+
+ url = md_json_gets(json, MD_KEY_URL, NULL);
+ if (!url) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "account has no url");
+ goto leave;
+ }
+
+ ca_url = md_json_gets(json, MD_KEY_CA_URL, NULL);
+ if (!ca_url) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "account has no CA url: %s", url);
+ goto leave;
+ }
+
+ contacts = apr_array_make(p, 5, sizeof(const char *));
+ if (md_json_has_key(json, MD_KEY_CONTACT, NULL)) {
+ md_json_getsa(contacts, json, MD_KEY_CONTACT, NULL);
+ }
+ else {
+ md_json_getsa(contacts, json, MD_KEY_REGISTRATION, MD_KEY_CONTACT, NULL);
+ }
+ rv = acct_make(&acct, p, ca_url, contacts);
+ if (APR_SUCCESS != rv) goto leave;
+
+ acct->status = status;
+ acct->url = url;
+ acct->agreement = md_json_gets(json, MD_KEY_AGREEMENT, NULL);
+ if (!acct->agreement) {
+ /* backward compatible check */
+ acct->agreement = md_json_gets(json, "terms-of-service", NULL);
+ }
+ acct->orders = md_json_gets(json, MD_KEY_ORDERS, NULL);
+ if (md_json_has_key(json, MD_KEY_EAB, MD_KEY_KID, NULL)
+ && md_json_has_key(json, MD_KEY_EAB, MD_KEY_HMAC, NULL)) {
+ acct->eab_kid = md_json_gets(json, MD_KEY_EAB, MD_KEY_KID, NULL);
+ acct->eab_hmac = md_json_gets(json, MD_KEY_EAB, MD_KEY_HMAC, NULL);
+ }
+
+leave:
+ *pacct = (APR_SUCCESS == rv)? acct : NULL;
+ return rv;
+}
+
+apr_status_t md_acme_acct_save(md_store_t *store, apr_pool_t *p, md_acme_t *acme,
+ const char **pid, md_acme_acct_t *acct, md_pkey_t *acct_key)
+{
+ md_json_t *jacct;
+ apr_status_t rv;
+ int i;
+ const char *id = pid? *pid : NULL;
+
+ jacct = md_acme_acct_to_json(acct, p);
+ if (id) {
+ rv = md_store_save(store, p, MD_SG_ACCOUNTS, id, MD_FN_ACCOUNT, MD_SV_JSON, jacct, 0);
+ }
+ else {
+ rv = APR_EAGAIN;
+ for (i = 0; i < 1000 && APR_SUCCESS != rv; ++i) {
+ id = mk_acct_id(p, acme, i);
+ rv = md_store_save(store, p, MD_SG_ACCOUNTS, id, MD_FN_ACCOUNT, MD_SV_JSON, jacct, 1);
+ }
+ }
+ if (APR_SUCCESS == rv) {
+ if (pid) *pid = id;
+ rv = md_store_save(store, p, MD_SG_ACCOUNTS, id, MD_FN_ACCT_KEY, MD_SV_PKEY, acct_key, 0);
+ }
+ return rv;
+}
+
+apr_status_t md_acme_acct_load(md_acme_acct_t **pacct, md_pkey_t **ppkey,
+ md_store_t *store, md_store_group_t group,
+ const char *name, apr_pool_t *p)
+{
+ md_json_t *json;
+ apr_status_t rv;
+
+ rv = md_store_load_json(store, group, name, MD_FN_ACCOUNT, &json, p);
+ if (APR_STATUS_IS_ENOENT(rv)) {
+ goto out;
+ }
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "error reading account: %s", name);
+ goto out;
+ }
+
+ rv = md_acme_acct_from_json(pacct, json, p);
+ if (APR_SUCCESS == rv) {
+ rv = md_store_load(store, group, name, MD_FN_ACCT_KEY, MD_SV_PKEY, (void**)ppkey, p);
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "loading key: %s", name);
+ goto out;
+ }
+ }
+out:
+ if (APR_SUCCESS != rv) {
+ *pacct = NULL;
+ *ppkey = NULL;
+ }
+ return rv;
+}
+
+/**************************************************************************************************/
+/* Lookup */
+
+int md_acme_acct_matches_url(md_acme_acct_t *acct, const char *url)
+{
+ /* The ACME url must match exactly */
+ if (!url || !acct->ca_url || strcmp(acct->ca_url, url)) return 0;
+ return 1;
+}
+
+int md_acme_acct_matches_md(md_acme_acct_t *acct, const md_t *md)
+{
+ if (!md_acme_acct_matches_url(acct, md->ca_effective)) return 0;
+ /* if eab values are not mentioned, we match an account regardless
+ * if it was registered with eab or not */
+ if (!md->ca_eab_kid || !md->ca_eab_hmac) {
+ /* No eab only acceptable when no eab is asked for.
+ * This prevents someone that has no external account binding
+ * to re-use an account from another MDomain that was created
+ * with a binding. */
+ return !acct->eab_kid || !acct->eab_hmac;
+ }
+ /* But of eab is asked for, we need an acct that matches exactly.
+ * When someone configures a new EAB and we need
+ * to created a new account for it. */
+ if (!acct->eab_kid || !acct->eab_hmac) return 0;
+ return !strcmp(acct->eab_kid, md->ca_eab_kid)
+ && !strcmp(acct->eab_hmac, md->ca_eab_hmac);
+}
+
+typedef struct {
+ apr_pool_t *p;
+ const md_t *md;
+ const char *id;
+} find_ctx;
+
+static int find_acct(void *baton, const char *name, const char *aspect,
+ md_store_vtype_t vtype, void *value, apr_pool_t *ptemp)
+{
+ find_ctx *ctx = baton;
+ md_acme_acct_t *acct;
+ apr_status_t rv;
+
+ (void)aspect;
+ (void)ptemp;
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ctx->p, "account candidate %s/%s", name, aspect);
+ if (MD_SV_JSON == vtype) {
+ rv = md_acme_acct_from_json(&acct, (md_json_t*)value, ptemp);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ if (MD_ACME_ACCT_ST_VALID == acct->status
+ && (!ctx->md || md_acme_acct_matches_md(acct, ctx->md))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ctx->p,
+ "found account %s for %s: %s, status=%d",
+ acct->id, ctx->md->ca_effective, aspect, acct->status);
+ ctx->id = apr_pstrdup(ctx->p, name);
+ return 0;
+ }
+ }
+cleanup:
+ return 1;
+}
+
+static apr_status_t acct_find(const char **pid, md_acme_acct_t **pacct, md_pkey_t **ppkey,
+ md_store_t *store, md_store_group_t group,
+ const char *name_pattern,
+ const md_t *md, apr_pool_t *p)
+{
+ apr_status_t rv;
+ find_ctx ctx;
+
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.p = p;
+ ctx.md = md;
+
+ rv = md_store_iter(find_acct, &ctx, store, p, group, name_pattern, MD_FN_ACCOUNT, MD_SV_JSON);
+ if (ctx.id) {
+ *pid = ctx.id;
+ rv = md_acme_acct_load(pacct, ppkey, store, group, ctx.id, p);
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "acct_find: got account %s", ctx.id);
+ }
+ else {
+ *pacct = NULL;
+ rv = APR_ENOENT;
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, p, "acct_find: none found");
+ }
+ return rv;
+}
+
+static apr_status_t acct_find_and_verify(md_store_t *store, md_store_group_t group,
+ const char *name_pattern,
+ md_acme_t *acme, const md_t *md,
+ apr_pool_t *p)
+{
+ md_acme_acct_t *acct;
+ md_pkey_t *pkey;
+ const char *id;
+ apr_status_t rv;
+
+ rv = acct_find(&id, &acct, &pkey, store, group, name_pattern, md, p);
+ if (APR_SUCCESS == rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, p, "acct_find_and_verify: found %s",
+ id);
+ acme->acct_id = (MD_SG_STAGING == group)? NULL : id;
+ acme->acct = acct;
+ acme->acct_key = pkey;
+ rv = md_acme_acct_validate(acme, (MD_SG_STAGING == group)? NULL : store, p);
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, p, "acct_find_and_verify: verified %s",
+ id);
+
+ if (APR_SUCCESS != rv) {
+ acme->acct_id = NULL;
+ acme->acct = NULL;
+ acme->acct_key = NULL;
+ if (APR_STATUS_IS_ENOENT(rv)) {
+ /* verification failed and account has been disabled.
+ Indicate to caller that he may try again. */
+ rv = APR_EAGAIN;
+ }
+ }
+ }
+ return rv;
+}
+
+apr_status_t md_acme_find_acct_for_md(md_acme_t *acme, md_store_t *store, const md_t *md)
+{
+ apr_status_t rv;
+
+ while (APR_EAGAIN == (rv = acct_find_and_verify(store, MD_SG_ACCOUNTS,
+ mk_acct_pattern(acme->p, acme),
+ acme, md, acme->p))) {
+ /* nop */
+ }
+
+ if (APR_STATUS_IS_ENOENT(rv)) {
+ /* No suitable account found in MD_SG_ACCOUNTS. Maybe a new account
+ * can already be found in MD_SG_STAGING? */
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, acme->p,
+ "no account found, looking in STAGING");
+ rv = acct_find_and_verify(store, MD_SG_STAGING, "*", acme, md, acme->p);
+ if (APR_EAGAIN == rv) {
+ rv = APR_ENOENT;
+ }
+ }
+ return rv;
+}
+
+apr_status_t md_acme_acct_id_for_md(const char **pid, md_store_t *store,
+ md_store_group_t group, const md_t *md,
+ apr_pool_t *p)
+{
+ apr_status_t rv;
+ find_ctx ctx;
+
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.p = p;
+ ctx.md = md;
+
+ rv = md_store_iter(find_acct, &ctx, store, p, group, "*", MD_FN_ACCOUNT, MD_SV_JSON);
+ if (ctx.id) {
+ *pid = ctx.id;
+ rv = APR_SUCCESS;
+ }
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "acct_id_for_md %s -> %s", md->name, *pid);
+ return rv;
+}
+
+/**************************************************************************************************/
+/* acct operation context */
+typedef struct {
+ md_acme_t *acme;
+ apr_pool_t *p;
+ const char *agreement;
+ const char *eab_kid;
+ const char *eab_hmac;
+} acct_ctx_t;
+
+/**************************************************************************************************/
+/* acct update */
+
+static apr_status_t on_init_acct_upd(md_acme_req_t *req, void *baton)
+{
+ (void)baton;
+ return md_acme_req_body_init(req, NULL);
+}
+
+static apr_status_t acct_upd(md_acme_t *acme, apr_pool_t *p,
+ const apr_table_t *hdrs, md_json_t *body, void *baton)
+{
+ acct_ctx_t *ctx = baton;
+ apr_status_t rv = APR_SUCCESS;
+ md_acme_acct_t *acct = acme->acct;
+
+ if (md_log_is_level(p, MD_LOG_TRACE2)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, acme->p, "acct update response: %s",
+ md_json_writep(body, p, MD_JSON_FMT_COMPACT));
+ }
+
+ if (!acct->url) {
+ const char *location = apr_table_get(hdrs, "location");
+ if (!location) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, APR_EINVAL, p, "new acct without location");
+ return APR_EINVAL;
+ }
+ acct->url = apr_pstrdup(ctx->p, location);
+ }
+
+ apr_array_clear(acct->contacts);
+ md_json_dupsa(acct->contacts, acme->p, body, MD_KEY_CONTACT, NULL);
+ if (md_json_has_key(body, MD_KEY_STATUS, NULL)) {
+ acct->status = acct_st_from_str(md_json_gets(body, MD_KEY_STATUS, NULL));
+ }
+ if (md_json_has_key(body, MD_KEY_AGREEMENT, NULL)) {
+ acct->agreement = md_json_dups(acme->p, body, MD_KEY_AGREEMENT, NULL);
+ }
+ if (md_json_has_key(body, MD_KEY_ORDERS, NULL)) {
+ acct->orders = md_json_dups(acme->p, body, MD_KEY_ORDERS, NULL);
+ }
+ if (ctx->eab_kid && ctx->eab_hmac) {
+ acct->eab_kid = ctx->eab_kid;
+ acct->eab_hmac = ctx->eab_hmac;
+ }
+ acct->registration = md_json_clone(ctx->p, body);
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "updated acct %s", acct->url);
+ return rv;
+}
+
+apr_status_t md_acme_acct_update(md_acme_t *acme)
+{
+ acct_ctx_t ctx;
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, acme->p, "acct update");
+ if (!acme->acct) {
+ return APR_EINVAL;
+ }
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.acme = acme;
+ ctx.p = acme->p;
+ return md_acme_POST(acme, acme->acct->url, on_init_acct_upd, acct_upd, NULL, NULL, &ctx);
+}
+
+apr_status_t md_acme_acct_validate(md_acme_t *acme, md_store_t *store, apr_pool_t *p)
+{
+ apr_status_t rv;
+
+ if (APR_SUCCESS != (rv = md_acme_acct_update(acme))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, acme->p,
+ "acct update failed for %s", acme->acct->url);
+ if (APR_EINVAL == rv && (acme->acct->agreement || !acme->ca_agreement)) {
+ /* Sadly, some proprietary ACME servers choke on empty POSTs
+ * on accounts. Try a faked ToS agreement. */
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, acme->p,
+ "trying acct update via ToS agreement");
+ rv = md_acme_agree(acme, p, "accepted");
+ }
+ if (acme->acct && (APR_ENOENT == rv || APR_EACCES == rv || APR_EINVAL == rv)) {
+ if (MD_ACME_ACCT_ST_VALID == acme->acct->status) {
+ acme->acct->status = MD_ACME_ACCT_ST_UNKNOWN;
+ if (store) {
+ md_acme_acct_save(store, p, acme, &acme->acct_id, acme->acct, acme->acct_key);
+ }
+ }
+ acme->acct = NULL;
+ acme->acct_key = NULL;
+ rv = APR_ENOENT;
+ }
+ }
+ return rv;
+}
+
+/**************************************************************************************************/
+/* Register a new account */
+
+static apr_status_t get_eab(md_json_t **peab, md_acme_req_t *req, const char *kid,
+ const char *hmac64, md_pkey_t *account_key,
+ const char *url)
+{
+ md_json_t *eab, *prot_fields, *jwk;
+ md_data_t payload, hmac_key;
+ apr_status_t rv;
+
+ prot_fields = md_json_create(req->p);
+ md_json_sets(url, prot_fields, "url", NULL);
+ md_json_sets(kid, prot_fields, "kid", NULL);
+
+ rv = md_jws_get_jwk(&jwk, req->p, account_key);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ md_data_null(&payload);
+ payload.data = md_json_writep(jwk, req->p, MD_JSON_FMT_COMPACT);
+ if (!payload.data) {
+ rv = APR_EINVAL;
+ goto cleanup;
+ }
+ payload.len = strlen(payload.data);
+
+ md_util_base64url_decode(&hmac_key, hmac64, req->p);
+ if (!hmac_key.len) {
+ rv = APR_EINVAL;
+ md_result_problem_set(req->result, rv, "apache:eab-hmac-invalid",
+ "external account binding HMAC value is not valid base64", NULL);
+ goto cleanup;
+ }
+
+ rv = md_jws_hmac(&eab, req->p, &payload, prot_fields, &hmac_key);
+ if (APR_SUCCESS != rv) {
+ md_result_problem_set(req->result, rv, "apache:eab-hmac-fail",
+ "external account binding MAC could not be computed", NULL);
+ }
+
+cleanup:
+ *peab = (APR_SUCCESS == rv)? eab : NULL;
+ return rv;
+}
+
+static apr_status_t on_init_acct_new(md_acme_req_t *req, void *baton)
+{
+ acct_ctx_t *ctx = baton;
+ md_json_t *jpayload, *jeab;
+ apr_status_t rv;
+
+ jpayload = md_json_create(req->p);
+ md_json_setsa(ctx->acme->acct->contacts, jpayload, MD_KEY_CONTACT, NULL);
+ if (ctx->agreement) {
+ md_json_setb(1, jpayload, "termsOfServiceAgreed", NULL);
+ }
+ if (ctx->eab_kid && ctx->eab_hmac) {
+ rv = get_eab(&jeab, req, ctx->eab_kid, ctx->eab_hmac,
+ req->acme->acct_key, req->url);
+ if (APR_SUCCESS != rv) goto cleanup;
+ md_json_setj(jeab, jpayload, "externalAccountBinding", NULL);
+ }
+ rv = md_acme_req_body_init(req, jpayload);
+
+cleanup:
+ return rv;
+}
+
+apr_status_t md_acme_acct_register(md_acme_t *acme, md_store_t *store,
+ const md_t *md, apr_pool_t *p)
+{
+ apr_status_t rv;
+ md_pkey_t *pkey;
+ const char *err = NULL, *uri;
+ md_pkey_spec_t spec;
+ int i;
+ acct_ctx_t ctx;
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "create new account");
+
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.acme = acme;
+ ctx.p = p;
+ /* The agreement URL is submitted when the ACME server announces Terms-of-Service
+ * in its directory meta data. The magic value "accepted" will always use the
+ * advertised URL. */
+ ctx.agreement = NULL;
+ if (acme->ca_agreement && md->ca_agreement) {
+ ctx.agreement = !strcmp("accepted", md->ca_agreement)?
+ acme->ca_agreement : md->ca_agreement;
+ }
+
+ if (ctx.agreement) {
+ if (APR_SUCCESS != (rv = md_util_abs_uri_check(acme->p, ctx.agreement, &err))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p,
+ "invalid agreement uri (%s): %s", err, ctx.agreement);
+ goto out;
+ }
+ }
+ ctx.eab_kid = md->ca_eab_kid;
+ ctx.eab_hmac = md->ca_eab_hmac;
+
+ for (i = 0; i < md->contacts->nelts; ++i) {
+ uri = APR_ARRAY_IDX(md->contacts, i, const char *);
+ if (APR_SUCCESS != (rv = md_util_abs_uri_check(acme->p, uri, &err))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p,
+ "invalid contact uri (%s): %s", err, uri);
+ goto out;
+ }
+ }
+
+ /* If there is no key selected yet, try to find an existing one for the same host.
+ * Let's Encrypt identifies accounts by their key for their ACMEv1 and v2 services.
+ * Although the account appears on both services with different urls, it is
+ * internally the same one.
+ * I think this is beneficial if someone migrates from ACMEv1 to v2 and not a leak
+ * of identifying information.
+ */
+ if (!acme->acct_key) {
+ find_ctx fctx;
+
+ memset(&fctx, 0, sizeof(fctx));
+ fctx.p = p;
+ fctx.md = md;
+
+ md_store_iter(find_acct, &fctx, store, p, MD_SG_ACCOUNTS,
+ mk_acct_pattern(p, acme), MD_FN_ACCOUNT, MD_SV_JSON);
+ if (fctx.id) {
+ rv = md_store_load(store, MD_SG_ACCOUNTS, fctx.id, MD_FN_ACCT_KEY, MD_SV_PKEY,
+ (void**)&acme->acct_key, p);
+ if (APR_SUCCESS == rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
+ "reusing key from account %s", fctx.id);
+ }
+ else {
+ acme->acct_key = NULL;
+ }
+ }
+ }
+
+ /* If we still have no key, generate a new one */
+ if (!acme->acct_key) {
+ spec.type = MD_PKEY_TYPE_RSA;
+ spec.params.rsa.bits = MD_ACME_ACCT_PKEY_BITS;
+
+ if (APR_SUCCESS != (rv = md_pkey_gen(&pkey, acme->p, &spec))) goto out;
+ acme->acct_key = pkey;
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "created new account key");
+ }
+
+ if (APR_SUCCESS != (rv = acct_make(&acme->acct, p, acme->url, md->contacts))) goto out;
+ rv = md_acme_POST_new_account(acme, on_init_acct_new, acct_upd, NULL, NULL, &ctx);
+ if (APR_SUCCESS == rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, p,
+ "registered new account %s", acme->acct->url);
+ }
+
+out:
+ if (APR_SUCCESS != rv && acme->acct) {
+ acme->acct = NULL;
+ }
+ return rv;
+}
+
+/**************************************************************************************************/
+/* Deactivate the account */
+
+static apr_status_t on_init_acct_del(md_acme_req_t *req, void *baton)
+{
+ md_json_t *jpayload;
+
+ (void)baton;
+ jpayload = md_json_create(req->p);
+ md_json_sets("deactivated", jpayload, MD_KEY_STATUS, NULL);
+ return md_acme_req_body_init(req, jpayload);
+}
+
+apr_status_t md_acme_acct_deactivate(md_acme_t *acme, apr_pool_t *p)
+{
+ md_acme_acct_t *acct = acme->acct;
+ acct_ctx_t ctx;
+
+ (void)p;
+ if (!acct) {
+ return APR_EINVAL;
+ }
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, acme->p, "delete account %s from %s",
+ acct->url, acct->ca_url);
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.acme = acme;
+ ctx.p = p;
+ return md_acme_POST(acme, acct->url, on_init_acct_del, acct_upd, NULL, NULL, &ctx);
+}
+
+/**************************************************************************************************/
+/* terms-of-service */
+
+static apr_status_t on_init_agree_tos(md_acme_req_t *req, void *baton)
+{
+ acct_ctx_t *ctx = baton;
+ md_json_t *jpayload;
+
+ jpayload = md_json_create(req->p);
+ if (ctx->acme->acct->agreement) {
+ md_json_setb(1, jpayload, "termsOfServiceAgreed", NULL);
+ }
+ return md_acme_req_body_init(req, jpayload);
+}
+
+apr_status_t md_acme_agree(md_acme_t *acme, apr_pool_t *p, const char *agreement)
+{
+ acct_ctx_t ctx;
+
+ acme->acct->agreement = agreement;
+ if (!strcmp("accepted", agreement) && acme->ca_agreement) {
+ acme->acct->agreement = acme->ca_agreement;
+ }
+
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.acme = acme;
+ ctx.p = p;
+ return md_acme_POST(acme, acme->acct->url, on_init_agree_tos, acct_upd, NULL, NULL, &ctx);
+}
+
+apr_status_t md_acme_check_agreement(md_acme_t *acme, apr_pool_t *p,
+ const char *agreement, const char **prequired)
+{
+ apr_status_t rv = APR_SUCCESS;
+
+ /* We used to really check if the account agreement and the one indicated in meta
+ * are the very same. However, LE is happy if the account has agreed to a ToS in
+ * the past and does not require a renewed acceptance.
+ */
+ *prequired = NULL;
+ if (!acme->acct->agreement && acme->ca_agreement) {
+ if (agreement) {
+ rv = md_acme_agree(acme, p, acme->ca_agreement);
+ }
+ else {
+ *prequired = acme->ca_agreement;
+ rv = APR_INCOMPLETE;
+ }
+ }
+ return rv;
+}
diff --git a/modules/md/md_acme_acct.h b/modules/md/md_acme_acct.h
new file mode 100644
index 0000000..b5bba63
--- /dev/null
+++ b/modules/md/md_acme_acct.h
@@ -0,0 +1,148 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mod_md_md_acme_acct_h
+#define mod_md_md_acme_acct_h
+
+struct md_acme_req;
+struct md_json_t;
+struct md_pkey_t;
+
+#include "md_store.h"
+
+/**
+ * An ACME account at an ACME server.
+ */
+typedef struct md_acme_acct_t md_acme_acct_t;
+
+typedef enum {
+ MD_ACME_ACCT_ST_UNKNOWN,
+ MD_ACME_ACCT_ST_VALID,
+ MD_ACME_ACCT_ST_DEACTIVATED,
+ MD_ACME_ACCT_ST_REVOKED,
+} md_acme_acct_st;
+
+struct md_acme_acct_t {
+ const char *id; /* short, unique id for the account */
+ const char *url; /* url of the account, once registered */
+ const char *ca_url; /* url of the ACME protocol endpoint */
+ md_acme_acct_st status; /* status of this account */
+ apr_array_header_t *contacts; /* list of contact uris, e.g. mailto:xxx */
+ const char *tos_required; /* terms of service asked for by CA */
+ const char *agreement; /* terms of service agreed to by user */
+ const char *orders; /* URL where certificate orders are found (ACMEv2) */
+ const char *eab_kid; /* external account binding keyid used or NULL */
+ const char *eab_hmac; /* external account binding hmac used or NULL */
+ struct md_json_t *registration; /* data from server registration */
+};
+
+#define MD_FN_ACCOUNT "account.json"
+#define MD_FN_ACCT_KEY "account.pem"
+
+/* ACME account private keys are always RSA and have that many bits. Since accounts
+ * are expected to live long, better err on the safe side. */
+#define MD_ACME_ACCT_PKEY_BITS 3072
+
+#define MD_ACME_ACCT_STAGED "staged"
+
+/**
+ * Convert an ACME account form/to JSON.
+ */
+struct md_json_t *md_acme_acct_to_json(md_acme_acct_t *acct, apr_pool_t *p);
+apr_status_t md_acme_acct_from_json(md_acme_acct_t **pacct, struct md_json_t *json, apr_pool_t *p);
+
+/**
+ * Update the account from the ACME server.
+ * - Will update acme->acct structure from server on success
+ * - Will return error status when request failed or account is not known.
+ */
+apr_status_t md_acme_acct_update(md_acme_t *acme);
+
+/**
+ * Update the account and persist changes in the store, if given (and not NULL).
+ */
+apr_status_t md_acme_acct_validate(md_acme_t *acme, md_store_t *store, apr_pool_t *p);
+
+/**
+ * Agree to the given Terms-of-Service url for the current account.
+ */
+apr_status_t md_acme_agree(md_acme_t *acme, apr_pool_t *p, const char *tos);
+
+/**
+ * Confirm with the server that the current account agrees to the Terms-of-Service
+ * given in the agreement url.
+ * If the known agreement is equal to this, nothing is done.
+ * If it differs, the account is re-validated in the hope that the server
+ * announces the Tos URL it wants. If this is equal to the agreement specified,
+ * the server is notified of this. If the server requires a ToS that the account
+ * thinks it has already given, it is resend.
+ *
+ * If an agreement is required, different from the current one, APR_INCOMPLETE is
+ * returned and the agreement url is returned in the parameter.
+ */
+apr_status_t md_acme_check_agreement(md_acme_t *acme, apr_pool_t *p,
+ const char *agreement, const char **prequired);
+
+/**
+ * Get the ToS agreement for current account.
+ */
+const char *md_acme_get_agreement(md_acme_t *acme);
+
+
+/**
+ * Find an existing account in the local store. On APR_SUCCESS, the acme
+ * instance will have a current, validated account to use.
+ */
+apr_status_t md_acme_find_acct_for_md(md_acme_t *acme, md_store_t *store, const md_t *md);
+
+/**
+ * Find the account id for a given md.
+ */
+apr_status_t md_acme_acct_id_for_md(const char **pid, md_store_t *store,
+ md_store_group_t group, const md_t *md, apr_pool_t *p);
+
+/**
+ * Create a new account at the ACME server for an MD. The
+ * new account is the one used by the acme instance afterwards, on success.
+ */
+apr_status_t md_acme_acct_register(md_acme_t *acme, md_store_t *store,
+ const md_t *md, apr_pool_t *p);
+
+apr_status_t md_acme_acct_save(md_store_t *store, apr_pool_t *p, md_acme_t *acme,
+ const char **pid, struct md_acme_acct_t *acct,
+ struct md_pkey_t *acct_key);
+
+/**
+ * Deactivate the current account at the ACME server.
+ */
+apr_status_t md_acme_acct_deactivate(md_acme_t *acme, apr_pool_t *p);
+
+apr_status_t md_acme_acct_load(struct md_acme_acct_t **pacct, struct md_pkey_t **ppkey,
+ md_store_t *store, md_store_group_t group,
+ const char *name, apr_pool_t *p);
+
+/*
+ * Return != 0 iff the account can be used for the ACME url.
+ */
+int md_acme_acct_matches_url(md_acme_acct_t *acct, const char *url);
+
+/*
+ * Return != 0 iff the account can be used for the MD, including
+ * its CA url and EAB settings.
+ */
+int md_acme_acct_matches_md(md_acme_acct_t *acct, const md_t *md);
+
+#endif /* md_acme_acct_h */
diff --git a/modules/md/md_acme_authz.c b/modules/md/md_acme_authz.c
new file mode 100644
index 0000000..a55804e
--- /dev/null
+++ b/modules/md/md_acme_authz.c
@@ -0,0 +1,697 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+
+#include <apr_lib.h>
+#include <apr_buckets.h>
+#include <apr_file_info.h>
+#include <apr_file_io.h>
+#include <apr_fnmatch.h>
+#include <apr_hash.h>
+#include <apr_strings.h>
+#include <apr_tables.h>
+
+#include "md.h"
+#include "md_crypt.h"
+#include "md_json.h"
+#include "md_http.h"
+#include "md_log.h"
+#include "md_jws.h"
+#include "md_result.h"
+#include "md_store.h"
+#include "md_util.h"
+
+#include "md_acme.h"
+#include "md_acme_authz.h"
+
+md_acme_authz_t *md_acme_authz_create(apr_pool_t *p)
+{
+ md_acme_authz_t *authz;
+ authz = apr_pcalloc(p, sizeof(*authz));
+
+ return authz;
+}
+
+/**************************************************************************************************/
+/* Register a new authorization */
+
+typedef struct {
+ size_t index;
+ const char *type;
+ const char *uri;
+ const char *token;
+ const char *key_authz;
+} md_acme_authz_cha_t;
+
+typedef struct {
+ apr_pool_t *p;
+ md_acme_t *acme;
+ const char *domain;
+ md_acme_authz_t *authz;
+ md_acme_authz_cha_t *challenge;
+} authz_req_ctx;
+
+static void authz_req_ctx_init(authz_req_ctx *ctx, md_acme_t *acme,
+ const char *domain, md_acme_authz_t *authz, apr_pool_t *p)
+{
+ memset(ctx, 0, sizeof(*ctx));
+ ctx->p = p;
+ ctx->acme = acme;
+ ctx->domain = domain;
+ ctx->authz = authz;
+}
+
+/**************************************************************************************************/
+/* Update an existing authorization */
+
+apr_status_t md_acme_authz_retrieve(md_acme_t *acme, apr_pool_t *p, const char *url,
+ md_acme_authz_t **pauthz)
+{
+ md_acme_authz_t *authz;
+ apr_status_t rv;
+
+ authz = apr_pcalloc(p, sizeof(*authz));
+ authz->url = apr_pstrdup(p, url);
+ rv = md_acme_authz_update(authz, acme, p);
+
+ *pauthz = (APR_SUCCESS == rv)? authz : NULL;
+ return rv;
+}
+
+typedef struct {
+ apr_pool_t *p;
+ md_acme_authz_t *authz;
+} error_ctx_t;
+
+static int copy_challenge_error(void *baton, size_t index, md_json_t *json)
+{
+ error_ctx_t *ctx = baton;
+
+ (void)index;
+ if (md_json_has_key(json, MD_KEY_ERROR, NULL)) {
+ ctx->authz->error_type = md_json_dups(ctx->p, json, MD_KEY_ERROR, MD_KEY_TYPE, NULL);
+ ctx->authz->error_detail = md_json_dups(ctx->p, json, MD_KEY_ERROR, MD_KEY_DETAIL, NULL);
+ ctx->authz->error_subproblems = md_json_dupj(ctx->p, json, MD_KEY_ERROR, MD_KEY_SUBPROBLEMS, NULL);
+ }
+ return 1;
+}
+
+apr_status_t md_acme_authz_update(md_acme_authz_t *authz, md_acme_t *acme, apr_pool_t *p)
+{
+ md_json_t *json;
+ const char *s, *err;
+ md_log_level_t log_level;
+ apr_status_t rv;
+ error_ctx_t ctx;
+
+ assert(acme);
+ assert(acme->http);
+ assert(authz);
+ assert(authz->url);
+
+ authz->state = MD_ACME_AUTHZ_S_UNKNOWN;
+ json = NULL;
+ authz->error_type = authz->error_detail = NULL;
+ authz->error_subproblems = NULL;
+ err = "unable to parse response";
+ log_level = MD_LOG_ERR;
+
+ if (APR_SUCCESS == (rv = md_acme_get_json(&json, acme, authz->url, p))
+ && (s = md_json_gets(json, MD_KEY_STATUS, NULL))) {
+
+ authz->domain = md_json_gets(json, MD_KEY_IDENTIFIER, MD_KEY_VALUE, NULL);
+ authz->resource = json;
+ if (!strcmp(s, "pending")) {
+ authz->state = MD_ACME_AUTHZ_S_PENDING;
+ err = "challenge 'pending'";
+ log_level = MD_LOG_DEBUG;
+ }
+ else if (!strcmp(s, "valid")) {
+ authz->state = MD_ACME_AUTHZ_S_VALID;
+ err = "challenge 'valid'";
+ log_level = MD_LOG_DEBUG;
+ }
+ else if (!strcmp(s, "invalid")) {
+ ctx.p = p;
+ ctx.authz = authz;
+ authz->state = MD_ACME_AUTHZ_S_INVALID;
+ md_json_itera(copy_challenge_error, &ctx, json, MD_KEY_CHALLENGES, NULL);
+ err = "challenge 'invalid'";
+ }
+ }
+
+ if (json && authz->state == MD_ACME_AUTHZ_S_UNKNOWN) {
+ err = "unable to understand response";
+ rv = APR_EINVAL;
+ }
+
+ if (md_log_is_level(p, log_level)) {
+ md_log_perror(MD_LOG_MARK, log_level, rv, p, "ACME server authz: %s for %s at %s. "
+ "Exact response was: %s", err, authz->domain, authz->url,
+ json? md_json_writep(json, p, MD_JSON_FMT_COMPACT) : "not available");
+ }
+
+ return rv;
+}
+
+/**************************************************************************************************/
+/* response to a challenge */
+
+static md_acme_authz_cha_t *cha_from_json(apr_pool_t *p, size_t index, md_json_t *json)
+{
+ md_acme_authz_cha_t * cha;
+
+ cha = apr_pcalloc(p, sizeof(*cha));
+ cha->index = index;
+ cha->type = md_json_dups(p, json, MD_KEY_TYPE, NULL);
+ if (md_json_has_key(json, MD_KEY_URL, NULL)) { /* ACMEv2 */
+ cha->uri = md_json_dups(p, json, MD_KEY_URL, NULL);
+ }
+ else { /* ACMEv1 */
+ cha->uri = md_json_dups(p, json, MD_KEY_URI, NULL);
+ }
+ cha->token = md_json_dups(p, json, MD_KEY_TOKEN, NULL);
+ cha->key_authz = md_json_dups(p, json, MD_KEY_KEYAUTHZ, NULL);
+
+ return cha;
+}
+
+static apr_status_t on_init_authz_resp(md_acme_req_t *req, void *baton)
+{
+ md_json_t *jpayload;
+
+ (void)baton;
+ jpayload = md_json_create(req->p);
+ return md_acme_req_body_init(req, jpayload);
+}
+
+static apr_status_t authz_http_set(md_acme_t *acme, apr_pool_t *p, const apr_table_t *hdrs,
+ md_json_t *body, void *baton)
+{
+ authz_req_ctx *ctx = baton;
+
+ (void)acme;
+ (void)p;
+ (void)hdrs;
+ (void)body;
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ctx->p, "updated authz %s", ctx->authz->url);
+ return APR_SUCCESS;
+}
+
+static apr_status_t setup_key_authz(md_acme_authz_cha_t *cha, md_acme_authz_t *authz,
+ md_acme_t *acme, apr_pool_t *p, int *pchanged)
+{
+ const char *thumb64, *key_authz;
+ apr_status_t rv;
+
+ (void)authz;
+ assert(cha);
+ assert(cha->token);
+
+ *pchanged = 0;
+ if (APR_SUCCESS == (rv = md_jws_pkey_thumb(&thumb64, p, acme->acct_key))) {
+ key_authz = apr_psprintf(p, "%s.%s", cha->token, thumb64);
+ if (cha->key_authz) {
+ if (strcmp(key_authz, cha->key_authz)) {
+ /* Hu? Did the account change key? */
+ cha->key_authz = NULL;
+ }
+ }
+ if (!cha->key_authz) {
+ cha->key_authz = key_authz;
+ *pchanged = 1;
+ }
+ }
+ return rv;
+}
+
+static apr_status_t cha_http_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz,
+ md_acme_t *acme, md_store_t *store,
+ md_pkeys_spec_t *key_specs,
+ apr_array_header_t *acme_tls_1_domains, const md_t *md,
+ apr_table_t *env, md_result_t *result, apr_pool_t *p)
+{
+ const char *data;
+ apr_status_t rv;
+ int notify_server;
+
+ (void)key_specs;
+ (void)env;
+ (void)acme_tls_1_domains;
+ (void)md;
+
+ if (APR_SUCCESS != (rv = setup_key_authz(cha, authz, acme, p, &notify_server))) {
+ goto out;
+ }
+
+ rv = md_store_load(store, MD_SG_CHALLENGES, authz->domain, MD_FN_HTTP01,
+ MD_SV_TEXT, (void**)&data, p);
+ if ((APR_SUCCESS == rv && strcmp(cha->key_authz, data)) || APR_STATUS_IS_ENOENT(rv)) {
+ const char *content = apr_psprintf(p, "%s\n", cha->key_authz);
+ rv = md_store_save(store, p, MD_SG_CHALLENGES, authz->domain, MD_FN_HTTP01,
+ MD_SV_TEXT, (void*)content, 0);
+ notify_server = 1;
+ }
+
+ if (APR_SUCCESS == rv && notify_server) {
+ authz_req_ctx ctx;
+ const char *event;
+
+ /* Raise event that challenge data has been set up before we tell the
+ ACME server. Clusters might want to distribute it. */
+ event = apr_psprintf(p, "challenge-setup:%s:%s", MD_AUTHZ_TYPE_HTTP01, authz->domain);
+ rv = md_result_raise(result, event, p);
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
+ "%s: event '%s' failed. aborting challenge setup",
+ authz->domain, event);
+ goto out;
+ }
+ /* challenge is setup or was changed from previous data, tell ACME server
+ * so it may (re)try verification */
+ authz_req_ctx_init(&ctx, acme, NULL, authz, p);
+ ctx.challenge = cha;
+ rv = md_acme_POST(acme, cha->uri, on_init_authz_resp, authz_http_set, NULL, NULL, &ctx);
+ }
+out:
+ return rv;
+}
+
+void tls_alpn01_fnames(apr_pool_t *p, md_pkey_spec_t *kspec, char **keyfn, char **certfn )
+{
+ *keyfn = apr_pstrcat(p, "acme-tls-alpn-01-", md_pkey_filename(kspec, p), NULL);
+ *certfn = apr_pstrcat(p, "acme-tls-alpn-01-", md_chain_filename(kspec, p), NULL);
+}
+
+static apr_status_t cha_tls_alpn_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz,
+ md_acme_t *acme, md_store_t *store,
+ md_pkeys_spec_t *key_specs,
+ apr_array_header_t *acme_tls_1_domains, const md_t *md,
+ apr_table_t *env, md_result_t *result, apr_pool_t *p)
+{
+ const char *acme_id, *token;
+ apr_status_t rv;
+ int notify_server;
+ md_data_t data;
+ int i;
+
+ (void)env;
+ (void)md;
+ if (md_array_str_index(acme_tls_1_domains, authz->domain, 0, 0) < 0) {
+ rv = APR_ENOTIMPL;
+ if (acme_tls_1_domains->nelts) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p,
+ "%s: protocol 'acme-tls/1' seems not enabled for this domain, "
+ "but is enabled for other associated domains. "
+ "Continuing with fingers crossed.", authz->domain);
+ }
+ else {
+ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, p,
+ "%s: protocol 'acme-tls/1' seems not enabled for this or "
+ "any other associated domain. Not attempting challenge "
+ "type tls-alpn-01.", authz->domain);
+ goto out;
+ }
+ }
+ if (APR_SUCCESS != (rv = setup_key_authz(cha, authz, acme, p, &notify_server))) {
+ goto out;
+ }
+
+ /* Create a "tls-alpn-01" certificate for the domain we want to authenticate.
+ * The server will need to answer a TLS connection with SNI == authz->domain
+ * and ALPN protocol "acme-tls/1" with this certificate.
+ */
+ md_data_init_str(&data, cha->key_authz);
+ rv = md_crypt_sha256_digest_hex(&token, p, &data);
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: create tls-alpn-01 validation token",
+ authz->domain);
+ goto out;
+ }
+ acme_id = apr_psprintf(p, "critical,DER:04:20:%s", token);
+
+ /* Each configured key type must be generated to ensure:
+ * that any fallback certs already given to mod_ssl are replaced.
+ * We expect that the validation client (at the CA) can deal with at
+ * least one of them.
+ */
+
+ for (i = 0; i < md_pkeys_spec_count(key_specs); ++i) {
+ char *kfn, *cfn;
+ md_cert_t *cha_cert;
+ md_pkey_t *cha_key;
+ md_pkey_spec_t *key_spec;
+
+ key_spec = md_pkeys_spec_get(key_specs, i);
+ tls_alpn01_fnames(p, key_spec, &kfn, &cfn);
+
+ rv = md_store_load(store, MD_SG_CHALLENGES, authz->domain, cfn,
+ MD_SV_CERT, (void**)&cha_cert, p);
+ if ((APR_SUCCESS == rv && !md_cert_covers_domain(cha_cert, authz->domain))
+ || APR_STATUS_IS_ENOENT(rv)) {
+ if (APR_SUCCESS != (rv = md_pkey_gen(&cha_key, p, key_spec))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: create tls-alpn-01 %s challenge key",
+ authz->domain, md_pkey_spec_name(key_spec));
+ goto out;
+ }
+
+ if (APR_SUCCESS != (rv = md_cert_make_tls_alpn_01(&cha_cert, authz->domain, acme_id, cha_key,
+ apr_time_from_sec(7 * MD_SECS_PER_DAY), p))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: create tls-alpn-01 %s challenge cert",
+ authz->domain, md_pkey_spec_name(key_spec));
+ goto out;
+ }
+
+ if (APR_SUCCESS == (rv = md_store_save(store, p, MD_SG_CHALLENGES, authz->domain, kfn,
+ MD_SV_PKEY, (void*)cha_key, 0))) {
+ rv = md_store_save(store, p, MD_SG_CHALLENGES, authz->domain, cfn,
+ MD_SV_CERT, (void*)cha_cert, 0);
+ }
+ ++notify_server;
+ }
+ }
+
+ if (APR_SUCCESS == rv && notify_server) {
+ authz_req_ctx ctx;
+ const char *event;
+
+ /* Raise event that challenge data has been set up before we tell the
+ ACME server. Clusters might want to distribute it. */
+ event = apr_psprintf(p, "challenge-setup:%s:%s", MD_AUTHZ_TYPE_TLSALPN01, authz->domain);
+ rv = md_result_raise(result, event, p);
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
+ "%s: event '%s' failed. aborting challenge setup",
+ authz->domain, event);
+ goto out;
+ }
+ /* challenge is setup or was changed from previous data, tell ACME server
+ * so it may (re)try verification */
+ authz_req_ctx_init(&ctx, acme, NULL, authz, p);
+ ctx.challenge = cha;
+ rv = md_acme_POST(acme, cha->uri, on_init_authz_resp, authz_http_set, NULL, NULL, &ctx);
+ }
+out:
+ return rv;
+}
+
+static apr_status_t cha_dns_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz,
+ md_acme_t *acme, md_store_t *store,
+ md_pkeys_spec_t *key_specs,
+ apr_array_header_t *acme_tls_1_domains, const md_t *md,
+ apr_table_t *env, md_result_t *result, apr_pool_t *p)
+{
+ const char *token;
+ const char * const *argv;
+ const char *cmdline, *dns01_cmd;
+ apr_status_t rv;
+ int exit_code, notify_server;
+ authz_req_ctx ctx;
+ md_data_t data;
+ const char *event;
+
+ (void)store;
+ (void)key_specs;
+ (void)acme_tls_1_domains;
+
+ dns01_cmd = md->dns01_cmd;
+ if (!dns01_cmd)
+ dns01_cmd = apr_table_get(env, MD_KEY_CMD_DNS01);
+ if (!dns01_cmd) {
+ rv = APR_ENOTIMPL;
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: dns-01 command not set",
+ authz->domain);
+ goto out;
+ }
+
+ if (APR_SUCCESS != (rv = setup_key_authz(cha, authz, acme, p, &notify_server))) {
+ goto out;
+ }
+
+ md_data_init_str(&data, cha->key_authz);
+ rv = md_crypt_sha256_digest64(&token, p, &data);
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: create dns-01 token for %s",
+ md->name, authz->domain);
+ goto out;
+ }
+
+ cmdline = apr_psprintf(p, "%s setup %s %s", dns01_cmd, authz->domain, token);
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p,
+ "%s: dns-01 setup command: %s", authz->domain, cmdline);
+
+ apr_tokenize_to_argv(cmdline, (char***)&argv, p);
+ if (APR_SUCCESS != (rv = md_util_exec(p, argv[0], argv, NULL, &exit_code))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p,
+ "%s: dns-01 setup command failed to execute for %s", md->name, authz->domain);
+ goto out;
+ }
+ if (exit_code) {
+ rv = APR_EGENERAL;
+ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, rv, p,
+ "%s: dns-01 setup command returns %d for %s", md->name, exit_code, authz->domain);
+ goto out;
+ }
+
+ /* Raise event that challenge data has been set up before we tell the
+ ACME server. Clusters might want to distribute it. */
+ event = apr_psprintf(p, "challenge-setup:%s:%s", MD_AUTHZ_TYPE_DNS01, authz->domain);
+ rv = md_result_raise(result, event, p);
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
+ "%s: event '%s' failed. aborting challenge setup",
+ authz->domain, event);
+ goto out;
+ }
+ /* challenge is setup, tell ACME server so it may (re)try verification */
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: dns-01 setup succeeded for %s",
+ md->name, authz->domain);
+ authz_req_ctx_init(&ctx, acme, NULL, authz, p);
+ ctx.challenge = cha;
+ rv = md_acme_POST(acme, cha->uri, on_init_authz_resp, authz_http_set, NULL, NULL, &ctx);
+
+out:
+ return rv;
+}
+
+static apr_status_t cha_dns_01_teardown(md_store_t *store, const char *domain, const md_t *md,
+ apr_table_t *env, apr_pool_t *p)
+{
+ const char * const *argv;
+ const char *cmdline, *dns01_cmd;
+ apr_status_t rv;
+ int exit_code;
+
+ (void)store;
+
+ dns01_cmd = md->dns01_cmd;
+ if (!dns01_cmd)
+ dns01_cmd = apr_table_get(env, MD_KEY_CMD_DNS01);
+ if (!dns01_cmd) {
+ rv = APR_ENOTIMPL;
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "%s: dns-01 command not set for %s",
+ md->name, domain);
+ goto out;
+ }
+
+ cmdline = apr_psprintf(p, "%s teardown %s", dns01_cmd, domain);
+ apr_tokenize_to_argv(cmdline, (char***)&argv, p);
+ if (APR_SUCCESS != (rv = md_util_exec(p, argv[0], argv, NULL, &exit_code)) || exit_code) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p,
+ "%s: dns-01 teardown command failed (exit code=%d) for %s",
+ md->name, exit_code, domain);
+ }
+out:
+ return rv;
+}
+
+static apr_status_t cha_teardown_dir(md_store_t *store, const char *domain, const md_t *md,
+ apr_table_t *env, apr_pool_t *p)
+{
+ (void)md;
+ (void)env;
+ return md_store_purge(store, p, MD_SG_CHALLENGES, domain);
+}
+
+typedef apr_status_t cha_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz,
+ md_acme_t *acme, md_store_t *store,
+ md_pkeys_spec_t *key_specs,
+ apr_array_header_t *acme_tls_1_domains, const md_t *md,
+ apr_table_t *env, md_result_t *result, apr_pool_t *p);
+
+typedef apr_status_t cha_teardown(md_store_t *store, const char *domain, const md_t *md,
+ apr_table_t *env, apr_pool_t *p);
+
+typedef struct {
+ const char *name;
+ cha_setup *setup;
+ cha_teardown *teardown;
+} cha_type;
+
+static const cha_type CHA_TYPES[] = {
+ { MD_AUTHZ_TYPE_HTTP01, cha_http_01_setup, cha_teardown_dir },
+ { MD_AUTHZ_TYPE_TLSALPN01, cha_tls_alpn_01_setup, cha_teardown_dir },
+ { MD_AUTHZ_TYPE_DNS01, cha_dns_01_setup, cha_dns_01_teardown },
+};
+static const apr_size_t CHA_TYPES_LEN = (sizeof(CHA_TYPES)/sizeof(CHA_TYPES[0]));
+
+typedef struct {
+ apr_pool_t *p;
+ const char *type;
+ md_acme_authz_cha_t *accepted;
+ apr_array_header_t *offered;
+} cha_find_ctx;
+
+static apr_status_t collect_offered(void *baton, size_t index, md_json_t *json)
+{
+ cha_find_ctx *ctx = baton;
+ const char *ctype;
+
+ (void)index;
+ if ((ctype = md_json_gets(json, MD_KEY_TYPE, NULL))) {
+ APR_ARRAY_PUSH(ctx->offered, const char*) = apr_pstrdup(ctx->p, ctype);
+ }
+ return 1;
+}
+
+static apr_status_t find_type(void *baton, size_t index, md_json_t *json)
+{
+ cha_find_ctx *ctx = baton;
+
+ const char *ctype = md_json_gets(json, MD_KEY_TYPE, NULL);
+ if (ctype && !apr_strnatcasecmp(ctx->type, ctype)) {
+ ctx->accepted = cha_from_json(ctx->p, index, json);
+ return 0;
+ }
+ return 1;
+}
+
+apr_status_t md_acme_authz_respond(md_acme_authz_t *authz, md_acme_t *acme, md_store_t *store,
+ apr_array_header_t *challenges, md_pkeys_spec_t *key_specs,
+ apr_array_header_t *acme_tls_1_domains, const md_t *md,
+ apr_table_t *env, apr_pool_t *p, const char **psetup_token,
+ md_result_t *result)
+{
+ apr_status_t rv;
+ int i, j;
+ cha_find_ctx fctx;
+ const char *challenge_setup;
+
+ assert(acme);
+ assert(authz);
+ assert(authz->resource);
+
+ fctx.p = p;
+ fctx.accepted = NULL;
+
+ /* Look in the order challenge types are defined:
+ * - if they are offered by the CA, try to set it up
+ * - if setup was successful, we are done and the CA will evaluate us
+ * - if setup failed, continue to look for another supported challenge type
+ * - if there is no overlap in types, tell the user that she has to configure
+ * either more types (dns, tls-alpn-01), make ports available or refrain
+ * from using wildcard domains when dns is not available. etc.
+ * - if there was an overlap, but no setup was successful, report that. We
+ * will retry this, maybe the failure is temporary (e.g. command to setup DNS
+ */
+ md_result_printf(result, 0, "%s: selecting suitable authorization challenge "
+ "type, this domain supports %s",
+ authz->domain, apr_array_pstrcat(p, challenges, ' '));
+ rv = APR_ENOTIMPL;
+ challenge_setup = NULL;
+ for (i = 0; i < challenges->nelts; ++i) {
+ fctx.type = APR_ARRAY_IDX(challenges, i, const char *);
+ fctx.accepted = NULL;
+ md_json_itera(find_type, &fctx, authz->resource, MD_KEY_CHALLENGES, NULL);
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, p,
+ "%s: challenge type '%s' for %s: %s",
+ authz->domain, fctx.type, md->name,
+ fctx.accepted? "maybe acceptable" : "not applicable");
+
+ if (fctx.accepted) {
+ for (j = 0; j < (int)CHA_TYPES_LEN; ++j) {
+ if (!apr_strnatcasecmp(CHA_TYPES[j].name, fctx.accepted->type)) {
+ md_result_activity_printf(result, "Setting up challenge '%s' for domain %s",
+ fctx.accepted->type, authz->domain);
+ rv = CHA_TYPES[j].setup(fctx.accepted, authz, acme, store, key_specs,
+ acme_tls_1_domains, md, env, result, p);
+ if (APR_SUCCESS == rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
+ "%s: set up challenge '%s' for %s",
+ authz->domain, fctx.accepted->type, md->name);
+ challenge_setup = CHA_TYPES[j].name;
+ goto out;
+ }
+ md_result_printf(result, rv, "error setting up challenge '%s' for %s, "
+ "for domain %s, looking for other option",
+ fctx.accepted->type, authz->domain, md->name);
+ md_result_log(result, MD_LOG_INFO);
+ }
+ }
+ }
+ }
+
+out:
+ *psetup_token = (APR_SUCCESS == rv)? apr_psprintf(p, "%s:%s", challenge_setup, authz->domain) : NULL;
+ if (!fctx.accepted || APR_ENOTIMPL == rv) {
+ rv = APR_EINVAL;
+ fctx.offered = apr_array_make(p, 5, sizeof(const char*));
+ md_json_itera(collect_offered, &fctx, authz->resource, MD_KEY_CHALLENGES, NULL);
+ md_result_printf(result, rv, "None of offered challenge types for domain %s are supported. "
+ "The server offered '%s' and available are: '%s'.",
+ authz->domain,
+ apr_array_pstrcat(p, fctx.offered, ' '),
+ apr_array_pstrcat(p, challenges, ' '));
+ result->problem = "challenge-mismatch";
+ md_result_log(result, MD_LOG_ERR);
+ }
+ else if (APR_SUCCESS != rv) {
+ fctx.offered = apr_array_make(p, 5, sizeof(const char*));
+ md_json_itera(collect_offered, &fctx, authz->resource, MD_KEY_CHALLENGES, NULL);
+ md_result_printf(result, rv, "None of the offered challenge types %s offered "
+ "for domain %s could be setup successfully. Please check the "
+ "log for errors.", authz->domain,
+ apr_array_pstrcat(p, fctx.offered, ' '));
+ result->problem = "challenge-setup-failure";
+ md_result_log(result, MD_LOG_ERR);
+ }
+ return rv;
+}
+
+apr_status_t md_acme_authz_teardown(struct md_store_t *store, const char *token,
+ const md_t *md, apr_table_t *env, apr_pool_t *p)
+{
+ char *challenge, *domain;
+ int i;
+
+ if (strchr(token, ':')) {
+ challenge = apr_pstrdup(p, token);
+ domain = strchr(challenge, ':');
+ *domain = '\0'; domain++;
+ for (i = 0; i < (int)CHA_TYPES_LEN; ++i) {
+ if (!apr_strnatcasecmp(CHA_TYPES[i].name, challenge)) {
+ if (CHA_TYPES[i].teardown) {
+ return CHA_TYPES[i].teardown(store, domain, md, env, p);
+ }
+ break;
+ }
+ }
+ }
+ return APR_SUCCESS;
+}
+
diff --git a/modules/md/md_acme_authz.h b/modules/md/md_acme_authz.h
new file mode 100644
index 0000000..d74beeb
--- /dev/null
+++ b/modules/md/md_acme_authz.h
@@ -0,0 +1,79 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mod_md_md_acme_authz_h
+#define mod_md_md_acme_authz_h
+
+struct apr_array_header_t;
+struct apr_table_t;
+struct md_acme_t;
+struct md_acme_acct_t;
+struct md_json_t;
+struct md_store_t;
+struct md_pkey_spec_t;
+struct md_result_t;
+
+typedef struct md_acme_challenge_t md_acme_challenge_t;
+
+/**************************************************************************************************/
+/* authorization request for a specific domain name */
+
+#define MD_AUTHZ_TYPE_DNS01 "dns-01"
+#define MD_AUTHZ_TYPE_HTTP01 "http-01"
+#define MD_AUTHZ_TYPE_TLSALPN01 "tls-alpn-01"
+
+typedef enum {
+ MD_ACME_AUTHZ_S_UNKNOWN,
+ MD_ACME_AUTHZ_S_PENDING,
+ MD_ACME_AUTHZ_S_VALID,
+ MD_ACME_AUTHZ_S_INVALID,
+} md_acme_authz_state_t;
+
+typedef struct md_acme_authz_t md_acme_authz_t;
+
+struct md_acme_authz_t {
+ const char *domain;
+ const char *url;
+ md_acme_authz_state_t state;
+ apr_time_t expires;
+ const char *error_type;
+ const char *error_detail;
+ const struct md_json_t *error_subproblems;
+ struct md_json_t *resource;
+};
+
+#define MD_FN_HTTP01 "acme-http-01.txt"
+
+void tls_alpn01_fnames(apr_pool_t *p, struct md_pkey_spec_t *kspec, char **keyfn, char **certfn );
+
+md_acme_authz_t *md_acme_authz_create(apr_pool_t *p);
+
+apr_status_t md_acme_authz_retrieve(md_acme_t *acme, apr_pool_t *p, const char *url,
+ md_acme_authz_t **pauthz);
+apr_status_t md_acme_authz_update(md_acme_authz_t *authz, struct md_acme_t *acme, apr_pool_t *p);
+
+apr_status_t md_acme_authz_respond(md_acme_authz_t *authz, struct md_acme_t *acme,
+ struct md_store_t *store, apr_array_header_t *challenges,
+ struct md_pkeys_spec_t *key_spec,
+ apr_array_header_t *acme_tls_1_domains, const md_t *md,
+ struct apr_table_t *env,
+ apr_pool_t *p, const char **setup_token,
+ struct md_result_t *result);
+
+apr_status_t md_acme_authz_teardown(struct md_store_t *store, const char *setup_token,
+ const md_t *md, struct apr_table_t *env, apr_pool_t *p);
+
+#endif /* md_acme_authz_h */
diff --git a/modules/md/md_acme_drive.c b/modules/md/md_acme_drive.c
new file mode 100644
index 0000000..4bb04f3
--- /dev/null
+++ b/modules/md/md_acme_drive.c
@@ -0,0 +1,1106 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include <apr_lib.h>
+#include <apr_strings.h>
+#include <apr_buckets.h>
+#include <apr_hash.h>
+#include <apr_uri.h>
+
+#include "md.h"
+#include "md_crypt.h"
+#include "md_json.h"
+#include "md_jws.h"
+#include "md_http.h"
+#include "md_log.h"
+#include "md_result.h"
+#include "md_reg.h"
+#include "md_store.h"
+#include "md_util.h"
+
+#include "md_acme.h"
+#include "md_acme_acct.h"
+#include "md_acme_authz.h"
+#include "md_acme_order.h"
+
+#include "md_acme_drive.h"
+#include "md_acmev2_drive.h"
+
+/**************************************************************************************************/
+/* account setup */
+
+static apr_status_t use_staged_acct(md_acme_t *acme, struct md_store_t *store,
+ const md_t *md, apr_pool_t *p)
+{
+ md_acme_acct_t *acct;
+ md_pkey_t *pkey;
+ apr_status_t rv;
+
+ if (APR_SUCCESS == (rv = md_acme_acct_load(&acct, &pkey, store,
+ MD_SG_STAGING, md->name, acme->p))) {
+ acme->acct_id = NULL;
+ acme->acct = acct;
+ acme->acct_key = pkey;
+ rv = md_acme_acct_validate(acme, NULL, p);
+ }
+ return rv;
+}
+
+static apr_status_t save_acct_staged(md_acme_t *acme, md_store_t *store,
+ const char *md_name, apr_pool_t *p)
+{
+ md_json_t *jacct;
+ apr_status_t rv;
+
+ jacct = md_acme_acct_to_json(acme->acct, p);
+
+ rv = md_store_save(store, p, MD_SG_STAGING, md_name, MD_FN_ACCOUNT, MD_SV_JSON, jacct, 0);
+ if (APR_SUCCESS == rv) {
+ rv = md_store_save(store, p, MD_SG_STAGING, md_name, MD_FN_ACCT_KEY,
+ MD_SV_PKEY, acme->acct_key, 0);
+ }
+ return rv;
+}
+
+apr_status_t md_acme_drive_set_acct(md_proto_driver_t *d, md_result_t *result)
+{
+ md_acme_driver_t *ad = d->baton;
+ md_t *md = ad->md;
+ apr_status_t rv = APR_SUCCESS;
+ int update_md = 0, update_acct = 0;
+
+ md_result_activity_printf(result, "Selecting account to use for %s", d->md->name);
+ md_acme_clear_acct(ad->acme);
+
+ /* Do we have a staged (modified) account? */
+ if (APR_SUCCESS == (rv = use_staged_acct(ad->acme, d->store, md, d->p))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "re-using staged account");
+ }
+ else if (!APR_STATUS_IS_ENOENT(rv)) {
+ goto leave;
+ }
+
+ /* Get an account for the ACME server for this MD */
+ if (!ad->acme->acct && md->ca_account) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "re-use account '%s'", md->ca_account);
+ rv = md_acme_use_acct_for_md(ad->acme, d->store, d->p, md->ca_account, md);
+ if (APR_STATUS_IS_ENOENT(rv) || APR_STATUS_IS_EINVAL(rv)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "rejected %s", md->ca_account);
+ md->ca_account = NULL;
+ update_md = 1;
+ }
+ else if (APR_SUCCESS != rv) {
+ goto leave;
+ }
+ }
+
+ if (!ad->acme->acct && !md->ca_account) {
+ /* Find a local account for server, store at MD */
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: looking at existing accounts",
+ d->proto->protocol);
+ if (APR_SUCCESS == (rv = md_acme_find_acct_for_md(ad->acme, d->store, md))) {
+ md->ca_account = md_acme_acct_id_get(ad->acme);
+ update_md = 1;
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: using account %s (id=%s)",
+ d->proto->protocol, ad->acme->acct->url, md->ca_account);
+ }
+ }
+
+ if (!ad->acme->acct) {
+ /* No account staged, no suitable found in store, register a new one */
+ md_result_activity_printf(result, "Creating new ACME account for %s", d->md->name);
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: creating new account",
+ d->proto->protocol);
+
+ if (!ad->md->contacts || apr_is_empty_array(md->contacts)) {
+ rv = APR_EINVAL;
+ md_result_printf(result, rv, "No contact information is available for MD %s. "
+ "Configure one using the MDContactEmail or ServerAdmin directive.", md->name);
+ md_result_log(result, MD_LOG_ERR);
+ goto leave;
+ }
+
+ /* ACMEv1 allowed registration of accounts without accepted Terms-of-Service.
+ * ACMEv2 requires it. Fail early in this case with a meaningful error message.
+ */
+ if (!md->ca_agreement) {
+ md_result_printf(result, APR_EINVAL,
+ "the CA requires you to accept the terms-of-service "
+ "as specified in <%s>. "
+ "Please read the document that you find at that URL and, "
+ "if you agree to the conditions, configure "
+ "\"MDCertificateAgreement accepted\" "
+ "in your Apache. Then (graceful) restart the server to activate.",
+ ad->acme->ca_agreement);
+ md_result_log(result, MD_LOG_ERR);
+ rv = result->status;
+ goto leave;
+ }
+
+ if (ad->acme->eab_required && (!md->ca_eab_kid || !strcmp("none", md->ca_eab_kid))) {
+ md_result_printf(result, APR_EINVAL,
+ "the CA requires 'External Account Binding' which is not "
+ "configured. This means you need to obtain a 'Key ID' and a "
+ "'HMAC' from the CA and configure that using the "
+ "MDExternalAccountBinding directive in your config. "
+ "The creation of a new ACME account will most likely fail, "
+ "but an attempt is made anyway.",
+ ad->acme->ca_agreement);
+ md_result_log(result, MD_LOG_INFO);
+ }
+
+ rv = md_acme_acct_register(ad->acme, d->store, md, d->p);
+ if (APR_SUCCESS != rv) {
+ if (APR_SUCCESS != ad->acme->last->status) {
+ md_result_dup(result, ad->acme->last);
+ md_result_log(result, MD_LOG_ERR);
+ }
+ goto leave;
+ }
+
+ md->ca_account = NULL;
+ update_md = 1;
+ update_acct = 1;
+ }
+
+leave:
+ /* Persist MD changes in STAGING, so we pick them up on next run */
+ if (APR_SUCCESS == rv && update_md) {
+ rv = md_save(d->store, d->p, MD_SG_STAGING, ad->md, 0);
+ }
+ /* Persist account changes in STAGING, so we pick them up on next run */
+ if (APR_SUCCESS == rv && update_acct) {
+ rv = save_acct_staged(ad->acme, d->store, md->name, d->p);
+ }
+ return rv;
+}
+
+/**************************************************************************************************/
+/* poll cert */
+
+static void get_up_link(md_proto_driver_t *d, apr_table_t *headers)
+{
+ md_acme_driver_t *ad = d->baton;
+
+ ad->chain_up_link = md_link_find_relation(headers, d->p, "up");
+ if (ad->chain_up_link) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p,
+ "server reports up link as %s", ad->chain_up_link);
+ }
+}
+
+static apr_status_t add_http_certs(apr_array_header_t *chain, apr_pool_t *p,
+ const md_http_response_t *res)
+{
+ apr_status_t rv = APR_SUCCESS;
+ const char *ct;
+
+ ct = apr_table_get(res->headers, "Content-Type");
+ ct = md_util_parse_ct(res->req->pool, ct);
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, p,
+ "parse certs from %s -> %d (%s)", res->req->url, res->status, ct);
+ if (ct && !strcmp("application/x-pkcs7-mime", ct)) {
+ /* this looks like a root cert and we do not want those in our chain */
+ goto out;
+ }
+
+ /* Lets try to read one or more certificates */
+ if (APR_SUCCESS != (rv = md_cert_chain_read_http(chain, p, res))
+ && APR_STATUS_IS_ENOENT(rv)) {
+ rv = APR_EAGAIN;
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
+ "cert not in response from %s", res->req->url);
+ }
+out:
+ return rv;
+}
+
+static apr_status_t on_add_cert(md_acme_t *acme, const md_http_response_t *res, void *baton)
+{
+ md_proto_driver_t *d = baton;
+ md_acme_driver_t *ad = d->baton;
+ apr_status_t rv = APR_SUCCESS;
+ int count;
+
+ (void)acme;
+ count = ad->cred->chain->nelts;
+ if (APR_SUCCESS == (rv = add_http_certs(ad->cred->chain, d->p, res))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%d certs parsed",
+ ad->cred->chain->nelts - count);
+ get_up_link(d, res->headers);
+ }
+ return rv;
+}
+
+static apr_status_t get_cert(void *baton, int attempt)
+{
+ md_proto_driver_t *d = baton;
+ md_acme_driver_t *ad = d->baton;
+
+ (void)attempt;
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, d->p, "retrieving cert from %s",
+ ad->order->certificate);
+ return md_acme_GET(ad->acme, ad->order->certificate, NULL, NULL, on_add_cert, NULL, d);
+}
+
+apr_status_t md_acme_drive_cert_poll(md_proto_driver_t *d, int only_once)
+{
+ md_acme_driver_t *ad = d->baton;
+ apr_status_t rv;
+
+ assert(ad->md);
+ assert(ad->acme);
+ assert(ad->order);
+ assert(ad->order->certificate);
+
+ if (only_once) {
+ rv = get_cert(d, 0);
+ }
+ else {
+ rv = md_util_try(get_cert, d, 1, ad->cert_poll_timeout, 0, 0, 1);
+ }
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "poll for cert at %s", ad->order->certificate);
+ return rv;
+}
+
+/**************************************************************************************************/
+/* order finalization */
+
+static apr_status_t on_init_csr_req(md_acme_req_t *req, void *baton)
+{
+ md_proto_driver_t *d = baton;
+ md_acme_driver_t *ad = d->baton;
+ md_json_t *jpayload;
+
+ jpayload = md_json_create(req->p);
+ md_json_sets(ad->csr_der_64, jpayload, MD_KEY_CSR, NULL);
+
+ return md_acme_req_body_init(req, jpayload);
+}
+
+static apr_status_t csr_req(md_acme_t *acme, const md_http_response_t *res, void *baton)
+{
+ md_proto_driver_t *d = baton;
+ md_acme_driver_t *ad = d->baton;
+ const char *location;
+ md_cert_t *cert;
+ apr_status_t rv = APR_SUCCESS;
+
+ (void)acme;
+ location = apr_table_get(res->headers, "location");
+ if (!location) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, d->p,
+ "cert created without giving its location header");
+ return APR_EINVAL;
+ }
+ ad->order->certificate = apr_pstrdup(d->p, location);
+ if (APR_SUCCESS != (rv = md_acme_order_save(d->store, d->p, MD_SG_STAGING,
+ d->md->name, ad->order, 0))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, d->p,
+ "%s: saving cert url %s", d->md->name, location);
+ return rv;
+ }
+
+ /* Check if it already was sent with this response */
+ ad->chain_up_link = NULL;
+ if (APR_SUCCESS == (rv = md_cert_read_http(&cert, d->p, res))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "cert parsed");
+ apr_array_clear(ad->cred->chain);
+ APR_ARRAY_PUSH(ad->cred->chain, md_cert_t*) = cert;
+ get_up_link(d, res->headers);
+ }
+ else if (APR_STATUS_IS_ENOENT(rv)) {
+ rv = APR_SUCCESS;
+ if (location) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p,
+ "cert not in response, need to poll %s", location);
+ }
+ }
+
+ return rv;
+}
+
+/**
+ * Pre-Req: all domains have been validated by the ACME server, e.g. all have AUTHZ
+ * resources that have status 'valid'
+ * - acme_driver->cred keeps the credentials to setup (key spec)
+ * - Setup private key, if not already there
+ * - Generate a CSR with org, contact, etc
+ * - Optionally enable must-staple OCSP extension
+ * - Submit CSR, expect 201 with location
+ * - POLL location for certificate
+ * - store certificate
+ * - retrieve cert chain information from cert
+ * - GET cert chain
+ * - store cert chain
+ */
+apr_status_t md_acme_drive_setup_cred_chain(md_proto_driver_t *d, md_result_t *result)
+{
+ md_acme_driver_t *ad = d->baton;
+ md_pkey_spec_t *spec;
+ md_pkey_t *privkey;
+ apr_status_t rv;
+
+ md_result_activity_printf(result, "Finalizing order for %s", ad->md->name);
+
+ assert(ad->cred);
+ spec = ad->cred->spec;
+
+ rv = md_pkey_load(d->store, MD_SG_STAGING, d->md->name, spec, &privkey, d->p);
+ if (APR_STATUS_IS_ENOENT(rv)) {
+ if (APR_SUCCESS == (rv = md_pkey_gen(&privkey, d->p, spec))) {
+ rv = md_pkey_save(d->store, d->p, MD_SG_STAGING, d->md->name, spec, privkey, 1);
+ }
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p,
+ "%s: generate %s privkey", d->md->name, md_pkey_spec_name(spec));
+ }
+ if (APR_SUCCESS != rv) goto leave;
+
+ md_result_activity_printf(result, "Creating %s CSR", md_pkey_spec_name(spec));
+ rv = md_cert_req_create(&ad->csr_der_64, d->md->name, ad->domains,
+ ad->md->must_staple, privkey, d->p);
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: create %s CSR",
+ d->md->name, md_pkey_spec_name(spec));
+ if (APR_SUCCESS != rv) goto leave;
+
+ md_result_activity_printf(result, "Submitting %s CSR to CA", md_pkey_spec_name(spec));
+ assert(ad->order->finalize);
+ rv = md_acme_POST(ad->acme, ad->order->finalize, on_init_csr_req, NULL, csr_req, NULL, d);
+
+leave:
+ md_acme_report_result(ad->acme, rv, result);
+ return rv;
+}
+
+/**************************************************************************************************/
+/* cert chain retrieval */
+
+static apr_status_t on_add_chain(md_acme_t *acme, const md_http_response_t *res, void *baton)
+{
+ md_proto_driver_t *d = baton;
+ md_acme_driver_t *ad = d->baton;
+ apr_status_t rv = APR_SUCCESS;
+ const char *ct;
+
+ (void)acme;
+ ct = apr_table_get(res->headers, "Content-Type");
+ ct = md_util_parse_ct(res->req->pool, ct);
+ if (ct && !strcmp("application/x-pkcs7-mime", ct)) {
+ /* root cert most likely, end it here */
+ return APR_SUCCESS;
+ }
+
+ if (APR_SUCCESS == (rv = add_http_certs(ad->cred->chain, d->p, res))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "chain cert parsed");
+ get_up_link(d, res->headers);
+ }
+ return rv;
+}
+
+static apr_status_t get_chain(void *baton, int attempt)
+{
+ md_proto_driver_t *d = baton;
+ md_acme_driver_t *ad = d->baton;
+ const char *prev_link = NULL;
+ apr_status_t rv = APR_SUCCESS;
+
+ while (APR_SUCCESS == rv && ad->cred->chain->nelts < 10) {
+ int nelts = ad->cred->chain->nelts;
+
+ if (ad->chain_up_link && (!prev_link || strcmp(prev_link, ad->chain_up_link))) {
+ prev_link = ad->chain_up_link;
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p,
+ "next chain cert at %s", ad->chain_up_link);
+ rv = md_acme_GET(ad->acme, ad->chain_up_link, NULL, NULL, on_add_chain, NULL, d);
+
+ if (APR_SUCCESS == rv && nelts == ad->cred->chain->nelts) {
+ break;
+ }
+ else if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, d->p,
+ "error retrieving certificate from %s", ad->chain_up_link);
+ return rv;
+ }
+ }
+ else if (ad->cred->chain->nelts <= 1) {
+ /* This cannot be the complete chain (no one signs new web certs with their root)
+ * and we did not see a "Link: ...rel=up", so we do not know how to continue. */
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, d->p,
+ "no link header 'up' for new certificate, unable to retrieve chain");
+ rv = APR_EINVAL;
+ break;
+ }
+ else {
+ rv = APR_SUCCESS;
+ break;
+ }
+ }
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, d->p,
+ "got chain with %d certs (%d. attempt)", ad->cred->chain->nelts, attempt);
+ return rv;
+}
+
+static apr_status_t ad_chain_retrieve(md_proto_driver_t *d)
+{
+ md_acme_driver_t *ad = d->baton;
+ apr_status_t rv;
+
+ /* This may be called repeatedly and needs to progress. The relevant state is in
+ * ad->cred->chain the certificate chain, starting with the new cert for the md
+ * ad->order->certificate the url where ACME offers us the new md certificate. This may
+ * be a single one or even the complete chain
+ * ad->chain_up_link in case the last certificate retrieval did not end the chain,
+ * the link header with relation "up" gives us the location
+ * for the next cert in the chain
+ */
+ if (md_array_is_empty(ad->cred->chain)) {
+ /* Need to start at the order */
+ ad->chain_up_link = NULL;
+ if (!ad->order) {
+ rv = APR_EGENERAL;
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, d->p,
+ "%s: asked to retrieve chain, but no order in context", d->md->name);
+ goto out;
+ }
+ if (!ad->order->certificate) {
+ rv = APR_EGENERAL;
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, d->p,
+ "%s: asked to retrieve chain, but no certificate url part of order", d->md->name);
+ goto out;
+ }
+
+ if (APR_SUCCESS != (rv = md_acme_drive_cert_poll(d, 0))) {
+ goto out;
+ }
+ }
+
+ rv = md_util_try(get_chain, d, 0, ad->cert_poll_timeout, 0, 0, 0);
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "chain retrieved");
+
+out:
+ return rv;
+}
+
+/**************************************************************************************************/
+/* ACME driver init */
+
+static apr_status_t acme_driver_preload_init(md_proto_driver_t *d, md_result_t *result)
+{
+ md_acme_driver_t *ad;
+ md_credentials_t *cred;
+ int i;
+
+ md_result_set(result, APR_SUCCESS, NULL);
+
+ ad = apr_pcalloc(d->p, sizeof(*ad));
+
+ d->baton = ad;
+
+ ad->driver = d;
+ ad->authz_monitor_timeout = apr_time_from_sec(30);
+ ad->cert_poll_timeout = apr_time_from_sec(30);
+ ad->ca_challenges = apr_array_make(d->p, 3, sizeof(const char*));
+
+ /* We want to obtain credentials (key+certificate) for every key spec in this MD */
+ ad->creds = apr_array_make(d->p, md_pkeys_spec_count(d->md->pks), sizeof(md_credentials_t*));
+ for (i = 0; i < md_pkeys_spec_count(d->md->pks); ++i) {
+ cred = apr_pcalloc(d->p, sizeof(*cred));
+ cred->spec = md_pkeys_spec_get(d->md->pks, i);
+ cred->chain = apr_array_make(d->p, 5, sizeof(md_cert_t*));
+ APR_ARRAY_PUSH(ad->creds, md_credentials_t*) = cred;
+ }
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, result->status, d->p,
+ "%s: init_base driver", d->md->name);
+ return result->status;
+}
+
+static apr_status_t acme_driver_init(md_proto_driver_t *d, md_result_t *result)
+{
+ md_acme_driver_t *ad;
+ int dis_http, dis_https, dis_alpn_acme, dis_dns;
+ const char *challenge;
+
+ acme_driver_preload_init(d, result);
+ md_result_set(result, APR_SUCCESS, NULL);
+ if (APR_SUCCESS != result->status) goto leave;
+
+ ad = d->baton;
+
+ /* We can only support challenges if the server is reachable from the outside
+ * via port 80 and/or 443. These ports might be mapped for httpd to something
+ * else, but a mapping needs to exist. */
+ challenge = apr_table_get(d->env, MD_KEY_CHALLENGE);
+ if (challenge) {
+ APR_ARRAY_PUSH(ad->ca_challenges, const char*) = apr_pstrdup(d->p, challenge);
+ }
+ else if (d->md->ca_challenges && d->md->ca_challenges->nelts > 0) {
+ /* pre-configured set for this managed domain */
+ apr_array_cat(ad->ca_challenges, d->md->ca_challenges);
+ }
+ else {
+ /* free to chose. Add all we support and see what we get offered */
+ APR_ARRAY_PUSH(ad->ca_challenges, const char*) = MD_AUTHZ_TYPE_TLSALPN01;
+ APR_ARRAY_PUSH(ad->ca_challenges, const char*) = MD_AUTHZ_TYPE_HTTP01;
+ APR_ARRAY_PUSH(ad->ca_challenges, const char*) = MD_AUTHZ_TYPE_DNS01;
+
+ if (!d->can_http && !d->can_https
+ && md_array_str_index(ad->ca_challenges, MD_AUTHZ_TYPE_DNS01, 0, 0) < 0) {
+ md_result_printf(result, APR_EGENERAL,
+ "the server seems neither reachable via http (port 80) nor https (port 443). "
+ "Please look at the MDPortMap configuration directive on how to correct this. "
+ "The ACME protocol needs at least one of those so the CA can talk to the server "
+ "and verify a domain ownership. Alternatively, you may configure support "
+ "for the %s challenge directive.", MD_AUTHZ_TYPE_DNS01);
+ goto leave;
+ }
+
+ dis_http = dis_https = dis_alpn_acme = dis_dns = 0;
+ if (!d->can_http && md_array_str_index(ad->ca_challenges, MD_AUTHZ_TYPE_HTTP01, 0, 1) >= 0) {
+ ad->ca_challenges = md_array_str_remove(d->p, ad->ca_challenges, MD_AUTHZ_TYPE_HTTP01, 0);
+ dis_http = 1;
+ }
+ if (!d->can_https && md_array_str_index(ad->ca_challenges, MD_AUTHZ_TYPE_TLSALPN01, 0, 1) >= 0) {
+ ad->ca_challenges = md_array_str_remove(d->p, ad->ca_challenges, MD_AUTHZ_TYPE_TLSALPN01, 0);
+ dis_https = 1;
+ }
+ if (apr_is_empty_array(d->md->acme_tls_1_domains)
+ && md_array_str_index(ad->ca_challenges, MD_AUTHZ_TYPE_TLSALPN01, 0, 1) >= 0) {
+ ad->ca_challenges = md_array_str_remove(d->p, ad->ca_challenges, MD_AUTHZ_TYPE_TLSALPN01, 0);
+ dis_alpn_acme = 1;
+ }
+ if (!apr_table_get(d->env, MD_KEY_CMD_DNS01)
+ && NULL == d->md->dns01_cmd
+ && md_array_str_index(ad->ca_challenges, MD_AUTHZ_TYPE_DNS01, 0, 1) >= 0) {
+ ad->ca_challenges = md_array_str_remove(d->p, ad->ca_challenges, MD_AUTHZ_TYPE_DNS01, 0);
+ dis_dns = 1;
+ }
+
+ if (apr_is_empty_array(ad->ca_challenges)) {
+ md_result_printf(result, APR_EGENERAL,
+ "None of the ACME challenge methods configured for this domain are suitable.%s%s%s%s",
+ dis_http? " The http: challenge 'http-01' is disabled because the server seems not reachable on public port 80." : "",
+ dis_https? " The https: challenge 'tls-alpn-01' is disabled because the server seems not reachable on public port 443." : "",
+ dis_alpn_acme? " The https: challenge 'tls-alpn-01' is disabled because the Protocols configuration does not include the 'acme-tls/1' protocol." : "",
+ dis_dns? " The DNS challenge 'dns-01' is disabled because the directive 'MDChallengeDns01' is not configured." : ""
+ );
+ goto leave;
+ }
+ }
+
+ md_result_printf(result, 0, "MDomain %s initialized with support for ACME challenges %s",
+ d->md->name, apr_array_pstrcat(d->p, ad->ca_challenges, ' '));
+
+leave:
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, result->status, d->p, "%s: init driver", d->md->name);
+ return result->status;
+}
+
+/**************************************************************************************************/
+/* ACME staging */
+
+static apr_status_t load_missing_creds(md_proto_driver_t *d)
+{
+ md_acme_driver_t *ad = d->baton;
+ md_credentials_t *cred;
+ apr_array_header_t *chain;
+ int i, complete;
+ apr_status_t rv;
+
+ complete = 1;
+ for (i = 0; i < ad->creds->nelts; ++i) {
+ rv = APR_SUCCESS;
+ cred = APR_ARRAY_IDX(ad->creds, i, md_credentials_t*);
+ if (!cred->pkey) {
+ rv = md_pkey_load(d->store, MD_SG_STAGING, d->md->name, cred->spec, &cred->pkey, d->p);
+ }
+ if (APR_SUCCESS == rv && md_array_is_empty(cred->chain)) {
+ rv = md_pubcert_load(d->store, MD_SG_STAGING, d->md->name, cred->spec, &chain, d->p);
+ if (APR_SUCCESS == rv) {
+ apr_array_cat(cred->chain, chain);
+ }
+ }
+ if (APR_SUCCESS == rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, d->p, "%s: credentials staged for %s certificate",
+ d->md->name, md_pkey_spec_name(cred->spec));
+ }
+ else {
+ complete = 0;
+ }
+ }
+ return complete? APR_SUCCESS : APR_EAGAIN;
+}
+
+static apr_status_t acme_renew(md_proto_driver_t *d, md_result_t *result)
+{
+ md_acme_driver_t *ad = d->baton;
+ int reset_staging = d->reset;
+ apr_status_t rv = APR_SUCCESS;
+ apr_time_t now, t, t2;
+ md_credentials_t *cred;
+ const char *ca_effective = NULL;
+ char ts[APR_RFC822_DATE_LEN];
+ int i, first = 0;
+
+ if (!d->md->ca_urls || d->md->ca_urls->nelts <= 0) {
+ /* No CA defined? This is checked in several other places, but lets be sure */
+ md_result_printf(result, APR_INCOMPLETE,
+ "The managed domain %s is missing MDCertificateAuthority", d->md->name);
+ goto out;
+ }
+
+ /* When not explicitly told to reset, we check the existing data. If
+ * it is incomplete or old, we trigger the reset for a clean start. */
+ if (!reset_staging) {
+ md_result_activity_setn(result, "Checking staging area");
+ rv = md_load(d->store, MD_SG_STAGING, d->md->name, &ad->md, d->p);
+ if (APR_SUCCESS == rv) {
+ /* So, we have a copy in staging, but is it a recent or an old one? */
+ if (md_is_newer(d->store, MD_SG_DOMAINS, MD_SG_STAGING, d->md->name, d->p)) {
+ reset_staging = 1;
+ }
+ }
+ else if (APR_STATUS_IS_ENOENT(rv)) {
+ reset_staging = 1;
+ rv = APR_SUCCESS;
+ }
+ }
+
+ /* What CA are we using this time? */
+ if (ad->md && ad->md->ca_effective) {
+ /* There was one chosen on the previous run. Do we stick to it? */
+ ca_effective = ad->md->ca_effective;
+ if (d->md->ca_urls->nelts > 1 && d->attempt >= d->retry_failover) {
+ /* We have more than one CA to choose from and this is the (at least)
+ * third attempt with the same CA. Let's switch to the next one. */
+ int last_idx = md_array_str_index(d->md->ca_urls, ca_effective, 0, 1);
+ if (last_idx >= 0) {
+ int next_idx = (last_idx+1) % d->md->ca_urls->nelts;
+ ca_effective = APR_ARRAY_IDX(d->md->ca_urls, next_idx, const char*);
+ }
+ else {
+ /* not part of current configuration? */
+ ca_effective = NULL;
+ }
+ /* switching CA means we need to wipe the staging area */
+ reset_staging = 1;
+ }
+ }
+
+ if (!ca_effective) {
+ /* None chosen yet, pick the first one configured */
+ ca_effective = APR_ARRAY_IDX(d->md->ca_urls, 0, const char*);
+ }
+
+ if (md_log_is_level(d->p, MD_LOG_DEBUG)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: staging started, "
+ "state=%d, attempt=%d, acme=%s, challenges='%s'",
+ d->md->name, d->md->state, d->attempt, ca_effective,
+ apr_array_pstrcat(d->p, ad->ca_challenges, ' '));
+ }
+
+ if (reset_staging) {
+ md_result_activity_setn(result, "Resetting staging area");
+ /* reset the staging area for this domain */
+ rv = md_store_purge(d->store, d->p, MD_SG_STAGING, d->md->name);
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, d->p,
+ "%s: reset staging area", d->md->name);
+ if (APR_SUCCESS != rv && !APR_STATUS_IS_ENOENT(rv)) {
+ md_result_printf(result, rv, "resetting staging area");
+ goto out;
+ }
+ rv = APR_SUCCESS;
+ ad->md = NULL;
+ ad->order = NULL;
+ }
+
+ md_result_activity_setn(result, "Assessing current status");
+ if (ad->md && ad->md->state == MD_S_MISSING_INFORMATION) {
+ /* ToS agreement is missing. It makes no sense to drive this MD further */
+ md_result_printf(result, APR_INCOMPLETE,
+ "The managed domain %s is missing required information", d->md->name);
+ goto out;
+ }
+
+ if (ad->md && APR_SUCCESS == load_missing_creds(d)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: all credentials staged", d->md->name);
+ goto ready;
+ }
+
+ /* Need to renew */
+ if (!ad->md || !md_array_str_eq(ad->md->ca_urls, d->md->ca_urls, 1)) {
+ md_result_activity_printf(result, "Resetting staging for %s", d->md->name);
+ /* re-initialize staging */
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: setup staging", d->md->name);
+ md_store_purge(d->store, d->p, MD_SG_STAGING, d->md->name);
+ ad->md = md_copy(d->p, d->md);
+ ad->md->ca_effective = ca_effective;
+ ad->md->ca_account = NULL;
+ ad->order = NULL;
+ rv = md_save(d->store, d->p, MD_SG_STAGING, ad->md, 0);
+ if (APR_SUCCESS != rv) {
+ md_result_printf(result, rv, "Saving MD information in staging area.");
+ md_result_log(result, MD_LOG_ERR);
+ goto out;
+ }
+ }
+ if (!ad->domains) {
+ ad->domains = md_dns_make_minimal(d->p, ad->md->domains);
+ }
+
+ md_result_activity_printf(result, "Contacting ACME server for %s at %s",
+ d->md->name, ca_effective);
+ if (APR_SUCCESS != (rv = md_acme_create(&ad->acme, d->p, ca_effective,
+ d->proxy_url, d->ca_file))) {
+ md_result_printf(result, rv, "setup ACME communications");
+ md_result_log(result, MD_LOG_ERR);
+ goto out;
+ }
+ if (APR_SUCCESS != (rv = md_acme_setup(ad->acme, result))) {
+ md_result_log(result, MD_LOG_ERR);
+ goto out;
+ }
+
+ if (APR_SUCCESS != load_missing_creds(d)) {
+ for (i = 0; i < ad->creds->nelts; ++i) {
+ ad->cred = APR_ARRAY_IDX(ad->creds, i, md_credentials_t*);
+ if (!ad->cred->pkey || md_array_is_empty(ad->cred->chain)) {
+ md_result_activity_printf(result, "Driving ACME to renew %s certificate for %s",
+ md_pkey_spec_name(ad->cred->spec),d->md->name);
+ /* The process of setting up challenges and verifying domain
+ * names differs between ACME versions. */
+ switch (MD_ACME_VERSION_MAJOR(ad->acme->version)) {
+ case 1:
+ md_result_printf(result, APR_EINVAL,
+ "ACME server speaks version 1, an obsolete version of the ACME "
+ "protocol that is no longer supported.");
+ rv = result->status;
+ break;
+ default:
+ /* In principle, we only know ACME version 2. But we assume
+ that a new protocol which announces a directory with all members
+ from version 2 will act backward compatible.
+ This is, of course, an assumption...
+ */
+ rv = md_acmev2_drive_renew(ad, d, result);
+ break;
+ }
+ if (APR_SUCCESS != rv) goto out;
+
+ if (md_array_is_empty(ad->cred->chain) || ad->chain_up_link) {
+ md_result_activity_printf(result, "Retrieving %s certificate chain for %s",
+ md_pkey_spec_name(ad->cred->spec), d->md->name);
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p,
+ "%s: retrieving %s certificate chain",
+ d->md->name, md_pkey_spec_name(ad->cred->spec));
+ rv = ad_chain_retrieve(d);
+ if (APR_SUCCESS != rv) {
+ md_result_printf(result, rv, "Unable to retrieve %s certificate chain.",
+ md_pkey_spec_name(ad->cred->spec));
+ goto out;
+ }
+
+ if (!md_array_is_empty(ad->cred->chain)) {
+
+ if (!ad->cred->pkey) {
+ rv = md_pkey_load(d->store, MD_SG_STAGING, d->md->name, ad->cred->spec, &ad->cred->pkey, d->p);
+ if (APR_SUCCESS != rv) {
+ md_result_printf(result, rv, "Loading the private key.");
+ goto out;
+ }
+ }
+
+ if (ad->cred->pkey) {
+ rv = md_check_cert_and_pkey(ad->cred->chain, ad->cred->pkey);
+ if (APR_SUCCESS != rv) {
+ md_result_printf(result, rv, "Certificate and private key do not match.");
+
+ /* Delete the order */
+ md_acme_order_purge(d->store, d->p, MD_SG_STAGING, d->md, d->env);
+
+ goto out;
+ }
+ }
+
+ rv = md_pubcert_save(d->store, d->p, MD_SG_STAGING, d->md->name,
+ ad->cred->spec, ad->cred->chain, 0);
+ if (APR_SUCCESS != rv) {
+ md_result_printf(result, rv, "Saving new %s certificate chain.",
+ md_pkey_spec_name(ad->cred->spec));
+ goto out;
+ }
+ }
+ }
+
+ /* Clean up the order, so the next pkey spec sets up a new one */
+ md_acme_order_purge(d->store, d->p, MD_SG_STAGING, d->md, d->env);
+ }
+ }
+ }
+
+
+ /* As last step, cleanup any order we created so that challenge data
+ * may be removed asap. */
+ md_acme_order_purge(d->store, d->p, MD_SG_STAGING, d->md, d->env);
+
+ /* first time this job ran through */
+ first = 1;
+ready:
+ md_result_activity_setn(result, NULL);
+ /* we should have the complete cert chain now */
+ assert(APR_SUCCESS == load_missing_creds(d));
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, d->p,
+ "%s: certificates ready, activation delay set to %s",
+ d->md->name, md_duration_format(d->p, d->activation_delay));
+
+ /* determine when it should be activated */
+ t = apr_time_now();
+ for (i = 0; i < ad->creds->nelts; ++i) {
+ cred = APR_ARRAY_IDX(ad->creds, i, md_credentials_t*);
+ t2 = md_cert_get_not_before(APR_ARRAY_IDX(cred->chain, 0, md_cert_t*));
+ if (t2 > t) t = t2;
+ }
+ md_result_delay_set(result, t);
+
+ /* If the existing MD is complete and un-expired, delay the activation
+ * to 24 hours after new cert is valid (if there is enough time left), so
+ * that cients with skewed clocks do not see a problem. */
+ now = apr_time_now();
+ if (d->md->state == MD_S_COMPLETE) {
+ apr_time_t valid_until, delay_activation;
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, d->p,
+ "%s: state is COMPLETE, checking existing certificates", d->md->name);
+ valid_until = md_reg_valid_until(d->reg, d->md, d->p);
+ if (d->activation_delay < 0) {
+ /* special simulation for test case */
+ if (first) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p,
+ "%s: delay ready_at to now+1s", d->md->name);
+ md_result_delay_set(result, apr_time_now() + apr_time_from_sec(1));
+ }
+ }
+ else if (valid_until > now) {
+ delay_activation = d->activation_delay;
+ if (delay_activation > (valid_until - now)) {
+ delay_activation = (valid_until - now);
+ }
+ md_result_delay_set(result, result->ready_at + delay_activation);
+ }
+ }
+
+ /* There is a full set staged, to be loaded */
+ apr_rfc822_date(ts, result->ready_at);
+ if (result->ready_at > now) {
+ md_result_printf(result, APR_SUCCESS,
+ "The certificate for the managed domain has been renewed successfully and can "
+ "be used from %s on.", ts);
+ }
+ else {
+ md_result_printf(result, APR_SUCCESS,
+ "The certificate for the managed domain has been renewed successfully and can "
+ "be used (valid since %s). A graceful server restart now is recommended.", ts);
+ }
+
+out:
+ return rv;
+}
+
+static apr_status_t acme_driver_renew(md_proto_driver_t *d, md_result_t *result)
+{
+ apr_status_t rv;
+
+ rv = acme_renew(d, result);
+ md_result_log(result, MD_LOG_DEBUG);
+ return rv;
+}
+
+/**************************************************************************************************/
+/* ACME preload */
+
+static apr_status_t acme_preload(md_proto_driver_t *d, md_store_group_t load_group,
+ const char *name, md_result_t *result)
+{
+ apr_status_t rv;
+ md_pkey_t *acct_key;
+ md_t *md;
+ md_pkey_spec_t *pkspec;
+ md_credentials_t *creds;
+ apr_array_header_t *all_creds;
+ struct md_acme_acct_t *acct;
+ const char *id;
+ int i;
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: preload start", name);
+ /* Load data from MD_SG_STAGING and save it into "load_group".
+ * This serves several purposes:
+ * 1. It's a format check on the input data.
+ * 2. We write back what we read, creating data with our own access permissions
+ * 3. We ignore any other accumulated data in STAGING
+ * 4. Once "load_group" is complete an ok, we can swap/archive groups with a rename
+ * 5. Reading/Writing the data will apply/remove any group specific data encryption.
+ */
+ if (APR_SUCCESS != (rv = md_load(d->store, MD_SG_STAGING, name, &md, d->p))) {
+ md_result_set(result, rv, "loading staged md.json");
+ goto leave;
+ }
+ if (!md->ca_effective) {
+ rv = APR_ENOENT;
+ md_result_set(result, rv, "effective CA url not set");
+ goto leave;
+ }
+
+ all_creds = apr_array_make(d->p, 5, sizeof(md_credentials_t*));
+ for (i = 0; i < md_pkeys_spec_count(md->pks); ++i) {
+ pkspec = md_pkeys_spec_get(md->pks, i);
+ if (APR_SUCCESS != (rv = md_creds_load(d->store, MD_SG_STAGING, name, pkspec, &creds, d->p))) {
+ md_result_printf(result, rv, "loading staged credentials #%d", i);
+ goto leave;
+ }
+ if (!creds->chain) {
+ rv = APR_ENOENT;
+ md_result_printf(result, rv, "no certificate in staged credentials #%d", i);
+ goto leave;
+ }
+ if (APR_SUCCESS != (rv = md_check_cert_and_pkey(creds->chain, creds->pkey))) {
+ md_result_printf(result, rv, "certificate and private key do not match in staged credentials #%d", i);
+ goto leave;
+ }
+ APR_ARRAY_PUSH(all_creds, md_credentials_t*) = creds;
+ }
+
+ /* See if staging holds a new or modified account data */
+ rv = md_acme_acct_load(&acct, &acct_key, d->store, MD_SG_STAGING, name, d->p);
+ if (APR_STATUS_IS_ENOENT(rv)) {
+ acct = NULL;
+ acct_key = NULL;
+ rv = APR_SUCCESS;
+ }
+ else if (APR_SUCCESS != rv) {
+ md_result_set(result, rv, "loading staged account");
+ goto leave;
+ }
+
+ md_result_activity_setn(result, "purging order information");
+ md_acme_order_purge(d->store, d->p, MD_SG_STAGING, md, d->env);
+
+ md_result_activity_setn(result, "purging store tmp space");
+ rv = md_store_purge(d->store, d->p, load_group, name);
+ if (APR_SUCCESS != rv) {
+ md_result_set(result, rv, NULL);
+ goto leave;
+ }
+
+ if (acct) {
+ md_acme_t *acme;
+
+ /* We may have STAGED the same account several times. This happens when
+ * several MDs are renewed at once and need a new account. They will all store
+ * the new account in their own STAGING area. By checking for accounts with
+ * the same url, we save them all into a single one.
+ */
+ md_result_activity_setn(result, "saving staged account");
+ id = md->ca_account;
+ if (!id) {
+ rv = md_acme_acct_id_for_md(&id, d->store, MD_SG_ACCOUNTS, md, d->p);
+ if (APR_STATUS_IS_ENOENT(rv)) {
+ id = NULL;
+ }
+ else if (APR_SUCCESS != rv) {
+ md_result_set(result, rv, "error searching for existing account by url");
+ goto leave;
+ }
+ }
+
+ if (APR_SUCCESS != (rv = md_acme_create(&acme, d->p, md->ca_effective,
+ d->proxy_url, d->ca_file))) {
+ md_result_set(result, rv, "error setting up acme");
+ goto leave;
+ }
+
+ if (APR_SUCCESS != (rv = md_acme_acct_save(d->store, d->p, acme, &id, acct, acct_key))) {
+ md_result_set(result, rv, "error saving account");
+ goto leave;
+ }
+ md->ca_account = id;
+ }
+ else if (!md->ca_account) {
+ /* staging reused another account and did not create a new one. find
+ * the account, if it is already there */
+ rv = md_acme_acct_id_for_md(&id, d->store, MD_SG_ACCOUNTS, md, d->p);
+ if (APR_SUCCESS == rv) {
+ md->ca_account = id;
+ }
+ }
+
+ md_result_activity_setn(result, "saving staged md/privkey/pubcert");
+ if (APR_SUCCESS != (rv = md_save(d->store, d->p, load_group, md, 1))) {
+ md_result_set(result, rv, "writing md.json");
+ goto leave;
+ }
+
+ for (i = 0; i < all_creds->nelts; ++i) {
+ creds = APR_ARRAY_IDX(all_creds, i, md_credentials_t*);
+ if (APR_SUCCESS != (rv = md_creds_save(d->store, d->p, load_group, name, creds, 1))) {
+ md_result_printf(result, rv, "writing credentials #%d", i);
+ goto leave;
+ }
+ }
+
+ md_result_set(result, APR_SUCCESS, "saved staged data successfully");
+
+leave:
+ md_result_log(result, MD_LOG_DEBUG);
+ return rv;
+}
+
+static apr_status_t acme_driver_preload(md_proto_driver_t *d,
+ md_store_group_t group, md_result_t *result)
+{
+ apr_status_t rv;
+
+ rv = acme_preload(d, group, d->md->name, result);
+ md_result_log(result, MD_LOG_DEBUG);
+ return rv;
+}
+
+static apr_status_t acme_complete_md(md_t *md, apr_pool_t *p)
+{
+ (void)p;
+ if (!md->ca_urls || apr_is_empty_array(md->ca_urls)) {
+ md->ca_urls = apr_array_make(p, 3, sizeof(const char *));
+ APR_ARRAY_PUSH(md->ca_urls, const char*) = MD_ACME_DEF_URL;
+ }
+ return APR_SUCCESS;
+}
+
+static md_proto_t ACME_PROTO = {
+ MD_PROTO_ACME, acme_driver_init, acme_driver_renew,
+ acme_driver_preload_init, acme_driver_preload,
+ acme_complete_md,
+};
+
+apr_status_t md_acme_protos_add(apr_hash_t *protos, apr_pool_t *p)
+{
+ (void)p;
+ apr_hash_set(protos, MD_PROTO_ACME, sizeof(MD_PROTO_ACME)-1, &ACME_PROTO);
+ return APR_SUCCESS;
+}
diff --git a/modules/md/md_acme_drive.h b/modules/md/md_acme_drive.h
new file mode 100644
index 0000000..88761fa
--- /dev/null
+++ b/modules/md/md_acme_drive.h
@@ -0,0 +1,55 @@
+/* Copyright 2019 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef md_acme_drive_h
+#define md_acme_drive_h
+
+struct apr_array_header_t;
+struct md_acme_order_t;
+struct md_credentials_t;
+struct md_result_t;
+
+typedef struct md_acme_driver_t {
+ md_proto_driver_t *driver;
+ void *sub_driver;
+
+ md_acme_t *acme;
+ md_t *md;
+ struct apr_array_header_t *domains;
+ apr_array_header_t *ca_challenges;
+
+ int complete;
+ apr_array_header_t *creds; /* the new md_credentials_t */
+
+ struct md_credentials_t *cred; /* credentials currently being processed */
+ const char *chain_up_link; /* Link header "up" from last chain retrieval,
+ needs to be followed */
+
+ struct md_acme_order_t *order;
+ apr_interval_time_t authz_monitor_timeout;
+
+ const char *csr_der_64;
+ apr_interval_time_t cert_poll_timeout;
+
+} md_acme_driver_t;
+
+apr_status_t md_acme_drive_set_acct(struct md_proto_driver_t *d,
+ struct md_result_t *result);
+apr_status_t md_acme_drive_setup_cred_chain(struct md_proto_driver_t *d,
+ struct md_result_t *result);
+apr_status_t md_acme_drive_cert_poll(struct md_proto_driver_t *d, int only_once);
+
+#endif /* md_acme_drive_h */
+
diff --git a/modules/md/md_acme_order.c b/modules/md/md_acme_order.c
new file mode 100644
index 0000000..9e25e84
--- /dev/null
+++ b/modules/md/md_acme_order.c
@@ -0,0 +1,562 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+
+#include <apr_lib.h>
+#include <apr_buckets.h>
+#include <apr_file_info.h>
+#include <apr_file_io.h>
+#include <apr_fnmatch.h>
+#include <apr_hash.h>
+#include <apr_strings.h>
+#include <apr_tables.h>
+
+#include "md.h"
+#include "md_crypt.h"
+#include "md_json.h"
+#include "md_http.h"
+#include "md_log.h"
+#include "md_jws.h"
+#include "md_result.h"
+#include "md_store.h"
+#include "md_util.h"
+
+#include "md_acme.h"
+#include "md_acme_authz.h"
+#include "md_acme_order.h"
+
+
+md_acme_order_t *md_acme_order_create(apr_pool_t *p)
+{
+ md_acme_order_t *order;
+
+ order = apr_pcalloc(p, sizeof(*order));
+ order->p = p;
+ order->authz_urls = apr_array_make(p, 5, sizeof(const char *));
+ order->challenge_setups = apr_array_make(p, 5, sizeof(const char *));
+
+ return order;
+}
+
+/**************************************************************************************************/
+/* order conversion */
+
+#define MD_KEY_CHALLENGE_SETUPS "challenge-setups"
+
+static md_acme_order_st order_st_from_str(const char *s)
+{
+ if (s) {
+ if (!strcmp("valid", s)) {
+ return MD_ACME_ORDER_ST_VALID;
+ }
+ else if (!strcmp("invalid", s)) {
+ return MD_ACME_ORDER_ST_INVALID;
+ }
+ else if (!strcmp("ready", s)) {
+ return MD_ACME_ORDER_ST_READY;
+ }
+ else if (!strcmp("pending", s)) {
+ return MD_ACME_ORDER_ST_PENDING;
+ }
+ else if (!strcmp("processing", s)) {
+ return MD_ACME_ORDER_ST_PROCESSING;
+ }
+ }
+ return MD_ACME_ORDER_ST_PENDING;
+}
+
+static const char *order_st_to_str(md_acme_order_st status)
+{
+ switch (status) {
+ case MD_ACME_ORDER_ST_PENDING:
+ return "pending";
+ case MD_ACME_ORDER_ST_READY:
+ return "ready";
+ case MD_ACME_ORDER_ST_PROCESSING:
+ return "processing";
+ case MD_ACME_ORDER_ST_VALID:
+ return "valid";
+ case MD_ACME_ORDER_ST_INVALID:
+ return "invalid";
+ default:
+ return "invalid";
+ }
+}
+
+md_json_t *md_acme_order_to_json(md_acme_order_t *order, apr_pool_t *p)
+{
+ md_json_t *json = md_json_create(p);
+
+ if (order->url) {
+ md_json_sets(order->url, json, MD_KEY_URL, NULL);
+ }
+ md_json_sets(order_st_to_str(order->status), json, MD_KEY_STATUS, NULL);
+ md_json_setsa(order->authz_urls, json, MD_KEY_AUTHORIZATIONS, NULL);
+ md_json_setsa(order->challenge_setups, json, MD_KEY_CHALLENGE_SETUPS, NULL);
+ if (order->finalize) {
+ md_json_sets(order->finalize, json, MD_KEY_FINALIZE, NULL);
+ }
+ if (order->certificate) {
+ md_json_sets(order->certificate, json, MD_KEY_CERTIFICATE, NULL);
+ }
+ return json;
+}
+
+static void order_update_from_json(md_acme_order_t *order, md_json_t *json, apr_pool_t *p)
+{
+ if (!order->url && md_json_has_key(json, MD_KEY_URL, NULL)) {
+ order->url = md_json_dups(p, json, MD_KEY_URL, NULL);
+ }
+ order->status = order_st_from_str(md_json_gets(json, MD_KEY_STATUS, NULL));
+ if (md_json_has_key(json, MD_KEY_AUTHORIZATIONS, NULL)) {
+ md_json_dupsa(order->authz_urls, p, json, MD_KEY_AUTHORIZATIONS, NULL);
+ }
+ if (md_json_has_key(json, MD_KEY_CHALLENGE_SETUPS, NULL)) {
+ md_json_dupsa(order->challenge_setups, p, json, MD_KEY_CHALLENGE_SETUPS, NULL);
+ }
+ if (md_json_has_key(json, MD_KEY_FINALIZE, NULL)) {
+ order->finalize = md_json_dups(p, json, MD_KEY_FINALIZE, NULL);
+ }
+ if (md_json_has_key(json, MD_KEY_CERTIFICATE, NULL)) {
+ order->certificate = md_json_dups(p, json, MD_KEY_CERTIFICATE, NULL);
+ }
+}
+
+md_acme_order_t *md_acme_order_from_json(md_json_t *json, apr_pool_t *p)
+{
+ md_acme_order_t *order = md_acme_order_create(p);
+
+ order_update_from_json(order, json, p);
+ return order;
+}
+
+apr_status_t md_acme_order_add(md_acme_order_t *order, const char *authz_url)
+{
+ assert(authz_url);
+ if (md_array_str_index(order->authz_urls, authz_url, 0, 1) < 0) {
+ APR_ARRAY_PUSH(order->authz_urls, const char*) = apr_pstrdup(order->p, authz_url);
+ }
+ return APR_SUCCESS;
+}
+
+apr_status_t md_acme_order_remove(md_acme_order_t *order, const char *authz_url)
+{
+ int i;
+
+ assert(authz_url);
+ i = md_array_str_index(order->authz_urls, authz_url, 0, 1);
+ if (i >= 0) {
+ order->authz_urls = md_array_str_remove(order->p, order->authz_urls, authz_url, 1);
+ return APR_SUCCESS;
+ }
+ return APR_ENOENT;
+}
+
+static apr_status_t add_setup_token(md_acme_order_t *order, const char *token)
+{
+ if (md_array_str_index(order->challenge_setups, token, 0, 1) < 0) {
+ APR_ARRAY_PUSH(order->challenge_setups, const char*) = apr_pstrdup(order->p, token);
+ }
+ return APR_SUCCESS;
+}
+
+/**************************************************************************************************/
+/* persistence */
+
+apr_status_t md_acme_order_load(struct md_store_t *store, md_store_group_t group,
+ const char *md_name, md_acme_order_t **pauthz_set,
+ apr_pool_t *p)
+{
+ apr_status_t rv;
+ md_json_t *json;
+ md_acme_order_t *authz_set;
+
+ rv = md_store_load_json(store, group, md_name, MD_FN_ORDER, &json, p);
+ if (APR_SUCCESS == rv) {
+ authz_set = md_acme_order_from_json(json, p);
+ }
+ *pauthz_set = (APR_SUCCESS == rv)? authz_set : NULL;
+ return rv;
+}
+
+static apr_status_t p_save(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+{
+ md_store_t *store = baton;
+ md_json_t *json;
+ md_store_group_t group;
+ md_acme_order_t *set;
+ const char *md_name;
+ int create;
+
+ (void)p;
+ group = (md_store_group_t)va_arg(ap, int);
+ md_name = va_arg(ap, const char *);
+ set = va_arg(ap, md_acme_order_t *);
+ create = va_arg(ap, int);
+
+ json = md_acme_order_to_json(set, ptemp);
+ assert(json);
+ return md_store_save_json(store, ptemp, group, md_name, MD_FN_ORDER, json, create);
+}
+
+apr_status_t md_acme_order_save(struct md_store_t *store, apr_pool_t *p,
+ md_store_group_t group, const char *md_name,
+ md_acme_order_t *authz_set, int create)
+{
+ return md_util_pool_vdo(p_save, store, p, group, md_name, authz_set, create, NULL);
+}
+
+static apr_status_t p_purge(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+{
+ md_store_t *store = baton;
+ md_acme_order_t *order;
+ md_store_group_t group;
+ const md_t *md;
+ const char *setup_token;
+ apr_table_t *env;
+ int i;
+
+ group = (md_store_group_t)va_arg(ap, int);
+ md = va_arg(ap, const md_t *);
+ env = va_arg(ap, apr_table_t *);
+
+ if (APR_SUCCESS == md_acme_order_load(store, group, md->name, &order, p)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "order loaded for %s", md->name);
+ for (i = 0; i < order->challenge_setups->nelts; ++i) {
+ setup_token = APR_ARRAY_IDX(order->challenge_setups, i, const char*);
+ if (setup_token) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p,
+ "order teardown setup %s", setup_token);
+ md_acme_authz_teardown(store, setup_token, md, env, p);
+ }
+ }
+ }
+ return md_store_remove(store, group, md->name, MD_FN_ORDER, ptemp, 1);
+}
+
+apr_status_t md_acme_order_purge(md_store_t *store, apr_pool_t *p, md_store_group_t group,
+ const md_t *md, apr_table_t *env)
+{
+ return md_util_pool_vdo(p_purge, store, p, group, md, env, NULL);
+}
+
+/**************************************************************************************************/
+/* ACMEv2 order requests */
+
+typedef struct {
+ apr_pool_t *p;
+ md_acme_order_t *order;
+ md_acme_t *acme;
+ const char *name;
+ apr_array_header_t *domains;
+ md_result_t *result;
+} order_ctx_t;
+
+#define ORDER_CTX_INIT(ctx, p, o, a, n, d, r) \
+ (ctx)->p = (p); (ctx)->order = (o); (ctx)->acme = (a); \
+ (ctx)->name = (n); (ctx)->domains = d; (ctx)->result = r
+
+static apr_status_t identifier_to_json(void *value, md_json_t *json, apr_pool_t *p, void *baton)
+{
+ md_json_t *jid;
+
+ (void)baton;
+ jid = md_json_create(p);
+ md_json_sets("dns", jid, "type", NULL);
+ md_json_sets(value, jid, "value", NULL);
+ return md_json_setj(jid, json, NULL);
+}
+
+static apr_status_t on_init_order_register(md_acme_req_t *req, void *baton)
+{
+ order_ctx_t *ctx = baton;
+ md_json_t *jpayload;
+
+ jpayload = md_json_create(req->p);
+ md_json_seta(ctx->domains, identifier_to_json, NULL, jpayload, "identifiers", NULL);
+
+ return md_acme_req_body_init(req, jpayload);
+}
+
+static apr_status_t on_order_upd(md_acme_t *acme, apr_pool_t *p, const apr_table_t *hdrs,
+ md_json_t *body, void *baton)
+{
+ order_ctx_t *ctx = baton;
+ const char *location = apr_table_get(hdrs, "location");
+ apr_status_t rv = APR_SUCCESS;
+
+ (void)acme;
+ (void)p;
+ if (!ctx->order) {
+ if (location) {
+ ctx->order = md_acme_order_create(ctx->p);
+ ctx->order->url = apr_pstrdup(ctx->p, location);
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, ctx->p, "new order at %s", location);
+ }
+ else {
+ rv = APR_EINVAL;
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, ctx->p, "new order, no location header");
+ goto out;
+ }
+ }
+
+ order_update_from_json(ctx->order, body, ctx->p);
+out:
+ return rv;
+}
+
+apr_status_t md_acme_order_register(md_acme_order_t **porder, md_acme_t *acme, apr_pool_t *p,
+ const char *name, apr_array_header_t *domains)
+{
+ order_ctx_t ctx;
+ apr_status_t rv;
+
+ assert(MD_ACME_VERSION_MAJOR(acme->version) > 1);
+ ORDER_CTX_INIT(&ctx, p, NULL, acme, name, domains, NULL);
+ rv = md_acme_POST(acme, acme->api.v2.new_order, on_init_order_register, on_order_upd, NULL, NULL, &ctx);
+ *porder = (APR_SUCCESS == rv)? ctx.order : NULL;
+ return rv;
+}
+
+apr_status_t md_acme_order_update(md_acme_order_t *order, md_acme_t *acme,
+ md_result_t *result, apr_pool_t *p)
+{
+ order_ctx_t ctx;
+ apr_status_t rv;
+
+ assert(MD_ACME_VERSION_MAJOR(acme->version) > 1);
+ ORDER_CTX_INIT(&ctx, p, order, acme, NULL, NULL, result);
+ rv = md_acme_GET(acme, order->url, NULL, on_order_upd, NULL, NULL, &ctx);
+ if (APR_SUCCESS != rv && APR_SUCCESS != acme->last->status) {
+ md_result_dup(result, acme->last);
+ }
+ return rv;
+}
+
+static apr_status_t await_ready(void *baton, int attempt)
+{
+ order_ctx_t *ctx = baton;
+ apr_status_t rv = APR_SUCCESS;
+
+ (void)attempt;
+ if (APR_SUCCESS != (rv = md_acme_order_update(ctx->order, ctx->acme,
+ ctx->result, ctx->p))) goto out;
+ switch (ctx->order->status) {
+ case MD_ACME_ORDER_ST_READY:
+ case MD_ACME_ORDER_ST_PROCESSING:
+ case MD_ACME_ORDER_ST_VALID:
+ break;
+ case MD_ACME_ORDER_ST_PENDING:
+ rv = APR_EAGAIN;
+ break;
+ default:
+ rv = APR_EINVAL;
+ break;
+ }
+out:
+ return rv;
+}
+
+apr_status_t md_acme_order_await_ready(md_acme_order_t *order, md_acme_t *acme,
+ const md_t *md, apr_interval_time_t timeout,
+ md_result_t *result, apr_pool_t *p)
+{
+ order_ctx_t ctx;
+ apr_status_t rv;
+
+ assert(MD_ACME_VERSION_MAJOR(acme->version) > 1);
+ ORDER_CTX_INIT(&ctx, p, order, acme, md->name, NULL, result);
+
+ md_result_activity_setn(result, "Waiting for order to become ready");
+ rv = md_util_try(await_ready, &ctx, 0, timeout, 0, 0, 1);
+ md_result_log(result, MD_LOG_DEBUG);
+ return rv;
+}
+
+static apr_status_t await_valid(void *baton, int attempt)
+{
+ order_ctx_t *ctx = baton;
+ apr_status_t rv = APR_SUCCESS;
+
+ (void)attempt;
+ if (APR_SUCCESS != (rv = md_acme_order_update(ctx->order, ctx->acme,
+ ctx->result, ctx->p))) goto out;
+ switch (ctx->order->status) {
+ case MD_ACME_ORDER_ST_VALID:
+ md_result_set(ctx->result, APR_EINVAL, "ACME server order status is 'valid'.");
+ break;
+ case MD_ACME_ORDER_ST_PROCESSING:
+ rv = APR_EAGAIN;
+ break;
+ case MD_ACME_ORDER_ST_INVALID:
+ md_result_set(ctx->result, APR_EINVAL, "ACME server order status is 'invalid'.");
+ rv = APR_EINVAL;
+ break;
+ default:
+ rv = APR_EINVAL;
+ break;
+ }
+out:
+ return rv;
+}
+
+apr_status_t md_acme_order_await_valid(md_acme_order_t *order, md_acme_t *acme,
+ const md_t *md, apr_interval_time_t timeout,
+ md_result_t *result, apr_pool_t *p)
+{
+ order_ctx_t ctx;
+ apr_status_t rv;
+
+ assert(MD_ACME_VERSION_MAJOR(acme->version) > 1);
+ ORDER_CTX_INIT(&ctx, p, order, acme, md->name, NULL, result);
+
+ md_result_activity_setn(result, "Waiting for finalized order to become valid");
+ rv = md_util_try(await_valid, &ctx, 0, timeout, 0, 0, 1);
+ md_result_log(result, MD_LOG_DEBUG);
+ return rv;
+}
+
+/**************************************************************************************************/
+/* processing */
+
+apr_status_t md_acme_order_start_challenges(md_acme_order_t *order, md_acme_t *acme,
+ apr_array_header_t *challenge_types,
+ md_store_t *store, const md_t *md,
+ apr_table_t *env, md_result_t *result,
+ apr_pool_t *p)
+{
+ apr_status_t rv = APR_SUCCESS;
+ md_acme_authz_t *authz;
+ const char *url, *setup_token;
+ int i;
+
+ md_result_activity_printf(result, "Starting challenges for domains");
+ for (i = 0; i < order->authz_urls->nelts; ++i) {
+ url = APR_ARRAY_IDX(order->authz_urls, i, const char*);
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: check AUTHZ at %s", md->name, url);
+
+ if (APR_SUCCESS != (rv = md_acme_authz_retrieve(acme, p, url, &authz))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: check authz for %s",
+ md->name, authz->domain);
+ goto leave;
+ }
+
+ switch (authz->state) {
+ case MD_ACME_AUTHZ_S_VALID:
+ break;
+
+ case MD_ACME_AUTHZ_S_PENDING:
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
+ "%s: authorization pending for %s",
+ md->name, authz->domain);
+ rv = md_acme_authz_respond(authz, acme, store, challenge_types,
+ md->pks,
+ md->acme_tls_1_domains, md,
+ env, p, &setup_token, result);
+ if (APR_SUCCESS != rv) {
+ goto leave;
+ }
+ add_setup_token(order, setup_token);
+ md_acme_order_save(store, p, MD_SG_STAGING, md->name, order, 0);
+ break;
+
+ case MD_ACME_AUTHZ_S_INVALID:
+ rv = APR_EINVAL;
+ if (authz->error_type) {
+ md_result_problem_set(result, rv, authz->error_type, authz->error_detail, NULL);
+ goto leave;
+ }
+ /* fall through */
+ default:
+ rv = APR_EINVAL;
+ md_result_printf(result, rv, "unexpected AUTHZ state %d for domain %s",
+ authz->state, authz->domain);
+ md_result_log(result, MD_LOG_ERR);
+ goto leave;
+ }
+ }
+leave:
+ return rv;
+}
+
+static apr_status_t check_challenges(void *baton, int attempt)
+{
+ order_ctx_t *ctx = baton;
+ const char *url;
+ md_acme_authz_t *authz;
+ apr_status_t rv = APR_SUCCESS;
+ int i;
+
+ for (i = 0; i < ctx->order->authz_urls->nelts; ++i) {
+ url = APR_ARRAY_IDX(ctx->order->authz_urls, i, const char*);
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ctx->p, "%s: check AUTHZ at %s (attempt %d)",
+ ctx->name, url, attempt);
+
+ rv = md_acme_authz_retrieve(ctx->acme, ctx->p, url, &authz);
+ if (APR_SUCCESS == rv) {
+ switch (authz->state) {
+ case MD_ACME_AUTHZ_S_VALID:
+ md_result_printf(ctx->result, rv,
+ "domain authorization for %s is valid", authz->domain);
+ break;
+ case MD_ACME_AUTHZ_S_PENDING:
+ rv = APR_EAGAIN;
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ctx->p,
+ "%s: status pending at %s", authz->domain, authz->url);
+ goto leave;
+ case MD_ACME_AUTHZ_S_INVALID:
+ rv = APR_EINVAL;
+ md_result_printf(ctx->result, rv,
+ "domain authorization for %s failed, CA considers "
+ "answer to challenge invalid%s.",
+ authz->domain, authz->error_type? "" : ", no error given");
+ md_result_log(ctx->result, MD_LOG_ERR);
+ goto leave;
+ default:
+ rv = APR_EINVAL;
+ md_result_printf(ctx->result, rv,
+ "domain authorization for %s failed with state %d",
+ authz->domain, authz->state);
+ md_result_log(ctx->result, MD_LOG_ERR);
+ goto leave;
+ }
+ }
+ else {
+ md_result_printf(ctx->result, rv, "authorization retrieval failed for domain %s",
+ authz->domain);
+ }
+ }
+leave:
+ return rv;
+}
+
+apr_status_t md_acme_order_monitor_authzs(md_acme_order_t *order, md_acme_t *acme,
+ const md_t *md, apr_interval_time_t timeout,
+ md_result_t *result, apr_pool_t *p)
+{
+ order_ctx_t ctx;
+ apr_status_t rv;
+
+ ORDER_CTX_INIT(&ctx, p, order, acme, md->name, NULL, result);
+
+ md_result_activity_printf(result, "Monitoring challenge status for %s", md->name);
+ rv = md_util_try(check_challenges, &ctx, 0, timeout, 0, 0, 1);
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: checked authorizations", md->name);
+ return rv;
+}
+
diff --git a/modules/md/md_acme_order.h b/modules/md/md_acme_order.h
new file mode 100644
index 0000000..4170440
--- /dev/null
+++ b/modules/md/md_acme_order.h
@@ -0,0 +1,91 @@
+/* Copyright 2019 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef md_acme_order_h
+#define md_acme_order_h
+
+struct md_json_t;
+struct md_result_t;
+
+typedef struct md_acme_order_t md_acme_order_t;
+
+typedef enum {
+ MD_ACME_ORDER_ST_PENDING,
+ MD_ACME_ORDER_ST_READY,
+ MD_ACME_ORDER_ST_PROCESSING,
+ MD_ACME_ORDER_ST_VALID,
+ MD_ACME_ORDER_ST_INVALID,
+} md_acme_order_st;
+
+struct md_acme_order_t {
+ apr_pool_t *p;
+ const char *url;
+ md_acme_order_st status;
+ struct apr_array_header_t *authz_urls;
+ struct apr_array_header_t *challenge_setups;
+ struct md_json_t *json;
+ const char *finalize;
+ const char *certificate;
+};
+
+#define MD_FN_ORDER "order.json"
+
+/**************************************************************************************************/
+
+md_acme_order_t *md_acme_order_create(apr_pool_t *p);
+
+apr_status_t md_acme_order_add(md_acme_order_t *order, const char *authz_url);
+apr_status_t md_acme_order_remove(md_acme_order_t *order, const char *authz_url);
+
+struct md_json_t *md_acme_order_to_json(md_acme_order_t *set, apr_pool_t *p);
+md_acme_order_t *md_acme_order_from_json(struct md_json_t *json, apr_pool_t *p);
+
+apr_status_t md_acme_order_load(struct md_store_t *store, md_store_group_t group,
+ const char *md_name, md_acme_order_t **pauthz_set,
+ apr_pool_t *p);
+apr_status_t md_acme_order_save(struct md_store_t *store, apr_pool_t *p,
+ md_store_group_t group, const char *md_name,
+ md_acme_order_t *authz_set, int create);
+
+apr_status_t md_acme_order_purge(struct md_store_t *store, apr_pool_t *p,
+ md_store_group_t group, const md_t *md,
+ apr_table_t *env);
+
+apr_status_t md_acme_order_start_challenges(md_acme_order_t *order, md_acme_t *acme,
+ apr_array_header_t *challenge_types,
+ md_store_t *store, const md_t *md,
+ apr_table_t *env, struct md_result_t *result,
+ apr_pool_t *p);
+
+apr_status_t md_acme_order_monitor_authzs(md_acme_order_t *order, md_acme_t *acme,
+ const md_t *md, apr_interval_time_t timeout,
+ struct md_result_t *result, apr_pool_t *p);
+
+/* ACMEv2 only ************************************************************************************/
+
+apr_status_t md_acme_order_register(md_acme_order_t **porder, md_acme_t *acme, apr_pool_t *p,
+ const char *name, struct apr_array_header_t *domains);
+
+apr_status_t md_acme_order_update(md_acme_order_t *order, md_acme_t *acme,
+ struct md_result_t *result, apr_pool_t *p);
+
+apr_status_t md_acme_order_await_ready(md_acme_order_t *order, md_acme_t *acme,
+ const md_t *md, apr_interval_time_t timeout,
+ struct md_result_t *result, apr_pool_t *p);
+apr_status_t md_acme_order_await_valid(md_acme_order_t *order, md_acme_t *acme,
+ const md_t *md, apr_interval_time_t timeout,
+ struct md_result_t *result, apr_pool_t *p);
+
+#endif /* md_acme_order_h */
diff --git a/modules/md/md_acmev2_drive.c b/modules/md/md_acmev2_drive.c
new file mode 100644
index 0000000..9dfca96
--- /dev/null
+++ b/modules/md/md_acmev2_drive.c
@@ -0,0 +1,181 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include <apr_lib.h>
+#include <apr_strings.h>
+#include <apr_buckets.h>
+#include <apr_hash.h>
+#include <apr_uri.h>
+
+#include "md.h"
+#include "md_crypt.h"
+#include "md_json.h"
+#include "md_jws.h"
+#include "md_http.h"
+#include "md_log.h"
+#include "md_result.h"
+#include "md_reg.h"
+#include "md_store.h"
+#include "md_util.h"
+
+#include "md_acme.h"
+#include "md_acme_acct.h"
+#include "md_acme_authz.h"
+#include "md_acme_order.h"
+
+#include "md_acme_drive.h"
+#include "md_acmev2_drive.h"
+
+
+
+/**************************************************************************************************/
+/* order setup */
+
+/**
+ * Either we have an order stored in the STAGING area, or we need to create a
+ * new one at the ACME server.
+ */
+static apr_status_t ad_setup_order(md_proto_driver_t *d, md_result_t *result, int *pis_new)
+{
+ md_acme_driver_t *ad = d->baton;
+ apr_status_t rv;
+ md_t *md = ad->md;
+
+ assert(ad->md);
+ assert(ad->acme);
+
+ /* For each domain in MD: AUTHZ setup
+ * if an AUTHZ resource is known, check if it is still valid
+ * if known AUTHZ resource is not valid, remove, goto 4.1.1
+ * if no AUTHZ available, create a new one for the domain, store it
+ */
+ if (pis_new) *pis_new = 0;
+ rv = md_acme_order_load(d->store, MD_SG_STAGING, md->name, &ad->order, d->p);
+ if (APR_SUCCESS == rv) {
+ md_result_activity_setn(result, "Loaded order from staging");
+ goto leave;
+ }
+ else if (!APR_STATUS_IS_ENOENT(rv)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: loading order", md->name);
+ md_acme_order_purge(d->store, d->p, MD_SG_STAGING, md, d->env);
+ }
+
+ md_result_activity_setn(result, "Creating new order");
+ rv = md_acme_order_register(&ad->order, ad->acme, d->p, d->md->name, ad->domains);
+ if (APR_SUCCESS !=rv) goto leave;
+ rv = md_acme_order_save(d->store, d->p, MD_SG_STAGING, d->md->name, ad->order, 0);
+ if (APR_SUCCESS != rv) {
+ md_result_set(result, rv, "saving order in staging");
+ }
+ if (pis_new) *pis_new = 1;
+
+leave:
+ md_acme_report_result(ad->acme, rv, result);
+ return rv;
+}
+
+/**************************************************************************************************/
+/* ACMEv2 renewal */
+
+apr_status_t md_acmev2_drive_renew(md_acme_driver_t *ad, md_proto_driver_t *d, md_result_t *result)
+{
+ apr_status_t rv = APR_SUCCESS;
+ int is_new_order = 0;
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: (ACMEv2) need certificate", d->md->name);
+
+ /* Chose (or create) and ACME account to use */
+ rv = md_acme_drive_set_acct(d, result);
+ if (APR_SUCCESS != rv) goto leave;
+
+ if (!md_array_is_empty(ad->cred->chain)) goto leave;
+
+ /* ACMEv2 strategy:
+ * 1. load an md_acme_order_t from STAGING, if present
+ * 2. if no order found, register a new order at ACME server
+ * 3. update the order from the server
+ * 4. Switch order state:
+ * * PENDING: process authz challenges
+ * * READY: finalize the order
+ * * PROCESSING: wait and re-assses later
+ * * VALID: retrieve certificate
+ * * COMPLETE: all done, return success
+ * * INVALID and otherwise: fail renewal, delete local order
+ */
+ if (APR_SUCCESS != (rv = ad_setup_order(d, result, &is_new_order))) {
+ goto leave;
+ }
+
+ rv = md_acme_order_update(ad->order, ad->acme, result, d->p);
+ if (APR_STATUS_IS_ENOENT(rv)
+ || APR_STATUS_IS_EACCES(rv)
+ || MD_ACME_ORDER_ST_INVALID == ad->order->status) {
+ /* order is invalid or no longer known at the ACME server */
+ ad->order = NULL;
+ md_acme_order_purge(d->store, d->p, MD_SG_STAGING, d->md, d->env);
+ }
+ else if (APR_SUCCESS != rv) {
+ goto leave;
+ }
+
+retry:
+ if (!ad->order) {
+ rv = ad_setup_order(d, result, &is_new_order);
+ if (APR_SUCCESS != rv) goto leave;
+ }
+
+ rv = md_acme_order_start_challenges(ad->order, ad->acme, ad->ca_challenges,
+ d->store, d->md, d->env, result, d->p);
+ if (!is_new_order && APR_STATUS_IS_EINVAL(rv)) {
+ /* found 'invalid' domains in previous order, need to start over */
+ ad->order = NULL;
+ md_acme_order_purge(d->store, d->p, MD_SG_STAGING, d->md, d->env);
+ goto retry;
+ }
+ if (APR_SUCCESS != rv) goto leave;
+
+ rv = md_acme_order_monitor_authzs(ad->order, ad->acme, d->md,
+ ad->authz_monitor_timeout, result, d->p);
+ if (APR_SUCCESS != rv) goto leave;
+
+ rv = md_acme_order_await_ready(ad->order, ad->acme, d->md,
+ ad->authz_monitor_timeout, result, d->p);
+ if (APR_SUCCESS != rv) goto leave;
+
+ if (MD_ACME_ORDER_ST_READY == ad->order->status) {
+ rv = md_acme_drive_setup_cred_chain(d, result);
+ if (APR_SUCCESS != rv) goto leave;
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: finalized order", d->md->name);
+ }
+
+ rv = md_acme_order_await_valid(ad->order, ad->acme, d->md,
+ ad->authz_monitor_timeout, result, d->p);
+ if (APR_SUCCESS != rv) goto leave;
+
+ if (!ad->order->certificate) {
+ md_result_set(result, APR_EINVAL, "Order valid, but certificate url is missing.");
+ goto leave;
+ }
+ md_result_set(result, APR_SUCCESS, NULL);
+
+leave:
+ md_result_log(result, MD_LOG_DEBUG);
+ return result->status;
+}
+
diff --git a/modules/md/md_acmev2_drive.h b/modules/md/md_acmev2_drive.h
new file mode 100644
index 0000000..7552c4f
--- /dev/null
+++ b/modules/md/md_acmev2_drive.h
@@ -0,0 +1,27 @@
+/* Copyright 2019 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef md_acmev2_drive_h
+#define md_acmev2_drive_h
+
+struct md_json_t;
+struct md_proto_driver_t;
+struct md_result_t;
+
+apr_status_t md_acmev2_drive_renew(struct md_acme_driver_t *ad,
+ struct md_proto_driver_t *d,
+ struct md_result_t *result);
+
+#endif /* md_acmev2_drive_h */
diff --git a/modules/md/md_core.c b/modules/md/md_core.c
new file mode 100644
index 0000000..7aacff0
--- /dev/null
+++ b/modules/md/md_core.c
@@ -0,0 +1,462 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include <apr_lib.h>
+#include <apr_strings.h>
+#include <apr_uri.h>
+#include <apr_tables.h>
+#include <apr_time.h>
+#include <apr_date.h>
+
+#include "md_json.h"
+#include "md.h"
+#include "md_crypt.h"
+#include "md_log.h"
+#include "md_store.h"
+#include "md_util.h"
+
+
+int md_contains(const md_t *md, const char *domain, int case_sensitive)
+{
+ if (md_array_str_index(md->domains, domain, 0, case_sensitive) >= 0) {
+ return 1;
+ }
+ return md_dns_domains_match(md->domains, domain);
+}
+
+const char *md_common_name(const md_t *md1, const md_t *md2)
+{
+ int i;
+
+ if (md1 == NULL || md1->domains == NULL
+ || md2 == NULL || md2->domains == NULL) {
+ return NULL;
+ }
+
+ for (i = 0; i < md1->domains->nelts; ++i) {
+ const char *name1 = APR_ARRAY_IDX(md1->domains, i, const char*);
+ if (md_contains(md2, name1, 0)) {
+ return name1;
+ }
+ }
+ return NULL;
+}
+
+int md_domains_overlap(const md_t *md1, const md_t *md2)
+{
+ return md_common_name(md1, md2) != NULL;
+}
+
+apr_size_t md_common_name_count(const md_t *md1, const md_t *md2)
+{
+ int i;
+ apr_size_t hits;
+
+ if (md1 == NULL || md1->domains == NULL
+ || md2 == NULL || md2->domains == NULL) {
+ return 0;
+ }
+
+ hits = 0;
+ for (i = 0; i < md1->domains->nelts; ++i) {
+ const char *name1 = APR_ARRAY_IDX(md1->domains, i, const char*);
+ if (md_contains(md2, name1, 0)) {
+ ++hits;
+ }
+ }
+ return hits;
+}
+
+int md_is_covered_by_alt_names(const md_t *md, const struct apr_array_header_t* alt_names)
+{
+ const char *name;
+ int i;
+
+ if (alt_names) {
+ for (i = 0; i < md->domains->nelts; ++i) {
+ name = APR_ARRAY_IDX(md->domains, i, const char *);
+ if (!md_dns_domains_match(alt_names, name)) {
+ return 0;
+ }
+ }
+ return 1;
+ }
+ return 0;
+}
+
+md_t *md_create_empty(apr_pool_t *p)
+{
+ md_t *md = apr_pcalloc(p, sizeof(*md));
+ if (md) {
+ md->domains = apr_array_make(p, 5, sizeof(const char *));
+ md->contacts = apr_array_make(p, 5, sizeof(const char *));
+ md->renew_mode = MD_RENEW_DEFAULT;
+ md->require_https = MD_REQUIRE_UNSET;
+ md->must_staple = -1;
+ md->transitive = -1;
+ md->acme_tls_1_domains = apr_array_make(p, 5, sizeof(const char *));
+ md->stapling = -1;
+ md->defn_name = "unknown";
+ md->defn_line_number = 0;
+ }
+ return md;
+}
+
+int md_equal_domains(const md_t *md1, const md_t *md2, int case_sensitive)
+{
+ int i;
+ if (md1->domains->nelts == md2->domains->nelts) {
+ for (i = 0; i < md1->domains->nelts; ++i) {
+ const char *name1 = APR_ARRAY_IDX(md1->domains, i, const char*);
+ if (!md_contains(md2, name1, case_sensitive)) {
+ return 0;
+ }
+ }
+ return 1;
+ }
+ return 0;
+}
+
+int md_contains_domains(const md_t *md1, const md_t *md2)
+{
+ int i;
+ if (md1->domains->nelts >= md2->domains->nelts) {
+ for (i = 0; i < md2->domains->nelts; ++i) {
+ const char *name2 = APR_ARRAY_IDX(md2->domains, i, const char*);
+ if (!md_contains(md1, name2, 0)) {
+ return 0;
+ }
+ }
+ return 1;
+ }
+ return 0;
+}
+
+md_t *md_get_by_name(struct apr_array_header_t *mds, const char *name)
+{
+ int i;
+ for (i = 0; i < mds->nelts; ++i) {
+ md_t *md = APR_ARRAY_IDX(mds, i, md_t *);
+ if (!strcmp(name, md->name)) {
+ return md;
+ }
+ }
+ return NULL;
+}
+
+md_t *md_get_by_domain(struct apr_array_header_t *mds, const char *domain)
+{
+ int i;
+ for (i = 0; i < mds->nelts; ++i) {
+ md_t *md = APR_ARRAY_IDX(mds, i, md_t *);
+ if (md_contains(md, domain, 0)) {
+ return md;
+ }
+ }
+ return NULL;
+}
+
+md_t *md_get_by_dns_overlap(struct apr_array_header_t *mds, const md_t *md)
+{
+ int i;
+ for (i = 0; i < mds->nelts; ++i) {
+ md_t *o = APR_ARRAY_IDX(mds, i, md_t *);
+ if (strcmp(o->name, md->name) && md_common_name(o, md)) {
+ return o;
+ }
+ }
+ return NULL;
+}
+
+int md_cert_count(const md_t *md)
+{
+ /* cert are defined as a list of static files or a list of private key specs */
+ if (md->cert_files && md->cert_files->nelts) {
+ return md->cert_files->nelts;
+ }
+ return md_pkeys_spec_count(md->pks);
+}
+
+md_t *md_create(apr_pool_t *p, apr_array_header_t *domains)
+{
+ md_t *md;
+
+ md = md_create_empty(p);
+ md->domains = md_array_str_compact(p, domains, 0);
+ md->name = APR_ARRAY_IDX(md->domains, 0, const char *);
+
+ return md;
+}
+
+/**************************************************************************************************/
+/* lifetime */
+
+md_t *md_copy(apr_pool_t *p, const md_t *src)
+{
+ md_t *md;
+
+ md = apr_pcalloc(p, sizeof(*md));
+ if (md) {
+ memcpy(md, src, sizeof(*md));
+ md->domains = apr_array_copy(p, src->domains);
+ md->contacts = apr_array_copy(p, src->contacts);
+ if (src->ca_challenges) {
+ md->ca_challenges = apr_array_copy(p, src->ca_challenges);
+ }
+ md->acme_tls_1_domains = apr_array_copy(p, src->acme_tls_1_domains);
+ md->pks = md_pkeys_spec_clone(p, src->pks);
+ }
+ return md;
+}
+
+md_t *md_clone(apr_pool_t *p, const md_t *src)
+{
+ md_t *md;
+
+ md = apr_pcalloc(p, sizeof(*md));
+ if (md) {
+ md->state = src->state;
+ md->name = apr_pstrdup(p, src->name);
+ md->require_https = src->require_https;
+ md->must_staple = src->must_staple;
+ md->renew_mode = src->renew_mode;
+ md->domains = md_array_str_compact(p, src->domains, 0);
+ md->pks = md_pkeys_spec_clone(p, src->pks);
+ md->renew_window = src->renew_window;
+ md->warn_window = src->warn_window;
+ md->contacts = md_array_str_clone(p, src->contacts);
+ if (src->ca_proto) md->ca_proto = apr_pstrdup(p, src->ca_proto);
+ if (src->ca_urls) {
+ md->ca_urls = md_array_str_clone(p, src->ca_urls);
+ }
+ if (src->ca_effective) md->ca_effective = apr_pstrdup(p, src->ca_effective);
+ if (src->ca_account) md->ca_account = apr_pstrdup(p, src->ca_account);
+ if (src->ca_agreement) md->ca_agreement = apr_pstrdup(p, src->ca_agreement);
+ if (src->defn_name) md->defn_name = apr_pstrdup(p, src->defn_name);
+ md->defn_line_number = src->defn_line_number;
+ if (src->ca_challenges) {
+ md->ca_challenges = md_array_str_clone(p, src->ca_challenges);
+ }
+ md->acme_tls_1_domains = md_array_str_compact(p, src->acme_tls_1_domains, 0);
+ md->stapling = src->stapling;
+ if (src->dns01_cmd) md->dns01_cmd = apr_pstrdup(p, src->dns01_cmd);
+ if (src->cert_files) md->cert_files = md_array_str_clone(p, src->cert_files);
+ if (src->pkey_files) md->pkey_files = md_array_str_clone(p, src->pkey_files);
+ }
+ return md;
+}
+
+/**************************************************************************************************/
+/* format conversion */
+
+md_json_t *md_to_json(const md_t *md, apr_pool_t *p)
+{
+ md_json_t *json = md_json_create(p);
+ if (json) {
+ apr_array_header_t *domains = md_array_str_compact(p, md->domains, 0);
+ md_json_sets(md->name, json, MD_KEY_NAME, NULL);
+ md_json_setsa(domains, json, MD_KEY_DOMAINS, NULL);
+ md_json_setsa(md->contacts, json, MD_KEY_CONTACTS, NULL);
+ md_json_setl(md->transitive, json, MD_KEY_TRANSITIVE, NULL);
+ md_json_sets(md->ca_account, json, MD_KEY_CA, MD_KEY_ACCOUNT, NULL);
+ md_json_sets(md->ca_proto, json, MD_KEY_CA, MD_KEY_PROTO, NULL);
+ md_json_sets(md->ca_effective, json, MD_KEY_CA, MD_KEY_URL, NULL);
+ if (md->ca_urls && !apr_is_empty_array(md->ca_urls)) {
+ md_json_setsa(md->ca_urls, json, MD_KEY_CA, MD_KEY_URLS, NULL);
+ }
+ md_json_sets(md->ca_agreement, json, MD_KEY_CA, MD_KEY_AGREEMENT, NULL);
+ if (!md_pkeys_spec_is_empty(md->pks)) {
+ md_json_setj(md_pkeys_spec_to_json(md->pks, p), json, MD_KEY_PKEY, NULL);
+ }
+ md_json_setl(md->state, json, MD_KEY_STATE, NULL);
+ if (md->state_descr)
+ md_json_sets(md->state_descr, json, MD_KEY_STATE_DESCR, NULL);
+ md_json_setl(md->renew_mode, json, MD_KEY_RENEW_MODE, NULL);
+ if (md->renew_window)
+ md_json_sets(md_timeslice_format(md->renew_window, p), json, MD_KEY_RENEW_WINDOW, NULL);
+ if (md->warn_window)
+ md_json_sets(md_timeslice_format(md->warn_window, p), json, MD_KEY_WARN_WINDOW, NULL);
+ if (md->ca_challenges && md->ca_challenges->nelts > 0) {
+ apr_array_header_t *na;
+ na = md_array_str_compact(p, md->ca_challenges, 0);
+ md_json_setsa(na, json, MD_KEY_CA, MD_KEY_CHALLENGES, NULL);
+ }
+ switch (md->require_https) {
+ case MD_REQUIRE_TEMPORARY:
+ md_json_sets(MD_KEY_TEMPORARY, json, MD_KEY_REQUIRE_HTTPS, NULL);
+ break;
+ case MD_REQUIRE_PERMANENT:
+ md_json_sets(MD_KEY_PERMANENT, json, MD_KEY_REQUIRE_HTTPS, NULL);
+ break;
+ default:
+ break;
+ }
+ md_json_setb(md->must_staple > 0, json, MD_KEY_MUST_STAPLE, NULL);
+ md_json_setsa(md->acme_tls_1_domains, json, MD_KEY_PROTO, MD_KEY_ACME_TLS_1, NULL);
+ if (md->cert_files) md_json_setsa(md->cert_files, json, MD_KEY_CERT_FILES, NULL);
+ if (md->pkey_files) md_json_setsa(md->pkey_files, json, MD_KEY_PKEY_FILES, NULL);
+ md_json_setb(md->stapling > 0, json, MD_KEY_STAPLING, NULL);
+ if (md->dns01_cmd) md_json_sets(md->dns01_cmd, json, MD_KEY_CMD_DNS01, NULL);
+ if (md->ca_eab_kid && strcmp("none", md->ca_eab_kid)) {
+ md_json_sets(md->ca_eab_kid, json, MD_KEY_EAB, MD_KEY_KID, NULL);
+ if (md->ca_eab_hmac) md_json_sets(md->ca_eab_hmac, json, MD_KEY_EAB, MD_KEY_HMAC, NULL);
+ }
+ return json;
+ }
+ return NULL;
+}
+
+md_t *md_from_json(md_json_t *json, apr_pool_t *p)
+{
+ const char *s;
+ md_t *md = md_create_empty(p);
+ if (md) {
+ md->name = md_json_dups(p, json, MD_KEY_NAME, NULL);
+ md_json_dupsa(md->domains, p, json, MD_KEY_DOMAINS, NULL);
+ md_json_dupsa(md->contacts, p, json, MD_KEY_CONTACTS, NULL);
+ md->ca_account = md_json_dups(p, json, MD_KEY_CA, MD_KEY_ACCOUNT, NULL);
+ md->ca_proto = md_json_dups(p, json, MD_KEY_CA, MD_KEY_PROTO, NULL);
+ md->ca_effective = md_json_dups(p, json, MD_KEY_CA, MD_KEY_URL, NULL);
+ if (md_json_has_key(json, MD_KEY_CA, MD_KEY_URLS, NULL)) {
+ md->ca_urls = apr_array_make(p, 5, sizeof(const char*));
+ md_json_dupsa(md->ca_urls, p, json, MD_KEY_CA, MD_KEY_URLS, NULL);
+ }
+ else if (md->ca_effective) {
+ /* compat for old format where we had only a single url */
+ md->ca_urls = apr_array_make(p, 5, sizeof(const char*));
+ APR_ARRAY_PUSH(md->ca_urls, const char*) = md->ca_effective;
+ }
+ md->ca_agreement = md_json_dups(p, json, MD_KEY_CA, MD_KEY_AGREEMENT, NULL);
+ if (md_json_has_key(json, MD_KEY_PKEY, NULL)) {
+ md->pks = md_pkeys_spec_from_json(md_json_getj(json, MD_KEY_PKEY, NULL), p);
+ }
+ md->state = (md_state_t)md_json_getl(json, MD_KEY_STATE, NULL);
+ md->state_descr = md_json_dups(p, json, MD_KEY_STATE_DESCR, NULL);
+ if (MD_S_EXPIRED_DEPRECATED == md->state) md->state = MD_S_COMPLETE;
+ md->renew_mode = (int)md_json_getl(json, MD_KEY_RENEW_MODE, NULL);
+ md->domains = md_array_str_compact(p, md->domains, 0);
+ md->transitive = (int)md_json_getl(json, MD_KEY_TRANSITIVE, NULL);
+ s = md_json_gets(json, MD_KEY_RENEW_WINDOW, NULL);
+ md_timeslice_parse(&md->renew_window, p, s, MD_TIME_LIFE_NORM);
+ s = md_json_gets(json, MD_KEY_WARN_WINDOW, NULL);
+ md_timeslice_parse(&md->warn_window, p, s, MD_TIME_LIFE_NORM);
+ if (md_json_has_key(json, MD_KEY_CA, MD_KEY_CHALLENGES, NULL)) {
+ md->ca_challenges = apr_array_make(p, 5, sizeof(const char*));
+ md_json_dupsa(md->ca_challenges, p, json, MD_KEY_CA, MD_KEY_CHALLENGES, NULL);
+ }
+ md->require_https = MD_REQUIRE_OFF;
+ s = md_json_gets(json, MD_KEY_REQUIRE_HTTPS, NULL);
+ if (s && !strcmp(MD_KEY_TEMPORARY, s)) {
+ md->require_https = MD_REQUIRE_TEMPORARY;
+ }
+ else if (s && !strcmp(MD_KEY_PERMANENT, s)) {
+ md->require_https = MD_REQUIRE_PERMANENT;
+ }
+ md->must_staple = (int)md_json_getb(json, MD_KEY_MUST_STAPLE, NULL);
+ md_json_dupsa(md->acme_tls_1_domains, p, json, MD_KEY_PROTO, MD_KEY_ACME_TLS_1, NULL);
+
+ if (md_json_has_key(json, MD_KEY_CERT_FILES, NULL)) {
+ md->cert_files = apr_array_make(p, 3, sizeof(char*));
+ md->pkey_files = apr_array_make(p, 3, sizeof(char*));
+ md_json_dupsa(md->cert_files, p, json, MD_KEY_CERT_FILES, NULL);
+ md_json_dupsa(md->pkey_files, p, json, MD_KEY_PKEY_FILES, NULL);
+ }
+ md->stapling = (int)md_json_getb(json, MD_KEY_STAPLING, NULL);
+ md->dns01_cmd = md_json_dups(p, json, MD_KEY_CMD_DNS01, NULL);
+ if (md_json_has_key(json, MD_KEY_EAB, NULL)) {
+ md->ca_eab_kid = md_json_dups(p, json, MD_KEY_EAB, MD_KEY_KID, NULL);
+ md->ca_eab_hmac = md_json_dups(p, json, MD_KEY_EAB, MD_KEY_HMAC, NULL);
+ }
+ return md;
+ }
+ return NULL;
+}
+
+md_json_t *md_to_public_json(const md_t *md, apr_pool_t *p)
+{
+ md_json_t *json = md_to_json(md, p);
+ if (md_json_has_key(json, MD_KEY_EAB, MD_KEY_HMAC, NULL)) {
+ md_json_sets("***", json, MD_KEY_EAB, MD_KEY_HMAC, NULL);
+ }
+ return json;
+}
+
+typedef struct {
+ const char *name;
+ const char *url;
+} md_ca_t;
+
+#define LE_ACMEv2_PROD "https://acme-v02.api.letsencrypt.org/directory"
+#define LE_ACMEv2_STAGING "https://acme-staging-v02.api.letsencrypt.org/directory"
+#define BUYPASS_ACME "https://api.buypass.com/acme/directory"
+#define BUYPASS_ACME_TEST "https://api.test4.buypass.no/acme/directory"
+
+static md_ca_t KNOWN_CAs[] = {
+ { "LetsEncrypt", LE_ACMEv2_PROD },
+ { "LetsEncrypt-Test", LE_ACMEv2_STAGING },
+ { "Buypass", BUYPASS_ACME },
+ { "Buypass-Test", BUYPASS_ACME_TEST },
+};
+
+const char *md_get_ca_name_from_url(apr_pool_t *p, const char *url)
+{
+ apr_uri_t uri_parsed;
+ unsigned int i;
+
+ for (i = 0; i < sizeof(KNOWN_CAs)/sizeof(KNOWN_CAs[0]); ++i) {
+ if (!apr_strnatcasecmp(KNOWN_CAs[i].url, url)) {
+ return KNOWN_CAs[i].name;
+ }
+ }
+ if (APR_SUCCESS == apr_uri_parse(p, url, &uri_parsed)) {
+ return uri_parsed.hostname;
+ }
+ return apr_pstrdup(p, url);
+}
+
+apr_status_t md_get_ca_url_from_name(const char **purl, apr_pool_t *p, const char *name)
+{
+ const char *err;
+ unsigned int i;
+ apr_status_t rv = APR_SUCCESS;
+
+ *purl = NULL;
+ for (i = 0; i < sizeof(KNOWN_CAs)/sizeof(KNOWN_CAs[0]); ++i) {
+ if (!apr_strnatcasecmp(KNOWN_CAs[i].name, name)) {
+ *purl = KNOWN_CAs[i].url;
+ goto leave;
+ }
+ }
+ *purl = name;
+ rv = md_util_abs_uri_check(p, name, &err);
+ if (APR_SUCCESS != rv) {
+ apr_array_header_t *names;
+
+ names = apr_array_make(p, 10, sizeof(const char*));
+ for (i = 0; i < sizeof(KNOWN_CAs)/sizeof(KNOWN_CAs[0]); ++i) {
+ APR_ARRAY_PUSH(names, const char *) = KNOWN_CAs[i].name;
+ }
+ *purl = apr_psprintf(p,
+ "The CA name '%s' is not known and it is not a URL either (%s). "
+ "Known CA names are: %s.",
+ name, err, apr_array_pstrcat(p, names, ' '));
+ }
+leave:
+ return rv;
+}
diff --git a/modules/md/md_crypt.c b/modules/md/md_crypt.c
new file mode 100644
index 0000000..f2b0cd5
--- /dev/null
+++ b/modules/md/md_crypt.c
@@ -0,0 +1,2140 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <apr_lib.h>
+#include <apr_buckets.h>
+#include <apr_file_io.h>
+#include <apr_strings.h>
+#include <httpd.h>
+#include <http_core.h>
+
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+#include <openssl/pem.h>
+#include <openssl/rand.h>
+#include <openssl/rsa.h>
+#include <openssl/x509v3.h>
+
+#include "md.h"
+#include "md_crypt.h"
+#include "md_json.h"
+#include "md_log.h"
+#include "md_http.h"
+#include "md_time.h"
+#include "md_util.h"
+
+/* getpid for *NIX */
+#if APR_HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+/* getpid for Windows */
+#if APR_HAVE_PROCESS_H
+#include <process.h>
+#endif
+
+#if defined(LIBRESSL_VERSION_NUMBER)
+/* Missing from LibreSSL */
+#define MD_USE_OPENSSL_PRE_1_1_API (LIBRESSL_VERSION_NUMBER < 0x2070000f)
+#else /* defined(LIBRESSL_VERSION_NUMBER) */
+#define MD_USE_OPENSSL_PRE_1_1_API (OPENSSL_VERSION_NUMBER < 0x10100000L)
+#endif
+
+#if (defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER < 0x3050000fL)) || (OPENSSL_VERSION_NUMBER < 0x10100000L)
+/* Missing from LibreSSL < 3.5.0 and only available since OpenSSL v1.1.x */
+#ifndef OPENSSL_NO_CT
+#define OPENSSL_NO_CT
+#endif
+#endif
+
+#ifndef OPENSSL_NO_CT
+#include <openssl/ct.h>
+#endif
+
+static int initialized;
+
+struct md_pkey_t {
+ apr_pool_t *pool;
+ EVP_PKEY *pkey;
+};
+
+#ifdef MD_HAVE_ARC4RANDOM
+
+static void seed_RAND(int pid)
+{
+ char seed[128];
+
+ (void)pid;
+ arc4random_buf(seed, sizeof(seed));
+ RAND_seed(seed, sizeof(seed));
+}
+
+#else /* ifdef MD_HAVE_ARC4RANDOM */
+
+static int rand_choosenum(int l, int h)
+{
+ int i;
+ char buf[50];
+
+ apr_snprintf(buf, sizeof(buf), "%.0f",
+ (((double)(rand()%RAND_MAX)/RAND_MAX)*(h-l)));
+ i = atoi(buf)+1;
+ if (i < l) i = l;
+ if (i > h) i = h;
+ return i;
+}
+
+static void seed_RAND(int pid)
+{
+ unsigned char stackdata[256];
+ /* stolen from mod_ssl/ssl_engine_rand.c */
+ int n;
+ struct {
+ time_t t;
+ pid_t pid;
+ } my_seed;
+
+ /*
+ * seed in the current time (usually just 4 bytes)
+ */
+ my_seed.t = time(NULL);
+
+ /*
+ * seed in the current process id (usually just 4 bytes)
+ */
+ my_seed.pid = pid;
+
+ RAND_seed((unsigned char *)&my_seed, sizeof(my_seed));
+
+ /*
+ * seed in some current state of the run-time stack (128 bytes)
+ */
+ n = rand_choosenum(0, sizeof(stackdata)-128-1);
+ RAND_seed(stackdata+n, 128);
+}
+
+#endif /*ifdef MD_HAVE_ARC4RANDOM (else part) */
+
+
+apr_status_t md_crypt_init(apr_pool_t *pool)
+{
+ (void)pool;
+
+ if (!initialized) {
+ int pid = getpid();
+
+ ERR_load_crypto_strings();
+ OpenSSL_add_all_algorithms();
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, pool, "initializing RAND");
+ while (!RAND_status()) {
+ seed_RAND(pid);
+ }
+
+ initialized = 1;
+ }
+ return APR_SUCCESS;
+}
+
+static apr_status_t fwrite_buffer(void *baton, apr_file_t *f, apr_pool_t *p)
+{
+ md_data_t *buf = baton;
+ apr_size_t wlen;
+
+ (void)p;
+ return apr_file_write_full(f, buf->data, buf->len, &wlen);
+}
+
+apr_status_t md_rand_bytes(unsigned char *buf, apr_size_t len, apr_pool_t *p)
+{
+ apr_status_t rv;
+
+ if (len > INT_MAX) {
+ return APR_ENOTIMPL;
+ }
+ if (APR_SUCCESS == (rv = md_crypt_init(p))) {
+ RAND_bytes((unsigned char*)buf, (int)len);
+ }
+ return rv;
+}
+
+typedef struct {
+ const char *pass_phrase;
+ int pass_len;
+} passwd_ctx;
+
+static int pem_passwd(char *buf, int size, int rwflag, void *baton)
+{
+ passwd_ctx *ctx = baton;
+
+ (void)rwflag;
+ if (ctx->pass_len > 0) {
+ if (ctx->pass_len < size) {
+ size = (int)ctx->pass_len;
+ }
+ memcpy(buf, ctx->pass_phrase, (size_t)size);
+ } else {
+ return 0;
+ }
+ return size;
+}
+
+/**************************************************************************************************/
+/* date time things */
+
+/* Get the apr time (micro seconds, since 1970) from an ASN1 time, as stored in X509
+ * certificates. OpenSSL now has a utility function, but other *SSL derivatives have
+ * not caughts up yet or chose to ignore. An alternative is implemented, we prefer
+ * however the *SSL to maintain such things.
+ */
+static apr_time_t md_asn1_time_get(const ASN1_TIME* time)
+{
+#if OPENSSL_VERSION_NUMBER < 0x10002000L || (defined(LIBRESSL_VERSION_NUMBER) && \
+ LIBRESSL_VERSION_NUMBER < 0x3060000fL)
+ /* courtesy: https://stackoverflow.com/questions/10975542/asn1-time-to-time-t-conversion#11263731
+ * all bugs are mine */
+ apr_time_exp_t t;
+ apr_time_t ts;
+ const char* str = (const char*) time->data;
+ apr_size_t i = 0;
+
+ memset(&t, 0, sizeof(t));
+
+ if (time->type == V_ASN1_UTCTIME) {/* two digit year */
+ t.tm_year = (str[i++] - '0') * 10;
+ t.tm_year += (str[i++] - '0');
+ if (t.tm_year < 70)
+ t.tm_year += 100;
+ }
+ else if (time->type == V_ASN1_GENERALIZEDTIME) {/* four digit year */
+ t.tm_year = (str[i++] - '0') * 1000;
+ t.tm_year+= (str[i++] - '0') * 100;
+ t.tm_year+= (str[i++] - '0') * 10;
+ t.tm_year+= (str[i++] - '0');
+ t.tm_year -= 1900;
+ }
+ t.tm_mon = (str[i++] - '0') * 10;
+ t.tm_mon += (str[i++] - '0') - 1; /* -1 since January is 0 not 1. */
+ t.tm_mday = (str[i++] - '0') * 10;
+ t.tm_mday+= (str[i++] - '0');
+ t.tm_hour = (str[i++] - '0') * 10;
+ t.tm_hour+= (str[i++] - '0');
+ t.tm_min = (str[i++] - '0') * 10;
+ t.tm_min += (str[i++] - '0');
+ t.tm_sec = (str[i++] - '0') * 10;
+ t.tm_sec += (str[i++] - '0');
+
+ if (APR_SUCCESS == apr_time_exp_gmt_get(&ts, &t)) {
+ return ts;
+ }
+ return 0;
+#else
+ int secs, days;
+ apr_time_t ts = apr_time_now();
+
+ if (ASN1_TIME_diff(&days, &secs, NULL, time)) {
+ ts += apr_time_from_sec((days * MD_SECS_PER_DAY) + secs);
+ }
+ return ts;
+#endif
+}
+
+apr_time_t md_asn1_generalized_time_get(void *ASN1_GENERALIZEDTIME)
+{
+ return md_asn1_time_get(ASN1_GENERALIZEDTIME);
+}
+
+/**************************************************************************************************/
+/* OID/NID things */
+
+static int get_nid(const char *num, const char *sname, const char *lname)
+{
+ /* Funny API, an OID for a feature might be configured or
+ * maybe not. In the second case, we need to add it. But adding
+ * when it already is there is an error... */
+ int nid = OBJ_txt2nid(num);
+ if (NID_undef == nid) {
+ nid = OBJ_create(num, sname, lname);
+ }
+ return nid;
+}
+
+#define MD_GET_NID(x) get_nid(MD_OID_##x##_NUM, MD_OID_##x##_SNAME, MD_OID_##x##_LNAME)
+
+/**************************************************************************************************/
+/* private keys */
+
+md_pkeys_spec_t *md_pkeys_spec_make(apr_pool_t *p)
+{
+ md_pkeys_spec_t *pks;
+
+ pks = apr_pcalloc(p, sizeof(*pks));
+ pks->p = p;
+ pks->specs = apr_array_make(p, 2, sizeof(md_pkey_spec_t*));
+ return pks;
+}
+
+void md_pkeys_spec_add(md_pkeys_spec_t *pks, md_pkey_spec_t *spec)
+{
+ APR_ARRAY_PUSH(pks->specs, md_pkey_spec_t*) = spec;
+}
+
+void md_pkeys_spec_add_default(md_pkeys_spec_t *pks)
+{
+ md_pkey_spec_t *spec;
+
+ spec = apr_pcalloc(pks->p, sizeof(*spec));
+ spec->type = MD_PKEY_TYPE_DEFAULT;
+ md_pkeys_spec_add(pks, spec);
+}
+
+int md_pkeys_spec_contains_rsa(md_pkeys_spec_t *pks)
+{
+ md_pkey_spec_t *spec;
+ int i;
+ for (i = 0; i < pks->specs->nelts; ++i) {
+ spec = APR_ARRAY_IDX(pks->specs, i, md_pkey_spec_t*);
+ if (MD_PKEY_TYPE_RSA == spec->type) return 1;
+ }
+ return 0;
+}
+
+void md_pkeys_spec_add_rsa(md_pkeys_spec_t *pks, unsigned int bits)
+{
+ md_pkey_spec_t *spec;
+
+ spec = apr_pcalloc(pks->p, sizeof(*spec));
+ spec->type = MD_PKEY_TYPE_RSA;
+ spec->params.rsa.bits = bits;
+ md_pkeys_spec_add(pks, spec);
+}
+
+int md_pkeys_spec_contains_ec(md_pkeys_spec_t *pks, const char *curve)
+{
+ md_pkey_spec_t *spec;
+ int i;
+ for (i = 0; i < pks->specs->nelts; ++i) {
+ spec = APR_ARRAY_IDX(pks->specs, i, md_pkey_spec_t*);
+ if (MD_PKEY_TYPE_EC == spec->type
+ && !apr_strnatcasecmp(curve, spec->params.ec.curve)) return 1;
+ }
+ return 0;
+}
+
+void md_pkeys_spec_add_ec(md_pkeys_spec_t *pks, const char *curve)
+{
+ md_pkey_spec_t *spec;
+
+ spec = apr_pcalloc(pks->p, sizeof(*spec));
+ spec->type = MD_PKEY_TYPE_EC;
+ spec->params.ec.curve = apr_pstrdup(pks->p, curve);
+ md_pkeys_spec_add(pks, spec);
+}
+
+md_json_t *md_pkey_spec_to_json(const md_pkey_spec_t *spec, apr_pool_t *p)
+{
+ md_json_t *json = md_json_create(p);
+ if (json) {
+ switch (spec->type) {
+ case MD_PKEY_TYPE_DEFAULT:
+ md_json_sets("Default", json, MD_KEY_TYPE, NULL);
+ break;
+ case MD_PKEY_TYPE_RSA:
+ md_json_sets("RSA", json, MD_KEY_TYPE, NULL);
+ if (spec->params.rsa.bits >= MD_PKEY_RSA_BITS_MIN) {
+ md_json_setl((long)spec->params.rsa.bits, json, MD_KEY_BITS, NULL);
+ }
+ break;
+ case MD_PKEY_TYPE_EC:
+ md_json_sets("EC", json, MD_KEY_TYPE, NULL);
+ if (spec->params.ec.curve) {
+ md_json_sets(spec->params.ec.curve, json, MD_KEY_CURVE, NULL);
+ }
+ break;
+ default:
+ md_json_sets("Unsupported", json, MD_KEY_TYPE, NULL);
+ break;
+ }
+ }
+ return json;
+}
+
+static apr_status_t spec_to_json(void *value, md_json_t *json, apr_pool_t *p, void *baton)
+{
+ md_json_t *jspec;
+
+ (void)baton;
+ jspec = md_pkey_spec_to_json((md_pkey_spec_t*)value, p);
+ return md_json_setj(jspec, json, NULL);
+}
+
+md_json_t *md_pkeys_spec_to_json(const md_pkeys_spec_t *pks, apr_pool_t *p)
+{
+ md_json_t *j;
+
+ if (pks->specs->nelts == 1) {
+ return md_pkey_spec_to_json(md_pkeys_spec_get(pks, 0), p);
+ }
+ j = md_json_create(p);
+ md_json_seta(pks->specs, spec_to_json, (void*)pks, j, "specs", NULL);
+ return md_json_getj(j, "specs", NULL);
+}
+
+md_pkey_spec_t *md_pkey_spec_from_json(struct md_json_t *json, apr_pool_t *p)
+{
+ md_pkey_spec_t *spec = apr_pcalloc(p, sizeof(*spec));
+ const char *s;
+ long l;
+
+ if (spec) {
+ s = md_json_gets(json, MD_KEY_TYPE, NULL);
+ if (!s || !apr_strnatcasecmp("Default", s)) {
+ spec->type = MD_PKEY_TYPE_DEFAULT;
+ }
+ else if (!apr_strnatcasecmp("RSA", s)) {
+ spec->type = MD_PKEY_TYPE_RSA;
+ l = md_json_getl(json, MD_KEY_BITS, NULL);
+ if (l >= MD_PKEY_RSA_BITS_MIN) {
+ spec->params.rsa.bits = (unsigned int)l;
+ }
+ else {
+ spec->params.rsa.bits = MD_PKEY_RSA_BITS_DEF;
+ }
+ }
+ else if (!apr_strnatcasecmp("EC", s)) {
+ spec->type = MD_PKEY_TYPE_EC;
+ s = md_json_gets(json, MD_KEY_CURVE, NULL);
+ if (s) {
+ spec->params.ec.curve = apr_pstrdup(p, s);
+ }
+ else {
+ spec->params.ec.curve = NULL;
+ }
+ }
+ }
+ return spec;
+}
+
+static apr_status_t spec_from_json(void **pvalue, md_json_t *json, apr_pool_t *p, void *baton)
+{
+ (void)baton;
+ *pvalue = md_pkey_spec_from_json(json, p);
+ return APR_SUCCESS;
+}
+
+md_pkeys_spec_t *md_pkeys_spec_from_json(struct md_json_t *json, apr_pool_t *p)
+{
+ md_pkeys_spec_t *pks;
+ md_pkey_spec_t *spec;
+
+ pks = md_pkeys_spec_make(p);
+ if (md_json_is(MD_JSON_TYPE_ARRAY, json, NULL)) {
+ md_json_geta(pks->specs, spec_from_json, pks, json, NULL);
+ }
+ else {
+ spec = md_pkey_spec_from_json(json, p);
+ md_pkeys_spec_add(pks, spec);
+ }
+ return pks;
+}
+
+static int pkey_spec_eq(md_pkey_spec_t *s1, md_pkey_spec_t *s2)
+{
+ if (s1 == s2) {
+ return 1;
+ }
+ if (s1 && s2 && s1->type == s2->type) {
+ switch (s1->type) {
+ case MD_PKEY_TYPE_DEFAULT:
+ return 1;
+ case MD_PKEY_TYPE_RSA:
+ if (s1->params.rsa.bits == s2->params.rsa.bits) {
+ return 1;
+ }
+ break;
+ case MD_PKEY_TYPE_EC:
+ if (s1->params.ec.curve == s2->params.ec.curve) {
+ return 1;
+ }
+ else if (!s1->params.ec.curve || !s2->params.ec.curve) {
+ return 0;
+ }
+ return !strcmp(s1->params.ec.curve, s2->params.ec.curve);
+ }
+ }
+ return 0;
+}
+
+int md_pkeys_spec_eq(md_pkeys_spec_t *pks1, md_pkeys_spec_t *pks2)
+{
+ int i;
+
+ if (pks1 == pks2) {
+ return 1;
+ }
+ if (pks1 && pks2 && pks1->specs->nelts == pks2->specs->nelts) {
+ for(i = 0; i < pks1->specs->nelts; ++i) {
+ if (!pkey_spec_eq(APR_ARRAY_IDX(pks1->specs, i, md_pkey_spec_t *),
+ APR_ARRAY_IDX(pks2->specs, i, md_pkey_spec_t *))) {
+ return 0;
+ }
+ }
+ return 1;
+ }
+ return 0;
+}
+
+static md_pkey_spec_t *pkey_spec_clone(apr_pool_t *p, md_pkey_spec_t *spec)
+{
+ md_pkey_spec_t *nspec;
+
+ nspec = apr_pcalloc(p, sizeof(*nspec));
+ nspec->type = spec->type;
+ switch (spec->type) {
+ case MD_PKEY_TYPE_DEFAULT:
+ break;
+ case MD_PKEY_TYPE_RSA:
+ nspec->params.rsa.bits = spec->params.rsa.bits;
+ break;
+ case MD_PKEY_TYPE_EC:
+ nspec->params.ec.curve = apr_pstrdup(p, spec->params.ec.curve);
+ break;
+ }
+ return nspec;
+}
+
+const char *md_pkey_spec_name(const md_pkey_spec_t *spec)
+{
+ if (!spec) return "rsa";
+ switch (spec->type) {
+ case MD_PKEY_TYPE_DEFAULT:
+ case MD_PKEY_TYPE_RSA:
+ return "rsa";
+ case MD_PKEY_TYPE_EC:
+ return spec->params.ec.curve;
+ }
+ return "unknown";
+}
+
+int md_pkeys_spec_is_empty(const md_pkeys_spec_t *pks)
+{
+ return NULL == pks || 0 == pks->specs->nelts;
+}
+
+md_pkeys_spec_t *md_pkeys_spec_clone(apr_pool_t *p, const md_pkeys_spec_t *pks)
+{
+ md_pkeys_spec_t *npks = NULL;
+ md_pkey_spec_t *spec;
+ int i;
+
+ if (pks && pks->specs->nelts > 0) {
+ npks = apr_pcalloc(p, sizeof(*npks));
+ npks->specs = apr_array_make(p, pks->specs->nelts, sizeof(md_pkey_spec_t*));
+ for (i = 0; i < pks->specs->nelts; ++i) {
+ spec = APR_ARRAY_IDX(pks->specs, i, md_pkey_spec_t*);
+ APR_ARRAY_PUSH(npks->specs, md_pkey_spec_t*) = pkey_spec_clone(p, spec);
+ }
+ }
+ return npks;
+}
+
+int md_pkeys_spec_count(const md_pkeys_spec_t *pks)
+{
+ return md_pkeys_spec_is_empty(pks)? 1 : pks->specs->nelts;
+}
+
+static md_pkey_spec_t PkeySpecDef = { MD_PKEY_TYPE_DEFAULT, {{ 0 }} };
+
+md_pkey_spec_t *md_pkeys_spec_get(const md_pkeys_spec_t *pks, int index)
+{
+ if (md_pkeys_spec_is_empty(pks)) {
+ return index == 1? &PkeySpecDef : NULL;
+ }
+ else if (pks && index >= 0 && index < pks->specs->nelts) {
+ return APR_ARRAY_IDX(pks->specs, index, md_pkey_spec_t*);
+ }
+ return NULL;
+}
+
+static md_pkey_t *make_pkey(apr_pool_t *p)
+{
+ md_pkey_t *pkey = apr_pcalloc(p, sizeof(*pkey));
+ pkey->pool = p;
+ return pkey;
+}
+
+static apr_status_t pkey_cleanup(void *data)
+{
+ md_pkey_t *pkey = data;
+ if (pkey->pkey) {
+ EVP_PKEY_free(pkey->pkey);
+ pkey->pkey = NULL;
+ }
+ return APR_SUCCESS;
+}
+
+void md_pkey_free(md_pkey_t *pkey)
+{
+ pkey_cleanup(pkey);
+}
+
+void *md_pkey_get_EVP_PKEY(struct md_pkey_t *pkey)
+{
+ return pkey->pkey;
+}
+
+apr_status_t md_pkey_fload(md_pkey_t **ppkey, apr_pool_t *p,
+ const char *key, apr_size_t key_len,
+ const char *fname)
+{
+ apr_status_t rv = APR_ENOENT;
+ md_pkey_t *pkey;
+ BIO *bf;
+ passwd_ctx ctx;
+
+ pkey = make_pkey(p);
+ if (NULL != (bf = BIO_new_file(fname, "r"))) {
+ ctx.pass_phrase = key;
+ ctx.pass_len = (int)key_len;
+
+ ERR_clear_error();
+ pkey->pkey = PEM_read_bio_PrivateKey(bf, NULL, pem_passwd, &ctx);
+ BIO_free(bf);
+
+ if (pkey->pkey != NULL) {
+ rv = APR_SUCCESS;
+ apr_pool_cleanup_register(p, pkey, pkey_cleanup, apr_pool_cleanup_null);
+ }
+ else {
+ unsigned long err = ERR_get_error();
+ rv = APR_EINVAL;
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p,
+ "error loading pkey %s: %s (pass phrase was %snull)", fname,
+ ERR_error_string(err, NULL), key? "not " : "");
+ }
+ }
+ *ppkey = (APR_SUCCESS == rv)? pkey : NULL;
+ return rv;
+}
+
+static apr_status_t pkey_to_buffer(md_data_t *buf, md_pkey_t *pkey, apr_pool_t *p,
+ const char *pass, apr_size_t pass_len)
+{
+ BIO *bio = BIO_new(BIO_s_mem());
+ const EVP_CIPHER *cipher = NULL;
+ pem_password_cb *cb = NULL;
+ void *cb_baton = NULL;
+ apr_status_t rv = APR_SUCCESS;
+ passwd_ctx ctx;
+ unsigned long err;
+ int i;
+
+ if (!bio) {
+ return APR_ENOMEM;
+ }
+ if (pass_len > INT_MAX) {
+ rv = APR_EINVAL;
+ goto cleanup;
+ }
+ if (pass && pass_len > 0) {
+ ctx.pass_phrase = pass;
+ ctx.pass_len = (int)pass_len;
+ cb = pem_passwd;
+ cb_baton = &ctx;
+ cipher = EVP_aes_256_cbc();
+ if (!cipher) {
+ rv = APR_ENOTIMPL;
+ goto cleanup;
+ }
+ }
+
+ ERR_clear_error();
+#if 1
+ if (!PEM_write_bio_PKCS8PrivateKey(bio, pkey->pkey, cipher, NULL, 0, cb, cb_baton)) {
+#else
+ if (!PEM_write_bio_PrivateKey(bio, pkey->pkey, cipher, NULL, 0, cb, cb_baton)) {
+#endif
+ err = ERR_get_error();
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "PEM_write key: %ld %s",
+ err, ERR_error_string(err, NULL));
+ rv = APR_EINVAL;
+ goto cleanup;
+ }
+
+ md_data_null(buf);
+ i = BIO_pending(bio);
+ if (i > 0) {
+ buf->data = apr_palloc(p, (apr_size_t)i);
+ i = BIO_read(bio, (char*)buf->data, i);
+ buf->len = (apr_size_t)i;
+ }
+
+cleanup:
+ BIO_free(bio);
+ return rv;
+}
+
+apr_status_t md_pkey_fsave(md_pkey_t *pkey, apr_pool_t *p,
+ const char *pass_phrase, apr_size_t pass_len,
+ const char *fname, apr_fileperms_t perms)
+{
+ md_data_t buffer;
+ apr_status_t rv;
+
+ if (APR_SUCCESS == (rv = pkey_to_buffer(&buffer, pkey, p, pass_phrase, pass_len))) {
+ return md_util_freplace(fname, perms, p, fwrite_buffer, &buffer);
+ }
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "save pkey %s (%s pass phrase, len=%d)",
+ fname, pass_len > 0? "with" : "without", (int)pass_len);
+ return rv;
+}
+
+apr_status_t md_pkey_read_http(md_pkey_t **ppkey, apr_pool_t *pool,
+ const struct md_http_response_t *res)
+{
+ apr_status_t rv;
+ apr_off_t data_len;
+ char *pem_data;
+ apr_size_t pem_len;
+ md_pkey_t *pkey;
+ BIO *bf;
+ passwd_ctx ctx;
+
+ rv = apr_brigade_length(res->body, 1, &data_len);
+ if (APR_SUCCESS != rv) goto leave;
+ if (data_len > 1024*1024) { /* certs usually are <2k each */
+ rv = APR_EINVAL;
+ goto leave;
+ }
+ rv = apr_brigade_pflatten(res->body, &pem_data, &pem_len, res->req->pool);
+ if (APR_SUCCESS != rv) goto leave;
+
+ if (NULL == (bf = BIO_new_mem_buf(pem_data, (int)pem_len))) {
+ rv = APR_ENOMEM;
+ goto leave;
+ }
+ pkey = make_pkey(pool);
+ ctx.pass_phrase = NULL;
+ ctx.pass_len = 0;
+ ERR_clear_error();
+ pkey->pkey = PEM_read_bio_PrivateKey(bf, NULL, NULL, &ctx);
+ BIO_free(bf);
+
+ if (pkey->pkey == NULL) {
+ unsigned long err = ERR_get_error();
+ rv = APR_EINVAL;
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, pool,
+ "error loading pkey from http response: %s",
+ ERR_error_string(err, NULL));
+ goto leave;
+ }
+ rv = APR_SUCCESS;
+ apr_pool_cleanup_register(pool, pkey, pkey_cleanup, apr_pool_cleanup_null);
+
+leave:
+ *ppkey = (APR_SUCCESS == rv)? pkey : NULL;
+ return rv;
+}
+
+/* Determine the message digest used for signing with the given private key.
+ */
+static const EVP_MD *pkey_get_MD(md_pkey_t *pkey)
+{
+ switch (EVP_PKEY_id(pkey->pkey)) {
+#ifdef NID_ED25519
+ case NID_ED25519:
+ return NULL;
+#endif
+#ifdef NID_ED448
+ case NID_ED448:
+ return NULL;
+#endif
+ default:
+ return EVP_sha256();
+ }
+}
+
+static apr_status_t gen_rsa(md_pkey_t **ppkey, apr_pool_t *p, unsigned int bits)
+{
+ EVP_PKEY_CTX *ctx = NULL;
+ apr_status_t rv;
+
+ *ppkey = make_pkey(p);
+ ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
+ if (ctx
+ && EVP_PKEY_keygen_init(ctx) >= 0
+ && EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, (int)bits) >= 0
+ && EVP_PKEY_keygen(ctx, &(*ppkey)->pkey) >= 0) {
+ rv = APR_SUCCESS;
+ }
+ else {
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p, "error generate pkey RSA %d", bits);
+ *ppkey = NULL;
+ rv = APR_EGENERAL;
+ }
+
+ if (ctx != NULL) {
+ EVP_PKEY_CTX_free(ctx);
+ }
+ return rv;
+}
+
+static apr_status_t check_EC_curve(int nid, apr_pool_t *p) {
+ EC_builtin_curve *curves = NULL;
+ size_t nc, i;
+ int rv = APR_ENOENT;
+
+ nc = EC_get_builtin_curves(NULL, 0);
+ if (NULL == (curves = OPENSSL_malloc(sizeof(*curves) * nc)) ||
+ nc != EC_get_builtin_curves(curves, nc)) {
+ rv = APR_EGENERAL;
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p,
+ "error looking up OpenSSL builtin EC curves");
+ goto leave;
+ }
+ for (i = 0; i < nc; ++i) {
+ if (nid == curves[i].nid) {
+ rv = APR_SUCCESS;
+ break;
+ }
+ }
+leave:
+ OPENSSL_free(curves);
+ return rv;
+}
+
+static apr_status_t gen_ec(md_pkey_t **ppkey, apr_pool_t *p, const char *curve)
+{
+ EVP_PKEY_CTX *ctx = NULL;
+ apr_status_t rv;
+ int curve_nid = NID_undef;
+
+ /* 1. Convert the cure into its registered identifier. Curves can be known under
+ * different names.
+ * 2. Determine, if the curve is supported by OpenSSL (or whatever is linked).
+ * 3. Generate the key, respecting the specific quirks some curves require.
+ */
+ curve_nid = EC_curve_nist2nid(curve);
+ /* In case this fails, try some names from other standards, like SECG */
+#ifdef NID_secp384r1
+ if (NID_undef == curve_nid && !apr_strnatcasecmp("secp384r1", curve)) {
+ curve_nid = NID_secp384r1;
+ curve = EC_curve_nid2nist(curve_nid);
+ }
+#endif
+#ifdef NID_X9_62_prime256v1
+ if (NID_undef == curve_nid && !apr_strnatcasecmp("secp256r1", curve)) {
+ curve_nid = NID_X9_62_prime256v1;
+ curve = EC_curve_nid2nist(curve_nid);
+ }
+#endif
+#ifdef NID_X9_62_prime192v1
+ if (NID_undef == curve_nid && !apr_strnatcasecmp("secp192r1", curve)) {
+ curve_nid = NID_X9_62_prime192v1;
+ curve = EC_curve_nid2nist(curve_nid);
+ }
+#endif
+#if defined(NID_X25519) && (!defined(LIBRESSL_VERSION_NUMBER) || \
+ LIBRESSL_VERSION_NUMBER >= 0x3070000fL)
+ if (NID_undef == curve_nid && !apr_strnatcasecmp("X25519", curve)) {
+ curve_nid = NID_X25519;
+ curve = EC_curve_nid2nist(curve_nid);
+ }
+#endif
+ if (NID_undef == curve_nid) {
+ /* OpenSSL object/curve names */
+ curve_nid = OBJ_sn2nid(curve);
+ }
+ if (NID_undef == curve_nid) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "ec curve unknown: %s", curve);
+ rv = APR_ENOTIMPL; goto leave;
+ }
+
+ *ppkey = make_pkey(p);
+ switch (curve_nid) {
+
+#if defined(NID_X25519) && (!defined(LIBRESSL_VERSION_NUMBER) || \
+ LIBRESSL_VERSION_NUMBER >= 0x3070000fL)
+ case NID_X25519:
+ /* no parameters */
+ if (NULL == (ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_X25519, NULL))
+ || EVP_PKEY_keygen_init(ctx) <= 0
+ || EVP_PKEY_keygen(ctx, &(*ppkey)->pkey) <= 0) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p,
+ "error generate EC key for group: %s", curve);
+ rv = APR_EGENERAL; goto leave;
+ }
+ rv = APR_SUCCESS;
+ break;
+#endif
+
+#if defined(NID_X448) && !defined(LIBRESSL_VERSION_NUMBER)
+ case NID_X448:
+ /* no parameters */
+ if (NULL == (ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_X448, NULL))
+ || EVP_PKEY_keygen_init(ctx) <= 0
+ || EVP_PKEY_keygen(ctx, &(*ppkey)->pkey) <= 0) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p,
+ "error generate EC key for group: %s", curve);
+ rv = APR_EGENERAL; goto leave;
+ }
+ rv = APR_SUCCESS;
+ break;
+#endif
+
+ default:
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+ if (APR_SUCCESS != (rv = check_EC_curve(curve_nid, p))) goto leave;
+ if (NULL == (ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL))
+ || EVP_PKEY_paramgen_init(ctx) <= 0
+ || EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, curve_nid) <= 0
+ || EVP_PKEY_CTX_set_ec_param_enc(ctx, OPENSSL_EC_NAMED_CURVE) <= 0
+ || EVP_PKEY_keygen_init(ctx) <= 0
+ || EVP_PKEY_keygen(ctx, &(*ppkey)->pkey) <= 0) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p,
+ "error generate EC key for group: %s", curve);
+ rv = APR_EGENERAL; goto leave;
+ }
+#else
+ if (APR_SUCCESS != (rv = check_EC_curve(curve_nid, p))) goto leave;
+ if (NULL == (ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL))
+ || EVP_PKEY_keygen_init(ctx) <= 0
+ || EVP_PKEY_CTX_ctrl_str(ctx, "ec_paramgen_curve", curve) <= 0
+ || EVP_PKEY_keygen(ctx, &(*ppkey)->pkey) <= 0) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p,
+ "error generate EC key for group: %s", curve);
+ rv = APR_EGENERAL; goto leave;
+ }
+#endif
+ rv = APR_SUCCESS;
+ break;
+ }
+
+leave:
+ if (APR_SUCCESS != rv) *ppkey = NULL;
+ EVP_PKEY_CTX_free(ctx);
+ return rv;
+}
+
+apr_status_t md_pkey_gen(md_pkey_t **ppkey, apr_pool_t *p, md_pkey_spec_t *spec)
+{
+ md_pkey_type_t ptype = spec? spec->type : MD_PKEY_TYPE_DEFAULT;
+ switch (ptype) {
+ case MD_PKEY_TYPE_DEFAULT:
+ return gen_rsa(ppkey, p, MD_PKEY_RSA_BITS_DEF);
+ case MD_PKEY_TYPE_RSA:
+ return gen_rsa(ppkey, p, spec->params.rsa.bits);
+ case MD_PKEY_TYPE_EC:
+ return gen_ec(ppkey, p, spec->params.ec.curve);
+ default:
+ return APR_ENOTIMPL;
+ }
+}
+
+#if MD_USE_OPENSSL_PRE_1_1_API || (defined(LIBRESSL_VERSION_NUMBER) && \
+ LIBRESSL_VERSION_NUMBER < 0x2070000f)
+
+#ifndef NID_tlsfeature
+#define NID_tlsfeature 1020
+#endif
+
+static void RSA_get0_key(const RSA *r,
+ const BIGNUM **n, const BIGNUM **e, const BIGNUM **d)
+{
+ if (n != NULL)
+ *n = r->n;
+ if (e != NULL)
+ *e = r->e;
+ if (d != NULL)
+ *d = r->d;
+}
+
+#endif
+
+static const char *bn64(const BIGNUM *b, apr_pool_t *p)
+{
+ if (b) {
+ md_data_t buffer;
+
+ md_data_pinit(&buffer, (apr_size_t)BN_num_bytes(b), p);
+ if (buffer.data) {
+ BN_bn2bin(b, (unsigned char *)buffer.data);
+ return md_util_base64url_encode(&buffer, p);
+ }
+ }
+ return NULL;
+}
+
+const char *md_pkey_get_rsa_e64(md_pkey_t *pkey, apr_pool_t *p)
+{
+ const BIGNUM *e;
+ RSA *rsa = EVP_PKEY_get1_RSA(pkey->pkey);
+
+ if (!rsa) {
+ return NULL;
+ }
+ RSA_get0_key(rsa, NULL, &e, NULL);
+ return bn64(e, p);
+}
+
+const char *md_pkey_get_rsa_n64(md_pkey_t *pkey, apr_pool_t *p)
+{
+ const BIGNUM *n;
+ RSA *rsa = EVP_PKEY_get1_RSA(pkey->pkey);
+
+ if (!rsa) {
+ return NULL;
+ }
+ RSA_get0_key(rsa, &n, NULL, NULL);
+ return bn64(n, p);
+}
+
+apr_status_t md_crypt_sign64(const char **psign64, md_pkey_t *pkey, apr_pool_t *p,
+ const char *d, size_t dlen)
+{
+ EVP_MD_CTX *ctx = NULL;
+ md_data_t buffer;
+ unsigned int blen;
+ const char *sign64 = NULL;
+ apr_status_t rv = APR_ENOMEM;
+
+ md_data_pinit(&buffer, (apr_size_t)EVP_PKEY_size(pkey->pkey), p);
+ if (buffer.data) {
+ ctx = EVP_MD_CTX_create();
+ if (ctx) {
+ rv = APR_ENOTIMPL;
+ if (EVP_SignInit_ex(ctx, EVP_sha256(), NULL)) {
+ rv = APR_EGENERAL;
+ if (EVP_SignUpdate(ctx, d, dlen)) {
+ if (EVP_SignFinal(ctx, (unsigned char*)buffer.data, &blen, pkey->pkey)) {
+ buffer.len = blen;
+ sign64 = md_util_base64url_encode(&buffer, p);
+ if (sign64) {
+ rv = APR_SUCCESS;
+ }
+ }
+ }
+ }
+ }
+
+ if (ctx) {
+ EVP_MD_CTX_destroy(ctx);
+ }
+ }
+
+ if (rv != APR_SUCCESS) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, "signing");
+ }
+
+ *psign64 = sign64;
+ return rv;
+}
+
+static apr_status_t sha256_digest(md_data_t **pdigest, apr_pool_t *p, const md_data_t *buf)
+{
+ EVP_MD_CTX *ctx = NULL;
+ md_data_t *digest;
+ apr_status_t rv = APR_ENOMEM;
+ unsigned int dlen;
+
+ digest = md_data_pmake(EVP_MAX_MD_SIZE, p);
+ ctx = EVP_MD_CTX_create();
+ if (ctx) {
+ rv = APR_ENOTIMPL;
+ if (EVP_DigestInit_ex(ctx, EVP_sha256(), NULL)) {
+ rv = APR_EGENERAL;
+ if (EVP_DigestUpdate(ctx, (unsigned char*)buf->data, buf->len)) {
+ if (EVP_DigestFinal(ctx, (unsigned char*)digest->data, &dlen)) {
+ digest->len = dlen;
+ rv = APR_SUCCESS;
+ }
+ }
+ }
+ }
+ if (ctx) {
+ EVP_MD_CTX_destroy(ctx);
+ }
+ *pdigest = (APR_SUCCESS == rv)? digest : NULL;
+ return rv;
+}
+
+apr_status_t md_crypt_sha256_digest64(const char **pdigest64, apr_pool_t *p, const md_data_t *d)
+{
+ const char *digest64 = NULL;
+ md_data_t *digest;
+ apr_status_t rv;
+
+ if (APR_SUCCESS == (rv = sha256_digest(&digest, p, d))) {
+ if (NULL == (digest64 = md_util_base64url_encode(digest, p))) {
+ rv = APR_EGENERAL;
+ }
+ }
+ *pdigest64 = digest64;
+ return rv;
+}
+
+apr_status_t md_crypt_sha256_digest_hex(const char **pdigesthex, apr_pool_t *p,
+ const md_data_t *data)
+{
+ md_data_t *digest;
+ apr_status_t rv;
+
+ if (APR_SUCCESS == (rv = sha256_digest(&digest, p, data))) {
+ return md_data_to_hex(pdigesthex, 0, p, digest);
+ }
+ *pdigesthex = NULL;
+ return rv;
+}
+
+apr_status_t md_crypt_hmac64(const char **pmac64, const md_data_t *hmac_key,
+ apr_pool_t *p, const char *d, size_t dlen)
+{
+ const char *mac64 = NULL;
+ unsigned char *s;
+ unsigned int digest_len = 0;
+ md_data_t *digest;
+ apr_status_t rv = APR_SUCCESS;
+
+ digest = md_data_pmake(EVP_MAX_MD_SIZE, p);
+ s = HMAC(EVP_sha256(), (const unsigned char*)hmac_key->data, (int)hmac_key->len,
+ (const unsigned char*)d, (size_t)dlen,
+ (unsigned char*)digest->data, &digest_len);
+ if (!s) {
+ rv = APR_EINVAL;
+ goto cleanup;
+ }
+ digest->len = digest_len;
+ mac64 = md_util_base64url_encode(digest, p);
+
+cleanup:
+ *pmac64 = (APR_SUCCESS == rv)? mac64 : NULL;
+ return rv;
+}
+
+/**************************************************************************************************/
+/* certificates */
+
+struct md_cert_t {
+ apr_pool_t *pool;
+ X509 *x509;
+ apr_array_header_t *alt_names;
+};
+
+static apr_status_t cert_cleanup(void *data)
+{
+ md_cert_t *cert = data;
+ if (cert->x509) {
+ X509_free(cert->x509);
+ cert->x509 = NULL;
+ }
+ return APR_SUCCESS;
+}
+
+md_cert_t *md_cert_wrap(apr_pool_t *p, void *x509)
+{
+ md_cert_t *cert = apr_pcalloc(p, sizeof(*cert));
+ cert->pool = p;
+ cert->x509 = x509;
+ return cert;
+}
+
+md_cert_t *md_cert_make(apr_pool_t *p, void *x509)
+{
+ md_cert_t *cert = md_cert_wrap(p, x509);
+ apr_pool_cleanup_register(p, cert, cert_cleanup, apr_pool_cleanup_null);
+ return cert;
+}
+
+void *md_cert_get_X509(const md_cert_t *cert)
+{
+ return cert->x509;
+}
+
+const char *md_cert_get_serial_number(const md_cert_t *cert, apr_pool_t *p)
+{
+ const char *s = "";
+ BIGNUM *bn;
+ const char *serial;
+ const ASN1_INTEGER *ai = X509_get_serialNumber(cert->x509);
+ if (ai) {
+ bn = ASN1_INTEGER_to_BN(ai, NULL);
+ serial = BN_bn2hex(bn);
+ s = apr_pstrdup(p, serial);
+ OPENSSL_free((void*)serial);
+ OPENSSL_free((void*)bn);
+ }
+ return s;
+}
+
+int md_certs_are_equal(const md_cert_t *a, const md_cert_t *b)
+{
+ return X509_cmp(a->x509, b->x509) == 0;
+}
+
+int md_cert_is_valid_now(const md_cert_t *cert)
+{
+ return ((X509_cmp_current_time(X509_get_notBefore(cert->x509)) < 0)
+ && (X509_cmp_current_time(X509_get_notAfter(cert->x509)) > 0));
+}
+
+int md_cert_has_expired(const md_cert_t *cert)
+{
+ return (X509_cmp_current_time(X509_get_notAfter(cert->x509)) <= 0);
+}
+
+apr_time_t md_cert_get_not_after(const md_cert_t *cert)
+{
+ return md_asn1_time_get(X509_get_notAfter(cert->x509));
+}
+
+apr_time_t md_cert_get_not_before(const md_cert_t *cert)
+{
+ return md_asn1_time_get(X509_get_notBefore(cert->x509));
+}
+
+md_timeperiod_t md_cert_get_valid(const md_cert_t *cert)
+{
+ md_timeperiod_t p;
+ p.start = md_cert_get_not_before(cert);
+ p.end = md_cert_get_not_after(cert);
+ return p;
+}
+
+int md_cert_covers_domain(md_cert_t *cert, const char *domain_name)
+{
+ apr_array_header_t *alt_names;
+
+ md_cert_get_alt_names(&alt_names, cert, cert->pool);
+ if (alt_names) {
+ return md_array_str_index(alt_names, domain_name, 0, 0) >= 0;
+ }
+ return 0;
+}
+
+int md_cert_covers_md(md_cert_t *cert, const md_t *md)
+{
+ const char *name;
+ int i;
+
+ if (!cert->alt_names) {
+ md_cert_get_alt_names(&cert->alt_names, cert, cert->pool);
+ }
+ if (cert->alt_names) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, cert->pool, "cert has %d alt names",
+ cert->alt_names->nelts);
+ for (i = 0; i < md->domains->nelts; ++i) {
+ name = APR_ARRAY_IDX(md->domains, i, const char *);
+ if (!md_dns_domains_match(cert->alt_names, name)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, cert->pool,
+ "md domain %s not covered by cert", name);
+ return 0;
+ }
+ }
+ return 1;
+ }
+ else {
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, cert->pool, "cert has NO alt names");
+ }
+ return 0;
+}
+
+apr_status_t md_cert_get_issuers_uri(const char **puri, const md_cert_t *cert, apr_pool_t *p)
+{
+ apr_status_t rv = APR_ENOENT;
+ STACK_OF(ACCESS_DESCRIPTION) *xinfos;
+ const char *uri = NULL;
+ unsigned char *buf;
+ int i;
+
+ xinfos = X509_get_ext_d2i(cert->x509, NID_info_access, NULL, NULL);
+ if (xinfos) {
+ for (i = 0; i < sk_ACCESS_DESCRIPTION_num(xinfos); i++) {
+ ACCESS_DESCRIPTION *val = sk_ACCESS_DESCRIPTION_value(xinfos, i);
+ if (OBJ_obj2nid(val->method) == NID_ad_ca_issuers
+ && val->location && val->location->type == GEN_URI) {
+ ASN1_STRING_to_UTF8(&buf, val->location->d.uniformResourceIdentifier);
+ uri = apr_pstrdup(p, (char *)buf);
+ OPENSSL_free(buf);
+ rv = APR_SUCCESS;
+ break;
+ }
+ }
+ sk_ACCESS_DESCRIPTION_pop_free(xinfos, ACCESS_DESCRIPTION_free);
+ }
+ *puri = (APR_SUCCESS == rv)? uri : NULL;
+ return rv;
+}
+
+apr_status_t md_cert_get_alt_names(apr_array_header_t **pnames, const md_cert_t *cert, apr_pool_t *p)
+{
+ apr_array_header_t *names;
+ apr_status_t rv = APR_ENOENT;
+ STACK_OF(GENERAL_NAME) *xalt_names;
+ unsigned char *buf;
+ int i;
+
+ xalt_names = X509_get_ext_d2i(cert->x509, NID_subject_alt_name, NULL, NULL);
+ if (xalt_names) {
+ GENERAL_NAME *cval;
+ const unsigned char *ip;
+ int len;
+
+ names = apr_array_make(p, sk_GENERAL_NAME_num(xalt_names), sizeof(char *));
+ for (i = 0; i < sk_GENERAL_NAME_num(xalt_names); ++i) {
+ cval = sk_GENERAL_NAME_value(xalt_names, i);
+ switch (cval->type) {
+ case GEN_DNS:
+ case GEN_URI:
+ ASN1_STRING_to_UTF8(&buf, cval->d.ia5);
+ APR_ARRAY_PUSH(names, const char *) = apr_pstrdup(p, (char*)buf);
+ OPENSSL_free(buf);
+ break;
+ case GEN_IPADD:
+ len = ASN1_STRING_length(cval->d.iPAddress);
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ ip = ASN1_STRING_data(cval->d.iPAddress);
+#else
+ ip = ASN1_STRING_get0_data(cval->d.iPAddress);
+#endif
+ if (len == 4) /* IPv4 address */
+ APR_ARRAY_PUSH(names, const char *) = apr_psprintf(p, "%u.%u.%u.%u",
+ ip[0], ip[1], ip[2], ip[3]);
+ else if (len == 16) /* IPv6 address */
+ APR_ARRAY_PUSH(names, const char *) = apr_psprintf(p, "%02x%02x%02x%02x:"
+ "%02x%02x%02x%02x:"
+ "%02x%02x%02x%02x:"
+ "%02x%02x%02x%02x",
+ ip[0], ip[1], ip[2], ip[3],
+ ip[4], ip[5], ip[6], ip[7],
+ ip[8], ip[9], ip[10], ip[11],
+ ip[12], ip[13], ip[14], ip[15]);
+ else {
+ ; /* Unknown address type - Log? Assert? */
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ sk_GENERAL_NAME_pop_free(xalt_names, GENERAL_NAME_free);
+ rv = APR_SUCCESS;
+ }
+ *pnames = (APR_SUCCESS == rv)? names : NULL;
+ return rv;
+}
+
+apr_status_t md_cert_fload(md_cert_t **pcert, apr_pool_t *p, const char *fname)
+{
+ FILE *f;
+ apr_status_t rv;
+ md_cert_t *cert;
+ X509 *x509;
+
+ rv = md_util_fopen(&f, fname, "r");
+ if (rv == APR_SUCCESS) {
+
+ x509 = PEM_read_X509(f, NULL, NULL, NULL);
+ rv = fclose(f);
+ if (x509 != NULL) {
+ cert = md_cert_make(p, x509);
+ }
+ else {
+ rv = APR_EINVAL;
+ }
+ }
+
+ *pcert = (APR_SUCCESS == rv)? cert : NULL;
+ return rv;
+}
+
+static apr_status_t cert_to_buffer(md_data_t *buffer, const md_cert_t *cert, apr_pool_t *p)
+{
+ BIO *bio = BIO_new(BIO_s_mem());
+ int i;
+
+ if (!bio) {
+ return APR_ENOMEM;
+ }
+
+ ERR_clear_error();
+ PEM_write_bio_X509(bio, cert->x509);
+ if (ERR_get_error() > 0) {
+ BIO_free(bio);
+ return APR_EINVAL;
+ }
+
+ i = BIO_pending(bio);
+ if (i > 0) {
+ buffer->data = apr_palloc(p, (apr_size_t)i);
+ i = BIO_read(bio, (char*)buffer->data, i);
+ buffer->len = (apr_size_t)i;
+ }
+ BIO_free(bio);
+ return APR_SUCCESS;
+}
+
+apr_status_t md_cert_fsave(md_cert_t *cert, apr_pool_t *p,
+ const char *fname, apr_fileperms_t perms)
+{
+ md_data_t buffer;
+ apr_status_t rv;
+
+ md_data_null(&buffer);
+ if (APR_SUCCESS == (rv = cert_to_buffer(&buffer, cert, p))) {
+ return md_util_freplace(fname, perms, p, fwrite_buffer, &buffer);
+ }
+ return rv;
+}
+
+apr_status_t md_cert_to_base64url(const char **ps64, const md_cert_t *cert, apr_pool_t *p)
+{
+ md_data_t buffer;
+ apr_status_t rv;
+
+ md_data_null(&buffer);
+ if (APR_SUCCESS == (rv = cert_to_buffer(&buffer, cert, p))) {
+ *ps64 = md_util_base64url_encode(&buffer, p);
+ return APR_SUCCESS;
+ }
+ *ps64 = NULL;
+ return rv;
+}
+
+apr_status_t md_cert_to_sha256_digest(md_data_t **pdigest, const md_cert_t *cert, apr_pool_t *p)
+{
+ md_data_t *digest;
+ unsigned int dlen;
+
+ digest = md_data_pmake(EVP_MAX_MD_SIZE, p);
+ X509_digest(cert->x509, EVP_sha256(), (unsigned char*)digest->data, &dlen);
+ digest->len = dlen;
+
+ *pdigest = digest;
+ return APR_SUCCESS;
+}
+
+apr_status_t md_cert_to_sha256_fingerprint(const char **pfinger, const md_cert_t *cert, apr_pool_t *p)
+{
+ md_data_t *digest;
+ apr_status_t rv;
+
+ rv = md_cert_to_sha256_digest(&digest, cert, p);
+ if (APR_SUCCESS == rv) {
+ return md_data_to_hex(pfinger, 0, p, digest);
+ }
+ *pfinger = NULL;
+ return rv;
+}
+
+static int md_cert_read_pem(BIO *bf, apr_pool_t *p, md_cert_t **pcert)
+{
+ md_cert_t *cert;
+ X509 *x509;
+ apr_status_t rv = APR_ENOENT;
+
+ ERR_clear_error();
+ x509 = PEM_read_bio_X509(bf, NULL, NULL, NULL);
+ if (x509 == NULL) goto cleanup;
+ cert = md_cert_make(p, x509);
+ rv = APR_SUCCESS;
+cleanup:
+ *pcert = (APR_SUCCESS == rv)? cert : NULL;
+ return rv;
+}
+
+apr_status_t md_cert_read_chain(apr_array_header_t *chain, apr_pool_t *p,
+ const char *pem, apr_size_t pem_len)
+{
+ BIO *bf = NULL;
+ apr_status_t rv = APR_SUCCESS;
+ md_cert_t *cert;
+ int added = 0;
+
+ if (NULL == (bf = BIO_new_mem_buf(pem, (int)pem_len))) {
+ rv = APR_ENOMEM;
+ goto cleanup;
+ }
+ while (APR_SUCCESS == (rv = md_cert_read_pem(bf, chain->pool, &cert))) {
+ APR_ARRAY_PUSH(chain, md_cert_t *) = cert;
+ added = 1;
+ }
+ if (APR_ENOENT == rv && added) {
+ rv = APR_SUCCESS;
+ }
+
+cleanup:
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, p, "read chain with %d certs", chain->nelts);
+ if (bf) BIO_free(bf);
+ return rv;
+}
+
+apr_status_t md_cert_read_http(md_cert_t **pcert, apr_pool_t *p,
+ const md_http_response_t *res)
+{
+ const char *ct;
+ apr_off_t data_len;
+ char *der;
+ apr_size_t der_len;
+ md_cert_t *cert = NULL;
+ apr_status_t rv;
+
+ ct = apr_table_get(res->headers, "Content-Type");
+ ct = md_util_parse_ct(res->req->pool, ct);
+ if (!res->body || !ct || strcmp("application/pkix-cert", ct)) {
+ rv = APR_ENOENT;
+ goto out;
+ }
+
+ if (APR_SUCCESS == (rv = apr_brigade_length(res->body, 1, &data_len))) {
+ if (data_len > 1024*1024) { /* certs usually are <2k each */
+ return APR_EINVAL;
+ }
+ if (APR_SUCCESS == (rv = apr_brigade_pflatten(res->body, &der, &der_len, res->req->pool))) {
+ const unsigned char *bf = (const unsigned char*)der;
+ X509 *x509;
+
+ if (NULL == (x509 = d2i_X509(NULL, &bf, (long)der_len))) {
+ rv = APR_EINVAL;
+ goto out;
+ }
+ else {
+ cert = md_cert_make(p, x509);
+ rv = APR_SUCCESS;
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, p,
+ "parsing cert from content-type=%s, content-length=%ld", ct, (long)data_len);
+ }
+ }
+ }
+out:
+ *pcert = (APR_SUCCESS == rv)? cert : NULL;
+ return rv;
+}
+
+apr_status_t md_cert_chain_read_http(struct apr_array_header_t *chain,
+ apr_pool_t *p, const struct md_http_response_t *res)
+{
+ const char *ct = NULL;
+ apr_off_t blen;
+ apr_size_t data_len = 0;
+ char *data;
+ md_cert_t *cert;
+ apr_status_t rv = APR_ENOENT;
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, p,
+ "chain_read, processing %d response", res->status);
+ if (APR_SUCCESS != (rv = apr_brigade_length(res->body, 1, &blen))) goto cleanup;
+ if (blen > 1024*1024) { /* certs usually are <2k each */
+ rv = APR_EINVAL;
+ goto cleanup;
+ }
+
+ data_len = (apr_size_t)blen;
+ ct = apr_table_get(res->headers, "Content-Type");
+ if (!res->body || !ct) goto cleanup;
+ ct = md_util_parse_ct(res->req->pool, ct);
+ if (!strcmp("application/pkix-cert", ct)) {
+ rv = md_cert_read_http(&cert, p, res);
+ if (APR_SUCCESS != rv) goto cleanup;
+ APR_ARRAY_PUSH(chain, md_cert_t *) = cert;
+ }
+ else if (!strcmp("application/pem-certificate-chain", ct)
+ || !strncmp("text/plain", ct, sizeof("text/plain")-1)) {
+ /* Some servers seem to think 'text/plain' is sufficient, see #232 */
+ rv = apr_brigade_pflatten(res->body, &data, &data_len, res->req->pool);
+ if (APR_SUCCESS != rv) goto cleanup;
+ rv = md_cert_read_chain(chain, res->req->pool, data, data_len);
+ }
+ else {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p,
+ "attempting to parse certificates from unrecognized content-type: %s", ct);
+ rv = apr_brigade_pflatten(res->body, &data, &data_len, res->req->pool);
+ if (APR_SUCCESS != rv) goto cleanup;
+ rv = md_cert_read_chain(chain, res->req->pool, data, data_len);
+ if (APR_SUCCESS == rv && chain->nelts == 0) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p,
+ "certificate chain response did not contain any certificates "
+ "(suspicious content-type: %s)", ct);
+ rv = APR_ENOENT;
+ }
+ }
+cleanup:
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, p,
+ "parsed certs from content-type=%s, content-length=%ld", ct, (long)data_len);
+ return rv;
+}
+
+md_cert_state_t md_cert_state_get(const md_cert_t *cert)
+{
+ if (cert->x509) {
+ return md_cert_is_valid_now(cert)? MD_CERT_VALID : MD_CERT_EXPIRED;
+ }
+ return MD_CERT_UNKNOWN;
+}
+
+apr_status_t md_chain_fappend(struct apr_array_header_t *certs, apr_pool_t *p, const char *fname)
+{
+ FILE *f;
+ apr_status_t rv;
+ X509 *x509;
+ md_cert_t *cert;
+ unsigned long err;
+
+ rv = md_util_fopen(&f, fname, "r");
+ if (rv == APR_SUCCESS) {
+ ERR_clear_error();
+ while (NULL != (x509 = PEM_read_X509(f, NULL, NULL, NULL))) {
+ cert = md_cert_make(p, x509);
+ APR_ARRAY_PUSH(certs, md_cert_t *) = cert;
+ }
+ fclose(f);
+
+ if (0 < (err = ERR_get_error())
+ && !(ERR_GET_LIB(err) == ERR_LIB_PEM && ERR_GET_REASON(err) == PEM_R_NO_START_LINE)) {
+ /* not the expected one when no more PEM encodings are found */
+ rv = APR_EINVAL;
+ goto out;
+ }
+
+ if (certs->nelts == 0) {
+ /* Did not find any. This is acceptable unless the file has a certain size
+ * when we no longer accept it as empty chain file. Something seems to be
+ * wrong then. */
+ apr_finfo_t info;
+ if (APR_SUCCESS == apr_stat(&info, fname, APR_FINFO_SIZE, p) && info.size >= 1024) {
+ /* "Too big for a moon." */
+ rv = APR_EINVAL;
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p,
+ "no certificates in non-empty chain %s", fname);
+ goto out;
+ }
+ }
+ }
+out:
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, p, "read chain file %s, found %d certs",
+ fname, certs? certs->nelts : 0);
+ return rv;
+}
+
+apr_status_t md_chain_fload(apr_array_header_t **pcerts, apr_pool_t *p, const char *fname)
+{
+ apr_array_header_t *certs;
+ apr_status_t rv;
+
+ certs = apr_array_make(p, 5, sizeof(md_cert_t *));
+ rv = md_chain_fappend(certs, p, fname);
+ *pcerts = (APR_SUCCESS == rv)? certs : NULL;
+ return rv;
+}
+
+apr_status_t md_chain_fsave(apr_array_header_t *certs, apr_pool_t *p,
+ const char *fname, apr_fileperms_t perms)
+{
+ FILE *f;
+ apr_status_t rv;
+ const md_cert_t *cert;
+ unsigned long err = 0;
+ int i;
+
+ (void)p;
+ rv = md_util_fopen(&f, fname, "w");
+ if (rv == APR_SUCCESS) {
+ apr_file_perms_set(fname, perms);
+ ERR_clear_error();
+ for (i = 0; i < certs->nelts; ++i) {
+ cert = APR_ARRAY_IDX(certs, i, const md_cert_t *);
+ assert(cert->x509);
+
+ PEM_write_X509(f, cert->x509);
+
+ if (0 < (err = ERR_get_error())) {
+ break;
+ }
+
+ }
+ rv = fclose(f);
+ if (err) {
+ rv = APR_EINVAL;
+ }
+ }
+ return rv;
+}
+
+/**************************************************************************************************/
+/* certificate signing requests */
+
+static const char *alt_names(apr_array_header_t *domains, apr_pool_t *p)
+{
+ const char *alts = "", *sep = "", *domain;
+ int i;
+
+ for (i = 0; i < domains->nelts; ++i) {
+ domain = APR_ARRAY_IDX(domains, i, const char *);
+ alts = apr_psprintf(p, "%s%sDNS:%s", alts, sep, domain);
+ sep = ",";
+ }
+ return alts;
+}
+
+static apr_status_t add_ext(X509 *x, int nid, const char *value, apr_pool_t *p)
+{
+ X509_EXTENSION *ext = NULL;
+ X509V3_CTX ctx;
+ apr_status_t rv;
+
+ ERR_clear_error();
+ X509V3_set_ctx_nodb(&ctx);
+ X509V3_set_ctx(&ctx, x, x, NULL, NULL, 0);
+ if (NULL == (ext = X509V3_EXT_conf_nid(NULL, &ctx, nid, (char*)value))) {
+ unsigned long err = ERR_get_error();
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "add_ext, create, nid=%d value='%s' "
+ "(lib=%d, reason=%d)", nid, value, ERR_GET_LIB(err), ERR_GET_REASON(err));
+ return APR_EGENERAL;
+ }
+
+ ERR_clear_error();
+ rv = X509_add_ext(x, ext, -1)? APR_SUCCESS : APR_EINVAL;
+ if (APR_SUCCESS != rv) {
+ unsigned long err = ERR_get_error();
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "add_ext, add, nid=%d value='%s' "
+ "(lib=%d, reason=%d)", nid, value, ERR_GET_LIB(err), ERR_GET_REASON(err));
+ }
+ X509_EXTENSION_free(ext);
+ return rv;
+}
+
+static apr_status_t sk_add_alt_names(STACK_OF(X509_EXTENSION) *exts,
+ apr_array_header_t *domains, apr_pool_t *p)
+{
+ if (domains->nelts > 0) {
+ X509_EXTENSION *x;
+
+ x = X509V3_EXT_conf_nid(NULL, NULL, NID_subject_alt_name, (char*)alt_names(domains, p));
+ if (NULL == x) {
+ return APR_EGENERAL;
+ }
+ sk_X509_EXTENSION_push(exts, x);
+ }
+ return APR_SUCCESS;
+}
+
+#define MD_OID_MUST_STAPLE_NUM "1.3.6.1.5.5.7.1.24"
+#define MD_OID_MUST_STAPLE_SNAME "tlsfeature"
+#define MD_OID_MUST_STAPLE_LNAME "TLS Feature"
+
+int md_cert_must_staple(const md_cert_t *cert)
+{
+ /* In case we do not get the NID for it, we treat this as not set. */
+ int nid = MD_GET_NID(MUST_STAPLE);
+ return ((NID_undef != nid)) && X509_get_ext_by_NID(cert->x509, nid, -1) >= 0;
+}
+
+static apr_status_t add_must_staple(STACK_OF(X509_EXTENSION) *exts, const char *name, apr_pool_t *p)
+{
+ X509_EXTENSION *x;
+ int nid;
+
+ nid = MD_GET_NID(MUST_STAPLE);
+ if (NID_undef == nid) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p,
+ "%s: unable to get NID for v3 must-staple TLS feature", name);
+ return APR_ENOTIMPL;
+ }
+ x = X509V3_EXT_conf_nid(NULL, NULL, nid, (char*)"DER:30:03:02:01:05");
+ if (NULL == x) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p,
+ "%s: unable to create x509 extension for must-staple", name);
+ return APR_EGENERAL;
+ }
+ sk_X509_EXTENSION_push(exts, x);
+ return APR_SUCCESS;
+}
+
+apr_status_t md_cert_req_create(const char **pcsr_der_64, const char *name,
+ apr_array_header_t *domains, int must_staple,
+ md_pkey_t *pkey, apr_pool_t *p)
+{
+ const char *s, *csr_der_64 = NULL;
+ const unsigned char *domain;
+ X509_REQ *csr;
+ X509_NAME *n = NULL;
+ STACK_OF(X509_EXTENSION) *exts = NULL;
+ apr_status_t rv;
+ md_data_t csr_der;
+ int csr_der_len;
+
+ assert(domains->nelts > 0);
+ md_data_null(&csr_der);
+
+ if (NULL == (csr = X509_REQ_new())
+ || NULL == (exts = sk_X509_EXTENSION_new_null())
+ || NULL == (n = X509_NAME_new())) {
+ rv = APR_ENOMEM;
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: openssl alloc X509 things", name);
+ goto out;
+ }
+
+ /* subject name == first domain */
+ domain = APR_ARRAY_IDX(domains, 0, const unsigned char *);
+ /* Do not set the domain in the CN if it is longer than 64 octets.
+ * Instead, let the CA choose a 'proper' name. At the moment (2021-01), LE will
+ * inspect all SAN names and use one < 64 chars if it can be found. It will fail
+ * otherwise.
+ * The reason we do not check this beforehand is that the restrictions on CNs
+ * are in flux. They used to be authoritative, now browsers no longer do that, but
+ * no one wants to hand out a cert with "google.com" as CN either. So, we leave
+ * it for the CA to decide if and how it hands out a cert for this or fails.
+ * This solves issue where the name is too long, see #227 */
+ if (strlen((const char*)domain) < 64
+ && (!X509_NAME_add_entry_by_txt(n, "CN", MBSTRING_ASC, domain, -1, -1, 0)
+ || !X509_REQ_set_subject_name(csr, n))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "%s: REQ name add entry", name);
+ rv = APR_EGENERAL; goto out;
+ }
+ /* collect extensions, such as alt names and must staple */
+ if (APR_SUCCESS != (rv = sk_add_alt_names(exts, domains, p))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: collecting alt names", name);
+ rv = APR_EGENERAL; goto out;
+ }
+ if (must_staple && APR_SUCCESS != (rv = add_must_staple(exts, name, p))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: you requested that a certificate "
+ "is created with the 'must-staple' extension, however the SSL library was "
+ "unable to initialized that extension. Please file a bug report on which platform "
+ "and with which library this happens. To continue before this problem is resolved, "
+ "configure 'MDMustStaple off' for your domains", name);
+ rv = APR_EGENERAL; goto out;
+ }
+ /* add extensions to csr */
+ if (sk_X509_EXTENSION_num(exts) > 0 && !X509_REQ_add_extensions(csr, exts)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: adding exts", name);
+ rv = APR_EGENERAL; goto out;
+ }
+ /* add our key */
+ if (!X509_REQ_set_pubkey(csr, pkey->pkey)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set pkey in csr", name);
+ rv = APR_EGENERAL; goto out;
+ }
+ /* sign, der encode and base64url encode */
+ if (!X509_REQ_sign(csr, pkey->pkey, pkey_get_MD(pkey))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: sign csr", name);
+ rv = APR_EGENERAL; goto out;
+ }
+ if ((csr_der_len = i2d_X509_REQ(csr, NULL)) < 0) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: der length", name);
+ rv = APR_EGENERAL; goto out;
+ }
+ csr_der.len = (apr_size_t)csr_der_len;
+ s = csr_der.data = apr_pcalloc(p, csr_der.len + 1);
+ if (i2d_X509_REQ(csr, (unsigned char**)&s) != csr_der_len) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: csr der enc", name);
+ rv = APR_EGENERAL; goto out;
+ }
+ csr_der_64 = md_util_base64url_encode(&csr_der, p);
+ rv = APR_SUCCESS;
+
+out:
+ if (exts) {
+ sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free);
+ }
+ if (csr) {
+ X509_REQ_free(csr);
+ }
+ if (n) {
+ X509_NAME_free(n);
+ }
+ *pcsr_der_64 = (APR_SUCCESS == rv)? csr_der_64 : NULL;
+ return rv;
+}
+
+static apr_status_t mk_x509(X509 **px, md_pkey_t *pkey, const char *cn,
+ apr_interval_time_t valid_for, apr_pool_t *p)
+{
+ X509 *x = NULL;
+ X509_NAME *n = NULL;
+ BIGNUM *big_rnd = NULL;
+ ASN1_INTEGER *asn1_rnd = NULL;
+ unsigned char rnd[20];
+ int days;
+ apr_status_t rv;
+
+ if (NULL == (x = X509_new())
+ || NULL == (n = X509_NAME_new())) {
+ rv = APR_ENOMEM;
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "%s: openssl alloc X509 things", cn);
+ goto out;
+ }
+
+ if (APR_SUCCESS != (rv = md_rand_bytes(rnd, sizeof(rnd), p))
+ || !(big_rnd = BN_bin2bn(rnd, sizeof(rnd), NULL))
+ || !(asn1_rnd = BN_to_ASN1_INTEGER(big_rnd, NULL))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "%s: setup random serial", cn);
+ rv = APR_EGENERAL; goto out;
+ }
+ if (!X509_set_serialNumber(x, asn1_rnd)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "%s: set serial number", cn);
+ rv = APR_EGENERAL; goto out;
+ }
+ if (1 != X509_set_version(x, 2L)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "%s: setting x.509v3", cn);
+ rv = APR_EGENERAL; goto out;
+ }
+ /* set common name and issuer */
+ if (!X509_NAME_add_entry_by_txt(n, "CN", MBSTRING_ASC, (const unsigned char*)cn, -1, -1, 0)
+ || !X509_set_subject_name(x, n)
+ || !X509_set_issuer_name(x, n)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "%s: name add entry", cn);
+ rv = APR_EGENERAL; goto out;
+ }
+ /* cert are unconstrained (but not very trustworthy) */
+ if (APR_SUCCESS != (rv = add_ext(x, NID_basic_constraints, "critical,CA:FALSE", p))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set basic constraints ext", cn);
+ goto out;
+ }
+ /* add our key */
+ if (!X509_set_pubkey(x, pkey->pkey)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set pkey in x509", cn);
+ rv = APR_EGENERAL; goto out;
+ }
+ /* validity */
+ days = (int)((apr_time_sec(valid_for) + MD_SECS_PER_DAY - 1)/ MD_SECS_PER_DAY);
+ if (!X509_set_notBefore(x, ASN1_TIME_set(NULL, time(NULL)))) {
+ rv = APR_EGENERAL; goto out;
+ }
+ if (!X509_set_notAfter(x, ASN1_TIME_adj(NULL, time(NULL), days, 0))) {
+ rv = APR_EGENERAL; goto out;
+ }
+
+out:
+ *px = (APR_SUCCESS == rv)? x : NULL;
+ if (APR_SUCCESS != rv && x) X509_free(x);
+ if (big_rnd) BN_free(big_rnd);
+ if (asn1_rnd) ASN1_INTEGER_free(asn1_rnd);
+ if (n) X509_NAME_free(n);
+ return rv;
+}
+
+apr_status_t md_cert_self_sign(md_cert_t **pcert, const char *cn,
+ apr_array_header_t *domains, md_pkey_t *pkey,
+ apr_interval_time_t valid_for, apr_pool_t *p)
+{
+ X509 *x;
+ md_cert_t *cert = NULL;
+ apr_status_t rv;
+
+ assert(domains);
+
+ if (APR_SUCCESS != (rv = mk_x509(&x, pkey, cn, valid_for, p))) goto out;
+
+ /* add the domain as alt name */
+ if (APR_SUCCESS != (rv = add_ext(x, NID_subject_alt_name, alt_names(domains, p), p))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set alt_name ext", cn);
+ goto out;
+ }
+
+ /* keyUsage, ExtendedKeyUsage */
+
+ if (APR_SUCCESS != (rv = add_ext(x, NID_key_usage, "critical,digitalSignature", p))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set keyUsage", cn);
+ goto out;
+ }
+ if (APR_SUCCESS != (rv = add_ext(x, NID_ext_key_usage, "serverAuth", p))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set extKeyUsage", cn);
+ goto out;
+ }
+
+ /* sign with same key */
+ if (!X509_sign(x, pkey->pkey, pkey_get_MD(pkey))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: sign x509", cn);
+ rv = APR_EGENERAL; goto out;
+ }
+
+ cert = md_cert_make(p, x);
+ rv = APR_SUCCESS;
+
+out:
+ *pcert = (APR_SUCCESS == rv)? cert : NULL;
+ if (!cert && x) X509_free(x);
+ return rv;
+}
+
+#define MD_OID_ACME_VALIDATION_NUM "1.3.6.1.5.5.7.1.31"
+#define MD_OID_ACME_VALIDATION_SNAME "pe-acmeIdentifier"
+#define MD_OID_ACME_VALIDATION_LNAME "ACME Identifier"
+
+static int get_acme_validation_nid(void)
+{
+ int nid = OBJ_txt2nid(MD_OID_ACME_VALIDATION_NUM);
+ if (NID_undef == nid) {
+ nid = OBJ_create(MD_OID_ACME_VALIDATION_NUM,
+ MD_OID_ACME_VALIDATION_SNAME, MD_OID_ACME_VALIDATION_LNAME);
+ }
+ return nid;
+}
+
+apr_status_t md_cert_make_tls_alpn_01(md_cert_t **pcert, const char *domain,
+ const char *acme_id, md_pkey_t *pkey,
+ apr_interval_time_t valid_for, apr_pool_t *p)
+{
+ X509 *x;
+ md_cert_t *cert = NULL;
+ const char *alts;
+ apr_status_t rv;
+
+ if (APR_SUCCESS != (rv = mk_x509(&x, pkey, "tls-alpn-01-challenge", valid_for, p))) goto out;
+
+ /* add the domain as alt name */
+ alts = apr_psprintf(p, "DNS:%s", domain);
+ if (APR_SUCCESS != (rv = add_ext(x, NID_subject_alt_name, alts, p))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set alt_name ext", domain);
+ goto out;
+ }
+
+ if (APR_SUCCESS != (rv = add_ext(x, get_acme_validation_nid(), acme_id, p))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set pe-acmeIdentifier", domain);
+ goto out;
+ }
+
+ /* sign with same key */
+ if (!X509_sign(x, pkey->pkey, pkey_get_MD(pkey))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: sign x509", domain);
+ rv = APR_EGENERAL; goto out;
+ }
+
+ cert = md_cert_make(p, x);
+ rv = APR_SUCCESS;
+
+out:
+ if (!cert && x) {
+ X509_free(x);
+ }
+ *pcert = (APR_SUCCESS == rv)? cert : NULL;
+ return rv;
+}
+
+#define MD_OID_CT_SCTS_NUM "1.3.6.1.4.1.11129.2.4.2"
+#define MD_OID_CT_SCTS_SNAME "CT-SCTs"
+#define MD_OID_CT_SCTS_LNAME "CT Certificate SCTs"
+
+#ifndef OPENSSL_NO_CT
+static int get_ct_scts_nid(void)
+{
+ int nid = OBJ_txt2nid(MD_OID_CT_SCTS_NUM);
+ if (NID_undef == nid) {
+ nid = OBJ_create(MD_OID_CT_SCTS_NUM,
+ MD_OID_CT_SCTS_SNAME, MD_OID_CT_SCTS_LNAME);
+ }
+ return nid;
+}
+#endif
+
+const char *md_nid_get_sname(int nid)
+{
+ return OBJ_nid2sn(nid);
+}
+
+const char *md_nid_get_lname(int nid)
+{
+ return OBJ_nid2ln(nid);
+}
+
+apr_status_t md_cert_get_ct_scts(apr_array_header_t *scts, apr_pool_t *p, const md_cert_t *cert)
+{
+#ifndef OPENSSL_NO_CT
+ int nid, i, idx, critical;
+ STACK_OF(SCT) *sct_list;
+ SCT *sct_handle;
+ md_sct *sct;
+ size_t len;
+ const char *data;
+
+ nid = get_ct_scts_nid();
+ if (NID_undef == nid) return APR_ENOTIMPL;
+
+ idx = -1;
+ while (1) {
+ sct_list = X509_get_ext_d2i(cert->x509, nid, &critical, &idx);
+ if (sct_list) {
+ for (i = 0; i < sk_SCT_num(sct_list); i++) {
+ sct_handle = sk_SCT_value(sct_list, i);
+ if (sct_handle) {
+ sct = apr_pcalloc(p, sizeof(*sct));
+ sct->version = SCT_get_version(sct_handle);
+ sct->timestamp = apr_time_from_msec(SCT_get_timestamp(sct_handle));
+ len = SCT_get0_log_id(sct_handle, (unsigned char**)&data);
+ sct->logid = md_data_make_pcopy(p, data, len);
+ sct->signature_type_nid = SCT_get_signature_nid(sct_handle);
+ len = SCT_get0_signature(sct_handle, (unsigned char**)&data);
+ sct->signature = md_data_make_pcopy(p, data, len);
+
+ APR_ARRAY_PUSH(scts, md_sct*) = sct;
+ }
+ }
+ }
+ if (idx < 0) break;
+ }
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, p, "ct_sct, found %d SCT extensions", scts->nelts);
+ return APR_SUCCESS;
+#else
+ (void)scts;
+ (void)p;
+ (void)cert;
+ return APR_ENOTIMPL;
+#endif
+}
+
+apr_status_t md_cert_get_ocsp_responder_url(const char **purl, apr_pool_t *p, const md_cert_t *cert)
+{
+ STACK_OF(OPENSSL_STRING) *ssk;
+ apr_status_t rv = APR_SUCCESS;
+ const char *url = NULL;
+
+ ssk = X509_get1_ocsp(md_cert_get_X509(cert));
+ if (!ssk) {
+ rv = APR_ENOENT;
+ goto cleanup;
+ }
+ url = apr_pstrdup(p, sk_OPENSSL_STRING_value(ssk, 0));
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, p, "ocsp responder found '%s'", url);
+
+cleanup:
+ if (ssk) X509_email_free(ssk);
+ *purl = url;
+ return rv;
+}
+
+apr_status_t md_check_cert_and_pkey(struct apr_array_header_t *certs, md_pkey_t *pkey)
+{
+ const md_cert_t *cert;
+
+ if (certs->nelts == 0) {
+ return APR_ENOENT;
+ }
+
+ cert = APR_ARRAY_IDX(certs, 0, const md_cert_t*);
+
+ if (1 != X509_check_private_key(cert->x509, pkey->pkey)) {
+ return APR_EGENERAL;
+ }
+
+ return APR_SUCCESS;
+}
diff --git a/modules/md/md_crypt.h b/modules/md/md_crypt.h
new file mode 100644
index 0000000..a892e00
--- /dev/null
+++ b/modules/md/md_crypt.h
@@ -0,0 +1,253 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mod_md_md_crypt_h
+#define mod_md_md_crypt_h
+
+#include <apr_file_io.h>
+
+struct apr_array_header_t;
+struct md_t;
+struct md_http_response_t;
+struct md_cert_t;
+struct md_pkey_t;
+struct md_data_t;
+struct md_timeperiod_t;
+
+/**************************************************************************************************/
+/* random */
+
+apr_status_t md_rand_bytes(unsigned char *buf, apr_size_t len, apr_pool_t *p);
+
+apr_time_t md_asn1_generalized_time_get(void *ASN1_GENERALIZEDTIME);
+
+/**************************************************************************************************/
+/* digests */
+apr_status_t md_crypt_sha256_digest64(const char **pdigest64, apr_pool_t *p,
+ const struct md_data_t *data);
+apr_status_t md_crypt_sha256_digest_hex(const char **pdigesthex, apr_pool_t *p,
+ const struct md_data_t *data);
+
+/**************************************************************************************************/
+/* private keys */
+
+typedef struct md_pkey_t md_pkey_t;
+
+typedef enum {
+ MD_PKEY_TYPE_DEFAULT,
+ MD_PKEY_TYPE_RSA,
+ MD_PKEY_TYPE_EC,
+} md_pkey_type_t;
+
+typedef struct md_pkey_rsa_params_t {
+ apr_uint32_t bits;
+} md_pkey_rsa_params_t;
+
+typedef struct md_pkey_ec_params_t {
+ const char *curve;
+} md_pkey_ec_params_t;
+
+typedef struct md_pkey_spec_t {
+ md_pkey_type_t type;
+ union {
+ md_pkey_rsa_params_t rsa;
+ md_pkey_ec_params_t ec;
+ } params;
+} md_pkey_spec_t;
+
+typedef struct md_pkeys_spec_t {
+ apr_pool_t *p;
+ struct apr_array_header_t *specs;
+} md_pkeys_spec_t;
+
+apr_status_t md_crypt_init(apr_pool_t *pool);
+
+const char *md_pkey_spec_name(const md_pkey_spec_t *spec);
+
+md_pkeys_spec_t *md_pkeys_spec_make(apr_pool_t *p);
+void md_pkeys_spec_add_default(md_pkeys_spec_t *pks);
+int md_pkeys_spec_contains_rsa(md_pkeys_spec_t *pks);
+void md_pkeys_spec_add_rsa(md_pkeys_spec_t *pks, unsigned int bits);
+int md_pkeys_spec_contains_ec(md_pkeys_spec_t *pks, const char *curve);
+void md_pkeys_spec_add_ec(md_pkeys_spec_t *pks, const char *curve);
+int md_pkeys_spec_eq(md_pkeys_spec_t *pks1, md_pkeys_spec_t *pks2);
+md_pkeys_spec_t *md_pkeys_spec_clone(apr_pool_t *p, const md_pkeys_spec_t *pks);
+int md_pkeys_spec_is_empty(const md_pkeys_spec_t *pks);
+md_pkey_spec_t *md_pkeys_spec_get(const md_pkeys_spec_t *pks, int index);
+int md_pkeys_spec_count(const md_pkeys_spec_t *pks);
+void md_pkeys_spec_add(md_pkeys_spec_t *pks, md_pkey_spec_t *spec);
+
+struct md_json_t *md_pkey_spec_to_json(const md_pkey_spec_t *spec, apr_pool_t *p);
+md_pkey_spec_t *md_pkey_spec_from_json(struct md_json_t *json, apr_pool_t *p);
+struct md_json_t *md_pkeys_spec_to_json(const md_pkeys_spec_t *pks, apr_pool_t *p);
+md_pkeys_spec_t *md_pkeys_spec_from_json(struct md_json_t *json, apr_pool_t *p);
+
+
+apr_status_t md_pkey_gen(md_pkey_t **ppkey, apr_pool_t *p, md_pkey_spec_t *key_props);
+void md_pkey_free(md_pkey_t *pkey);
+
+const char *md_pkey_get_rsa_e64(md_pkey_t *pkey, apr_pool_t *p);
+const char *md_pkey_get_rsa_n64(md_pkey_t *pkey, apr_pool_t *p);
+
+apr_status_t md_pkey_fload(md_pkey_t **ppkey, apr_pool_t *p,
+ const char *pass_phrase, apr_size_t pass_len,
+ const char *fname);
+apr_status_t md_pkey_fsave(md_pkey_t *pkey, apr_pool_t *p,
+ const char *pass_phrase, apr_size_t pass_len,
+ const char *fname, apr_fileperms_t perms);
+
+apr_status_t md_crypt_sign64(const char **psign64, md_pkey_t *pkey, apr_pool_t *p,
+ const char *d, size_t dlen);
+
+void *md_pkey_get_EVP_PKEY(struct md_pkey_t *pkey);
+
+apr_status_t md_crypt_hmac64(const char **pmac64, const struct md_data_t *hmac_key,
+ apr_pool_t *p, const char *d, size_t dlen);
+
+/**
+ * Read a private key from a http response.
+ */
+apr_status_t md_pkey_read_http(md_pkey_t **ppkey, apr_pool_t *pool,
+ const struct md_http_response_t *res);
+
+/**************************************************************************************************/
+/* X509 certificates */
+
+typedef struct md_cert_t md_cert_t;
+
+typedef enum {
+ MD_CERT_UNKNOWN,
+ MD_CERT_VALID,
+ MD_CERT_EXPIRED
+} md_cert_state_t;
+
+/**
+ * Create a holder of the certificate that will free its memory when the
+ * pool is destroyed.
+ */
+md_cert_t *md_cert_make(apr_pool_t *p, void *x509);
+
+/**
+ * Wrap a x509 certificate into our own structure, without taking ownership
+ * of its memory. The caller remains responsible.
+ */
+md_cert_t *md_cert_wrap(apr_pool_t *p, void *x509);
+
+void *md_cert_get_X509(const md_cert_t *cert);
+
+apr_status_t md_cert_fload(md_cert_t **pcert, apr_pool_t *p, const char *fname);
+apr_status_t md_cert_fsave(md_cert_t *cert, apr_pool_t *p,
+ const char *fname, apr_fileperms_t perms);
+
+/**
+ * Read a x509 certificate from a http response.
+ * Will return APR_ENOENT if content-type is not recognized (currently
+ * only "application/pkix-cert" is supported).
+ */
+apr_status_t md_cert_read_http(md_cert_t **pcert, apr_pool_t *pool,
+ const struct md_http_response_t *res);
+
+/**
+ * Read at least one certificate from the given PEM data.
+ */
+apr_status_t md_cert_read_chain(apr_array_header_t *chain, apr_pool_t *p,
+ const char *pem, apr_size_t pem_len);
+
+/**
+ * Read one or even a chain of certificates from a http response.
+ * Will return APR_ENOENT if content-type is not recognized (currently
+ * supports only "application/pem-certificate-chain" and "application/pkix-cert").
+ * @param chain must be non-NULL, retrieved certificates will be added.
+ */
+apr_status_t md_cert_chain_read_http(struct apr_array_header_t *chain,
+ apr_pool_t *pool, const struct md_http_response_t *res);
+
+md_cert_state_t md_cert_state_get(const md_cert_t *cert);
+int md_cert_is_valid_now(const md_cert_t *cert);
+int md_cert_has_expired(const md_cert_t *cert);
+int md_cert_covers_domain(md_cert_t *cert, const char *domain_name);
+int md_cert_covers_md(md_cert_t *cert, const struct md_t *md);
+int md_cert_must_staple(const md_cert_t *cert);
+apr_time_t md_cert_get_not_after(const md_cert_t *cert);
+apr_time_t md_cert_get_not_before(const md_cert_t *cert);
+struct md_timeperiod_t md_cert_get_valid(const md_cert_t *cert);
+
+/**
+ * Return != 0 iff the hash values of the certificates are equal.
+ */
+int md_certs_are_equal(const md_cert_t *a, const md_cert_t *b);
+
+apr_status_t md_cert_get_issuers_uri(const char **puri, const md_cert_t *cert, apr_pool_t *p);
+apr_status_t md_cert_get_alt_names(apr_array_header_t **pnames, const md_cert_t *cert, apr_pool_t *p);
+
+apr_status_t md_cert_to_base64url(const char **ps64, const md_cert_t *cert, apr_pool_t *p);
+apr_status_t md_cert_from_base64url(md_cert_t **pcert, const char *s64, apr_pool_t *p);
+
+apr_status_t md_cert_to_sha256_digest(struct md_data_t **pdigest, const md_cert_t *cert, apr_pool_t *p);
+apr_status_t md_cert_to_sha256_fingerprint(const char **pfinger, const md_cert_t *cert, apr_pool_t *p);
+
+const char *md_cert_get_serial_number(const md_cert_t *cert, apr_pool_t *p);
+
+apr_status_t md_chain_fload(struct apr_array_header_t **pcerts,
+ apr_pool_t *p, const char *fname);
+apr_status_t md_chain_fsave(struct apr_array_header_t *certs,
+ apr_pool_t *p, const char *fname, apr_fileperms_t perms);
+apr_status_t md_chain_fappend(struct apr_array_header_t *certs,
+ apr_pool_t *p, const char *fname);
+
+apr_status_t md_cert_req_create(const char **pcsr_der_64, const char *name,
+ apr_array_header_t *domains, int must_staple,
+ md_pkey_t *pkey, apr_pool_t *p);
+
+/**
+ * Create a self-signed cerftificate with the given cn, key and list
+ * of alternate domain names.
+ */
+apr_status_t md_cert_self_sign(md_cert_t **pcert, const char *cn,
+ struct apr_array_header_t *domains, md_pkey_t *pkey,
+ apr_interval_time_t valid_for, apr_pool_t *p);
+
+/**
+ * Create a certificate for answering "tls-alpn-01" ACME challenges
+ * (see <https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-01>).
+ */
+apr_status_t md_cert_make_tls_alpn_01(md_cert_t **pcert, const char *domain,
+ const char *acme_id, md_pkey_t *pkey,
+ apr_interval_time_t valid_for, apr_pool_t *p);
+
+apr_status_t md_cert_get_ct_scts(apr_array_header_t *scts, apr_pool_t *p, const md_cert_t *cert);
+
+apr_status_t md_cert_get_ocsp_responder_url(const char **purl, apr_pool_t *p, const md_cert_t *cert);
+
+apr_status_t md_check_cert_and_pkey(struct apr_array_header_t *certs, md_pkey_t *pkey);
+
+
+/**************************************************************************************************/
+/* X509 certificate transparency */
+
+const char *md_nid_get_sname(int nid);
+const char *md_nid_get_lname(int nid);
+
+typedef struct md_sct md_sct;
+struct md_sct {
+ int version;
+ apr_time_t timestamp;
+ struct md_data_t *logid;
+ int signature_type_nid;
+ struct md_data_t *signature;
+};
+
+#endif /* md_crypt_h */
diff --git a/modules/md/md_curl.c b/modules/md/md_curl.c
new file mode 100644
index 0000000..217e857
--- /dev/null
+++ b/modules/md/md_curl.c
@@ -0,0 +1,653 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+
+#include <curl/curl.h>
+
+#include <apr_lib.h>
+#include <apr_strings.h>
+#include <apr_buckets.h>
+
+#include "md_http.h"
+#include "md_log.h"
+#include "md_util.h"
+#include "md_curl.h"
+
+/**************************************************************************************************/
+/* md_http curl implementation */
+
+
+static apr_status_t curl_status(unsigned int curl_code)
+{
+ switch (curl_code) {
+ case CURLE_OK: return APR_SUCCESS;
+ case CURLE_UNSUPPORTED_PROTOCOL: return APR_ENOTIMPL;
+ case CURLE_NOT_BUILT_IN: return APR_ENOTIMPL;
+ case CURLE_URL_MALFORMAT: return APR_EINVAL;
+ case CURLE_COULDNT_RESOLVE_PROXY:return APR_ECONNREFUSED;
+ case CURLE_COULDNT_RESOLVE_HOST: return APR_ECONNREFUSED;
+ case CURLE_COULDNT_CONNECT: return APR_ECONNREFUSED;
+ case CURLE_REMOTE_ACCESS_DENIED: return APR_EACCES;
+ case CURLE_OUT_OF_MEMORY: return APR_ENOMEM;
+ case CURLE_OPERATION_TIMEDOUT: return APR_TIMEUP;
+ case CURLE_SSL_CONNECT_ERROR: return APR_ECONNABORTED;
+ case CURLE_AGAIN: return APR_EAGAIN;
+ default: return APR_EGENERAL;
+ }
+}
+
+typedef struct {
+ CURL *curl;
+ CURLM *curlm;
+ struct curl_slist *req_hdrs;
+ md_http_response_t *response;
+ apr_status_t rv;
+ int status_fired;
+} md_curl_internals_t;
+
+static size_t req_data_cb(void *data, size_t len, size_t nmemb, void *baton)
+{
+ apr_bucket_brigade *body = baton;
+ size_t blen, read_len = 0, max_len = len * nmemb;
+ const char *bdata;
+ char *rdata = data;
+ apr_bucket *b;
+ apr_status_t rv;
+
+ while (body && !APR_BRIGADE_EMPTY(body) && max_len > 0) {
+ b = APR_BRIGADE_FIRST(body);
+ if (APR_BUCKET_IS_METADATA(b)) {
+ if (APR_BUCKET_IS_EOS(b)) {
+ body = NULL;
+ }
+ }
+ else {
+ rv = apr_bucket_read(b, &bdata, &blen, APR_BLOCK_READ);
+ if (rv == APR_SUCCESS) {
+ if (blen > max_len) {
+ apr_bucket_split(b, max_len);
+ blen = max_len;
+ }
+ memcpy(rdata, bdata, blen);
+ read_len += blen;
+ max_len -= blen;
+ rdata += blen;
+ }
+ else {
+ body = NULL;
+ if (!APR_STATUS_IS_EOF(rv)) {
+ /* everything beside EOF is an error */
+ read_len = CURL_READFUNC_ABORT;
+ }
+ }
+
+ }
+ apr_bucket_delete(b);
+ }
+
+ return read_len;
+}
+
+static size_t resp_data_cb(void *data, size_t len, size_t nmemb, void *baton)
+{
+ md_curl_internals_t *internals = baton;
+ md_http_response_t *res = internals->response;
+ size_t blen = len * nmemb;
+ apr_status_t rv;
+
+ if (res->body) {
+ if (res->req->resp_limit) {
+ apr_off_t body_len = 0;
+ apr_brigade_length(res->body, 0, &body_len);
+ if (body_len + (apr_off_t)blen > res->req->resp_limit) {
+ return 0; /* signal curl failure */
+ }
+ }
+ rv = apr_brigade_write(res->body, NULL, NULL, (const char *)data, blen);
+ if (rv != APR_SUCCESS) {
+ /* returning anything != blen will make CURL fail this */
+ return 0;
+ }
+ }
+ return blen;
+}
+
+static size_t header_cb(void *buffer, size_t elen, size_t nmemb, void *baton)
+{
+ md_curl_internals_t *internals = baton;
+ md_http_response_t *res = internals->response;
+ size_t len, clen = elen * nmemb;
+ const char *name = NULL, *value = "", *b = buffer;
+ apr_size_t i;
+
+ len = (clen && b[clen-1] == '\n')? clen-1 : clen;
+ len = (len && b[len-1] == '\r')? len-1 : len;
+ for (i = 0; i < len; ++i) {
+ if (b[i] == ':') {
+ name = apr_pstrndup(res->req->pool, b, i);
+ ++i;
+ while (i < len && b[i] == ' ') {
+ ++i;
+ }
+ if (i < len) {
+ value = apr_pstrndup(res->req->pool, b+i, len - i);
+ }
+ break;
+ }
+ }
+
+ if (name != NULL) {
+ apr_table_add(res->headers, name, value);
+ }
+ return clen;
+}
+
+typedef struct {
+ md_http_request_t *req;
+ struct curl_slist *hdrs;
+ apr_status_t rv;
+} curlify_hdrs_ctx;
+
+static int curlify_headers(void *baton, const char *key, const char *value)
+{
+ curlify_hdrs_ctx *ctx = baton;
+ const char *s;
+
+ if (strchr(key, '\r') || strchr(key, '\n')
+ || strchr(value, '\r') || strchr(value, '\n')) {
+ ctx->rv = APR_EINVAL;
+ return 0;
+ }
+ s = apr_psprintf(ctx->req->pool, "%s: %s", key, value);
+ ctx->hdrs = curl_slist_append(ctx->hdrs, s);
+ return 1;
+}
+
+/* Convert timeout values for curl. Since curl uses 0 to disable
+ * timeout, return at least 1 if the apr_time_t value is non-zero. */
+static long timeout_msec(apr_time_t timeout)
+{
+ long ms = (long)apr_time_as_msec(timeout);
+ return ms? ms : (timeout? 1 : 0);
+}
+
+static long timeout_sec(apr_time_t timeout)
+{
+ long s = (long)apr_time_sec(timeout);
+ return s? s : (timeout? 1 : 0);
+}
+
+static int curl_debug_log(CURL *curl, curl_infotype type, char *data, size_t size, void *baton)
+{
+ md_http_request_t *req = baton;
+
+ (void)curl;
+ switch (type) {
+ case CURLINFO_TEXT:
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, req->pool,
+ "req[%d]: info %s", req->id, apr_pstrndup(req->pool, data, size));
+ break;
+ case CURLINFO_HEADER_OUT:
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, req->pool,
+ "req[%d]: header --> %s", req->id, apr_pstrndup(req->pool, data, size));
+ break;
+ case CURLINFO_HEADER_IN:
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, req->pool,
+ "req[%d]: header <-- %s", req->id, apr_pstrndup(req->pool, data, size));
+ break;
+ case CURLINFO_DATA_OUT:
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, req->pool,
+ "req[%d]: data --> %ld bytes", req->id, (long)size);
+ if (md_log_is_level(req->pool, MD_LOG_TRACE5)) {
+ md_data_t d;
+ const char *s;
+ md_data_init(&d, data, size);
+ md_data_to_hex(&s, 0, req->pool, &d);
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE5, 0, req->pool,
+ "req[%d]: data(hex) --> %s", req->id, s);
+ }
+ break;
+ case CURLINFO_DATA_IN:
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, req->pool,
+ "req[%d]: data <-- %ld bytes", req->id, (long)size);
+ if (md_log_is_level(req->pool, MD_LOG_TRACE5)) {
+ md_data_t d;
+ const char *s;
+ md_data_init(&d, data, size);
+ md_data_to_hex(&s, 0, req->pool, &d);
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE5, 0, req->pool,
+ "req[%d]: data(hex) <-- %s", req->id, s);
+ }
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static apr_status_t internals_setup(md_http_request_t *req)
+{
+ md_curl_internals_t *internals;
+ CURL *curl;
+ apr_status_t rv = APR_SUCCESS;
+
+ curl = md_http_get_impl_data(req->http);
+ if (!curl) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, req->pool, "creating curl instance");
+ curl = curl_easy_init();
+ if (!curl) {
+ rv = APR_EGENERAL;
+ goto leave;
+ }
+ curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_cb);
+ curl_easy_setopt(curl, CURLOPT_HEADERDATA, NULL);
+ curl_easy_setopt(curl, CURLOPT_READFUNCTION, req_data_cb);
+ curl_easy_setopt(curl, CURLOPT_READDATA, NULL);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, resp_data_cb);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, NULL);
+ }
+ else {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, req->pool, "reusing curl instance from http");
+ }
+
+ internals = apr_pcalloc(req->pool, sizeof(*internals));
+ internals->curl = curl;
+
+ internals->response = apr_pcalloc(req->pool, sizeof(md_http_response_t));
+ internals->response->req = req;
+ internals->response->status = 400;
+ internals->response->headers = apr_table_make(req->pool, 5);
+ internals->response->body = apr_brigade_create(req->pool, req->bucket_alloc);
+
+ curl_easy_setopt(curl, CURLOPT_URL, req->url);
+ if (!apr_strnatcasecmp("GET", req->method)) {
+ /* nop */
+ }
+ else if (!apr_strnatcasecmp("HEAD", req->method)) {
+ curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
+ }
+ else if (!apr_strnatcasecmp("POST", req->method)) {
+ curl_easy_setopt(curl, CURLOPT_POST, 1L);
+ }
+ else {
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, req->method);
+ }
+ curl_easy_setopt(curl, CURLOPT_HEADERDATA, internals);
+ curl_easy_setopt(curl, CURLOPT_READDATA, req->body);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, internals);
+
+ if (req->timeout.overall > 0) {
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, timeout_msec(req->timeout.overall));
+ }
+ if (req->timeout.connect > 0) {
+ curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, timeout_msec(req->timeout.connect));
+ }
+ if (req->timeout.stalled > 0) {
+ curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, req->timeout.stall_bytes_per_sec);
+ curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, timeout_sec(req->timeout.stalled));
+ }
+ if (req->ca_file) {
+ curl_easy_setopt(curl, CURLOPT_CAINFO, req->ca_file);
+ }
+ if (req->unix_socket_path) {
+ curl_easy_setopt(curl, CURLOPT_UNIX_SOCKET_PATH, req->unix_socket_path);
+ }
+
+ if (req->body_len >= 0) {
+ /* set the Content-Length */
+ curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)req->body_len);
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)req->body_len);
+ }
+
+ if (req->user_agent) {
+ curl_easy_setopt(curl, CURLOPT_USERAGENT, req->user_agent);
+ }
+ if (req->proxy_url) {
+ curl_easy_setopt(curl, CURLOPT_PROXY, req->proxy_url);
+ }
+ if (!apr_is_empty_table(req->headers)) {
+ curlify_hdrs_ctx ctx;
+
+ ctx.req = req;
+ ctx.hdrs = NULL;
+ ctx.rv = APR_SUCCESS;
+ apr_table_do(curlify_headers, &ctx, req->headers, NULL);
+ internals->req_hdrs = ctx.hdrs;
+ if (ctx.rv == APR_SUCCESS) {
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, internals->req_hdrs);
+ }
+ }
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, req->pool,
+ "req[%d]: %s %s", req->id, req->method, req->url);
+
+ if (md_log_is_level(req->pool, MD_LOG_TRACE4)) {
+ curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
+ curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, curl_debug_log);
+ curl_easy_setopt(curl, CURLOPT_DEBUGDATA, req);
+ }
+
+leave:
+ req->internals = (APR_SUCCESS == rv)? internals : NULL;
+ return rv;
+}
+
+static apr_status_t update_status(md_http_request_t *req)
+{
+ md_curl_internals_t *internals = req->internals;
+ long l;
+ apr_status_t rv = APR_SUCCESS;
+
+ if (internals) {
+ rv = curl_status(curl_easy_getinfo(internals->curl, CURLINFO_RESPONSE_CODE, &l));
+ if (APR_SUCCESS == rv) {
+ internals->response->status = (int)l;
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, req->pool,
+ "req[%d]: http status is %d",
+ req->id, internals->response->status);
+ }
+ }
+ return rv;
+}
+
+static void fire_status(md_http_request_t *req, apr_status_t rv)
+{
+ md_curl_internals_t *internals = req->internals;
+
+ if (internals && !internals->status_fired) {
+ internals->status_fired = 1;
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, req->pool,
+ "req[%d] fire callbacks", req->id);
+ if ((APR_SUCCESS == rv) && req->cb.on_response) {
+ rv = req->cb.on_response(internals->response, req->cb.on_response_data);
+ }
+
+ internals->rv = rv;
+ if (req->cb.on_status) {
+ req->cb.on_status(req, rv, req->cb.on_status_data);
+ }
+ }
+}
+
+static apr_status_t md_curl_perform(md_http_request_t *req)
+{
+ apr_status_t rv = APR_SUCCESS;
+ CURLcode curle;
+ md_curl_internals_t *internals;
+ long l;
+
+ if (APR_SUCCESS != (rv = internals_setup(req))) goto leave;
+ internals = req->internals;
+
+ curle = curl_easy_perform(internals->curl);
+
+ rv = curl_status(curle);
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, req->pool,
+ "request failed(%d): %s", curle, curl_easy_strerror(curle));
+ goto leave;
+ }
+
+ rv = curl_status(curl_easy_getinfo(internals->curl, CURLINFO_RESPONSE_CODE, &l));
+ if (APR_SUCCESS == rv) {
+ internals->response->status = (int)l;
+ }
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, req->pool, "request <-- %d",
+ internals->response->status);
+
+ if (req->cb.on_response) {
+ rv = req->cb.on_response(internals->response, req->cb.on_response_data);
+ req->cb.on_response = NULL;
+ }
+
+leave:
+ fire_status(req, rv);
+ md_http_req_destroy(req);
+ return rv;
+}
+
+static md_http_request_t *find_curl_request(apr_array_header_t *requests, CURL *curl)
+{
+ md_http_request_t *req;
+ md_curl_internals_t *internals;
+ int i;
+
+ for (i = 0; i < requests->nelts; ++i) {
+ req = APR_ARRAY_IDX(requests, i, md_http_request_t*);
+ internals = req->internals;
+ if (internals && internals->curl == curl) {
+ return req;
+ }
+ }
+ return NULL;
+}
+
+static void add_to_curlm(md_http_request_t *req, CURLM *curlm)
+{
+ md_curl_internals_t *internals = req->internals;
+
+ assert(curlm);
+ assert(internals);
+ if (internals->curlm == NULL) {
+ internals->curlm = curlm;
+ }
+ assert(internals->curlm == curlm);
+ curl_multi_add_handle(curlm, internals->curl);
+}
+
+static void remove_from_curlm_and_destroy(md_http_request_t *req, CURLM *curlm)
+{
+ md_curl_internals_t *internals = req->internals;
+
+ assert(curlm);
+ assert(internals);
+ assert(internals->curlm == curlm);
+ curl_multi_remove_handle(curlm, internals->curl);
+ internals->curlm = NULL;
+ md_http_req_destroy(req);
+}
+
+static apr_status_t md_curl_multi_perform(md_http_t *http, apr_pool_t *p,
+ md_http_next_req *nextreq, void *baton)
+{
+ md_http_t *sub_http;
+ md_http_request_t *req;
+ CURLM *curlm = NULL;
+ CURLMcode mc;
+ struct CURLMsg *curlmsg;
+ apr_array_header_t *http_spares;
+ apr_array_header_t *requests;
+ int i, running, numfds, slowdown, msgcount;
+ apr_status_t rv;
+
+ http_spares = apr_array_make(p, 10, sizeof(md_http_t*));
+ requests = apr_array_make(p, 10, sizeof(md_http_request_t*));
+ curlm = curl_multi_init();
+ if (!curlm) {
+ rv = APR_ENOMEM;
+ goto leave;
+ }
+
+ running = 1;
+ slowdown = 0;
+ while(1) {
+ while (1) {
+ /* fetch as many requests as nextreq gives us */
+ if (http_spares->nelts > 0) {
+ sub_http = *(md_http_t **)(apr_array_pop(http_spares));
+ }
+ else {
+ rv = md_http_clone(&sub_http, p, http);
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p,
+ "multi_perform[%d reqs]: setup failed", requests->nelts);
+ goto leave;
+ }
+ }
+
+ rv = nextreq(&req, baton, sub_http, requests->nelts);
+ if (APR_STATUS_IS_ENOENT(rv)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, p,
+ "multi_perform[%d reqs]: no more requests", requests->nelts);
+ if (!requests->nelts) {
+ goto leave;
+ }
+ break;
+ }
+ else if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, p,
+ "multi_perform[%d reqs]: nextreq() failed", requests->nelts);
+ APR_ARRAY_PUSH(http_spares, md_http_t*) = sub_http;
+ goto leave;
+ }
+
+ if (APR_SUCCESS != (rv = internals_setup(req))) {
+ if (req->cb.on_status) req->cb.on_status(req, rv, req->cb.on_status_data);
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, p,
+ "multi_perform[%d reqs]: setup failed", requests->nelts);
+ APR_ARRAY_PUSH(http_spares, md_http_t*) = sub_http;
+ goto leave;
+ }
+
+ APR_ARRAY_PUSH(requests, md_http_request_t*) = req;
+ add_to_curlm(req, curlm);
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, p,
+ "multi_perform[%d reqs]: added request", requests->nelts);
+ }
+
+ mc = curl_multi_perform(curlm, &running);
+ if (CURLM_OK == mc) {
+ mc = curl_multi_wait(curlm, NULL, 0, 1000, &numfds);
+ if (numfds) slowdown = 0;
+ }
+ if (CURLM_OK != mc) {
+ rv = APR_ECONNABORTED;
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
+ "multi_perform[%d reqs] failed(%d): %s",
+ requests->nelts, mc, curl_multi_strerror(mc));
+ goto leave;
+ }
+ if (!numfds) {
+ /* no activity on any connection, timeout */
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, p,
+ "multi_perform[%d reqs]: slowdown %d", requests->nelts, slowdown);
+ if (slowdown) apr_sleep(apr_time_from_msec(100));
+ ++slowdown;
+ }
+
+ /* process status messages, e.g. that a request is done */
+ while (running < requests->nelts) {
+ curlmsg = curl_multi_info_read(curlm, &msgcount);
+ if (!curlmsg) break;
+ if (curlmsg->msg == CURLMSG_DONE) {
+ req = find_curl_request(requests, curlmsg->easy_handle);
+ if (req) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, p,
+ "multi_perform[%d reqs]: req[%d] done",
+ requests->nelts, req->id);
+ update_status(req);
+ fire_status(req, curl_status(curlmsg->data.result));
+ md_array_remove(requests, req);
+ sub_http = req->http;
+ APR_ARRAY_PUSH(http_spares, md_http_t*) = sub_http;
+ remove_from_curlm_and_destroy(req, curlm);
+ }
+ else {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p,
+ "multi_perform[%d reqs]: req done, but not found by handle",
+ requests->nelts);
+ }
+ }
+ }
+ };
+
+leave:
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, p,
+ "multi_perform[%d reqs]: leaving", requests->nelts);
+ for (i = 0; i < requests->nelts; ++i) {
+ req = APR_ARRAY_IDX(requests, i, md_http_request_t*);
+ fire_status(req, APR_SUCCESS);
+ sub_http = req->http;
+ APR_ARRAY_PUSH(http_spares, md_http_t*) = sub_http;
+ remove_from_curlm_and_destroy(req, curlm);
+ }
+ if (curlm) curl_multi_cleanup(curlm);
+ return rv;
+}
+
+static int initialized;
+
+static apr_status_t md_curl_init(void) {
+ if (!initialized) {
+ initialized = 1;
+ curl_global_init(CURL_GLOBAL_DEFAULT);
+ }
+ return APR_SUCCESS;
+}
+
+static void md_curl_req_cleanup(md_http_request_t *req)
+{
+ md_curl_internals_t *internals = req->internals;
+ if (internals) {
+ if (internals->curl) {
+ CURL *curl = md_http_get_impl_data(req->http);
+ if (curl == internals->curl) {
+ /* NOP: we have this curl at the md_http_t already */
+ }
+ else if (!curl) {
+ /* no curl at the md_http_t yet, install this one */
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, req->pool, "register curl instance at http");
+ md_http_set_impl_data(req->http, internals->curl);
+ }
+ else {
+ /* There already is a curl at the md_http_t and it's not this one. */
+ curl_easy_cleanup(internals->curl);
+ }
+ }
+ if (internals->req_hdrs) curl_slist_free_all(internals->req_hdrs);
+ req->internals = NULL;
+ }
+}
+
+static void md_curl_cleanup(md_http_t *http, apr_pool_t *pool)
+{
+ CURL *curl;
+
+ curl = md_http_get_impl_data(http);
+ if (curl) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, pool, "cleanup curl instance");
+ md_http_set_impl_data(http, NULL);
+ curl_easy_cleanup(curl);
+ }
+}
+
+static md_http_impl_t impl = {
+ md_curl_init,
+ md_curl_req_cleanup,
+ md_curl_perform,
+ md_curl_multi_perform,
+ md_curl_cleanup,
+};
+
+md_http_impl_t * md_curl_get_impl(apr_pool_t *p)
+{
+ /* trigger early global curl init, before we are down a rabbit hole */
+ (void)p;
+ md_curl_init();
+ return &impl;
+}
diff --git a/modules/md/md_curl.h b/modules/md/md_curl.h
new file mode 100644
index 0000000..cbc1dd2
--- /dev/null
+++ b/modules/md/md_curl.h
@@ -0,0 +1,24 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef md_curl_h
+#define md_curl_h
+
+struct md_http_impl;
+
+struct md_http_impl_t * md_curl_get_impl(apr_pool_t *p);
+
+#endif /* md_curl_h */
diff --git a/modules/md/md_event.c b/modules/md/md_event.c
new file mode 100644
index 0000000..c731d55
--- /dev/null
+++ b/modules/md/md_event.c
@@ -0,0 +1,89 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements. See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+#include <assert.h>
+#include <apr_optional.h>
+#include <apr_strings.h>
+
+#include "md.h"
+#include "md_event.h"
+
+
+typedef struct md_subscription {
+ struct md_subscription *next;
+ md_event_cb *cb;
+ void *baton;
+} md_subscription;
+
+static struct {
+ apr_pool_t *p;
+ md_subscription *subs;
+} EVNT;
+
+static apr_status_t cleanup_setup(void *dummy)
+{
+ (void)dummy;
+ memset(&EVNT, 0, sizeof(EVNT));
+ return APR_SUCCESS;
+}
+
+void md_event_init(apr_pool_t *p)
+{
+ memset(&EVNT, 0, sizeof(EVNT));
+ EVNT.p = p;
+ apr_pool_cleanup_register(p, NULL, cleanup_setup, apr_pool_cleanup_null);
+}
+
+void md_event_subscribe(md_event_cb *cb, void *baton)
+{
+ md_subscription *sub;
+
+ sub = apr_pcalloc(EVNT.p, sizeof(*sub));
+ sub->cb = cb;
+ sub->baton = baton;
+ sub->next = EVNT.subs;
+ EVNT.subs = sub;
+}
+
+apr_status_t md_event_raise(const char *event,
+ const char *mdomain,
+ struct md_job_t *job,
+ struct md_result_t *result,
+ apr_pool_t *p)
+{
+ md_subscription *sub = EVNT.subs;
+ apr_status_t rv;
+
+ while (sub) {
+ rv = sub->cb(event, mdomain, sub->baton, job, result, p);
+ if (APR_SUCCESS != rv) return rv;
+ sub = sub->next;
+ }
+ return APR_SUCCESS;
+}
+
+void md_event_holler(const char *event,
+ const char *mdomain,
+ struct md_job_t *job,
+ struct md_result_t *result,
+ apr_pool_t *p)
+{
+ md_subscription *sub = EVNT.subs;
+ while (sub) {
+ sub->cb(event, mdomain, sub->baton, job, result, p);
+ sub = sub->next;
+ }
+}
diff --git a/modules/md/md_event.h b/modules/md/md_event.h
new file mode 100644
index 0000000..e66c3c2
--- /dev/null
+++ b/modules/md/md_event.h
@@ -0,0 +1,46 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements. See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+#ifndef md_event_h
+#define md_event_h
+
+struct md_job_t;
+struct md_result_t;
+
+typedef apr_status_t md_event_cb(const char *event,
+ const char *mdomain,
+ void *baton,
+ struct md_job_t *job,
+ struct md_result_t *result,
+ apr_pool_t *p);
+
+void md_event_init(apr_pool_t *p);
+
+void md_event_subscribe(md_event_cb *cb, void *baton);
+
+apr_status_t md_event_raise(const char *event,
+ const char *mdomain,
+ struct md_job_t *job,
+ struct md_result_t *result,
+ apr_pool_t *p);
+
+void md_event_holler(const char *event,
+ const char *mdomain,
+ struct md_job_t *job,
+ struct md_result_t *result,
+ apr_pool_t *p);
+
+#endif /* md_event_h */
diff --git a/modules/md/md_http.c b/modules/md/md_http.c
new file mode 100644
index 0000000..0d21e7b
--- /dev/null
+++ b/modules/md/md_http.c
@@ -0,0 +1,397 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+
+#include <apr_lib.h>
+#include <apr_strings.h>
+#include <apr_buckets.h>
+
+#include "md_http.h"
+#include "md_log.h"
+#include "md_util.h"
+
+struct md_http_t {
+ apr_pool_t *pool;
+ apr_bucket_alloc_t *bucket_alloc;
+ int next_id;
+ apr_off_t resp_limit;
+ md_http_impl_t *impl;
+ void *impl_data; /* to be used by the implementation */
+ const char *user_agent;
+ const char *proxy_url;
+ const char *unix_socket_path;
+ md_http_timeouts_t timeout;
+ const char *ca_file;
+};
+
+static md_http_impl_t *cur_impl;
+static int cur_init_done;
+
+void md_http_use_implementation(md_http_impl_t *impl)
+{
+ if (cur_impl != impl) {
+ cur_impl = impl;
+ cur_init_done = 0;
+ }
+}
+
+static apr_status_t http_cleanup(void *data)
+{
+ md_http_t *http = data;
+ if (http && http->impl && http->impl->cleanup) {
+ http->impl->cleanup(http, http->pool);
+ }
+ return APR_SUCCESS;
+}
+
+apr_status_t md_http_create(md_http_t **phttp, apr_pool_t *p, const char *user_agent,
+ const char *proxy_url)
+{
+ md_http_t *http;
+ apr_status_t rv = APR_SUCCESS;
+
+ if (!cur_impl) {
+ *phttp = NULL;
+ return APR_ENOTIMPL;
+ }
+
+ if (!cur_init_done) {
+ if (APR_SUCCESS == (rv = cur_impl->init())) {
+ cur_init_done = 1;
+ }
+ else {
+ return rv;
+ }
+ }
+
+ http = apr_pcalloc(p, sizeof(*http));
+ http->pool = p;
+ http->impl = cur_impl;
+ http->user_agent = apr_pstrdup(p, user_agent);
+ http->proxy_url = proxy_url? apr_pstrdup(p, proxy_url) : NULL;
+ http->bucket_alloc = apr_bucket_alloc_create(p);
+ if (!http->bucket_alloc) {
+ return APR_EGENERAL;
+ }
+ apr_pool_cleanup_register(p, http, http_cleanup, apr_pool_cleanup_null);
+ *phttp = http;
+ return APR_SUCCESS;
+}
+
+apr_status_t md_http_clone(md_http_t **phttp,
+ apr_pool_t *p, md_http_t *source_http)
+{
+ apr_status_t rv;
+
+ rv = md_http_create(phttp, p, source_http->user_agent, source_http->proxy_url);
+ if (APR_SUCCESS == rv) {
+ (*phttp)->resp_limit = source_http->resp_limit;
+ (*phttp)->timeout = source_http->timeout;
+ if (source_http->unix_socket_path) {
+ (*phttp)->unix_socket_path = apr_pstrdup(p, source_http->unix_socket_path);
+ }
+ if (source_http->ca_file) {
+ (*phttp)->ca_file = apr_pstrdup(p, source_http->ca_file);
+ }
+ }
+ return rv;
+}
+
+void md_http_set_impl_data(md_http_t *http, void *data)
+{
+ http->impl_data = data;
+}
+
+void *md_http_get_impl_data(md_http_t *http)
+{
+ return http->impl_data;
+}
+
+void md_http_set_response_limit(md_http_t *http, apr_off_t resp_limit)
+{
+ http->resp_limit = resp_limit;
+}
+
+void md_http_set_timeout_default(md_http_t *http, apr_time_t timeout)
+{
+ http->timeout.overall = timeout;
+}
+
+void md_http_set_timeout(md_http_request_t *req, apr_time_t timeout)
+{
+ req->timeout.overall = timeout;
+}
+
+void md_http_set_connect_timeout_default(md_http_t *http, apr_time_t timeout)
+{
+ http->timeout.connect = timeout;
+}
+
+void md_http_set_connect_timeout(md_http_request_t *req, apr_time_t timeout)
+{
+ req->timeout.connect = timeout;
+}
+
+void md_http_set_stalling_default(md_http_t *http, long bytes_per_sec, apr_time_t timeout)
+{
+ http->timeout.stall_bytes_per_sec = bytes_per_sec;
+ http->timeout.stalled = timeout;
+}
+
+void md_http_set_stalling(md_http_request_t *req, long bytes_per_sec, apr_time_t timeout)
+{
+ req->timeout.stall_bytes_per_sec = bytes_per_sec;
+ req->timeout.stalled = timeout;
+}
+
+void md_http_set_ca_file(md_http_t *http, const char *ca_file)
+{
+ http->ca_file = ca_file;
+}
+
+void md_http_set_unix_socket_path(md_http_t *http, const char *path)
+{
+ http->unix_socket_path = path;
+}
+
+static apr_status_t req_set_body(md_http_request_t *req, const char *content_type,
+ apr_bucket_brigade *body, apr_off_t body_len,
+ int detect_len)
+{
+ apr_status_t rv = APR_SUCCESS;
+
+ if (body && detect_len) {
+ rv = apr_brigade_length(body, 1, &body_len);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+
+ req->body = body;
+ req->body_len = body? body_len : 0;
+ if (content_type) {
+ apr_table_set(req->headers, "Content-Type", content_type);
+ }
+ else {
+ apr_table_unset(req->headers, "Content-Type");
+ }
+ return rv;
+}
+
+static apr_status_t req_set_body_data(md_http_request_t *req, const char *content_type,
+ const md_data_t *body)
+{
+ apr_bucket_brigade *bbody = NULL;
+ apr_status_t rv;
+
+ if (body && body->len > 0) {
+ bbody = apr_brigade_create(req->pool, req->http->bucket_alloc);
+ rv = apr_brigade_write(bbody, NULL, NULL, body->data, body->len);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+ return req_set_body(req, content_type, bbody, body? (apr_off_t)body->len : 0, 0);
+}
+
+static apr_status_t req_create(md_http_request_t **preq, md_http_t *http,
+ const char *method, const char *url,
+ struct apr_table_t *headers)
+{
+ md_http_request_t *req;
+ apr_pool_t *pool;
+ apr_status_t rv;
+
+ rv = apr_pool_create(&pool, http->pool);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ apr_pool_tag(pool, "md_http_req");
+
+ req = apr_pcalloc(pool, sizeof(*req));
+ req->pool = pool;
+ req->id = http->next_id++;
+ req->bucket_alloc = http->bucket_alloc;
+ req->http = http;
+ req->method = method;
+ req->url = url;
+ req->headers = headers? apr_table_copy(req->pool, headers) : apr_table_make(req->pool, 5);
+ req->resp_limit = http->resp_limit;
+ req->user_agent = http->user_agent;
+ req->proxy_url = http->proxy_url;
+ req->timeout = http->timeout;
+ req->ca_file = http->ca_file;
+ req->unix_socket_path = http->unix_socket_path;
+ *preq = req;
+ return rv;
+}
+
+void md_http_req_destroy(md_http_request_t *req)
+{
+ if (req->internals) {
+ req->http->impl->req_cleanup(req);
+ req->internals = NULL;
+ }
+ apr_pool_destroy(req->pool);
+}
+
+void md_http_set_on_status_cb(md_http_request_t *req, md_http_status_cb *cb, void *baton)
+{
+ req->cb.on_status = cb;
+ req->cb.on_status_data = baton;
+}
+
+void md_http_set_on_response_cb(md_http_request_t *req, md_http_response_cb *cb, void *baton)
+{
+ req->cb.on_response = cb;
+ req->cb.on_response_data = baton;
+}
+
+apr_status_t md_http_perform(md_http_request_t *req)
+{
+ return req->http->impl->perform(req);
+}
+
+typedef struct {
+ md_http_next_req *nextreq;
+ void *baton;
+} nextreq_proxy_t;
+
+static apr_status_t proxy_nextreq(md_http_request_t **preq, void *baton,
+ md_http_t *http, int in_flight)
+{
+ nextreq_proxy_t *proxy = baton;
+
+ return proxy->nextreq(preq, proxy->baton, http, in_flight);
+}
+
+apr_status_t md_http_multi_perform(md_http_t *http, md_http_next_req *nextreq, void *baton)
+{
+ nextreq_proxy_t proxy;
+
+ proxy.nextreq = nextreq;
+ proxy.baton = baton;
+ return http->impl->multi_perform(http, http->pool, proxy_nextreq, &proxy);
+}
+
+apr_status_t md_http_GET_create(md_http_request_t **preq, md_http_t *http, const char *url,
+ struct apr_table_t *headers)
+{
+ md_http_request_t *req;
+ apr_status_t rv;
+
+ rv = req_create(&req, http, "GET", url, headers);
+ *preq = (APR_SUCCESS == rv)? req : NULL;
+ return rv;
+}
+
+apr_status_t md_http_HEAD_create(md_http_request_t **preq, md_http_t *http, const char *url,
+ struct apr_table_t *headers)
+{
+ md_http_request_t *req;
+ apr_status_t rv;
+
+ rv = req_create(&req, http, "HEAD", url, headers);
+ *preq = (APR_SUCCESS == rv)? req : NULL;
+ return rv;
+}
+
+apr_status_t md_http_POST_create(md_http_request_t **preq, md_http_t *http, const char *url,
+ struct apr_table_t *headers, const char *content_type,
+ struct apr_bucket_brigade *body, int detect_len)
+{
+ md_http_request_t *req;
+ apr_status_t rv;
+
+ rv = req_create(&req, http, "POST", url, headers);
+ if (APR_SUCCESS == rv) {
+ rv = req_set_body(req, content_type, body, -1, detect_len);
+ }
+ *preq = (APR_SUCCESS == rv)? req : NULL;
+ return rv;
+}
+
+apr_status_t md_http_POSTd_create(md_http_request_t **preq, md_http_t *http, const char *url,
+ struct apr_table_t *headers, const char *content_type,
+ const struct md_data_t *body)
+{
+ md_http_request_t *req;
+ apr_status_t rv;
+
+ rv = req_create(&req, http, "POST", url, headers);
+ if (APR_SUCCESS != rv) goto cleanup;
+ rv = req_set_body_data(req, content_type, body);
+cleanup:
+ if (APR_SUCCESS == rv) {
+ *preq = req;
+ }
+ else {
+ *preq = NULL;
+ if (req) md_http_req_destroy(req);
+ }
+ return rv;
+}
+
+apr_status_t md_http_GET_perform(struct md_http_t *http,
+ const char *url, struct apr_table_t *headers,
+ md_http_response_cb *cb, void *baton)
+{
+ md_http_request_t *req;
+ apr_status_t rv;
+
+ rv = md_http_GET_create(&req, http, url, headers);
+ if (APR_SUCCESS == rv) md_http_set_on_response_cb(req, cb, baton);
+ return (APR_SUCCESS == rv)? md_http_perform(req) : rv;
+}
+
+apr_status_t md_http_HEAD_perform(struct md_http_t *http,
+ const char *url, struct apr_table_t *headers,
+ md_http_response_cb *cb, void *baton)
+{
+ md_http_request_t *req;
+ apr_status_t rv;
+
+ rv = md_http_HEAD_create(&req, http, url, headers);
+ if (APR_SUCCESS == rv) md_http_set_on_response_cb(req, cb, baton);
+ return (APR_SUCCESS == rv)? md_http_perform(req) : rv;
+}
+
+apr_status_t md_http_POST_perform(struct md_http_t *http, const char *url,
+ struct apr_table_t *headers, const char *content_type,
+ apr_bucket_brigade *body, int detect_len,
+ md_http_response_cb *cb, void *baton)
+{
+ md_http_request_t *req;
+ apr_status_t rv;
+
+ rv = md_http_POST_create(&req, http, url, headers, content_type, body, detect_len);
+ if (APR_SUCCESS == rv) md_http_set_on_response_cb(req, cb, baton);
+ return (APR_SUCCESS == rv)? md_http_perform(req) : rv;
+}
+
+apr_status_t md_http_POSTd_perform(md_http_t *http, const char *url,
+ struct apr_table_t *headers, const char *content_type,
+ const md_data_t *body,
+ md_http_response_cb *cb, void *baton)
+{
+ md_http_request_t *req;
+ apr_status_t rv;
+
+ rv = md_http_POSTd_create(&req, http, url, headers, content_type, body);
+ if (APR_SUCCESS == rv) md_http_set_on_response_cb(req, cb, baton);
+ return (APR_SUCCESS == rv)? md_http_perform(req) : rv;
+}
diff --git a/modules/md/md_http.h b/modules/md/md_http.h
new file mode 100644
index 0000000..2f250f6
--- /dev/null
+++ b/modules/md/md_http.h
@@ -0,0 +1,272 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mod_md_md_http_h
+#define mod_md_md_http_h
+
+struct apr_table_t;
+struct apr_bucket_brigade;
+struct apr_bucket_alloc_t;
+struct md_data_t;
+
+typedef struct md_http_t md_http_t;
+
+typedef struct md_http_request_t md_http_request_t;
+typedef struct md_http_response_t md_http_response_t;
+
+/**
+ * Callback invoked once per request, either when an error was encountered
+ * or when everything succeeded and the request is about to be released. Only
+ * in the last case will the status be APR_SUCCESS.
+ */
+typedef apr_status_t md_http_status_cb(const md_http_request_t *req, apr_status_t status, void *data);
+
+/**
+ * Callback invoked when the complete response has been received.
+ */
+typedef apr_status_t md_http_response_cb(const md_http_response_t *res, void *data);
+
+typedef struct md_http_callbacks_t md_http_callbacks_t;
+struct md_http_callbacks_t {
+ md_http_status_cb *on_status;
+ void *on_status_data;
+ md_http_response_cb *on_response;
+ void *on_response_data;
+};
+
+typedef struct md_http_timeouts_t md_http_timeouts_t;
+struct md_http_timeouts_t {
+ apr_time_t overall;
+ apr_time_t connect;
+ long stall_bytes_per_sec;
+ apr_time_t stalled;
+};
+
+struct md_http_request_t {
+ md_http_t *http;
+ apr_pool_t *pool;
+ int id;
+ struct apr_bucket_alloc_t *bucket_alloc;
+ const char *method;
+ const char *url;
+ const char *user_agent;
+ const char *proxy_url;
+ const char *ca_file;
+ const char *unix_socket_path;
+ apr_table_t *headers;
+ struct apr_bucket_brigade *body;
+ apr_off_t body_len;
+ apr_off_t resp_limit;
+ md_http_timeouts_t timeout;
+ md_http_callbacks_t cb;
+ void *internals;
+};
+
+struct md_http_response_t {
+ md_http_request_t *req;
+ int status;
+ apr_table_t *headers;
+ struct apr_bucket_brigade *body;
+};
+
+apr_status_t md_http_create(md_http_t **phttp, apr_pool_t *p, const char *user_agent,
+ const char *proxy_url);
+
+void md_http_set_response_limit(md_http_t *http, apr_off_t resp_limit);
+
+/**
+ * Clone a http instance, inheriting all settings from source_http.
+ * The cloned instance is not tied in any way to the source.
+ */
+apr_status_t md_http_clone(md_http_t **phttp,
+ apr_pool_t *p, md_http_t *source_http);
+
+/**
+ * Set the timeout for the complete request. This needs to take everything from
+ * DNS looksups, to conntects, to transfer of all data into account and should
+ * be sufficiently large.
+ * Set to 0 the have no timeout for this.
+ */
+void md_http_set_timeout_default(md_http_t *http, apr_time_t timeout);
+void md_http_set_timeout(md_http_request_t *req, apr_time_t timeout);
+
+/**
+ * Set the timeout for establishing a connection.
+ * Set to 0 the have no special timeout for this.
+ */
+void md_http_set_connect_timeout_default(md_http_t *http, apr_time_t timeout);
+void md_http_set_connect_timeout(md_http_request_t *req, apr_time_t timeout);
+
+/**
+ * Set the condition for when a transfer is considered "stalled", e.g. does not
+ * progress at a sufficient rate and will be aborted.
+ * Set to 0 the have no stall detection in place.
+ */
+void md_http_set_stalling_default(md_http_t *http, long bytes_per_sec, apr_time_t timeout);
+void md_http_set_stalling(md_http_request_t *req, long bytes_per_sec, apr_time_t timeout);
+
+/**
+ * Set a CA file (in PERM format) to use for root certificates when
+ * verifying SSL connections. If not set (or set to NULL), the systems
+ * certificate store will be used.
+ */
+void md_http_set_ca_file(md_http_t *http, const char *ca_file);
+
+/**
+ * Set the path of a unix domain socket for use instead of TCP
+ * in a connection. Disable by providing NULL as path.
+ */
+void md_http_set_unix_socket_path(md_http_t *http, const char *path);
+
+/**
+ * Perform the request. Then this function returns, the request and
+ * all its memory has been freed and must no longer be used.
+ */
+apr_status_t md_http_perform(md_http_request_t *request);
+
+/**
+ * Set the callback to be invoked once the status of a request is known.
+ * @param req the request
+ * @param cb the callback to invoke on the response
+ * @param baton data passed to the callback
+ */
+void md_http_set_on_status_cb(md_http_request_t *req, md_http_status_cb *cb, void *baton);
+
+/**
+ * Set the callback to be invoked when the complete
+ * response has been successfully received. The HTTP status may
+ * be 500, however.
+ * @param req the request
+ * @param cb the callback to invoke on the response
+ * @param baton data passed to the callback
+ */
+void md_http_set_on_response_cb(md_http_request_t *req, md_http_response_cb *cb, void *baton);
+
+/**
+ * Create a GET request.
+ * @param preq the created request after success
+ * @param http the md_http instance
+ * @param url the url to GET
+ * @param headers request headers
+ */
+apr_status_t md_http_GET_create(md_http_request_t **preq, md_http_t *http, const char *url,
+ struct apr_table_t *headers);
+
+/**
+ * Create a HEAD request.
+ * @param preq the created request after success
+ * @param http the md_http instance
+ * @param url the url to GET
+ * @param headers request headers
+ */
+apr_status_t md_http_HEAD_create(md_http_request_t **preq, md_http_t *http, const char *url,
+ struct apr_table_t *headers);
+
+/**
+ * Create a POST request with a bucket brigade as request body.
+ * @param preq the created request after success
+ * @param http the md_http instance
+ * @param url the url to GET
+ * @param headers request headers
+ * @param content_type the content_type of the body or NULL
+ * @param body the body of the request or NULL
+ * @param detect_len scan the body to detect its length
+ */
+apr_status_t md_http_POST_create(md_http_request_t **preq, md_http_t *http, const char *url,
+ struct apr_table_t *headers, const char *content_type,
+ struct apr_bucket_brigade *body, int detect_len);
+
+/**
+ * Create a POST request with known request body data.
+ * @param preq the created request after success
+ * @param http the md_http instance
+ * @param url the url to GET
+ * @param headers request headers
+ * @param content_type the content_type of the body or NULL
+ * @param body the body of the request or NULL
+ */
+apr_status_t md_http_POSTd_create(md_http_request_t **preq, md_http_t *http, const char *url,
+ struct apr_table_t *headers, const char *content_type,
+ const struct md_data_t *body);
+
+/*
+ * Convenience functions for create+perform.
+ */
+apr_status_t md_http_GET_perform(md_http_t *http, const char *url,
+ struct apr_table_t *headers,
+ md_http_response_cb *cb, void *baton);
+apr_status_t md_http_HEAD_perform(md_http_t *http, const char *url,
+ struct apr_table_t *headers,
+ md_http_response_cb *cb, void *baton);
+apr_status_t md_http_POST_perform(md_http_t *http, const char *url,
+ struct apr_table_t *headers, const char *content_type,
+ struct apr_bucket_brigade *body, int detect_len,
+ md_http_response_cb *cb, void *baton);
+apr_status_t md_http_POSTd_perform(md_http_t *http, const char *url,
+ struct apr_table_t *headers, const char *content_type,
+ const struct md_data_t *body,
+ md_http_response_cb *cb, void *baton);
+
+void md_http_req_destroy(md_http_request_t *req);
+
+/** Return the next request for processing on APR_SUCCESS. Return ARP_ENOENT
+ * when no request is available. Anything else is an error.
+ */
+typedef apr_status_t md_http_next_req(md_http_request_t **preq, void *baton,
+ md_http_t *http, int in_flight);
+
+/**
+ * Perform requests in parallel as retrieved from the nextreq function.
+ * There are as many requests in flight as the nextreq functions provides.
+ *
+ * To limit the number of parallel requests, nextreq should return APR_ENOENT when the limit
+ * is reached. It will be called again when the number of in_flight requests changes.
+ *
+ * When all requests are done, nextreq will be called one more time. Should it not
+ * return anything, this function returns.
+ */
+apr_status_t md_http_multi_perform(md_http_t *http, md_http_next_req *nextreq, void *baton);
+
+/**************************************************************************************************/
+/* interface to implementation */
+
+typedef apr_status_t md_http_init_cb(void);
+typedef void md_http_cleanup_cb(md_http_t *req, apr_pool_t *p);
+typedef void md_http_req_cleanup_cb(md_http_request_t *req);
+typedef apr_status_t md_http_perform_cb(md_http_request_t *req);
+typedef apr_status_t md_http_multi_perform_cb(md_http_t *http, apr_pool_t *p,
+ md_http_next_req *nextreq, void *baton);
+
+typedef struct md_http_impl_t md_http_impl_t;
+struct md_http_impl_t {
+ md_http_init_cb *init;
+ md_http_req_cleanup_cb *req_cleanup;
+ md_http_perform_cb *perform;
+ md_http_multi_perform_cb *multi_perform;
+ md_http_cleanup_cb *cleanup;
+};
+
+void md_http_use_implementation(md_http_impl_t *impl);
+
+/**
+ * get/set data the implementation wants to remember between requests
+ * in the same md_http_t instance.
+ */
+void md_http_set_impl_data(md_http_t *http, void *data);
+void *md_http_get_impl_data(md_http_t *http);
+
+
+#endif /* md_http_h */
diff --git a/modules/md/md_json.c b/modules/md/md_json.c
new file mode 100644
index 0000000..e0f977e
--- /dev/null
+++ b/modules/md/md_json.c
@@ -0,0 +1,1311 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <apr_lib.h>
+#include <apr_strings.h>
+#include <apr_buckets.h>
+#include <apr_date.h>
+
+#include "md_json.h"
+#include "md_log.h"
+#include "md_http.h"
+#include "md_time.h"
+#include "md_util.h"
+
+/* jansson thinks everyone compiles with the platform's cc in its fullest capabilities
+ * when undefining their INLINEs, we get static, unused functions, arg
+ */
+#if defined(__GNUC__)
+#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
+#pragma GCC diagnostic push
+#endif
+#pragma GCC diagnostic ignored "-Wunused-function"
+#pragma GCC diagnostic ignored "-Wunreachable-code"
+#elif defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-function"
+#endif
+
+#include <jansson_config.h>
+#undef JSON_INLINE
+#define JSON_INLINE
+#include <jansson.h>
+
+#if defined(__GNUC__)
+#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
+#pragma GCC diagnostic pop
+#endif
+#elif defined(__clang__)
+#pragma clang diagnostic pop
+#endif
+
+struct md_json_t {
+ apr_pool_t *p;
+ json_t *j;
+};
+
+/**************************************************************************************************/
+/* lifecycle */
+
+static apr_status_t json_pool_cleanup(void *data)
+{
+ md_json_t *json = data;
+ if (json) {
+ md_json_destroy(json);
+ }
+ return APR_SUCCESS;
+}
+
+static md_json_t *json_create(apr_pool_t *pool, json_t *j)
+{
+ md_json_t *json;
+
+ if (!j) {
+ apr_abortfunc_t abfn = apr_pool_abort_get(pool);
+ if (abfn) {
+ abfn(APR_ENOMEM);
+ }
+ assert(j != NULL); /* failsafe in case abort is unset */
+ }
+ json = apr_pcalloc(pool, sizeof(*json));
+ json->p = pool;
+ json->j = j;
+ apr_pool_cleanup_register(pool, json, json_pool_cleanup, apr_pool_cleanup_null);
+
+ return json;
+}
+
+md_json_t *md_json_create(apr_pool_t *pool)
+{
+ return json_create(pool, json_object());
+}
+
+md_json_t *md_json_create_s(apr_pool_t *pool, const char *s)
+{
+ return json_create(pool, json_string(s));
+}
+
+void md_json_destroy(md_json_t *json)
+{
+ if (json && json->j) {
+ assert(json->j->refcount > 0);
+ json_decref(json->j);
+ json->j = NULL;
+ }
+}
+
+md_json_t *md_json_copy(apr_pool_t *pool, const md_json_t *json)
+{
+ return json_create(pool, json_copy(json->j));
+}
+
+md_json_t *md_json_clone(apr_pool_t *pool, const md_json_t *json)
+{
+ return json_create(pool, json_deep_copy(json->j));
+}
+
+/**************************************************************************************************/
+/* selectors */
+
+
+static json_t *jselect(const md_json_t *json, va_list ap)
+{
+ json_t *j;
+ const char *key;
+
+ j = json->j;
+ key = va_arg(ap, char *);
+ while (key && j) {
+ j = json_object_get(j, key);
+ key = va_arg(ap, char *);
+ }
+ return j;
+}
+
+static json_t *jselect_parent(const char **child_key, int create, md_json_t *json, va_list ap)
+{
+ const char *key, *next;
+ json_t *j, *jn;
+
+ *child_key = NULL;
+ j = json->j;
+ key = va_arg(ap, char *);
+ while (key && j) {
+ next = va_arg(ap, char *);
+ if (next) {
+ jn = json_object_get(j, key);
+ if (!jn && create) {
+ jn = json_object();
+ json_object_set_new(j, key, jn);
+ }
+ j = jn;
+ }
+ else {
+ *child_key = key;
+ }
+ key = next;
+ }
+ return j;
+}
+
+static apr_status_t jselect_add(json_t *val, md_json_t *json, va_list ap)
+{
+ const char *key;
+ json_t *j, *aj;
+
+ j = jselect_parent(&key, 1, json, ap);
+
+ if (!j || !json_is_object(j)) {
+ return APR_EINVAL;
+ }
+
+ aj = json_object_get(j, key);
+ if (!aj) {
+ aj = json_array();
+ json_object_set_new(j, key, aj);
+ }
+
+ if (!json_is_array(aj)) {
+ return APR_EINVAL;
+ }
+
+ json_array_append(aj, val);
+ return APR_SUCCESS;
+}
+
+static apr_status_t jselect_insert(json_t *val, size_t index, md_json_t *json, va_list ap)
+{
+ const char *key;
+ json_t *j, *aj;
+
+ j = jselect_parent(&key, 1, json, ap);
+
+ if (!j || !json_is_object(j)) {
+ json_decref(val);
+ return APR_EINVAL;
+ }
+
+ aj = json_object_get(j, key);
+ if (!aj) {
+ aj = json_array();
+ json_object_set_new(j, key, aj);
+ }
+
+ if (!json_is_array(aj)) {
+ json_decref(val);
+ return APR_EINVAL;
+ }
+
+ if (json_array_size(aj) <= index) {
+ json_array_append(aj, val);
+ }
+ else {
+ json_array_insert(aj, index, val);
+ }
+ return APR_SUCCESS;
+}
+
+static apr_status_t jselect_set(json_t *val, md_json_t *json, va_list ap)
+{
+ const char *key;
+ json_t *j;
+
+ j = jselect_parent(&key, 1, json, ap);
+
+ if (!j) {
+ return APR_EINVAL;
+ }
+
+ if (key) {
+ if (!json_is_object(j)) {
+ return APR_EINVAL;
+ }
+ json_object_set(j, key, val);
+ }
+ else {
+ /* replace */
+ if (json->j) {
+ json_decref(json->j);
+ }
+ json_incref(val);
+ json->j = val;
+ }
+ return APR_SUCCESS;
+}
+
+static apr_status_t jselect_set_new(json_t *val, md_json_t *json, va_list ap)
+{
+ const char *key;
+ json_t *j;
+
+ j = jselect_parent(&key, 1, json, ap);
+
+ if (!j) {
+ json_decref(val);
+ return APR_EINVAL;
+ }
+
+ if (key) {
+ if (!json_is_object(j)) {
+ json_decref(val);
+ return APR_EINVAL;
+ }
+ json_object_set_new(j, key, val);
+ }
+ else {
+ /* replace */
+ if (json->j) {
+ json_decref(json->j);
+ }
+ json->j = val;
+ }
+ return APR_SUCCESS;
+}
+
+int md_json_has_key(const md_json_t *json, ...)
+{
+ json_t *j;
+ va_list ap;
+
+ va_start(ap, json);
+ j = jselect(json, ap);
+ va_end(ap);
+
+ return j != NULL;
+}
+
+/**************************************************************************************************/
+/* type things */
+
+int md_json_is(const md_json_type_t jtype, md_json_t *json, ...)
+{
+ json_t *j;
+ va_list ap;
+
+ va_start(ap, json);
+ j = jselect(json, ap);
+ va_end(ap);
+ switch (jtype) {
+ case MD_JSON_TYPE_OBJECT: return (j && json_is_object(j));
+ case MD_JSON_TYPE_ARRAY: return (j && json_is_array(j));
+ case MD_JSON_TYPE_STRING: return (j && json_is_string(j));
+ case MD_JSON_TYPE_REAL: return (j && json_is_real(j));
+ case MD_JSON_TYPE_INT: return (j && json_is_integer(j));
+ case MD_JSON_TYPE_BOOL: return (j && (json_is_true(j) || json_is_false(j)));
+ case MD_JSON_TYPE_NULL: return (j == NULL);
+ }
+ return 0;
+}
+
+static const char *md_json_type_name(const md_json_t *json)
+{
+ json_t *j = json->j;
+ if (json_is_object(j)) return "object";
+ if (json_is_array(j)) return "array";
+ if (json_is_string(j)) return "string";
+ if (json_is_real(j)) return "real";
+ if (json_is_integer(j)) return "integer";
+ if (json_is_true(j)) return "true";
+ if (json_is_false(j)) return "false";
+ return "unknown";
+}
+
+/**************************************************************************************************/
+/* booleans */
+
+int md_json_getb(const md_json_t *json, ...)
+{
+ json_t *j;
+ va_list ap;
+
+ va_start(ap, json);
+ j = jselect(json, ap);
+ va_end(ap);
+
+ return j? json_is_true(j) : 0;
+}
+
+apr_status_t md_json_setb(int value, md_json_t *json, ...)
+{
+ va_list ap;
+ apr_status_t rv;
+
+ va_start(ap, json);
+ rv = jselect_set_new(json_boolean(value), json, ap);
+ va_end(ap);
+ return rv;
+}
+
+/**************************************************************************************************/
+/* numbers */
+
+double md_json_getn(const md_json_t *json, ...)
+{
+ json_t *j;
+ va_list ap;
+
+ va_start(ap, json);
+ j = jselect(json, ap);
+ va_end(ap);
+ return (j && json_is_number(j))? json_number_value(j) : 0.0;
+}
+
+apr_status_t md_json_setn(double value, md_json_t *json, ...)
+{
+ va_list ap;
+ apr_status_t rv;
+
+ va_start(ap, json);
+ rv = jselect_set_new(json_real(value), json, ap);
+ va_end(ap);
+ return rv;
+}
+
+/**************************************************************************************************/
+/* longs */
+
+long md_json_getl(const md_json_t *json, ...)
+{
+ json_t *j;
+ va_list ap;
+
+ va_start(ap, json);
+ j = jselect(json, ap);
+ va_end(ap);
+ return (long)((j && json_is_number(j))? json_integer_value(j) : 0L);
+}
+
+apr_status_t md_json_setl(long value, md_json_t *json, ...)
+{
+ va_list ap;
+ apr_status_t rv;
+
+ va_start(ap, json);
+ rv = jselect_set_new(json_integer(value), json, ap);
+ va_end(ap);
+ return rv;
+}
+
+/**************************************************************************************************/
+/* strings */
+
+const char *md_json_gets(const md_json_t *json, ...)
+{
+ json_t *j;
+ va_list ap;
+
+ va_start(ap, json);
+ j = jselect(json, ap);
+ va_end(ap);
+
+ return (j && json_is_string(j))? json_string_value(j) : NULL;
+}
+
+const char *md_json_dups(apr_pool_t *p, const md_json_t *json, ...)
+{
+ json_t *j;
+ va_list ap;
+
+ va_start(ap, json);
+ j = jselect(json, ap);
+ va_end(ap);
+
+ return (j && json_is_string(j))? apr_pstrdup(p, json_string_value(j)) : NULL;
+}
+
+apr_status_t md_json_sets(const char *value, md_json_t *json, ...)
+{
+ va_list ap;
+ apr_status_t rv;
+
+ va_start(ap, json);
+ rv = jselect_set_new(json_string(value), json, ap);
+ va_end(ap);
+ return rv;
+}
+
+/**************************************************************************************************/
+/* time */
+
+apr_time_t md_json_get_time(const md_json_t *json, ...)
+{
+ json_t *j;
+ va_list ap;
+
+ va_start(ap, json);
+ j = jselect(json, ap);
+ va_end(ap);
+
+ if (!j || !json_is_string(j)) return 0;
+ return apr_date_parse_rfc(json_string_value(j));
+}
+
+apr_status_t md_json_set_time(apr_time_t value, md_json_t *json, ...)
+{
+ char ts[APR_RFC822_DATE_LEN];
+ va_list ap;
+ apr_status_t rv;
+
+ apr_rfc822_date(ts, value);
+ va_start(ap, json);
+ rv = jselect_set_new(json_string(ts), json, ap);
+ va_end(ap);
+ return rv;
+}
+
+/**************************************************************************************************/
+/* json itself */
+
+md_json_t *md_json_getj(md_json_t *json, ...)
+{
+ json_t *j;
+ va_list ap;
+
+ va_start(ap, json);
+ j = jselect(json, ap);
+ va_end(ap);
+
+ if (j) {
+ if (j == json->j) {
+ return json;
+ }
+ json_incref(j);
+ return json_create(json->p, j);
+ }
+ return NULL;
+}
+
+md_json_t *md_json_dupj(apr_pool_t *p, const md_json_t *json, ...)
+{
+ json_t *j;
+ va_list ap;
+
+ va_start(ap, json);
+ j = jselect(json, ap);
+ va_end(ap);
+
+ if (j) {
+ json_incref(j);
+ return json_create(p, j);
+ }
+ return NULL;
+}
+
+const md_json_t *md_json_getcj(const md_json_t *json, ...)
+{
+ json_t *j;
+ va_list ap;
+
+ va_start(ap, json);
+ j = jselect(json, ap);
+ va_end(ap);
+
+ if (j) {
+ if (j == json->j) {
+ return json;
+ }
+ json_incref(j);
+ return json_create(json->p, j);
+ }
+ return NULL;
+}
+
+apr_status_t md_json_setj(const md_json_t *value, md_json_t *json, ...)
+{
+ va_list ap;
+ apr_status_t rv;
+ const char *key;
+ json_t *j;
+
+ if (value) {
+ va_start(ap, json);
+ rv = jselect_set(value->j, json, ap);
+ va_end(ap);
+ }
+ else {
+ va_start(ap, json);
+ j = jselect_parent(&key, 1, json, ap);
+ va_end(ap);
+
+ if (key && j && !json_is_object(j)) {
+ json_object_del(j, key);
+ rv = APR_SUCCESS;
+ }
+ else {
+ rv = APR_EINVAL;
+ }
+ }
+ return rv;
+}
+
+apr_status_t md_json_addj(const md_json_t *value, md_json_t *json, ...)
+{
+ va_list ap;
+ apr_status_t rv;
+
+ va_start(ap, json);
+ rv = jselect_add(value->j, json, ap);
+ va_end(ap);
+ return rv;
+}
+
+apr_status_t md_json_insertj(md_json_t *value, size_t index, md_json_t *json, ...)
+{
+ va_list ap;
+ apr_status_t rv;
+
+ va_start(ap, json);
+ rv = jselect_insert(value->j, index, json, ap);
+ va_end(ap);
+ return rv;
+}
+
+apr_size_t md_json_limita(size_t max_elements, md_json_t *json, ...)
+{
+ json_t *j;
+ va_list ap;
+ apr_size_t n = 0;
+
+ va_start(ap, json);
+ j = jselect(json, ap);
+ va_end(ap);
+
+ if (j && json_is_array(j)) {
+ n = json_array_size(j);
+ while (n > max_elements) {
+ json_array_remove(j, n-1);
+ n = json_array_size(j);
+ }
+ }
+ return n;
+}
+
+/**************************************************************************************************/
+/* arrays / objects */
+
+apr_status_t md_json_clr(md_json_t *json, ...)
+{
+ json_t *j;
+ va_list ap;
+
+ va_start(ap, json);
+ j = jselect(json, ap);
+ va_end(ap);
+
+ if (j && json_is_object(j)) {
+ json_object_clear(j);
+ }
+ else if (j && json_is_array(j)) {
+ json_array_clear(j);
+ }
+ return APR_SUCCESS;
+}
+
+apr_status_t md_json_del(md_json_t *json, ...)
+{
+ const char *key;
+ json_t *j;
+ va_list ap;
+
+ va_start(ap, json);
+ j = jselect_parent(&key, 0, json, ap);
+ va_end(ap);
+
+ if (key && j && json_is_object(j)) {
+ json_object_del(j, key);
+ }
+ return APR_SUCCESS;
+}
+
+/**************************************************************************************************/
+/* object strings */
+
+apr_status_t md_json_gets_dict(apr_table_t *dict, const md_json_t *json, ...)
+{
+ json_t *j;
+ va_list ap;
+
+ va_start(ap, json);
+ j = jselect(json, ap);
+ va_end(ap);
+
+ if (j && json_is_object(j)) {
+ const char *key;
+ json_t *val;
+
+ json_object_foreach(j, key, val) {
+ if (json_is_string(val)) {
+ apr_table_set(dict, key, json_string_value(val));
+ }
+ }
+ return APR_SUCCESS;
+ }
+ return APR_ENOENT;
+}
+
+static int object_set(void *data, const char *key, const char *val)
+{
+ json_t *j = data, *nj = json_string(val);
+ json_object_set(j, key, nj);
+ json_decref(nj);
+ return 1;
+}
+
+apr_status_t md_json_sets_dict(apr_table_t *dict, md_json_t *json, ...)
+{
+ json_t *nj, *j;
+ va_list ap;
+
+ va_start(ap, json);
+ j = jselect(json, ap);
+ va_end(ap);
+
+ if (!j || !json_is_object(j)) {
+ const char *key;
+
+ va_start(ap, json);
+ j = jselect_parent(&key, 1, json, ap);
+ va_end(ap);
+
+ if (!key || !j || !json_is_object(j)) {
+ return APR_EINVAL;
+ }
+ nj = json_object();
+ json_object_set_new(j, key, nj);
+ j = nj;
+ }
+
+ apr_table_do(object_set, j, dict, NULL);
+ return APR_SUCCESS;
+}
+
+/**************************************************************************************************/
+/* conversions */
+
+apr_status_t md_json_pass_to(void *value, md_json_t *json, apr_pool_t *p, void *baton)
+{
+ (void)p;
+ (void)baton;
+ return md_json_setj(value, json, NULL);
+}
+
+apr_status_t md_json_pass_from(void **pvalue, md_json_t *json, apr_pool_t *p, void *baton)
+{
+ (void)p;
+ (void)baton;
+ *pvalue = json;
+ return APR_SUCCESS;
+}
+
+apr_status_t md_json_clone_to(void *value, md_json_t *json, apr_pool_t *p, void *baton)
+{
+ (void)baton;
+ return md_json_setj(md_json_clone(p, value), json, NULL);
+}
+
+apr_status_t md_json_clone_from(void **pvalue, const md_json_t *json, apr_pool_t *p, void *baton)
+{
+ (void)baton;
+ *pvalue = md_json_clone(p, json);
+ return APR_SUCCESS;
+}
+
+/**************************************************************************************************/
+/* array generic */
+
+apr_status_t md_json_geta(apr_array_header_t *a, md_json_from_cb *cb, void *baton,
+ const md_json_t *json, ...)
+{
+ json_t *j;
+ va_list ap;
+ apr_status_t rv = APR_SUCCESS;
+ size_t index;
+ json_t *val;
+ md_json_t wrap;
+ void *element;
+
+ va_start(ap, json);
+ j = jselect(json, ap);
+ va_end(ap);
+
+ if (!j || !json_is_array(j)) {
+ return APR_ENOENT;
+ }
+
+ wrap.p = a->pool;
+ json_array_foreach(j, index, val) {
+ wrap.j = val;
+ if (APR_SUCCESS == (rv = cb(&element, &wrap, wrap.p, baton))) {
+ if (element) {
+ APR_ARRAY_PUSH(a, void*) = element;
+ }
+ }
+ else if (APR_ENOENT == rv) {
+ rv = APR_SUCCESS;
+ }
+ else {
+ break;
+ }
+ }
+ return rv;
+}
+
+apr_status_t md_json_seta(apr_array_header_t *a, md_json_to_cb *cb, void *baton,
+ md_json_t *json, ...)
+{
+ json_t *j, *nj;
+ md_json_t wrap;
+ apr_status_t rv = APR_SUCCESS;
+ va_list ap;
+ int i;
+
+ va_start(ap, json);
+ j = jselect(json, ap);
+ va_end(ap);
+
+ if (!j || !json_is_array(j)) {
+ const char *key;
+
+ va_start(ap, json);
+ j = jselect_parent(&key, 1, json, ap);
+ va_end(ap);
+
+ if (!key || !j || !json_is_object(j)) {
+ return APR_EINVAL;
+ }
+ nj = json_array();
+ json_object_set_new(j, key, nj);
+ j = nj;
+ }
+
+ json_array_clear(j);
+ wrap.p = json->p;
+ for (i = 0; i < a->nelts; ++i) {
+ if (!cb) {
+ return APR_EINVAL;
+ }
+ wrap.j = json_string("");
+ if (APR_SUCCESS == (rv = cb(APR_ARRAY_IDX(a, i, void*), &wrap, json->p, baton))) {
+ json_array_append_new(j, wrap.j);
+ }
+ }
+ return rv;
+}
+
+int md_json_itera(md_json_itera_cb *cb, void *baton, md_json_t *json, ...)
+{
+ json_t *j;
+ va_list ap;
+ size_t index;
+ json_t *val;
+ md_json_t wrap;
+
+ va_start(ap, json);
+ j = jselect(json, ap);
+ va_end(ap);
+
+ if (!j || !json_is_array(j)) {
+ return 0;
+ }
+
+ wrap.p = json->p;
+ json_array_foreach(j, index, val) {
+ wrap.j = val;
+ if (!cb(baton, index, &wrap)) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+int md_json_iterkey(md_json_iterkey_cb *cb, void *baton, md_json_t *json, ...)
+{
+ json_t *j;
+ va_list ap;
+ const char *key;
+ json_t *val;
+ md_json_t wrap;
+
+ va_start(ap, json);
+ j = jselect(json, ap);
+ va_end(ap);
+
+ if (!j || !json_is_object(j)) {
+ return 0;
+ }
+
+ wrap.p = json->p;
+ json_object_foreach(j, key, val) {
+ wrap.j = val;
+ if (!cb(baton, key, &wrap)) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+/**************************************************************************************************/
+/* array strings */
+
+apr_status_t md_json_getsa(apr_array_header_t *a, const md_json_t *json, ...)
+{
+ json_t *j;
+ va_list ap;
+
+ va_start(ap, json);
+ j = jselect(json, ap);
+ va_end(ap);
+
+ if (j && json_is_array(j)) {
+ size_t index;
+ json_t *val;
+
+ json_array_foreach(j, index, val) {
+ if (json_is_string(val)) {
+ APR_ARRAY_PUSH(a, const char *) = json_string_value(val);
+ }
+ }
+ return APR_SUCCESS;
+ }
+ return APR_ENOENT;
+}
+
+apr_status_t md_json_dupsa(apr_array_header_t *a, apr_pool_t *p, md_json_t *json, ...)
+{
+ json_t *j;
+ va_list ap;
+
+ va_start(ap, json);
+ j = jselect(json, ap);
+ va_end(ap);
+
+ if (j && json_is_array(j)) {
+ size_t index;
+ json_t *val;
+
+ apr_array_clear(a);
+ json_array_foreach(j, index, val) {
+ if (json_is_string(val)) {
+ APR_ARRAY_PUSH(a, const char *) = apr_pstrdup(p, json_string_value(val));
+ }
+ }
+ return APR_SUCCESS;
+ }
+ return APR_ENOENT;
+}
+
+apr_status_t md_json_setsa(apr_array_header_t *a, md_json_t *json, ...)
+{
+ json_t *nj, *j;
+ va_list ap;
+ int i;
+
+ va_start(ap, json);
+ j = jselect(json, ap);
+ va_end(ap);
+
+ if (!j || !json_is_array(j)) {
+ const char *key;
+
+ va_start(ap, json);
+ j = jselect_parent(&key, 1, json, ap);
+ va_end(ap);
+
+ if (!key || !j || !json_is_object(j)) {
+ return APR_EINVAL;
+ }
+ nj = json_array();
+ json_object_set_new(j, key, nj);
+ j = nj;
+ }
+
+ json_array_clear(j);
+ for (i = 0; i < a->nelts; ++i) {
+ json_array_append_new(j, json_string(APR_ARRAY_IDX(a, i, const char*)));
+ }
+ return APR_SUCCESS;
+}
+
+/**************************************************************************************************/
+/* formatting, parsing */
+
+typedef struct {
+ const md_json_t *json;
+ md_json_fmt_t fmt;
+ const char *fname;
+ apr_file_t *f;
+} j_write_ctx;
+
+/* Convert from md_json_fmt_t to the Jansson json_dumpX flags. */
+static size_t fmt_to_flags(md_json_fmt_t fmt)
+{
+ /* NOTE: JSON_PRESERVE_ORDER is off by default before Jansson 2.8. It
+ * doesn't have any semantic effect on the protocol, but it does let the
+ * md_json_writeX unit tests run deterministically. */
+ return JSON_PRESERVE_ORDER |
+ ((fmt == MD_JSON_FMT_COMPACT) ? JSON_COMPACT : JSON_INDENT(2));
+}
+
+static int dump_cb(const char *buffer, size_t len, void *baton)
+{
+ apr_bucket_brigade *bb = baton;
+ apr_status_t rv;
+
+ rv = apr_brigade_write(bb, NULL, NULL, buffer, len);
+ return (rv == APR_SUCCESS)? 0 : -1;
+}
+
+apr_status_t md_json_writeb(const md_json_t *json, md_json_fmt_t fmt, apr_bucket_brigade *bb)
+{
+ int rv = json_dump_callback(json->j, dump_cb, bb, fmt_to_flags(fmt));
+ return rv? APR_EGENERAL : APR_SUCCESS;
+}
+
+static int chunk_cb(const char *buffer, size_t len, void *baton)
+{
+ apr_array_header_t *chunks = baton;
+ char *chunk;
+
+ if (len > 0) {
+ chunk = apr_palloc(chunks->pool, len+1);
+ memcpy(chunk, buffer, len);
+ chunk[len] = '\0';
+ APR_ARRAY_PUSH(chunks, const char*) = chunk;
+ }
+ return 0;
+}
+
+const char *md_json_writep(const md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt)
+{
+ apr_array_header_t *chunks;
+ int rv;
+
+ chunks = apr_array_make(p, 10, sizeof(char *));
+ rv = json_dump_callback(json->j, chunk_cb, chunks, fmt_to_flags(fmt));
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p,
+ "md_json_writep failed to dump JSON");
+ return NULL;
+ }
+
+ switch (chunks->nelts) {
+ case 0:
+ return "";
+ case 1:
+ return APR_ARRAY_IDX(chunks, 0, const char*);
+ default:
+ return apr_array_pstrcat(p, chunks, 0);
+ }
+}
+
+apr_status_t md_json_writef(const md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt, apr_file_t *f)
+{
+ apr_status_t rv;
+ const char *s;
+
+ if ((s = md_json_writep(json, p, fmt))) {
+ rv = apr_file_write_full(f, s, strlen(s), NULL);
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, json->p, "md_json_writef: error writing file");
+ }
+ }
+ else {
+ rv = APR_EINVAL;
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, json->p,
+ "md_json_writef: error dumping json (%s)", md_json_dump_state(json, p));
+ }
+ return rv;
+}
+
+apr_status_t md_json_fcreatex(const md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt,
+ const char *fpath, apr_fileperms_t perms)
+{
+ apr_status_t rv;
+ apr_file_t *f;
+
+ rv = md_util_fcreatex(&f, fpath, perms, p);
+ if (APR_SUCCESS == rv) {
+ rv = md_json_writef(json, p, fmt, f);
+ apr_file_close(f);
+ }
+ return rv;
+}
+
+static apr_status_t write_json(void *baton, apr_file_t *f, apr_pool_t *p)
+{
+ j_write_ctx *ctx = baton;
+ apr_status_t rv = md_json_writef(ctx->json, p, ctx->fmt, f);
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "freplace json in %s", ctx->fname);
+ }
+ return rv;
+}
+
+apr_status_t md_json_freplace(const md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt,
+ const char *fpath, apr_fileperms_t perms)
+{
+ j_write_ctx ctx;
+ ctx.json = json;
+ ctx.fmt = fmt;
+ ctx.fname = fpath;
+ return md_util_freplace(fpath, perms, p, write_json, &ctx);
+}
+
+apr_status_t md_json_readd(md_json_t **pjson, apr_pool_t *pool, const char *data, size_t data_len)
+{
+ json_error_t error;
+ json_t *j;
+
+ j = json_loadb(data, data_len, 0, &error);
+ if (!j) {
+ return APR_EINVAL;
+ }
+ *pjson = json_create(pool, j);
+ return APR_SUCCESS;
+}
+
+static size_t load_cb(void *data, size_t max_len, void *baton)
+{
+ apr_bucket_brigade *body = baton;
+ size_t blen, read_len = 0;
+ const char *bdata;
+ char *dest = data;
+ apr_bucket *b;
+ apr_status_t rv;
+
+ while (body && !APR_BRIGADE_EMPTY(body) && max_len > 0) {
+ b = APR_BRIGADE_FIRST(body);
+ if (APR_BUCKET_IS_METADATA(b)) {
+ if (APR_BUCKET_IS_EOS(b)) {
+ body = NULL;
+ }
+ }
+ else {
+ rv = apr_bucket_read(b, &bdata, &blen, APR_BLOCK_READ);
+ if (rv == APR_SUCCESS) {
+ if (blen > max_len) {
+ apr_bucket_split(b, max_len);
+ blen = max_len;
+ }
+ memcpy(dest, bdata, blen);
+ read_len += blen;
+ max_len -= blen;
+ dest += blen;
+ }
+ else {
+ body = NULL;
+ if (!APR_STATUS_IS_EOF(rv)) {
+ /* everything beside EOF is an error */
+ read_len = (size_t)-1;
+ }
+ }
+ }
+ APR_BUCKET_REMOVE(b);
+ apr_bucket_delete(b);
+ }
+
+ return read_len;
+}
+
+apr_status_t md_json_readb(md_json_t **pjson, apr_pool_t *pool, apr_bucket_brigade *bb)
+{
+ json_error_t error;
+ json_t *j;
+
+ j = json_load_callback(load_cb, bb, 0, &error);
+ if (j) {
+ *pjson = json_create(pool, j);
+ } else {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, pool,
+ "failed to load JSON file: %s (line %d:%d)",
+ error.text, error.line, error.column);
+ }
+ return (j && *pjson) ? APR_SUCCESS : APR_EINVAL;
+}
+
+static size_t load_file_cb(void *data, size_t max_len, void *baton)
+{
+ apr_file_t *f = baton;
+ apr_size_t len = max_len;
+ apr_status_t rv;
+
+ rv = apr_file_read(f, data, &len);
+ if (APR_SUCCESS == rv) {
+ return len;
+ }
+ else if (APR_EOF == rv) {
+ return 0;
+ }
+ return (size_t)-1;
+}
+
+apr_status_t md_json_readf(md_json_t **pjson, apr_pool_t *p, const char *fpath)
+{
+ apr_file_t *f;
+ json_t *j;
+ apr_status_t rv;
+ json_error_t error;
+
+ rv = apr_file_open(&f, fpath, APR_FOPEN_READ, 0, p);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ j = json_load_callback(load_file_cb, f, 0, &error);
+ if (j) {
+ *pjson = json_create(p, j);
+ }
+ else {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p,
+ "failed to load JSON file %s: %s (line %d:%d)",
+ fpath, error.text, error.line, error.column);
+ }
+
+ apr_file_close(f);
+ return (j && *pjson) ? APR_SUCCESS : APR_EINVAL;
+}
+
+/**************************************************************************************************/
+/* http get */
+
+apr_status_t md_json_read_http(md_json_t **pjson, apr_pool_t *pool, const md_http_response_t *res)
+{
+ apr_status_t rv = APR_ENOENT;
+ const char *ctype, *p;
+
+ *pjson = NULL;
+ if (!res->body) goto cleanup;
+ ctype = md_util_parse_ct(res->req->pool, apr_table_get(res->headers, "content-type"));
+ if (!ctype) goto cleanup;
+ p = ctype + strlen(ctype) +1;
+ if (!strcmp(p - sizeof("/json"), "/json")
+ || !strcmp(p - sizeof("+json"), "+json")) {
+ rv = md_json_readb(pjson, pool, res->body);
+ }
+cleanup:
+ return rv;
+}
+
+typedef struct {
+ apr_status_t rv;
+ apr_pool_t *pool;
+ md_json_t *json;
+} resp_data;
+
+static apr_status_t json_resp_cb(const md_http_response_t *res, void *data)
+{
+ resp_data *resp = data;
+ return md_json_read_http(&resp->json, resp->pool, res);
+}
+
+apr_status_t md_json_http_get(md_json_t **pjson, apr_pool_t *pool,
+ struct md_http_t *http, const char *url)
+{
+ apr_status_t rv;
+ resp_data resp;
+
+ memset(&resp, 0, sizeof(resp));
+ resp.pool = pool;
+
+ rv = md_http_GET_perform(http, url, NULL, json_resp_cb, &resp);
+
+ if (rv == APR_SUCCESS) {
+ *pjson = resp.json;
+ return resp.rv;
+ }
+ *pjson = NULL;
+ return rv;
+}
+
+
+apr_status_t md_json_copy_to(md_json_t *dest, const md_json_t *src, ...)
+{
+ json_t *j;
+ va_list ap;
+ apr_status_t rv = APR_SUCCESS;
+
+ va_start(ap, src);
+ j = jselect(src, ap);
+ va_end(ap);
+
+ if (j) {
+ va_start(ap, src);
+ rv = jselect_set(j, dest, ap);
+ va_end(ap);
+ }
+ return rv;
+}
+
+const char *md_json_dump_state(const md_json_t *json, apr_pool_t *p)
+{
+ if (!json) return "NULL";
+ return apr_psprintf(p, "%s, refc=%ld", md_json_type_name(json), (long)json->j->refcount);
+}
+
+apr_status_t md_json_set_timeperiod(const md_timeperiod_t *tp, md_json_t *json, ...)
+{
+ char ts[APR_RFC822_DATE_LEN];
+ json_t *jn, *j;
+ va_list ap;
+ const char *key;
+ apr_status_t rv;
+
+ if (tp && tp->start && tp->end) {
+ jn = json_object();
+ apr_rfc822_date(ts, tp->start);
+ json_object_set_new(jn, "from", json_string(ts));
+ apr_rfc822_date(ts, tp->end);
+ json_object_set_new(jn, "until", json_string(ts));
+
+ va_start(ap, json);
+ rv = jselect_set_new(jn, json, ap);
+ va_end(ap);
+ return rv;
+ }
+ else {
+ va_start(ap, json);
+ j = jselect_parent(&key, 0, json, ap);
+ va_end(ap);
+
+ if (key && j && json_is_object(j)) {
+ json_object_del(j, key);
+ }
+ return APR_SUCCESS;
+ }
+}
+
+apr_status_t md_json_get_timeperiod(md_timeperiod_t *tp, md_json_t *json, ...)
+{
+ json_t *j, *jts;
+ va_list ap;
+
+ va_start(ap, json);
+ j = jselect(json, ap);
+ va_end(ap);
+
+ memset(tp, 0, sizeof(*tp));
+ if (!j) goto not_found;
+ jts = json_object_get(j, "from");
+ if (!jts || !json_is_string(jts)) goto not_found;
+ tp->start = apr_date_parse_rfc(json_string_value(jts));
+ jts = json_object_get(j, "until");
+ if (!jts || !json_is_string(jts)) goto not_found;
+ tp->end = apr_date_parse_rfc(json_string_value(jts));
+ return APR_SUCCESS;
+not_found:
+ return APR_ENOENT;
+}
diff --git a/modules/md/md_json.h b/modules/md/md_json.h
new file mode 100644
index 0000000..50b8828
--- /dev/null
+++ b/modules/md/md_json.h
@@ -0,0 +1,157 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mod_md_md_json_h
+#define mod_md_md_json_h
+
+#include <apr_file_io.h>
+
+struct apr_bucket_brigade;
+struct apr_file_t;
+
+struct md_http_t;
+struct md_http_response_t;
+struct md_timeperiod_t;
+
+typedef struct md_json_t md_json_t;
+
+typedef enum {
+ MD_JSON_TYPE_OBJECT,
+ MD_JSON_TYPE_ARRAY,
+ MD_JSON_TYPE_STRING,
+ MD_JSON_TYPE_REAL,
+ MD_JSON_TYPE_INT,
+ MD_JSON_TYPE_BOOL,
+ MD_JSON_TYPE_NULL,
+} md_json_type_t;
+
+
+typedef enum {
+ MD_JSON_FMT_COMPACT,
+ MD_JSON_FMT_INDENT,
+} md_json_fmt_t;
+
+md_json_t *md_json_create(apr_pool_t *pool);
+void md_json_destroy(md_json_t *json);
+
+md_json_t *md_json_copy(apr_pool_t *pool, const md_json_t *json);
+md_json_t *md_json_clone(apr_pool_t *pool, const md_json_t *json);
+
+
+int md_json_has_key(const md_json_t *json, ...);
+int md_json_is(const md_json_type_t type, md_json_t *json, ...);
+
+/* boolean manipulation */
+int md_json_getb(const md_json_t *json, ...);
+apr_status_t md_json_setb(int value, md_json_t *json, ...);
+
+/* number manipulation */
+double md_json_getn(const md_json_t *json, ...);
+apr_status_t md_json_setn(double value, md_json_t *json, ...);
+
+/* long manipulation */
+long md_json_getl(const md_json_t *json, ...);
+apr_status_t md_json_setl(long value, md_json_t *json, ...);
+
+/* string manipulation */
+md_json_t *md_json_create_s(apr_pool_t *pool, const char *s);
+const char *md_json_gets(const md_json_t *json, ...);
+const char *md_json_dups(apr_pool_t *p, const md_json_t *json, ...);
+apr_status_t md_json_sets(const char *s, md_json_t *json, ...);
+
+/* timestamp manipulation */
+apr_time_t md_json_get_time(const md_json_t *json, ...);
+apr_status_t md_json_set_time(apr_time_t value, md_json_t *json, ...);
+
+/* json manipulation */
+md_json_t *md_json_getj(md_json_t *json, ...);
+md_json_t *md_json_dupj(apr_pool_t *p, const md_json_t *json, ...);
+const md_json_t *md_json_getcj(const md_json_t *json, ...);
+apr_status_t md_json_setj(const md_json_t *value, md_json_t *json, ...);
+apr_status_t md_json_addj(const md_json_t *value, md_json_t *json, ...);
+apr_status_t md_json_insertj(md_json_t *value, size_t index, md_json_t *json, ...);
+
+/* Array/Object manipulation */
+apr_status_t md_json_clr(md_json_t *json, ...);
+apr_status_t md_json_del(md_json_t *json, ...);
+
+/* Remove all array elements beyond max_elements */
+apr_size_t md_json_limita(size_t max_elements, md_json_t *json, ...);
+
+/* conversion function from and to json */
+typedef apr_status_t md_json_to_cb(void *value, md_json_t *json, apr_pool_t *p, void *baton);
+typedef apr_status_t md_json_from_cb(void **pvalue, md_json_t *json, apr_pool_t *p, void *baton);
+
+/* identity pass through from json to json */
+apr_status_t md_json_pass_to(void *value, md_json_t *json, apr_pool_t *p, void *baton);
+apr_status_t md_json_pass_from(void **pvalue, md_json_t *json, apr_pool_t *p, void *baton);
+
+/* conversions from json to json in specified pool */
+apr_status_t md_json_clone_to(void *value, md_json_t *json, apr_pool_t *p, void *baton);
+apr_status_t md_json_clone_from(void **pvalue, const md_json_t *json, apr_pool_t *p, void *baton);
+
+/* Manipulating/Iteration on generic Arrays */
+apr_status_t md_json_geta(apr_array_header_t *a, md_json_from_cb *cb,
+ void *baton, const md_json_t *json, ...);
+apr_status_t md_json_seta(apr_array_header_t *a, md_json_to_cb *cb,
+ void *baton, md_json_t *json, ...);
+
+/* Called on each array element, aborts iteration when returning 0 */
+typedef int md_json_itera_cb(void *baton, size_t index, md_json_t *json);
+int md_json_itera(md_json_itera_cb *cb, void *baton, md_json_t *json, ...);
+
+/* Called on each object key, aborts iteration when returning 0 */
+typedef int md_json_iterkey_cb(void *baton, const char* key, md_json_t *json);
+int md_json_iterkey(md_json_iterkey_cb *cb, void *baton, md_json_t *json, ...);
+
+/* Manipulating Object String values */
+apr_status_t md_json_gets_dict(apr_table_t *dict, const md_json_t *json, ...);
+apr_status_t md_json_sets_dict(apr_table_t *dict, md_json_t *json, ...);
+
+/* Manipulating String Arrays */
+apr_status_t md_json_getsa(apr_array_header_t *a, const md_json_t *json, ...);
+apr_status_t md_json_dupsa(apr_array_header_t *a, apr_pool_t *p, md_json_t *json, ...);
+apr_status_t md_json_setsa(apr_array_header_t *a, md_json_t *json, ...);
+
+/* serialization & parsing */
+apr_status_t md_json_writeb(const md_json_t *json, md_json_fmt_t fmt, struct apr_bucket_brigade *bb);
+const char *md_json_writep(const md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt);
+apr_status_t md_json_writef(const md_json_t *json, apr_pool_t *p,
+ md_json_fmt_t fmt, struct apr_file_t *f);
+apr_status_t md_json_fcreatex(const md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt,
+ const char *fpath, apr_fileperms_t perms);
+apr_status_t md_json_freplace(const md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt,
+ const char *fpath, apr_fileperms_t perms);
+
+apr_status_t md_json_readb(md_json_t **pjson, apr_pool_t *pool, struct apr_bucket_brigade *bb);
+apr_status_t md_json_readd(md_json_t **pjson, apr_pool_t *pool, const char *data, size_t data_len);
+apr_status_t md_json_readf(md_json_t **pjson, apr_pool_t *pool, const char *fpath);
+
+
+/* http retrieval */
+apr_status_t md_json_http_get(md_json_t **pjson, apr_pool_t *pool,
+ struct md_http_t *http, const char *url);
+apr_status_t md_json_read_http(md_json_t **pjson, apr_pool_t *pool,
+ const struct md_http_response_t *res);
+
+apr_status_t md_json_copy_to(md_json_t *dest, const md_json_t *src, ...);
+
+const char *md_json_dump_state(const md_json_t *json, apr_pool_t *p);
+
+apr_status_t md_json_set_timeperiod(const struct md_timeperiod_t *tp, md_json_t *json, ...);
+apr_status_t md_json_get_timeperiod(struct md_timeperiod_t *tp, md_json_t *json, ...);
+
+#endif /* md_json_h */
diff --git a/modules/md/md_jws.c b/modules/md/md_jws.c
new file mode 100644
index 0000000..c0e8c1b
--- /dev/null
+++ b/modules/md/md_jws.c
@@ -0,0 +1,148 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <apr_lib.h>
+#include <apr_strings.h>
+#include <apr_tables.h>
+#include <apr_buckets.h>
+
+#include "md_crypt.h"
+#include "md_json.h"
+#include "md_jws.h"
+#include "md_log.h"
+#include "md_util.h"
+
+apr_status_t md_jws_get_jwk(md_json_t **pjwk, apr_pool_t *p, struct md_pkey_t *pkey)
+{
+ md_json_t *jwk;
+
+ if (!pkey) return APR_EINVAL;
+
+ jwk = md_json_create(p);
+ md_json_sets(md_pkey_get_rsa_e64(pkey, p), jwk, "e", NULL);
+ md_json_sets("RSA", jwk, "kty", NULL);
+ md_json_sets(md_pkey_get_rsa_n64(pkey, p), jwk, "n", NULL);
+ *pjwk = jwk;
+ return APR_SUCCESS;
+}
+
+apr_status_t md_jws_sign(md_json_t **pmsg, apr_pool_t *p,
+ md_data_t *payload, md_json_t *prot_fields,
+ struct md_pkey_t *pkey, const char *key_id)
+{
+ md_json_t *msg, *jprotected, *jwk;
+ const char *prot64, *pay64, *sign64, *sign, *prot;
+ md_data_t data;
+ apr_status_t rv;
+
+ msg = md_json_create(p);
+ jprotected = md_json_clone(p, prot_fields);
+ md_json_sets("RS256", jprotected, "alg", NULL);
+ if (key_id) {
+ md_json_sets(key_id, jprotected, "kid", NULL);
+ }
+ else {
+ rv = md_jws_get_jwk(&jwk, p, pkey);
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, "get jwk");
+ goto cleanup;
+ }
+ md_json_setj(jwk, jprotected, "jwk", NULL);
+ }
+
+ prot = md_json_writep(jprotected, p, MD_JSON_FMT_COMPACT);
+ if (!prot) {
+ rv = APR_EINVAL;
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, "serialize protected");
+ goto cleanup;
+ }
+
+ md_data_init(&data, prot, strlen(prot));
+ prot64 = md_util_base64url_encode(&data, p);
+ md_json_sets(prot64, msg, "protected", NULL);
+
+ pay64 = md_util_base64url_encode(payload, p);
+ md_json_sets(pay64, msg, "payload", NULL);
+ sign = apr_psprintf(p, "%s.%s", prot64, pay64);
+
+ rv = md_crypt_sign64(&sign64, pkey, p, sign, strlen(sign));
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, "jwk signed message");
+ goto cleanup;
+ }
+ md_json_sets(sign64, msg, "signature", NULL);
+
+cleanup:
+ *pmsg = (APR_SUCCESS == rv)? msg : NULL;
+ return rv;
+}
+
+apr_status_t md_jws_pkey_thumb(const char **pthumb, apr_pool_t *p, struct md_pkey_t *pkey)
+{
+ const char *e64, *n64, *s;
+ md_data_t data;
+ apr_status_t rv;
+
+ e64 = md_pkey_get_rsa_e64(pkey, p);
+ n64 = md_pkey_get_rsa_n64(pkey, p);
+ if (!e64 || !n64) {
+ return APR_EINVAL;
+ }
+
+ /* whitespace and order is relevant, since we hand out a digest of this */
+ s = apr_psprintf(p, "{\"e\":\"%s\",\"kty\":\"RSA\",\"n\":\"%s\"}", e64, n64);
+ md_data_init_str(&data, s);
+ rv = md_crypt_sha256_digest64(pthumb, p, &data);
+ return rv;
+}
+
+apr_status_t md_jws_hmac(md_json_t **pmsg, apr_pool_t *p,
+ md_data_t *payload, md_json_t *prot_fields,
+ const md_data_t *hmac_key)
+{
+ md_json_t *msg, *jprotected;
+ const char *prot64, *pay64, *mac64, *sign, *prot;
+ md_data_t data;
+ apr_status_t rv;
+
+ msg = md_json_create(p);
+ jprotected = md_json_clone(p, prot_fields);
+ md_json_sets("HS256", jprotected, "alg", NULL);
+ prot = md_json_writep(jprotected, p, MD_JSON_FMT_COMPACT);
+ if (!prot) {
+ rv = APR_EINVAL;
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, "serialize protected");
+ goto cleanup;
+ }
+
+ md_data_init(&data, prot, strlen(prot));
+ prot64 = md_util_base64url_encode(&data, p);
+ md_json_sets(prot64, msg, "protected", NULL);
+
+ pay64 = md_util_base64url_encode(payload, p);
+ md_json_sets(pay64, msg, "payload", NULL);
+ sign = apr_psprintf(p, "%s.%s", prot64, pay64);
+
+ rv = md_crypt_hmac64(&mac64, hmac_key, p, sign, strlen(sign));
+ if (APR_SUCCESS != rv) {
+ goto cleanup;
+ }
+ md_json_sets(mac64, msg, "signature", NULL);
+
+cleanup:
+ *pmsg = (APR_SUCCESS == rv)? msg : NULL;
+ return rv;
+}
diff --git a/modules/md/md_jws.h b/modules/md/md_jws.h
new file mode 100644
index 0000000..466f2df
--- /dev/null
+++ b/modules/md/md_jws.h
@@ -0,0 +1,52 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mod_md_md_jws_h
+#define mod_md_md_jws_h
+
+struct apr_table_t;
+struct md_json_t;
+struct md_pkey_t;
+struct md_data_t;
+
+/**
+ * Get the JSON value of the 'jwk' field for the given key.
+ */
+apr_status_t md_jws_get_jwk(md_json_t **pjwk, apr_pool_t *p, struct md_pkey_t *pkey);
+
+/**
+ * Get the JWS key signed JSON message with given payload and protected fields, signed
+ * using the given key and optional key_id.
+ */
+apr_status_t md_jws_sign(md_json_t **pmsg, apr_pool_t *p,
+ struct md_data_t *payload, md_json_t *prot_fields,
+ struct md_pkey_t *pkey, const char *key_id);
+/**
+ * Get the 'Thumbprint' as defined in RFC8555 for the given key in
+ * base64 encoding.
+ */
+apr_status_t md_jws_pkey_thumb(const char **pthumb64, apr_pool_t *p, struct md_pkey_t *pkey);
+
+/**
+ * Get the JWS HS256 signed message for given payload and protected fields,
+ * using the base64 encoded MAC key.
+ */
+apr_status_t md_jws_hmac(md_json_t **pmsg, apr_pool_t *p,
+ struct md_data_t *payload, md_json_t *prot_fields,
+ const struct md_data_t *hmac_key);
+
+
+#endif /* md_jws_h */
diff --git a/modules/md/md_log.c b/modules/md/md_log.c
new file mode 100644
index 0000000..d236e0f
--- /dev/null
+++ b/modules/md/md_log.c
@@ -0,0 +1,78 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <apr_lib.h>
+#include <apr_strings.h>
+#include <apr_buckets.h>
+
+#include "md_log.h"
+
+#define LOG_BUFFER_LEN 1024
+
+static const char *level_names[] = {
+ "emergency",
+ "alert",
+ "crit",
+ "err",
+ "warning",
+ "notice",
+ "info",
+ "debug",
+ "trace1",
+ "trace2",
+ "trace3",
+ "trace4",
+ "trace5",
+ "trace6",
+ "trace7",
+ "trace8",
+};
+
+const char *md_log_level_name(md_log_level_t level)
+{
+ return level_names[level];
+}
+
+static md_log_print_cb *log_printv;
+static md_log_level_cb *log_level;
+static void *log_baton;
+
+void md_log_set(md_log_level_cb *level_cb, md_log_print_cb *print_cb, void *baton)
+{
+ log_printv = print_cb;
+ log_level = level_cb;
+ log_baton = baton;
+}
+
+int md_log_is_level(apr_pool_t *p, md_log_level_t level)
+{
+ if (!log_level) {
+ return 0;
+ }
+ return log_level(log_baton, p, level);
+}
+
+void md_log_perror(const char *file, int line, md_log_level_t level,
+ apr_status_t rv, apr_pool_t *p, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ if (log_printv) {
+ log_printv(file, line, level, rv, log_baton, p, fmt, ap);
+ }
+ va_end(ap);
+}
diff --git a/modules/md/md_log.h b/modules/md/md_log.h
new file mode 100644
index 0000000..19e688f
--- /dev/null
+++ b/modules/md/md_log.h
@@ -0,0 +1,60 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mod_md_md_log_h
+#define mod_md_md_log_h
+
+typedef enum {
+ MD_LOG_EMERG,
+ MD_LOG_ALERT,
+ MD_LOG_CRIT,
+ MD_LOG_ERR,
+ MD_LOG_WARNING,
+ MD_LOG_NOTICE,
+ MD_LOG_INFO,
+ MD_LOG_DEBUG,
+ MD_LOG_TRACE1,
+ MD_LOG_TRACE2,
+ MD_LOG_TRACE3,
+ MD_LOG_TRACE4,
+ MD_LOG_TRACE5,
+ MD_LOG_TRACE6,
+ MD_LOG_TRACE7,
+ MD_LOG_TRACE8,
+} md_log_level_t;
+
+#define MD_LOG_MARK __FILE__,__LINE__
+
+#ifndef APLOGNO
+#define APLOGNO(n) "AH" #n ": "
+#endif
+
+const char *md_log_level_name(md_log_level_t level);
+
+int md_log_is_level(apr_pool_t *p, md_log_level_t level);
+
+void md_log_perror(const char *file, int line, md_log_level_t level,
+ apr_status_t rv, apr_pool_t *p, const char *fmt, ...)
+ __attribute__((format(printf,6,7)));
+
+typedef int md_log_level_cb(void *baton, apr_pool_t *p, md_log_level_t level);
+
+typedef void md_log_print_cb(const char *file, int line, md_log_level_t level,
+ apr_status_t rv, void *baton, apr_pool_t *p, const char *fmt, va_list ap);
+
+void md_log_set(md_log_level_cb *level_cb, md_log_print_cb *print_cb, void *baton);
+
+#endif /* md_log_h */
diff --git a/modules/md/md_ocsp.c b/modules/md/md_ocsp.c
new file mode 100644
index 0000000..8cbf05b
--- /dev/null
+++ b/modules/md/md_ocsp.c
@@ -0,0 +1,1063 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <apr_lib.h>
+#include <apr_buckets.h>
+#include <apr_hash.h>
+#include <apr_time.h>
+#include <apr_date.h>
+#include <apr_strings.h>
+#include <apr_thread_mutex.h>
+
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/ocsp.h>
+#include <openssl/pem.h>
+#include <openssl/x509v3.h>
+
+#if defined(LIBRESSL_VERSION_NUMBER)
+/* Missing from LibreSSL */
+#define MD_USE_OPENSSL_PRE_1_1_API (LIBRESSL_VERSION_NUMBER < 0x2070000f)
+#else /* defined(LIBRESSL_VERSION_NUMBER) */
+#define MD_USE_OPENSSL_PRE_1_1_API (OPENSSL_VERSION_NUMBER < 0x10100000L)
+#endif
+
+#include "md.h"
+#include "md_crypt.h"
+#include "md_event.h"
+#include "md_json.h"
+#include "md_log.h"
+#include "md_http.h"
+#include "md_json.h"
+#include "md_result.h"
+#include "md_status.h"
+#include "md_store.h"
+#include "md_util.h"
+#include "md_ocsp.h"
+
+#define MD_OCSP_ID_LENGTH SHA_DIGEST_LENGTH
+
+struct md_ocsp_reg_t {
+ apr_pool_t *p;
+ md_store_t *store;
+ const char *user_agent;
+ const char *proxy_url;
+ apr_hash_t *id_by_external_id;
+ apr_hash_t *ostat_by_id;
+ apr_thread_mutex_t *mutex;
+ md_timeslice_t renew_window;
+ md_job_notify_cb *notify;
+ void *notify_ctx;
+ apr_time_t min_delay;
+};
+
+typedef struct md_ocsp_status_t md_ocsp_status_t;
+struct md_ocsp_status_t {
+ md_data_t id;
+ const char *hexid;
+ const char *hex_sha256;
+ OCSP_CERTID *certid;
+ const char *responder_url;
+
+ apr_time_t next_run; /* when the responder shall be asked again */
+ int errors; /* consecutive failed attempts */
+
+ md_ocsp_cert_stat_t resp_stat;
+ md_data_t resp_der;
+ md_timeperiod_t resp_valid;
+
+ md_data_t req_der;
+ OCSP_REQUEST *ocsp_req;
+ md_ocsp_reg_t *reg;
+
+ const char *md_name;
+ const char *file_name;
+
+ apr_time_t resp_mtime;
+ apr_time_t resp_last_check;
+};
+
+typedef struct md_ocsp_id_map_t md_ocsp_id_map_t;
+struct md_ocsp_id_map_t {
+ md_data_t id;
+ md_data_t external_id;
+};
+
+static void md_openssl_free(void *d)
+{
+ OPENSSL_free(d);
+}
+
+const char *md_ocsp_cert_stat_name(md_ocsp_cert_stat_t stat)
+{
+ switch (stat) {
+ case MD_OCSP_CERT_ST_GOOD: return "good";
+ case MD_OCSP_CERT_ST_REVOKED: return "revoked";
+ default: return "unknown";
+ }
+}
+
+md_ocsp_cert_stat_t md_ocsp_cert_stat_value(const char *name)
+{
+ if (name && !strcmp("good", name)) return MD_OCSP_CERT_ST_GOOD;
+ if (name && !strcmp("revoked", name)) return MD_OCSP_CERT_ST_REVOKED;
+ return MD_OCSP_CERT_ST_UNKNOWN;
+}
+
+apr_status_t md_ocsp_init_id(md_data_t *id, apr_pool_t *p, const md_cert_t *cert)
+{
+ unsigned char iddata[SHA_DIGEST_LENGTH];
+ X509 *x = md_cert_get_X509(cert);
+ unsigned int ulen = 0;
+
+ md_data_null(id);
+ if (X509_digest(x, EVP_sha1(), iddata, &ulen) != 1) {
+ return APR_EGENERAL;
+ }
+ md_data_assign_pcopy(id, (const char*)iddata, ulen, p);
+ return APR_SUCCESS;
+}
+
+static void ostat_req_cleanup(md_ocsp_status_t *ostat)
+{
+ if (ostat->ocsp_req) {
+ OCSP_REQUEST_free(ostat->ocsp_req);
+ ostat->ocsp_req = NULL;
+ }
+ md_data_clear(&ostat->req_der);
+}
+
+static int ostat_cleanup(void *ctx, const void *key, apr_ssize_t klen, const void *val)
+{
+ md_ocsp_reg_t *reg = ctx;
+ md_ocsp_status_t *ostat = (md_ocsp_status_t *)val;
+
+ (void)reg;
+ (void)key;
+ (void)klen;
+ ostat_req_cleanup(ostat);
+ if (ostat->certid) {
+ OCSP_CERTID_free(ostat->certid);
+ ostat->certid = NULL;
+ }
+ md_data_clear(&ostat->resp_der);
+ return 1;
+}
+
+static int ostat_should_renew(md_ocsp_status_t *ostat)
+{
+ md_timeperiod_t renewal;
+
+ renewal = md_timeperiod_slice_before_end(&ostat->resp_valid, &ostat->reg->renew_window);
+ return md_timeperiod_has_started(&renewal, apr_time_now());
+}
+
+static apr_status_t ostat_set(md_ocsp_status_t *ostat, md_ocsp_cert_stat_t stat,
+ md_data_t *der, md_timeperiod_t *valid, apr_time_t mtime)
+{
+ apr_status_t rv;
+
+ rv = md_data_assign_copy(&ostat->resp_der, der->data, der->len);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ ostat->resp_stat = stat;
+ ostat->resp_valid = *valid;
+ ostat->resp_mtime = mtime;
+
+ ostat->errors = 0;
+ ostat->next_run = md_timeperiod_slice_before_end(
+ &ostat->resp_valid, &ostat->reg->renew_window).start;
+
+cleanup:
+ return rv;
+}
+
+static apr_status_t ostat_from_json(md_ocsp_cert_stat_t *pstat,
+ md_data_t *resp_der, md_timeperiod_t *resp_valid,
+ md_json_t *json, apr_pool_t *p)
+{
+ const char *s;
+ md_timeperiod_t valid;
+ apr_status_t rv = APR_ENOENT;
+
+ memset(resp_der, 0, sizeof(*resp_der));
+ memset(resp_valid, 0, sizeof(*resp_valid));
+ s = md_json_dups(p, json, MD_KEY_VALID, MD_KEY_FROM, NULL);
+ if (s && *s) valid.start = apr_date_parse_rfc(s);
+ s = md_json_dups(p, json, MD_KEY_VALID, MD_KEY_UNTIL, NULL);
+ if (s && *s) valid.end = apr_date_parse_rfc(s);
+ s = md_json_dups(p, json, MD_KEY_RESPONSE, NULL);
+ if (!s || !*s) goto cleanup;
+ md_util_base64url_decode(resp_der, s, p);
+ *pstat = md_ocsp_cert_stat_value(md_json_gets(json, MD_KEY_STATUS, NULL));
+ *resp_valid = valid;
+ rv = APR_SUCCESS;
+cleanup:
+ return rv;
+}
+
+static void ostat_to_json(md_json_t *json, md_ocsp_cert_stat_t stat,
+ const md_data_t *resp_der, const md_timeperiod_t *resp_valid,
+ apr_pool_t *p)
+{
+ const char *s = NULL;
+
+ if (resp_der->len > 0) {
+ md_json_sets(md_util_base64url_encode(resp_der, p), json, MD_KEY_RESPONSE, NULL);
+ s = md_ocsp_cert_stat_name(stat);
+ if (s) md_json_sets(s, json, MD_KEY_STATUS, NULL);
+ md_json_set_timeperiod(resp_valid, json, MD_KEY_VALID, NULL);
+ }
+}
+
+static apr_status_t ocsp_status_refresh(md_ocsp_status_t *ostat, apr_pool_t *ptemp)
+{
+ md_store_t *store = ostat->reg->store;
+ md_json_t *jprops;
+ apr_time_t mtime;
+ apr_status_t rv = APR_EAGAIN;
+ md_data_t resp_der;
+ md_timeperiod_t resp_valid;
+ md_ocsp_cert_stat_t resp_stat;
+ /* Check if the store holds a newer response than the one we have */
+ mtime = md_store_get_modified(store, MD_SG_OCSP, ostat->md_name, ostat->file_name, ptemp);
+ if (mtime <= ostat->resp_mtime) goto cleanup;
+ rv = md_store_load_json(store, MD_SG_OCSP, ostat->md_name, ostat->file_name, &jprops, ptemp);
+ if (APR_SUCCESS != rv) goto cleanup;
+ rv = ostat_from_json(&resp_stat, &resp_der, &resp_valid, jprops, ptemp);
+ if (APR_SUCCESS != rv) goto cleanup;
+ rv = ostat_set(ostat, resp_stat, &resp_der, &resp_valid, mtime);
+ if (APR_SUCCESS != rv) goto cleanup;
+cleanup:
+ return rv;
+}
+
+
+static apr_status_t ocsp_status_save(md_ocsp_cert_stat_t stat, const md_data_t *resp_der,
+ const md_timeperiod_t *resp_valid,
+ md_ocsp_status_t *ostat, apr_pool_t *ptemp)
+{
+ md_store_t *store = ostat->reg->store;
+ md_json_t *jprops;
+ apr_time_t mtime;
+ apr_status_t rv;
+
+ jprops = md_json_create(ptemp);
+ ostat_to_json(jprops, stat, resp_der, resp_valid, ptemp);
+ rv = md_store_save_json(store, ptemp, MD_SG_OCSP, ostat->md_name, ostat->file_name, jprops, 0);
+ if (APR_SUCCESS != rv) goto cleanup;
+ mtime = md_store_get_modified(store, MD_SG_OCSP, ostat->md_name, ostat->file_name, ptemp);
+ if (mtime) ostat->resp_mtime = mtime;
+cleanup:
+ return rv;
+}
+
+static apr_status_t ocsp_reg_cleanup(void *data)
+{
+ md_ocsp_reg_t *reg = data;
+
+ /* free all OpenSSL structures that we hold */
+ apr_hash_do(ostat_cleanup, reg, reg->ostat_by_id);
+ return APR_SUCCESS;
+}
+
+apr_status_t md_ocsp_reg_make(md_ocsp_reg_t **preg, apr_pool_t *p, md_store_t *store,
+ const md_timeslice_t *renew_window,
+ const char *user_agent, const char *proxy_url,
+ apr_time_t min_delay)
+{
+ md_ocsp_reg_t *reg;
+ apr_status_t rv = APR_SUCCESS;
+
+ reg = apr_palloc(p, sizeof(*reg));
+ if (!reg) {
+ rv = APR_ENOMEM;
+ goto cleanup;
+ }
+ reg->p = p;
+ reg->store = store;
+ reg->user_agent = user_agent;
+ reg->proxy_url = proxy_url;
+ reg->id_by_external_id = apr_hash_make(p);
+ reg->ostat_by_id = apr_hash_make(p);
+ reg->renew_window = *renew_window;
+ reg->min_delay = min_delay;
+
+ rv = apr_thread_mutex_create(&reg->mutex, APR_THREAD_MUTEX_NESTED, p);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ apr_pool_cleanup_register(p, reg, ocsp_reg_cleanup, apr_pool_cleanup_null);
+cleanup:
+ *preg = (APR_SUCCESS == rv)? reg : NULL;
+ return rv;
+}
+
+apr_status_t md_ocsp_prime(md_ocsp_reg_t *reg, const char *ext_id, apr_size_t ext_id_len,
+ md_cert_t *cert, md_cert_t *issuer, const md_t *md)
+{
+ md_ocsp_status_t *ostat;
+ const char *name;
+ md_data_t id;
+ apr_status_t rv = APR_SUCCESS;
+
+ /* Called during post_config. no mutex protection needed */
+ name = md? md->name : MD_OTHER;
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, reg->p,
+ "md[%s]: priming OCSP status", name);
+
+ rv = md_ocsp_init_id(&id, reg->p, cert);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ ostat = apr_hash_get(reg->ostat_by_id, id.data, (apr_ssize_t)id.len);
+ if (ostat) goto cleanup; /* already seen it, cert is used in >1 server_rec */
+
+ ostat = apr_pcalloc(reg->p, sizeof(*ostat));
+ ostat->id = id;
+ ostat->reg = reg;
+ ostat->md_name = name;
+ md_data_to_hex(&ostat->hexid, 0, reg->p, &ostat->id);
+ ostat->file_name = apr_psprintf(reg->p, "ocsp-%s.json", ostat->hexid);
+ rv = md_cert_to_sha256_fingerprint(&ostat->hex_sha256, cert, reg->p);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p,
+ "md[%s]: getting ocsp responder from cert", name);
+ rv = md_cert_get_ocsp_responder_url(&ostat->responder_url, reg->p, cert);
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, reg->p,
+ "md[%s]: certificate with serial %s has no OCSP responder URL",
+ name, md_cert_get_serial_number(cert, reg->p));
+ goto cleanup;
+ }
+
+ ostat->certid = OCSP_cert_to_id(NULL, md_cert_get_X509(cert), md_cert_get_X509(issuer));
+ if (!ostat->certid) {
+ rv = APR_EGENERAL;
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, reg->p,
+ "md[%s]: unable to create OCSP certid for certificate with serial %s",
+ name, md_cert_get_serial_number(cert, reg->p));
+ goto cleanup;
+ }
+
+ /* See, if we have something in store */
+ ocsp_status_refresh(ostat, reg->p);
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, reg->p,
+ "md[%s]: adding ocsp info (responder=%s)",
+ name, ostat->responder_url);
+ apr_hash_set(reg->ostat_by_id, ostat->id.data, (apr_ssize_t)ostat->id.len, ostat);
+ if (ext_id) {
+ md_ocsp_id_map_t *id_map;
+
+ id_map = apr_pcalloc(reg->p, sizeof(*id_map));
+ id_map->id = id;
+ md_data_assign_pcopy(&id_map->external_id, ext_id, ext_id_len, reg->p);
+ /* check for collision/uniqness? */
+ apr_hash_set(reg->id_by_external_id, id_map->external_id.data,
+ (apr_ssize_t)id_map->external_id.len, id_map);
+ }
+ rv = APR_SUCCESS;
+cleanup:
+ return rv;
+}
+
+apr_status_t md_ocsp_get_status(md_ocsp_copy_der *cb, void *userdata, md_ocsp_reg_t *reg,
+ const char *ext_id, apr_size_t ext_id_len,
+ apr_pool_t *p, const md_t *md)
+{
+ md_ocsp_status_t *ostat;
+ const char *name;
+ apr_status_t rv = APR_SUCCESS;
+ md_ocsp_id_map_t *id_map;
+ const char *id;
+ apr_size_t id_len;
+ int locked = 0;
+
+ (void)p;
+ (void)md;
+ name = md? md->name : MD_OTHER;
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p,
+ "md[%s]: OCSP, get_status", name);
+
+ id_map = apr_hash_get(reg->id_by_external_id, ext_id, (apr_ssize_t)ext_id_len);
+ id = id_map? id_map->id.data : ext_id;
+ id_len = id_map? id_map->id.len : ext_id_len;
+ ostat = apr_hash_get(reg->ostat_by_id, id, (apr_ssize_t)id_len);
+ if (!ostat) {
+ rv = APR_ENOENT;
+ goto cleanup;
+ }
+
+ /* While the ostat instance itself always exists, the response data it holds
+ * may vary over time and we need locked access to make a copy. */
+ apr_thread_mutex_lock(reg->mutex);
+ locked = 1;
+
+ if (ostat->resp_der.len <= 0) {
+ /* No response known, check store for new response. */
+ ocsp_status_refresh(ostat, p);
+ if (ostat->resp_der.len <= 0) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p,
+ "md[%s]: OCSP, no response available", name);
+ cb(NULL, 0, userdata);
+ goto cleanup;
+ }
+ }
+ /* We have a response */
+ if (ostat_should_renew(ostat)) {
+ /* But it is up for renewal. A watchdog should be busy with
+ * retrieving a new one. In case of outages, this might take
+ * a while, however. Pace the frequency of checks with the
+ * urgency of a new response based on the remaining time. */
+ long secs = (long)apr_time_sec(md_timeperiod_remaining(&ostat->resp_valid, apr_time_now()));
+ apr_time_t waiting_time;
+
+ /* every hour, every minute, every second */
+ waiting_time = ((secs >= MD_SECS_PER_DAY)?
+ apr_time_from_sec(60 * 60) : ((secs >= 60)?
+ apr_time_from_sec(60) : apr_time_from_sec(1)));
+ if ((apr_time_now() - ostat->resp_last_check) >= waiting_time) {
+ ostat->resp_last_check = apr_time_now();
+ ocsp_status_refresh(ostat, p);
+ }
+ }
+
+ cb((const unsigned char*)ostat->resp_der.data, ostat->resp_der.len, userdata);
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p,
+ "md[%s]: OCSP, provided %ld bytes of response",
+ name, (long)ostat->resp_der.len);
+cleanup:
+ if (locked) apr_thread_mutex_unlock(reg->mutex);
+ return rv;
+}
+
+static void ocsp_get_meta(md_ocsp_cert_stat_t *pstat, md_timeperiod_t *pvalid,
+ md_ocsp_reg_t *reg, md_ocsp_status_t *ostat, apr_pool_t *p)
+{
+ apr_thread_mutex_lock(reg->mutex);
+ if (ostat->resp_der.len <= 0) {
+ /* No response known, check the store if out watchdog retrieved one
+ * in the meantime. */
+ ocsp_status_refresh(ostat, p);
+ }
+ *pvalid = ostat->resp_valid;
+ *pstat = ostat->resp_stat;
+ apr_thread_mutex_unlock(reg->mutex);
+}
+
+apr_status_t md_ocsp_get_meta(md_ocsp_cert_stat_t *pstat, md_timeperiod_t *pvalid,
+ md_ocsp_reg_t *reg, const md_cert_t *cert,
+ apr_pool_t *p, const md_t *md)
+{
+ md_ocsp_status_t *ostat;
+ const char *name;
+ apr_status_t rv;
+ md_timeperiod_t valid;
+ md_ocsp_cert_stat_t stat;
+ md_data_t id;
+
+ (void)p;
+ (void)md;
+ name = md? md->name : MD_OTHER;
+ memset(&valid, 0, sizeof(valid));
+ stat = MD_OCSP_CERT_ST_UNKNOWN;
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p,
+ "md[%s]: OCSP, get_status", name);
+
+ rv = md_ocsp_init_id(&id, p, cert);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ ostat = apr_hash_get(reg->ostat_by_id, id.data, (apr_ssize_t)id.len);
+ if (!ostat) {
+ rv = APR_ENOENT;
+ goto cleanup;
+ }
+ ocsp_get_meta(&stat, &valid, reg, ostat, p);
+cleanup:
+ *pstat = stat;
+ *pvalid = valid;
+ return rv;
+}
+
+apr_size_t md_ocsp_count(md_ocsp_reg_t *reg)
+{
+ return apr_hash_count(reg->ostat_by_id);
+}
+
+static const char *certid_as_hex(const OCSP_CERTID *certid, apr_pool_t *p)
+{
+ md_data_t der;
+ const char *hex;
+
+ memset(&der, 0, sizeof(der));
+ der.len = (apr_size_t)i2d_OCSP_CERTID((OCSP_CERTID*)certid, (unsigned char**)&der.data);
+ der.free_data = md_openssl_free;
+ md_data_to_hex(&hex, 0, p, &der);
+ md_data_clear(&der);
+ return hex;
+}
+
+static const char *certid_summary(const OCSP_CERTID *certid, apr_pool_t *p)
+{
+ const char *serial, *issuer, *key, *s;
+ ASN1_INTEGER *aserial;
+ ASN1_OCTET_STRING *aname_hash, *akey_hash;
+ ASN1_OBJECT *amd_nid;
+ BIGNUM *bn;
+ md_data_t data;
+
+ serial = issuer = key = "???";
+ OCSP_id_get0_info(&aname_hash, &amd_nid, &akey_hash, &aserial, (OCSP_CERTID*)certid);
+ if (aname_hash) {
+ data.len = (apr_size_t)aname_hash->length;
+ data.data = (const char*)aname_hash->data;
+ md_data_to_hex(&issuer, 0, p, &data);
+ }
+ if (akey_hash) {
+ data.len = (apr_size_t)akey_hash->length;
+ data.data = (const char*)akey_hash->data;
+ md_data_to_hex(&key, 0, p, &data);
+ }
+ if (aserial) {
+ bn = ASN1_INTEGER_to_BN(aserial, NULL);
+ s = BN_bn2hex(bn);
+ serial = apr_pstrdup(p, s);
+ OPENSSL_free((void*)bn);
+ OPENSSL_free((void*)s);
+ }
+ return apr_psprintf(p, "certid[der=%s, issuer=%s, key=%s, serial=%s]",
+ certid_as_hex(certid, p), issuer, key, serial);
+}
+
+static const char *certstatus_string(int status)
+{
+ switch (status) {
+ case V_OCSP_CERTSTATUS_GOOD: return "good";
+ case V_OCSP_CERTSTATUS_REVOKED: return "revoked";
+ case V_OCSP_CERTSTATUS_UNKNOWN: return "unknown";
+ default: return "???";
+ }
+
+}
+
+static const char *single_resp_summary(OCSP_SINGLERESP* resp, apr_pool_t *p)
+{
+ const OCSP_CERTID *certid;
+ int status, reason = 0;
+ ASN1_GENERALIZEDTIME *bup = NULL, *bnextup = NULL;
+ md_timeperiod_t valid;
+
+#if MD_USE_OPENSSL_PRE_1_1_API
+ certid = resp->certId;
+#else
+ certid = OCSP_SINGLERESP_get0_id(resp);
+#endif
+ status = OCSP_single_get0_status(resp, &reason, NULL, &bup, &bnextup);
+ valid.start = bup? md_asn1_generalized_time_get(bup) : apr_time_now();
+ valid.end = md_asn1_generalized_time_get(bnextup);
+
+ return apr_psprintf(p, "ocsp-single-resp[%s, status=%s, reason=%d, valid=%s]",
+ certid_summary(certid, p),
+ certstatus_string(status), reason,
+ md_timeperiod_print(p, &valid));
+}
+
+typedef struct {
+ apr_pool_t *p;
+ md_ocsp_status_t *ostat;
+ md_result_t *result;
+ md_job_t *job;
+} md_ocsp_update_t;
+
+static apr_status_t ostat_on_resp(const md_http_response_t *resp, void *baton)
+{
+ md_ocsp_update_t *update = baton;
+ md_ocsp_status_t *ostat = update->ostat;
+ md_http_request_t *req = resp->req;
+ OCSP_RESPONSE *ocsp_resp = NULL;
+ OCSP_BASICRESP *basic_resp = NULL;
+ OCSP_SINGLERESP *single_resp;
+ apr_status_t rv = APR_SUCCESS;
+ int n, breason = 0, bstatus;
+ ASN1_GENERALIZEDTIME *bup = NULL, *bnextup = NULL;
+ md_data_t der, new_der;
+ md_timeperiod_t valid;
+ md_ocsp_cert_stat_t nstat;
+
+ der.data = new_der.data = NULL;
+ der.len = new_der.len = 0;
+
+ md_result_activity_printf(update->result, "status of certid %s, reading response",
+ ostat->hexid);
+ if (APR_SUCCESS != (rv = apr_brigade_pflatten(resp->body, (char**)&der.data,
+ &der.len, req->pool))) {
+ goto cleanup;
+ }
+ if (NULL == (ocsp_resp = d2i_OCSP_RESPONSE(NULL, (const unsigned char**)&der.data,
+ (long)der.len))) {
+ rv = APR_EINVAL;
+
+ md_result_set(update->result, rv,
+ apr_psprintf(req->pool, "req[%d] response body does not parse as "
+ "OCSP response, status=%d, body brigade length=%ld",
+ resp->req->id, resp->status, (long)der.len));
+ md_result_log(update->result, MD_LOG_DEBUG);
+ goto cleanup;
+ }
+ /* got a response! but what does it say? */
+ n = OCSP_response_status(ocsp_resp);
+ if (OCSP_RESPONSE_STATUS_SUCCESSFUL != n) {
+ rv = APR_EINVAL;
+ md_result_printf(update->result, rv, "OCSP response status is, unsuccessfully, %d", n);
+ md_result_log(update->result, MD_LOG_DEBUG);
+ goto cleanup;
+ }
+ basic_resp = OCSP_response_get1_basic(ocsp_resp);
+ if (!basic_resp) {
+ rv = APR_EINVAL;
+ md_result_set(update->result, rv, "OCSP response has no basicresponse");
+ md_result_log(update->result, MD_LOG_DEBUG);
+ goto cleanup;
+ }
+ /* The notion of nonce enabled freshness in OCSP responses, e.g. that the response
+ * contains the signed nonce we sent to the responder, does not scale well. Responders
+ * like to return cached response bytes and therefore do not add a nonce to it.
+ * So, in reality, we can only detect a mismatch when present and otherwise have
+ * to accept it. */
+ switch ((n = OCSP_check_nonce(ostat->ocsp_req, basic_resp))) {
+ case 1:
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, req->pool,
+ "req[%d]: OCSP response nonce does match", req->id);
+ break;
+ case 0:
+ rv = APR_EINVAL;
+ md_result_printf(update->result, rv, "OCSP nonce mismatch in response", n);
+ md_result_log(update->result, MD_LOG_WARNING);
+ goto cleanup;
+
+ case -1:
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, req->pool,
+ "req[%d]: OCSP response did not return the nonce", req->id);
+ break;
+ default:
+ break;
+ }
+
+ if (!OCSP_resp_find_status(basic_resp, ostat->certid, &bstatus,
+ &breason, NULL, &bup, &bnextup)) {
+ const char *prefix, *slist = "", *sep = "";
+ int i;
+
+ rv = APR_EINVAL;
+ prefix = apr_psprintf(req->pool, "OCSP response, no matching status reported for %s",
+ certid_summary(ostat->certid, req->pool));
+ for (i = 0; i < OCSP_resp_count(basic_resp); ++i) {
+ single_resp = OCSP_resp_get0(basic_resp, i);
+ slist = apr_psprintf(req->pool, "%s%s%s", slist, sep,
+ single_resp_summary(single_resp, req->pool));
+ sep = ", ";
+ }
+ md_result_printf(update->result, rv, "%s, status list [%s]", prefix, slist);
+ md_result_log(update->result, MD_LOG_DEBUG);
+ goto cleanup;
+ }
+ if (V_OCSP_CERTSTATUS_UNKNOWN == bstatus) {
+ rv = APR_ENOENT;
+ md_result_set(update->result, rv, "OCSP basicresponse says cert is unknown");
+ md_result_log(update->result, MD_LOG_DEBUG);
+ goto cleanup;
+ }
+ if (!bnextup) {
+ rv = APR_EINVAL;
+ md_result_set(update->result, rv, "OCSP basicresponse reports not valid dates");
+ md_result_log(update->result, MD_LOG_DEBUG);
+ goto cleanup;
+ }
+
+ /* Coming here, we have a response for our certid and it is either GOOD
+ * or REVOKED. Both cases we want to remember and use in stapling. */
+ n = i2d_OCSP_RESPONSE(ocsp_resp, (unsigned char**)&new_der.data);
+ if (n <= 0) {
+ rv = APR_EGENERAL;
+ md_result_set(update->result, rv, "error DER encoding OCSP response");
+ md_result_log(update->result, MD_LOG_WARNING);
+ goto cleanup;
+ }
+ new_der.len = (apr_size_t)n;
+ new_der.free_data = md_openssl_free;
+ nstat = (bstatus == V_OCSP_CERTSTATUS_GOOD)? MD_OCSP_CERT_ST_GOOD : MD_OCSP_CERT_ST_REVOKED;
+ valid.start = bup? md_asn1_generalized_time_get(bup) : apr_time_now();
+ valid.end = md_asn1_generalized_time_get(bnextup);
+
+ /* First, update the instance with a copy */
+ apr_thread_mutex_lock(ostat->reg->mutex);
+ ostat_set(ostat, nstat, &new_der, &valid, apr_time_now());
+ apr_thread_mutex_unlock(ostat->reg->mutex);
+
+ /* Next, save the original response */
+ rv = ocsp_status_save(nstat, &new_der, &valid, ostat, req->pool);
+ if (APR_SUCCESS != rv) {
+ md_result_set(update->result, rv, "error saving OCSP status");
+ md_result_log(update->result, MD_LOG_ERR);
+ goto cleanup;
+ }
+
+ md_result_printf(update->result, rv, "certificate status is %s, status valid %s",
+ (nstat == MD_OCSP_CERT_ST_GOOD)? "GOOD" : "REVOKED",
+ md_timeperiod_print(req->pool, &ostat->resp_valid));
+ md_result_log(update->result, MD_LOG_DEBUG);
+
+cleanup:
+ md_data_clear(&new_der);
+ if (basic_resp) OCSP_BASICRESP_free(basic_resp);
+ if (ocsp_resp) OCSP_RESPONSE_free(ocsp_resp);
+ return rv;
+}
+
+static apr_status_t ostat_on_req_status(const md_http_request_t *req, apr_status_t status,
+ void *baton)
+{
+ md_ocsp_update_t *update = baton;
+ md_ocsp_status_t *ostat = update->ostat;
+
+ (void)req;
+ md_job_end_run(update->job, update->result);
+ if (APR_SUCCESS != status) {
+ ++ostat->errors;
+ ostat->next_run = apr_time_now() + md_job_delay_on_errors(update->job, ostat->errors, NULL);
+ md_result_printf(update->result, status, "OCSP status update failed (%d. time)",
+ ostat->errors);
+ md_result_log(update->result, MD_LOG_DEBUG);
+ md_job_log_append(update->job, "ocsp-error",
+ update->result->problem, update->result->detail);
+ md_event_holler("ocsp-errored", update->job->mdomain, update->job, update->result, update->p);
+ goto cleanup;
+ }
+ md_event_holler("ocsp-renewed", update->job->mdomain, update->job, update->result, update->p);
+
+cleanup:
+ md_job_save(update->job, update->result, update->p);
+ ostat_req_cleanup(ostat);
+ return APR_SUCCESS;
+}
+
+typedef struct {
+ md_ocsp_reg_t *reg;
+ apr_array_header_t *todos;
+ apr_pool_t *ptemp;
+ apr_time_t time;
+ int max_parallel;
+} md_ocsp_todo_ctx_t;
+
+static apr_status_t ocsp_req_make(OCSP_REQUEST **pocsp_req, OCSP_CERTID *certid)
+{
+ OCSP_REQUEST *req = NULL;
+ OCSP_CERTID *id_copy = NULL;
+ apr_status_t rv = APR_ENOMEM;
+
+ req = OCSP_REQUEST_new();
+ if (!req) goto cleanup;
+ id_copy = OCSP_CERTID_dup(certid);
+ if (!id_copy) goto cleanup;
+ if (!OCSP_request_add0_id(req, id_copy)) goto cleanup;
+ id_copy = NULL;
+ OCSP_request_add1_nonce(req, 0, -1);
+ rv = APR_SUCCESS;
+cleanup:
+ if (id_copy) OCSP_CERTID_free(id_copy);
+ if (APR_SUCCESS != rv && req) {
+ OCSP_REQUEST_free(req);
+ req = NULL;
+ }
+ *pocsp_req = req;
+ return rv;
+}
+
+static apr_status_t ocsp_req_assign_der(md_data_t *d, OCSP_REQUEST *ocsp_req)
+{
+ int len;
+
+ md_data_clear(d);
+ len = i2d_OCSP_REQUEST(ocsp_req, (unsigned char**)&d->data);
+ if (len < 0) return APR_ENOMEM;
+ d->len = (apr_size_t)len;
+ d->free_data = md_openssl_free;
+ return APR_SUCCESS;
+}
+
+static apr_status_t next_todo(md_http_request_t **preq, void *baton,
+ md_http_t *http, int in_flight)
+{
+ md_ocsp_todo_ctx_t *ctx = baton;
+ md_ocsp_update_t *update, **pupdate;
+ md_ocsp_status_t *ostat;
+ md_http_request_t *req = NULL;
+ apr_status_t rv = APR_ENOENT;
+ apr_table_t *headers;
+
+ if (in_flight < ctx->max_parallel) {
+ pupdate = apr_array_pop(ctx->todos);
+ if (pupdate) {
+ update = *pupdate;
+ ostat = update->ostat;
+
+ update->job = md_ocsp_job_make(ctx->reg, ostat->md_name, update->p);
+ md_job_load(update->job);
+ md_job_start_run(update->job, update->result, ctx->reg->store);
+
+ if (!ostat->ocsp_req) {
+ rv = ocsp_req_make(&ostat->ocsp_req, ostat->certid);
+ if (APR_SUCCESS != rv) goto cleanup;
+ }
+ if (0 == ostat->req_der.len) {
+ rv = ocsp_req_assign_der(&ostat->req_der, ostat->ocsp_req);
+ if (APR_SUCCESS != rv) goto cleanup;
+ }
+ md_result_activity_printf(update->result, "status of certid %s, "
+ "contacting %s", ostat->hexid, ostat->responder_url);
+ headers = apr_table_make(ctx->ptemp, 5);
+ apr_table_set(headers, "Expect", "");
+ rv = md_http_POSTd_create(&req, http, ostat->responder_url, headers,
+ "application/ocsp-request", &ostat->req_der);
+ if (APR_SUCCESS != rv) goto cleanup;
+ md_http_set_on_status_cb(req, ostat_on_req_status, update);
+ md_http_set_on_response_cb(req, ostat_on_resp, update);
+ rv = APR_SUCCESS;
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, req->pool,
+ "scheduling OCSP request[%d] for %s, %d request in flight",
+ req->id, ostat->md_name, in_flight);
+ }
+ }
+cleanup:
+ *preq = (APR_SUCCESS == rv)? req : NULL;
+ return rv;
+}
+
+static int select_updates(void *baton, const void *key, apr_ssize_t klen, const void *val)
+{
+ md_ocsp_todo_ctx_t *ctx = baton;
+ md_ocsp_status_t *ostat = (md_ocsp_status_t *)val;
+ md_ocsp_update_t *update;
+
+ (void)key;
+ (void)klen;
+ if (ostat->next_run <= ctx->time) {
+ update = apr_pcalloc(ctx->ptemp, sizeof(*update));
+ update->p = ctx->ptemp;
+ update->ostat = ostat;
+ update->result = md_result_md_make(update->p, ostat->md_name);
+ update->job = NULL;
+ APR_ARRAY_PUSH(ctx->todos, md_ocsp_update_t*) = update;
+ }
+ return 1;
+}
+
+static int select_next_run(void *baton, const void *key, apr_ssize_t klen, const void *val)
+{
+ md_ocsp_todo_ctx_t *ctx = baton;
+ md_ocsp_status_t *ostat = (md_ocsp_status_t *)val;
+
+ (void)key;
+ (void)klen;
+ if (ostat->next_run < ctx->time && ostat->next_run > apr_time_now()) {
+ ctx->time = ostat->next_run;
+ }
+ return 1;
+}
+
+void md_ocsp_renew(md_ocsp_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp, apr_time_t *pnext_run)
+{
+ md_ocsp_todo_ctx_t ctx;
+ md_http_t *http;
+ apr_status_t rv = APR_SUCCESS;
+
+ (void)p;
+ (void)pnext_run;
+
+ ctx.reg = reg;
+ ctx.ptemp = ptemp;
+ ctx.todos = apr_array_make(ptemp, (int)md_ocsp_count(reg), sizeof(md_ocsp_status_t*));
+ ctx.max_parallel = 6; /* the magic number in HTTP */
+
+ /* Create a list of update tasks that are needed now or in the next minute */
+ ctx.time = apr_time_now() + apr_time_from_sec(60);;
+ apr_hash_do(select_updates, &ctx, reg->ostat_by_id);
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p,
+ "OCSP status updates due: %d", ctx.todos->nelts);
+ if (!ctx.todos->nelts) goto cleanup;
+
+ rv = md_http_create(&http, ptemp, reg->user_agent, reg->proxy_url);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ rv = md_http_multi_perform(http, next_todo, &ctx);
+
+cleanup:
+ /* When do we need to run next? *pnext_run contains the planned schedule from
+ * the watchdog. We can make that earlier if we need it. */
+ ctx.time = *pnext_run;
+ apr_hash_do(select_next_run, &ctx, reg->ostat_by_id);
+
+ /* sanity check and return */
+ if (ctx.time < apr_time_now()) ctx.time = apr_time_now() + apr_time_from_sec(1);
+ *pnext_run = ctx.time;
+
+ if (APR_SUCCESS != rv && APR_ENOENT != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "ocsp_renew done");
+ }
+ return;
+}
+
+apr_status_t md_ocsp_remove_responses_older_than(md_ocsp_reg_t *reg, apr_pool_t *p,
+ apr_time_t timestamp)
+{
+ return md_store_remove_not_modified_since(reg->store, p, timestamp,
+ MD_SG_OCSP, "*", "ocsp*.json");
+}
+
+typedef struct {
+ apr_pool_t *p;
+ md_ocsp_reg_t *reg;
+ int good;
+ int revoked;
+ int unknown;
+} ocsp_summary_ctx_t;
+
+static int add_to_summary(void *baton, const void *key, apr_ssize_t klen, const void *val)
+{
+ ocsp_summary_ctx_t *ctx = baton;
+ md_ocsp_status_t *ostat = (md_ocsp_status_t *)val;
+ md_ocsp_cert_stat_t stat;
+ md_timeperiod_t valid;
+
+ (void)key;
+ (void)klen;
+ ocsp_get_meta(&stat, &valid, ctx->reg, ostat, ctx->p);
+ switch (stat) {
+ case MD_OCSP_CERT_ST_GOOD: ++ctx->good; break;
+ case MD_OCSP_CERT_ST_REVOKED: ++ctx->revoked; break;
+ case MD_OCSP_CERT_ST_UNKNOWN: ++ctx->unknown; break;
+ }
+ return 1;
+}
+
+void md_ocsp_get_summary(md_json_t **pjson, md_ocsp_reg_t *reg, apr_pool_t *p)
+{
+ md_json_t *json;
+ ocsp_summary_ctx_t ctx;
+
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.p = p;
+ ctx.reg = reg;
+ apr_hash_do(add_to_summary, &ctx, reg->ostat_by_id);
+
+ json = md_json_create(p);
+ md_json_setl(ctx.good+ctx.revoked+ctx.unknown, json, MD_KEY_TOTAL, NULL);
+ md_json_setl(ctx.good, json, MD_KEY_GOOD, NULL);
+ md_json_setl(ctx.revoked, json, MD_KEY_REVOKED, NULL);
+ md_json_setl(ctx.unknown, json, MD_KEY_UNKNOWN, NULL);
+ *pjson = json;
+}
+
+static apr_status_t job_loadj(md_json_t **pjson, const char *name,
+ md_ocsp_reg_t *reg, apr_pool_t *p)
+{
+ return md_store_load_json(reg->store, MD_SG_OCSP, name, MD_FN_JOB, pjson, p);
+}
+
+typedef struct {
+ apr_pool_t *p;
+ md_ocsp_reg_t *reg;
+ apr_array_header_t *ostats;
+} ocsp_status_ctx_t;
+
+static md_json_t *mk_jstat(md_ocsp_status_t *ostat, md_ocsp_reg_t *reg, apr_pool_t *p)
+{
+ md_ocsp_cert_stat_t stat;
+ md_timeperiod_t valid, renewal;
+ md_json_t *json, *jobj;
+ apr_status_t rv;
+
+ json = md_json_create(p);
+ md_json_sets(ostat->md_name, json, MD_KEY_DOMAIN, NULL);
+ md_json_sets(ostat->hexid, json, MD_KEY_ID, NULL);
+ ocsp_get_meta(&stat, &valid, reg, ostat, p);
+ md_json_sets(md_ocsp_cert_stat_name(stat), json, MD_KEY_STATUS, NULL);
+ md_json_sets(ostat->hex_sha256, json, MD_KEY_CERT, MD_KEY_SHA256_FINGERPRINT, NULL);
+ md_json_sets(ostat->responder_url, json, MD_KEY_URL, NULL);
+ md_json_set_timeperiod(&valid, json, MD_KEY_VALID, NULL);
+ renewal = md_timeperiod_slice_before_end(&valid, &reg->renew_window);
+ md_json_set_time(renewal.start, json, MD_KEY_RENEW_AT, NULL);
+ if ((MD_OCSP_CERT_ST_UNKNOWN == stat) || renewal.start < apr_time_now()) {
+ /* We have no answer yet, or it should be in renew now. Add job information */
+ rv = job_loadj(&jobj, ostat->md_name, reg, p);
+ if (APR_SUCCESS == rv) {
+ md_json_setj(jobj, json, MD_KEY_RENEWAL, NULL);
+ }
+ }
+ return json;
+}
+
+static int add_ostat(void *baton, const void *key, apr_ssize_t klen, const void *val)
+{
+ ocsp_status_ctx_t *ctx = baton;
+ const md_ocsp_status_t *ostat = val;
+
+ (void)key;
+ (void)klen;
+ APR_ARRAY_PUSH(ctx->ostats, const md_ocsp_status_t*) = ostat;
+ return 1;
+}
+
+static int md_ostat_cmp(const void *v1, const void *v2)
+{
+ int n;
+ n = strcmp((*(md_ocsp_status_t**)v1)->md_name, (*(md_ocsp_status_t**)v2)->md_name);
+ if (!n) {
+ n = strcmp((*(md_ocsp_status_t**)v1)->hexid, (*(md_ocsp_status_t**)v2)->hexid);
+ }
+ return n;
+}
+
+void md_ocsp_get_status_all(md_json_t **pjson, md_ocsp_reg_t *reg, apr_pool_t *p)
+{
+ md_json_t *json;
+ ocsp_status_ctx_t ctx;
+ md_ocsp_status_t *ostat;
+ int i;
+
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.p = p;
+ ctx.reg = reg;
+ ctx.ostats = apr_array_make(p, (int)apr_hash_count(reg->ostat_by_id), sizeof(md_ocsp_status_t*));
+ json = md_json_create(p);
+
+ apr_hash_do(add_ostat, &ctx, reg->ostat_by_id);
+ qsort(ctx.ostats->elts, (size_t)ctx.ostats->nelts, sizeof(md_json_t*), md_ostat_cmp);
+
+ for (i = 0; i < ctx.ostats->nelts; ++i) {
+ ostat = APR_ARRAY_IDX(ctx.ostats, i, md_ocsp_status_t*);
+ md_json_addj(mk_jstat(ostat, reg, p), json, MD_KEY_OCSPS, NULL);
+ }
+ *pjson = json;
+}
+
+md_job_t *md_ocsp_job_make(md_ocsp_reg_t *ocsp, const char *mdomain, apr_pool_t *p)
+{
+ return md_job_make(p, ocsp->store, MD_SG_OCSP, mdomain, ocsp->min_delay);
+}
diff --git a/modules/md/md_ocsp.h b/modules/md/md_ocsp.h
new file mode 100644
index 0000000..c91dc54
--- /dev/null
+++ b/modules/md/md_ocsp.h
@@ -0,0 +1,71 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef md_ocsp_h
+#define md_ocsp_h
+
+struct md_data_t;
+struct md_job_t;
+struct md_json_t;
+struct md_result_t;
+struct md_store_t;
+struct md_timeslice_t;
+
+typedef enum {
+ MD_OCSP_CERT_ST_UNKNOWN,
+ MD_OCSP_CERT_ST_GOOD,
+ MD_OCSP_CERT_ST_REVOKED,
+} md_ocsp_cert_stat_t;
+
+const char *md_ocsp_cert_stat_name(md_ocsp_cert_stat_t stat);
+md_ocsp_cert_stat_t md_ocsp_cert_stat_value(const char *name);
+
+typedef struct md_ocsp_reg_t md_ocsp_reg_t;
+
+apr_status_t md_ocsp_reg_make(md_ocsp_reg_t **preg, apr_pool_t *p,
+ struct md_store_t *store,
+ const md_timeslice_t *renew_window,
+ const char *user_agent, const char *proxy_url,
+ apr_time_t min_delay);
+
+apr_status_t md_ocsp_init_id(struct md_data_t *id, apr_pool_t *p, const md_cert_t *cert);
+
+apr_status_t md_ocsp_prime(md_ocsp_reg_t *reg, const char *ext_id, apr_size_t ext_id_len,
+ md_cert_t *x, md_cert_t *issuer, const md_t *md);
+
+typedef void md_ocsp_copy_der(const unsigned char *der, apr_size_t der_len, void *userdata);
+
+apr_status_t md_ocsp_get_status(md_ocsp_copy_der *cb, void *userdata, md_ocsp_reg_t *reg,
+ const char *ext_id, apr_size_t ext_id_len,
+ apr_pool_t *p, const md_t *md);
+
+apr_status_t md_ocsp_get_meta(md_ocsp_cert_stat_t *pstat, md_timeperiod_t *pvalid,
+ md_ocsp_reg_t *reg, const md_cert_t *cert,
+ apr_pool_t *p, const md_t *md);
+
+apr_size_t md_ocsp_count(md_ocsp_reg_t *reg);
+
+void md_ocsp_renew(md_ocsp_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp, apr_time_t *pnext_run);
+
+apr_status_t md_ocsp_remove_responses_older_than(md_ocsp_reg_t *reg, apr_pool_t *p,
+ apr_time_t timestamp);
+
+void md_ocsp_get_summary(struct md_json_t **pjson, md_ocsp_reg_t *reg, apr_pool_t *p);
+void md_ocsp_get_status_all(struct md_json_t **pjson, md_ocsp_reg_t *reg, apr_pool_t *p);
+
+struct md_job_t *md_ocsp_job_make(md_ocsp_reg_t *ocsp, const char *mdomain, apr_pool_t *p);
+
+#endif /* md_ocsp_h */
diff --git a/modules/md/md_reg.c b/modules/md/md_reg.c
new file mode 100644
index 0000000..8bceb0e
--- /dev/null
+++ b/modules/md/md_reg.c
@@ -0,0 +1,1323 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <apr_lib.h>
+#include <apr_hash.h>
+#include <apr_strings.h>
+#include <apr_uri.h>
+
+#include "md.h"
+#include "md_crypt.h"
+#include "md_event.h"
+#include "md_log.h"
+#include "md_json.h"
+#include "md_result.h"
+#include "md_reg.h"
+#include "md_store.h"
+#include "md_status.h"
+#include "md_tailscale.h"
+#include "md_util.h"
+
+#include "md_acme.h"
+#include "md_acme_acct.h"
+
+struct md_reg_t {
+ apr_pool_t *p;
+ struct md_store_t *store;
+ struct apr_hash_t *protos;
+ struct apr_hash_t *certs;
+ int can_http;
+ int can_https;
+ const char *proxy_url;
+ const char *ca_file;
+ int domains_frozen;
+ md_timeslice_t *renew_window;
+ md_timeslice_t *warn_window;
+ md_job_notify_cb *notify;
+ void *notify_ctx;
+ apr_time_t min_delay;
+ int retry_failover;
+ int use_store_locks;
+ apr_time_t lock_wait_timeout;
+};
+
+/**************************************************************************************************/
+/* life cycle */
+
+static apr_status_t load_props(md_reg_t *reg, apr_pool_t *p)
+{
+ md_json_t *json;
+ apr_status_t rv;
+
+ rv = md_store_load(reg->store, MD_SG_NONE, NULL, MD_FN_HTTPD_JSON,
+ MD_SV_JSON, (void**)&json, p);
+ if (APR_SUCCESS == rv) {
+ if (md_json_has_key(json, MD_KEY_PROTO, MD_KEY_HTTP, NULL)) {
+ reg->can_http = md_json_getb(json, MD_KEY_PROTO, MD_KEY_HTTP, NULL);
+ }
+ if (md_json_has_key(json, MD_KEY_PROTO, MD_KEY_HTTPS, NULL)) {
+ reg->can_https = md_json_getb(json, MD_KEY_PROTO, MD_KEY_HTTPS, NULL);
+ }
+ }
+ else if (APR_STATUS_IS_ENOENT(rv)) {
+ rv = APR_SUCCESS;
+ }
+ return rv;
+}
+
+apr_status_t md_reg_create(md_reg_t **preg, apr_pool_t *p, struct md_store_t *store,
+ const char *proxy_url, const char *ca_file,
+ apr_time_t min_delay, int retry_failover,
+ int use_store_locks, apr_time_t lock_wait_timeout)
+{
+ md_reg_t *reg;
+ apr_status_t rv;
+
+ reg = apr_pcalloc(p, sizeof(*reg));
+ reg->p = p;
+ reg->store = store;
+ reg->protos = apr_hash_make(p);
+ reg->certs = apr_hash_make(p);
+ reg->can_http = 1;
+ reg->can_https = 1;
+ reg->proxy_url = proxy_url? apr_pstrdup(p, proxy_url) : NULL;
+ reg->ca_file = (ca_file && apr_strnatcasecmp("none", ca_file))?
+ apr_pstrdup(p, ca_file) : NULL;
+ reg->min_delay = min_delay;
+ reg->retry_failover = retry_failover;
+ reg->use_store_locks = use_store_locks;
+ reg->lock_wait_timeout = lock_wait_timeout;
+
+ md_timeslice_create(&reg->renew_window, p, MD_TIME_LIFE_NORM, MD_TIME_RENEW_WINDOW_DEF);
+ md_timeslice_create(&reg->warn_window, p, MD_TIME_LIFE_NORM, MD_TIME_WARN_WINDOW_DEF);
+
+ if (APR_SUCCESS == (rv = md_acme_protos_add(reg->protos, p))
+ && APR_SUCCESS == (rv = md_tailscale_protos_add(reg->protos, p))) {
+ rv = load_props(reg, p);
+ }
+
+ *preg = (rv == APR_SUCCESS)? reg : NULL;
+ return rv;
+}
+
+struct md_store_t *md_reg_store_get(md_reg_t *reg)
+{
+ return reg->store;
+}
+
+/**************************************************************************************************/
+/* checks */
+
+static apr_status_t check_values(md_reg_t *reg, apr_pool_t *p, const md_t *md, int fields)
+{
+ apr_status_t rv = APR_SUCCESS;
+ const char *err = NULL;
+
+ if (MD_UPD_DOMAINS & fields) {
+ const md_t *other;
+ const char *domain;
+ int i;
+
+ if (!md->domains || md->domains->nelts <= 0) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, APR_EINVAL, p,
+ "empty domain list: %s", md->name);
+ return APR_EINVAL;
+ }
+
+ for (i = 0; i < md->domains->nelts; ++i) {
+ domain = APR_ARRAY_IDX(md->domains, i, const char *);
+ if (!md_dns_is_name(p, domain, 1) && !md_dns_is_wildcard(p, domain)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, p,
+ "md %s with invalid domain name: %s", md->name, domain);
+ return APR_EINVAL;
+ }
+ }
+
+ if (NULL != (other = md_reg_find_overlap(reg, md, &domain, p))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, p,
+ "md %s shares domain '%s' with md %s",
+ md->name, domain, other->name);
+ return APR_EINVAL;
+ }
+ }
+
+ if (MD_UPD_CONTACTS & fields) {
+ const char *contact;
+ int i;
+
+ for (i = 0; i < md->contacts->nelts && !err; ++i) {
+ contact = APR_ARRAY_IDX(md->contacts, i, const char *);
+ rv = md_util_abs_uri_check(p, contact, &err);
+
+ if (err) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, p,
+ "contact for %s invalid (%s): %s", md->name, err, contact);
+ return APR_EINVAL;
+ }
+ }
+ }
+
+ if ((MD_UPD_CA_URL & fields) && md->ca_urls) { /* setting to empty is ok */
+ int i;
+ const char *url;
+ for (i = 0; i < md->ca_urls->nelts; ++i) {
+ url = APR_ARRAY_IDX(md->ca_urls, i, const char*);
+ rv = md_util_abs_uri_check(p, url, &err);
+ if (err) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, p,
+ "CA url for %s invalid (%s): %s", md->name, err, url);
+ return APR_EINVAL;
+ }
+ }
+ }
+
+ if ((MD_UPD_CA_PROTO & fields) && md->ca_proto) { /* setting to empty is ok */
+ /* Do we want to restrict this to "known" protocols? */
+ }
+
+ if ((MD_UPD_CA_ACCOUNT & fields) && md->ca_account) { /* setting to empty is ok */
+ /* hmm, in case we know the protocol, some checks could be done */
+ }
+
+ if ((MD_UPD_AGREEMENT & fields) && md->ca_agreement
+ && strcmp("accepted", md->ca_agreement)) { /* setting to empty is ok */
+ rv = md_util_abs_uri_check(p, md->ca_agreement, &err);
+ if (err) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, p,
+ "CA url for %s invalid (%s): %s", md->name, err, md->ca_agreement);
+ return APR_EINVAL;
+ }
+ }
+
+ return rv;
+}
+
+/**************************************************************************************************/
+/* state assessment */
+
+static apr_status_t state_init(md_reg_t *reg, apr_pool_t *p, md_t *md)
+{
+ md_state_t state = MD_S_COMPLETE;
+ const char *state_descr = NULL;
+ const md_pubcert_t *pub;
+ const md_cert_t *cert;
+ const md_pkey_spec_t *spec;
+ apr_status_t rv = APR_SUCCESS;
+ int i;
+
+ if (md->renew_window == NULL) md->renew_window = reg->renew_window;
+ if (md->warn_window == NULL) md->warn_window = reg->warn_window;
+
+ if (md->domains && md->domains->pool != p) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p,
+ "md{%s}: state_init called with foreign pool", md->name);
+ }
+
+ for (i = 0; i < md_cert_count(md); ++i) {
+ spec = md_pkeys_spec_get(md->pks, i);
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, p,
+ "md{%s}: check cert %s", md->name, md_pkey_spec_name(spec));
+ rv = md_reg_get_pubcert(&pub, reg, md, i, p);
+ if (APR_SUCCESS == rv) {
+ cert = APR_ARRAY_IDX(pub->certs, 0, const md_cert_t*);
+ if (!md_is_covered_by_alt_names(md, pub->alt_names)) {
+ state = MD_S_INCOMPLETE;
+ state_descr = apr_psprintf(p, "certificate(%s) does not cover all domains.",
+ md_pkey_spec_name(spec));
+ goto cleanup;
+ }
+ if (!md->must_staple != !md_cert_must_staple(cert)) {
+ state = MD_S_INCOMPLETE;
+ state_descr = apr_psprintf(p, "'must-staple' is%s requested, but "
+ "certificate(%s) has it%s enabled.",
+ md->must_staple? "" : " not",
+ md_pkey_spec_name(spec),
+ !md->must_staple? "" : " not");
+ goto cleanup;
+ }
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "md{%s}: certificate(%d) is ok",
+ md->name, i);
+ }
+ else if (APR_STATUS_IS_ENOENT(rv)) {
+ state = MD_S_INCOMPLETE;
+ state_descr = apr_psprintf(p, "certificate(%s) is missing",
+ md_pkey_spec_name(spec));
+ rv = APR_SUCCESS;
+ goto cleanup;
+ }
+ else {
+ state = MD_S_ERROR;
+ state_descr = "error initializing";
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, "md{%s}: error", md->name);
+ goto cleanup;
+ }
+ }
+
+cleanup:
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, p, "md{%s}: state=%d, %s",
+ md->name, state, state_descr);
+ md->state = state;
+ md->state_descr = state_descr;
+ return rv;
+}
+
+/**************************************************************************************************/
+/* iteration */
+
+typedef struct {
+ md_reg_t *reg;
+ md_reg_do_cb *cb;
+ void *baton;
+ const char *exclude;
+ const void *result;
+} reg_do_ctx;
+
+static int reg_md_iter(void *baton, md_store_t *store, md_t *md, apr_pool_t *ptemp)
+{
+ reg_do_ctx *ctx = baton;
+
+ (void)store;
+ if (!ctx->exclude || strcmp(ctx->exclude, md->name)) {
+ state_init(ctx->reg, ptemp, (md_t*)md);
+ return ctx->cb(ctx->baton, ctx->reg, md);
+ }
+ return 1;
+}
+
+static int reg_do(md_reg_do_cb *cb, void *baton, md_reg_t *reg, apr_pool_t *p, const char *exclude)
+{
+ reg_do_ctx ctx;
+
+ ctx.reg = reg;
+ ctx.cb = cb;
+ ctx.baton = baton;
+ ctx.exclude = exclude;
+ return md_store_md_iter(reg_md_iter, &ctx, reg->store, p, MD_SG_DOMAINS, "*");
+}
+
+
+int md_reg_do(md_reg_do_cb *cb, void *baton, md_reg_t *reg, apr_pool_t *p)
+{
+ return reg_do(cb, baton, reg, p, NULL);
+}
+
+/**************************************************************************************************/
+/* lookup */
+
+md_t *md_reg_get(md_reg_t *reg, const char *name, apr_pool_t *p)
+{
+ md_t *md;
+
+ if (APR_SUCCESS == md_load(reg->store, MD_SG_DOMAINS, name, &md, p)) {
+ state_init(reg, p, md);
+ return md;
+ }
+ return NULL;
+}
+
+typedef struct {
+ const char *domain;
+ md_t *md;
+} find_domain_ctx;
+
+static int find_domain(void *baton, md_reg_t *reg, md_t *md)
+{
+ find_domain_ctx *ctx = baton;
+
+ (void)reg;
+ if (md_contains(md, ctx->domain, 0)) {
+ ctx->md = md;
+ return 0;
+ }
+ return 1;
+}
+
+md_t *md_reg_find(md_reg_t *reg, const char *domain, apr_pool_t *p)
+{
+ find_domain_ctx ctx;
+
+ ctx.domain = domain;
+ ctx.md = NULL;
+
+ md_reg_do(find_domain, &ctx, reg, p);
+ if (ctx.md) {
+ state_init(reg, p, ctx.md);
+ }
+ return ctx.md;
+}
+
+typedef struct {
+ const md_t *md_checked;
+ md_t *md;
+ const char *s;
+} find_overlap_ctx;
+
+static int find_overlap(void *baton, md_reg_t *reg, md_t *md)
+{
+ find_overlap_ctx *ctx = baton;
+ const char *overlap;
+
+ (void)reg;
+ if ((overlap = md_common_name(ctx->md_checked, md))) {
+ ctx->md = md;
+ ctx->s = overlap;
+ return 0;
+ }
+ return 1;
+}
+
+md_t *md_reg_find_overlap(md_reg_t *reg, const md_t *md, const char **pdomain, apr_pool_t *p)
+{
+ find_overlap_ctx ctx;
+
+ ctx.md_checked = md;
+ ctx.md = NULL;
+ ctx.s = NULL;
+
+ reg_do(find_overlap, &ctx, reg, p, md->name);
+ if (pdomain && ctx.s) {
+ *pdomain = ctx.s;
+ }
+ if (ctx.md) {
+ state_init(reg, p, ctx.md);
+ }
+ return ctx.md;
+}
+
+/**************************************************************************************************/
+/* manipulation */
+
+static apr_status_t p_md_add(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+{
+ md_reg_t *reg = baton;
+ apr_status_t rv = APR_SUCCESS;
+ md_t *md, *mine;
+ int do_check;
+
+ md = va_arg(ap, md_t *);
+ do_check = va_arg(ap, int);
+
+ if (reg->domains_frozen) return APR_EACCES;
+ mine = md_clone(ptemp, md);
+ if (do_check && APR_SUCCESS != (rv = check_values(reg, ptemp, md, MD_UPD_ALL))) goto leave;
+ if (APR_SUCCESS != (rv = state_init(reg, ptemp, mine))) goto leave;
+ rv = md_save(reg->store, p, MD_SG_DOMAINS, mine, 1);
+leave:
+ return rv;
+}
+
+static apr_status_t add_md(md_reg_t *reg, md_t *md, apr_pool_t *p, int do_checks)
+{
+ return md_util_pool_vdo(p_md_add, reg, p, md, do_checks, NULL);
+}
+
+apr_status_t md_reg_add(md_reg_t *reg, md_t *md, apr_pool_t *p)
+{
+ return add_md(reg, md, p, 1);
+}
+
+static apr_status_t p_md_update(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+{
+ md_reg_t *reg = baton;
+ apr_status_t rv = APR_SUCCESS;
+ const char *name;
+ const md_t *md, *updates;
+ int fields, do_checks;
+ md_t *nmd;
+
+ name = va_arg(ap, const char *);
+ updates = va_arg(ap, const md_t *);
+ fields = va_arg(ap, int);
+ do_checks = va_arg(ap, int);
+
+ if (NULL == (md = md_reg_get(reg, name, ptemp))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, APR_ENOENT, ptemp, "md %s", name);
+ return APR_ENOENT;
+ }
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ptemp, "md[%s]: update store", name);
+
+ if (do_checks && APR_SUCCESS != (rv = check_values(reg, ptemp, updates, fields))) {
+ return rv;
+ }
+
+ if (reg->domains_frozen) return APR_EACCES;
+ nmd = md_copy(ptemp, md);
+ if (MD_UPD_DOMAINS & fields) {
+ nmd->domains = updates->domains;
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update domains: %s", name);
+ }
+ if (MD_UPD_CA_URL & fields) {
+ nmd->ca_urls = (updates->ca_urls?
+ apr_array_copy(p, updates->ca_urls) : NULL);
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update ca url: %s", name);
+ }
+ if (MD_UPD_CA_PROTO & fields) {
+ nmd->ca_proto = updates->ca_proto;
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update ca protocol: %s", name);
+ }
+ if (MD_UPD_CA_ACCOUNT & fields) {
+ nmd->ca_account = updates->ca_account;
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update account: %s", name);
+ }
+ if (MD_UPD_CONTACTS & fields) {
+ nmd->contacts = updates->contacts;
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update contacts: %s", name);
+ }
+ if (MD_UPD_AGREEMENT & fields) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update agreement: %s", name);
+ nmd->ca_agreement = updates->ca_agreement;
+ }
+ if (MD_UPD_DRIVE_MODE & fields) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update drive-mode: %s", name);
+ nmd->renew_mode = updates->renew_mode;
+ }
+ if (MD_UPD_RENEW_WINDOW & fields) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update renew-window: %s", name);
+ *nmd->renew_window = *updates->renew_window;
+ }
+ if (MD_UPD_WARN_WINDOW & fields) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update warn-window: %s", name);
+ *nmd->warn_window = *updates->warn_window;
+ }
+ if (MD_UPD_CA_CHALLENGES & fields) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update ca challenges: %s", name);
+ nmd->ca_challenges = (updates->ca_challenges?
+ apr_array_copy(p, updates->ca_challenges) : NULL);
+ }
+ if (MD_UPD_PKEY_SPEC & fields) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update pkey spec: %s", name);
+ nmd->pks = md_pkeys_spec_clone(p, updates->pks);
+ }
+ if (MD_UPD_REQUIRE_HTTPS & fields) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update require-https: %s", name);
+ nmd->require_https = updates->require_https;
+ }
+ if (MD_UPD_TRANSITIVE & fields) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update transitive: %s", name);
+ nmd->transitive = updates->transitive;
+ }
+ if (MD_UPD_MUST_STAPLE & fields) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update must-staple: %s", name);
+ nmd->must_staple = updates->must_staple;
+ }
+ if (MD_UPD_PROTO & fields) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update proto: %s", name);
+ nmd->acme_tls_1_domains = updates->acme_tls_1_domains;
+ }
+ if (MD_UPD_STAPLING & fields) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update stapling: %s", name);
+ nmd->stapling = updates->stapling;
+ }
+
+ if (fields && APR_SUCCESS == (rv = md_save(reg->store, p, MD_SG_DOMAINS, nmd, 0))) {
+ rv = state_init(reg, ptemp, nmd);
+ }
+ return rv;
+}
+
+apr_status_t md_reg_update(md_reg_t *reg, apr_pool_t *p,
+ const char *name, const md_t *md, int fields,
+ int do_checks)
+{
+ return md_util_pool_vdo(p_md_update, reg, p, name, md, fields, do_checks, NULL);
+}
+
+apr_status_t md_reg_delete_acct(md_reg_t *reg, apr_pool_t *p, const char *acct_id)
+{
+ apr_status_t rv = APR_SUCCESS;
+
+ rv = md_store_remove(reg->store, MD_SG_ACCOUNTS, acct_id, MD_FN_ACCOUNT, p, 1);
+ if (APR_SUCCESS == rv) {
+ md_store_remove(reg->store, MD_SG_ACCOUNTS, acct_id, MD_FN_ACCT_KEY, p, 1);
+ }
+ return rv;
+}
+
+/**************************************************************************************************/
+/* certificate related */
+
+static apr_status_t pubcert_load(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+{
+ md_reg_t *reg = baton;
+ apr_array_header_t *certs;
+ md_pubcert_t *pubcert, **ppubcert;
+ const md_t *md;
+ int index;
+ const md_cert_t *cert;
+ md_cert_state_t cert_state;
+ md_store_group_t group;
+ apr_status_t rv;
+
+ ppubcert = va_arg(ap, md_pubcert_t **);
+ group = (md_store_group_t)va_arg(ap, int);
+ md = va_arg(ap, const md_t *);
+ index = va_arg(ap, int);
+
+ if (md->cert_files && md->cert_files->nelts) {
+ rv = md_chain_fload(&certs, p, APR_ARRAY_IDX(md->cert_files, index, const char *));
+ }
+ else {
+ md_pkey_spec_t *spec = md_pkeys_spec_get(md->pks, index);;
+ rv = md_pubcert_load(reg->store, group, md->name, spec, &certs, p);
+ }
+ if (APR_SUCCESS != rv) goto leave;
+ if (certs->nelts == 0) {
+ rv = APR_ENOENT;
+ goto leave;
+ }
+
+ pubcert = apr_pcalloc(p, sizeof(*pubcert));
+ pubcert->certs = certs;
+ cert = APR_ARRAY_IDX(certs, 0, const md_cert_t *);
+ if (APR_SUCCESS != (rv = md_cert_get_alt_names(&pubcert->alt_names, cert, p))) goto leave;
+ switch ((cert_state = md_cert_state_get(cert))) {
+ case MD_CERT_VALID:
+ case MD_CERT_EXPIRED:
+ break;
+ default:
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, ptemp,
+ "md %s has unexpected cert state: %d", md->name, cert_state);
+ rv = APR_ENOTIMPL;
+ break;
+ }
+leave:
+ *ppubcert = (APR_SUCCESS == rv)? pubcert : NULL;
+ return rv;
+}
+
+apr_status_t md_reg_get_pubcert(const md_pubcert_t **ppubcert, md_reg_t *reg,
+ const md_t *md, int i, apr_pool_t *p)
+{
+ apr_status_t rv = APR_SUCCESS;
+ const md_pubcert_t *pubcert;
+ const char *name;
+
+ name = apr_psprintf(p, "%s[%d]", md->name, i);
+ pubcert = apr_hash_get(reg->certs, name, (apr_ssize_t)strlen(name));
+ if (!pubcert && !reg->domains_frozen) {
+ rv = md_util_pool_vdo(pubcert_load, reg, reg->p, &pubcert, MD_SG_DOMAINS, md, i, NULL);
+ if (APR_STATUS_IS_ENOENT(rv)) {
+ /* We cache it missing with an empty record */
+ pubcert = apr_pcalloc(reg->p, sizeof(*pubcert));
+ }
+ else if (APR_SUCCESS != rv) goto leave;
+ if (p != reg->p) name = apr_pstrdup(reg->p, name);
+ apr_hash_set(reg->certs, name, (apr_ssize_t)strlen(name), pubcert);
+ }
+leave:
+ if (APR_SUCCESS == rv && (!pubcert || !pubcert->certs)) {
+ rv = APR_ENOENT;
+ }
+ *ppubcert = (APR_SUCCESS == rv)? pubcert : NULL;
+ return rv;
+}
+
+apr_status_t md_reg_get_cred_files(const char **pkeyfile, const char **pcertfile,
+ md_reg_t *reg, md_store_group_t group,
+ const md_t *md, md_pkey_spec_t *spec, apr_pool_t *p)
+{
+ apr_status_t rv;
+
+ rv = md_store_get_fname(pkeyfile, reg->store, group, md->name, md_pkey_filename(spec, p), p);
+ if (APR_SUCCESS != rv) return rv;
+ if (!md_file_exists(*pkeyfile, p)) return APR_ENOENT;
+ rv = md_store_get_fname(pcertfile, reg->store, group, md->name, md_chain_filename(spec, p), p);
+ if (APR_SUCCESS != rv) return rv;
+ if (!md_file_exists(*pcertfile, p)) return APR_ENOENT;
+ return APR_SUCCESS;
+}
+
+apr_time_t md_reg_valid_until(md_reg_t *reg, const md_t *md, apr_pool_t *p)
+{
+ const md_pubcert_t *pub;
+ const md_cert_t *cert;
+ int i;
+ apr_time_t t, valid_until = 0;
+ apr_status_t rv;
+
+ for (i = 0; i < md_cert_count(md); ++i) {
+ rv = md_reg_get_pubcert(&pub, reg, md, i, p);
+ if (APR_SUCCESS == rv) {
+ cert = APR_ARRAY_IDX(pub->certs, 0, const md_cert_t*);
+ t = md_cert_get_not_after(cert);
+ if (valid_until == 0 || t < valid_until) {
+ valid_until = t;
+ }
+ }
+ }
+ return valid_until;
+}
+
+apr_time_t md_reg_renew_at(md_reg_t *reg, const md_t *md, apr_pool_t *p)
+{
+ const md_pubcert_t *pub;
+ const md_cert_t *cert;
+ md_timeperiod_t certlife, renewal;
+ int i;
+ apr_time_t renew_at = 0;
+ apr_status_t rv;
+
+ if (md->state == MD_S_INCOMPLETE) return apr_time_now();
+ for (i = 0; i < md_cert_count(md); ++i) {
+ rv = md_reg_get_pubcert(&pub, reg, md, i, p);
+ if (APR_STATUS_IS_ENOENT(rv)) return apr_time_now();
+ if (APR_SUCCESS == rv) {
+ cert = APR_ARRAY_IDX(pub->certs, 0, const md_cert_t*);
+ certlife.start = md_cert_get_not_before(cert);
+ certlife.end = md_cert_get_not_after(cert);
+
+ renewal = md_timeperiod_slice_before_end(&certlife, md->renew_window);
+ if (md_log_is_level(p, MD_LOG_TRACE1)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, p,
+ "md[%s]: certificate(%d) valid[%s] renewal[%s]",
+ md->name, i,
+ md_timeperiod_print(p, &certlife),
+ md_timeperiod_print(p, &renewal));
+ }
+
+ if (renew_at == 0 || renewal.start < renew_at) {
+ renew_at = renewal.start;
+ }
+ }
+ }
+ return renew_at;
+}
+
+int md_reg_should_renew(md_reg_t *reg, const md_t *md, apr_pool_t *p)
+{
+ apr_time_t renew_at;
+
+ renew_at = md_reg_renew_at(reg, md, p);
+ return renew_at && (renew_at <= apr_time_now());
+}
+
+int md_reg_should_warn(md_reg_t *reg, const md_t *md, apr_pool_t *p)
+{
+ const md_pubcert_t *pub;
+ const md_cert_t *cert;
+ md_timeperiod_t certlife, warn;
+ int i;
+ apr_status_t rv;
+
+ if (md->state == MD_S_INCOMPLETE) return 0;
+ for (i = 0; i < md_cert_count(md); ++i) {
+ rv = md_reg_get_pubcert(&pub, reg, md, i, p);
+ if (APR_STATUS_IS_ENOENT(rv)) return 0;
+ if (APR_SUCCESS == rv) {
+ cert = APR_ARRAY_IDX(pub->certs, 0, const md_cert_t*);
+ certlife.start = md_cert_get_not_before(cert);
+ certlife.end = md_cert_get_not_after(cert);
+
+ warn = md_timeperiod_slice_before_end(&certlife, md->warn_window);
+ if (md_log_is_level(p, MD_LOG_TRACE1)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, p,
+ "md[%s]: certificate(%d) life[%s] warn[%s]",
+ md->name, i,
+ md_timeperiod_print(p, &certlife),
+ md_timeperiod_print(p, &warn));
+ }
+ if (md_timeperiod_has_started(&warn, apr_time_now())) {
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+/**************************************************************************************************/
+/* syncing */
+
+apr_status_t md_reg_set_props(md_reg_t *reg, apr_pool_t *p, int can_http, int can_https)
+{
+ if (reg->can_http != can_http || reg->can_https != can_https) {
+ md_json_t *json;
+
+ if (reg->domains_frozen) return APR_EACCES;
+ reg->can_http = can_http;
+ reg->can_https = can_https;
+
+ json = md_json_create(p);
+ md_json_setb(can_http, json, MD_KEY_PROTO, MD_KEY_HTTP, NULL);
+ md_json_setb(can_https, json, MD_KEY_PROTO, MD_KEY_HTTPS, NULL);
+
+ return md_store_save(reg->store, p, MD_SG_NONE, NULL, MD_FN_HTTPD_JSON, MD_SV_JSON, json, 0);
+ }
+ return APR_SUCCESS;
+}
+
+static md_t *find_closest_match(apr_array_header_t *mds, const md_t *md)
+{
+ md_t *candidate, *m;
+ apr_size_t cand_n, n;
+ int i;
+
+ candidate = md_get_by_name(mds, md->name);
+ if (!candidate) {
+ /* try to find an instance that contains all domain names from md */
+ for (i = 0; i < mds->nelts; ++i) {
+ m = APR_ARRAY_IDX(mds, i, md_t *);
+ if (md_contains_domains(m, md)) {
+ return m;
+ }
+ }
+ /* no matching name and no md in the list has all domains.
+ * We consider that managed domain as closest match that contains at least one
+ * domain name from md, ONLY if there is no other one that also has.
+ */
+ cand_n = 0;
+ for (i = 0; i < mds->nelts; ++i) {
+ m = APR_ARRAY_IDX(mds, i, md_t *);
+ n = md_common_name_count(md, m);
+ if (n > cand_n) {
+ candidate = m;
+ cand_n = n;
+ }
+ }
+ }
+ return candidate;
+}
+
+typedef struct {
+ apr_pool_t *p;
+ apr_array_header_t *master_mds;
+ apr_array_header_t *store_names;
+ apr_array_header_t *maybe_new_mds;
+ apr_array_header_t *new_mds;
+ apr_array_header_t *unassigned_mds;
+} sync_ctx_v2;
+
+static int iter_add_name(void *baton, const char *dir, const char *name,
+ md_store_vtype_t vtype, void *value, apr_pool_t *ptemp)
+{
+ sync_ctx_v2 *ctx = baton;
+
+ (void)dir;
+ (void)value;
+ (void)ptemp;
+ (void)vtype;
+ APR_ARRAY_PUSH(ctx->store_names, const char*) = apr_pstrdup(ctx->p, name);
+ return APR_SUCCESS;
+}
+
+/* A better scaling version:
+ * 1. The consistency of the MDs in 'master_mds' has already been verified. E.g.
+ * that no domain lists overlap etc.
+ * 2. All MD storage that exists will be overwritten by the settings we have.
+ * And "exists" meaning that "store/MD_SG_DOMAINS/name" exists.
+ * 3. For MDs that have no directory in "store/MD_SG_DOMAINS", we load all MDs
+ * outside the list of known names from MD_SG_DOMAINS. In this list, we
+ * look for the MD with the most domain overlap.
+ * - if we find it, we assume this is a rename and move the old MD to the new name.
+ * - if not, MD is completely new.
+ * 4. Any MD in store that does not match the "master_mds" will just be left as is.
+ */
+apr_status_t md_reg_sync_start(md_reg_t *reg, apr_array_header_t *master_mds, apr_pool_t *p)
+{
+ sync_ctx_v2 ctx;
+ apr_status_t rv;
+ md_t *md, *oldmd;
+ const char *name;
+ int i, idx;
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "sync MDs, start");
+
+ ctx.p = p;
+ ctx.master_mds = master_mds;
+ ctx.store_names = apr_array_make(p, master_mds->nelts + 100, sizeof(const char*));
+ ctx.maybe_new_mds = apr_array_make(p, master_mds->nelts, sizeof(md_t*));
+ ctx.new_mds = apr_array_make(p, master_mds->nelts, sizeof(md_t*));
+ ctx.unassigned_mds = apr_array_make(p, master_mds->nelts, sizeof(md_t*));
+
+ rv = md_store_iter_names(iter_add_name, &ctx, reg->store, p, MD_SG_DOMAINS, "*");
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "listing existing store MD names");
+ goto leave;
+ }
+
+ /* Get all MDs that are not already present in store */
+ for (i = 0; i < ctx.master_mds->nelts; ++i) {
+ md = APR_ARRAY_IDX(ctx.master_mds, i, md_t*);
+ idx = md_array_str_index(ctx.store_names, md->name, 0, 1);
+ if (idx < 0) {
+ APR_ARRAY_PUSH(ctx.maybe_new_mds, md_t*) = md;
+ md_array_remove_at(ctx.store_names, idx);
+ }
+ }
+
+ if (ctx.maybe_new_mds->nelts == 0) goto leave; /* none new */
+ if (ctx.store_names->nelts == 0) goto leave; /* all new */
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p,
+ "sync MDs, %d potentially new MDs detected, looking for renames among "
+ "the %d unassigned store domains", (int)ctx.maybe_new_mds->nelts,
+ (int)ctx.store_names->nelts);
+ for (i = 0; i < ctx.store_names->nelts; ++i) {
+ name = APR_ARRAY_IDX(ctx.store_names, i, const char*);
+ if (APR_SUCCESS == md_load(reg->store, MD_SG_DOMAINS, name, &md, p)) {
+ APR_ARRAY_PUSH(ctx.unassigned_mds, md_t*) = md;
+ }
+ }
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p,
+ "sync MDs, %d MDs maybe new, checking store", (int)ctx.maybe_new_mds->nelts);
+ for (i = 0; i < ctx.maybe_new_mds->nelts; ++i) {
+ md = APR_ARRAY_IDX(ctx.maybe_new_mds, i, md_t*);
+ oldmd = find_closest_match(ctx.unassigned_mds, md);
+ if (oldmd) {
+ /* found the rename, move the domains and possible staging directory */
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p,
+ "sync MDs, found MD %s under previous name %s", md->name, oldmd->name);
+ rv = md_store_rename(reg->store, p, MD_SG_DOMAINS, oldmd->name, md->name);
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p,
+ "sync MDs, renaming MD %s to %s failed", oldmd->name, md->name);
+ /* ignore it? */
+ }
+ md_store_rename(reg->store, p, MD_SG_STAGING, oldmd->name, md->name);
+ md_array_remove(ctx.unassigned_mds, oldmd);
+ }
+ else {
+ APR_ARRAY_PUSH(ctx.new_mds, md_t*) = md;
+ }
+ }
+
+leave:
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p,
+ "sync MDs, %d existing, %d moved, %d new.",
+ (int)ctx.master_mds->nelts - ctx.maybe_new_mds->nelts,
+ (int)ctx.maybe_new_mds->nelts - ctx.new_mds->nelts,
+ (int)ctx.new_mds->nelts);
+ return rv;
+}
+
+/**
+ * Finish syncing an MD with the store.
+ * 1. if there are changed properties (or if the MD is new), save it.
+ * 2. read any existing certificate and init the state of the memory MD
+ */
+apr_status_t md_reg_sync_finish(md_reg_t *reg, md_t *md, apr_pool_t *p, apr_pool_t *ptemp)
+{
+ md_t *old;
+ apr_status_t rv;
+ int changed = 1;
+ md_proto_t *proto;
+
+ if (!md->ca_proto) {
+ md->ca_proto = MD_PROTO_ACME;
+ }
+ proto = apr_hash_get(reg->protos, md->ca_proto, (apr_ssize_t)strlen(md->ca_proto));
+ if (!proto) {
+ rv = APR_ENOTIMPL;
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, ptemp,
+ "[%s] uses unknown CA protocol '%s'",
+ md->name, md->ca_proto);
+ goto leave;
+ }
+ rv = proto->complete_md(md, p);
+ if (APR_SUCCESS != rv) goto leave;
+
+ rv = state_init(reg, p, md);
+ if (APR_SUCCESS != rv) goto leave;
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ptemp, "loading md %s", md->name);
+ if (APR_SUCCESS == md_load(reg->store, MD_SG_DOMAINS, md->name, &old, ptemp)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ptemp, "loaded md %s", md->name);
+ /* Some parts are kept from old, lacking new values */
+ if ((!md->contacts || apr_is_empty_array(md->contacts)) && old->contacts) {
+ md->contacts = md_array_str_clone(p, old->contacts);
+ }
+ if (md->ca_challenges && old->ca_challenges) {
+ if (!md_array_str_eq(md->ca_challenges, old->ca_challenges, 0)) {
+ md->ca_challenges = md_array_str_compact(p, md->ca_challenges, 0);
+ }
+ }
+ if (!md->ca_effective && old->ca_effective) {
+ md->ca_effective = apr_pstrdup(p, old->ca_effective);
+ }
+ if (!md->ca_account && old->ca_account) {
+ md->ca_account = apr_pstrdup(p, old->ca_account);
+ }
+
+ /* if everything remains the same, spare the write back */
+ if (!MD_VAL_UPDATE(md, old, state)
+ && md_array_str_eq(md->ca_urls, old->ca_urls, 0)
+ && !MD_SVAL_UPDATE(md, old, ca_proto)
+ && !MD_SVAL_UPDATE(md, old, ca_agreement)
+ && !MD_VAL_UPDATE(md, old, transitive)
+ && md_equal_domains(md, old, 1)
+ && !MD_VAL_UPDATE(md, old, renew_mode)
+ && md_timeslice_eq(md->renew_window, old->renew_window)
+ && md_timeslice_eq(md->warn_window, old->warn_window)
+ && md_pkeys_spec_eq(md->pks, old->pks)
+ && !MD_VAL_UPDATE(md, old, require_https)
+ && !MD_VAL_UPDATE(md, old, must_staple)
+ && md_array_str_eq(md->acme_tls_1_domains, old->acme_tls_1_domains, 0)
+ && !MD_VAL_UPDATE(md, old, stapling)
+ && md_array_str_eq(md->contacts, old->contacts, 0)
+ && md_array_str_eq(md->cert_files, old->cert_files, 0)
+ && md_array_str_eq(md->pkey_files, old->pkey_files, 0)
+ && md_array_str_eq(md->ca_challenges, old->ca_challenges, 0)) {
+ changed = 0;
+ }
+ }
+ if (changed) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ptemp, "saving md %s", md->name);
+ rv = md_save(reg->store, ptemp, MD_SG_DOMAINS, md, 0);
+ }
+leave:
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ptemp, "sync MDs, finish done");
+ return rv;
+}
+
+apr_status_t md_reg_remove(md_reg_t *reg, apr_pool_t *p, const char *name, int archive)
+{
+ if (reg->domains_frozen) return APR_EACCES;
+ return md_store_move(reg->store, p, MD_SG_DOMAINS, MD_SG_ARCHIVE, name, archive);
+}
+
+typedef struct {
+ md_reg_t *reg;
+ apr_pool_t *p;
+ apr_array_header_t *mds;
+} cleanup_challenge_ctx;
+
+static apr_status_t cleanup_challenge_inspector(void *baton, const char *dir, const char *name,
+ md_store_vtype_t vtype, void *value,
+ apr_pool_t *ptemp)
+{
+ cleanup_challenge_ctx *ctx = baton;
+ const md_t *md;
+ int i, used;
+ apr_status_t rv;
+
+ (void)value;
+ (void)vtype;
+ (void)dir;
+ for (used = 0, i = 0; i < ctx->mds->nelts && !used; ++i) {
+ md = APR_ARRAY_IDX(ctx->mds, i, const md_t *);
+ used = !strcmp(name, md->name);
+ }
+ if (!used) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ptemp,
+ "challenges/%s: not in use, purging", name);
+ rv = md_store_purge(ctx->reg->store, ctx->p, MD_SG_CHALLENGES, name);
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, ptemp,
+ "challenges/%s: unable to purge", name);
+ }
+ }
+ return APR_SUCCESS;
+}
+
+apr_status_t md_reg_cleanup_challenges(md_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp,
+ apr_array_header_t *mds)
+{
+ apr_status_t rv;
+ cleanup_challenge_ctx ctx;
+
+ (void)p;
+ ctx.reg = reg;
+ ctx.p = ptemp;
+ ctx.mds = mds;
+ rv = md_store_iter_names(cleanup_challenge_inspector, &ctx, reg->store, ptemp,
+ MD_SG_CHALLENGES, "*");
+ return rv;
+}
+
+
+/**************************************************************************************************/
+/* driving */
+
+static apr_status_t run_init(void *baton, apr_pool_t *p, ...)
+{
+ va_list ap;
+ md_reg_t *reg = baton;
+ const md_t *md;
+ md_proto_driver_t *driver, **pdriver;
+ md_result_t *result;
+ apr_table_t *env;
+ const char *s;
+ int preload;
+
+ (void)p;
+ va_start(ap, p);
+ pdriver = va_arg(ap, md_proto_driver_t **);
+ md = va_arg(ap, const md_t *);
+ preload = va_arg(ap, int);
+ env = va_arg(ap, apr_table_t *);
+ result = va_arg(ap, md_result_t *);
+ va_end(ap);
+
+ *pdriver = driver = apr_pcalloc(p, sizeof(*driver));
+
+ driver->p = p;
+ driver->env = env? apr_table_copy(p, env) : apr_table_make(p, 10);
+ driver->reg = reg;
+ driver->store = md_reg_store_get(reg);
+ driver->proxy_url = reg->proxy_url;
+ driver->ca_file = reg->ca_file;
+ driver->md = md;
+ driver->can_http = reg->can_http;
+ driver->can_https = reg->can_https;
+
+ s = apr_table_get(driver->env, MD_KEY_ACTIVATION_DELAY);
+ if (!s || APR_SUCCESS != md_duration_parse(&driver->activation_delay, s, "d")) {
+ driver->activation_delay = 0;
+ }
+
+ if (!md->ca_proto) {
+ md_result_printf(result, APR_EGENERAL, "CA protocol is not defined");
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p, "md[%s]: %s", md->name, result->detail);
+ goto leave;
+ }
+
+ driver->proto = apr_hash_get(reg->protos, md->ca_proto, (apr_ssize_t)strlen(md->ca_proto));
+ if (!driver->proto) {
+ md_result_printf(result, APR_EGENERAL, "Unknown CA protocol '%s'", md->ca_proto);
+ goto leave;
+ }
+
+ if (preload) {
+ result->status = driver->proto->init_preload(driver, result);
+ }
+ else {
+ result->status = driver->proto->init(driver, result);
+ }
+
+leave:
+ if (APR_SUCCESS != result->status) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, result->status, p, "md[%s]: %s", md->name,
+ result->detail? result->detail : "<see error log for details>");
+ }
+ else {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "%s: init done", md->name);
+ }
+ return result->status;
+}
+
+static apr_status_t run_test_init(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+{
+ const md_t *md;
+ apr_table_t *env;
+ md_result_t *result;
+ md_proto_driver_t *driver;
+
+ (void)p;
+ md = va_arg(ap, const md_t *);
+ env = va_arg(ap, apr_table_t *);
+ result = va_arg(ap, md_result_t *);
+
+ return run_init(baton, ptemp, &driver, md, 0, env, result, NULL);
+}
+
+apr_status_t md_reg_test_init(md_reg_t *reg, const md_t *md, struct apr_table_t *env,
+ md_result_t *result, apr_pool_t *p)
+{
+ return md_util_pool_vdo(run_test_init, reg, p, md, env, result, NULL);
+}
+
+static apr_status_t run_renew(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+{
+ md_reg_t *reg = baton;
+ const md_t *md;
+ int reset, attempt;
+ md_proto_driver_t *driver;
+ apr_table_t *env;
+ apr_status_t rv;
+ md_result_t *result;
+
+ (void)p;
+ md = va_arg(ap, const md_t *);
+ env = va_arg(ap, apr_table_t *);
+ reset = va_arg(ap, int);
+ attempt = va_arg(ap, int);
+ result = va_arg(ap, md_result_t *);
+
+ rv = run_init(reg, ptemp, &driver, md, 0, env, result, NULL);
+ if (APR_SUCCESS == rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ptemp, "%s: run staging", md->name);
+ driver->reset = reset;
+ driver->attempt = attempt;
+ driver->retry_failover = reg->retry_failover;
+ rv = driver->proto->renew(driver, result);
+ }
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ptemp, "%s: staging done", md->name);
+ return rv;
+}
+
+apr_status_t md_reg_renew(md_reg_t *reg, const md_t *md, apr_table_t *env,
+ int reset, int attempt,
+ md_result_t *result, apr_pool_t *p)
+{
+ return md_util_pool_vdo(run_renew, reg, p, md, env, reset, attempt, result, NULL);
+}
+
+static apr_status_t run_load_staging(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+{
+ md_reg_t *reg = baton;
+ const md_t *md;
+ md_proto_driver_t *driver;
+ md_result_t *result;
+ apr_table_t *env;
+ md_job_t *job;
+ apr_status_t rv;
+
+ /* For the MD, check if something is in the STAGING area. If none is there,
+ * return that status. Otherwise ask the protocol driver to preload it into
+ * a new, temporary area.
+ * If that succeeds, we move the TEMP area over the DOMAINS (causing the
+ * existing one go to ARCHIVE).
+ * Finally, we clean up the data from CHALLENGES and STAGING.
+ */
+ md = va_arg(ap, const md_t*);
+ env = va_arg(ap, apr_table_t*);
+ result = va_arg(ap, md_result_t*);
+
+ if (APR_STATUS_IS_ENOENT(rv = md_load(reg->store, MD_SG_STAGING, md->name, NULL, ptemp))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, ptemp, "%s: nothing staged", md->name);
+ goto out;
+ }
+
+ rv = run_init(baton, ptemp, &driver, md, 1, env, result, NULL);
+ if (APR_SUCCESS != rv) goto out;
+
+ apr_hash_set(reg->certs, md->name, (apr_ssize_t)strlen(md->name), NULL);
+ md_result_activity_setn(result, "preloading staged to tmp");
+ rv = driver->proto->preload(driver, MD_SG_TMP, result);
+ if (APR_SUCCESS != rv) goto out;
+
+ /* If we had a job saved in STAGING, copy it over too */
+ job = md_reg_job_make(reg, md->name, ptemp);
+ if (APR_SUCCESS == md_job_load(job)) {
+ md_job_set_group(job, MD_SG_TMP);
+ md_job_save(job, NULL, ptemp);
+ }
+
+ /* swap */
+ md_result_activity_setn(result, "moving tmp to become new domains");
+ rv = md_store_move(reg->store, p, MD_SG_TMP, MD_SG_DOMAINS, md->name, 1);
+ if (APR_SUCCESS != rv) {
+ md_result_set(result, rv, NULL);
+ goto out;
+ }
+
+ md_store_purge(reg->store, p, MD_SG_STAGING, md->name);
+ md_store_purge(reg->store, p, MD_SG_CHALLENGES, md->name);
+ md_result_set(result, APR_SUCCESS, "new certificate successfully saved in domains");
+ md_event_holler("installed", md->name, job, result, ptemp);
+ if (job->dirty) md_job_save(job, result, ptemp);
+
+out:
+ if (!APR_STATUS_IS_ENOENT(rv)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, ptemp, "%s: load done", md->name);
+ }
+ return rv;
+}
+
+apr_status_t md_reg_load_staging(md_reg_t *reg, const md_t *md, apr_table_t *env,
+ md_result_t *result, apr_pool_t *p)
+{
+ if (reg->domains_frozen) return APR_EACCES;
+ return md_util_pool_vdo(run_load_staging, reg, p, md, env, result, NULL);
+}
+
+apr_status_t md_reg_load_stagings(md_reg_t *reg, apr_array_header_t *mds,
+ apr_table_t *env, apr_pool_t *p)
+{
+ apr_status_t rv = APR_SUCCESS;
+ md_t *md;
+ md_result_t *result;
+ int i;
+
+ for (i = 0; i < mds->nelts; ++i) {
+ md = APR_ARRAY_IDX(mds, i, md_t *);
+ result = md_result_md_make(p, md->name);
+ rv = md_reg_load_staging(reg, md, env, result, p);
+ if (APR_SUCCESS == rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, rv, p, APLOGNO(10068)
+ "%s: staged set activated", md->name);
+ }
+ else if (!APR_STATUS_IS_ENOENT(rv)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, APLOGNO(10069)
+ "%s: error loading staged set", md->name);
+ }
+ }
+
+ return rv;
+}
+
+apr_status_t md_reg_lock_global(md_reg_t *reg, apr_pool_t *p)
+{
+ apr_status_t rv = APR_SUCCESS;
+
+ if (reg->use_store_locks) {
+ rv = md_store_lock_global(reg->store, p, reg->lock_wait_timeout);
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
+ "unable to acquire global store lock");
+ }
+ }
+ return rv;
+}
+
+void md_reg_unlock_global(md_reg_t *reg, apr_pool_t *p)
+{
+ if (reg->use_store_locks) {
+ md_store_unlock_global(reg->store, p);
+ }
+}
+
+apr_status_t md_reg_freeze_domains(md_reg_t *reg, apr_array_header_t *mds)
+{
+ apr_status_t rv = APR_SUCCESS;
+ md_t *md;
+ const md_pubcert_t *pubcert;
+ int i, j;
+
+ assert(!reg->domains_frozen);
+ /* prefill the certs cache for all mds */
+ for (i = 0; i < mds->nelts; ++i) {
+ md = APR_ARRAY_IDX(mds, i, md_t*);
+ for (j = 0; j < md_cert_count(md); ++j) {
+ rv = md_reg_get_pubcert(&pubcert, reg, md, i, reg->p);
+ if (APR_SUCCESS != rv && !APR_STATUS_IS_ENOENT(rv)) goto leave;
+ }
+ }
+ reg->domains_frozen = 1;
+leave:
+ return rv;
+}
+
+void md_reg_set_renew_window_default(md_reg_t *reg, md_timeslice_t *renew_window)
+{
+ *reg->renew_window = *renew_window;
+}
+
+void md_reg_set_warn_window_default(md_reg_t *reg, md_timeslice_t *warn_window)
+{
+ *reg->warn_window = *warn_window;
+}
+
+md_job_t *md_reg_job_make(md_reg_t *reg, const char *mdomain, apr_pool_t *p)
+{
+ return md_job_make(p, reg->store, MD_SG_STAGING, mdomain, reg->min_delay);
+}
diff --git a/modules/md/md_reg.h b/modules/md/md_reg.h
new file mode 100644
index 0000000..58ee16a
--- /dev/null
+++ b/modules/md/md_reg.h
@@ -0,0 +1,313 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mod_md_md_reg_h
+#define mod_md_md_reg_h
+
+struct apr_hash_t;
+struct apr_array_header_t;
+struct md_pkey_t;
+struct md_cert_t;
+struct md_result_t;
+struct md_pkey_spec_t;
+
+#include "md_store.h"
+
+/**
+ * A registry for managed domains with a md_store_t as persistence.
+ *
+ */
+typedef struct md_reg_t md_reg_t;
+
+/**
+ * Create the MD registry, using the pool and store.
+ * @param preg on APR_SUCCESS, the create md_reg_t
+ * @param pm memory pool to use for creation
+ * @param store the store to base on
+ * @param proxy_url optional URL of a proxy to use for requests
+ * @param ca_file optioinal CA trust anchor file to use
+ * @param min_delay minimum delay between renewal attempts for a domain
+ * @param retry_failover numer of failed renewals attempt to fail over to alternate ACME ca
+ */
+apr_status_t md_reg_create(md_reg_t **preg, apr_pool_t *pm, md_store_t *store,
+ const char *proxy_url, const char *ca_file,
+ apr_time_t min_delay, int retry_failover,
+ int use_store_locks, apr_time_t lock_wait_timeout);
+
+md_store_t *md_reg_store_get(md_reg_t *reg);
+
+apr_status_t md_reg_set_props(md_reg_t *reg, apr_pool_t *p, int can_http, int can_https);
+
+/**
+ * Add a new md to the registry. This will check the name for uniqueness and
+ * that domain names do not overlap with already existing mds.
+ */
+apr_status_t md_reg_add(md_reg_t *reg, md_t *md, apr_pool_t *p);
+
+/**
+ * Find the md, if any, that contains the given domain name.
+ * NULL if none found.
+ */
+md_t *md_reg_find(md_reg_t *reg, const char *domain, apr_pool_t *p);
+
+/**
+ * Find one md, which domain names overlap with the given md and that has a different
+ * name. There may be more than one existing md that overlaps. It is not defined
+ * which one will be returned.
+ */
+md_t *md_reg_find_overlap(md_reg_t *reg, const md_t *md, const char **pdomain, apr_pool_t *p);
+
+/**
+ * Get the md with the given unique name. NULL if it does not exist.
+ * Will update the md->state.
+ */
+md_t *md_reg_get(md_reg_t *reg, const char *name, apr_pool_t *p);
+
+/**
+ * Callback invoked for every md in the registry. If 0 is returned, iteration stops.
+ */
+typedef int md_reg_do_cb(void *baton, md_reg_t *reg, md_t *md);
+
+/**
+ * Invoke callback for all mds in this registry. Order is not guaranteed.
+ * If the callback returns 0, iteration stops. Returns 0 if iteration was
+ * aborted.
+ */
+int md_reg_do(md_reg_do_cb *cb, void *baton, md_reg_t *reg, apr_pool_t *p);
+
+/**
+ * Bitmask for fields that are updated.
+ */
+#define MD_UPD_DOMAINS 0x00001
+#define MD_UPD_CA_URL 0x00002
+#define MD_UPD_CA_PROTO 0x00004
+#define MD_UPD_CA_ACCOUNT 0x00008
+#define MD_UPD_CONTACTS 0x00010
+#define MD_UPD_AGREEMENT 0x00020
+#define MD_UPD_DRIVE_MODE 0x00080
+#define MD_UPD_RENEW_WINDOW 0x00100
+#define MD_UPD_CA_CHALLENGES 0x00200
+#define MD_UPD_PKEY_SPEC 0x00400
+#define MD_UPD_REQUIRE_HTTPS 0x00800
+#define MD_UPD_TRANSITIVE 0x01000
+#define MD_UPD_MUST_STAPLE 0x02000
+#define MD_UPD_PROTO 0x04000
+#define MD_UPD_WARN_WINDOW 0x08000
+#define MD_UPD_STAPLING 0x10000
+#define MD_UPD_ALL 0x7FFFFFFF
+
+/**
+ * Update the given fields for the managed domain. Take the new
+ * values from the given md, all other values remain unchanged.
+ */
+apr_status_t md_reg_update(md_reg_t *reg, apr_pool_t *p,
+ const char *name, const md_t *md,
+ int fields, int check_consistency);
+
+/**
+ * Get the chain of public certificates of the managed domain md, starting with the cert
+ * of the domain and going up the issuers. Returns APR_ENOENT when not available.
+ */
+apr_status_t md_reg_get_pubcert(const md_pubcert_t **ppubcert, md_reg_t *reg,
+ const md_t *md, int i, apr_pool_t *p);
+
+/**
+ * Get the filenames of private key and pubcert of the MD - if they exist.
+ * @return APR_ENOENT if one or both do not exist.
+ */
+apr_status_t md_reg_get_cred_files(const char **pkeyfile, const char **pcertfile,
+ md_reg_t *reg, md_store_group_t group,
+ const md_t *md, struct md_pkey_spec_t *spec, apr_pool_t *p);
+
+/**
+ * Synchronize the given master mds with the store.
+ */
+apr_status_t md_reg_sync_start(md_reg_t *reg, apr_array_header_t *master_mds, apr_pool_t *p);
+
+/**
+ * Re-compute the state of the MD, given current store contents.
+ */
+apr_status_t md_reg_sync_finish(md_reg_t *reg, md_t *md, apr_pool_t *p, apr_pool_t *ptemp);
+
+
+apr_status_t md_reg_remove(md_reg_t *reg, apr_pool_t *p, const char *name, int archive);
+
+/**
+ * Delete the account from the local store.
+ */
+apr_status_t md_reg_delete_acct(md_reg_t *reg, apr_pool_t *p, const char *acct_id);
+
+
+/**
+ * Cleanup any challenges that are no longer in use.
+ *
+ * @param reg the registry
+ * @param p pool for permanent storage
+ * @param ptemp pool for temporary storage
+ * @param mds the list of configured MDs
+ */
+apr_status_t md_reg_cleanup_challenges(md_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp,
+ apr_array_header_t *mds);
+
+/**
+ * Mark all information from group MD_SG_DOMAINS as readonly, deny future modifications
+ * (MD_SG_STAGING and MD_SG_CHALLENGES remain writeable). For the given MDs, cache
+ * the public information (MDs themselves and their pubcerts or lack of).
+ */
+apr_status_t md_reg_freeze_domains(md_reg_t *reg, apr_array_header_t *mds);
+
+/**
+ * Return if the certificate of the MD should be renewed. This includes reaching
+ * the renewal window of an otherwise valid certificate. It return also !0 iff
+ * no certificate has been obtained yet.
+ */
+int md_reg_should_renew(md_reg_t *reg, const md_t *md, apr_pool_t *p);
+
+/**
+ * Return the timestamp when the certificate should be renewed. A value of 0
+ * indicates that that renewal is not configured (see renew_mode).
+ */
+apr_time_t md_reg_renew_at(md_reg_t *reg, const md_t *md, apr_pool_t *p);
+
+/**
+ * Return the timestamp up to which *all* certificates for the MD can be used.
+ * A value of 0 indicates that there is no certificate.
+ */
+apr_time_t md_reg_valid_until(md_reg_t *reg, const md_t *md, apr_pool_t *p);
+
+/**
+ * Return if a warning should be issued about the certificate expiration.
+ * This applies the configured warn window to the remaining lifetime of the
+ * current certiciate. If no certificate is present, this returns 0.
+ */
+int md_reg_should_warn(md_reg_t *reg, const md_t *md, apr_pool_t *p);
+
+/**************************************************************************************************/
+/* protocol drivers */
+
+typedef struct md_proto_t md_proto_t;
+
+typedef struct md_proto_driver_t md_proto_driver_t;
+
+/**
+ * Operating environment for a protocol driver. This is valid only for the
+ * duration of one run (init + renew, init + preload).
+ */
+struct md_proto_driver_t {
+ const md_proto_t *proto;
+ apr_pool_t *p;
+ void *baton;
+ struct apr_table_t *env;
+
+ md_reg_t *reg;
+ md_store_t *store;
+ const char *proxy_url;
+ const char *ca_file;
+ const md_t *md;
+
+ int can_http;
+ int can_https;
+ int reset;
+ int attempt;
+ int retry_failover;
+ apr_interval_time_t activation_delay;
+};
+
+typedef apr_status_t md_proto_init_cb(md_proto_driver_t *driver, struct md_result_t *result);
+typedef apr_status_t md_proto_renew_cb(md_proto_driver_t *driver, struct md_result_t *result);
+typedef apr_status_t md_proto_init_preload_cb(md_proto_driver_t *driver, struct md_result_t *result);
+typedef apr_status_t md_proto_preload_cb(md_proto_driver_t *driver,
+ md_store_group_t group, struct md_result_t *result);
+typedef apr_status_t md_proto_complete_md_cb(md_t *md, apr_pool_t *p);
+
+struct md_proto_t {
+ const char *protocol;
+ md_proto_init_cb *init;
+ md_proto_renew_cb *renew;
+ md_proto_init_preload_cb *init_preload;
+ md_proto_preload_cb *preload;
+ md_proto_complete_md_cb *complete_md;
+};
+
+/**
+ * Run a test initialization of the renew protocol for the given MD. This verifies
+ * basic parameter settings and is expected to return a description of encountered
+ * problems in <pmessage> when != APR_SUCCESS.
+ * A message return is allocated fromt the given pool.
+ */
+apr_status_t md_reg_test_init(md_reg_t *reg, const md_t *md, struct apr_table_t *env,
+ struct md_result_t *result, apr_pool_t *p);
+
+/**
+ * Obtain new credentials for the given managed domain in STAGING.
+ * @param reg the registry instance
+ * @param md the mdomain to renew
+ * @param env global environment of settings
+ * @param reset != 0 if any previous, partial information should be wiped
+ * @param attempt the number of attempts made this far (for this md)
+ * @param result for reporting results of the renewal
+ * @param p the memory pool to use
+ * @return APR_SUCCESS if new credentials have been staged successfully
+ */
+apr_status_t md_reg_renew(md_reg_t *reg, const md_t *md,
+ struct apr_table_t *env, int reset, int attempt,
+ struct md_result_t *result, apr_pool_t *p);
+
+/**
+ * Load a new set of credentials for the managed domain from STAGING - if it exists.
+ * This will archive any existing credential data and make the staged set the new one
+ * in DOMAINS.
+ * If staging is incomplete or missing, the load will fail and all credentials remain
+ * as they are.
+ *
+ * @return APR_SUCCESS on loading new data, APR_ENOENT when nothing is staged, error otherwise.
+ */
+apr_status_t md_reg_load_staging(md_reg_t *reg, const md_t *md, struct apr_table_t *env,
+ struct md_result_t *result, apr_pool_t *p);
+
+/**
+ * Check given MDomains for new data in staging areas and, if it exists, load
+ * the new credentials. On encountering errors, leave the credentails as
+ * they are.
+ */
+apr_status_t md_reg_load_stagings(md_reg_t *reg, apr_array_header_t *mds,
+ apr_table_t *env, apr_pool_t *p);
+
+void md_reg_set_renew_window_default(md_reg_t *reg, md_timeslice_t *renew_window);
+void md_reg_set_warn_window_default(md_reg_t *reg, md_timeslice_t *warn_window);
+
+struct md_job_t *md_reg_job_make(md_reg_t *reg, const char *mdomain, apr_pool_t *p);
+
+/**
+ * Acquire a cooperative, global lock on registry modifications. Will
+ * do nothing if locking is not configured.
+ *
+ * This will only prevent other children/processes/cluster nodes from
+ * doing the same and does not protect individual store functions from
+ * being called without it.
+ * @param reg the registy
+ * @param p memory pool to use
+ * @param max_wait maximum time to wait in order to acquire
+ * @return APR_SUCCESS when lock was obtained
+ */
+apr_status_t md_reg_lock_global(md_reg_t *reg, apr_pool_t *p);
+
+/**
+ * Realease the global registry lock. Will do nothing if there is no lock.
+ */
+void md_reg_unlock_global(md_reg_t *reg, apr_pool_t *p);
+
+#endif /* mod_md_md_reg_h */
diff --git a/modules/md/md_result.c b/modules/md/md_result.c
new file mode 100644
index 0000000..64a2f70
--- /dev/null
+++ b/modules/md/md_result.c
@@ -0,0 +1,285 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <apr_lib.h>
+#include <apr_date.h>
+#include <apr_time.h>
+#include <apr_strings.h>
+
+#include "md.h"
+#include "md_json.h"
+#include "md_log.h"
+#include "md_result.h"
+
+static const char *dup_trim(apr_pool_t *p, const char *s)
+{
+ char *d = apr_pstrdup(p, s);
+ if (d) apr_collapse_spaces(d, d);
+ return d;
+}
+
+md_result_t *md_result_make(apr_pool_t *p, apr_status_t status)
+{
+ md_result_t *result;
+
+ result = apr_pcalloc(p, sizeof(*result));
+ result->p = p;
+ result->md_name = MD_OTHER;
+ result->status = status;
+ return result;
+}
+
+md_result_t *md_result_md_make(apr_pool_t *p, const char *md_name)
+{
+ md_result_t *result = md_result_make(p, APR_SUCCESS);
+ result->md_name = md_name;
+ return result;
+}
+
+void md_result_reset(md_result_t *result)
+{
+ apr_pool_t *p = result->p;
+ memset(result, 0, sizeof(*result));
+ result->p = p;
+}
+
+static void on_change(md_result_t *result)
+{
+ if (result->on_change) result->on_change(result, result->on_change_data);
+}
+
+void md_result_activity_set(md_result_t *result, const char *activity)
+{
+ md_result_activity_setn(result, activity? apr_pstrdup(result->p, activity) : NULL);
+}
+
+void md_result_activity_setn(md_result_t *result, const char *activity)
+{
+ result->activity = activity;
+ result->problem = result->detail = NULL;
+ result->subproblems = NULL;
+ on_change(result);
+}
+
+void md_result_activity_printf(md_result_t *result, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ md_result_activity_setn(result, apr_pvsprintf(result->p, fmt, ap));
+ va_end(ap);
+}
+
+void md_result_set(md_result_t *result, apr_status_t status, const char *detail)
+{
+ result->status = status;
+ result->problem = NULL;
+ result->detail = detail? apr_pstrdup(result->p, detail) : NULL;
+ result->subproblems = NULL;
+ on_change(result);
+}
+
+void md_result_problem_set(md_result_t *result, apr_status_t status,
+ const char *problem, const char *detail,
+ const md_json_t *subproblems)
+{
+ result->status = status;
+ result->problem = dup_trim(result->p, problem);
+ result->detail = apr_pstrdup(result->p, detail);
+ result->subproblems = subproblems? md_json_clone(result->p, subproblems) : NULL;
+ on_change(result);
+}
+
+void md_result_problem_printf(md_result_t *result, apr_status_t status,
+ const char *problem, const char *fmt, ...)
+{
+ va_list ap;
+
+ result->status = status;
+ result->problem = dup_trim(result->p, problem);
+
+ va_start(ap, fmt);
+ result->detail = apr_pvsprintf(result->p, fmt, ap);
+ va_end(ap);
+ result->subproblems = NULL;
+ on_change(result);
+}
+
+void md_result_printf(md_result_t *result, apr_status_t status, const char *fmt, ...)
+{
+ va_list ap;
+
+ result->status = status;
+ va_start(ap, fmt);
+ result->detail = apr_pvsprintf(result->p, fmt, ap);
+ va_end(ap);
+ result->subproblems = NULL;
+ on_change(result);
+}
+
+void md_result_delay_set(md_result_t *result, apr_time_t ready_at)
+{
+ result->ready_at = ready_at;
+ on_change(result);
+}
+
+md_result_t*md_result_from_json(const struct md_json_t *json, apr_pool_t *p)
+{
+ md_result_t *result;
+ const char *s;
+
+ result = md_result_make(p, APR_SUCCESS);
+ result->status = (int)md_json_getl(json, MD_KEY_STATUS, NULL);
+ result->problem = md_json_dups(p, json, MD_KEY_PROBLEM, NULL);
+ result->detail = md_json_dups(p, json, MD_KEY_DETAIL, NULL);
+ result->activity = md_json_dups(p, json, MD_KEY_ACTIVITY, NULL);
+ s = md_json_dups(p, json, MD_KEY_VALID_FROM, NULL);
+ if (s && *s) result->ready_at = apr_date_parse_rfc(s);
+ result->subproblems = md_json_dupj(p, json, MD_KEY_SUBPROBLEMS, NULL);
+ return result;
+}
+
+struct md_json_t *md_result_to_json(const md_result_t *result, apr_pool_t *p)
+{
+ md_json_t *json;
+ char ts[APR_RFC822_DATE_LEN];
+
+ json = md_json_create(p);
+ md_json_setl(result->status, json, MD_KEY_STATUS, NULL);
+ if (result->status > 0) {
+ char buffer[HUGE_STRING_LEN];
+ apr_strerror(result->status, buffer, sizeof(buffer));
+ md_json_sets(buffer, json, "status-description", NULL);
+ }
+ if (result->problem) md_json_sets(result->problem, json, MD_KEY_PROBLEM, NULL);
+ if (result->detail) md_json_sets(result->detail, json, MD_KEY_DETAIL, NULL);
+ if (result->activity) md_json_sets(result->activity, json, MD_KEY_ACTIVITY, NULL);
+ if (result->ready_at > 0) {
+ apr_rfc822_date(ts, result->ready_at);
+ md_json_sets(ts, json, MD_KEY_VALID_FROM, NULL);
+ }
+ if (result->subproblems) {
+ md_json_setj(result->subproblems, json, MD_KEY_SUBPROBLEMS, NULL);
+ }
+ return json;
+}
+
+static int str_cmp(const char *s1, const char *s2)
+{
+ if (s1 == s2) return 0;
+ if (!s1) return -1;
+ if (!s2) return 1;
+ return strcmp(s1, s2);
+}
+
+int md_result_cmp(const md_result_t *r1, const md_result_t *r2)
+{
+ int n;
+ if (r1 == r2) return 0;
+ if (!r1) return -1;
+ if (!r2) return 1;
+ if ((n = r1->status - r2->status)) return n;
+ if ((n = str_cmp(r1->problem, r2->problem))) return n;
+ if ((n = str_cmp(r1->detail, r2->detail))) return n;
+ if ((n = str_cmp(r1->activity, r2->activity))) return n;
+ return (int)(r1->ready_at - r2->ready_at);
+}
+
+void md_result_assign(md_result_t *dest, const md_result_t *src)
+{
+ dest->status = src->status;
+ dest->problem = src->problem;
+ dest->detail = src->detail;
+ dest->activity = src->activity;
+ dest->ready_at = src->ready_at;
+ dest->subproblems = src->subproblems;
+}
+
+void md_result_dup(md_result_t *dest, const md_result_t *src)
+{
+ dest->status = src->status;
+ dest->problem = src->problem? dup_trim(dest->p, src->problem) : NULL;
+ dest->detail = src->detail? apr_pstrdup(dest->p, src->detail) : NULL;
+ dest->activity = src->activity? apr_pstrdup(dest->p, src->activity) : NULL;
+ dest->ready_at = src->ready_at;
+ dest->subproblems = src->subproblems? md_json_clone(dest->p, src->subproblems) : NULL;
+ on_change(dest);
+}
+
+void md_result_log(md_result_t *result, unsigned int level)
+{
+ if (md_log_is_level(result->p, (md_log_level_t)level)) {
+ const char *sep = "";
+ const char *msg = "";
+
+ if (result->md_name) {
+ msg = apr_psprintf(result->p, "md[%s]", result->md_name);
+ sep = " ";
+ }
+ if (result->activity) {
+ msg = apr_psprintf(result->p, "%s%swhile[%s]", msg, sep, result->activity);
+ sep = " ";
+ }
+ if (result->problem) {
+ msg = apr_psprintf(result->p, "%s%sproblem[%s]", msg, sep, result->problem);
+ sep = " ";
+ }
+ if (result->detail) {
+ msg = apr_psprintf(result->p, "%s%sdetail[%s]", msg, sep, result->detail);
+ sep = " ";
+ }
+ if (result->subproblems) {
+ msg = apr_psprintf(result->p, "%s%ssubproblems[%s]", msg, sep,
+ md_json_writep(result->subproblems, result->p, MD_JSON_FMT_COMPACT));
+ sep = " ";
+ }
+ md_log_perror(MD_LOG_MARK, (md_log_level_t)level, result->status, result->p, "%s", msg);
+ }
+}
+
+void md_result_on_change(md_result_t *result, md_result_change_cb *cb, void *data)
+{
+ result->on_change = cb;
+ result->on_change_data = data;
+}
+
+apr_status_t md_result_raise(md_result_t *result, const char *event, apr_pool_t *p)
+{
+ if (result->on_raise) return result->on_raise(result, result->on_raise_data, event, p);
+ return APR_SUCCESS;
+}
+
+void md_result_holler(md_result_t *result, const char *event, apr_pool_t *p)
+{
+ if (result->on_holler) result->on_holler(result, result->on_holler_data, event, p);
+}
+
+void md_result_on_raise(md_result_t *result, md_result_raise_cb *cb, void *data)
+{
+ result->on_raise = cb;
+ result->on_raise_data = data;
+}
+
+void md_result_on_holler(md_result_t *result, md_result_holler_cb *cb, void *data)
+{
+ result->on_holler = cb;
+ result->on_holler_data = data;
+}
diff --git a/modules/md/md_result.h b/modules/md/md_result.h
new file mode 100644
index 0000000..e83bdd2
--- /dev/null
+++ b/modules/md/md_result.h
@@ -0,0 +1,87 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mod_md_md_result_h
+#define mod_md_md_result_h
+
+struct md_json_t;
+struct md_t;
+
+typedef struct md_result_t md_result_t;
+
+typedef void md_result_change_cb(md_result_t *result, void *data);
+typedef apr_status_t md_result_raise_cb(md_result_t *result, void *data, const char *event, apr_pool_t *p);
+typedef void md_result_holler_cb(md_result_t *result, void *data, const char *event, apr_pool_t *p);
+
+struct md_result_t {
+ apr_pool_t *p;
+ const char *md_name;
+ apr_status_t status;
+ const char *problem;
+ const char *detail;
+ const struct md_json_t *subproblems;
+ const char *activity;
+ apr_time_t ready_at;
+ md_result_change_cb *on_change;
+ void *on_change_data;
+ md_result_raise_cb *on_raise;
+ void *on_raise_data;
+ md_result_holler_cb *on_holler;
+ void *on_holler_data;
+};
+
+md_result_t *md_result_make(apr_pool_t *p, apr_status_t status);
+md_result_t *md_result_md_make(apr_pool_t *p, const char *md_name);
+void md_result_reset(md_result_t *result);
+
+void md_result_activity_set(md_result_t *result, const char *activity);
+void md_result_activity_setn(md_result_t *result, const char *activity);
+void md_result_activity_printf(md_result_t *result, const char *fmt, ...);
+
+void md_result_set(md_result_t *result, apr_status_t status, const char *detail);
+void md_result_problem_set(md_result_t *result, apr_status_t status,
+ const char *problem, const char *detail,
+ const struct md_json_t *subproblems);
+void md_result_problem_printf(md_result_t *result, apr_status_t status,
+ const char *problem, const char *fmt, ...);
+
+#define MD_RESULT_LOG_ID(logno) "urn:org:apache:httpd:log:"logno
+
+void md_result_printf(md_result_t *result, apr_status_t status, const char *fmt, ...);
+
+void md_result_delay_set(md_result_t *result, apr_time_t ready_at);
+
+md_result_t*md_result_from_json(const struct md_json_t *json, apr_pool_t *p);
+struct md_json_t *md_result_to_json(const md_result_t *result, apr_pool_t *p);
+
+int md_result_cmp(const md_result_t *r1, const md_result_t *r2);
+
+void md_result_assign(md_result_t *dest, const md_result_t *src);
+void md_result_dup(md_result_t *dest, const md_result_t *src);
+
+void md_result_log(md_result_t *result, unsigned int level);
+
+void md_result_on_change(md_result_t *result, md_result_change_cb *cb, void *data);
+
+/* events in the context of a result genesis */
+
+apr_status_t md_result_raise(md_result_t *result, const char *event, apr_pool_t *p);
+void md_result_holler(md_result_t *result, const char *event, apr_pool_t *p);
+
+void md_result_on_raise(md_result_t *result, md_result_raise_cb *cb, void *data);
+void md_result_on_holler(md_result_t *result, md_result_holler_cb *cb, void *data);
+
+#endif /* mod_md_md_result_h */
diff --git a/modules/md/md_status.c b/modules/md/md_status.c
new file mode 100644
index 0000000..936c653
--- /dev/null
+++ b/modules/md/md_status.c
@@ -0,0 +1,653 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include <apr_lib.h>
+#include <apr_strings.h>
+#include <apr_tables.h>
+#include <apr_time.h>
+#include <apr_date.h>
+
+#include "md_json.h"
+#include "md.h"
+#include "md_acme.h"
+#include "md_crypt.h"
+#include "md_event.h"
+#include "md_log.h"
+#include "md_ocsp.h"
+#include "md_store.h"
+#include "md_result.h"
+#include "md_reg.h"
+#include "md_util.h"
+#include "md_status.h"
+
+#define MD_STATUS_WITH_SCTS 0
+
+/**************************************************************************************************/
+/* certificate status information */
+
+static apr_status_t status_get_cert_json(md_json_t **pjson, const md_cert_t *cert, apr_pool_t *p)
+{
+ const char *finger;
+ apr_status_t rv = APR_SUCCESS;
+ md_timeperiod_t valid;
+ md_json_t *json;
+
+ json = md_json_create(p);
+ valid.start = md_cert_get_not_before(cert);
+ valid.end = md_cert_get_not_after(cert);
+ md_json_set_timeperiod(&valid, json, MD_KEY_VALID, NULL);
+ md_json_sets(md_cert_get_serial_number(cert, p), json, MD_KEY_SERIAL, NULL);
+ if (APR_SUCCESS != (rv = md_cert_to_sha256_fingerprint(&finger, cert, p))) goto leave;
+ md_json_sets(finger, json, MD_KEY_SHA256_FINGERPRINT, NULL);
+
+#if MD_STATUS_WITH_SCTS
+ do {
+ apr_array_header_t *scts;
+ const char *hex;
+ const md_sct *sct;
+ md_json_t *sctj;
+ int i;
+
+ scts = apr_array_make(p, 5, sizeof(const md_sct*));
+ if (APR_SUCCESS == md_cert_get_ct_scts(scts, p, cert)) {
+ for (i = 0; i < scts->nelts; ++i) {
+ sct = APR_ARRAY_IDX(scts, i, const md_sct*);
+ sctj = md_json_create(p);
+
+ apr_rfc822_date(ts, sct->timestamp);
+ md_json_sets(ts, sctj, "signed", NULL);
+ md_json_setl(sct->version, sctj, MD_KEY_VERSION, NULL);
+ md_data_to_hex(&hex, 0, p, sct->logid);
+ md_json_sets(hex, sctj, "logid", NULL);
+ md_data_to_hex(&hex, 0, p, sct->signature);
+ md_json_sets(hex, sctj, "signature", NULL);
+ md_json_sets(md_nid_get_sname(sct->signature_type_nid), sctj, "signature-type", NULL);
+ md_json_addj(sctj, json, "scts", NULL);
+ }
+ }
+ while (0);
+#endif
+leave:
+ *pjson = (APR_SUCCESS == rv)? json : NULL;
+ return rv;
+}
+
+static apr_status_t job_loadj(md_json_t **pjson, md_store_group_t group, const char *name,
+ struct md_reg_t *reg, int with_log, apr_pool_t *p)
+{
+ apr_status_t rv;
+
+ md_store_t *store = md_reg_store_get(reg);
+ rv = md_store_load_json(store, group, name, MD_FN_JOB, pjson, p);
+ if (APR_SUCCESS == rv && !with_log) md_json_del(*pjson, MD_KEY_LOG, NULL);
+ return rv;
+}
+
+static apr_status_t status_get_cert_json_ex(
+ md_json_t **pjson,
+ const md_cert_t *cert,
+ const md_t *md,
+ md_reg_t *reg,
+ md_ocsp_reg_t *ocsp,
+ int with_logs,
+ apr_pool_t *p)
+{
+ md_json_t *certj, *jobj;
+ md_timeperiod_t ocsp_valid;
+ md_ocsp_cert_stat_t cert_stat;
+ apr_status_t rv;
+
+ if (APR_SUCCESS != (rv = status_get_cert_json(&certj, cert, p))) goto leave;
+ if (md->stapling && ocsp) {
+ rv = md_ocsp_get_meta(&cert_stat, &ocsp_valid, ocsp, cert, p, md);
+ if (APR_SUCCESS == rv) {
+ md_json_sets(md_ocsp_cert_stat_name(cert_stat), certj, MD_KEY_OCSP, MD_KEY_STATUS, NULL);
+ md_json_set_timeperiod(&ocsp_valid, certj, MD_KEY_OCSP, MD_KEY_VALID, NULL);
+ }
+ else if (!APR_STATUS_IS_ENOENT(rv)) goto leave;
+ rv = APR_SUCCESS;
+ if (APR_SUCCESS == job_loadj(&jobj, MD_SG_OCSP, md->name, reg, with_logs, p)) {
+ md_json_setj(jobj, certj, MD_KEY_OCSP, MD_KEY_RENEWAL, NULL);
+ }
+ }
+leave:
+ *pjson = (APR_SUCCESS == rv)? certj : NULL;
+ return rv;
+}
+
+static int get_cert_count(const md_t *md, int from_staging)
+{
+ if (!from_staging && md->cert_files && md->cert_files->nelts) {
+ return md->cert_files->nelts;
+ }
+ return md_pkeys_spec_count(md->pks);
+}
+
+static const char *get_cert_name(const md_t *md, int i, int from_staging, apr_pool_t *p)
+{
+ if (!from_staging && md->cert_files && md->cert_files->nelts) {
+ /* static files configured, not from staging, used index names */
+ return apr_psprintf(p, "%d", i);
+ }
+ return md_pkey_spec_name(md_pkeys_spec_get(md->pks, i));
+}
+
+static apr_status_t status_get_certs_json(md_json_t **pjson, apr_array_header_t *certs,
+ int from_staging,
+ const md_t *md, md_reg_t *reg,
+ md_ocsp_reg_t *ocsp, int with_logs,
+ apr_pool_t *p)
+{
+ md_json_t *json, *certj;
+ md_timeperiod_t certs_valid = {0, 0}, valid;
+ md_cert_t *cert;
+ int i;
+ apr_status_t rv = APR_SUCCESS;
+
+ json = md_json_create(p);
+ for (i = 0; i < get_cert_count(md, from_staging); ++i) {
+ cert = APR_ARRAY_IDX(certs, i, md_cert_t*);
+ if (!cert) continue;
+
+ rv = status_get_cert_json_ex(&certj, cert, md, reg, ocsp, with_logs, p);
+ if (APR_SUCCESS != rv) goto leave;
+ valid = md_cert_get_valid(cert);
+ certs_valid = i? md_timeperiod_common(&certs_valid, &valid) : valid;
+ md_json_setj(certj, json, get_cert_name(md, i, from_staging, p), NULL);
+ }
+
+ if (certs_valid.start) {
+ md_json_set_timeperiod(&certs_valid, json, MD_KEY_VALID, NULL);
+ }
+leave:
+ *pjson = (APR_SUCCESS == rv)? json : NULL;
+ return rv;
+}
+
+static apr_status_t get_staging_certs_json(md_json_t **pjson, const md_t *md,
+ md_reg_t *reg, apr_pool_t *p)
+{
+ md_pkey_spec_t *spec;
+ int i;
+ apr_array_header_t *chain, *certs;
+ const md_cert_t *cert;
+ apr_status_t rv;
+
+ certs = apr_array_make(p, 5, sizeof(md_cert_t*));
+ for (i = 0; i < get_cert_count(md, 1); ++i) {
+ spec = md_pkeys_spec_get(md->pks, i);
+ cert = NULL;
+ rv = md_pubcert_load(md_reg_store_get(reg), MD_SG_STAGING, md->name, spec, &chain, p);
+ if (APR_SUCCESS == rv) {
+ cert = APR_ARRAY_IDX(chain, 0, const md_cert_t*);
+ }
+ APR_ARRAY_PUSH(certs, const md_cert_t*) = cert;
+ }
+ return status_get_certs_json(pjson, certs, 1, md, reg, NULL, 0, p);
+}
+
+static apr_status_t status_get_md_json(md_json_t **pjson, const md_t *md,
+ md_reg_t *reg, md_ocsp_reg_t *ocsp,
+ int with_logs, apr_pool_t *p)
+{
+ md_json_t *mdj, *certsj, *jobj;
+ int renew;
+ const md_pubcert_t *pubcert;
+ const md_cert_t *cert = NULL;
+ apr_array_header_t *certs;
+ apr_status_t rv = APR_SUCCESS;
+ apr_time_t renew_at;
+ int i;
+
+ mdj = md_to_public_json(md, p);
+ certs = apr_array_make(p, 5, sizeof(md_cert_t*));
+ for (i = 0; i < get_cert_count(md, 0); ++i) {
+ cert = NULL;
+ if (APR_SUCCESS == md_reg_get_pubcert(&pubcert, reg, md, i, p)) {
+ cert = APR_ARRAY_IDX(pubcert->certs, 0, const md_cert_t*);
+ }
+ APR_ARRAY_PUSH(certs, const md_cert_t*) = cert;
+ }
+
+ rv = status_get_certs_json(&certsj, certs, 0, md, reg, ocsp, with_logs, p);
+ if (APR_SUCCESS != rv) goto leave;
+ md_json_setj(certsj, mdj, MD_KEY_CERT, NULL);
+
+ renew_at = md_reg_renew_at(reg, md, p);
+ if (renew_at > 0) {
+ md_json_set_time(renew_at, mdj, MD_KEY_RENEW_AT, NULL);
+ }
+
+ md_json_setb(md->stapling, mdj, MD_KEY_STAPLING, NULL);
+ md_json_setb(md->watched, mdj, MD_KEY_WATCHED, NULL);
+ renew = md_reg_should_renew(reg, md, p);
+ if (renew) {
+ md_json_setb(renew, mdj, MD_KEY_RENEW, NULL);
+ rv = job_loadj(&jobj, MD_SG_STAGING, md->name, reg, with_logs, p);
+ if (APR_SUCCESS == rv) {
+ if (APR_SUCCESS == get_staging_certs_json(&certsj, md, reg, p)) {
+ md_json_setj(certsj, jobj, MD_KEY_CERT, NULL);
+ }
+ md_json_setj(jobj, mdj, MD_KEY_RENEWAL, NULL);
+ }
+ else if (APR_STATUS_IS_ENOENT(rv)) rv = APR_SUCCESS;
+ else goto leave;
+ }
+
+leave:
+ if (APR_SUCCESS != rv) {
+ md_json_setl(rv, mdj, MD_KEY_ERROR, NULL);
+ }
+ *pjson = mdj;
+ return rv;
+}
+
+apr_status_t md_status_get_md_json(md_json_t **pjson, const md_t *md,
+ md_reg_t *reg, md_ocsp_reg_t *ocsp, apr_pool_t *p)
+{
+ return status_get_md_json(pjson, md, reg, ocsp, 1, p);
+}
+
+apr_status_t md_status_get_json(md_json_t **pjson, apr_array_header_t *mds,
+ md_reg_t *reg, md_ocsp_reg_t *ocsp, apr_pool_t *p)
+{
+ md_json_t *json, *mdj;
+ const md_t *md;
+ int i;
+
+ json = md_json_create(p);
+ md_json_sets(MOD_MD_VERSION, json, MD_KEY_VERSION, NULL);
+ for (i = 0; i < mds->nelts; ++i) {
+ md = APR_ARRAY_IDX(mds, i, const md_t *);
+ status_get_md_json(&mdj, md, reg, ocsp, 0, p);
+ md_json_addj(mdj, json, MD_KEY_MDS, NULL);
+ }
+ *pjson = json;
+ return APR_SUCCESS;
+}
+
+/**************************************************************************************************/
+/* drive job persistence */
+
+md_job_t *md_job_make(apr_pool_t *p, md_store_t *store,
+ md_store_group_t group, const char *name,
+ apr_time_t min_delay)
+{
+ md_job_t *job = apr_pcalloc(p, sizeof(*job));
+ job->group = group;
+ job->mdomain = apr_pstrdup(p, name);
+ job->store = store;
+ job->p = p;
+ job->max_log = 128;
+ job->min_delay = min_delay;
+ return job;
+}
+
+void md_job_set_group(md_job_t *job, md_store_group_t group)
+{
+ job->group = group;
+}
+
+static void md_job_from_json(md_job_t *job, md_json_t *json, apr_pool_t *p)
+{
+ const char *s;
+
+ /* not good, this is malloced from a temp pool */
+ /*job->mdomain = md_json_gets(json, MD_KEY_NAME, NULL);*/
+ job->finished = md_json_getb(json, MD_KEY_FINISHED, NULL);
+ job->notified = md_json_getb(json, MD_KEY_NOTIFIED, NULL);
+ job->notified_renewed = md_json_getb(json, MD_KEY_NOTIFIED_RENEWED, NULL);
+ s = md_json_dups(p, json, MD_KEY_NEXT_RUN, NULL);
+ if (s && *s) job->next_run = apr_date_parse_rfc(s);
+ s = md_json_dups(p, json, MD_KEY_LAST_RUN, NULL);
+ if (s && *s) job->last_run = apr_date_parse_rfc(s);
+ s = md_json_dups(p, json, MD_KEY_VALID_FROM, NULL);
+ if (s && *s) job->valid_from = apr_date_parse_rfc(s);
+ job->error_runs = (int)md_json_getl(json, MD_KEY_ERRORS, NULL);
+ if (md_json_has_key(json, MD_KEY_LAST, NULL)) {
+ job->last_result = md_result_from_json(md_json_getcj(json, MD_KEY_LAST, NULL), p);
+ }
+ job->log = md_json_getj(json, MD_KEY_LOG, NULL);
+}
+
+static void job_to_json(md_json_t *json, const md_job_t *job,
+ md_result_t *result, apr_pool_t *p)
+{
+ char ts[APR_RFC822_DATE_LEN];
+
+ md_json_sets(job->mdomain, json, MD_KEY_NAME, NULL);
+ md_json_setb(job->finished, json, MD_KEY_FINISHED, NULL);
+ md_json_setb(job->notified, json, MD_KEY_NOTIFIED, NULL);
+ md_json_setb(job->notified_renewed, json, MD_KEY_NOTIFIED_RENEWED, NULL);
+ if (job->next_run > 0) {
+ apr_rfc822_date(ts, job->next_run);
+ md_json_sets(ts, json, MD_KEY_NEXT_RUN, NULL);
+ }
+ if (job->last_run > 0) {
+ apr_rfc822_date(ts, job->last_run);
+ md_json_sets(ts, json, MD_KEY_LAST_RUN, NULL);
+ }
+ if (job->valid_from > 0) {
+ apr_rfc822_date(ts, job->valid_from);
+ md_json_sets(ts, json, MD_KEY_VALID_FROM, NULL);
+ }
+ md_json_setl(job->error_runs, json, MD_KEY_ERRORS, NULL);
+ if (!result) result = job->last_result;
+ if (result) {
+ md_json_setj(md_result_to_json(result, p), json, MD_KEY_LAST, NULL);
+ }
+ if (job->log) md_json_setj(job->log, json, MD_KEY_LOG, NULL);
+}
+
+apr_status_t md_job_load(md_job_t *job)
+{
+ md_json_t *jprops;
+ apr_status_t rv;
+
+ rv = md_store_load_json(job->store, job->group, job->mdomain, MD_FN_JOB, &jprops, job->p);
+ if (APR_SUCCESS == rv) {
+ md_job_from_json(job, jprops, job->p);
+ }
+ return rv;
+}
+
+apr_status_t md_job_save(md_job_t *job, md_result_t *result, apr_pool_t *p)
+{
+ md_json_t *jprops;
+ apr_status_t rv;
+
+ jprops = md_json_create(p);
+ job_to_json(jprops, job, result, p);
+ rv = md_store_save_json(job->store, p, job->group, job->mdomain, MD_FN_JOB, jprops, 0);
+ if (APR_SUCCESS == rv) job->dirty = 0;
+ return rv;
+}
+
+void md_job_log_append(md_job_t *job, const char *type,
+ const char *status, const char *detail)
+{
+ md_json_t *entry;
+ char ts[APR_RFC822_DATE_LEN];
+
+ entry = md_json_create(job->p);
+ apr_rfc822_date(ts, apr_time_now());
+ md_json_sets(ts, entry, MD_KEY_WHEN, NULL);
+ md_json_sets(type, entry, MD_KEY_TYPE, NULL);
+ if (status) md_json_sets(status, entry, MD_KEY_STATUS, NULL);
+ if (detail) md_json_sets(detail, entry, MD_KEY_DETAIL, NULL);
+ if (!job->log) job->log = md_json_create(job->p);
+ md_json_insertj(entry, 0, job->log, MD_KEY_ENTRIES, NULL);
+ md_json_limita(job->max_log, job->log, MD_KEY_ENTRIES, NULL);
+ job->dirty = 1;
+}
+
+typedef struct {
+ md_job_t *job;
+ const char *type;
+ md_json_t *entry;
+ size_t index;
+} log_find_ctx;
+
+static int find_first_log_entry(void *baton, size_t index, md_json_t *entry)
+{
+ log_find_ctx *ctx = baton;
+ const char *etype;
+
+ etype = md_json_gets(entry, MD_KEY_TYPE, NULL);
+ if (etype == ctx->type || (etype && ctx->type && !strcmp(etype, ctx->type))) {
+ ctx->entry = entry;
+ ctx->index = index;
+ return 0;
+ }
+ return 1;
+}
+
+md_json_t *md_job_log_get_latest(md_job_t *job, const char *type)
+
+{
+ log_find_ctx ctx;
+
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.job = job;
+ ctx.type = type;
+ if (job->log) md_json_itera(find_first_log_entry, &ctx, job->log, MD_KEY_ENTRIES, NULL);
+ return ctx.entry;
+}
+
+apr_time_t md_job_log_get_time_of_latest(md_job_t *job, const char *type)
+{
+ md_json_t *entry;
+ const char *s;
+
+ entry = md_job_log_get_latest(job, type);
+ if (entry) {
+ s = md_json_gets(entry, MD_KEY_WHEN, NULL);
+ if (s) return apr_date_parse_rfc(s);
+ }
+ return 0;
+}
+
+void md_status_take_stock(md_json_t **pjson, apr_array_header_t *mds,
+ md_reg_t *reg, apr_pool_t *p)
+{
+ const md_t *md;
+ md_job_t *job;
+ int i, complete, renewing, errored, ready, total;
+ md_json_t *json;
+
+ json = md_json_create(p);
+ complete = renewing = errored = ready = total = 0;
+ for (i = 0; i < mds->nelts; ++i) {
+ md = APR_ARRAY_IDX(mds, i, const md_t *);
+ ++total;
+ switch (md->state) {
+ case MD_S_COMPLETE: ++complete; /* fall through */
+ case MD_S_INCOMPLETE:
+ if (md_reg_should_renew(reg, md, p)) {
+ ++renewing;
+ job = md_reg_job_make(reg, md->name, p);
+ if (APR_SUCCESS == md_job_load(job)) {
+ if (job->error_runs > 0
+ || (job->last_result && job->last_result->status != APR_SUCCESS)) {
+ ++errored;
+ }
+ else if (job->finished) {
+ ++ready;
+ }
+ }
+ }
+ break;
+ default: ++errored; break;
+ }
+ }
+ md_json_setl(total, json, MD_KEY_TOTAL, NULL);
+ md_json_setl(complete, json, MD_KEY_COMPLETE, NULL);
+ md_json_setl(renewing, json, MD_KEY_RENEWING, NULL);
+ md_json_setl(errored, json, MD_KEY_ERRORED, NULL);
+ md_json_setl(ready, json, MD_KEY_READY, NULL);
+ *pjson = json;
+}
+
+typedef struct {
+ apr_pool_t *p;
+ md_job_t *job;
+ md_store_t *store;
+ md_result_t *last;
+ apr_time_t last_save;
+} md_job_result_ctx;
+
+static void job_result_update(md_result_t *result, void *data)
+{
+ md_job_result_ctx *ctx = data;
+ apr_time_t now;
+ const char *msg, *sep;
+
+ if (md_result_cmp(ctx->last, result)) {
+ now = apr_time_now();
+ md_result_assign(ctx->last, result);
+ if (result->activity || result->problem || result->detail) {
+ msg = sep = "";
+ if (result->activity) {
+ msg = apr_psprintf(result->p, "%s", result->activity);
+ sep = ": ";
+ }
+ if (result->detail) {
+ msg = apr_psprintf(result->p, "%s%s%s", msg, sep, result->detail);
+ sep = ", ";
+ }
+ if (result->problem) {
+ msg = apr_psprintf(result->p, "%s%sproblem: %s", msg, sep, result->problem);
+ sep = " ";
+ }
+ md_job_log_append(ctx->job, "progress", NULL, msg);
+
+ if (ctx->store && apr_time_as_msec(now - ctx->last_save) > 500) {
+ md_job_save(ctx->job, result, ctx->p);
+ ctx->last_save = now;
+ }
+ }
+ }
+}
+
+static apr_status_t job_result_raise(md_result_t *result, void *data, const char *event, apr_pool_t *p)
+{
+ md_job_result_ctx *ctx = data;
+ (void)p;
+ if (result == ctx->job->observing) {
+ return md_job_notify(ctx->job, event, result);
+ }
+ return APR_SUCCESS;
+}
+
+static void job_result_holler(md_result_t *result, void *data, const char *event, apr_pool_t *p)
+{
+ md_job_result_ctx *ctx = data;
+ if (result == ctx->job->observing) {
+ md_event_holler(event, ctx->job->mdomain, ctx->job, result, p);
+ }
+}
+
+static void job_observation_start(md_job_t *job, md_result_t *result, md_store_t *store)
+{
+ md_job_result_ctx *ctx;
+
+ if (job->observing) md_result_on_change(job->observing, NULL, NULL);
+ job->observing = result;
+
+ ctx = apr_pcalloc(result->p, sizeof(*ctx));
+ ctx->p = result->p;
+ ctx->job = job;
+ ctx->store = store;
+ ctx->last = md_result_md_make(result->p, APR_SUCCESS);
+ md_result_assign(ctx->last, result);
+ md_result_on_change(result, job_result_update, ctx);
+ md_result_on_raise(result, job_result_raise, ctx);
+ md_result_on_holler(result, job_result_holler, ctx);
+}
+
+static void job_observation_end(md_job_t *job)
+{
+ if (job->observing) md_result_on_change(job->observing, NULL, NULL);
+ job->observing = NULL;
+}
+
+void md_job_start_run(md_job_t *job, md_result_t *result, md_store_t *store)
+{
+ job->fatal_error = 0;
+ job->last_run = apr_time_now();
+ job_observation_start(job, result, store);
+ md_job_log_append(job, "starting", NULL, NULL);
+}
+
+apr_time_t md_job_delay_on_errors(md_job_t *job, int err_count, const char *last_problem)
+{
+ apr_time_t delay = 0, max_delay = apr_time_from_sec(24*60*60); /* daily */
+ unsigned char c;
+
+ if (last_problem && md_acme_problem_is_input_related(last_problem)) {
+ /* If ACME server reported a problem and that problem indicates that our
+ * input values, e.g. our configuration, has something wrong, we always
+ * go to max delay as frequent retries are unlikely to resolve the situation.
+ * However, we should nevertheless retry daily, bc. it might be that there
+ * is a bug in the server. Unlikely, but... */
+ delay = max_delay;
+ }
+ else if (err_count > 0) {
+ /* back off duration, depending on the errors we encounter in a row */
+ delay = job->min_delay << (err_count - 1);
+ if (delay > max_delay) {
+ delay = max_delay;
+ }
+ }
+ if (delay > 0) {
+ /* jitter the delay by +/- 0-50%.
+ * Background: we see retries of jobs being too regular (e.g. all at midnight),
+ * possibly cumulating from many installations that restart their Apache at a
+ * fixed hour. This can contribute to an overload at the CA and a continuation
+ * of failure.
+ */
+ md_rand_bytes(&c, sizeof(c), job->p);
+ delay += apr_time_from_sec((apr_time_sec(delay) * (c - 128)) / 256);
+ }
+ return delay;
+}
+
+void md_job_end_run(md_job_t *job, md_result_t *result)
+{
+ if (APR_SUCCESS == result->status) {
+ job->finished = 1;
+ job->valid_from = result->ready_at;
+ job->error_runs = 0;
+ job->dirty = 1;
+ md_job_log_append(job, "finished", NULL, NULL);
+ }
+ else {
+ ++job->error_runs;
+ job->dirty = 1;
+ job->next_run = apr_time_now() + md_job_delay_on_errors(job, job->error_runs, result->problem);
+ }
+ job_observation_end(job);
+}
+
+void md_job_retry_at(md_job_t *job, apr_time_t later)
+{
+ job->next_run = later;
+ job->dirty = 1;
+}
+
+apr_status_t md_job_notify(md_job_t *job, const char *reason, md_result_t *result)
+{
+ apr_status_t rv;
+
+ md_result_set(result, APR_SUCCESS, NULL);
+ rv = md_event_raise(reason, job->mdomain, job, result, job->p);
+ job->dirty = 1;
+ if (APR_SUCCESS == rv && APR_SUCCESS == result->status) {
+ job->notified = 1;
+ if (!strcmp("renewed", reason)) {
+ job->notified_renewed = 1;
+ }
+ }
+ else {
+ ++job->error_runs;
+ job->next_run = apr_time_now() + md_job_delay_on_errors(job, job->error_runs, result->problem);
+ }
+ return result->status;
+}
+
diff --git a/modules/md/md_status.h b/modules/md/md_status.h
new file mode 100644
index 0000000..f4d09bd
--- /dev/null
+++ b/modules/md/md_status.h
@@ -0,0 +1,126 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef md_status_h
+#define md_status_h
+
+struct md_json_t;
+struct md_reg_t;
+struct md_result_t;
+struct md_ocsp_reg_t;
+
+#include "md_store.h"
+
+/**
+ * Get a JSON summary of the MD and its status (certificates, jobs, etc.).
+ */
+apr_status_t md_status_get_md_json(struct md_json_t **pjson, const md_t *md,
+ struct md_reg_t *reg, struct md_ocsp_reg_t *ocsp,
+ apr_pool_t *p);
+
+/**
+ * Get a JSON summary of all MDs and their status.
+ */
+apr_status_t md_status_get_json(struct md_json_t **pjson, apr_array_header_t *mds,
+ struct md_reg_t *reg, struct md_ocsp_reg_t *ocsp,
+ apr_pool_t *p);
+
+/**
+ * Take stock of all MDs given for a short overview. The JSON returned
+ * will carry integers for MD_KEY_COMPLETE, MD_KEY_RENEWING,
+ * MD_KEY_ERRORED, MD_KEY_READY and MD_KEY_TOTAL.
+ */
+void md_status_take_stock(struct md_json_t **pjson, apr_array_header_t *mds,
+ struct md_reg_t *reg, apr_pool_t *p);
+
+
+typedef struct md_job_t md_job_t;
+
+struct md_job_t {
+ md_store_group_t group;/* group where job is persisted */
+ const char *mdomain; /* Name of the MD this job is about */
+ md_store_t *store; /* store where it is persisted */
+ apr_pool_t *p;
+ apr_time_t next_run; /* Time this job wants to be processed next */
+ apr_time_t last_run; /* Time this job ran last (or 0) */
+ struct md_result_t *last_result; /* Result from last run */
+ int finished; /* true iff the job finished successfully */
+ int notified; /* true iff notifications were handled successfully */
+ int notified_renewed; /* true iff a 'renewed' notification was handled successfully */
+ apr_time_t valid_from; /* at which time the finished job results become valid, 0 if immediate */
+ int error_runs; /* Number of errored runs of an unfinished job */
+ int fatal_error; /* a fatal error is remedied by retrying */
+ md_json_t *log; /* array of log objects with minimum fields
+ MD_KEY_WHEN (timestamp) and MD_KEY_TYPE (string) */
+ apr_size_t max_log; /* max number of log entries, new ones replace oldest */
+ int dirty;
+ struct md_result_t *observing;
+ apr_time_t min_delay; /* smallest delay a repeated attempt should have */
+};
+
+/**
+ * Create a new job instance for the given MD name.
+ * Job load/save will work using the name.
+ */
+md_job_t *md_job_make(apr_pool_t *p, md_store_t *store,
+ md_store_group_t group, const char *name,
+ apr_time_t min_delay);
+
+void md_job_set_group(md_job_t *job, md_store_group_t group);
+
+/**
+ * Update the job from storage in <group>/job->mdomain.
+ */
+apr_status_t md_job_load(md_job_t *job);
+
+/**
+ * Update storage from job in <group>/job->mdomain.
+ */
+apr_status_t md_job_save(md_job_t *job, struct md_result_t *result, apr_pool_t *p);
+
+/**
+ * Append to the job's log. Timestamp is automatically added.
+ * @param type type of log entry
+ * @param status status of entry (maybe NULL)
+ * @param detail description of what happened
+ */
+void md_job_log_append(md_job_t *job, const char *type,
+ const char *status, const char *detail);
+
+/**
+ * Retrieve the latest log entry of a certain type.
+ */
+md_json_t *md_job_log_get_latest(md_job_t *job, const char *type);
+
+/**
+ * Get the time the latest log entry of the given type happened, or 0 if
+ * none is found.
+ */
+apr_time_t md_job_log_get_time_of_latest(md_job_t *job, const char *type);
+
+void md_job_start_run(md_job_t *job, struct md_result_t *result, md_store_t *store);
+void md_job_end_run(md_job_t *job, struct md_result_t *result);
+void md_job_retry_at(md_job_t *job, apr_time_t later);
+
+/**
+ * Given the number of errors and the last problem encountered,
+ * recommend a delay for the next attempt of job
+ */
+apr_time_t md_job_delay_on_errors(md_job_t *job, int err_count, const char *last_problem);
+
+apr_status_t md_job_notify(md_job_t *job, const char *reason, struct md_result_t *result);
+
+#endif /* md_status_h */
diff --git a/modules/md/md_store.c b/modules/md/md_store.c
new file mode 100644
index 0000000..59dbd67
--- /dev/null
+++ b/modules/md/md_store.c
@@ -0,0 +1,385 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <apr_lib.h>
+#include <apr_file_info.h>
+#include <apr_file_io.h>
+#include <apr_fnmatch.h>
+#include <apr_hash.h>
+#include <apr_strings.h>
+
+#include "md.h"
+#include "md_crypt.h"
+#include "md_log.h"
+#include "md_json.h"
+#include "md_store.h"
+#include "md_util.h"
+
+/**************************************************************************************************/
+/* generic callback handling */
+
+#define ASPECT_MD "md.json"
+#define ASPECT_CERT "cert.pem"
+#define ASPECT_PKEY "key.pem"
+#define ASPECT_CHAIN "chain.pem"
+
+#define GNAME_ACCOUNTS
+#define GNAME_CHALLENGES
+#define GNAME_DOMAINS
+#define GNAME_STAGING
+#define GNAME_ARCHIVE
+
+static const char *GROUP_NAME[] = {
+ "none",
+ "accounts",
+ "challenges",
+ "domains",
+ "staging",
+ "archive",
+ "tmp",
+ "ocsp",
+ NULL
+};
+
+const char *md_store_group_name(unsigned int group)
+{
+ if (group < sizeof(GROUP_NAME)/sizeof(GROUP_NAME[0])) {
+ return GROUP_NAME[group];
+ }
+ return "UNKNOWN";
+}
+
+apr_status_t md_store_load(md_store_t *store, md_store_group_t group,
+ const char *name, const char *aspect,
+ md_store_vtype_t vtype, void **pdata,
+ apr_pool_t *p)
+{
+ return store->load(store, group, name, aspect, vtype, pdata, p);
+}
+
+apr_status_t md_store_save(md_store_t *store, apr_pool_t *p, md_store_group_t group,
+ const char *name, const char *aspect,
+ md_store_vtype_t vtype, void *data,
+ int create)
+{
+ return store->save(store, p, group, name, aspect, vtype, data, create);
+}
+
+apr_status_t md_store_remove(md_store_t *store, md_store_group_t group,
+ const char *name, const char *aspect,
+ apr_pool_t *p, int force)
+{
+ return store->remove(store, group, name, aspect, p, force);
+}
+
+apr_status_t md_store_purge(md_store_t *store, apr_pool_t *p, md_store_group_t group,
+ const char *name)
+{
+ return store->purge(store, p, group, name);
+}
+
+apr_status_t md_store_iter(md_store_inspect *inspect, void *baton, md_store_t *store,
+ apr_pool_t *p, md_store_group_t group, const char *pattern,
+ const char *aspect, md_store_vtype_t vtype)
+{
+ return store->iterate(inspect, baton, store, p, group, pattern, aspect, vtype);
+}
+
+apr_status_t md_store_load_json(md_store_t *store, md_store_group_t group,
+ const char *name, const char *aspect,
+ struct md_json_t **pdata, apr_pool_t *p)
+{
+ return md_store_load(store, group, name, aspect, MD_SV_JSON, (void**)pdata, p);
+}
+
+apr_status_t md_store_save_json(md_store_t *store, apr_pool_t *p, md_store_group_t group,
+ const char *name, const char *aspect,
+ struct md_json_t *data, int create)
+{
+ return md_store_save(store, p, group, name, aspect, MD_SV_JSON, (void*)data, create);
+}
+
+apr_status_t md_store_move(md_store_t *store, apr_pool_t *p,
+ md_store_group_t from, md_store_group_t to,
+ const char *name, int archive)
+{
+ return store->move(store, p, from, to, name, archive);
+}
+
+apr_status_t md_store_get_fname(const char **pfname,
+ md_store_t *store, md_store_group_t group,
+ const char *name, const char *aspect,
+ apr_pool_t *p)
+{
+ if (store->get_fname) {
+ return store->get_fname(pfname, store, group, name, aspect, p);
+ }
+ return APR_ENOTIMPL;
+}
+
+int md_store_is_newer(md_store_t *store, md_store_group_t group1, md_store_group_t group2,
+ const char *name, const char *aspect, apr_pool_t *p)
+{
+ return store->is_newer(store, group1, group2, name, aspect, p);
+}
+
+apr_time_t md_store_get_modified(md_store_t *store, md_store_group_t group,
+ const char *name, const char *aspect, apr_pool_t *p)
+{
+ return store->get_modified(store, group, name, aspect, p);
+}
+
+apr_status_t md_store_iter_names(md_store_inspect *inspect, void *baton, md_store_t *store,
+ apr_pool_t *p, md_store_group_t group, const char *pattern)
+{
+ return store->iterate_names(inspect, baton, store, p, group, pattern);
+}
+
+apr_status_t md_store_remove_not_modified_since(md_store_t *store, apr_pool_t *p,
+ apr_time_t modified,
+ md_store_group_t group,
+ const char *name,
+ const char *aspect)
+{
+ return store->remove_nms(store, p, modified, group, name, aspect);
+}
+
+apr_status_t md_store_rename(md_store_t *store, apr_pool_t *p,
+ md_store_group_t group, const char *name, const char *to)
+{
+ return store->rename(store, p, group, name, to);
+}
+
+/**************************************************************************************************/
+/* convenience */
+
+typedef struct {
+ md_store_t *store;
+ md_store_group_t group;
+} md_group_ctx;
+
+apr_status_t md_load(md_store_t *store, md_store_group_t group,
+ const char *name, md_t **pmd, apr_pool_t *p)
+{
+ md_json_t *json;
+ apr_status_t rv;
+
+ rv = md_store_load_json(store, group, name, MD_FN_MD, pmd? &json : NULL, p);
+ if (APR_SUCCESS == rv) {
+ if (pmd) {
+ *pmd = md_from_json(json, p);
+ }
+ return APR_SUCCESS;
+ }
+ return rv;
+}
+
+static apr_status_t p_save(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+{
+ md_group_ctx *ctx = baton;
+ md_json_t *json;
+ md_t *md;
+ int create;
+
+ md = va_arg(ap, md_t *);
+ create = va_arg(ap, int);
+
+ json = md_to_json(md, ptemp);
+ assert(json);
+ assert(md->name);
+ return md_store_save_json(ctx->store, p, ctx->group, md->name, MD_FN_MD, json, create);
+}
+
+apr_status_t md_save(md_store_t *store, apr_pool_t *p,
+ md_store_group_t group, md_t *md, int create)
+{
+ md_group_ctx ctx;
+
+ ctx.store = store;
+ ctx.group = group;
+ return md_util_pool_vdo(p_save, &ctx, p, md, create, NULL);
+}
+
+static apr_status_t p_remove(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+{
+ md_group_ctx *ctx = baton;
+ const char *name;
+ int force;
+
+ (void)p;
+ name = va_arg(ap, const char *);
+ force = va_arg(ap, int);
+
+ assert(name);
+ return md_store_remove(ctx->store, ctx->group, name, MD_FN_MD, ptemp, force);
+}
+
+apr_status_t md_remove(md_store_t *store, apr_pool_t *p,
+ md_store_group_t group, const char *name, int force)
+{
+ md_group_ctx ctx;
+
+ ctx.store = store;
+ ctx.group = group;
+ return md_util_pool_vdo(p_remove, &ctx, p, name, force, NULL);
+}
+
+int md_is_newer(md_store_t *store, md_store_group_t group1, md_store_group_t group2,
+ const char *name, apr_pool_t *p)
+{
+ return md_store_is_newer(store, group1, group2, name, MD_FN_MD, p);
+}
+
+
+typedef struct {
+ apr_pool_t *p;
+ apr_array_header_t *mds;
+} md_load_ctx;
+
+static const char *pk_filename(const char *keyname, const char *base, apr_pool_t *p)
+{
+ char *s, *t;
+ /* We also run on various filesystems with difference upper/lower preserve matching
+ * rules. Normalize the names we use, since private key specifications are basically
+ * user input. */
+ s = (keyname && apr_strnatcasecmp("rsa", keyname))?
+ apr_pstrcat(p, base, ".", keyname, ".pem", NULL)
+ : apr_pstrcat(p, base, ".pem", NULL);
+ for (t = s; *t; t++ )
+ *t = (char)apr_tolower(*t);
+ return s;
+}
+
+const char *md_pkey_filename(md_pkey_spec_t *spec, apr_pool_t *p)
+{
+ return pk_filename(md_pkey_spec_name(spec), "privkey", p);
+}
+
+const char *md_chain_filename(md_pkey_spec_t *spec, apr_pool_t *p)
+{
+ return pk_filename(md_pkey_spec_name(spec), "pubcert", p);
+}
+
+apr_status_t md_pkey_load(md_store_t *store, md_store_group_t group, const char *name,
+ md_pkey_spec_t *spec, md_pkey_t **ppkey, apr_pool_t *p)
+{
+ const char *fname = md_pkey_filename(spec, p);
+ return md_store_load(store, group, name, fname, MD_SV_PKEY, (void**)ppkey, p);
+}
+
+apr_status_t md_pkey_save(md_store_t *store, apr_pool_t *p, md_store_group_t group, const char *name,
+ md_pkey_spec_t *spec, struct md_pkey_t *pkey, int create)
+{
+ const char *fname = md_pkey_filename(spec, p);
+ return md_store_save(store, p, group, name, fname, MD_SV_PKEY, pkey, create);
+}
+
+apr_status_t md_pubcert_load(md_store_t *store, md_store_group_t group, const char *name,
+ md_pkey_spec_t *spec, struct apr_array_header_t **ppubcert,
+ apr_pool_t *p)
+{
+ const char *fname = md_chain_filename(spec, p);
+ return md_store_load(store, group, name, fname, MD_SV_CHAIN, (void**)ppubcert, p);
+}
+
+apr_status_t md_pubcert_save(md_store_t *store, apr_pool_t *p,
+ md_store_group_t group, const char *name,
+ md_pkey_spec_t *spec, struct apr_array_header_t *pubcert, int create)
+{
+ const char *fname = md_chain_filename(spec, p);
+ return md_store_save(store, p, group, name, fname, MD_SV_CHAIN, pubcert, create);
+}
+
+apr_status_t md_creds_load(md_store_t *store, md_store_group_t group, const char *name,
+ md_pkey_spec_t *spec, md_credentials_t **pcreds, apr_pool_t *p)
+{
+ md_credentials_t *creds = apr_pcalloc(p, sizeof(*creds));
+ apr_status_t rv;
+
+ creds->spec = spec;
+ if (APR_SUCCESS != (rv = md_pkey_load(store, group, name, spec, &creds->pkey, p))) {
+ goto leave;
+ }
+ /* chain is optional */
+ rv = md_pubcert_load(store, group, name, spec, &creds->chain, p);
+ if (APR_STATUS_IS_ENOENT(rv)) rv = APR_SUCCESS;
+leave:
+ *pcreds = (APR_SUCCESS == rv)? creds : NULL;
+ return rv;
+}
+
+apr_status_t md_creds_save(md_store_t *store, apr_pool_t *p, md_store_group_t group,
+ const char *name, md_credentials_t *creds, int create)
+{
+ apr_status_t rv;
+
+ if (APR_SUCCESS != (rv = md_pkey_save(store, p, group, name, creds->spec, creds->pkey, create))) {
+ goto leave;
+ }
+ rv = md_pubcert_save(store, p, group, name, creds->spec, creds->chain, create);
+leave:
+ return rv;
+}
+
+typedef struct {
+ md_store_t *store;
+ md_store_group_t group;
+ const char *pattern;
+ const char *aspect;
+ md_store_md_inspect *inspect;
+ void *baton;
+} inspect_md_ctx;
+
+static int insp_md(void *baton, const char *name, const char *aspect,
+ md_store_vtype_t vtype, void *value, apr_pool_t *ptemp)
+{
+ inspect_md_ctx *ctx = baton;
+
+ if (!strcmp(MD_FN_MD, aspect) && vtype == MD_SV_JSON) {
+ md_t *md = md_from_json(value, ptemp);
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, ptemp, "inspecting md at: %s", name);
+ return ctx->inspect(ctx->baton, ctx->store, md, ptemp);
+ }
+ return 1;
+}
+
+apr_status_t md_store_md_iter(md_store_md_inspect *inspect, void *baton, md_store_t *store,
+ apr_pool_t *p, md_store_group_t group, const char *pattern)
+{
+ inspect_md_ctx ctx;
+
+ ctx.store = store;
+ ctx.group = group;
+ ctx.inspect = inspect;
+ ctx.baton = baton;
+
+ return md_store_iter(insp_md, &ctx, store, p, group, pattern, MD_FN_MD, MD_SV_JSON);
+}
+
+apr_status_t md_store_lock_global(md_store_t *store, apr_pool_t *p, apr_time_t max_wait)
+{
+ return store->lock_global(store, p, max_wait);
+}
+
+void md_store_unlock_global(md_store_t *store, apr_pool_t *p)
+{
+ store->unlock_global(store, p);
+}
diff --git a/modules/md/md_store.h b/modules/md/md_store.h
new file mode 100644
index 0000000..73c840f
--- /dev/null
+++ b/modules/md/md_store.h
@@ -0,0 +1,343 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mod_md_md_store_h
+#define mod_md_md_store_h
+
+struct apr_array_header_t;
+struct md_cert_t;
+struct md_pkey_t;
+struct md_pkey_spec_t;
+
+const char *md_store_group_name(unsigned int group);
+
+typedef struct md_store_t md_store_t;
+
+/**
+ * A store for domain related data.
+ *
+ * The Key for a piece of data is the set of 3 items
+ * <group> + <domain> + <aspect>
+ *
+ * Examples:
+ * "domains" + "greenbytes.de" + "pubcert.pem"
+ * "ocsp" + "greenbytes.de" + "ocsp-XXXXX.json"
+ *
+ * Storage groups are pre-defined, domain and aspect names can be freely chosen.
+ *
+ * Groups reflect use cases and come with security restrictions. The groups
+ * DOMAINS, ARCHIVE and NONE are only accessible during the startup
+ * phase of httpd.
+ *
+ * Private key are stored unencrypted only in restricted groups. Meaning that certificate
+ * keys in group DOMAINS are not encrypted, but only readable at httpd start/reload.
+ * Keys in unrestricted groups are encrypted using a pass phrase generated once and stored
+ * in NONE.
+ */
+
+/** Value types handled by a store */
+typedef enum {
+ MD_SV_TEXT, /* plain text, value is (char*) */
+ MD_SV_JSON, /* JSON serialization, value is (md_json_t*) */
+ MD_SV_CERT, /* PEM x509 certificate, value is (md_cert_t*) */
+ MD_SV_PKEY, /* PEM private key, value is (md_pkey_t*) */
+ MD_SV_CHAIN, /* list of PEM x509 certificates, value is
+ (apr_array_header_t*) of (md_cert*) */
+} md_store_vtype_t;
+
+/** Store storage groups */
+typedef enum {
+ MD_SG_NONE, /* top level of store, name MUST be NULL in calls */
+ MD_SG_ACCOUNTS, /* ACME accounts */
+ MD_SG_CHALLENGES, /* challenge response data for a domain */
+ MD_SG_DOMAINS, /* live certificates and settings for a domain */
+ MD_SG_STAGING, /* staged set of certificate and settings, maybe incomplete */
+ MD_SG_ARCHIVE, /* Archived live sets of a domain */
+ MD_SG_TMP, /* temporary domain storage */
+ MD_SG_OCSP, /* OCSP stapling related domain data */
+ MD_SG_COUNT, /* number of storage groups, used in setups */
+} md_store_group_t;
+
+#define MD_FN_MD "md.json"
+#define MD_FN_JOB "job.json"
+#define MD_FN_HTTPD_JSON "httpd.json"
+
+/* The corresponding names for current cert & key files are constructed
+ * in md_store and md_crypt.
+ */
+
+/* These three legacy filenames are only used in md_store_fs to
+ * upgrade 1.0 directories. They should not be used for any other
+ * purpose.
+ */
+#define MD_FN_PRIVKEY "privkey.pem"
+#define MD_FN_PUBCERT "pubcert.pem"
+#define MD_FN_CERT "cert.pem"
+
+/**
+ * Load the JSON value at key "group/name/aspect", allocated from pool p.
+ * @return APR_ENOENT if there is no such value
+ */
+apr_status_t md_store_load_json(md_store_t *store, md_store_group_t group,
+ const char *name, const char *aspect,
+ struct md_json_t **pdata, apr_pool_t *p);
+/**
+ * Save the JSON value at key "group/name/aspect". If create != 0, fail if there
+ * already is a value for this key.
+ */
+apr_status_t md_store_save_json(md_store_t *store, apr_pool_t *p, md_store_group_t group,
+ const char *name, const char *aspect,
+ struct md_json_t *data, int create);
+
+/**
+ * Load the value of type at key "group/name/aspect", allocated from pool p. Usually, the
+ * type is expected to be the same as used in saving the value. Some conversions will work,
+ * others will fail the format.
+ * @return APR_ENOENT if there is no such value
+ */
+apr_status_t md_store_load(md_store_t *store, md_store_group_t group,
+ const char *name, const char *aspect,
+ md_store_vtype_t vtype, void **pdata,
+ apr_pool_t *p);
+/**
+ * Save the JSON value at key "group/name/aspect". If create != 0, fail if there
+ * already is a value for this key. The provided data MUST be of the correct type.
+ */
+apr_status_t md_store_save(md_store_t *store, apr_pool_t *p, md_store_group_t group,
+ const char *name, const char *aspect,
+ md_store_vtype_t vtype, void *data,
+ int create);
+
+/**
+ * Remove the value stored at key "group/name/aspect". Unless force != 0, a missing
+ * value will cause the call to fail with APR_ENOENT.
+ */
+apr_status_t md_store_remove(md_store_t *store, md_store_group_t group,
+ const char *name, const char *aspect,
+ apr_pool_t *p, int force);
+/**
+ * Remove everything matching key "group/name".
+ */
+apr_status_t md_store_purge(md_store_t *store, apr_pool_t *p,
+ md_store_group_t group, const char *name);
+
+/**
+ * Remove all items matching the name/aspect patterns that have not been
+ * modified since the given timestamp.
+ */
+apr_status_t md_store_remove_not_modified_since(md_store_t *store, apr_pool_t *p,
+ apr_time_t modified,
+ md_store_group_t group,
+ const char *name,
+ const char *aspect);
+
+/**
+ * inspect callback function. Invoked for each matched value. Values allocated from
+ * ptemp may disappear any time after the call returned. If this function returns
+ * 0, the iteration is aborted.
+ */
+typedef int md_store_inspect(void *baton, const char *name, const char *aspect,
+ md_store_vtype_t vtype, void *value, apr_pool_t *ptemp);
+
+/**
+ * Iterator over all existing values matching the name pattern. Patterns are evaluated
+ * using apr_fnmatch() without flags.
+ */
+apr_status_t md_store_iter(md_store_inspect *inspect, void *baton, md_store_t *store,
+ apr_pool_t *p, md_store_group_t group, const char *pattern,
+ const char *aspect, md_store_vtype_t vtype);
+
+/**
+ * Move everything matching key "from/name" from one group to another. If archive != 0,
+ * move any existing "to/name" into a new "archive/new_name" location.
+ */
+apr_status_t md_store_move(md_store_t *store, apr_pool_t *p,
+ md_store_group_t from, md_store_group_t to,
+ const char *name, int archive);
+
+/**
+ * Rename a group member.
+ */
+apr_status_t md_store_rename(md_store_t *store, apr_pool_t *p,
+ md_store_group_t group, const char *name, const char *to);
+
+/**
+ * Get the filename of an item stored in "group/name/aspect". The item does
+ * not have to exist.
+ */
+apr_status_t md_store_get_fname(const char **pfname,
+ md_store_t *store, md_store_group_t group,
+ const char *name, const char *aspect,
+ apr_pool_t *p);
+
+/**
+ * Make a compare on the modification time of "group1/name/aspect" vs. "group2/name/aspect".
+ */
+int md_store_is_newer(md_store_t *store, md_store_group_t group1, md_store_group_t group2,
+ const char *name, const char *aspect, apr_pool_t *p);
+
+/**
+ * Iterate over all names that exist in a group, e.g. there are items matching
+ * "group/pattern". The inspect function is called with the name and NULL aspect
+ * and value.
+ */
+apr_status_t md_store_iter_names(md_store_inspect *inspect, void *baton, md_store_t *store,
+ apr_pool_t *p, md_store_group_t group, const char *pattern);
+
+/**
+ * Get the modification time of the item store under "group/name/aspect".
+ * @return modification time or 0 if the item does not exist.
+ */
+apr_time_t md_store_get_modified(md_store_t *store, md_store_group_t group,
+ const char *name, const char *aspect, apr_pool_t *p);
+
+/**
+ * Acquire a cooperative, global lock on store modifications.
+
+ * This will only prevent other children/processes/cluster nodes from
+ * doing the same and does not protect individual store functions from
+ * being called without it.
+ * @param store the store
+ * @param p memory pool to use
+ * @param max_wait maximum time to wait in order to acquire
+ * @return APR_SUCCESS when lock was obtained
+ */
+apr_status_t md_store_lock_global(md_store_t *store, apr_pool_t *p, apr_time_t max_wait);
+
+/**
+ * Realease the global store lock. Will do nothing if there is no lock.
+ */
+void md_store_unlock_global(md_store_t *store, apr_pool_t *p);
+
+/**************************************************************************************************/
+/* Storage handling utils */
+
+apr_status_t md_load(md_store_t *store, md_store_group_t group,
+ const char *name, md_t **pmd, apr_pool_t *p);
+apr_status_t md_save(struct md_store_t *store, apr_pool_t *p, md_store_group_t group,
+ md_t *md, int create);
+apr_status_t md_remove(md_store_t *store, apr_pool_t *p, md_store_group_t group,
+ const char *name, int force);
+
+int md_is_newer(md_store_t *store, md_store_group_t group1, md_store_group_t group2,
+ const char *name, apr_pool_t *p);
+
+typedef int md_store_md_inspect(void *baton, md_store_t *store, md_t *md, apr_pool_t *ptemp);
+
+apr_status_t md_store_md_iter(md_store_md_inspect *inspect, void *baton, md_store_t *store,
+ apr_pool_t *p, md_store_group_t group, const char *pattern);
+
+
+const char *md_pkey_filename(struct md_pkey_spec_t *spec, apr_pool_t *p);
+const char *md_chain_filename(struct md_pkey_spec_t *spec, apr_pool_t *p);
+
+apr_status_t md_pkey_load(md_store_t *store, md_store_group_t group,
+ const char *name, struct md_pkey_spec_t *spec,
+ struct md_pkey_t **ppkey, apr_pool_t *p);
+apr_status_t md_pkey_save(md_store_t *store, apr_pool_t *p, md_store_group_t group,
+ const char *name, struct md_pkey_spec_t *spec,
+ struct md_pkey_t *pkey, int create);
+
+apr_status_t md_pubcert_load(md_store_t *store, md_store_group_t group, const char *name,
+ struct md_pkey_spec_t *spec, struct apr_array_header_t **ppubcert,
+ apr_pool_t *p);
+apr_status_t md_pubcert_save(md_store_t *store, apr_pool_t *p,
+ md_store_group_t group, const char *name,
+ struct md_pkey_spec_t *spec,
+ struct apr_array_header_t *pubcert, int create);
+
+/**************************************************************************************************/
+/* X509 complete credentials */
+
+typedef struct md_credentials_t md_credentials_t;
+struct md_credentials_t {
+ struct md_pkey_spec_t *spec;
+ struct md_pkey_t *pkey;
+ struct apr_array_header_t *chain;
+};
+
+apr_status_t md_creds_load(md_store_t *store, md_store_group_t group, const char *name,
+ struct md_pkey_spec_t *spec, md_credentials_t **pcreds, apr_pool_t *p);
+apr_status_t md_creds_save(md_store_t *store, apr_pool_t *p, md_store_group_t group,
+ const char *name, md_credentials_t *creds, int create);
+
+/**************************************************************************************************/
+/* implementation interface */
+
+typedef apr_status_t md_store_load_cb(md_store_t *store, md_store_group_t group,
+ const char *name, const char *aspect,
+ md_store_vtype_t vtype, void **pvalue,
+ apr_pool_t *p);
+typedef apr_status_t md_store_save_cb(md_store_t *store, apr_pool_t *p, md_store_group_t group,
+ const char *name, const char *aspect,
+ md_store_vtype_t vtype, void *value,
+ int create);
+typedef apr_status_t md_store_remove_cb(md_store_t *store, md_store_group_t group,
+ const char *name, const char *aspect,
+ apr_pool_t *p, int force);
+typedef apr_status_t md_store_purge_cb(md_store_t *store, apr_pool_t *p, md_store_group_t group,
+ const char *name);
+
+typedef apr_status_t md_store_iter_cb(md_store_inspect *inspect, void *baton, md_store_t *store,
+ apr_pool_t *p, md_store_group_t group, const char *pattern,
+ const char *aspect, md_store_vtype_t vtype);
+
+typedef apr_status_t md_store_names_iter_cb(md_store_inspect *inspect, void *baton, md_store_t *store,
+ apr_pool_t *p, md_store_group_t group, const char *pattern);
+
+typedef apr_status_t md_store_move_cb(md_store_t *store, apr_pool_t *p, md_store_group_t from,
+ md_store_group_t to, const char *name, int archive);
+
+typedef apr_status_t md_store_rename_cb(md_store_t *store, apr_pool_t *p, md_store_group_t group,
+ const char *from, const char *to);
+
+typedef apr_status_t md_store_get_fname_cb(const char **pfname,
+ md_store_t *store, md_store_group_t group,
+ const char *name, const char *aspect,
+ apr_pool_t *p);
+
+typedef int md_store_is_newer_cb(md_store_t *store,
+ md_store_group_t group1, md_store_group_t group2,
+ const char *name, const char *aspect, apr_pool_t *p);
+
+typedef apr_time_t md_store_get_modified_cb(md_store_t *store, md_store_group_t group,
+ const char *name, const char *aspect, apr_pool_t *p);
+
+typedef apr_status_t md_store_remove_nms_cb(md_store_t *store, apr_pool_t *p,
+ apr_time_t modified, md_store_group_t group,
+ const char *name, const char *aspect);
+typedef apr_status_t md_store_lock_global_cb(md_store_t *store, apr_pool_t *p, apr_time_t max_wait);
+typedef void md_store_unlock_global_cb(md_store_t *store, apr_pool_t *p);
+
+struct md_store_t {
+ md_store_save_cb *save;
+ md_store_load_cb *load;
+ md_store_remove_cb *remove;
+ md_store_move_cb *move;
+ md_store_rename_cb *rename;
+ md_store_iter_cb *iterate;
+ md_store_names_iter_cb *iterate_names;
+ md_store_purge_cb *purge;
+ md_store_get_fname_cb *get_fname;
+ md_store_is_newer_cb *is_newer;
+ md_store_get_modified_cb *get_modified;
+ md_store_remove_nms_cb *remove_nms;
+ md_store_lock_global_cb *lock_global;
+ md_store_unlock_global_cb *unlock_global;
+};
+
+
+#endif /* mod_md_md_store_h */
diff --git a/modules/md/md_store_fs.c b/modules/md/md_store_fs.c
new file mode 100644
index 0000000..35c24b4
--- /dev/null
+++ b/modules/md/md_store_fs.c
@@ -0,0 +1,1169 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <apr_lib.h>
+#include <apr_file_info.h>
+#include <apr_file_io.h>
+#include <apr_fnmatch.h>
+#include <apr_hash.h>
+#include <apr_strings.h>
+
+#include "md.h"
+#include "md_crypt.h"
+#include "md_json.h"
+#include "md_log.h"
+#include "md_store.h"
+#include "md_store_fs.h"
+#include "md_util.h"
+#include "md_version.h"
+
+/**************************************************************************************************/
+/* file system based implementation of md_store_t */
+
+#define MD_STORE_VERSION 3
+#define MD_FS_LOCK_NAME "store.lock"
+
+typedef struct {
+ apr_fileperms_t dir;
+ apr_fileperms_t file;
+} perms_t;
+
+typedef struct md_store_fs_t md_store_fs_t;
+struct md_store_fs_t {
+ md_store_t s;
+
+ const char *base; /* base directory of store */
+ perms_t def_perms;
+ perms_t group_perms[MD_SG_COUNT];
+ md_store_fs_cb *event_cb;
+ void *event_baton;
+
+ md_data_t key;
+ int plain_pkey[MD_SG_COUNT];
+
+ int port_80;
+ int port_443;
+
+ apr_file_t *global_lock;
+};
+
+#define FS_STORE(store) (md_store_fs_t*)(((char*)store)-offsetof(md_store_fs_t, s))
+#define FS_STORE_JSON "md_store.json"
+#define FS_STORE_KLEN 48
+
+static apr_status_t fs_load(md_store_t *store, md_store_group_t group,
+ const char *name, const char *aspect,
+ md_store_vtype_t vtype, void **pvalue, apr_pool_t *p);
+static apr_status_t fs_save(md_store_t *store, apr_pool_t *p, md_store_group_t group,
+ const char *name, const char *aspect,
+ md_store_vtype_t vtype, void *value, int create);
+static apr_status_t fs_remove(md_store_t *store, md_store_group_t group,
+ const char *name, const char *aspect,
+ apr_pool_t *p, int force);
+static apr_status_t fs_purge(md_store_t *store, apr_pool_t *p,
+ md_store_group_t group, const char *name);
+static apr_status_t fs_remove_nms(md_store_t *store, apr_pool_t *p,
+ apr_time_t modified, md_store_group_t group,
+ const char *name, const char *aspect);
+static apr_status_t fs_move(md_store_t *store, apr_pool_t *p,
+ md_store_group_t from, md_store_group_t to,
+ const char *name, int archive);
+static apr_status_t fs_rename(md_store_t *store, apr_pool_t *p,
+ md_store_group_t group, const char *from, const char *to);
+static apr_status_t fs_iterate(md_store_inspect *inspect, void *baton, md_store_t *store,
+ apr_pool_t *p, md_store_group_t group, const char *pattern,
+ const char *aspect, md_store_vtype_t vtype);
+static apr_status_t fs_iterate_names(md_store_inspect *inspect, void *baton, md_store_t *store,
+ apr_pool_t *p, md_store_group_t group, const char *pattern);
+
+static apr_status_t fs_get_fname(const char **pfname,
+ md_store_t *store, md_store_group_t group,
+ const char *name, const char *aspect,
+ apr_pool_t *p);
+static int fs_is_newer(md_store_t *store, md_store_group_t group1, md_store_group_t group2,
+ const char *name, const char *aspect, apr_pool_t *p);
+
+static apr_time_t fs_get_modified(md_store_t *store, md_store_group_t group,
+ const char *name, const char *aspect, apr_pool_t *p);
+
+static apr_status_t fs_lock_global(md_store_t *store, apr_pool_t *p, apr_time_t max_wait);
+static void fs_unlock_global(md_store_t *store, apr_pool_t *p);
+
+static apr_status_t init_store_file(md_store_fs_t *s_fs, const char *fname,
+ apr_pool_t *p, apr_pool_t *ptemp)
+{
+ md_json_t *json = md_json_create(p);
+ const char *key64;
+ apr_status_t rv;
+
+ md_json_setn(MD_STORE_VERSION, json, MD_KEY_STORE, MD_KEY_VERSION, NULL);
+
+ md_data_pinit(&s_fs->key, FS_STORE_KLEN, p);
+ if (APR_SUCCESS != (rv = md_rand_bytes((unsigned char*)s_fs->key.data, s_fs->key.len, p))) {
+ return rv;
+ }
+
+ key64 = md_util_base64url_encode(&s_fs->key, ptemp);
+ md_json_sets(key64, json, MD_KEY_KEY, NULL);
+ rv = md_json_fcreatex(json, ptemp, MD_JSON_FMT_INDENT, fname, MD_FPROT_F_UONLY);
+ memset((char*)key64, 0, strlen(key64));
+
+ return rv;
+}
+
+static apr_status_t rename_pkey(void *baton, apr_pool_t *p, apr_pool_t *ptemp,
+ const char *dir, const char *name,
+ apr_filetype_e ftype)
+{
+ const char *from, *to;
+ apr_status_t rv = APR_SUCCESS;
+
+ (void)baton;
+ (void)ftype;
+ if ( MD_OK(md_util_path_merge(&from, ptemp, dir, name, NULL))
+ && MD_OK(md_util_path_merge(&to, ptemp, dir, MD_FN_PRIVKEY, NULL))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, p, "renaming %s/%s to %s",
+ dir, name, MD_FN_PRIVKEY);
+ return apr_file_rename(from, to, ptemp);
+ }
+ return rv;
+}
+
+static apr_status_t mk_pubcert(void *baton, apr_pool_t *p, apr_pool_t *ptemp,
+ const char *dir, const char *name,
+ apr_filetype_e ftype)
+{
+ md_cert_t *cert;
+ apr_array_header_t *chain, *pubcert;
+ const char *fname, *fpubcert;
+ apr_status_t rv = APR_SUCCESS;
+
+ (void)baton;
+ (void)ftype;
+ (void)p;
+ if ( MD_OK(md_util_path_merge(&fpubcert, ptemp, dir, MD_FN_PUBCERT, NULL))
+ && APR_STATUS_IS_ENOENT(rv = md_chain_fload(&pubcert, ptemp, fpubcert))
+ && MD_OK(md_util_path_merge(&fname, ptemp, dir, name, NULL))
+ && MD_OK(md_cert_fload(&cert, ptemp, fname))
+ && MD_OK(md_util_path_merge(&fname, ptemp, dir, "chain.pem", NULL))) {
+
+ rv = md_chain_fload(&chain, ptemp, fname);
+ if (APR_STATUS_IS_ENOENT(rv)) {
+ chain = apr_array_make(ptemp, 1, sizeof(md_cert_t*));
+ rv = APR_SUCCESS;
+ }
+ if (APR_SUCCESS == rv) {
+ pubcert = apr_array_make(ptemp, chain->nelts + 1, sizeof(md_cert_t*));
+ APR_ARRAY_PUSH(pubcert, md_cert_t *) = cert;
+ apr_array_cat(pubcert, chain);
+ rv = md_chain_fsave(pubcert, ptemp, fpubcert, MD_FPROT_F_UONLY);
+ }
+ }
+ return rv;
+}
+
+static apr_status_t upgrade_from_1_0(md_store_fs_t *s_fs, apr_pool_t *p, apr_pool_t *ptemp)
+{
+ md_store_group_t g;
+ apr_status_t rv = APR_SUCCESS;
+
+ (void)ptemp;
+ /* Migrate pkey.pem -> privkey.pem */
+ for (g = MD_SG_NONE; g < MD_SG_COUNT && APR_SUCCESS == rv; ++g) {
+ rv = md_util_files_do(rename_pkey, s_fs, p, s_fs->base,
+ md_store_group_name(g), "*", "pkey.pem", NULL);
+ }
+ /* Generate fullcert.pem from cert.pem and chain.pem where missing */
+ rv = md_util_files_do(mk_pubcert, s_fs, p, s_fs->base,
+ md_store_group_name(MD_SG_DOMAINS), "*", MD_FN_CERT, NULL);
+ rv = md_util_files_do(mk_pubcert, s_fs, p, s_fs->base,
+ md_store_group_name(MD_SG_ARCHIVE), "*", MD_FN_CERT, NULL);
+
+ return rv;
+}
+
+static apr_status_t read_store_file(md_store_fs_t *s_fs, const char *fname,
+ apr_pool_t *p, apr_pool_t *ptemp)
+{
+ md_json_t *json;
+ const char *key64;
+ apr_status_t rv;
+ double store_version;
+
+ if (MD_OK(md_json_readf(&json, p, fname))) {
+ store_version = md_json_getn(json, MD_KEY_STORE, MD_KEY_VERSION, NULL);
+ if (store_version <= 0.0) {
+ /* ok, an old one, compatible to 1.0 */
+ store_version = 1.0;
+ }
+ if (store_version > MD_STORE_VERSION) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "version too new: %f", store_version);
+ return APR_EINVAL;
+ }
+
+ key64 = md_json_dups(p, json, MD_KEY_KEY, NULL);
+ if (!key64) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "missing key: %s", MD_KEY_KEY);
+ return APR_EINVAL;
+ }
+
+ md_util_base64url_decode(&s_fs->key, key64, p);
+ if (s_fs->key.len != FS_STORE_KLEN) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "key length unexpected: %" APR_SIZE_T_FMT,
+ s_fs->key.len);
+ return APR_EINVAL;
+ }
+
+ /* Need to migrate format? */
+ if (store_version < MD_STORE_VERSION) {
+ if (store_version <= 1.0) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "migrating store v1 -> v2");
+ rv = upgrade_from_1_0(s_fs, p, ptemp);
+ }
+ if (store_version <= 2.0) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "migrating store v2 -> v3");
+ md_json_del(json, MD_KEY_VERSION, NULL);
+ }
+
+ if (APR_SUCCESS == rv) {
+ md_json_setn(MD_STORE_VERSION, json, MD_KEY_STORE, MD_KEY_VERSION, NULL);
+ rv = md_json_freplace(json, ptemp, MD_JSON_FMT_INDENT, fname, MD_FPROT_F_UONLY);
+ }
+ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, rv, p, "migrated store");
+ }
+ }
+ return rv;
+}
+
+static apr_status_t setup_store_file(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+{
+ md_store_fs_t *s_fs = baton;
+ const char *fname;
+ apr_status_t rv;
+
+ (void)ap;
+ s_fs->plain_pkey[MD_SG_DOMAINS] = 1;
+ /* Added: the encryption of tls-alpn-01 certificate keys is not a security issue
+ * for these self-signed, short-lived certificates. Having them unencrypted let's
+ * use pass around the files insteak of an *SSL implementation dependent PKEY_something.
+ */
+ s_fs->plain_pkey[MD_SG_CHALLENGES] = 1;
+ s_fs->plain_pkey[MD_SG_TMP] = 1;
+
+ if (!MD_OK(md_util_path_merge(&fname, ptemp, s_fs->base, FS_STORE_JSON, NULL))) {
+ return rv;
+ }
+
+read:
+ if (MD_OK(md_util_is_file(fname, ptemp))) {
+ rv = read_store_file(s_fs, fname, p, ptemp);
+ }
+ else if (APR_STATUS_IS_ENOENT(rv)
+ && APR_STATUS_IS_EEXIST(rv = init_store_file(s_fs, fname, p, ptemp))) {
+ goto read;
+ }
+ return rv;
+}
+
+apr_status_t md_store_fs_init(md_store_t **pstore, apr_pool_t *p, const char *path)
+{
+ md_store_fs_t *s_fs;
+ apr_status_t rv = APR_SUCCESS;
+
+ s_fs = apr_pcalloc(p, sizeof(*s_fs));
+
+ s_fs->s.load = fs_load;
+ s_fs->s.save = fs_save;
+ s_fs->s.remove = fs_remove;
+ s_fs->s.move = fs_move;
+ s_fs->s.rename = fs_rename;
+ s_fs->s.purge = fs_purge;
+ s_fs->s.iterate = fs_iterate;
+ s_fs->s.iterate_names = fs_iterate_names;
+ s_fs->s.get_fname = fs_get_fname;
+ s_fs->s.is_newer = fs_is_newer;
+ s_fs->s.get_modified = fs_get_modified;
+ s_fs->s.remove_nms = fs_remove_nms;
+ s_fs->s.lock_global = fs_lock_global;
+ s_fs->s.unlock_global = fs_unlock_global;
+
+ /* by default, everything is only readable by the current user */
+ s_fs->def_perms.dir = MD_FPROT_D_UONLY;
+ s_fs->def_perms.file = MD_FPROT_F_UONLY;
+
+ /* Account information needs to be accessible to httpd child processes.
+ * private keys are, similar to staging, encrypted. */
+ s_fs->group_perms[MD_SG_ACCOUNTS].dir = MD_FPROT_D_UALL_WREAD;
+ s_fs->group_perms[MD_SG_ACCOUNTS].file = MD_FPROT_F_UALL_WREAD;
+ s_fs->group_perms[MD_SG_STAGING].dir = MD_FPROT_D_UALL_WREAD;
+ s_fs->group_perms[MD_SG_STAGING].file = MD_FPROT_F_UALL_WREAD;
+ /* challenges dir and files are readable by all, no secrets involved */
+ s_fs->group_perms[MD_SG_CHALLENGES].dir = MD_FPROT_D_UALL_WREAD;
+ s_fs->group_perms[MD_SG_CHALLENGES].file = MD_FPROT_F_UALL_WREAD;
+ /* OCSP data is readable by all, no secrets involved */
+ s_fs->group_perms[MD_SG_OCSP].dir = MD_FPROT_D_UALL_WREAD;
+ s_fs->group_perms[MD_SG_OCSP].file = MD_FPROT_F_UALL_WREAD;
+
+ s_fs->base = apr_pstrdup(p, path);
+
+ rv = md_util_is_dir(s_fs->base, p);
+ if (APR_STATUS_IS_ENOENT(rv)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, rv, p,
+ "store directory does not exist, creating %s", s_fs->base);
+ rv = apr_dir_make_recursive(s_fs->base, s_fs->def_perms.dir, p);
+ if (APR_SUCCESS != rv) goto cleanup;
+ rv = apr_file_perms_set(s_fs->base, MD_FPROT_D_UALL_WREAD);
+ if (APR_STATUS_IS_ENOTIMPL(rv)) {
+ rv = APR_SUCCESS;
+ }
+ if (APR_SUCCESS != rv) goto cleanup;
+ }
+ else if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
+ "not a plain directory, maybe a symlink? %s", s_fs->base);
+ }
+
+ rv = md_util_pool_vdo(setup_store_file, s_fs, p, NULL);
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "init fs store at %s", s_fs->base);
+ }
+cleanup:
+ *pstore = (rv == APR_SUCCESS)? &(s_fs->s) : NULL;
+ return rv;
+}
+
+apr_status_t md_store_fs_default_perms_set(md_store_t *store,
+ apr_fileperms_t file_perms,
+ apr_fileperms_t dir_perms)
+{
+ md_store_fs_t *s_fs = FS_STORE(store);
+
+ s_fs->def_perms.file = file_perms;
+ s_fs->def_perms.dir = dir_perms;
+ return APR_SUCCESS;
+}
+
+apr_status_t md_store_fs_group_perms_set(md_store_t *store, md_store_group_t group,
+ apr_fileperms_t file_perms,
+ apr_fileperms_t dir_perms)
+{
+ md_store_fs_t *s_fs = FS_STORE(store);
+
+ if (group >= (sizeof(s_fs->group_perms)/sizeof(s_fs->group_perms[0]))) {
+ return APR_ENOTIMPL;
+ }
+ s_fs->group_perms[group].file = file_perms;
+ s_fs->group_perms[group].dir = dir_perms;
+ return APR_SUCCESS;
+}
+
+apr_status_t md_store_fs_set_event_cb(struct md_store_t *store, md_store_fs_cb *cb, void *baton)
+{
+ md_store_fs_t *s_fs = FS_STORE(store);
+
+ s_fs->event_cb = cb;
+ s_fs->event_baton = baton;
+ return APR_SUCCESS;
+}
+
+static const perms_t *gperms(md_store_fs_t *s_fs, md_store_group_t group)
+{
+ if (group >= (sizeof(s_fs->group_perms)/sizeof(s_fs->group_perms[0]))
+ || !s_fs->group_perms[group].dir) {
+ return &s_fs->def_perms;
+ }
+ return &s_fs->group_perms[group];
+}
+
+static apr_status_t fs_get_fname(const char **pfname,
+ md_store_t *store, md_store_group_t group,
+ const char *name, const char *aspect,
+ apr_pool_t *p)
+{
+ md_store_fs_t *s_fs = FS_STORE(store);
+ if (group == MD_SG_NONE) {
+ return md_util_path_merge(pfname, p, s_fs->base, aspect, NULL);
+ }
+ return md_util_path_merge(pfname, p,
+ s_fs->base, md_store_group_name(group), name, aspect, NULL);
+}
+
+static apr_status_t fs_get_dname(const char **pdname,
+ md_store_t *store, md_store_group_t group,
+ const char *name, apr_pool_t *p)
+{
+ md_store_fs_t *s_fs = FS_STORE(store);
+ if (group == MD_SG_NONE) {
+ *pdname = s_fs->base;
+ return APR_SUCCESS;
+ }
+ return md_util_path_merge(pdname, p, s_fs->base, md_store_group_name(group), name, NULL);
+}
+
+static void get_pass(const char **ppass, apr_size_t *plen,
+ md_store_fs_t *s_fs, md_store_group_t group)
+{
+ if (s_fs->plain_pkey[group]) {
+ *ppass = NULL;
+ *plen = 0;
+ }
+ else {
+ *ppass = (const char *)s_fs->key.data;
+ *plen = s_fs->key.len;
+ }
+}
+
+static apr_status_t fs_fload(void **pvalue, md_store_fs_t *s_fs, const char *fpath,
+ md_store_group_t group, md_store_vtype_t vtype,
+ apr_pool_t *p, apr_pool_t *ptemp)
+{
+ apr_status_t rv;
+ const char *pass;
+ apr_size_t pass_len;
+
+ if (pvalue != NULL) {
+ switch (vtype) {
+ case MD_SV_TEXT:
+ rv = md_text_fread8k((const char **)pvalue, p, fpath);
+ break;
+ case MD_SV_JSON:
+ rv = md_json_readf((md_json_t **)pvalue, p, fpath);
+ break;
+ case MD_SV_CERT:
+ rv = md_cert_fload((md_cert_t **)pvalue, p, fpath);
+ break;
+ case MD_SV_PKEY:
+ get_pass(&pass, &pass_len, s_fs, group);
+ rv = md_pkey_fload((md_pkey_t **)pvalue, p, pass, pass_len, fpath);
+ break;
+ case MD_SV_CHAIN:
+ rv = md_chain_fload((apr_array_header_t **)pvalue, p, fpath);
+ break;
+ default:
+ rv = APR_ENOTIMPL;
+ break;
+ }
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, ptemp,
+ "loading type %d from %s", vtype, fpath);
+ }
+ else { /* check for existence only */
+ rv = md_util_is_file(fpath, p);
+ }
+ return rv;
+}
+
+static apr_status_t pfs_load(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+{
+ md_store_fs_t *s_fs = baton;
+ const char *fpath, *name, *aspect;
+ md_store_vtype_t vtype;
+ md_store_group_t group;
+ void **pvalue;
+ apr_status_t rv;
+
+ group = (md_store_group_t)va_arg(ap, int);
+ name = va_arg(ap, const char *);
+ aspect = va_arg(ap, const char *);
+ vtype = (md_store_vtype_t)va_arg(ap, int);
+ pvalue= va_arg(ap, void **);
+
+ if (MD_OK(fs_get_fname(&fpath, &s_fs->s, group, name, aspect, ptemp))) {
+ rv = fs_fload(pvalue, s_fs, fpath, group, vtype, p, ptemp);
+ }
+ return rv;
+}
+
+static apr_status_t dispatch(md_store_fs_t *s_fs, md_store_fs_ev_t ev, unsigned int group,
+ const char *fname, apr_filetype_e ftype, apr_pool_t *p)
+{
+ (void)ev;
+ if (s_fs->event_cb) {
+ return s_fs->event_cb(s_fs->event_baton, &s_fs->s, MD_S_FS_EV_CREATED,
+ group, fname, ftype, p);
+ }
+ return APR_SUCCESS;
+}
+
+static apr_status_t mk_group_dir(const char **pdir, md_store_fs_t *s_fs,
+ md_store_group_t group, const char *name,
+ apr_pool_t *p)
+{
+ const perms_t *perms;
+ apr_status_t rv;
+
+ perms = gperms(s_fs, group);
+
+ *pdir = NULL;
+ rv = fs_get_dname(pdir, &s_fs->s, group, name, p);
+ if ((APR_SUCCESS != rv) || (MD_SG_NONE == group)) goto cleanup;
+
+ rv = md_util_is_dir(*pdir, p);
+ if (APR_STATUS_IS_ENOENT(rv)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, p, "not a directory, creating %s", *pdir);
+ rv = apr_dir_make_recursive(*pdir, perms->dir, p);
+ if (APR_SUCCESS != rv) goto cleanup;
+ dispatch(s_fs, MD_S_FS_EV_CREATED, group, *pdir, APR_DIR, p);
+ }
+
+ rv = apr_file_perms_set(*pdir, perms->dir);
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, p, "mk_group_dir %s perm set", *pdir);
+ if (APR_STATUS_IS_ENOTIMPL(rv)) {
+ rv = APR_SUCCESS;
+ }
+cleanup:
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "mk_group_dir %d %s",
+ group, (*pdir? *pdir : (name? name : "(null)")));
+ }
+ return rv;
+}
+
+static apr_status_t pfs_is_newer(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+{
+ md_store_fs_t *s_fs = baton;
+ const char *fname1, *fname2, *name, *aspect;
+ md_store_group_t group1, group2;
+ apr_finfo_t inf1, inf2;
+ int *pnewer;
+ apr_status_t rv;
+
+ (void)p;
+ group1 = (md_store_group_t)va_arg(ap, int);
+ group2 = (md_store_group_t)va_arg(ap, int);
+ name = va_arg(ap, const char*);
+ aspect = va_arg(ap, const char*);
+ pnewer = va_arg(ap, int*);
+
+ *pnewer = 0;
+ if ( MD_OK(fs_get_fname(&fname1, &s_fs->s, group1, name, aspect, ptemp))
+ && MD_OK(fs_get_fname(&fname2, &s_fs->s, group2, name, aspect, ptemp))
+ && MD_OK(apr_stat(&inf1, fname1, APR_FINFO_MTIME, ptemp))
+ && MD_OK(apr_stat(&inf2, fname2, APR_FINFO_MTIME, ptemp))) {
+ *pnewer = inf1.mtime > inf2.mtime;
+ }
+
+ return rv;
+}
+
+static int fs_is_newer(md_store_t *store, md_store_group_t group1, md_store_group_t group2,
+ const char *name, const char *aspect, apr_pool_t *p)
+{
+ md_store_fs_t *s_fs = FS_STORE(store);
+ int newer = 0;
+ apr_status_t rv;
+
+ rv = md_util_pool_vdo(pfs_is_newer, s_fs, p, group1, group2, name, aspect, &newer, NULL);
+ if (APR_SUCCESS == rv) {
+ return newer;
+ }
+ return 0;
+}
+
+static apr_status_t pfs_get_modified(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+{
+ md_store_fs_t *s_fs = baton;
+ const char *fname, *name, *aspect;
+ md_store_group_t group;
+ apr_finfo_t inf;
+ apr_time_t *pmtime;
+ apr_status_t rv;
+
+ (void)p;
+ group = (md_store_group_t)va_arg(ap, int);
+ name = va_arg(ap, const char*);
+ aspect = va_arg(ap, const char*);
+ pmtime = va_arg(ap, apr_time_t*);
+
+ *pmtime = 0;
+ if ( MD_OK(fs_get_fname(&fname, &s_fs->s, group, name, aspect, ptemp))
+ && MD_OK(apr_stat(&inf, fname, APR_FINFO_MTIME, ptemp))) {
+ *pmtime = inf.mtime;
+ }
+
+ return rv;
+}
+
+static apr_time_t fs_get_modified(md_store_t *store, md_store_group_t group,
+ const char *name, const char *aspect, apr_pool_t *p)
+{
+ md_store_fs_t *s_fs = FS_STORE(store);
+ apr_time_t mtime;
+ apr_status_t rv;
+
+ rv = md_util_pool_vdo(pfs_get_modified, s_fs, p, group, name, aspect, &mtime, NULL);
+ if (APR_SUCCESS == rv) {
+ return mtime;
+ }
+ return 0;
+}
+
+static apr_status_t pfs_save(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+{
+ md_store_fs_t *s_fs = baton;
+ const char *gdir, *dir, *fpath, *name, *aspect;
+ md_store_vtype_t vtype;
+ md_store_group_t group;
+ void *value;
+ int create;
+ apr_status_t rv;
+ const perms_t *perms;
+ const char *pass;
+ apr_size_t pass_len;
+
+ group = (md_store_group_t)va_arg(ap, int);
+ name = va_arg(ap, const char*);
+ aspect = va_arg(ap, const char*);
+ vtype = (md_store_vtype_t)va_arg(ap, int);
+ value = va_arg(ap, void *);
+ create = va_arg(ap, int);
+
+ perms = gperms(s_fs, group);
+
+ if ( MD_OK(mk_group_dir(&gdir, s_fs, group, NULL, p))
+ && MD_OK(mk_group_dir(&dir, s_fs, group, name, p))
+ && MD_OK(md_util_path_merge(&fpath, ptemp, dir, aspect, NULL))) {
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, ptemp, "storing in %s", fpath);
+ switch (vtype) {
+ case MD_SV_TEXT:
+ rv = (create? md_text_fcreatex(fpath, perms->file, p, value)
+ : md_text_freplace(fpath, perms->file, p, value));
+ break;
+ case MD_SV_JSON:
+ rv = (create? md_json_fcreatex((md_json_t *)value, p, MD_JSON_FMT_INDENT,
+ fpath, perms->file)
+ : md_json_freplace((md_json_t *)value, p, MD_JSON_FMT_INDENT,
+ fpath, perms->file));
+ break;
+ case MD_SV_CERT:
+ rv = md_cert_fsave((md_cert_t *)value, ptemp, fpath, perms->file);
+ break;
+ case MD_SV_PKEY:
+ /* Take care that we write private key with access only to the user,
+ * unless we write the key encrypted */
+ get_pass(&pass, &pass_len, s_fs, group);
+ rv = md_pkey_fsave((md_pkey_t *)value, ptemp, pass, pass_len,
+ fpath, (pass && pass_len)? perms->file : MD_FPROT_F_UONLY);
+ break;
+ case MD_SV_CHAIN:
+ rv = md_chain_fsave((apr_array_header_t*)value, ptemp, fpath, perms->file);
+ break;
+ default:
+ return APR_ENOTIMPL;
+ }
+ if (APR_SUCCESS == rv) {
+ rv = dispatch(s_fs, MD_S_FS_EV_CREATED, group, fpath, APR_REG, p);
+ }
+ }
+ return rv;
+}
+
+static apr_status_t pfs_remove(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+{
+ md_store_fs_t *s_fs = baton;
+ const char *dir, *name, *fpath, *groupname, *aspect;
+ apr_status_t rv;
+ int force;
+ apr_finfo_t info;
+ md_store_group_t group;
+
+ (void)p;
+ group = (md_store_group_t)va_arg(ap, int);
+ name = va_arg(ap, const char*);
+ aspect = va_arg(ap, const char *);
+ force = va_arg(ap, int);
+
+ groupname = md_store_group_name(group);
+
+ if ( MD_OK(md_util_path_merge(&dir, ptemp, s_fs->base, groupname, name, NULL))
+ && MD_OK(md_util_path_merge(&fpath, ptemp, dir, aspect, NULL))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "start remove of md %s/%s/%s",
+ groupname, name, aspect);
+
+ if (!MD_OK(apr_stat(&info, dir, APR_FINFO_TYPE, ptemp))) {
+ if (APR_ENOENT == rv && force) {
+ return APR_SUCCESS;
+ }
+ return rv;
+ }
+
+ rv = apr_file_remove(fpath, ptemp);
+ if (APR_ENOENT == rv && force) {
+ rv = APR_SUCCESS;
+ }
+ }
+ return rv;
+}
+
+static apr_status_t fs_load(md_store_t *store, md_store_group_t group,
+ const char *name, const char *aspect,
+ md_store_vtype_t vtype, void **pvalue, apr_pool_t *p)
+{
+ md_store_fs_t *s_fs = FS_STORE(store);
+ return md_util_pool_vdo(pfs_load, s_fs, p, group, name, aspect, vtype, pvalue, NULL);
+}
+
+static apr_status_t fs_save(md_store_t *store, apr_pool_t *p, md_store_group_t group,
+ const char *name, const char *aspect,
+ md_store_vtype_t vtype, void *value, int create)
+{
+ md_store_fs_t *s_fs = FS_STORE(store);
+ return md_util_pool_vdo(pfs_save, s_fs, p, group, name, aspect,
+ vtype, value, create, NULL);
+}
+
+static apr_status_t fs_remove(md_store_t *store, md_store_group_t group,
+ const char *name, const char *aspect,
+ apr_pool_t *p, int force)
+{
+ md_store_fs_t *s_fs = FS_STORE(store);
+ return md_util_pool_vdo(pfs_remove, s_fs, p, group, name, aspect, force, NULL);
+}
+
+static apr_status_t pfs_purge(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+{
+ md_store_fs_t *s_fs = baton;
+ const char *dir, *name, *groupname;
+ md_store_group_t group;
+ apr_status_t rv;
+
+ (void)p;
+ group = (md_store_group_t)va_arg(ap, int);
+ name = va_arg(ap, const char*);
+
+ groupname = md_store_group_name(group);
+
+ if (MD_OK(md_util_path_merge(&dir, ptemp, s_fs->base, groupname, name, NULL))) {
+ /* Remove all files in dir, there should be no sub-dirs */
+ rv = md_util_rm_recursive(dir, ptemp, 1);
+ }
+ if (!APR_STATUS_IS_ENOENT(rv)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, ptemp, "purge %s/%s (%s)", groupname, name, dir);
+ }
+ return APR_SUCCESS;
+}
+
+static apr_status_t fs_purge(md_store_t *store, apr_pool_t *p,
+ md_store_group_t group, const char *name)
+{
+ md_store_fs_t *s_fs = FS_STORE(store);
+ return md_util_pool_vdo(pfs_purge, s_fs, p, group, name, NULL);
+}
+
+/**************************************************************************************************/
+/* iteration */
+
+typedef struct {
+ md_store_fs_t *s_fs;
+ md_store_group_t group;
+ const char *pattern;
+ const char *aspect;
+ md_store_vtype_t vtype;
+ md_store_inspect *inspect;
+ const char *dirname;
+ void *baton;
+ apr_time_t ts;
+} inspect_ctx;
+
+static apr_status_t insp(void *baton, apr_pool_t *p, apr_pool_t *ptemp,
+ const char *dir, const char *name, apr_filetype_e ftype)
+{
+ inspect_ctx *ctx = baton;
+ apr_status_t rv;
+ void *value;
+ const char *fpath;
+
+ (void)ftype;
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, ptemp, "inspecting value at: %s/%s", dir, name);
+ if (APR_SUCCESS == (rv = md_util_path_merge(&fpath, ptemp, dir, name, NULL))) {
+ rv = fs_fload(&value, ctx->s_fs, fpath, ctx->group, ctx->vtype, p, ptemp);
+ if (APR_SUCCESS == rv
+ && !ctx->inspect(ctx->baton, ctx->dirname, name, ctx->vtype, value, p)) {
+ return APR_EOF;
+ }
+ else if (APR_STATUS_IS_ENOENT(rv)) {
+ rv = APR_SUCCESS;
+ }
+ }
+ return rv;
+}
+
+static apr_status_t insp_dir(void *baton, apr_pool_t *p, apr_pool_t *ptemp,
+ const char *dir, const char *name, apr_filetype_e ftype)
+{
+ inspect_ctx *ctx = baton;
+ apr_status_t rv;
+ const char *fpath;
+
+ (void)ftype;
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, ptemp, "inspecting dir at: %s/%s", dir, name);
+ if (MD_OK(md_util_path_merge(&fpath, p, dir, name, NULL))) {
+ ctx->dirname = name;
+ rv = md_util_files_do(insp, ctx, p, fpath, ctx->aspect, NULL);
+ if (APR_STATUS_IS_ENOENT(rv)) {
+ rv = APR_SUCCESS;
+ }
+ }
+ return rv;
+}
+
+static apr_status_t fs_iterate(md_store_inspect *inspect, void *baton, md_store_t *store,
+ apr_pool_t *p, md_store_group_t group, const char *pattern,
+ const char *aspect, md_store_vtype_t vtype)
+{
+ const char *groupname;
+ apr_status_t rv;
+ inspect_ctx ctx;
+
+ ctx.s_fs = FS_STORE(store);
+ ctx.group = group;
+ ctx.pattern = pattern;
+ ctx.aspect = aspect;
+ ctx.vtype = vtype;
+ ctx.inspect = inspect;
+ ctx.baton = baton;
+ groupname = md_store_group_name(group);
+
+ rv = md_util_files_do(insp_dir, &ctx, p, ctx.s_fs->base, groupname, pattern, NULL);
+
+ return rv;
+}
+
+static apr_status_t insp_name(void *baton, apr_pool_t *p, apr_pool_t *ptemp,
+ const char *dir, const char *name, apr_filetype_e ftype)
+{
+ inspect_ctx *ctx = baton;
+
+ (void)ftype;
+ (void)p;
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, ptemp, "inspecting name at: %s/%s", dir, name);
+ return ctx->inspect(ctx->baton, dir, name, 0, NULL, ptemp);
+}
+
+static apr_status_t fs_iterate_names(md_store_inspect *inspect, void *baton, md_store_t *store,
+ apr_pool_t *p, md_store_group_t group, const char *pattern)
+{
+ const char *groupname;
+ apr_status_t rv;
+ inspect_ctx ctx;
+
+ ctx.s_fs = FS_STORE(store);
+ ctx.group = group;
+ ctx.pattern = pattern;
+ ctx.inspect = inspect;
+ ctx.baton = baton;
+ groupname = md_store_group_name(group);
+
+ rv = md_util_files_do(insp_name, &ctx, p, ctx.s_fs->base, groupname, pattern, NULL);
+
+ return rv;
+}
+
+static apr_status_t remove_nms_file(void *baton, apr_pool_t *p, apr_pool_t *ptemp,
+ const char *dir, const char *name, apr_filetype_e ftype)
+{
+ inspect_ctx *ctx = baton;
+ const char *fname;
+ apr_finfo_t inf;
+ apr_status_t rv = APR_SUCCESS;
+
+ (void)p;
+ if (APR_DIR == ftype) goto leave;
+ if (APR_SUCCESS != (rv = md_util_path_merge(&fname, ptemp, dir, name, NULL))) goto leave;
+ if (APR_SUCCESS != (rv = apr_stat(&inf, fname, APR_FINFO_MTIME, ptemp))) goto leave;
+ if (inf.mtime >= ctx->ts) goto leave;
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, ptemp, "remove_nms file: %s/%s", dir, name);
+ rv = apr_file_remove(fname, ptemp);
+
+leave:
+ return rv;
+}
+
+static apr_status_t remove_nms_dir(void *baton, apr_pool_t *p, apr_pool_t *ptemp,
+ const char *dir, const char *name, apr_filetype_e ftype)
+{
+ inspect_ctx *ctx = baton;
+ apr_status_t rv;
+ const char *fpath;
+
+ (void)ftype;
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, ptemp, "remove_nms dir at: %s/%s", dir, name);
+ if (MD_OK(md_util_path_merge(&fpath, p, dir, name, NULL))) {
+ ctx->dirname = name;
+ rv = md_util_files_do(remove_nms_file, ctx, p, fpath, ctx->aspect, NULL);
+ if (APR_STATUS_IS_ENOENT(rv)) {
+ rv = APR_SUCCESS;
+ }
+ }
+ return rv;
+}
+
+static apr_status_t fs_remove_nms(md_store_t *store, apr_pool_t *p,
+ apr_time_t modified, md_store_group_t group,
+ const char *name, const char *aspect)
+{
+ const char *groupname;
+ apr_status_t rv;
+ inspect_ctx ctx;
+
+ ctx.s_fs = FS_STORE(store);
+ ctx.group = group;
+ ctx.pattern = name;
+ ctx.aspect = aspect;
+ ctx.ts = modified;
+ groupname = md_store_group_name(group);
+
+ rv = md_util_files_do(remove_nms_dir, &ctx, p, ctx.s_fs->base, groupname, name, NULL);
+
+ return rv;
+}
+
+/**************************************************************************************************/
+/* moving */
+
+static apr_status_t pfs_move(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+{
+ md_store_fs_t *s_fs = baton;
+ const char *name, *from_group, *to_group, *from_dir, *to_dir, *arch_dir, *dir;
+ md_store_group_t from, to;
+ int archive;
+ apr_status_t rv;
+
+ (void)p;
+ from = (md_store_group_t)va_arg(ap, int);
+ to = (md_store_group_t)va_arg(ap, int);
+ name = va_arg(ap, const char*);
+ archive = va_arg(ap, int);
+
+ from_group = md_store_group_name(from);
+ to_group = md_store_group_name(to);
+ if (!strcmp(from_group, to_group)) {
+ return APR_EINVAL;
+ }
+
+ if ( !MD_OK(md_util_path_merge(&from_dir, ptemp, s_fs->base, from_group, name, NULL))
+ || !MD_OK(md_util_path_merge(&to_dir, ptemp, s_fs->base, to_group, name, NULL))) {
+ goto out;
+ }
+
+ if (!MD_OK(md_util_is_dir(from_dir, ptemp))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ptemp, "source is no dir: %s", from_dir);
+ goto out;
+ }
+
+ if (MD_OK(archive? md_util_is_dir(to_dir, ptemp) : APR_ENOENT)) {
+ int n = 1;
+ const char *narch_dir;
+
+ if ( !MD_OK(md_util_path_merge(&dir, ptemp, s_fs->base,
+ md_store_group_name(MD_SG_ARCHIVE), NULL))
+ || !MD_OK(apr_dir_make_recursive(dir, MD_FPROT_D_UONLY, ptemp))
+ || !MD_OK(md_util_path_merge(&arch_dir, ptemp, dir, name, NULL))) {
+ goto out;
+ }
+
+#ifdef WIN32
+ /* WIN32 and handling of files/dirs. What can one say? */
+
+ while (n < 1000) {
+ narch_dir = apr_psprintf(ptemp, "%s.%d", arch_dir, n);
+ rv = md_util_is_dir(narch_dir, ptemp);
+ if (APR_STATUS_IS_ENOENT(rv)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, ptemp, "using archive dir: %s",
+ narch_dir);
+ break;
+ }
+ else {
+ ++n;
+ narch_dir = NULL;
+ }
+ }
+
+#else /* ifdef WIN32 */
+
+ while (n < 1000) {
+ narch_dir = apr_psprintf(ptemp, "%s.%d", arch_dir, n);
+ if (MD_OK(apr_dir_make(narch_dir, MD_FPROT_D_UONLY, ptemp))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, ptemp, "using archive dir: %s",
+ narch_dir);
+ break;
+ }
+ else if (APR_EEXIST == rv) {
+ ++n;
+ narch_dir = NULL;
+ }
+ else {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, ptemp, "creating archive dir: %s",
+ narch_dir);
+ goto out;
+ }
+ }
+
+#endif /* ifdef WIN32 (else part) */
+
+ if (!narch_dir) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, ptemp, "ran out of numbers less than 1000 "
+ "while looking for an available one in %s to archive the data "
+ "from %s. Either something is generally wrong or you need to "
+ "clean up some of those directories.", arch_dir, from_dir);
+ rv = APR_EGENERAL;
+ goto out;
+ }
+
+ if (!MD_OK(apr_file_rename(to_dir, narch_dir, ptemp))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, ptemp, "rename from %s to %s",
+ to_dir, narch_dir);
+ goto out;
+ }
+ if (!MD_OK(apr_file_rename(from_dir, to_dir, ptemp))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, ptemp, "rename from %s to %s",
+ from_dir, to_dir);
+ apr_file_rename(narch_dir, to_dir, ptemp);
+ goto out;
+ }
+ if (MD_OK(dispatch(s_fs, MD_S_FS_EV_MOVED, to, to_dir, APR_DIR, ptemp))) {
+ rv = dispatch(s_fs, MD_S_FS_EV_MOVED, MD_SG_ARCHIVE, narch_dir, APR_DIR, ptemp);
+ }
+ }
+ else if (APR_STATUS_IS_ENOENT(rv)) {
+ if (APR_SUCCESS != (rv = apr_file_rename(from_dir, to_dir, ptemp))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, ptemp, "rename from %s to %s",
+ from_dir, to_dir);
+ goto out;
+ }
+ }
+ else {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ptemp, "target is no dir: %s", to_dir);
+ goto out;
+ }
+
+out:
+ return rv;
+}
+
+static apr_status_t fs_move(md_store_t *store, apr_pool_t *p,
+ md_store_group_t from, md_store_group_t to,
+ const char *name, int archive)
+{
+ md_store_fs_t *s_fs = FS_STORE(store);
+ return md_util_pool_vdo(pfs_move, s_fs, p, from, to, name, archive, NULL);
+}
+
+static apr_status_t pfs_rename(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+{
+ md_store_fs_t *s_fs = baton;
+ const char *group_name, *from_dir, *to_dir;
+ md_store_group_t group;
+ const char *from, *to;
+ apr_status_t rv;
+
+ (void)p;
+ group = (md_store_group_t)va_arg(ap, int);
+ from = va_arg(ap, const char*);
+ to = va_arg(ap, const char*);
+
+ group_name = md_store_group_name(group);
+ if ( !MD_OK(md_util_path_merge(&from_dir, ptemp, s_fs->base, group_name, from, NULL))
+ || !MD_OK(md_util_path_merge(&to_dir, ptemp, s_fs->base, group_name, to, NULL))) {
+ goto out;
+ }
+
+ if (APR_SUCCESS != (rv = apr_file_rename(from_dir, to_dir, ptemp))
+ && !APR_STATUS_IS_ENOENT(rv)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, ptemp, "rename from %s to %s",
+ from_dir, to_dir);
+ goto out;
+ }
+out:
+ return rv;
+}
+
+static apr_status_t fs_rename(md_store_t *store, apr_pool_t *p,
+ md_store_group_t group, const char *from, const char *to)
+{
+ md_store_fs_t *s_fs = FS_STORE(store);
+ return md_util_pool_vdo(pfs_rename, s_fs, p, group, from, to, NULL);
+}
+
+static apr_status_t fs_lock_global(md_store_t *store, apr_pool_t *p, apr_time_t max_wait)
+{
+ md_store_fs_t *s_fs = FS_STORE(store);
+ apr_status_t rv;
+ const char *lpath;
+ apr_time_t end;
+
+ if (s_fs->global_lock) {
+ rv = APR_EEXIST;
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "already locked globally");
+ goto cleanup;
+ }
+
+ rv = md_util_path_merge(&lpath, p, s_fs->base, MD_FS_LOCK_NAME, NULL);
+ if (APR_SUCCESS != rv) goto cleanup;
+ end = apr_time_now() + max_wait;
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, p,
+ "acquire global lock: %s", lpath);
+ while (apr_time_now() < end) {
+ rv = apr_file_open(&s_fs->global_lock, lpath,
+ (APR_FOPEN_WRITE|APR_FOPEN_CREATE),
+ MD_FPROT_F_UALL_GREAD, p);
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, p,
+ "unable to create/open lock file: %s",
+ lpath);
+ goto next_try;
+ }
+ rv = apr_file_lock(s_fs->global_lock,
+ APR_FLOCK_EXCLUSIVE|APR_FLOCK_NONBLOCK);
+ if (APR_SUCCESS == rv) {
+ goto cleanup;
+ }
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, p,
+ "unable to obtain lock on: %s",
+ lpath);
+
+ next_try:
+ if (s_fs->global_lock) {
+ apr_file_close(s_fs->global_lock);
+ s_fs->global_lock = NULL;
+ }
+ apr_sleep(apr_time_from_msec(100));
+ }
+ rv = APR_EGENERAL;
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, p,
+ "acquire global lock: %s", lpath);
+
+cleanup:
+ return rv;
+}
+
+static void fs_unlock_global(md_store_t *store, apr_pool_t *p)
+{
+ md_store_fs_t *s_fs = FS_STORE(store);
+
+ (void)p;
+ if (s_fs->global_lock) {
+ apr_file_close(s_fs->global_lock);
+ s_fs->global_lock = NULL;
+ }
+}
diff --git a/modules/md/md_store_fs.h b/modules/md/md_store_fs.h
new file mode 100644
index 0000000..dcdb897
--- /dev/null
+++ b/modules/md/md_store_fs.h
@@ -0,0 +1,65 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mod_md_md_store_fs_h
+#define mod_md_md_store_fs_h
+
+struct md_store_t;
+
+/**
+ * Default file permissions set by the store, user only read/write(/exec),
+ * if so supported by the apr.
+ */
+#define MD_FPROT_F_UONLY (APR_FPROT_UREAD|APR_FPROT_UWRITE)
+#define MD_FPROT_D_UONLY (MD_FPROT_F_UONLY|APR_FPROT_UEXECUTE)
+
+/**
+ * User has all permission, group can read, other none
+ */
+#define MD_FPROT_F_UALL_GREAD (MD_FPROT_F_UONLY|APR_FPROT_GREAD)
+#define MD_FPROT_D_UALL_GREAD (MD_FPROT_D_UONLY|APR_FPROT_GREAD|APR_FPROT_GEXECUTE)
+
+/**
+ * User has all permission, group and others can read
+ */
+#define MD_FPROT_F_UALL_WREAD (MD_FPROT_F_UALL_GREAD|APR_FPROT_WREAD)
+#define MD_FPROT_D_UALL_WREAD (MD_FPROT_D_UALL_GREAD|APR_FPROT_WREAD|APR_FPROT_WEXECUTE)
+
+apr_status_t md_store_fs_init(struct md_store_t **pstore, apr_pool_t *p,
+ const char *path);
+
+
+apr_status_t md_store_fs_default_perms_set(struct md_store_t *store,
+ apr_fileperms_t file_perms,
+ apr_fileperms_t dir_perms);
+apr_status_t md_store_fs_group_perms_set(struct md_store_t *store,
+ md_store_group_t group,
+ apr_fileperms_t file_perms,
+ apr_fileperms_t dir_perms);
+
+typedef enum {
+ MD_S_FS_EV_CREATED,
+ MD_S_FS_EV_MOVED,
+} md_store_fs_ev_t;
+
+typedef apr_status_t md_store_fs_cb(void *baton, struct md_store_t *store,
+ md_store_fs_ev_t ev, unsigned int group,
+ const char *fname, apr_filetype_e ftype,
+ apr_pool_t *p);
+
+apr_status_t md_store_fs_set_event_cb(struct md_store_t *store, md_store_fs_cb *cb, void *baton);
+
+#endif /* mod_md_md_store_fs_h */
diff --git a/modules/md/md_tailscale.c b/modules/md/md_tailscale.c
new file mode 100644
index 0000000..c8d2bad
--- /dev/null
+++ b/modules/md/md_tailscale.c
@@ -0,0 +1,383 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include <apr_lib.h>
+#include <apr_strings.h>
+#include <apr_hash.h>
+#include <apr_uri.h>
+
+#include "md.h"
+#include "md_crypt.h"
+#include "md_json.h"
+#include "md_http.h"
+#include "md_log.h"
+#include "md_result.h"
+#include "md_reg.h"
+#include "md_store.h"
+#include "md_util.h"
+
+#include "md_tailscale.h"
+
+typedef struct {
+ apr_pool_t *pool;
+ md_proto_driver_t *driver;
+ const char *unix_socket_path;
+ md_t *md;
+ apr_array_header_t *chain;
+ md_pkey_t *pkey;
+} ts_ctx_t;
+
+static apr_status_t ts_init(md_proto_driver_t *d, md_result_t *result)
+{
+ ts_ctx_t *ts_ctx;
+ apr_uri_t uri;
+ const char *ca_url;
+ apr_status_t rv = APR_SUCCESS;
+
+ md_result_set(result, APR_SUCCESS, NULL);
+ ts_ctx = apr_pcalloc(d->p, sizeof(*ts_ctx));
+ ts_ctx->pool = d->p;
+ ts_ctx->driver = d;
+ ts_ctx->chain = apr_array_make(d->p, 5, sizeof(md_cert_t *));
+
+ ca_url = (d->md->ca_urls && !apr_is_empty_array(d->md->ca_urls))?
+ APR_ARRAY_IDX(d->md->ca_urls, 0, const char*) : NULL;
+ if (!ca_url) {
+ ca_url = MD_TAILSCALE_DEF_URL;
+ }
+ rv = apr_uri_parse(d->p, ca_url, &uri);
+ if (APR_SUCCESS != rv) {
+ md_result_printf(result, rv, "error parsing CA URL `%s`", ca_url);
+ goto leave;
+ }
+ if (uri.scheme && uri.scheme[0] && strcmp("file", uri.scheme)) {
+ rv = APR_ENOTIMPL;
+ md_result_printf(result, rv, "non `file` URLs not supported, CA URL is `%s`",
+ ca_url);
+ goto leave;
+ }
+ if (uri.hostname && uri.hostname[0] && strcmp("localhost", uri.hostname)) {
+ rv = APR_ENOTIMPL;
+ md_result_printf(result, rv, "non `localhost` URLs not supported, CA URL is `%s`",
+ ca_url);
+ goto leave;
+ }
+ ts_ctx->unix_socket_path = uri.path;
+ d->baton = ts_ctx;
+
+leave:
+ return rv;
+}
+
+static apr_status_t ts_preload_init(md_proto_driver_t *d, md_result_t *result)
+{
+ return ts_init(d, result);
+}
+
+static apr_status_t ts_preload(md_proto_driver_t *d,
+ md_store_group_t load_group, md_result_t *result)
+{
+ apr_status_t rv;
+ md_t *md;
+ md_credentials_t *creds;
+ md_pkey_spec_t *pkspec;
+ apr_array_header_t *all_creds;
+ const char *name;
+ int i;
+
+ name = d->md->name;
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: preload start", name);
+ /* Load data from MD_SG_STAGING and save it into "load_group".
+ */
+ if (APR_SUCCESS != (rv = md_load(d->store, MD_SG_STAGING, name, &md, d->p))) {
+ md_result_set(result, rv, "loading staged md.json");
+ goto leave;
+ }
+
+ /* tailscale generates one cert+key with key specification being whatever
+ * it chooses. Use the NULL spec here.
+ */
+ all_creds = apr_array_make(d->p, 5, sizeof(md_credentials_t*));
+ pkspec = NULL;
+ if (APR_SUCCESS != (rv = md_creds_load(d->store, MD_SG_STAGING, name, pkspec, &creds, d->p))) {
+ md_result_printf(result, rv, "loading staged credentials");
+ goto leave;
+ }
+ if (!creds->chain) {
+ rv = APR_ENOENT;
+ md_result_printf(result, rv, "no certificate in staged credentials");
+ goto leave;
+ }
+ if (APR_SUCCESS != (rv = md_check_cert_and_pkey(creds->chain, creds->pkey))) {
+ md_result_printf(result, rv, "certificate and private key do not match in staged credentials");
+ goto leave;
+ }
+ APR_ARRAY_PUSH(all_creds, md_credentials_t*) = creds;
+
+ md_result_activity_setn(result, "purging store tmp space");
+ rv = md_store_purge(d->store, d->p, load_group, name);
+ if (APR_SUCCESS != rv) {
+ md_result_set(result, rv, NULL);
+ goto leave;
+ }
+
+ md_result_activity_setn(result, "saving staged md/privkey/pubcert");
+ if (APR_SUCCESS != (rv = md_save(d->store, d->p, load_group, md, 1))) {
+ md_result_set(result, rv, "writing md.json");
+ goto leave;
+ }
+
+ for (i = 0; i < all_creds->nelts; ++i) {
+ creds = APR_ARRAY_IDX(all_creds, i, md_credentials_t*);
+ if (APR_SUCCESS != (rv = md_creds_save(d->store, d->p, load_group, name, creds, 1))) {
+ md_result_printf(result, rv, "writing credentials #%d", i);
+ goto leave;
+ }
+ }
+
+ md_result_set(result, APR_SUCCESS, "saved staged data successfully");
+
+leave:
+ md_result_log(result, MD_LOG_DEBUG);
+ return rv;
+}
+
+static apr_status_t rv_of_response(const md_http_response_t *res)
+{
+ switch (res->status) {
+ case 200:
+ return APR_SUCCESS;
+ case 400:
+ return APR_EINVAL;
+ case 401: /* sectigo returns this instead of 403 */
+ case 403:
+ return APR_EACCES;
+ case 404:
+ return APR_ENOENT;
+ default:
+ return APR_EGENERAL;
+ }
+ return APR_SUCCESS;
+}
+
+static apr_status_t on_get_cert(const md_http_response_t *res, void *baton)
+{
+ ts_ctx_t *ts_ctx = baton;
+ apr_status_t rv;
+
+ rv = rv_of_response(res);
+ if (APR_SUCCESS != rv) goto leave;
+ apr_array_clear(ts_ctx->chain);
+ rv = md_cert_chain_read_http(ts_ctx->chain, ts_ctx->pool, res);
+ if (APR_SUCCESS != rv) goto leave;
+
+leave:
+ return rv;
+}
+
+static apr_status_t on_get_key(const md_http_response_t *res, void *baton)
+{
+ ts_ctx_t *ts_ctx = baton;
+ apr_status_t rv;
+
+ rv = rv_of_response(res);
+ if (APR_SUCCESS != rv) goto leave;
+ rv = md_pkey_read_http(&ts_ctx->pkey, ts_ctx->pool, res);
+ if (APR_SUCCESS != rv) goto leave;
+
+leave:
+ return rv;
+}
+
+static apr_status_t ts_renew(md_proto_driver_t *d, md_result_t *result)
+{
+ const char *name, *domain, *url;
+ apr_status_t rv = APR_ENOENT;
+ ts_ctx_t *ts_ctx = d->baton;
+ md_http_t *http;
+ const md_pubcert_t *pubcert;
+ md_cert_t *old_cert, *new_cert;
+ int reset_staging = d->reset;
+
+ /* "renewing" the certificate from tailscale. Since tailscale has its
+ * own ideas on when to do this, we can only inspect the certificate
+ * it gives us and see if it is different from the current one we have.
+ * (if we have any. first time, lacking a cert, any it gives us is
+ * considered as 'renewed'.)
+ */
+ name = d->md->name;
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: renewing cert", name);
+
+ /* When not explicitly told to reset, we check the existing data. If
+ * it is incomplete or old, we trigger the reset for a clean start. */
+ if (!reset_staging) {
+ md_result_activity_setn(result, "Checking staging area");
+ rv = md_load(d->store, MD_SG_STAGING, d->md->name, &ts_ctx->md, d->p);
+ if (APR_SUCCESS == rv) {
+ /* So, we have a copy in staging, but is it a recent or an old one? */
+ if (md_is_newer(d->store, MD_SG_DOMAINS, MD_SG_STAGING, d->md->name, d->p)) {
+ reset_staging = 1;
+ }
+ }
+ else if (APR_STATUS_IS_ENOENT(rv)) {
+ reset_staging = 1;
+ rv = APR_SUCCESS;
+ }
+ }
+
+ if (reset_staging) {
+ md_result_activity_setn(result, "Resetting staging area");
+ /* reset the staging area for this domain */
+ rv = md_store_purge(d->store, d->p, MD_SG_STAGING, d->md->name);
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, d->p,
+ "%s: reset staging area", d->md->name);
+ if (APR_SUCCESS != rv && !APR_STATUS_IS_ENOENT(rv)) {
+ md_result_printf(result, rv, "resetting staging area");
+ goto leave;
+ }
+ rv = APR_SUCCESS;
+ ts_ctx->md = NULL;
+ }
+
+ if (!ts_ctx->md || !md_array_str_eq(ts_ctx->md->ca_urls, d->md->ca_urls, 1)) {
+ md_result_activity_printf(result, "Resetting staging for %s", d->md->name);
+ /* re-initialize staging */
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: setup staging", d->md->name);
+ md_store_purge(d->store, d->p, MD_SG_STAGING, d->md->name);
+ ts_ctx->md = md_copy(d->p, d->md);
+ rv = md_save(d->store, d->p, MD_SG_STAGING, ts_ctx->md, 0);
+ if (APR_SUCCESS != rv) {
+ md_result_printf(result, rv, "Saving MD information in staging area.");
+ md_result_log(result, MD_LOG_ERR);
+ goto leave;
+ }
+ }
+
+ if (!ts_ctx->unix_socket_path) {
+ rv = APR_ENOTIMPL;
+ md_result_set(result, rv, "only unix sockets are supported for tailscale connections");
+ goto leave;
+ }
+
+ rv = md_util_is_unix_socket(ts_ctx->unix_socket_path, d->p);
+ if (APR_SUCCESS != rv) {
+ md_result_printf(result, rv, "tailscale socket not available, may not be up: %s",
+ ts_ctx->unix_socket_path);
+ goto leave;
+ }
+
+ rv = md_http_create(&http, d->p,
+ apr_psprintf(d->p, "Apache mod_md/%s", MOD_MD_VERSION),
+ NULL);
+ if (APR_SUCCESS != rv) {
+ md_result_set(result, rv, "creating http context");
+ goto leave;
+ }
+ md_http_set_unix_socket_path(http, ts_ctx->unix_socket_path);
+
+ domain = (d->md->domains->nelts > 0)?
+ APR_ARRAY_IDX(d->md->domains, 0, const char*) : NULL;
+ if (!domain) {
+ rv = APR_EINVAL;
+ md_result_set(result, rv, "no domain names available");
+ }
+
+ url = apr_psprintf(d->p, "http://localhost/localapi/v0/cert/%s?type=crt",
+ domain);
+ rv = md_http_GET_perform(http, url, NULL, on_get_cert, ts_ctx);
+ if (APR_SUCCESS != rv) {
+ md_result_set(result, rv, "retrieving certificate from tailscale");
+ goto leave;
+ }
+ if (ts_ctx->chain->nelts <= 0) {
+ rv = APR_ENOENT;
+ md_result_set(result, rv, "tailscale returned no certificates");
+ goto leave;
+ }
+
+ /* Got the key and the chain, is it new? */
+ rv = md_reg_get_pubcert(&pubcert, d->reg,d->md, 0, d->p);
+ if (APR_SUCCESS == rv) {
+ old_cert = APR_ARRAY_IDX(pubcert->certs, 0, md_cert_t*);
+ new_cert = APR_ARRAY_IDX(ts_ctx->chain, 0, md_cert_t*);
+ if (md_certs_are_equal(old_cert, new_cert)) {
+ /* tailscale has not renewed the certificate, yet */
+ rv = APR_ENOENT;
+ md_result_set(result, rv, "tailscale has not renewed the certificate yet");
+ /* let's check this daily */
+ md_result_delay_set(result, apr_time_now() + apr_time_from_sec(MD_SECS_PER_DAY));
+ goto leave;
+ }
+ }
+
+ /* We have a new certificate (or had none before).
+ * Get the key and store both in STAGING.
+ */
+ url = apr_psprintf(d->p, "http://localhost/localapi/v0/cert/%s?type=key",
+ domain);
+ rv = md_http_GET_perform(http, url, NULL, on_get_key, ts_ctx);
+ if (APR_SUCCESS != rv) {
+ md_result_set(result, rv, "retrieving key from tailscale");
+ goto leave;
+ }
+
+ rv = md_pkey_save(d->store, d->p, MD_SG_STAGING, name, NULL, ts_ctx->pkey, 1);
+ if (APR_SUCCESS != rv) {
+ md_result_set(result, rv, "saving private key");
+ goto leave;
+ }
+
+ rv = md_pubcert_save(d->store, d->p, MD_SG_STAGING, name,
+ NULL, ts_ctx->chain, 1);
+ if (APR_SUCCESS != rv) {
+ md_result_printf(result, rv, "saving new certificate chain.");
+ goto leave;
+ }
+
+ md_result_set(result, APR_SUCCESS,
+ "A new tailscale certificate has been retrieved successfully and can "
+ "be used. A graceful server restart is recommended.");
+
+leave:
+ md_result_log(result, MD_LOG_DEBUG);
+ return rv;
+}
+
+static apr_status_t ts_complete_md(md_t *md, apr_pool_t *p)
+{
+ (void)p;
+ if (!md->ca_urls) {
+ md->ca_urls = apr_array_make(p, 3, sizeof(const char *));
+ APR_ARRAY_PUSH(md->ca_urls, const char*) = MD_TAILSCALE_DEF_URL;
+ }
+ return APR_SUCCESS;
+}
+
+
+static md_proto_t TAILSCALE_PROTO = {
+ MD_PROTO_TAILSCALE, ts_init, ts_renew,
+ ts_preload_init, ts_preload, ts_complete_md,
+};
+
+apr_status_t md_tailscale_protos_add(apr_hash_t *protos, apr_pool_t *p)
+{
+ (void)p;
+ apr_hash_set(protos, MD_PROTO_TAILSCALE, sizeof(MD_PROTO_TAILSCALE)-1, &TAILSCALE_PROTO);
+ return APR_SUCCESS;
+}
diff --git a/modules/md/md_tailscale.h b/modules/md/md_tailscale.h
new file mode 100644
index 0000000..67a874d
--- /dev/null
+++ b/modules/md/md_tailscale.h
@@ -0,0 +1,25 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mod_md_md_tailscale_h
+#define mod_md_md_tailscale_h
+
+#define MD_PROTO_TAILSCALE "tailscale"
+
+apr_status_t md_tailscale_protos_add(struct apr_hash_t *protos, apr_pool_t *p);
+
+#endif /* mod_md_md_tailscale_h */
+
diff --git a/modules/md/md_time.c b/modules/md/md_time.c
new file mode 100644
index 0000000..268ca83
--- /dev/null
+++ b/modules/md/md_time.c
@@ -0,0 +1,325 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+
+#include <apr_lib.h>
+#include <apr_strings.h>
+#include <apr_time.h>
+
+#include "md.h"
+#include "md_time.h"
+
+apr_time_t md_timeperiod_length(const md_timeperiod_t *period)
+{
+ return (period->start < period->end)? (period->end - period->start) : 0;
+}
+
+int md_timeperiod_contains(const md_timeperiod_t *period, apr_time_t time)
+{
+ return md_timeperiod_has_started(period, time)
+ && !md_timeperiod_has_ended(period, time);
+}
+
+int md_timeperiod_has_started(const md_timeperiod_t *period, apr_time_t time)
+{
+ return (time >= period->start);
+}
+
+int md_timeperiod_has_ended(const md_timeperiod_t *period, apr_time_t time)
+{
+ return (time >= period->start) && (time <= period->end);
+}
+
+apr_interval_time_t md_timeperiod_remaining(const md_timeperiod_t *period, apr_time_t time)
+{
+ if (time < period->start) return md_timeperiod_length(period);
+ if (time < period->end) return period->end - time;
+ return 0;
+}
+
+char *md_timeperiod_print(apr_pool_t *p, const md_timeperiod_t *period)
+{
+ char tstart[APR_RFC822_DATE_LEN];
+ char tend[APR_RFC822_DATE_LEN];
+
+ apr_rfc822_date(tstart, period->start);
+ apr_rfc822_date(tend, period->end);
+ return apr_pstrcat(p, tstart, " - ", tend, NULL);
+}
+
+static const char *duration_print(apr_pool_t *p, int roughly, apr_interval_time_t duration)
+{
+ const char *s = "", *sep = "";
+ long days = (long)(apr_time_sec(duration) / MD_SECS_PER_DAY);
+ int rem = (int)(apr_time_sec(duration) % MD_SECS_PER_DAY);
+
+ s = roughly? "~" : "";
+ if (days > 0) {
+ s = apr_psprintf(p, "%s%ld days", s, days);
+ if (roughly) return s;
+ sep = " ";
+ }
+ if (rem > 0) {
+ int hours = (rem / MD_SECS_PER_HOUR);
+ rem = (rem % MD_SECS_PER_HOUR);
+ if (hours > 0) {
+ s = apr_psprintf(p, "%s%s%d hours", s, sep, hours);
+ if (roughly) return s;
+ sep = " ";
+ }
+ if (rem > 0) {
+ int minutes = (rem / 60);
+ rem = (rem % 60);
+ if (minutes > 0) {
+ s = apr_psprintf(p, "%s%s%d minutes", s, sep, minutes);
+ if (roughly) return s;
+ sep = " ";
+ }
+ if (rem > 0) {
+ s = apr_psprintf(p, "%s%s%d seconds", s, sep, rem);
+ if (roughly) return s;
+ sep = " ";
+ }
+ }
+ }
+ else if (days == 0) {
+ s = "0 seconds";
+ if (duration != 0) {
+ s = apr_psprintf(p, "%d ms", (int)apr_time_msec(duration));
+ }
+ }
+ return s;
+}
+
+const char *md_duration_print(apr_pool_t *p, apr_interval_time_t duration)
+{
+ return duration_print(p, 0, duration);
+}
+
+const char *md_duration_roughly(apr_pool_t *p, apr_interval_time_t duration)
+{
+ return duration_print(p, 1, duration);
+}
+
+static const char *duration_format(apr_pool_t *p, apr_interval_time_t duration)
+{
+ const char *s = "0";
+ int units = (int)(apr_time_sec(duration) / MD_SECS_PER_DAY);
+ int rem = (int)(apr_time_sec(duration) % MD_SECS_PER_DAY);
+
+ if (rem == 0) {
+ s = apr_psprintf(p, "%dd", units);
+ }
+ else {
+ units = (int)(apr_time_sec(duration) / MD_SECS_PER_HOUR);
+ rem = (int)(apr_time_sec(duration) % MD_SECS_PER_HOUR);
+ if (rem == 0) {
+ s = apr_psprintf(p, "%dh", units);
+ }
+ else {
+ units = (int)(apr_time_sec(duration) / 60);
+ rem = (int)(apr_time_sec(duration) % 60);
+ if (rem == 0) {
+ s = apr_psprintf(p, "%dmi", units);
+ }
+ else {
+ units = (int)(apr_time_sec(duration));
+ rem = (int)(apr_time_msec(duration) % 1000);
+ if (rem == 0) {
+ s = apr_psprintf(p, "%ds", units);
+ }
+ else {
+ s = apr_psprintf(p, "%dms", (int)(apr_time_msec(duration)));
+ }
+ }
+ }
+ }
+ return s;
+}
+
+const char *md_duration_format(apr_pool_t *p, apr_interval_time_t duration)
+{
+ return duration_format(p, duration);
+}
+
+apr_status_t md_duration_parse(apr_interval_time_t *ptimeout, const char *value,
+ const char *def_unit)
+{
+ char *endp;
+ apr_int64_t n;
+
+ n = apr_strtoi64(value, &endp, 10);
+ if (errno) {
+ return errno;
+ }
+ if (!endp || !*endp) {
+ if (!def_unit) def_unit = "s";
+ }
+ else if (endp == value) {
+ return APR_EINVAL;
+ }
+ else {
+ def_unit = endp;
+ }
+
+ switch (*def_unit) {
+ case 'D':
+ case 'd':
+ *ptimeout = apr_time_from_sec(n * MD_SECS_PER_DAY);
+ break;
+ case 's':
+ case 'S':
+ *ptimeout = (apr_interval_time_t) apr_time_from_sec(n);
+ break;
+ case 'h':
+ case 'H':
+ /* Time is in hours */
+ *ptimeout = (apr_interval_time_t) apr_time_from_sec(n * MD_SECS_PER_HOUR);
+ break;
+ case 'm':
+ case 'M':
+ switch (*(++def_unit)) {
+ /* Time is in milliseconds */
+ case 's':
+ case 'S':
+ *ptimeout = (apr_interval_time_t) n * 1000;
+ break;
+ /* Time is in minutes */
+ case 'i':
+ case 'I':
+ *ptimeout = (apr_interval_time_t) apr_time_from_sec(n * 60);
+ break;
+ default:
+ return APR_EGENERAL;
+ }
+ break;
+ default:
+ return APR_EGENERAL;
+ }
+ return APR_SUCCESS;
+}
+
+static apr_status_t percentage_parse(const char *value, int *ppercent)
+{
+ char *endp;
+ apr_int64_t n;
+
+ n = apr_strtoi64(value, &endp, 10);
+ if (errno) {
+ return errno;
+ }
+ if (*endp == '%') {
+ if (n < 0) {
+ return APR_BADARG;
+ }
+ *ppercent = (int)n;
+ return APR_SUCCESS;
+ }
+ return APR_EINVAL;
+}
+
+apr_status_t md_timeslice_create(md_timeslice_t **pts, apr_pool_t *p,
+ apr_interval_time_t norm, apr_interval_time_t len)
+{
+ md_timeslice_t *ts;
+
+ ts = apr_pcalloc(p, sizeof(*ts));
+ ts->norm = norm;
+ ts->len = len;
+ *pts = ts;
+ return APR_SUCCESS;
+}
+
+const char *md_timeslice_parse(md_timeslice_t **pts, apr_pool_t *p,
+ const char *val, apr_interval_time_t norm)
+{
+ md_timeslice_t *ts;
+ int percent = 0;
+
+ *pts = NULL;
+ if (!val) {
+ return "cannot parse NULL value";
+ }
+
+ ts = apr_pcalloc(p, sizeof(*ts));
+ if (md_duration_parse(&ts->len, val, "d") == APR_SUCCESS) {
+ *pts = ts;
+ return NULL;
+ }
+ else {
+ switch (percentage_parse(val, &percent)) {
+ case APR_SUCCESS:
+ ts->norm = norm;
+ ts->len = apr_time_from_sec((apr_time_sec(norm) * percent / 100L));
+ *pts = ts;
+ return NULL;
+ case APR_BADARG:
+ return "percent must be less than 100";
+ }
+ }
+ return "has unrecognized format";
+}
+
+const char *md_timeslice_format(const md_timeslice_t *ts, apr_pool_t *p) {
+ if (ts->norm > 0) {
+ int percent = (int)(((long)apr_time_sec(ts->len)) * 100L
+ / ((long)apr_time_sec(ts->norm)));
+ return apr_psprintf(p, "%d%%", percent);
+ }
+ return duration_format(p, ts->len);
+}
+
+md_timeperiod_t md_timeperiod_slice_before_end(const md_timeperiod_t *period,
+ const md_timeslice_t *ts)
+{
+ md_timeperiod_t r;
+ apr_time_t duration = ts->len;
+
+ if (ts->norm > 0) {
+ int percent = (int)(((long)apr_time_sec(ts->len)) * 100L
+ / ((long)apr_time_sec(ts->norm)));
+ apr_time_t plen = md_timeperiod_length(period);
+ if (apr_time_sec(plen) > 100) {
+ duration = apr_time_from_sec(apr_time_sec(plen) * percent / 100);
+ }
+ else {
+ duration = plen * percent / 100;
+ }
+ }
+ r.start = period->end - duration;
+ r.end = period->end;
+ return r;
+}
+
+int md_timeslice_eq(const md_timeslice_t *ts1, const md_timeslice_t *ts2)
+{
+ if (ts1 == ts2) return 1;
+ if (!ts1 || !ts2) return 0;
+ return (ts1->norm == ts2->norm) && (ts1->len == ts2->len);
+}
+
+md_timeperiod_t md_timeperiod_common(const md_timeperiod_t *a, const md_timeperiod_t *b)
+{
+ md_timeperiod_t c;
+
+ c.start = (a->start > b->start)? a->start : b->start;
+ c.end = (a->end < b->end)? a->end : b->end;
+ if (c.start > c.end) {
+ c.start = c.end = 0;
+ }
+ return c;
+}
diff --git a/modules/md/md_time.h b/modules/md/md_time.h
new file mode 100644
index 0000000..92bd9d8
--- /dev/null
+++ b/modules/md/md_time.h
@@ -0,0 +1,77 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mod_md_md_time_h
+#define mod_md_md_time_h
+
+#include <stdio.h>
+
+#define MD_SECS_PER_HOUR (60*60)
+#define MD_SECS_PER_DAY (24*MD_SECS_PER_HOUR)
+
+typedef struct md_timeperiod_t md_timeperiod_t;
+
+struct md_timeperiod_t {
+ apr_time_t start;
+ apr_time_t end;
+};
+
+apr_time_t md_timeperiod_length(const md_timeperiod_t *period);
+
+int md_timeperiod_contains(const md_timeperiod_t *period, apr_time_t time);
+int md_timeperiod_has_started(const md_timeperiod_t *period, apr_time_t time);
+int md_timeperiod_has_ended(const md_timeperiod_t *period, apr_time_t time);
+apr_interval_time_t md_timeperiod_remaining(const md_timeperiod_t *period, apr_time_t time);
+
+/**
+ * Return the timeperiod common between a and b. If both do not overlap, return {0,0}.
+ */
+md_timeperiod_t md_timeperiod_common(const md_timeperiod_t *a, const md_timeperiod_t *b);
+
+char *md_timeperiod_print(apr_pool_t *p, const md_timeperiod_t *period);
+
+/**
+ * Print a human readable form of the give duration in days/hours/min/sec
+ */
+const char *md_duration_print(apr_pool_t *p, apr_interval_time_t duration);
+const char *md_duration_roughly(apr_pool_t *p, apr_interval_time_t duration);
+
+/**
+ * Parse a machine readable string duration in the form of NN[unit], where
+ * unit is d/h/mi/s/ms with the default given should the unit not be specified.
+ */
+apr_status_t md_duration_parse(apr_interval_time_t *ptimeout, const char *value,
+ const char *def_unit);
+const char *md_duration_format(apr_pool_t *p, apr_interval_time_t duration);
+
+typedef struct {
+ apr_interval_time_t norm; /* if > 0, normalized base length */
+ apr_interval_time_t len; /* length of the timespan */
+} md_timeslice_t;
+
+apr_status_t md_timeslice_create(md_timeslice_t **pts, apr_pool_t *p,
+ apr_interval_time_t norm, apr_interval_time_t len);
+
+int md_timeslice_eq(const md_timeslice_t *ts1, const md_timeslice_t *ts2);
+
+const char *md_timeslice_parse(md_timeslice_t **pts, apr_pool_t *p,
+ const char *val, apr_interval_time_t defnorm);
+const char *md_timeslice_format(const md_timeslice_t *ts, apr_pool_t *p);
+
+md_timeperiod_t md_timeperiod_slice_before_end(const md_timeperiod_t *period,
+ const md_timeslice_t *ts);
+
+#endif /* md_util_h */
diff --git a/modules/md/md_util.c b/modules/md/md_util.c
new file mode 100644
index 0000000..884c0bb
--- /dev/null
+++ b/modules/md/md_util.c
@@ -0,0 +1,1561 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+
+#include <apr_lib.h>
+#include <apr_strings.h>
+#include <apr_portable.h>
+#include <apr_file_info.h>
+#include <apr_fnmatch.h>
+#include <apr_tables.h>
+#include <apr_uri.h>
+
+#if APR_HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+
+#include "md.h"
+#include "md_log.h"
+#include "md_util.h"
+
+/**************************************************************************************************/
+/* pool utils */
+
+apr_status_t md_util_pool_do(md_util_action *cb, void *baton, apr_pool_t *p)
+{
+ apr_pool_t *ptemp;
+ apr_status_t rv = apr_pool_create(&ptemp, p);
+ if (APR_SUCCESS == rv) {
+ apr_pool_tag(ptemp, "md_pool_do");
+ rv = cb(baton, p, ptemp);
+ apr_pool_destroy(ptemp);
+ }
+ return rv;
+}
+
+static apr_status_t pool_vado(md_util_vaction *cb, void *baton, apr_pool_t *p, va_list ap)
+{
+ apr_pool_t *ptemp;
+ apr_status_t rv;
+
+ rv = apr_pool_create(&ptemp, p);
+ if (APR_SUCCESS == rv) {
+ apr_pool_tag(ptemp, "md_pool_vado");
+ rv = cb(baton, p, ptemp, ap);
+ apr_pool_destroy(ptemp);
+ }
+ return rv;
+}
+
+apr_status_t md_util_pool_vdo(md_util_vaction *cb, void *baton, apr_pool_t *p, ...)
+{
+ va_list ap;
+ apr_status_t rv;
+
+ va_start(ap, p);
+ rv = pool_vado(cb, baton, p, ap);
+ va_end(ap);
+ return rv;
+}
+
+/**************************************************************************************************/
+/* data chunks */
+
+void md_data_pinit(md_data_t *d, apr_size_t len, apr_pool_t *p)
+{
+ md_data_null(d);
+ d->data = apr_pcalloc(p, len);
+ d->len = len;
+}
+
+md_data_t *md_data_pmake(apr_size_t len, apr_pool_t *p)
+{
+ md_data_t *d;
+
+ d = apr_palloc(p, sizeof(*d));
+ md_data_pinit(d, len, p);
+ return d;
+}
+
+void md_data_init(md_data_t *d, const char *data, apr_size_t len)
+{
+ md_data_null(d);
+ d->len = len;
+ d->data = data;
+}
+
+void md_data_init_str(md_data_t *d, const char *str)
+{
+ md_data_init(d, str, strlen(str));
+}
+
+void md_data_null(md_data_t *d)
+{
+ memset(d, 0, sizeof(*d));
+}
+
+void md_data_clear(md_data_t *d)
+{
+ if (d) {
+ if (d->data && d->free_data) d->free_data((void*)d->data);
+ memset(d, 0, sizeof(*d));
+ }
+}
+
+md_data_t *md_data_make_pcopy(apr_pool_t *p, const char *data, apr_size_t len)
+{
+ md_data_t *d;
+
+ d = apr_palloc(p, sizeof(*d));
+ d->len = len;
+ d->data = len? apr_pmemdup(p, data, len) : NULL;
+ return d;
+}
+
+apr_status_t md_data_assign_copy(md_data_t *dest, const char *src, apr_size_t src_len)
+{
+ md_data_clear(dest);
+ if (src && src_len) {
+ dest->data = malloc(src_len);
+ if (!dest->data) return APR_ENOMEM;
+ memcpy((void*)dest->data, src, src_len);
+ dest->len = src_len;
+ dest->free_data = free;
+ }
+ return APR_SUCCESS;
+}
+
+void md_data_assign_pcopy(md_data_t *dest, const char *src, apr_size_t src_len, apr_pool_t *p)
+{
+ md_data_clear(dest);
+ dest->data = (src && src_len)? apr_pmemdup(p, src, src_len) : NULL;
+ dest->len = dest->data? src_len : 0;
+}
+
+static const char * const hex_const[] = {
+ "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0a", "0b", "0c", "0d", "0e", "0f",
+ "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "1a", "1b", "1c", "1d", "1e", "1f",
+ "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2a", "2b", "2c", "2d", "2e", "2f",
+ "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3a", "3b", "3c", "3d", "3e", "3f",
+ "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "4a", "4b", "4c", "4d", "4e", "4f",
+ "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "5a", "5b", "5c", "5d", "5e", "5f",
+ "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "6a", "6b", "6c", "6d", "6e", "6f",
+ "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "7a", "7b", "7c", "7d", "7e", "7f",
+ "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "8a", "8b", "8c", "8d", "8e", "8f",
+ "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "9a", "9b", "9c", "9d", "9e", "9f",
+ "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "aa", "ab", "ac", "ad", "ae", "af",
+ "b0", "b1", "b2", "b3", "b4", "b5", "b6", "b7", "b8", "b9", "ba", "bb", "bc", "bd", "be", "bf",
+ "c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "ca", "cb", "cc", "cd", "ce", "cf",
+ "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9", "da", "db", "dc", "dd", "de", "df",
+ "e0", "e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8", "e9", "ea", "eb", "ec", "ed", "ee", "ef",
+ "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "fa", "fb", "fc", "fd", "fe", "ff",
+};
+
+apr_status_t md_data_to_hex(const char **phex, char separator,
+ apr_pool_t *p, const md_data_t *data)
+{
+ char *hex, *cp;
+ const char * x;
+ unsigned int i;
+
+ cp = hex = apr_pcalloc(p, ((separator? 3 : 2) * data->len) + 1);
+ if (!hex) {
+ *phex = NULL;
+ return APR_ENOMEM;
+ }
+ for (i = 0; i < data->len; ++i) {
+ x = hex_const[(unsigned char)data->data[i]];
+ if (i && separator) *cp++ = separator;
+ *cp++ = x[0];
+ *cp++ = x[1];
+ }
+ *phex = hex;
+ return APR_SUCCESS;
+}
+
+/**************************************************************************************************/
+/* generic arrays */
+
+int md_array_remove_at(struct apr_array_header_t *a, int idx)
+{
+ char *ps, *pe;
+
+ if (idx < 0 || idx >= a->nelts) return 0;
+ if (idx+1 == a->nelts) {
+ --a->nelts;
+ }
+ else {
+ ps = (a->elts + (idx * a->elt_size));
+ pe = ps + a->elt_size;
+ memmove(ps, pe, (size_t)((a->nelts - (idx+1)) * a->elt_size));
+ --a->nelts;
+ }
+ return 1;
+}
+
+int md_array_remove(struct apr_array_header_t *a, void *elem)
+{
+ int i, n, m;
+ void **pe;
+
+ assert(sizeof(void*) == a->elt_size);
+ n = i = 0;
+ while (i < a->nelts) {
+ pe = &APR_ARRAY_IDX(a, i, void*);
+ if (*pe == elem) {
+ m = a->nelts - (i+1);
+ if (m > 0) memmove(pe, pe+1, (unsigned)m*sizeof(void*));
+ a->nelts--;
+ n++;
+ continue;
+ }
+ ++i;
+ }
+ return n;
+}
+
+/**************************************************************************************************/
+/* string related */
+
+int md_array_is_empty(const struct apr_array_header_t *array)
+{
+ return (array == NULL) || (array->nelts == 0);
+}
+
+char *md_util_str_tolower(char *s)
+{
+ char *orig = s;
+ while (*s) {
+ *s = (char)apr_tolower(*s);
+ ++s;
+ }
+ return orig;
+}
+
+int md_array_str_index(const apr_array_header_t *array, const char *s,
+ int start, int case_sensitive)
+{
+ if (start >= 0) {
+ int i;
+
+ for (i = start; i < array->nelts; i++) {
+ const char *p = APR_ARRAY_IDX(array, i, const char *);
+ if ((case_sensitive && !strcmp(p, s))
+ || (!case_sensitive && !apr_strnatcasecmp(p, s))) {
+ return i;
+ }
+ }
+ }
+
+ return -1;
+}
+
+int md_array_str_eq(const struct apr_array_header_t *a1,
+ const struct apr_array_header_t *a2, int case_sensitive)
+{
+ int i;
+ const char *s1, *s2;
+
+ if (a1 == a2) return 1;
+ if (!a1 || !a2) return 0;
+ if (a1->nelts != a2->nelts) return 0;
+ for (i = 0; i < a1->nelts; ++i) {
+ s1 = APR_ARRAY_IDX(a1, i, const char *);
+ s2 = APR_ARRAY_IDX(a2, i, const char *);
+ if ((case_sensitive && strcmp(s1, s2))
+ || (!case_sensitive && apr_strnatcasecmp(s1, s2))) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+apr_array_header_t *md_array_str_clone(apr_pool_t *p, apr_array_header_t *src)
+{
+ apr_array_header_t *dest = apr_array_make(p, src->nelts, sizeof(const char*));
+ if (dest) {
+ int i;
+ for (i = 0; i < src->nelts; i++) {
+ const char *s = APR_ARRAY_IDX(src, i, const char *);
+ APR_ARRAY_PUSH(dest, const char *) = apr_pstrdup(p, s);
+ }
+ }
+ return dest;
+}
+
+struct apr_array_header_t *md_array_str_compact(apr_pool_t *p, struct apr_array_header_t *src,
+ int case_sensitive)
+{
+ apr_array_header_t *dest = apr_array_make(p, src->nelts, sizeof(const char*));
+ if (dest) {
+ const char *s;
+ int i;
+ for (i = 0; i < src->nelts; ++i) {
+ s = APR_ARRAY_IDX(src, i, const char *);
+ if (md_array_str_index(dest, s, 0, case_sensitive) < 0) {
+ APR_ARRAY_PUSH(dest, char *) = md_util_str_tolower(apr_pstrdup(p, s));
+ }
+ }
+ }
+ return dest;
+}
+
+apr_array_header_t *md_array_str_remove(apr_pool_t *p, apr_array_header_t *src,
+ const char *exclude, int case_sensitive)
+{
+ apr_array_header_t *dest = apr_array_make(p, src->nelts, sizeof(const char*));
+ if (dest) {
+ int i;
+ for (i = 0; i < src->nelts; i++) {
+ const char *s = APR_ARRAY_IDX(src, i, const char *);
+ if (!exclude
+ || (case_sensitive && strcmp(exclude, s))
+ || (!case_sensitive && apr_strnatcasecmp(exclude, s))) {
+ APR_ARRAY_PUSH(dest, const char *) = apr_pstrdup(p, s);
+ }
+ }
+ }
+ return dest;
+}
+
+int md_array_str_add_missing(apr_array_header_t *dest, apr_array_header_t *src, int case_sensitive)
+{
+ int i, added = 0;
+ for (i = 0; i < src->nelts; i++) {
+ const char *s = APR_ARRAY_IDX(src, i, const char *);
+ if (md_array_str_index(dest, s, 0, case_sensitive) < 0) {
+ APR_ARRAY_PUSH(dest, const char *) = s;
+ ++added;
+ }
+ }
+ return added;
+}
+
+/**************************************************************************************************/
+/* file system related */
+
+apr_status_t md_util_fopen(FILE **pf, const char *fn, const char *mode)
+{
+ *pf = fopen(fn, mode);
+ if (*pf == NULL) {
+ return errno;
+ }
+
+ return APR_SUCCESS;
+}
+
+apr_status_t md_util_fcreatex(apr_file_t **pf, const char *fn,
+ apr_fileperms_t perms, apr_pool_t *p)
+{
+ apr_status_t rv;
+ rv = apr_file_open(pf, fn, (APR_FOPEN_WRITE|APR_FOPEN_CREATE|APR_FOPEN_EXCL),
+ perms, p);
+ if (APR_SUCCESS == rv) {
+ /* See <https://github.com/icing/mod_md/issues/117>
+ * Some people set umask 007 to deny all world read/writability to files
+ * created by apache. While this is a noble effort, we need the store files
+ * to have the permissions as specified. */
+ rv = apr_file_perms_set(fn, perms);
+ if (APR_STATUS_IS_ENOTIMPL(rv)) {
+ rv = APR_SUCCESS;
+ }
+ }
+ return rv;
+}
+
+apr_status_t md_util_is_dir(const char *path, apr_pool_t *pool)
+{
+ apr_finfo_t info;
+ apr_status_t rv = apr_stat(&info, path, APR_FINFO_TYPE, pool);
+ if (rv == APR_SUCCESS) {
+ rv = (info.filetype == APR_DIR)? APR_SUCCESS : APR_EINVAL;
+ }
+ return rv;
+}
+
+apr_status_t md_util_is_file(const char *path, apr_pool_t *pool)
+{
+ apr_finfo_t info;
+ apr_status_t rv = apr_stat(&info, path, APR_FINFO_TYPE, pool);
+ if (rv == APR_SUCCESS) {
+ rv = (info.filetype == APR_REG)? APR_SUCCESS : APR_EINVAL;
+ }
+ return rv;
+}
+
+apr_status_t md_util_is_unix_socket(const char *path, apr_pool_t *pool)
+{
+ apr_finfo_t info;
+ apr_status_t rv = apr_stat(&info, path, APR_FINFO_TYPE, pool);
+ if (rv == APR_SUCCESS) {
+ rv = (info.filetype == APR_SOCK)? APR_SUCCESS : APR_EINVAL;
+ }
+ return rv;
+}
+
+int md_file_exists(const char *fname, apr_pool_t *p)
+{
+ return (fname && *fname && APR_SUCCESS == md_util_is_file(fname, p));
+}
+
+apr_status_t md_util_path_merge(const char **ppath, apr_pool_t *p, ...)
+{
+ const char *segment, *path;
+ va_list ap;
+ apr_status_t rv = APR_SUCCESS;
+
+ va_start(ap, p);
+ path = va_arg(ap, char *);
+ while (path && APR_SUCCESS == rv && (segment = va_arg(ap, char *))) {
+ rv = apr_filepath_merge((char **)&path, path, segment, APR_FILEPATH_SECUREROOT , p);
+ }
+ va_end(ap);
+
+ *ppath = (APR_SUCCESS == rv)? (path? path : "") : NULL;
+ return rv;
+}
+
+apr_status_t md_util_freplace(const char *fpath, apr_fileperms_t perms, apr_pool_t *p,
+ md_util_file_cb *write_cb, void *baton)
+{
+ apr_status_t rv = APR_EEXIST;
+ apr_file_t *f;
+ const char *tmp;
+ int i, max;
+
+ tmp = apr_psprintf(p, "%s.tmp", fpath);
+ i = 0; max = 20;
+creat:
+ while (i < max && APR_EEXIST == (rv = md_util_fcreatex(&f, tmp, perms, p))) {
+ ++i;
+ apr_sleep(apr_time_from_msec(50));
+ }
+ if (APR_EEXIST == rv
+ && APR_SUCCESS == (rv = apr_file_remove(tmp, p))
+ && max <= 20) {
+ max *= 2;
+ goto creat;
+ }
+
+ if (APR_SUCCESS == rv) {
+ rv = write_cb(baton, f, p);
+ apr_file_close(f);
+
+ if (APR_SUCCESS == rv) {
+ rv = apr_file_rename(tmp, fpath, p);
+ if (APR_SUCCESS != rv) {
+ apr_file_remove(tmp, p);
+ }
+ }
+ }
+ return rv;
+}
+
+/**************************************************************************************************/
+/* text files */
+
+apr_status_t md_text_fread8k(const char **ptext, apr_pool_t *p, const char *fpath)
+{
+ apr_status_t rv;
+ apr_file_t *f;
+ char buffer[8 * 1024];
+
+ *ptext = NULL;
+ if (APR_SUCCESS == (rv = apr_file_open(&f, fpath, APR_FOPEN_READ, 0, p))) {
+ apr_size_t blen = sizeof(buffer)/sizeof(buffer[0]) - 1;
+ rv = apr_file_read_full(f, buffer, blen, &blen);
+ if (APR_SUCCESS == rv || APR_STATUS_IS_EOF(rv)) {
+ *ptext = apr_pstrndup(p, buffer, blen);
+ rv = APR_SUCCESS;
+ }
+ apr_file_close(f);
+ }
+ return rv;
+}
+
+static apr_status_t write_text(void *baton, struct apr_file_t *f, apr_pool_t *p)
+{
+ const char *text = baton;
+ apr_size_t len = strlen(text);
+
+ (void)p;
+ return apr_file_write_full(f, text, len, &len);
+}
+
+apr_status_t md_text_fcreatex(const char *fpath, apr_fileperms_t perms,
+ apr_pool_t *p, const char *text)
+{
+ apr_status_t rv;
+ apr_file_t *f;
+
+ rv = md_util_fcreatex(&f, fpath, perms, p);
+ if (APR_SUCCESS == rv) {
+ rv = write_text((void*)text, f, p);
+ apr_file_close(f);
+ /* See <https://github.com/icing/mod_md/issues/117>: when a umask
+ * is set, files need to be assigned permissions explicitly.
+ * Otherwise, as in the issues reported, it will break our access model. */
+ rv = apr_file_perms_set(fpath, perms);
+ if (APR_STATUS_IS_ENOTIMPL(rv)) {
+ rv = APR_SUCCESS;
+ }
+ }
+ return rv;
+}
+
+apr_status_t md_text_freplace(const char *fpath, apr_fileperms_t perms,
+ apr_pool_t *p, const char *text)
+{
+ return md_util_freplace(fpath, perms, p, write_text, (void*)text);
+}
+
+typedef struct {
+ const char *path;
+ apr_array_header_t *patterns;
+ int follow_links;
+ void *baton;
+ md_util_fdo_cb *cb;
+} md_util_fwalk_t;
+
+static apr_status_t rm_recursive(const char *fpath, apr_pool_t *p, int max_level)
+{
+ apr_finfo_t info;
+ apr_status_t rv;
+ const char *npath;
+
+ if (APR_SUCCESS != (rv = apr_stat(&info, fpath, (APR_FINFO_TYPE|APR_FINFO_LINK), p))) {
+ return rv;
+ }
+
+ if (info.filetype == APR_DIR) {
+ if (max_level > 0) {
+ apr_dir_t *d;
+
+ if (APR_SUCCESS == (rv = apr_dir_open(&d, fpath, p))) {
+
+ while (APR_SUCCESS == rv &&
+ APR_SUCCESS == (rv = apr_dir_read(&info, APR_FINFO_TYPE, d))) {
+ if (!strcmp(".", info.name) || !strcmp("..", info.name)) {
+ continue;
+ }
+
+ rv = md_util_path_merge(&npath, p, fpath, info.name, NULL);
+ if (APR_SUCCESS == rv) {
+ rv = rm_recursive(npath, p, max_level - 1);
+ }
+ }
+ apr_dir_close(d);
+ if (APR_STATUS_IS_ENOENT(rv)) {
+ rv = APR_SUCCESS;
+ }
+ }
+ }
+ if (APR_SUCCESS == rv) {
+ rv = apr_dir_remove(fpath, p);
+ }
+ }
+ else {
+ rv = apr_file_remove(fpath, p);
+ }
+ return rv;
+}
+
+static apr_status_t prm_recursive(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+{
+ int max_level = va_arg(ap, int);
+
+ (void)p;
+ return rm_recursive(baton, ptemp, max_level);
+}
+
+apr_status_t md_util_rm_recursive(const char *fpath, apr_pool_t *p, int max_level)
+{
+ return md_util_pool_vdo(prm_recursive, (void*)fpath, p, max_level, NULL);
+}
+
+static apr_status_t match_and_do(md_util_fwalk_t *ctx, const char *path, int depth,
+ apr_pool_t *p, apr_pool_t *ptemp)
+{
+ apr_status_t rv = APR_SUCCESS;
+ const char *pattern, *npath;
+ apr_dir_t *d;
+ apr_finfo_t finfo;
+ int ndepth = depth + 1;
+ apr_int32_t wanted = (APR_FINFO_TYPE);
+
+ if (depth >= ctx->patterns->nelts) {
+ return APR_SUCCESS;
+ }
+ pattern = APR_ARRAY_IDX(ctx->patterns, depth, const char *);
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, ptemp, "match_and_do "
+ "path=%s depth=%d pattern=%s", path, depth, pattern);
+ rv = apr_dir_open(&d, path, ptemp);
+ if (APR_SUCCESS != rv) {
+ return rv;
+ }
+
+ while (APR_SUCCESS == (rv = apr_dir_read(&finfo, wanted, d))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, ptemp, "match_and_do "
+ "candidate=%s", finfo.name);
+ if (!strcmp(".", finfo.name) || !strcmp("..", finfo.name)) {
+ continue;
+ }
+ if (APR_SUCCESS == apr_fnmatch(pattern, finfo.name, 0)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, ptemp, "match_and_do "
+ "candidate=%s matches pattern", finfo.name);
+ if (ndepth < ctx->patterns->nelts) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, ptemp, "match_and_do "
+ "need to go deeper");
+ if (APR_DIR == finfo.filetype) {
+ /* deeper and deeper, irgendwo in der tiefe leuchtet ein licht */
+ rv = md_util_path_merge(&npath, ptemp, path, finfo.name, NULL);
+ if (APR_SUCCESS == rv) {
+ rv = match_and_do(ctx, npath, ndepth, p, ptemp);
+ }
+ }
+ }
+ else {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, ptemp, "match_and_do "
+ "invoking inspector on name=%s", finfo.name);
+ rv = ctx->cb(ctx->baton, p, ptemp, path, finfo.name, finfo.filetype);
+ }
+ }
+ if (APR_SUCCESS != rv) {
+ break;
+ }
+ }
+
+ if (APR_STATUS_IS_ENOENT(rv)) {
+ rv = APR_SUCCESS;
+ }
+
+ apr_dir_close(d);
+ return rv;
+}
+
+static apr_status_t files_do_start(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+{
+ md_util_fwalk_t *ctx = baton;
+ const char *segment;
+
+ ctx->patterns = apr_array_make(ptemp, 5, sizeof(const char*));
+
+ segment = va_arg(ap, char *);
+ while (segment) {
+ APR_ARRAY_PUSH(ctx->patterns, const char *) = segment;
+ segment = va_arg(ap, char *);
+ }
+
+ return match_and_do(ctx, ctx->path, 0, p, ptemp);
+}
+
+apr_status_t md_util_files_do(md_util_fdo_cb *cb, void *baton, apr_pool_t *p,
+ const char *path, ...)
+{
+ apr_status_t rv;
+ va_list ap;
+ md_util_fwalk_t ctx;
+
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.path = path;
+ ctx.follow_links = 1;
+ ctx.cb = cb;
+ ctx.baton = baton;
+
+ va_start(ap, path);
+ rv = pool_vado(files_do_start, &ctx, p, ap);
+ va_end(ap);
+
+ return rv;
+}
+
+static apr_status_t tree_do(void *baton, apr_pool_t *p, apr_pool_t *ptemp, const char *path)
+{
+ md_util_fwalk_t *ctx = baton;
+
+ apr_status_t rv = APR_SUCCESS;
+ const char *name, *fpath;
+ apr_filetype_e ftype;
+ apr_dir_t *d;
+ apr_int32_t wanted = APR_FINFO_TYPE;
+ apr_finfo_t finfo;
+
+ if (APR_SUCCESS == (rv = apr_dir_open(&d, path, ptemp))) {
+ while (APR_SUCCESS == (rv = apr_dir_read(&finfo, wanted, d))) {
+ name = finfo.name;
+ if (!strcmp(".", name) || !strcmp("..", name)) {
+ continue;
+ }
+
+ fpath = NULL;
+ ftype = finfo.filetype;
+
+ if (APR_LNK == ftype && ctx->follow_links) {
+ rv = md_util_path_merge(&fpath, ptemp, path, name, NULL);
+ if (APR_SUCCESS == rv) {
+ rv = apr_stat(&finfo, ctx->path, wanted, ptemp);
+ }
+ }
+
+ if (APR_DIR == finfo.filetype) {
+ if (!fpath) {
+ rv = md_util_path_merge(&fpath, ptemp, path, name, NULL);
+ }
+ if (APR_SUCCESS == rv) {
+ rv = tree_do(ctx, p, ptemp, fpath);
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, ptemp, "dir cb(%s/%s)",
+ path, name);
+ rv = ctx->cb(ctx->baton, p, ptemp, path, name, ftype);
+ }
+ }
+ else {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, ptemp, "file cb(%s/%s)",
+ path, name);
+ rv = ctx->cb(ctx->baton, p, ptemp, path, name, finfo.filetype);
+ }
+ }
+
+ apr_dir_close(d);
+
+ if (APR_STATUS_IS_ENOENT(rv)) {
+ rv = APR_SUCCESS;
+ }
+ }
+ return rv;
+}
+
+static apr_status_t tree_start_do(void *baton, apr_pool_t *p, apr_pool_t *ptemp)
+{
+ md_util_fwalk_t *ctx = baton;
+ apr_finfo_t info;
+ apr_status_t rv;
+ apr_int32_t wanted = ctx->follow_links? APR_FINFO_TYPE : (APR_FINFO_TYPE|APR_FINFO_LINK);
+
+ rv = apr_stat(&info, ctx->path, wanted, ptemp);
+ if (rv == APR_SUCCESS) {
+ switch (info.filetype) {
+ case APR_DIR:
+ rv = tree_do(ctx, p, ptemp, ctx->path);
+ break;
+ default:
+ rv = APR_EINVAL;
+ }
+ }
+ return rv;
+}
+
+apr_status_t md_util_tree_do(md_util_fdo_cb *cb, void *baton, apr_pool_t *p,
+ const char *path, int follow_links)
+{
+ apr_status_t rv;
+ md_util_fwalk_t ctx;
+
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.path = path;
+ ctx.follow_links = follow_links;
+ ctx.cb = cb;
+ ctx.baton = baton;
+
+ rv = md_util_pool_do(tree_start_do, &ctx, p);
+
+ return rv;
+}
+
+static apr_status_t rm_cb(void *baton, apr_pool_t *p, apr_pool_t *ptemp,
+ const char *path, const char *name, apr_filetype_e ftype)
+{
+ apr_status_t rv;
+ const char *fpath;
+
+ (void)baton;
+ (void)p;
+ rv = md_util_path_merge(&fpath, ptemp, path, name, NULL);
+ if (APR_SUCCESS == rv) {
+ if (APR_DIR == ftype) {
+ rv = apr_dir_remove(fpath, ptemp);
+ }
+ else {
+ rv = apr_file_remove(fpath, ptemp);
+ }
+ }
+ return rv;
+}
+
+apr_status_t md_util_ftree_remove(const char *path, apr_pool_t *p)
+{
+ apr_status_t rv = md_util_tree_do(rm_cb, NULL, p, path, 0);
+ if (APR_SUCCESS == rv) {
+ rv = apr_dir_remove(path, p);
+ }
+ return rv;
+}
+
+/* DNS name checks ********************************************************************************/
+
+int md_dns_is_name(apr_pool_t *p, const char *hostname, int need_fqdn)
+{
+ char c, last = 0;
+ const char *cp = hostname;
+ int dots = 0;
+
+ /* Since we use the names in certificates, we need pure ASCII domain names
+ * and IDN need to be converted to unicode. */
+ while ((c = *cp++)) {
+ switch (c) {
+ case '.':
+ if (last == '.') {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, p, "dns name with ..: %s",
+ hostname);
+ return 0;
+ }
+ ++dots;
+ break;
+ case '-':
+ break;
+ default:
+ if (!apr_isalnum(c)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, p, "dns invalid char %c: %s",
+ c, hostname);
+ return 0;
+ }
+ break;
+ }
+ last = c;
+ }
+
+ if (last == '.') { /* DNS names may end with '.' */
+ --dots;
+ }
+ if (need_fqdn && dots <= 0) { /* do not accept just top level domains */
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, p, "not a FQDN: %s", hostname);
+ return 0;
+ }
+ return 1; /* empty string not allowed */
+}
+
+int md_dns_is_wildcard(apr_pool_t *p, const char *domain)
+{
+ if (domain[0] != '*' || domain[1] != '.') return 0;
+ return md_dns_is_name(p, domain+2, 1);
+}
+
+int md_dns_matches(const char *pattern, const char *domain)
+{
+ const char *s;
+
+ if (!apr_strnatcasecmp(pattern, domain)) return 1;
+ if (pattern[0] == '*' && pattern[1] == '.') {
+ s = strchr(domain, '.');
+ if (s && !apr_strnatcasecmp(pattern+1, s)) return 1;
+ }
+ return 0;
+}
+
+apr_array_header_t *md_dns_make_minimal(apr_pool_t *p, apr_array_header_t *domains)
+{
+ apr_array_header_t *minimal;
+ const char *domain, *pattern;
+ int i, j, duplicate;
+
+ minimal = apr_array_make(p, domains->nelts, sizeof(const char *));
+ for (i = 0; i < domains->nelts; ++i) {
+ domain = APR_ARRAY_IDX(domains, i, const char*);
+ duplicate = 0;
+ /* is it matched in minimal already? */
+ for (j = 0; j < minimal->nelts; ++j) {
+ pattern = APR_ARRAY_IDX(minimal, j, const char*);
+ if (md_dns_matches(pattern, domain)) {
+ duplicate = 1;
+ break;
+ }
+ }
+ if (!duplicate) {
+ if (!md_dns_is_wildcard(p, domain)) {
+ /* plain name, will we see a wildcard that replaces it? */
+ for (j = i+1; j < domains->nelts; ++j) {
+ pattern = APR_ARRAY_IDX(domains, j, const char*);
+ if (md_dns_is_wildcard(p, pattern) && md_dns_matches(pattern, domain)) {
+ duplicate = 1;
+ break;
+ }
+ }
+ }
+ if (!duplicate) {
+ APR_ARRAY_PUSH(minimal, const char *) = domain;
+ }
+ }
+ }
+ return minimal;
+}
+
+int md_dns_domains_match(const apr_array_header_t *domains, const char *name)
+{
+ const char *domain;
+ int i;
+
+ for (i = 0; i < domains->nelts; ++i) {
+ domain = APR_ARRAY_IDX(domains, i, const char*);
+ if (md_dns_matches(domain, name)) return 1;
+ }
+ return 0;
+}
+
+const char *md_util_schemify(apr_pool_t *p, const char *s, const char *def_scheme)
+{
+ const char *cp = s;
+ while (*cp) {
+ if (*cp == ':') {
+ /* could be an url scheme, leave unchanged */
+ return s;
+ }
+ else if (!apr_isalnum(*cp)) {
+ break;
+ }
+ ++cp;
+ }
+ return apr_psprintf(p, "%s:%s", def_scheme, s);
+}
+
+static apr_status_t uri_check(apr_uri_t *uri_parsed, apr_pool_t *p,
+ const char *uri, const char **perr)
+{
+ const char *s, *err = NULL;
+ apr_status_t rv;
+
+ if (APR_SUCCESS != (rv = apr_uri_parse(p, uri, uri_parsed))) {
+ err = "not an uri";
+ }
+ else if (uri_parsed->scheme) {
+ if (strlen(uri_parsed->scheme) + 1 >= strlen(uri)) {
+ err = "missing uri identifier";
+ }
+ else if (!strncmp("http", uri_parsed->scheme, 4)) {
+ if (!uri_parsed->hostname) {
+ err = "missing hostname";
+ }
+ else if (!md_dns_is_name(p, uri_parsed->hostname, 0)) {
+ err = "invalid hostname";
+ }
+ if (uri_parsed->port_str
+ && (!apr_isdigit(uri_parsed->port_str[0])
+ || uri_parsed->port == 0
+ || uri_parsed->port > 65353)) {
+ err = "invalid port";
+ }
+ }
+ else if (!strcmp("mailto", uri_parsed->scheme)) {
+ s = strchr(uri, '@');
+ if (!s) {
+ err = "missing @";
+ }
+ else if (strchr(s+1, '@')) {
+ err = "duplicate @";
+ }
+ else if (s == uri + strlen(uri_parsed->scheme) + 1) {
+ err = "missing local part";
+ }
+ else if (s == (uri + strlen(uri)-1)) {
+ err = "missing hostname";
+ }
+ else if (strstr(uri, "..")) {
+ err = "double period";
+ }
+ }
+ }
+ if (strchr(uri, ' ') || strchr(uri, '\t') ) {
+ err = "whitespace in uri";
+ }
+
+ if (err) {
+ rv = APR_EINVAL;
+ }
+ *perr = err;
+ return rv;
+}
+
+apr_status_t md_util_abs_uri_check(apr_pool_t *p, const char *uri, const char **perr)
+{
+ apr_uri_t uri_parsed;
+ apr_status_t rv;
+
+ if (APR_SUCCESS == (rv = uri_check(&uri_parsed, p, uri, perr))) {
+ if (!uri_parsed.scheme) {
+ *perr = "missing uri scheme";
+ return APR_EINVAL;
+ }
+ }
+ return rv;
+}
+
+apr_status_t md_util_abs_http_uri_check(apr_pool_t *p, const char *uri, const char **perr)
+{
+ apr_uri_t uri_parsed;
+ apr_status_t rv;
+
+ if (APR_SUCCESS == (rv = uri_check(&uri_parsed, p, uri, perr))) {
+ if (!uri_parsed.scheme) {
+ *perr = "missing uri scheme";
+ return APR_EINVAL;
+ }
+ if (apr_strnatcasecmp("http", uri_parsed.scheme)
+ && apr_strnatcasecmp("https", uri_parsed.scheme)) {
+ *perr = "uri scheme must be http or https";
+ return APR_EINVAL;
+ }
+ }
+ return rv;
+}
+
+/* try and retry for a while **********************************************************************/
+
+apr_status_t md_util_try(md_util_try_fn *fn, void *baton, int ignore_errs,
+ apr_interval_time_t timeout, apr_interval_time_t start_delay,
+ apr_interval_time_t max_delay, int backoff)
+{
+ apr_status_t rv;
+ apr_time_t now = apr_time_now();
+ apr_time_t giveup = now + timeout;
+ apr_interval_time_t nap_duration = start_delay? start_delay : apr_time_from_msec(100);
+ apr_interval_time_t nap_max = max_delay? max_delay : apr_time_from_sec(10);
+ apr_interval_time_t left;
+ int i = 0;
+
+ while (1) {
+ if (APR_SUCCESS == (rv = fn(baton, i++))) {
+ break;
+ }
+ else if (!APR_STATUS_IS_EAGAIN(rv) && !ignore_errs) {
+ break;
+ }
+
+ now = apr_time_now();
+ if (now > giveup) {
+ rv = APR_TIMEUP;
+ break;
+ }
+
+ left = giveup - now;
+ if (nap_duration > left) {
+ nap_duration = left;
+ }
+ if (nap_duration > nap_max) {
+ nap_duration = nap_max;
+ }
+
+ apr_sleep(nap_duration);
+ if (backoff) {
+ nap_duration *= 2;
+ }
+ }
+ return rv;
+}
+
+/* execute process ********************************************************************************/
+
+apr_status_t md_util_exec(apr_pool_t *p, const char *cmd, const char * const *argv,
+ apr_array_header_t *env, int *exit_code)
+{
+ apr_status_t rv;
+ apr_procattr_t *procattr;
+ apr_proc_t *proc;
+ apr_exit_why_e ewhy;
+ const char * const *envp = NULL;
+ char buffer[1024];
+
+ *exit_code = 0;
+ if (!(proc = apr_pcalloc(p, sizeof(*proc)))) {
+ return APR_ENOMEM;
+ }
+ if (env && env->nelts > 0) {
+ apr_array_header_t *nenv;
+
+ nenv = apr_array_copy(p, env);
+ APR_ARRAY_PUSH(nenv, const char *) = NULL;
+ envp = (const char * const *)nenv->elts;
+ }
+ if ( APR_SUCCESS == (rv = apr_procattr_create(&procattr, p))
+ && APR_SUCCESS == (rv = apr_procattr_io_set(procattr, APR_NO_FILE,
+ APR_NO_PIPE, APR_FULL_BLOCK))
+ && APR_SUCCESS == (rv = apr_procattr_cmdtype_set(procattr, APR_PROGRAM))
+ && APR_SUCCESS == (rv = apr_proc_create(proc, cmd, argv, envp, procattr, p))) {
+
+ /* read stderr and log on INFO for possible fault analysis. */
+ while(APR_SUCCESS == (rv = apr_file_gets(buffer, sizeof(buffer)-1, proc->err))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, p, "cmd(%s) stderr: %s", cmd, buffer);
+ }
+ if (!APR_STATUS_IS_EOF(rv)) goto out;
+ apr_file_close(proc->err);
+
+ if (APR_CHILD_DONE == (rv = apr_proc_wait(proc, exit_code, &ewhy, APR_WAIT))) {
+ /* let's not dwell on exit stati, but core should signal something's bad */
+ if (*exit_code > 127 || APR_PROC_SIGNAL_CORE == ewhy) {
+ return APR_EINCOMPLETE;
+ }
+ return APR_SUCCESS;
+ }
+ }
+out:
+ return rv;
+}
+
+/* base64 url encoding ****************************************************************************/
+
+#define N6 (unsigned int)-1
+
+static const unsigned int BASE64URL_UINT6[] = {
+/* 0 1 2 3 4 5 6 7 8 9 a b c d e f */
+ N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* 0 */
+ N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* 1 */
+ N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, 62, N6, N6, /* 2 */
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, N6, N6, N6, N6, N6, N6, /* 3 */
+ N6, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /* 4 */
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, N6, N6, N6, N6, 63, /* 5 */
+ N6, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* 6 */
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, N6, N6, N6, N6, N6, /* 7 */
+ N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* 8 */
+ N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* 9 */
+ N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* a */
+ N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* b */
+ N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* c */
+ N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* d */
+ N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* e */
+ N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6 /* f */
+};
+static const unsigned char BASE64URL_CHARS[] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', /* 0 - 9 */
+ 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', /* 10 - 19 */
+ 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', /* 20 - 29 */
+ 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', /* 30 - 39 */
+ 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', /* 40 - 49 */
+ 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', /* 50 - 59 */
+ '8', '9', '-', '_', ' ', ' ', ' ', ' ', ' ', ' ', /* 60 - 69 */
+};
+
+#define BASE64URL_CHAR(x) BASE64URL_CHARS[ (unsigned int)(x) & 0x3fu ]
+
+apr_size_t md_util_base64url_decode(md_data_t *decoded, const char *encoded,
+ apr_pool_t *pool)
+{
+ const unsigned char *e = (const unsigned char *)encoded;
+ const unsigned char *p = e;
+ unsigned char *d;
+ unsigned int n;
+ long len, mlen, remain, i;
+
+ while (*p && BASE64URL_UINT6[ *p ] != N6) {
+ ++p;
+ }
+ len = (int)(p - e);
+ mlen = (len/4)*4;
+ decoded->data = apr_pcalloc(pool, (apr_size_t)len + 1);
+
+ i = 0;
+ d = (unsigned char*)decoded->data;
+ for (; i < mlen; i += 4) {
+ n = ((BASE64URL_UINT6[ e[i+0] ] << 18) +
+ (BASE64URL_UINT6[ e[i+1] ] << 12) +
+ (BASE64URL_UINT6[ e[i+2] ] << 6) +
+ (BASE64URL_UINT6[ e[i+3] ]));
+ *d++ = (unsigned char)(n >> 16);
+ *d++ = (unsigned char)(n >> 8 & 0xffu);
+ *d++ = (unsigned char)(n & 0xffu);
+ }
+ remain = len - mlen;
+ switch (remain) {
+ case 2:
+ n = ((BASE64URL_UINT6[ e[mlen+0] ] << 18) +
+ (BASE64URL_UINT6[ e[mlen+1] ] << 12));
+ *d++ = (unsigned char)(n >> 16);
+ remain = 1;
+ break;
+ case 3:
+ n = ((BASE64URL_UINT6[ e[mlen+0] ] << 18) +
+ (BASE64URL_UINT6[ e[mlen+1] ] << 12) +
+ (BASE64URL_UINT6[ e[mlen+2] ] << 6));
+ *d++ = (unsigned char)(n >> 16);
+ *d++ = (unsigned char)(n >> 8 & 0xffu);
+ remain = 2;
+ break;
+ default: /* do nothing */
+ break;
+ }
+ decoded->len = (apr_size_t)(mlen/4*3 + remain);
+ return decoded->len;
+}
+
+const char *md_util_base64url_encode(const md_data_t *data, apr_pool_t *pool)
+{
+ int i, len = (int)data->len;
+ apr_size_t slen = ((data->len+2)/3)*4 + 1; /* 0 terminated */
+ const unsigned char *udata = (const unsigned char*)data->data;
+ unsigned char *enc, *p = apr_pcalloc(pool, slen);
+
+ enc = p;
+ for (i = 0; i < len-2; i+= 3) {
+ *p++ = BASE64URL_CHAR( (udata[i] >> 2) );
+ *p++ = BASE64URL_CHAR( (udata[i] << 4) + (udata[i+1] >> 4) );
+ *p++ = BASE64URL_CHAR( (udata[i+1] << 2) + (udata[i+2] >> 6) );
+ *p++ = BASE64URL_CHAR( (udata[i+2]) );
+ }
+
+ if (i < len) {
+ *p++ = BASE64URL_CHAR( (udata[i] >> 2) );
+ if (i == (len - 1)) {
+ *p++ = BASE64URL_CHARS[ ((unsigned int)udata[i] << 4) & 0x3fu ];
+ }
+ else {
+ *p++ = BASE64URL_CHAR( (udata[i] << 4) + (udata[i+1] >> 4) );
+ *p++ = BASE64URL_CHAR( (udata[i+1] << 2) );
+ }
+ }
+ *p++ = '\0';
+ return (char *)enc;
+}
+
+/*******************************************************************************
+ * link header handling
+ ******************************************************************************/
+
+typedef struct {
+ const char *s;
+ apr_size_t slen;
+ apr_size_t i;
+ apr_size_t link_start;
+ apr_size_t link_len;
+ apr_size_t pn_start;
+ apr_size_t pn_len;
+ apr_size_t pv_start;
+ apr_size_t pv_len;
+} link_ctx;
+
+static int attr_char(char c)
+{
+ switch (c) {
+ case '!':
+ case '#':
+ case '$':
+ case '&':
+ case '+':
+ case '-':
+ case '.':
+ case '^':
+ case '_':
+ case '`':
+ case '|':
+ case '~':
+ return 1;
+ default:
+ return apr_isalnum(c);
+ }
+}
+
+static int ptoken_char(char c)
+{
+ switch (c) {
+ case '!':
+ case '#':
+ case '$':
+ case '&':
+ case '\'':
+ case '(':
+ case ')':
+ case '*':
+ case '+':
+ case '-':
+ case '.':
+ case '/':
+ case ':':
+ case '<':
+ case '=':
+ case '>':
+ case '?':
+ case '@':
+ case '[':
+ case ']':
+ case '^':
+ case '_':
+ case '`':
+ case '{':
+ case '|':
+ case '}':
+ case '~':
+ return 1;
+ default:
+ return apr_isalnum(c);
+ }
+}
+
+static int skip_ws(link_ctx *ctx)
+{
+ char c;
+ while (ctx->i < ctx->slen
+ && (((c = ctx->s[ctx->i]) == ' ') || (c == '\t'))) {
+ ++ctx->i;
+ }
+ return (ctx->i < ctx->slen);
+}
+
+static int skip_nonws(link_ctx *ctx)
+{
+ char c;
+ while (ctx->i < ctx->slen
+ && (((c = ctx->s[ctx->i]) != ' ') && (c != '\t'))) {
+ ++ctx->i;
+ }
+ return (ctx->i < ctx->slen);
+}
+
+static unsigned int find_chr(link_ctx *ctx, char c, apr_size_t *pidx)
+{
+ apr_size_t j;
+ for (j = ctx->i; j < ctx->slen; ++j) {
+ if (ctx->s[j] == c) {
+ *pidx = j;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int read_chr(link_ctx *ctx, char c)
+{
+ if (ctx->i < ctx->slen && ctx->s[ctx->i] == c) {
+ ++ctx->i;
+ return 1;
+ }
+ return 0;
+}
+
+static int skip_qstring(link_ctx *ctx)
+{
+ if (skip_ws(ctx) && read_chr(ctx, '\"')) {
+ apr_size_t end;
+ if (find_chr(ctx, '\"', &end)) {
+ ctx->i = end + 1;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int skip_ptoken(link_ctx *ctx)
+{
+ if (skip_ws(ctx)) {
+ apr_size_t i;
+ for (i = ctx->i; i < ctx->slen && ptoken_char(ctx->s[i]); ++i) {
+ /* nop */
+ }
+ if (i > ctx->i) {
+ ctx->i = i;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+static int read_link(link_ctx *ctx)
+{
+ ctx->link_start = ctx->link_len = 0;
+ if (skip_ws(ctx) && read_chr(ctx, '<')) {
+ apr_size_t end;
+ if (find_chr(ctx, '>', &end)) {
+ ctx->link_start = ctx->i;
+ ctx->link_len = end - ctx->link_start;
+ ctx->i = end + 1;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int skip_pname(link_ctx *ctx)
+{
+ if (skip_ws(ctx)) {
+ apr_size_t i;
+ for (i = ctx->i; i < ctx->slen && attr_char(ctx->s[i]); ++i) {
+ /* nop */
+ }
+ if (i > ctx->i) {
+ ctx->i = i;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int skip_pvalue(link_ctx *ctx)
+{
+ if (skip_ws(ctx) && read_chr(ctx, '=')) {
+ ctx->pv_start = ctx->i;
+ if (skip_qstring(ctx) || skip_ptoken(ctx)) {
+ ctx->pv_len = ctx->i - ctx->pv_start;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int skip_param(link_ctx *ctx)
+{
+ if (skip_ws(ctx) && read_chr(ctx, ';')) {
+ ctx->pn_start = ctx->i;
+ ctx->pn_len = 0;
+ if (skip_pname(ctx)) {
+ ctx->pn_len = ctx->i - ctx->pn_start;
+ ctx->pv_len = 0;
+ skip_pvalue(ctx); /* value is optional */
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int pv_contains(link_ctx *ctx, const char *s)
+{
+ apr_size_t pvstart = ctx->pv_start;
+ apr_size_t pvlen = ctx->pv_len;
+
+ if (ctx->s[pvstart] == '\"' && pvlen > 1) {
+ ++pvstart;
+ pvlen -= 2;
+ }
+ if (pvlen > 0) {
+ apr_size_t slen = strlen(s);
+ link_ctx pvctx;
+ apr_size_t i;
+
+ memset(&pvctx, 0, sizeof(pvctx));
+ pvctx.s = ctx->s + pvstart;
+ pvctx.slen = pvlen;
+
+ for (i = 0; i < pvctx.slen; i = pvctx.i) {
+ skip_nonws(&pvctx);
+ if ((pvctx.i - i) == slen && !strncmp(s, pvctx.s + i, slen)) {
+ return 1;
+ }
+ skip_ws(&pvctx);
+ }
+ }
+ return 0;
+}
+
+/* RFC 5988 <https://tools.ietf.org/html/rfc5988#section-6.2.1>
+ Link = "Link" ":" #link-value
+ link-value = "<" URI-Reference ">" *( ";" link-param )
+ link-param = ( ( "rel" "=" relation-types )
+ | ( "anchor" "=" <"> URI-Reference <"> )
+ | ( "rev" "=" relation-types )
+ | ( "hreflang" "=" Language-Tag )
+ | ( "media" "=" ( MediaDesc | ( <"> MediaDesc <"> ) ) )
+ | ( "title" "=" quoted-string )
+ | ( "title*" "=" ext-value )
+ | ( "type" "=" ( media-type | quoted-mt ) )
+ | ( link-extension ) )
+ link-extension = ( parmname [ "=" ( ptoken | quoted-string ) ] )
+ | ( ext-name-star "=" ext-value )
+ ext-name-star = parmname "*" ; reserved for RFC2231-profiled
+ ; extensions. Whitespace NOT
+ ; allowed in between.
+ ptoken = 1*ptokenchar
+ ptokenchar = "!" | "#" | "$" | "%" | "&" | "'" | "("
+ | ")" | "*" | "+" | "-" | "." | "/" | DIGIT
+ | ":" | "<" | "=" | ">" | "?" | "@" | ALPHA
+ | "[" | "]" | "^" | "_" | "`" | "{" | "|"
+ | "}" | "~"
+ media-type = type-name "/" subtype-name
+ quoted-mt = <"> media-type <">
+ relation-types = relation-type
+ | <"> relation-type *( 1*SP relation-type ) <">
+ relation-type = reg-rel-type | ext-rel-type
+ reg-rel-type = LOALPHA *( LOALPHA | DIGIT | "." | "-" )
+ ext-rel-type = URI
+
+ and from <https://tools.ietf.org/html/rfc5987>
+ parmname = 1*attr-char
+ attr-char = ALPHA / DIGIT
+ / "!" / "#" / "$" / "&" / "+" / "-" / "."
+ / "^" / "_" / "`" / "|" / "~"
+ */
+
+typedef struct {
+ apr_pool_t *pool;
+ const char *relation;
+ const char *url;
+} find_ctx;
+
+static int find_url(void *baton, const char *key, const char *value)
+{
+ find_ctx *outer = baton;
+
+ if (!apr_strnatcasecmp("link", key)) {
+ link_ctx ctx;
+
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.s = value;
+ ctx.slen = strlen(value);
+
+ while (read_link(&ctx)) {
+ while (skip_param(&ctx)) {
+ if (ctx.pn_len == 3 && !strncmp("rel", ctx.s + ctx.pn_start, 3)
+ && pv_contains(&ctx, outer->relation)) {
+ /* this is the link relation we are looking for */
+ outer->url = apr_pstrndup(outer->pool, ctx.s + ctx.link_start, ctx.link_len);
+ return 0;
+ }
+ }
+ }
+ }
+ return 1;
+}
+
+const char *md_link_find_relation(const apr_table_t *headers,
+ apr_pool_t *pool, const char *relation)
+{
+ find_ctx ctx;
+
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.pool = pool;
+ ctx.relation = relation;
+
+ apr_table_do(find_url, &ctx, headers, NULL);
+
+ return ctx.url;
+}
+
+const char *md_util_parse_ct(apr_pool_t *pool, const char *cth)
+{
+ char *type;
+ const char *p;
+ apr_size_t hlen;
+
+ if (!cth) return NULL;
+
+ for( p = cth; *p && *p != ' ' && *p != ';'; ++p)
+ ;
+ hlen = (apr_size_t)(p - cth);
+ type = apr_pcalloc( pool, hlen + 1 );
+ assert(type);
+ memcpy(type, cth, hlen);
+ type[hlen] = '\0';
+
+ return type;
+ /* Could parse and return parameters here, but we don't need any at present.
+ */
+}
diff --git a/modules/md/md_util.h b/modules/md/md_util.h
new file mode 100644
index 0000000..e430655
--- /dev/null
+++ b/modules/md/md_util.h
@@ -0,0 +1,253 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mod_md_md_util_h
+#define mod_md_md_util_h
+
+#include <stdio.h>
+#include <apr_file_io.h>
+
+struct apr_array_header_t;
+struct apr_table_t;
+
+/**************************************************************************************************/
+/* pool utils */
+
+typedef apr_status_t md_util_action(void *baton, apr_pool_t *p, apr_pool_t *ptemp);
+typedef apr_status_t md_util_vaction(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap);
+
+apr_status_t md_util_pool_do(md_util_action *cb, void *baton, apr_pool_t *p);
+apr_status_t md_util_pool_vdo(md_util_vaction *cb, void *baton, apr_pool_t *p, ...);
+
+/**************************************************************************************************/
+/* data chunks */
+
+typedef void md_data_free_fn(void *data);
+
+typedef struct md_data_t md_data_t;
+struct md_data_t {
+ const char *data;
+ apr_size_t len;
+ md_data_free_fn *free_data;
+};
+
+/**
+ * Init the data to empty, overwriting any content.
+ */
+void md_data_null(md_data_t *d);
+
+/**
+ * Create a new md_data_t, providing `len` bytes allocated from pool `p`.
+ */
+md_data_t *md_data_pmake(apr_size_t len, apr_pool_t *p);
+/**
+ * Initialize md_data_t 'd', providing `len` bytes allocated from pool `p`.
+ */
+void md_data_pinit(md_data_t *d, apr_size_t len, apr_pool_t *p);
+/**
+ * Initialize md_data_t 'd', by borrowing 'len' bytes in `data` without copying.
+ * `d` will not take ownership.
+ */
+void md_data_init(md_data_t *d, const char *data, apr_size_t len);
+
+/**
+ * Initialize md_data_t 'd', by borrowing the NUL-terminated `str`.
+ * `d` will not take ownership.
+ */
+void md_data_init_str(md_data_t *d, const char *str);
+
+/**
+ * Free any present data and clear (NULL) it. Passing NULL is permitted.
+ */
+void md_data_clear(md_data_t *d);
+
+md_data_t *md_data_make_pcopy(apr_pool_t *p, const char *data, apr_size_t len);
+
+apr_status_t md_data_assign_copy(md_data_t *dest, const char *src, apr_size_t src_len);
+void md_data_assign_pcopy(md_data_t *dest, const char *src, apr_size_t src_len, apr_pool_t *p);
+
+apr_status_t md_data_to_hex(const char **phex, char separator,
+ apr_pool_t *p, const md_data_t *data);
+
+/**************************************************************************************************/
+/* generic arrays */
+
+/**
+ * In an array of pointers, remove all entries == elem. Returns the number
+ * of entries removed.
+ */
+int md_array_remove(struct apr_array_header_t *a, void *elem);
+
+/*
+ * Remove the ith entry from the array.
+ * @return != 0 iff an entry was removed, e.g. idx was not outside range
+ */
+int md_array_remove_at(struct apr_array_header_t *a, int idx);
+
+/**************************************************************************************************/
+/* string related */
+char *md_util_str_tolower(char *s);
+
+/**
+ * Return != 0 iff array is either NULL or empty
+ */
+int md_array_is_empty(const struct apr_array_header_t *array);
+
+int md_array_str_index(const struct apr_array_header_t *array, const char *s,
+ int start, int case_sensitive);
+
+int md_array_str_eq(const struct apr_array_header_t *a1,
+ const struct apr_array_header_t *a2, int case_sensitive);
+
+struct apr_array_header_t *md_array_str_clone(apr_pool_t *p, struct apr_array_header_t *array);
+
+/**
+ * Create a new array with duplicates removed.
+ */
+struct apr_array_header_t *md_array_str_compact(apr_pool_t *p, struct apr_array_header_t *src,
+ int case_sensitive);
+
+/**
+ * Create a new array with all occurrences of <exclude> removed.
+ */
+struct apr_array_header_t *md_array_str_remove(apr_pool_t *p, struct apr_array_header_t *src,
+ const char *exclude, int case_sensitive);
+
+int md_array_str_add_missing(struct apr_array_header_t *dest,
+ struct apr_array_header_t *src, int case_sensitive);
+
+/**************************************************************************************************/
+/* process execution */
+
+apr_status_t md_util_exec(apr_pool_t *p, const char *cmd, const char * const *argv,
+ struct apr_array_header_t *env, int *exit_code);
+
+/**************************************************************************************************/
+/* dns name check */
+
+/**
+ * Is a host/domain name using allowed characters. Not a wildcard.
+ * @param domain name to check
+ * @param need_fqdn iff != 0, check that domain contains '.'
+ * @return != 0 iff domain looks like a non-wildcard, legal DNS domain name.
+ */
+int md_dns_is_name(apr_pool_t *p, const char *domain, int need_fqdn);
+
+/**
+ * Check if the given domain is a valid wildcard DNS name, e.g. *.example.org
+ * @param domain name to check
+ * @return != 0 iff domain is a DNS wildcard.
+ */
+int md_dns_is_wildcard(apr_pool_t *p, const char *domain);
+
+/**
+ * Determine iff pattern matches domain, including case-ignore and wildcard domains.
+ * It is assumed that both names follow dns syntax.
+ * @return != 0 iff pattern matches domain
+ */
+int md_dns_matches(const char *pattern, const char *domain);
+
+/**
+ * Create a new array with the minimal set out of the given domain names that match all
+ * of them. If none of the domains is a wildcard, only duplicates are removed.
+ * If domains contain a wildcard, any name matching the wildcard will be removed.
+ */
+struct apr_array_header_t *md_dns_make_minimal(apr_pool_t *p,
+ struct apr_array_header_t *domains);
+
+/**
+ * Determine if the given domains cover the name, including wildcard matching.
+ * @return != 0 iff name is matched by list of domains
+ */
+int md_dns_domains_match(const apr_array_header_t *domains, const char *name);
+
+/**************************************************************************************************/
+/* file system related */
+
+struct apr_file_t;
+struct apr_finfo_t;
+
+apr_status_t md_util_fopen(FILE **pf, const char *fn, const char *mode);
+
+apr_status_t md_util_fcreatex(struct apr_file_t **pf, const char *fn,
+ apr_fileperms_t perms, apr_pool_t *p);
+
+apr_status_t md_util_path_merge(const char **ppath, apr_pool_t *p, ...);
+
+apr_status_t md_util_is_dir(const char *path, apr_pool_t *pool);
+apr_status_t md_util_is_file(const char *path, apr_pool_t *pool);
+apr_status_t md_util_is_unix_socket(const char *path, apr_pool_t *pool);
+int md_file_exists(const char *fname, apr_pool_t *p);
+
+typedef apr_status_t md_util_file_cb(void *baton, struct apr_file_t *f, apr_pool_t *p);
+
+apr_status_t md_util_freplace(const char *fpath, apr_fileperms_t perms, apr_pool_t *p,
+ md_util_file_cb *write, void *baton);
+
+/**
+ * Remove a file/directory and all files/directories contain up to max_level. If max_level == 0,
+ * only an empty directory or a file can be removed.
+ */
+apr_status_t md_util_rm_recursive(const char *fpath, apr_pool_t *p, int max_level);
+
+typedef apr_status_t md_util_fdo_cb(void *baton, apr_pool_t *p, apr_pool_t *ptemp,
+ const char *dir, const char *name,
+ apr_filetype_e ftype);
+
+apr_status_t md_util_files_do(md_util_fdo_cb *cb, void *baton, apr_pool_t *p,
+ const char *path, ...);
+
+/**
+ * Depth first traversal of directory tree starting at path.
+ */
+apr_status_t md_util_tree_do(md_util_fdo_cb *cb, void *baton, apr_pool_t *p,
+ const char *path, int follow_links);
+
+apr_status_t md_util_ftree_remove(const char *path, apr_pool_t *p);
+
+apr_status_t md_text_fread8k(const char **ptext, apr_pool_t *p, const char *fpath);
+apr_status_t md_text_fcreatex(const char *fpath, apr_fileperms_t
+ perms, apr_pool_t *p, const char *text);
+apr_status_t md_text_freplace(const char *fpath, apr_fileperms_t perms,
+ apr_pool_t *p, const char *text);
+
+/**************************************************************************************************/
+/* base64 url encodings */
+const char *md_util_base64url_encode(const md_data_t *data, apr_pool_t *pool);
+apr_size_t md_util_base64url_decode(md_data_t *decoded, const char *encoded,
+ apr_pool_t *pool);
+
+/**************************************************************************************************/
+/* http/url related */
+const char *md_util_schemify(apr_pool_t *p, const char *s, const char *def_scheme);
+
+apr_status_t md_util_abs_uri_check(apr_pool_t *p, const char *s, const char **perr);
+apr_status_t md_util_abs_http_uri_check(apr_pool_t *p, const char *uri, const char **perr);
+
+const char *md_link_find_relation(const struct apr_table_t *headers,
+ apr_pool_t *pool, const char *relation);
+
+const char *md_util_parse_ct(apr_pool_t *pool, const char *cth);
+/**************************************************************************************************/
+/* retry logic */
+
+typedef apr_status_t md_util_try_fn(void *baton, int i);
+
+apr_status_t md_util_try(md_util_try_fn *fn, void *baton, int ignore_errs,
+ apr_interval_time_t timeout, apr_interval_time_t start_delay,
+ apr_interval_time_t max_delay, int backoff);
+
+#endif /* md_util_h */
diff --git a/modules/md/md_version.h b/modules/md/md_version.h
new file mode 100644
index 0000000..a8f3ef2
--- /dev/null
+++ b/modules/md/md_version.h
@@ -0,0 +1,43 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mod_md_md_version_h
+#define mod_md_md_version_h
+
+#undef PACKAGE_VERSION
+#undef PACKAGE_TARNAME
+#undef PACKAGE_STRING
+#undef PACKAGE_NAME
+#undef PACKAGE_BUGREPORT
+
+/**
+ * @macro
+ * Version number of the md module as c string
+ */
+#define MOD_MD_VERSION "2.4.21"
+
+/**
+ * @macro
+ * Numerical representation of the version number of the md module
+ * release. This is a 24 bit number with 8 bits for major number, 8 bits
+ * for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203.
+ */
+#define MOD_MD_VERSION_NUM 0x020415
+
+#define MD_ACME_DEF_URL "https://acme-v02.api.letsencrypt.org/directory"
+#define MD_TAILSCALE_DEF_URL "file://localhost/var/run/tailscale/tailscaled.sock"
+
+#endif /* mod_md_md_version_h */
diff --git a/modules/md/mod_md.c b/modules/md/mod_md.c
new file mode 100644
index 0000000..32dea4f
--- /dev/null
+++ b/modules/md/mod_md.c
@@ -0,0 +1,1535 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <apr_optional.h>
+#include <apr_strings.h>
+
+#include <mpm_common.h>
+#include <httpd.h>
+#include <http_core.h>
+#include <http_protocol.h>
+#include <http_request.h>
+#include <http_ssl.h>
+#include <http_log.h>
+#include <http_vhost.h>
+#include <ap_listen.h>
+
+#include "mod_status.h"
+
+#include "md.h"
+#include "md_curl.h"
+#include "md_crypt.h"
+#include "md_event.h"
+#include "md_http.h"
+#include "md_json.h"
+#include "md_store.h"
+#include "md_store_fs.h"
+#include "md_log.h"
+#include "md_ocsp.h"
+#include "md_result.h"
+#include "md_reg.h"
+#include "md_status.h"
+#include "md_util.h"
+#include "md_version.h"
+#include "md_acme.h"
+#include "md_acme_authz.h"
+
+#include "mod_md.h"
+#include "mod_md_config.h"
+#include "mod_md_drive.h"
+#include "mod_md_ocsp.h"
+#include "mod_md_os.h"
+#include "mod_md_status.h"
+
+static void md_hooks(apr_pool_t *pool);
+
+AP_DECLARE_MODULE(md) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* func to create per dir config */
+ NULL, /* func to merge per dir config */
+ md_config_create_svr, /* func to create per server config */
+ md_config_merge_svr, /* func to merge per server config */
+ md_cmds, /* command handlers */
+ md_hooks,
+#if defined(AP_MODULE_FLAG_NONE)
+ AP_MODULE_FLAG_ALWAYS_MERGE
+#endif
+};
+
+/**************************************************************************************************/
+/* logging setup */
+
+static server_rec *log_server;
+
+static int log_is_level(void *baton, apr_pool_t *p, md_log_level_t level)
+{
+ (void)baton;
+ (void)p;
+ if (log_server) {
+ return APLOG_IS_LEVEL(log_server, (int)level);
+ }
+ return level <= MD_LOG_INFO;
+}
+
+#define LOG_BUF_LEN 16*1024
+
+static void log_print(const char *file, int line, md_log_level_t level,
+ apr_status_t rv, void *baton, apr_pool_t *p, const char *fmt, va_list ap)
+{
+ if (log_is_level(baton, p, level)) {
+ char buffer[LOG_BUF_LEN];
+
+ memset(buffer, 0, sizeof(buffer));
+ apr_vsnprintf(buffer, LOG_BUF_LEN-1, fmt, ap);
+ buffer[LOG_BUF_LEN-1] = '\0';
+
+ if (log_server) {
+ ap_log_error(file, line, APLOG_MODULE_INDEX, (int)level, rv, log_server, "%s", buffer);
+ }
+ else {
+ ap_log_perror(file, line, APLOG_MODULE_INDEX, (int)level, rv, p, "%s", buffer);
+ }
+ }
+}
+
+/**************************************************************************************************/
+/* mod_ssl interface */
+
+static void init_ssl(void)
+{
+ /* nop */
+}
+
+/**************************************************************************************************/
+/* lifecycle */
+
+static apr_status_t cleanup_setups(void *dummy)
+{
+ (void)dummy;
+ log_server = NULL;
+ return APR_SUCCESS;
+}
+
+static void init_setups(apr_pool_t *p, server_rec *base_server)
+{
+ log_server = base_server;
+ apr_pool_cleanup_register(p, NULL, cleanup_setups, apr_pool_cleanup_null);
+}
+
+/**************************************************************************************************/
+/* notification handling */
+
+typedef struct {
+ const char *reason; /* what the notification is about */
+ apr_time_t min_interim; /* minimum time between notifying for this reason */
+} notify_rate;
+
+static notify_rate notify_rates[] = {
+ { "renewing", apr_time_from_sec(MD_SECS_PER_HOUR) }, /* once per hour */
+ { "renewed", apr_time_from_sec(MD_SECS_PER_DAY) }, /* once per day */
+ { "installed", apr_time_from_sec(MD_SECS_PER_DAY) }, /* once per day */
+ { "expiring", apr_time_from_sec(MD_SECS_PER_DAY) }, /* once per day */
+ { "errored", apr_time_from_sec(MD_SECS_PER_HOUR) }, /* once per hour */
+ { "ocsp-renewed", apr_time_from_sec(MD_SECS_PER_DAY) }, /* once per day */
+ { "ocsp-errored", apr_time_from_sec(MD_SECS_PER_HOUR) }, /* once per hour */
+};
+
+static apr_status_t notify(md_job_t *job, const char *reason,
+ md_result_t *result, apr_pool_t *p, void *baton)
+{
+ md_mod_conf_t *mc = baton;
+ const char * const *argv;
+ const char *cmdline;
+ int exit_code;
+ apr_status_t rv = APR_SUCCESS;
+ apr_time_t min_interim = 0;
+ md_timeperiod_t since_last;
+ const char *log_msg_reason;
+ int i;
+
+ log_msg_reason = apr_psprintf(p, "message-%s", reason);
+ for (i = 0; i < (int)(sizeof(notify_rates)/sizeof(notify_rates[0])); ++i) {
+ if (!strcmp(reason, notify_rates[i].reason)) {
+ min_interim = notify_rates[i].min_interim;
+ }
+ }
+ if (min_interim > 0) {
+ since_last.start = md_job_log_get_time_of_latest(job, log_msg_reason);
+ since_last.end = apr_time_now();
+ if (since_last.start > 0 && md_timeperiod_length(&since_last) < min_interim) {
+ /* not enough time has passed since we sent the last notification
+ * for this reason. */
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, APLOGNO(10267)
+ "%s: rate limiting notification about '%s'", job->mdomain, reason);
+ return APR_SUCCESS;
+ }
+ }
+
+ if (!strcmp("renewed", reason)) {
+ if (mc->notify_cmd) {
+ cmdline = apr_psprintf(p, "%s %s", mc->notify_cmd, job->mdomain);
+ apr_tokenize_to_argv(cmdline, (char***)&argv, p);
+ rv = md_util_exec(p, argv[0], argv, NULL, &exit_code);
+
+ if (APR_SUCCESS == rv && exit_code) rv = APR_EGENERAL;
+ if (APR_SUCCESS != rv) {
+ md_result_problem_printf(result, rv, MD_RESULT_LOG_ID(APLOGNO(10108)),
+ "MDNotifyCmd %s failed with exit code %d.",
+ mc->notify_cmd, exit_code);
+ md_result_log(result, MD_LOG_ERR);
+ md_job_log_append(job, "notify-error", result->problem, result->detail);
+ return rv;
+ }
+ }
+ md_log_perror(MD_LOG_MARK, MD_LOG_NOTICE, 0, p, APLOGNO(10059)
+ "The Managed Domain %s has been setup and changes "
+ "will be activated on next (graceful) server restart.", job->mdomain);
+ }
+ if (mc->message_cmd) {
+ cmdline = apr_psprintf(p, "%s %s %s", mc->message_cmd, reason, job->mdomain);
+ apr_tokenize_to_argv(cmdline, (char***)&argv, p);
+ rv = md_util_exec(p, argv[0], argv, NULL, &exit_code);
+
+ if (APR_SUCCESS == rv && exit_code) rv = APR_EGENERAL;
+ if (APR_SUCCESS != rv) {
+ md_result_problem_printf(result, rv, MD_RESULT_LOG_ID(APLOGNO(10109)),
+ "MDMessageCmd %s failed with exit code %d.",
+ mc->message_cmd, exit_code);
+ md_result_log(result, MD_LOG_ERR);
+ md_job_log_append(job, "message-error", reason, result->detail);
+ return rv;
+ }
+ }
+
+ md_job_log_append(job, log_msg_reason, NULL, NULL);
+ return APR_SUCCESS;
+}
+
+static apr_status_t on_event(const char *event, const char *mdomain, void *baton,
+ md_job_t *job, md_result_t *result, apr_pool_t *p)
+{
+ (void)mdomain;
+ return notify(job, event, result, p, baton);
+}
+
+/**************************************************************************************************/
+/* store setup */
+
+static apr_status_t store_file_ev(void *baton, struct md_store_t *store,
+ md_store_fs_ev_t ev, unsigned int group,
+ const char *fname, apr_filetype_e ftype,
+ apr_pool_t *p)
+{
+ server_rec *s = baton;
+ apr_status_t rv;
+
+ (void)store;
+ ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s, "store event=%d on %s %s (group %d)",
+ ev, (ftype == APR_DIR)? "dir" : "file", fname, group);
+
+ /* Directories in group CHALLENGES, STAGING and OCSP are written to
+ * under a different user. Give her ownership.
+ */
+ if (ftype == APR_DIR) {
+ switch (group) {
+ case MD_SG_CHALLENGES:
+ case MD_SG_STAGING:
+ case MD_SG_OCSP:
+ rv = md_make_worker_accessible(fname, p);
+ if (APR_ENOTIMPL != rv) {
+ return rv;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ return APR_SUCCESS;
+}
+
+static apr_status_t check_group_dir(md_store_t *store, md_store_group_t group,
+ apr_pool_t *p, server_rec *s)
+{
+ const char *dir;
+ apr_status_t rv;
+
+ if (APR_SUCCESS == (rv = md_store_get_fname(&dir, store, group, NULL, NULL, p))
+ && APR_SUCCESS == (rv = apr_dir_make_recursive(dir, MD_FPROT_D_UALL_GREAD, p))) {
+ rv = store_file_ev(s, store, MD_S_FS_EV_CREATED, group, dir, APR_DIR, p);
+ }
+ return rv;
+}
+
+static apr_status_t setup_store(md_store_t **pstore, md_mod_conf_t *mc,
+ apr_pool_t *p, server_rec *s)
+{
+ const char *base_dir;
+ apr_status_t rv;
+
+ base_dir = ap_server_root_relative(p, mc->base_dir);
+
+ if (APR_SUCCESS != (rv = md_store_fs_init(pstore, p, base_dir))) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10046)"setup store for %s", base_dir);
+ goto leave;
+ }
+
+ md_store_fs_set_event_cb(*pstore, store_file_ev, s);
+ if (APR_SUCCESS != (rv = check_group_dir(*pstore, MD_SG_CHALLENGES, p, s))
+ || APR_SUCCESS != (rv = check_group_dir(*pstore, MD_SG_STAGING, p, s))
+ || APR_SUCCESS != (rv = check_group_dir(*pstore, MD_SG_ACCOUNTS, p, s))
+ || APR_SUCCESS != (rv = check_group_dir(*pstore, MD_SG_OCSP, p, s))
+ ) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10047)
+ "setup challenges directory");
+ goto leave;
+ }
+
+leave:
+ return rv;
+}
+
+/**************************************************************************************************/
+/* post config handling */
+
+static void merge_srv_config(md_t *md, md_srv_conf_t *base_sc, apr_pool_t *p)
+{
+ const char *contact;
+
+ if (!md->sc) {
+ md->sc = base_sc;
+ }
+
+ if (!md->ca_urls && md->sc->ca_urls) {
+ md->ca_urls = apr_array_copy(p, md->sc->ca_urls);
+ }
+ if (!md->ca_proto) {
+ md->ca_proto = md_config_gets(md->sc, MD_CONFIG_CA_PROTO);
+ }
+ if (!md->ca_agreement) {
+ md->ca_agreement = md_config_gets(md->sc, MD_CONFIG_CA_AGREEMENT);
+ }
+ contact = md_config_gets(md->sc, MD_CONFIG_CA_CONTACT);
+ if (md->contacts && md->contacts->nelts > 0) {
+ /* set explicitly */
+ }
+ else if (contact && contact[0]) {
+ apr_array_clear(md->contacts);
+ APR_ARRAY_PUSH(md->contacts, const char *) =
+ md_util_schemify(p, contact, "mailto");
+ }
+ else if( md->sc->s->server_admin && strcmp(DEFAULT_ADMIN, md->sc->s->server_admin)) {
+ apr_array_clear(md->contacts);
+ APR_ARRAY_PUSH(md->contacts, const char *) =
+ md_util_schemify(p, md->sc->s->server_admin, "mailto");
+ }
+ if (md->renew_mode == MD_RENEW_DEFAULT) {
+ md->renew_mode = md_config_geti(md->sc, MD_CONFIG_DRIVE_MODE);
+ }
+ if (!md->renew_window) md_config_get_timespan(&md->renew_window, md->sc, MD_CONFIG_RENEW_WINDOW);
+ if (!md->warn_window) md_config_get_timespan(&md->warn_window, md->sc, MD_CONFIG_WARN_WINDOW);
+ if (md->transitive < 0) {
+ md->transitive = md_config_geti(md->sc, MD_CONFIG_TRANSITIVE);
+ }
+ if (!md->ca_challenges && md->sc->ca_challenges) {
+ md->ca_challenges = apr_array_copy(p, md->sc->ca_challenges);
+ }
+ if (md_pkeys_spec_is_empty(md->pks)) {
+ md->pks = md->sc->pks;
+ }
+ if (md->require_https < 0) {
+ md->require_https = md_config_geti(md->sc, MD_CONFIG_REQUIRE_HTTPS);
+ }
+ if (!md->ca_eab_kid) {
+ md->ca_eab_kid = md->sc->ca_eab_kid;
+ md->ca_eab_hmac = md->sc->ca_eab_hmac;
+ }
+ if (md->must_staple < 0) {
+ md->must_staple = md_config_geti(md->sc, MD_CONFIG_MUST_STAPLE);
+ }
+ if (md->stapling < 0) {
+ md->stapling = md_config_geti(md->sc, MD_CONFIG_STAPLING);
+ }
+}
+
+static apr_status_t check_coverage(md_t *md, const char *domain, server_rec *s,
+ int *pupdates, apr_pool_t *p)
+{
+ if (md_contains(md, domain, 0)) {
+ return APR_SUCCESS;
+ }
+ else if (md->transitive) {
+ APR_ARRAY_PUSH(md->domains, const char*) = apr_pstrdup(p, domain);
+ *pupdates |= MD_UPD_DOMAINS;
+ return APR_SUCCESS;
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(10040)
+ "Virtual Host %s:%d matches Managed Domain '%s', but the "
+ "name/alias %s itself is not managed. A requested MD certificate "
+ "will not match ServerName.",
+ s->server_hostname, s->port, md->name, domain);
+ return APR_EINVAL;
+ }
+}
+
+static apr_status_t md_cover_server(md_t *md, server_rec *s, int *pupdates, apr_pool_t *p)
+{
+ apr_status_t rv;
+ const char *name;
+ int i;
+
+ if (APR_SUCCESS == (rv = check_coverage(md, s->server_hostname, s, pupdates, p))) {
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s,
+ "md[%s]: auto add, covers name %s", md->name, s->server_hostname);
+ for (i = 0; s->names && i < s->names->nelts; ++i) {
+ name = APR_ARRAY_IDX(s->names, i, const char*);
+ if (APR_SUCCESS != (rv = check_coverage(md, name, s, pupdates, p))) {
+ break;
+ }
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s,
+ "md[%s]: auto add, covers alias %s", md->name, name);
+ }
+ }
+ return rv;
+}
+
+static int uses_port(server_rec *s, int port)
+{
+ server_addr_rec *sa;
+ int match = 0;
+ for (sa = s->addrs; sa; sa = sa->next) {
+ if (sa->host_port == port) {
+ /* host_addr might be general (0.0.0.0) or specific, we count this as match */
+ match = 1;
+ }
+ else {
+ /* uses other port/wildcard */
+ return 0;
+ }
+ }
+ return match;
+}
+
+static apr_status_t detect_supported_protocols(md_mod_conf_t *mc, server_rec *s,
+ apr_pool_t *p, int log_level)
+{
+ ap_listen_rec *lr;
+ apr_sockaddr_t *sa;
+ int can_http, can_https;
+
+ if (mc->can_http >= 0 && mc->can_https >= 0) goto set_and_leave;
+
+ can_http = can_https = 0;
+ for (lr = ap_listeners; lr; lr = lr->next) {
+ for (sa = lr->bind_addr; sa; sa = sa->next) {
+ if (sa->port == mc->local_80
+ && (!lr->protocol || !strncmp("http", lr->protocol, 4))) {
+ can_http = 1;
+ }
+ else if (sa->port == mc->local_443
+ && (!lr->protocol || !strncmp("http", lr->protocol, 4))) {
+ can_https = 1;
+ }
+ }
+ }
+ if (mc->can_http < 0) mc->can_http = can_http;
+ if (mc->can_https < 0) mc->can_https = can_https;
+ ap_log_error(APLOG_MARK, log_level, 0, s, APLOGNO(10037)
+ "server seems%s reachable via http: and%s reachable via https:",
+ mc->can_http? "" : " not", mc->can_https? "" : " not");
+set_and_leave:
+ return md_reg_set_props(mc->reg, p, mc->can_http, mc->can_https);
+}
+
+static server_rec *get_public_https_server(md_t *md, const char *domain, server_rec *base_server)
+{
+ md_srv_conf_t *sc;
+ md_mod_conf_t *mc;
+ server_rec *s;
+ server_rec *res = NULL;
+ request_rec r;
+ int i;
+ int check_port = 1;
+
+ sc = md_config_get(base_server);
+ mc = sc->mc;
+ memset(&r, 0, sizeof(r));
+
+ if (md->ca_challenges && md->ca_challenges->nelts > 0) {
+ /* skip the port check if "tls-alpn-01" is pre-configured */
+ check_port = !(md_array_str_index(md->ca_challenges, MD_AUTHZ_TYPE_TLSALPN01, 0, 0) >= 0);
+ }
+
+ if (check_port && !mc->can_https) return NULL;
+
+ /* find an ssl server matching domain from MD */
+ for (s = base_server; s; s = s->next) {
+ sc = md_config_get(s);
+ if (!sc || !sc->is_ssl || !sc->assigned) continue;
+ if (base_server == s && !mc->manage_base_server) continue;
+ if (base_server != s && check_port && mc->local_443 > 0 && !uses_port(s, mc->local_443)) continue;
+ for (i = 0; i < sc->assigned->nelts; ++i) {
+ if (md == APR_ARRAY_IDX(sc->assigned, i, md_t*)) {
+ r.server = s;
+ if (ap_matches_request_vhost(&r, domain, s->port)) {
+ if (check_port) {
+ return s;
+ }
+ else {
+ /* there may be multiple matching servers because we ignore the port.
+ if possible, choose a server that supports the acme-tls/1 protocol */
+ if (ap_is_allowed_protocol(NULL, NULL, s, PROTO_ACME_TLS_1)) {
+ return s;
+ }
+ res = s;
+ }
+ }
+ }
+ }
+ }
+ return res;
+}
+
+static apr_status_t auto_add_domains(md_t *md, server_rec *base_server, apr_pool_t *p)
+{
+ md_srv_conf_t *sc;
+ server_rec *s;
+ apr_status_t rv = APR_SUCCESS;
+ int updates;
+
+ /* Ad all domain names used in SSL VirtualHosts, if not already there */
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, base_server,
+ "md[%s]: auto add domains", md->name);
+ updates = 0;
+ for (s = base_server; s; s = s->next) {
+ sc = md_config_get(s);
+ if (!sc || !sc->is_ssl || !sc->assigned || sc->assigned->nelts != 1) continue;
+ if (md != APR_ARRAY_IDX(sc->assigned, 0, md_t*)) continue;
+ if (APR_SUCCESS != (rv = md_cover_server(md, s, &updates, p))) {
+ return rv;
+ }
+ }
+ return rv;
+}
+
+static void init_acme_tls_1_domains(md_t *md, server_rec *base_server)
+{
+ md_srv_conf_t *sc;
+ md_mod_conf_t *mc;
+ server_rec *s;
+ int i;
+ const char *domain;
+
+ /* Collect those domains that support the "acme-tls/1" protocol. This
+ * is part of the MD (and not tested dynamically), since challenge selection
+ * may be done outside the server, e.g. in the a2md command. */
+ sc = md_config_get(base_server);
+ mc = sc->mc;
+ apr_array_clear(md->acme_tls_1_domains);
+ for (i = 0; i < md->domains->nelts; ++i) {
+ domain = APR_ARRAY_IDX(md->domains, i, const char*);
+ s = get_public_https_server(md, domain, base_server);
+ /* If we did not find a specific virtualhost for md and manage
+ * the base_server, that one is inspected */
+ if (NULL == s && mc->manage_base_server) s = base_server;
+ if (NULL == s) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO(10168)
+ "%s: no https server_rec found for %s", md->name, domain);
+ continue;
+ }
+ if (!ap_is_allowed_protocol(NULL, NULL, s, PROTO_ACME_TLS_1)) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO(10169)
+ "%s: https server_rec for %s does not have protocol %s enabled",
+ md->name, domain, PROTO_ACME_TLS_1);
+ continue;
+ }
+ APR_ARRAY_PUSH(md->acme_tls_1_domains, const char*) = domain;
+ }
+}
+
+static apr_status_t link_md_to_servers(md_mod_conf_t *mc, md_t *md, server_rec *base_server,
+ apr_pool_t *p)
+{
+ server_rec *s;
+ request_rec r;
+ md_srv_conf_t *sc;
+ int i;
+ const char *domain, *uri;
+
+ sc = md_config_get(base_server);
+
+ /* Assign the MD to all server_rec configs that it matches. If there already
+ * is an assigned MD not equal this one, the configuration is in error.
+ */
+ memset(&r, 0, sizeof(r));
+ for (s = base_server; s; s = s->next) {
+ if (!mc->manage_base_server && s == base_server) {
+ /* we shall not assign ourselves to the base server */
+ continue;
+ }
+
+ r.server = s;
+ for (i = 0; i < md->domains->nelts; ++i) {
+ domain = APR_ARRAY_IDX(md->domains, i, const char*);
+
+ if (ap_matches_request_vhost(&r, domain, s->port)
+ || (md_dns_is_wildcard(p, domain) && md_dns_matches(domain, s->server_hostname))) {
+ /* Create a unique md_srv_conf_t record for this server, if there is none yet */
+ sc = md_config_get_unique(s, p);
+ if (!sc->assigned) sc->assigned = apr_array_make(p, 2, sizeof(md_t*));
+
+ APR_ARRAY_PUSH(sc->assigned, md_t*) = md;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO(10041)
+ "Server %s:%d matches md %s (config %s) for domain %s, "
+ "has now %d MDs",
+ s->server_hostname, s->port, md->name, sc->name,
+ domain, (int)sc->assigned->nelts);
+
+ if (md->contacts && md->contacts->nelts > 0) {
+ /* set explicitly */
+ }
+ else if (sc->ca_contact && sc->ca_contact[0]) {
+ uri = md_util_schemify(p, sc->ca_contact, "mailto");
+ if (md_array_str_index(md->contacts, uri, 0, 0) < 0) {
+ APR_ARRAY_PUSH(md->contacts, const char *) = uri;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO(10044)
+ "%s: added contact %s", md->name, uri);
+ }
+ }
+ else if (s->server_admin && strcmp(DEFAULT_ADMIN, s->server_admin)) {
+ uri = md_util_schemify(p, s->server_admin, "mailto");
+ if (md_array_str_index(md->contacts, uri, 0, 0) < 0) {
+ APR_ARRAY_PUSH(md->contacts, const char *) = uri;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO(10237)
+ "%s: added contact %s", md->name, uri);
+ }
+ }
+ break;
+ }
+ }
+ }
+ return APR_SUCCESS;
+}
+
+static apr_status_t link_mds_to_servers(md_mod_conf_t *mc, server_rec *s, apr_pool_t *p)
+{
+ int i;
+ md_t *md;
+ apr_status_t rv = APR_SUCCESS;
+
+ apr_array_clear(mc->unused_names);
+ for (i = 0; i < mc->mds->nelts; ++i) {
+ md = APR_ARRAY_IDX(mc->mds, i, md_t*);
+ if (APR_SUCCESS != (rv = link_md_to_servers(mc, md, s, p))) {
+ goto leave;
+ }
+ }
+leave:
+ return rv;
+}
+
+static apr_status_t merge_mds_with_conf(md_mod_conf_t *mc, apr_pool_t *p,
+ server_rec *base_server, int log_level)
+{
+ md_srv_conf_t *base_conf;
+ md_t *md, *omd;
+ const char *domain;
+ md_timeslice_t *ts;
+ apr_status_t rv = APR_SUCCESS;
+ int i, j;
+
+ /* The global module configuration 'mc' keeps a list of all configured MDomains
+ * in the server. This list is collected during configuration processing and,
+ * in the post config phase, get updated from all merged server configurations
+ * before the server starts processing.
+ */
+ base_conf = md_config_get(base_server);
+ md_config_get_timespan(&ts, base_conf, MD_CONFIG_RENEW_WINDOW);
+ if (ts) md_reg_set_renew_window_default(mc->reg, ts);
+ md_config_get_timespan(&ts, base_conf, MD_CONFIG_WARN_WINDOW);
+ if (ts) md_reg_set_warn_window_default(mc->reg, ts);
+
+ /* Complete the properties of the MDs, now that we have the complete, merged
+ * server configurations.
+ */
+ for (i = 0; i < mc->mds->nelts; ++i) {
+ md = APR_ARRAY_IDX(mc->mds, i, md_t*);
+ merge_srv_config(md, base_conf, p);
+
+ /* Check that we have no overlap with the MDs already completed */
+ for (j = 0; j < i; ++j) {
+ omd = APR_ARRAY_IDX(mc->mds, j, md_t*);
+ if ((domain = md_common_name(md, omd)) != NULL) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO(10038)
+ "two Managed Domains have an overlap in domain '%s'"
+ ", first definition in %s(line %d), second in %s(line %d)",
+ domain, md->defn_name, md->defn_line_number,
+ omd->defn_name, omd->defn_line_number);
+ return APR_EINVAL;
+ }
+ }
+
+ if (md->cert_files && md->cert_files->nelts) {
+ if (!md->pkey_files || (md->cert_files->nelts != md->pkey_files->nelts)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO(10170)
+ "The Managed Domain '%s' "
+ "needs one MDCertificateKeyFile for each MDCertificateFile.",
+ md->name);
+ return APR_EINVAL;
+ }
+ }
+ else if (md->pkey_files && md->pkey_files->nelts
+ && (!md->cert_files || !md->cert_files->nelts)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO(10171)
+ "The Managed Domain '%s' "
+ "has MDCertificateKeyFile(s) but no MDCertificateFile.",
+ md->name);
+ return APR_EINVAL;
+ }
+
+ if (APLOG_IS_LEVEL(base_server, log_level)) {
+ ap_log_error(APLOG_MARK, log_level, 0, base_server, APLOGNO(10039)
+ "Completed MD[%s, CA=%s, Proto=%s, Agreement=%s, renew-mode=%d "
+ "renew_window=%s, warn_window=%s",
+ md->name, md->ca_effective, md->ca_proto, md->ca_agreement, md->renew_mode,
+ md->renew_window? md_timeslice_format(md->renew_window, p) : "unset",
+ md->warn_window? md_timeslice_format(md->warn_window, p) : "unset");
+ }
+ }
+ return rv;
+}
+
+static apr_status_t check_invalid_duplicates(server_rec *base_server)
+{
+ server_rec *s;
+ md_srv_conf_t *sc;
+
+ ap_log_error( APLOG_MARK, APLOG_TRACE1, 0, base_server,
+ "checking duplicate ssl assignments");
+ for (s = base_server; s; s = s->next) {
+ sc = md_config_get(s);
+ if (!sc || !sc->assigned) continue;
+
+ if (sc->assigned->nelts > 1 && sc->is_ssl) {
+ /* duplicate assignment to SSL VirtualHost, not allowed */
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO(10042)
+ "conflict: %d MDs match to SSL VirtualHost %s, there can at most be one.",
+ (int)sc->assigned->nelts, s->server_hostname);
+ return APR_EINVAL;
+ }
+ }
+ return APR_SUCCESS;
+}
+
+static apr_status_t check_usage(md_mod_conf_t *mc, md_t *md, server_rec *base_server,
+ apr_pool_t *p, apr_pool_t *ptemp)
+{
+ server_rec *s;
+ md_srv_conf_t *sc;
+ apr_status_t rv = APR_SUCCESS;
+ int i, has_ssl;
+ apr_array_header_t *servers;
+
+ (void)p;
+ servers = apr_array_make(ptemp, 5, sizeof(server_rec*));
+ has_ssl = 0;
+ for (s = base_server; s; s = s->next) {
+ sc = md_config_get(s);
+ if (!sc || !sc->assigned) continue;
+ for (i = 0; i < sc->assigned->nelts; ++i) {
+ if (md == APR_ARRAY_IDX(sc->assigned, i, md_t*)) {
+ APR_ARRAY_PUSH(servers, server_rec*) = s;
+ if (sc->is_ssl) has_ssl = 1;
+ }
+ }
+ }
+
+ if (!has_ssl && md->require_https > MD_REQUIRE_OFF) {
+ /* We require https for this MD, but do we have a SSL vhost? */
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, base_server, APLOGNO(10105)
+ "MD %s does not match any VirtualHost with 'SSLEngine on', "
+ "but is configured to require https. This cannot work.", md->name);
+ }
+ if (apr_is_empty_array(servers)) {
+ if (md->renew_mode != MD_RENEW_ALWAYS) {
+ /* Not an error, but looks suspicious */
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, base_server, APLOGNO(10045)
+ "No VirtualHost matches Managed Domain %s", md->name);
+ APR_ARRAY_PUSH(mc->unused_names, const char*) = md->name;
+ }
+ }
+ return rv;
+}
+
+static int init_cert_watch_status(md_mod_conf_t *mc, apr_pool_t *p, apr_pool_t *ptemp, server_rec *s)
+{
+ md_t *md;
+ md_result_t *result;
+ int i, count;
+
+ /* Calculate the list of MD names which we need to watch:
+ * - all MDs that are used somewhere
+ * - all MDs in drive mode 'AUTO' that are not in 'unused_names'
+ */
+ count = 0;
+ result = md_result_make(ptemp, APR_SUCCESS);
+ for (i = 0; i < mc->mds->nelts; ++i) {
+ md = APR_ARRAY_IDX(mc->mds, i, md_t*);
+ md_result_set(result, APR_SUCCESS, NULL);
+ md->watched = 0;
+ if (md->state == MD_S_ERROR) {
+ md_result_set(result, APR_EGENERAL,
+ "in error state, unable to drive forward. This "
+ "indicates an incomplete or inconsistent configuration. "
+ "Please check the log for warnings in this regard.");
+ continue;
+ }
+
+ if (md->renew_mode == MD_RENEW_AUTO
+ && md_array_str_index(mc->unused_names, md->name, 0, 0) >= 0) {
+ /* This MD is not used in any virtualhost, do not watch */
+ continue;
+ }
+
+ if (md_will_renew_cert(md)) {
+ /* make a test init to detect early errors. */
+ md_reg_test_init(mc->reg, md, mc->env, result, p);
+ if (APR_SUCCESS != result->status && result->detail) {
+ apr_hash_set(mc->init_errors, md->name, APR_HASH_KEY_STRING, apr_pstrdup(p, result->detail));
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(10173)
+ "md[%s]: %s", md->name, result->detail);
+ }
+ }
+
+ md->watched = 1;
+ ++count;
+ }
+ return count;
+}
+
+static apr_status_t md_post_config_before_ssl(apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ void *data = NULL;
+ const char *mod_md_init_key = "mod_md_init_counter";
+ md_srv_conf_t *sc;
+ md_mod_conf_t *mc;
+ apr_status_t rv = APR_SUCCESS;
+ int dry_run = 0, log_level = APLOG_DEBUG;
+ md_store_t *store;
+
+ apr_pool_userdata_get(&data, mod_md_init_key, s->process->pool);
+ if (data == NULL) {
+ /* At the first start, httpd makes a config check dry run. It
+ * runs all config hooks to check if it can. If so, it does
+ * this all again and starts serving requests.
+ *
+ * On a dry run, we therefore do all the cheap config things we
+ * need to do to find out if the settings are ok. More expensive
+ * things we delay to the real run.
+ */
+ dry_run = 1;
+ log_level = APLOG_TRACE1;
+ ap_log_error( APLOG_MARK, log_level, 0, s, APLOGNO(10070)
+ "initializing post config dry run");
+ apr_pool_userdata_set((const void *)1, mod_md_init_key,
+ apr_pool_cleanup_null, s->process->pool);
+ }
+ else {
+ ap_log_error( APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(10071)
+ "mod_md (v%s), initializing...", MOD_MD_VERSION);
+ }
+
+ (void)plog;
+ init_setups(p, s);
+ md_log_set(log_is_level, log_print, NULL);
+
+ md_config_post_config(s, p);
+ sc = md_config_get(s);
+ mc = sc->mc;
+ mc->dry_run = dry_run;
+
+ md_event_init(p);
+ md_event_subscribe(on_event, mc);
+
+ rv = setup_store(&store, mc, p, s);
+ if (APR_SUCCESS != rv) goto leave;
+
+ rv = md_reg_create(&mc->reg, p, store, mc->proxy_url, mc->ca_certs,
+ mc->min_delay, mc->retry_failover,
+ mc->use_store_locks, mc->lock_wait_timeout);
+ if (APR_SUCCESS != rv) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10072) "setup md registry");
+ goto leave;
+ }
+
+ /* renew on 30% remaining /*/
+ rv = md_ocsp_reg_make(&mc->ocsp, p, store, mc->ocsp_renew_window,
+ AP_SERVER_BASEVERSION, mc->proxy_url,
+ mc->min_delay);
+ if (APR_SUCCESS != rv) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10196) "setup ocsp registry");
+ goto leave;
+ }
+
+ init_ssl();
+
+ /* How to bootstrap this module:
+ * 1. find out if we know if http: and/or https: requests will arrive
+ * 2. apply the now complete configuration settings to the MDs
+ * 3. Link MDs to the server_recs they are used in. Detect unused MDs.
+ * 4. Update the store with the MDs. Change domain names, create new MDs, etc.
+ * Basically all MD properties that are configured directly.
+ * WARNING: this may change the name of an MD. If an MD loses the first
+ * of its domain names, it first gets the new first one as name. The
+ * store will find the old settings and "recover" the previous name.
+ * 5. Load any staged data from previous driving.
+ * 6. on a dry run, this is all we do
+ * 7. Read back the MD properties that reflect the existence and aspect of
+ * credentials that are in the store (or missing there).
+ * Expiry times, MD state, etc.
+ * 8. Determine the list of MDs that need driving/supervision.
+ * 9. Cleanup any left-overs in registry/store that are no longer needed for
+ * the list of MDs as we know it now.
+ * 10. If this list is non-empty, setup a watchdog to run.
+ */
+ /*1*/
+ if (APR_SUCCESS != (rv = detect_supported_protocols(mc, s, p, log_level))) goto leave;
+ /*2*/
+ if (APR_SUCCESS != (rv = merge_mds_with_conf(mc, p, s, log_level))) goto leave;
+ /*3*/
+ if (APR_SUCCESS != (rv = link_mds_to_servers(mc, s, p))) goto leave;
+ /*4*/
+ if (APR_SUCCESS != (rv = md_reg_lock_global(mc->reg, ptemp))) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10398)
+ "unable to obtain global registry lock, "
+ "renewed certificates may remain inactive on "
+ "this httpd instance!");
+ /* FIXME: or should we fail the server start/reload here? */
+ rv = APR_SUCCESS;
+ goto leave;
+ }
+ if (APR_SUCCESS != (rv = md_reg_sync_start(mc->reg, mc->mds, ptemp))) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10073)
+ "syncing %d mds to registry", mc->mds->nelts);
+ goto leave;
+ }
+ /*5*/
+ md_reg_load_stagings(mc->reg, mc->mds, mc->env, p);
+leave:
+ md_reg_unlock_global(mc->reg, ptemp);
+ return rv;
+}
+
+static apr_status_t md_post_config_after_ssl(apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ md_srv_conf_t *sc;
+ apr_status_t rv = APR_SUCCESS;
+ md_mod_conf_t *mc;
+ int watched, i;
+ md_t *md;
+
+ (void)ptemp;
+ (void)plog;
+ sc = md_config_get(s);
+
+ /*6*/
+ if (!sc || !sc->mc || sc->mc->dry_run) goto leave;
+ mc = sc->mc;
+
+ /*7*/
+ if (APR_SUCCESS != (rv = check_invalid_duplicates(s))) {
+ goto leave;
+ }
+ apr_array_clear(mc->unused_names);
+ for (i = 0; i < mc->mds->nelts; ++i) {
+ md = APR_ARRAY_IDX(mc->mds, i, md_t *);
+
+ ap_log_error( APLOG_MARK, APLOG_TRACE2, rv, s, "md{%s}: auto_add", md->name);
+ if (APR_SUCCESS != (rv = auto_add_domains(md, s, p))) {
+ goto leave;
+ }
+ init_acme_tls_1_domains(md, s);
+ ap_log_error( APLOG_MARK, APLOG_TRACE2, rv, s, "md{%s}: check_usage", md->name);
+ if (APR_SUCCESS != (rv = check_usage(mc, md, s, p, ptemp))) {
+ goto leave;
+ }
+ ap_log_error( APLOG_MARK, APLOG_TRACE2, rv, s, "md{%s}: sync_finish", md->name);
+ if (APR_SUCCESS != (rv = md_reg_sync_finish(mc->reg, md, p, ptemp))) {
+ ap_log_error( APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10172)
+ "md[%s]: error syncing to store", md->name);
+ goto leave;
+ }
+ }
+ /*8*/
+ ap_log_error( APLOG_MARK, APLOG_TRACE2, rv, s, "init_cert_watch");
+ watched = init_cert_watch_status(mc, p, ptemp, s);
+ /*9*/
+ ap_log_error( APLOG_MARK, APLOG_TRACE2, rv, s, "cleanup challenges");
+ md_reg_cleanup_challenges(mc->reg, p, ptemp, mc->mds);
+
+ /* From here on, the domains in the registry are readonly
+ * and only staging/challenges may be manipulated */
+ md_reg_freeze_domains(mc->reg, mc->mds);
+
+ if (watched) {
+ /*10*/
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(10074)
+ "%d out of %d mds need watching", watched, mc->mds->nelts);
+
+ md_http_use_implementation(md_curl_get_impl(p));
+ rv = md_renew_start_watching(mc, s, p);
+ }
+ else {
+ ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10075) "no mds to supervise");
+ }
+
+ if (!mc->ocsp || md_ocsp_count(mc->ocsp) == 0) {
+ ap_log_error( APLOG_MARK, APLOG_TRACE1, 0, s, "no ocsp to manage");
+ goto leave;
+ }
+
+ md_http_use_implementation(md_curl_get_impl(p));
+ rv = md_ocsp_start_watching(mc, s, p);
+
+leave:
+ ap_log_error( APLOG_MARK, APLOG_TRACE2, rv, s, "post_config done");
+ return rv;
+}
+
+/**************************************************************************************************/
+/* connection context */
+
+typedef struct {
+ const char *protocol;
+} md_conn_ctx;
+
+static const char *md_protocol_get(const conn_rec *c)
+{
+ md_conn_ctx *ctx;
+
+ ctx = (md_conn_ctx*)ap_get_module_config(c->conn_config, &md_module);
+ return ctx? ctx->protocol : NULL;
+}
+
+/**************************************************************************************************/
+/* ALPN handling */
+
+static int md_protocol_propose(conn_rec *c, request_rec *r,
+ server_rec *s,
+ const apr_array_header_t *offers,
+ apr_array_header_t *proposals)
+{
+ (void)s;
+ if (!r && offers && ap_ssl_conn_is_ssl(c)
+ && ap_array_str_contains(offers, PROTO_ACME_TLS_1)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "proposing protocol '%s'", PROTO_ACME_TLS_1);
+ APR_ARRAY_PUSH(proposals, const char*) = PROTO_ACME_TLS_1;
+ return OK;
+ }
+ return DECLINED;
+}
+
+static int md_protocol_switch(conn_rec *c, request_rec *r, server_rec *s,
+ const char *protocol)
+{
+ md_conn_ctx *ctx;
+
+ (void)s;
+ if (!r && ap_ssl_conn_is_ssl(c) && !strcmp(PROTO_ACME_TLS_1, protocol)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "switching protocol '%s'", PROTO_ACME_TLS_1);
+ ctx = apr_pcalloc(c->pool, sizeof(*ctx));
+ ctx->protocol = PROTO_ACME_TLS_1;
+ ap_set_module_config(c->conn_config, &md_module, ctx);
+
+ c->keepalive = AP_CONN_CLOSE;
+ return OK;
+ }
+ return DECLINED;
+}
+
+
+/**************************************************************************************************/
+/* Access API to other httpd components */
+
+static void fallback_fnames(apr_pool_t *p, md_pkey_spec_t *kspec, char **keyfn, char **certfn )
+{
+ *keyfn = apr_pstrcat(p, "fallback-", md_pkey_filename(kspec, p), NULL);
+ *certfn = apr_pstrcat(p, "fallback-", md_chain_filename(kspec, p), NULL);
+}
+
+static apr_status_t make_fallback_cert(md_store_t *store, const md_t *md, md_pkey_spec_t *kspec,
+ server_rec *s, apr_pool_t *p, char *keyfn, char *crtfn)
+{
+ md_pkey_t *pkey;
+ md_cert_t *cert;
+ apr_status_t rv;
+
+ if (APR_SUCCESS != (rv = md_pkey_gen(&pkey, p, kspec))
+ || APR_SUCCESS != (rv = md_store_save(store, p, MD_SG_DOMAINS, md->name,
+ keyfn, MD_SV_PKEY, (void*)pkey, 0))
+ || APR_SUCCESS != (rv = md_cert_self_sign(&cert, "Apache Managed Domain Fallback",
+ md->domains, pkey, apr_time_from_sec(14 * MD_SECS_PER_DAY), p))
+ || APR_SUCCESS != (rv = md_store_save(store, p, MD_SG_DOMAINS, md->name,
+ crtfn, MD_SV_CERT, (void*)cert, 0))) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10174)
+ "%s: make fallback %s certificate", md->name, md_pkey_spec_name(kspec));
+ }
+ return rv;
+}
+
+static apr_status_t get_certificates(server_rec *s, apr_pool_t *p, int fallback,
+ apr_array_header_t **pcert_files,
+ apr_array_header_t **pkey_files)
+{
+ apr_status_t rv = APR_ENOENT;
+ md_srv_conf_t *sc;
+ md_reg_t *reg;
+ md_store_t *store;
+ const md_t *md;
+ apr_array_header_t *key_files, *chain_files;
+ const char *keyfile, *chainfile;
+ int i;
+
+ *pkey_files = *pcert_files = NULL;
+ key_files = apr_array_make(p, 5, sizeof(const char*));
+ chain_files = apr_array_make(p, 5, sizeof(const char*));
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10113)
+ "get_certificates called for vhost %s.", s->server_hostname);
+
+ sc = md_config_get(s);
+ if (!sc) {
+ ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, s,
+ "asked for certificate of server %s which has no md config",
+ s->server_hostname);
+ return APR_ENOENT;
+ }
+
+ assert(sc->mc);
+ reg = sc->mc->reg;
+ assert(reg);
+
+ sc->is_ssl = 1;
+
+ if (!sc->assigned) {
+ /* With the new hooks in mod_ssl, we are invoked for all server_rec. It is
+ * therefore normal, when we have nothing to add here. */
+ return APR_ENOENT;
+ }
+ else if (sc->assigned->nelts != 1) {
+ if (!fallback) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(10238)
+ "conflict: %d MDs match Virtualhost %s which uses SSL, however "
+ "there can be at most 1.",
+ (int)sc->assigned->nelts, s->server_hostname);
+ }
+ return APR_EINVAL;
+ }
+ md = APR_ARRAY_IDX(sc->assigned, 0, const md_t*);
+
+ if (md->cert_files && md->cert_files->nelts) {
+ apr_array_cat(chain_files, md->cert_files);
+ apr_array_cat(key_files, md->pkey_files);
+ rv = APR_SUCCESS;
+ }
+ else {
+ md_pkey_spec_t *spec;
+
+ for (i = 0; i < md_cert_count(md); ++i) {
+ spec = md_pkeys_spec_get(md->pks, i);
+ rv = md_reg_get_cred_files(&keyfile, &chainfile, reg, MD_SG_DOMAINS, md, spec, p);
+ if (APR_SUCCESS == rv) {
+ APR_ARRAY_PUSH(key_files, const char*) = keyfile;
+ APR_ARRAY_PUSH(chain_files, const char*) = chainfile;
+ }
+ else if (APR_STATUS_IS_ENOENT(rv)) {
+ /* certificate for this pkey is not available, others might
+ * if pkeys have been added for a running mdomain.
+ * see issue #260 */
+ rv = APR_SUCCESS;
+ }
+ else if (!APR_STATUS_IS_ENOENT(rv)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10110)
+ "retrieving credentials for MD %s (%s)",
+ md->name, md_pkey_spec_name(spec));
+ return rv;
+ }
+ }
+
+ if (md_array_is_empty(key_files)) {
+ if (fallback) {
+ /* Provide temporary, self-signed certificate as fallback, so that
+ * clients do not get obscure TLS handshake errors or will see a fallback
+ * virtual host that is not intended to be served here. */
+ char *kfn, *cfn;
+
+ store = md_reg_store_get(reg);
+ assert(store);
+
+ for (i = 0; i < md_cert_count(md); ++i) {
+ spec = md_pkeys_spec_get(md->pks, i);
+ fallback_fnames(p, spec, &kfn, &cfn);
+
+ md_store_get_fname(&keyfile, store, MD_SG_DOMAINS, md->name, kfn, p);
+ md_store_get_fname(&chainfile, store, MD_SG_DOMAINS, md->name, cfn, p);
+ if (!md_file_exists(keyfile, p) || !md_file_exists(chainfile, p)) {
+ if (APR_SUCCESS != (rv = make_fallback_cert(store, md, spec, s, p, kfn, cfn))) {
+ return rv;
+ }
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10116)
+ "%s: providing %s fallback certificate for server %s",
+ md->name, md_pkey_spec_name(spec), s->server_hostname);
+ APR_ARRAY_PUSH(key_files, const char*) = keyfile;
+ APR_ARRAY_PUSH(chain_files, const char*) = chainfile;
+ }
+ rv = APR_EAGAIN;
+ goto leave;
+ }
+ }
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(10077)
+ "%s[state=%d]: providing certificates for server %s",
+ md->name, md->state, s->server_hostname);
+leave:
+ if (!md_array_is_empty(key_files) && !md_array_is_empty(chain_files)) {
+ *pkey_files = key_files;
+ *pcert_files = chain_files;
+ }
+ else if (APR_SUCCESS == rv) {
+ rv = APR_ENOENT;
+ }
+ return rv;
+}
+
+static int md_add_cert_files(server_rec *s, apr_pool_t *p,
+ apr_array_header_t *cert_files,
+ apr_array_header_t *key_files)
+{
+ apr_array_header_t *md_cert_files;
+ apr_array_header_t *md_key_files;
+ apr_status_t rv;
+
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, "hook ssl_add_cert_files for %s",
+ s->server_hostname);
+ rv = get_certificates(s, p, 0, &md_cert_files, &md_key_files);
+ if (APR_SUCCESS == rv) {
+ if (!apr_is_empty_array(cert_files)) {
+ /* downgraded fromm WARNING to DEBUG, since installing separate certificates
+ * may be a valid use case. */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10084)
+ "host '%s' is covered by a Managed Domain, but "
+ "certificate/key files are already configured "
+ "for it (most likely via SSLCertificateFile).",
+ s->server_hostname);
+ }
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s,
+ "host '%s' is covered by a Managed Domaina and "
+ "is being provided with %d key/certificate files.",
+ s->server_hostname, md_cert_files->nelts);
+ apr_array_cat(cert_files, md_cert_files);
+ apr_array_cat(key_files, md_key_files);
+ return DONE;
+ }
+ return DECLINED;
+}
+
+static int md_add_fallback_cert_files(server_rec *s, apr_pool_t *p,
+ apr_array_header_t *cert_files,
+ apr_array_header_t *key_files)
+{
+ apr_array_header_t *md_cert_files;
+ apr_array_header_t *md_key_files;
+ apr_status_t rv;
+
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, "hook ssl_add_fallback_cert_files for %s",
+ s->server_hostname);
+ rv = get_certificates(s, p, 1, &md_cert_files, &md_key_files);
+ if (APR_EAGAIN == rv) {
+ apr_array_cat(cert_files, md_cert_files);
+ apr_array_cat(key_files, md_key_files);
+ return DONE;
+ }
+ return DECLINED;
+}
+
+static int md_answer_challenge(conn_rec *c, const char *servername,
+ const char **pcert_pem, const char **pkey_pem)
+{
+ const char *protocol;
+ int hook_rv = DECLINED;
+ apr_status_t rv = APR_ENOENT;
+ md_srv_conf_t *sc;
+ md_store_t *store;
+ char *cert_name, *pkey_name;
+ const char *cert_pem, *key_pem;
+ int i;
+
+ if (!servername
+ || !(protocol = md_protocol_get(c))
+ || strcmp(PROTO_ACME_TLS_1, protocol)) {
+ goto cleanup;
+ }
+ sc = md_config_get(c->base_server);
+ if (!sc || !sc->mc->reg) goto cleanup;
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "Answer challenge[tls-alpn-01] for %s", servername);
+ store = md_reg_store_get(sc->mc->reg);
+
+ for (i = 0; i < md_pkeys_spec_count( sc->pks ); i++) {
+ tls_alpn01_fnames(c->pool, md_pkeys_spec_get(sc->pks,i),
+ &pkey_name, &cert_name);
+
+ rv = md_store_load(store, MD_SG_CHALLENGES, servername, cert_name, MD_SV_TEXT,
+ (void**)&cert_pem, c->pool);
+ if (APR_STATUS_IS_ENOENT(rv)) continue;
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ rv = md_store_load(store, MD_SG_CHALLENGES, servername, pkey_name, MD_SV_TEXT,
+ (void**)&key_pem, c->pool);
+ if (APR_STATUS_IS_ENOENT(rv)) continue;
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "Found challenge cert %s, key %s for %s",
+ cert_name, pkey_name, servername);
+ *pcert_pem = cert_pem;
+ *pkey_pem = key_pem;
+ hook_rv = OK;
+ break;
+ }
+
+ if (DECLINED == hook_rv) {
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, rv, c, APLOGNO(10080)
+ "%s: unknown tls-alpn-01 challenge host", servername);
+ }
+
+cleanup:
+ return hook_rv;
+}
+
+
+/**************************************************************************************************/
+/* ACME 'http-01' challenge responses */
+
+#define WELL_KNOWN_PREFIX "/.well-known/"
+#define ACME_CHALLENGE_PREFIX WELL_KNOWN_PREFIX"acme-challenge/"
+
+static int md_http_challenge_pr(request_rec *r)
+{
+ apr_bucket_brigade *bb;
+ const md_srv_conf_t *sc;
+ const char *name, *data;
+ md_reg_t *reg;
+ const md_t *md;
+ apr_status_t rv;
+
+ if (r->parsed_uri.path
+ && !strncmp(ACME_CHALLENGE_PREFIX, r->parsed_uri.path, sizeof(ACME_CHALLENGE_PREFIX)-1)) {
+ sc = ap_get_module_config(r->server->module_config, &md_module);
+ if (sc && sc->mc) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "access inside /.well-known/acme-challenge for %s%s",
+ r->hostname, r->parsed_uri.path);
+ md = md_get_by_domain(sc->mc->mds, r->hostname);
+ name = r->parsed_uri.path + sizeof(ACME_CHALLENGE_PREFIX)-1;
+ reg = sc && sc->mc? sc->mc->reg : NULL;
+
+ if (md && md->ca_challenges
+ && md_array_str_index(md->ca_challenges, MD_AUTHZ_CHA_HTTP_01, 0, 1) < 0) {
+ /* The MD this challenge is for does not allow http-01 challanges,
+ * we have to decline. See #279 for a setup example where this
+ * is necessary.
+ */
+ return DECLINED;
+ }
+
+ if (strlen(name) && !ap_strchr_c(name, '/') && reg) {
+ md_store_t *store = md_reg_store_get(reg);
+
+ rv = md_store_load(store, MD_SG_CHALLENGES, r->hostname,
+ MD_FN_HTTP01, MD_SV_TEXT, (void**)&data, r->pool);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
+ "loading challenge for %s (%s)", r->hostname, r->uri);
+ if (APR_SUCCESS == rv) {
+ apr_size_t len = strlen(data);
+
+ if (r->method_number != M_GET) {
+ return HTTP_NOT_IMPLEMENTED;
+ }
+ /* A GET on a challenge resource for a hostname we are
+ * configured for. Let's send the content back */
+ r->status = HTTP_OK;
+ apr_table_setn(r->headers_out, "Content-Length", apr_ltoa(r->pool, (long)len));
+
+ bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+ apr_brigade_write(bb, NULL, NULL, data, len);
+ ap_pass_brigade(r->output_filters, bb);
+ apr_brigade_cleanup(bb);
+
+ return DONE;
+ }
+ else if (!md || md->renew_mode == MD_RENEW_MANUAL
+ || (md->cert_files && md->cert_files->nelts
+ && md->renew_mode == MD_RENEW_AUTO)) {
+ /* The request hostname is not for a domain - or at least not for
+ * a domain that we renew ourselves. We are not
+ * the sole authority here for /.well-known/acme-challenge (see PR62189).
+ * So, we decline to handle this and give others a chance to provide
+ * the answer.
+ */
+ return DECLINED;
+ }
+ else if (APR_STATUS_IS_ENOENT(rv)) {
+ return HTTP_NOT_FOUND;
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10081)
+ "loading challenge %s from store", name);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+ }
+ return DECLINED;
+}
+
+/**************************************************************************************************/
+/* Require Https hook */
+
+static int md_require_https_maybe(request_rec *r)
+{
+ const md_srv_conf_t *sc;
+ apr_uri_t uri;
+ const char *s, *host;
+ const md_t *md;
+ int status;
+
+ /* Requests outside the /.well-known path are subject to possible
+ * https: redirects or HSTS header additions.
+ */
+ sc = ap_get_module_config(r->server->module_config, &md_module);
+ if (!sc || !sc->assigned || !sc->assigned->nelts || !r->parsed_uri.path
+ || !strncmp(WELL_KNOWN_PREFIX, r->parsed_uri.path, sizeof(WELL_KNOWN_PREFIX)-1)) {
+ goto declined;
+ }
+
+ host = ap_get_server_name_for_url(r);
+ md = md_get_for_domain(r->server, host);
+ if (!md) goto declined;
+
+ if (ap_ssl_conn_is_ssl(r->connection)) {
+ /* Using https:
+ * if 'permanent' and no one else set a HSTS header already, do it */
+ if (md->require_https == MD_REQUIRE_PERMANENT
+ && sc->mc->hsts_header && !apr_table_get(r->headers_out, MD_HSTS_HEADER)) {
+ apr_table_setn(r->headers_out, MD_HSTS_HEADER, sc->mc->hsts_header);
+ }
+ }
+ else {
+ if (md->require_https > MD_REQUIRE_OFF) {
+ /* Not using https:, but require it. Redirect. */
+ if (r->method_number == M_GET) {
+ /* safe to use the old-fashioned codes */
+ status = ((MD_REQUIRE_PERMANENT == md->require_https)?
+ HTTP_MOVED_PERMANENTLY : HTTP_MOVED_TEMPORARILY);
+ }
+ else {
+ /* these should keep the method unchanged on retry */
+ status = ((MD_REQUIRE_PERMANENT == md->require_https)?
+ HTTP_PERMANENT_REDIRECT : HTTP_TEMPORARY_REDIRECT);
+ }
+
+ s = ap_construct_url(r->pool, r->uri, r);
+ if (APR_SUCCESS == apr_uri_parse(r->pool, s, &uri)) {
+ uri.scheme = (char*)"https";
+ uri.port = 443;
+ uri.port_str = (char*)"443";
+ uri.query = r->parsed_uri.query;
+ uri.fragment = r->parsed_uri.fragment;
+ s = apr_uri_unparse(r->pool, &uri, APR_URI_UNP_OMITUSERINFO);
+ if (s && *s) {
+ apr_table_setn(r->headers_out, "Location", s);
+ return status;
+ }
+ }
+ }
+ }
+declined:
+ return DECLINED;
+}
+
+/* Runs once per created child process. Perform any process
+ * related initialization here.
+ */
+static void md_child_init(apr_pool_t *pool, server_rec *s)
+{
+ (void)pool;
+ (void)s;
+}
+
+/* Install this module into the apache2 infrastructure.
+ */
+static void md_hooks(apr_pool_t *pool)
+{
+ static const char *const mod_ssl[] = { "mod_ssl.c", "mod_tls.c", NULL};
+ static const char *const mod_wd[] = { "mod_watchdog.c", NULL};
+
+ /* Leave the ssl initialization to mod_ssl or friends. */
+ md_acme_init(pool, AP_SERVER_BASEVERSION, 0);
+
+ ap_log_perror(APLOG_MARK, APLOG_TRACE1, 0, pool, "installing hooks");
+
+ /* Run once after configuration is set, before mod_ssl.
+ * Run again after mod_ssl is done.
+ */
+ ap_hook_post_config(md_post_config_before_ssl, NULL, mod_ssl, APR_HOOK_MIDDLE);
+ ap_hook_post_config(md_post_config_after_ssl, mod_ssl, mod_wd, APR_HOOK_LAST);
+
+ /* Run once after a child process has been created.
+ */
+ ap_hook_child_init(md_child_init, NULL, mod_ssl, APR_HOOK_MIDDLE);
+
+ /* answer challenges *very* early, before any configured authentication may strike */
+ ap_hook_post_read_request(md_require_https_maybe, mod_ssl, NULL, APR_HOOK_MIDDLE);
+ ap_hook_post_read_request(md_http_challenge_pr, NULL, NULL, APR_HOOK_MIDDLE);
+
+ ap_hook_protocol_propose(md_protocol_propose, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_protocol_switch(md_protocol_switch, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_protocol_get(md_protocol_get, NULL, NULL, APR_HOOK_MIDDLE);
+
+ /* Status request handlers and contributors */
+ ap_hook_post_read_request(md_http_cert_status, NULL, mod_ssl, APR_HOOK_MIDDLE);
+ APR_OPTIONAL_HOOK(ap, status_hook, md_domains_status_hook, NULL, NULL, APR_HOOK_MIDDLE);
+ APR_OPTIONAL_HOOK(ap, status_hook, md_ocsp_status_hook, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_handler(md_status_handler, NULL, NULL, APR_HOOK_MIDDLE);
+
+ ap_hook_ssl_answer_challenge(md_answer_challenge, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_ssl_add_cert_files(md_add_cert_files, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_ssl_add_fallback_cert_files(md_add_fallback_cert_files, NULL, NULL, APR_HOOK_MIDDLE);
+
+#if AP_MODULE_MAGIC_AT_LEAST(20120211, 105)
+ ap_hook_ssl_ocsp_prime_hook(md_ocsp_prime_status, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_ssl_ocsp_get_resp_hook(md_ocsp_provide_status, NULL, NULL, APR_HOOK_MIDDLE);
+#else
+#error "This version of mod_md requires Apache httpd 2.4.48 or newer."
+#endif /* AP_MODULE_MAGIC_AT_LEAST() */
+}
+
diff --git a/modules/md/mod_md.dep b/modules/md/mod_md.dep
new file mode 100644
index 0000000..0cbd691
--- /dev/null
+++ b/modules/md/mod_md.dep
@@ -0,0 +1,5 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_md.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
diff --git a/modules/md/mod_md.dsp b/modules/md/mod_md.dsp
new file mode 100644
index 0000000..d99fb1c
--- /dev/null
+++ b/modules/md/mod_md.dsp
@@ -0,0 +1,223 @@
+# Microsoft Developer Studio Project File - Name="mod_md" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_md - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_md.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_md.mak" CFG="mod_md - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_md - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_md - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_md - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "ssize_t=long" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../server/mpm/winnt" "/I ../ssl" /I "../../include" /I "../generators" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/openssl/inc32" /I "../../srclib/jansson/include" /I "../../srclib/curl/include" /I "../core" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "ssize_t=long" /Fd"Release\mod_md_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_md.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d "BIN_NAME=mod_md.so" /d "LONG_NAME=Letsencrypt module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_md.so" /base:@..\..\os\win32\BaseAddr.ref,mod_md.so
+# ADD LINK32 kernel32.lib libhttpd.lib libapr-1.lib libaprutil-1.lib ssleay32.lib libeay32.lib jansson.lib libcurl.lib /libpath:"../../srclib/apr/Release" /libpath:"../../srclib/apr-util/Release" /libpath:"../../Release/" /libpath:"../../srclib/openssl/out32dll" /libpath:"../../srclib/jansson/lib" /libpath:"../../srclib/curl/lib" /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_md.so" /base:@..\..\os\win32\BaseAddr.ref,mod_md.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_md.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_md - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "ssize_t=long" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../ssl" /I "../../include" /I "../../srclib/apr/include" /I "../generators" /I "../../srclib/apr-util/include" /I "../../srclib/openssl/inc32" /I "../../srclib/jansson/include" /I "../../srclib/curl/include" /I "../core" /src" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "ssize_t=long" /Fd"Debug\mod_md_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_md.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d "BIN_NAME=mod_md.so" /d "LONG_NAME=md_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_md.so" /base:@..\..\os\win32\BaseAddr.ref,mod_md.so
+# ADD LINK32 kernel32.lib libhttpd.lib libapr-1.lib libaprutil-1.lib ssleay32.lib libeay32.lib jansson_d.lib libcurl_debug.lib /nologo /subsystem:windows /dll /libpath:"../../srclib/openssl/out32dll" /libpath:"../../srclib/jansson/lib" /libpath:"../../srclib/curl/lib" /incremental:no /debug /out:".\Debug\mod_md.so" /base:@..\..\os\win32\BaseAddr.ref,mod_md.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_md.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_md - Win32 Release"
+# Name "mod_md - Win32 Debug"
+# Begin Source File
+
+SOURCE=./mod_md.c
+# End Source File
+# Begin Source File
+
+SOURCE=./mod_md_config.c
+# End Source File
+# Begin Source File
+
+SOURCE=./mod_md_drive.c
+# End Source File
+# Begin Source File
+
+SOURCE=./mod_md_ocsp.c
+# End Source File
+# Begin Source File
+
+SOURCE=./mod_md_os.c
+# End Source File
+# Begin Source File
+
+SOURCE=./mod_md_status.c
+# End Source File
+# Begin Source File
+
+SOURCE=./md_acme.c
+# End Source File
+# Begin Source File
+
+SOURCE=./md_acme_acct.c
+# End Source File
+# Begin Source File
+
+SOURCE=./md_acme_authz.c
+# End Source File
+# Begin Source File
+
+SOURCE=./md_acme_drive.c
+# End Source File
+# Begin Source File
+
+SOURCE=./md_acme_order.c
+# End Source File
+# Begin Source File
+
+SOURCE=./md_acmev2_drive.c
+# End Source File
+# Begin Source File
+
+SOURCE=./md_core.c
+# End Source File
+# Begin Source File
+
+SOURCE=./md_crypt.c
+# End Source File
+# Begin Source File
+
+SOURCE=./md_curl.c
+# End Source File
+# Begin Source File
+
+SOURCE=./md_http.c
+# End Source File
+# Begin Source File
+
+SOURCE=./md_event.c
+# End Source File
+# Begin Source File
+
+SOURCE=./md_json.c
+# End Source File
+# Begin Source File
+
+SOURCE=./md_jws.c
+# End Source File
+# Begin Source File
+
+SOURCE=./md_log.c
+# End Source File
+# Begin Source File
+
+SOURCE=./md_ocsp.c
+# End Source File
+# Begin Source File
+
+SOURCE=./md_reg.c
+# End Source File
+# Begin Source File
+
+SOURCE=./md_result.c
+# End Source File
+# Begin Source File
+
+SOURCE=./md_status.c
+# End Source File
+# Begin Source File
+
+SOURCE=./md_store.c
+# End Source File
+# Begin Source File
+
+SOURCE=./md_store_fs.c
+# End Source File
+# Begin Source File
+
+SOURCE=./md_tailscale.c
+# End Source File
+# Begin Source File
+
+SOURCE=./md_time.c
+# End Source File
+# Begin Source File
+
+SOURCE=./md_util.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/md/mod_md.h b/modules/md/mod_md.h
new file mode 100644
index 0000000..805737d
--- /dev/null
+++ b/modules/md/mod_md.h
@@ -0,0 +1,20 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mod_md_mod_md_h
+#define mod_md_mod_md_h
+
+#endif /* mod_md_mod_md_h */
diff --git a/modules/md/mod_md.mak b/modules/md/mod_md.mak
new file mode 100644
index 0000000..9779e6b
--- /dev/null
+++ b/modules/md/mod_md.mak
@@ -0,0 +1,618 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_md.dsp
+!IF "$(CFG)" == ""
+CFG=mod_md - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_md - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_md - Win32 Release" && "$(CFG)" != "mod_md - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_md.mak" CFG="mod_md - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_md - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_md - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(_HAVE_OSSL110)" == "1"
+SSLCRP=libcrypto
+SSLLIB=libssl
+SSLINC=/I ../../srclib/openssl/include
+SSLBIN=/libpath:../../srclib/openssl
+!ELSE
+SSLCRP=libeay32
+SSLLIB=ssleay32
+SSLINC=/I ../../srclib/openssl/inc32
+SSLBIN=/libpath:../../srclib/openssl/out32dll
+!ENDIF
+
+!IF "$(CFG)" == "mod_md - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_md.so"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_md.so"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\md_acme.obj"
+ -@erase "$(INTDIR)\md_acme_acct.obj"
+ -@erase "$(INTDIR)\md_acme_authz.obj"
+ -@erase "$(INTDIR)\md_acme_drive.obj"
+ -@erase "$(INTDIR)\md_acme_order.obj"
+ -@erase "$(INTDIR)\md_acmev2_drive.obj"
+ -@erase "$(INTDIR)\md_core.obj"
+ -@erase "$(INTDIR)\md_crypt.obj"
+ -@erase "$(INTDIR)\md_curl.obj"
+ -@erase "$(INTDIR)\md_event.obj"
+ -@erase "$(INTDIR)\md_http.obj"
+ -@erase "$(INTDIR)\md_json.obj"
+ -@erase "$(INTDIR)\md_jws.obj"
+ -@erase "$(INTDIR)\md_log.obj"
+ -@erase "$(INTDIR)\md_ocsp.obj"
+ -@erase "$(INTDIR)\md_reg.obj"
+ -@erase "$(INTDIR)\md_result.obj"
+ -@erase "$(INTDIR)\md_status.obj"
+ -@erase "$(INTDIR)\md_store.obj"
+ -@erase "$(INTDIR)\md_store_fs.obj"
+ -@erase "$(INTDIR)\md_tailscale.obj"
+ -@erase "$(INTDIR)\md_time.obj"
+ -@erase "$(INTDIR)\md_util.obj"
+ -@erase "$(INTDIR)\mod_md.obj"
+ -@erase "$(INTDIR)\mod_md.res"
+ -@erase "$(INTDIR)\mod_md_config.obj"
+ -@erase "$(INTDIR)\mod_md_drive.obj"
+ -@erase "$(INTDIR)\mod_md_status.obj"
+ -@erase "$(INTDIR)\mod_md_ocsp.obj"
+ -@erase "$(INTDIR)\mod_md_os.obj"
+ -@erase "$(INTDIR)\mod_md_src.idb"
+ -@erase "$(INTDIR)\mod_md_src.pdb"
+ -@erase "$(OUTDIR)\mod_md.exp"
+ -@erase "$(OUTDIR)\mod_md.lib"
+ -@erase "$(OUTDIR)\mod_md.pdb"
+ -@erase "$(OUTDIR)\mod_md.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../server/mpm/winnt" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" $(SSLINC) /I "../../srclib/jansson/include" /I "../../srclib/curl/include" /I "../ssl" /I "../core" /I "../generators" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D ssize_t=long /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_md_src" /FD /I " ../ssl" /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_md.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME=mod_md.so /d LONG_NAME=Letsencrypt module for Apache
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_md.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib libhttpd.lib libapr-1.lib libaprutil-1.lib $(SSLCRP).lib $(SSLLIB).lib jansson.lib libcurl.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_md.pdb" /debug /out:"$(OUTDIR)\mod_md.so" /implib:"$(OUTDIR)\mod_md.lib" /libpath:"../../srclib/apr/Release" /libpath:"../../srclib/apr-util/Release" /libpath:"../../Release/" $(SSLBIN) /libpath:"../../srclib/jansson/lib" /libpath:"../../srclib/curl/lib" /base:@..\..\os\win32\BaseAddr.ref,mod_md.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_md.obj" \
+ "$(INTDIR)\mod_md_config.obj" \
+ "$(INTDIR)\mod_md_drive.obj" \
+ "$(INTDIR)\mod_md_ocsp.obj" \
+ "$(INTDIR)\mod_md_os.obj" \
+ "$(INTDIR)\mod_md_status.obj" \
+ "$(INTDIR)\md_core.obj" \
+ "$(INTDIR)\md_crypt.obj" \
+ "$(INTDIR)\md_curl.obj" \
+ "$(INTDIR)\md_event.obj" \
+ "$(INTDIR)\md_http.obj" \
+ "$(INTDIR)\md_json.obj" \
+ "$(INTDIR)\md_jws.obj" \
+ "$(INTDIR)\md_log.obj" \
+ "$(INTDIR)\md_ocsp.obj" \
+ "$(INTDIR)\md_reg.obj" \
+ "$(INTDIR)\md_result.obj" \
+ "$(INTDIR)\md_status.obj" \
+ "$(INTDIR)\md_store.obj" \
+ "$(INTDIR)\md_store_fs.obj" \
+ "$(INTDIR)\md_tailscale.obj" \
+ "$(INTDIR)\md_time.obj" \
+ "$(INTDIR)\md_util.obj" \
+ "$(INTDIR)\md_acme.obj" \
+ "$(INTDIR)\md_acme_acct.obj" \
+ "$(INTDIR)\md_acme_authz.obj" \
+ "$(INTDIR)\md_acme_drive.obj" \
+ "$(INTDIR)\md_acme_order.obj" \
+ "$(INTDIR)\md_acmev2_drive.obj" \
+ "$(INTDIR)\mod_md.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_md.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_md.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+ALL : $(DS_POSTBUILD_DEP)
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+$(DS_POSTBUILD_DEP) : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_md.so"
+ if exist .\Release\mod_md.so.manifest mt.exe -manifest .\Release\mod_md.so.manifest -outputresource:.\Release\mod_md.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_md - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_md.so"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_md.so"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\md_acme.obj"
+ -@erase "$(INTDIR)\md_acme_acct.obj"
+ -@erase "$(INTDIR)\md_acme_authz.obj"
+ -@erase "$(INTDIR)\md_acme_drive.obj"
+ -@erase "$(INTDIR)\md_acme_order.obj"
+ -@erase "$(INTDIR)\md_acmev2_drive.obj"
+ -@erase "$(INTDIR)\md_core.obj"
+ -@erase "$(INTDIR)\md_crypt.obj"
+ -@erase "$(INTDIR)\md_curl.obj"
+ -@erase "$(INTDIR)\md_event.obj"
+ -@erase "$(INTDIR)\md_http.obj"
+ -@erase "$(INTDIR)\md_json.obj"
+ -@erase "$(INTDIR)\md_jws.obj"
+ -@erase "$(INTDIR)\md_log.obj"
+ -@erase "$(INTDIR)\md_ocsp.obj"
+ -@erase "$(INTDIR)\md_reg.obj"
+ -@erase "$(INTDIR)\md_result.obj"
+ -@erase "$(INTDIR)\md_status.obj"
+ -@erase "$(INTDIR)\md_store.obj"
+ -@erase "$(INTDIR)\md_store_fs.obj"
+ -@erase "$(INTDIR)\md_tailscale.obj"
+ -@erase "$(INTDIR)\md_time.obj"
+ -@erase "$(INTDIR)\md_util.obj"
+ -@erase "$(INTDIR)\mod_md.obj"
+ -@erase "$(INTDIR)\mod_md.res"
+ -@erase "$(INTDIR)\mod_md_config.obj"
+ -@erase "$(INTDIR)\mod_md_drive.obj"
+ -@erase "$(INTDIR)\mod_md_status.obj"
+ -@erase "$(INTDIR)\mod_md_ocsp.obj"
+ -@erase "$(INTDIR)\mod_md_os.obj"
+ -@erase "$(INTDIR)\mod_md_src.idb"
+ -@erase "$(INTDIR)\mod_md_src.pdb"
+ -@erase "$(OUTDIR)\mod_md.exp"
+ -@erase "$(OUTDIR)\mod_md.lib"
+ -@erase "$(OUTDIR)\mod_md.pdb"
+ -@erase "$(OUTDIR)\mod_md.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" $(SSLINC) /I "../../srclib/jansson/include" /I "../../srclib/curl/include" /I "../core" /I "../generators" /I "../ssl" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D ssize_t=long /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_md_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_md.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME=mod_md.so /d LONG_NAME=http2_module for Apache
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_md.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib libhttpd.lib libapr-1.lib libaprutil-1.lib $(SSLCRP).lib $(SSLLIB).lib jansson_d.lib libcurl_debug.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_md.pdb" /debug /out:"$(OUTDIR)\mod_md.so" /implib:"$(OUTDIR)\mod_md.lib" $(SSLBIN) /libpath:"../../srclib/jansson/lib" /libpath:"../../srclib/curl/lib" /base:@..\..\os\win32\BaseAddr.ref,mod_md.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_md.obj" \
+ "$(INTDIR)\mod_md_config.obj" \
+ "$(INTDIR)\mod_md_drive.obj" \
+ "$(INTDIR)\mod_md_ocsp.obj" \
+ "$(INTDIR)\mod_md_os.obj" \
+ "$(INTDIR)\mod_md_status.obj" \
+ "$(INTDIR)\md_core.obj" \
+ "$(INTDIR)\md_crypt.obj" \
+ "$(INTDIR)\md_curl.obj" \
+ "$(INTDIR)\md_event.obj" \
+ "$(INTDIR)\md_http.obj" \
+ "$(INTDIR)\md_json.obj" \
+ "$(INTDIR)\md_jws.obj" \
+ "$(INTDIR)\md_log.obj" \
+ "$(INTDIR)\md_ocsp.obj" \
+ "$(INTDIR)\md_reg.obj" \
+ "$(INTDIR)\md_result.obj" \
+ "$(INTDIR)\md_status.obj" \
+ "$(INTDIR)\md_store.obj" \
+ "$(INTDIR)\md_store_fs.obj" \
+ "$(INTDIR)\md_tailscale.obj" \
+ "$(INTDIR)\md_time.obj" \
+ "$(INTDIR)\md_util.obj" \
+ "$(INTDIR)\md_acme.obj" \
+ "$(INTDIR)\md_acme_acct.obj" \
+ "$(INTDIR)\md_acme_authz.obj" \
+ "$(INTDIR)\md_acme_drive.obj" \
+ "$(INTDIR)\md_acme_order.obj" \
+ "$(INTDIR)\md_acmev2_drive.obj" \
+ "$(INTDIR)\mod_md.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_md.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_md.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+ALL : $(DS_POSTBUILD_DEP)
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+$(DS_POSTBUILD_DEP) : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_md.so"
+ if exist .\Debug\mod_md.so.manifest mt.exe -manifest .\Debug\mod_md.so.manifest -outputresource:.\Debug\mod_md.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_md.dep")
+!INCLUDE "mod_md.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_md.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_md - Win32 Release" || "$(CFG)" == "mod_md - Win32 Debug"
+
+!IF "$(CFG)" == "mod_md - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd "..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\md"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd "..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\md"
+
+!ELSEIF "$(CFG)" == "mod_md - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd "..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\md"
+
+"libapr - Win32 DebugCLEAN" :
+ cd "..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\md"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_md - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd "..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\md"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd "..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\md"
+
+!ELSEIF "$(CFG)" == "mod_md - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd "..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\md"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd "..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\md"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_md - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd "..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\md"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd "..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\md"
+
+!ELSEIF "$(CFG)" == "mod_md - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd "..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\md"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd "..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\md"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_md - Win32 Release"
+
+
+"$(INTDIR)\mod_md.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_md.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_md.so" /d LONG_NAME="md_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_md - Win32 Debug"
+
+
+"$(INTDIR)\mod_md.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_md.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_md.so" /d LONG_NAME="md_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=./md_acme.c
+
+"$(INTDIR)\md_acme.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./md_acme_acct.c
+
+"$(INTDIR)\md_acme_acct.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./md_acme_authz.c
+
+"$(INTDIR)\md_acme_authz.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./md_acme_drive.c
+
+"$(INTDIR)\md_acme_drive.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./md_acme_order.c
+
+"$(INTDIR)\md_acme_order.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./md_acmev2_drive.c
+
+"$(INTDIR)\md_acmev2_drive.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./md_core.c
+
+"$(INTDIR)\md_core.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./md_crypt.c
+
+"$(INTDIR)\md_crypt.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./md_curl.c
+
+"$(INTDIR)\md_curl.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./md_event.c
+
+"$(INTDIR)\md_event.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./md_http.c
+
+"$(INTDIR)\md_http.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./md_json.c
+
+"$(INTDIR)\md_json.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./md_jws.c
+
+"$(INTDIR)\md_jws.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./md_log.c
+
+"$(INTDIR)\md_log.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./md_ocsp.c
+
+"$(INTDIR)\md_ocsp.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./md_reg.c
+
+"$(INTDIR)\md_reg.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./md_result.c
+
+"$(INTDIR)\md_result.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./md_status.c
+
+"$(INTDIR)\md_status.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./md_store.c
+
+"$(INTDIR)\md_store.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./md_store_fs.c
+
+"$(INTDIR)\md_store_fs.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./md_tailscale.c
+
+"$(INTDIR)\md_tailscale.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./md_time.c
+
+"$(INTDIR)\md_time.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./md_util.c
+
+"$(INTDIR)\md_util.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./mod_md.c
+
+"$(INTDIR)\mod_md.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./mod_md_config.c
+
+"$(INTDIR)\mod_md_config.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./mod_md_drive.c
+
+"$(INTDIR)\mod_md_drive.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./mod_md_ocsp.c
+
+"$(INTDIR)\mod_md_ocsp.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./mod_md_os.c
+
+"$(INTDIR)\mod_md_os.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=./mod_md_status.c
+
+"$(INTDIR)\mod_md_status.obj" : $(SOURCE) "$(INTDIR)"
+
+
+!ENDIF
+
diff --git a/modules/md/mod_md_config.c b/modules/md/mod_md_config.c
new file mode 100644
index 0000000..e117b16
--- /dev/null
+++ b/modules/md/mod_md_config.c
@@ -0,0 +1,1388 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+
+#include <apr_lib.h>
+#include <apr_strings.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_config.h>
+#include <http_log.h>
+#include <http_vhost.h>
+
+#include "md.h"
+#include "md_acme.h"
+#include "md_crypt.h"
+#include "md_log.h"
+#include "md_json.h"
+#include "md_util.h"
+#include "mod_md_private.h"
+#include "mod_md_config.h"
+
+#define MD_CMD_MD_SECTION "<MDomainSet"
+#define MD_CMD_MD2_SECTION "<MDomain"
+
+#define DEF_VAL (-1)
+
+#ifndef MD_DEFAULT_BASE_DIR
+#define MD_DEFAULT_BASE_DIR "md"
+#endif
+
+static md_timeslice_t def_ocsp_keep_window = {
+ 0,
+ MD_TIME_OCSP_KEEP_NORM,
+};
+
+static md_timeslice_t def_ocsp_renew_window = {
+ MD_TIME_LIFE_NORM,
+ MD_TIME_RENEW_WINDOW_DEF,
+};
+
+/* Default settings for the global conf */
+static md_mod_conf_t defmc = {
+ NULL, /* list of mds */
+#if AP_MODULE_MAGIC_AT_LEAST(20180906, 2)
+ NULL, /* base dirm by default state-dir-relative */
+#else
+ MD_DEFAULT_BASE_DIR,
+#endif
+ NULL, /* proxy url for outgoing http */
+ NULL, /* md_reg_t */
+ NULL, /* md_ocsp_reg_t */
+ 80, /* local http: port */
+ 443, /* local https: port */
+ -1, /* can http: */
+ -1, /* can https: */
+ 0, /* manage base server */
+ MD_HSTS_MAX_AGE_DEFAULT, /* hsts max-age */
+ NULL, /* hsts headers */
+ NULL, /* unused names */
+ NULL, /* init errors hash */
+ NULL, /* notify cmd */
+ NULL, /* message cmd */
+ NULL, /* env table */
+ 0, /* dry_run flag */
+ 1, /* server_status_enabled */
+ 1, /* certificate_status_enabled */
+ &def_ocsp_keep_window, /* default time to keep ocsp responses */
+ &def_ocsp_renew_window, /* default time to renew ocsp responses */
+ "crt.sh", /* default cert checker site name */
+ "https://crt.sh?q=", /* default cert checker site url */
+ NULL, /* CA cert file to use */
+ apr_time_from_sec(5), /* minimum delay for retries */
+ 13, /* retry_failover after 14 errors, with 5s delay ~ half a day */
+ 0, /* store locks, disabled by default */
+ apr_time_from_sec(5), /* max time to wait to obaint a store lock */
+};
+
+static md_timeslice_t def_renew_window = {
+ MD_TIME_LIFE_NORM,
+ MD_TIME_RENEW_WINDOW_DEF,
+};
+static md_timeslice_t def_warn_window = {
+ MD_TIME_LIFE_NORM,
+ MD_TIME_WARN_WINDOW_DEF,
+};
+
+/* Default server specific setting */
+static md_srv_conf_t defconf = {
+ "default", /* name */
+ NULL, /* server_rec */
+ &defmc, /* mc */
+ 1, /* transitive */
+ MD_REQUIRE_OFF, /* require https */
+ MD_RENEW_AUTO, /* renew mode */
+ 0, /* must staple */
+ NULL, /* pkey spec */
+ &def_renew_window, /* renew window */
+ &def_warn_window, /* warn window */
+ NULL, /* ca urls */
+ NULL, /* ca contact (email) */
+ MD_PROTO_ACME, /* ca protocol */
+ NULL, /* ca agreemnent */
+ NULL, /* ca challenges array */
+ NULL, /* ca eab kid */
+ NULL, /* ca eab hmac */
+ 0, /* stapling */
+ 1, /* staple others */
+ NULL, /* dns01_cmd */
+ NULL, /* currently defined md */
+ NULL, /* assigned md, post config */
+ 0, /* is_ssl, set during mod_ssl post_config */
+};
+
+static md_mod_conf_t *mod_md_config;
+
+static apr_status_t cleanup_mod_config(void *dummy)
+{
+ (void)dummy;
+ mod_md_config = NULL;
+ return APR_SUCCESS;
+}
+
+static md_mod_conf_t *md_mod_conf_get(apr_pool_t *pool, int create)
+{
+ if (mod_md_config) {
+ return mod_md_config; /* reused for lifetime of the pool */
+ }
+
+ if (create) {
+ mod_md_config = apr_pcalloc(pool, sizeof(*mod_md_config));
+ memcpy(mod_md_config, &defmc, sizeof(*mod_md_config));
+ mod_md_config->mds = apr_array_make(pool, 5, sizeof(const md_t *));
+ mod_md_config->unused_names = apr_array_make(pool, 5, sizeof(const md_t *));
+ mod_md_config->env = apr_table_make(pool, 10);
+ mod_md_config->init_errors = apr_hash_make(pool);
+
+ apr_pool_cleanup_register(pool, NULL, cleanup_mod_config, apr_pool_cleanup_null);
+ }
+
+ return mod_md_config;
+}
+
+#define CONF_S_NAME(s) (s && s->server_hostname? s->server_hostname : "default")
+
+static void srv_conf_props_clear(md_srv_conf_t *sc)
+{
+ sc->transitive = DEF_VAL;
+ sc->require_https = MD_REQUIRE_UNSET;
+ sc->renew_mode = DEF_VAL;
+ sc->must_staple = DEF_VAL;
+ sc->pks = NULL;
+ sc->renew_window = NULL;
+ sc->warn_window = NULL;
+ sc->ca_urls = NULL;
+ sc->ca_contact = NULL;
+ sc->ca_proto = NULL;
+ sc->ca_agreement = NULL;
+ sc->ca_challenges = NULL;
+ sc->ca_eab_kid = NULL;
+ sc->ca_eab_hmac = NULL;
+ sc->stapling = DEF_VAL;
+ sc->staple_others = DEF_VAL;
+ sc->dns01_cmd = NULL;
+}
+
+static void srv_conf_props_copy(md_srv_conf_t *to, const md_srv_conf_t *from)
+{
+ to->transitive = from->transitive;
+ to->require_https = from->require_https;
+ to->renew_mode = from->renew_mode;
+ to->must_staple = from->must_staple;
+ to->pks = from->pks;
+ to->warn_window = from->warn_window;
+ to->renew_window = from->renew_window;
+ to->ca_urls = from->ca_urls;
+ to->ca_contact = from->ca_contact;
+ to->ca_proto = from->ca_proto;
+ to->ca_agreement = from->ca_agreement;
+ to->ca_challenges = from->ca_challenges;
+ to->ca_eab_kid = from->ca_eab_kid;
+ to->ca_eab_hmac = from->ca_eab_hmac;
+ to->stapling = from->stapling;
+ to->staple_others = from->staple_others;
+ to->dns01_cmd = from->dns01_cmd;
+}
+
+static void srv_conf_props_apply(md_t *md, const md_srv_conf_t *from, apr_pool_t *p)
+{
+ if (from->require_https != MD_REQUIRE_UNSET) md->require_https = from->require_https;
+ if (from->transitive != DEF_VAL) md->transitive = from->transitive;
+ if (from->renew_mode != DEF_VAL) md->renew_mode = from->renew_mode;
+ if (from->must_staple != DEF_VAL) md->must_staple = from->must_staple;
+ if (from->pks) md->pks = md_pkeys_spec_clone(p, from->pks);
+ if (from->renew_window) md->renew_window = from->renew_window;
+ if (from->warn_window) md->warn_window = from->warn_window;
+ if (from->ca_urls) md->ca_urls = apr_array_copy(p, from->ca_urls);
+ if (from->ca_proto) md->ca_proto = from->ca_proto;
+ if (from->ca_agreement) md->ca_agreement = from->ca_agreement;
+ if (from->ca_contact) {
+ apr_array_clear(md->contacts);
+ APR_ARRAY_PUSH(md->contacts, const char *) =
+ md_util_schemify(p, from->ca_contact, "mailto");
+ }
+ if (from->ca_challenges) md->ca_challenges = apr_array_copy(p, from->ca_challenges);
+ if (from->ca_eab_kid) md->ca_eab_kid = from->ca_eab_kid;
+ if (from->ca_eab_hmac) md->ca_eab_hmac = from->ca_eab_hmac;
+ if (from->stapling != DEF_VAL) md->stapling = from->stapling;
+ if (from->dns01_cmd) md->dns01_cmd = from->dns01_cmd;
+}
+
+void *md_config_create_svr(apr_pool_t *pool, server_rec *s)
+{
+ md_srv_conf_t *conf = (md_srv_conf_t *)apr_pcalloc(pool, sizeof(md_srv_conf_t));
+
+ conf->name = apr_pstrcat(pool, "srv[", CONF_S_NAME(s), "]", NULL);
+ conf->s = s;
+ conf->mc = md_mod_conf_get(pool, 1);
+
+ srv_conf_props_clear(conf);
+
+ return conf;
+}
+
+static void *md_config_merge(apr_pool_t *pool, void *basev, void *addv)
+{
+ md_srv_conf_t *base = (md_srv_conf_t *)basev;
+ md_srv_conf_t *add = (md_srv_conf_t *)addv;
+ md_srv_conf_t *nsc;
+ char *name = apr_pstrcat(pool, "[", CONF_S_NAME(add->s), ", ", CONF_S_NAME(base->s), "]", NULL);
+
+ nsc = (md_srv_conf_t *)apr_pcalloc(pool, sizeof(md_srv_conf_t));
+ nsc->name = name;
+ nsc->mc = add->mc? add->mc : base->mc;
+
+ nsc->transitive = (add->transitive != DEF_VAL)? add->transitive : base->transitive;
+ nsc->require_https = (add->require_https != MD_REQUIRE_UNSET)? add->require_https : base->require_https;
+ nsc->renew_mode = (add->renew_mode != DEF_VAL)? add->renew_mode : base->renew_mode;
+ nsc->must_staple = (add->must_staple != DEF_VAL)? add->must_staple : base->must_staple;
+ nsc->pks = (!md_pkeys_spec_is_empty(add->pks))? add->pks : base->pks;
+ nsc->renew_window = add->renew_window? add->renew_window : base->renew_window;
+ nsc->warn_window = add->warn_window? add->warn_window : base->warn_window;
+
+ nsc->ca_urls = add->ca_urls? apr_array_copy(pool, add->ca_urls)
+ : (base->ca_urls? apr_array_copy(pool, base->ca_urls) : NULL);
+ nsc->ca_contact = add->ca_contact? add->ca_contact : base->ca_contact;
+ nsc->ca_proto = add->ca_proto? add->ca_proto : base->ca_proto;
+ nsc->ca_agreement = add->ca_agreement? add->ca_agreement : base->ca_agreement;
+ nsc->ca_challenges = (add->ca_challenges? apr_array_copy(pool, add->ca_challenges)
+ : (base->ca_challenges? apr_array_copy(pool, base->ca_challenges) : NULL));
+ nsc->ca_eab_kid = add->ca_eab_kid? add->ca_eab_kid : base->ca_eab_kid;
+ nsc->ca_eab_hmac = add->ca_eab_hmac? add->ca_eab_hmac : base->ca_eab_hmac;
+ nsc->stapling = (add->stapling != DEF_VAL)? add->stapling : base->stapling;
+ nsc->staple_others = (add->staple_others != DEF_VAL)? add->staple_others : base->staple_others;
+ nsc->dns01_cmd = (add->dns01_cmd)? add->dns01_cmd : base->dns01_cmd;
+ nsc->current = NULL;
+
+ return nsc;
+}
+
+void *md_config_merge_svr(apr_pool_t *pool, void *basev, void *addv)
+{
+ return md_config_merge(pool, basev, addv);
+}
+
+static int inside_section(cmd_parms *cmd, const char *section) {
+ ap_directive_t *d;
+ for (d = cmd->directive->parent; d; d = d->parent) {
+ if (!ap_cstr_casecmp(d->directive, section)) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int inside_md_section(cmd_parms *cmd) {
+ return (inside_section(cmd, MD_CMD_MD_SECTION) || inside_section(cmd, MD_CMD_MD2_SECTION));
+}
+
+static const char *md_section_check(cmd_parms *cmd) {
+ if (!inside_md_section(cmd)) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name, " is only valid inside a '",
+ MD_CMD_MD_SECTION, "' context, not here", NULL);
+ }
+ return NULL;
+}
+
+#define MD_LOC_GLOBAL (0x01)
+#define MD_LOC_MD (0x02)
+#define MD_LOC_ELSE (0x04)
+#define MD_LOC_ALL (0x07)
+#define MD_LOC_NOT_MD (0x102)
+
+static const char *md_conf_check_location(cmd_parms *cmd, int flags)
+{
+ if (MD_LOC_GLOBAL == flags) {
+ return ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ }
+ if (MD_LOC_NOT_MD == flags && inside_md_section(cmd)) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name, " is not allowed inside an '",
+ MD_CMD_MD_SECTION, "' context", NULL);
+ }
+ if (MD_LOC_MD == flags) {
+ return md_section_check(cmd);
+ }
+ else if ((MD_LOC_MD & flags) && inside_md_section(cmd)) {
+ return NULL;
+ }
+ return ap_check_cmd_context(cmd, NOT_IN_DIRECTORY|NOT_IN_LOCATION);
+}
+
+static const char *set_on_off(int *pvalue, const char *s, apr_pool_t *p)
+{
+ if (!apr_strnatcasecmp("off", s)) {
+ *pvalue = 0;
+ }
+ else if (!apr_strnatcasecmp("on", s)) {
+ *pvalue = 1;
+ }
+ else {
+ return apr_pstrcat(p, "unknown '", s,
+ "', supported parameter values are 'on' and 'off'", NULL);
+ }
+ return NULL;
+}
+
+
+static void add_domain_name(apr_array_header_t *domains, const char *name, apr_pool_t *p)
+{
+ if (md_array_str_index(domains, name, 0, 0) < 0) {
+ APR_ARRAY_PUSH(domains, char *) = md_util_str_tolower(apr_pstrdup(p, name));
+ }
+}
+
+static const char *set_transitive(int *ptransitive, const char *value)
+{
+ if (!apr_strnatcasecmp("auto", value)) {
+ *ptransitive = 1;
+ return NULL;
+ }
+ else if (!apr_strnatcasecmp("manual", value)) {
+ *ptransitive = 0;
+ return NULL;
+ }
+ return "unknown value, use \"auto|manual\"";
+}
+
+static const char *md_config_sec_start(cmd_parms *cmd, void *mconfig, const char *arg)
+{
+ md_srv_conf_t *sc;
+ md_srv_conf_t save;
+ const char *endp;
+ const char *err, *name;
+ apr_array_header_t *domains;
+ md_t *md;
+ int transitive = -1;
+
+ (void)mconfig;
+ if ((err = md_conf_check_location(cmd, MD_LOC_NOT_MD))) {
+ return err;
+ }
+
+ sc = md_config_get(cmd->server);
+ endp = ap_strrchr_c(arg, '>');
+ if (endp == NULL) {
+ return MD_CMD_MD_SECTION "> directive missing closing '>'";
+ }
+
+ arg = apr_pstrndup(cmd->pool, arg, (apr_size_t)(endp-arg));
+ if (!arg || !*arg) {
+ return MD_CMD_MD_SECTION " > section must specify a unique domain name";
+ }
+
+ name = ap_getword_conf(cmd->pool, &arg);
+ domains = apr_array_make(cmd->pool, 5, sizeof(const char *));
+ add_domain_name(domains, name, cmd->pool);
+ while (*arg != '\0') {
+ name = ap_getword_conf(cmd->pool, &arg);
+ if (NULL != set_transitive(&transitive, name)) {
+ add_domain_name(domains, name, cmd->pool);
+ }
+ }
+
+ if (domains->nelts == 0) {
+ return "needs at least one domain name";
+ }
+
+ md = md_create(cmd->pool, domains);
+ if (transitive >= 0) {
+ md->transitive = transitive;
+ }
+
+ /* Save the current settings in this srv_conf and apply+restore at the
+ * end of this section */
+ memcpy(&save, sc, sizeof(save));
+ srv_conf_props_clear(sc);
+ sc->current = md;
+
+ if (NULL == (err = ap_walk_config(cmd->directive->first_child, cmd, cmd->context))) {
+ srv_conf_props_apply(md, sc, cmd->pool);
+ APR_ARRAY_PUSH(sc->mc->mds, const md_t *) = md;
+ }
+
+ sc->current = NULL;
+ srv_conf_props_copy(sc, &save);
+
+ return err;
+}
+
+static const char *md_config_sec_add_members(cmd_parms *cmd, void *dc,
+ int argc, char *const argv[])
+{
+ md_srv_conf_t *sc = md_config_get(cmd->server);
+ const char *err;
+ int i;
+
+ (void)dc;
+ if (NULL != (err = md_section_check(cmd))) {
+ if (argc == 1) {
+ /* only these values are allowed outside a section */
+ return set_transitive(&sc->transitive, argv[0]);
+ }
+ return err;
+ }
+
+ assert(sc->current);
+ for (i = 0; i < argc; ++i) {
+ if (NULL != set_transitive(&sc->transitive, argv[i])) {
+ add_domain_name(sc->current->domains, argv[i], cmd->pool);
+ }
+ }
+ return NULL;
+}
+
+static const char *md_config_set_names(cmd_parms *cmd, void *dc,
+ int argc, char *const argv[])
+{
+ md_srv_conf_t *sc = md_config_get(cmd->server);
+ apr_array_header_t *domains = apr_array_make(cmd->pool, 5, sizeof(const char *));
+ const char *err;
+ md_t *md;
+ int i, transitive = -1;
+
+ (void)dc;
+ if ((err = md_conf_check_location(cmd, MD_LOC_NOT_MD))) {
+ return err;
+ }
+
+ for (i = 0; i < argc; ++i) {
+ if (NULL != set_transitive(&transitive, argv[i])) {
+ add_domain_name(domains, argv[i], cmd->pool);
+ }
+ }
+
+ if (domains->nelts == 0) {
+ return "needs at least one domain name";
+ }
+ md = md_create(cmd->pool, domains);
+
+ if (transitive >= 0) {
+ md->transitive = transitive;
+ }
+
+ if (cmd->config_file) {
+ md->defn_name = cmd->config_file->name;
+ md->defn_line_number = cmd->config_file->line_number;
+ }
+
+ APR_ARRAY_PUSH(sc->mc->mds, md_t *) = md;
+
+ return NULL;
+}
+
+static const char *md_config_set_ca(cmd_parms *cmd, void *dc,
+ int argc, char *const argv[])
+{
+ md_srv_conf_t *sc = md_config_get(cmd->server);
+ const char *err, *url;
+ int i;
+
+ (void)dc;
+ if ((err = md_conf_check_location(cmd, MD_LOC_ALL))) {
+ return err;
+ }
+ if (!sc->ca_urls) {
+ sc->ca_urls = apr_array_make(cmd->pool, 3, sizeof(const char *));
+ }
+ else {
+ apr_array_clear(sc->ca_urls);
+ }
+ for (i = 0; i < argc; ++i) {
+ if (APR_SUCCESS != md_get_ca_url_from_name(&url, cmd->pool, argv[i])) {
+ return url;
+ }
+ APR_ARRAY_PUSH(sc->ca_urls, const char *) = url;
+ }
+ return NULL;
+}
+
+static const char *md_config_set_contact(cmd_parms *cmd, void *dc, const char *value)
+{
+ md_srv_conf_t *sc = md_config_get(cmd->server);
+ const char *err;
+
+ (void)dc;
+ if ((err = md_conf_check_location(cmd, MD_LOC_ALL))) {
+ return err;
+ }
+ sc->ca_contact = value;
+ return NULL;
+}
+
+static const char *md_config_set_ca_proto(cmd_parms *cmd, void *dc, const char *value)
+{
+ md_srv_conf_t *config = md_config_get(cmd->server);
+ const char *err;
+
+ (void)dc;
+ if ((err = md_conf_check_location(cmd, MD_LOC_ALL))) {
+ return err;
+ }
+ config->ca_proto = value;
+ return NULL;
+}
+
+static const char *md_config_set_agreement(cmd_parms *cmd, void *dc, const char *value)
+{
+ md_srv_conf_t *config = md_config_get(cmd->server);
+ const char *err;
+
+ (void)dc;
+ if ((err = md_conf_check_location(cmd, MD_LOC_ALL))) {
+ return err;
+ }
+ config->ca_agreement = value;
+ return NULL;
+}
+
+static const char *md_config_set_renew_mode(cmd_parms *cmd, void *dc, const char *value)
+{
+ md_srv_conf_t *config = md_config_get(cmd->server);
+ const char *err;
+ md_renew_mode_t renew_mode;
+
+ (void)dc;
+ if (!apr_strnatcasecmp("auto", value) || !apr_strnatcasecmp("automatic", value)) {
+ renew_mode = MD_RENEW_AUTO;
+ }
+ else if (!apr_strnatcasecmp("always", value)) {
+ renew_mode = MD_RENEW_ALWAYS;
+ }
+ else if (!apr_strnatcasecmp("manual", value) || !apr_strnatcasecmp("stick", value)) {
+ renew_mode = MD_RENEW_MANUAL;
+ }
+ else {
+ return apr_pstrcat(cmd->pool, "unknown MDDriveMode ", value, NULL);
+ }
+
+ if ((err = md_conf_check_location(cmd, MD_LOC_ALL))) {
+ return err;
+ }
+ config->renew_mode = renew_mode;
+ return NULL;
+}
+
+static const char *md_config_set_must_staple(cmd_parms *cmd, void *dc, const char *value)
+{
+ md_srv_conf_t *config = md_config_get(cmd->server);
+ const char *err;
+
+ (void)dc;
+ if ((err = md_conf_check_location(cmd, MD_LOC_ALL))) {
+ return err;
+ }
+ return set_on_off(&config->must_staple, value, cmd->pool);
+}
+
+static const char *md_config_set_stapling(cmd_parms *cmd, void *dc, const char *value)
+{
+ md_srv_conf_t *config = md_config_get(cmd->server);
+ const char *err;
+
+ (void)dc;
+ if ((err = md_conf_check_location(cmd, MD_LOC_ALL))) {
+ return err;
+ }
+ return set_on_off(&config->stapling, value, cmd->pool);
+}
+
+static const char *md_config_set_staple_others(cmd_parms *cmd, void *dc, const char *value)
+{
+ md_srv_conf_t *config = md_config_get(cmd->server);
+ const char *err;
+
+ (void)dc;
+ if ((err = md_conf_check_location(cmd, MD_LOC_ALL))) {
+ return err;
+ }
+ return set_on_off(&config->staple_others, value, cmd->pool);
+}
+
+static const char *md_config_set_base_server(cmd_parms *cmd, void *dc, const char *value)
+{
+ md_srv_conf_t *config = md_config_get(cmd->server);
+ const char *err = md_conf_check_location(cmd, MD_LOC_NOT_MD);
+
+ (void)dc;
+ if (err) return err;
+ return set_on_off(&config->mc->manage_base_server, value, cmd->pool);
+}
+
+static const char *md_config_set_min_delay(cmd_parms *cmd, void *dc, const char *value)
+{
+ md_srv_conf_t *config = md_config_get(cmd->server);
+ const char *err = md_conf_check_location(cmd, MD_LOC_NOT_MD);
+ apr_time_t delay;
+
+ (void)dc;
+ if (err) return err;
+ if (md_duration_parse(&delay, value, "s") != APR_SUCCESS) {
+ return "unrecognized duration format";
+ }
+ config->mc->min_delay = delay;
+ return NULL;
+}
+
+static const char *md_config_set_retry_failover(cmd_parms *cmd, void *dc, const char *value)
+{
+ md_srv_conf_t *config = md_config_get(cmd->server);
+ const char *err = md_conf_check_location(cmd, MD_LOC_NOT_MD);
+ int retry_failover;
+
+ (void)dc;
+ if (err) return err;
+ retry_failover = atoi(value);
+ if (retry_failover <= 0) {
+ return "invalid argument, must be a number > 0";
+ }
+ config->mc->retry_failover = retry_failover;
+ return NULL;
+}
+
+static const char *md_config_set_store_locks(cmd_parms *cmd, void *dc, const char *s)
+{
+ md_srv_conf_t *config = md_config_get(cmd->server);
+ const char *err = md_conf_check_location(cmd, MD_LOC_NOT_MD);
+ int use_store_locks;
+ apr_time_t wait_time = 0;
+
+ (void)dc;
+ if (err) {
+ return err;
+ }
+ else if (!apr_strnatcasecmp("off", s)) {
+ use_store_locks = 0;
+ }
+ else if (!apr_strnatcasecmp("on", s)) {
+ use_store_locks = 1;
+ }
+ else {
+ if (md_duration_parse(&wait_time, s, "s") != APR_SUCCESS) {
+ return "neither 'on', 'off' or a duration specified";
+ }
+ use_store_locks = (wait_time != 0);
+ }
+ config->mc->use_store_locks = use_store_locks;
+ if (wait_time) {
+ config->mc->lock_wait_timeout = wait_time;
+ }
+ return NULL;
+}
+
+static const char *md_config_set_require_https(cmd_parms *cmd, void *dc, const char *value)
+{
+ md_srv_conf_t *config = md_config_get(cmd->server);
+ const char *err;
+
+ if ((err = md_conf_check_location(cmd, MD_LOC_ALL))) {
+ return err;
+ }
+ (void)dc;
+ if (!apr_strnatcasecmp("off", value)) {
+ config->require_https = MD_REQUIRE_OFF;
+ }
+ else if (!apr_strnatcasecmp(MD_KEY_TEMPORARY, value)) {
+ config->require_https = MD_REQUIRE_TEMPORARY;
+ }
+ else if (!apr_strnatcasecmp(MD_KEY_PERMANENT, value)) {
+ config->require_https = MD_REQUIRE_PERMANENT;
+ }
+ else {
+ return apr_pstrcat(cmd->pool, "unknown '", value,
+ "', supported parameter values are 'temporary' and 'permanent'", NULL);
+ }
+ return NULL;
+}
+
+static const char *md_config_set_renew_window(cmd_parms *cmd, void *dc, const char *value)
+{
+ md_srv_conf_t *config = md_config_get(cmd->server);
+ const char *err;
+
+ (void)dc;
+ if ((err = md_conf_check_location(cmd, MD_LOC_ALL))) {
+ return err;
+ }
+ err = md_timeslice_parse(&config->renew_window, cmd->pool, value, MD_TIME_LIFE_NORM);
+ if (!err && config->renew_window->norm
+ && (config->renew_window->len >= config->renew_window->norm)) {
+ err = "a length of 100% or more is not allowed.";
+ }
+ if (err) return apr_psprintf(cmd->pool, "MDRenewWindow %s", err);
+ return NULL;
+}
+
+static const char *md_config_set_warn_window(cmd_parms *cmd, void *dc, const char *value)
+{
+ md_srv_conf_t *config = md_config_get(cmd->server);
+ const char *err;
+
+ (void)dc;
+ if ((err = md_conf_check_location(cmd, MD_LOC_ALL))) {
+ return err;
+ }
+ err = md_timeslice_parse(&config->warn_window, cmd->pool, value, MD_TIME_LIFE_NORM);
+ if (!err && config->warn_window->norm
+ && (config->warn_window->len >= config->warn_window->norm)) {
+ err = "a length of 100% or more is not allowed.";
+ }
+ if (err) return apr_psprintf(cmd->pool, "MDWarnWindow %s", err);
+ return NULL;
+}
+
+static const char *md_config_set_proxy(cmd_parms *cmd, void *arg, const char *value)
+{
+ md_srv_conf_t *sc = md_config_get(cmd->server);
+ const char *err;
+
+ if ((err = md_conf_check_location(cmd, MD_LOC_NOT_MD))) {
+ return err;
+ }
+ md_util_abs_http_uri_check(cmd->pool, value, &err);
+ if (err) {
+ return err;
+ }
+ sc->mc->proxy_url = value;
+ (void)arg;
+ return NULL;
+}
+
+static const char *md_config_set_store_dir(cmd_parms *cmd, void *arg, const char *value)
+{
+ md_srv_conf_t *sc = md_config_get(cmd->server);
+ const char *err;
+
+ if ((err = md_conf_check_location(cmd, MD_LOC_NOT_MD))) {
+ return err;
+ }
+ sc->mc->base_dir = value;
+ (void)arg;
+ return NULL;
+}
+
+static const char *set_port_map(md_mod_conf_t *mc, const char *value)
+{
+ int net_port, local_port;
+ const char *endp;
+
+ if (!strncmp("http:", value, sizeof("http:") - 1)) {
+ net_port = 80; endp = value + sizeof("http") - 1;
+ }
+ else if (!strncmp("https:", value, sizeof("https:") - 1)) {
+ net_port = 443; endp = value + sizeof("https") - 1;
+ }
+ else {
+ net_port = (int)apr_strtoi64(value, (char**)&endp, 10);
+ if (errno) {
+ return "unable to parse first port number";
+ }
+ }
+ if (!endp || *endp != ':') {
+ return "no ':' after first port number";
+ }
+ ++endp;
+ if (*endp == '-') {
+ local_port = 0;
+ }
+ else {
+ local_port = (int)apr_strtoi64(endp, (char**)&endp, 10);
+ if (errno) {
+ return "unable to parse second port number";
+ }
+ if (local_port <= 0 || local_port > 65535) {
+ return "invalid number for port map, must be in ]0,65535]";
+ }
+ }
+ switch (net_port) {
+ case 80:
+ mc->local_80 = local_port;
+ break;
+ case 443:
+ mc->local_443 = local_port;
+ break;
+ default:
+ return "mapped port number must be 80 or 443";
+ }
+ return NULL;
+}
+
+static const char *md_config_set_port_map(cmd_parms *cmd, void *arg,
+ const char *v1, const char *v2)
+{
+ md_srv_conf_t *sc = md_config_get(cmd->server);
+ const char *err;
+
+ (void)arg;
+ if (!(err = md_conf_check_location(cmd, MD_LOC_NOT_MD))) {
+ err = set_port_map(sc->mc, v1);
+ }
+ if (!err && v2) {
+ err = set_port_map(sc->mc, v2);
+ }
+ return err;
+}
+
+static const char *md_config_set_cha_tyes(cmd_parms *cmd, void *dc,
+ int argc, char *const argv[])
+{
+ md_srv_conf_t *config = md_config_get(cmd->server);
+ apr_array_header_t **pcha, *ca_challenges;
+ const char *err;
+ int i;
+
+ (void)dc;
+ if ((err = md_conf_check_location(cmd, MD_LOC_ALL))) {
+ return err;
+ }
+ pcha = &config->ca_challenges;
+
+ ca_challenges = *pcha;
+ if (ca_challenges) {
+ apr_array_clear(ca_challenges);
+ }
+ else {
+ *pcha = ca_challenges = apr_array_make(cmd->pool, 5, sizeof(const char *));
+ }
+ for (i = 0; i < argc; ++i) {
+ APR_ARRAY_PUSH(ca_challenges, const char *) = argv[i];
+ }
+
+ return NULL;
+}
+
+static const char *md_config_set_pkeys(cmd_parms *cmd, void *dc,
+ int argc, char *const argv[])
+{
+ md_srv_conf_t *config = md_config_get(cmd->server);
+ const char *err, *ptype;
+ apr_int64_t bits;
+ int i;
+
+ (void)dc;
+ if ((err = md_conf_check_location(cmd, MD_LOC_ALL))) {
+ return err;
+ }
+ if (argc <= 0) {
+ return "needs to specify the private key type";
+ }
+
+ config->pks = md_pkeys_spec_make(cmd->pool);
+ for (i = 0; i < argc; ++i) {
+ ptype = argv[i];
+ if (!apr_strnatcasecmp("Default", ptype)) {
+ if (argc > 1) {
+ return "'Default' allows no other parameter";
+ }
+ md_pkeys_spec_add_default(config->pks);
+ }
+ else if (strlen(ptype) > 3
+ && (ptype[0] == 'R' || ptype[0] == 'r')
+ && (ptype[1] == 'S' || ptype[1] == 's')
+ && (ptype[2] == 'A' || ptype[2] == 'a')
+ && isdigit(ptype[3])) {
+ bits = (int)apr_atoi64(ptype+3);
+ if (bits < MD_PKEY_RSA_BITS_MIN) {
+ return apr_psprintf(cmd->pool,
+ "must be %d or higher in order to be considered safe.",
+ MD_PKEY_RSA_BITS_MIN);
+ }
+ if (bits >= INT_MAX) {
+ return apr_psprintf(cmd->pool, "is too large for an RSA key length.");
+ }
+ if (md_pkeys_spec_contains_rsa(config->pks)) {
+ return "two keys of type 'RSA' are not possible.";
+ }
+ md_pkeys_spec_add_rsa(config->pks, (unsigned int)bits);
+ }
+ else if (!apr_strnatcasecmp("RSA", ptype)) {
+ if (i+1 >= argc || !isdigit(argv[i+1][0])) {
+ bits = MD_PKEY_RSA_BITS_DEF;
+ }
+ else {
+ ++i;
+ bits = (int)apr_atoi64(argv[i]);
+ if (bits < MD_PKEY_RSA_BITS_MIN) {
+ return apr_psprintf(cmd->pool,
+ "must be %d or higher in order to be considered safe.",
+ MD_PKEY_RSA_BITS_MIN);
+ }
+ if (bits >= INT_MAX) {
+ return apr_psprintf(cmd->pool, "is too large for an RSA key length.");
+ }
+ }
+ if (md_pkeys_spec_contains_rsa(config->pks)) {
+ return "two keys of type 'RSA' are not possible.";
+ }
+ md_pkeys_spec_add_rsa(config->pks, (unsigned int)bits);
+ }
+ else {
+ if (md_pkeys_spec_contains_ec(config->pks, argv[i])) {
+ return apr_psprintf(cmd->pool, "two keys of type '%s' are not possible.", argv[i]);
+ }
+ md_pkeys_spec_add_ec(config->pks, argv[i]);
+ }
+ }
+ return NULL;
+}
+
+static const char *md_config_set_notify_cmd(cmd_parms *cmd, void *mconfig, const char *arg)
+{
+ md_srv_conf_t *sc = md_config_get(cmd->server);
+ const char *err;
+
+ if ((err = md_conf_check_location(cmd, MD_LOC_NOT_MD))) {
+ return err;
+ }
+ sc->mc->notify_cmd = arg;
+ (void)mconfig;
+ return NULL;
+}
+
+static const char *md_config_set_msg_cmd(cmd_parms *cmd, void *mconfig, const char *arg)
+{
+ md_srv_conf_t *sc = md_config_get(cmd->server);
+ const char *err;
+
+ if ((err = md_conf_check_location(cmd, MD_LOC_NOT_MD))) {
+ return err;
+ }
+ sc->mc->message_cmd = arg;
+ (void)mconfig;
+ return NULL;
+}
+
+static const char *md_config_set_dns01_cmd(cmd_parms *cmd, void *mconfig, const char *arg)
+{
+ md_srv_conf_t *sc = md_config_get(cmd->server);
+ const char *err;
+
+ if ((err = md_conf_check_location(cmd, MD_LOC_ALL))) {
+ return err;
+ }
+
+ if (inside_md_section(cmd)) {
+ sc->dns01_cmd = arg;
+ } else {
+ apr_table_set(sc->mc->env, MD_KEY_CMD_DNS01, arg);
+ }
+
+ (void)mconfig;
+ return NULL;
+}
+
+static const char *md_config_add_cert_file(cmd_parms *cmd, void *mconfig, const char *arg)
+{
+ md_srv_conf_t *sc = md_config_get(cmd->server);
+ const char *err, *fpath;
+
+ (void)mconfig;
+ if ((err = md_conf_check_location(cmd, MD_LOC_MD))) return err;
+ assert(sc->current);
+ fpath = ap_server_root_relative(cmd->pool, arg);
+ if (!fpath) return apr_psprintf(cmd->pool, "certificate file not found: %s", arg);
+ if (!sc->current->cert_files) {
+ sc->current->cert_files = apr_array_make(cmd->pool, 3, sizeof(char*));
+ }
+ APR_ARRAY_PUSH(sc->current->cert_files, const char*) = fpath;
+ return NULL;
+}
+
+static const char *md_config_add_key_file(cmd_parms *cmd, void *mconfig, const char *arg)
+{
+ md_srv_conf_t *sc = md_config_get(cmd->server);
+ const char *err, *fpath;
+
+ (void)mconfig;
+ if ((err = md_conf_check_location(cmd, MD_LOC_MD))) return err;
+ assert(sc->current);
+ fpath = ap_server_root_relative(cmd->pool, arg);
+ if (!fpath) return apr_psprintf(cmd->pool, "certificate key file not found: %s", arg);
+ if (!sc->current->pkey_files) {
+ sc->current->pkey_files = apr_array_make(cmd->pool, 3, sizeof(char*));
+ }
+ APR_ARRAY_PUSH(sc->current->pkey_files, const char*) = fpath;
+ return NULL;
+}
+
+static const char *md_config_set_server_status(cmd_parms *cmd, void *dc, const char *value)
+{
+ md_srv_conf_t *sc = md_config_get(cmd->server);
+ const char *err;
+
+ (void)dc;
+ if ((err = md_conf_check_location(cmd, MD_LOC_ALL))) {
+ return err;
+ }
+ return set_on_off(&sc->mc->server_status_enabled, value, cmd->pool);
+}
+
+static const char *md_config_set_certificate_status(cmd_parms *cmd, void *dc, const char *value)
+{
+ md_srv_conf_t *sc = md_config_get(cmd->server);
+ const char *err;
+
+ (void)dc;
+ if ((err = md_conf_check_location(cmd, MD_LOC_ALL))) {
+ return err;
+ }
+ return set_on_off(&sc->mc->certificate_status_enabled, value, cmd->pool);
+}
+
+static const char *md_config_set_ocsp_keep_window(cmd_parms *cmd, void *dc, const char *value)
+{
+ md_srv_conf_t *sc = md_config_get(cmd->server);
+ const char *err;
+
+ (void)dc;
+ if ((err = md_conf_check_location(cmd, MD_LOC_ALL))) {
+ return err;
+ }
+ err = md_timeslice_parse(&sc->mc->ocsp_keep_window, cmd->pool, value, MD_TIME_OCSP_KEEP_NORM);
+ if (err) return apr_psprintf(cmd->pool, "MDStaplingKeepResponse %s", err);
+ return NULL;
+}
+
+static const char *md_config_set_ocsp_renew_window(cmd_parms *cmd, void *dc, const char *value)
+{
+ md_srv_conf_t *sc = md_config_get(cmd->server);
+ const char *err;
+
+ (void)dc;
+ if ((err = md_conf_check_location(cmd, MD_LOC_ALL))) {
+ return err;
+ }
+ err = md_timeslice_parse(&sc->mc->ocsp_renew_window, cmd->pool, value, MD_TIME_LIFE_NORM);
+ if (!err && sc->mc->ocsp_renew_window->norm
+ && (sc->mc->ocsp_renew_window->len >= sc->mc->ocsp_renew_window->norm)) {
+ err = "with a length of 100% or more is not allowed.";
+ }
+ if (err) return apr_psprintf(cmd->pool, "MDStaplingRenewWindow %s", err);
+ return NULL;
+}
+
+static const char *md_config_set_cert_check(cmd_parms *cmd, void *dc,
+ const char *name, const char *url)
+{
+ md_srv_conf_t *sc = md_config_get(cmd->server);
+ const char *err;
+
+ (void)dc;
+ if ((err = md_conf_check_location(cmd, MD_LOC_ALL))) {
+ return err;
+ }
+ sc->mc->cert_check_name = name;
+ sc->mc->cert_check_url = url;
+ return NULL;
+}
+
+static const char *md_config_set_activation_delay(cmd_parms *cmd, void *mconfig, const char *arg)
+{
+ md_srv_conf_t *sc = md_config_get(cmd->server);
+ const char *err;
+ apr_interval_time_t delay;
+
+ (void)mconfig;
+ if ((err = md_conf_check_location(cmd, MD_LOC_NOT_MD))) {
+ return err;
+ }
+ if (md_duration_parse(&delay, arg, "d") != APR_SUCCESS) {
+ return "unrecognized duration format";
+ }
+ apr_table_set(sc->mc->env, MD_KEY_ACTIVATION_DELAY, md_duration_format(cmd->pool, delay));
+ return NULL;
+}
+
+static const char *md_config_set_ca_certs(cmd_parms *cmd, void *dc, const char *path)
+{
+ md_srv_conf_t *sc = md_config_get(cmd->server);
+
+ (void)dc;
+ sc->mc->ca_certs = path;
+ return NULL;
+}
+
+static const char *md_config_set_eab(cmd_parms *cmd, void *dc,
+ const char *keyid, const char *hmac)
+{
+ md_srv_conf_t *sc = md_config_get(cmd->server);
+ const char *err;
+
+ (void)dc;
+ if ((err = md_conf_check_location(cmd, MD_LOC_ALL))) {
+ return err;
+ }
+ if (!hmac) {
+ if (!apr_strnatcasecmp("None", keyid)) {
+ keyid = "none";
+ }
+ else {
+ /* a JSON file keeping keyid and hmac */
+ const char *fpath;
+ apr_status_t rv;
+ md_json_t *json;
+
+ /* If only dumping the config, don't verify the file */
+ if (ap_state_query(AP_SQ_RUN_MODE) == AP_SQ_RM_CONFIG_DUMP) {
+ goto leave;
+ }
+
+ fpath = ap_server_root_relative(cmd->pool, keyid);
+ if (!fpath) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ ": Invalid file path ", keyid, NULL);
+ }
+ if (!md_file_exists(fpath, cmd->pool)) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ ": file not found: ", fpath, NULL);
+ }
+
+ rv = md_json_readf(&json, cmd->pool, fpath);
+ if (APR_SUCCESS != rv) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ ": error reading JSON file ", fpath, NULL);
+ }
+ keyid = md_json_gets(json, MD_KEY_KID, NULL);
+ if (!keyid || !*keyid) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ ": JSON does not contain '", MD_KEY_KID,
+ "' element in file ", fpath, NULL);
+ }
+ hmac = md_json_gets(json, MD_KEY_HMAC, NULL);
+ if (!hmac || !*hmac) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ ": JSON does not contain '", MD_KEY_HMAC,
+ "' element in file ", fpath, NULL);
+ }
+ }
+ }
+leave:
+ sc->ca_eab_kid = keyid;
+ sc->ca_eab_hmac = hmac;
+ return NULL;
+}
+
+const command_rec md_cmds[] = {
+ AP_INIT_TAKE_ARGV("MDCertificateAuthority", md_config_set_ca, NULL, RSRC_CONF,
+ "URL(s) or known name(s) of CA issuing the certificates"),
+ AP_INIT_TAKE1("MDCertificateAgreement", md_config_set_agreement, NULL, RSRC_CONF,
+ "either 'accepted' or the URL of CA Terms-of-Service agreement you accept"),
+ AP_INIT_TAKE_ARGV("MDCAChallenges", md_config_set_cha_tyes, NULL, RSRC_CONF,
+ "A list of challenge types to be used."),
+ AP_INIT_TAKE1("MDCertificateProtocol", md_config_set_ca_proto, NULL, RSRC_CONF,
+ "Protocol used to obtain/renew certificates"),
+ AP_INIT_TAKE1("MDContactEmail", md_config_set_contact, NULL, RSRC_CONF,
+ "Email address used for account registration"),
+ AP_INIT_TAKE1("MDDriveMode", md_config_set_renew_mode, NULL, RSRC_CONF,
+ "deprecated, older name for MDRenewMode"),
+ AP_INIT_TAKE1("MDRenewMode", md_config_set_renew_mode, NULL, RSRC_CONF,
+ "Controls how renewal of Managed Domain certificates shall be handled."),
+ AP_INIT_TAKE_ARGV("MDomain", md_config_set_names, NULL, RSRC_CONF,
+ "A group of server names with one certificate"),
+ AP_INIT_RAW_ARGS(MD_CMD_MD_SECTION, md_config_sec_start, NULL, RSRC_CONF,
+ "Container for a managed domain with common settings and certificate."),
+ AP_INIT_RAW_ARGS(MD_CMD_MD2_SECTION, md_config_sec_start, NULL, RSRC_CONF,
+ "Short form for <MDomainSet> container."),
+ AP_INIT_TAKE_ARGV("MDMember", md_config_sec_add_members, NULL, RSRC_CONF,
+ "Define domain name(s) part of the Managed Domain. Use 'auto' or "
+ "'manual' to enable/disable auto adding names from virtual hosts."),
+ AP_INIT_TAKE_ARGV("MDMembers", md_config_sec_add_members, NULL, RSRC_CONF,
+ "Define domain name(s) part of the Managed Domain. Use 'auto' or "
+ "'manual' to enable/disable auto adding names from virtual hosts."),
+ AP_INIT_TAKE1("MDMustStaple", md_config_set_must_staple, NULL, RSRC_CONF,
+ "Enable/Disable the Must-Staple flag for new certificates."),
+ AP_INIT_TAKE12("MDPortMap", md_config_set_port_map, NULL, RSRC_CONF,
+ "Declare the mapped ports 80 and 443 on the local server. E.g. 80:8000 "
+ "to indicate that the server port 8000 is reachable as port 80 from the "
+ "internet. Use 80:- to indicate that port 80 is not reachable from "
+ "the outside."),
+ AP_INIT_TAKE_ARGV("MDPrivateKeys", md_config_set_pkeys, NULL, RSRC_CONF,
+ "set the type and parameters for private key generation"),
+ AP_INIT_TAKE1("MDHttpProxy", md_config_set_proxy, NULL, RSRC_CONF,
+ "URL of a HTTP(S) proxy to use for outgoing connections"),
+ AP_INIT_TAKE1("MDStoreDir", md_config_set_store_dir, NULL, RSRC_CONF,
+ "the directory for file system storage of managed domain data."),
+ AP_INIT_TAKE1("MDRenewWindow", md_config_set_renew_window, NULL, RSRC_CONF,
+ "Time length for renewal before certificate expires (defaults to days)."),
+ AP_INIT_TAKE1("MDRequireHttps", md_config_set_require_https, NULL, RSRC_CONF|OR_AUTHCFG,
+ "Redirect non-secure requests to the https: equivalent."),
+ AP_INIT_RAW_ARGS("MDNotifyCmd", md_config_set_notify_cmd, NULL, RSRC_CONF,
+ "Set the command to run when signup/renew of domain is complete."),
+ AP_INIT_TAKE1("MDBaseServer", md_config_set_base_server, NULL, RSRC_CONF,
+ "Allow managing of base server outside virtual hosts."),
+ AP_INIT_RAW_ARGS("MDChallengeDns01", md_config_set_dns01_cmd, NULL, RSRC_CONF,
+ "Set the command for setup/teardown of dns-01 challenges"),
+ AP_INIT_TAKE1("MDCertificateFile", md_config_add_cert_file, NULL, RSRC_CONF,
+ "set the static certificate (chain) file to use for this domain."),
+ AP_INIT_TAKE1("MDCertificateKeyFile", md_config_add_key_file, NULL, RSRC_CONF,
+ "set the static private key file to use for this domain."),
+ AP_INIT_TAKE1("MDServerStatus", md_config_set_server_status, NULL, RSRC_CONF,
+ "On to see Managed Domains in server-status."),
+ AP_INIT_TAKE1("MDCertificateStatus", md_config_set_certificate_status, NULL, RSRC_CONF,
+ "On to see Managed Domain expose /.httpd/certificate-status."),
+ AP_INIT_TAKE1("MDWarnWindow", md_config_set_warn_window, NULL, RSRC_CONF,
+ "When less time remains for a certificate, send our/log a warning (defaults to days)"),
+ AP_INIT_RAW_ARGS("MDMessageCmd", md_config_set_msg_cmd, NULL, RSRC_CONF,
+ "Set the command run when a message about a domain is issued."),
+ AP_INIT_TAKE1("MDStapling", md_config_set_stapling, NULL, RSRC_CONF,
+ "Enable/Disable OCSP Stapling for this/all Managed Domain(s)."),
+ AP_INIT_TAKE1("MDStapleOthers", md_config_set_staple_others, NULL, RSRC_CONF,
+ "Enable/Disable OCSP Stapling for certificates not in Managed Domains."),
+ AP_INIT_TAKE1("MDStaplingKeepResponse", md_config_set_ocsp_keep_window, NULL, RSRC_CONF,
+ "The amount of time to keep an OCSP response in the store."),
+ AP_INIT_TAKE1("MDStaplingRenewWindow", md_config_set_ocsp_renew_window, NULL, RSRC_CONF,
+ "Time length for renewal before OCSP responses expire (defaults to days)."),
+ AP_INIT_TAKE2("MDCertificateCheck", md_config_set_cert_check, NULL, RSRC_CONF,
+ "Set name and URL pattern for a certificate monitoring site."),
+ AP_INIT_TAKE1("MDActivationDelay", md_config_set_activation_delay, NULL, RSRC_CONF,
+ "How long to delay activation of new certificates"),
+ AP_INIT_TAKE1("MDCACertificateFile", md_config_set_ca_certs, NULL, RSRC_CONF,
+ "Set the CA file to use for connections"),
+ AP_INIT_TAKE12("MDExternalAccountBinding", md_config_set_eab, NULL, RSRC_CONF,
+ "Set the external account binding keyid and hmac values to use at CA"),
+ AP_INIT_TAKE1("MDRetryDelay", md_config_set_min_delay, NULL, RSRC_CONF,
+ "Time length for first retry, doubled on every consecutive error."),
+ AP_INIT_TAKE1("MDRetryFailover", md_config_set_retry_failover, NULL, RSRC_CONF,
+ "The number of errors before a failover to another CA is triggered."),
+ AP_INIT_TAKE1("MDStoreLocks", md_config_set_store_locks, NULL, RSRC_CONF,
+ "Configure locking of store for updates."),
+
+ AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL)
+};
+
+apr_status_t md_config_post_config(server_rec *s, apr_pool_t *p)
+{
+ md_srv_conf_t *sc;
+ md_mod_conf_t *mc;
+
+ sc = md_config_get(s);
+ mc = sc->mc;
+
+ mc->hsts_header = NULL;
+ if (mc->hsts_max_age > 0) {
+ mc->hsts_header = apr_psprintf(p, "max-age=%d", mc->hsts_max_age);
+ }
+
+#if AP_MODULE_MAGIC_AT_LEAST(20180906, 2)
+ if (mc->base_dir == NULL) {
+ mc->base_dir = ap_state_dir_relative(p, MD_DEFAULT_BASE_DIR);
+ }
+#endif
+
+ return APR_SUCCESS;
+}
+
+static md_srv_conf_t *config_get_int(server_rec *s, apr_pool_t *p)
+{
+ md_srv_conf_t *sc = (md_srv_conf_t *)ap_get_module_config(s->module_config, &md_module);
+ ap_assert(sc);
+ if (sc->s != s && p) {
+ sc = md_config_merge(p, &defconf, sc);
+ sc->s = s;
+ sc->name = apr_pstrcat(p, CONF_S_NAME(s), sc->name, NULL);
+ sc->mc = md_mod_conf_get(p, 1);
+ ap_set_module_config(s->module_config, &md_module, sc);
+ }
+ return sc;
+}
+
+md_srv_conf_t *md_config_get(server_rec *s)
+{
+ return config_get_int(s, NULL);
+}
+
+md_srv_conf_t *md_config_get_unique(server_rec *s, apr_pool_t *p)
+{
+ assert(p);
+ return config_get_int(s, p);
+}
+
+md_srv_conf_t *md_config_cget(conn_rec *c)
+{
+ return md_config_get(c->base_server);
+}
+
+const char *md_config_gets(const md_srv_conf_t *sc, md_config_var_t var)
+{
+ switch (var) {
+ case MD_CONFIG_CA_CONTACT:
+ return sc->ca_contact? sc->ca_contact : defconf.ca_contact;
+ case MD_CONFIG_CA_PROTO:
+ return sc->ca_proto? sc->ca_proto : defconf.ca_proto;
+ case MD_CONFIG_BASE_DIR:
+ return sc->mc->base_dir;
+ case MD_CONFIG_PROXY:
+ return sc->mc->proxy_url;
+ case MD_CONFIG_CA_AGREEMENT:
+ return sc->ca_agreement? sc->ca_agreement : defconf.ca_agreement;
+ case MD_CONFIG_NOTIFY_CMD:
+ return sc->mc->notify_cmd;
+ default:
+ return NULL;
+ }
+}
+
+int md_config_geti(const md_srv_conf_t *sc, md_config_var_t var)
+{
+ switch (var) {
+ case MD_CONFIG_DRIVE_MODE:
+ return (sc->renew_mode != DEF_VAL)? sc->renew_mode : defconf.renew_mode;
+ case MD_CONFIG_TRANSITIVE:
+ return (sc->transitive != DEF_VAL)? sc->transitive : defconf.transitive;
+ case MD_CONFIG_REQUIRE_HTTPS:
+ return (sc->require_https != MD_REQUIRE_UNSET)? sc->require_https : defconf.require_https;
+ case MD_CONFIG_MUST_STAPLE:
+ return (sc->must_staple != DEF_VAL)? sc->must_staple : defconf.must_staple;
+ case MD_CONFIG_STAPLING:
+ return (sc->stapling != DEF_VAL)? sc->stapling : defconf.stapling;
+ case MD_CONFIG_STAPLE_OTHERS:
+ return (sc->staple_others != DEF_VAL)? sc->staple_others : defconf.staple_others;
+ default:
+ return 0;
+ }
+}
+
+void md_config_get_timespan(md_timeslice_t **pspan, const md_srv_conf_t *sc, md_config_var_t var)
+{
+ switch (var) {
+ case MD_CONFIG_RENEW_WINDOW:
+ *pspan = sc->renew_window? sc->renew_window : defconf.renew_window;
+ break;
+ case MD_CONFIG_WARN_WINDOW:
+ *pspan = sc->warn_window? sc->warn_window : defconf.warn_window;
+ break;
+ default:
+ break;
+ }
+}
+
+const md_t *md_get_for_domain(server_rec *s, const char *domain)
+{
+ md_srv_conf_t *sc;
+ const md_t *md;
+ int i;
+
+ sc = md_config_get(s);
+ for (i = 0; sc && sc->assigned && i < sc->assigned->nelts; ++i) {
+ md = APR_ARRAY_IDX(sc->assigned, i, const md_t*);
+ if (md_contains(md, domain, 0)) goto leave;
+ }
+ md = NULL;
+leave:
+ return md;
+}
+
diff --git a/modules/md/mod_md_config.h b/modules/md/mod_md_config.h
new file mode 100644
index 0000000..de42169
--- /dev/null
+++ b/modules/md/mod_md_config.h
@@ -0,0 +1,132 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mod_md_md_config_h
+#define mod_md_md_config_h
+
+struct apr_hash_t;
+struct md_store_t;
+struct md_reg_t;
+struct md_ocsp_reg_t;
+struct md_pkeys_spec_t;
+
+typedef enum {
+ MD_CONFIG_CA_CONTACT,
+ MD_CONFIG_CA_PROTO,
+ MD_CONFIG_BASE_DIR,
+ MD_CONFIG_CA_AGREEMENT,
+ MD_CONFIG_DRIVE_MODE,
+ MD_CONFIG_RENEW_WINDOW,
+ MD_CONFIG_WARN_WINDOW,
+ MD_CONFIG_TRANSITIVE,
+ MD_CONFIG_PROXY,
+ MD_CONFIG_REQUIRE_HTTPS,
+ MD_CONFIG_MUST_STAPLE,
+ MD_CONFIG_NOTIFY_CMD,
+ MD_CONFIG_MESSGE_CMD,
+ MD_CONFIG_STAPLING,
+ MD_CONFIG_STAPLE_OTHERS,
+} md_config_var_t;
+
+typedef struct md_mod_conf_t md_mod_conf_t;
+struct md_mod_conf_t {
+ apr_array_header_t *mds; /* all md_t* defined in the config, shared */
+ const char *base_dir; /* base dir for store */
+ const char *proxy_url; /* proxy url to use (or NULL) */
+ struct md_reg_t *reg; /* md registry instance */
+ struct md_ocsp_reg_t *ocsp; /* ocsp status registry */
+
+ int local_80; /* On which port http:80 arrives */
+ int local_443; /* On which port https:443 arrives */
+ int can_http; /* Does someone listen to the local port 80 equivalent? */
+ int can_https; /* Does someone listen to the local port 443 equivalent? */
+ int manage_base_server; /* If base server outside vhost may be managed */
+ int hsts_max_age; /* max-age of HSTS (rfc6797) header */
+ const char *hsts_header; /* computed HTST header to use or NULL */
+ apr_array_header_t *unused_names; /* post config, names of all MDs not assigned to a vhost */
+ struct apr_hash_t *init_errors; /* init errors reported with MD name as key */
+
+ const char *notify_cmd; /* notification command to execute on signup/renew */
+ const char *message_cmd; /* message command to execute on signup/renew/warnings */
+ struct apr_table_t *env; /* environment for operation */
+ int dry_run; /* != 0 iff config dry run */
+ int server_status_enabled; /* if module should add to server-status handler */
+ int certificate_status_enabled; /* if module should expose /.httpd/certificate-status */
+ md_timeslice_t *ocsp_keep_window; /* time that we keep ocsp responses around */
+ md_timeslice_t *ocsp_renew_window; /* time before exp. that we start renewing ocsp resp. */
+ const char *cert_check_name; /* name of the linked certificate check site */
+ const char *cert_check_url; /* url "template for" checking a certificate */
+ const char *ca_certs; /* root certificates to use for connections */
+ apr_time_t min_delay; /* minimum delay for retries */
+ int retry_failover; /* number of errors to trigger CA failover */
+ int use_store_locks; /* use locks when updating store */
+ apr_time_t lock_wait_timeout; /* fail after this time when unable to obtain lock */
+};
+
+typedef struct md_srv_conf_t {
+ const char *name;
+ const server_rec *s; /* server this config belongs to */
+ md_mod_conf_t *mc; /* global config settings */
+
+ int transitive; /* != 0 iff VirtualHost names/aliases are auto-added */
+ md_require_t require_https; /* If MDs require https: access */
+ int renew_mode; /* mode of obtaining credentials */
+ int must_staple; /* certificates should set the OCSP Must Staple extension */
+ struct md_pkeys_spec_t *pks; /* specification for private keys */
+ md_timeslice_t *renew_window; /* time before expiration that starts renewal */
+ md_timeslice_t *warn_window; /* time before expiration that warning are sent out */
+
+ struct apr_array_header_t *ca_urls; /* urls of CAs */
+ const char *ca_contact; /* contact email registered to account */
+ const char *ca_proto; /* protocol used vs CA (e.g. ACME) */
+ const char *ca_agreement; /* accepted agreement uri between CA and user */
+ struct apr_array_header_t *ca_challenges; /* challenge types configured */
+ const char *ca_eab_kid; /* != NULL, external account binding keyid */
+ const char *ca_eab_hmac; /* != NULL, external account binding hmac */
+
+ int stapling; /* OCSP stapling enabled */
+ int staple_others; /* Provide OCSP stapling for non-MD certificates */
+
+ const char *dns01_cmd; /* DNS challenge command, override global command */
+
+ md_t *current; /* md currently defined in <MDomainSet xxx> section */
+ struct apr_array_header_t *assigned; /* post_config: MDs that apply to this server */
+ int is_ssl; /* SSLEngine is enabled here */
+} md_srv_conf_t;
+
+void *md_config_create_svr(apr_pool_t *pool, server_rec *s);
+void *md_config_merge_svr(apr_pool_t *pool, void *basev, void *addv);
+
+extern const command_rec md_cmds[];
+
+apr_status_t md_config_post_config(server_rec *s, apr_pool_t *p);
+
+/* Get the effective md configuration for the connection */
+md_srv_conf_t *md_config_cget(conn_rec *c);
+/* Get the effective md configuration for the server */
+md_srv_conf_t *md_config_get(server_rec *s);
+/* Get the effective md configuration for the server, but make it
+ * unique to this server_rec, so that any changes only affect this server */
+md_srv_conf_t *md_config_get_unique(server_rec *s, apr_pool_t *p);
+
+const char *md_config_gets(const md_srv_conf_t *config, md_config_var_t var);
+int md_config_geti(const md_srv_conf_t *config, md_config_var_t var);
+
+void md_config_get_timespan(md_timeslice_t **pspan, const md_srv_conf_t *sc, md_config_var_t var);
+
+const md_t *md_get_for_domain(server_rec *s, const char *domain);
+
+#endif /* md_config_h */
diff --git a/modules/md/mod_md_drive.c b/modules/md/mod_md_drive.c
new file mode 100644
index 0000000..5565f44
--- /dev/null
+++ b/modules/md/mod_md_drive.c
@@ -0,0 +1,345 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <apr_optional.h>
+#include <apr_hash.h>
+#include <apr_strings.h>
+#include <apr_date.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_protocol.h>
+#include <http_request.h>
+#include <http_log.h>
+
+#include "mod_watchdog.h"
+
+#include "md.h"
+#include "md_curl.h"
+#include "md_crypt.h"
+#include "md_event.h"
+#include "md_http.h"
+#include "md_json.h"
+#include "md_status.h"
+#include "md_store.h"
+#include "md_store_fs.h"
+#include "md_log.h"
+#include "md_result.h"
+#include "md_reg.h"
+#include "md_util.h"
+#include "md_version.h"
+#include "md_acme.h"
+#include "md_acme_authz.h"
+
+#include "mod_md.h"
+#include "mod_md_private.h"
+#include "mod_md_config.h"
+#include "mod_md_status.h"
+#include "mod_md_drive.h"
+
+/**************************************************************************************************/
+/* watchdog based impl. */
+
+#define MD_RENEW_WATCHDOG_NAME "_md_renew_"
+
+static APR_OPTIONAL_FN_TYPE(ap_watchdog_get_instance) *wd_get_instance;
+static APR_OPTIONAL_FN_TYPE(ap_watchdog_register_callback) *wd_register_callback;
+static APR_OPTIONAL_FN_TYPE(ap_watchdog_set_callback_interval) *wd_set_interval;
+
+struct md_renew_ctx_t {
+ apr_pool_t *p;
+ server_rec *s;
+ md_mod_conf_t *mc;
+ ap_watchdog_t *watchdog;
+
+ apr_array_header_t *jobs;
+};
+
+static void process_drive_job(md_renew_ctx_t *dctx, md_job_t *job, apr_pool_t *ptemp)
+{
+ const md_t *md;
+ md_result_t *result = NULL;
+ apr_status_t rv;
+
+ md_job_load(job);
+ /* Evaluate again on loaded value. Values will change when watchdog switches child process */
+ if (apr_time_now() < job->next_run) return;
+
+ job->next_run = 0;
+ if (job->finished && job->notified_renewed) {
+ /* finished and notification handled, nothing to do. */
+ goto leave;
+ }
+
+ md = md_get_by_name(dctx->mc->mds, job->mdomain);
+ AP_DEBUG_ASSERT(md);
+
+ result = md_result_md_make(ptemp, md->name);
+ if (job->last_result) md_result_assign(result, job->last_result);
+
+ if (md->state == MD_S_MISSING_INFORMATION) {
+ /* Missing information, this will not change until configuration
+ * is changed and server reloaded. */
+ job->fatal_error = 1;
+ job->next_run = 0;
+ goto leave;
+ }
+
+ if (md_will_renew_cert(md)) {
+ /* Renew the MDs credentials in a STAGING area. Might be invoked repeatedly
+ * without discarding previous/intermediate results.
+ * Only returns SUCCESS when the renewal is complete, e.g. STAGING has a
+ * complete set of new credentials.
+ */
+ ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO(10052)
+ "md(%s): state=%d, driving", job->mdomain, md->state);
+
+ if (!md_reg_should_renew(dctx->mc->reg, md, dctx->p)) {
+ ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO(10053)
+ "md(%s): no need to renew", job->mdomain);
+ goto expiry;
+ }
+
+ /* The (possibly configured) event handler may veto renewals. This
+ * is used in cluster installtations, see #233. */
+ rv = md_event_raise("renewing", md->name, job, result, ptemp);
+ if (APR_SUCCESS != rv) {
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, dctx->s, APLOGNO(10060)
+ "%s: event-handler for 'renewing' returned %d, preventing renewal to proceed.",
+ job->mdomain, rv);
+ goto leave;
+ }
+
+ md_job_start_run(job, result, md_reg_store_get(dctx->mc->reg));
+ md_reg_renew(dctx->mc->reg, md, dctx->mc->env, 0, job->error_runs, result, ptemp);
+ md_job_end_run(job, result);
+
+ if (APR_SUCCESS == result->status) {
+ /* Finished jobs might take a while before the results become valid.
+ * If that is in the future, request to run then */
+ if (apr_time_now() < result->ready_at) {
+ md_job_retry_at(job, result->ready_at);
+ goto leave;
+ }
+
+ if (!job->notified_renewed) {
+ md_job_save(job, result, ptemp);
+ md_job_notify(job, "renewed", result);
+ }
+ }
+ else {
+ ap_log_error( APLOG_MARK, APLOG_ERR, result->status, dctx->s, APLOGNO(10056)
+ "processing %s: %s", job->mdomain, result->detail);
+ md_job_log_append(job, "renewal-error", result->problem, result->detail);
+ md_event_holler("errored", job->mdomain, job, result, ptemp);
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, dctx->s, APLOGNO(10057)
+ "%s: encountered error for the %d. time, next run in %s",
+ job->mdomain, job->error_runs,
+ md_duration_print(ptemp, job->next_run - apr_time_now()));
+ }
+ }
+
+expiry:
+ if (!job->finished && md_reg_should_warn(dctx->mc->reg, md, dctx->p)) {
+ ap_log_error( APLOG_MARK, APLOG_TRACE1, 0, dctx->s,
+ "md(%s): warn about expiration", md->name);
+ md_job_start_run(job, result, md_reg_store_get(dctx->mc->reg));
+ md_job_notify(job, "expiring", result);
+ md_job_end_run(job, result);
+ }
+
+leave:
+ if (job->dirty && result) {
+ rv = md_job_save(job, result, ptemp);
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, dctx->s, "%s: saving job props", job->mdomain);
+ }
+}
+
+int md_will_renew_cert(const md_t *md)
+{
+ if (md->renew_mode == MD_RENEW_MANUAL) {
+ return 0;
+ }
+ else if (md->renew_mode == MD_RENEW_AUTO && md->cert_files && md->cert_files->nelts) {
+ return 0;
+ }
+ return 1;
+}
+
+static apr_time_t next_run_default(void)
+{
+ /* we'd like to run at least twice a day by default */
+ return apr_time_now() + apr_time_from_sec(MD_SECS_PER_DAY / 2);
+}
+
+static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp)
+{
+ md_renew_ctx_t *dctx = baton;
+ md_job_t *job;
+ apr_time_t next_run, wait_time;
+ int i;
+
+ /* mod_watchdog invoked us as a single thread inside the whole server (on this machine).
+ * This might be a repeated run inside the same child (mod_watchdog keeps affinity as
+ * long as the child lives) or another/new child.
+ */
+ switch (state) {
+ case AP_WATCHDOG_STATE_STARTING:
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO(10054)
+ "md watchdog start, auto drive %d mds", dctx->jobs->nelts);
+ break;
+
+ case AP_WATCHDOG_STATE_RUNNING:
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO(10055)
+ "md watchdog run, auto drive %d mds", dctx->jobs->nelts);
+
+ /* Process all drive jobs. They will update their next_run property
+ * and we schedule ourself at the earliest of all. A job may specify 0
+ * as next_run to indicate that it wants to participate in the normal
+ * regular runs. */
+ next_run = next_run_default();
+ for (i = 0; i < dctx->jobs->nelts; ++i) {
+ job = APR_ARRAY_IDX(dctx->jobs, i, md_job_t *);
+
+ if (apr_time_now() >= job->next_run) {
+ process_drive_job(dctx, job, ptemp);
+ }
+
+ if (job->next_run && job->next_run < next_run) {
+ next_run = job->next_run;
+ }
+ }
+
+ wait_time = next_run - apr_time_now();
+ if (APLOGdebug(dctx->s)) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO(10107)
+ "next run in %s", md_duration_print(ptemp, wait_time));
+ }
+ wd_set_interval(dctx->watchdog, wait_time, dctx, run_watchdog);
+ break;
+
+ case AP_WATCHDOG_STATE_STOPPING:
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO(10058)
+ "md watchdog stopping");
+ break;
+ }
+
+ return APR_SUCCESS;
+}
+
+apr_status_t md_renew_start_watching(md_mod_conf_t *mc, server_rec *s, apr_pool_t *p)
+{
+ apr_allocator_t *allocator;
+ md_renew_ctx_t *dctx;
+ apr_pool_t *dctxp;
+ apr_status_t rv;
+ md_t *md;
+ md_job_t *job;
+ int i;
+
+ /* We use mod_watchdog to run a single thread in one of the child processes
+ * to monitor the MDs marked as watched, using the const data in the list
+ * mc->mds of our MD structures.
+ *
+ * The data in mc cannot be changed, as we may spawn copies in new child processes
+ * of the original data at any time. The child which hosts the watchdog thread
+ * may also die or be recycled, which causes a new watchdog thread to run
+ * in another process with the original data.
+ *
+ * Instead, we use our store to persist changes in group STAGING. This is
+ * kept writable to child processes, but the data stored there is not live.
+ * However, mod_watchdog makes sure that we only ever have a single thread in
+ * our server (on this machine) that writes there. Other processes, e.g. informing
+ * the user about progress, only read from there.
+ *
+ * All changes during driving an MD are stored as files in MG_SG_STAGING/<MD.name>.
+ * All will have "md.json" and "job.json". There may be a range of other files used
+ * by the protocol obtaining the certificate/keys.
+ *
+ *
+ */
+ wd_get_instance = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_get_instance);
+ wd_register_callback = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_register_callback);
+ wd_set_interval = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_set_callback_interval);
+
+ if (!wd_get_instance || !wd_register_callback || !wd_set_interval) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s, APLOGNO(10061) "mod_watchdog is required");
+ return !OK;
+ }
+
+ /* We want our own pool with own allocator to keep data across watchdog invocations.
+ * Since we'll run in a single watchdog thread, using our own allocator will prevent
+ * any confusion in the parent pool. */
+ apr_allocator_create(&allocator);
+ apr_allocator_max_free_set(allocator, 1);
+ rv = apr_pool_create_ex(&dctxp, p, NULL, allocator);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10062) "md_renew_watchdog: create pool");
+ return rv;
+ }
+ apr_allocator_owner_set(allocator, dctxp);
+ apr_pool_tag(dctxp, "md_renew_watchdog");
+
+ dctx = apr_pcalloc(dctxp, sizeof(*dctx));
+ dctx->p = dctxp;
+ dctx->s = s;
+ dctx->mc = mc;
+
+ dctx->jobs = apr_array_make(dctx->p, mc->mds->nelts, sizeof(md_job_t *));
+ for (i = 0; i < mc->mds->nelts; ++i) {
+ md = APR_ARRAY_IDX(mc->mds, i, md_t*);
+ if (!md || !md->watched) continue;
+
+ job = md_reg_job_make(mc->reg, md->name, p);
+ APR_ARRAY_PUSH(dctx->jobs, md_job_t*) = job;
+ ap_log_error( APLOG_MARK, APLOG_TRACE1, 0, dctx->s,
+ "md(%s): state=%d, created drive job", md->name, md->state);
+
+ md_job_load(job);
+ if (job->error_runs) {
+ /* Server has just restarted. If we encounter an MD job with errors
+ * on a previous driving, we purge its STAGING area.
+ * This will reset the driving for the MD. It may run into the same
+ * error again, or in case of race/confusion/our error/CA error, it
+ * might allow the MD to succeed by a fresh start.
+ */
+ ap_log_error( APLOG_MARK, APLOG_NOTICE, 0, dctx->s, APLOGNO(10064)
+ "md(%s): previous drive job showed %d errors, purging STAGING "
+ "area to reset.", md->name, job->error_runs);
+ md_store_purge(md_reg_store_get(dctx->mc->reg), p, MD_SG_STAGING, md->name);
+ md_store_purge(md_reg_store_get(dctx->mc->reg), p, MD_SG_CHALLENGES, md->name);
+ job->error_runs = 0;
+ }
+ }
+
+ if (!dctx->jobs->nelts) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10065)
+ "no managed domain to drive, no watchdog needed.");
+ apr_pool_destroy(dctx->p);
+ return APR_SUCCESS;
+ }
+
+ if (APR_SUCCESS != (rv = wd_get_instance(&dctx->watchdog, MD_RENEW_WATCHDOG_NAME, 0, 1, dctx->p))) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(10066)
+ "create md renew watchdog(%s)", MD_RENEW_WATCHDOG_NAME);
+ return rv;
+ }
+ rv = wd_register_callback(dctx->watchdog, 0, dctx, run_watchdog);
+ ap_log_error(APLOG_MARK, rv? APLOG_CRIT : APLOG_DEBUG, rv, s, APLOGNO(10067)
+ "register md renew watchdog(%s)", MD_RENEW_WATCHDOG_NAME);
+ return rv;
+}
diff --git a/modules/md/mod_md_drive.h b/modules/md/mod_md_drive.h
new file mode 100644
index 0000000..40d6d67
--- /dev/null
+++ b/modules/md/mod_md_drive.h
@@ -0,0 +1,35 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mod_md_md_drive_h
+#define mod_md_md_drive_h
+
+struct md_mod_conf_t;
+struct md_reg_t;
+
+typedef struct md_renew_ctx_t md_renew_ctx_t;
+
+int md_will_renew_cert(const md_t *md);
+
+/**
+ * Start driving the certificate renewal for MDs marked with watched.
+ */
+apr_status_t md_renew_start_watching(struct md_mod_conf_t *mc, server_rec *s, apr_pool_t *p);
+
+
+
+
+#endif /* mod_md_md_drive_h */
diff --git a/modules/md/mod_md_ocsp.c b/modules/md/mod_md_ocsp.c
new file mode 100644
index 0000000..1d1e282
--- /dev/null
+++ b/modules/md/mod_md_ocsp.c
@@ -0,0 +1,272 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <apr_optional.h>
+#include <apr_time.h>
+#include <apr_date.h>
+#include <apr_strings.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_log.h>
+#include <http_ssl.h>
+
+#include "mod_watchdog.h"
+
+#include "md.h"
+#include "md_crypt.h"
+#include "md_http.h"
+#include "md_json.h"
+#include "md_ocsp.h"
+#include "md_store.h"
+#include "md_log.h"
+#include "md_reg.h"
+#include "md_time.h"
+#include "md_util.h"
+
+#include "mod_md.h"
+#include "mod_md_config.h"
+#include "mod_md_private.h"
+#include "mod_md_ocsp.h"
+
+static int staple_here(md_srv_conf_t *sc)
+{
+ if (!sc || !sc->mc->ocsp) return 0;
+ if (sc->assigned
+ && sc->assigned->nelts == 1
+ && APR_ARRAY_IDX(sc->assigned, 0, const md_t*)->stapling) return 1;
+ return (md_config_geti(sc, MD_CONFIG_STAPLING)
+ && md_config_geti(sc, MD_CONFIG_STAPLE_OTHERS));
+}
+
+int md_ocsp_prime_status(server_rec *s, apr_pool_t *p,
+ const char *id, apr_size_t id_len, const char *pem)
+{
+ md_srv_conf_t *sc;
+ const md_t *md;
+ apr_array_header_t *chain;
+ apr_status_t rv = APR_ENOENT;
+
+ sc = md_config_get(s);
+ if (!staple_here(sc)) goto cleanup;
+
+ md = ((sc->assigned && sc->assigned->nelts == 1)?
+ APR_ARRAY_IDX(sc->assigned, 0, const md_t*) : NULL);
+ chain = apr_array_make(p, 5, sizeof(md_cert_t*));
+ rv = md_cert_read_chain(chain, p, pem, strlen(pem));
+ if (APR_SUCCESS != rv) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10268) "init stapling for: %s, "
+ "unable to parse PEM data", md? md->name : s->server_hostname);
+ goto cleanup;
+ }
+ else if (chain->nelts < 2) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10269) "init stapling for: %s, "
+ "need at least 2 certificates in PEM data", md? md->name : s->server_hostname);
+ rv = APR_EINVAL;
+ goto cleanup;
+ }
+
+ rv = md_ocsp_prime(sc->mc->ocsp, id, id_len,
+ APR_ARRAY_IDX(chain, 0, md_cert_t*),
+ APR_ARRAY_IDX(chain, 1, md_cert_t*), md);
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, s, "init stapling for: %s",
+ md? md->name : s->server_hostname);
+
+cleanup:
+ return (APR_SUCCESS == rv)? OK : DECLINED;
+}
+
+typedef struct {
+ unsigned char *der;
+ apr_size_t der_len;
+} ocsp_copy_ctx_t;
+
+int md_ocsp_provide_status(server_rec *s, conn_rec *c,
+ const char *id, apr_size_t id_len,
+ ap_ssl_ocsp_copy_resp *cb, void *userdata)
+{
+ md_srv_conf_t *sc;
+ const md_t *md;
+ apr_status_t rv;
+
+ sc = md_config_get(s);
+ if (!staple_here(sc)) goto declined;
+
+ md = ((sc->assigned && sc->assigned->nelts == 1)?
+ APR_ARRAY_IDX(sc->assigned, 0, const md_t*) : NULL);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "get stapling for: %s",
+ md? md->name : s->server_hostname);
+
+ rv = md_ocsp_get_status(cb, userdata, sc->mc->ocsp, id, id_len, c->pool, md);
+ if (APR_STATUS_IS_ENOENT(rv)) goto declined;
+ return OK;
+
+declined:
+ return DECLINED;
+}
+
+
+/**************************************************************************************************/
+/* watchdog based impl. */
+
+#define MD_OCSP_WATCHDOG_NAME "_md_ocsp_"
+
+static APR_OPTIONAL_FN_TYPE(ap_watchdog_get_instance) *wd_get_instance;
+static APR_OPTIONAL_FN_TYPE(ap_watchdog_register_callback) *wd_register_callback;
+static APR_OPTIONAL_FN_TYPE(ap_watchdog_set_callback_interval) *wd_set_interval;
+
+typedef struct md_ocsp_ctx_t md_ocsp_ctx_t;
+
+struct md_ocsp_ctx_t {
+ apr_pool_t *p;
+ server_rec *s;
+ md_mod_conf_t *mc;
+ ap_watchdog_t *watchdog;
+};
+
+static apr_time_t next_run_default(void)
+{
+ /* we'd like to run at least hourly */
+ return apr_time_now() + apr_time_from_sec(MD_SECS_PER_HOUR);
+}
+
+static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp)
+{
+ md_ocsp_ctx_t *octx = baton;
+ apr_time_t next_run, wait_time;
+
+ /* mod_watchdog invoked us as a single thread inside the whole server (on this machine).
+ * This might be a repeated run inside the same child (mod_watchdog keeps affinity as
+ * long as the child lives) or another/new child.
+ */
+ switch (state) {
+ case AP_WATCHDOG_STATE_STARTING:
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, octx->s, APLOGNO(10197)
+ "md ocsp watchdog start, ocsp stapling %d certificates",
+ (int)md_ocsp_count(octx->mc->ocsp));
+ break;
+
+ case AP_WATCHDOG_STATE_RUNNING:
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, octx->s, APLOGNO(10198)
+ "md ocsp watchdog run, ocsp stapling %d certificates",
+ (int)md_ocsp_count(octx->mc->ocsp));
+
+ /* Process all drive jobs. They will update their next_run property
+ * and we schedule ourself at the earliest of all. A job may specify 0
+ * as next_run to indicate that it wants to participate in the normal
+ * regular runs. */
+ next_run = next_run_default();
+
+ md_ocsp_renew(octx->mc->ocsp, octx->p, ptemp, &next_run);
+
+ wait_time = next_run - apr_time_now();
+ if (APLOGdebug(octx->s)) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, octx->s, APLOGNO(10199)
+ "md ocsp watchdog next run in %s",
+ md_duration_print(ptemp, wait_time));
+ }
+ wd_set_interval(octx->watchdog, wait_time, octx, run_watchdog);
+ break;
+
+ case AP_WATCHDOG_STATE_STOPPING:
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, octx->s, APLOGNO(10200)
+ "md ocsp watchdog stopping");
+ break;
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t ocsp_remove_old_responses(md_mod_conf_t *mc, apr_pool_t *p)
+{
+ md_timeperiod_t keep_norm, keep;
+
+ keep_norm.end = apr_time_now();
+ keep_norm.start = keep_norm.end - MD_TIME_OCSP_KEEP_NORM;
+ keep = md_timeperiod_slice_before_end(&keep_norm, mc->ocsp_keep_window);
+ /* remove any ocsp response older than keep.start */
+ return md_ocsp_remove_responses_older_than(mc->ocsp, p, keep.start);
+}
+
+apr_status_t md_ocsp_start_watching(md_mod_conf_t *mc, server_rec *s, apr_pool_t *p)
+{
+ apr_allocator_t *allocator;
+ md_ocsp_ctx_t *octx;
+ apr_pool_t *octxp;
+ apr_status_t rv;
+
+ wd_get_instance = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_get_instance);
+ wd_register_callback = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_register_callback);
+ wd_set_interval = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_set_callback_interval);
+
+ if (!wd_get_instance || !wd_register_callback || !wd_set_interval) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s, APLOGNO(10201)
+ "mod_watchdog is required for OCSP stapling");
+ return APR_EGENERAL;
+ }
+
+ /* We want our own pool with own allocator to keep data across watchdog invocations.
+ * Since we'll run in a single watchdog thread, using our own allocator will prevent
+ * any confusion in the parent pool. */
+ apr_allocator_create(&allocator);
+ apr_allocator_max_free_set(allocator, 1);
+ rv = apr_pool_create_ex(&octxp, p, NULL, allocator);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10205) "md_ocsp_watchdog: create pool");
+ return rv;
+ }
+ apr_allocator_owner_set(allocator, octxp);
+ apr_pool_tag(octxp, "md_ocsp_watchdog");
+
+ octx = apr_pcalloc(octxp, sizeof(*octx));
+ octx->p = octxp;
+ octx->s = s;
+ octx->mc = mc;
+
+ /* Time for some house keeping, before the server goes live (again):
+ * - we store OCSP responses for each certificate individually by its SHA-1 id
+ * - this means, as long as certificate do not change, the number of response
+ * files remains stable.
+ * - But when a certificate changes (is replaced), the response is obsolete
+ * - we do not get notified when a certificate is no longer used. An admin
+ * might just reconfigure or change the content of a file (backup/restore etc.)
+ * - also, certificates might be added by some openssl config commands or other
+ * modules that we do not immediately see right at startup. We cannot assume
+ * that any OCSP response we cannot relate to a certificate RIGHT NOW, is no
+ * longer needed.
+ * - since the response files are relatively small, we have no problem with
+ * keeping them around for a while. We just do not want an ever growing store.
+ * - The simplest and effective way seems to be to just remove files older
+ * a certain amount of time. Take a 7 day default and let the admin configure
+ * it for very special setups.
+ */
+ ocsp_remove_old_responses(mc, octx->p);
+
+ rv = wd_get_instance(&octx->watchdog, MD_OCSP_WATCHDOG_NAME, 0, 1, octx->p);
+ if (APR_SUCCESS != rv) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(10202)
+ "create md ocsp watchdog(%s)", MD_OCSP_WATCHDOG_NAME);
+ return rv;
+ }
+ rv = wd_register_callback(octx->watchdog, 0, octx, run_watchdog);
+ ap_log_error(APLOG_MARK, rv? APLOG_CRIT : APLOG_DEBUG, rv, s, APLOGNO(10203)
+ "register md ocsp watchdog(%s)", MD_OCSP_WATCHDOG_NAME);
+ return rv;
+}
+
+
+
diff --git a/modules/md/mod_md_ocsp.h b/modules/md/mod_md_ocsp.h
new file mode 100644
index 0000000..a3f9502
--- /dev/null
+++ b/modules/md/mod_md_ocsp.h
@@ -0,0 +1,33 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mod_md_md_ocsp_h
+#define mod_md_md_ocsp_h
+
+
+int md_ocsp_prime_status(server_rec *s, apr_pool_t *p,
+ const char *id, apr_size_t id_len, const char *pem);
+
+int md_ocsp_provide_status(server_rec *s, conn_rec *c, const char *id, apr_size_t id_len,
+ ap_ssl_ocsp_copy_resp *cb, void *userdata);
+
+/**
+ * Start watchdog for retrieving/updating ocsp status.
+ */
+apr_status_t md_ocsp_start_watching(struct md_mod_conf_t *mc, server_rec *s, apr_pool_t *p);
+
+
+#endif /* mod_md_md_ocsp_h */
diff --git a/modules/md/mod_md_os.c b/modules/md/mod_md_os.c
new file mode 100644
index 0000000..06a5bee
--- /dev/null
+++ b/modules/md/mod_md_os.c
@@ -0,0 +1,88 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <apr_strings.h>
+
+#include <mpm_common.h>
+#include <httpd.h>
+#include <http_log.h>
+#include <ap_mpm.h>
+
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if AP_NEED_SET_MUTEX_PERMS
+#include "unixd.h"
+#endif
+
+#include "md_util.h"
+#include "mod_md_os.h"
+
+apr_status_t md_try_chown(const char *fname, unsigned int uid, int gid, apr_pool_t *p)
+{
+#if AP_NEED_SET_MUTEX_PERMS && HAVE_UNISTD_H
+ /* Since we only switch user when running as root, we only need to chown directories
+ * in that case. Otherwise, the server will ignore any "user/group" directives and
+ * child processes have the same privileges as the parent.
+ */
+ if (!geteuid()) {
+ if (-1 == chown(fname, (uid_t)uid, (gid_t)gid)) {
+ apr_status_t rv = APR_FROM_OS_ERROR(errno);
+ if (!APR_STATUS_IS_ENOENT(rv)) {
+ ap_log_perror(APLOG_MARK, APLOG_ERR, rv, p, APLOGNO(10082)
+ "Can't change owner of %s", fname);
+ }
+ return rv;
+ }
+ }
+ return APR_SUCCESS;
+#else
+ return APR_ENOTIMPL;
+#endif
+}
+
+apr_status_t md_make_worker_accessible(const char *fname, apr_pool_t *p)
+{
+#ifdef WIN32
+ return APR_ENOTIMPL;
+#else
+ return md_try_chown(fname, ap_unixd_config.user_id, -1, p);
+#endif
+}
+
+#ifdef WIN32
+
+apr_status_t md_server_graceful(apr_pool_t *p, server_rec *s)
+{
+ return APR_ENOTIMPL;
+}
+
+#else
+
+apr_status_t md_server_graceful(apr_pool_t *p, server_rec *s)
+{
+ apr_status_t rv;
+
+ (void)p;
+ (void)s;
+ rv = (kill(getppid(), AP_SIG_GRACEFUL) < 0)? APR_ENOTIMPL : APR_SUCCESS;
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, errno, NULL, "sent signal to parent");
+ return rv;
+}
+
+#endif
+
diff --git a/modules/md/mod_md_os.h b/modules/md/mod_md_os.h
new file mode 100644
index 0000000..3085076
--- /dev/null
+++ b/modules/md/mod_md_os.h
@@ -0,0 +1,37 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mod_md_md_os_h
+#define mod_md_md_os_h
+
+/**
+ * Try chown'ing the file/directory. Give id -1 to not change uid/gid.
+ * Will return APR_ENOTIMPL on platforms not supporting this operation.
+ */
+apr_status_t md_try_chown(const char *fname, unsigned int uid, int gid, apr_pool_t *p);
+
+/**
+ * Make a file or directory read/write(/searchable) by httpd workers.
+ */
+apr_status_t md_make_worker_accessible(const char *fname, apr_pool_t *p);
+
+/**
+ * Trigger a graceful restart of the server. Depending on the architecture, may
+ * return APR_ENOTIMPL.
+ */
+apr_status_t md_server_graceful(apr_pool_t *p, server_rec *s);
+
+#endif /* mod_md_md_os_h */
diff --git a/modules/md/mod_md_private.h b/modules/md/mod_md_private.h
new file mode 100644
index 0000000..45521ea
--- /dev/null
+++ b/modules/md/mod_md_private.h
@@ -0,0 +1,24 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mod_md_md_private_h
+#define mod_md_md_private_h
+
+extern module AP_MODULE_DECLARE_DATA md_module;
+
+APLOG_USE_MODULE(md);
+
+#endif
diff --git a/modules/md/mod_md_status.c b/modules/md/mod_md_status.c
new file mode 100644
index 0000000..2286051
--- /dev/null
+++ b/modules/md/mod_md_status.c
@@ -0,0 +1,987 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <apr_optional.h>
+#include <apr_time.h>
+#include <apr_date.h>
+#include <apr_strings.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_protocol.h>
+#include <http_request.h>
+#include <http_log.h>
+
+#include "mod_status.h"
+
+#include "md.h"
+#include "md_curl.h"
+#include "md_crypt.h"
+#include "md_http.h"
+#include "md_ocsp.h"
+#include "md_json.h"
+#include "md_status.h"
+#include "md_store.h"
+#include "md_store_fs.h"
+#include "md_log.h"
+#include "md_reg.h"
+#include "md_util.h"
+#include "md_version.h"
+#include "md_acme.h"
+#include "md_acme_authz.h"
+
+#include "mod_md.h"
+#include "mod_md_private.h"
+#include "mod_md_config.h"
+#include "mod_md_drive.h"
+#include "mod_md_status.h"
+
+/**************************************************************************************************/
+/* Certificate status */
+
+#define APACHE_PREFIX "/.httpd/"
+#define MD_STATUS_RESOURCE APACHE_PREFIX"certificate-status"
+#define HTML_STATUS(X) (!((X)->flags & AP_STATUS_SHORT))
+
+int md_http_cert_status(request_rec *r)
+{
+ int i;
+ md_json_t *resp, *mdj, *cj;
+ const md_srv_conf_t *sc;
+ const md_t *md;
+ md_pkey_spec_t *spec;
+ const char *keyname;
+ apr_bucket_brigade *bb;
+ apr_status_t rv;
+
+ if (!r->parsed_uri.path || strcmp(MD_STATUS_RESOURCE, r->parsed_uri.path))
+ return DECLINED;
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+ "requesting status for: %s", r->hostname);
+
+ /* We are looking for information about a staged certificate */
+ sc = ap_get_module_config(r->server->module_config, &md_module);
+ if (!sc || !sc->mc || !sc->mc->reg || !sc->mc->certificate_status_enabled) return DECLINED;
+ md = md_get_by_domain(sc->mc->mds, r->hostname);
+ if (!md) return DECLINED;
+
+ if (r->method_number != M_GET) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+ "md(%s): status supports only GET", md->name);
+ return HTTP_NOT_IMPLEMENTED;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+ "requesting status for MD: %s", md->name);
+
+ rv = md_status_get_md_json(&mdj, md, sc->mc->reg, sc->mc->ocsp, r->pool);
+ if (APR_SUCCESS != rv) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10204)
+ "loading md status for %s", md->name);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+ "status for MD: %s is %s", md->name, md_json_writep(mdj, r->pool, MD_JSON_FMT_INDENT));
+
+ resp = md_json_create(r->pool);
+
+ if (md_json_has_key(mdj, MD_KEY_CERT, MD_KEY_VALID, NULL)) {
+ md_json_setj(md_json_getj(mdj, MD_KEY_CERT, MD_KEY_VALID, NULL), resp, MD_KEY_VALID, NULL);
+ }
+
+ for (i = 0; i < md_cert_count(md); ++i) {
+ spec = md_pkeys_spec_get(md->pks, i);
+ keyname = md_pkey_spec_name(spec);
+ cj = md_json_create(r->pool);
+
+ if (md_json_has_key(mdj, MD_KEY_CERT, keyname, MD_KEY_VALID, NULL)) {
+ md_json_setj(md_json_getj(mdj, MD_KEY_CERT, keyname, MD_KEY_VALID, NULL),
+ cj, MD_KEY_VALID, NULL);
+ }
+
+ if (md_json_has_key(mdj, MD_KEY_CERT, keyname, MD_KEY_SERIAL, NULL)) {
+ md_json_sets(md_json_gets(mdj, MD_KEY_CERT, keyname, MD_KEY_SERIAL, NULL),
+ cj, MD_KEY_SERIAL, NULL);
+ }
+ if (md_json_has_key(mdj, MD_KEY_CERT, keyname, MD_KEY_SHA256_FINGERPRINT, NULL)) {
+ md_json_sets(md_json_gets(mdj, MD_KEY_CERT, keyname, MD_KEY_SHA256_FINGERPRINT, NULL),
+ cj, MD_KEY_SHA256_FINGERPRINT, NULL);
+ }
+ md_json_setj(cj, resp, keyname, NULL );
+ }
+
+ if (md_json_has_key(mdj, MD_KEY_RENEWAL, NULL)) {
+ /* copy over the information we want to make public about this:
+ * - when not finished, add an empty object to indicate something is going on
+ * - when a certificate is staged, add the information from that */
+ cj = md_json_getj(mdj, MD_KEY_RENEWAL, MD_KEY_CERT, NULL);
+ cj = cj? cj : md_json_create(r->pool);
+ md_json_setj(cj, resp, MD_KEY_RENEWAL, MD_KEY_CERT, NULL);
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "md[%s]: sending status", md->name);
+ apr_table_set(r->headers_out, "Content-Type", "application/json");
+ bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+ md_json_writeb(resp, MD_JSON_FMT_INDENT, bb);
+ ap_pass_brigade(r->output_filters, bb);
+ apr_brigade_cleanup(bb);
+
+ return DONE;
+}
+
+/**************************************************************************************************/
+/* Status hook */
+
+typedef struct {
+ apr_pool_t *p;
+ const md_mod_conf_t *mc;
+ apr_bucket_brigade *bb;
+ int flags;
+ const char *prefix;
+ const char *separator;
+} status_ctx;
+
+typedef struct status_info status_info;
+
+static void add_json_val(status_ctx *ctx, md_json_t *j);
+
+typedef void add_status_fn(status_ctx *ctx, md_json_t *mdj, const status_info *info);
+
+struct status_info {
+ const char *label;
+ const char *key;
+ add_status_fn *fn;
+};
+
+static void si_val_status(status_ctx *ctx, md_json_t *mdj, const status_info *info)
+{
+ const char *s = "unknown";
+ apr_time_t until;
+ (void)info;
+ switch (md_json_getl(mdj, info->key, NULL)) {
+ case MD_S_INCOMPLETE:
+ s = md_json_gets(mdj, MD_KEY_STATE_DESCR, NULL);
+ s = s? apr_psprintf(ctx->p, "incomplete: %s", s) : "incomplete";
+ break;
+ case MD_S_EXPIRED_DEPRECATED:
+ case MD_S_COMPLETE:
+ until = md_json_get_time(mdj, MD_KEY_CERT, MD_KEY_VALID, MD_KEY_UNTIL, NULL);
+ s = (!until || until > apr_time_now())? "good" : "expired";
+ break;
+ case MD_S_ERROR: s = "error"; break;
+ case MD_S_MISSING_INFORMATION: s = "missing information"; break;
+ default: break;
+ }
+ if (HTML_STATUS(ctx)) {
+ apr_brigade_puts(ctx->bb, NULL, NULL, s);
+ }
+ else {
+ apr_brigade_printf(ctx->bb, NULL, NULL, "%s%s: %s\n",
+ ctx->prefix, info->label, s);
+ }
+}
+
+static void si_val_url(status_ctx *ctx, md_json_t *mdj, const status_info *info)
+{
+ const char *url, *s;
+
+ s = url = md_json_gets(mdj, info->key, NULL);
+ if (!url) return;
+ s = md_get_ca_name_from_url(ctx->p, url);
+ if (HTML_STATUS(ctx)) {
+ apr_brigade_printf(ctx->bb, NULL, NULL, "<a href='%s'>%s</a>",
+ ap_escape_html2(ctx->p, url, 1),
+ ap_escape_html2(ctx->p, s, 1));
+ }
+ else {
+ apr_brigade_printf(ctx->bb, NULL, NULL, "%s%sName: %s\n",
+ ctx->prefix, info->label, s);
+ apr_brigade_printf(ctx->bb, NULL, NULL, "%s%sURL: %s\n",
+ ctx->prefix, info->label, url);
+ }
+}
+
+static void print_date(status_ctx *ctx, apr_time_t timestamp, const char *title)
+{
+ apr_bucket_brigade *bb = ctx->bb;
+ if (timestamp > 0) {
+ char ts[128];
+ char ts2[128];
+ apr_time_exp_t texp;
+ apr_size_t len;
+
+ apr_time_exp_gmt(&texp, timestamp);
+ apr_strftime(ts, &len, sizeof(ts2)-1, "%Y-%m-%d", &texp);
+ ts[len] = '\0';
+ if (!title) {
+ apr_strftime(ts2, &len, sizeof(ts)-1, "%Y-%m-%dT%H:%M:%SZ", &texp);
+ ts2[len] = '\0';
+ title = ts2;
+ }
+ if (HTML_STATUS(ctx)) {
+ apr_brigade_printf(bb, NULL, NULL,
+ "<span title='%s' style='white-space: nowrap;'>%s</span>",
+ ap_escape_html2(bb->p, title, 1), ts);
+ }
+ else {
+ apr_brigade_printf(bb, NULL, NULL, "%s%s: %s\n",
+ ctx->prefix, title, ts);
+ }
+ }
+}
+
+static void print_time(status_ctx *ctx, const char *label, apr_time_t t)
+{
+ apr_bucket_brigade *bb = ctx->bb;
+ apr_time_t now;
+ const char *pre, *post, *sep;
+ char ts[APR_RFC822_DATE_LEN];
+ char ts2[128];
+ apr_time_exp_t texp;
+ apr_size_t len;
+ apr_interval_time_t delta;
+
+ if (t == 0) {
+ /* timestamp is 0, we use that for "not set" */
+ return;
+ }
+ apr_time_exp_gmt(&texp, t);
+ now = apr_time_now();
+ pre = post = "";
+ sep = (label && strlen(label))? " " : "";
+ delta = 0;
+ if (HTML_STATUS(ctx)) {
+ apr_rfc822_date(ts, t);
+ if (t > now) {
+ delta = t - now;
+ pre = "in ";
+ }
+ else {
+ delta = now - t;
+ post = " ago";
+ }
+ if (delta >= (4 * apr_time_from_sec(MD_SECS_PER_DAY))) {
+ apr_strftime(ts2, &len, sizeof(ts2)-1, "%Y-%m-%d", &texp);
+ ts2[len] = '\0';
+ apr_brigade_printf(bb, NULL, NULL, "%s%s<span title='%s' "
+ "style='white-space: nowrap;'>%s</span>",
+ label, sep, ts, ts2);
+ }
+ else {
+ apr_brigade_printf(bb, NULL, NULL, "%s%s<span title='%s'>%s%s%s</span>",
+ label, sep, ts, pre, md_duration_roughly(bb->p, delta), post);
+ }
+ }
+ else {
+ delta = t - now;
+ apr_brigade_printf(bb, NULL, NULL, "%s%s: %" APR_TIME_T_FMT "\n",
+ ctx->prefix, label, apr_time_sec(delta));
+ }
+}
+
+static void si_val_valid_time(status_ctx *ctx, md_json_t *mdj, const status_info *info)
+{
+ const char *sfrom, *suntil, *sep, *title;
+ apr_time_t from, until;
+
+ sep = NULL;
+ sfrom = md_json_gets(mdj, info->key, MD_KEY_FROM, NULL);
+ from = sfrom? apr_date_parse_rfc(sfrom) : 0;
+ suntil = md_json_gets(mdj, info->key, MD_KEY_UNTIL, NULL);
+ until = suntil?apr_date_parse_rfc(suntil) : 0;
+
+ if (HTML_STATUS(ctx)) {
+ if (from > apr_time_now()) {
+ apr_brigade_puts(ctx->bb, NULL, NULL, "from ");
+ print_date(ctx, from, sfrom);
+ sep = " ";
+ }
+ if (until) {
+ if (sep) apr_brigade_puts(ctx->bb, NULL, NULL, sep);
+ apr_brigade_puts(ctx->bb, NULL, NULL, "until ");
+ title = sfrom? apr_psprintf(ctx->p, "%s - %s", sfrom, suntil) : suntil;
+ print_date(ctx, until, title);
+ }
+ }
+ else {
+ if (from > apr_time_now()) {
+ print_date(ctx, from,
+ apr_pstrcat(ctx->p, info->label, "From", NULL));
+ }
+ if (until) {
+ print_date(ctx, from,
+ apr_pstrcat(ctx->p, info->label, "Until", NULL));
+ }
+ }
+}
+
+static void si_add_header(status_ctx *ctx, const status_info *info)
+{
+ if (HTML_STATUS(ctx)) {
+ const char *html = ap_escape_html2(ctx->p, info->label, 1);
+ apr_brigade_printf(ctx->bb, NULL, NULL, "<th class=\"%s\">%s</th>", html, html);
+ }
+}
+
+static void si_val_cert_valid_time(status_ctx *ctx, md_json_t *mdj, const status_info *info)
+{
+ md_json_t *jcert;
+ status_info sub = *info;
+
+ sub.key = MD_KEY_VALID;
+ jcert = md_json_getj(mdj, info->key, NULL);
+ if (jcert) si_val_valid_time(ctx, jcert, &sub);
+}
+
+static void val_url_print(status_ctx *ctx, const status_info *info,
+ const char*url, const char *proto, int i)
+{
+ const char *s;
+
+ if (proto && !strcmp(proto, "tailscale")) {
+ s = "tailscale";
+ }
+ else if (url) {
+ s = md_get_ca_name_from_url(ctx->p, url);
+ }
+ else {
+ return;
+ }
+ if (HTML_STATUS(ctx)) {
+ apr_brigade_printf(ctx->bb, NULL, NULL, "%s<a href='%s'>%s</a>",
+ i? " " : "",
+ ap_escape_html2(ctx->p, url, 1),
+ ap_escape_html2(ctx->p, s, 1));
+ }
+ else if (i == 0) {
+ apr_brigade_printf(ctx->bb, NULL, NULL, "%s%sName: %s\n",
+ ctx->prefix, info->label, s);
+ apr_brigade_printf(ctx->bb, NULL, NULL, "%s%sURL: %s\n",
+ ctx->prefix, info->label, url);
+ }
+ else {
+ apr_brigade_printf(ctx->bb, NULL, NULL, "%s%sName%d: %s\n",
+ ctx->prefix, info->label, i, s);
+ apr_brigade_printf(ctx->bb, NULL, NULL, "%s%sURL%d: %s\n",
+ ctx->prefix, info->label, i, url);
+ }
+}
+
+static void si_val_ca_urls(status_ctx *ctx, md_json_t *mdj, const status_info *info)
+{
+ md_json_t *jcert;
+ const char *proto, *url;
+ apr_array_header_t *urls;
+ int i;
+
+ jcert = md_json_getj(mdj, info->key, NULL);
+ if (!jcert) {
+ return;
+ }
+
+ proto = md_json_gets(jcert, MD_KEY_PROTO, NULL);
+ url = md_json_gets(jcert, MD_KEY_URL, NULL);
+ if (url) {
+ /* print the effective CA url used, if set */
+ val_url_print(ctx, info, url, proto, 0);
+ }
+ else {
+ /* print the available CA urls configured */
+ urls = apr_array_make(ctx->p, 3, sizeof(const char*));
+ md_json_getsa(urls, jcert, MD_KEY_URLS, NULL);
+ for (i = 0; i < urls->nelts; ++i) {
+ url = APR_ARRAY_IDX(urls, i, const char*);
+ val_url_print(ctx, info, url, proto, i);
+ }
+ }
+}
+
+static int count_certs(void *baton, const char *key, md_json_t *json)
+{
+ int *pcount = baton;
+
+ (void)json;
+ if (strcmp(key, MD_KEY_VALID)) {
+ *pcount += 1;
+ }
+ return 1;
+}
+
+static void print_job_summary(status_ctx *ctx, md_json_t *mdj, const char *key,
+ const char *separator)
+{
+ apr_bucket_brigade *bb = ctx->bb;
+ char buffer[HUGE_STRING_LEN];
+ apr_status_t rv;
+ int finished, errors, cert_count;
+ apr_time_t t;
+ const char *s, *line;
+
+ if (!md_json_has_key(mdj, key, NULL)) {
+ return;
+ }
+
+ finished = md_json_getb(mdj, key, MD_KEY_FINISHED, NULL);
+ errors = (int)md_json_getl(mdj, key, MD_KEY_ERRORS, NULL);
+ rv = (apr_status_t)md_json_getl(mdj, key, MD_KEY_LAST, MD_KEY_STATUS, NULL);
+
+ line = separator? separator : "";
+
+ if (rv != APR_SUCCESS) {
+ char *errstr = apr_strerror(rv, buffer, sizeof(buffer));
+ s = md_json_gets(mdj, key, MD_KEY_LAST, MD_KEY_PROBLEM, NULL);
+ if (HTML_STATUS(ctx)) {
+ line = apr_psprintf(bb->p, "%s Error[%s]: %s", line,
+ errstr, s? s : "");
+ }
+ else {
+ apr_brigade_printf(bb, NULL, NULL, "%sLastStatus: %s\n", ctx->prefix, errstr);
+ apr_brigade_printf(bb, NULL, NULL, "%sLastProblem: %s\n", ctx->prefix, s);
+ }
+ }
+
+ if (!HTML_STATUS(ctx)) {
+ apr_brigade_printf(bb, NULL, NULL, "%sFinished: %s\n", ctx->prefix,
+ finished ? "yes" : "no");
+ }
+ if (finished) {
+ cert_count = 0;
+ md_json_iterkey(count_certs, &cert_count, mdj, key, MD_KEY_CERT, NULL);
+ if (HTML_STATUS(ctx)) {
+ if (cert_count > 0) {
+ line =apr_psprintf(bb->p, "%s finished, %d new certificate%s staged.",
+ line, cert_count, cert_count > 1? "s" : "");
+ }
+ else {
+ line = apr_psprintf(bb->p, "%s finished successfully.", line);
+ }
+ }
+ else {
+ apr_brigade_printf(bb, NULL, NULL, "%sNewStaged: %d\n", ctx->prefix, cert_count);
+ }
+ }
+ else {
+ s = md_json_gets(mdj, key, MD_KEY_LAST, MD_KEY_DETAIL, NULL);
+ if (s) {
+ if (HTML_STATUS(ctx)) {
+ line = apr_psprintf(bb->p, "%s %s", line, s);
+ }
+ else {
+ apr_brigade_printf(bb, NULL, NULL, "%sLastDetail: %s\n", ctx->prefix, s);
+ }
+ }
+ }
+
+ errors = (int)md_json_getl(mdj, MD_KEY_ERRORS, NULL);
+ if (errors > 0) {
+ if (HTML_STATUS(ctx)) {
+ line = apr_psprintf(bb->p, "%s (%d retr%s) ", line,
+ errors, (errors > 1)? "y" : "ies");
+ }
+ else {
+ apr_brigade_printf(bb, NULL, NULL, "%sRetries: %d\n", ctx->prefix, errors);
+ }
+ }
+
+ if (HTML_STATUS(ctx)) {
+ apr_brigade_puts(bb, NULL, NULL, line);
+ }
+
+ t = md_json_get_time(mdj, key, MD_KEY_NEXT_RUN, NULL);
+ if (t > apr_time_now() && !finished) {
+ print_time(ctx,
+ HTML_STATUS(ctx) ? "\nNext run" : "NextRun",
+ t);
+ }
+ else if (line[0] != '\0') {
+ if (HTML_STATUS(ctx)) {
+ apr_brigade_puts(bb, NULL, NULL, "\nOngoing...");
+ }
+ else {
+ apr_brigade_printf(bb, NULL, NULL, "%s: Ongoing\n", ctx->prefix);
+ }
+ }
+}
+
+static void si_val_activity(status_ctx *ctx, md_json_t *mdj, const status_info *info)
+{
+ apr_time_t t;
+ const char *prefix = ctx->prefix;
+
+ (void)info;
+ if (!HTML_STATUS(ctx)) {
+ ctx->prefix = apr_pstrcat(ctx->p, prefix, info->label, NULL);
+ }
+
+ if (md_json_has_key(mdj, MD_KEY_RENEWAL, NULL)) {
+ print_job_summary(ctx, mdj, MD_KEY_RENEWAL, NULL);
+ return;
+ }
+
+ t = md_json_get_time(mdj, MD_KEY_RENEW_AT, NULL);
+ if (t > apr_time_now()) {
+ print_time(ctx, "Renew", t);
+ }
+ else if (t) {
+ if (HTML_STATUS(ctx)) {
+ apr_brigade_puts(ctx->bb, NULL, NULL, "Pending");
+ }
+ else {
+ apr_brigade_printf(ctx->bb, NULL, NULL, "%s: %s", ctx->prefix, "Pending");
+ }
+ }
+ else if (MD_RENEW_MANUAL == md_json_getl(mdj, MD_KEY_RENEW_MODE, NULL)) {
+ if (HTML_STATUS(ctx)) {
+ apr_brigade_puts(ctx->bb, NULL, NULL, "Manual renew");
+ }
+ else {
+ apr_brigade_printf(ctx->bb, NULL, NULL, "%s: %s", ctx->prefix, "Manual renew");
+ }
+ }
+ if (!HTML_STATUS(ctx)) {
+ ctx->prefix = prefix;
+ }
+}
+
+static int cert_check_iter(void *baton, const char *key, md_json_t *json)
+{
+ status_ctx *ctx = baton;
+ const char *fingerprint;
+
+ fingerprint = md_json_gets(json, MD_KEY_SHA256_FINGERPRINT, NULL);
+ if (fingerprint) {
+ if (HTML_STATUS(ctx)) {
+ apr_brigade_printf(ctx->bb, NULL, NULL,
+ "<a href=\"%s%s\">%s[%s]</a><br>",
+ ctx->mc->cert_check_url, fingerprint,
+ ctx->mc->cert_check_name, key);
+ }
+ else {
+ apr_brigade_printf(ctx->bb, NULL, NULL,
+ "%sType: %s\n",
+ ctx->prefix,
+ key);
+ apr_brigade_printf(ctx->bb, NULL, NULL,
+ "%sName: %s\n",
+ ctx->prefix,
+ ctx->mc->cert_check_name);
+ apr_brigade_printf(ctx->bb, NULL, NULL,
+ "%sURL: %s%s\n",
+ ctx->prefix,
+ ctx->mc->cert_check_url, fingerprint);
+ apr_brigade_printf(ctx->bb, NULL, NULL,
+ "%sFingerprint: %s\n",
+ ctx->prefix,
+ fingerprint);
+ }
+ }
+ return 1;
+}
+
+static void si_val_remote_check(status_ctx *ctx, md_json_t *mdj, const status_info *info)
+{
+ (void)info;
+ if (ctx->mc->cert_check_name && ctx->mc->cert_check_url) {
+ const char *prefix = ctx->prefix;
+ if (!HTML_STATUS(ctx)) {
+ ctx->prefix = apr_pstrcat(ctx->p, prefix, info->label, NULL);
+ }
+ md_json_iterkey(cert_check_iter, ctx, mdj, MD_KEY_CERT, NULL);
+ if (!HTML_STATUS(ctx)) {
+ ctx->prefix = prefix;
+ }
+ }
+}
+
+static void si_val_stapling(status_ctx *ctx, md_json_t *mdj, const status_info *info)
+{
+ (void)info;
+ if (!md_json_getb(mdj, MD_KEY_STAPLING, NULL)) return;
+ if (HTML_STATUS(ctx)) {
+ apr_brigade_puts(ctx->bb, NULL, NULL, "on");
+ }
+ else {
+ apr_brigade_printf(ctx->bb, NULL, NULL, "%s: on", ctx->prefix);
+ }
+}
+
+static int json_iter_val(void *data, size_t index, md_json_t *json)
+{
+ status_ctx *ctx = data;
+ const char *prefix = ctx->prefix;
+ if (HTML_STATUS(ctx)) {
+ if (index) apr_brigade_puts(ctx->bb, NULL, NULL, ctx->separator);
+ }
+ else {
+ ctx->prefix = apr_pstrcat(ctx->p, prefix, apr_psprintf(ctx->p, "[%" APR_SIZE_T_FMT "]", index), NULL);
+ }
+ add_json_val(ctx, json);
+ if (!HTML_STATUS(ctx)) {
+ ctx->prefix = prefix;
+ }
+ return 1;
+}
+
+static void add_json_val(status_ctx *ctx, md_json_t *j)
+{
+ if (!j) return;
+ if (md_json_is(MD_JSON_TYPE_ARRAY, j, NULL)) {
+ md_json_itera(json_iter_val, ctx, j, NULL);
+ return;
+ }
+ if (!HTML_STATUS(ctx)) {
+ apr_brigade_puts(ctx->bb, NULL, NULL, ctx->prefix);
+ apr_brigade_puts(ctx->bb, NULL, NULL, ": ");
+ }
+ if (md_json_is(MD_JSON_TYPE_INT, j, NULL)) {
+ md_json_writeb(j, MD_JSON_FMT_COMPACT, ctx->bb);
+ }
+ else if (md_json_is(MD_JSON_TYPE_STRING, j, NULL)) {
+ apr_brigade_puts(ctx->bb, NULL, NULL, md_json_gets(j, NULL));
+ }
+ else if (md_json_is(MD_JSON_TYPE_OBJECT, j, NULL)) {
+ md_json_writeb(j, MD_JSON_FMT_COMPACT, ctx->bb);
+ }
+ else if (md_json_is(MD_JSON_TYPE_BOOL, j, NULL)) {
+ apr_brigade_puts(ctx->bb, NULL, NULL, md_json_getb(j, NULL)? "on" : "off");
+ }
+ if (!HTML_STATUS(ctx)) {
+ apr_brigade_puts(ctx->bb, NULL, NULL, "\n");
+ }
+}
+
+static void si_val_names(status_ctx *ctx, md_json_t *mdj, const status_info *info)
+{
+ const char *prefix = ctx->prefix;
+ if (HTML_STATUS(ctx)) {
+ apr_brigade_puts(ctx->bb, NULL, NULL, "<div style=\"max-width:400px;\">");
+ }
+ else {
+ ctx->prefix = apr_pstrcat(ctx->p, prefix, info->label, NULL);
+ }
+ add_json_val(ctx, md_json_getj(mdj, info->key, NULL));
+ if (HTML_STATUS(ctx)) {
+ apr_brigade_puts(ctx->bb, NULL, NULL, "</div>");
+ }
+ else {
+ ctx->prefix = prefix;
+ }
+}
+
+static void add_status_cell(status_ctx *ctx, md_json_t *mdj, const status_info *info)
+{
+ if (info->fn) {
+ info->fn(ctx, mdj, info);
+ }
+ else {
+ const char *prefix = ctx->prefix;
+ if (!HTML_STATUS(ctx)) {
+ ctx->prefix = apr_pstrcat(ctx->p, prefix, info->label, NULL);
+ }
+ add_json_val(ctx, md_json_getj(mdj, info->key, NULL));
+ if (!HTML_STATUS(ctx)) {
+ ctx->prefix = prefix;
+ }
+ }
+}
+
+static const status_info status_infos[] = {
+ { "Domain", MD_KEY_NAME, NULL },
+ { "Names", MD_KEY_DOMAINS, si_val_names },
+ { "Status", MD_KEY_STATE, si_val_status },
+ { "Valid", MD_KEY_CERT, si_val_cert_valid_time },
+ { "CA", MD_KEY_CA, si_val_ca_urls },
+ { "Stapling", MD_KEY_STAPLING, si_val_stapling },
+ { "CheckAt", MD_KEY_SHA256_FINGERPRINT, si_val_remote_check },
+ { "Activity", MD_KEY_NOTIFIED, si_val_activity },
+};
+
+static int add_md_row(void *baton, apr_size_t index, md_json_t *mdj)
+{
+ status_ctx *ctx = baton;
+ const char *prefix = ctx->prefix;
+ int i;
+
+ if (HTML_STATUS(ctx)) {
+ apr_brigade_printf(ctx->bb, NULL, NULL, "<tr class=\"%s\">", (index % 2)? "odd" : "even");
+ for (i = 0; i < (int)(sizeof(status_infos)/sizeof(status_infos[0])); ++i) {
+ apr_brigade_puts(ctx->bb, NULL, NULL, "<td>");
+ add_status_cell(ctx, mdj, &status_infos[i]);
+ apr_brigade_puts(ctx->bb, NULL, NULL, "</td>");
+ }
+ apr_brigade_puts(ctx->bb, NULL, NULL, "</tr>");
+ } else {
+ for (i = 0; i < (int)(sizeof(status_infos)/sizeof(status_infos[0])); ++i) {
+ ctx->prefix = apr_pstrcat(ctx->p, prefix, apr_psprintf(ctx->p, "[%" APR_SIZE_T_FMT "]", index), NULL);
+ add_status_cell(ctx, mdj, &status_infos[i]);
+ ctx->prefix = prefix;
+ }
+ }
+ return 1;
+}
+
+static int md_name_cmp(const void *v1, const void *v2)
+{
+ return strcmp((*(const md_t**)v1)->name, (*(const md_t**)v2)->name);
+}
+
+int md_domains_status_hook(request_rec *r, int flags)
+{
+ const md_srv_conf_t *sc;
+ const md_mod_conf_t *mc;
+ int i;
+ status_ctx ctx;
+ apr_array_header_t *mds;
+ md_json_t *jstatus, *jstock;
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "server-status for managed domains, start");
+ sc = ap_get_module_config(r->server->module_config, &md_module);
+ if (!sc) return DECLINED;
+ mc = sc->mc;
+ if (!mc || !mc->server_status_enabled) return DECLINED;
+
+ ctx.p = r->pool;
+ ctx.mc = mc;
+ ctx.bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+ ctx.flags = flags;
+ ctx.prefix = "ManagedCertificates";
+ ctx.separator = " ";
+
+ mds = apr_array_copy(r->pool, mc->mds);
+ qsort(mds->elts, (size_t)mds->nelts, sizeof(md_t *), md_name_cmp);
+
+ if (!HTML_STATUS(&ctx)) {
+ int total = 0, complete = 0, renewing = 0, errored = 0, ready = 0;
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "no-html managed domain status summary");
+ if (mc->mds->nelts > 0) {
+ md_status_take_stock(&jstock, mds, mc->reg, r->pool);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "got JSON managed domain status summary");
+ total = (int)md_json_getl(jstock, MD_KEY_TOTAL, NULL);
+ complete = (int)md_json_getl(jstock, MD_KEY_COMPLETE, NULL);
+ renewing = (int)md_json_getl(jstock, MD_KEY_RENEWING, NULL);
+ errored = (int)md_json_getl(jstock, MD_KEY_ERRORED, NULL);
+ ready = (int)md_json_getl(jstock, MD_KEY_READY, NULL);
+ }
+ apr_brigade_printf(ctx.bb, NULL, NULL, "%sTotal: %d\n", ctx.prefix, total);
+ apr_brigade_printf(ctx.bb, NULL, NULL, "%sOK: %d\n", ctx.prefix, complete);
+ apr_brigade_printf(ctx.bb, NULL, NULL, "%sRenew: %d\n", ctx.prefix, renewing);
+ apr_brigade_printf(ctx.bb, NULL, NULL, "%sErrored: %d\n", ctx.prefix, errored);
+ apr_brigade_printf(ctx.bb, NULL, NULL, "%sReady: %d\n", ctx.prefix, ready);
+ }
+ if (mc->mds->nelts > 0) {
+ md_status_get_json(&jstatus, mds, mc->reg, mc->ocsp, r->pool);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "got JSON managed domain status");
+ if (HTML_STATUS(&ctx)) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "html managed domain status table");
+ apr_brigade_puts(ctx.bb, NULL, NULL,
+ "<hr>\n<h3>Managed Certificates</h3>\n<table class='md_status'><thead><tr>\n");
+ for (i = 0; i < (int)(sizeof(status_infos)/sizeof(status_infos[0])); ++i) {
+ si_add_header(&ctx, &status_infos[i]);
+ }
+ apr_brigade_puts(ctx.bb, NULL, NULL, "</tr>\n</thead><tbody>");
+ }
+ else {
+ ctx.prefix = "ManagedDomain";
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "iterating JSON managed domain status");
+ md_json_itera(add_md_row, &ctx, jstatus, MD_KEY_MDS, NULL);
+ if (HTML_STATUS(&ctx)) {
+ apr_brigade_puts(ctx.bb, NULL, NULL, "</td></tr>\n</tbody>\n</table>\n");
+ }
+ }
+
+ ap_pass_brigade(r->output_filters, ctx.bb);
+ apr_brigade_cleanup(ctx.bb);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "server-status for managed domains, end");
+
+ return OK;
+}
+
+static void si_val_ocsp_activity(status_ctx *ctx, md_json_t *mdj, const status_info *info)
+{
+ apr_time_t t;
+ const char *prefix = ctx->prefix;
+
+ (void)info;
+ if (!HTML_STATUS(ctx)) {
+ ctx->prefix = apr_pstrcat(ctx->p, prefix, info->label, NULL);
+ }
+ t = md_json_get_time(mdj, MD_KEY_RENEW_AT, NULL);
+ print_time(ctx, "Refresh", t);
+ print_job_summary(ctx, mdj, MD_KEY_RENEWAL, ": ");
+ if (!HTML_STATUS(ctx)) {
+ ctx->prefix = prefix;
+ }
+}
+
+static const status_info ocsp_status_infos[] = {
+ { "Domain", MD_KEY_DOMAIN, NULL },
+ { "CertificateID", MD_KEY_ID, NULL },
+ { "OCSPStatus", MD_KEY_STATUS, NULL },
+ { "StaplingValid", MD_KEY_VALID, si_val_valid_time },
+ { "Responder", MD_KEY_URL, si_val_url },
+ { "Activity", MD_KEY_NOTIFIED, si_val_ocsp_activity },
+};
+
+static int add_ocsp_row(void *baton, apr_size_t index, md_json_t *mdj)
+{
+ status_ctx *ctx = baton;
+ const char *prefix = ctx->prefix;
+ int i;
+
+ if (HTML_STATUS(ctx)) {
+ apr_brigade_printf(ctx->bb, NULL, NULL, "<tr class=\"%s\">", (index % 2)? "odd" : "even");
+ for (i = 0; i < (int)(sizeof(ocsp_status_infos)/sizeof(ocsp_status_infos[0])); ++i) {
+ apr_brigade_puts(ctx->bb, NULL, NULL, "<td>");
+ add_status_cell(ctx, mdj, &ocsp_status_infos[i]);
+ apr_brigade_puts(ctx->bb, NULL, NULL, "</td>");
+ }
+ apr_brigade_puts(ctx->bb, NULL, NULL, "</tr>");
+ } else {
+ for (i = 0; i < (int)(sizeof(ocsp_status_infos)/sizeof(ocsp_status_infos[0])); ++i) {
+ ctx->prefix = apr_pstrcat(ctx->p, prefix, apr_psprintf(ctx->p, "[%" APR_SIZE_T_FMT "]", index), NULL);
+ add_status_cell(ctx, mdj, &ocsp_status_infos[i]);
+ ctx->prefix = prefix;
+ }
+ }
+ return 1;
+}
+
+int md_ocsp_status_hook(request_rec *r, int flags)
+{
+ const md_srv_conf_t *sc;
+ const md_mod_conf_t *mc;
+ int i;
+ status_ctx ctx;
+ md_json_t *jstatus, *jstock;
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "server-status for ocsp stapling, start");
+ sc = ap_get_module_config(r->server->module_config, &md_module);
+ if (!sc) return DECLINED;
+ mc = sc->mc;
+ if (!mc || !mc->server_status_enabled) return DECLINED;
+
+ ctx.p = r->pool;
+ ctx.mc = mc;
+ ctx.bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+ ctx.flags = flags;
+ ctx.prefix = "ManagedStaplings";
+ ctx.separator = " ";
+
+ if (!HTML_STATUS(&ctx)) {
+ int total = 0, good = 0, revoked = 0, unknown = 0;
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "no-html ocsp stapling status summary");
+ if (md_ocsp_count(mc->ocsp) > 0) {
+ md_ocsp_get_summary(&jstock, mc->ocsp, r->pool);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "got JSON ocsp stapling status summary");
+ total = (int)md_json_getl(jstock, MD_KEY_TOTAL, NULL);
+ good = (int)md_json_getl(jstock, MD_KEY_GOOD, NULL);
+ revoked = (int)md_json_getl(jstock, MD_KEY_REVOKED, NULL);
+ unknown = (int)md_json_getl(jstock, MD_KEY_UNKNOWN, NULL);
+ }
+ apr_brigade_printf(ctx.bb, NULL, NULL, "%sTotal: %d\n", ctx.prefix, total);
+ apr_brigade_printf(ctx.bb, NULL, NULL, "%sOK: %d\n", ctx.prefix, good);
+ apr_brigade_printf(ctx.bb, NULL, NULL, "%sRenew: %d\n", ctx.prefix, revoked);
+ apr_brigade_printf(ctx.bb, NULL, NULL, "%sErrored: %d\n", ctx.prefix, unknown);
+ }
+ if (md_ocsp_count(mc->ocsp) > 0) {
+ md_ocsp_get_status_all(&jstatus, mc->ocsp, r->pool);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "got JSON ocsp stapling status");
+ if (HTML_STATUS(&ctx)) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "html ocsp stapling status table");
+ apr_brigade_puts(ctx.bb, NULL, NULL,
+ "<hr>\n<h3>Managed Staplings</h3>\n<table class='md_ocsp_status'><thead><tr>\n");
+ for (i = 0; i < (int)(sizeof(ocsp_status_infos)/sizeof(ocsp_status_infos[0])); ++i) {
+ si_add_header(&ctx, &ocsp_status_infos[i]);
+ }
+ apr_brigade_puts(ctx.bb, NULL, NULL, "</tr>\n</thead><tbody>");
+ }
+ else {
+ ctx.prefix = "ManagedStapling";
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "iterating JSON ocsp stapling status");
+ md_json_itera(add_ocsp_row, &ctx, jstatus, MD_KEY_OCSPS, NULL);
+ if (HTML_STATUS(&ctx)) {
+ apr_brigade_puts(ctx.bb, NULL, NULL, "</td></tr>\n</tbody>\n</table>\n");
+ }
+ }
+
+ ap_pass_brigade(r->output_filters, ctx.bb);
+ apr_brigade_cleanup(ctx.bb);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "server-status for ocsp stapling, end");
+
+ return OK;
+}
+
+/**************************************************************************************************/
+/* Status handlers */
+
+int md_status_handler(request_rec *r)
+{
+ const md_srv_conf_t *sc;
+ const md_mod_conf_t *mc;
+ apr_array_header_t *mds;
+ md_json_t *jstatus;
+ apr_bucket_brigade *bb;
+ const md_t *md;
+ const char *name;
+
+ if (strcmp(r->handler, "md-status")) {
+ return DECLINED;
+ }
+
+ sc = ap_get_module_config(r->server->module_config, &md_module);
+ if (!sc) return DECLINED;
+ mc = sc->mc;
+ if (!mc) return DECLINED;
+
+ if (r->method_number != M_GET) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "md-status supports only GET");
+ return HTTP_NOT_IMPLEMENTED;
+ }
+
+ jstatus = NULL;
+ md = NULL;
+ if (r->path_info && r->path_info[0] == '/' && r->path_info[1] != '\0') {
+ name = strrchr(r->path_info, '/') + 1;
+ md = md_get_by_name(mc->mds, name);
+ if (!md) md = md_get_by_domain(mc->mds, name);
+ }
+
+ if (md) {
+ md_status_get_md_json(&jstatus, md, mc->reg, mc->ocsp, r->pool);
+ }
+ else {
+ mds = apr_array_copy(r->pool, mc->mds);
+ qsort(mds->elts, (size_t)mds->nelts, sizeof(md_t *), md_name_cmp);
+ md_status_get_json(&jstatus, mds, mc->reg, mc->ocsp, r->pool);
+ }
+
+ if (jstatus) {
+ apr_table_set(r->headers_out, "Content-Type", "application/json");
+ bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+ md_json_writeb(jstatus, MD_JSON_FMT_INDENT, bb);
+ ap_pass_brigade(r->output_filters, bb);
+ apr_brigade_cleanup(bb);
+
+ return DONE;
+ }
+ return DECLINED;
+}
+
diff --git a/modules/md/mod_md_status.h b/modules/md/mod_md_status.h
new file mode 100644
index 0000000..f347826
--- /dev/null
+++ b/modules/md/mod_md_status.h
@@ -0,0 +1,27 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mod_md_md_status_h
+#define mod_md_md_status_h
+
+int md_http_cert_status(request_rec *r);
+
+int md_domains_status_hook(request_rec *r, int flags);
+int md_ocsp_status_hook(request_rec *r, int flags);
+
+int md_status_handler(request_rec *r);
+
+#endif /* mod_md_md_status_h */
diff --git a/modules/metadata/.indent.pro b/modules/metadata/.indent.pro
new file mode 100644
index 0000000..a9fbe9f
--- /dev/null
+++ b/modules/metadata/.indent.pro
@@ -0,0 +1,54 @@
+-i4 -npsl -di0 -br -nce -d0 -cli0 -npcs -nfc1
+-TBUFF
+-TFILE
+-TTRANS
+-TUINT4
+-T_trans
+-Tallow_options_t
+-Tapache_sfio
+-Tarray_header
+-Tbool_int
+-Tbuf_area
+-Tbuff_struct
+-Tbuffy
+-Tcmd_how
+-Tcmd_parms
+-Tcommand_rec
+-Tcommand_struct
+-Tconn_rec
+-Tcore_dir_config
+-Tcore_server_config
+-Tdir_maker_func
+-Tevent
+-Tglobals_s
+-Thandler_func
+-Thandler_rec
+-Tjoblist_s
+-Tlisten_rec
+-Tmerger_func
+-Tmode_t
+-Tmodule
+-Tmodule_struct
+-Tmutex
+-Tn_long
+-Tother_child_rec
+-Toverrides_t
+-Tparent_score
+-Tpid_t
+-Tpiped_log
+-Tpool
+-Trequest_rec
+-Trequire_line
+-Trlim_t
+-Tscoreboard
+-Tsemaphore
+-Tserver_addr_rec
+-Tserver_rec
+-Tserver_rec_chain
+-Tshort_score
+-Ttable
+-Ttable_entry
+-Tthread
+-Tu_wide_int
+-Tvtime_t
+-Twide_int
diff --git a/modules/metadata/Makefile.in b/modules/metadata/Makefile.in
new file mode 100644
index 0000000..167b343
--- /dev/null
+++ b/modules/metadata/Makefile.in
@@ -0,0 +1,3 @@
+
+include $(top_srcdir)/build/special.mk
+
diff --git a/modules/metadata/NWGNUcernmeta b/modules/metadata/NWGNUcernmeta
new file mode 100644
index 0000000..64a7cb7
--- /dev/null
+++ b/modules/metadata/NWGNUcernmeta
@@ -0,0 +1,248 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = cernmeta
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) CERN Meta Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = CERN Meta Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/cernmeta.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_cern_meta.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ cern_meta_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/metadata/NWGNUexpires b/modules/metadata/NWGNUexpires
new file mode 100644
index 0000000..e9601b0
--- /dev/null
+++ b/modules/metadata/NWGNUexpires
@@ -0,0 +1,248 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = expires
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Expires Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Expires Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/expires.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_expires.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ expires_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/metadata/NWGNUheaders b/modules/metadata/NWGNUheaders
new file mode 100644
index 0000000..77ae60d
--- /dev/null
+++ b/modules/metadata/NWGNUheaders
@@ -0,0 +1,249 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/modules/ssl \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = headers
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Headers Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Headers Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/headers.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_headers.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ headers_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/metadata/NWGNUmakefile b/modules/metadata/NWGNUmakefile
new file mode 100644
index 0000000..fd93e0e
--- /dev/null
+++ b/modules/metadata/NWGNUmakefile
@@ -0,0 +1,254 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME =
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION =
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME =
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE =
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/cernmeta.nlm \
+ $(OBJDIR)/expires.nlm \
+ $(OBJDIR)/headers.nlm \
+ $(OBJDIR)/mimemagi.nlm \
+ $(OBJDIR)/modident.nlm \
+ $(OBJDIR)/uniqueid.nlm \
+ $(OBJDIR)/usertrk.nlm \
+ $(OBJDIR)/modversion.nlm \
+ $(OBJDIR)/remoteip.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/metadata/NWGNUmimemagi b/modules/metadata/NWGNUmimemagi
new file mode 100644
index 0000000..ae508ff
--- /dev/null
+++ b/modules/metadata/NWGNUmimemagi
@@ -0,0 +1,248 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = mimemagi
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Mime Magic Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = CERN Meta Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/mimemagi.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_mime_magic.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ mime_magic_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/metadata/NWGNUmodident b/modules/metadata/NWGNUmodident
new file mode 100644
index 0000000..8ce871d
--- /dev/null
+++ b/modules/metadata/NWGNUmodident
@@ -0,0 +1,248 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = modident
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Remote User Identity Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Mod_Ident Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/modident.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_ident.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ ident_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/metadata/NWGNUmodversion b/modules/metadata/NWGNUmodversion
new file mode 100644
index 0000000..756b572
--- /dev/null
+++ b/modules/metadata/NWGNUmodversion
@@ -0,0 +1,248 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = modversion
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Version Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Version Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_version.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ version_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/metadata/NWGNUremoteip b/modules/metadata/NWGNUremoteip
new file mode 100644
index 0000000..8e9ddb5
--- /dev/null
+++ b/modules/metadata/NWGNUremoteip
@@ -0,0 +1,248 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = remoteip
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) RemoteIP Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = RemoteIP Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_remoteip.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ remoteip_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/metadata/NWGNUuniqueid b/modules/metadata/NWGNUuniqueid
new file mode 100644
index 0000000..d39a57e
--- /dev/null
+++ b/modules/metadata/NWGNUuniqueid
@@ -0,0 +1,257 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = uniqueid
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Unique ID Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Unique ID Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/uniqueid.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_unique_id.o \
+ $(OBJDIR)/libprews.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+# Don't link with Winsock if standard sockets are being used
+ifndef USE_STDSOCKETS
+FILES_nlm_Ximports += @ws2nlm.imp \
+ $(EOLIST)
+endif
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ unique_id_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+vpath %.c ../arch/netware
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/metadata/NWGNUusertrk b/modules/metadata/NWGNUusertrk
new file mode 100644
index 0000000..1be79de
--- /dev/null
+++ b/modules/metadata/NWGNUusertrk
@@ -0,0 +1,248 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = usertrk
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) User Track Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = User Track Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/usertrk.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_usertrack.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ usertrack_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/metadata/config.m4 b/modules/metadata/config.m4
new file mode 100644
index 0000000..25d1aec
--- /dev/null
+++ b/modules/metadata/config.m4
@@ -0,0 +1,24 @@
+dnl modules enabled in this directory by default
+
+dnl APACHE_MODULE(name, helptext[, objects[, structname[, default[, config]]]])
+
+APACHE_MODPATH_INIT(metadata)
+
+APACHE_MODULE(env, clearing/setting of ENV vars, , , yes)
+APACHE_MODULE(mime_magic, automagically determining MIME type)
+APACHE_MODULE(cern_meta, CERN-type meta files, , , no)
+APACHE_MODULE(expires, Expires header control, , , most)
+APACHE_MODULE(headers, HTTP header control, , , yes)
+APACHE_MODULE(ident, RFC 1413 identity check, , , no)
+
+APACHE_MODULE(usertrack, user-session tracking, , , , [
+ AC_CHECK_HEADERS(sys/times.h)
+ AC_CHECK_FUNCS(times)
+])
+
+APACHE_MODULE(unique_id, per-request unique ids, , , most)
+APACHE_MODULE(setenvif, basing ENV vars on headers, , , yes)
+APACHE_MODULE(version, determining httpd version in config files, , , yes)
+APACHE_MODULE(remoteip, translate header contents to an apparent client remote_ip, , , most)
+
+APACHE_MODPATH_FINISH
diff --git a/modules/metadata/mod_cern_meta.c b/modules/metadata/mod_cern_meta.c
new file mode 100644
index 0000000..3f36b2d
--- /dev/null
+++ b/modules/metadata/mod_cern_meta.c
@@ -0,0 +1,371 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * mod_cern_meta.c
+ * version 0.1.0
+ * status beta
+ *
+ * Andrew Wilson <Andrew.Wilson@cm.cf.ac.uk> 25.Jan.96
+ *
+ * *** IMPORTANT ***
+ * This version of mod_cern_meta.c controls Meta File behaviour on a
+ * per-directory basis. Previous versions of the module defined behaviour
+ * on a per-server basis. The upshot is that you'll need to revisit your
+ * configuration files in order to make use of the new module.
+ * ***
+ *
+ * Emulate the CERN HTTPD Meta file semantics. Meta files are HTTP
+ * headers that can be output in addition to the normal range of
+ * headers for each file accessed. They appear rather like the Apache
+ * .asis files, and are able to provide a crude way of influencing
+ * the Expires: header, as well as providing other curiosities.
+ * There are many ways to manage meta information, this one was
+ * chosen because there is already a large number of CERN users
+ * who can exploit this module. It should be noted that there are probably
+ * more sensitive ways of managing the Expires: header specifically.
+ *
+ * The module obeys the following directives, which can appear
+ * in the server's .conf files and in .htaccess files.
+ *
+ * MetaFiles <on|off>
+ *
+ * turns on|off meta file processing for any directory.
+ * Default value is off
+ *
+ * # turn on MetaFiles in this directory
+ * MetaFiles on
+ *
+ * MetaDir <directory name>
+ *
+ * specifies the name of the directory in which Apache can find
+ * meta information files. The directory is usually a 'hidden'
+ * subdirectory of the directory that contains the file being
+ * accessed. eg:
+ *
+ * # .meta files are in the *same* directory as the
+ * # file being accessed
+ * MetaDir .
+ *
+ * the default is to look in a '.web' subdirectory. This is the
+ * same as for CERN 3.+ webservers and behaviour is the same as
+ * for the directive:
+ *
+ * MetaDir .web
+ *
+ * MetaSuffix <meta file suffix>
+ *
+ * specifies the file name suffix for the file containing the
+ * meta information. eg:
+ *
+ * # our meta files are suffixed with '.cern_meta'
+ * MetaSuffix .cern_meta
+ *
+ * the default is to look for files with the suffix '.meta'. This
+ * behaviour is the same as for the directive:
+ *
+ * MetaSuffix .meta
+ *
+ * When accessing the file
+ *
+ * DOCUMENT_ROOT/somedir/index.html
+ *
+ * this module will look for the file
+ *
+ * DOCUMENT_ROOT/somedir/.web/index.html.meta
+ *
+ * and will use its contents to generate additional MIME header
+ * information.
+ *
+ * For more information on the CERN Meta file semantics see:
+ *
+ * http://www.w3.org/hypertext/WWW/Daemon/User/Config/General.html#MetaDir
+ *
+ * Change-log:
+ * 29.Jan.96 pfopen/pfclose instead of fopen/fclose
+ * DECLINE when real file not found, we may be checking each
+ * of the index.html/index.shtml/index.htm variants and don't
+ * need to report missing ones as spurious errors.
+ * 31.Jan.96 log_error reports about a malformed .meta file, rather
+ * than a script error.
+ * 20.Jun.96 MetaFiles <on|off> default off, added, so that module
+ * can be configured per-directory. Prior to this the module
+ * was running for each request anywhere on the server, naughty..
+ * 29.Jun.96 All directives made per-directory.
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#if APR_HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "util_script.h"
+#include "http_log.h"
+#include "http_request.h"
+#include "http_protocol.h"
+#include "apr_lib.h"
+
+#define DIR_CMD_PERMS OR_INDEXES
+
+#define DEFAULT_METADIR ".web"
+#define DEFAULT_METASUFFIX ".meta"
+#define DEFAULT_METAFILES 0
+
+module AP_MODULE_DECLARE_DATA cern_meta_module;
+
+typedef struct {
+ const char *metadir;
+ const char *metasuffix;
+ int metafiles;
+} cern_meta_dir_config;
+
+static void *create_cern_meta_dir_config(apr_pool_t *p, char *dummy)
+{
+ cern_meta_dir_config *new =
+ (cern_meta_dir_config *) apr_palloc(p, sizeof(cern_meta_dir_config));
+
+ new->metadir = NULL;
+ new->metasuffix = NULL;
+ new->metafiles = DEFAULT_METAFILES;
+
+ return new;
+}
+
+static void *merge_cern_meta_dir_configs(apr_pool_t *p, void *basev, void *addv)
+{
+ cern_meta_dir_config *base = (cern_meta_dir_config *) basev;
+ cern_meta_dir_config *add = (cern_meta_dir_config *) addv;
+ cern_meta_dir_config *new =
+ (cern_meta_dir_config *) apr_palloc(p, sizeof(cern_meta_dir_config));
+
+ new->metadir = add->metadir ? add->metadir : base->metadir;
+ new->metasuffix = add->metasuffix ? add->metasuffix : base->metasuffix;
+ new->metafiles = add->metafiles;
+
+ return new;
+}
+
+static const char *set_metadir(cmd_parms *parms, void *in_dconf, const char *arg)
+{
+ cern_meta_dir_config *dconf = in_dconf;
+
+ dconf->metadir = arg;
+ return NULL;
+}
+
+static const char *set_metasuffix(cmd_parms *parms, void *in_dconf, const char *arg)
+{
+ cern_meta_dir_config *dconf = in_dconf;
+
+ dconf->metasuffix = arg;
+ return NULL;
+}
+
+static const char *set_metafiles(cmd_parms *parms, void *in_dconf, int arg)
+{
+ cern_meta_dir_config *dconf = in_dconf;
+
+ dconf->metafiles = arg;
+ return NULL;
+}
+
+
+static const command_rec cern_meta_cmds[] =
+{
+ AP_INIT_FLAG("MetaFiles", set_metafiles, NULL, DIR_CMD_PERMS,
+ "Limited to 'on' or 'off'"),
+ AP_INIT_TAKE1("MetaDir", set_metadir, NULL, DIR_CMD_PERMS,
+ "the name of the directory containing meta files"),
+ AP_INIT_TAKE1("MetaSuffix", set_metasuffix, NULL, DIR_CMD_PERMS,
+ "the filename suffix for meta files"),
+ {NULL}
+};
+
+/* XXX: this is very similar to ap_scan_script_header_err_core...
+ * are the differences deliberate, or just a result of bit rot?
+ */
+static int scan_meta_file(request_rec *r, apr_file_t *f)
+{
+ char w[MAX_STRING_LEN];
+ char *l;
+ int p;
+ apr_table_t *tmp_headers;
+
+ tmp_headers = apr_table_make(r->pool, 5);
+ while (apr_file_gets(w, MAX_STRING_LEN - 1, f) == APR_SUCCESS) {
+
+ /* Delete terminal (CR?)LF */
+ p = strlen(w);
+ if (p > 0 && w[p - 1] == '\n') {
+ if (p > 1 && w[p - 2] == '\015')
+ w[p - 2] = '\0';
+ else
+ w[p - 1] = '\0';
+ }
+
+ if (w[0] == '\0') {
+ return OK;
+ }
+
+ /* if we see a bogus header don't ignore it. Shout and scream */
+
+ if (!(l = strchr(w, ':'))) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01560)
+ "malformed header in meta file: %s", r->filename);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ *l++ = '\0';
+ while (apr_isspace(*l))
+ ++l;
+
+ if (!ap_cstr_casecmp(w, "Content-type")) {
+ char *tmp;
+ /* Nuke trailing whitespace */
+
+ char *endp = l + strlen(l) - 1;
+ while (endp > l && apr_isspace(*endp))
+ *endp-- = '\0';
+
+ tmp = apr_pstrdup(r->pool, l);
+ ap_content_type_tolower(tmp);
+ ap_set_content_type(r, tmp);
+ }
+ else if (!ap_cstr_casecmp(w, "Status")) {
+ sscanf(l, "%d", &r->status);
+ r->status_line = apr_pstrdup(r->pool, l);
+ }
+ else {
+ apr_table_set(tmp_headers, w, l);
+ }
+ }
+ apr_table_overlap(r->headers_out, tmp_headers, APR_OVERLAP_TABLES_SET);
+ return OK;
+}
+
+static int add_cern_meta_data(request_rec *r)
+{
+ char *metafilename;
+ char *leading_slash;
+ char *last_slash;
+ char *real_file;
+ char *scrap_book;
+ apr_file_t *f = NULL;
+ apr_status_t retcode;
+ cern_meta_dir_config *dconf;
+ int rv;
+ request_rec *rr;
+
+ dconf = ap_get_module_config(r->per_dir_config, &cern_meta_module);
+
+ if (!dconf->metafiles) {
+ return DECLINED;
+ }
+
+ /* if ./.web/$1.meta exists then output 'asis' */
+
+ if (r->finfo.filetype == APR_NOFILE) {
+ return DECLINED;
+ }
+
+ /* is this a directory? */
+ if (r->finfo.filetype == APR_DIR || r->uri[strlen(r->uri) - 1] == '/') {
+ return DECLINED;
+ }
+
+ /* what directory is this file in? */
+ scrap_book = apr_pstrdup(r->pool, r->filename);
+
+ leading_slash = strchr(scrap_book, '/');
+ last_slash = strrchr(scrap_book, '/');
+ if ((last_slash != NULL) && (last_slash != leading_slash)) {
+ /* skip over last slash */
+ real_file = last_slash;
+ real_file++;
+ *last_slash = '\0';
+ }
+ else {
+ /* no last slash, buh?! */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01561)
+ "internal error in mod_cern_meta: %s", r->filename);
+ /* should really barf, but hey, let's be friends... */
+ return DECLINED;
+ }
+
+ metafilename = apr_pstrcat(r->pool, scrap_book, "/",
+ dconf->metadir ? dconf->metadir : DEFAULT_METADIR,
+ "/", real_file,
+ dconf->metasuffix ? dconf->metasuffix : DEFAULT_METASUFFIX,
+ NULL);
+
+ /* It sucks to require this subrequest to complete, because this
+ * means people must leave their meta files accessible to the world.
+ * A better solution might be a "safe open" feature of pfopen to avoid
+ * pipes, symlinks, and crap like that.
+ *
+ * In fact, this doesn't suck. Because <Location > blocks are never run
+ * against sub_req_lookup_file, the meta can be somewhat protected by
+ * either masking it with a <Location > directive or alias, or stowing
+ * the file outside of the web document tree, while providing the
+ * appropriate directory blocks to allow access to it as a file.
+ */
+ rr = ap_sub_req_lookup_file(metafilename, r, NULL);
+ if (rr->status != HTTP_OK) {
+ ap_destroy_sub_req(rr);
+ return DECLINED;
+ }
+ ap_destroy_sub_req(rr);
+
+ retcode = apr_file_open(&f, metafilename, APR_READ, APR_OS_DEFAULT, r->pool);
+ if (retcode != APR_SUCCESS) {
+ if (APR_STATUS_IS_ENOENT(retcode)) {
+ return DECLINED;
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01562)
+ "meta file permissions deny server access: %s", metafilename);
+ return HTTP_FORBIDDEN;
+ }
+
+ /* read the headers in */
+ rv = scan_meta_file(r, f);
+ apr_file_close(f);
+
+ return rv;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_hook_fixups(add_cern_meta_data,NULL,NULL,APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(cern_meta) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_cern_meta_dir_config, /* dir config creater */
+ merge_cern_meta_dir_configs, /* dir merger --- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server configs */
+ cern_meta_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/metadata/mod_cern_meta.dep b/modules/metadata/mod_cern_meta.dep
new file mode 100644
index 0000000..e01504b
--- /dev/null
+++ b/modules/metadata/mod_cern_meta.dep
@@ -0,0 +1,55 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_cern_meta.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_cern_meta.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/metadata/mod_cern_meta.dsp b/modules/metadata/mod_cern_meta.dsp
new file mode 100644
index 0000000..ae36b0d
--- /dev/null
+++ b/modules/metadata/mod_cern_meta.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_cern_meta" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_cern_meta - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_cern_meta.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_cern_meta.mak" CFG="mod_cern_meta - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_cern_meta - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_cern_meta - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_cern_meta - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_cern_meta_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_cern_meta.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_cern_meta.so" /d LONG_NAME="cern_meta_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_cern_meta.so" /base:@..\..\os\win32\BaseAddr.ref,mod_cern_meta.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_cern_meta.so" /base:@..\..\os\win32\BaseAddr.ref,mod_cern_meta.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_cern_meta.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_cern_meta - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_cern_meta_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_cern_meta.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_cern_meta.so" /d LONG_NAME="cern_meta_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_cern_meta.so" /base:@..\..\os\win32\BaseAddr.ref,mod_cern_meta.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_cern_meta.so" /base:@..\..\os\win32\BaseAddr.ref,mod_cern_meta.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_cern_meta.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_cern_meta - Win32 Release"
+# Name "mod_cern_meta - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_cern_meta.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/metadata/mod_cern_meta.exp b/modules/metadata/mod_cern_meta.exp
new file mode 100644
index 0000000..d36e2be
--- /dev/null
+++ b/modules/metadata/mod_cern_meta.exp
@@ -0,0 +1 @@
+cern_meta_module
diff --git a/modules/metadata/mod_cern_meta.mak b/modules/metadata/mod_cern_meta.mak
new file mode 100644
index 0000000..08ba414
--- /dev/null
+++ b/modules/metadata/mod_cern_meta.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_cern_meta.dsp
+!IF "$(CFG)" == ""
+CFG=mod_cern_meta - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_cern_meta - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_cern_meta - Win32 Release" && "$(CFG)" != "mod_cern_meta - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_cern_meta.mak" CFG="mod_cern_meta - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_cern_meta - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_cern_meta - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_cern_meta - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_cern_meta.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_cern_meta.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_cern_meta.obj"
+ -@erase "$(INTDIR)\mod_cern_meta.res"
+ -@erase "$(INTDIR)\mod_cern_meta_src.idb"
+ -@erase "$(INTDIR)\mod_cern_meta_src.pdb"
+ -@erase "$(OUTDIR)\mod_cern_meta.exp"
+ -@erase "$(OUTDIR)\mod_cern_meta.lib"
+ -@erase "$(OUTDIR)\mod_cern_meta.pdb"
+ -@erase "$(OUTDIR)\mod_cern_meta.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_cern_meta_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_cern_meta.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_cern_meta.so" /d LONG_NAME="cern_meta_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_cern_meta.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_cern_meta.pdb" /debug /out:"$(OUTDIR)\mod_cern_meta.so" /implib:"$(OUTDIR)\mod_cern_meta.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_cern_meta.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_cern_meta.obj" \
+ "$(INTDIR)\mod_cern_meta.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_cern_meta.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_cern_meta.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_cern_meta.so"
+ if exist .\Release\mod_cern_meta.so.manifest mt.exe -manifest .\Release\mod_cern_meta.so.manifest -outputresource:.\Release\mod_cern_meta.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_cern_meta - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_cern_meta.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_cern_meta.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_cern_meta.obj"
+ -@erase "$(INTDIR)\mod_cern_meta.res"
+ -@erase "$(INTDIR)\mod_cern_meta_src.idb"
+ -@erase "$(INTDIR)\mod_cern_meta_src.pdb"
+ -@erase "$(OUTDIR)\mod_cern_meta.exp"
+ -@erase "$(OUTDIR)\mod_cern_meta.lib"
+ -@erase "$(OUTDIR)\mod_cern_meta.pdb"
+ -@erase "$(OUTDIR)\mod_cern_meta.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_cern_meta_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_cern_meta.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_cern_meta.so" /d LONG_NAME="cern_meta_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_cern_meta.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_cern_meta.pdb" /debug /out:"$(OUTDIR)\mod_cern_meta.so" /implib:"$(OUTDIR)\mod_cern_meta.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_cern_meta.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_cern_meta.obj" \
+ "$(INTDIR)\mod_cern_meta.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_cern_meta.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_cern_meta.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_cern_meta.so"
+ if exist .\Debug\mod_cern_meta.so.manifest mt.exe -manifest .\Debug\mod_cern_meta.so.manifest -outputresource:.\Debug\mod_cern_meta.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_cern_meta.dep")
+!INCLUDE "mod_cern_meta.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_cern_meta.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_cern_meta - Win32 Release" || "$(CFG)" == "mod_cern_meta - Win32 Debug"
+
+!IF "$(CFG)" == "mod_cern_meta - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\metadata"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_cern_meta - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\metadata"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_cern_meta - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\metadata"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_cern_meta - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\metadata"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_cern_meta - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\metadata"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_cern_meta - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\metadata"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\metadata"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_cern_meta - Win32 Release"
+
+
+"$(INTDIR)\mod_cern_meta.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_cern_meta.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_cern_meta.so" /d LONG_NAME="cern_meta_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_cern_meta - Win32 Debug"
+
+
+"$(INTDIR)\mod_cern_meta.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_cern_meta.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_cern_meta.so" /d LONG_NAME="cern_meta_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_cern_meta.c
+
+"$(INTDIR)\mod_cern_meta.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/metadata/mod_env.c b/modules/metadata/mod_env.c
new file mode 100644
index 0000000..52594e9
--- /dev/null
+++ b/modules/metadata/mod_env.c
@@ -0,0 +1,189 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+
+#if APR_HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_request.h"
+#include "http_log.h"
+
+typedef struct {
+ apr_table_t *vars;
+ apr_table_t *unsetenv;
+} env_dir_config_rec;
+
+module AP_MODULE_DECLARE_DATA env_module;
+
+static void *create_env_dir_config(apr_pool_t *p, char *dummy)
+{
+ env_dir_config_rec *conf = apr_palloc(p, sizeof(*conf));
+
+ conf->vars = apr_table_make(p, 10);
+ conf->unsetenv = apr_table_make(p, 10);
+
+ return conf;
+}
+
+static void *merge_env_dir_configs(apr_pool_t *p, void *basev, void *addv)
+{
+ env_dir_config_rec *base = basev;
+ env_dir_config_rec *add = addv;
+ env_dir_config_rec *res = apr_palloc(p, sizeof(*res));
+
+ const apr_table_entry_t *elts;
+ const apr_array_header_t *arr;
+
+ int i;
+
+ /*
+ * res->vars = copy_table( p, base->vars );
+ * foreach $unsetenv ( @add->unsetenv )
+ * table_unset( res->vars, $unsetenv );
+ * foreach $element ( @add->vars )
+ * table_set( res->vars, $element.key, $element.val );
+ *
+ * add->unsetenv already removed the vars from add->vars,
+ * if they preceded the UnsetEnv directive.
+ */
+ res->vars = apr_table_copy(p, base->vars);
+ res->unsetenv = NULL;
+
+ arr = apr_table_elts(add->unsetenv);
+ if (arr) {
+ elts = (const apr_table_entry_t *)arr->elts;
+
+ for (i = 0; i < arr->nelts; ++i) {
+ apr_table_unset(res->vars, elts[i].key);
+ }
+ }
+
+ arr = apr_table_elts(add->vars);
+ if (arr) {
+ elts = (const apr_table_entry_t *)arr->elts;
+
+ for (i = 0; i < arr->nelts; ++i) {
+ apr_table_setn(res->vars, elts[i].key, elts[i].val);
+ }
+ }
+
+ return res;
+}
+
+static const char *add_env_module_vars_passed(cmd_parms *cmd, void *sconf_,
+ const char *arg)
+{
+ env_dir_config_rec *sconf = sconf_;
+ apr_table_t *vars = sconf->vars;
+ const char *env_var;
+
+ env_var = getenv(arg);
+ if (env_var != NULL) {
+ apr_table_setn(vars, arg, apr_pstrdup(cmd->pool, env_var));
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(01506)
+ "PassEnv variable %s was undefined", arg);
+ }
+
+ return NULL;
+}
+
+static const char *add_env_module_vars_set(cmd_parms *cmd, void *sconf_,
+ const char *name, const char *value)
+{
+ env_dir_config_rec *sconf = sconf_;
+
+ if (ap_strchr_c(name, '=')) {
+ char *env, *plast;
+
+ env = apr_strtok(apr_pstrdup(cmd->temp_pool, name), "=", &plast);
+
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(10032)
+ "Spurious usage of '=' in an environment variable name. "
+ "'%s %s %s' expected instead?", cmd->cmd->name, env, plast);
+ }
+
+ /* name is mandatory, value is optional. no value means
+ * set the variable to an empty string
+ */
+ apr_table_setn(sconf->vars, name, value ? value : "");
+
+ return NULL;
+}
+
+static const char *add_env_module_vars_unset(cmd_parms *cmd, void *sconf_,
+ const char *arg)
+{
+ env_dir_config_rec *sconf = sconf_;
+
+ /* Always UnsetEnv FOO in the same context as {Set,Pass}Env FOO
+ * only if this UnsetEnv follows the {Set,Pass}Env. The merge
+ * will only apply unsetenv to the parent env (main server).
+ */
+ apr_table_set(sconf->unsetenv, arg, NULL);
+ apr_table_unset(sconf->vars, arg);
+
+ return NULL;
+}
+
+static const command_rec env_module_cmds[] =
+{
+AP_INIT_ITERATE("PassEnv", add_env_module_vars_passed, NULL,
+ OR_FILEINFO, "a list of environment variables to pass to CGI."),
+AP_INIT_TAKE12("SetEnv", add_env_module_vars_set, NULL,
+ OR_FILEINFO, "an environment variable name and optional value to pass to CGI."),
+AP_INIT_ITERATE("UnsetEnv", add_env_module_vars_unset, NULL,
+ OR_FILEINFO, "a list of variables to remove from the CGI environment."),
+ {NULL},
+};
+
+static int fixup_env_module(request_rec *r)
+{
+ env_dir_config_rec *sconf = ap_get_module_config(r->per_dir_config,
+ &env_module);
+
+ if (apr_is_empty_table(sconf->vars)) {
+ return DECLINED;
+ }
+
+ r->subprocess_env = apr_table_overlay(r->pool, r->subprocess_env,
+ sconf->vars);
+
+ return OK;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_hook_fixups(fixup_env_module, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(env) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_env_dir_config, /* dir config creater */
+ merge_env_dir_configs, /* dir merger --- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server configs */
+ env_module_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/metadata/mod_env.dep b/modules/metadata/mod_env.dep
new file mode 100644
index 0000000..88c0fc2
--- /dev/null
+++ b/modules/metadata/mod_env.dep
@@ -0,0 +1,47 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_env.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_env.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/metadata/mod_env.dsp b/modules/metadata/mod_env.dsp
new file mode 100644
index 0000000..9b9bd53
--- /dev/null
+++ b/modules/metadata/mod_env.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_env" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_env - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_env.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_env.mak" CFG="mod_env - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_env - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_env - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_env - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_env_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_env.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_env.so" /d LONG_NAME="env_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_env.so" /base:@..\..\os\win32\BaseAddr.ref,mod_env.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_env.so" /base:@..\..\os\win32\BaseAddr.ref,mod_env.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_env.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_env - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_env_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_env.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_env.so" /d LONG_NAME="env_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_env.so" /base:@..\..\os\win32\BaseAddr.ref,mod_env.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_env.so" /base:@..\..\os\win32\BaseAddr.ref,mod_env.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_env.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_env - Win32 Release"
+# Name "mod_env - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_env.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/metadata/mod_env.exp b/modules/metadata/mod_env.exp
new file mode 100644
index 0000000..b487bf0
--- /dev/null
+++ b/modules/metadata/mod_env.exp
@@ -0,0 +1 @@
+env_module
diff --git a/modules/metadata/mod_env.mak b/modules/metadata/mod_env.mak
new file mode 100644
index 0000000..6207830
--- /dev/null
+++ b/modules/metadata/mod_env.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_env.dsp
+!IF "$(CFG)" == ""
+CFG=mod_env - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_env - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_env - Win32 Release" && "$(CFG)" != "mod_env - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_env.mak" CFG="mod_env - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_env - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_env - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_env - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_env.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_env.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_env.obj"
+ -@erase "$(INTDIR)\mod_env.res"
+ -@erase "$(INTDIR)\mod_env_src.idb"
+ -@erase "$(INTDIR)\mod_env_src.pdb"
+ -@erase "$(OUTDIR)\mod_env.exp"
+ -@erase "$(OUTDIR)\mod_env.lib"
+ -@erase "$(OUTDIR)\mod_env.pdb"
+ -@erase "$(OUTDIR)\mod_env.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_env_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_env.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_env.so" /d LONG_NAME="env_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_env.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_env.pdb" /debug /out:"$(OUTDIR)\mod_env.so" /implib:"$(OUTDIR)\mod_env.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_env.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_env.obj" \
+ "$(INTDIR)\mod_env.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_env.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_env.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_env.so"
+ if exist .\Release\mod_env.so.manifest mt.exe -manifest .\Release\mod_env.so.manifest -outputresource:.\Release\mod_env.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_env - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_env.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_env.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_env.obj"
+ -@erase "$(INTDIR)\mod_env.res"
+ -@erase "$(INTDIR)\mod_env_src.idb"
+ -@erase "$(INTDIR)\mod_env_src.pdb"
+ -@erase "$(OUTDIR)\mod_env.exp"
+ -@erase "$(OUTDIR)\mod_env.lib"
+ -@erase "$(OUTDIR)\mod_env.pdb"
+ -@erase "$(OUTDIR)\mod_env.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_env_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_env.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_env.so" /d LONG_NAME="env_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_env.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_env.pdb" /debug /out:"$(OUTDIR)\mod_env.so" /implib:"$(OUTDIR)\mod_env.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_env.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_env.obj" \
+ "$(INTDIR)\mod_env.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_env.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_env.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_env.so"
+ if exist .\Debug\mod_env.so.manifest mt.exe -manifest .\Debug\mod_env.so.manifest -outputresource:.\Debug\mod_env.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_env.dep")
+!INCLUDE "mod_env.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_env.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_env - Win32 Release" || "$(CFG)" == "mod_env - Win32 Debug"
+
+!IF "$(CFG)" == "mod_env - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\metadata"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_env - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\metadata"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_env - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\metadata"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_env - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\metadata"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_env - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\metadata"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_env - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\metadata"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\metadata"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_env - Win32 Release"
+
+
+"$(INTDIR)\mod_env.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_env.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_env.so" /d LONG_NAME="env_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_env - Win32 Debug"
+
+
+"$(INTDIR)\mod_env.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_env.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_env.so" /d LONG_NAME="env_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_env.c
+
+"$(INTDIR)\mod_env.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/metadata/mod_expires.c b/modules/metadata/mod_expires.c
new file mode 100644
index 0000000..447fce4
--- /dev/null
+++ b/modules/metadata/mod_expires.c
@@ -0,0 +1,571 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * mod_expires.c
+ * version 0.0.11
+ * status beta
+ *
+ * Andrew Wilson <Andrew.Wilson@cm.cf.ac.uk> 26.Jan.96
+ *
+ * This module allows you to control the form of the Expires: header
+ * that Apache issues for each access. Directives can appear in
+ * configuration files or in .htaccess files so expiry semantics can
+ * be defined on a per-directory basis.
+ *
+ * DIRECTIVE SYNTAX
+ *
+ * Valid directives are:
+ *
+ * ExpiresActive on | off
+ * ExpiresDefault <code><seconds>
+ * ExpiresByType type/encoding <code><seconds>
+ *
+ * Valid values for <code> are:
+ *
+ * 'M' expires header shows file modification date + <seconds>
+ * 'A' expires header shows access time + <seconds>
+ *
+ * [I'm not sure which of these is best under different
+ * circumstances, I guess it's for other people to explore.
+ * The effects may be indistinguishable for a number of cases]
+ *
+ * <seconds> should be an integer value [acceptable to atoi()]
+ *
+ * There is NO space between the <code> and <seconds>.
+ *
+ * For example, a directory which contains information which changes
+ * frequently might contain:
+ *
+ * # reports generated by cron every hour. don't let caches
+ * # hold onto stale information
+ * ExpiresDefault M3600
+ *
+ * Another example, our html pages can change all the time, the gifs
+ * tend not to change often:
+ *
+ * # pages are hot (1 week), images are cold (1 month)
+ * ExpiresByType text/html A604800
+ * ExpiresByType image/gif A2592000
+ *
+ * Expires can be turned on for all URLs on the server by placing the
+ * following directive in a conf file:
+ *
+ * ExpiresActive on
+ *
+ * ExpiresActive can also appear in .htaccess files, enabling the
+ * behaviour to be turned on or off for each chosen directory.
+ *
+ * # turn off Expires behaviour in this directory
+ * # and subdirectories
+ * ExpiresActive off
+ *
+ * Directives defined for a directory are valid in subdirectories
+ * unless explicitly overridden by new directives in the subdirectory
+ * .htaccess files.
+ *
+ * ALTERNATIVE DIRECTIVE SYNTAX
+ *
+ * Directives can also be defined in a more readable syntax of the form:
+ *
+ * ExpiresDefault "<base> [plus] {<num> <type>}*"
+ * ExpiresByType type/encoding "<base> [plus] {<num> <type>}*"
+ *
+ * where <base> is one of:
+ * access
+ * now equivalent to 'access'
+ * modification
+ *
+ * where the 'plus' keyword is optional
+ *
+ * where <num> should be an integer value [acceptable to atoi()]
+ *
+ * where <type> is one of:
+ * years
+ * months
+ * weeks
+ * days
+ * hours
+ * minutes
+ * seconds
+ *
+ * For example, any of the following directives can be used to make
+ * documents expire 1 month after being accessed, by default:
+ *
+ * ExpiresDefault "access plus 1 month"
+ * ExpiresDefault "access plus 4 weeks"
+ * ExpiresDefault "access plus 30 days"
+ *
+ * The expiry time can be fine-tuned by adding several '<num> <type>'
+ * clauses:
+ *
+ * ExpiresByType text/html "access plus 1 month 15 days 2 hours"
+ * ExpiresByType image/gif "modification plus 5 hours 3 minutes"
+ *
+ * ---
+ *
+ * Change-log:
+ * 29.Jan.96 Hardened the add_* functions. Server will now bail out
+ * if bad directives are given in the conf files.
+ * 02.Feb.96 Returns DECLINED if not 'ExpiresActive on', giving other
+ * expires-aware modules a chance to play with the same
+ * directives. [Michael Rutman]
+ * 03.Feb.96 Call tzset() before localtime(). Trying to get the module
+ * to work properly in non GMT timezones.
+ * 12.Feb.96 Modified directive syntax to allow more readable commands:
+ * ExpiresDefault "now plus 10 days 20 seconds"
+ * ExpiresDefault "access plus 30 days"
+ * ExpiresDefault "modification plus 1 year 10 months 30 days"
+ * 13.Feb.96 Fix call to table_get() with NULL 2nd parameter [Rob Hartill]
+ * 19.Feb.96 Call gm_timestr_822() to get time formatted correctly, can't
+ * rely on presence of HTTP_TIME_FORMAT in Apache 1.1+.
+ * 21.Feb.96 This version (0.0.9) reverses assumptions made in 0.0.8
+ * about star/star handlers. Reverting to 0.0.7 behaviour.
+ * 08.Jun.96 allows ExpiresDefault to be used with responses that use
+ * the DefaultType by not DECLINING, but instead skipping
+ * the table_get check and then looking for an ExpiresDefault.
+ * [Rob Hartill]
+ * 04.Nov.96 'const' definitions added.
+ *
+ * TODO
+ * add support for Cache-Control: max-age=20 from the HTTP/1.1
+ * proposal (in this case, a ttl of 20 seconds) [ask roy]
+ * add per-file expiry and explicit expiry times - duplicates some
+ * of the mod_cern_meta.c functionality. eg:
+ * ExpiresExplicit index.html "modification plus 30 days"
+ *
+ * BUGS
+ * Hi, welcome to the internet.
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "http_request.h"
+#include "http_protocol.h"
+
+typedef struct {
+ int active;
+ int wildcards;
+ char *expiresdefault;
+ apr_table_t *expiresbytype;
+} expires_dir_config;
+
+/* from mod_dir, why is this alias used?
+ */
+#define DIR_CMD_PERMS OR_INDEXES
+
+#define ACTIVE_ON 1
+#define ACTIVE_OFF 0
+#define ACTIVE_DONTCARE 2
+
+module AP_MODULE_DECLARE_DATA expires_module;
+
+static void *create_dir_expires_config(apr_pool_t *p, char *dummy)
+{
+ expires_dir_config *new =
+ (expires_dir_config *) apr_pcalloc(p, sizeof(expires_dir_config));
+ new->active = ACTIVE_DONTCARE;
+ new->wildcards = 0;
+ new->expiresdefault = NULL;
+ new->expiresbytype = apr_table_make(p, 4);
+ return (void *) new;
+}
+
+static const char *set_expiresactive(cmd_parms *cmd, void *in_dir_config, int arg)
+{
+ expires_dir_config *dir_config = in_dir_config;
+
+ /* if we're here at all it's because someone explicitly
+ * set the active flag
+ */
+ dir_config->active = ACTIVE_ON;
+ if (arg == 0) {
+ dir_config->active = ACTIVE_OFF;
+ }
+ return NULL;
+}
+
+/* check_code() parse 'code' and return NULL or an error response
+ * string. If we return NULL then real_code contains code converted
+ * to the cnnnn format.
+ */
+static char *check_code(apr_pool_t *p, const char *code, char **real_code)
+{
+ char *word;
+ char base = 'X';
+ int modifier = 0;
+ int num = 0;
+ int factor;
+
+ /* 0.0.4 compatibility?
+ */
+ if ((code[0] == 'A') || (code[0] == 'M')) {
+ *real_code = (char *)code;
+ return NULL;
+ }
+
+ /* <base> [plus] {<num> <type>}*
+ */
+
+ /* <base>
+ */
+ word = ap_getword_conf(p, &code);
+ if (!strncasecmp(word, "now", 1) ||
+ !strncasecmp(word, "access", 1)) {
+ base = 'A';
+ }
+ else if (!strncasecmp(word, "modification", 1)) {
+ base = 'M';
+ }
+ else {
+ return apr_pstrcat(p, "bad expires code, unrecognised <base> '",
+ word, "'", NULL);
+ }
+
+ /* [plus]
+ */
+ word = ap_getword_conf(p, &code);
+ if (!strncasecmp(word, "plus", 1)) {
+ word = ap_getword_conf(p, &code);
+ }
+
+ /* {<num> <type>}*
+ */
+ while (word[0]) {
+ /* <num>
+ */
+ if (apr_isdigit(word[0])) {
+ num = atoi(word);
+ }
+ else {
+ return apr_pstrcat(p, "bad expires code, numeric value expected <num> '",
+ word, "'", NULL);
+ }
+
+ /* <type>
+ */
+ word = ap_getword_conf(p, &code);
+ if (word[0] == '\0') {
+ return apr_pstrcat(p, "bad expires code, missing <type>", NULL);
+ }
+
+ if (!strncasecmp(word, "years", 1)) {
+ factor = 60 * 60 * 24 * 365;
+ }
+ else if (!strncasecmp(word, "months", 2)) {
+ factor = 60 * 60 * 24 * 30;
+ }
+ else if (!strncasecmp(word, "weeks", 1)) {
+ factor = 60 * 60 * 24 * 7;
+ }
+ else if (!strncasecmp(word, "days", 1)) {
+ factor = 60 * 60 * 24;
+ }
+ else if (!strncasecmp(word, "hours", 1)) {
+ factor = 60 * 60;
+ }
+ else if (!strncasecmp(word, "minutes", 2)) {
+ factor = 60;
+ }
+ else if (!strncasecmp(word, "seconds", 1)) {
+ factor = 1;
+ }
+ else {
+ return apr_pstrcat(p, "bad expires code, unrecognised <type>",
+ "'", word, "'", NULL);
+ }
+
+ modifier = modifier + factor * num;
+
+ /* next <num>
+ */
+ word = ap_getword_conf(p, &code);
+ }
+
+ *real_code = apr_psprintf(p, "%c%d", base, modifier);
+
+ return NULL;
+}
+
+static const char *set_expiresbytype(cmd_parms *cmd, void *in_dir_config,
+ const char *mime, const char *code)
+{
+ expires_dir_config *dir_config = in_dir_config;
+ char *response, *real_code;
+ const char *check;
+
+ check = ap_strrchr_c(mime, '/');
+ if (check == NULL) {
+ return "Invalid mimetype: should contain a slash";
+ }
+ if ((strlen(++check) == 1) && (*check == '*')) {
+ dir_config->wildcards = 1;
+ }
+
+ if ((response = check_code(cmd->pool, code, &real_code)) == NULL) {
+ apr_table_setn(dir_config->expiresbytype, mime, real_code);
+ return NULL;
+ }
+ return apr_pstrcat(cmd->pool,
+ "'ExpiresByType ", mime, " ", code, "': ", response, NULL);
+}
+
+static const char *set_expiresdefault(cmd_parms *cmd, void *in_dir_config,
+ const char *code)
+{
+ expires_dir_config * dir_config = in_dir_config;
+ char *response, *real_code;
+
+ if ((response = check_code(cmd->pool, code, &real_code)) == NULL) {
+ dir_config->expiresdefault = real_code;
+ return NULL;
+ }
+ return apr_pstrcat(cmd->pool,
+ "'ExpiresDefault ", code, "': ", response, NULL);
+}
+
+static const command_rec expires_cmds[] =
+{
+ AP_INIT_FLAG("ExpiresActive", set_expiresactive, NULL, DIR_CMD_PERMS,
+ "Limited to 'on' or 'off'"),
+ AP_INIT_TAKE2("ExpiresByType", set_expiresbytype, NULL, DIR_CMD_PERMS,
+ "a MIME type followed by an expiry date code"),
+ AP_INIT_TAKE1("ExpiresDefault", set_expiresdefault, NULL, DIR_CMD_PERMS,
+ "an expiry date code"),
+ {NULL}
+};
+
+static void *merge_expires_dir_configs(apr_pool_t *p, void *basev, void *addv)
+{
+ expires_dir_config *new = (expires_dir_config *) apr_pcalloc(p, sizeof(expires_dir_config));
+ expires_dir_config *base = (expires_dir_config *) basev;
+ expires_dir_config *add = (expires_dir_config *) addv;
+
+ if (add->active == ACTIVE_DONTCARE) {
+ new->active = base->active;
+ }
+ else {
+ new->active = add->active;
+ }
+
+ if (add->expiresdefault != NULL) {
+ new->expiresdefault = add->expiresdefault;
+ }
+ else {
+ new->expiresdefault = base->expiresdefault;
+ }
+ new->wildcards = add->wildcards;
+ new->expiresbytype = apr_table_overlay(p, add->expiresbytype,
+ base->expiresbytype);
+ return new;
+}
+
+/*
+ * Handle the setting of the expiration response header fields according
+ * to our criteria.
+ */
+
+static int set_expiration_fields(request_rec *r, const char *code,
+ apr_table_t *t)
+{
+ apr_time_t base;
+ apr_time_t additional;
+ apr_time_t expires;
+ int additional_sec;
+ char *timestr;
+
+ switch (code[0]) {
+ case 'M':
+ if (r->finfo.filetype == APR_NOFILE) {
+ /* file doesn't exist on disk, so we can't do anything based on
+ * modification time. Note that this does _not_ log an error.
+ */
+ return DECLINED;
+ }
+ base = r->finfo.mtime;
+ additional_sec = atoi(&code[1]);
+ additional = apr_time_from_sec(additional_sec);
+ break;
+ case 'A':
+ /* there's been some discussion and it's possible that
+ * 'access time' will be stored in request structure
+ */
+ base = r->request_time;
+ additional_sec = atoi(&code[1]);
+ additional = apr_time_from_sec(additional_sec);
+ break;
+ default:
+ /* expecting the add_* routines to be case-hardened this
+ * is just a reminder that module is beta
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01500)
+ "internal error: bad expires code: %s", r->filename);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ expires = base + additional;
+ if (expires < r->request_time) {
+ expires = r->request_time;
+ }
+ apr_table_mergen(t, "Cache-Control",
+ apr_psprintf(r->pool, "max-age=%" APR_TIME_T_FMT,
+ apr_time_sec(expires - r->request_time)));
+ timestr = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
+ apr_rfc822_date(timestr, expires);
+ apr_table_setn(t, "Expires", timestr);
+ return OK;
+}
+
+/*
+ * Output filter to set the Expires response header field
+ * according to the content-type of the response -- if it hasn't
+ * already been set.
+ */
+static apr_status_t expires_filter(ap_filter_t *f,
+ apr_bucket_brigade *b)
+{
+ request_rec *r;
+ expires_dir_config *conf;
+ const char *expiry;
+ apr_table_t *t;
+
+ /* Don't add Expires headers to errors */
+ if (ap_is_HTTP_ERROR(f->r->status)) {
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, b);
+ }
+
+ r = f->r;
+ conf = (expires_dir_config *) ap_get_module_config(r->per_dir_config,
+ &expires_module);
+
+ /*
+ * Check to see which output header table we should use;
+ * mod_cgi loads script fields into r->err_headers_out,
+ * for instance.
+ */
+ expiry = apr_table_get(r->err_headers_out, "Expires");
+ if (expiry != NULL) {
+ t = r->err_headers_out;
+ }
+ else {
+ expiry = apr_table_get(r->headers_out, "Expires");
+ t = r->headers_out;
+ }
+ if (expiry == NULL) {
+ /*
+ * No expiration has been set, so we can apply any managed by
+ * this module. First, check to see if there is an applicable
+ * ExpiresByType directive.
+ */
+ expiry = apr_table_get(conf->expiresbytype,
+ ap_field_noparam(r->pool, r->content_type));
+ if (expiry == NULL) {
+ int usedefault = 1;
+ /*
+ * See if we have a wildcard entry for the major type.
+ */
+ if (conf->wildcards) {
+ char *checkmime;
+ char *spos;
+ checkmime = apr_pstrdup(r->pool, r->content_type);
+ spos = checkmime ? ap_strchr(checkmime, '/') : NULL;
+ if (spos != NULL) {
+ /*
+ * Without a '/' character, nothing we have will match.
+ * However, we have one.
+ */
+ if (strlen(++spos) > 0) {
+ *spos++ = '*';
+ *spos = '\0';
+ }
+ else {
+ checkmime = apr_pstrcat(r->pool, checkmime, "*", NULL);
+ }
+ expiry = apr_table_get(conf->expiresbytype, checkmime);
+ usedefault = (expiry == NULL);
+ }
+ }
+ if (usedefault) {
+ /*
+ * Use the ExpiresDefault directive
+ */
+ expiry = conf->expiresdefault;
+ }
+ }
+ if (expiry != NULL) {
+ set_expiration_fields(r, expiry, t);
+ }
+ }
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, b);
+}
+
+static void expires_insert_filter(request_rec *r)
+{
+ expires_dir_config *conf;
+
+ /* Don't add Expires headers to errors */
+ if (ap_is_HTTP_ERROR(r->status)) {
+ return;
+ }
+ /* Say no to subrequests */
+ if (r->main != NULL) {
+ return;
+ }
+ conf = (expires_dir_config *) ap_get_module_config(r->per_dir_config,
+ &expires_module);
+
+ /* Check to see if the filter is enabled and if there are any applicable
+ * config directives for this directory scope
+ */
+ if (conf->active != ACTIVE_ON ||
+ (apr_is_empty_table(conf->expiresbytype) && !conf->expiresdefault)) {
+ return;
+ }
+ ap_add_output_filter("MOD_EXPIRES", NULL, r, r->connection);
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ /* mod_expires needs to run *before* the cache save filter which is
+ * AP_FTYPE_CONTENT_SET-1. Otherwise, our expires won't be honored.
+ */
+ ap_register_output_filter("MOD_EXPIRES", expires_filter, NULL,
+ AP_FTYPE_CONTENT_SET-2);
+ ap_hook_insert_error_filter(expires_insert_filter, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_insert_filter(expires_insert_filter, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(expires) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_dir_expires_config, /* dir config creater */
+ merge_expires_dir_configs, /* dir merger --- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server configs */
+ expires_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/metadata/mod_expires.dep b/modules/metadata/mod_expires.dep
new file mode 100644
index 0000000..f2170ce
--- /dev/null
+++ b/modules/metadata/mod_expires.dep
@@ -0,0 +1,54 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_expires.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_expires.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/metadata/mod_expires.dsp b/modules/metadata/mod_expires.dsp
new file mode 100644
index 0000000..a87ddda
--- /dev/null
+++ b/modules/metadata/mod_expires.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_expires" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_expires - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_expires.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_expires.mak" CFG="mod_expires - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_expires - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_expires - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_expires - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_expires_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_expires.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_expires.so" /d LONG_NAME="expires_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_expires.so" /base:@..\..\os\win32\BaseAddr.ref,mod_expires.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_expires.so" /base:@..\..\os\win32\BaseAddr.ref,mod_expires.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_expires.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_expires - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_expires_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_expires.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_expires.so" /d LONG_NAME="expires_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_expires.so" /base:@..\..\os\win32\BaseAddr.ref,mod_expires.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_expires.so" /base:@..\..\os\win32\BaseAddr.ref,mod_expires.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_expires.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_expires - Win32 Release"
+# Name "mod_expires - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_expires.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/metadata/mod_expires.exp b/modules/metadata/mod_expires.exp
new file mode 100644
index 0000000..863a968
--- /dev/null
+++ b/modules/metadata/mod_expires.exp
@@ -0,0 +1 @@
+expires_module
diff --git a/modules/metadata/mod_expires.mak b/modules/metadata/mod_expires.mak
new file mode 100644
index 0000000..db62d31
--- /dev/null
+++ b/modules/metadata/mod_expires.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_expires.dsp
+!IF "$(CFG)" == ""
+CFG=mod_expires - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_expires - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_expires - Win32 Release" && "$(CFG)" != "mod_expires - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_expires.mak" CFG="mod_expires - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_expires - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_expires - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_expires - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_expires.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_expires.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_expires.obj"
+ -@erase "$(INTDIR)\mod_expires.res"
+ -@erase "$(INTDIR)\mod_expires_src.idb"
+ -@erase "$(INTDIR)\mod_expires_src.pdb"
+ -@erase "$(OUTDIR)\mod_expires.exp"
+ -@erase "$(OUTDIR)\mod_expires.lib"
+ -@erase "$(OUTDIR)\mod_expires.pdb"
+ -@erase "$(OUTDIR)\mod_expires.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_expires_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_expires.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_expires.so" /d LONG_NAME="expires_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_expires.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_expires.pdb" /debug /out:"$(OUTDIR)\mod_expires.so" /implib:"$(OUTDIR)\mod_expires.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_expires.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_expires.obj" \
+ "$(INTDIR)\mod_expires.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_expires.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_expires.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_expires.so"
+ if exist .\Release\mod_expires.so.manifest mt.exe -manifest .\Release\mod_expires.so.manifest -outputresource:.\Release\mod_expires.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_expires - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_expires.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_expires.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_expires.obj"
+ -@erase "$(INTDIR)\mod_expires.res"
+ -@erase "$(INTDIR)\mod_expires_src.idb"
+ -@erase "$(INTDIR)\mod_expires_src.pdb"
+ -@erase "$(OUTDIR)\mod_expires.exp"
+ -@erase "$(OUTDIR)\mod_expires.lib"
+ -@erase "$(OUTDIR)\mod_expires.pdb"
+ -@erase "$(OUTDIR)\mod_expires.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_expires_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_expires.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_expires.so" /d LONG_NAME="expires_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_expires.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_expires.pdb" /debug /out:"$(OUTDIR)\mod_expires.so" /implib:"$(OUTDIR)\mod_expires.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_expires.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_expires.obj" \
+ "$(INTDIR)\mod_expires.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_expires.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_expires.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_expires.so"
+ if exist .\Debug\mod_expires.so.manifest mt.exe -manifest .\Debug\mod_expires.so.manifest -outputresource:.\Debug\mod_expires.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_expires.dep")
+!INCLUDE "mod_expires.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_expires.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_expires - Win32 Release" || "$(CFG)" == "mod_expires - Win32 Debug"
+
+!IF "$(CFG)" == "mod_expires - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\metadata"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_expires - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\metadata"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_expires - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\metadata"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_expires - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\metadata"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_expires - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\metadata"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_expires - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\metadata"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\metadata"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_expires - Win32 Release"
+
+
+"$(INTDIR)\mod_expires.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_expires.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_expires.so" /d LONG_NAME="expires_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_expires - Win32 Debug"
+
+
+"$(INTDIR)\mod_expires.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_expires.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_expires.so" /d LONG_NAME="expires_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_expires.c
+
+"$(INTDIR)\mod_expires.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/metadata/mod_headers.c b/modules/metadata/mod_headers.c
new file mode 100644
index 0000000..ef812cd
--- /dev/null
+++ b/modules/metadata/mod_headers.c
@@ -0,0 +1,1010 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * mod_headers.c: Add/append/remove HTTP response headers
+ * Written by Paul Sutton, paul@ukweb.com, 1 Oct 1996
+ *
+ * The Header directive can be used to add/replace/remove HTTP headers
+ * within the response message. The RequestHeader directive can be used
+ * to add/replace/remove HTTP headers before a request message is processed.
+ * Valid in both per-server and per-dir configurations.
+ *
+ * Syntax is:
+ *
+ * Header action header value
+ * RequestHeader action header value
+ *
+ * Where action is one of:
+ * set - set this header, replacing any old value
+ * add - add this header, possible resulting in two or more
+ * headers with the same name
+ * append - append this text onto any existing header of this same
+ * merge - merge this text onto any existing header of this same,
+ * avoiding duplicate values
+ * unset - remove this header
+ * edit - transform the header value according to a regexp
+ *
+ * Where action is unset, the third argument (value) should not be given.
+ * The header name can include the colon, or not.
+ *
+ * The Header and RequestHeader directives can only be used where allowed
+ * by the FileInfo override.
+ *
+ * When the request is processed, the header directives are processed in
+ * this order: firstly, the main server, then the virtual server handling
+ * this request (if any), then any <Directory> sections (working downwards
+ * from the root dir), then an <Location> sections (working down from
+ * shortest URL component), the any <File> sections. This order is
+ * important if any 'set' or 'unset' actions are used. For example,
+ * the following two directives have different effect if applied in
+ * the reverse order:
+ *
+ * Header append Author "John P. Doe"
+ * Header unset Author
+ *
+ * Examples:
+ *
+ * To set the "Author" header, use
+ * Header add Author "John P. Doe"
+ *
+ * To remove a header:
+ * Header unset Author
+ *
+ */
+
+#include "apr.h"
+#include "apr_lib.h"
+#include "apr_strings.h"
+#include "apr_buckets.h"
+
+#include "apr_hash.h"
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_request.h"
+#include "http_ssl.h"
+#include "http_log.h"
+#include "util_filter.h"
+#include "http_protocol.h"
+#include "ap_expr.h"
+
+/* format_tag_hash is initialized during pre-config */
+static apr_hash_t *format_tag_hash;
+
+typedef enum {
+ hdr_add = 'a', /* add header (could mean multiple hdrs) */
+ hdr_set = 's', /* set (replace old value) */
+ hdr_append = 'm', /* append (merge into any old value) */
+ hdr_merge = 'g', /* merge (merge, but avoid duplicates) */
+ hdr_unset = 'u', /* unset header */
+ hdr_echo = 'e', /* echo headers from request to response */
+ hdr_edit = 'r', /* change value by regexp, match once */
+ hdr_edit_r = 'R', /* change value by regexp, everymatch */
+ hdr_setifempty = 'i', /* set value if header not already present*/
+ hdr_note = 'n' /* set value of header in a note */
+} hdr_actions;
+
+/*
+ * magic cmd->info values
+ */
+static char hdr_in = '0'; /* RequestHeader */
+static char hdr_out_onsuccess = '1'; /* Header onsuccess */
+static char hdr_out_always = '2'; /* Header always */
+
+/* Callback function type. */
+typedef const char *format_tag_fn(request_rec *r, char *a);
+
+/*
+ * There is an array of struct format_tag per Header/RequestHeader
+ * config directive
+ */
+typedef struct {
+ format_tag_fn *func;
+ char *arg;
+} format_tag;
+
+/* 'Magic' condition_var value to run action in post_read_request */
+static const char* condition_early = "early";
+/*
+ * There is one "header_entry" per Header/RequestHeader config directive
+ */
+typedef struct {
+ hdr_actions action;
+ const char *header;
+ apr_array_header_t *ta; /* Array of format_tag structs */
+ ap_regex_t *regex;
+ const char *condition_var;
+ const char *subs;
+ ap_expr_info_t *expr;
+ ap_expr_info_t *expr_out;
+} header_entry;
+
+/* echo_do is used for Header echo to iterate through the request headers*/
+typedef struct {
+ request_rec *r;
+ header_entry *hdr;
+} echo_do;
+
+/* edit_do is used for Header edit to iterate through the request headers */
+typedef struct {
+ request_rec *r;
+ header_entry *hdr;
+ apr_table_t *t;
+} edit_do;
+
+/*
+ * headers_conf is our per-module configuration. This is used as both
+ * a per-dir and per-server config
+ */
+typedef struct {
+ apr_array_header_t *fixup_in;
+ apr_array_header_t *fixup_out;
+ apr_array_header_t *fixup_err;
+} headers_conf;
+
+module AP_MODULE_DECLARE_DATA headers_module;
+
+/*
+ * Tag formatting functions
+ */
+static const char *constant_item(request_rec *r, char *stuff)
+{
+ return stuff;
+}
+static const char *header_request_duration(request_rec *r, char *a)
+{
+ return apr_psprintf(r->pool, "D=%" APR_TIME_T_FMT,
+ (apr_time_now() - r->request_time));
+}
+static const char *header_request_time(request_rec *r, char *a)
+{
+ return apr_psprintf(r->pool, "t=%" APR_TIME_T_FMT, r->request_time);
+}
+
+/* unwrap_header returns HDR with any newlines converted into
+ * whitespace if necessary. */
+static const char *unwrap_header(apr_pool_t *p, const char *hdr)
+{
+ if (ap_strchr_c(hdr, APR_ASCII_LF) || ap_strchr_c(hdr, APR_ASCII_CR)) {
+ char *ptr;
+
+ hdr = ptr = apr_pstrdup(p, hdr);
+
+ do {
+ if (*ptr == APR_ASCII_LF || *ptr == APR_ASCII_CR)
+ *ptr = APR_ASCII_BLANK;
+ } while (*ptr++);
+ }
+ return hdr;
+}
+
+static const char *header_request_env_var(request_rec *r, char *a)
+{
+ const char *s = apr_table_get(r->subprocess_env,a);
+
+ if (s)
+ return unwrap_header(r->pool, s);
+ else
+ return "(null)";
+}
+
+static const char *header_request_ssl_var(request_rec *r, char *name)
+{
+ const char *val = ap_ssl_var_lookup(r->pool, r->server,
+ r->connection, r, name);
+ if (val && val[0])
+ return unwrap_header(r->pool, val);
+ else
+ return "(null)";
+}
+
+static const char *header_request_loadavg(request_rec *r, char *a)
+{
+ ap_loadavg_t t;
+ ap_get_loadavg(&t);
+ return apr_psprintf(r->pool, "l=%.2f/%.2f/%.2f", t.loadavg,
+ t.loadavg5, t.loadavg15);
+}
+
+static const char *header_request_idle(request_rec *r, char *a)
+{
+ ap_sload_t t;
+ ap_get_sload(&t);
+ return apr_psprintf(r->pool, "i=%d", t.idle);
+}
+
+static const char *header_request_busy(request_rec *r, char *a)
+{
+ ap_sload_t t;
+ ap_get_sload(&t);
+ return apr_psprintf(r->pool, "b=%d", t.busy);
+}
+
+/*
+ * Config routines
+ */
+
+static void *create_headers_dir_config(apr_pool_t *p, char *d)
+{
+ headers_conf *conf = apr_pcalloc(p, sizeof(*conf));
+
+ conf->fixup_in = apr_array_make(p, 2, sizeof(header_entry));
+ conf->fixup_out = apr_array_make(p, 2, sizeof(header_entry));
+ conf->fixup_err = apr_array_make(p, 2, sizeof(header_entry));
+
+ return conf;
+}
+
+static void *merge_headers_config(apr_pool_t *p, void *basev, void *overridesv)
+{
+ headers_conf *newconf = apr_pcalloc(p, sizeof(*newconf));
+ headers_conf *base = basev;
+ headers_conf *overrides = overridesv;
+
+ newconf->fixup_in = apr_array_append(p, base->fixup_in,
+ overrides->fixup_in);
+ newconf->fixup_out = apr_array_append(p, base->fixup_out,
+ overrides->fixup_out);
+ newconf->fixup_err = apr_array_append(p, base->fixup_err,
+ overrides->fixup_err);
+
+ return newconf;
+}
+
+static char *parse_misc_string(apr_pool_t *p, format_tag *tag, const char **sa)
+{
+ const char *s;
+ char *d;
+
+ tag->func = constant_item;
+
+ s = *sa;
+ while (*s && *s != '%') {
+ s++;
+ }
+ /*
+ * This might allocate a few chars extra if there's a backslash
+ * escape in the format string.
+ */
+ tag->arg = apr_palloc(p, s - *sa + 1);
+
+ d = tag->arg;
+ s = *sa;
+ while (*s && *s != '%') {
+ if (*s != '\\') {
+ *d++ = *s++;
+ }
+ else {
+ s++;
+ switch (*s) {
+ case '\\':
+ *d++ = '\\';
+ s++;
+ break;
+ case 'r':
+ *d++ = '\r';
+ s++;
+ break;
+ case 'n':
+ *d++ = '\n';
+ s++;
+ break;
+ case 't':
+ *d++ = '\t';
+ s++;
+ break;
+ default:
+ /* copy verbatim */
+ *d++ = '\\';
+ /*
+ * Allow the loop to deal with this *s in the normal
+ * fashion so that it handles end of string etc.
+ * properly.
+ */
+ break;
+ }
+ }
+ }
+ *d = '\0';
+
+ *sa = s;
+ return NULL;
+}
+
+static char *parse_format_tag(apr_pool_t *p, format_tag *tag, const char **sa)
+{
+ const char *s = *sa;
+ const char * (*tag_handler)(request_rec *,char *);
+
+ /* Handle string literal/conditionals */
+ if (*s != '%') {
+ return parse_misc_string(p, tag, sa);
+ }
+ s++; /* skip the % */
+
+ /* Pass through %% or % at end of string as % */
+ if ((*s == '%') || (*s == '\0')) {
+ tag->func = constant_item;
+ tag->arg = "%";
+ if (*s)
+ s++;
+ *sa = s;
+ return NULL;
+ }
+
+ tag->arg = "\0";
+ /* grab the argument if there is one */
+ if (*s == '{') {
+ ++s;
+ tag->arg = ap_getword(p,&s,'}');
+ }
+
+ tag_handler = (const char * (*)(request_rec *,char *))apr_hash_get(format_tag_hash, s++, 1);
+
+ if (!tag_handler) {
+ char dummy[2];
+ dummy[0] = s[-1];
+ dummy[1] = '\0';
+ return apr_pstrcat(p, "Unrecognized header format %", dummy, NULL);
+ }
+ tag->func = tag_handler;
+
+ *sa = s;
+ return NULL;
+}
+
+/*
+ * A format string consists of white space, text and optional format
+ * tags in any order. E.g.,
+ *
+ * Header add MyHeader "Free form text %D %t more text"
+ *
+ * Decompose the format string into its tags. Each tag (struct format_tag)
+ * contains a pointer to the function used to format the tag. Then save each
+ * tag in the tag array anchored in the header_entry.
+ */
+static char *parse_format_string(cmd_parms *cmd, header_entry *hdr, const char *s)
+{
+ apr_pool_t *p = cmd->pool;
+ char *res;
+
+ /* No string to parse with unset and echo commands */
+ if (hdr->action == hdr_unset || hdr->action == hdr_echo) {
+ return NULL;
+ }
+ /* Tags are in the replacement value for edit */
+ else if (hdr->action == hdr_edit || hdr->action == hdr_edit_r ) {
+ s = hdr->subs;
+ }
+
+ if (!strncmp(s, "expr=", 5)) {
+ const char *err;
+ hdr->expr_out = ap_expr_parse_cmd(cmd, s+5,
+ AP_EXPR_FLAG_STRING_RESULT,
+ &err, NULL);
+ if (err) {
+ return apr_pstrcat(cmd->pool,
+ "Can't parse value expression : ", err, NULL);
+ }
+ return NULL;
+ }
+
+ hdr->ta = apr_array_make(p, 10, sizeof(format_tag));
+
+ while (*s) {
+ if ((res = parse_format_tag(p, (format_tag *) apr_array_push(hdr->ta), &s))) {
+ return res;
+ }
+ }
+ return NULL;
+}
+
+/* handle RequestHeader and Header directive */
+static APR_INLINE const char *header_inout_cmd(cmd_parms *cmd,
+ void *indirconf,
+ const char *action,
+ const char *hdr,
+ const char *value,
+ const char *subs,
+ const char *envclause)
+{
+ headers_conf *dirconf = indirconf;
+ const char *condition_var = NULL;
+ const char *colon;
+ header_entry *new;
+ ap_expr_info_t *expr = NULL;
+
+ apr_array_header_t *fixup = (cmd->info == &hdr_in)
+ ? dirconf->fixup_in : (cmd->info == &hdr_out_always)
+ ? dirconf->fixup_err
+ : dirconf->fixup_out;
+
+ new = (header_entry *) apr_array_push(fixup);
+
+ if (!strcasecmp(action, "set"))
+ new->action = hdr_set;
+ else if (!strcasecmp(action, "setifempty"))
+ new->action = hdr_setifempty;
+ else if (!strcasecmp(action, "add"))
+ new->action = hdr_add;
+ else if (!strcasecmp(action, "append"))
+ new->action = hdr_append;
+ else if (!strcasecmp(action, "merge"))
+ new->action = hdr_merge;
+ else if (!strcasecmp(action, "unset"))
+ new->action = hdr_unset;
+ else if (!strcasecmp(action, "echo"))
+ new->action = hdr_echo;
+ else if (!strcasecmp(action, "edit"))
+ new->action = hdr_edit;
+ else if (!strcasecmp(action, "edit*"))
+ new->action = hdr_edit_r;
+ else if (!strcasecmp(action, "note"))
+ new->action = hdr_note;
+ else
+ return "first argument must be 'add', 'set', 'setifempty', 'append', 'merge', "
+ "'unset', 'echo', 'note', 'edit', or 'edit*'.";
+
+ if (new->action == hdr_edit || new->action == hdr_edit_r) {
+ if (subs == NULL) {
+ return "Header edit requires a match and a substitution";
+ }
+ new->regex = ap_pregcomp(cmd->pool, value, AP_REG_EXTENDED);
+ if (new->regex == NULL) {
+ return "Header edit regex could not be compiled";
+ }
+ new->subs = subs;
+ }
+ else {
+ /* there's no subs, so envclause is really that argument */
+ if (envclause != NULL) {
+ return "Too many arguments to directive";
+ }
+ envclause = subs;
+ }
+ if (new->action == hdr_unset) {
+ if (value) {
+ if (envclause) {
+ return "header unset takes two arguments";
+ }
+ envclause = value;
+ value = NULL;
+ }
+ }
+ else if (new->action == hdr_echo) {
+ ap_regex_t *regex;
+
+ if (value) {
+ if (envclause) {
+ return "Header echo takes two arguments";
+ }
+ envclause = value;
+ value = NULL;
+ }
+ if (cmd->info != &hdr_out_onsuccess && cmd->info != &hdr_out_always)
+ return "Header echo only valid on Header "
+ "directives";
+ else {
+ regex = ap_pregcomp(cmd->pool, hdr, AP_REG_EXTENDED | AP_REG_NOSUB);
+ if (regex == NULL) {
+ return "Header echo regex could not be compiled";
+ }
+ }
+ new->regex = regex;
+ }
+ else if (!value)
+ return "Header requires three arguments";
+
+ /* Handle the envclause on Header */
+ if (envclause != NULL) {
+ if (strcasecmp(envclause, "early") == 0) {
+ condition_var = condition_early;
+ }
+ else if (strncasecmp(envclause, "env=", 4) == 0) {
+ if ((envclause[4] == '\0')
+ || ((envclause[4] == '!') && (envclause[5] == '\0'))) {
+ return "error: missing environment variable name. "
+ "envclause should be in the form env=envar ";
+ }
+ condition_var = envclause + 4;
+ }
+ else if (strncasecmp(envclause, "expr=", 5) == 0) {
+ const char *err = NULL;
+ expr = ap_expr_parse_cmd(cmd, envclause + 5, 0, &err, NULL);
+ if (err) {
+ return apr_pstrcat(cmd->pool,
+ "Can't parse envclause/expression: ", err,
+ NULL);
+ }
+ }
+ else {
+ return apr_pstrcat(cmd->pool, "Unknown parameter: ", envclause,
+ NULL);
+ }
+ }
+
+ if ((colon = ap_strchr_c(hdr, ':'))) {
+ hdr = apr_pstrmemdup(cmd->pool, hdr, colon-hdr);
+ }
+
+ new->header = hdr;
+ new->condition_var = condition_var;
+ new->expr = expr;
+
+ return parse_format_string(cmd, new, value);
+}
+
+/* Handle all (xxx)Header directives */
+static const char *header_cmd(cmd_parms *cmd, void *indirconf,
+ const char *args)
+{
+ const char *action;
+ const char *hdr;
+ const char *val;
+ const char *envclause;
+ const char *subs;
+
+ action = ap_getword_conf(cmd->temp_pool, &args);
+ if (cmd->info == &hdr_out_onsuccess) {
+ if (!strcasecmp(action, "always")) {
+ cmd->info = &hdr_out_always;
+ action = ap_getword_conf(cmd->temp_pool, &args);
+ }
+ else if (!strcasecmp(action, "onsuccess")) {
+ action = ap_getword_conf(cmd->temp_pool, &args);
+ }
+ }
+ hdr = ap_getword_conf(cmd->pool, &args);
+ val = *args ? ap_getword_conf(cmd->pool, &args) : NULL;
+ subs = *args ? ap_getword_conf(cmd->pool, &args) : NULL;
+ envclause = *args ? ap_getword_conf(cmd->pool, &args) : NULL;
+
+ if (*args) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ " has too many arguments", NULL);
+ }
+
+ return header_inout_cmd(cmd, indirconf, action, hdr, val, subs, envclause);
+}
+
+/*
+ * Process the tags in the format string. Tags may be format specifiers
+ * (%D, %t, etc.), whitespace or text strings. For each tag, run the handler
+ * (formatter) specific to the tag. Handlers return text strings.
+ * Concatenate the return from each handler into one string that is
+ * returned from this call.
+ * If the original value was prefixed with "expr=", processing is
+ * handled instead by ap_expr.
+ */
+static char* process_tags(header_entry *hdr, request_rec *r)
+{
+ int i;
+ const char *s;
+ char *str = NULL;
+ format_tag *tag = NULL;
+
+ if (hdr->expr_out) {
+ const char *err;
+ const char *val;
+ val = ap_expr_str_exec(r, hdr->expr_out, &err);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02557)
+ "Can't evaluate value expression: %s", err);
+ return "";
+ }
+ return apr_pstrdup(r->pool, val);
+ }
+
+ tag = (format_tag*) hdr->ta->elts;
+
+ for (i = 0; i < hdr->ta->nelts; i++) {
+ s = tag[i].func(r, tag[i].arg);
+ if (str == NULL)
+ str = apr_pstrdup(r->pool, s);
+ else
+ str = apr_pstrcat(r->pool, str, s, NULL);
+ }
+ return str ? str : "";
+}
+static const char *process_regexp(header_entry *hdr, const char *value,
+ request_rec *r)
+{
+ ap_regmatch_t pmatch[AP_MAX_REG_MATCH];
+ const char *subs;
+ const char *remainder;
+ char *ret;
+ int diffsz;
+ if (ap_regexec(hdr->regex, value, AP_MAX_REG_MATCH, pmatch, 0)) {
+ /* no match, nothing to do */
+ return value;
+ }
+ /* Process tags in the input string rather than the resulting
+ * substitution to avoid surprises
+ */
+ subs = ap_pregsub(r->pool, process_tags(hdr, r), value, AP_MAX_REG_MATCH, pmatch);
+ if (subs == NULL)
+ return NULL;
+ diffsz = strlen(subs) - (pmatch[0].rm_eo - pmatch[0].rm_so);
+ if (hdr->action == hdr_edit) {
+ remainder = value + pmatch[0].rm_eo;
+ }
+ else { /* recurse to edit multiple matches if applicable */
+ remainder = process_regexp(hdr, value + pmatch[0].rm_eo, r);
+ if (remainder == NULL)
+ return NULL;
+ diffsz += strlen(remainder) - strlen(value + pmatch[0].rm_eo);
+ }
+ ret = apr_palloc(r->pool, strlen(value) + 1 + diffsz);
+ memcpy(ret, value, pmatch[0].rm_so);
+ strcpy(ret + pmatch[0].rm_so, subs);
+ strcat(ret, remainder);
+ return ret;
+}
+
+static int echo_header(void *v, const char *key, const char *val)
+{
+ echo_do *ed = (echo_do *)v;
+
+ /* If the input header (key) matches the regex, echo it intact to
+ * r->headers_out.
+ */
+ if (!ap_regexec(ed->hdr->regex, key, 0, NULL, 0)) {
+ apr_table_add(ed->r->headers_out, key, val);
+ }
+
+ return 1;
+}
+
+static int edit_header(void *v, const char *key, const char *val)
+{
+ edit_do *ed = (edit_do *)v;
+ const char *repl = process_regexp(ed->hdr, val, ed->r);
+ if (repl == NULL)
+ return 0;
+
+ apr_table_addn(ed->t, key, repl);
+ return 1;
+}
+
+static int add_them_all(void *v, const char *key, const char *val)
+{
+ apr_table_t *headers = (apr_table_t *)v;
+
+ apr_table_addn(headers, key, val);
+ return 1;
+}
+
+static int do_headers_fixup(request_rec *r, apr_table_t *headers,
+ apr_array_header_t *fixup, int early)
+{
+ echo_do v;
+ int i;
+ const char *val;
+
+ for (i = 0; i < fixup->nelts; ++i) {
+ header_entry *hdr = &((header_entry *) (fixup->elts))[i];
+ const char *envar = hdr->condition_var;
+
+ /* ignore early headers in late calls */
+ if (!early && (envar == condition_early)) {
+ continue;
+ }
+ /* ignore late headers in early calls */
+ else if (early && (envar != condition_early)) {
+ continue;
+ }
+ /* Do we have an expression to evaluate? */
+ else if (hdr->expr != NULL) {
+ const char *err = NULL;
+ int eval = ap_expr_exec(r, hdr->expr, &err);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01501)
+ "Failed to evaluate expression (%s) - ignoring",
+ err);
+ }
+ else if (!eval) {
+ continue;
+ }
+ }
+ /* Have any conditional envar-controlled Header processing to do? */
+ else if (envar && !early) {
+ if (*envar != '!') {
+ if (apr_table_get(r->subprocess_env, envar) == NULL)
+ continue;
+ }
+ else {
+ if (apr_table_get(r->subprocess_env, &envar[1]) != NULL)
+ continue;
+ }
+ }
+
+ switch (hdr->action) {
+ case hdr_add:
+ apr_table_addn(headers, hdr->header, process_tags(hdr, r));
+ break;
+ case hdr_append:
+ apr_table_mergen(headers, hdr->header, process_tags(hdr, r));
+ break;
+ case hdr_merge:
+ val = apr_table_get(headers, hdr->header);
+ if (val == NULL) {
+ apr_table_addn(headers, hdr->header, process_tags(hdr, r));
+ } else {
+ char *new_val = process_tags(hdr, r);
+ apr_size_t new_val_len = strlen(new_val);
+ int tok_found = 0;
+
+ /* modified version of logic in ap_get_token() */
+ while (*val) {
+ const char *tok_start;
+
+ while (apr_isspace(*val))
+ ++val;
+
+ tok_start = val;
+
+ while (*val && *val != ',') {
+ if (*val++ == '"')
+ while (*val)
+ if (*val++ == '"')
+ break;
+ }
+
+ if (new_val_len == (apr_size_t)(val - tok_start)
+ && !strncmp(tok_start, new_val, new_val_len)) {
+ tok_found = 1;
+ break;
+ }
+
+ if (*val)
+ ++val;
+ }
+
+ if (!tok_found) {
+ apr_table_mergen(headers, hdr->header, new_val);
+ }
+ }
+ break;
+ case hdr_set:
+ if (!ap_cstr_casecmp(hdr->header, "Content-Type")) {
+ ap_set_content_type(r, process_tags(hdr, r));
+ }
+ apr_table_setn(headers, hdr->header, process_tags(hdr, r));
+ break;
+ case hdr_setifempty:
+ if (NULL == apr_table_get(headers, hdr->header)) {
+ if (!ap_cstr_casecmp(hdr->header, "Content-Type")) {
+ ap_set_content_type(r, process_tags(hdr, r));
+ }
+ apr_table_setn(headers, hdr->header, process_tags(hdr, r));
+ }
+ break;
+ case hdr_unset:
+ apr_table_unset(headers, hdr->header);
+ break;
+ case hdr_echo:
+ v.r = r;
+ v.hdr = hdr;
+ apr_table_do(echo_header, &v, r->headers_in, NULL);
+ break;
+ case hdr_edit:
+ case hdr_edit_r:
+ if (!ap_cstr_casecmp(hdr->header, "Content-Type") && r->content_type) {
+ const char *repl = process_regexp(hdr, r->content_type, r);
+ if (repl == NULL)
+ return 0;
+ ap_set_content_type(r, repl);
+ }
+ if (apr_table_get(headers, hdr->header)) {
+ edit_do ed;
+
+ ed.r = r;
+ ed.hdr = hdr;
+ ed.t = apr_table_make(r->pool, 5);
+ if (!apr_table_do(edit_header, (void *) &ed, headers,
+ hdr->header, NULL))
+ return 0;
+ apr_table_unset(headers, hdr->header);
+ apr_table_do(add_them_all, (void *) headers, ed.t, NULL);
+ }
+ break;
+ case hdr_note:
+ apr_table_setn(r->notes, process_tags(hdr, r), apr_table_get(headers, hdr->header));
+ break;
+
+ }
+ }
+ return 1;
+}
+
+static void ap_headers_insert_output_filter(request_rec *r)
+{
+ headers_conf *dirconf = ap_get_module_config(r->per_dir_config,
+ &headers_module);
+
+ if (dirconf->fixup_out->nelts || dirconf->fixup_err->nelts) {
+ ap_add_output_filter("FIXUP_HEADERS_OUT", NULL, r, r->connection);
+ }
+}
+
+/*
+ * Make sure our error-path filter is in place.
+ */
+static void ap_headers_insert_error_filter(request_rec *r)
+{
+ headers_conf *dirconf = ap_get_module_config(r->per_dir_config,
+ &headers_module);
+
+ if (dirconf->fixup_err->nelts) {
+ ap_add_output_filter("FIXUP_HEADERS_ERR", NULL, r, r->connection);
+ }
+}
+
+static apr_status_t ap_headers_output_filter(ap_filter_t *f,
+ apr_bucket_brigade *in)
+{
+ headers_conf *dirconf = ap_get_module_config(f->r->per_dir_config,
+ &headers_module);
+
+ ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, f->r->server, APLOGNO(01502)
+ "headers: ap_headers_output_filter()");
+
+ /* do the fixup */
+ do_headers_fixup(f->r, f->r->err_headers_out, dirconf->fixup_err, 0);
+ do_headers_fixup(f->r, f->r->headers_out, dirconf->fixup_out, 0);
+
+ /* remove ourselves from the filter chain */
+ ap_remove_output_filter(f);
+
+ /* send the data up the stack */
+ return ap_pass_brigade(f->next,in);
+}
+
+/*
+ * Make sure we propagate any "Header always" settings on the error
+ * path through http_protocol.c.
+ */
+static apr_status_t ap_headers_error_filter(ap_filter_t *f,
+ apr_bucket_brigade *in)
+{
+ headers_conf *dirconf;
+
+ dirconf = ap_get_module_config(f->r->per_dir_config,
+ &headers_module);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, f->r->server, APLOGNO(01503)
+ "headers: ap_headers_error_filter()");
+
+ /*
+ * Add any header fields defined by "Header always" to r->err_headers_out.
+ * Server-wide first, then per-directory to allow overriding.
+ */
+ do_headers_fixup(f->r, f->r->err_headers_out, dirconf->fixup_err, 0);
+
+ /*
+ * We've done our bit; remove ourself from the filter chain so there's
+ * no possibility we'll be called again.
+ */
+ ap_remove_output_filter(f);
+
+ /*
+ * Pass the buck. (euro?)
+ */
+ return ap_pass_brigade(f->next, in);
+}
+
+static apr_status_t ap_headers_fixup(request_rec *r)
+{
+ headers_conf *dirconf = ap_get_module_config(r->per_dir_config,
+ &headers_module);
+
+ /* do the fixup */
+ if (dirconf->fixup_in->nelts) {
+ do_headers_fixup(r, r->headers_in, dirconf->fixup_in, 0);
+ }
+
+ return DECLINED;
+}
+static apr_status_t ap_headers_early(request_rec *r)
+{
+ headers_conf *dirconf = ap_get_module_config(r->per_dir_config,
+ &headers_module);
+
+ /* do the fixup */
+ if (dirconf->fixup_in->nelts) {
+ if (!do_headers_fixup(r, r->headers_in, dirconf->fixup_in, 1))
+ goto err;
+ }
+ if (dirconf->fixup_err->nelts) {
+ if (!do_headers_fixup(r, r->err_headers_out, dirconf->fixup_err, 1))
+ goto err;
+ }
+ if (dirconf->fixup_out->nelts) {
+ if (!do_headers_fixup(r, r->headers_out, dirconf->fixup_out, 1))
+ goto err;
+ }
+
+ return DECLINED;
+err:
+ ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, APLOGNO(01504)
+ "Regular expression replacement failed (replacement too long?)");
+ return HTTP_INTERNAL_SERVER_ERROR;
+}
+
+static const command_rec headers_cmds[] =
+{
+ AP_INIT_RAW_ARGS("Header", header_cmd, &hdr_out_onsuccess, OR_FILEINFO,
+ "an optional condition, an action, header and value "
+ "followed by optional env clause"),
+ AP_INIT_RAW_ARGS("RequestHeader", header_cmd, &hdr_in, OR_FILEINFO,
+ "an action, header and value followed by optional env "
+ "clause"),
+ {NULL}
+};
+
+static void register_format_tag_handler(const char *tag,
+ format_tag_fn *tag_handler)
+{
+ apr_hash_set(format_tag_hash, tag, 1, tag_handler);
+}
+
+static int header_pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp)
+{
+ format_tag_hash = apr_hash_make(p);
+ register_format_tag_handler("D", header_request_duration);
+ register_format_tag_handler("t", header_request_time);
+ register_format_tag_handler("e", header_request_env_var);
+ register_format_tag_handler("s", header_request_ssl_var);
+ register_format_tag_handler("l", header_request_loadavg);
+ register_format_tag_handler("i", header_request_idle);
+ register_format_tag_handler("b", header_request_busy);
+
+ return OK;
+}
+
+static int header_post_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ return OK;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_register_output_filter("FIXUP_HEADERS_OUT", ap_headers_output_filter,
+ NULL, AP_FTYPE_CONTENT_SET);
+ ap_register_output_filter("FIXUP_HEADERS_ERR", ap_headers_error_filter,
+ NULL, AP_FTYPE_CONTENT_SET);
+ ap_hook_pre_config(header_pre_config,NULL,NULL,APR_HOOK_MIDDLE);
+ ap_hook_post_config(header_post_config,NULL,NULL,APR_HOOK_MIDDLE);
+ ap_hook_insert_filter(ap_headers_insert_output_filter, NULL, NULL, APR_HOOK_LAST);
+ ap_hook_insert_error_filter(ap_headers_insert_error_filter,
+ NULL, NULL, APR_HOOK_LAST);
+ ap_hook_fixups(ap_headers_fixup, NULL, NULL, APR_HOOK_LAST);
+ ap_hook_post_read_request(ap_headers_early, NULL, NULL, APR_HOOK_FIRST);
+}
+
+AP_DECLARE_MODULE(headers) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_headers_dir_config, /* dir config creater */
+ merge_headers_config, /* dir merger --- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server configs */
+ headers_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/metadata/mod_headers.dep b/modules/metadata/mod_headers.dep
new file mode 100644
index 0000000..d4cef71
--- /dev/null
+++ b/modules/metadata/mod_headers.dep
@@ -0,0 +1,57 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_headers.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_headers.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ "..\ssl\mod_ssl.h"\
+
diff --git a/modules/metadata/mod_headers.dsp b/modules/metadata/mod_headers.dsp
new file mode 100644
index 0000000..c36c490
--- /dev/null
+++ b/modules/metadata/mod_headers.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_headers" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_headers - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_headers.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_headers.mak" CFG="mod_headers - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_headers - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_headers - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_headers - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../ssl" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_headers_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_headers.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_headers.so" /d LONG_NAME="headers_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_headers.so" /base:@..\..\os\win32\BaseAddr.ref,mod_headers.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /debug /out:".\Release\mod_headers.so" /base:@..\..\os\win32\BaseAddr.ref,mod_headers.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_headers.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_headers - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../ssl" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_headers_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_headers.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_headers.so" /d LONG_NAME="headers_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_headers.so" /base:@..\..\os\win32\BaseAddr.ref,mod_headers.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_headers.so" /base:@..\..\os\win32\BaseAddr.ref,mod_headers.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_headers.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_headers - Win32 Release"
+# Name "mod_headers - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_headers.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/metadata/mod_headers.exp b/modules/metadata/mod_headers.exp
new file mode 100644
index 0000000..3f30638
--- /dev/null
+++ b/modules/metadata/mod_headers.exp
@@ -0,0 +1 @@
+headers_module
diff --git a/modules/metadata/mod_headers.mak b/modules/metadata/mod_headers.mak
new file mode 100644
index 0000000..7f9cddd
--- /dev/null
+++ b/modules/metadata/mod_headers.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_headers.dsp
+!IF "$(CFG)" == ""
+CFG=mod_headers - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_headers - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_headers - Win32 Release" && "$(CFG)" != "mod_headers - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_headers.mak" CFG="mod_headers - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_headers - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_headers - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_headers - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_headers.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_headers.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_headers.obj"
+ -@erase "$(INTDIR)\mod_headers.res"
+ -@erase "$(INTDIR)\mod_headers_src.idb"
+ -@erase "$(INTDIR)\mod_headers_src.pdb"
+ -@erase "$(OUTDIR)\mod_headers.exp"
+ -@erase "$(OUTDIR)\mod_headers.lib"
+ -@erase "$(OUTDIR)\mod_headers.pdb"
+ -@erase "$(OUTDIR)\mod_headers.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../ssl" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_headers_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_headers.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_headers.so" /d LONG_NAME="headers_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_headers.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_headers.pdb" /debug /out:"$(OUTDIR)\mod_headers.so" /implib:"$(OUTDIR)\mod_headers.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_headers.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_headers.obj" \
+ "$(INTDIR)\mod_headers.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_headers.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_headers.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_headers.so"
+ if exist .\Release\mod_headers.so.manifest mt.exe -manifest .\Release\mod_headers.so.manifest -outputresource:.\Release\mod_headers.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_headers - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_headers.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_headers.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_headers.obj"
+ -@erase "$(INTDIR)\mod_headers.res"
+ -@erase "$(INTDIR)\mod_headers_src.idb"
+ -@erase "$(INTDIR)\mod_headers_src.pdb"
+ -@erase "$(OUTDIR)\mod_headers.exp"
+ -@erase "$(OUTDIR)\mod_headers.lib"
+ -@erase "$(OUTDIR)\mod_headers.pdb"
+ -@erase "$(OUTDIR)\mod_headers.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../ssl" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_headers_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_headers.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_headers.so" /d LONG_NAME="headers_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_headers.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_headers.pdb" /debug /out:"$(OUTDIR)\mod_headers.so" /implib:"$(OUTDIR)\mod_headers.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_headers.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_headers.obj" \
+ "$(INTDIR)\mod_headers.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_headers.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_headers.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_headers.so"
+ if exist .\Debug\mod_headers.so.manifest mt.exe -manifest .\Debug\mod_headers.so.manifest -outputresource:.\Debug\mod_headers.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_headers.dep")
+!INCLUDE "mod_headers.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_headers.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_headers - Win32 Release" || "$(CFG)" == "mod_headers - Win32 Debug"
+
+!IF "$(CFG)" == "mod_headers - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\metadata"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_headers - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\metadata"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_headers - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\metadata"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_headers - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\metadata"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_headers - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\metadata"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_headers - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\metadata"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\metadata"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_headers - Win32 Release"
+
+
+"$(INTDIR)\mod_headers.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_headers.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_headers.so" /d LONG_NAME="headers_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_headers - Win32 Debug"
+
+
+"$(INTDIR)\mod_headers.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_headers.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_headers.so" /d LONG_NAME="headers_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_headers.c
+
+"$(INTDIR)\mod_headers.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/metadata/mod_ident.c b/modules/metadata/mod_ident.c
new file mode 100644
index 0000000..5520a80
--- /dev/null
+++ b/modules/metadata/mod_ident.c
@@ -0,0 +1,344 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * mod_ident: Handle RFC 1413 ident request
+ * obtained from rfc1413.c
+ *
+ * rfc1413() speaks a common subset of the RFC 1413, AUTH, TAP and IDENT
+ * protocols. The code queries an RFC 1413 etc. compatible daemon on a remote
+ * host to look up the owner of a connection. The information should not be
+ * used for authentication purposes. This routine intercepts alarm signals.
+ *
+ * Author: Wietse Venema, Eindhoven University of Technology,
+ * The Netherlands.
+ */
+
+/* Some small additions for Apache --- ditch the "sccsid" var if
+ * compiling with gcc (it *has* changed), include ap_config.h for the
+ * prototypes it defines on at least one system (SunlOSs) which has
+ * them missing from the standard header files, and one minor change
+ * below (extra parens around assign "if (foo = bar) ..." to shut up
+ * gcc -Wall).
+ */
+
+/* Rewritten by David Robinson */
+
+#include "apr.h"
+#include "apr_network_io.h"
+#include "apr_strings.h"
+#include "apr_optional.h"
+
+#define APR_WANT_STDIO
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "httpd.h" /* for server_rec, conn_rec, etc. */
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h" /* for aplog_error */
+#include "util_ebcdic.h"
+
+/* Whether we should enable rfc1413 identity checking */
+#ifndef DEFAULT_RFC1413
+#define DEFAULT_RFC1413 0
+#endif
+
+#define RFC1413_UNSET 2
+
+/* request timeout (sec) */
+#ifndef RFC1413_TIMEOUT
+#define RFC1413_TIMEOUT 30
+#endif
+
+/* Local stuff. */
+
+/* Semi-well-known port */
+#define RFC1413_PORT 113
+
+/* maximum allowed length of userid */
+#define RFC1413_USERLEN 512
+
+/* rough limit on the amount of data we accept. */
+#define RFC1413_MAXDATA 1000
+
+/* default username, if it could not determined */
+#define FROM_UNKNOWN "unknown"
+
+typedef struct {
+ int do_rfc1413;
+ int timeout_unset;
+ apr_time_t timeout;
+} ident_config_rec;
+
+static apr_status_t rfc1413_connect(apr_socket_t **newsock, conn_rec *conn,
+ server_rec *srv, apr_time_t timeout)
+{
+ apr_status_t rv;
+ apr_sockaddr_t *localsa, *destsa;
+
+ if ((rv = apr_sockaddr_info_get(&localsa, conn->local_ip, APR_UNSPEC,
+ 0, /* ephemeral port */
+ 0, conn->pool)) != APR_SUCCESS) {
+ /* This should not fail since we have a numeric address string
+ * as the host. */
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, srv, APLOGNO(01492)
+ "rfc1413: apr_sockaddr_info_get(%s) failed",
+ conn->local_ip);
+ return rv;
+ }
+
+ if ((rv = apr_sockaddr_info_get(&destsa, conn->client_ip,
+ localsa->family, /* has to match */
+ RFC1413_PORT, 0, conn->pool)) != APR_SUCCESS) {
+ /* This should not fail since we have a numeric address string
+ * as the host. */
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, srv, APLOGNO(01493)
+ "rfc1413: apr_sockaddr_info_get(%s) failed",
+ conn->client_ip);
+ return rv;
+ }
+
+ if ((rv = apr_socket_create(newsock,
+ localsa->family, /* has to match */
+ SOCK_STREAM, 0, conn->pool)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, srv, APLOGNO(01494)
+ "rfc1413: error creating query socket");
+ return rv;
+ }
+
+ if ((rv = apr_socket_timeout_set(*newsock, timeout)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, srv, APLOGNO(01495)
+ "rfc1413: error setting query socket timeout");
+ apr_socket_close(*newsock);
+ return rv;
+ }
+
+/*
+ * Bind the local and remote ends of the query socket to the same
+ * IP addresses as the connection under investigation. We go
+ * through all this trouble because the local or remote system
+ * might have more than one network address. The RFC1413 etc.
+ * client sends only port numbers; the server takes the IP
+ * addresses from the query socket.
+ */
+
+ if ((rv = apr_socket_bind(*newsock, localsa)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, srv, APLOGNO(01496)
+ "rfc1413: Error binding query socket to local port");
+ apr_socket_close(*newsock);
+ return rv;
+ }
+
+/*
+ * errors from connect usually imply the remote machine doesn't support
+ * the service; don't log such an error
+ */
+ if ((rv = apr_socket_connect(*newsock, destsa)) != APR_SUCCESS) {
+ apr_socket_close(*newsock);
+ return rv;
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t rfc1413_query(apr_socket_t *sock, conn_rec *conn,
+ server_rec *srv)
+{
+ apr_port_t rmt_port, our_port;
+ apr_port_t sav_rmt_port, sav_our_port;
+ apr_size_t i;
+ char *cp;
+ char buffer[RFC1413_MAXDATA + 1];
+ char user[RFC1413_USERLEN + 1]; /* XXX */
+ apr_size_t buflen;
+
+ sav_our_port = conn->local_addr->port;
+ sav_rmt_port = conn->client_addr->port;
+
+ /* send the data */
+ buflen = apr_snprintf(buffer, sizeof(buffer), "%hu,%hu\r\n", sav_rmt_port,
+ sav_our_port);
+ ap_xlate_proto_to_ascii(buffer, buflen);
+
+ /* send query to server. Handle short write. */
+ i = 0;
+ while (i < buflen) {
+ apr_size_t j = strlen(buffer + i);
+ apr_status_t status;
+ status = apr_socket_send(sock, buffer+i, &j);
+ if (status != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, status, srv, APLOGNO(01497)
+ "write: rfc1413: error sending request");
+ return status;
+ }
+ else if (j > 0) {
+ i+=j;
+ }
+ }
+
+ /*
+ * Read response from server. - the response should be newline
+ * terminated according to rfc - make sure it doesn't stomp its
+ * way out of the buffer.
+ */
+
+ i = 0;
+ memset(buffer, '\0', sizeof(buffer));
+ /*
+ * Note that the strchr function below checks for \012 instead of '\n'
+ * this allows it to work on both ASCII and EBCDIC machines.
+ */
+ while ((cp = strchr(buffer, '\012')) == NULL && i < sizeof(buffer) - 1) {
+ apr_size_t j = sizeof(buffer) - 1 - i;
+ apr_status_t status;
+ status = apr_socket_recv(sock, buffer+i, &j);
+ if (status != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, status, srv, APLOGNO(01498)
+ "read: rfc1413: error reading response");
+ return status;
+ }
+ else if (j > 0) {
+ i+=j;
+ }
+ else if (status == APR_SUCCESS && j == 0) {
+ /* Oops... we ran out of data before finding newline */
+ return APR_EINVAL;
+ }
+ }
+
+/* RFC1413_USERLEN = 512 */
+ ap_xlate_proto_from_ascii(buffer, i);
+ if (sscanf(buffer, "%hu , %hu : USERID :%*[^:]:%512s", &rmt_port, &our_port,
+ user) != 3 || sav_rmt_port != rmt_port
+ || sav_our_port != our_port)
+ return APR_EINVAL;
+
+ /*
+ * Strip trailing carriage return. It is part of the
+ * protocol, not part of the data.
+ */
+
+ if ((cp = strchr(user, '\r')))
+ *cp = '\0';
+
+ conn->remote_logname = apr_pstrdup(conn->pool, user);
+
+ return APR_SUCCESS;
+}
+
+static const char *set_idcheck(cmd_parms *cmd, void *d_, int arg)
+{
+ ident_config_rec *d = d_;
+
+ d->do_rfc1413 = arg ? 1 : 0;
+ return NULL;
+}
+
+static const char *set_timeout(cmd_parms *cmd, void *d_, const char *arg)
+{
+ ident_config_rec *d = d_;
+
+ d->timeout = apr_time_from_sec(atoi(arg));
+ d->timeout_unset = 0;
+ return NULL;
+}
+
+static void *create_ident_dir_config(apr_pool_t *p, char *d)
+{
+ ident_config_rec *conf = apr_palloc(p, sizeof(*conf));
+
+ conf->do_rfc1413 = DEFAULT_RFC1413 | RFC1413_UNSET;
+ conf->timeout = apr_time_from_sec(RFC1413_TIMEOUT);
+ conf->timeout_unset = 1;
+
+ return (void *)conf;
+}
+
+static void *merge_ident_dir_config(apr_pool_t *p, void *old_, void *new_)
+{
+ ident_config_rec *conf = (ident_config_rec *)apr_pcalloc(p, sizeof(*conf));
+ ident_config_rec *old = (ident_config_rec *) old_;
+ ident_config_rec *new = (ident_config_rec *) new_;
+
+ conf->timeout = new->timeout_unset
+ ? old->timeout
+ : new->timeout;
+
+ conf->do_rfc1413 = new->do_rfc1413 & RFC1413_UNSET
+ ? old->do_rfc1413
+ : new->do_rfc1413;
+
+ return (void *)conf;
+}
+
+static const command_rec ident_cmds[] =
+{
+ AP_INIT_FLAG("IdentityCheck", set_idcheck, NULL, RSRC_CONF|ACCESS_CONF,
+ "Enable identd (RFC 1413) user lookups - SLOW"),
+ AP_INIT_TAKE1("IdentityCheckTimeout", set_timeout, NULL,
+ RSRC_CONF|ACCESS_CONF,
+ "Identity check (RFC 1413) timeout duration (sec)"),
+ {NULL}
+};
+
+module AP_MODULE_DECLARE_DATA ident_module;
+
+/*
+ * Optional function for the core to the actual ident request
+ */
+static const char *ap_ident_lookup(request_rec *r)
+{
+ ident_config_rec *conf;
+ apr_socket_t *sock;
+ apr_status_t rv;
+ conn_rec *conn = r->connection;
+ server_rec *srv = r->server;
+
+ conf = ap_get_module_config(r->per_dir_config, &ident_module);
+
+ /* return immediately if ident requests are disabled */
+ if (!(conf->do_rfc1413 & ~RFC1413_UNSET)) {
+ return NULL;
+ }
+
+ rv = rfc1413_connect(&sock, conn, srv, conf->timeout);
+ if (rv == APR_SUCCESS) {
+ rv = rfc1413_query(sock, conn, srv);
+ apr_socket_close(sock);
+ }
+ if (rv != APR_SUCCESS) {
+ conn->remote_logname = FROM_UNKNOWN;
+ }
+
+ return (const char *)conn->remote_logname;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ APR_REGISTER_OPTIONAL_FN(ap_ident_lookup);
+}
+
+AP_DECLARE_MODULE(ident) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_ident_dir_config, /* dir config creater */
+ merge_ident_dir_config, /* dir merger --- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ ident_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/metadata/mod_ident.dep b/modules/metadata/mod_ident.dep
new file mode 100644
index 0000000..37ff424
--- /dev/null
+++ b/modules/metadata/mod_ident.dep
@@ -0,0 +1,52 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_ident.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_ident.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/metadata/mod_ident.dsp b/modules/metadata/mod_ident.dsp
new file mode 100644
index 0000000..89fbd57
--- /dev/null
+++ b/modules/metadata/mod_ident.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_ident" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_ident - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_ident.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_ident.mak" CFG="mod_ident - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_ident - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_ident - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_ident - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_ident_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_ident.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_ident.so" /d LONG_NAME="ident_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_ident.so" /base:@..\..\os\win32\BaseAddr.ref,mod_ident.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_ident.so" /base:@..\..\os\win32\BaseAddr.ref,mod_ident.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_ident.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_ident - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_ident_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_ident.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_ident.so" /d LONG_NAME="ident_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_ident.so" /base:@..\..\os\win32\BaseAddr.ref,mod_ident.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_ident.so" /base:@..\..\os\win32\BaseAddr.ref,mod_ident.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_ident.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_ident - Win32 Release"
+# Name "mod_ident - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_ident.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/metadata/mod_ident.exp b/modules/metadata/mod_ident.exp
new file mode 100644
index 0000000..26fe353
--- /dev/null
+++ b/modules/metadata/mod_ident.exp
@@ -0,0 +1 @@
+ident_module
diff --git a/modules/metadata/mod_ident.mak b/modules/metadata/mod_ident.mak
new file mode 100644
index 0000000..cc45e21
--- /dev/null
+++ b/modules/metadata/mod_ident.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_ident.dsp
+!IF "$(CFG)" == ""
+CFG=mod_ident - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_ident - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_ident - Win32 Release" && "$(CFG)" != "mod_ident - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_ident.mak" CFG="mod_ident - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_ident - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_ident - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_ident - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_ident.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_ident.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_ident.obj"
+ -@erase "$(INTDIR)\mod_ident.res"
+ -@erase "$(INTDIR)\mod_ident_src.idb"
+ -@erase "$(INTDIR)\mod_ident_src.pdb"
+ -@erase "$(OUTDIR)\mod_ident.exp"
+ -@erase "$(OUTDIR)\mod_ident.lib"
+ -@erase "$(OUTDIR)\mod_ident.pdb"
+ -@erase "$(OUTDIR)\mod_ident.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_ident_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_ident.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_ident.so" /d LONG_NAME="ident_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_ident.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_ident.pdb" /debug /out:"$(OUTDIR)\mod_ident.so" /implib:"$(OUTDIR)\mod_ident.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_ident.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_ident.obj" \
+ "$(INTDIR)\mod_ident.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_ident.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_ident.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_ident.so"
+ if exist .\Release\mod_ident.so.manifest mt.exe -manifest .\Release\mod_ident.so.manifest -outputresource:.\Release\mod_ident.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_ident - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_ident.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_ident.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_ident.obj"
+ -@erase "$(INTDIR)\mod_ident.res"
+ -@erase "$(INTDIR)\mod_ident_src.idb"
+ -@erase "$(INTDIR)\mod_ident_src.pdb"
+ -@erase "$(OUTDIR)\mod_ident.exp"
+ -@erase "$(OUTDIR)\mod_ident.lib"
+ -@erase "$(OUTDIR)\mod_ident.pdb"
+ -@erase "$(OUTDIR)\mod_ident.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_ident_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_ident.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_ident.so" /d LONG_NAME="ident_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_ident.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_ident.pdb" /debug /out:"$(OUTDIR)\mod_ident.so" /implib:"$(OUTDIR)\mod_ident.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_ident.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_ident.obj" \
+ "$(INTDIR)\mod_ident.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_ident.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_ident.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_ident.so"
+ if exist .\Debug\mod_ident.so.manifest mt.exe -manifest .\Debug\mod_ident.so.manifest -outputresource:.\Debug\mod_ident.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_ident.dep")
+!INCLUDE "mod_ident.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_ident.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_ident - Win32 Release" || "$(CFG)" == "mod_ident - Win32 Debug"
+
+!IF "$(CFG)" == "mod_ident - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\metadata"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_ident - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\metadata"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_ident - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\metadata"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_ident - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\metadata"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_ident - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\metadata"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_ident - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\metadata"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\metadata"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_ident - Win32 Release"
+
+
+"$(INTDIR)\mod_ident.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_ident.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_ident.so" /d LONG_NAME="ident_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_ident - Win32 Debug"
+
+
+"$(INTDIR)\mod_ident.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_ident.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_ident.so" /d LONG_NAME="ident_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_ident.c
+
+"$(INTDIR)\mod_ident.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/metadata/mod_mime_magic.c b/modules/metadata/mod_mime_magic.c
new file mode 100644
index 0000000..7dac4fd
--- /dev/null
+++ b/modules/metadata/mod_mime_magic.c
@@ -0,0 +1,2471 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * mod_mime_magic: MIME type lookup via file magic numbers
+ * Copyright (c) 1996-1997 Cisco Systems, Inc.
+ *
+ * This software was submitted by Cisco Systems to the Apache Software Foundation in July
+ * 1997. Future revisions and derivatives of this source code must
+ * acknowledge Cisco Systems as the original contributor of this module.
+ * All other licensing and usage conditions are those of the Apache Software Foundation.
+ *
+ * Some of this code is derived from the free version of the file command
+ * originally posted to comp.sources.unix. Copyright info for that program
+ * is included below as required.
+ * ---------------------------------------------------------------------------
+ * - Copyright (c) Ian F. Darwin, 1987. Written by Ian F. Darwin.
+ *
+ * This software is not subject to any license of the American Telephone and
+ * Telegraph Company or of the Regents of the University of California.
+ *
+ * Permission is granted to anyone to use this software for any purpose on any
+ * computer system, and to alter it and redistribute it freely, subject to
+ * the following restrictions:
+ *
+ * 1. The author is not responsible for the consequences of use of this
+ * software, no matter how awful, even if they arise from flaws in it.
+ *
+ * 2. The origin of this software must not be misrepresented, either by
+ * explicit claim or by omission. Since few users ever read sources, credits
+ * must appear in the documentation.
+ *
+ * 3. Altered versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software. Since few users ever read
+ * sources, credits must appear in the documentation.
+ *
+ * 4. This notice may not be removed or altered.
+ * -------------------------------------------------------------------------
+ *
+ * For compliance with Mr Darwin's terms: this has been very significantly
+ * modified from the free "file" command.
+ * - all-in-one file for compilation convenience when moving from one
+ * version of Apache to the next.
+ * - Memory allocation is done through the Apache API's apr_pool_t structure.
+ * - All functions have had necessary Apache API request or server
+ * structures passed to them where necessary to call other Apache API
+ * routines. (i.e. usually for logging, files, or memory allocation in
+ * itself or a called function.)
+ * - struct magic has been converted from an array to a single-ended linked
+ * list because it only grows one record at a time, it's only accessed
+ * sequentially, and the Apache API has no equivalent of realloc().
+ * - Functions have been changed to get their parameters from the server
+ * configuration instead of globals. (It should be reentrant now but has
+ * not been tested in a threaded environment.)
+ * - Places where it used to print results to stdout now saves them in a
+ * list where they're used to set the MIME type in the Apache request
+ * record.
+ * - Command-line flags have been removed since they will never be used here.
+ *
+ * Ian Kluft <ikluft@cisco.com>
+ * Engineering Information Framework
+ * Central Engineering
+ * Cisco Systems, Inc.
+ * San Jose, CA, USA
+ *
+ * Initial installation July/August 1996
+ * Misc bug fixes May 1997
+ * Submission to Apache Software Foundation July 1997
+ *
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_request.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "util_script.h"
+
+/* ### this isn't set by configure? does anybody set this? */
+#ifdef HAVE_UTIME_H
+#include <utime.h>
+#endif
+
+/*
+ * data structures and related constants
+ */
+
+#define MODNAME "mod_mime_magic"
+#define MIME_MAGIC_DEBUG 0
+
+#define MIME_BINARY_UNKNOWN "application/octet-stream"
+#define MIME_TEXT_UNKNOWN "text/plain"
+
+#define MAXMIMESTRING 256
+
+/* HOWMANY must be at least 4096 to make gzip -dcq work */
+#define HOWMANY 4096
+/* SMALL_HOWMANY limits how much work we do to figure out text files */
+#define SMALL_HOWMANY 1024
+#define MAXDESC 50 /* max leng of text description */
+#define MAXstring 64 /* max leng of "string" types */
+
+struct magic {
+ struct magic *next; /* link to next entry */
+ int lineno; /* line number from magic file */
+
+ short flag;
+#define INDIR 1 /* if '>(...)' appears, */
+#define UNSIGNED 2 /* comparison is unsigned */
+ short cont_level; /* level of ">" */
+ struct {
+ char type; /* byte short long */
+ long offset; /* offset from indirection */
+ } in;
+ long offset; /* offset to magic number */
+ unsigned char reln; /* relation (0=eq, '>'=gt, etc) */
+ char type; /* int, short, long or string. */
+ char vallen; /* length of string value, if any */
+#define BYTE 1
+#define SHORT 2
+#define LONG 4
+#define STRING 5
+#define DATE 6
+#define BESHORT 7
+#define BELONG 8
+#define BEDATE 9
+#define LESHORT 10
+#define LELONG 11
+#define LEDATE 12
+ union VALUETYPE {
+ unsigned char b;
+ unsigned short h;
+ unsigned long l;
+ char s[MAXstring];
+ unsigned char hs[2]; /* 2 bytes of a fixed-endian "short" */
+ unsigned char hl[4]; /* 2 bytes of a fixed-endian "long" */
+ } value; /* either number or string */
+ unsigned long mask; /* mask before comparison with value */
+ char nospflag; /* suppress space character */
+
+ /* NOTE: this string is suspected of overrunning - find it! */
+ char desc[MAXDESC]; /* description */
+};
+
+/*
+ * data structures for tar file recognition
+ * --------------------------------------------------------------------------
+ * Header file for public domain tar (tape archive) program.
+ *
+ * @(#)tar.h 1.20 86/10/29 Public Domain. Created 25 August 1985 by John
+ * Gilmore, ihnp4!hoptoad!gnu.
+ *
+ * Header block on tape.
+ *
+ * I'm going to use traditional DP naming conventions here. A "block" is a big
+ * chunk of stuff that we do I/O on. A "record" is a piece of info that we
+ * care about. Typically many "record"s fit into a "block".
+ */
+#define RECORDSIZE 512
+#define NAMSIZ 100
+#define TUNMLEN 32
+#define TGNMLEN 32
+
+union record {
+ char charptr[RECORDSIZE];
+ struct header {
+ char name[NAMSIZ];
+ char mode[8];
+ char uid[8];
+ char gid[8];
+ char size[12];
+ char mtime[12];
+ char chksum[8];
+ char linkflag;
+ char linkname[NAMSIZ];
+ char magic[8];
+ char uname[TUNMLEN];
+ char gname[TGNMLEN];
+ char devmajor[8];
+ char devminor[8];
+ } header;
+};
+
+/* The magic field is filled with this if uname and gname are valid. */
+#define TMAGIC "ustar " /* 7 chars and a null */
+
+/*
+ * file-function prototypes
+ */
+static int ascmagic(request_rec *, unsigned char *, apr_size_t);
+static int is_tar(unsigned char *, apr_size_t);
+static int softmagic(request_rec *, unsigned char *, apr_size_t);
+static int tryit(request_rec *, unsigned char *, apr_size_t, int);
+static int zmagic(request_rec *, unsigned char *, apr_size_t);
+
+static int getvalue(server_rec *, struct magic *, char **);
+static int hextoint(int);
+static char *getstr(server_rec *, char *, char *, int, int *);
+static int parse(server_rec *, apr_pool_t *p, char *, int);
+
+static int match(request_rec *, unsigned char *, apr_size_t);
+static int mget(request_rec *, union VALUETYPE *, unsigned char *,
+ struct magic *, apr_size_t);
+static int mcheck(request_rec *, union VALUETYPE *, struct magic *);
+static void mprint(request_rec *, union VALUETYPE *, struct magic *);
+
+static int uncompress(request_rec *, int,
+ unsigned char **, apr_size_t);
+static long from_oct(int, char *);
+static int fsmagic(request_rec *r, const char *fn);
+
+/*
+ * includes for ASCII substring recognition formerly "names.h" in file
+ * command
+ *
+ * Original notes: names and types used by ascmagic in file(1). These tokens are
+ * here because they can appear anywhere in the first HOWMANY bytes, while
+ * tokens in /etc/magic must appear at fixed offsets into the file. Don't
+ * make HOWMANY too high unless you have a very fast CPU.
+ */
+
+/* these types are used to index the apr_table_t 'types': keep em in sync! */
+/* HTML inserted in first because this is a web server module now */
+#define L_HTML 0 /* HTML */
+#define L_C 1 /* first and foremost on UNIX */
+#define L_FORT 2 /* the oldest one */
+#define L_MAKE 3 /* Makefiles */
+#define L_PLI 4 /* PL/1 */
+#define L_MACH 5 /* some kinda assembler */
+#define L_ENG 6 /* English */
+#define L_PAS 7 /* Pascal */
+#define L_MAIL 8 /* Electronic mail */
+#define L_NEWS 9 /* Usenet Netnews */
+
+static const char *const types[] =
+{
+ "text/html", /* HTML */
+ "text/plain", /* "c program text", */
+ "text/plain", /* "fortran program text", */
+ "text/plain", /* "make commands text", */
+ "text/plain", /* "pl/1 program text", */
+ "text/plain", /* "assembler program text", */
+ "text/plain", /* "English text", */
+ "text/plain", /* "pascal program text", */
+ "message/rfc822", /* "mail text", */
+ "message/news", /* "news text", */
+ "application/binary", /* "can't happen error on names.h/types", */
+ 0
+};
+
+static const struct names {
+ const char *name;
+ short type;
+} names[] = {
+
+ /* These must be sorted by eye for optimal hit rate */
+ /* Add to this list only after substantial meditation */
+ {
+ "<html>", L_HTML
+ },
+ {
+ "<HTML>", L_HTML
+ },
+ {
+ "<head>", L_HTML
+ },
+ {
+ "<HEAD>", L_HTML
+ },
+ {
+ "<title>", L_HTML
+ },
+ {
+ "<TITLE>", L_HTML
+ },
+ {
+ "<h1>", L_HTML
+ },
+ {
+ "<H1>", L_HTML
+ },
+ {
+ "<!--", L_HTML
+ },
+ {
+ "<!DOCTYPE HTML", L_HTML
+ },
+ {
+ "/*", L_C
+ }, /* must precede "The", "the", etc. */
+ {
+ "#include", L_C
+ },
+ {
+ "char", L_C
+ },
+ {
+ "The", L_ENG
+ },
+ {
+ "the", L_ENG
+ },
+ {
+ "double", L_C
+ },
+ {
+ "extern", L_C
+ },
+ {
+ "float", L_C
+ },
+ {
+ "real", L_C
+ },
+ {
+ "struct", L_C
+ },
+ {
+ "union", L_C
+ },
+ {
+ "CFLAGS", L_MAKE
+ },
+ {
+ "LDFLAGS", L_MAKE
+ },
+ {
+ "all:", L_MAKE
+ },
+ {
+ ".PRECIOUS", L_MAKE
+ },
+ /*
+ * Too many files of text have these words in them. Find another way to
+ * recognize Fortrash.
+ */
+#ifdef NOTDEF
+ {
+ "subroutine", L_FORT
+ },
+ {
+ "function", L_FORT
+ },
+ {
+ "block", L_FORT
+ },
+ {
+ "common", L_FORT
+ },
+ {
+ "dimension", L_FORT
+ },
+ {
+ "integer", L_FORT
+ },
+ {
+ "data", L_FORT
+ },
+#endif /* NOTDEF */
+ {
+ ".ascii", L_MACH
+ },
+ {
+ ".asciiz", L_MACH
+ },
+ {
+ ".byte", L_MACH
+ },
+ {
+ ".even", L_MACH
+ },
+ {
+ ".globl", L_MACH
+ },
+ {
+ "clr", L_MACH
+ },
+ {
+ "(input,", L_PAS
+ },
+ {
+ "dcl", L_PLI
+ },
+ {
+ "Received:", L_MAIL
+ },
+ {
+ ">From", L_MAIL
+ },
+ {
+ "Return-Path:", L_MAIL
+ },
+ {
+ "Cc:", L_MAIL
+ },
+ {
+ "Newsgroups:", L_NEWS
+ },
+ {
+ "Path:", L_NEWS
+ },
+ {
+ "Organization:", L_NEWS
+ },
+ {
+ NULL, 0
+ }
+};
+
+#define NNAMES ((sizeof(names)/sizeof(struct names)) - 1)
+
+/*
+ * Result String List (RSL)
+ *
+ * The file(1) command prints its output. Instead, we store the various
+ * "printed" strings in a list (allocating memory as we go) and concatenate
+ * them at the end when we finally know how much space they'll need.
+ */
+
+typedef struct magic_rsl_s {
+ const char *str; /* string, possibly a fragment */
+ struct magic_rsl_s *next; /* pointer to next fragment */
+} magic_rsl;
+
+/*
+ * Apache module configuration structures
+ */
+
+/* per-server info */
+typedef struct {
+ const char *magicfile; /* where magic be found */
+ struct magic *magic; /* head of magic config list */
+ struct magic *last;
+} magic_server_config_rec;
+
+/* per-request info */
+typedef struct {
+ magic_rsl *head; /* result string list */
+ magic_rsl *tail;
+} magic_req_rec;
+
+/*
+ * configuration functions - called by Apache API routines
+ */
+
+module AP_MODULE_DECLARE_DATA mime_magic_module;
+
+static void *create_magic_server_config(apr_pool_t *p, server_rec *d)
+{
+ /* allocate the config - use pcalloc because it needs to be zeroed */
+ return apr_pcalloc(p, sizeof(magic_server_config_rec));
+}
+
+static void *merge_magic_server_config(apr_pool_t *p, void *basev, void *addv)
+{
+ magic_server_config_rec *base = (magic_server_config_rec *) basev;
+ magic_server_config_rec *add = (magic_server_config_rec *) addv;
+ magic_server_config_rec *new = (magic_server_config_rec *)
+ apr_palloc(p, sizeof(magic_server_config_rec));
+
+ new->magicfile = add->magicfile ? add->magicfile : base->magicfile;
+ new->magic = NULL;
+ new->last = NULL;
+ return new;
+}
+
+static const char *set_magicfile(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ magic_server_config_rec *conf = (magic_server_config_rec *)
+ ap_get_module_config(cmd->server->module_config,
+ &mime_magic_module);
+
+ if (!conf) {
+ return MODNAME ": server structure not allocated";
+ }
+ conf->magicfile = arg;
+ return NULL;
+}
+
+/*
+ * configuration file commands - exported to Apache API
+ */
+
+static const command_rec mime_magic_cmds[] =
+{
+ AP_INIT_TAKE1("MimeMagicFile", set_magicfile, NULL, RSRC_CONF,
+ "Path to MIME Magic file (in file(1) format)"),
+ {NULL}
+};
+
+/*
+ * RSL (result string list) processing routines
+ *
+ * These collect strings that would have been printed in fragments by file(1)
+ * into a list of magic_rsl structures with the strings. When complete,
+ * they're concatenated together to become the MIME content and encoding
+ * types.
+ *
+ * return value conventions for these functions: functions which return int:
+ * failure = -1, other = result functions which return pointers: failure = 0,
+ * other = result
+ */
+
+/* allocate a per-request structure and put it in the request record */
+static magic_req_rec *magic_set_config(request_rec *r)
+{
+ magic_req_rec *req_dat = (magic_req_rec *) apr_palloc(r->pool,
+ sizeof(magic_req_rec));
+
+ req_dat->head = req_dat->tail = (magic_rsl *) NULL;
+ ap_set_module_config(r->request_config, &mime_magic_module, req_dat);
+ return req_dat;
+}
+
+/* add a string to the result string list for this request */
+/* it is the responsibility of the caller to allocate "str" */
+static int magic_rsl_add(request_rec *r, const char *str)
+{
+ magic_req_rec *req_dat = (magic_req_rec *)
+ ap_get_module_config(r->request_config, &mime_magic_module);
+ magic_rsl *rsl;
+
+ /* make sure we have a list to put it in */
+ if (!req_dat) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EINVAL, r, APLOGNO(01507)
+ MODNAME ": request config should not be NULL");
+ if (!(req_dat = magic_set_config(r))) {
+ /* failure */
+ return -1;
+ }
+ }
+
+ /* allocate the list entry */
+ rsl = (magic_rsl *) apr_palloc(r->pool, sizeof(magic_rsl));
+
+ /* fill it */
+ rsl->str = str;
+ rsl->next = (magic_rsl *) NULL;
+
+ /* append to the list */
+ if (req_dat->head && req_dat->tail) {
+ req_dat->tail->next = rsl;
+ req_dat->tail = rsl;
+ }
+ else {
+ req_dat->head = req_dat->tail = rsl;
+ }
+
+ /* success */
+ return 0;
+}
+
+/* RSL hook for puts-type functions */
+static int magic_rsl_puts(request_rec *r, const char *str)
+{
+ return magic_rsl_add(r, str);
+}
+
+/* RSL hook for printf-type functions */
+static int magic_rsl_printf(request_rec *r, char *str,...)
+{
+ va_list ap;
+
+ char buf[MAXMIMESTRING];
+
+ /* assemble the string into the buffer */
+ va_start(ap, str);
+ apr_vsnprintf(buf, sizeof(buf), str, ap);
+ va_end(ap);
+
+ /* add the buffer to the list */
+ return magic_rsl_add(r, apr_pstrdup(r->pool, buf));
+}
+
+/* RSL hook for putchar-type functions */
+static int magic_rsl_putchar(request_rec *r, char c)
+{
+ char str[2];
+
+ /* high overhead for 1 char - just hope they don't do this much */
+ str[0] = c;
+ str[1] = '\0';
+ return magic_rsl_add(r, apr_pstrdup(r->pool, str));
+}
+
+/* allocate and copy a contiguous string from a result string list */
+static char *rsl_strdup(request_rec *r, int start_frag, int start_pos, int len)
+{
+ char *result; /* return value */
+ int cur_frag, /* current fragment number/counter */
+ cur_pos, /* current position within fragment */
+ res_pos; /* position in result string */
+ magic_rsl *frag; /* list-traversal pointer */
+ magic_req_rec *req_dat = (magic_req_rec *)
+ ap_get_module_config(r->request_config, &mime_magic_module);
+
+ /* allocate the result string */
+ result = (char *) apr_palloc(r->pool, len + 1);
+
+ /* loop through and collect the string */
+ res_pos = 0;
+ for (frag = req_dat->head, cur_frag = 0;
+ frag->next;
+ frag = frag->next, cur_frag++) {
+ /* loop to the first fragment */
+ if (cur_frag < start_frag)
+ continue;
+
+ /* loop through and collect chars */
+ for (cur_pos = (cur_frag == start_frag) ? start_pos : 0;
+ frag->str[cur_pos];
+ cur_pos++) {
+ if (cur_frag >= start_frag
+ && cur_pos >= start_pos
+ && res_pos <= len) {
+ result[res_pos++] = frag->str[cur_pos];
+ if (res_pos > len) {
+ break;
+ }
+ }
+ }
+ }
+
+ /* clean up and return */
+ result[res_pos] = 0;
+#if MIME_MAGIC_DEBUG
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01508)
+ MODNAME ": rsl_strdup() %d chars: %s", res_pos - 1, result);
+#endif
+ return result;
+}
+
+/* states for the state-machine algorithm in magic_rsl_to_request() */
+typedef enum {
+ rsl_leading_space, rsl_type, rsl_subtype, rsl_separator, rsl_encoding
+} rsl_states;
+
+/* process the RSL and set the MIME info in the request record */
+static int magic_rsl_to_request(request_rec *r)
+{
+ int cur_frag, /* current fragment number/counter */
+ cur_pos, /* current position within fragment */
+ type_frag, /* content type starting point: fragment */
+ type_pos, /* content type starting point: position */
+ type_len, /* content type length */
+ encoding_frag, /* content encoding starting point: fragment */
+ encoding_pos, /* content encoding starting point: position */
+ encoding_len; /* content encoding length */
+
+ char *tmp;
+ magic_rsl *frag; /* list-traversal pointer */
+ rsl_states state;
+
+ magic_req_rec *req_dat = (magic_req_rec *)
+ ap_get_module_config(r->request_config, &mime_magic_module);
+
+ /* check if we have a result */
+ if (!req_dat || !req_dat->head) {
+ /* empty - no match, we defer to other Apache modules */
+ return DECLINED;
+ }
+
+ /* start searching for the type and encoding */
+ state = rsl_leading_space;
+ type_frag = type_pos = type_len = 0;
+ encoding_frag = encoding_pos = encoding_len = 0;
+ for (frag = req_dat->head, cur_frag = 0;
+ frag && frag->next;
+ frag = frag->next, cur_frag++) {
+ /* loop through the characters in the fragment */
+ for (cur_pos = 0; frag->str[cur_pos]; cur_pos++) {
+ if (apr_isspace(frag->str[cur_pos])) {
+ /* process whitespace actions for each state */
+ if (state == rsl_leading_space) {
+ /* eat whitespace in this state */
+ continue;
+ }
+ else if (state == rsl_type) {
+ /* whitespace: type has no slash! */
+ return DECLINED;
+ }
+ else if (state == rsl_subtype) {
+ /* whitespace: end of MIME type */
+ state++;
+ continue;
+ }
+ else if (state == rsl_separator) {
+ /* eat whitespace in this state */
+ continue;
+ }
+ else if (state == rsl_encoding) {
+ /* whitespace: end of MIME encoding */
+ /* we're done */
+ frag = req_dat->tail;
+ break;
+ }
+ else {
+ /* should not be possible */
+ /* abandon malfunctioning module */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01509)
+ MODNAME ": bad state %d (ws)", state);
+ return DECLINED;
+ }
+ /* NOTREACHED */
+ }
+ else if (state == rsl_type &&
+ frag->str[cur_pos] == '/') {
+ /* copy the char and go to rsl_subtype state */
+ type_len++;
+ state++;
+ }
+ else {
+ /* process non-space actions for each state */
+ if (state == rsl_leading_space) {
+ /* non-space: begin MIME type */
+ state++;
+ type_frag = cur_frag;
+ type_pos = cur_pos;
+ type_len = 1;
+ continue;
+ }
+ else if (state == rsl_type ||
+ state == rsl_subtype) {
+ /* non-space: adds to type */
+ type_len++;
+ continue;
+ }
+ else if (state == rsl_separator) {
+ /* non-space: begin MIME encoding */
+ state++;
+ encoding_frag = cur_frag;
+ encoding_pos = cur_pos;
+ encoding_len = 1;
+ continue;
+ }
+ else if (state == rsl_encoding) {
+ /* non-space: adds to encoding */
+ encoding_len++;
+ continue;
+ }
+ else {
+ /* should not be possible */
+ /* abandon malfunctioning module */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01510)
+ MODNAME ": bad state %d (ns)", state);
+ return DECLINED;
+ }
+ /* NOTREACHED */
+ }
+ /* NOTREACHED */
+ }
+ }
+
+ /* if we ended prior to state rsl_subtype, we had incomplete info */
+ if (state != rsl_subtype && state != rsl_separator &&
+ state != rsl_encoding) {
+ /* defer to other modules */
+ return DECLINED;
+ }
+
+ /* save the info in the request record */
+ tmp = rsl_strdup(r, type_frag, type_pos, type_len);
+ /* XXX: this could be done at config time I'm sure... but I'm
+ * confused by all this magic_rsl stuff. -djg */
+ ap_content_type_tolower(tmp);
+ ap_set_content_type(r, tmp);
+
+ if (state == rsl_encoding) {
+ tmp = rsl_strdup(r, encoding_frag,
+ encoding_pos, encoding_len);
+ /* XXX: this could be done at config time I'm sure... but I'm
+ * confused by all this magic_rsl stuff. -djg */
+ ap_str_tolower(tmp);
+ r->content_encoding = tmp;
+ }
+
+ /* detect memory allocation or other errors */
+ if (!r->content_type ||
+ (state == rsl_encoding && !r->content_encoding)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01511)
+ MODNAME ": unexpected state %d; could be caused by bad "
+ "data in magic file",
+ state);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* success! */
+ return OK;
+}
+
+/*
+ * magic_process - process input file r Apache API request record
+ * (formerly called "process" in file command, prefix added for clarity) Opens
+ * the file and reads a fixed-size buffer to begin processing the contents.
+ */
+static int magic_process(request_rec *r)
+{
+ apr_file_t *fd = NULL;
+ unsigned char buf[HOWMANY + 1]; /* one extra for terminating '\0' */
+ apr_size_t nbytes = 0; /* number of bytes read from a datafile */
+ int result;
+
+ /*
+ * first try judging the file based on its filesystem status
+ */
+ switch ((result = fsmagic(r, r->filename))) {
+ case DONE:
+ magic_rsl_putchar(r, '\n');
+ return OK;
+ case OK:
+ break;
+ default:
+ /* fatal error, bail out */
+ return result;
+ }
+
+ if (apr_file_open(&fd, r->filename, APR_READ, APR_OS_DEFAULT, r->pool) != APR_SUCCESS) {
+ /* We can't open it, but we were able to stat it. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01512)
+ MODNAME ": can't read `%s'", r->filename);
+ /* let some other handler decide what the problem is */
+ return DECLINED;
+ }
+
+ /*
+ * try looking at the first HOWMANY bytes
+ */
+ nbytes = sizeof(buf) - 1;
+ if ((result = apr_file_read(fd, (char *) buf, &nbytes)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, result, r, APLOGNO(01513)
+ MODNAME ": read failed: %s", r->filename);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (nbytes == 0) {
+ return DECLINED;
+ }
+ else {
+ buf[nbytes++] = '\0'; /* null-terminate it */
+ result = tryit(r, buf, nbytes, 1);
+ if (result != OK) {
+ return result;
+ }
+ }
+
+ (void) apr_file_close(fd);
+ (void) magic_rsl_putchar(r, '\n');
+
+ return OK;
+}
+
+
+static int tryit(request_rec *r, unsigned char *buf, apr_size_t nb,
+ int checkzmagic)
+{
+ /*
+ * Try compression stuff
+ */
+ if (checkzmagic == 1) {
+ if (zmagic(r, buf, nb) == 1)
+ return OK;
+ }
+
+ /*
+ * try tests in /etc/magic (or surrogate magic file)
+ */
+ if (softmagic(r, buf, nb) == 1)
+ return OK;
+
+ /*
+ * try known keywords, check for ascii-ness too.
+ */
+ if (ascmagic(r, buf, nb) == 1)
+ return OK;
+
+ /*
+ * abandon hope, all ye who remain here
+ */
+ return DECLINED;
+}
+
+#define EATAB {while (apr_isspace(*l)) ++l;}
+
+/*
+ * apprentice - load configuration from the magic file r
+ * API request record
+ */
+static int apprentice(server_rec *s, apr_pool_t *p)
+{
+ apr_file_t *f = NULL;
+ apr_status_t result;
+ char line[BUFSIZ + 1];
+ int errs = 0;
+ int lineno;
+#if MIME_MAGIC_DEBUG
+ int rule = 0;
+ struct magic *m, *prevm;
+#endif
+ magic_server_config_rec *conf = (magic_server_config_rec *)
+ ap_get_module_config(s->module_config, &mime_magic_module);
+ const char *fname = ap_server_root_relative(p, conf->magicfile);
+
+ if (!fname) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, APR_EBADPATH, s, APLOGNO(01514)
+ MODNAME ": Invalid magic file path %s", conf->magicfile);
+ return -1;
+ }
+ if ((result = apr_file_open(&f, fname, APR_READ | APR_BUFFERED,
+ APR_OS_DEFAULT, p)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, result, s, APLOGNO(01515)
+ MODNAME ": can't read magic file %s", fname);
+ return -1;
+ }
+
+ /* set up the magic list (empty) */
+ conf->magic = conf->last = NULL;
+
+ /* parse it */
+ for (lineno = 1; apr_file_gets(line, BUFSIZ, f) == APR_SUCCESS; lineno++) {
+ int ws_offset;
+ char *last = line + strlen(line) - 1; /* guaranteed that len >= 1 since an
+ * "empty" line contains a '\n'
+ */
+
+ /* delete newline and any other trailing whitespace */
+ while (last >= line
+ && apr_isspace(*last)) {
+ *last = '\0';
+ --last;
+ }
+
+ /* skip leading whitespace */
+ ws_offset = 0;
+ while (line[ws_offset] && apr_isspace(line[ws_offset])) {
+ ws_offset++;
+ }
+
+ /* skip blank lines */
+ if (line[ws_offset] == 0) {
+ continue;
+ }
+
+ /* comment, do not parse */
+ if (line[ws_offset] == '#')
+ continue;
+
+#if MIME_MAGIC_DEBUG
+ /* if we get here, we're going to use it so count it */
+ rule++;
+#endif
+
+ /* parse it */
+ if (parse(s, p, line + ws_offset, lineno) != 0)
+ ++errs;
+ }
+
+ (void) apr_file_close(f);
+
+#if MIME_MAGIC_DEBUG
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01516)
+ MODNAME ": apprentice conf=%pp file=%s m=%s m->next=%s last=%s",
+ conf,
+ conf->magicfile ? conf->magicfile : "NULL",
+ conf->magic ? "set" : "NULL",
+ (conf->magic && conf->magic->next) ? "set" : "NULL",
+ conf->last ? "set" : "NULL");
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01517)
+ MODNAME ": apprentice read %d lines, %d rules, %d errors",
+ lineno, rule, errs);
+#endif
+
+#if MIME_MAGIC_DEBUG
+ prevm = 0;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01518)
+ MODNAME ": apprentice test");
+ for (m = conf->magic; m; m = m->next) {
+ if (apr_isprint((((unsigned long) m) >> 24) & 255) &&
+ apr_isprint((((unsigned long) m) >> 16) & 255) &&
+ apr_isprint((((unsigned long) m) >> 8) & 255) &&
+ apr_isprint(((unsigned long) m) & 255)) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01519)
+ MODNAME ": apprentice: POINTER CLOBBERED! "
+ "m=\"%c%c%c%c\" line=%d",
+ (((unsigned long) m) >> 24) & 255,
+ (((unsigned long) m) >> 16) & 255,
+ (((unsigned long) m) >> 8) & 255,
+ ((unsigned long) m) & 255,
+ prevm ? prevm->lineno : -1);
+ break;
+ }
+ prevm = m;
+ }
+#endif
+
+ return (errs ? -1 : 0);
+}
+
+/*
+ * extend the sign bit if the comparison is to be signed
+ */
+static unsigned long signextend(server_rec *s, struct magic *m, unsigned long v)
+{
+ if (!(m->flag & UNSIGNED))
+ switch (m->type) {
+ /*
+ * Do not remove the casts below. They are vital. When later
+ * compared with the data, the sign extension must have happened.
+ */
+ case BYTE:
+ v = (char) v;
+ break;
+ case SHORT:
+ case BESHORT:
+ case LESHORT:
+ v = (short) v;
+ break;
+ case DATE:
+ case BEDATE:
+ case LEDATE:
+ case LONG:
+ case BELONG:
+ case LELONG:
+ v = (long) v;
+ break;
+ case STRING:
+ break;
+ default:
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(01520)
+ MODNAME ": can't happen: m->type=%d", m->type);
+ return -1;
+ }
+ return v;
+}
+
+/*
+ * parse one line from magic file, put into magic[index++] if valid
+ */
+static int parse(server_rec *serv, apr_pool_t *p, char *l, int lineno)
+{
+ struct magic *m;
+ char *t, *s;
+ magic_server_config_rec *conf = (magic_server_config_rec *)
+ ap_get_module_config(serv->module_config, &mime_magic_module);
+
+ /* allocate magic structure entry */
+ m = (struct magic *) apr_pcalloc(p, sizeof(struct magic));
+
+ /* append to linked list */
+ m->next = NULL;
+ if (!conf->magic || !conf->last) {
+ conf->magic = conf->last = m;
+ }
+ else {
+ conf->last->next = m;
+ conf->last = m;
+ }
+
+ /* set values in magic structure */
+ m->flag = 0;
+ m->cont_level = 0;
+ m->lineno = lineno;
+
+ while (*l == '>') {
+ ++l; /* step over */
+ m->cont_level++;
+ }
+
+ if (m->cont_level != 0 && *l == '(') {
+ ++l; /* step over */
+ m->flag |= INDIR;
+ }
+
+ /* get offset, then skip over it */
+ m->offset = (int) strtol(l, &t, 0);
+ if (l == t) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, serv, APLOGNO(01521)
+ MODNAME ": offset %s invalid", l);
+ }
+ l = t;
+
+ if (m->flag & INDIR) {
+ m->in.type = LONG;
+ m->in.offset = 0;
+ /*
+ * read [.lbs][+-]nnnnn)
+ */
+ if (*l == '.') {
+ switch (*++l) {
+ case 'l':
+ m->in.type = LONG;
+ break;
+ case 's':
+ m->in.type = SHORT;
+ break;
+ case 'b':
+ m->in.type = BYTE;
+ break;
+ default:
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, serv, APLOGNO(01522)
+ MODNAME ": indirect offset type %c invalid", *l);
+ break;
+ }
+ l++;
+ }
+ s = l;
+ if (*l == '+' || *l == '-')
+ l++;
+ if (apr_isdigit((unsigned char) *l)) {
+ m->in.offset = strtol(l, &t, 0);
+ if (*s == '-')
+ m->in.offset = -m->in.offset;
+ }
+ else
+ t = l;
+ if (*t++ != ')') {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, serv, APLOGNO(01523)
+ MODNAME ": missing ')' in indirect offset");
+ }
+ l = t;
+ }
+
+
+ while (apr_isdigit((unsigned char) *l))
+ ++l;
+ EATAB;
+
+#define NBYTE 4
+#define NSHORT 5
+#define NLONG 4
+#define NSTRING 6
+#define NDATE 4
+#define NBESHORT 7
+#define NBELONG 6
+#define NBEDATE 6
+#define NLESHORT 7
+#define NLELONG 6
+#define NLEDATE 6
+
+ if (*l == 'u') {
+ ++l;
+ m->flag |= UNSIGNED;
+ }
+
+ /* get type, skip it */
+ if (strncmp(l, "byte", NBYTE) == 0) {
+ m->type = BYTE;
+ l += NBYTE;
+ }
+ else if (strncmp(l, "short", NSHORT) == 0) {
+ m->type = SHORT;
+ l += NSHORT;
+ }
+ else if (strncmp(l, "long", NLONG) == 0) {
+ m->type = LONG;
+ l += NLONG;
+ }
+ else if (strncmp(l, "string", NSTRING) == 0) {
+ m->type = STRING;
+ l += NSTRING;
+ }
+ else if (strncmp(l, "date", NDATE) == 0) {
+ m->type = DATE;
+ l += NDATE;
+ }
+ else if (strncmp(l, "beshort", NBESHORT) == 0) {
+ m->type = BESHORT;
+ l += NBESHORT;
+ }
+ else if (strncmp(l, "belong", NBELONG) == 0) {
+ m->type = BELONG;
+ l += NBELONG;
+ }
+ else if (strncmp(l, "bedate", NBEDATE) == 0) {
+ m->type = BEDATE;
+ l += NBEDATE;
+ }
+ else if (strncmp(l, "leshort", NLESHORT) == 0) {
+ m->type = LESHORT;
+ l += NLESHORT;
+ }
+ else if (strncmp(l, "lelong", NLELONG) == 0) {
+ m->type = LELONG;
+ l += NLELONG;
+ }
+ else if (strncmp(l, "ledate", NLEDATE) == 0) {
+ m->type = LEDATE;
+ l += NLEDATE;
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, serv, APLOGNO(01524)
+ MODNAME ": type %s invalid", l);
+ return -1;
+ }
+ /* New-style anding: "0 byte&0x80 =0x80 dynamically linked" */
+ if (*l == '&') {
+ ++l;
+ m->mask = signextend(serv, m, strtol(l, &l, 0));
+ }
+ else
+ m->mask = ~0L;
+ EATAB;
+
+ switch (*l) {
+ case '>':
+ case '<':
+ /* Old-style anding: "0 byte &0x80 dynamically linked" */
+ case '&':
+ case '^':
+ case '=':
+ m->reln = *l;
+ ++l;
+ break;
+ case '!':
+ if (m->type != STRING) {
+ m->reln = *l;
+ ++l;
+ break;
+ }
+ /* FALL THROUGH */
+ default:
+ if (*l == 'x' && apr_isspace(l[1])) {
+ m->reln = *l;
+ ++l;
+ goto GetDesc; /* Bill The Cat */
+ }
+ m->reln = '=';
+ break;
+ }
+ EATAB;
+
+ if (getvalue(serv, m, &l))
+ return -1;
+ /*
+ * now get last part - the description
+ */
+ GetDesc:
+ EATAB;
+ if (l[0] == '\b') {
+ ++l;
+ m->nospflag = 1;
+ }
+ else if ((l[0] == '\\') && (l[1] == 'b')) {
+ ++l;
+ ++l;
+ m->nospflag = 1;
+ }
+ else
+ m->nospflag = 0;
+ apr_cpystrn(m->desc, l, sizeof(m->desc));
+
+#if MIME_MAGIC_DEBUG
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, serv, APLOGNO(01525)
+ MODNAME ": parse line=%d m=%pp next=%pp cont=%d desc=%s",
+ lineno, m, m->next, m->cont_level, m->desc);
+#endif /* MIME_MAGIC_DEBUG */
+
+ return 0;
+}
+
+/*
+ * Read a numeric value from a pointer, into the value union of a magic
+ * pointer, according to the magic type. Update the string pointer to point
+ * just after the number read. Return 0 for success, non-zero for failure.
+ */
+static int getvalue(server_rec *s, struct magic *m, char **p)
+{
+ int slen;
+
+ if (m->type == STRING) {
+ *p = getstr(s, *p, m->value.s, sizeof(m->value.s), &slen);
+ m->vallen = slen;
+ }
+ else if (m->reln != 'x')
+ m->value.l = signextend(s, m, strtol(*p, p, 0));
+ return 0;
+}
+
+/*
+ * Convert a string containing C character escapes. Stop at an unescaped
+ * space or tab. Copy the converted version to "p", returning its length in
+ * *slen. Return updated scan pointer as function result.
+ */
+static char *getstr(server_rec *serv, register char *s, register char *p,
+ int plen, int *slen)
+{
+ char *origs = s, *origp = p;
+ char *pmax = p + plen - 1;
+ register int c;
+ register int val;
+
+ while ((c = *s++) != '\0') {
+ if (apr_isspace(c))
+ break;
+ if (p >= pmax) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, serv, APLOGNO(01526)
+ MODNAME ": string too long: %s", origs);
+ break;
+ }
+ if (c == '\\') {
+ switch (c = *s++) {
+
+ case '\0':
+ goto out;
+
+ default:
+ *p++ = (char) c;
+ break;
+
+ case 'n':
+ *p++ = '\n';
+ break;
+
+ case 'r':
+ *p++ = '\r';
+ break;
+
+ case 'b':
+ *p++ = '\b';
+ break;
+
+ case 't':
+ *p++ = '\t';
+ break;
+
+ case 'f':
+ *p++ = '\f';
+ break;
+
+ case 'v':
+ *p++ = '\v';
+ break;
+
+ /* \ and up to 3 octal digits */
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ val = c - '0';
+ c = *s++; /* try for 2 */
+ if (c >= '0' && c <= '7') {
+ val = (val << 3) | (c - '0');
+ c = *s++; /* try for 3 */
+ if (c >= '0' && c <= '7')
+ val = (val << 3) | (c - '0');
+ else
+ --s;
+ }
+ else
+ --s;
+ *p++ = (char) val;
+ break;
+
+ /* \x and up to 3 hex digits */
+ case 'x':
+ val = 'x'; /* Default if no digits */
+ c = hextoint(*s++); /* Get next char */
+ if (c >= 0) {
+ val = c;
+ c = hextoint(*s++);
+ if (c >= 0) {
+ val = (val << 4) + c;
+ c = hextoint(*s++);
+ if (c >= 0) {
+ val = (val << 4) + c;
+ }
+ else
+ --s;
+ }
+ else
+ --s;
+ }
+ else
+ --s;
+ *p++ = (char) val;
+ break;
+ }
+ }
+ else
+ *p++ = (char) c;
+ }
+ out:
+ *p = '\0';
+ *slen = p - origp;
+ return s;
+}
+
+
+/* Single hex char to int; -1 if not a hex char. */
+static int hextoint(int c)
+{
+ if (apr_isdigit(c))
+ return c - '0';
+ if ((c >= 'a') && (c <= 'f'))
+ return c + 10 - 'a';
+ if ((c >= 'A') && (c <= 'F'))
+ return c + 10 - 'A';
+ return -1;
+}
+
+
+/*
+ * return DONE to indicate it's been handled
+ * return OK to indicate it's a regular file still needing handling
+ * other returns indicate a failure of some sort
+ */
+static int fsmagic(request_rec *r, const char *fn)
+{
+ switch (r->finfo.filetype) {
+ case APR_DIR:
+ magic_rsl_puts(r, DIR_MAGIC_TYPE);
+ return DONE;
+ case APR_CHR:
+ /*
+ * (void) magic_rsl_printf(r,"character special (%d/%d)",
+ * major(sb->st_rdev), minor(sb->st_rdev));
+ */
+ (void) magic_rsl_puts(r, MIME_BINARY_UNKNOWN);
+ return DONE;
+ case APR_BLK:
+ /*
+ * (void) magic_rsl_printf(r,"block special (%d/%d)",
+ * major(sb->st_rdev), minor(sb->st_rdev));
+ */
+ (void) magic_rsl_puts(r, MIME_BINARY_UNKNOWN);
+ return DONE;
+ /* TODO add code to handle V7 MUX and Blit MUX files */
+ case APR_PIPE:
+ /*
+ * magic_rsl_puts(r,"fifo (named pipe)");
+ */
+ (void) magic_rsl_puts(r, MIME_BINARY_UNKNOWN);
+ return DONE;
+ case APR_LNK:
+ /* We used stat(), the only possible reason for this is that the
+ * symlink is broken.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01527)
+ MODNAME ": broken symlink (%s)", fn);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ case APR_SOCK:
+ magic_rsl_puts(r, MIME_BINARY_UNKNOWN);
+ return DONE;
+ case APR_REG:
+ break;
+ default:
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01528)
+ MODNAME ": invalid file type %d.", r->finfo.filetype);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /*
+ * regular file, check next possibility
+ */
+ if (r->finfo.size == 0) {
+ magic_rsl_puts(r, MIME_TEXT_UNKNOWN);
+ return DONE;
+ }
+ return OK;
+}
+
+/*
+ * softmagic - lookup one file in database (already read from /etc/magic by
+ * apprentice.c). Passed the name and FILE * of one file to be typed.
+ */
+ /* ARGSUSED1 *//* nbytes passed for regularity, maybe need later */
+static int softmagic(request_rec *r, unsigned char *buf, apr_size_t nbytes)
+{
+ if (match(r, buf, nbytes))
+ return 1;
+
+ return 0;
+}
+
+/*
+ * Go through the whole list, stopping if you find a match. Process all the
+ * continuations of that match before returning.
+ *
+ * We support multi-level continuations:
+ *
+ * At any time when processing a successful top-level match, there is a current
+ * continuation level; it represents the level of the last successfully
+ * matched continuation.
+ *
+ * Continuations above that level are skipped as, if we see one, it means that
+ * the continuation that controls them - i.e, the lower-level continuation
+ * preceding them - failed to match.
+ *
+ * Continuations below that level are processed as, if we see one, it means
+ * we've finished processing or skipping higher-level continuations under the
+ * control of a successful or unsuccessful lower-level continuation, and are
+ * now seeing the next lower-level continuation and should process it. The
+ * current continuation level reverts to the level of the one we're seeing.
+ *
+ * Continuations at the current level are processed as, if we see one, there's
+ * no lower-level continuation that may have failed.
+ *
+ * If a continuation matches, we bump the current continuation level so that
+ * higher-level continuations are processed.
+ */
+static int match(request_rec *r, unsigned char *s, apr_size_t nbytes)
+{
+#if MIME_MAGIC_DEBUG
+ int rule_counter = 0;
+#endif
+ int cont_level = 0;
+ int need_separator = 0;
+ union VALUETYPE p;
+ magic_server_config_rec *conf = (magic_server_config_rec *)
+ ap_get_module_config(r->server->module_config, &mime_magic_module);
+ struct magic *m;
+
+#if MIME_MAGIC_DEBUG
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01529)
+ MODNAME ": match conf=%pp file=%s m=%s m->next=%s last=%s",
+ conf,
+ conf->magicfile ? conf->magicfile : "NULL",
+ conf->magic ? "set" : "NULL",
+ (conf->magic && conf->magic->next) ? "set" : "NULL",
+ conf->last ? "set" : "NULL");
+#endif
+
+#if MIME_MAGIC_DEBUG
+ for (m = conf->magic; m; m = m->next) {
+ if (apr_isprint((((unsigned long) m) >> 24) & 255) &&
+ apr_isprint((((unsigned long) m) >> 16) & 255) &&
+ apr_isprint((((unsigned long) m) >> 8) & 255) &&
+ apr_isprint(((unsigned long) m) & 255)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01530)
+ MODNAME ": match: POINTER CLOBBERED! "
+ "m=\"%c%c%c%c\"",
+ (((unsigned long) m) >> 24) & 255,
+ (((unsigned long) m) >> 16) & 255,
+ (((unsigned long) m) >> 8) & 255,
+ ((unsigned long) m) & 255);
+ break;
+ }
+ }
+#endif
+
+ for (m = conf->magic; m; m = m->next) {
+#if MIME_MAGIC_DEBUG
+ rule_counter++;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01531)
+ MODNAME ": line=%d desc=%s", m->lineno, m->desc);
+#endif
+
+ /* check if main entry matches */
+ if (!mget(r, &p, s, m, nbytes) ||
+ !mcheck(r, &p, m)) {
+ struct magic *m_cont;
+
+ /*
+ * main entry didn't match, flush its continuations
+ */
+ if (!m->next || (m->next->cont_level == 0)) {
+ continue;
+ }
+
+ m_cont = m->next;
+ while (m_cont && (m_cont->cont_level != 0)) {
+#if MIME_MAGIC_DEBUG
+ rule_counter++;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01532)
+ MODNAME ": line=%d mc=%pp mc->next=%pp cont=%d desc=%s",
+ m_cont->lineno, m_cont,
+ m_cont->next, m_cont->cont_level,
+ m_cont->desc);
+#endif
+ /*
+ * this trick allows us to keep *m in sync when the continue
+ * advances the pointer
+ */
+ m = m_cont;
+ m_cont = m_cont->next;
+ }
+ continue;
+ }
+
+ /* if we get here, the main entry rule was a match */
+ /* this will be the last run through the loop */
+#if MIME_MAGIC_DEBUG
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01533)
+ MODNAME ": rule matched, line=%d type=%d %s",
+ m->lineno, m->type,
+ (m->type == STRING) ? m->value.s : "");
+#endif
+
+ /* print the match */
+ mprint(r, &p, m);
+
+ /*
+ * If we printed something, we'll need to print a blank before we
+ * print something else.
+ */
+ if (m->desc[0])
+ need_separator = 1;
+ /* and any continuations that match */
+ cont_level++;
+ /*
+ * while (m && m->next && m->next->cont_level != 0 && ( m = m->next
+ * ))
+ */
+ m = m->next;
+ while (m && (m->cont_level != 0)) {
+#if MIME_MAGIC_DEBUG
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01534)
+ MODNAME ": match line=%d cont=%d type=%d %s",
+ m->lineno, m->cont_level, m->type,
+ (m->type == STRING) ? m->value.s : "");
+#endif
+ if (cont_level >= m->cont_level) {
+ if (cont_level > m->cont_level) {
+ /*
+ * We're at the end of the level "cont_level"
+ * continuations.
+ */
+ cont_level = m->cont_level;
+ }
+ if (mget(r, &p, s, m, nbytes) &&
+ mcheck(r, &p, m)) {
+ /*
+ * This continuation matched. Print its message, with a
+ * blank before it if the previous item printed and this
+ * item isn't empty.
+ */
+ /* space if previous printed */
+ if (need_separator
+ && (m->nospflag == 0)
+ && (m->desc[0] != '\0')
+ ) {
+ (void) magic_rsl_putchar(r, ' ');
+ need_separator = 0;
+ }
+ mprint(r, &p, m);
+ if (m->desc[0])
+ need_separator = 1;
+
+ /*
+ * If we see any continuations at a higher level, process
+ * them.
+ */
+ cont_level++;
+ }
+ }
+
+ /* move to next continuation record */
+ m = m->next;
+ }
+#if MIME_MAGIC_DEBUG
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01535)
+ MODNAME ": matched after %d rules", rule_counter);
+#endif
+ return 1; /* all through */
+ }
+#if MIME_MAGIC_DEBUG
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01536)
+ MODNAME ": failed after %d rules", rule_counter);
+#endif
+ return 0; /* no match at all */
+}
+
+static void mprint(request_rec *r, union VALUETYPE *p, struct magic *m)
+{
+ char *pp;
+ unsigned long v;
+ char time_str[APR_CTIME_LEN];
+
+ switch (m->type) {
+ case BYTE:
+ v = p->b;
+ break;
+
+ case SHORT:
+ case BESHORT:
+ case LESHORT:
+ v = p->h;
+ break;
+
+ case LONG:
+ case BELONG:
+ case LELONG:
+ v = p->l;
+ break;
+
+ case STRING:
+ if (m->reln == '=') {
+ (void) magic_rsl_printf(r, m->desc, m->value.s);
+ }
+ else {
+ (void) magic_rsl_printf(r, m->desc, p->s);
+ }
+ return;
+
+ case DATE:
+ case BEDATE:
+ case LEDATE:
+ apr_ctime(time_str, apr_time_from_sec(*(time_t *)&p->l));
+ pp = time_str;
+ (void) magic_rsl_printf(r, m->desc, pp);
+ return;
+ default:
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01537)
+ MODNAME ": invalid m->type (%d) in mprint().",
+ m->type);
+ return;
+ }
+
+ v = signextend(r->server, m, v) & m->mask;
+ (void) magic_rsl_printf(r, m->desc, (unsigned long) v);
+}
+
+/*
+ * Convert the byte order of the data we are looking at
+ */
+static int mconvert(request_rec *r, union VALUETYPE *p, struct magic *m)
+{
+ char *rt;
+
+ switch (m->type) {
+ case BYTE:
+ case SHORT:
+ case LONG:
+ case DATE:
+ return 1;
+ case STRING:
+ /* Null terminate and eat the return */
+ p->s[sizeof(p->s) - 1] = '\0';
+ if ((rt = strchr(p->s, '\n')) != NULL)
+ *rt = '\0';
+ return 1;
+ case BESHORT:
+ p->h = (short) ((p->hs[0] << 8) | (p->hs[1]));
+ return 1;
+ case BELONG:
+ case BEDATE:
+ p->l = (long)
+ ((p->hl[0] << 24) | (p->hl[1] << 16) | (p->hl[2] << 8) | (p->hl[3]));
+ return 1;
+ case LESHORT:
+ p->h = (short) ((p->hs[1] << 8) | (p->hs[0]));
+ return 1;
+ case LELONG:
+ case LEDATE:
+ p->l = (long)
+ ((p->hl[3] << 24) | (p->hl[2] << 16) | (p->hl[1] << 8) | (p->hl[0]));
+ return 1;
+ default:
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01538)
+ MODNAME ": invalid type %d in mconvert().", m->type);
+ return 0;
+ }
+}
+
+
+static int mget(request_rec *r, union VALUETYPE *p, unsigned char *s,
+ struct magic *m, apr_size_t nbytes)
+{
+ long offset = m->offset;
+
+ if (offset + sizeof(union VALUETYPE) > nbytes)
+ return 0;
+
+ memcpy(p, s + offset, sizeof(union VALUETYPE));
+
+ if (!mconvert(r, p, m))
+ return 0;
+
+ if (m->flag & INDIR) {
+
+ switch (m->in.type) {
+ case BYTE:
+ offset = p->b + m->in.offset;
+ break;
+ case SHORT:
+ offset = p->h + m->in.offset;
+ break;
+ case LONG:
+ offset = p->l + m->in.offset;
+ break;
+ }
+
+ if (offset + sizeof(union VALUETYPE) > nbytes)
+ return 0;
+
+ memcpy(p, s + offset, sizeof(union VALUETYPE));
+
+ if (!mconvert(r, p, m))
+ return 0;
+ }
+ return 1;
+}
+
+static int mcheck(request_rec *r, union VALUETYPE *p, struct magic *m)
+{
+ register unsigned long l = m->value.l;
+ register unsigned long v;
+ int matched;
+
+ if ((m->value.s[0] == 'x') && (m->value.s[1] == '\0')) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01539)
+ MODNAME ": BOINK");
+ return 1;
+ }
+
+ switch (m->type) {
+ case BYTE:
+ v = p->b;
+ break;
+
+ case SHORT:
+ case BESHORT:
+ case LESHORT:
+ v = p->h;
+ break;
+
+ case LONG:
+ case BELONG:
+ case LELONG:
+ case DATE:
+ case BEDATE:
+ case LEDATE:
+ v = p->l;
+ break;
+
+ case STRING:
+ l = 0;
+ /*
+ * What we want here is: v = strncmp(m->value.s, p->s, m->vallen);
+ * but ignoring any nulls. bcmp doesn't give -/+/0 and isn't
+ * universally available anyway.
+ */
+ v = 0;
+ {
+ register unsigned char *a = (unsigned char *) m->value.s;
+ register unsigned char *b = (unsigned char *) p->s;
+ register int len = m->vallen;
+
+ while (--len >= 0)
+ if ((v = *b++ - *a++) != 0)
+ break;
+ }
+ break;
+ default:
+ /* bogosity, pretend that it just wasn't a match */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01540)
+ MODNAME ": invalid type %d in mcheck().", m->type);
+ return 0;
+ }
+
+ v = signextend(r->server, m, v) & m->mask;
+
+ switch (m->reln) {
+ case 'x':
+#if MIME_MAGIC_DEBUG
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01541)
+ "%lu == *any* = 1", v);
+#endif
+ matched = 1;
+ break;
+
+ case '!':
+ matched = v != l;
+#if MIME_MAGIC_DEBUG
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01542)
+ "%lu != %lu = %d", v, l, matched);
+#endif
+ break;
+
+ case '=':
+ matched = v == l;
+#if MIME_MAGIC_DEBUG
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01543)
+ "%lu == %lu = %d", v, l, matched);
+#endif
+ break;
+
+ case '>':
+ if (m->flag & UNSIGNED) {
+ matched = v > l;
+#if MIME_MAGIC_DEBUG
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01544)
+ "%lu > %lu = %d", v, l, matched);
+#endif
+ }
+ else {
+ matched = (long) v > (long) l;
+#if MIME_MAGIC_DEBUG
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01545)
+ "%ld > %ld = %d", v, l, matched);
+#endif
+ }
+ break;
+
+ case '<':
+ if (m->flag & UNSIGNED) {
+ matched = v < l;
+#if MIME_MAGIC_DEBUG
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01546)
+ "%lu < %lu = %d", v, l, matched);
+#endif
+ }
+ else {
+ matched = (long) v < (long) l;
+#if MIME_MAGIC_DEBUG
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01547)
+ "%ld < %ld = %d", v, l, matched);
+#endif
+ }
+ break;
+
+ case '&':
+ matched = (v & l) == l;
+#if MIME_MAGIC_DEBUG
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01548)
+ "((%lx & %lx) == %lx) = %d", v, l, l, matched);
+#endif
+ break;
+
+ case '^':
+ matched = (v & l) != l;
+#if MIME_MAGIC_DEBUG
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01549)
+ "((%lx & %lx) != %lx) = %d", v, l, l, matched);
+#endif
+ break;
+
+ default:
+ /* bogosity, pretend it didn't match */
+ matched = 0;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01550)
+ MODNAME ": mcheck: can't happen: invalid relation %d.",
+ m->reln);
+ break;
+ }
+
+ return matched;
+}
+
+/* an optimization over plain strcmp() */
+#define STREQ(a, b) (*(a) == *(b) && strcmp((a), (b)) == 0)
+
+static int ascmagic(request_rec *r, unsigned char *buf, apr_size_t nbytes)
+{
+ int has_escapes = 0;
+ unsigned char *s;
+ char nbuf[SMALL_HOWMANY + 1]; /* one extra for terminating '\0' */
+ char *token;
+ const struct names *p;
+ int small_nbytes;
+ char *strtok_state;
+
+ /* these are easy, do them first */
+
+ /*
+ * for troff, look for . + letter + letter or .\"; this must be done to
+ * disambiguate tar archives' ./file and other trash from real troff
+ * input.
+ */
+ if (*buf == '.') {
+ unsigned char *tp = buf + 1;
+
+ while (apr_isspace(*tp))
+ ++tp; /* skip leading whitespace */
+ if ((apr_isalnum(*tp) || *tp == '\\') &&
+ (apr_isalnum(*(tp + 1)) || *tp == '"')) {
+ magic_rsl_puts(r, "application/x-troff");
+ return 1;
+ }
+ }
+ if ((*buf == 'c' || *buf == 'C') && apr_isspace(*(buf + 1))) {
+ /* Fortran */
+ magic_rsl_puts(r, "text/plain");
+ return 1;
+ }
+
+ /* look for tokens from names.h - this is expensive!, so we'll limit
+ * ourselves to only SMALL_HOWMANY bytes */
+ small_nbytes = (nbytes > SMALL_HOWMANY) ? SMALL_HOWMANY : nbytes;
+ /* make a copy of the buffer here because apr_strtok() will destroy it */
+ s = (unsigned char *) memcpy(nbuf, buf, small_nbytes);
+ s[small_nbytes] = '\0';
+ has_escapes = (memchr(s, '\033', small_nbytes) != NULL);
+ while ((token = apr_strtok((char *) s, " \t\n\r\f", &strtok_state)) != NULL) {
+ s = NULL; /* make apr_strtok() keep on tokin' */
+ for (p = names; p < names + NNAMES; p++) {
+ if (STREQ(p->name, token)) {
+ magic_rsl_puts(r, types[p->type]);
+ if (has_escapes)
+ magic_rsl_puts(r, " (with escape sequences)");
+ return 1;
+ }
+ }
+ }
+
+ switch (is_tar(buf, nbytes)) {
+ case 1:
+ /* V7 tar archive */
+ magic_rsl_puts(r, "application/x-tar");
+ return 1;
+ case 2:
+ /* POSIX tar archive */
+ magic_rsl_puts(r, "application/x-tar");
+ return 1;
+ }
+
+ /* all else fails, but it is ascii... */
+ return 0;
+}
+
+
+/*
+ * compress routines: zmagic() - returns 0 if not recognized, uncompresses
+ * and prints information if recognized uncompress(s, method, old, n, newch)
+ * - uncompress old into new, using method, return sizeof new
+ */
+
+static const struct {
+ const char *magic;
+ apr_size_t maglen;
+ const char *argv[3];
+ int silent;
+ const char *encoding; /* MUST be lowercase */
+} compr[] = {
+
+ /* we use gzip here rather than uncompress because we have to pass
+ * it a full filename -- and uncompress only considers filenames
+ * ending with .Z
+ */
+ {
+ "\037\235", 2, {
+ "gzip", "-dcq", NULL
+ }, 0, "x-compress"
+ },
+ {
+ "\037\213", 2, {
+ "gzip", "-dcq", NULL
+ }, 1, "x-gzip"
+ },
+ /*
+ * XXX pcat does not work, cause I don't know how to make it read stdin,
+ * so we use gzip
+ */
+ {
+ "\037\036", 2, {
+ "gzip", "-dcq", NULL
+ }, 0, "x-gzip"
+ },
+};
+
+#define ncompr (sizeof(compr) / sizeof(compr[0]))
+
+static int zmagic(request_rec *r, unsigned char *buf, apr_size_t nbytes)
+{
+ unsigned char *newbuf;
+ int newsize;
+ int i;
+
+ for (i = 0; i < ncompr; i++) {
+ if (nbytes < compr[i].maglen)
+ continue;
+ if (memcmp(buf, compr[i].magic, compr[i].maglen) == 0)
+ break;
+ }
+
+ if (i == ncompr)
+ return 0;
+
+ if ((newsize = uncompress(r, i, &newbuf, HOWMANY)) > 0) {
+ /* set encoding type in the request record */
+ r->content_encoding = compr[i].encoding;
+
+ newbuf[newsize-1] = '\0'; /* null-terminate uncompressed data */
+ /* Try to detect the content type of the uncompressed data */
+ if (tryit(r, newbuf, newsize, 0) != OK) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+
+struct uncompress_parms {
+ request_rec *r;
+ int method;
+};
+
+static int create_uncompress_child(struct uncompress_parms *parm, apr_pool_t *cntxt,
+ apr_file_t **pipe_in)
+{
+ int rc = 1;
+ const char *new_argv[4];
+ request_rec *r = parm->r;
+ apr_pool_t *child_context = cntxt;
+ apr_procattr_t *procattr;
+ apr_proc_t *procnew;
+
+ /* XXX missing 1.3 logic:
+ *
+ * what happens when !compr[parm->method].silent?
+ * Should we create the err pipe, read it, and copy to the log?
+ */
+
+ if ((apr_procattr_create(&procattr, child_context) != APR_SUCCESS) ||
+ (apr_procattr_io_set(procattr, APR_FULL_BLOCK,
+ APR_FULL_BLOCK, APR_NO_PIPE) != APR_SUCCESS) ||
+ (apr_procattr_dir_set(procattr,
+ ap_make_dirstr_parent(r->pool, r->filename)) != APR_SUCCESS) ||
+ (apr_procattr_cmdtype_set(procattr, APR_PROGRAM_PATH) != APR_SUCCESS)) {
+ /* Something bad happened, tell the world. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_ENOPROC, r, APLOGNO(01551)
+ "couldn't setup child process: %s", r->filename);
+ }
+ else {
+ new_argv[0] = compr[parm->method].argv[0];
+ new_argv[1] = compr[parm->method].argv[1];
+ new_argv[2] = r->filename;
+ new_argv[3] = NULL;
+
+ procnew = apr_pcalloc(child_context, sizeof(*procnew));
+ rc = apr_proc_create(procnew, compr[parm->method].argv[0],
+ new_argv, NULL, procattr, child_context);
+
+ if (rc != APR_SUCCESS) {
+ /* Bad things happened. Everyone should have cleaned up. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_ENOPROC, r, APLOGNO(01552)
+ MODNAME ": could not execute `%s'.",
+ compr[parm->method].argv[0]);
+ }
+ else {
+ apr_pool_note_subprocess(child_context, procnew, APR_KILL_AFTER_TIMEOUT);
+ *pipe_in = procnew->out;
+ }
+ }
+
+ return (rc);
+}
+
+static int uncompress(request_rec *r, int method,
+ unsigned char **newch, apr_size_t n)
+{
+ struct uncompress_parms parm;
+ apr_file_t *pipe_out = NULL;
+ apr_pool_t *sub_context;
+ apr_status_t rv;
+
+ parm.r = r;
+ parm.method = method;
+
+ /* We make a sub_pool so that we can collect our child early, otherwise
+ * there are cases (i.e. generating directory indices with mod_autoindex)
+ * where we would end up with LOTS of zombies.
+ */
+ if (apr_pool_create(&sub_context, r->pool) != APR_SUCCESS)
+ return -1;
+ apr_pool_tag(sub_context, "magic_uncompress");
+
+ if ((rv = create_uncompress_child(&parm, sub_context, &pipe_out)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01553)
+ MODNAME ": couldn't spawn uncompress process: %s", r->uri);
+ return -1;
+ }
+
+ *newch = (unsigned char *) apr_palloc(r->pool, n);
+ rv = apr_file_read(pipe_out, *newch, &n);
+ if (n == 0) {
+ apr_pool_destroy(sub_context);
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01554)
+ MODNAME ": read failed from uncompress of %s", r->filename);
+ return -1;
+ }
+ apr_pool_destroy(sub_context);
+ return n;
+}
+
+/*
+ * is_tar() -- figure out whether file is a tar archive.
+ *
+ * Stolen (by author of file utility) from the public domain tar program: Public
+ * Domain version written 26 Aug 1985 John Gilmore (ihnp4!hoptoad!gnu).
+ *
+ * @(#)list.c 1.18 9/23/86 Public Domain - gnu $Id: mod_mime_magic.c,v 1.7
+ * 1997/06/24 00:41:02 ikluft Exp ikluft $
+ *
+ * Comments changed and some code/comments reformatted for file command by Ian
+ * Darwin.
+ */
+
+#define isodigit(c) (((unsigned char)(c) >= '0') && ((unsigned char)(c) <= '7'))
+
+/*
+ * Return 0 if the checksum is bad (i.e., probably not a tar archive), 1 for
+ * old UNIX tar file, 2 for Unix Std (POSIX) tar file.
+ */
+
+static int is_tar(unsigned char *buf, apr_size_t nbytes)
+{
+ register union record *header = (union record *) buf;
+ register int i;
+ register long sum, recsum;
+ register char *p;
+
+ if (nbytes < sizeof(union record))
+ return 0;
+
+ recsum = from_oct(8, header->header.chksum);
+
+ sum = 0;
+ p = header->charptr;
+ for (i = sizeof(union record); --i >= 0;) {
+ /*
+ * We can't use unsigned char here because of old compilers, e.g. V7.
+ */
+ sum += 0xFF & *p++;
+ }
+
+ /* Adjust checksum to count the "chksum" field as blanks. */
+ for (i = sizeof(header->header.chksum); --i >= 0;)
+ sum -= 0xFF & header->header.chksum[i];
+ sum += ' ' * sizeof header->header.chksum;
+
+ if (sum != recsum)
+ return 0; /* Not a tar archive */
+
+ if (0 == strcmp(header->header.magic, TMAGIC))
+ return 2; /* Unix Standard tar archive */
+
+ return 1; /* Old fashioned tar archive */
+}
+
+
+/*
+ * Quick and dirty octal conversion.
+ *
+ * Result is -1 if the field is invalid (all blank, or nonoctal).
+ */
+static long from_oct(int digs, char *where)
+{
+ register long value;
+
+ while (apr_isspace(*where)) { /* Skip spaces */
+ where++;
+ if (--digs <= 0)
+ return -1; /* All blank field */
+ }
+ value = 0;
+ while (digs > 0 && isodigit(*where)) { /* Scan til nonoctal */
+ value = (value << 3) | (*where++ - '0');
+ --digs;
+ }
+
+ if (digs > 0 && *where && !apr_isspace(*where))
+ return -1; /* Ended on non-space/nul */
+
+ return value;
+}
+
+/*
+ * Check for file-revision suffix
+ *
+ * This is for an obscure document control system used on an intranet.
+ * The web representation of each file's revision has an @1, @2, etc
+ * appended with the revision number. This needs to be stripped off to
+ * find the file suffix, which can be recognized by sending the name back
+ * through a sub-request. The base file name (without the @num suffix)
+ * must exist because its type will be used as the result.
+ */
+static int revision_suffix(request_rec *r)
+{
+ int suffix_pos, result;
+ char *sub_filename;
+ request_rec *sub;
+
+#if MIME_MAGIC_DEBUG
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01555)
+ MODNAME ": revision_suffix checking %s", r->filename);
+#endif /* MIME_MAGIC_DEBUG */
+
+ /* check for recognized revision suffix */
+ suffix_pos = strlen(r->filename) - 1;
+ if (!apr_isdigit(r->filename[suffix_pos])) {
+ return 0;
+ }
+ while (suffix_pos >= 0 && apr_isdigit(r->filename[suffix_pos]))
+ suffix_pos--;
+ if (suffix_pos < 0 || r->filename[suffix_pos] != '@') {
+ return 0;
+ }
+
+ /* perform sub-request for the file name without the suffix */
+ result = 0;
+ sub_filename = apr_pstrndup(r->pool, r->filename, suffix_pos);
+#if MIME_MAGIC_DEBUG
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01556)
+ MODNAME ": subrequest lookup for %s", sub_filename);
+#endif /* MIME_MAGIC_DEBUG */
+ sub = ap_sub_req_lookup_file(sub_filename, r, NULL);
+
+ /* extract content type/encoding/language from sub-request */
+ if (sub->content_type) {
+ ap_set_content_type(r, apr_pstrdup(r->pool, sub->content_type));
+#if MIME_MAGIC_DEBUG
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01557)
+ MODNAME ": subrequest %s got %s",
+ sub_filename, r->content_type);
+#endif /* MIME_MAGIC_DEBUG */
+ if (sub->content_encoding)
+ r->content_encoding =
+ apr_pstrdup(r->pool, sub->content_encoding);
+ if (sub->content_languages) {
+ int n;
+ r->content_languages = apr_array_copy(r->pool,
+ sub->content_languages);
+ for (n = 0; n < r->content_languages->nelts; ++n) {
+ char **lang = ((char **)r->content_languages->elts) + n;
+ *lang = apr_pstrdup(r->pool, *lang);
+ }
+ }
+ result = 1;
+ }
+
+ /* clean up */
+ ap_destroy_sub_req(sub);
+
+ return result;
+}
+
+/*
+ * initialize the module
+ */
+static int magic_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *main_server)
+{
+ int result;
+ magic_server_config_rec *conf;
+ magic_server_config_rec *main_conf;
+ server_rec *s;
+#if MIME_MAGIC_DEBUG
+ struct magic *m, *prevm;
+#endif /* MIME_MAGIC_DEBUG */
+
+ main_conf = ap_get_module_config(main_server->module_config, &mime_magic_module);
+ for (s = main_server; s; s = s->next) {
+ conf = ap_get_module_config(s->module_config, &mime_magic_module);
+ if (conf->magicfile == NULL && s != main_server) {
+ /* inherits from the parent */
+ *conf = *main_conf;
+ }
+ else if (conf->magicfile) {
+ result = apprentice(s, p);
+ if (result == -1)
+ return OK;
+#if MIME_MAGIC_DEBUG
+ prevm = 0;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01558)
+ MODNAME ": magic_init 1 test");
+ for (m = conf->magic; m; m = m->next) {
+ if (apr_isprint((((unsigned long) m) >> 24) & 255) &&
+ apr_isprint((((unsigned long) m) >> 16) & 255) &&
+ apr_isprint((((unsigned long) m) >> 8) & 255) &&
+ apr_isprint(((unsigned long) m) & 255)) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01559)
+ MODNAME ": magic_init 1: POINTER CLOBBERED! "
+ "m=\"%c%c%c%c\" line=%d",
+ (((unsigned long) m) >> 24) & 255,
+ (((unsigned long) m) >> 16) & 255,
+ (((unsigned long) m) >> 8) & 255,
+ ((unsigned long) m) & 255,
+ prevm ? prevm->lineno : -1);
+ break;
+ }
+ prevm = m;
+ }
+#endif
+ }
+ }
+ return OK;
+}
+
+/*
+ * Find the Content-Type from any resource this module has available
+ */
+
+static int magic_find_ct(request_rec *r)
+{
+ int result;
+ magic_server_config_rec *conf;
+
+ /* the file has to exist */
+ if (r->finfo.filetype == APR_NOFILE || !r->filename) {
+ return DECLINED;
+ }
+
+ /* was someone else already here? */
+ if (r->content_type) {
+ return DECLINED;
+ }
+
+ conf = ap_get_module_config(r->server->module_config, &mime_magic_module);
+ if (!conf || !conf->magic) {
+ return DECLINED;
+ }
+
+ /* initialize per-request info */
+ if (!magic_set_config(r)) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* try excluding file-revision suffixes */
+ if (revision_suffix(r) != 1) {
+ /* process it based on the file contents */
+ if ((result = magic_process(r)) != OK) {
+ return result;
+ }
+ }
+
+ /* if we have any results, put them in the request structure */
+ return magic_rsl_to_request(r);
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ static const char * const aszPre[]={ "mod_mime.c", NULL };
+
+ /* mod_mime_magic should be run after mod_mime, if at all. */
+
+ ap_hook_type_checker(magic_find_ct, aszPre, NULL, APR_HOOK_MIDDLE);
+ ap_hook_post_config(magic_init, NULL, NULL, APR_HOOK_FIRST);
+}
+
+/*
+ * Apache API module interface
+ */
+
+AP_DECLARE_MODULE(mime_magic) =
+{
+ STANDARD20_MODULE_STUFF,
+ NULL, /* dir config creator */
+ NULL, /* dir merger --- default is to override */
+ create_magic_server_config, /* server config */
+ merge_magic_server_config, /* merge server config */
+ mime_magic_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/metadata/mod_mime_magic.dep b/modules/metadata/mod_mime_magic.dep
new file mode 100644
index 0000000..9257aa2
--- /dev/null
+++ b/modules/metadata/mod_mime_magic.dep
@@ -0,0 +1,58 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_mime_magic.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_mime_magic.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/metadata/mod_mime_magic.dsp b/modules/metadata/mod_mime_magic.dsp
new file mode 100644
index 0000000..9989b9a
--- /dev/null
+++ b/modules/metadata/mod_mime_magic.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_mime_magic" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_mime_magic - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_mime_magic.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_mime_magic.mak" CFG="mod_mime_magic - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_mime_magic - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_mime_magic - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_mime_magic - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_mime_magic_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_mime_magic.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_mime_magic.so" /d LONG_NAME="mime_magic_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_mime_magic.so" /base:@..\..\os\win32\BaseAddr.ref,mod_mime_magic.so
+# ADD LINK32 kernel32.lib ws2_32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_mime_magic.so" /base:@..\..\os\win32\BaseAddr.ref,mod_mime_magic.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_mime_magic.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_mime_magic - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_mime_magic_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_mime_magic.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_mime_magic.so" /d LONG_NAME="mime_magic_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_mime_magic.so" /base:@..\..\os\win32\BaseAddr.ref,mod_mime_magic.so
+# ADD LINK32 kernel32.lib ws2_32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_mime_magic.so" /base:@..\..\os\win32\BaseAddr.ref,mod_mime_magic.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_mime_magic.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_mime_magic - Win32 Release"
+# Name "mod_mime_magic - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_mime_magic.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/metadata/mod_mime_magic.exp b/modules/metadata/mod_mime_magic.exp
new file mode 100644
index 0000000..42068a4
--- /dev/null
+++ b/modules/metadata/mod_mime_magic.exp
@@ -0,0 +1 @@
+mime_magic_module
diff --git a/modules/metadata/mod_mime_magic.mak b/modules/metadata/mod_mime_magic.mak
new file mode 100644
index 0000000..f4573f7
--- /dev/null
+++ b/modules/metadata/mod_mime_magic.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_mime_magic.dsp
+!IF "$(CFG)" == ""
+CFG=mod_mime_magic - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_mime_magic - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_mime_magic - Win32 Release" && "$(CFG)" != "mod_mime_magic - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_mime_magic.mak" CFG="mod_mime_magic - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_mime_magic - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_mime_magic - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_mime_magic - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_mime_magic.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_mime_magic.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_mime_magic.obj"
+ -@erase "$(INTDIR)\mod_mime_magic.res"
+ -@erase "$(INTDIR)\mod_mime_magic_src.idb"
+ -@erase "$(INTDIR)\mod_mime_magic_src.pdb"
+ -@erase "$(OUTDIR)\mod_mime_magic.exp"
+ -@erase "$(OUTDIR)\mod_mime_magic.lib"
+ -@erase "$(OUTDIR)\mod_mime_magic.pdb"
+ -@erase "$(OUTDIR)\mod_mime_magic.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_mime_magic_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_mime_magic.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_mime_magic.so" /d LONG_NAME="mime_magic_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_mime_magic.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_mime_magic.pdb" /debug /out:"$(OUTDIR)\mod_mime_magic.so" /implib:"$(OUTDIR)\mod_mime_magic.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_mime_magic.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_mime_magic.obj" \
+ "$(INTDIR)\mod_mime_magic.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_mime_magic.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_mime_magic.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_mime_magic.so"
+ if exist .\Release\mod_mime_magic.so.manifest mt.exe -manifest .\Release\mod_mime_magic.so.manifest -outputresource:.\Release\mod_mime_magic.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_mime_magic - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_mime_magic.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_mime_magic.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_mime_magic.obj"
+ -@erase "$(INTDIR)\mod_mime_magic.res"
+ -@erase "$(INTDIR)\mod_mime_magic_src.idb"
+ -@erase "$(INTDIR)\mod_mime_magic_src.pdb"
+ -@erase "$(OUTDIR)\mod_mime_magic.exp"
+ -@erase "$(OUTDIR)\mod_mime_magic.lib"
+ -@erase "$(OUTDIR)\mod_mime_magic.pdb"
+ -@erase "$(OUTDIR)\mod_mime_magic.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_mime_magic_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_mime_magic.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_mime_magic.so" /d LONG_NAME="mime_magic_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_mime_magic.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_mime_magic.pdb" /debug /out:"$(OUTDIR)\mod_mime_magic.so" /implib:"$(OUTDIR)\mod_mime_magic.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_mime_magic.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_mime_magic.obj" \
+ "$(INTDIR)\mod_mime_magic.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_mime_magic.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_mime_magic.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_mime_magic.so"
+ if exist .\Debug\mod_mime_magic.so.manifest mt.exe -manifest .\Debug\mod_mime_magic.so.manifest -outputresource:.\Debug\mod_mime_magic.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_mime_magic.dep")
+!INCLUDE "mod_mime_magic.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_mime_magic.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_mime_magic - Win32 Release" || "$(CFG)" == "mod_mime_magic - Win32 Debug"
+
+!IF "$(CFG)" == "mod_mime_magic - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\metadata"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_mime_magic - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\metadata"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_mime_magic - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\metadata"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_mime_magic - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\metadata"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_mime_magic - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\metadata"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_mime_magic - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\metadata"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\metadata"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_mime_magic - Win32 Release"
+
+
+"$(INTDIR)\mod_mime_magic.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_mime_magic.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_mime_magic.so" /d LONG_NAME="mime_magic_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_mime_magic - Win32 Debug"
+
+
+"$(INTDIR)\mod_mime_magic.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_mime_magic.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_mime_magic.so" /d LONG_NAME="mime_magic_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_mime_magic.c
+
+"$(INTDIR)\mod_mime_magic.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/metadata/mod_remoteip.c b/modules/metadata/mod_remoteip.c
new file mode 100644
index 0000000..045e988
--- /dev/null
+++ b/modules/metadata/mod_remoteip.c
@@ -0,0 +1,1267 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Portions of the input filter code for PROXY protocol support is
+ * Copyright 2014 Cloudzilla Inc.
+ */
+
+#include "ap_config.h"
+#include "ap_mmn.h"
+#include "ap_listen.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_connection.h"
+#include "http_protocol.h"
+#include "http_log.h"
+#include "http_main.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+#define APR_WANT_BYTEFUNC
+#include "apr_want.h"
+#include "apr_network_io.h"
+#include "apr_version.h"
+
+module AP_MODULE_DECLARE_DATA remoteip_module;
+
+typedef struct {
+ /** A proxy IP mask to match */
+ apr_ipsubnet_t *ip;
+ /** Flagged if internal, otherwise an external trusted proxy */
+ void *internal;
+} remoteip_proxymatch_t;
+
+typedef struct remoteip_addr_info {
+ struct remoteip_addr_info *next;
+ apr_sockaddr_t *addr;
+ server_rec *source;
+} remoteip_addr_info;
+
+typedef struct {
+ /** The header to retrieve a proxy-via IP list */
+ const char *header_name;
+ /** A header to record the proxied IP's
+ * (removed as the physical connection and
+ * from the proxy-via IP header value list)
+ */
+ const char *proxies_header_name;
+ /** A list of trusted proxies, ideally configured
+ * with the most commonly encountered listed first
+ */
+ apr_array_header_t *proxymatch_ip;
+
+ remoteip_addr_info *proxy_protocol_enabled;
+ remoteip_addr_info *proxy_protocol_disabled;
+
+ apr_array_header_t *disabled_subnets;
+ apr_pool_t *pool;
+} remoteip_config_t;
+
+typedef struct {
+ apr_sockaddr_t *useragent_addr;
+ char *useragent_ip;
+ /** The list of proxy IP's ignored as remote IP's */
+ const char *proxy_ips;
+ /** The remaining list of untrusted proxied remote IP's */
+ const char *proxied_remote;
+} remoteip_req_t;
+
+/* For PROXY protocol processing */
+static ap_filter_rec_t *remoteip_filter;
+
+typedef struct {
+ char line[108];
+} proxy_v1;
+
+typedef union {
+ struct { /* for TCP/UDP over IPv4, len = 12 */
+ apr_uint32_t src_addr;
+ apr_uint32_t dst_addr;
+ apr_uint16_t src_port;
+ apr_uint16_t dst_port;
+ } ip4;
+ struct { /* for TCP/UDP over IPv6, len = 36 */
+ apr_byte_t src_addr[16];
+ apr_byte_t dst_addr[16];
+ apr_uint16_t src_port;
+ apr_uint16_t dst_port;
+ } ip6;
+ struct { /* for AF_UNIX sockets, len = 216 */
+ apr_byte_t src_addr[108];
+ apr_byte_t dst_addr[108];
+ } unx;
+} proxy_v2_addr;
+
+typedef struct {
+ apr_byte_t sig[12]; /* hex 0D 0A 0D 0A 00 0D 0A 51 55 49 54 0A */
+ apr_byte_t ver_cmd; /* protocol version and command */
+ apr_byte_t fam; /* protocol family and address */
+ apr_uint16_t len; /* number of following bytes part of the header */
+ proxy_v2_addr addr;
+} proxy_v2;
+
+typedef union {
+ proxy_v1 v1;
+ proxy_v2 v2;
+} proxy_header;
+
+static const char v2sig[12] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";
+#define MIN_V1_HDR_LEN 15
+#define MIN_V2_HDR_LEN 16
+#define MIN_HDR_LEN MIN_V1_HDR_LEN
+
+/* XXX: Unsure if this is needed if v6 support is not available on
+ this platform */
+#ifndef INET6_ADDRSTRLEN
+#define INET6_ADDRSTRLEN 46
+#endif
+
+typedef struct {
+ char header[sizeof(proxy_header)];
+ apr_size_t rcvd;
+ apr_size_t need;
+ int version;
+ ap_input_mode_t mode;
+ apr_bucket_brigade *bb;
+ int done;
+} remoteip_filter_context;
+
+/** Holds the resolved proxy info for this connection and any additional
+ configurable parameters
+*/
+typedef struct {
+ /** The parsed client address in native format */
+ apr_sockaddr_t *client_addr;
+ /** Character representation of the client */
+ char *client_ip;
+} remoteip_conn_config_t;
+
+typedef enum { HDR_DONE, HDR_ERROR, HDR_NEED_MORE } remoteip_parse_status_t;
+
+static void *create_remoteip_server_config(apr_pool_t *p, server_rec *s)
+{
+ remoteip_config_t *config = apr_pcalloc(p, sizeof(*config));
+ config->disabled_subnets = apr_array_make(p, 1, sizeof(apr_ipsubnet_t *));
+ /* config->header_name = NULL;
+ * config->proxies_header_name = NULL;
+ * config->proxy_protocol_enabled = NULL;
+ * config->proxy_protocol_disabled = NULL;
+ */
+ config->pool = p;
+ return config;
+}
+
+static void *merge_remoteip_server_config(apr_pool_t *p, void *globalv,
+ void *serverv)
+{
+ remoteip_config_t *global = (remoteip_config_t *) globalv;
+ remoteip_config_t *server = (remoteip_config_t *) serverv;
+ remoteip_config_t *config;
+
+ config = (remoteip_config_t *) apr_palloc(p, sizeof(*config));
+ config->header_name = server->header_name
+ ? server->header_name
+ : global->header_name;
+ config->proxies_header_name = server->proxies_header_name
+ ? server->proxies_header_name
+ : global->proxies_header_name;
+ config->proxymatch_ip = server->proxymatch_ip
+ ? server->proxymatch_ip
+ : global->proxymatch_ip;
+ return config;
+}
+
+static const char *header_name_set(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ remoteip_config_t *config = ap_get_module_config(cmd->server->module_config,
+ &remoteip_module);
+ config->header_name = arg;
+ return NULL;
+}
+
+static const char *proxies_header_name_set(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ remoteip_config_t *config = ap_get_module_config(cmd->server->module_config,
+ &remoteip_module);
+ config->proxies_header_name = arg;
+ return NULL;
+}
+
+/* Would be quite nice if APR exported this */
+/* apr:network_io/unix/sockaddr.c */
+static int looks_like_ip(const char *ipstr)
+{
+ if (ap_strchr_c(ipstr, ':')) {
+ /* definitely not a hostname; assume it is intended to be an IPv6 address */
+ return 1;
+ }
+
+ /* simple IPv4 address string check */
+ while ((*ipstr == '.') || apr_isdigit(*ipstr))
+ ipstr++;
+ return (*ipstr == '\0');
+}
+
+static const char *proxies_set(cmd_parms *cmd, void *cfg,
+ const char *arg)
+{
+ remoteip_config_t *config = ap_get_module_config(cmd->server->module_config,
+ &remoteip_module);
+ remoteip_proxymatch_t *match;
+ apr_status_t rv;
+ char *ip = apr_pstrdup(cmd->temp_pool, arg);
+ char *s = ap_strchr(ip, '/');
+ if (s) {
+ *s++ = '\0';
+ }
+
+ if (!config->proxymatch_ip) {
+ config->proxymatch_ip = apr_array_make(cmd->pool, 1, sizeof(*match));
+ }
+ match = (remoteip_proxymatch_t *) apr_array_push(config->proxymatch_ip);
+ match->internal = cmd->info;
+
+ if (looks_like_ip(ip)) {
+ /* Note s may be null, that's fine (explicit host) */
+ rv = apr_ipsubnet_create(&match->ip, ip, s, cmd->pool);
+ }
+ else
+ {
+ apr_sockaddr_t *temp_sa;
+
+ if (s) {
+ return apr_pstrcat(cmd->pool, "RemoteIP: Error parsing IP ", arg,
+ " the subnet /", s, " is invalid for ",
+ cmd->cmd->name, NULL);
+ }
+
+ rv = apr_sockaddr_info_get(&temp_sa, ip, APR_UNSPEC, 0,
+ APR_IPV4_ADDR_OK, cmd->temp_pool);
+ while (rv == APR_SUCCESS)
+ {
+ apr_sockaddr_ip_get(&ip, temp_sa);
+ rv = apr_ipsubnet_create(&match->ip, ip, NULL, cmd->pool);
+ if (!(temp_sa = temp_sa->next)) {
+ break;
+ }
+ match = (remoteip_proxymatch_t *)
+ apr_array_push(config->proxymatch_ip);
+ match->internal = cmd->info;
+ }
+ }
+
+ if (rv != APR_SUCCESS) {
+ return apr_psprintf(cmd->pool,
+ "RemoteIP: Error parsing IP %s (%pm error) for %s",
+ arg, &rv, cmd->cmd->name);
+ }
+
+ return NULL;
+}
+
+static const char *proxylist_read(cmd_parms *cmd, void *cfg,
+ const char *filename)
+{
+ char lbuf[MAX_STRING_LEN];
+ char *arg;
+ const char *args;
+ const char *errmsg;
+ ap_configfile_t *cfp;
+ apr_status_t rv;
+
+ filename = ap_server_root_relative(cmd->temp_pool, filename);
+ rv = ap_pcfg_openfile(&cfp, cmd->temp_pool, filename);
+ if (rv != APR_SUCCESS) {
+ return apr_psprintf(cmd->pool, "%s: Could not open file %s: %pm",
+ cmd->cmd->name, filename, &rv);
+ }
+
+ while (!(ap_cfg_getline(lbuf, MAX_STRING_LEN, cfp))) {
+ args = lbuf;
+ while (*(arg = ap_getword_conf(cmd->temp_pool, &args)) != '\0') {
+ if (*arg == '#') {
+ break;
+ }
+ errmsg = proxies_set(cmd, cfg, arg);
+ if (errmsg) {
+ ap_cfg_closefile(cfp);
+ errmsg = apr_psprintf(cmd->pool, "%s at line %d of %s",
+ errmsg, cfp->line_number, filename);
+ return errmsg;
+ }
+ }
+ }
+
+ ap_cfg_closefile(cfp);
+ return NULL;
+}
+
+/** Similar to apr_sockaddr_equal, except that it compares ports too. */
+static int remoteip_sockaddr_equal(apr_sockaddr_t *addr1, apr_sockaddr_t *addr2)
+{
+ return (addr1->port == addr2->port && apr_sockaddr_equal(addr1, addr2));
+}
+
+#if !APR_VERSION_AT_LEAST(1,5,0)
+#define apr_sockaddr_is_wildcard sockaddr_is_wildcard
+/* XXX: temp build fix from apr 1.5.x */
+static int sockaddr_is_wildcard(const apr_sockaddr_t *addr)
+{
+ static const char inaddr_any[
+#if APR_HAVE_IPV6
+ sizeof(struct in6_addr)
+#else
+ sizeof(struct in_addr)
+#endif
+ ] = {0};
+
+ if (addr->ipaddr_ptr /* IP address initialized */
+ && addr->ipaddr_len <= sizeof inaddr_any) { /* else bug elsewhere? */
+ if (!memcmp(inaddr_any, addr->ipaddr_ptr, addr->ipaddr_len)) {
+ return 1;
+ }
+#if APR_HAVE_IPV6
+ if (addr->family == AF_INET6
+ && IN6_IS_ADDR_V4MAPPED((struct in6_addr *)addr->ipaddr_ptr)) {
+ struct in_addr *v4 = (struct in_addr *)&((apr_uint32_t *)addr->ipaddr_ptr)[3];
+
+ if (!memcmp(inaddr_any, v4, sizeof *v4)) {
+ return 1;
+ }
+ }
+#endif
+ }
+ return 0;
+}
+#endif
+
+
+/** Similar to remoteip_sockaddr_equal, except that it handles wildcard addresses
+ * and ports too.
+ */
+static int remoteip_sockaddr_compat(apr_sockaddr_t *addr1, apr_sockaddr_t *addr2)
+{
+ /* test exact address equality */
+ if (apr_sockaddr_equal(addr1, addr2) &&
+ (addr1->port == addr2->port || addr1->port == 0 || addr2->port == 0)) {
+ return 1;
+ }
+
+ /* test address wildcards */
+ if (apr_sockaddr_is_wildcard(addr1) &&
+ (addr1->port == 0 || addr1->port == addr2->port)) {
+ return 1;
+ }
+
+ if (apr_sockaddr_is_wildcard(addr2) &&
+ (addr2->port == 0 || addr2->port == addr1->port)) {
+ return 1;
+ }
+
+ return 0;
+}
+
+static int remoteip_addr_in_list(remoteip_addr_info *list, apr_sockaddr_t *addr)
+{
+ for (; list; list = list->next) {
+ if (remoteip_sockaddr_compat(list->addr, addr)) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static void remoteip_warn_enable_conflict(remoteip_addr_info *prev, server_rec *new, int flag)
+{
+ char buf[INET6_ADDRSTRLEN];
+
+ apr_sockaddr_ip_getbuf(buf, sizeof(buf), prev->addr);
+
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, new, APLOGNO(03491)
+ "RemoteIPProxyProtocol: previous setting for %s:%hu from virtual "
+ "host {%s:%hu in %s} is being overridden by virtual host "
+ "{%s:%hu in %s}; new setting is '%s'",
+ buf, prev->addr->port, prev->source->server_hostname,
+ prev->source->addrs->host_port, prev->source->defn_name,
+ new->server_hostname, new->addrs->host_port, new->defn_name,
+ flag ? "On" : "Off");
+}
+
+static const char *remoteip_enable_proxy_protocol(cmd_parms *cmd, void *config,
+ int flag)
+{
+ remoteip_config_t *conf;
+ server_addr_rec *addr;
+ remoteip_addr_info **add;
+ remoteip_addr_info **rem;
+ remoteip_addr_info *list;
+
+ conf = ap_get_module_config(ap_server_conf->module_config,
+ &remoteip_module);
+
+ if (flag) {
+ add = &conf->proxy_protocol_enabled;
+ rem = &conf->proxy_protocol_disabled;
+ }
+ else {
+ add = &conf->proxy_protocol_disabled;
+ rem = &conf->proxy_protocol_enabled;
+ }
+
+ for (addr = cmd->server->addrs; addr; addr = addr->next) {
+ /* remove address from opposite list */
+ if (*rem) {
+ if (remoteip_sockaddr_equal((*rem)->addr, addr->host_addr)) {
+ remoteip_warn_enable_conflict(*rem, cmd->server, flag);
+ *rem = (*rem)->next;
+ }
+ else {
+ for (list = *rem; list->next; list = list->next) {
+ if (remoteip_sockaddr_equal(list->next->addr, addr->host_addr)) {
+ remoteip_warn_enable_conflict(list->next, cmd->server, flag);
+ list->next = list->next->next;
+ break;
+ }
+ }
+ }
+ }
+
+ /* add address to desired list */
+ if (!remoteip_addr_in_list(*add, addr->host_addr)) {
+ remoteip_addr_info *info = apr_palloc(conf->pool, sizeof(*info));
+ info->addr = addr->host_addr;
+ info->source = cmd->server;
+ info->next = *add;
+ *add = info;
+ }
+ }
+
+ return NULL;
+}
+
+static const char *remoteip_disable_networks(cmd_parms *cmd, void *d,
+ int argc, char *const argv[])
+{
+ int i;
+ apr_pool_t *ptemp = cmd->temp_pool;
+ apr_pool_t *p = cmd->pool;
+ remoteip_config_t *conf = ap_get_module_config(ap_server_conf->module_config,
+ &remoteip_module);
+
+ if (argc == 0)
+ return apr_pstrcat(p, cmd->cmd->name, " requires an argument", NULL);
+
+
+ for (i=0; i<argc; i++) {
+ char *addr = apr_pstrdup(ptemp, argv[i]);
+ char *mask;
+ apr_status_t rv;
+ apr_ipsubnet_t **ip = apr_pcalloc(p, sizeof(apr_ipsubnet_t *));
+
+ if ((mask = ap_strchr(addr, '/')))
+ *mask++ = '\0';
+
+ rv = apr_ipsubnet_create(ip, addr, mask, p);
+
+ if (APR_STATUS_IS_EINVAL(rv)) {
+ /* looked nothing like an IP address */
+ return apr_psprintf(p, "ip address '%s' appears to be invalid", addr);
+ }
+ else if (rv != APR_SUCCESS) {
+ return apr_psprintf(p, "ip address '%s' appears to be invalid: %pm",
+ addr, &rv);
+ }
+
+ *(apr_ipsubnet_t**)apr_array_push(conf->disabled_subnets) = *ip;
+ }
+
+ return NULL;
+}
+
+static int remoteip_hook_post_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ remoteip_config_t *conf;
+ remoteip_addr_info *info;
+ char buf[INET6_ADDRSTRLEN];
+
+ conf = ap_get_module_config(ap_server_conf->module_config,
+ &remoteip_module);
+
+ for (info = conf->proxy_protocol_enabled; info; info = info->next) {
+ apr_sockaddr_ip_getbuf(buf, sizeof(buf), info->addr);
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, APLOGNO(03492)
+ "RemoteIPProxyProtocol: enabled on %s:%hu", buf, info->addr->port);
+ }
+ for (info = conf->proxy_protocol_disabled; info; info = info->next) {
+ apr_sockaddr_ip_getbuf(buf, sizeof(buf), info->addr);
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, APLOGNO(03494)
+ "RemoteIPProxyProtocol: disabled on %s:%hu", buf, info->addr->port);
+ }
+
+ return OK;
+}
+
+static int remoteip_modify_request(request_rec *r)
+{
+ conn_rec *c = r->connection;
+ remoteip_config_t *config = (remoteip_config_t *)
+ ap_get_module_config(r->server->module_config, &remoteip_module);
+ remoteip_conn_config_t *conn_config = (remoteip_conn_config_t *)
+ ap_get_module_config(r->connection->conn_config, &remoteip_module);
+
+ remoteip_req_t *req = NULL;
+ apr_sockaddr_t *temp_sa;
+
+ apr_status_t rv;
+ char *remote;
+ char *proxy_ips = NULL;
+ char *parse_remote;
+ char *eos;
+ unsigned char *addrbyte;
+
+ /* If no RemoteIPInternalProxy, RemoteIPInternalProxyList, RemoteIPTrustedProxy
+ or RemoteIPTrustedProxyList directive is configured,
+ all proxies will be considered as external trusted proxies.
+ */
+ void *internal = NULL;
+
+ /* No header defined or results from our input filter */
+ if (!config->header_name && !conn_config) {
+ return DECLINED;
+ }
+
+ /* Easy parsing case - just position the data we already have from PROXY
+ protocol handling allowing it to take precedence and return
+ */
+ if (conn_config) {
+ if (!conn_config->client_addr) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(03496)
+ "RemoteIPProxyProtocol data is missing, but required! Aborting request.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ r->useragent_addr = conn_config->client_addr;
+ r->useragent_ip = conn_config->client_ip;
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "Using %s as client's IP from PROXY protocol", r->useragent_ip);
+ return OK;
+ }
+
+ if (config->proxymatch_ip) {
+ /* This indicates that a RemoteIPInternalProxy, RemoteIPInternalProxyList, RemoteIPTrustedProxy
+ or RemoteIPTrustedProxyList directive is configured.
+ In this case, default to internal proxy.
+ */
+ internal = (void *) 1;
+ }
+
+ remote = (char *) apr_table_get(r->headers_in, config->header_name);
+ if (!remote) {
+ return OK;
+ }
+ remote = apr_pstrdup(r->pool, remote);
+
+ temp_sa = r->useragent_addr ? r->useragent_addr : c->client_addr;
+
+ while (remote) {
+
+ /* verify user agent IP against the trusted proxy list
+ */
+ if (config->proxymatch_ip) {
+ int i;
+ remoteip_proxymatch_t *match;
+ match = (remoteip_proxymatch_t *)config->proxymatch_ip->elts;
+ for (i = 0; i < config->proxymatch_ip->nelts; ++i) {
+ if (apr_ipsubnet_test(match[i].ip, temp_sa)) {
+ if (internal) {
+ /* Allow an internal proxy to present an external proxy,
+ but do not allow an external proxy to present an internal proxy.
+ In this case, the presented internal proxy will be considered external.
+ */
+ internal = match[i].internal;
+ }
+ break;
+ }
+ }
+ if (i && i >= config->proxymatch_ip->nelts) {
+ break;
+ }
+ }
+
+ if ((parse_remote = strrchr(remote, ',')) == NULL) {
+ parse_remote = remote;
+ remote = NULL;
+ }
+ else {
+ *(parse_remote++) = '\0';
+ }
+
+ while (*parse_remote == ' ') {
+ ++parse_remote;
+ }
+
+ eos = parse_remote + strlen(parse_remote) - 1;
+ while (eos >= parse_remote && *eos == ' ') {
+ *(eos--) = '\0';
+ }
+
+ if (eos < parse_remote) {
+ if (remote) {
+ *(remote + strlen(remote)) = ',';
+ }
+ else {
+ remote = parse_remote;
+ }
+ break;
+ }
+
+ /* We map as IPv4 rather than IPv6 for equivalent host names
+ * or IPV4OVERIPV6
+ */
+ rv = apr_sockaddr_info_get(&temp_sa, parse_remote,
+ APR_UNSPEC, temp_sa->port,
+ APR_IPV4_ADDR_OK, r->pool);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(01568)
+ "RemoteIP: Header %s value of %s cannot be parsed "
+ "as a client IP",
+ config->header_name, parse_remote);
+
+ if (remote) {
+ *(remote + strlen(remote)) = ',';
+ }
+ else {
+ remote = parse_remote;
+ }
+ break;
+ }
+
+ addrbyte = (unsigned char *) &temp_sa->sa.sin.sin_addr;
+
+ /* For intranet (Internal proxies) ignore all restrictions below */
+ if (!internal
+ && ((temp_sa->family == APR_INET
+ /* For internet (non-Internal proxies) deny all
+ * RFC3330 designated local/private subnets:
+ * 10.0.0.0/8 169.254.0.0/16 192.168.0.0/16
+ * 127.0.0.0/8 172.16.0.0/12
+ */
+ && (addrbyte[0] == 10
+ || addrbyte[0] == 127
+ || (addrbyte[0] == 169 && addrbyte[1] == 254)
+ || (addrbyte[0] == 172 && (addrbyte[1] & 0xf0) == 16)
+ || (addrbyte[0] == 192 && addrbyte[1] == 168)))
+#if APR_HAVE_IPV6
+ || (temp_sa->family == APR_INET6
+ /* For internet (non-Internal proxies) we translated
+ * IPv4-over-IPv6-mapped addresses as IPv4, above.
+ * Accept only Global Unicast 2000::/3 defined by RFC4291
+ */
+ && ((temp_sa->sa.sin6.sin6_addr.s6_addr[0] & 0xe0) != 0x20))
+#endif
+ )) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(01569)
+ "RemoteIP: Header %s value of %s appears to be "
+ "a private IP or nonsensical. Ignored",
+ config->header_name, parse_remote);
+ if (remote) {
+ *(remote + strlen(remote)) = ',';
+ }
+ else {
+ remote = parse_remote;
+ }
+
+ break;
+ }
+
+ /* save away our results */
+ if (!req) {
+ req = (remoteip_req_t *) apr_palloc(r->pool, sizeof(remoteip_req_t));
+ req->useragent_ip = r->useragent_ip;
+ }
+
+ /* Set useragent_ip string */
+ if (!internal) {
+ if (proxy_ips) {
+ proxy_ips = apr_pstrcat(r->pool, proxy_ips, ", ",
+ req->useragent_ip, NULL);
+ }
+ else {
+ proxy_ips = req->useragent_ip;
+ }
+ }
+
+ req->useragent_addr = temp_sa;
+ apr_sockaddr_ip_get(&req->useragent_ip, req->useragent_addr);
+ }
+
+ /* Nothing happened? */
+ if (!req) {
+ return OK;
+ }
+
+ /* Port is not known so set it to zero; otherwise it can be misleading */
+ req->useragent_addr->port = 0;
+
+ req->proxied_remote = remote;
+ req->proxy_ips = proxy_ips;
+
+ if (req->proxied_remote) {
+ apr_table_setn(r->headers_in, config->header_name,
+ req->proxied_remote);
+ }
+ else {
+ apr_table_unset(r->headers_in, config->header_name);
+ }
+ if (req->proxy_ips) {
+ apr_table_setn(r->notes, "remoteip-proxy-ip-list", req->proxy_ips);
+ if (config->proxies_header_name) {
+ apr_table_setn(r->headers_in, config->proxies_header_name,
+ req->proxy_ips);
+ }
+ }
+
+ r->useragent_addr = req->useragent_addr;
+ r->useragent_ip = req->useragent_ip;
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ req->proxy_ips
+ ? "Using %s as client's IP by proxies %s"
+ : "Using %s as client's IP by internal proxies%s",
+ req->useragent_ip,
+ (req->proxy_ips ? req->proxy_ips : ""));
+ return OK;
+}
+
+static int remoteip_is_server_port(apr_port_t port)
+{
+ ap_listen_rec *lr;
+
+ for (lr = ap_listeners; lr; lr = lr->next) {
+ if (lr->bind_addr && lr->bind_addr->port == port) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Human readable format:
+ * PROXY {TCP4|TCP6|UNKNOWN} <client-ip-addr> <dest-ip-addr> <client-port> <dest-port><CR><LF>
+ */
+static remoteip_parse_status_t remoteip_process_v1_header(conn_rec *c,
+ remoteip_conn_config_t *conn_conf,
+ proxy_header *hdr, apr_size_t len,
+ apr_size_t *hdr_len)
+{
+ char *end, *word, *host, *valid_addr_chars, *saveptr;
+ char buf[sizeof(hdr->v1.line)];
+ apr_port_t port;
+ apr_status_t ret;
+ apr_int32_t family;
+
+#define GET_NEXT_WORD(field) \
+ word = apr_strtok(NULL, " ", &saveptr); \
+ if (!word) { \
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(03497) \
+ "RemoteIPProxyProtocol: no " field " found in header '%s'", \
+ hdr->v1.line); \
+ return HDR_ERROR; \
+ }
+
+ end = memchr(hdr->v1.line, '\r', len - 1);
+ if (!end || end[1] != '\n') {
+ return HDR_NEED_MORE; /* partial or invalid header */
+ }
+
+ *end = '\0';
+ *hdr_len = end + 2 - hdr->v1.line; /* skip header + CRLF */
+
+ /* parse in separate buffer so have the original for error messages */
+ strcpy(buf, hdr->v1.line);
+
+ apr_strtok(buf, " ", &saveptr);
+
+ /* parse family */
+ GET_NEXT_WORD("family")
+ if (strcmp(word, "UNKNOWN") == 0) {
+ conn_conf->client_addr = c->client_addr;
+ conn_conf->client_ip = c->client_ip;
+ return HDR_DONE;
+ }
+ else if (strcmp(word, "TCP4") == 0) {
+ family = APR_INET;
+ valid_addr_chars = "0123456789.";
+ }
+ else if (strcmp(word, "TCP6") == 0) {
+#if APR_HAVE_IPV6
+ family = APR_INET6;
+ valid_addr_chars = "0123456789abcdefABCDEF:";
+#else
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(03498)
+ "RemoteIPProxyProtocol: Unable to parse v6 address - APR is not compiled with IPv6 support");
+ return HDR_ERROR;
+#endif
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(03499)
+ "RemoteIPProxyProtocol: unknown family '%s' in header '%s'",
+ word, hdr->v1.line);
+ return HDR_ERROR;
+ }
+
+ /* parse client-addr */
+ GET_NEXT_WORD("client-address")
+
+ if (strspn(word, valid_addr_chars) != strlen(word)) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(03500)
+ "RemoteIPProxyProtocol: invalid client-address '%s' found in "
+ "header '%s'", word, hdr->v1.line);
+ return HDR_ERROR;
+ }
+
+ host = word;
+
+ /* parse dest-addr */
+ GET_NEXT_WORD("destination-address")
+
+ /* parse client-port */
+ GET_NEXT_WORD("client-port")
+ if (sscanf(word, "%hu", &port) != 1) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(03501)
+ "RemoteIPProxyProtocol: error parsing port '%s' in header '%s'",
+ word, hdr->v1.line);
+ return HDR_ERROR;
+ }
+
+ /* parse dest-port */
+ /* GET_NEXT_WORD("destination-port") - no-op since we don't care about it */
+
+ /* create a socketaddr from the info */
+ ret = apr_sockaddr_info_get(&conn_conf->client_addr, host, family, port, 0,
+ c->pool);
+ if (ret != APR_SUCCESS) {
+ conn_conf->client_addr = NULL;
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, ret, c, APLOGNO(03502)
+ "RemoteIPProxyProtocol: error converting family '%d', host '%s',"
+ " and port '%hu' to sockaddr; header was '%s'",
+ family, host, port, hdr->v1.line);
+ return HDR_ERROR;
+ }
+
+ conn_conf->client_ip = apr_pstrdup(c->pool, host);
+
+ return HDR_DONE;
+}
+
+/** Add our filter to the connection if it is requested
+ */
+static int remoteip_hook_pre_connection(conn_rec *c, void *csd)
+{
+ remoteip_config_t *conf;
+ remoteip_conn_config_t *conn_conf;
+ int i;
+
+ /* Establish master config in slave connections, so that request processing
+ * finds it. */
+ if (c->master != NULL) {
+ conn_conf = ap_get_module_config(c->master->conn_config, &remoteip_module);
+ if (conn_conf) {
+ ap_set_module_config(c->conn_config, &remoteip_module, conn_conf);
+ }
+ return DECLINED;
+ }
+
+ conf = ap_get_module_config(ap_server_conf->module_config,
+ &remoteip_module);
+
+ /* check if we're enabled for this connection */
+ if (!remoteip_addr_in_list(conf->proxy_protocol_enabled, c->local_addr)
+ || remoteip_addr_in_list(conf->proxy_protocol_disabled, c->local_addr)) {
+
+ return DECLINED;
+ }
+
+ /* We are enabled for this IP/port, but check that we aren't
+ explicitly disabled */
+ for (i = 0; i < conf->disabled_subnets->nelts; i++) {
+ apr_ipsubnet_t *ip = ((apr_ipsubnet_t**)conf->disabled_subnets->elts)[i];
+
+ if (ip && apr_ipsubnet_test(ip, c->client_addr))
+ return DECLINED;
+ }
+
+ /* mod_proxy creates outgoing connections - we don't want those */
+ if (!remoteip_is_server_port(c->local_addr->port)) {
+ return DECLINED;
+ }
+
+ /* add our filter */
+ if (!ap_add_input_filter_handle(remoteip_filter, NULL, NULL, c)) {
+ /* XXX: Shouldn't this WARN in log? */
+ return DECLINED;
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03503)
+ "RemoteIPProxyProtocol: enabled on connection to %s:%hu",
+ c->local_ip, c->local_addr->port);
+
+ /* this holds the resolved proxy info for this connection */
+ conn_conf = apr_pcalloc(c->pool, sizeof(*conn_conf));
+
+ ap_set_module_config(c->conn_config, &remoteip_module, conn_conf);
+
+ return OK;
+}
+
+/* Binary format:
+ * <sig><cmd><proto><addr-len><addr>
+ * sig = \x0D \x0A \x0D \x0A \x00 \x0D \x0A \x51 \x55 \x49 \x54 \x0A
+ * cmd = <4-bits-version><4-bits-command>
+ * 4-bits-version = \x02
+ * 4-bits-command = {\x00|\x01} (\x00 = LOCAL: discard con info; \x01 = PROXY)
+ * proto = <4-bits-family><4-bits-protocol>
+ * 4-bits-family = {\x00|\x01|\x02|\x03} (AF_UNSPEC, AF_INET, AF_INET6, AF_UNIX)
+ * 4-bits-protocol = {\x00|\x01|\x02} (UNSPEC, STREAM, DGRAM)
+ */
+static remoteip_parse_status_t remoteip_process_v2_header(conn_rec *c,
+ remoteip_conn_config_t *conn_conf,
+ proxy_header *hdr)
+{
+ apr_status_t ret;
+
+ switch (hdr->v2.ver_cmd & 0xF) {
+ case 0x01: /* PROXY command */
+ switch (hdr->v2.fam) {
+ case 0x11: /* TCPv4 */
+ ret = apr_sockaddr_info_get(&conn_conf->client_addr, NULL,
+ APR_INET,
+ ntohs(hdr->v2.addr.ip4.src_port),
+ 0, c->pool);
+ if (ret != APR_SUCCESS) {
+ conn_conf->client_addr = NULL;
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, ret, c, APLOGNO(03504)
+ "RemoteIPPProxyProtocol: error creating sockaddr");
+ return HDR_ERROR;
+ }
+
+ conn_conf->client_addr->sa.sin.sin_addr.s_addr =
+ hdr->v2.addr.ip4.src_addr;
+ break;
+
+ case 0x21: /* TCPv6 */
+#if APR_HAVE_IPV6
+ ret = apr_sockaddr_info_get(&conn_conf->client_addr, NULL,
+ APR_INET6,
+ ntohs(hdr->v2.addr.ip6.src_port),
+ 0, c->pool);
+ if (ret != APR_SUCCESS) {
+ conn_conf->client_addr = NULL;
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, ret, c, APLOGNO(03505)
+ "RemoteIPProxyProtocol: error creating sockaddr");
+ return HDR_ERROR;
+ }
+ memcpy(&conn_conf->client_addr->sa.sin6.sin6_addr.s6_addr,
+ hdr->v2.addr.ip6.src_addr, 16);
+ break;
+#else
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(03506)
+ "RemoteIPProxyProtocol: APR is not compiled with IPv6 support");
+ return HDR_ERROR;
+#endif
+ default:
+ /* unsupported protocol */
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(10183)
+ "RemoteIPProxyProtocol: unsupported protocol %.2hx",
+ (unsigned short)hdr->v2.fam);
+ return HDR_ERROR;
+ }
+ break; /* we got a sockaddr now */
+ default:
+ /* not a supported command */
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(03507)
+ "RemoteIPProxyProtocol: unsupported command %.2hx",
+ (unsigned short)hdr->v2.ver_cmd);
+ return HDR_ERROR;
+ }
+
+ /* got address - compute the client_ip from it */
+ ret = apr_sockaddr_ip_get(&conn_conf->client_ip, conn_conf->client_addr);
+ if (ret != APR_SUCCESS) {
+ conn_conf->client_addr = NULL;
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, ret, c, APLOGNO(03508)
+ "RemoteIPProxyProtocol: error converting address to string");
+ return HDR_ERROR;
+ }
+
+ return HDR_DONE;
+}
+
+/** Return length for a v2 protocol header. */
+static apr_size_t remoteip_get_v2_len(proxy_header *hdr)
+{
+ return ntohs(hdr->v2.len);
+}
+
+/** Determine if this is a v1 or v2 PROXY header.
+ */
+static int remoteip_determine_version(conn_rec *c, const char *ptr)
+{
+ proxy_header *hdr = (proxy_header *) ptr;
+
+ /* assert len >= 14 */
+
+ if (memcmp(&hdr->v2, v2sig, sizeof(v2sig)) == 0 &&
+ (hdr->v2.ver_cmd & 0xF0) == 0x20) {
+ return 2;
+ }
+ else if (memcmp(hdr->v1.line, "PROXY ", 6) == 0) {
+ return 1;
+ }
+ else {
+ return -1;
+ }
+}
+
+/* Capture the first bytes on the protocol and parse the PROXY protocol header.
+ * Removes itself when the header is complete.
+ */
+static apr_status_t remoteip_input_filter(ap_filter_t *f,
+ apr_bucket_brigade *bb_out,
+ ap_input_mode_t mode,
+ apr_read_type_e block,
+ apr_off_t readbytes)
+{
+ apr_status_t ret;
+ remoteip_filter_context *ctx = f->ctx;
+ remoteip_conn_config_t *conn_conf;
+ apr_bucket *b;
+ remoteip_parse_status_t psts = HDR_NEED_MORE;
+ const char *ptr;
+ apr_size_t len;
+
+ if (f->c->aborted) {
+ return APR_ECONNABORTED;
+ }
+
+ /* allocate/retrieve the context that holds our header */
+ if (!ctx) {
+ ctx = f->ctx = apr_palloc(f->c->pool, sizeof(*ctx));
+ ctx->rcvd = 0;
+ ctx->need = MIN_HDR_LEN;
+ ctx->version = 0;
+ ctx->mode = AP_MODE_READBYTES;
+ ctx->bb = apr_brigade_create(f->c->pool, f->c->bucket_alloc);
+ ctx->done = 0;
+ }
+
+ if (ctx->done) {
+ /* Note: because we're a connection filter we can't remove ourselves
+ * when we're done, so we have to stay in the chain and just go into
+ * passthrough mode.
+ */
+ return ap_get_brigade(f->next, bb_out, mode, block, readbytes);
+ }
+
+ conn_conf = ap_get_module_config(f->c->conn_config, &remoteip_module);
+
+ /* try to read a header's worth of data */
+ while (!ctx->done) {
+ if (APR_BRIGADE_EMPTY(ctx->bb)) {
+ apr_off_t got, want = ctx->need - ctx->rcvd;
+
+ ret = ap_get_brigade(f->next, ctx->bb, ctx->mode, block, want);
+ if (ret != APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, ret, f->c, APLOGNO(10184)
+ "failed reading input");
+ return ret;
+ }
+
+ ret = apr_brigade_length(ctx->bb, 1, &got);
+ if (ret || got > want) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, ret, f->c, APLOGNO(10185)
+ "RemoteIPProxyProtocol header too long, "
+ "got %" APR_OFF_T_FMT " expected %" APR_OFF_T_FMT,
+ got, want);
+ f->c->aborted = 1;
+ return APR_ECONNABORTED;
+ }
+ }
+ if (APR_BRIGADE_EMPTY(ctx->bb)) {
+ return block == APR_NONBLOCK_READ ? APR_SUCCESS : APR_EOF;
+ }
+
+ while (!ctx->done && !APR_BRIGADE_EMPTY(ctx->bb)) {
+ b = APR_BRIGADE_FIRST(ctx->bb);
+
+ ret = apr_bucket_read(b, &ptr, &len, block);
+ if (APR_STATUS_IS_EAGAIN(ret) && block == APR_NONBLOCK_READ) {
+ return APR_SUCCESS;
+ }
+ if (ret != APR_SUCCESS) {
+ return ret;
+ }
+
+ memcpy(ctx->header + ctx->rcvd, ptr, len);
+ ctx->rcvd += len;
+
+ apr_bucket_delete(b);
+ psts = HDR_NEED_MORE;
+
+ if (ctx->version == 0) {
+ /* reading initial chunk */
+ if (ctx->rcvd >= MIN_HDR_LEN) {
+ ctx->version = remoteip_determine_version(f->c, ctx->header);
+ if (ctx->version < 0) {
+ psts = HDR_ERROR;
+ }
+ else if (ctx->version == 1) {
+ ctx->mode = AP_MODE_GETLINE;
+ ctx->need = sizeof(proxy_v1);
+ }
+ else if (ctx->version == 2) {
+ ctx->need = MIN_V2_HDR_LEN;
+ }
+ }
+ }
+ else if (ctx->version == 1) {
+ psts = remoteip_process_v1_header(f->c, conn_conf,
+ (proxy_header *) ctx->header,
+ ctx->rcvd, &ctx->need);
+ }
+ else if (ctx->version == 2) {
+ if (ctx->rcvd >= MIN_V2_HDR_LEN) {
+ ctx->need = MIN_V2_HDR_LEN +
+ remoteip_get_v2_len((proxy_header *) ctx->header);
+ if (ctx->need > sizeof(proxy_v2)) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, f->c, APLOGNO(10186)
+ "RemoteIPProxyProtocol protocol header length too long");
+ f->c->aborted = 1;
+ apr_brigade_destroy(ctx->bb);
+ return APR_ECONNABORTED;
+ }
+ }
+ if (ctx->rcvd >= ctx->need) {
+ psts = remoteip_process_v2_header(f->c, conn_conf,
+ (proxy_header *) ctx->header);
+ }
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, f->c, APLOGNO(03509)
+ "RemoteIPProxyProtocol: internal error: unknown version "
+ "%d", ctx->version);
+ f->c->aborted = 1;
+ apr_brigade_destroy(ctx->bb);
+ return APR_ECONNABORTED;
+ }
+
+ switch (psts) {
+ case HDR_ERROR:
+ f->c->aborted = 1;
+ apr_brigade_destroy(ctx->bb);
+ return APR_ECONNABORTED;
+
+ case HDR_DONE:
+ ctx->done = 1;
+ break;
+
+ case HDR_NEED_MORE:
+ break;
+ }
+ }
+ }
+
+ /* we only get here when done == 1 */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, APLOGNO(03511)
+ "RemoteIPProxyProtocol: received valid PROXY header: %s:%hu",
+ conn_conf->client_ip, conn_conf->client_addr->port);
+
+ if (ctx->rcvd > ctx->need || !APR_BRIGADE_EMPTY(ctx->bb)) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, f->c, APLOGNO(03513)
+ "RemoteIPProxyProtocol: internal error: have data left over; "
+ " need=%" APR_SIZE_T_FMT ", rcvd=%" APR_SIZE_T_FMT
+ ", brigade-empty=%d", ctx->need, ctx->rcvd,
+ APR_BRIGADE_EMPTY(ctx->bb));
+ f->c->aborted = 1;
+ apr_brigade_destroy(ctx->bb);
+ return APR_ECONNABORTED;
+ }
+
+ /* clean up */
+ apr_brigade_destroy(ctx->bb);
+ ctx->bb = NULL;
+
+ /* now do the real read for the upper layer */
+ return ap_get_brigade(f->next, bb_out, mode, block, readbytes);
+}
+
+static const command_rec remoteip_cmds[] =
+{
+ AP_INIT_TAKE1("RemoteIPHeader", header_name_set, NULL, RSRC_CONF,
+ "Specifies a request header to trust as the client IP, "
+ "e.g. X-Forwarded-For"),
+ AP_INIT_TAKE1("RemoteIPProxiesHeader", proxies_header_name_set,
+ NULL, RSRC_CONF,
+ "Specifies a request header to record proxy IP's, "
+ "e.g. X-Forwarded-By; if not given then do not record"),
+ AP_INIT_ITERATE("RemoteIPTrustedProxy", proxies_set, 0, RSRC_CONF,
+ "Specifies one or more proxies which are trusted "
+ "to present IP headers"),
+ AP_INIT_ITERATE("RemoteIPInternalProxy", proxies_set, (void*)1, RSRC_CONF,
+ "Specifies one or more internal (transparent) proxies "
+ "which are trusted to present IP headers"),
+ AP_INIT_TAKE1("RemoteIPTrustedProxyList", proxylist_read, 0,
+ RSRC_CONF | EXEC_ON_READ,
+ "The filename to read the list of trusted proxies, "
+ "see the RemoteIPTrustedProxy directive"),
+ AP_INIT_TAKE1("RemoteIPInternalProxyList", proxylist_read, (void*)1,
+ RSRC_CONF | EXEC_ON_READ,
+ "The filename to read the list of internal proxies, "
+ "see the RemoteIPInternalProxy directive"),
+ AP_INIT_FLAG("RemoteIPProxyProtocol", remoteip_enable_proxy_protocol, NULL,
+ RSRC_CONF, "Enable PROXY protocol handling ('on', 'off')"),
+ AP_INIT_TAKE_ARGV("RemoteIPProxyProtocolExceptions",
+ remoteip_disable_networks, NULL, RSRC_CONF, "Disable PROXY "
+ "protocol handling for this list of networks in CIDR format"),
+ { NULL }
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+ /* mod_ssl is CONNECTION + 5, so we want something higher (earlier);
+ * mod_reqtimeout is CONNECTION + 8, so we want something lower (later) */
+ remoteip_filter =
+ ap_register_input_filter("REMOTEIP_INPUT", remoteip_input_filter, NULL,
+ AP_FTYPE_CONNECTION + 7);
+
+ ap_hook_post_config(remoteip_hook_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_pre_connection(remoteip_hook_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_post_read_request(remoteip_modify_request, NULL, NULL, APR_HOOK_FIRST);
+}
+
+AP_DECLARE_MODULE(remoteip) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ create_remoteip_server_config, /* create per-server config structure */
+ merge_remoteip_server_config, /* merge per-server config structures */
+ remoteip_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/metadata/mod_remoteip.dep b/modules/metadata/mod_remoteip.dep
new file mode 100644
index 0000000..ee99a16
--- /dev/null
+++ b/modules/metadata/mod_remoteip.dep
@@ -0,0 +1,53 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_remoteip.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_remoteip.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/metadata/mod_remoteip.dsp b/modules/metadata/mod_remoteip.dsp
new file mode 100644
index 0000000..ee9b7e9
--- /dev/null
+++ b/modules/metadata/mod_remoteip.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_remoteip" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_remoteip - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_remoteip.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_remoteip.mak" CFG="mod_remoteip - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_remoteip - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_remoteip - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_remoteip - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_remoteip_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_remoteip.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_remoteip.so" /d LONG_NAME="remoteip_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_remoteip.so" /base:@..\..\os\win32\BaseAddr.ref,mod_remoteip.so
+# ADD LINK32 kernel32.lib ws2_32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_remoteip.so" /base:@..\..\os\win32\BaseAddr.ref,mod_remoteip.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_remoteip.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_remoteip - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_remoteip_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_remoteip.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_remoteip.so" /d LONG_NAME="remoteip_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_remoteip.so" /base:@..\..\os\win32\BaseAddr.ref,mod_remoteip.so
+# ADD LINK32 kernel32.lib ws2_32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_remoteip.so" /base:@..\..\os\win32\BaseAddr.ref,mod_remoteip.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_remoteip.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_remoteip - Win32 Release"
+# Name "mod_remoteip - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_remoteip.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/metadata/mod_remoteip.mak b/modules/metadata/mod_remoteip.mak
new file mode 100644
index 0000000..9cbd495
--- /dev/null
+++ b/modules/metadata/mod_remoteip.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_remoteip.dsp
+!IF "$(CFG)" == ""
+CFG=mod_remoteip - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_remoteip - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_remoteip - Win32 Release" && "$(CFG)" != "mod_remoteip - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_remoteip.mak" CFG="mod_remoteip - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_remoteip - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_remoteip - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_remoteip - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_remoteip.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_remoteip.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_remoteip.obj"
+ -@erase "$(INTDIR)\mod_remoteip.res"
+ -@erase "$(INTDIR)\mod_remoteip_src.idb"
+ -@erase "$(INTDIR)\mod_remoteip_src.pdb"
+ -@erase "$(OUTDIR)\mod_remoteip.exp"
+ -@erase "$(OUTDIR)\mod_remoteip.lib"
+ -@erase "$(OUTDIR)\mod_remoteip.pdb"
+ -@erase "$(OUTDIR)\mod_remoteip.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_remoteip_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_remoteip.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_remoteip.so" /d LONG_NAME="remoteip_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_remoteip.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_remoteip.pdb" /debug /out:"$(OUTDIR)\mod_remoteip.so" /implib:"$(OUTDIR)\mod_remoteip.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_remoteip.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_remoteip.obj" \
+ "$(INTDIR)\mod_remoteip.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_remoteip.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_remoteip.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_remoteip.so"
+ if exist .\Release\mod_remoteip.so.manifest mt.exe -manifest .\Release\mod_remoteip.so.manifest -outputresource:.\Release\mod_remoteip.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_remoteip - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_remoteip.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_remoteip.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_remoteip.obj"
+ -@erase "$(INTDIR)\mod_remoteip.res"
+ -@erase "$(INTDIR)\mod_remoteip_src.idb"
+ -@erase "$(INTDIR)\mod_remoteip_src.pdb"
+ -@erase "$(OUTDIR)\mod_remoteip.exp"
+ -@erase "$(OUTDIR)\mod_remoteip.lib"
+ -@erase "$(OUTDIR)\mod_remoteip.pdb"
+ -@erase "$(OUTDIR)\mod_remoteip.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_remoteip_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_remoteip.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_remoteip.so" /d LONG_NAME="remoteip_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_remoteip.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_remoteip.pdb" /debug /out:"$(OUTDIR)\mod_remoteip.so" /implib:"$(OUTDIR)\mod_remoteip.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_remoteip.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_remoteip.obj" \
+ "$(INTDIR)\mod_remoteip.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_remoteip.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_remoteip.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_remoteip.so"
+ if exist .\Debug\mod_remoteip.so.manifest mt.exe -manifest .\Debug\mod_remoteip.so.manifest -outputresource:.\Debug\mod_remoteip.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_remoteip.dep")
+!INCLUDE "mod_remoteip.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_remoteip.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_remoteip - Win32 Release" || "$(CFG)" == "mod_remoteip - Win32 Debug"
+
+!IF "$(CFG)" == "mod_remoteip - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\metadata"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_remoteip - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\metadata"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_remoteip - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\metadata"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_remoteip - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\metadata"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_remoteip - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\metadata"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_remoteip - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\metadata"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\metadata"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_remoteip - Win32 Release"
+
+
+"$(INTDIR)\mod_remoteip.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_remoteip.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_remoteip.so" /d LONG_NAME="remoteip_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_remoteip - Win32 Debug"
+
+
+"$(INTDIR)\mod_remoteip.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_remoteip.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_remoteip.so" /d LONG_NAME="remoteip_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_remoteip.c
+
+"$(INTDIR)\mod_remoteip.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/metadata/mod_setenvif.c b/modules/metadata/mod_setenvif.c
new file mode 100644
index 0000000..23d60cd
--- /dev/null
+++ b/modules/metadata/mod_setenvif.c
@@ -0,0 +1,658 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * mod_setenvif.c
+ * Set environment variables based on matching request headers or
+ * attributes against regex strings
+ *
+ * Paul Sutton <paul@ukweb.com> 27 Oct 1996
+ * Based on mod_browser by Alexei Kosut <akosut@organic.com>
+ */
+
+/*
+ * Used to set environment variables based on the incoming request headers,
+ * or some selected other attributes of the request (e.g., the remote host
+ * name).
+ *
+ * Usage:
+ *
+ * SetEnvIf name regex var ...
+ *
+ * where name is either a HTTP request header name, or one of the
+ * special values (see below). 'name' may be a regex when it is used
+ * to specify an HTTP request header name. The 'value' of the header
+ & (or the value of the special value from below) are compared against
+ * the regex argument. If this is a simple string, a simple sub-string
+ * match is performed. Otherwise, a request expression match is
+ * done. If the value matches the string or regular expression, the
+ * environment variables listed as var ... are set. Each var can
+ * be in one of three formats: var, which sets the named variable
+ * (the value "1"); var=value, which sets the variable to
+ * the given value; or !var, which unsets the variable is it has
+ * been previously set.
+ *
+ * Normally the strings are compared with regard to case. To ignore
+ * case, use the directive SetEnvIfNoCase instead.
+ *
+ * Special values for 'name' are:
+ *
+ * server_addr IP address of interface on which request arrived
+ * (analogous to SERVER_ADDR set in ap_add_common_vars())
+ * remote_host Remote host name (if available)
+ * remote_addr Remote IP address
+ * request_method Request method (GET, POST, etc)
+ * request_uri Requested URI
+ *
+ * Examples:
+ *
+ * To set the environment variable LOCALHOST if the client is the local
+ * machine:
+ *
+ * SetEnvIf remote_addr 127.0.0.1 LOCALHOST
+ *
+ * To set LOCAL if the client is the local host, or within our company's
+ * domain (192.168.10):
+ *
+ * SetEnvIf remote_addr 192.168.10. LOCAL
+ * SetEnvIf remote_addr 127.0.0.1 LOCALHOST
+ *
+ * This could be written as:
+ *
+ * SetEnvIf remote_addr (127.0.0.1|192.168.10.) LOCAL
+ *
+ * To set HAVE_TS if the client request contains any header beginning
+ * with "TS" with a value beginning with a lower case alphabet:
+ *
+ * SetEnvIf ^TS* ^[a-z].* HAVE_TS
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_strmatch.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_protocol.h"
+
+enum special {
+ SPECIAL_NOT,
+ SPECIAL_REMOTE_ADDR,
+ SPECIAL_REMOTE_HOST,
+ SPECIAL_REQUEST_URI,
+ SPECIAL_REQUEST_METHOD,
+ SPECIAL_REQUEST_PROTOCOL,
+ SPECIAL_SERVER_ADDR
+};
+typedef struct {
+ char *name; /* header name */
+ ap_regex_t *pnamereg; /* compiled header name regex */
+ char *regex; /* regex to match against */
+ ap_regex_t *preg; /* compiled regex */
+ const apr_strmatch_pattern *pattern; /* non-regex pattern to match */
+ ap_expr_info_t *expr; /* parsed expression */
+ apr_table_t *features; /* env vars to set (or unset) */
+ enum special special_type; /* is it a "special" header ? */
+ int icase; /* ignoring case? */
+} sei_entry;
+
+typedef struct {
+ apr_array_header_t *conditionals;
+} sei_cfg_rec;
+
+module AP_MODULE_DECLARE_DATA setenvif_module;
+
+/*
+ * These routines, the create- and merge-config functions, are called
+ * for both the server-wide and the per-directory contexts. This is
+ * because the different definitions are used at different times; the
+ * server-wide ones are used in the post-read-request phase, and the
+ * per-directory ones are used during the header-parse phase (after
+ * the URI has been mapped to a file and we have anything from the
+ * .htaccess file and <Directory> and <Files> containers).
+ */
+static void *create_setenvif_config(apr_pool_t *p)
+{
+ sei_cfg_rec *new = (sei_cfg_rec *) apr_palloc(p, sizeof(sei_cfg_rec));
+
+ new->conditionals = apr_array_make(p, 20, sizeof(sei_entry));
+ return (void *) new;
+}
+
+static void *create_setenvif_config_svr(apr_pool_t *p, server_rec *dummy)
+{
+ return create_setenvif_config(p);
+}
+
+static void *create_setenvif_config_dir(apr_pool_t *p, char *dummy)
+{
+ return create_setenvif_config(p);
+}
+
+static void *merge_setenvif_config(apr_pool_t *p, void *basev, void *overridesv)
+{
+ sei_cfg_rec *a = apr_pcalloc(p, sizeof(sei_cfg_rec));
+ sei_cfg_rec *base = basev, *overrides = overridesv;
+
+ a->conditionals = apr_array_append(p, base->conditionals,
+ overrides->conditionals);
+ return a;
+}
+
+/*
+ * any non-NULL magic constant will do... used to indicate if AP_REG_ICASE should
+ * be used
+ */
+#define ICASE_MAGIC ((void *)(&setenvif_module))
+#define SEI_MAGIC_HEIRLOOM "setenvif-phase-flag"
+
+static ap_regex_t *is_header_regex_regex;
+
+static int is_header_regex(apr_pool_t *p, const char* name)
+{
+ /* If a Header name contains characters other than:
+ * -,_,[A-Z\, [a-z] and [0-9].
+ * assume the header name is a regular expression.
+ */
+ if (ap_regexec(is_header_regex_regex, name, 0, NULL, 0)) {
+ return 1;
+ }
+
+ return 0;
+}
+
+/* If the input string does not take advantage of regular
+ * expression metacharacters, return a pointer to an equivalent
+ * string that can be searched using apr_strmatch(). (The
+ * returned string will often be the input string. But if
+ * the input string contains escaped characters, the returned
+ * string will be a copy with the escapes removed.)
+ */
+static const char *non_regex_pattern(apr_pool_t *p, const char *s)
+{
+ const char *src = s;
+ int escapes_found = 0;
+ int in_escape = 0;
+
+ while (*src) {
+ switch (*src) {
+ case '^':
+ case '.':
+ case '$':
+ case '|':
+ case '(':
+ case ')':
+ case '[':
+ case ']':
+ case '*':
+ case '+':
+ case '?':
+ case '{':
+ case '}':
+ if (!in_escape) {
+ return NULL;
+ }
+ in_escape = 0;
+ break;
+ case '\\':
+ if (!in_escape) {
+ in_escape = 1;
+ escapes_found = 1;
+ }
+ else {
+ in_escape = 0;
+ }
+ break;
+ default:
+ if (in_escape) {
+ return NULL;
+ }
+ break;
+ }
+ src++;
+ }
+ if (!escapes_found) {
+ return s;
+ }
+ else {
+ char *unescaped = (char *)apr_palloc(p, src - s + 1);
+ char *dst = unescaped;
+ src = s;
+ do {
+ if (*src == '\\') {
+ src++;
+ }
+ } while ((*dst++ = *src++));
+ return unescaped;
+ }
+}
+
+static const char *add_envvars(cmd_parms *cmd, const char *args, sei_entry *new)
+{
+ const char *feature;
+ int beenhere = 0;
+ char *var;
+
+ for ( ; ; ) {
+ feature = ap_getword_conf(cmd->pool, &args);
+ if (!*feature) {
+ break;
+ }
+ beenhere++;
+
+ var = ap_getword(cmd->pool, &feature, '=');
+ if (*feature) {
+ apr_table_setn(new->features, var, feature);
+ }
+ else if (*var == '!') {
+ apr_table_setn(new->features, var + 1, "!");
+ }
+ else {
+ apr_table_setn(new->features, var, "1");
+ }
+ }
+
+ if (!beenhere) {
+ return apr_pstrcat(cmd->pool, "Missing envariable expression for ",
+ cmd->cmd->name, NULL);
+ }
+
+ return NULL;
+}
+
+static const char *add_setenvif_core(cmd_parms *cmd, void *mconfig,
+ char *fname, const char *args)
+{
+ char *regex;
+ const char *simple_pattern;
+ sei_cfg_rec *sconf;
+ sei_entry *new;
+ sei_entry *entries;
+ int i;
+ int icase;
+
+ /*
+ * Determine from our context into which record to put the entry.
+ * cmd->path == NULL means we're in server-wide context; otherwise,
+ * we're dealing with a per-directory setting.
+ */
+ sconf = (cmd->path != NULL)
+ ? (sei_cfg_rec *) mconfig
+ : (sei_cfg_rec *) ap_get_module_config(cmd->server->module_config,
+ &setenvif_module);
+ entries = (sei_entry *) sconf->conditionals->elts;
+ /* get regex */
+ regex = ap_getword_conf(cmd->pool, &args);
+ if (!*regex) {
+ return apr_pstrcat(cmd->pool, "Missing regular expression for ",
+ cmd->cmd->name, NULL);
+ }
+
+ /*
+ * If we've already got a sei_entry with the same name we want to
+ * just copy the name pointer... so that later on we can compare
+ * two header names just by comparing the pointers.
+ */
+ for (i = 0; i < sconf->conditionals->nelts; ++i) {
+ new = &entries[i];
+ if (new->name && !strcasecmp(new->name, fname)) {
+ fname = new->name;
+ break;
+ }
+ }
+
+ /* if the last entry has an identical headername and regex then
+ * merge with it
+ */
+ i = sconf->conditionals->nelts - 1;
+ icase = cmd->info == ICASE_MAGIC;
+ if (i < 0
+ || entries[i].name != fname
+ || entries[i].icase != icase
+ || strcmp(entries[i].regex, regex)) {
+
+ /* no match, create a new entry */
+ new = apr_array_push(sconf->conditionals);
+ new->name = fname;
+ new->regex = regex;
+ new->icase = icase;
+ if ((simple_pattern = non_regex_pattern(cmd->pool, regex))) {
+ new->pattern = apr_strmatch_precompile(cmd->pool,
+ simple_pattern, !icase);
+ if (new->pattern == NULL) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ " pattern could not be compiled.", NULL);
+ }
+ new->preg = NULL;
+ }
+ else {
+ new->preg = ap_pregcomp(cmd->pool, regex,
+ (AP_REG_EXTENDED | (icase ? AP_REG_ICASE : 0)));
+ if (new->preg == NULL) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ " regex could not be compiled.", NULL);
+ }
+ new->pattern = NULL;
+ }
+ new->features = apr_table_make(cmd->pool, 2);
+
+ if (!strcasecmp(fname, "remote_addr")) {
+ new->special_type = SPECIAL_REMOTE_ADDR;
+ }
+ else if (!strcasecmp(fname, "remote_host")) {
+ new->special_type = SPECIAL_REMOTE_HOST;
+ }
+ else if (!strcasecmp(fname, "request_uri")) {
+ new->special_type = SPECIAL_REQUEST_URI;
+ }
+ else if (!strcasecmp(fname, "request_method")) {
+ new->special_type = SPECIAL_REQUEST_METHOD;
+ }
+ else if (!strcasecmp(fname, "request_protocol")) {
+ new->special_type = SPECIAL_REQUEST_PROTOCOL;
+ }
+ else if (!strcasecmp(fname, "server_addr")) {
+ new->special_type = SPECIAL_SERVER_ADDR;
+ }
+ else {
+ new->special_type = SPECIAL_NOT;
+ /* Handle fname as a regular expression.
+ * If fname a simple header string, identify as such
+ * (new->pnamereg = NULL) to avoid the overhead of searching
+ * through headers_in for a regex match.
+ */
+ if (is_header_regex(cmd->temp_pool, fname)) {
+ new->pnamereg = ap_pregcomp(cmd->pool, fname,
+ (AP_REG_EXTENDED | AP_REG_NOSUB
+ | (icase ? AP_REG_ICASE : 0)));
+ if (new->pnamereg == NULL)
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ "Header name regex could not be "
+ "compiled.", NULL);
+ }
+ else {
+ new->pnamereg = NULL;
+ }
+ }
+ }
+ else {
+ new = &entries[i];
+ }
+
+ return add_envvars(cmd, args, new);
+}
+
+static const char *add_setenvif(cmd_parms *cmd, void *mconfig,
+ const char *args)
+{
+ char *fname;
+
+ /* get header name */
+ fname = ap_getword_conf(cmd->pool, &args);
+ if (!*fname) {
+ return apr_pstrcat(cmd->pool, "Missing header-field name for ",
+ cmd->cmd->name, NULL);
+ }
+ return add_setenvif_core(cmd, mconfig, fname, args);
+}
+
+static const char *add_setenvifexpr(cmd_parms *cmd, void *mconfig,
+ const char *args)
+{
+ char *expr;
+ sei_cfg_rec *sconf;
+ sei_entry *new;
+ const char *err;
+
+ /*
+ * Determine from our context into which record to put the entry.
+ * cmd->path == NULL means we're in server-wide context; otherwise,
+ * we're dealing with a per-directory setting.
+ */
+ sconf = (cmd->path != NULL)
+ ? (sei_cfg_rec *) mconfig
+ : (sei_cfg_rec *) ap_get_module_config(cmd->server->module_config,
+ &setenvif_module);
+ /* get expr */
+ expr = ap_getword_conf(cmd->pool, &args);
+ if (!*expr) {
+ return apr_pstrcat(cmd->pool, "Missing expression for ",
+ cmd->cmd->name, NULL);
+ }
+
+ new = apr_array_push(sconf->conditionals);
+ new->features = apr_table_make(cmd->pool, 2);
+ new->name = NULL;
+ new->regex = NULL;
+ new->pattern = NULL;
+ new->preg = NULL;
+ new->expr = ap_expr_parse_cmd(cmd, expr, 0, &err, NULL);
+ if (err)
+ return apr_psprintf(cmd->pool, "Could not parse expression \"%s\": %s",
+ expr, err);
+
+ return add_envvars(cmd, args, new);
+}
+
+/*
+ * This routine handles the BrowserMatch* directives. It simply turns around
+ * and feeds them, with the appropriate embellishments, to the general-purpose
+ * command handler.
+ */
+static const char *add_browser(cmd_parms *cmd, void *mconfig, const char *args)
+{
+ return add_setenvif_core(cmd, mconfig, "User-Agent", args);
+}
+
+static const command_rec setenvif_module_cmds[] =
+{
+ AP_INIT_RAW_ARGS("SetEnvIf", add_setenvif, NULL, OR_FILEINFO,
+ "A header-name, regex and a list of variables."),
+ AP_INIT_RAW_ARGS("SetEnvIfNoCase", add_setenvif, ICASE_MAGIC, OR_FILEINFO,
+ "a header-name, regex and a list of variables."),
+ AP_INIT_RAW_ARGS("SetEnvIfExpr", add_setenvifexpr, NULL, OR_FILEINFO,
+ "an expression and a list of variables."),
+ AP_INIT_RAW_ARGS("BrowserMatch", add_browser, NULL, OR_FILEINFO,
+ "A browser regex and a list of variables."),
+ AP_INIT_RAW_ARGS("BrowserMatchNoCase", add_browser, ICASE_MAGIC,
+ OR_FILEINFO,
+ "A browser regex and a list of variables."),
+ { NULL },
+};
+
+/*
+ * This routine gets called at two different points in request processing:
+ * once before the URI has been translated (during the post-read-request
+ * phase) and once after (during the header-parse phase). We use different
+ * config records for the two different calls to reduce overhead (by not
+ * re-doing the server-wide settings during directory processing), and
+ * signal which call it is by having the earlier one pass a flag to the
+ * later one.
+ */
+static int match_headers(request_rec *r)
+{
+ sei_cfg_rec *sconf;
+ sei_entry *entries;
+ const apr_table_entry_t *elts;
+ const char *val, *err;
+ apr_size_t val_len = 0;
+ int i, j;
+ char *last_name;
+ ap_regmatch_t regm[AP_MAX_REG_MATCH];
+
+ if (!ap_get_module_config(r->request_config, &setenvif_module)) {
+ ap_set_module_config(r->request_config, &setenvif_module,
+ SEI_MAGIC_HEIRLOOM);
+ sconf = (sei_cfg_rec *) ap_get_module_config(r->server->module_config,
+ &setenvif_module);
+ }
+ else {
+ sconf = (sei_cfg_rec *) ap_get_module_config(r->per_dir_config,
+ &setenvif_module);
+ }
+ entries = (sei_entry *) sconf->conditionals->elts;
+ last_name = NULL;
+ val = NULL;
+ for (i = 0; i < sconf->conditionals->nelts; ++i) {
+ sei_entry *b = &entries[i];
+
+ if (!b->expr) {
+ /* Optimize the case where a bunch of directives in a row use the
+ * same header. Remember we don't need to strcmp the two header
+ * names because we made sure the pointers were equal during
+ * configuration.
+ */
+ if (b->name != last_name) {
+ last_name = b->name;
+ switch (b->special_type) {
+ case SPECIAL_REMOTE_ADDR:
+ val = r->useragent_ip;
+ break;
+ case SPECIAL_SERVER_ADDR:
+ val = r->connection->local_ip;
+ break;
+ case SPECIAL_REMOTE_HOST:
+ val = ap_get_useragent_host(r, REMOTE_NAME, NULL);
+ break;
+ case SPECIAL_REQUEST_URI:
+ val = r->uri;
+ break;
+ case SPECIAL_REQUEST_METHOD:
+ val = r->method;
+ break;
+ case SPECIAL_REQUEST_PROTOCOL:
+ val = r->protocol;
+ break;
+ case SPECIAL_NOT:
+ if (b->pnamereg) {
+ /* Matching headers_in against a regex. Iterate through
+ * the headers_in until we find a match or run out of
+ * headers.
+ */
+ const apr_array_header_t
+ *arr = apr_table_elts(r->headers_in);
+
+ elts = (const apr_table_entry_t *) arr->elts;
+ val = NULL;
+ for (j = 0; j < arr->nelts; ++j) {
+ if (!ap_regexec(b->pnamereg, elts[j].key, 0, NULL, 0)) {
+ val = elts[j].val;
+ }
+ }
+ }
+ else {
+ /* Not matching against a regex */
+ val = apr_table_get(r->headers_in, b->name);
+ if (val == NULL) {
+ val = apr_table_get(r->subprocess_env, b->name);
+ }
+ }
+ }
+ val_len = val ? strlen(val) : 0;
+ }
+
+ }
+
+ /*
+ * A NULL value indicates that the header field or special entity
+ * wasn't present or is undefined. Represent that as an empty string
+ * so that REs like "^$" will work and allow envariable setting
+ * based on missing or empty field. This is also necessary to make
+ * ap_pregsub work after evaluating an ap_expr_t which does set the
+ * regexp backref data.
+ */
+ if (val == NULL) {
+ val = "";
+ val_len = 0;
+ }
+
+ if ((b->pattern && apr_strmatch(b->pattern, val, val_len)) ||
+ (b->preg && !ap_regexec(b->preg, val, AP_MAX_REG_MATCH, regm, 0)) ||
+ (b->expr && ap_expr_exec_re(r, b->expr, AP_MAX_REG_MATCH, regm, &val, &err) > 0))
+ {
+ const apr_array_header_t *arr = apr_table_elts(b->features);
+ elts = (const apr_table_entry_t *) arr->elts;
+
+ for (j = 0; j < arr->nelts; ++j) {
+ if (*(elts[j].val) == '!') {
+ apr_table_unset(r->subprocess_env, elts[j].key);
+ }
+ else {
+ /*
+ * Do regex replacement, if we did not use a pattern, so
+ * either a regex or an expression and if we have a val
+ * or at least we did not use an expression.
+ * Background: We can have expressions that become true
+ * if a regex pattern in the expression does NOT match.
+ * In this case val is NULL and we should just set the
+ * value for the environment variable like in the pattern
+ * case.
+ */
+ if (!b->pattern && (val || !b->expr)) {
+ char *replaced = ap_pregsub(r->pool, elts[j].val, val,
+ AP_MAX_REG_MATCH, regm);
+ if (replaced) {
+ apr_table_setn(r->subprocess_env, elts[j].key,
+ replaced);
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, APLOGNO(01505)
+ "Regular expression replacement "
+ "failed for '%s', value too long?",
+ elts[j].key);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+ else {
+ apr_table_setn(r->subprocess_env, elts[j].key,
+ elts[j].val);
+ }
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "Setting %s",
+ elts[j].key);
+ }
+ }
+ }
+
+ return DECLINED;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_hook_header_parser(match_headers, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_post_read_request(match_headers, NULL, NULL, APR_HOOK_MIDDLE);
+
+ is_header_regex_regex = ap_pregcomp(p, "^[-A-Za-z0-9_]*$",
+ (AP_REG_EXTENDED | AP_REG_NOSUB ));
+ ap_assert(is_header_regex_regex != NULL);
+}
+
+AP_DECLARE_MODULE(setenvif) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_setenvif_config_dir, /* dir config creater */
+ merge_setenvif_config, /* dir merger --- default is to override */
+ create_setenvif_config_svr, /* server config */
+ merge_setenvif_config, /* merge server configs */
+ setenvif_module_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/metadata/mod_setenvif.dep b/modules/metadata/mod_setenvif.dep
new file mode 100644
index 0000000..f13a73d
--- /dev/null
+++ b/modules/metadata/mod_setenvif.dep
@@ -0,0 +1,56 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_setenvif.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_setenvif.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_strmatch.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/metadata/mod_setenvif.dsp b/modules/metadata/mod_setenvif.dsp
new file mode 100644
index 0000000..0581017
--- /dev/null
+++ b/modules/metadata/mod_setenvif.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_setenvif" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_setenvif - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_setenvif.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_setenvif.mak" CFG="mod_setenvif - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_setenvif - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_setenvif - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_setenvif - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../ssl" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_setenvif_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_setenvif.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_setenvif.so" /d LONG_NAME="setenvif_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_setenvif.so" /base:@..\..\os\win32\BaseAddr.ref,mod_setenvif.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_setenvif.so" /base:@..\..\os\win32\BaseAddr.ref,mod_setenvif.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_setenvif.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_setenvif - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../ssl" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_setenvif_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_setenvif.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_setenvif.so" /d LONG_NAME="setenvif_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_setenvif.so" /base:@..\..\os\win32\BaseAddr.ref,mod_setenvif.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_setenvif.so" /base:@..\..\os\win32\BaseAddr.ref,mod_setenvif.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_setenvif.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_setenvif - Win32 Release"
+# Name "mod_setenvif - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_setenvif.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/metadata/mod_setenvif.exp b/modules/metadata/mod_setenvif.exp
new file mode 100644
index 0000000..4f3800e
--- /dev/null
+++ b/modules/metadata/mod_setenvif.exp
@@ -0,0 +1 @@
+setenvif_module
diff --git a/modules/metadata/mod_setenvif.mak b/modules/metadata/mod_setenvif.mak
new file mode 100644
index 0000000..8c748b6
--- /dev/null
+++ b/modules/metadata/mod_setenvif.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_setenvif.dsp
+!IF "$(CFG)" == ""
+CFG=mod_setenvif - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_setenvif - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_setenvif - Win32 Release" && "$(CFG)" != "mod_setenvif - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_setenvif.mak" CFG="mod_setenvif - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_setenvif - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_setenvif - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_setenvif - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_setenvif.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_setenvif.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_setenvif.obj"
+ -@erase "$(INTDIR)\mod_setenvif.res"
+ -@erase "$(INTDIR)\mod_setenvif_src.idb"
+ -@erase "$(INTDIR)\mod_setenvif_src.pdb"
+ -@erase "$(OUTDIR)\mod_setenvif.exp"
+ -@erase "$(OUTDIR)\mod_setenvif.lib"
+ -@erase "$(OUTDIR)\mod_setenvif.pdb"
+ -@erase "$(OUTDIR)\mod_setenvif.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../ssl" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_setenvif_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_setenvif.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_setenvif.so" /d LONG_NAME="setenvif_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_setenvif.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_setenvif.pdb" /debug /out:"$(OUTDIR)\mod_setenvif.so" /implib:"$(OUTDIR)\mod_setenvif.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_setenvif.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_setenvif.obj" \
+ "$(INTDIR)\mod_setenvif.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_setenvif.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_setenvif.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_setenvif.so"
+ if exist .\Release\mod_setenvif.so.manifest mt.exe -manifest .\Release\mod_setenvif.so.manifest -outputresource:.\Release\mod_setenvif.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_setenvif - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_setenvif.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_setenvif.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_setenvif.obj"
+ -@erase "$(INTDIR)\mod_setenvif.res"
+ -@erase "$(INTDIR)\mod_setenvif_src.idb"
+ -@erase "$(INTDIR)\mod_setenvif_src.pdb"
+ -@erase "$(OUTDIR)\mod_setenvif.exp"
+ -@erase "$(OUTDIR)\mod_setenvif.lib"
+ -@erase "$(OUTDIR)\mod_setenvif.pdb"
+ -@erase "$(OUTDIR)\mod_setenvif.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../ssl" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_setenvif_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_setenvif.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_setenvif.so" /d LONG_NAME="setenvif_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_setenvif.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_setenvif.pdb" /debug /out:"$(OUTDIR)\mod_setenvif.so" /implib:"$(OUTDIR)\mod_setenvif.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_setenvif.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_setenvif.obj" \
+ "$(INTDIR)\mod_setenvif.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_setenvif.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_setenvif.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_setenvif.so"
+ if exist .\Debug\mod_setenvif.so.manifest mt.exe -manifest .\Debug\mod_setenvif.so.manifest -outputresource:.\Debug\mod_setenvif.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_setenvif.dep")
+!INCLUDE "mod_setenvif.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_setenvif.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_setenvif - Win32 Release" || "$(CFG)" == "mod_setenvif - Win32 Debug"
+
+!IF "$(CFG)" == "mod_setenvif - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\metadata"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_setenvif - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\metadata"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_setenvif - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\metadata"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_setenvif - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\metadata"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_setenvif - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\metadata"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_setenvif - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\metadata"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\metadata"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_setenvif - Win32 Release"
+
+
+"$(INTDIR)\mod_setenvif.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_setenvif.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_setenvif.so" /d LONG_NAME="setenvif_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_setenvif - Win32 Debug"
+
+
+"$(INTDIR)\mod_setenvif.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_setenvif.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_setenvif.so" /d LONG_NAME="setenvif_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_setenvif.c
+
+"$(INTDIR)\mod_setenvif.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/metadata/mod_unique_id.c b/modules/metadata/mod_unique_id.c
new file mode 100644
index 0000000..2555749
--- /dev/null
+++ b/modules/metadata/mod_unique_id.c
@@ -0,0 +1,336 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * mod_unique_id.c: generate a unique identifier for each request
+ *
+ * Original author: Dean Gaudet <dgaudet@arctic.org>
+ * UUencoding modified by: Alvaro Martinez Echevarria <alvaro@lander.es>
+ */
+
+#define APR_WANT_BYTEFUNC /* for htons() et al */
+#include "apr_want.h"
+#include "apr_general.h" /* for APR_OFFSETOF */
+#include "apr_network_io.h"
+
+#ifdef APR_HAS_THREADS
+#include "apr_atomic.h" /* for apr_atomic_inc32 */
+#include "mpm_common.h" /* for ap_mpm_query */
+#endif
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "http_protocol.h" /* for ap_hook_post_read_request */
+
+#define ROOT_SIZE 10
+
+typedef struct {
+ unsigned int stamp;
+ char root[ROOT_SIZE];
+ unsigned short counter;
+ unsigned int thread_index;
+} unique_id_rec;
+
+/* We are using thread_index (the index into the scoreboard), because we
+ * cannot guarantee the thread_id will be an integer.
+ *
+ * This code looks like it won't give a unique ID with the new thread logic.
+ * It will. The reason is, we don't increment the counter in a thread_safe
+ * manner. Because the thread_index is also in the unique ID now, this does
+ * not matter. In order for the id to not be unique, the same thread would
+ * have to get the same counter twice in the same second.
+ */
+
+/* Comments:
+ *
+ * We want an identifier which is unique across all hits, everywhere.
+ * "everywhere" includes multiple httpd instances on the same machine, or on
+ * multiple machines. Essentially "everywhere" should include all possible
+ * httpds across all servers at a particular "site". We make some assumptions
+ * that if the site has a cluster of machines then their time is relatively
+ * synchronized. We also assume that the first address returned by a
+ * gethostbyname (gethostname()) is unique across all the machines at the
+ * "site".
+ *
+ * The root is assumed to absolutely uniquely identify this one child
+ * from all other currently running children on all servers (including
+ * this physical server if it is running multiple httpds) from each
+ * other.
+ *
+ * The stamp and counter are used to distinguish all hits for a
+ * particular root. The stamp is updated using r->request_time,
+ * saving cpu cycles. The counter is never reset, and is used to
+ * permit up to 64k requests in a single second by a single child.
+ *
+ * The 144-bits of unique_id_rec are encoded using the alphabet
+ * [A-Za-z0-9@-], resulting in 24 bytes of printable characters. That is then
+ * stuffed into the environment variable UNIQUE_ID so that it is available to
+ * other modules. The alphabet choice differs from normal base64 encoding
+ * [A-Za-z0-9+/] because + and / are special characters in URLs and we want to
+ * make it easy to use UNIQUE_ID in URLs.
+ *
+ * Note that UNIQUE_ID should be considered an opaque token by other
+ * applications. No attempt should be made to dissect its internal components.
+ * It is an abstraction that may change in the future as the needs of this
+ * module change.
+ *
+ * It is highly desirable that identifiers exist for "eternity". But future
+ * needs (such as much faster webservers, or moving to a
+ * multithreaded server) may dictate a need to change the contents of
+ * unique_id_rec. Such a future implementation should ensure that the first
+ * field is still a time_t stamp. By doing that, it is possible for a site to
+ * have a "flag second" in which they stop all of their old-format servers,
+ * wait one entire second, and then start all of their new-servers. This
+ * procedure will ensure that the new space of identifiers is completely unique
+ * from the old space. (Since the first four unencoded bytes always differ.)
+ *
+ * Note: previous implementations used 32-bits of IP address plus pid
+ * in place of the PRNG output in the "root" field. This was
+ * insufficient for IPv6-only hosts, required working DNS to determine
+ * a unique IP address (fragile), and needed a [0, 1) second sleep
+ * call at startup to avoid pid reuse. Use of the PRNG avoids all
+ * these issues.
+ */
+
+/*
+ * Sun Jun 7 05:43:49 CEST 1998 -- Alvaro
+ * More comments:
+ * 1) The UUencoding procedure is now done in a general way, avoiding the problems
+ * with sizes and paddings that can arise depending on the architecture. Now the
+ * offsets and sizes of the elements of the unique_id_rec structure are calculated
+ * in unique_id_global_init; and then used to duplicate the structure without the
+ * paddings that might exist. The multithreaded server fix should be now very easy:
+ * just add a new "tid" field to the unique_id_rec structure, and increase by one
+ * UNIQUE_ID_REC_MAX.
+ * 2) unique_id_rec.stamp has been changed from "time_t" to "unsigned int", because
+ * its size is 64bits on some platforms (linux/alpha), and this caused problems with
+ * htonl/ntohl. Well, this shouldn't be a problem till year 2106.
+ */
+
+/*
+ * XXX: We should have a per-thread counter and not use cur_unique_id.counter
+ * XXX: in all threads, because this is bad for performance on multi-processor
+ * XXX: systems: Writing to the same address from several CPUs causes cache
+ * XXX: thrashing.
+ */
+static unique_id_rec cur_unique_id;
+static apr_uint32_t cur_unique_counter;
+#ifdef APR_HAS_THREADS
+static int is_threaded_mpm;
+#endif
+
+/*
+ * Number of elements in the structure unique_id_rec.
+ */
+#define UNIQUE_ID_REC_MAX 4
+
+static unsigned short unique_id_rec_offset[UNIQUE_ID_REC_MAX],
+ unique_id_rec_size[UNIQUE_ID_REC_MAX],
+ unique_id_rec_total_size,
+ unique_id_rec_size_uu;
+
+static int unique_id_global_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *main_server)
+{
+ /*
+ * Calculate the sizes and offsets in cur_unique_id.
+ */
+ unique_id_rec_offset[0] = APR_OFFSETOF(unique_id_rec, stamp);
+ unique_id_rec_size[0] = sizeof(cur_unique_id.stamp);
+ unique_id_rec_offset[1] = APR_OFFSETOF(unique_id_rec, root);
+ unique_id_rec_size[1] = sizeof(cur_unique_id.root);
+ unique_id_rec_offset[2] = APR_OFFSETOF(unique_id_rec, counter);
+ unique_id_rec_size[2] = sizeof(cur_unique_id.counter);
+ unique_id_rec_offset[3] = APR_OFFSETOF(unique_id_rec, thread_index);
+ unique_id_rec_size[3] = sizeof(cur_unique_id.thread_index);
+ unique_id_rec_total_size = unique_id_rec_size[0] + unique_id_rec_size[1] +
+ unique_id_rec_size[2] + unique_id_rec_size[3];
+
+ /*
+ * Calculate the size of the structure when encoded.
+ */
+ unique_id_rec_size_uu = (unique_id_rec_total_size*8+5)/6;
+
+ return OK;
+}
+
+static void unique_id_child_init(apr_pool_t *p, server_rec *s)
+{
+#ifdef APR_HAS_THREADS
+ is_threaded_mpm = 0;
+ ap_mpm_query(AP_MPMQ_IS_THREADED, &is_threaded_mpm);
+#endif
+
+ ap_random_insecure_bytes(&cur_unique_id.root,
+ sizeof(cur_unique_id.root));
+
+ /*
+ * If we use 0 as the initial counter we have a little less protection
+ * against restart problems, and a little less protection against a clock
+ * going backwards in time.
+ */
+ ap_random_insecure_bytes(&cur_unique_counter,
+ sizeof(cur_unique_counter));
+}
+
+/* Use the base64url encoding per RFC 4648, avoiding characters which
+ * are not safe in URLs. ### TODO: can switch to apr_encode_*. */
+static const char uuencoder[64] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_',
+};
+
+#ifndef APR_UINT16_MAX
+#define APR_UINT16_MAX 0xffffu
+#endif
+
+static const char *gen_unique_id(const request_rec *r)
+{
+ char *str;
+ /*
+ * Buffer padded with two final bytes, used to copy the unique_id_rec
+ * structure without the internal paddings that it could have.
+ */
+ unique_id_rec new_unique_id;
+ struct {
+ unique_id_rec foo;
+ unsigned char pad[2];
+ } paddedbuf;
+ apr_uint32_t counter;
+ unsigned char *x,*y;
+ int i,j,k;
+
+ memcpy(&new_unique_id.root, &cur_unique_id.root, ROOT_SIZE);
+ new_unique_id.stamp = htonl((unsigned int)apr_time_sec(r->request_time));
+ new_unique_id.thread_index = htonl((unsigned int)r->connection->id);
+#ifdef APR_HAS_THREADS
+ if (is_threaded_mpm)
+ counter = apr_atomic_inc32(&cur_unique_counter);
+ else
+#endif
+ counter = cur_unique_counter++;
+
+ /* The counter is two bytes for the uuencoded unique id, in network
+ * byte order.
+ */
+ new_unique_id.counter = htons(counter % APR_UINT16_MAX);
+
+ /* we'll use a temporal buffer to avoid uuencoding the possible internal
+ * paddings of the original structure */
+ x = (unsigned char *) &paddedbuf;
+ k = 0;
+ for (i = 0; i < UNIQUE_ID_REC_MAX; i++) {
+ y = ((unsigned char *) &new_unique_id) + unique_id_rec_offset[i];
+ for (j = 0; j < unique_id_rec_size[i]; j++, k++) {
+ x[k] = y[j];
+ }
+ }
+ /*
+ * We reset two more bytes just in case padding is needed for the uuencoding.
+ */
+ x[k++] = '\0';
+ x[k++] = '\0';
+
+ /* alloc str and do the uuencoding */
+ str = (char *)apr_palloc(r->pool, unique_id_rec_size_uu + 1);
+ k = 0;
+ for (i = 0; i < unique_id_rec_total_size; i += 3) {
+ y = x + i;
+ str[k++] = uuencoder[y[0] >> 2];
+ str[k++] = uuencoder[((y[0] & 0x03) << 4) | ((y[1] & 0xf0) >> 4)];
+ if (k == unique_id_rec_size_uu) break;
+ str[k++] = uuencoder[((y[1] & 0x0f) << 2) | ((y[2] & 0xc0) >> 6)];
+ if (k == unique_id_rec_size_uu) break;
+ str[k++] = uuencoder[y[2] & 0x3f];
+ }
+ str[k++] = '\0';
+
+ return str;
+}
+
+/*
+ * There are two ways the generation of a unique id can be triggered:
+ *
+ * - from the post_read_request hook which calls set_unique_id()
+ * - from error logging via the generate_log_id hook which calls
+ * generate_log_id(). This may happen before or after set_unique_id()
+ * has been called, or not at all.
+ */
+
+static int generate_log_id(const conn_rec *c, const request_rec *r,
+ const char **id)
+{
+ /* we do not care about connection ids */
+ if (r == NULL)
+ return DECLINED;
+
+ /* XXX: do we need special handling for internal redirects? */
+
+ /* if set_unique_id() has been called for this request, use it */
+ *id = apr_table_get(r->subprocess_env, "UNIQUE_ID");
+
+ if (!*id)
+ *id = gen_unique_id(r);
+ return OK;
+}
+
+static int set_unique_id(request_rec *r)
+{
+ const char *id = NULL;
+ /* copy the unique_id if this is an internal redirect (we're never
+ * actually called for sub requests, so we don't need to test for
+ * them) */
+ if (r->prev) {
+ id = apr_table_get(r->subprocess_env, "REDIRECT_UNIQUE_ID");
+ }
+
+ if (!id) {
+ /* if we have a log id, it was set by our generate_log_id() function
+ * and we should reuse the same id
+ */
+ id = r->log_id;
+ }
+
+ if (!id) {
+ id = gen_unique_id(r);
+ }
+
+ /* set the environment variable */
+ apr_table_setn(r->subprocess_env, "UNIQUE_ID", id);
+
+ return DECLINED;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_hook_post_config(unique_id_global_init, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_child_init(unique_id_child_init, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_post_read_request(set_unique_id, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_generate_log_id(generate_log_id, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(unique_id) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* dir config creater */
+ NULL, /* dir merger --- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server configs */
+ NULL, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/metadata/mod_unique_id.dep b/modules/metadata/mod_unique_id.dep
new file mode 100644
index 0000000..18c6668
--- /dev/null
+++ b/modules/metadata/mod_unique_id.dep
@@ -0,0 +1,50 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_unique_id.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_unique_id.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/metadata/mod_unique_id.dsp b/modules/metadata/mod_unique_id.dsp
new file mode 100644
index 0000000..46480a9
--- /dev/null
+++ b/modules/metadata/mod_unique_id.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_unique_id" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_unique_id - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_unique_id.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_unique_id.mak" CFG="mod_unique_id - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_unique_id - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_unique_id - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_unique_id - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_unique_id_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_unique_id.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_unique_id.so" /d LONG_NAME="unique_id_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_unique_id.so" /base:@..\..\os\win32\BaseAddr.ref,mod_unique_id.so
+# ADD LINK32 kernel32.lib ws2_32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_unique_id.so" /base:@..\..\os\win32\BaseAddr.ref,mod_unique_id.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_unique_id.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_unique_id - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_unique_id_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_unique_id.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_unique_id.so" /d LONG_NAME="unique_id_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_unique_id.so" /base:@..\..\os\win32\BaseAddr.ref,mod_unique_id.so
+# ADD LINK32 kernel32.lib ws2_32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_unique_id.so" /base:@..\..\os\win32\BaseAddr.ref,mod_unique_id.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_unique_id.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_unique_id - Win32 Release"
+# Name "mod_unique_id - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_unique_id.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/metadata/mod_unique_id.exp b/modules/metadata/mod_unique_id.exp
new file mode 100644
index 0000000..93000f1
--- /dev/null
+++ b/modules/metadata/mod_unique_id.exp
@@ -0,0 +1 @@
+unique_id_module
diff --git a/modules/metadata/mod_unique_id.mak b/modules/metadata/mod_unique_id.mak
new file mode 100644
index 0000000..b10097b
--- /dev/null
+++ b/modules/metadata/mod_unique_id.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_unique_id.dsp
+!IF "$(CFG)" == ""
+CFG=mod_unique_id - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_unique_id - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_unique_id - Win32 Release" && "$(CFG)" != "mod_unique_id - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_unique_id.mak" CFG="mod_unique_id - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_unique_id - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_unique_id - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_unique_id - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_unique_id.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_unique_id.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_unique_id.obj"
+ -@erase "$(INTDIR)\mod_unique_id.res"
+ -@erase "$(INTDIR)\mod_unique_id_src.idb"
+ -@erase "$(INTDIR)\mod_unique_id_src.pdb"
+ -@erase "$(OUTDIR)\mod_unique_id.exp"
+ -@erase "$(OUTDIR)\mod_unique_id.lib"
+ -@erase "$(OUTDIR)\mod_unique_id.pdb"
+ -@erase "$(OUTDIR)\mod_unique_id.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_unique_id_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_unique_id.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_unique_id.so" /d LONG_NAME="unique_id_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_unique_id.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_unique_id.pdb" /debug /out:"$(OUTDIR)\mod_unique_id.so" /implib:"$(OUTDIR)\mod_unique_id.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_unique_id.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_unique_id.obj" \
+ "$(INTDIR)\mod_unique_id.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_unique_id.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_unique_id.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_unique_id.so"
+ if exist .\Release\mod_unique_id.so.manifest mt.exe -manifest .\Release\mod_unique_id.so.manifest -outputresource:.\Release\mod_unique_id.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_unique_id - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_unique_id.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_unique_id.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_unique_id.obj"
+ -@erase "$(INTDIR)\mod_unique_id.res"
+ -@erase "$(INTDIR)\mod_unique_id_src.idb"
+ -@erase "$(INTDIR)\mod_unique_id_src.pdb"
+ -@erase "$(OUTDIR)\mod_unique_id.exp"
+ -@erase "$(OUTDIR)\mod_unique_id.lib"
+ -@erase "$(OUTDIR)\mod_unique_id.pdb"
+ -@erase "$(OUTDIR)\mod_unique_id.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_unique_id_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_unique_id.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_unique_id.so" /d LONG_NAME="unique_id_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_unique_id.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_unique_id.pdb" /debug /out:"$(OUTDIR)\mod_unique_id.so" /implib:"$(OUTDIR)\mod_unique_id.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_unique_id.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_unique_id.obj" \
+ "$(INTDIR)\mod_unique_id.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_unique_id.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_unique_id.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_unique_id.so"
+ if exist .\Debug\mod_unique_id.so.manifest mt.exe -manifest .\Debug\mod_unique_id.so.manifest -outputresource:.\Debug\mod_unique_id.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_unique_id.dep")
+!INCLUDE "mod_unique_id.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_unique_id.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_unique_id - Win32 Release" || "$(CFG)" == "mod_unique_id - Win32 Debug"
+
+!IF "$(CFG)" == "mod_unique_id - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\metadata"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_unique_id - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\metadata"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_unique_id - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\metadata"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_unique_id - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\metadata"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_unique_id - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\metadata"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_unique_id - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\metadata"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\metadata"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_unique_id - Win32 Release"
+
+
+"$(INTDIR)\mod_unique_id.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_unique_id.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_unique_id.so" /d LONG_NAME="unique_id_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_unique_id - Win32 Debug"
+
+
+"$(INTDIR)\mod_unique_id.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_unique_id.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_unique_id.so" /d LONG_NAME="unique_id_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_unique_id.c
+
+"$(INTDIR)\mod_unique_id.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/metadata/mod_usertrack.c b/modules/metadata/mod_usertrack.c
new file mode 100644
index 0000000..55252ec
--- /dev/null
+++ b/modules/metadata/mod_usertrack.c
@@ -0,0 +1,504 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* User Tracking Module (Was mod_cookies.c)
+ *
+ * *** IMPORTANT NOTE: This module is not designed to generate
+ * *** cryptographically secure cookies. This means you should not
+ * *** use cookies generated by this module for authentication purposes
+ *
+ * This Apache module is designed to track users paths through a site.
+ * It uses the client-side state ("Cookie") protocol developed by Netscape.
+ * It is known to work on most browsers.
+ *
+ * Each time a page is requested we look to see if the browser is sending
+ * us a Cookie: header that we previously generated.
+ *
+ * If we don't find one then the user hasn't been to this site since
+ * starting their browser or their browser doesn't support cookies. So
+ * we generate a unique Cookie for the transaction and send it back to
+ * the browser (via a "Set-Cookie" header)
+ * Future requests from the same browser should keep the same Cookie line.
+ *
+ * By matching up all the requests with the same cookie you can
+ * work out exactly what path a user took through your site. To log
+ * the cookie use the " %{Cookie}n " directive in a custom access log;
+ *
+ * Example 1 : If you currently use the standard Log file format (CLF)
+ * and use the command "TransferLog somefilename", add the line
+ * LogFormat "%h %l %u %t \"%r\" %s %b %{Cookie}n"
+ * to your config file.
+ *
+ * Example 2 : If you used to use the old "CookieLog" directive, you
+ * can emulate it by adding the following command to your config file
+ * CustomLog filename "%{Cookie}n \"%r\" %t"
+ *
+ * Mark Cox, mjc@apache.org, 6 July 95
+ *
+ * This file replaces mod_cookies.c
+ */
+
+#include "apr.h"
+#include "apr_lib.h"
+#include "apr_strings.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_request.h"
+#include "http_log.h"
+
+
+module AP_MODULE_DECLARE_DATA usertrack_module;
+
+typedef struct {
+ int always;
+ int expires;
+} cookie_log_state;
+
+typedef enum {
+ CT_UNSET,
+ CT_NETSCAPE,
+ CT_COOKIE,
+ CT_COOKIE2
+} cookie_type_e;
+
+typedef struct {
+ int enabled;
+ cookie_type_e style;
+ const char *cookie_name;
+ const char *cookie_domain;
+ char *regexp_string; /* used to compile regexp; save for debugging */
+ ap_regex_t *regexp; /* used to find usertrack cookie in cookie header */
+ int is_secure;
+ int is_httponly;
+ const char *samesite;
+} cookie_dir_rec;
+
+/* Make Cookie: Now we have to generate something that is going to be
+ * pretty unique. We can base it on the pid, time, hostip */
+
+#define COOKIE_NAME "Apache"
+
+static void make_cookie(request_rec *r)
+{
+ cookie_log_state *cls = ap_get_module_config(r->server->module_config,
+ &usertrack_module);
+ char cookiebuf[2 * (sizeof(apr_uint64_t) + sizeof(int)) + 2];
+ unsigned int random;
+ apr_time_t now = r->request_time ? r->request_time : apr_time_now();
+ char *new_cookie;
+ cookie_dir_rec *dcfg;
+
+ ap_random_insecure_bytes(&random, sizeof(random));
+ apr_snprintf(cookiebuf, sizeof(cookiebuf), "%x.%" APR_UINT64_T_HEX_FMT,
+ random, (apr_uint64_t)now);
+ dcfg = ap_get_module_config(r->per_dir_config, &usertrack_module);
+ if (cls->expires) {
+
+ /* Cookie with date; as strftime '%a, %d-%h-%y %H:%M:%S GMT' */
+ new_cookie = apr_psprintf(r->pool, "%s=%s; path=/",
+ dcfg->cookie_name, cookiebuf);
+
+ if ((dcfg->style == CT_UNSET) || (dcfg->style == CT_NETSCAPE)) {
+ apr_time_exp_t tms;
+ apr_time_exp_gmt(&tms, r->request_time
+ + apr_time_from_sec(cls->expires));
+ new_cookie = apr_psprintf(r->pool,
+ "%s; expires=%s, "
+ "%.2d-%s-%.2d %.2d:%.2d:%.2d GMT",
+ new_cookie, apr_day_snames[tms.tm_wday],
+ tms.tm_mday,
+ apr_month_snames[tms.tm_mon],
+ tms.tm_year % 100,
+ tms.tm_hour, tms.tm_min, tms.tm_sec);
+ }
+ else {
+ new_cookie = apr_psprintf(r->pool, "%s; max-age=%d",
+ new_cookie, cls->expires);
+ }
+ }
+ else {
+ new_cookie = apr_psprintf(r->pool, "%s=%s; path=/",
+ dcfg->cookie_name, cookiebuf);
+ }
+ if (dcfg->cookie_domain != NULL) {
+ new_cookie = apr_pstrcat(r->pool, new_cookie, "; domain=",
+ dcfg->cookie_domain,
+ (dcfg->style == CT_COOKIE2
+ ? "; version=1"
+ : ""),
+ NULL);
+ }
+ if (dcfg->samesite != NULL) {
+ new_cookie = apr_pstrcat(r->pool, new_cookie, "; ",
+ dcfg->samesite,
+ NULL);
+ }
+ if (dcfg->is_secure) {
+ new_cookie = apr_pstrcat(r->pool, new_cookie, "; Secure",
+ NULL);
+ }
+ if (dcfg->is_httponly) {
+ new_cookie = apr_pstrcat(r->pool, new_cookie, "; HttpOnly",
+ NULL);
+ }
+
+
+
+ apr_table_addn(r->err_headers_out,
+ (dcfg->style == CT_COOKIE2 ? "Set-Cookie2" : "Set-Cookie"),
+ new_cookie);
+ apr_table_setn(r->notes, "cookie", apr_pstrdup(r->pool, cookiebuf)); /* log first time */
+}
+
+/* dcfg->regexp is "^cookie_name=([^;]+)|;[ \t]+cookie_name=([^;]+)",
+ * which has three subexpressions, $0..$2 */
+#define NUM_SUBS 3
+
+static void set_and_comp_regexp(cookie_dir_rec *dcfg,
+ apr_pool_t *p,
+ const char *cookie_name)
+{
+ int danger_chars = 0;
+ const char *sp = cookie_name;
+
+ /* The goal is to end up with this regexp,
+ * ^cookie_name=([^;,]+)|[;,][ \t]+cookie_name=([^;,]+)
+ * with cookie_name obviously substituted either
+ * with the real cookie name set by the user in httpd.conf, or with the
+ * default COOKIE_NAME. */
+
+ /* Anyway, we need to escape the cookie_name before pasting it
+ * into the regex
+ */
+ while (*sp) {
+ if (!apr_isalnum(*sp)) {
+ ++danger_chars;
+ }
+ ++sp;
+ }
+
+ if (danger_chars) {
+ char *cp;
+ cp = apr_palloc(p, sp - cookie_name + danger_chars + 1); /* 1 == \0 */
+ sp = cookie_name;
+ cookie_name = cp;
+ while (*sp) {
+ if (!apr_isalnum(*sp)) {
+ *cp++ = '\\';
+ }
+ *cp++ = *sp++;
+ }
+ *cp = '\0';
+ }
+
+ dcfg->regexp_string = apr_pstrcat(p, "^",
+ cookie_name,
+ "=([^;,]+)|[;,][ \t]*",
+ cookie_name,
+ "=([^;,]+)", NULL);
+
+ dcfg->regexp = ap_pregcomp(p, dcfg->regexp_string, AP_REG_EXTENDED);
+ ap_assert(dcfg->regexp != NULL);
+}
+
+static int spot_cookie(request_rec *r)
+{
+ cookie_dir_rec *dcfg = ap_get_module_config(r->per_dir_config,
+ &usertrack_module);
+ const char *cookie_header;
+ ap_regmatch_t regm[NUM_SUBS];
+
+ /* Do not run in subrequests */
+ if (!dcfg->enabled || r->main) {
+ return DECLINED;
+ }
+
+ if ((cookie_header = apr_table_get(r->headers_in, "Cookie"))) {
+ if (!ap_regexec(dcfg->regexp, cookie_header, NUM_SUBS, regm, 0)) {
+ char *cookieval = NULL;
+ int err = 0;
+ /* Our regexp,
+ * ^cookie_name=([^;]+)|;[ \t]+cookie_name=([^;]+)
+ * only allows for $1 or $2 to be available. ($0 is always
+ * filled with the entire matched expression, not just
+ * the part in parentheses.) So just check for either one
+ * and assign to cookieval if present. */
+ if (regm[1].rm_so != -1) {
+ cookieval = ap_pregsub(r->pool, "$1", cookie_header,
+ NUM_SUBS, regm);
+ if (cookieval == NULL)
+ err = 1;
+ }
+ if (regm[2].rm_so != -1) {
+ cookieval = ap_pregsub(r->pool, "$2", cookie_header,
+ NUM_SUBS, regm);
+ if (cookieval == NULL)
+ err = 1;
+ }
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, APLOGNO(01499)
+ "Failed to extract cookie value (out of mem?)");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ /* Set the cookie in a note, for logging */
+ apr_table_setn(r->notes, "cookie", cookieval);
+
+ return DECLINED; /* There's already a cookie, no new one */
+ }
+ }
+ make_cookie(r);
+ return OK; /* We set our cookie */
+}
+
+static void *make_cookie_log_state(apr_pool_t *p, server_rec *s)
+{
+ cookie_log_state *cls =
+ (cookie_log_state *) apr_palloc(p, sizeof(cookie_log_state));
+
+ cls->expires = 0;
+
+ return (void *) cls;
+}
+
+static void *make_cookie_dir(apr_pool_t *p, char *d)
+{
+ cookie_dir_rec *dcfg;
+
+ dcfg = (cookie_dir_rec *) apr_pcalloc(p, sizeof(cookie_dir_rec));
+ dcfg->cookie_name = COOKIE_NAME;
+ dcfg->style = CT_UNSET;
+ /* calloc'ed to disabled: enabled, cookie_domain, samesite, is_secure,
+ * is_httponly */
+
+ /* In case the user does not use the CookieName directive,
+ * we need to compile the regexp for the default cookie name. */
+ set_and_comp_regexp(dcfg, p, COOKIE_NAME);
+
+ return dcfg;
+}
+
+static const char *set_cookie_exp(cmd_parms *parms, void *dummy,
+ const char *arg)
+{
+ cookie_log_state *cls;
+ time_t factor, modifier = 0;
+ time_t num = 0;
+ char *word;
+
+ cls = ap_get_module_config(parms->server->module_config,
+ &usertrack_module);
+ /* The simple case first - all numbers (we assume) */
+ if (apr_isdigit(arg[0]) && apr_isdigit(arg[strlen(arg) - 1])) {
+ cls->expires = atol(arg);
+ return NULL;
+ }
+
+ /*
+ * The harder case - stolen from mod_expires
+ *
+ * CookieExpires "[plus] {<num> <type>}*"
+ */
+
+ word = ap_getword_conf(parms->temp_pool, &arg);
+ if (!strncasecmp(word, "plus", 1)) {
+ word = ap_getword_conf(parms->temp_pool, &arg);
+ };
+
+ /* {<num> <type>}* */
+ while (word[0]) {
+ /* <num> */
+ if (apr_isdigit(word[0]))
+ num = atoi(word);
+ else
+ return "bad expires code, numeric value expected.";
+
+ /* <type> */
+ word = ap_getword_conf(parms->temp_pool, &arg);
+ if (!word[0])
+ return "bad expires code, missing <type>";
+
+ if (!strncasecmp(word, "years", 1))
+ factor = 60 * 60 * 24 * 365;
+ else if (!strncasecmp(word, "months", 2))
+ factor = 60 * 60 * 24 * 30;
+ else if (!strncasecmp(word, "weeks", 1))
+ factor = 60 * 60 * 24 * 7;
+ else if (!strncasecmp(word, "days", 1))
+ factor = 60 * 60 * 24;
+ else if (!strncasecmp(word, "hours", 1))
+ factor = 60 * 60;
+ else if (!strncasecmp(word, "minutes", 2))
+ factor = 60;
+ else if (!strncasecmp(word, "seconds", 1))
+ factor = 1;
+ else
+ return "bad expires code, unrecognized type";
+
+ modifier = modifier + factor * num;
+
+ /* next <num> */
+ word = ap_getword_conf(parms->temp_pool, &arg);
+ }
+
+ cls->expires = modifier;
+
+ return NULL;
+}
+
+static const char *set_cookie_name(cmd_parms *cmd, void *mconfig,
+ const char *name)
+{
+ cookie_dir_rec *dcfg = (cookie_dir_rec *) mconfig;
+
+ dcfg->cookie_name = name;
+
+ set_and_comp_regexp(dcfg, cmd->pool, name);
+
+ if (dcfg->regexp == NULL) {
+ return "Regular expression could not be compiled.";
+ }
+ if (dcfg->regexp->re_nsub + 1 != NUM_SUBS) {
+ return apr_pstrcat(cmd->pool, "Invalid cookie name \"",
+ name, "\"", NULL);
+ }
+
+ return NULL;
+}
+
+/*
+ * Set the value for the 'Domain=' attribute.
+ */
+static const char *set_cookie_domain(cmd_parms *cmd, void *mconfig,
+ const char *name)
+{
+ cookie_dir_rec *dcfg;
+
+ dcfg = (cookie_dir_rec *) mconfig;
+
+ /*
+ * Apply the restrictions on cookie domain attributes.
+ */
+ if (!name[0]) {
+ return "CookieDomain values may not be null";
+ }
+ if (name[0] != '.') {
+ return "CookieDomain values must begin with a dot";
+ }
+ if (ap_strchr_c(&name[1], '.') == NULL) {
+ return "CookieDomain values must contain at least one embedded dot";
+ }
+
+ dcfg->cookie_domain = name;
+ return NULL;
+}
+
+/*
+ * Make a note of the cookie style we should use.
+ */
+static const char *set_cookie_style(cmd_parms *cmd, void *mconfig,
+ const char *name)
+{
+ cookie_dir_rec *dcfg;
+
+ dcfg = (cookie_dir_rec *) mconfig;
+
+ if (strcasecmp(name, "Netscape") == 0) {
+ dcfg->style = CT_NETSCAPE;
+ }
+ else if ((strcasecmp(name, "Cookie") == 0)
+ || (strcasecmp(name, "RFC2109") == 0)) {
+ dcfg->style = CT_COOKIE;
+ }
+ else if ((strcasecmp(name, "Cookie2") == 0)
+ || (strcasecmp(name, "RFC2965") == 0)) {
+ dcfg->style = CT_COOKIE2;
+ }
+ else {
+ return apr_psprintf(cmd->pool, "Invalid %s keyword: '%s'",
+ cmd->cmd->name, name);
+ }
+
+ return NULL;
+}
+
+/*
+ * SameSite enabled disabled
+ */
+
+static const char *set_samesite_value(cmd_parms *cmd, void *mconfig,
+ const char *name)
+{
+ cookie_dir_rec *dcfg;
+
+ dcfg = (cookie_dir_rec *) mconfig;
+
+ if (strcasecmp(name, "strict") == 0) {
+ dcfg->samesite = "SameSite=Strict";
+ } else if (strcasecmp(name, "lax") == 0) {
+ dcfg->samesite = "SameSite=Lax";
+ } else if (strcasecmp(name, "none") == 0) {
+ dcfg->samesite = "SameSite=None";
+ } else {
+ return "CookieSameSite accepts 'Strict', 'Lax', or 'None'";
+ }
+
+
+ return NULL;
+}
+
+static const command_rec cookie_log_cmds[] = {
+ AP_INIT_TAKE1("CookieExpires", set_cookie_exp, NULL, OR_FILEINFO,
+ "an expiry date code"),
+ AP_INIT_TAKE1("CookieDomain", set_cookie_domain, NULL, OR_FILEINFO,
+ "domain to which this cookie applies"),
+ AP_INIT_TAKE1("CookieStyle", set_cookie_style, NULL, OR_FILEINFO,
+ "'Netscape', 'Cookie' (RFC2109), or 'Cookie2' (RFC2965)"),
+ AP_INIT_FLAG("CookieTracking", ap_set_flag_slot,
+ (void *)APR_OFFSETOF(cookie_dir_rec, enabled), OR_FILEINFO,
+ "whether or not to enable cookies"),
+ AP_INIT_TAKE1("CookieName", set_cookie_name, NULL, OR_FILEINFO,
+ "name of the tracking cookie"),
+ AP_INIT_TAKE1("CookieSameSite", set_samesite_value, NULL, OR_FILEINFO,
+ "SameSite setting"),
+ AP_INIT_FLAG("CookieSecure", ap_set_flag_slot,
+ (void *)APR_OFFSETOF(cookie_dir_rec, is_secure), OR_FILEINFO,
+ "is cookie secure"),
+ AP_INIT_FLAG("CookieHttpOnly", ap_set_flag_slot,
+ (void *)APR_OFFSETOF(cookie_dir_rec, is_httponly),OR_FILEINFO,
+ "is cookie http only"),
+
+ {NULL}
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_hook_fixups(spot_cookie,NULL,NULL,APR_HOOK_REALLY_FIRST);
+}
+
+AP_DECLARE_MODULE(usertrack) = {
+ STANDARD20_MODULE_STUFF,
+ make_cookie_dir, /* dir config creater */
+ NULL, /* dir merger --- default is to override */
+ make_cookie_log_state, /* server config */
+ NULL, /* merge server configs */
+ cookie_log_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/metadata/mod_usertrack.dep b/modules/metadata/mod_usertrack.dep
new file mode 100644
index 0000000..4b31cb3
--- /dev/null
+++ b/modules/metadata/mod_usertrack.dep
@@ -0,0 +1,51 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_usertrack.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_usertrack.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/metadata/mod_usertrack.dsp b/modules/metadata/mod_usertrack.dsp
new file mode 100644
index 0000000..5d1fb82
--- /dev/null
+++ b/modules/metadata/mod_usertrack.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_usertrack" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_usertrack - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_usertrack.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_usertrack.mak" CFG="mod_usertrack - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_usertrack - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_usertrack - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_usertrack - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_usertrack_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_usertrack.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_usertrack.so" /d LONG_NAME="usertrack_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_usertrack.so" /base:@..\..\os\win32\BaseAddr.ref,mod_usertrack.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_usertrack.so" /base:@..\..\os\win32\BaseAddr.ref,mod_usertrack.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_usertrack.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_usertrack - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_usertrack_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_usertrack.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_usertrack.so" /d LONG_NAME="usertrack_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_usertrack.so" /base:@..\..\os\win32\BaseAddr.ref,mod_usertrack.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_usertrack.so" /base:@..\..\os\win32\BaseAddr.ref,mod_usertrack.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_usertrack.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_usertrack - Win32 Release"
+# Name "mod_usertrack - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_usertrack.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/metadata/mod_usertrack.exp b/modules/metadata/mod_usertrack.exp
new file mode 100644
index 0000000..234a5f7
--- /dev/null
+++ b/modules/metadata/mod_usertrack.exp
@@ -0,0 +1 @@
+usertrack_module
diff --git a/modules/metadata/mod_usertrack.mak b/modules/metadata/mod_usertrack.mak
new file mode 100644
index 0000000..0912540
--- /dev/null
+++ b/modules/metadata/mod_usertrack.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_usertrack.dsp
+!IF "$(CFG)" == ""
+CFG=mod_usertrack - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_usertrack - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_usertrack - Win32 Release" && "$(CFG)" != "mod_usertrack - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_usertrack.mak" CFG="mod_usertrack - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_usertrack - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_usertrack - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_usertrack - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_usertrack.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_usertrack.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_usertrack.obj"
+ -@erase "$(INTDIR)\mod_usertrack.res"
+ -@erase "$(INTDIR)\mod_usertrack_src.idb"
+ -@erase "$(INTDIR)\mod_usertrack_src.pdb"
+ -@erase "$(OUTDIR)\mod_usertrack.exp"
+ -@erase "$(OUTDIR)\mod_usertrack.lib"
+ -@erase "$(OUTDIR)\mod_usertrack.pdb"
+ -@erase "$(OUTDIR)\mod_usertrack.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_usertrack_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_usertrack.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_usertrack.so" /d LONG_NAME="usertrack_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_usertrack.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_usertrack.pdb" /debug /out:"$(OUTDIR)\mod_usertrack.so" /implib:"$(OUTDIR)\mod_usertrack.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_usertrack.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_usertrack.obj" \
+ "$(INTDIR)\mod_usertrack.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_usertrack.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_usertrack.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_usertrack.so"
+ if exist .\Release\mod_usertrack.so.manifest mt.exe -manifest .\Release\mod_usertrack.so.manifest -outputresource:.\Release\mod_usertrack.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_usertrack - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_usertrack.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_usertrack.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_usertrack.obj"
+ -@erase "$(INTDIR)\mod_usertrack.res"
+ -@erase "$(INTDIR)\mod_usertrack_src.idb"
+ -@erase "$(INTDIR)\mod_usertrack_src.pdb"
+ -@erase "$(OUTDIR)\mod_usertrack.exp"
+ -@erase "$(OUTDIR)\mod_usertrack.lib"
+ -@erase "$(OUTDIR)\mod_usertrack.pdb"
+ -@erase "$(OUTDIR)\mod_usertrack.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_usertrack_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_usertrack.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_usertrack.so" /d LONG_NAME="usertrack_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_usertrack.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_usertrack.pdb" /debug /out:"$(OUTDIR)\mod_usertrack.so" /implib:"$(OUTDIR)\mod_usertrack.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_usertrack.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_usertrack.obj" \
+ "$(INTDIR)\mod_usertrack.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_usertrack.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_usertrack.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_usertrack.so"
+ if exist .\Debug\mod_usertrack.so.manifest mt.exe -manifest .\Debug\mod_usertrack.so.manifest -outputresource:.\Debug\mod_usertrack.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_usertrack.dep")
+!INCLUDE "mod_usertrack.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_usertrack.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_usertrack - Win32 Release" || "$(CFG)" == "mod_usertrack - Win32 Debug"
+
+!IF "$(CFG)" == "mod_usertrack - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\metadata"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_usertrack - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\metadata"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_usertrack - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\metadata"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_usertrack - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\metadata"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_usertrack - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\metadata"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_usertrack - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\metadata"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\metadata"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_usertrack - Win32 Release"
+
+
+"$(INTDIR)\mod_usertrack.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_usertrack.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_usertrack.so" /d LONG_NAME="usertrack_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_usertrack - Win32 Debug"
+
+
+"$(INTDIR)\mod_usertrack.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_usertrack.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_usertrack.so" /d LONG_NAME="usertrack_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_usertrack.c
+
+"$(INTDIR)\mod_usertrack.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/metadata/mod_version.c b/modules/metadata/mod_version.c
new file mode 100644
index 0000000..688cd8d
--- /dev/null
+++ b/modules/metadata/mod_version.c
@@ -0,0 +1,313 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * mod_version.c
+ * Allow conditional configuration depending on the httpd version
+ *
+ * André Malo (nd/perlig.de), January 2004
+ *
+ * Some stuff coded here is heavily based on the core <IfModule>
+ * containers.
+ *
+ * The module makes the following confgurations possible:
+ *
+ * <IfVersion op major.minor.patch>
+ * # conditional config here ...
+ *</IfVersion>
+ *
+ * where "op" is one of:
+ * = / == equal
+ * > greater than
+ * >= greater or equal
+ * < less than
+ * <= less or equal
+ *
+ * If minor version and patch level are omitted they are assumed to be 0.
+ *
+ * Alternatively you can match the whole version (including some vendor-added
+ * string of the CORE version, see ap_release.h) against a regular expression:
+ *
+ * <IfVersion op regex>
+ * # conditional config here ...
+ *</IfVersion>
+ *
+ * where "op" is one of:
+ * = / == match; regex must be surrounded by slashes
+ * ~ match; regex MAY NOT be surrounded by slashes
+ *
+ * Note that all operators may be preceded by an exclamation mark
+ * (without spaces) in order to reverse their meaning.
+ *
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+
+
+/* module structure */
+module AP_MODULE_DECLARE_DATA version_module;
+
+/* queried httpd version */
+static ap_version_t httpd_version;
+
+
+/*
+ * compare the supplied version with the core one
+ */
+static int compare_version(char *version_string, const char **error)
+{
+ char *p = version_string, *ep;
+ int version[3] = {0, 0, 0};
+ int c = 0;
+
+ *error = "Version appears to be invalid. It must have the format "
+ "major[.minor[.patch]] where major, minor and patch are "
+ "numbers.";
+
+ if (!apr_isdigit(*p)) {
+ return 0;
+ }
+
+ /* parse supplied version */
+ ep = version_string + strlen(version_string);
+ while (p <= ep && c < 3) {
+ if (*p == '.') {
+ *p = '\0';
+ }
+
+ if (!*p) {
+ version[c++] = atoi(version_string);
+ version_string = ++p;
+ continue;
+ }
+
+ if (!apr_isdigit(*p)) {
+ break;
+ }
+
+ ++p;
+ }
+
+ if (p < ep) { /* syntax error */
+ return 0;
+ }
+
+ *error = NULL;
+
+ if (httpd_version.major > version[0]) {
+ return 1;
+ }
+ else if (httpd_version.major < version[0]) {
+ return -1;
+ }
+ else if (httpd_version.minor > version[1]) {
+ return 1;
+ }
+ else if (httpd_version.minor < version[1]) {
+ return -1;
+ }
+ else if (httpd_version.patch > version[2]) {
+ return 1;
+ }
+ else if (httpd_version.patch < version[2]) {
+ return -1;
+ }
+
+ /* seems to be the same */
+ return 0;
+}
+
+/*
+ * match version against a regular expression
+ */
+static int match_version(apr_pool_t *pool, char *version_string,
+ const char **error)
+{
+ ap_regex_t *compiled;
+ const char *to_match;
+ int rc;
+
+ compiled = ap_pregcomp(pool, version_string, AP_REG_EXTENDED);
+ if (!compiled) {
+ *error = "Unable to compile regular expression";
+ return 0;
+ }
+
+ *error = NULL;
+
+ to_match = apr_psprintf(pool, "%d.%d.%d%s",
+ httpd_version.major,
+ httpd_version.minor,
+ httpd_version.patch,
+ httpd_version.add_string);
+
+ rc = !ap_regexec(compiled, to_match, 0, NULL, 0);
+
+ ap_pregfree(pool, compiled);
+ return rc;
+}
+
+/*
+ * Implements the <IfVersion> container
+ */
+static const char *start_ifversion(cmd_parms *cmd, void *mconfig,
+ const char *arg1, const char *arg2,
+ const char *arg3)
+{
+ const char *endp;
+ int reverse = 0, done = 0, match = 0, compare;
+ const char *p, *error;
+ char c;
+
+ /* supplying one argument is possible, we assume an equality check then */
+ if (!arg2) {
+ arg2 = arg1;
+ arg1 = "=";
+ }
+
+ /* surrounding quotes without operator */
+ if (!arg3 && *arg2 == '>' && !arg2[1]) {
+ arg3 = ">";
+ arg2 = arg1;
+ arg1 = "=";
+ }
+
+ /* the third argument makes version surrounding quotes plus operator
+ * possible.
+ */
+ endp = arg2 + strlen(arg2);
+ if ( endp == arg2
+ || (!(arg3 && *arg3 == '>' && !arg3[1]) && *--endp != '>')) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ "> directive missing closing '>'", NULL);
+ }
+
+ p = arg1;
+ if (*p == '!') {
+ reverse = 1;
+ if (p[1]) {
+ ++p;
+ }
+ }
+
+ c = *p++;
+ if (!*p || (*p == '=' && !p[1] && c != '~')) {
+ if (!httpd_version.major) {
+ ap_get_server_revision(&httpd_version);
+ }
+
+ done = 1;
+ switch (c) {
+ case '=':
+ /* normal comparison */
+ if (*arg2 != '/') {
+ compare = compare_version(apr_pstrmemdup(cmd->temp_pool, arg2,
+ endp-arg2),
+ &error);
+ if (error) {
+ return error;
+ }
+
+ match = !compare;
+ break;
+ }
+
+ /* regexp otherwise */
+ if (endp == ++arg2 || *--endp != '/') {
+ return "Missing delimiting / of regular expression.";
+ }
+
+ case '~':
+ /* regular expression */
+ match = match_version(cmd->temp_pool,
+ apr_pstrmemdup(cmd->temp_pool, arg2,
+ endp-arg2),
+ &error);
+ if (error) {
+ return error;
+ }
+ break;
+
+ case '<':
+ compare = compare_version(apr_pstrmemdup(cmd->temp_pool, arg2,
+ endp-arg2),
+ &error);
+ if (error) {
+ return error;
+ }
+
+ match = ((-1 == compare) || (*p && !compare));
+ break;
+
+ case '>':
+ compare = compare_version(apr_pstrmemdup(cmd->temp_pool, arg2,
+ endp-arg2),
+ &error);
+ if (error) {
+ return error;
+ }
+
+ match = ((1 == compare) || (*p && !compare));
+ break;
+
+ default:
+ done = 0;
+ break;
+ }
+ }
+
+ if (!done) {
+ return apr_pstrcat(cmd->pool, "unrecognized operator '", arg1, "'",
+ NULL);
+ }
+
+ if ((!reverse && match) || (reverse && !match)) {
+ ap_directive_t *parent = NULL;
+ ap_directive_t *current = NULL;
+ const char *retval;
+
+ retval = ap_build_cont_config(cmd->pool, cmd->temp_pool, cmd,
+ &current, &parent, "<IfVersion");
+ *(ap_directive_t **)mconfig = current;
+ return retval;
+ }
+
+ *(ap_directive_t **)mconfig = NULL;
+ return ap_soak_end_container(cmd, "<IfVersion");
+}
+
+static const command_rec version_cmds[] = {
+ AP_INIT_TAKE123("<IfVersion", start_ifversion, NULL, EXEC_ON_READ | OR_ALL,
+ "a comparison operator, a version (and a delimiter)"),
+ { NULL }
+};
+
+AP_DECLARE_MODULE(version) =
+{
+ STANDARD20_MODULE_STUFF,
+ NULL, /* dir config creater */
+ NULL, /* dir merger --- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server configs */
+ version_cmds, /* command apr_table_t */
+ NULL, /* register hooks */
+};
diff --git a/modules/metadata/mod_version.dep b/modules/metadata/mod_version.dep
new file mode 100644
index 0000000..3661364
--- /dev/null
+++ b/modules/metadata/mod_version.dep
@@ -0,0 +1,45 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_version.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_version.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/metadata/mod_version.dsp b/modules/metadata/mod_version.dsp
new file mode 100644
index 0000000..907ea73
--- /dev/null
+++ b/modules/metadata/mod_version.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_version" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_version - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_version.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_version.mak" CFG="mod_version - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_version - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_version - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_version - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_version_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_version.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_version.so" /d LONG_NAME="version_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_version.so" /base:@..\..\os\win32\BaseAddr.ref,mod_version.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_version.so" /base:@..\..\os\win32\BaseAddr.ref,mod_version.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_version.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_version - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_version_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_version.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_version.so" /d LONG_NAME="version_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_version.so" /base:@..\..\os\win32\BaseAddr.ref,mod_version.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_version.so" /base:@..\..\os\win32\BaseAddr.ref,mod_version.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_version.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_version - Win32 Release"
+# Name "mod_version - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_version.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/metadata/mod_version.exp b/modules/metadata/mod_version.exp
new file mode 100644
index 0000000..3dce845
--- /dev/null
+++ b/modules/metadata/mod_version.exp
@@ -0,0 +1 @@
+version_module
diff --git a/modules/metadata/mod_version.mak b/modules/metadata/mod_version.mak
new file mode 100644
index 0000000..a723251
--- /dev/null
+++ b/modules/metadata/mod_version.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_version.dsp
+!IF "$(CFG)" == ""
+CFG=mod_version - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_version - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_version - Win32 Release" && "$(CFG)" != "mod_version - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_version.mak" CFG="mod_version - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_version - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_version - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_version - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_version.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_version.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_version.obj"
+ -@erase "$(INTDIR)\mod_version.res"
+ -@erase "$(INTDIR)\mod_version_src.idb"
+ -@erase "$(INTDIR)\mod_version_src.pdb"
+ -@erase "$(OUTDIR)\mod_version.exp"
+ -@erase "$(OUTDIR)\mod_version.lib"
+ -@erase "$(OUTDIR)\mod_version.pdb"
+ -@erase "$(OUTDIR)\mod_version.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_version_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_version.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_version.so" /d LONG_NAME="version_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_version.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_version.pdb" /debug /out:"$(OUTDIR)\mod_version.so" /implib:"$(OUTDIR)\mod_version.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_version.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_version.obj" \
+ "$(INTDIR)\mod_version.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_version.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_version.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_version.so"
+ if exist .\Release\mod_version.so.manifest mt.exe -manifest .\Release\mod_version.so.manifest -outputresource:.\Release\mod_version.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_version - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_version.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_version.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_version.obj"
+ -@erase "$(INTDIR)\mod_version.res"
+ -@erase "$(INTDIR)\mod_version_src.idb"
+ -@erase "$(INTDIR)\mod_version_src.pdb"
+ -@erase "$(OUTDIR)\mod_version.exp"
+ -@erase "$(OUTDIR)\mod_version.lib"
+ -@erase "$(OUTDIR)\mod_version.pdb"
+ -@erase "$(OUTDIR)\mod_version.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_version_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_version.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_version.so" /d LONG_NAME="version_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_version.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_version.pdb" /debug /out:"$(OUTDIR)\mod_version.so" /implib:"$(OUTDIR)\mod_version.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_version.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_version.obj" \
+ "$(INTDIR)\mod_version.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_version.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_version.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_version.so"
+ if exist .\Debug\mod_version.so.manifest mt.exe -manifest .\Debug\mod_version.so.manifest -outputresource:.\Debug\mod_version.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_version.dep")
+!INCLUDE "mod_version.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_version.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_version - Win32 Release" || "$(CFG)" == "mod_version - Win32 Debug"
+
+!IF "$(CFG)" == "mod_version - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\metadata"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_version - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\metadata"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_version - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\metadata"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_version - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\metadata"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\metadata"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_version - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\metadata"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\metadata"
+
+!ELSEIF "$(CFG)" == "mod_version - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\metadata"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\metadata"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_version - Win32 Release"
+
+
+"$(INTDIR)\mod_version.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_version.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_version.so" /d LONG_NAME="version_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_version - Win32 Debug"
+
+
+"$(INTDIR)\mod_version.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_version.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_version.so" /d LONG_NAME="version_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_version.c
+
+"$(INTDIR)\mod_version.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/proxy/.indent.pro b/modules/proxy/.indent.pro
new file mode 100644
index 0000000..e2cd357
--- /dev/null
+++ b/modules/proxy/.indent.pro
@@ -0,0 +1,58 @@
+-i4 -npsl -di0 -br -nce -d0 -cli0 -npcs -nfc1
+-TBUFF
+-TFILE
+-TTRANS
+-TUINT4
+-T_trans
+-Tallow_options_t
+-Tapache_sfio
+-Tarray_header
+-Tbool_int
+-Tapr_bucket_brigade
+-Tapr_pool_t
+-Tap_filter_t
+-Tbuf_area
+-Tbuff_struct
+-Tbuffy
+-Tcmd_how
+-Tcmd_parms
+-Tcommand_rec
+-Tcommand_struct
+-Tconn_rec
+-Tcore_dir_config
+-Tcore_server_config
+-Tdir_maker_func
+-Tevent
+-Tglobals_s
+-Thandler_func
+-Thandler_rec
+-Tjoblist_s
+-Tlisten_rec
+-Tmerger_func
+-Tmode_t
+-Tmodule
+-Tmodule_struct
+-Tmutex
+-Tn_long
+-Tother_child_rec
+-Toverrides_t
+-Tparent_score
+-Tpid_t
+-Tpiped_log
+-Tpool
+-Trequest_rec
+-Trequire_line
+-Trlim_t
+-Tscoreboard
+-Tsemaphore
+-Tserver_addr_rec
+-Tserver_rec
+-Tserver_rec_chain
+-Tshort_score
+-Ttable
+-Ttable_entry
+-Tthread
+-Tu_wide_int
+-Tvtime_t
+-Twide_int
+-Tproxy_server_conf
diff --git a/modules/proxy/CHANGES b/modules/proxy/CHANGES
new file mode 100644
index 0000000..b8e03a1
--- /dev/null
+++ b/modules/proxy/CHANGES
@@ -0,0 +1,223 @@
+******************************************
+* PLEASE NOTE: Now that development for *
+* mod_proxy has been folded back into *
+* the httpd-2.1 tree, this file has *
+* been deprecated. Proxy changes should *
+* be noted in httpd-2.x's CHANGES file. *
+* This file exists for historical *
+* purposes. *
+******************************************
+
+mod_proxy changes for httpd 2.0.29-dev
+ *) don't do keepalives for sub-requests. [Ian Holsman]
+
+ *) fix up proxypass handling [Ian Holsman]
+
+ *) don't send If-Modified-Since, Cache-Control, or If-None-Match on
+ a subrequest [Ian Holsman]
+
+mod_proxy changes for httpd 2.0.26-dev
+ *) Add New option 'HTTPProxyOverrideReturnedErrors'. By Turning the
+ Flag on, you will mask the error pages returned by the proxied
+ server, and will it will be handled as if your server generated
+ the error. This change was put in so that a 404 on a included
+ r-proxied component will act in the same manner as a 404 on a
+ included file. [Ian Holsman <ianh@cnet.com>]
+
+mod_proxy changes for httpd 2.0.25-dev
+
+ *) Split proxy: space using <Proxy[Match] > directive blocks from
+ the <Directory[Match] > and <Files[Match] > blocks. Mod_proxy
+ now bypasses the directory and files testing phase (and skips
+ the http TRACE default handler on it's own, as well). Note that
+ <Location > blocks continue to be processed for proxy: requests.
+ [William Rowe <wrowe@covalent.net>]
+
+ *) apr_uri type/function namespace changes in apr_uri functions
+ [Doug MacEachern <dougm@covalent.net>]
+
+mod_proxy changes for httpd 2.0.23-dev
+
+ *) break the proxy_http_handler into multiple smaller functions.
+ [John Barbee <barbee@veribox.net>]
+
+ *) Fix the proxy when the origin server sends back a 100
+ Continue response. [John Barbee <barbee@veribox.net>]
+
+ *) Change 'readbytes' from apr_size_t to apr_off_t due to change
+ in ap_get_brigade's parameters [John Barbee <barbee@veribox.net>]
+
+mod_proxy changes for httpd 2.0.20-dev
+ *) Timeout added for backend connections.
+ [Victor Orlikowski <v.j.orlikowski@gte.net>]
+
+ *) Fix abort code path in proxy_http.c, similar to FTP fix.
+ [Chuck Murcko <chuck@topsail.org>]
+
+ *) Fix FTP ABOR command execution path.
+ [Victor Orlikowski <v.j.orlikowski@gte.net>]
+
+ *) FTP return code variable cleanup; fixed problem in login
+ [Chuck Murcko <chuck@topsail.org>]
+
+ *) Get PORT working again in the ftp proxy.
+ [Victor Orlikowski <v.j.orlikowski@gte.net>]
+
+ *) Return result code check for FTP QUIT, after fixing
+ problems with passive connection handling.
+ [Victor Orlikowski <v.j.orlikowski@gte.net>]
+
+ *) Reorganize ap_proxy_string_read() internally to not process eos
+ buckets.
+ [Chuck Murcko <chuck@topsail.org>]
+ [Victor Orlikowski <v.j.orlikowski@gte.net>]
+
+ *) Remove result code check for FTP QUIT command. Some servers send
+ nothing at all back in response to QUIT.
+ [Chuck Murcko <chuck@topsail.org>]
+ [Victor Orlikowski <v.j.orlikowski@gte.net>]
+
+mod_proxy changes for httpd 2.0.19
+
+ *) Reverse previous patch since the core reverted.
+ [Chuck Murcko <chuck@topsail.org>]
+
+ *) Remove indirection on number of bytes to read for input filters.
+ [Chuck Murcko <chuck@topsail.org>]
+
+ *) Fixed a problem with directory listing corruption in the
+ PROXY_DIR filter.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) mod_proxy and the proxy submodules now build properly as DSOs.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Stopped the HTTP proxy from trying to read entity bodies when there
+ wasn't one (response was 1xx, 204, 205 or 304).
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Made sure dates were canonicalised correctly when passed to the client
+ browser through the HTTP proxy.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Split each individual proxy protocol into separate modules.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Added Max-Forwards support for all request types so as to prevent
+ loops.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Fix warnings about byte count type on Darwin (connect handler).
+ [Chuck Murcko <chuck@topsail.org>]
+
+mod_proxy changes for httpd 2.0.18
+
+ *) IPV6 EPSV support for IPV6 in FTP proxy.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) FTP directory filter works now.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Fixed some thread-safety issues with the HTTP proxy in mod_proxy.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) PASV FTP works now.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Reworked the line-at-a-time read from the control connection to
+ workaround a stray empty bucket returned by the HTTP_IN filter.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Stopped the CORE filter from sending off an HTTP response when a
+ CONNECT tunnel was closed.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Fixed the poll() loop in proxy_connect.c -> it works now!!!
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Converted send_dir() to ap_proxy_send_dir_filter() in proxy_ftp.c.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+mod_proxy changes for httpd 2.0.17
+
+ *) Major rework of ap_proxy_ftp_handler() to use filters (begone foul
+ BUFF!!!). It compiles, but is untested, and the build environment needs
+ to be fixed to include proxy_ftp.c.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Cleanup of dead functions within proxy_util.c.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Reworked the storage of the client socket between keepalive connections
+ to fix some nasty problems with the socket lasting longer than the
+ memory pool it was allocated from.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Fixed bug where a hostname without a "." in it (such as "localhost")
+ would not trigger an IP address check with ProxyBlock.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+mod_proxy changes for httpd 2.0.16
+
+ *) Fixed ProxyBlock bugs with ap_proxy_http_handler() and
+ ap_proxy_connect_handler().
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Updated ap_proxy_connect_handler() to support APR, while
+ moving some common code between http_handler and connect_handler
+ to proxy_util.c.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Updated mod_proxy.html docs to include v2.0 configuration.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Fixed problem where responses without entity bodies would cause
+ the directly following proxy keepalive request to fail.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+mod_proxy changes for httpd 2.0.15
+
+ *) Added support for downstream keepalives in mod_proxy.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Changed mod_proxy ap_proxy_http_handler() to support APR properly.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Fix problem where incoming response headers were not being returned
+ to the client in mod_proxy.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Added X-Forwarded-For, X-Forwarded-Host and X-Forwarded-Server to
+ reverse proxied request headers in mod_proxy.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) replace INADDR_NONE with APR_INADDR_NONE [Ian Holsman <IanH@cnet.com>]
+
+ *) Fix problem with proxy configuration where globally set
+ configuration options were overridden inside virtual hosts.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Fix ProxyReceiveBufferSize where default value was left
+ uninitialised.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+ *) Some small changes:
+ - Ensured hop-by-hop headers were stripped as per
+ RFC2616 13.5.1.
+ - Upgraded version code to HTTP/1.1.
+ - Added Connection: close until Keepalives come.
+ - Some cosmetic fixes and commenting.
+ [Graham Leggett <minfrin@sharp.fm>]
+
+mod_proxy changes for httpd 2.0.14
+
+ *) removed ProxyNoCache and ProxyCacheForceCompletion config directives,
+ since we no longer directly cache from this module
+ [Chuck Murcko <chuck@topsail.org>]
+
+ *) removed cache
+ [Chuck Murcko <chuck@topsail.org>]
+
+ *) initial rerebuild for 2.0
+ [Chuck Murcko <chuck@topsail.org>]
+
diff --git a/modules/proxy/Makefile.in b/modules/proxy/Makefile.in
new file mode 100644
index 0000000..6ea6775
--- /dev/null
+++ b/modules/proxy/Makefile.in
@@ -0,0 +1,4 @@
+# a modules Makefile has no explicit targets -- they will be defined by
+# whatever modules are enabled. just grab special.mk to deal with this.
+#SUBDIRS = balancers
+include $(top_srcdir)/build/special.mk
diff --git a/modules/proxy/NWGNUmakefile b/modules/proxy/NWGNUmakefile
new file mode 100644
index 0000000..d44644f
--- /dev/null
+++ b/modules/proxy/NWGNUmakefile
@@ -0,0 +1,259 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME =
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION =
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME =
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE =
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/proxy.nlm \
+ $(OBJDIR)/proxycon.nlm \
+ $(OBJDIR)/proxyftp.nlm \
+ $(OBJDIR)/proxyhtp.nlm \
+ $(OBJDIR)/proxybalancer.nlm \
+ $(OBJDIR)/proxyajp.nlm \
+ $(OBJDIR)/proxyfcgi.nlm \
+ $(OBJDIR)/proxyscgi.nlm \
+ $(OBJDIR)/proxyexpress.nlm \
+ $(OBJDIR)/proxyhcheck.nlm \
+ $(OBJDIR)/proxylbm_busy.nlm \
+ $(OBJDIR)/proxylbm_hb.nlm \
+ $(OBJDIR)/proxylbm_req.nlm \
+ $(OBJDIR)/proxylbm_traf.nlm \
+ $(OBJDIR)/proxywstunnel.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/proxy/NWGNUproxy b/modules/proxy/NWGNUproxy
new file mode 100644
index 0000000..d731c5a
--- /dev/null
+++ b/modules/proxy/NWGNUproxy
@@ -0,0 +1,337 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/modules/http \
+ $(AP_WORK)/modules/generators \
+ $(AP_WORK)/modules/ssl \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = proxy
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Proxy Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Proxy Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/proxy.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_proxy.o \
+ $(OBJDIR)/proxy_util.o \
+ $(OBJDIR)/libprews.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+# Don't link with Winsock if standard sockets are being used
+ifndef USE_STDSOCKETS
+FILES_nlm_Ximports += @ws2nlm.imp \
+ $(EOLIST)
+endif
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ @$(OBJDIR)/mod_proxy.imp \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(OBJDIR)/mod_proxy.imp $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+vpath %.c ../arch/netware
+
+$(OBJDIR)/mod_proxy.imp: NWGNUproxy
+ @echo $(DL)GEN $@$(DL)
+ @echo $(DL)# Exports of mod_proxy$(DL)> $@
+ @echo $(DL) (AP$(VERSION_MAJMIN))$(DL)>> $@
+ @echo $(DL) proxy_module,$(DL)>> $@
+ @echo $(DL) proxy_hcmethods,$(DL)>> $@
+ @echo $(DL) proxy_hook_canon_handler,$(DL)>> $@
+ @echo $(DL) proxy_hook_get_canon_handler,$(DL)>> $@
+ @echo $(DL) proxy_hook_get_post_request,$(DL)>> $@
+ @echo $(DL) proxy_hook_get_pre_request,$(DL)>> $@
+ @echo $(DL) proxy_hook_get_scheme_handler,$(DL)>> $@
+ @echo $(DL) proxy_hook_post_request,$(DL)>> $@
+ @echo $(DL) proxy_hook_pre_request,$(DL)>> $@
+ @echo $(DL) proxy_hook_scheme_handler,$(DL)>> $@
+ @echo $(DL) proxy_run_canon_handler,$(DL)>> $@
+ @echo $(DL) proxy_run_create_req,$(DL)>> $@
+ @echo $(DL) proxy_run_fixups,$(DL)>> $@
+ @echo $(DL) proxy_run_post_request,$(DL)>> $@
+ @echo $(DL) proxy_run_pre_request,$(DL)>> $@
+ @echo $(DL) proxy_run_request_status,$(DL)>> $@
+ @echo $(DL) proxy_run_scheme_handler,$(DL)>> $@
+ @echo $(DL) ap_proxy_acquire_connection,$(DL)>> $@
+ @echo $(DL) ap_proxy_backend_broke,$(DL)>> $@
+ @echo $(DL) ap_proxy_buckets_lifetime_transform,$(DL)>> $@
+ @echo $(DL) ap_proxy_c2hex,$(DL)>> $@
+ @echo $(DL) ap_proxy_canon_netloc,$(DL)>> $@
+ @echo $(DL) ap_proxy_canonenc,$(DL)>> $@
+ @echo $(DL) ap_proxy_check_connection,$(DL)>> $@
+ @echo $(DL) ap_proxy_checkproxyblock,$(DL)>> $@
+ @echo $(DL) ap_proxy_checkproxyblock2,$(DL)>> $@
+ @echo $(DL) ap_proxy_conn_is_https,$(DL)>> $@
+ @echo $(DL) ap_proxy_connect_backend,$(DL)>> $@
+ @echo $(DL) ap_proxy_connect_to_backend,$(DL)>> $@
+ @echo $(DL) ap_proxy_connection_create,$(DL)>> $@
+ @echo $(DL) ap_proxy_connection_reusable,$(DL)>> $@
+ @echo $(DL) ap_proxy_cookie_reverse_map,$(DL)>> $@
+ @echo $(DL) ap_proxy_create_hdrbrgd,$(DL)>> $@
+ @echo $(DL) ap_proxy_define_balancer,$(DL)>> $@
+ @echo $(DL) ap_proxy_define_worker,$(DL)>> $@
+ @echo $(DL) ap_proxy_determine_connection,$(DL)>> $@
+ @echo $(DL) ap_proxy_find_balancershm,$(DL)>> $@
+ @echo $(DL) ap_proxy_find_workershm,$(DL)>> $@
+ @echo $(DL) ap_proxy_get_balancer,$(DL)>> $@
+ @echo $(DL) ap_proxy_get_worker,$(DL)>> $@
+ @echo $(DL) ap_proxy_hashfunc,$(DL)>> $@
+ @echo $(DL) ap_proxy_hex2c,$(DL)>> $@
+ @echo $(DL) ap_proxy_initialize_balancer,$(DL)>> $@
+ @echo $(DL) ap_proxy_initialize_worker,$(DL)>> $@
+ @echo $(DL) ap_proxy_is_domainname,$(DL)>> $@
+ @echo $(DL) ap_proxy_is_hostname,$(DL)>> $@
+ @echo $(DL) ap_proxy_is_ipaddr,$(DL)>> $@
+ @echo $(DL) ap_proxy_is_word,$(DL)>> $@
+ @echo $(DL) ap_proxy_location_reverse_map,$(DL)>> $@
+ @echo $(DL) ap_proxy_parse_wstatus,$(DL)>> $@
+ @echo $(DL) ap_proxy_pass_brigade,$(DL)>> $@
+ @echo $(DL) ap_proxy_port_of_scheme,$(DL)>> $@
+ @echo $(DL) ap_proxy_post_request,$(DL)>> $@
+ @echo $(DL) ap_proxy_pre_http_request,$(DL)>> $@
+ @echo $(DL) ap_proxy_pre_request,$(DL)>> $@
+ @echo $(DL) ap_proxy_release_connection,$(DL)>> $@
+ @echo $(DL) ap_proxy_set_wstatus,$(DL)>> $@
+ @echo $(DL) ap_proxy_share_balancer,$(DL)>> $@
+ @echo $(DL) ap_proxy_share_worker,$(DL)>> $@
+ @echo $(DL) ap_proxy_show_hcmethod,$(DL)>> $@
+ @echo $(DL) ap_proxy_ssl_connection_cleanup,$(DL)>> $@
+ @echo $(DL) ap_proxy_ssl_disable,$(DL)>> $@
+ @echo $(DL) ap_proxy_ssl_enable,$(DL)>> $@
+ @echo $(DL) ap_proxy_ssl_val,$(DL)>> $@
+ @echo $(DL) ap_proxy_strncpy,$(DL)>> $@
+ @echo $(DL) ap_proxy_sync_balancer,$(DL)>> $@
+ @echo $(DL) ap_proxy_trans_match,$(DL)>> $@
+ @echo $(DL) ap_proxy_transfer_between_connections,$(DL)>> $@
+ @echo $(DL) ap_proxy_valid_balancer_name,$(DL)>> $@
+ @echo $(DL) ap_proxy_worker_name,$(DL)>> $@
+ @echo $(DL) ap_proxyerror$(DL)>> $@
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/proxy/NWGNUproxyajp b/modules/proxy/NWGNUproxyajp
new file mode 100644
index 0000000..0f41248
--- /dev/null
+++ b/modules/proxy/NWGNUproxyajp
@@ -0,0 +1,264 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/modules/http \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = proxyajp
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Proxy AJP Sub-Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Proxy AJP Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/proxyajp.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_proxy_ajp.o \
+ $(OBJDIR)/ajp_header.o \
+ $(OBJDIR)/ajp_msg.o \
+ $(OBJDIR)/ajp_link.o \
+ $(OBJDIR)/ajp_utils.o \
+ $(OBJDIR)/libprews.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ proxy \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @$(OBJDIR)/mod_proxy.imp \
+ @libc.imp \
+ $(EOLIST)
+
+# Don't link with Winsock if standard sockets are being used
+ifndef USE_STDSOCKETS
+FILES_nlm_Ximports += @ws2nlm.imp \
+ $(EOLIST)
+endif
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ proxy_ajp_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+vpath %.c ../arch/netware
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/proxy/NWGNUproxybalancer b/modules/proxy/NWGNUproxybalancer
new file mode 100644
index 0000000..976fd50
--- /dev/null
+++ b/modules/proxy/NWGNUproxybalancer
@@ -0,0 +1,260 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/modules/http \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = proxybalancer
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Proxy Balancer Sub-Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Prxy Blncr Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/proxybalancer.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_proxy_balancer.o \
+ $(OBJDIR)/libprews.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ proxy \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @$(OBJDIR)/mod_proxy.imp \
+ @libc.imp \
+ $(EOLIST)
+
+# Don't link with Winsock if standard sockets are being used
+ifndef USE_STDSOCKETS
+FILES_nlm_Ximports += @ws2nlm.imp \
+ $(EOLIST)
+endif
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ proxy_balancer_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+vpath %.c ../arch/netware
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/proxy/NWGNUproxycon b/modules/proxy/NWGNUproxycon
new file mode 100644
index 0000000..0215a49
--- /dev/null
+++ b/modules/proxy/NWGNUproxycon
@@ -0,0 +1,251 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/modules/http \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = proxycon
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Proxy Connection Sub-Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Proxy Conn Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/proxycon.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_proxy_connect.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ proxy \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @$(OBJDIR)/mod_proxy.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ proxy_connect_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/proxy/NWGNUproxyexpress b/modules/proxy/NWGNUproxyexpress
new file mode 100644
index 0000000..84bb503
--- /dev/null
+++ b/modules/proxy/NWGNUproxyexpress
@@ -0,0 +1,256 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/modules/http \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = proxyexpress
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Proxy Express Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Prxy Xpress Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = $(OBJDIR)/$(NLM_NAME).nlm
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib =
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_proxy_express.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ libc \
+ aprlib \
+ proxy \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @libc.imp \
+ @aprlib.imp \
+ @httpd.imp \
+ @$(OBJDIR)/mod_proxy.imp \
+ $(EOLIST)
+
+# Don't link with Winsock if standard sockets are being used
+ifndef USE_STDSOCKETS
+FILES_nlm_Ximports += @ws2nlm.imp \
+ $(EOLIST)
+endif
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ proxy_express_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+vpath %.c ../arch/netware
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/proxy/NWGNUproxyfcgi b/modules/proxy/NWGNUproxyfcgi
new file mode 100644
index 0000000..a441222
--- /dev/null
+++ b/modules/proxy/NWGNUproxyfcgi
@@ -0,0 +1,261 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/modules/http \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ -relax_pointers \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = proxyfcgi
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Proxy Fast CGI Sub-Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Proxy FCGI Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/proxyfcgi.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_proxy_fcgi.o \
+ $(OBJDIR)/libprews.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ proxy \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @$(OBJDIR)/mod_proxy.imp \
+ @libc.imp \
+ $(EOLIST)
+
+# Don't link with Winsock if standard sockets are being used
+ifndef USE_STDSOCKETS
+FILES_nlm_Ximports += @ws2nlm.imp \
+ $(EOLIST)
+endif
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ proxy_fcgi_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+vpath %.c ../arch/netware
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/proxy/NWGNUproxyftp b/modules/proxy/NWGNUproxyftp
new file mode 100644
index 0000000..ee7fbf8
--- /dev/null
+++ b/modules/proxy/NWGNUproxyftp
@@ -0,0 +1,260 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/modules/http \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = proxyftp
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Proxy FTP Sub-Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Proxy FTP Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/proxyftp.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_proxy_ftp.o \
+ $(OBJDIR)/libprews.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ proxy \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @$(OBJDIR)/mod_proxy.imp \
+ @libc.imp \
+ $(EOLIST)
+
+# Don't link with Winsock if standard sockets are being used
+ifndef USE_STDSOCKETS
+FILES_nlm_Ximports += @ws2nlm.imp \
+ $(EOLIST)
+endif
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ proxy_ftp_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+vpath %.c ../arch/netware
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/proxy/NWGNUproxyhcheck b/modules/proxy/NWGNUproxyhcheck
new file mode 100644
index 0000000..d200650
--- /dev/null
+++ b/modules/proxy/NWGNUproxyhcheck
@@ -0,0 +1,254 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(SRC)/include \
+ $(STDMOD)/core \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = proxyhcheck
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Proxy Health Check Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Proxy Health Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = $(OBJDIR)/$(NLM_NAME).nlm
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib =
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_proxy_hcheck.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ libc \
+ aprlib \
+ proxy \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @libc.imp \
+ @aprlib.imp \
+ @httpd.imp \
+ @mod_proxy.imp \
+ $(EOLIST)
+
+# Don't link with Winsock if standard sockets are being used
+ifndef USE_STDSOCKETS
+FILES_nlm_Ximports += @ws2nlm.imp \
+ $(EOLIST)
+endif
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ proxy_hcheck_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+vpath %.c ../arch/netware
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
diff --git a/modules/proxy/NWGNUproxyhtp b/modules/proxy/NWGNUproxyhtp
new file mode 100644
index 0000000..0257d52
--- /dev/null
+++ b/modules/proxy/NWGNUproxyhtp
@@ -0,0 +1,260 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/modules/http \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = proxyhtp
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Proxy HTTP Sub-Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Proxy HTTP Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/proxyhtp.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_proxy_http.o \
+ $(OBJDIR)/libprews.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ proxy \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @$(OBJDIR)/mod_proxy.imp \
+ @libc.imp \
+ $(EOLIST)
+
+# Don't link with Winsock if standard sockets are being used
+ifndef USE_STDSOCKETS
+FILES_nlm_Ximports += @ws2nlm.imp \
+ $(EOLIST)
+endif
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ proxy_http_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+vpath %.c ../arch/netware
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/proxy/NWGNUproxylbm_busy b/modules/proxy/NWGNUproxylbm_busy
new file mode 100644
index 0000000..c1b91f6
--- /dev/null
+++ b/modules/proxy/NWGNUproxylbm_busy
@@ -0,0 +1,250 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/modules/proxy \
+ $(AP_WORK)/modules/http \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = proxylbm_busy
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Proxy LoadBalance by Busyness Sub-Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = LBM Busy Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = $(OBJDIR)/$(NLM_NAME).nlm
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib =
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_lbmethod_bybusyness.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ libc \
+ aprlib \
+ proxy \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @libc.imp \
+ @aprlib.imp \
+ @httpd.imp \
+ @$(OBJDIR)/mod_proxy.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ lbmethod_bybusyness_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+vpath %.c balancers
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/proxy/NWGNUproxylbm_hb b/modules/proxy/NWGNUproxylbm_hb
new file mode 100644
index 0000000..19563da
--- /dev/null
+++ b/modules/proxy/NWGNUproxylbm_hb
@@ -0,0 +1,251 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/modules/proxy \
+ $(AP_WORK)/modules/http \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = proxylbm_hb
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Proxy LoadBalance by Heartbeat Sub-Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = LBM Heart Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = $(OBJDIR)/$(NLM_NAME).nlm
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib =
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_lbmethod_heartbeat.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ libc \
+ aprlib \
+ proxy \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @libc.imp \
+ @aprlib.imp \
+ @httpd.imp \
+ @$(OBJDIR)/mod_proxy.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ lbmethod_heartbeat_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+vpath %.c balancers
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/proxy/NWGNUproxylbm_req b/modules/proxy/NWGNUproxylbm_req
new file mode 100644
index 0000000..6a2c4cd
--- /dev/null
+++ b/modules/proxy/NWGNUproxylbm_req
@@ -0,0 +1,251 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/modules/proxy \
+ $(AP_WORK)/modules/http \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = proxylbm_req
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Proxy LoadBalance by Requests Sub-Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = LBM Requests Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = $(OBJDIR)/$(NLM_NAME).nlm
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib =
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_lbmethod_byrequests.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ libc \
+ aprlib \
+ proxy \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @libc.imp \
+ @aprlib.imp \
+ @httpd.imp \
+ @$(OBJDIR)/mod_proxy.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ lbmethod_byrequests_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+vpath %.c balancers
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/proxy/NWGNUproxylbm_traf b/modules/proxy/NWGNUproxylbm_traf
new file mode 100644
index 0000000..1927d62
--- /dev/null
+++ b/modules/proxy/NWGNUproxylbm_traf
@@ -0,0 +1,251 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/modules/proxy \
+ $(AP_WORK)/modules/http \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = proxylbm_traf
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Proxy LoadBalance by Traffic Sub-Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = LBM Traf Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = $(OBJDIR)/$(NLM_NAME).nlm
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib =
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_lbmethod_bytraffic.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ libc \
+ aprlib \
+ proxy \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @libc.imp \
+ @aprlib.imp \
+ @httpd.imp \
+ @$(OBJDIR)/mod_proxy.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ lbmethod_bytraffic_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+vpath %.c balancers
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/proxy/NWGNUproxyscgi b/modules/proxy/NWGNUproxyscgi
new file mode 100644
index 0000000..9068c69
--- /dev/null
+++ b/modules/proxy/NWGNUproxyscgi
@@ -0,0 +1,260 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/modules/http \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = proxyscgi
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Proxy SCGI Sub-Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Proxy SCGI Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/proxyscgi.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_proxy_scgi.o \
+ $(OBJDIR)/libprews.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ proxy \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @$(OBJDIR)/mod_proxy.imp \
+ @libc.imp \
+ $(EOLIST)
+
+# Don't link with Winsock if standard sockets are being used
+ifndef USE_STDSOCKETS
+FILES_nlm_Ximports += @ws2nlm.imp \
+ $(EOLIST)
+endif
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ proxy_scgi_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+vpath %.c ../arch/netware
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/proxy/NWGNUproxywstunnel b/modules/proxy/NWGNUproxywstunnel
new file mode 100644
index 0000000..ce84ce4
--- /dev/null
+++ b/modules/proxy/NWGNUproxywstunnel
@@ -0,0 +1,250 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(SRC)/include \
+ $(STDMOD)/http \
+ $(STDMOD)/proxy \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = proxywstunnel
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Proxy Web Socket Tunnel Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Prxy WbSkt Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = $(OBJDIR)/$(NLM_NAME).nlm
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib =
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_proxy_wstunnel.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ libc \
+ aprlib \
+ proxy \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @libc.imp \
+ @aprlib.imp \
+ @httpd.imp \
+ @$(OBJDIR)/mod_proxy.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ proxy_wstunnel_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+vpath %.c balancers
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/proxy/ajp.h b/modules/proxy/ajp.h
new file mode 100644
index 0000000..a950ee9
--- /dev/null
+++ b/modules/proxy/ajp.h
@@ -0,0 +1,522 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file ajp.h
+ * @brief Apache Jserv Protocol
+ *
+ * @defgroup AJP_defines mod_proxy AJP definitions
+ * @ingroup APACHE_INTERNAL
+ * @{
+ */
+
+#ifndef AJP_H
+#define AJP_H
+
+#include "apr_version.h"
+#include "apr.h"
+
+#include "apr_hooks.h"
+#include "apr_lib.h"
+#include "apr_strings.h"
+#include "apr_buckets.h"
+#include "apr_md5.h"
+#include "apr_network_io.h"
+#include "apr_poll.h"
+#include "apr_pools.h"
+#include "apr_strings.h"
+#include "apr_uri.h"
+#include "apr_date.h"
+#include "apr_fnmatch.h"
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#if APR_HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#if APR_HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+
+#define AJP13_DEF_HOST "127.0.0.1"
+#ifdef NETWARE
+#define AJP13_DEF_PORT 9009 /* default to 9009 since 8009 is used by OS */
+#else
+#define AJP13_DEF_PORT 8009
+#endif
+
+/* The following environment variables match mod_ssl! */
+#define AJP13_HTTPS_INDICATOR "HTTPS"
+#define AJP13_SSL_PROTOCOL_INDICATOR "SSL_PROTOCOL"
+#define AJP13_SSL_CLIENT_CERT_INDICATOR "SSL_CLIENT_CERT"
+#define AJP13_SSL_CIPHER_INDICATOR "SSL_CIPHER"
+#define AJP13_SSL_SESSION_INDICATOR "SSL_SESSION_ID"
+#define AJP13_SSL_KEY_SIZE_INDICATOR "SSL_CIPHER_USEKEYSIZE"
+
+#ifdef AJP_USE_HTTPD_WRAP
+#include "httpd_wrap.h"
+#else
+#include "httpd.h"
+#include "http_config.h"
+#include "http_request.h"
+#include "http_core.h"
+#include "http_protocol.h"
+#include "http_main.h"
+#include "http_log.h"
+#endif
+
+#include "mod_proxy.h"
+#include "util_ebcdic.h"
+
+/** AJP Specific error codes
+ */
+/** Buffer overflow exception */
+#define AJP_EOVERFLOW (APR_OS_START_USERERR + 1)
+/** Destination Buffer is to small */
+#define AJP_ETOSMALL (APR_OS_START_USERERR + 2)
+/** Invalid input parameters */
+#define AJP_EINVAL (APR_OS_START_USERERR + 3)
+/** Bad message signature */
+#define AJP_EBAD_SIGNATURE (APR_OS_START_USERERR + 4)
+/** Incoming message too bg */
+#define AJP_ETOBIG (APR_OS_START_USERERR + 5)
+/** Missing message header */
+#define AJP_ENO_HEADER (APR_OS_START_USERERR + 6)
+/** Bad message header */
+#define AJP_EBAD_HEADER (APR_OS_START_USERERR + 7)
+/** Bad message */
+#define AJP_EBAD_MESSAGE (APR_OS_START_USERERR + 8)
+/** Cant log via AJP14 */
+#define AJP_ELOGFAIL (APR_OS_START_USERERR + 9)
+/** Bad request method */
+#define AJP_EBAD_METHOD (APR_OS_START_USERERR + 10)
+
+
+/** A structure that represents ajp message */
+typedef struct ajp_msg ajp_msg_t;
+
+/** A structure that represents ajp message */
+struct ajp_msg
+{
+ /** The buffer holding a AJP message */
+ apr_byte_t *buf;
+ /** The length of AJP message header (defaults to AJP_HEADER_LEN) */
+ apr_size_t header_len;
+ /** The length of AJP message */
+ apr_size_t len;
+ /** The current read position */
+ apr_size_t pos;
+ /** Flag indicating the origing of the message */
+ int server_side;
+ /** The size of the buffer */
+ apr_size_t max_size;
+};
+
+/**
+ * Signature for the messages sent from Apache to tomcat
+ */
+#define AJP13_WS_HEADER 0x1234
+#define AJP_HEADER_LEN 4
+#define AJP_HEADER_SZ_LEN 2
+#define AJP_HEADER_SZ 6
+#define AJP_MSG_BUFFER_SZ 8192
+#define AJP_MAX_BUFFER_SZ 65536
+#define AJP13_MAX_SEND_BODY_SZ (AJP_MAX_BUFFER_SZ - AJP_HEADER_SZ)
+#define AJP_PING_PONG_SZ 128
+
+/** Send a request from web server to container*/
+#define CMD_AJP13_FORWARD_REQUEST (unsigned char)2
+/** Write a body chunk from the servlet container to the web server */
+#define CMD_AJP13_SEND_BODY_CHUNK (unsigned char)3
+/** Send response headers from the servlet container to the web server. */
+#define CMD_AJP13_SEND_HEADERS (unsigned char)4
+/** Marks the end of response. */
+#define CMD_AJP13_END_RESPONSE (unsigned char)5
+/** Get further data from the web server if it hasn't all been transferred yet. */
+#define CMD_AJP13_GET_BODY_CHUNK (unsigned char)6
+/** The web server asks the container to shut itself down. */
+#define CMD_AJP13_SHUTDOWN (unsigned char)7
+/** Webserver ask container to take control (logon phase) */
+#define CMD_AJP13_PING (unsigned char)8
+/** Container response to cping request */
+#define CMD_AJP13_CPONG (unsigned char)9
+/** Webserver check if container is alive, since container should respond by cpong */
+#define CMD_AJP13_CPING (unsigned char)10
+
+/** @} */
+
+/**
+ * @defgroup AJP_api AJP API functions
+ * @ingroup MOD_PROXY
+ * @{
+ */
+/**
+ * Check a new AJP Message by looking at signature and return its size
+ *
+ * @param msg AJP Message to check
+ * @param len Pointer to returned len
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_check_header(ajp_msg_t *msg, apr_size_t *len);
+
+/**
+ * Reset an AJP Message
+ *
+ * @param msg AJP Message to reset
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_reset(ajp_msg_t *msg);
+
+/**
+ * Reuse an AJP Message
+ *
+ * @param msg AJP Message to reuse
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_reuse(ajp_msg_t *msg);
+
+/**
+ * Mark the end of an AJP Message
+ *
+ * @param msg AJP Message to end
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_end(ajp_msg_t *msg);
+
+/**
+ * Add an unsigned 32bits value to AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param value value to add to AJP Message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_append_uint32(ajp_msg_t *msg, apr_uint32_t value);
+
+/**
+ * Add an unsigned 16bits value to AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param value value to add to AJP Message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_append_uint16(ajp_msg_t *msg, apr_uint16_t value);
+
+/**
+ * Add an unsigned 8bits value to AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param value value to add to AJP Message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_append_uint8(ajp_msg_t *msg, apr_byte_t value);
+
+/**
+ * Add a String in AJP message, and transform the String in ASCII
+ * if convert is set and we're on an EBCDIC machine
+ *
+ * @param msg AJP Message to get value from
+ * @param value Pointer to String
+ * @param convert When set told to convert String to ASCII
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_append_string_ex(ajp_msg_t *msg, const char *value,
+ int convert);
+/**
+ * Add a String in AJP message, and transform
+ * the String in ASCII if we're on an EBCDIC machine
+ */
+#define ajp_msg_append_string(m, v) ajp_msg_append_string_ex(m, v, 1)
+
+/**
+ * Add a String in AJP message.
+ */
+#define ajp_msg_append_string_ascii(m, v) ajp_msg_append_string_ex(m, v, 0)
+
+/**
+ * Add a Byte array to AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param value Pointer to Byte array
+ * @param valuelen Byte array len
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_append_bytes(ajp_msg_t *msg, const apr_byte_t *value,
+ apr_size_t valuelen);
+
+/**
+ * Get a 32bits unsigned value from AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param rvalue Pointer where value will be returned
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_get_uint32(ajp_msg_t *msg, apr_uint32_t *rvalue);
+
+/**
+ * Get a 16bits unsigned value from AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param rvalue Pointer where value will be returned
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_get_uint16(ajp_msg_t *msg, apr_uint16_t *rvalue);
+
+/**
+ * Peek a 16bits unsigned value from AJP Message, position in message
+ * is not updated
+ *
+ * @param msg AJP Message to get value from
+ * @param rvalue Pointer where value will be returned
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_peek_uint16(ajp_msg_t *msg, apr_uint16_t *rvalue);
+
+/**
+ * Get a 8bits unsigned value from AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param rvalue Pointer where value will be returned
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_get_uint8(ajp_msg_t *msg, apr_byte_t *rvalue);
+
+/**
+ * Peek a 8bits unsigned value from AJP Message, position in message
+ * is not updated
+ *
+ * @param msg AJP Message to get value from
+ * @param rvalue Pointer where value will be returned
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_peek_uint8(ajp_msg_t *msg, apr_byte_t *rvalue);
+
+/**
+ * Get a String value from AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param rvalue Pointer where value will be returned
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_get_string(ajp_msg_t *msg, const char **rvalue);
+
+
+/**
+ * Get a Byte array from AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param rvalue Pointer where value will be returned
+ * @param rvalue_len Pointer where Byte array len will be returned
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_get_bytes(ajp_msg_t *msg, apr_byte_t **rvalue,
+ apr_size_t *rvalue_len);
+
+/**
+ * Create an AJP Message from pool
+ *
+ * @param pool memory pool to allocate AJP message from
+ * @param size size of the buffer to create
+ * @param rmsg Pointer to newly created AJP message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_create(apr_pool_t *pool, apr_size_t size, ajp_msg_t **rmsg);
+
+/**
+ * Recopy an AJP Message to another
+ *
+ * @param smsg source AJP message
+ * @param dmsg destination AJP message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_copy(ajp_msg_t *smsg, ajp_msg_t *dmsg);
+
+/**
+ * Serialize in an AJP Message a PING command
+ *
+ * +-----------------------+
+ * | PING CMD (1 byte) |
+ * +-----------------------+
+ *
+ * @param msg AJP message to put serialized message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_serialize_ping(ajp_msg_t *msg);
+
+/**
+ * Serialize in an AJP Message a CPING command
+ *
+ * +-----------------------+
+ * | CPING CMD (1 byte) |
+ * +-----------------------+
+ *
+ * @param msg AJP message to put serialized message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_serialize_cping(ajp_msg_t *msg);
+
+/**
+ * Dump up to the first 1024 bytes on an AJP Message
+ *
+ * @param pool pool to allocate from
+ * @param msg AJP Message to dump
+ * @param err error string to display
+ * @param count the number of bytes to dump
+ * @param buf buffer pointer for dump message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_dump(apr_pool_t *pool, ajp_msg_t *msg, char *err,
+ apr_size_t count, char **buf);
+
+/**
+ * Log an AJP message
+ *
+ * @param r The current request
+ * @param msg AJP Message to dump
+ * @param err error string to display
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_log(request_rec *r, ajp_msg_t *msg, char *err);
+
+/**
+ * Send an AJP message to backend
+ *
+ * @param sock backend socket
+ * @param msg AJP message to put serialized message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_ilink_send(apr_socket_t *sock, ajp_msg_t *msg);
+
+/**
+ * Receive an AJP message from backend
+ *
+ * @param sock backend socket
+ * @param msg AJP message to put serialized message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_ilink_receive(apr_socket_t *sock, ajp_msg_t *msg);
+
+/**
+ * Build the ajp header message and send it
+ * @param sock backend socket
+ * @param r current request
+ * @param buffsize max size of the AJP packet.
+ * @param uri requested uri
+ * @param secret authentication secret
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_send_header(apr_socket_t *sock, request_rec *r,
+ apr_size_t buffsize,
+ apr_uri_t *uri,
+ const char *secret);
+
+/**
+ * Read the ajp message and return the type of the message.
+ * @param sock backend socket
+ * @param r current request
+ * @param buffsize size of the buffer.
+ * @param msg returned AJP message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_read_header(apr_socket_t *sock,
+ request_rec *r,
+ apr_size_t buffsize,
+ ajp_msg_t **msg);
+
+/**
+ * Allocate a msg to send data
+ * @param pool pool to allocate from
+ * @param ptr data buffer
+ * @param len the length of allocated data buffer
+ * @param msg returned AJP message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_alloc_data_msg(apr_pool_t *pool, char **ptr,
+ apr_size_t *len, ajp_msg_t **msg);
+
+/**
+ * Send the data message
+ * @param sock backend socket
+ * @param msg AJP message to send
+ * @param len AJP message length
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_send_data_msg(apr_socket_t *sock,
+ ajp_msg_t *msg, apr_size_t len);
+
+/**
+ * Parse the message type
+ * @param r current request
+ * @param msg AJP message
+ * @return AJP message type.
+ */
+int ajp_parse_type(request_rec *r, ajp_msg_t *msg);
+
+/**
+ * Parse the header message from container
+ * @param r current request
+ * @param conf proxy config
+ * @param msg AJP message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_parse_header(request_rec *r, proxy_dir_conf *conf,
+ ajp_msg_t *msg);
+
+/**
+ * Parse the message body and return data address and length
+ * @param r current request
+ * @param msg AJP message
+ * @param len returned AJP message length
+ * @param ptr returned data
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_parse_data(request_rec *r, ajp_msg_t *msg,
+ apr_uint16_t *len, char **ptr);
+
+
+/**
+ * Check the reuse flag in CMD_AJP13_END_RESPONSE
+ * @param r current request
+ * @param msg AJP message
+ * @param reuse returned reuse flag
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_parse_reuse(request_rec *r, ajp_msg_t *msg,
+ apr_byte_t *reuse);
+
+
+/**
+ * Handle the CPING/CPONG messages
+ * @param sock backend socket
+ * @param r current request
+ * @param timeout time window for receiving cpong reply
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_handle_cping_cpong(apr_socket_t *sock,
+ request_rec *r,
+ apr_interval_time_t timeout);
+
+
+/**
+ * Convert numeric message type into string
+ * @param type AJP message type
+ * @return AJP message type as a string
+ */
+const char *ajp_type_str(int type);
+
+/** @} */
+
+#endif /* AJP_H */
+
diff --git a/modules/proxy/ajp_header.c b/modules/proxy/ajp_header.c
new file mode 100644
index 0000000..a09a2e4
--- /dev/null
+++ b/modules/proxy/ajp_header.c
@@ -0,0 +1,893 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ajp_header.h"
+#include "ajp.h"
+
+APLOG_USE_MODULE(proxy_ajp);
+
+static const char *response_trans_headers[] = {
+ "Content-Type",
+ "Content-Language",
+ "Content-Length",
+ "Date",
+ "Last-Modified",
+ "Location",
+ "Set-Cookie",
+ "Set-Cookie2",
+ "Servlet-Engine",
+ "Status",
+ "WWW-Authenticate"
+};
+
+static const char *long_res_header_for_sc(int sc)
+{
+ const char *rc = NULL;
+ sc = sc & 0X00FF;
+ if (sc <= SC_RES_HEADERS_NUM && sc > 0) {
+ rc = response_trans_headers[sc - 1];
+ }
+
+ return rc;
+}
+
+#define UNKNOWN_METHOD (-1)
+
+static int sc_for_req_header(const char *header_name)
+{
+ char header[16];
+ apr_size_t len = strlen(header_name);
+ const char *p = header_name;
+ int i = 0;
+
+ /* ACCEPT-LANGUAGE is the longest header
+ * that is of interest.
+ */
+ if (len < 4 || len > 15)
+ return UNKNOWN_METHOD;
+
+ memset(header, 0, sizeof header);
+ while (*p)
+ header[i++] = apr_toupper(*p++);
+ header[i] = '\0';
+ p = &header[1];
+
+ switch (header[0]) {
+ case 'A':
+ if (memcmp(p, "CCEPT", 5) == 0) {
+ if (!header[6])
+ return SC_ACCEPT;
+ else if (header[6] == '-') {
+ p += 6;
+ if (strcmp(p, "CHARSET") == 0)
+ return SC_ACCEPT_CHARSET;
+ else if (strcmp(p, "ENCODING") == 0)
+ return SC_ACCEPT_ENCODING;
+ else if (strcmp(p, "LANGUAGE") == 0)
+ return SC_ACCEPT_LANGUAGE;
+ else
+ return UNKNOWN_METHOD;
+ }
+ else
+ return UNKNOWN_METHOD;
+ }
+ else if (strcmp(p, "UTHORIZATION") == 0)
+ return SC_AUTHORIZATION;
+ else
+ return UNKNOWN_METHOD;
+ break;
+ case 'C':
+ if (strcmp(p, "OOKIE2") == 0)
+ return SC_COOKIE2;
+ else if (strcmp(p, "OOKIE") == 0)
+ return SC_COOKIE;
+ else if (strcmp(p, "ONNECTION") == 0)
+ return SC_CONNECTION;
+ else if (strcmp(p, "ONTENT-TYPE") == 0)
+ return SC_CONTENT_TYPE;
+ else if (strcmp(p, "ONTENT-LENGTH") == 0)
+ return SC_CONTENT_LENGTH;
+ else
+ return UNKNOWN_METHOD;
+ break;
+ case 'H':
+ if (strcmp(p, "OST") == 0)
+ return SC_HOST;
+ else
+ return UNKNOWN_METHOD;
+ break;
+ case 'P':
+ if (strcmp(p, "RAGMA") == 0)
+ return SC_PRAGMA;
+ else
+ return UNKNOWN_METHOD;
+ break;
+ case 'R':
+ if (strcmp(p, "EFERER") == 0)
+ return SC_REFERER;
+ else
+ return UNKNOWN_METHOD;
+ break;
+ case 'U':
+ if (strcmp(p, "SER-AGENT") == 0)
+ return SC_USER_AGENT;
+ else
+ return UNKNOWN_METHOD;
+ break;
+ default:
+ return UNKNOWN_METHOD;
+ }
+
+ /* NOTREACHED */
+}
+
+/* Apache method number to SC methods transform table */
+static const unsigned char sc_for_req_method_table[] = {
+ SC_M_GET,
+ SC_M_PUT,
+ SC_M_POST,
+ SC_M_DELETE,
+ 0, /* M_CONNECT */
+ SC_M_OPTIONS,
+ SC_M_TRACE,
+ 0, /* M_PATCH */
+ SC_M_PROPFIND,
+ SC_M_PROPPATCH,
+ SC_M_MKCOL,
+ SC_M_COPY,
+ SC_M_MOVE,
+ SC_M_LOCK,
+ SC_M_UNLOCK,
+ SC_M_VERSION_CONTROL,
+ SC_M_CHECKOUT,
+ SC_M_UNCHECKOUT,
+ SC_M_CHECKIN,
+ SC_M_UPDATE,
+ SC_M_LABEL,
+ SC_M_REPORT,
+ SC_M_MKWORKSPACE,
+ SC_M_MKACTIVITY,
+ SC_M_BASELINE_CONTROL,
+ SC_M_MERGE,
+ 0 /* M_INVALID */
+};
+
+static int sc_for_req_method_by_id(request_rec *r)
+{
+ int method_id = r->method_number;
+ if (method_id < 0 || method_id > M_INVALID) {
+ return UNKNOWN_METHOD;
+ }
+ else if (r->header_only) {
+ return SC_M_HEAD;
+ }
+ else {
+ return sc_for_req_method_table[method_id] ?
+ sc_for_req_method_table[method_id] : UNKNOWN_METHOD;
+ }
+}
+
+/*
+ * Message structure
+ *
+ *
+AJPV13_REQUEST/AJPV14_REQUEST=
+ request_prefix (1) (byte)
+ method (byte)
+ protocol (string)
+ req_uri (string)
+ remote_addr (string)
+ remote_host (string)
+ server_name (string)
+ server_port (short)
+ is_ssl (boolean)
+ num_headers (short)
+ num_headers*(req_header_name header_value)
+
+ ?context (byte)(string)
+ ?servlet_path (byte)(string)
+ ?remote_user (byte)(string)
+ ?auth_type (byte)(string)
+ ?query_string (byte)(string)
+ ?jvm_route (byte)(string)
+ ?ssl_cert (byte)(string)
+ ?ssl_cipher (byte)(string)
+ ?ssl_session (byte)(string)
+ ?ssl_key_size (byte)(int) via JkOptions +ForwardKeySize
+ request_terminator (byte)
+ ?body content_length*(var binary)
+
+ */
+
+static apr_status_t ajp_marshal_into_msgb(ajp_msg_t *msg,
+ request_rec *r,
+ apr_uri_t *uri,
+ const char *secret)
+{
+ int method;
+ apr_uint32_t i, num_headers = 0;
+ apr_byte_t is_ssl;
+ char *remote_host;
+ const char *session_route, *envvar;
+ const apr_array_header_t *arr = apr_table_elts(r->subprocess_env);
+ const apr_table_entry_t *elts = (const apr_table_entry_t *)arr->elts;
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, r, "Into ajp_marshal_into_msgb");
+
+ if ((method = sc_for_req_method_by_id(r)) == UNKNOWN_METHOD) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, r, APLOGNO(02437)
+ "ajp_marshal_into_msgb - Sending unknown method %s as request attribute",
+ r->method);
+ method = SC_M_JK_STORED;
+ }
+
+ is_ssl = (apr_byte_t) ap_proxy_conn_is_https(r->connection);
+
+ if (r->headers_in && apr_table_elts(r->headers_in)) {
+ const apr_array_header_t *t = apr_table_elts(r->headers_in);
+ num_headers = t->nelts;
+ }
+
+ remote_host = (char *)ap_get_useragent_host(r, REMOTE_HOST, NULL);
+
+ ajp_msg_reset(msg);
+
+ if (ajp_msg_append_uint8(msg, CMD_AJP13_FORWARD_REQUEST) ||
+ ajp_msg_append_uint8(msg, (apr_byte_t) method) ||
+ ajp_msg_append_string(msg, r->protocol) ||
+ ajp_msg_append_string(msg, uri->path) ||
+ ajp_msg_append_string(msg, r->useragent_ip) ||
+ ajp_msg_append_string(msg, remote_host) ||
+ ajp_msg_append_string(msg, ap_get_server_name(r)) ||
+ ajp_msg_append_uint16(msg, (apr_uint16_t)r->connection->local_addr->port) ||
+ ajp_msg_append_uint8(msg, is_ssl) ||
+ ajp_msg_append_uint16(msg, (apr_uint16_t) num_headers)) {
+
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00968)
+ "ajp_marshal_into_msgb: "
+ "Error appending the message beginning");
+ return APR_EGENERAL;
+ }
+
+ for (i = 0 ; i < num_headers ; i++) {
+ int sc;
+ const apr_array_header_t *t = apr_table_elts(r->headers_in);
+ const apr_table_entry_t *elts = (apr_table_entry_t *)t->elts;
+
+ if ((sc = sc_for_req_header(elts[i].key)) != UNKNOWN_METHOD) {
+ if (ajp_msg_append_uint16(msg, (apr_uint16_t)sc)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00969)
+ "ajp_marshal_into_msgb: "
+ "Error appending the header name");
+ return AJP_EOVERFLOW;
+ }
+ }
+ else {
+ if (ajp_msg_append_string(msg, elts[i].key)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00970)
+ "ajp_marshal_into_msgb: "
+ "Error appending the header name");
+ return AJP_EOVERFLOW;
+ }
+ }
+
+ if (ajp_msg_append_string(msg, elts[i].val)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00971)
+ "ajp_marshal_into_msgb: "
+ "Error appending the header value");
+ return AJP_EOVERFLOW;
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r,
+ "ajp_marshal_into_msgb: Header[%d] [%s] = [%s]",
+ i, elts[i].key, elts[i].val);
+ }
+
+ if (secret) {
+ if (ajp_msg_append_uint8(msg, SC_A_SECRET) ||
+ ajp_msg_append_string(msg, secret)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(03228)
+ "ajp_marshal_into_msgb: "
+ "Error appending secret");
+ return APR_EGENERAL;
+ }
+ }
+
+ if (r->user) {
+ if (ajp_msg_append_uint8(msg, SC_A_REMOTE_USER) ||
+ ajp_msg_append_string(msg, r->user)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00972)
+ "ajp_marshal_into_msgb: "
+ "Error appending the remote user");
+ return AJP_EOVERFLOW;
+ }
+ }
+ if (r->ap_auth_type) {
+ if (ajp_msg_append_uint8(msg, SC_A_AUTH_TYPE) ||
+ ajp_msg_append_string(msg, r->ap_auth_type)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00973)
+ "ajp_marshal_into_msgb: "
+ "Error appending the auth type");
+ return AJP_EOVERFLOW;
+ }
+ }
+ /* XXXX ebcdic (args converted?) */
+ if (uri->query) {
+ if (ajp_msg_append_uint8(msg, SC_A_QUERY_STRING) ||
+ ajp_msg_append_string(msg, uri->query)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00974)
+ "ajp_marshal_into_msgb: "
+ "Error appending the query string");
+ return AJP_EOVERFLOW;
+ }
+ }
+ if ((session_route = apr_table_get(r->notes, "session-route"))) {
+ if (ajp_msg_append_uint8(msg, SC_A_JVM_ROUTE) ||
+ ajp_msg_append_string(msg, session_route)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00975)
+ "ajp_marshal_into_msgb: "
+ "Error appending the jvm route");
+ return AJP_EOVERFLOW;
+ }
+ }
+/* XXX: Is the subprocess_env a right place?
+ * <Location /examples>
+ * ProxyPass ajp://remote:8009/servlets-examples
+ * SetEnv SSL_SESSION_ID CUSTOM_SSL_SESSION_ID
+ * </Location>
+ */
+ /*
+ * Only lookup SSL variables if we are currently running HTTPS.
+ * Furthermore ensure that only variables get set in the AJP message
+ * that are not NULL and not empty.
+ */
+ if (is_ssl) {
+ if ((envvar = ap_proxy_ssl_val(r->pool, r->server, r->connection, r,
+ AJP13_SSL_CLIENT_CERT_INDICATOR))
+ && envvar[0]) {
+ if (ajp_msg_append_uint8(msg, SC_A_SSL_CERT)
+ || ajp_msg_append_string(msg, envvar)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00976)
+ "ajp_marshal_into_msgb: "
+ "Error appending the SSL certificates");
+ return AJP_EOVERFLOW;
+ }
+ }
+
+ if ((envvar = ap_proxy_ssl_val(r->pool, r->server, r->connection, r,
+ AJP13_SSL_CIPHER_INDICATOR))
+ && envvar[0]) {
+ if (ajp_msg_append_uint8(msg, SC_A_SSL_CIPHER)
+ || ajp_msg_append_string(msg, envvar)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00977)
+ "ajp_marshal_into_msgb: "
+ "Error appending the SSL ciphers");
+ return AJP_EOVERFLOW;
+ }
+ }
+
+ if ((envvar = ap_proxy_ssl_val(r->pool, r->server, r->connection, r,
+ AJP13_SSL_SESSION_INDICATOR))
+ && envvar[0]) {
+ if (ajp_msg_append_uint8(msg, SC_A_SSL_SESSION)
+ || ajp_msg_append_string(msg, envvar)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00978)
+ "ajp_marshal_into_msgb: "
+ "Error appending the SSL session");
+ return AJP_EOVERFLOW;
+ }
+ }
+
+ /* ssl_key_size is required by Servlet 2.3 API */
+ if ((envvar = ap_proxy_ssl_val(r->pool, r->server, r->connection, r,
+ AJP13_SSL_KEY_SIZE_INDICATOR))
+ && envvar[0]) {
+
+ if (ajp_msg_append_uint8(msg, SC_A_SSL_KEY_SIZE)
+ || ajp_msg_append_uint16(msg, (unsigned short) atoi(envvar))) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00979)
+ "ajp_marshal_into_msgb: "
+ "Error appending the SSL key size");
+ return APR_EGENERAL;
+ }
+ }
+ }
+ /* If the method was unrecognized, encode it as an attribute */
+ if (method == SC_M_JK_STORED) {
+ if (ajp_msg_append_uint8(msg, SC_A_STORED_METHOD)
+ || ajp_msg_append_string(msg, r->method)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02438)
+ "ajp_marshal_into_msgb: "
+ "Error appending the method '%s' as request attribute",
+ r->method);
+ return AJP_EOVERFLOW;
+ }
+ }
+ /* Forward the SSL protocol name.
+ * Modern Tomcat versions know how to retrieve
+ * the protocol name from this attribute.
+ */
+ if (is_ssl) {
+ if ((envvar = ap_proxy_ssl_val(r->pool, r->server, r->connection, r,
+ AJP13_SSL_PROTOCOL_INDICATOR))
+ && envvar[0]) {
+ const char *key = SC_A_SSL_PROTOCOL;
+ if (ajp_msg_append_uint8(msg, SC_A_REQ_ATTRIBUTE) ||
+ ajp_msg_append_string(msg, key) ||
+ ajp_msg_append_string(msg, envvar)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02830)
+ "ajp_marshal_into_msgb: "
+ "Error appending attribute %s=%s",
+ key, envvar);
+ return AJP_EOVERFLOW;
+ }
+ }
+ }
+ /* Forward the remote port information, which was forgotten
+ * from the builtin data of the AJP 13 protocol.
+ * Since the servlet spec allows to retrieve it via getRemotePort(),
+ * we provide the port to the Tomcat connector as a request
+ * attribute. Modern Tomcat versions know how to retrieve
+ * the remote port from this attribute.
+ */
+ {
+ const char *key = SC_A_REQ_REMOTE_PORT;
+ char *val = apr_itoa(r->pool, r->useragent_addr->port);
+ if (ajp_msg_append_uint8(msg, SC_A_REQ_ATTRIBUTE) ||
+ ajp_msg_append_string(msg, key) ||
+ ajp_msg_append_string(msg, val)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00980)
+ "ajp_marshal_into_msgb: "
+ "Error appending attribute %s=%s",
+ key, val);
+ return AJP_EOVERFLOW;
+ }
+ }
+ /* Forward the local ip address information, which was forgotten
+ * from the builtin data of the AJP 13 protocol.
+ * Since the servlet spec allows to retrieve it via getLocalAddr(),
+ * we provide the address to the Tomcat connector as a request
+ * attribute. Modern Tomcat versions know how to retrieve
+ * the local address from this attribute.
+ */
+ {
+ const char *key = SC_A_REQ_LOCAL_ADDR;
+ char *val = r->connection->local_ip;
+ if (ajp_msg_append_uint8(msg, SC_A_REQ_ATTRIBUTE) ||
+ ajp_msg_append_string(msg, key) ||
+ ajp_msg_append_string(msg, val)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02646)
+ "ajp_marshal_into_msgb: "
+ "Error appending attribute %s=%s",
+ key, val);
+ return AJP_EOVERFLOW;
+ }
+ }
+ /* Use the environment vars prefixed with AJP_
+ * and pass it to the header striping that prefix.
+ */
+ for (i = 0; i < (apr_uint32_t)arr->nelts; i++) {
+ if (!strncmp(elts[i].key, "AJP_", 4)) {
+ if (ajp_msg_append_uint8(msg, SC_A_REQ_ATTRIBUTE) ||
+ ajp_msg_append_string(msg, elts[i].key + 4) ||
+ ajp_msg_append_string(msg, elts[i].val)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00981)
+ "ajp_marshal_into_msgb: "
+ "Error appending attribute %s=%s",
+ elts[i].key, elts[i].val);
+ return AJP_EOVERFLOW;
+ }
+ }
+ }
+
+ if (ajp_msg_append_uint8(msg, SC_A_ARE_DONE)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00982)
+ "ajp_marshal_into_msgb: "
+ "Error appending the message end");
+ return AJP_EOVERFLOW;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, r,
+ "ajp_marshal_into_msgb: Done");
+ return APR_SUCCESS;
+}
+
+/*
+AJPV13_RESPONSE/AJPV14_RESPONSE:=
+ response_prefix (2)
+ status (short)
+ status_msg (short)
+ num_headers (short)
+ num_headers*(res_header_name header_value)
+ *body_chunk
+ terminator boolean <! -- recycle connection or not -->
+
+req_header_name :=
+ sc_req_header_name | (string)
+
+res_header_name :=
+ sc_res_header_name | (string)
+
+header_value :=
+ (string)
+
+body_chunk :=
+ length (short)
+ body length*(var binary)
+
+ */
+
+static int addit_dammit(void *v, const char *key, const char *val)
+{
+ apr_table_addn(v, key, val);
+ return 1;
+}
+
+static apr_status_t ajp_unmarshal_response(ajp_msg_t *msg,
+ request_rec *r,
+ proxy_dir_conf *dconf)
+{
+ apr_uint16_t status;
+ apr_status_t rc;
+ const char *ptr;
+ apr_uint16_t num_headers;
+ int i;
+
+ rc = ajp_msg_get_uint16(msg, &status);
+
+ if (rc != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00983)
+ "ajp_unmarshal_response: Null status");
+ return rc;
+ }
+ r->status = status;
+
+ rc = ajp_msg_get_string(msg, &ptr);
+ if (rc == APR_SUCCESS) {
+#if APR_CHARSET_EBCDIC /* copy only if we have to */
+ ptr = apr_pstrdup(r->pool, ptr);
+ ap_xlate_proto_from_ascii(ptr, strlen(ptr));
+#endif
+ r->status_line = apr_psprintf(r->pool, "%d %s", status, ptr);
+ }
+ else {
+ r->status_line = NULL;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r,
+ "ajp_unmarshal_response: status = %d", status);
+
+ rc = ajp_msg_get_uint16(msg, &num_headers);
+ if (rc == APR_SUCCESS) {
+ apr_table_t *save_table;
+
+ /* First, tuck away all already existing cookies */
+ /*
+ * Could optimize here, but just in case we want to
+ * also save other headers, keep this logic.
+ */
+ save_table = apr_table_make(r->pool, num_headers + 2);
+ apr_table_do(addit_dammit, save_table, r->headers_out,
+ "Set-Cookie", NULL);
+ r->headers_out = save_table;
+ }
+ else {
+ /*
+ * Reset headers, but not to NULL because things below the chain expect
+ * this to be non NULL e.g. the ap_content_length_filter.
+ */
+ r->headers_out = apr_table_make(r->pool, 1);
+ num_headers = 0;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10405)
+ "ajp_unmarshal_response: Bad number of headers");
+ return rc;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r,
+ "ajp_unmarshal_response: Number of headers is = %d",
+ num_headers);
+
+ for (i = 0; i < (int)num_headers; i++) {
+ apr_uint16_t name;
+ const char *stringname;
+ const char *value;
+ rc = ajp_msg_peek_uint16(msg, &name);
+ if (rc != APR_SUCCESS) {
+ return rc;
+ }
+
+ if ((name & 0XFF00) == 0XA000) {
+ ajp_msg_get_uint16(msg, &name);
+ stringname = long_res_header_for_sc(name);
+ if (stringname == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00984)
+ "ajp_unmarshal_response: "
+ "No such sc (%08x)",
+ name);
+ return AJP_EBAD_HEADER;
+ }
+ }
+ else {
+ name = 0;
+ rc = ajp_msg_get_string(msg, &stringname);
+ if (rc != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00985)
+ "ajp_unmarshal_response: "
+ "Null header name");
+ return rc;
+ }
+ ap_xlate_proto_from_ascii(stringname, strlen(stringname));
+ }
+
+ rc = ajp_msg_get_string(msg, &value);
+ if (rc != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00986)
+ "ajp_unmarshal_response: "
+ "Null header value");
+ return rc;
+ }
+
+ /* Set-Cookie need additional processing */
+ if (!ap_cstr_casecmp(stringname, "Set-Cookie")) {
+ value = ap_proxy_cookie_reverse_map(r, dconf, value);
+ }
+ /* Location, Content-Location, URI and Destination need additional
+ * processing */
+ else if (!ap_cstr_casecmp(stringname, "Location")
+ || !ap_cstr_casecmp(stringname, "Content-Location")
+ || !ap_cstr_casecmp(stringname, "URI")
+ || !ap_cstr_casecmp(stringname, "Destination"))
+ {
+ value = ap_proxy_location_reverse_map(r, dconf, value);
+ }
+
+ ap_xlate_proto_from_ascii(value, strlen(value));
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r,
+ "ajp_unmarshal_response: Header[%d] [%s] = [%s]",
+ i, stringname, value);
+
+ apr_table_add(r->headers_out, stringname, value);
+
+ /* Content-type needs an additional handling */
+ if (ap_cstr_casecmp(stringname, "Content-Type") == 0) {
+ /* add corresponding filter */
+ ap_set_content_type(r, apr_pstrdup(r->pool, value));
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r,
+ "ajp_unmarshal_response: ap_set_content_type to '%s'", value);
+ }
+ }
+
+ return APR_SUCCESS;
+}
+
+/*
+ * Build the ajp header message and send it
+ */
+apr_status_t ajp_send_header(apr_socket_t *sock,
+ request_rec *r,
+ apr_size_t buffsize,
+ apr_uri_t *uri,
+ const char *secret)
+{
+ ajp_msg_t *msg;
+ apr_status_t rc;
+
+ rc = ajp_msg_create(r->pool, buffsize, &msg);
+ if (rc != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00987)
+ "ajp_send_header: ajp_msg_create failed");
+ return rc;
+ }
+
+ rc = ajp_marshal_into_msgb(msg, r, uri, secret);
+ if (rc != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00988)
+ "ajp_send_header: ajp_marshal_into_msgb failed");
+ return rc;
+ }
+
+ rc = ajp_ilink_send(sock, msg);
+ ajp_msg_log(r, msg, "ajp_send_header: ajp_ilink_send packet dump");
+ if (rc != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00989)
+ "ajp_send_header: ajp_ilink_send failed");
+ return rc;
+ }
+
+ return APR_SUCCESS;
+}
+
+/*
+ * Read the ajp message and return the type of the message.
+ */
+apr_status_t ajp_read_header(apr_socket_t *sock,
+ request_rec *r,
+ apr_size_t buffsize,
+ ajp_msg_t **msg)
+{
+ apr_byte_t result;
+ apr_status_t rc;
+
+ if (*msg) {
+ rc = ajp_msg_reuse(*msg);
+ if (rc != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00990)
+ "ajp_read_header: ajp_msg_reuse failed");
+ return rc;
+ }
+ }
+ else {
+ rc = ajp_msg_create(r->pool, buffsize, msg);
+ if (rc != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00991)
+ "ajp_read_header: ajp_msg_create failed");
+ return rc;
+ }
+ }
+ ajp_msg_reset(*msg);
+ rc = ajp_ilink_receive(sock, *msg);
+ if (rc != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00992)
+ "ajp_read_header: ajp_ilink_receive failed");
+ return rc;
+ }
+ ajp_msg_log(r, *msg, "ajp_read_header: ajp_ilink_receive packet dump");
+ rc = ajp_msg_peek_uint8(*msg, &result);
+ if (rc != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00993)
+ "ajp_read_header: ajp_msg_peek_uint8 failed");
+ return rc;
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "ajp_read_header: ajp_ilink_received %s (0x%02x)",
+ ajp_type_str(result), result);
+ return APR_SUCCESS;
+}
+
+/* parse the msg to read the type */
+int ajp_parse_type(request_rec *r, ajp_msg_t *msg)
+{
+ apr_byte_t result;
+ ajp_msg_peek_uint8(msg, &result);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE6, 0, r,
+ "ajp_parse_type: got %s (0x%02x)",
+ ajp_type_str(result), result);
+ return (int) result;
+}
+
+/* parse the header */
+apr_status_t ajp_parse_header(request_rec *r, proxy_dir_conf *conf,
+ ajp_msg_t *msg)
+{
+ apr_byte_t result;
+ apr_status_t rc;
+
+ rc = ajp_msg_get_uint8(msg, &result);
+ if (rc != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00994)
+ "ajp_parse_headers: ajp_msg_get_byte failed");
+ return rc;
+ }
+ if (result != CMD_AJP13_SEND_HEADERS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00995)
+ "ajp_parse_headers: wrong type %s (0x%02x) expecting %s (0x%02x)",
+ ajp_type_str(result), result,
+ ajp_type_str(CMD_AJP13_SEND_HEADERS), CMD_AJP13_SEND_HEADERS);
+ return AJP_EBAD_HEADER;
+ }
+ return ajp_unmarshal_response(msg, r, conf);
+}
+
+/* parse the body and return data address and length */
+apr_status_t ajp_parse_data(request_rec *r, ajp_msg_t *msg,
+ apr_uint16_t *len, char **ptr)
+{
+ apr_byte_t result;
+ apr_status_t rc;
+ apr_uint16_t expected_len;
+
+ rc = ajp_msg_get_uint8(msg, &result);
+ if (rc != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00996)
+ "ajp_parse_data: ajp_msg_get_byte failed");
+ return rc;
+ }
+ if (result != CMD_AJP13_SEND_BODY_CHUNK) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00997)
+ "ajp_parse_data: wrong type %s (0x%02x) expecting %s (0x%02x)",
+ ajp_type_str(result), result,
+ ajp_type_str(CMD_AJP13_SEND_BODY_CHUNK), CMD_AJP13_SEND_BODY_CHUNK);
+ return AJP_EBAD_HEADER;
+ }
+ rc = ajp_msg_get_uint16(msg, len);
+ if (rc != APR_SUCCESS) {
+ return rc;
+ }
+ /*
+ * msg->len contains the complete length of the message including all
+ * headers. So the expected length for a CMD_AJP13_SEND_BODY_CHUNK is
+ * msg->len minus the sum of
+ * AJP_HEADER_LEN : The length of the header to every AJP message.
+ * AJP_HEADER_SZ_LEN : The header giving the size of the chunk.
+ * 1 : The CMD_AJP13_SEND_BODY_CHUNK indicator byte (0x03).
+ * 1 : The last byte of this message always seems to be
+ * 0x00 and is not part of the chunk.
+ */
+ expected_len = msg->len - (AJP_HEADER_LEN + AJP_HEADER_SZ_LEN + 1 + 1);
+ if (*len != expected_len) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00998)
+ "ajp_parse_data: Wrong chunk length. Length of chunk is %i,"
+ " expected length is %i.", *len, expected_len);
+ return AJP_EBAD_HEADER;
+ }
+ *ptr = (char *)&(msg->buf[msg->pos]);
+ return APR_SUCCESS;
+}
+
+/* Check the reuse flag in CMD_AJP13_END_RESPONSE */
+apr_status_t ajp_parse_reuse(request_rec *r, ajp_msg_t *msg,
+ apr_byte_t *reuse)
+{
+ apr_byte_t result;
+ apr_status_t rc;
+
+ rc = ajp_msg_get_uint8(msg, &result);
+ if (rc != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00999)
+ "ajp_parse_reuse: ajp_msg_get_byte failed");
+ return rc;
+ }
+ if (result != CMD_AJP13_END_RESPONSE) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01000)
+ "ajp_parse_reuse: wrong type %s (0x%02x) expecting %s (0x%02x)",
+ ajp_type_str(result), result,
+ ajp_type_str(CMD_AJP13_END_RESPONSE), CMD_AJP13_END_RESPONSE);
+ return AJP_EBAD_HEADER;
+ }
+ return ajp_msg_get_uint8(msg, reuse);
+}
+
+/*
+ * Allocate a msg to send data
+ */
+apr_status_t ajp_alloc_data_msg(apr_pool_t *pool, char **ptr, apr_size_t *len,
+ ajp_msg_t **msg)
+{
+ apr_status_t rc;
+
+ if ((rc = ajp_msg_create(pool, *len, msg)) != APR_SUCCESS)
+ return rc;
+ ajp_msg_reset(*msg);
+ *ptr = (char *)&((*msg)->buf[6]);
+ *len = *len - 6;
+
+ return APR_SUCCESS;
+}
+
+/*
+ * Send the data message
+ */
+apr_status_t ajp_send_data_msg(apr_socket_t *sock,
+ ajp_msg_t *msg, apr_size_t len)
+{
+
+ msg->buf[4] = (apr_byte_t)((len >> 8) & 0xFF);
+ msg->buf[5] = (apr_byte_t)(len & 0xFF);
+
+ msg->len += len + 2; /* + 1 XXXX where is '\0' */
+
+ return ajp_ilink_send(sock, msg);
+
+}
diff --git a/modules/proxy/ajp_header.h b/modules/proxy/ajp_header.h
new file mode 100644
index 0000000..4c22ac7
--- /dev/null
+++ b/modules/proxy/ajp_header.h
@@ -0,0 +1,195 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file ajp_header.h
+ * @brief AJP defines
+ *
+ * @addtogroup AJP_defines
+ * @{
+ */
+
+#ifndef AJP_HEADER_H
+#define AJP_HEADER_H
+
+/*
+ * Conditional request attributes
+ *
+ */
+#define SC_A_CONTEXT (unsigned char)1
+#define SC_A_SERVLET_PATH (unsigned char)2
+#define SC_A_REMOTE_USER (unsigned char)3
+#define SC_A_AUTH_TYPE (unsigned char)4
+#define SC_A_QUERY_STRING (unsigned char)5
+#define SC_A_JVM_ROUTE (unsigned char)6
+#define SC_A_SSL_CERT (unsigned char)7
+#define SC_A_SSL_CIPHER (unsigned char)8
+#define SC_A_SSL_SESSION (unsigned char)9
+#define SC_A_REQ_ATTRIBUTE (unsigned char)10
+#define SC_A_SSL_KEY_SIZE (unsigned char)11 /* only in if JkOptions +ForwardKeySize */
+#define SC_A_SECRET (unsigned char)12
+#define SC_A_STORED_METHOD (unsigned char)13
+#define SC_A_ARE_DONE (unsigned char)0xFF
+
+/*
+ * AJP private request attributes
+ *
+ * The following request attribute is recognized by Tomcat
+ * to contain the SSL protocol name
+ */
+#define SC_A_SSL_PROTOCOL ("AJP_SSL_PROTOCOL")
+/*
+ * The following request attribute is recognized by Tomcat
+ * to contain the forwarded remote port.
+ */
+#define SC_A_REQ_REMOTE_PORT ("AJP_REMOTE_PORT")
+/*
+ * The following request attribute is recognized by Tomcat
+ * to contain the forwarded local ip address.
+ */
+#define SC_A_REQ_LOCAL_ADDR ("AJP_LOCAL_ADDR")
+
+/*
+ * Request methods, coded as numbers instead of strings.
+ * The list of methods was taken from Section 5.1.1 of RFC 2616,
+ * RFC 2518, the ACL IETF draft, and the DeltaV IESG Proposed Standard.
+ * Method = "OPTIONS"
+ * | "GET"
+ * | "HEAD"
+ * | "POST"
+ * | "PUT"
+ * | "DELETE"
+ * | "TRACE"
+ * | "PROPFIND"
+ * | "PROPPATCH"
+ * | "MKCOL"
+ * | "COPY"
+ * | "MOVE"
+ * | "LOCK"
+ * | "UNLOCK"
+ * | "ACL"
+ * | "REPORT"
+ * | "VERSION-CONTROL"
+ * | "CHECKIN"
+ * | "CHECKOUT"
+ * | "UNCHECKOUT"
+ * | "SEARCH"
+ * | "MKWORKSPACE"
+ * | "UPDATE"
+ * | "LABEL"
+ * | "MERGE"
+ * | "BASELINE-CONTROL"
+ * | "MKACTIVITY"
+ *
+ */
+#define SC_M_OPTIONS (unsigned char)1
+#define SC_M_GET (unsigned char)2
+#define SC_M_HEAD (unsigned char)3
+#define SC_M_POST (unsigned char)4
+#define SC_M_PUT (unsigned char)5
+#define SC_M_DELETE (unsigned char)6
+#define SC_M_TRACE (unsigned char)7
+#define SC_M_PROPFIND (unsigned char)8
+#define SC_M_PROPPATCH (unsigned char)9
+#define SC_M_MKCOL (unsigned char)10
+#define SC_M_COPY (unsigned char)11
+#define SC_M_MOVE (unsigned char)12
+#define SC_M_LOCK (unsigned char)13
+#define SC_M_UNLOCK (unsigned char)14
+#define SC_M_ACL (unsigned char)15
+#define SC_M_REPORT (unsigned char)16
+#define SC_M_VERSION_CONTROL (unsigned char)17
+#define SC_M_CHECKIN (unsigned char)18
+#define SC_M_CHECKOUT (unsigned char)19
+#define SC_M_UNCHECKOUT (unsigned char)20
+#define SC_M_SEARCH (unsigned char)21
+#define SC_M_MKWORKSPACE (unsigned char)22
+#define SC_M_UPDATE (unsigned char)23
+#define SC_M_LABEL (unsigned char)24
+#define SC_M_MERGE (unsigned char)25
+#define SC_M_BASELINE_CONTROL (unsigned char)26
+#define SC_M_MKACTIVITY (unsigned char)27
+#define SC_M_JK_STORED (unsigned char)0xFF
+
+
+/*
+ * Frequent request headers, these headers are coded as numbers
+ * instead of strings.
+ *
+ * Accept
+ * Accept-Charset
+ * Accept-Encoding
+ * Accept-Language
+ * Authorization
+ * Connection
+ * Content-Type
+ * Content-Length
+ * Cookie
+ * Cookie2
+ * Host
+ * Pragma
+ * Referer
+ * User-Agent
+ *
+ */
+
+#define SC_ACCEPT (unsigned short)0xA001
+#define SC_ACCEPT_CHARSET (unsigned short)0xA002
+#define SC_ACCEPT_ENCODING (unsigned short)0xA003
+#define SC_ACCEPT_LANGUAGE (unsigned short)0xA004
+#define SC_AUTHORIZATION (unsigned short)0xA005
+#define SC_CONNECTION (unsigned short)0xA006
+#define SC_CONTENT_TYPE (unsigned short)0xA007
+#define SC_CONTENT_LENGTH (unsigned short)0xA008
+#define SC_COOKIE (unsigned short)0xA009
+#define SC_COOKIE2 (unsigned short)0xA00A
+#define SC_HOST (unsigned short)0xA00B
+#define SC_PRAGMA (unsigned short)0xA00C
+#define SC_REFERER (unsigned short)0xA00D
+#define SC_USER_AGENT (unsigned short)0xA00E
+
+/*
+ * Frequent response headers, these headers are coded as numbers
+ * instead of strings.
+ *
+ * Content-Type
+ * Content-Language
+ * Content-Length
+ * Date
+ * Last-Modified
+ * Location
+ * Set-Cookie
+ * Servlet-Engine
+ * Status
+ * WWW-Authenticate
+ *
+ */
+
+#define SC_RESP_CONTENT_TYPE (unsigned short)0xA001
+#define SC_RESP_CONTENT_LANGUAGE (unsigned short)0xA002
+#define SC_RESP_CONTENT_LENGTH (unsigned short)0xA003
+#define SC_RESP_DATE (unsigned short)0xA004
+#define SC_RESP_LAST_MODIFIED (unsigned short)0xA005
+#define SC_RESP_LOCATION (unsigned short)0xA006
+#define SC_RESP_SET_COOKIE (unsigned short)0xA007
+#define SC_RESP_SET_COOKIE2 (unsigned short)0xA008
+#define SC_RESP_SERVLET_ENGINE (unsigned short)0xA009
+#define SC_RESP_STATUS (unsigned short)0xA00A
+#define SC_RESP_WWW_AUTHENTICATE (unsigned short)0xA00B
+#define SC_RES_HEADERS_NUM 11
+
+#endif /* AJP_HEADER_H */
+/** @} */
diff --git a/modules/proxy/ajp_link.c b/modules/proxy/ajp_link.c
new file mode 100644
index 0000000..4f8a9ef
--- /dev/null
+++ b/modules/proxy/ajp_link.c
@@ -0,0 +1,115 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ajp.h"
+
+APLOG_USE_MODULE(proxy_ajp);
+
+apr_status_t ajp_ilink_send(apr_socket_t *sock, ajp_msg_t *msg)
+{
+ char *buf;
+ apr_status_t status;
+ apr_size_t length;
+
+ ajp_msg_end(msg);
+
+ length = msg->len;
+ buf = (char *)msg->buf;
+
+ do {
+ apr_size_t written = length;
+
+ status = apr_socket_send(sock, buf, &written);
+ if (status != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, status, NULL, APLOGNO(01029)
+ "ajp_ilink_send(): send failed");
+ return status;
+ }
+ length -= written;
+ buf += written;
+ } while (length);
+
+ return APR_SUCCESS;
+}
+
+
+static apr_status_t ilink_read(apr_socket_t *sock, apr_byte_t *buf,
+ apr_size_t len)
+{
+ apr_size_t length = len;
+ apr_size_t rdlen = 0;
+ apr_status_t status;
+
+ while (rdlen < len) {
+
+ status = apr_socket_recv(sock, (char *)(buf + rdlen), &length);
+
+ if (status == APR_EOF)
+ return status; /* socket closed. */
+ else if (APR_STATUS_IS_EAGAIN(status))
+ continue;
+ else if (status != APR_SUCCESS)
+ return status; /* any error. */
+
+ rdlen += length;
+ length = len - rdlen;
+ }
+ return APR_SUCCESS;
+}
+
+
+apr_status_t ajp_ilink_receive(apr_socket_t *sock, ajp_msg_t *msg)
+{
+ apr_status_t status;
+ apr_size_t hlen;
+ apr_size_t blen;
+
+ hlen = msg->header_len;
+
+ status = ilink_read(sock, msg->buf, hlen);
+
+ if (status != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, status, NULL, APLOGNO(01030)
+ "ajp_ilink_receive() can't receive header");
+ return (APR_STATUS_IS_TIMEUP(status) ? APR_TIMEUP : AJP_ENO_HEADER);
+ }
+
+ status = ajp_msg_check_header(msg, &blen);
+
+ if (status != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(01031)
+ "ajp_ilink_receive() received bad header");
+ return AJP_EBAD_HEADER;
+ }
+
+ status = ilink_read(sock, msg->buf + hlen, blen);
+
+ if (status != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, status, NULL, APLOGNO(01032)
+ "ajp_ilink_receive() error while receiving message body "
+ "of length %" APR_SIZE_T_FMT,
+ hlen);
+ return AJP_EBAD_MESSAGE;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, APLOGNO(01033)
+ "ajp_ilink_receive() received packet len=%" APR_SIZE_T_FMT
+ "type=%d",
+ blen, (int)msg->buf[hlen]);
+
+ return APR_SUCCESS;
+}
+
diff --git a/modules/proxy/ajp_msg.c b/modules/proxy/ajp_msg.c
new file mode 100644
index 0000000..3367b5d
--- /dev/null
+++ b/modules/proxy/ajp_msg.c
@@ -0,0 +1,641 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ajp.h"
+
+APLOG_USE_MODULE(proxy_ajp);
+
+#define AJP_MSG_DUMP_BYTES_PER_LINE 16
+/* 2 hex digits plus space plus one char per dumped byte */
+/* plus prefix plus separator plus '\0' */
+#define AJP_MSG_DUMP_PREFIX_LENGTH strlen("XXXX ")
+#define AJP_MSG_DUMP_LINE_LENGTH ((AJP_MSG_DUMP_BYTES_PER_LINE * \
+ strlen("XX .")) + \
+ AJP_MSG_DUMP_PREFIX_LENGTH + \
+ strlen(" - ") + 1)
+
+static char *hex_table = "0123456789ABCDEF";
+
+/**
+ * Dump the given number of bytes on an AJP Message
+ *
+ * @param pool pool to allocate from
+ * @param msg AJP Message to dump
+ * @param err error string to display
+ * @param count the number of bytes to dump
+ * @param buf buffer pointer for dump message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_dump(apr_pool_t *pool, ajp_msg_t *msg, char *err,
+ apr_size_t count, char **buf)
+{
+ apr_size_t i, j;
+ char *current;
+ apr_size_t bl, rl;
+ apr_byte_t x;
+ apr_size_t len = msg->len;
+ apr_size_t line_len;
+
+ /* Display only first "count" bytes */
+ if (len > count)
+ len = count;
+ /* First the space needed for the first line */
+ bl = strlen(err) + 3 * (strlen(" XXX=") + 20) + 1 +
+ /* Now for the data lines */
+ (len + 15) / 16 * AJP_MSG_DUMP_LINE_LENGTH;
+ *buf = apr_palloc(pool, bl);
+ if (!*buf)
+ return APR_ENOMEM;
+ apr_snprintf(*buf, bl,
+ "%s pos=%" APR_SIZE_T_FMT
+ " len=%" APR_SIZE_T_FMT " max=%" APR_SIZE_T_FMT "\n",
+ err, msg->pos, msg->len, msg->max_size);
+ current = *buf + strlen(*buf);
+ for (i = 0; i < len; i += AJP_MSG_DUMP_BYTES_PER_LINE) {
+ /* Safety check: do we have enough buffer for another line? */
+ rl = bl - (current - *buf);
+ if (AJP_MSG_DUMP_LINE_LENGTH > rl) {
+ *(current - 1) = '\0';
+ return APR_ENOMEM;
+ }
+ apr_snprintf(current, rl, "%.4lx ", (unsigned long)i);
+ current += AJP_MSG_DUMP_PREFIX_LENGTH;
+ line_len = len - i;
+ if (line_len > AJP_MSG_DUMP_BYTES_PER_LINE) {
+ line_len = AJP_MSG_DUMP_BYTES_PER_LINE;
+ }
+ for (j = 0; j < line_len; j++) {
+ x = msg->buf[i + j];
+
+ *current++ = hex_table[x >> 4];
+ *current++ = hex_table[x & 0x0f];
+ *current++ = ' ';
+ }
+ *current++ = ' ';
+ *current++ = '-';
+ *current++ = ' ';
+ for (j = 0; j < line_len; j++) {
+ x = msg->buf[i + j];
+
+ if (x > 0x20 && x < 0x7F) {
+ *current++ = x;
+ }
+ else {
+ *current++ = '.';
+ }
+ }
+ *current++ = '\n';
+ }
+ *(current - 1) = '\0';
+
+ return APR_SUCCESS;
+}
+
+/**
+ * Log an AJP message
+ *
+ * @param request The current request
+ * @param msg AJP Message to dump
+ * @param err error string to display
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_log(request_rec *r, ajp_msg_t *msg, char *err)
+{
+ int level;
+ apr_size_t count;
+ char *buf, *next;
+ apr_status_t rc = APR_SUCCESS;
+
+ if (APLOGrtrace7(r)) {
+ level = APLOG_TRACE7;
+ count = 1024;
+ if (APLOGrtrace8(r)) {
+ level = APLOG_TRACE8;
+ count = AJP_MAX_BUFFER_SZ;
+ }
+ rc = ajp_msg_dump(r->pool, msg, err, count, &buf);
+ if (rc == APR_SUCCESS) {
+ while ((next = ap_strchr(buf, '\n'))) {
+ *next = '\0';
+ /* Intentional no APLOGNO */
+ ap_log_rerror(APLOG_MARK, level, 0, r, "%s", buf);
+ buf = next + 1;
+ }
+ /* Intentional no APLOGNO */
+ ap_log_rerror(APLOG_MARK, level, 0, r, "%s", buf);
+ }
+ }
+ return rc;
+}
+
+/**
+ * Check a new AJP Message by looking at signature and return its size
+ *
+ * @param msg AJP Message to check
+ * @param len Pointer to returned len
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_check_header(ajp_msg_t *msg, apr_size_t *len)
+{
+ apr_byte_t *head = msg->buf;
+ apr_size_t msglen;
+
+ if (!((head[0] == 0x41 && head[1] == 0x42) ||
+ (head[0] == 0x12 && head[1] == 0x34))) {
+
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(01080)
+ "ajp_msg_check_header() got bad signature %02x%02x",
+ head[0], head[1]);
+
+ return AJP_EBAD_SIGNATURE;
+ }
+
+ msglen = ((head[2] & 0xff) << 8);
+ msglen += (head[3] & 0xFF);
+
+ if (msglen > msg->max_size) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(01081)
+ "ajp_msg_check_header() incoming message is "
+ "too big %" APR_SIZE_T_FMT ", max is %" APR_SIZE_T_FMT,
+ msglen, msg->max_size);
+ return AJP_ETOBIG;
+ }
+
+ msg->len = msglen + AJP_HEADER_LEN;
+ msg->pos = AJP_HEADER_LEN;
+ *len = msglen;
+
+ return APR_SUCCESS;
+}
+
+/**
+ * Reset an AJP Message
+ *
+ * @param msg AJP Message to reset
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_reset(ajp_msg_t *msg)
+{
+ msg->len = AJP_HEADER_LEN;
+ msg->pos = AJP_HEADER_LEN;
+
+ return APR_SUCCESS;
+}
+
+/**
+ * Reuse an AJP Message
+ *
+ * @param msg AJP Message to reuse
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_reuse(ajp_msg_t *msg)
+{
+ apr_byte_t *buf;
+ apr_size_t max_size;
+
+ buf = msg->buf;
+ max_size = msg->max_size;
+ memset(msg, 0, sizeof(ajp_msg_t));
+ msg->buf = buf;
+ msg->max_size = max_size;
+ msg->header_len = AJP_HEADER_LEN;
+ ajp_msg_reset(msg);
+ return APR_SUCCESS;
+}
+
+/**
+ * Mark the end of an AJP Message
+ *
+ * @param msg AJP Message to end
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_end(ajp_msg_t *msg)
+{
+ apr_size_t len = msg->len - AJP_HEADER_LEN;
+
+ if (msg->server_side) {
+ msg->buf[0] = 0x41;
+ msg->buf[1] = 0x42;
+ }
+ else {
+ msg->buf[0] = 0x12;
+ msg->buf[1] = 0x34;
+ }
+
+ msg->buf[2] = (apr_byte_t)((len >> 8) & 0xFF);
+ msg->buf[3] = (apr_byte_t)(len & 0xFF);
+
+ return APR_SUCCESS;
+}
+
+static APR_INLINE int ajp_log_overflow(ajp_msg_t *msg, const char *context)
+{
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(03229)
+ "%s(): BufferOverflowException %" APR_SIZE_T_FMT
+ " %" APR_SIZE_T_FMT,
+ context, msg->pos, msg->len);
+ return AJP_EOVERFLOW;
+}
+
+/**
+ * Add an unsigned 32bits value to AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param value value to add to AJP Message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_append_uint32(ajp_msg_t *msg, apr_uint32_t value)
+{
+ apr_size_t len = msg->len;
+
+ if ((len + 4) > msg->max_size) {
+ return ajp_log_overflow(msg, "ajp_msg_append_uint32");
+ }
+
+ msg->buf[len] = (apr_byte_t)((value >> 24) & 0xFF);
+ msg->buf[len + 1] = (apr_byte_t)((value >> 16) & 0xFF);
+ msg->buf[len + 2] = (apr_byte_t)((value >> 8) & 0xFF);
+ msg->buf[len + 3] = (apr_byte_t)(value & 0xFF);
+
+ msg->len += 4;
+
+ return APR_SUCCESS;
+}
+
+/**
+ * Add an unsigned 16bits value to AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param value value to add to AJP Message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_append_uint16(ajp_msg_t *msg, apr_uint16_t value)
+{
+ apr_size_t len = msg->len;
+
+ if ((len + 2) > msg->max_size) {
+ return ajp_log_overflow(msg, "ajp_msg_append_uint16");
+ }
+
+ msg->buf[len] = (apr_byte_t)((value >> 8) & 0xFF);
+ msg->buf[len + 1] = (apr_byte_t)(value & 0xFF);
+
+ msg->len += 2;
+
+ return APR_SUCCESS;
+}
+
+/**
+ * Add an unsigned 8bits value to AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param value value to add to AJP Message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_append_uint8(ajp_msg_t *msg, apr_byte_t value)
+{
+ apr_size_t len = msg->len;
+
+ if ((len + 1) > msg->max_size) {
+ return ajp_log_overflow(msg, "ajp_msg_append_uint8");
+ }
+
+ msg->buf[len] = value;
+ msg->len += 1;
+
+ return APR_SUCCESS;
+}
+
+/**
+ * Add a String in AJP message, and transform the String in ASCII
+ * if convert is set and we're on an EBCDIC machine
+ *
+ * @param msg AJP Message to get value from
+ * @param value Pointer to String
+ * @param convert When set told to convert String to ASCII
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_append_string_ex(ajp_msg_t *msg, const char *value,
+ int convert)
+{
+ apr_size_t len;
+
+ if (value == NULL) {
+ return(ajp_msg_append_uint16(msg, 0xFFFF));
+ }
+
+ len = strlen(value);
+ if ((msg->len + len + 3) > msg->max_size) {
+ return ajp_log_overflow(msg, "ajp_msg_append_cvt_string");
+ }
+
+ /* ignore error - we checked once */
+ ajp_msg_append_uint16(msg, (apr_uint16_t)len);
+
+ /* We checked for space !! */
+ memcpy(msg->buf + msg->len, value, len + 1); /* including \0 */
+
+ if (convert) {
+ /* convert from EBCDIC if needed */
+ ap_xlate_proto_to_ascii((char *)msg->buf + msg->len, len + 1);
+ }
+
+ msg->len += len + 1;
+
+ return APR_SUCCESS;
+}
+
+/**
+ * Add a Byte array to AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param value Pointer to Byte array
+ * @param valuelen Byte array len
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_append_bytes(ajp_msg_t *msg, const apr_byte_t *value,
+ apr_size_t valuelen)
+{
+ if (! valuelen) {
+ return APR_SUCCESS; /* Shouldn't we indicate an error ? */
+ }
+
+ if ((msg->len + valuelen) > msg->max_size) {
+ return ajp_log_overflow(msg, "ajp_msg_append_bytes");
+ }
+
+ /* We checked for space !! */
+ memcpy(msg->buf + msg->len, value, valuelen);
+ msg->len += valuelen;
+
+ return APR_SUCCESS;
+}
+
+/**
+ * Get a 32bits unsigned value from AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param rvalue Pointer where value will be returned
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_get_uint32(ajp_msg_t *msg, apr_uint32_t *rvalue)
+{
+ apr_uint32_t value;
+
+ if ((msg->pos + 3) > msg->len) {
+ return ajp_log_overflow(msg, "ajp_msg_get_uint32");
+ }
+
+ value = ((msg->buf[(msg->pos++)] & 0xFF) << 24);
+ value |= ((msg->buf[(msg->pos++)] & 0xFF) << 16);
+ value |= ((msg->buf[(msg->pos++)] & 0xFF) << 8);
+ value |= ((msg->buf[(msg->pos++)] & 0xFF));
+
+ *rvalue = value;
+ return APR_SUCCESS;
+}
+
+
+/**
+ * Get a 16bits unsigned value from AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param rvalue Pointer where value will be returned
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_get_uint16(ajp_msg_t *msg, apr_uint16_t *rvalue)
+{
+ apr_uint16_t value;
+
+ if ((msg->pos + 1) > msg->len) {
+ return ajp_log_overflow(msg, "ajp_msg_get_uint16");
+ }
+
+ value = ((msg->buf[(msg->pos++)] & 0xFF) << 8);
+ value += ((msg->buf[(msg->pos++)] & 0xFF));
+
+ *rvalue = value;
+ return APR_SUCCESS;
+}
+
+/**
+ * Peek a 16bits unsigned value from AJP Message, position in message
+ * is not updated
+ *
+ * @param msg AJP Message to get value from
+ * @param rvalue Pointer where value will be returned
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_peek_uint16(ajp_msg_t *msg, apr_uint16_t *rvalue)
+{
+ apr_uint16_t value;
+
+ if ((msg->pos + 1) > msg->len) {
+ return ajp_log_overflow(msg, "ajp_msg_peek_uint16");
+ }
+
+ value = ((msg->buf[(msg->pos)] & 0xFF) << 8);
+ value += ((msg->buf[(msg->pos + 1)] & 0xFF));
+
+ *rvalue = value;
+ return APR_SUCCESS;
+}
+
+/**
+ * Peek a 8bits unsigned value from AJP Message, position in message
+ * is not updated
+ *
+ * @param msg AJP Message to get value from
+ * @param rvalue Pointer where value will be returned
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_peek_uint8(ajp_msg_t *msg, apr_byte_t *rvalue)
+{
+ if (msg->pos > msg->len) {
+ return ajp_log_overflow(msg, "ajp_msg_peek_uint8");
+ }
+
+ *rvalue = msg->buf[msg->pos];
+ return APR_SUCCESS;
+}
+
+/**
+ * Get a 8bits unsigned value from AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param rvalue Pointer where value will be returned
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_get_uint8(ajp_msg_t *msg, apr_byte_t *rvalue)
+{
+
+ if (msg->pos > msg->len) {
+ return ajp_log_overflow(msg, "ajp_msg_get_uint8");
+ }
+
+ *rvalue = msg->buf[msg->pos++];
+ return APR_SUCCESS;
+}
+
+
+/**
+ * Get a String value from AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param rvalue Pointer where value will be returned
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_get_string(ajp_msg_t *msg, const char **rvalue)
+{
+ apr_uint16_t size;
+ apr_size_t start;
+ apr_status_t status;
+
+ status = ajp_msg_get_uint16(msg, &size);
+ start = msg->pos;
+
+ if ((status != APR_SUCCESS) || (size + start > msg->max_size)) {
+ return ajp_log_overflow(msg, "ajp_msg_get_string");
+ }
+
+ msg->pos += (apr_size_t)size;
+ msg->pos++; /* a String in AJP is NULL terminated */
+
+ *rvalue = (const char *)(msg->buf + start);
+ return APR_SUCCESS;
+}
+
+
+/**
+ * Get a Byte array from AJP Message
+ *
+ * @param msg AJP Message to get value from
+ * @param rvalue Pointer where value will be returned
+ * @param rvalueLen Pointer where Byte array len will be returned
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_get_bytes(ajp_msg_t *msg, apr_byte_t **rvalue,
+ apr_size_t *rvalue_len)
+{
+ apr_uint16_t size;
+ apr_size_t start;
+ apr_status_t status;
+
+ status = ajp_msg_get_uint16(msg, &size);
+ /* save the current position */
+ start = msg->pos;
+
+ if ((status != APR_SUCCESS) || (size + start > msg->max_size)) {
+ return ajp_log_overflow(msg, "ajp_msg_get_bytes");
+ }
+ msg->pos += (apr_size_t)size; /* only bytes, no trailer */
+
+ *rvalue = msg->buf + start;
+ *rvalue_len = size;
+
+ return APR_SUCCESS;
+}
+
+
+/**
+ * Create an AJP Message from pool
+ *
+ * @param pool memory pool to allocate AJP message from
+ * @param size size of the buffer to create
+ * @param rmsg Pointer to newly created AJP message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_create(apr_pool_t *pool, apr_size_t size, ajp_msg_t **rmsg)
+{
+ ajp_msg_t *msg = (ajp_msg_t *)apr_pcalloc(pool, sizeof(ajp_msg_t));
+
+ msg->server_side = 0;
+
+ msg->buf = (apr_byte_t *)apr_palloc(pool, size);
+ msg->len = 0;
+ msg->header_len = AJP_HEADER_LEN;
+ msg->max_size = size;
+ *rmsg = msg;
+
+ return APR_SUCCESS;
+}
+
+/**
+ * Recopy an AJP Message to another
+ *
+ * @param smsg source AJP message
+ * @param dmsg destination AJP message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_copy(ajp_msg_t *smsg, ajp_msg_t *dmsg)
+{
+ if (smsg->len > smsg->max_size) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(01082)
+ "ajp_msg_copy(): destination buffer too "
+ "small %" APR_SIZE_T_FMT ", max size is %" APR_SIZE_T_FMT,
+ smsg->len, smsg->max_size);
+ return AJP_ETOSMALL;
+ }
+
+ memcpy(dmsg->buf, smsg->buf, smsg->len);
+ dmsg->len = smsg->len;
+ dmsg->pos = smsg->pos;
+
+ return APR_SUCCESS;
+}
+
+
+/**
+ * Serialize in an AJP Message a PING command
+ *
+ * +-----------------------+
+ * | PING CMD (1 byte) |
+ * +-----------------------+
+ *
+ * @param smsg AJP message to put serialized message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_serialize_ping(ajp_msg_t *msg)
+{
+ apr_status_t rc;
+ ajp_msg_reset(msg);
+
+ if ((rc = ajp_msg_append_uint8(msg, CMD_AJP13_PING)) != APR_SUCCESS)
+ return rc;
+
+ return APR_SUCCESS;
+}
+
+/**
+ * Serialize in an AJP Message a CPING command
+ *
+ * +-----------------------+
+ * | CPING CMD (1 byte) |
+ * +-----------------------+
+ *
+ * @param smsg AJP message to put serialized message
+ * @return APR_SUCCESS or error
+ */
+apr_status_t ajp_msg_serialize_cping(ajp_msg_t *msg)
+{
+ apr_status_t rc;
+ ajp_msg_reset(msg);
+
+ if ((rc = ajp_msg_append_uint8(msg, CMD_AJP13_CPING)) != APR_SUCCESS)
+ return rc;
+
+ return APR_SUCCESS;
+}
diff --git a/modules/proxy/ajp_utils.c b/modules/proxy/ajp_utils.c
new file mode 100644
index 0000000..2fe9f72
--- /dev/null
+++ b/modules/proxy/ajp_utils.c
@@ -0,0 +1,137 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ajp.h"
+
+APLOG_USE_MODULE(proxy_ajp);
+
+/*
+ * Handle the CPING/CPONG
+ */
+apr_status_t ajp_handle_cping_cpong(apr_socket_t *sock,
+ request_rec *r,
+ apr_interval_time_t timeout)
+{
+ ajp_msg_t *msg;
+ apr_status_t rc, rv;
+ apr_interval_time_t org;
+ apr_byte_t result;
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, r,
+ "Into ajp_handle_cping_cpong");
+
+ rc = ajp_msg_create(r->pool, AJP_PING_PONG_SZ, &msg);
+ if (rc != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01007)
+ "ajp_handle_cping_cpong: ajp_msg_create failed");
+ return rc;
+ }
+
+ rc = ajp_msg_serialize_cping(msg);
+ if (rc != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01008)
+ "ajp_handle_cping_cpong: ajp_marshal_into_msgb failed");
+ return rc;
+ }
+
+ rc = ajp_ilink_send(sock, msg);
+ ajp_msg_log(r, msg, "ajp_handle_cping_cpong: ajp_ilink_send packet dump");
+ if (rc != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01009)
+ "ajp_handle_cping_cpong: ajp_ilink_send failed");
+ return rc;
+ }
+
+ rc = apr_socket_timeout_get(sock, &org);
+ if (rc != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01010)
+ "ajp_handle_cping_cpong: apr_socket_timeout_get failed");
+ return rc;
+ }
+
+ /* Set CPING/CPONG response timeout */
+ rc = apr_socket_timeout_set(sock, timeout);
+ if (rc != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01011)
+ "ajp_handle_cping_cpong: apr_socket_timeout_set failed");
+ return rc;
+ }
+ ajp_msg_reuse(msg);
+
+ /* Read CPONG reply */
+ rv = ajp_ilink_receive(sock, msg);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01012)
+ "ajp_handle_cping_cpong: ajp_ilink_receive failed");
+ goto cleanup;
+ }
+
+ ajp_msg_log(r, msg, "ajp_handle_cping_cpong: ajp_ilink_receive packet dump");
+ rv = ajp_msg_get_uint8(msg, &result);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01013)
+ "ajp_handle_cping_cpong: invalid CPONG message");
+ goto cleanup;
+ }
+ if (result != CMD_AJP13_CPONG) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01014)
+ "ajp_handle_cping_cpong: awaited CPONG, received %d ",
+ result);
+ rv = APR_EGENERAL;
+ goto cleanup;
+ }
+
+cleanup:
+ /* Restore original socket timeout */
+ rc = apr_socket_timeout_set(sock, org);
+ if (rc != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01015)
+ "ajp_handle_cping_cpong: apr_socket_timeout_set failed");
+ return rc;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, r,
+ "ajp_handle_cping_cpong: Done");
+ return rv;
+}
+
+
+#define case_to_str(x) case CMD_AJP13_##x:\
+ return #x;\
+ break;
+
+/**
+ * Convert numeric message type into string
+ * @param type AJP message type
+ * @return AJP message type as a string
+ */
+const char *ajp_type_str(int type)
+{
+ switch (type) {
+ case_to_str(FORWARD_REQUEST)
+ case_to_str(SEND_BODY_CHUNK)
+ case_to_str(SEND_HEADERS)
+ case_to_str(END_RESPONSE)
+ case_to_str(GET_BODY_CHUNK)
+ case_to_str(SHUTDOWN)
+ case_to_str(PING)
+ case_to_str(CPONG)
+ case_to_str(CPING)
+ default:
+ return "CMD_AJP13_UNKNOWN";
+ }
+
+}
diff --git a/modules/proxy/balancers/Makefile.in b/modules/proxy/balancers/Makefile.in
new file mode 100644
index 0000000..7c5c149
--- /dev/null
+++ b/modules/proxy/balancers/Makefile.in
@@ -0,0 +1,3 @@
+# a modules Makefile has no explicit targets -- they will be defined by
+# whatever modules are enabled. just grab special.mk to deal with this.
+include $(top_srcdir)/build/special.mk
diff --git a/modules/proxy/balancers/config2.m4 b/modules/proxy/balancers/config2.m4
new file mode 100644
index 0000000..f637281
--- /dev/null
+++ b/modules/proxy/balancers/config2.m4
@@ -0,0 +1,8 @@
+APACHE_MODPATH_INIT(proxy/balancers)
+
+APACHE_MODULE(lbmethod_byrequests, Apache proxy Load balancing by request counting, , , $enable_proxy_balancer, , proxy_balancer)
+APACHE_MODULE(lbmethod_bytraffic, Apache proxy Load balancing by traffic counting, , , $enable_proxy_balancer, , proxy_balancer)
+APACHE_MODULE(lbmethod_bybusyness, Apache proxy Load balancing by busyness, , , $enable_proxy_balancer, , proxy_balancer)
+APACHE_MODULE(lbmethod_heartbeat, Apache proxy Load balancing from Heartbeats, , , $enable_proxy_balancer, , proxy_balancer)
+
+APACHE_MODPATH_FINISH
diff --git a/modules/proxy/balancers/mod_lbmethod_bybusyness.c b/modules/proxy/balancers/mod_lbmethod_bybusyness.c
new file mode 100644
index 0000000..80d538c
--- /dev/null
+++ b/modules/proxy/balancers/mod_lbmethod_bybusyness.c
@@ -0,0 +1,124 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mod_proxy.h"
+#include "scoreboard.h"
+#include "ap_mpm.h"
+#include "apr_version.h"
+#include "ap_hooks.h"
+
+module AP_MODULE_DECLARE_DATA lbmethod_bybusyness_module;
+
+static APR_OPTIONAL_FN_TYPE(proxy_balancer_get_best_worker)
+ *ap_proxy_balancer_get_best_worker_fn = NULL;
+
+static int is_best_bybusyness(proxy_worker *current, proxy_worker *prev_best, void *baton)
+{
+ int *total_factor = (int *)baton;
+
+ current->s->lbstatus += current->s->lbfactor;
+ *total_factor += current->s->lbfactor;
+
+ return (
+ !prev_best
+ || (current->s->busy < prev_best->s->busy)
+ || (
+ (current->s->busy == prev_best->s->busy)
+ && (current->s->lbstatus > prev_best->s->lbstatus)
+ )
+ );
+}
+
+static proxy_worker *find_best_bybusyness(proxy_balancer *balancer,
+ request_rec *r)
+{
+ int total_factor = 0;
+ proxy_worker *worker =
+ ap_proxy_balancer_get_best_worker_fn(balancer, r, is_best_bybusyness,
+ &total_factor);
+
+ if (worker) {
+ worker->s->lbstatus -= total_factor;
+ }
+
+ return worker;
+}
+
+/* assumed to be mutex protected by caller */
+static apr_status_t reset(proxy_balancer *balancer, server_rec *s)
+{
+ int i;
+ proxy_worker **worker;
+ worker = (proxy_worker **)balancer->workers->elts;
+ for (i = 0; i < balancer->workers->nelts; i++, worker++) {
+ (*worker)->s->lbstatus = 0;
+ (*worker)->s->busy = 0;
+ }
+ return APR_SUCCESS;
+}
+
+static apr_status_t age(proxy_balancer *balancer, server_rec *s)
+{
+ return APR_SUCCESS;
+}
+
+static const proxy_balancer_method bybusyness =
+{
+ "bybusyness",
+ &find_best_bybusyness,
+ NULL,
+ &reset,
+ &age,
+ NULL
+};
+
+/* post_config hook: */
+static int lbmethod_bybusyness_post_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+
+ /* lbmethod_bybusyness_post_config() will be called twice during startup. So, don't
+ * set up the static data the 1st time through. */
+ if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) {
+ return OK;
+ }
+
+ ap_proxy_balancer_get_best_worker_fn =
+ APR_RETRIEVE_OPTIONAL_FN(proxy_balancer_get_best_worker);
+ if (!ap_proxy_balancer_get_best_worker_fn) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10151)
+ "mod_proxy must be loaded for mod_lbmethod_bybusyness");
+ return !OK;
+ }
+
+ return OK;
+}
+
+static void register_hook(apr_pool_t *p)
+{
+ ap_register_provider(p, PROXY_LBMETHOD, "bybusyness", "0", &bybusyness);
+ ap_hook_post_config(lbmethod_bybusyness_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(lbmethod_bybusyness) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ NULL, /* command apr_table_t */
+ register_hook /* register hooks */
+};
diff --git a/modules/proxy/balancers/mod_lbmethod_bybusyness.dep b/modules/proxy/balancers/mod_lbmethod_bybusyness.dep
new file mode 100644
index 0000000..12cbe7f
--- /dev/null
+++ b/modules/proxy/balancers/mod_lbmethod_bybusyness.dep
@@ -0,0 +1,76 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_lbmethod_bybusyness.mak
+
+.\mod_lbmethod_bybusyness.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_expr.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_mpm.h"\
+ "..\..\..\include\ap_provider.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\ap_slotmem.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\http_config.h"\
+ "..\..\..\include\http_connection.h"\
+ "..\..\..\include\http_core.h"\
+ "..\..\..\include\http_log.h"\
+ "..\..\..\include\http_main.h"\
+ "..\..\..\include\http_protocol.h"\
+ "..\..\..\include\http_request.h"\
+ "..\..\..\include\http_vhost.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\scoreboard.h"\
+ "..\..\..\include\util_cfgtree.h"\
+ "..\..\..\include\util_charset.h"\
+ "..\..\..\include\util_ebcdic.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_mutex.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_date.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_reslist.h"\
+ "..\..\..\srclib\apr-util\include\apr_strmatch.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_version.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ "..\mod_proxy.h"\
+
+
+..\..\..\build\win32\httpd.rc : \
+ "..\..\..\include\ap_release.h"\
+
diff --git a/modules/proxy/balancers/mod_lbmethod_bybusyness.dsp b/modules/proxy/balancers/mod_lbmethod_bybusyness.dsp
new file mode 100644
index 0000000..8544600
--- /dev/null
+++ b/modules/proxy/balancers/mod_lbmethod_bybusyness.dsp
@@ -0,0 +1,123 @@
+# Microsoft Developer Studio Project File - Name="mod_lbmethod_bybusyness" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_lbmethod_bybusyness - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_lbmethod_bybusyness.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_lbmethod_bybusyness.mak" CFG="mod_lbmethod_bybusyness - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_lbmethod_bybusyness - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_lbmethod_bybusyness - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_lbmethod_bybusyness - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I ".." /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_lbmethod_bybusyness_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_lbmethod_bybusyness.res" /i "../../../include" /i "../../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_lbmethod_bybusyness.so" /d LONG_NAME="lbmethod_bybusyness_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:".\Release\mod_lbmethod_bybusyness.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_lbmethod_bybusyness.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_lbmethod_bybusyness.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_lbmethod_bybusyness.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_lbmethod_bybusyness.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_lbmethod_bybusyness - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I ".." /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_lbmethod_bybusyness_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_lbmethod_bybusyness.res" /i "../../../include" /i "../../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_lbmethod_bybusyness.so" /d LONG_NAME="lbmethod_bybusyness_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_lbmethod_bybusyness.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_lbmethod_bybusyness.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_lbmethod_bybusyness.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_lbmethod_bybusyness.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_lbmethod_bybusyness.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_lbmethod_bybusyness - Win32 Release"
+# Name "mod_lbmethod_bybusyness - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\mod_lbmethod_bybusyness.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter ".h"
+# Begin Source File
+
+SOURCE=..\mod_proxy.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/proxy/balancers/mod_lbmethod_bybusyness.mak b/modules/proxy/balancers/mod_lbmethod_bybusyness.mak
new file mode 100644
index 0000000..4a04fd6
--- /dev/null
+++ b/modules/proxy/balancers/mod_lbmethod_bybusyness.mak
@@ -0,0 +1,408 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_lbmethod_bybusyness.dsp
+!IF "$(CFG)" == ""
+CFG=mod_lbmethod_bybusyness - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_lbmethod_bybusyness - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_lbmethod_bybusyness - Win32 Release" && "$(CFG)" != "mod_lbmethod_bybusyness - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_lbmethod_bybusyness.mak" CFG="mod_lbmethod_bybusyness - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_lbmethod_bybusyness - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_lbmethod_bybusyness - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_lbmethod_bybusyness - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_lbmethod_bybusyness.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_proxy_balancer - Win32 Release" "mod_proxy - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_lbmethod_bybusyness.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_proxy - Win32 ReleaseCLEAN" "mod_proxy_balancer - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_lbmethod_bybusyness.obj"
+ -@erase "$(INTDIR)\mod_lbmethod_bybusyness.res"
+ -@erase "$(INTDIR)\mod_lbmethod_bybusyness_src.idb"
+ -@erase "$(INTDIR)\mod_lbmethod_bybusyness_src.pdb"
+ -@erase "$(OUTDIR)\mod_lbmethod_bybusyness.exp"
+ -@erase "$(OUTDIR)\mod_lbmethod_bybusyness.lib"
+ -@erase "$(OUTDIR)\mod_lbmethod_bybusyness.pdb"
+ -@erase "$(OUTDIR)\mod_lbmethod_bybusyness.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I ".." /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_lbmethod_bybusyness_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_lbmethod_bybusyness.res" /i "../../../include" /i "../../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_lbmethod_bybusyness.so" /d LONG_NAME="lbmethod_bybusyness_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_lbmethod_bybusyness.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_lbmethod_bybusyness.pdb" /debug /out:"$(OUTDIR)\mod_lbmethod_bybusyness.so" /implib:"$(OUTDIR)\mod_lbmethod_bybusyness.lib" /base:@..\..\..\os\win32\BaseAddr.ref,mod_lbmethod_bybusyness.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_lbmethod_bybusyness.obj" \
+ "$(INTDIR)\mod_lbmethod_bybusyness.res" \
+ "..\..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\..\Release\libhttpd.lib" \
+ "..\Release\mod_proxy.lib" \
+ "..\Release\mod_proxy_balancer.lib"
+
+"$(OUTDIR)\mod_lbmethod_bybusyness.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_lbmethod_bybusyness.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_lbmethod_bybusyness.so"
+ if exist .\Release\mod_lbmethod_bybusyness.so.manifest mt.exe -manifest .\Release\mod_lbmethod_bybusyness.so.manifest -outputresource:.\Release\mod_lbmethod_bybusyness.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_lbmethod_bybusyness - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_lbmethod_bybusyness.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_proxy_balancer - Win32 Debug" "mod_proxy - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_lbmethod_bybusyness.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_proxy - Win32 DebugCLEAN" "mod_proxy_balancer - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_lbmethod_bybusyness.obj"
+ -@erase "$(INTDIR)\mod_lbmethod_bybusyness.res"
+ -@erase "$(INTDIR)\mod_lbmethod_bybusyness_src.idb"
+ -@erase "$(INTDIR)\mod_lbmethod_bybusyness_src.pdb"
+ -@erase "$(OUTDIR)\mod_lbmethod_bybusyness.exp"
+ -@erase "$(OUTDIR)\mod_lbmethod_bybusyness.lib"
+ -@erase "$(OUTDIR)\mod_lbmethod_bybusyness.pdb"
+ -@erase "$(OUTDIR)\mod_lbmethod_bybusyness.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I ".." /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_lbmethod_bybusyness_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_lbmethod_bybusyness.res" /i "../../../include" /i "../../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_lbmethod_bybusyness.so" /d LONG_NAME="lbmethod_bybusyness_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_lbmethod_bybusyness.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_lbmethod_bybusyness.pdb" /debug /out:"$(OUTDIR)\mod_lbmethod_bybusyness.so" /implib:"$(OUTDIR)\mod_lbmethod_bybusyness.lib" /base:@..\..\..\os\win32\BaseAddr.ref,mod_lbmethod_bybusyness.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_lbmethod_bybusyness.obj" \
+ "$(INTDIR)\mod_lbmethod_bybusyness.res" \
+ "..\..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\..\Debug\libhttpd.lib" \
+ "..\Debug\mod_proxy.lib" \
+ "..\Debug\mod_proxy_balancer.lib"
+
+"$(OUTDIR)\mod_lbmethod_bybusyness.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_lbmethod_bybusyness.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_lbmethod_bybusyness.so"
+ if exist .\Debug\mod_lbmethod_bybusyness.so.manifest mt.exe -manifest .\Debug\mod_lbmethod_bybusyness.so.manifest -outputresource:.\Debug\mod_lbmethod_bybusyness.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_lbmethod_bybusyness.dep")
+!INCLUDE "mod_lbmethod_bybusyness.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_lbmethod_bybusyness.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_lbmethod_bybusyness - Win32 Release" || "$(CFG)" == "mod_lbmethod_bybusyness - Win32 Debug"
+SOURCE=.\mod_lbmethod_bybusyness.c
+
+"$(INTDIR)\mod_lbmethod_bybusyness.obj" : $(SOURCE) "$(INTDIR)"
+
+
+!IF "$(CFG)" == "mod_lbmethod_bybusyness - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\proxy\balancers"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy\balancers"
+
+!ELSEIF "$(CFG)" == "mod_lbmethod_bybusyness - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\proxy\balancers"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy\balancers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_lbmethod_bybusyness - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\proxy\balancers"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy\balancers"
+
+!ELSEIF "$(CFG)" == "mod_lbmethod_bybusyness - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\proxy\balancers"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy\balancers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_lbmethod_bybusyness - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\proxy\balancers"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\proxy\balancers"
+
+!ELSEIF "$(CFG)" == "mod_lbmethod_bybusyness - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\proxy\balancers"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\proxy\balancers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_lbmethod_bybusyness - Win32 Release"
+
+"mod_proxy - Win32 Release" :
+ cd ".\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Release"
+ cd ".\balancers"
+
+"mod_proxy - Win32 ReleaseCLEAN" :
+ cd ".\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Release" RECURSE=1 CLEAN
+ cd ".\balancers"
+
+!ELSEIF "$(CFG)" == "mod_lbmethod_bybusyness - Win32 Debug"
+
+"mod_proxy - Win32 Debug" :
+ cd ".\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Debug"
+ cd ".\balancers"
+
+"mod_proxy - Win32 DebugCLEAN" :
+ cd ".\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\balancers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_lbmethod_bybusyness - Win32 Release"
+
+"mod_proxy_balancer - Win32 Release" :
+ cd ".\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy_balancer.mak" CFG="mod_proxy_balancer - Win32 Release"
+ cd ".\balancers"
+
+"mod_proxy_balancer - Win32 ReleaseCLEAN" :
+ cd ".\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy_balancer.mak" CFG="mod_proxy_balancer - Win32 Release" RECURSE=1 CLEAN
+ cd ".\balancers"
+
+!ELSEIF "$(CFG)" == "mod_lbmethod_bybusyness - Win32 Debug"
+
+"mod_proxy_balancer - Win32 Debug" :
+ cd ".\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy_balancer.mak" CFG="mod_proxy_balancer - Win32 Debug"
+ cd ".\balancers"
+
+"mod_proxy_balancer - Win32 DebugCLEAN" :
+ cd ".\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy_balancer.mak" CFG="mod_proxy_balancer - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\balancers"
+
+!ENDIF
+
+SOURCE=..\..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_lbmethod_bybusyness - Win32 Release"
+
+
+"$(INTDIR)\mod_lbmethod_bybusyness.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_lbmethod_bybusyness.res" /i "../../../include" /i "../../../srclib/apr/include" /i "../../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_lbmethod_bybusyness.so" /d LONG_NAME="lbmethod_bybusyness_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_lbmethod_bybusyness - Win32 Debug"
+
+
+"$(INTDIR)\mod_lbmethod_bybusyness.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_lbmethod_bybusyness.res" /i "../../../include" /i "../../../srclib/apr/include" /i "../../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_lbmethod_bybusyness.so" /d LONG_NAME="lbmethod_bybusyness_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+
+!ENDIF
+
diff --git a/modules/proxy/balancers/mod_lbmethod_byrequests.c b/modules/proxy/balancers/mod_lbmethod_byrequests.c
new file mode 100644
index 0000000..b6cd8d8
--- /dev/null
+++ b/modules/proxy/balancers/mod_lbmethod_byrequests.c
@@ -0,0 +1,165 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mod_proxy.h"
+#include "scoreboard.h"
+#include "ap_mpm.h"
+#include "apr_version.h"
+#include "ap_hooks.h"
+
+module AP_MODULE_DECLARE_DATA lbmethod_byrequests_module;
+
+static APR_OPTIONAL_FN_TYPE(proxy_balancer_get_best_worker)
+ *ap_proxy_balancer_get_best_worker_fn = NULL;
+
+static int is_best_byrequests(proxy_worker *current, proxy_worker *prev_best, void *baton)
+{
+ int *total_factor = (int *)baton;
+
+ current->s->lbstatus += current->s->lbfactor;
+ *total_factor += current->s->lbfactor;
+
+ return (!prev_best || (current->s->lbstatus > prev_best->s->lbstatus));
+}
+
+/*
+ * The idea behind the find_best_byrequests scheduler is the following:
+ *
+ * lbfactor is "how much we expect this worker to work", or "the worker's
+ * normalized work quota".
+ *
+ * lbstatus is "how urgent this worker has to work to fulfill its quota
+ * of work".
+ *
+ * We distribute each worker's work quota to the worker, and then look
+ * which of them needs to work most urgently (biggest lbstatus). This
+ * worker is then selected for work, and its lbstatus reduced by the
+ * total work quota we distributed to all workers. Thus the sum of all
+ * lbstatus does not change.(*)
+ *
+ * If some workers are disabled, the others will
+ * still be scheduled correctly.
+ *
+ * If a balancer is configured as follows:
+ *
+ * worker a b c d
+ * lbfactor 25 25 25 25
+ *
+ * And b gets disabled, the following schedule is produced:
+ *
+ * a c d a c d a c d ...
+ *
+ * Note that the above lbfactor setting is the *exact* same as:
+ *
+ * worker a b c d
+ * lbfactor 1 1 1 1
+ *
+ * Asymmetric configurations work as one would expect. For
+ * example:
+ *
+ * worker a b c d
+ * lbfactor 1 1 1 2
+ *
+ * would have a, b and c all handling about the same
+ * amount of load with d handling twice what a or b
+ * or c handles individually. So we could see:
+ *
+ * b a d c d a c d b d ...
+ *
+ */
+static proxy_worker *find_best_byrequests(proxy_balancer *balancer,
+ request_rec *r)
+{
+ int total_factor = 0;
+ proxy_worker *worker = ap_proxy_balancer_get_best_worker_fn(balancer, r, is_best_byrequests, &total_factor);
+
+ if (worker) {
+ worker->s->lbstatus -= total_factor;
+ }
+
+ return worker;
+}
+
+/* assumed to be mutex protected by caller */
+static apr_status_t reset(proxy_balancer *balancer, server_rec *s)
+{
+ int i;
+ proxy_worker **worker;
+ worker = (proxy_worker **)balancer->workers->elts;
+ for (i = 0; i < balancer->workers->nelts; i++, worker++) {
+ (*worker)->s->lbstatus = 0;
+ }
+ return APR_SUCCESS;
+}
+
+static apr_status_t age(proxy_balancer *balancer, server_rec *s)
+{
+ return APR_SUCCESS;
+}
+
+/*
+ * How to add additional lbmethods:
+ * 1. Create func which determines "best" candidate worker
+ * (eg: find_best_bytraffic, above)
+ * 2. Register it as a provider.
+ */
+static const proxy_balancer_method byrequests =
+{
+ "byrequests",
+ &find_best_byrequests,
+ NULL,
+ &reset,
+ &age,
+ NULL
+};
+
+/* post_config hook: */
+static int lbmethod_byrequests_post_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+
+ /* lbmethod_byrequests_post_config() will be called twice during startup. So, don't
+ * set up the static data the 1st time through. */
+ if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) {
+ return OK;
+ }
+
+ ap_proxy_balancer_get_best_worker_fn =
+ APR_RETRIEVE_OPTIONAL_FN(proxy_balancer_get_best_worker);
+ if (!ap_proxy_balancer_get_best_worker_fn) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10152)
+ "mod_proxy must be loaded for mod_lbmethod_byrequests");
+ return !OK;
+ }
+
+ return OK;
+}
+
+static void register_hook(apr_pool_t *p)
+{
+ ap_register_provider(p, PROXY_LBMETHOD, "byrequests", "0", &byrequests);
+ ap_hook_post_config(lbmethod_byrequests_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(lbmethod_byrequests) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ NULL, /* command apr_table_t */
+ register_hook /* register hooks */
+};
diff --git a/modules/proxy/balancers/mod_lbmethod_byrequests.dep b/modules/proxy/balancers/mod_lbmethod_byrequests.dep
new file mode 100644
index 0000000..acaeb54
--- /dev/null
+++ b/modules/proxy/balancers/mod_lbmethod_byrequests.dep
@@ -0,0 +1,76 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_lbmethod_byrequests.mak
+
+.\mod_lbmethod_byrequests.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_expr.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_mpm.h"\
+ "..\..\..\include\ap_provider.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\ap_slotmem.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\http_config.h"\
+ "..\..\..\include\http_connection.h"\
+ "..\..\..\include\http_core.h"\
+ "..\..\..\include\http_log.h"\
+ "..\..\..\include\http_main.h"\
+ "..\..\..\include\http_protocol.h"\
+ "..\..\..\include\http_request.h"\
+ "..\..\..\include\http_vhost.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\scoreboard.h"\
+ "..\..\..\include\util_cfgtree.h"\
+ "..\..\..\include\util_charset.h"\
+ "..\..\..\include\util_ebcdic.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_mutex.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_date.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_reslist.h"\
+ "..\..\..\srclib\apr-util\include\apr_strmatch.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_version.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ "..\mod_proxy.h"\
+
+
+..\..\..\build\win32\httpd.rc : \
+ "..\..\..\include\ap_release.h"\
+
diff --git a/modules/proxy/balancers/mod_lbmethod_byrequests.dsp b/modules/proxy/balancers/mod_lbmethod_byrequests.dsp
new file mode 100644
index 0000000..cfa90bf
--- /dev/null
+++ b/modules/proxy/balancers/mod_lbmethod_byrequests.dsp
@@ -0,0 +1,123 @@
+# Microsoft Developer Studio Project File - Name="mod_lbmethod_byrequests" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_lbmethod_byrequests - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_lbmethod_byrequests.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_lbmethod_byrequests.mak" CFG="mod_lbmethod_byrequests - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_lbmethod_byrequests - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_lbmethod_byrequests - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_lbmethod_byrequests - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I ".." /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_lbmethod_byrequests_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_lbmethod_byrequests.res" /i "../../../include" /i "../../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_lbmethod_byrequests.so" /d LONG_NAME="lbmethod_byrequests_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:".\Release\mod_lbmethod_byrequests.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_lbmethod_byrequests.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_lbmethod_byrequests.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_lbmethod_byrequests.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_lbmethod_byrequests.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_lbmethod_byrequests - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I ".." /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_lbmethod_byrequests_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_lbmethod_byrequests.res" /i "../../../include" /i "../../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_lbmethod_byrequests.so" /d LONG_NAME="lbmethod_byrequests_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_lbmethod_byrequests.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_lbmethod_byrequests.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_lbmethod_byrequests.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_lbmethod_byrequests.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_lbmethod_byrequests.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_lbmethod_byrequests - Win32 Release"
+# Name "mod_lbmethod_byrequests - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\mod_lbmethod_byrequests.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter ".h"
+# Begin Source File
+
+SOURCE=..\mod_proxy.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/proxy/balancers/mod_lbmethod_byrequests.mak b/modules/proxy/balancers/mod_lbmethod_byrequests.mak
new file mode 100644
index 0000000..b5914a2
--- /dev/null
+++ b/modules/proxy/balancers/mod_lbmethod_byrequests.mak
@@ -0,0 +1,408 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_lbmethod_byrequests.dsp
+!IF "$(CFG)" == ""
+CFG=mod_lbmethod_byrequests - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_lbmethod_byrequests - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_lbmethod_byrequests - Win32 Release" && "$(CFG)" != "mod_lbmethod_byrequests - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_lbmethod_byrequests.mak" CFG="mod_lbmethod_byrequests - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_lbmethod_byrequests - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_lbmethod_byrequests - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_lbmethod_byrequests - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_lbmethod_byrequests.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_proxy_balancer - Win32 Release" "mod_proxy - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_lbmethod_byrequests.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_proxy - Win32 ReleaseCLEAN" "mod_proxy_balancer - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_lbmethod_byrequests.obj"
+ -@erase "$(INTDIR)\mod_lbmethod_byrequests.res"
+ -@erase "$(INTDIR)\mod_lbmethod_byrequests_src.idb"
+ -@erase "$(INTDIR)\mod_lbmethod_byrequests_src.pdb"
+ -@erase "$(OUTDIR)\mod_lbmethod_byrequests.exp"
+ -@erase "$(OUTDIR)\mod_lbmethod_byrequests.lib"
+ -@erase "$(OUTDIR)\mod_lbmethod_byrequests.pdb"
+ -@erase "$(OUTDIR)\mod_lbmethod_byrequests.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I ".." /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_lbmethod_byrequests_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_lbmethod_byrequests.res" /i "../../../include" /i "../../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_lbmethod_byrequests.so" /d LONG_NAME="lbmethod_byrequests_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_lbmethod_byrequests.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_lbmethod_byrequests.pdb" /debug /out:"$(OUTDIR)\mod_lbmethod_byrequests.so" /implib:"$(OUTDIR)\mod_lbmethod_byrequests.lib" /base:@..\..\..\os\win32\BaseAddr.ref,mod_lbmethod_byrequests.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_lbmethod_byrequests.obj" \
+ "$(INTDIR)\mod_lbmethod_byrequests.res" \
+ "..\..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\..\Release\libhttpd.lib" \
+ "..\Release\mod_proxy.lib" \
+ "..\Release\mod_proxy_balancer.lib"
+
+"$(OUTDIR)\mod_lbmethod_byrequests.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_lbmethod_byrequests.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_lbmethod_byrequests.so"
+ if exist .\Release\mod_lbmethod_byrequests.so.manifest mt.exe -manifest .\Release\mod_lbmethod_byrequests.so.manifest -outputresource:.\Release\mod_lbmethod_byrequests.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_lbmethod_byrequests - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_lbmethod_byrequests.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_proxy_balancer - Win32 Debug" "mod_proxy - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_lbmethod_byrequests.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_proxy - Win32 DebugCLEAN" "mod_proxy_balancer - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_lbmethod_byrequests.obj"
+ -@erase "$(INTDIR)\mod_lbmethod_byrequests.res"
+ -@erase "$(INTDIR)\mod_lbmethod_byrequests_src.idb"
+ -@erase "$(INTDIR)\mod_lbmethod_byrequests_src.pdb"
+ -@erase "$(OUTDIR)\mod_lbmethod_byrequests.exp"
+ -@erase "$(OUTDIR)\mod_lbmethod_byrequests.lib"
+ -@erase "$(OUTDIR)\mod_lbmethod_byrequests.pdb"
+ -@erase "$(OUTDIR)\mod_lbmethod_byrequests.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I ".." /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_lbmethod_byrequests_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_lbmethod_byrequests.res" /i "../../../include" /i "../../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_lbmethod_byrequests.so" /d LONG_NAME="lbmethod_byrequests_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_lbmethod_byrequests.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_lbmethod_byrequests.pdb" /debug /out:"$(OUTDIR)\mod_lbmethod_byrequests.so" /implib:"$(OUTDIR)\mod_lbmethod_byrequests.lib" /base:@..\..\..\os\win32\BaseAddr.ref,mod_lbmethod_byrequests.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_lbmethod_byrequests.obj" \
+ "$(INTDIR)\mod_lbmethod_byrequests.res" \
+ "..\..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\..\Debug\libhttpd.lib" \
+ "..\Debug\mod_proxy.lib" \
+ "..\Debug\mod_proxy_balancer.lib"
+
+"$(OUTDIR)\mod_lbmethod_byrequests.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_lbmethod_byrequests.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_lbmethod_byrequests.so"
+ if exist .\Debug\mod_lbmethod_byrequests.so.manifest mt.exe -manifest .\Debug\mod_lbmethod_byrequests.so.manifest -outputresource:.\Debug\mod_lbmethod_byrequests.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_lbmethod_byrequests.dep")
+!INCLUDE "mod_lbmethod_byrequests.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_lbmethod_byrequests.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_lbmethod_byrequests - Win32 Release" || "$(CFG)" == "mod_lbmethod_byrequests - Win32 Debug"
+SOURCE=.\mod_lbmethod_byrequests.c
+
+"$(INTDIR)\mod_lbmethod_byrequests.obj" : $(SOURCE) "$(INTDIR)"
+
+
+!IF "$(CFG)" == "mod_lbmethod_byrequests - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\proxy\balancers"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy\balancers"
+
+!ELSEIF "$(CFG)" == "mod_lbmethod_byrequests - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\proxy\balancers"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy\balancers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_lbmethod_byrequests - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\proxy\balancers"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy\balancers"
+
+!ELSEIF "$(CFG)" == "mod_lbmethod_byrequests - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\proxy\balancers"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy\balancers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_lbmethod_byrequests - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\proxy\balancers"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\proxy\balancers"
+
+!ELSEIF "$(CFG)" == "mod_lbmethod_byrequests - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\proxy\balancers"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\proxy\balancers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_lbmethod_byrequests - Win32 Release"
+
+"mod_proxy - Win32 Release" :
+ cd ".\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Release"
+ cd ".\balancers"
+
+"mod_proxy - Win32 ReleaseCLEAN" :
+ cd ".\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Release" RECURSE=1 CLEAN
+ cd ".\balancers"
+
+!ELSEIF "$(CFG)" == "mod_lbmethod_byrequests - Win32 Debug"
+
+"mod_proxy - Win32 Debug" :
+ cd ".\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Debug"
+ cd ".\balancers"
+
+"mod_proxy - Win32 DebugCLEAN" :
+ cd ".\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\balancers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_lbmethod_byrequests - Win32 Release"
+
+"mod_proxy_balancer - Win32 Release" :
+ cd ".\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy_balancer.mak" CFG="mod_proxy_balancer - Win32 Release"
+ cd ".\balancers"
+
+"mod_proxy_balancer - Win32 ReleaseCLEAN" :
+ cd ".\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy_balancer.mak" CFG="mod_proxy_balancer - Win32 Release" RECURSE=1 CLEAN
+ cd ".\balancers"
+
+!ELSEIF "$(CFG)" == "mod_lbmethod_byrequests - Win32 Debug"
+
+"mod_proxy_balancer - Win32 Debug" :
+ cd ".\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy_balancer.mak" CFG="mod_proxy_balancer - Win32 Debug"
+ cd ".\balancers"
+
+"mod_proxy_balancer - Win32 DebugCLEAN" :
+ cd ".\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy_balancer.mak" CFG="mod_proxy_balancer - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\balancers"
+
+!ENDIF
+
+SOURCE=..\..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_lbmethod_byrequests - Win32 Release"
+
+
+"$(INTDIR)\mod_lbmethod_byrequests.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_lbmethod_byrequests.res" /i "../../../include" /i "../../../srclib/apr/include" /i "../../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_lbmethod_byrequests.so" /d LONG_NAME="lbmethod_byrequests_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_lbmethod_byrequests - Win32 Debug"
+
+
+"$(INTDIR)\mod_lbmethod_byrequests.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_lbmethod_byrequests.res" /i "../../../include" /i "../../../srclib/apr/include" /i "../../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_lbmethod_byrequests.so" /d LONG_NAME="lbmethod_byrequests_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+
+!ENDIF
+
diff --git a/modules/proxy/balancers/mod_lbmethod_bytraffic.c b/modules/proxy/balancers/mod_lbmethod_bytraffic.c
new file mode 100644
index 0000000..6cfab94
--- /dev/null
+++ b/modules/proxy/balancers/mod_lbmethod_bytraffic.c
@@ -0,0 +1,135 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mod_proxy.h"
+#include "scoreboard.h"
+#include "ap_mpm.h"
+#include "apr_version.h"
+#include "ap_hooks.h"
+
+module AP_MODULE_DECLARE_DATA lbmethod_bytraffic_module;
+
+static APR_OPTIONAL_FN_TYPE(proxy_balancer_get_best_worker)
+ *ap_proxy_balancer_get_best_worker_fn = NULL;
+
+static int is_best_bytraffic(proxy_worker *current, proxy_worker *prev_best, void *baton)
+{
+ apr_off_t *min_traffic = (apr_off_t *)baton;
+ apr_off_t traffic = (current->s->transferred / current->s->lbfactor)
+ + (current->s->read / current->s->lbfactor);
+
+ if (!prev_best || (traffic < *min_traffic)) {
+ *min_traffic = traffic;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/*
+ * The idea behind the find_best_bytraffic scheduler is the following:
+ *
+ * We know the amount of traffic (bytes in and out) handled by each
+ * worker. We normalize that traffic by each workers' weight. So assuming
+ * a setup as below:
+ *
+ * worker a b c
+ * lbfactor 1 1 3
+ *
+ * the scheduler will allow worker c to handle 3 times the
+ * traffic of a and b. If each request/response results in the
+ * same amount of traffic, then c would be accessed 3 times as
+ * often as a or b. If, for example, a handled a request that
+ * resulted in a large i/o bytecount, then b and c would be
+ * chosen more often, to even things out.
+ */
+static proxy_worker *find_best_bytraffic(proxy_balancer *balancer,
+ request_rec *r)
+{
+ apr_off_t min_traffic = 0;
+
+ return ap_proxy_balancer_get_best_worker_fn(balancer, r, is_best_bytraffic,
+ &min_traffic);
+}
+
+/* assumed to be mutex protected by caller */
+static apr_status_t reset(proxy_balancer *balancer, server_rec *s)
+{
+ int i;
+ proxy_worker **worker;
+ worker = (proxy_worker **)balancer->workers->elts;
+ for (i = 0; i < balancer->workers->nelts; i++, worker++) {
+ (*worker)->s->lbstatus = 0;
+ (*worker)->s->busy = 0;
+ (*worker)->s->transferred = 0;
+ (*worker)->s->read = 0;
+ }
+ return APR_SUCCESS;
+}
+
+static apr_status_t age(proxy_balancer *balancer, server_rec *s)
+{
+ return APR_SUCCESS;
+}
+
+static const proxy_balancer_method bytraffic =
+{
+ "bytraffic",
+ &find_best_bytraffic,
+ NULL,
+ &reset,
+ &age,
+ NULL
+};
+
+/* post_config hook: */
+static int lbmethod_bytraffic_post_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+
+ /* lbmethod_bytraffic_post_config() will be called twice during startup. So, don't
+ * set up the static data the 1st time through. */
+ if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) {
+ return OK;
+ }
+
+ ap_proxy_balancer_get_best_worker_fn =
+ APR_RETRIEVE_OPTIONAL_FN(proxy_balancer_get_best_worker);
+ if (!ap_proxy_balancer_get_best_worker_fn) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10150)
+ "mod_proxy must be loaded for mod_lbmethod_bytraffic");
+ return !OK;
+ }
+
+ return OK;
+}
+
+static void register_hook(apr_pool_t *p)
+{
+ ap_register_provider(p, PROXY_LBMETHOD, "bytraffic", "0", &bytraffic);
+ ap_hook_post_config(lbmethod_bytraffic_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(lbmethod_bytraffic) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ NULL, /* command apr_table_t */
+ register_hook /* register hooks */
+};
diff --git a/modules/proxy/balancers/mod_lbmethod_bytraffic.dep b/modules/proxy/balancers/mod_lbmethod_bytraffic.dep
new file mode 100644
index 0000000..75dfce4
--- /dev/null
+++ b/modules/proxy/balancers/mod_lbmethod_bytraffic.dep
@@ -0,0 +1,76 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_lbmethod_bytraffic.mak
+
+.\mod_lbmethod_bytraffic.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_expr.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_mpm.h"\
+ "..\..\..\include\ap_provider.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\ap_slotmem.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\http_config.h"\
+ "..\..\..\include\http_connection.h"\
+ "..\..\..\include\http_core.h"\
+ "..\..\..\include\http_log.h"\
+ "..\..\..\include\http_main.h"\
+ "..\..\..\include\http_protocol.h"\
+ "..\..\..\include\http_request.h"\
+ "..\..\..\include\http_vhost.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\scoreboard.h"\
+ "..\..\..\include\util_cfgtree.h"\
+ "..\..\..\include\util_charset.h"\
+ "..\..\..\include\util_ebcdic.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_mutex.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_date.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_reslist.h"\
+ "..\..\..\srclib\apr-util\include\apr_strmatch.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_version.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ "..\mod_proxy.h"\
+
+
+..\..\..\build\win32\httpd.rc : \
+ "..\..\..\include\ap_release.h"\
+
diff --git a/modules/proxy/balancers/mod_lbmethod_bytraffic.dsp b/modules/proxy/balancers/mod_lbmethod_bytraffic.dsp
new file mode 100644
index 0000000..3b53fd9
--- /dev/null
+++ b/modules/proxy/balancers/mod_lbmethod_bytraffic.dsp
@@ -0,0 +1,123 @@
+# Microsoft Developer Studio Project File - Name="mod_lbmethod_bytraffic" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_lbmethod_bytraffic - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_lbmethod_bytraffic.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_lbmethod_bytraffic.mak" CFG="mod_lbmethod_bytraffic - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_lbmethod_bytraffic - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_lbmethod_bytraffic - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_lbmethod_bytraffic - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I ".." /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_lbmethod_bytraffic_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_lbmethod_bytraffic.res" /i "../../../include" /i "../../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_lbmethod_bytraffic.so" /d LONG_NAME="lbmethod_bytraffic_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:".\Release\mod_lbmethod_bytraffic.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_lbmethod_bytraffic.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_lbmethod_bytraffic.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_lbmethod_bytraffic.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_lbmethod_bytraffic.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_lbmethod_bytraffic - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I ".." /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_lbmethod_bytraffic_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_lbmethod_bytraffic.res" /i "../../../include" /i "../../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_lbmethod_bytraffic.so" /d LONG_NAME="lbmethod_bytraffic_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_lbmethod_bytraffic.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_lbmethod_bytraffic.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_lbmethod_bytraffic.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_lbmethod_bytraffic.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_lbmethod_bytraffic.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_lbmethod_bytraffic - Win32 Release"
+# Name "mod_lbmethod_bytraffic - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\mod_lbmethod_bytraffic.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter ".h"
+# Begin Source File
+
+SOURCE=..\mod_proxy.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/proxy/balancers/mod_lbmethod_bytraffic.mak b/modules/proxy/balancers/mod_lbmethod_bytraffic.mak
new file mode 100644
index 0000000..fe68c2b
--- /dev/null
+++ b/modules/proxy/balancers/mod_lbmethod_bytraffic.mak
@@ -0,0 +1,408 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_lbmethod_bytraffic.dsp
+!IF "$(CFG)" == ""
+CFG=mod_lbmethod_bytraffic - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_lbmethod_bytraffic - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_lbmethod_bytraffic - Win32 Release" && "$(CFG)" != "mod_lbmethod_bytraffic - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_lbmethod_bytraffic.mak" CFG="mod_lbmethod_bytraffic - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_lbmethod_bytraffic - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_lbmethod_bytraffic - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_lbmethod_bytraffic - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_lbmethod_bytraffic.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_proxy_balancer - Win32 Release" "mod_proxy - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_lbmethod_bytraffic.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_proxy - Win32 ReleaseCLEAN" "mod_proxy_balancer - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_lbmethod_bytraffic.obj"
+ -@erase "$(INTDIR)\mod_lbmethod_bytraffic.res"
+ -@erase "$(INTDIR)\mod_lbmethod_bytraffic_src.idb"
+ -@erase "$(INTDIR)\mod_lbmethod_bytraffic_src.pdb"
+ -@erase "$(OUTDIR)\mod_lbmethod_bytraffic.exp"
+ -@erase "$(OUTDIR)\mod_lbmethod_bytraffic.lib"
+ -@erase "$(OUTDIR)\mod_lbmethod_bytraffic.pdb"
+ -@erase "$(OUTDIR)\mod_lbmethod_bytraffic.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I ".." /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_lbmethod_bytraffic_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_lbmethod_bytraffic.res" /i "../../../include" /i "../../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_lbmethod_bytraffic.so" /d LONG_NAME="lbmethod_bytraffic_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_lbmethod_bytraffic.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_lbmethod_bytraffic.pdb" /debug /out:"$(OUTDIR)\mod_lbmethod_bytraffic.so" /implib:"$(OUTDIR)\mod_lbmethod_bytraffic.lib" /base:@..\..\..\os\win32\BaseAddr.ref,mod_lbmethod_bytraffic.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_lbmethod_bytraffic.obj" \
+ "$(INTDIR)\mod_lbmethod_bytraffic.res" \
+ "..\..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\..\Release\libhttpd.lib" \
+ "..\Release\mod_proxy.lib" \
+ "..\Release\mod_proxy_balancer.lib"
+
+"$(OUTDIR)\mod_lbmethod_bytraffic.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_lbmethod_bytraffic.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_lbmethod_bytraffic.so"
+ if exist .\Release\mod_lbmethod_bytraffic.so.manifest mt.exe -manifest .\Release\mod_lbmethod_bytraffic.so.manifest -outputresource:.\Release\mod_lbmethod_bytraffic.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_lbmethod_bytraffic - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_lbmethod_bytraffic.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_proxy_balancer - Win32 Debug" "mod_proxy - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_lbmethod_bytraffic.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_proxy - Win32 DebugCLEAN" "mod_proxy_balancer - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_lbmethod_bytraffic.obj"
+ -@erase "$(INTDIR)\mod_lbmethod_bytraffic.res"
+ -@erase "$(INTDIR)\mod_lbmethod_bytraffic_src.idb"
+ -@erase "$(INTDIR)\mod_lbmethod_bytraffic_src.pdb"
+ -@erase "$(OUTDIR)\mod_lbmethod_bytraffic.exp"
+ -@erase "$(OUTDIR)\mod_lbmethod_bytraffic.lib"
+ -@erase "$(OUTDIR)\mod_lbmethod_bytraffic.pdb"
+ -@erase "$(OUTDIR)\mod_lbmethod_bytraffic.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I ".." /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_lbmethod_bytraffic_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_lbmethod_bytraffic.res" /i "../../../include" /i "../../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_lbmethod_bytraffic.so" /d LONG_NAME="lbmethod_bytraffic_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_lbmethod_bytraffic.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_lbmethod_bytraffic.pdb" /debug /out:"$(OUTDIR)\mod_lbmethod_bytraffic.so" /implib:"$(OUTDIR)\mod_lbmethod_bytraffic.lib" /base:@..\..\..\os\win32\BaseAddr.ref,mod_lbmethod_bytraffic.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_lbmethod_bytraffic.obj" \
+ "$(INTDIR)\mod_lbmethod_bytraffic.res" \
+ "..\..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\..\Debug\libhttpd.lib" \
+ "..\Debug\mod_proxy.lib" \
+ "..\Debug\mod_proxy_balancer.lib"
+
+"$(OUTDIR)\mod_lbmethod_bytraffic.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_lbmethod_bytraffic.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_lbmethod_bytraffic.so"
+ if exist .\Debug\mod_lbmethod_bytraffic.so.manifest mt.exe -manifest .\Debug\mod_lbmethod_bytraffic.so.manifest -outputresource:.\Debug\mod_lbmethod_bytraffic.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_lbmethod_bytraffic.dep")
+!INCLUDE "mod_lbmethod_bytraffic.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_lbmethod_bytraffic.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_lbmethod_bytraffic - Win32 Release" || "$(CFG)" == "mod_lbmethod_bytraffic - Win32 Debug"
+SOURCE=.\mod_lbmethod_bytraffic.c
+
+"$(INTDIR)\mod_lbmethod_bytraffic.obj" : $(SOURCE) "$(INTDIR)"
+
+
+!IF "$(CFG)" == "mod_lbmethod_bytraffic - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\proxy\balancers"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy\balancers"
+
+!ELSEIF "$(CFG)" == "mod_lbmethod_bytraffic - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\proxy\balancers"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy\balancers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_lbmethod_bytraffic - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\proxy\balancers"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy\balancers"
+
+!ELSEIF "$(CFG)" == "mod_lbmethod_bytraffic - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\proxy\balancers"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy\balancers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_lbmethod_bytraffic - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\proxy\balancers"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\proxy\balancers"
+
+!ELSEIF "$(CFG)" == "mod_lbmethod_bytraffic - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\proxy\balancers"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\proxy\balancers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_lbmethod_bytraffic - Win32 Release"
+
+"mod_proxy - Win32 Release" :
+ cd ".\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Release"
+ cd ".\balancers"
+
+"mod_proxy - Win32 ReleaseCLEAN" :
+ cd ".\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Release" RECURSE=1 CLEAN
+ cd ".\balancers"
+
+!ELSEIF "$(CFG)" == "mod_lbmethod_bytraffic - Win32 Debug"
+
+"mod_proxy - Win32 Debug" :
+ cd ".\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Debug"
+ cd ".\balancers"
+
+"mod_proxy - Win32 DebugCLEAN" :
+ cd ".\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\balancers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_lbmethod_bytraffic - Win32 Release"
+
+"mod_proxy_balancer - Win32 Release" :
+ cd ".\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy_balancer.mak" CFG="mod_proxy_balancer - Win32 Release"
+ cd ".\balancers"
+
+"mod_proxy_balancer - Win32 ReleaseCLEAN" :
+ cd ".\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy_balancer.mak" CFG="mod_proxy_balancer - Win32 Release" RECURSE=1 CLEAN
+ cd ".\balancers"
+
+!ELSEIF "$(CFG)" == "mod_lbmethod_bytraffic - Win32 Debug"
+
+"mod_proxy_balancer - Win32 Debug" :
+ cd ".\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy_balancer.mak" CFG="mod_proxy_balancer - Win32 Debug"
+ cd ".\balancers"
+
+"mod_proxy_balancer - Win32 DebugCLEAN" :
+ cd ".\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy_balancer.mak" CFG="mod_proxy_balancer - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\balancers"
+
+!ENDIF
+
+SOURCE=..\..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_lbmethod_bytraffic - Win32 Release"
+
+
+"$(INTDIR)\mod_lbmethod_bytraffic.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_lbmethod_bytraffic.res" /i "../../../include" /i "../../../srclib/apr/include" /i "../../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_lbmethod_bytraffic.so" /d LONG_NAME="lbmethod_bytraffic_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_lbmethod_bytraffic - Win32 Debug"
+
+
+"$(INTDIR)\mod_lbmethod_bytraffic.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_lbmethod_bytraffic.res" /i "../../../include" /i "../../../srclib/apr/include" /i "../../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_lbmethod_bytraffic.so" /d LONG_NAME="lbmethod_bytraffic_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+
+!ENDIF
+
diff --git a/modules/proxy/balancers/mod_lbmethod_heartbeat.c b/modules/proxy/balancers/mod_lbmethod_heartbeat.c
new file mode 100644
index 0000000..5f4873a
--- /dev/null
+++ b/modules/proxy/balancers/mod_lbmethod_heartbeat.c
@@ -0,0 +1,466 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mod_proxy.h"
+#include "scoreboard.h"
+#include "ap_mpm.h"
+#include "apr_version.h"
+#include "ap_hooks.h"
+#include "ap_slotmem.h"
+#include "heartbeat.h"
+
+#ifndef LBM_HEARTBEAT_MAX_LASTSEEN
+/* If we haven't seen a heartbeat in the last N seconds, don't count this IP
+ * as allive.
+ */
+#define LBM_HEARTBEAT_MAX_LASTSEEN (10)
+#endif
+
+module AP_MODULE_DECLARE_DATA lbmethod_heartbeat_module;
+
+static int (*ap_proxy_retry_worker_fn)(const char *proxy_function,
+ proxy_worker *worker, server_rec *s) = NULL;
+
+static const ap_slotmem_provider_t *storage = NULL;
+static ap_slotmem_instance_t *hm_serversmem = NULL;
+
+/*
+ * configuration structure
+ * path: path of the file where the heartbeat information is stored.
+ */
+typedef struct lb_hb_ctx_t
+{
+ const char *path;
+} lb_hb_ctx_t;
+
+typedef struct hb_server_t {
+ const char *ip;
+ int busy;
+ int ready;
+ int port;
+ int id;
+ apr_time_t seen;
+ proxy_worker *worker;
+} hb_server_t;
+
+typedef struct ctx_servers {
+ apr_time_t now;
+ apr_hash_t *servers;
+} ctx_servers_t;
+
+static void
+argstr_to_table(apr_pool_t *p, char *str, apr_table_t *parms)
+{
+ char *key;
+ char *value;
+ char *strtok_state;
+
+ key = apr_strtok(str, "&", &strtok_state);
+ while (key) {
+ value = strchr(key, '=');
+ if (value) {
+ *value = '\0'; /* Split the string in two */
+ value++; /* Skip passed the = */
+ }
+ else {
+ value = "1";
+ }
+ ap_unescape_url(key);
+ ap_unescape_url(value);
+ apr_table_set(parms, key, value);
+ /*
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03230)
+ "Found query arg: %s = %s", key, value);
+ */
+ key = apr_strtok(NULL, "&", &strtok_state);
+ }
+}
+
+static apr_status_t readfile_heartbeats(const char *path, apr_hash_t *servers,
+ apr_pool_t *pool)
+{
+ apr_finfo_t fi;
+ apr_status_t rv;
+ apr_file_t *fp;
+
+ if (!path) {
+ return APR_SUCCESS;
+ }
+
+ rv = apr_file_open(&fp, path, APR_READ|APR_BINARY|APR_BUFFERED,
+ APR_OS_DEFAULT, pool);
+
+ if (rv) {
+ return rv;
+ }
+
+ rv = apr_file_info_get(&fi, APR_FINFO_SIZE, fp);
+
+ if (rv) {
+ return rv;
+ }
+
+ {
+ char *t;
+ int lineno = 0;
+ apr_bucket_alloc_t *ba = apr_bucket_alloc_create(pool);
+ apr_bucket_brigade *bb = apr_brigade_create(pool, ba);
+ apr_bucket_brigade *tmpbb = apr_brigade_create(pool, ba);
+ apr_table_t *hbt = apr_table_make(pool, 10);
+
+ apr_brigade_insert_file(bb, fp, 0, fi.size, pool);
+
+ do {
+ hb_server_t *server;
+ char buf[4096];
+ apr_size_t bsize = sizeof(buf);
+ const char *ip, *val;
+
+ apr_brigade_cleanup(tmpbb);
+
+ if (APR_BRIGADE_EMPTY(bb)) {
+ break;
+ }
+
+ rv = apr_brigade_split_line(tmpbb, bb,
+ APR_BLOCK_READ, sizeof(buf));
+ lineno++;
+
+ if (rv) {
+ return rv;
+ }
+
+ apr_brigade_flatten(tmpbb, buf, &bsize);
+
+ if (bsize == 0) {
+ break;
+ }
+
+ buf[bsize - 1] = 0;
+
+ /* comment */
+ if (buf[0] == '#') {
+ continue;
+ }
+
+ /* line format: <IP> <query_string>\n */
+ t = strchr(buf, ' ');
+ if (!t) {
+ continue;
+ }
+
+ ip = apr_pstrmemdup(pool, buf, t - buf);
+ t++;
+
+ server = apr_hash_get(servers, ip, APR_HASH_KEY_STRING);
+
+ if (server == NULL) {
+ server = apr_pcalloc(pool, sizeof(hb_server_t));
+ server->ip = ip;
+ server->port = 80;
+ server->seen = -1;
+
+ apr_hash_set(servers, server->ip, APR_HASH_KEY_STRING, server);
+ }
+
+ apr_table_clear(hbt);
+
+ argstr_to_table(pool, apr_pstrdup(pool, t), hbt);
+
+ if ((val = apr_table_get(hbt, "busy"))) {
+ server->busy = atoi(val);
+ }
+
+ if ((val = apr_table_get(hbt, "ready"))) {
+ server->ready = atoi(val);
+ }
+
+ if ((val = apr_table_get(hbt, "lastseen"))) {
+ server->seen = atoi(val);
+ }
+
+ if ((val = apr_table_get(hbt, "port"))) {
+ server->port = atoi(val);
+ }
+
+ if (server->busy == 0 && server->ready != 0) {
+ /* Server has zero threads active, but lots of them ready,
+ * it likely just started up, so lets /4 the number ready,
+ * to prevent us from completely flooding it with all new
+ * requests.
+ */
+ server->ready = server->ready / 4;
+ }
+
+ } while (1);
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t hm_read(void* mem, void *data, apr_pool_t *pool)
+{
+ hm_slot_server_t *slotserver = (hm_slot_server_t *) mem;
+ ctx_servers_t *ctx = (ctx_servers_t *) data;
+ apr_hash_t *servers = (apr_hash_t *) ctx->servers;
+ hb_server_t *server = apr_hash_get(servers, slotserver->ip, APR_HASH_KEY_STRING);
+ if (server == NULL) {
+ server = apr_pcalloc(pool, sizeof(hb_server_t));
+ server->ip = apr_pstrdup(pool, slotserver->ip);
+ server->seen = -1;
+
+ apr_hash_set(servers, server->ip, APR_HASH_KEY_STRING, server);
+
+ }
+ server->busy = slotserver->busy;
+ server->ready = slotserver->ready;
+ server->seen = apr_time_sec(ctx->now - slotserver->seen);
+ server->id = slotserver->id;
+ if (server->busy == 0 && server->ready != 0) {
+ server->ready = server->ready / 4;
+ }
+ return APR_SUCCESS;
+}
+static apr_status_t readslot_heartbeats(ctx_servers_t *ctx,
+ apr_pool_t *pool)
+{
+ storage->doall(hm_serversmem, hm_read, ctx, pool);
+ return APR_SUCCESS;
+}
+
+
+static apr_status_t read_heartbeats(const char *path, apr_hash_t *servers,
+ apr_pool_t *pool)
+{
+ apr_status_t rv;
+ if (hm_serversmem) {
+ ctx_servers_t ctx;
+ ctx.now = apr_time_now();
+ ctx.servers = servers;
+ rv = readslot_heartbeats(&ctx, pool);
+ } else
+ rv = readfile_heartbeats(path, servers, pool);
+ return rv;
+}
+
+static proxy_worker *find_best_hb(proxy_balancer *balancer,
+ request_rec *r)
+{
+ apr_status_t rv;
+ int i;
+ apr_uint32_t openslots = 0;
+ proxy_worker **worker;
+ hb_server_t *server;
+ apr_array_header_t *up_servers;
+ proxy_worker *mycandidate = NULL;
+ apr_pool_t *tpool;
+ apr_hash_t *servers;
+
+ lb_hb_ctx_t *ctx =
+ ap_get_module_config(r->server->module_config,
+ &lbmethod_heartbeat_module);
+
+ ap_proxy_retry_worker_fn =
+ APR_RETRIEVE_OPTIONAL_FN(ap_proxy_retry_worker);
+ if (!ap_proxy_retry_worker_fn) {
+ /* can only happen if mod_proxy isn't loaded */
+ return NULL;
+ }
+
+ apr_pool_create(&tpool, r->pool);
+ apr_pool_tag(tpool, "lb_heartbeat_tpool");
+
+ servers = apr_hash_make(tpool);
+
+ rv = read_heartbeats(ctx->path, servers, tpool);
+
+ if (rv) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01213)
+ "lb_heartbeat: Unable to read heartbeats at '%s'",
+ ctx->path);
+ apr_pool_destroy(tpool);
+ return NULL;
+ }
+
+ up_servers = apr_array_make(tpool, apr_hash_count(servers), sizeof(hb_server_t *));
+
+ for (i = 0; i < balancer->workers->nelts; i++) {
+ worker = &APR_ARRAY_IDX(balancer->workers, i, proxy_worker *);
+ server = apr_hash_get(servers, (*worker)->s->hostname_ex, APR_HASH_KEY_STRING);
+
+ if (!server) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(01214)
+ "lb_heartbeat: No server for worker %s", (*worker)->s->name_ex);
+ continue;
+ }
+
+ if (!PROXY_WORKER_IS_USABLE(*worker)) {
+ ap_proxy_retry_worker_fn("BALANCER", *worker, r->server);
+ }
+
+ if (PROXY_WORKER_IS_USABLE(*worker)) {
+ server->worker = *worker;
+ if (server->seen < LBM_HEARTBEAT_MAX_LASTSEEN) {
+ openslots += server->ready;
+ APR_ARRAY_PUSH(up_servers, hb_server_t *) = server;
+ }
+ }
+ }
+
+ if (openslots > 0) {
+ apr_uint32_t c = 0;
+ apr_uint32_t pick = 0;
+
+ pick = ap_random_pick(0, openslots);
+
+ for (i = 0; i < up_servers->nelts; i++) {
+ server = APR_ARRAY_IDX(up_servers, i, hb_server_t *);
+ if (pick >= c && pick <= c + server->ready) {
+ mycandidate = server->worker;
+ }
+
+ c += server->ready;
+ }
+ }
+
+ apr_pool_destroy(tpool);
+
+ return mycandidate;
+}
+
+static apr_status_t reset(proxy_balancer *balancer, server_rec *s)
+{
+ return APR_SUCCESS;
+}
+
+static apr_status_t age(proxy_balancer *balancer, server_rec *s)
+{
+ return APR_SUCCESS;
+}
+
+static const proxy_balancer_method heartbeat =
+{
+ "heartbeat",
+ &find_best_hb,
+ NULL,
+ &reset,
+ &age,
+ NULL
+};
+
+static int lb_hb_init(apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ apr_size_t size;
+ unsigned int num;
+ lb_hb_ctx_t *ctx = ap_get_module_config(s->module_config,
+ &lbmethod_heartbeat_module);
+
+ /* do nothing on first call */
+ if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG)
+ return OK;
+
+ storage = ap_lookup_provider(AP_SLOTMEM_PROVIDER_GROUP, "shm",
+ AP_SLOTMEM_PROVIDER_VERSION);
+ if (!storage) {
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, APLOGNO(02281)
+ "Failed to lookup provider 'shm' for '%s'. Maybe you "
+ "need to load mod_slotmem_shm?",
+ AP_SLOTMEM_PROVIDER_GROUP);
+ return OK;
+ }
+
+ /* Try to use a slotmem created by mod_heartmonitor */
+ storage->attach(&hm_serversmem, "mod_heartmonitor", &size, &num, p);
+ if (!hm_serversmem)
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, APLOGNO(02282)
+ "No slotmem from mod_heartmonitor");
+ else
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, APLOGNO(02283)
+ "Using slotmem from mod_heartmonitor");
+
+ if (hm_serversmem)
+ ctx->path = "(slotmem)";
+
+ return OK;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ static const char * const aszPred[]={ "mod_heartmonitor.c", NULL };
+ ap_register_provider(p, PROXY_LBMETHOD, "heartbeat", "0", &heartbeat);
+ ap_hook_post_config(lb_hb_init, aszPred, NULL, APR_HOOK_MIDDLE);
+}
+
+static void *lb_hb_create_config(apr_pool_t *p, server_rec *s)
+{
+ lb_hb_ctx_t *ctx = (lb_hb_ctx_t *) apr_palloc(p, sizeof(lb_hb_ctx_t));
+
+ ctx->path = ap_runtime_dir_relative(p, DEFAULT_HEARTBEAT_STORAGE);
+
+ return ctx;
+}
+
+static void *lb_hb_merge_config(apr_pool_t *p, void *basev, void *overridesv)
+{
+ lb_hb_ctx_t *ps = apr_pcalloc(p, sizeof(lb_hb_ctx_t));
+ lb_hb_ctx_t *base = (lb_hb_ctx_t *) basev;
+ lb_hb_ctx_t *overrides = (lb_hb_ctx_t *) overridesv;
+
+ if (overrides->path) {
+ ps->path = apr_pstrdup(p, overrides->path);
+ }
+ else {
+ ps->path = apr_pstrdup(p, base->path);
+ }
+
+ return ps;
+}
+
+static const char *cmd_lb_hb_storage(cmd_parms *cmd,
+ void *dconf, const char *path)
+{
+ apr_pool_t *p = cmd->pool;
+ lb_hb_ctx_t *ctx =
+ (lb_hb_ctx_t *) ap_get_module_config(cmd->server->module_config,
+ &lbmethod_heartbeat_module);
+
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ ctx->path = ap_runtime_dir_relative(p, path);
+
+ return NULL;
+}
+
+static const command_rec cmds[] = {
+ AP_INIT_TAKE1("HeartbeatStorage", cmd_lb_hb_storage, NULL, RSRC_CONF,
+ "Path to read heartbeat data."),
+ {NULL}
+};
+
+AP_DECLARE_MODULE(lbmethod_heartbeat) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ lb_hb_create_config, /* create per-server config structure */
+ lb_hb_merge_config, /* merge per-server config structures */
+ cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/proxy/balancers/mod_lbmethod_heartbeat.dep b/modules/proxy/balancers/mod_lbmethod_heartbeat.dep
new file mode 100644
index 0000000..6000482
--- /dev/null
+++ b/modules/proxy/balancers/mod_lbmethod_heartbeat.dep
@@ -0,0 +1,77 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_lbmethod_heartbeat.mak
+
+.\mod_lbmethod_heartbeat.c : \
+ "..\..\..\include\ap_config.h"\
+ "..\..\..\include\ap_config_layout.h"\
+ "..\..\..\include\ap_expr.h"\
+ "..\..\..\include\ap_hooks.h"\
+ "..\..\..\include\ap_mmn.h"\
+ "..\..\..\include\ap_mpm.h"\
+ "..\..\..\include\ap_provider.h"\
+ "..\..\..\include\ap_regex.h"\
+ "..\..\..\include\ap_release.h"\
+ "..\..\..\include\ap_slotmem.h"\
+ "..\..\..\include\apache_noprobes.h"\
+ "..\..\..\include\heartbeat.h"\
+ "..\..\..\include\http_config.h"\
+ "..\..\..\include\http_connection.h"\
+ "..\..\..\include\http_core.h"\
+ "..\..\..\include\http_log.h"\
+ "..\..\..\include\http_main.h"\
+ "..\..\..\include\http_protocol.h"\
+ "..\..\..\include\http_request.h"\
+ "..\..\..\include\http_vhost.h"\
+ "..\..\..\include\httpd.h"\
+ "..\..\..\include\os.h"\
+ "..\..\..\include\scoreboard.h"\
+ "..\..\..\include\util_cfgtree.h"\
+ "..\..\..\include\util_charset.h"\
+ "..\..\..\include\util_ebcdic.h"\
+ "..\..\..\include\util_filter.h"\
+ "..\..\..\include\util_mutex.h"\
+ "..\..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\..\srclib\apr-util\include\apr_date.h"\
+ "..\..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\..\srclib\apr-util\include\apr_reslist.h"\
+ "..\..\..\srclib\apr-util\include\apr_strmatch.h"\
+ "..\..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\..\srclib\apr-util\include\apu.h"\
+ "..\..\..\srclib\apr\include\apr.h"\
+ "..\..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\..\srclib\apr\include\apr_general.h"\
+ "..\..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\..\srclib\apr\include\apr_time.h"\
+ "..\..\..\srclib\apr\include\apr_user.h"\
+ "..\..\..\srclib\apr\include\apr_version.h"\
+ "..\..\..\srclib\apr\include\apr_want.h"\
+ "..\mod_proxy.h"\
+
+
+..\..\..\build\win32\httpd.rc : \
+ "..\..\..\include\ap_release.h"\
+
diff --git a/modules/proxy/balancers/mod_lbmethod_heartbeat.dsp b/modules/proxy/balancers/mod_lbmethod_heartbeat.dsp
new file mode 100644
index 0000000..cb003b0
--- /dev/null
+++ b/modules/proxy/balancers/mod_lbmethod_heartbeat.dsp
@@ -0,0 +1,123 @@
+# Microsoft Developer Studio Project File - Name="mod_lbmethod_heartbeat" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_lbmethod_heartbeat - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_lbmethod_heartbeat.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_lbmethod_heartbeat.mak" CFG="mod_lbmethod_heartbeat - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_lbmethod_heartbeat - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_lbmethod_heartbeat - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_lbmethod_heartbeat - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I ".." /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_lbmethod_heartbeat_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_lbmethod_heartbeat.res" /i "../../../include" /i "../../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_lbmethod_heartbeat.so" /d LONG_NAME="lbmethod_heartbeat_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:".\Release\mod_lbmethod_heartbeat.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_lbmethod_heartbeat.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_lbmethod_heartbeat.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_lbmethod_heartbeat.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_lbmethod_heartbeat.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_lbmethod_heartbeat - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I ".." /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_lbmethod_heartbeat_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_lbmethod_heartbeat.res" /i "../../../include" /i "../../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_lbmethod_heartbeat.so" /d LONG_NAME="lbmethod_heartbeat_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_lbmethod_heartbeat.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_lbmethod_heartbeat.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_lbmethod_heartbeat.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_lbmethod_heartbeat.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_lbmethod_heartbeat.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_lbmethod_heartbeat - Win32 Release"
+# Name "mod_lbmethod_heartbeat - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\mod_lbmethod_heartbeat.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter ".h"
+# Begin Source File
+
+SOURCE=..\mod_proxy.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/proxy/balancers/mod_lbmethod_heartbeat.mak b/modules/proxy/balancers/mod_lbmethod_heartbeat.mak
new file mode 100644
index 0000000..31bd4af
--- /dev/null
+++ b/modules/proxy/balancers/mod_lbmethod_heartbeat.mak
@@ -0,0 +1,408 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_lbmethod_heartbeat.dsp
+!IF "$(CFG)" == ""
+CFG=mod_lbmethod_heartbeat - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_lbmethod_heartbeat - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_lbmethod_heartbeat - Win32 Release" && "$(CFG)" != "mod_lbmethod_heartbeat - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_lbmethod_heartbeat.mak" CFG="mod_lbmethod_heartbeat - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_lbmethod_heartbeat - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_lbmethod_heartbeat - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_lbmethod_heartbeat - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_lbmethod_heartbeat.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_proxy_balancer - Win32 Release" "mod_proxy - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_lbmethod_heartbeat.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_proxy - Win32 ReleaseCLEAN" "mod_proxy_balancer - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_lbmethod_heartbeat.obj"
+ -@erase "$(INTDIR)\mod_lbmethod_heartbeat.res"
+ -@erase "$(INTDIR)\mod_lbmethod_heartbeat_src.idb"
+ -@erase "$(INTDIR)\mod_lbmethod_heartbeat_src.pdb"
+ -@erase "$(OUTDIR)\mod_lbmethod_heartbeat.exp"
+ -@erase "$(OUTDIR)\mod_lbmethod_heartbeat.lib"
+ -@erase "$(OUTDIR)\mod_lbmethod_heartbeat.pdb"
+ -@erase "$(OUTDIR)\mod_lbmethod_heartbeat.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I ".." /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_lbmethod_heartbeat_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_lbmethod_heartbeat.res" /i "../../../include" /i "../../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_lbmethod_heartbeat.so" /d LONG_NAME="lbmethod_heartbeat_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_lbmethod_heartbeat.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_lbmethod_heartbeat.pdb" /debug /out:"$(OUTDIR)\mod_lbmethod_heartbeat.so" /implib:"$(OUTDIR)\mod_lbmethod_heartbeat.lib" /base:@..\..\..\os\win32\BaseAddr.ref,mod_lbmethod_heartbeat.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_lbmethod_heartbeat.obj" \
+ "$(INTDIR)\mod_lbmethod_heartbeat.res" \
+ "..\..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\..\Release\libhttpd.lib" \
+ "..\Release\mod_proxy.lib" \
+ "..\Release\mod_proxy_balancer.lib"
+
+"$(OUTDIR)\mod_lbmethod_heartbeat.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_lbmethod_heartbeat.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_lbmethod_heartbeat.so"
+ if exist .\Release\mod_lbmethod_heartbeat.so.manifest mt.exe -manifest .\Release\mod_lbmethod_heartbeat.so.manifest -outputresource:.\Release\mod_lbmethod_heartbeat.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_lbmethod_heartbeat - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_lbmethod_heartbeat.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_proxy_balancer - Win32 Debug" "mod_proxy - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_lbmethod_heartbeat.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_proxy - Win32 DebugCLEAN" "mod_proxy_balancer - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_lbmethod_heartbeat.obj"
+ -@erase "$(INTDIR)\mod_lbmethod_heartbeat.res"
+ -@erase "$(INTDIR)\mod_lbmethod_heartbeat_src.idb"
+ -@erase "$(INTDIR)\mod_lbmethod_heartbeat_src.pdb"
+ -@erase "$(OUTDIR)\mod_lbmethod_heartbeat.exp"
+ -@erase "$(OUTDIR)\mod_lbmethod_heartbeat.lib"
+ -@erase "$(OUTDIR)\mod_lbmethod_heartbeat.pdb"
+ -@erase "$(OUTDIR)\mod_lbmethod_heartbeat.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I ".." /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_lbmethod_heartbeat_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_lbmethod_heartbeat.res" /i "../../../include" /i "../../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_lbmethod_heartbeat.so" /d LONG_NAME="lbmethod_heartbeat_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_lbmethod_heartbeat.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_lbmethod_heartbeat.pdb" /debug /out:"$(OUTDIR)\mod_lbmethod_heartbeat.so" /implib:"$(OUTDIR)\mod_lbmethod_heartbeat.lib" /base:@..\..\..\os\win32\BaseAddr.ref,mod_lbmethod_heartbeat.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_lbmethod_heartbeat.obj" \
+ "$(INTDIR)\mod_lbmethod_heartbeat.res" \
+ "..\..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\..\Debug\libhttpd.lib" \
+ "..\Debug\mod_proxy.lib" \
+ "..\Debug\mod_proxy_balancer.lib"
+
+"$(OUTDIR)\mod_lbmethod_heartbeat.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_lbmethod_heartbeat.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_lbmethod_heartbeat.so"
+ if exist .\Debug\mod_lbmethod_heartbeat.so.manifest mt.exe -manifest .\Debug\mod_lbmethod_heartbeat.so.manifest -outputresource:.\Debug\mod_lbmethod_heartbeat.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_lbmethod_heartbeat.dep")
+!INCLUDE "mod_lbmethod_heartbeat.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_lbmethod_heartbeat.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_lbmethod_heartbeat - Win32 Release" || "$(CFG)" == "mod_lbmethod_heartbeat - Win32 Debug"
+SOURCE=.\mod_lbmethod_heartbeat.c
+
+"$(INTDIR)\mod_lbmethod_heartbeat.obj" : $(SOURCE) "$(INTDIR)"
+
+
+!IF "$(CFG)" == "mod_lbmethod_heartbeat - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\proxy\balancers"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy\balancers"
+
+!ELSEIF "$(CFG)" == "mod_lbmethod_heartbeat - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\proxy\balancers"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy\balancers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_lbmethod_heartbeat - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\proxy\balancers"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy\balancers"
+
+!ELSEIF "$(CFG)" == "mod_lbmethod_heartbeat - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\proxy\balancers"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy\balancers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_lbmethod_heartbeat - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\proxy\balancers"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\proxy\balancers"
+
+!ELSEIF "$(CFG)" == "mod_lbmethod_heartbeat - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\proxy\balancers"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\proxy\balancers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_lbmethod_heartbeat - Win32 Release"
+
+"mod_proxy - Win32 Release" :
+ cd ".\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Release"
+ cd ".\balancers"
+
+"mod_proxy - Win32 ReleaseCLEAN" :
+ cd ".\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Release" RECURSE=1 CLEAN
+ cd ".\balancers"
+
+!ELSEIF "$(CFG)" == "mod_lbmethod_heartbeat - Win32 Debug"
+
+"mod_proxy - Win32 Debug" :
+ cd ".\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Debug"
+ cd ".\balancers"
+
+"mod_proxy - Win32 DebugCLEAN" :
+ cd ".\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\balancers"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_lbmethod_heartbeat - Win32 Release"
+
+"mod_proxy_balancer - Win32 Release" :
+ cd ".\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy_balancer.mak" CFG="mod_proxy_balancer - Win32 Release"
+ cd ".\balancers"
+
+"mod_proxy_balancer - Win32 ReleaseCLEAN" :
+ cd ".\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy_balancer.mak" CFG="mod_proxy_balancer - Win32 Release" RECURSE=1 CLEAN
+ cd ".\balancers"
+
+!ELSEIF "$(CFG)" == "mod_lbmethod_heartbeat - Win32 Debug"
+
+"mod_proxy_balancer - Win32 Debug" :
+ cd ".\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy_balancer.mak" CFG="mod_proxy_balancer - Win32 Debug"
+ cd ".\balancers"
+
+"mod_proxy_balancer - Win32 DebugCLEAN" :
+ cd ".\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy_balancer.mak" CFG="mod_proxy_balancer - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\balancers"
+
+!ENDIF
+
+SOURCE=..\..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_lbmethod_heartbeat - Win32 Release"
+
+
+"$(INTDIR)\mod_lbmethod_heartbeat.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_lbmethod_heartbeat.res" /i "../../../include" /i "../../../srclib/apr/include" /i "../../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_lbmethod_heartbeat.so" /d LONG_NAME="lbmethod_heartbeat_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_lbmethod_heartbeat - Win32 Debug"
+
+
+"$(INTDIR)\mod_lbmethod_heartbeat.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_lbmethod_heartbeat.res" /i "../../../include" /i "../../../srclib/apr/include" /i "../../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_lbmethod_heartbeat.so" /d LONG_NAME="lbmethod_heartbeat_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+
+!ENDIF
+
diff --git a/modules/proxy/config.m4 b/modules/proxy/config.m4
new file mode 100644
index 0000000..01f09fa
--- /dev/null
+++ b/modules/proxy/config.m4
@@ -0,0 +1,79 @@
+dnl modules enabled in this directory by default
+
+APACHE_MODPATH_INIT(proxy)
+
+proxy_objs="mod_proxy.lo proxy_util.lo"
+APACHE_MODULE(proxy, Apache proxy module, $proxy_objs, , most)
+
+dnl set aside module selections and default, and set the module default to the
+dnl same scope (shared|static) as selected for mod proxy, along with setting
+dnl the default selection to "most" for remaining proxy modules, mirroring the
+dnl behavior of 2.4.1 and later, but failing ./configure only if an explicitly
+dnl enabled module is missing its prereqs
+save_module_selection=$module_selection
+save_module_default=$module_default
+if test "$enable_proxy" != "no"; then
+ module_selection=most
+ if test "$enable_proxy" = "shared" -o "$enable_proxy" = "static"; then
+ module_default=$enable_proxy
+ fi
+fi
+
+proxy_connect_objs="mod_proxy_connect.lo"
+proxy_ftp_objs="mod_proxy_ftp.lo"
+proxy_http_objs="mod_proxy_http.lo"
+proxy_fcgi_objs="mod_proxy_fcgi.lo"
+proxy_scgi_objs="mod_proxy_scgi.lo"
+proxy_uwsgi_objs="mod_proxy_uwsgi.lo"
+proxy_fdpass_objs="mod_proxy_fdpass.lo"
+proxy_ajp_objs="mod_proxy_ajp.lo ajp_header.lo ajp_link.lo ajp_msg.lo ajp_utils.lo"
+proxy_wstunnel_objs="mod_proxy_wstunnel.lo"
+proxy_balancer_objs="mod_proxy_balancer.lo"
+
+case "$host" in
+ *os2*)
+ # OS/2 DLLs must resolve all symbols at build time and
+ # these sub-modules need some from the main proxy module
+ proxy_connect_objs="$proxy_connect_objs mod_proxy.la"
+ proxy_ftp_objs="$proxy_ftp_objs mod_proxy.la"
+ proxy_http_objs="$proxy_http_objs mod_proxy.la"
+ proxy_fcgi_objs="$proxy_fcgi_objs mod_proxy.la"
+ proxy_scgi_objs="$proxy_scgi_objs mod_proxy.la"
+ proxy_uwsgi_objs="$proxy_uwsgi_objs mod_proxy.la"
+ proxy_fdpass_objs="$proxy_fdpass_objs mod_proxy.la"
+ proxy_ajp_objs="$proxy_ajp_objs mod_proxy.la"
+ proxy_wstunnel_objs="$proxy_wstunnel_objs mod_proxy.la"
+ proxy_balancer_objs="$proxy_balancer_objs mod_proxy.la"
+ ;;
+esac
+
+APACHE_MODULE(proxy_connect, Apache proxy CONNECT module. Requires --enable-proxy., $proxy_connect_objs, , most, , proxy)
+APACHE_MODULE(proxy_ftp, Apache proxy FTP module. Requires --enable-proxy., $proxy_ftp_objs, , most, , proxy)
+APACHE_MODULE(proxy_http, Apache proxy HTTP module. Requires --enable-proxy., $proxy_http_objs, , most, , proxy)
+APACHE_MODULE(proxy_fcgi, Apache proxy FastCGI module. Requires --enable-proxy., $proxy_fcgi_objs, , most, , proxy)
+APACHE_MODULE(proxy_scgi, Apache proxy SCGI module. Requires --enable-proxy., $proxy_scgi_objs, , most, , proxy)
+APACHE_MODULE(proxy_uwsgi, Apache proxy UWSGI module. Requires --enable-proxy., $proxy_uwsgi_objs, , most, , proxy)
+APACHE_MODULE(proxy_fdpass, Apache proxy to Unix Daemon Socket module. Requires --enable-proxy., $proxy_fdpass_objs, , most, [
+ AC_CHECK_DECL(CMSG_DATA,,, [
+ #include <sys/types.h>
+ #include <sys/socket.h>
+ ])
+ if test $ac_cv_have_decl_CMSG_DATA = "no"; then
+ AC_MSG_WARN([Your system does not support CMSG_DATA.])
+ enable_proxy_fdpass=no
+ fi
+],proxy)
+APACHE_MODULE(proxy_wstunnel, Apache proxy Websocket Tunnel module. Requires --enable-proxy., $proxy_wstunnel_objs, , most, , proxy)
+APACHE_MODULE(proxy_ajp, Apache proxy AJP module. Requires --enable-proxy., $proxy_ajp_objs, , most, , proxy)
+APACHE_MODULE(proxy_balancer, Apache proxy BALANCER module. Requires --enable-proxy., $proxy_balancer_objs, , most, , proxy)
+
+APACHE_MODULE(proxy_express, mass reverse-proxy module. Requires --enable-proxy., , , most, , proxy)
+APACHE_MODULE(proxy_hcheck, [reverse-proxy health-check module. Requires --enable-proxy and --enable-watchdog.], , , most, , [proxy,watchdog])
+
+APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/$modpath_current -I\$(top_srcdir)/modules/http2])
+
+module_selection=$save_module_selection
+module_default=$save_module_default
+
+APACHE_MODPATH_FINISH
+
diff --git a/modules/proxy/libproxy.exp b/modules/proxy/libproxy.exp
new file mode 100644
index 0000000..a20f237
--- /dev/null
+++ b/modules/proxy/libproxy.exp
@@ -0,0 +1 @@
+proxy_module
diff --git a/modules/proxy/mod_proxy.c b/modules/proxy/mod_proxy.c
new file mode 100644
index 0000000..9225bc9
--- /dev/null
+++ b/modules/proxy/mod_proxy.c
@@ -0,0 +1,3474 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mod_proxy.h"
+#include "mod_core.h"
+#include "apr_optional.h"
+#include "apr_strings.h"
+#include "scoreboard.h"
+#include "mod_status.h"
+#include "proxy_util.h"
+
+#if (MODULE_MAGIC_NUMBER_MAJOR > 20020903)
+#include "mod_ssl.h"
+#else
+APR_DECLARE_OPTIONAL_FN(int, ssl_proxy_enable, (conn_rec *));
+APR_DECLARE_OPTIONAL_FN(int, ssl_engine_disable, (conn_rec *));
+APR_DECLARE_OPTIONAL_FN(int, ssl_engine_set, (conn_rec *,
+ ap_conf_vector_t *,
+ int proxy, int enable));
+#endif
+
+#ifndef MAX
+#define MAX(x,y) ((x) >= (y) ? (x) : (y))
+#endif
+
+/*
+ * We do health-checks only if that (sub)module is loaded in. This
+ * allows for us to continue as is w/o requiring mod_watchdog for
+ * those implementations which aren't using health checks
+ */
+static APR_OPTIONAL_FN_TYPE(set_worker_hc_param) *set_worker_hc_param_f = NULL;
+
+/* Externals */
+proxy_hcmethods_t PROXY_DECLARE_DATA proxy_hcmethods[] = {
+ {NONE, "NONE", 1},
+ {TCP, "TCP", 1},
+ {OPTIONS, "OPTIONS", 1},
+ {HEAD, "HEAD", 1},
+ {GET, "GET", 1},
+ {CPING, "CPING", 0},
+ {PROVIDER, "PROVIDER", 0},
+ {OPTIONS11, "OPTIONS11", 1},
+ {HEAD11, "HEAD11", 1},
+ {GET11, "GET11", 1},
+ {EOT, NULL, 1}
+};
+
+proxy_wstat_t PROXY_DECLARE_DATA proxy_wstat_tbl[] = {
+ {PROXY_WORKER_INITIALIZED, PROXY_WORKER_INITIALIZED_FLAG, "Init "},
+ {PROXY_WORKER_IGNORE_ERRORS, PROXY_WORKER_IGNORE_ERRORS_FLAG, "Ign "},
+ {PROXY_WORKER_DRAIN, PROXY_WORKER_DRAIN_FLAG, "Drn "},
+ {PROXY_WORKER_GENERIC, PROXY_WORKER_GENERIC_FLAG, "Gen "},
+ {PROXY_WORKER_IN_SHUTDOWN, PROXY_WORKER_IN_SHUTDOWN_FLAG, "Shut "},
+ {PROXY_WORKER_DISABLED, PROXY_WORKER_DISABLED_FLAG, "Dis "},
+ {PROXY_WORKER_STOPPED, PROXY_WORKER_STOPPED_FLAG, "Stop "},
+ {PROXY_WORKER_IN_ERROR, PROXY_WORKER_IN_ERROR_FLAG, "Err "},
+ {PROXY_WORKER_HOT_STANDBY, PROXY_WORKER_HOT_STANDBY_FLAG, "Stby "},
+ {PROXY_WORKER_HOT_SPARE, PROXY_WORKER_HOT_SPARE_FLAG, "Spar "},
+ {PROXY_WORKER_FREE, PROXY_WORKER_FREE_FLAG, "Free "},
+ {PROXY_WORKER_HC_FAIL, PROXY_WORKER_HC_FAIL_FLAG, "HcFl "},
+ {0x0, '\0', NULL}
+};
+
+static const char * const proxy_id = "proxy";
+apr_global_mutex_t *proxy_mutex = NULL;
+
+/*
+ * A Web proxy module. Stages:
+ *
+ * translate_name: set filename to proxy:<URL>
+ * map_to_storage: run proxy_walk (rather than directory_walk/file_walk)
+ * can't trust directory_walk/file_walk since these are
+ * not in our filesystem. Prevents mod_http from serving
+ * the TRACE request we will set aside to handle later.
+ * fix_ups: convert the URL stored in the filename to the
+ * canonical form.
+ * handler: handle proxy requests
+ */
+
+/* -------------------------------------------------------------- */
+/* Translate the URL into a 'filename' */
+
+static const char *set_worker_param(apr_pool_t *p,
+ server_rec *s,
+ proxy_worker *worker,
+ const char *key,
+ const char *val)
+{
+
+ int ival;
+ apr_interval_time_t timeout;
+
+ if (!strcasecmp(key, "loadfactor")) {
+ /* Normalized load factor. Used with BalancerMember,
+ * it is a number between 1 and 100.
+ */
+ double fval = atof(val);
+ ival = fval * 100.0;
+ if (ival < 100 || ival > 10000)
+ return "LoadFactor must be a number between 1..100";
+ worker->s->lbfactor = ival;
+ }
+ else if (!strcasecmp(key, "retry")) {
+ /* If set it will give the retry timeout for the worker
+ * The default value is 60 seconds, meaning that if
+ * in error state, it will be retried after that timeout.
+ */
+ ival = atoi(val);
+ if (ival < 0)
+ return "Retry must be a positive value";
+ worker->s->retry = apr_time_from_sec(ival);
+ worker->s->retry_set = 1;
+ }
+ else if (!strcasecmp(key, "ttl")) {
+ /* Time in seconds that will destroy all the connections
+ * that exceed the smax
+ */
+ ival = atoi(val);
+ if (ival < 1)
+ return "TTL must be at least one second";
+ worker->s->ttl = apr_time_from_sec(ival);
+ }
+ else if (!strcasecmp(key, "min")) {
+ /* Initial number of connections to remote
+ */
+ ival = atoi(val);
+ if (ival < 0)
+ return "Min must be a positive number";
+ worker->s->min = ival;
+ }
+ else if (!strcasecmp(key, "max")) {
+ /* Maximum number of connections to remote
+ */
+ ival = atoi(val);
+ if (ival < 0)
+ return "Max must be a positive number";
+ worker->s->hmax = ival;
+ }
+ /* XXX: More intelligent naming needed */
+ else if (!strcasecmp(key, "smax")) {
+ /* Maximum number of connections to remote that
+ * will not be destroyed
+ */
+ ival = atoi(val);
+ if (ival < 0)
+ return "Smax must be a positive number";
+ worker->s->smax = ival;
+ }
+ else if (!strcasecmp(key, "acquire")) {
+ /* Acquire timeout in given unit (default is milliseconds).
+ * If set this will be the maximum time to
+ * wait for a free connection.
+ */
+ if (ap_timeout_parameter_parse(val, &timeout, "ms") != APR_SUCCESS)
+ return "Acquire timeout has wrong format";
+ if (timeout < 1000)
+ return "Acquire must be at least one millisecond";
+ worker->s->acquire = timeout;
+ worker->s->acquire_set = 1;
+ }
+ else if (!strcasecmp(key, "timeout")) {
+ /* Connection timeout in seconds.
+ * Defaults to server timeout.
+ */
+ ival = atoi(val);
+ if (ival < 1)
+ return "Timeout must be at least one second";
+ worker->s->timeout = apr_time_from_sec(ival);
+ worker->s->timeout_set = 1;
+ }
+ else if (!strcasecmp(key, "iobuffersize")) {
+ long s = atol(val);
+ if (s < 512 && s) {
+ return "IOBufferSize must be >= 512 bytes, or 0 for system default.";
+ }
+ worker->s->io_buffer_size = (s ? s : AP_IOBUFSIZE);
+ worker->s->io_buffer_size_set = 1;
+ }
+ else if (!strcasecmp(key, "receivebuffersize")) {
+ ival = atoi(val);
+ if (ival < 512 && ival != 0) {
+ return "ReceiveBufferSize must be >= 512 bytes, or 0 for system default.";
+ }
+ worker->s->recv_buffer_size = ival;
+ worker->s->recv_buffer_size_set = 1;
+ }
+ else if (!strcasecmp(key, "keepalive")) {
+ if (!strcasecmp(val, "on"))
+ worker->s->keepalive = 1;
+ else if (!strcasecmp(val, "off"))
+ worker->s->keepalive = 0;
+ else
+ return "KeepAlive must be On|Off";
+ worker->s->keepalive_set = 1;
+ }
+ else if (!strcasecmp(key, "disablereuse")) {
+ if (!strcasecmp(val, "on"))
+ worker->s->disablereuse = 1;
+ else if (!strcasecmp(val, "off"))
+ worker->s->disablereuse = 0;
+ else
+ return "DisableReuse must be On|Off";
+ worker->s->disablereuse_set = 1;
+ }
+ else if (!strcasecmp(key, "enablereuse")) {
+ if (!strcasecmp(val, "on"))
+ worker->s->disablereuse = 0;
+ else if (!strcasecmp(val, "off"))
+ worker->s->disablereuse = 1;
+ else
+ return "EnableReuse must be On|Off";
+ worker->s->disablereuse_set = 1;
+ }
+ else if (!strcasecmp(key, "route")) {
+ /* Worker route.
+ */
+ if (strlen(val) >= sizeof(worker->s->route))
+ return apr_psprintf(p, "Route length must be < %d characters",
+ (int)sizeof(worker->s->route));
+ PROXY_STRNCPY(worker->s->route, val);
+ }
+ else if (!strcasecmp(key, "redirect")) {
+ /* Worker redirection route.
+ */
+ if (strlen(val) >= sizeof(worker->s->redirect))
+ return apr_psprintf(p, "Redirect length must be < %d characters",
+ (int)sizeof(worker->s->redirect));
+ PROXY_STRNCPY(worker->s->redirect, val);
+ }
+ else if (!strcasecmp(key, "status")) {
+ const char *v;
+ int mode = 1;
+ apr_status_t rv;
+ /* Worker status.
+ */
+ for (v = val; *v; v++) {
+ if (*v == '+') {
+ mode = 1;
+ v++;
+ }
+ else if (*v == '-') {
+ mode = 0;
+ v++;
+ }
+ rv = ap_proxy_set_wstatus(*v, mode, worker);
+ if (rv != APR_SUCCESS)
+ return "Unknown status parameter option";
+ }
+ }
+ else if (!strcasecmp(key, "flushpackets")) {
+ if (!strcasecmp(val, "on"))
+ worker->s->flush_packets = flush_on;
+ else if (!strcasecmp(val, "off"))
+ worker->s->flush_packets = flush_off;
+ else if (!strcasecmp(val, "auto"))
+ worker->s->flush_packets = flush_auto;
+ else
+ return "flushpackets must be on|off|auto";
+ }
+ else if (!strcasecmp(key, "flushwait")) {
+ ival = atoi(val);
+ if (ival > 1000 || ival < 0) {
+ return "flushwait must be <= 1000, or 0 for system default of 10 millseconds.";
+ }
+ if (ival == 0)
+ worker->s->flush_wait = PROXY_FLUSH_WAIT;
+ else
+ worker->s->flush_wait = ival * 1000; /* change to microseconds */
+ }
+ else if (!strcasecmp(key, "ping")) {
+ /* Ping/Pong timeout in given unit (default is second).
+ */
+ if (ap_timeout_parameter_parse(val, &timeout, "s") != APR_SUCCESS)
+ return "Ping/Pong timeout has wrong format";
+ if (timeout < 1000)
+ return "Ping/Pong timeout must be at least one millisecond";
+ worker->s->ping_timeout = timeout;
+ worker->s->ping_timeout_set = 1;
+ }
+ else if (!strcasecmp(key, "lbset")) {
+ ival = atoi(val);
+ if (ival < 0 || ival > 99)
+ return "lbset must be between 0 and 99";
+ worker->s->lbset = ival;
+ }
+ else if (!strcasecmp(key, "connectiontimeout")) {
+ /* Request timeout in given unit (default is second).
+ * Defaults to connection timeout
+ */
+ if (ap_timeout_parameter_parse(val, &timeout, "s") != APR_SUCCESS)
+ return "Connectiontimeout has wrong format";
+ if (timeout < 1000)
+ return "Connectiontimeout must be at least one millisecond.";
+ worker->s->conn_timeout = timeout;
+ worker->s->conn_timeout_set = 1;
+ }
+ else if (!strcasecmp(key, "flusher")) {
+ if (PROXY_STRNCPY(worker->s->flusher, val) != APR_SUCCESS) {
+ return apr_psprintf(p, "flusher name length must be < %d characters",
+ (int)sizeof(worker->s->flusher));
+ }
+ }
+ else if (!strcasecmp(key, "upgrade")) {
+ if (PROXY_STRNCPY(worker->s->upgrade,
+ strcasecmp(val, "ANY") ? val : "*") != APR_SUCCESS) {
+ return apr_psprintf(p, "upgrade protocol length must be < %d characters",
+ (int)sizeof(worker->s->upgrade));
+ }
+ }
+ else if (!strcasecmp(key, "responsefieldsize")) {
+ long s = atol(val);
+ if (s < 0) {
+ return "ResponseFieldSize must be greater than 0 bytes, or 0 for system default.";
+ }
+ worker->s->response_field_size = (s ? s : HUGE_STRING_LEN);
+ worker->s->response_field_size_set = 1;
+ }
+ else if (!strcasecmp(key, "secret")) {
+ if (PROXY_STRNCPY(worker->s->secret, val) != APR_SUCCESS) {
+ return apr_psprintf(p, "Secret length must be < %d characters",
+ (int)sizeof(worker->s->secret));
+ }
+ }
+ else {
+ if (set_worker_hc_param_f) {
+ return set_worker_hc_param_f(p, s, worker, key, val, NULL);
+ } else {
+ return "unknown Worker parameter";
+ }
+ }
+ return NULL;
+}
+
+static const char *set_balancer_param(proxy_server_conf *conf,
+ apr_pool_t *p,
+ proxy_balancer *balancer,
+ const char *key,
+ const char *val)
+{
+
+ int ival;
+ if (!strcasecmp(key, "stickysession")) {
+ char *path;
+ /* Balancer sticky session name.
+ * Set to something like JSESSIONID or
+ * PHPSESSIONID, etc..,
+ */
+ if (strlen(val) >= sizeof(balancer->s->sticky_path))
+ apr_psprintf(p, "stickysession length must be < %d characters",
+ (int)sizeof(balancer->s->sticky_path));
+ PROXY_STRNCPY(balancer->s->sticky_path, val);
+ PROXY_STRNCPY(balancer->s->sticky, val);
+
+ if ((path = strchr((char *)balancer->s->sticky, '|'))) {
+ *path++ = '\0';
+ PROXY_STRNCPY(balancer->s->sticky_path, path);
+ }
+ }
+ else if (!strcasecmp(key, "stickysessionsep")) {
+ /* separator/delimiter for sessionid and route,
+ * normally '.'
+ */
+ if (strlen(val) != 1) {
+ if (!strcasecmp(val, "off"))
+ balancer->s->sticky_separator = 0;
+ else
+ return "stickysessionsep must be a single character or Off";
+ }
+ else
+ balancer->s->sticky_separator = *val;
+ balancer->s->sticky_separator_set = 1;
+ }
+ else if (!strcasecmp(key, "nofailover")) {
+ /* If set to 'on' the session will break
+ * if the worker is in error state or
+ * disabled.
+ */
+ if (!strcasecmp(val, "on"))
+ balancer->s->sticky_force = 1;
+ else if (!strcasecmp(val, "off"))
+ balancer->s->sticky_force = 0;
+ else
+ return "failover must be On|Off";
+ balancer->s->sticky_force_set = 1;
+ }
+ else if (!strcasecmp(key, "timeout")) {
+ /* Balancer timeout in seconds.
+ * If set this will be the maximum time to
+ * wait for a free worker.
+ * Default is not to wait.
+ */
+ ival = atoi(val);
+ if (ival < 1)
+ return "timeout must be at least one second";
+ balancer->s->timeout = apr_time_from_sec(ival);
+ }
+ else if (!strcasecmp(key, "maxattempts")) {
+ /* Maximum number of failover attempts before
+ * giving up.
+ */
+ ival = atoi(val);
+ if (ival < 0)
+ return "maximum number of attempts must be a positive number";
+ balancer->s->max_attempts = ival;
+ balancer->s->max_attempts_set = 1;
+ }
+ else if (!strcasecmp(key, "lbmethod")) {
+ proxy_balancer_method *provider;
+ if (strlen(val) > (sizeof(balancer->s->lbpname)-1))
+ return "unknown lbmethod";
+ provider = ap_lookup_provider(PROXY_LBMETHOD, val, "0");
+ if (provider) {
+ balancer->lbmethod = provider;
+ if (PROXY_STRNCPY(balancer->s->lbpname, val) == APR_SUCCESS) {
+ balancer->lbmethod_set = 1;
+ return NULL;
+ }
+ else {
+ return "lbmethod name too large";
+ }
+ }
+ return "unknown lbmethod";
+ }
+ else if (!strcasecmp(key, "scolonpathdelim")) {
+ /* If set to 'on' then ';' will also be
+ * used as a session path separator/delim (ala
+ * mod_jk)
+ */
+ if (!strcasecmp(val, "on"))
+ balancer->s->scolonsep = 1;
+ else if (!strcasecmp(val, "off"))
+ balancer->s->scolonsep = 0;
+ else
+ return "scolonpathdelim must be On|Off";
+ balancer->s->scolonsep_set = 1;
+ }
+ else if (!strcasecmp(key, "failonstatus")) {
+ char *val_split;
+ char *status;
+ char *tok_state;
+
+ val_split = apr_pstrdup(p, val);
+
+ balancer->errstatuses = apr_array_make(p, 1, sizeof(int));
+
+ status = apr_strtok(val_split, ", ", &tok_state);
+ while (status != NULL) {
+ ival = atoi(status);
+ if (ap_is_HTTP_VALID_RESPONSE(ival)) {
+ *(int *)apr_array_push(balancer->errstatuses) = ival;
+ }
+ else {
+ return "failonstatus must be one or more HTTP response codes";
+ }
+ status = apr_strtok(NULL, ", ", &tok_state);
+ }
+
+ }
+ else if (!strcasecmp(key, "failontimeout")) {
+ if (!strcasecmp(val, "on"))
+ balancer->failontimeout = 1;
+ else if (!strcasecmp(val, "off"))
+ balancer->failontimeout = 0;
+ else
+ return "failontimeout must be On|Off";
+ balancer->failontimeout_set = 1;
+ }
+ else if (!strcasecmp(key, "nonce")) {
+ if (!strcasecmp(val, "None")) {
+ *balancer->s->nonce = '\0';
+ }
+ else {
+ if (PROXY_STRNCPY(balancer->s->nonce, val) != APR_SUCCESS) {
+ return "Provided nonce is too large";
+ }
+ }
+ balancer->s->nonce_set = 1;
+ }
+ else if (!strcasecmp(key, "growth")) {
+ ival = atoi(val);
+ if (ival < 1 || ival > 100) /* arbitrary limit here */
+ return "growth must be between 1 and 100";
+ balancer->growth = ival;
+ balancer->growth_set = 1;
+ }
+ else if (!strcasecmp(key, "forcerecovery")) {
+ if (!strcasecmp(val, "on"))
+ balancer->s->forcerecovery = 1;
+ else if (!strcasecmp(val, "off"))
+ balancer->s->forcerecovery = 0;
+ else
+ return "forcerecovery must be On|Off";
+ balancer->s->forcerecovery_set = 1;
+ }
+ else {
+ return "unknown Balancer parameter";
+ }
+ return NULL;
+}
+
+static int alias_match(const char *uri, const char *alias_fakename)
+{
+ const char *end_fakename = alias_fakename + strlen(alias_fakename);
+ const char *aliasp = alias_fakename, *urip = uri;
+ const char *end_uri = uri + strlen(uri);
+
+ while (aliasp < end_fakename && urip < end_uri) {
+ if (*aliasp == '/') {
+ /* any number of '/' in the alias matches any number in
+ * the supplied URI, but there must be at least one...
+ */
+ if (*urip != '/')
+ return 0;
+
+ while (*aliasp == '/')
+ ++aliasp;
+ while (*urip == '/')
+ ++urip;
+ }
+ else {
+ /* Other characters are compared literally */
+ if (*urip++ != *aliasp++)
+ return 0;
+ }
+ }
+
+ /* fixup badly encoded stuff (e.g. % as last character) */
+ if (aliasp > end_fakename) {
+ aliasp = end_fakename;
+ }
+ if (urip > end_uri) {
+ urip = end_uri;
+ }
+
+ /* We reach the end of the uri before the end of "alias_fakename"
+ * for example uri is "/" and alias_fakename "/examples"
+ */
+ if (urip == end_uri && aliasp != end_fakename) {
+ return 0;
+ }
+
+ /* Check last alias path component matched all the way */
+ if (aliasp[-1] != '/' && *urip != '\0' && *urip != '/')
+ return 0;
+
+ /* Return number of characters from URI which matched (may be
+ * greater than length of alias, since we may have matched
+ * doubled slashes)
+ */
+
+ return urip - uri;
+}
+
+/*
+ * Inspired by mod_jk's jk_servlet_normalize().
+ */
+static int alias_match_servlet(apr_pool_t *p,
+ const char **urip,
+ const char *alias)
+{
+ char *map;
+ const char *uri = *urip;
+ apr_array_header_t *stack;
+ int map_pos, uri_pos, alias_pos, first_pos;
+ int alias_depth = 0, depth;
+
+ /* Both uri and alias should start with '/' */
+ if (uri[0] != '/' || alias[0] != '/') {
+ return 0;
+ }
+
+ stack = apr_array_make(p, 5, sizeof(int));
+ map = apr_palloc(p, strlen(uri) + 1);
+ map[0] = '/';
+ map[1] = '\0';
+
+ map_pos = uri_pos = alias_pos = first_pos = 1;
+ while (uri[uri_pos] != '\0') {
+ /* Remove path parameters ;foo=bar/ from any path segment */
+ if (uri[uri_pos] == ';') {
+ do {
+ uri_pos++;
+ } while (uri[uri_pos] != '/' && uri[uri_pos] != '\0');
+ continue;
+ }
+
+ if (map[map_pos - 1] == '/') {
+ /* Collapse ///// sequences to / */
+ if (uri[uri_pos] == '/') {
+ do {
+ uri_pos++;
+ } while (uri[uri_pos] == '/');
+ continue;
+ }
+
+ if (uri[uri_pos] == '.') {
+ /* Remove /./ segments */
+ if (uri[uri_pos + 1] == '/'
+ || uri[uri_pos + 1] == ';'
+ || uri[uri_pos + 1] == '\0') {
+ uri_pos++;
+ if (uri[uri_pos] == '/') {
+ uri_pos++;
+ }
+ continue;
+ }
+
+ /* Remove /xx/../ segments */
+ if (uri[uri_pos + 1] == '.'
+ && (uri[uri_pos + 2] == '/'
+ || uri[uri_pos + 2] == ';'
+ || uri[uri_pos + 2] == '\0')) {
+ /* Wind map segment back the previous one */
+ if (map_pos == 1) {
+ /* Above root */
+ return 0;
+ }
+ do {
+ map_pos--;
+ } while (map[map_pos - 1] != '/');
+ map[map_pos] = '\0';
+
+ /* Wind alias segment back, unless in deeper segment */
+ if (alias_depth == stack->nelts) {
+ if (alias[alias_pos] == '\0') {
+ alias_pos--;
+ }
+ while (alias_pos > 0 && alias[alias_pos] == '/') {
+ alias_pos--;
+ }
+ while (alias_pos > 0 && alias[alias_pos - 1] != '/') {
+ alias_pos--;
+ }
+ AP_DEBUG_ASSERT(alias_pos > 0);
+ alias_depth--;
+ }
+ apr_array_pop(stack);
+
+ /* Move uri forward to the next segment */
+ uri_pos += 2;
+ if (uri[uri_pos] == '/') {
+ uri_pos++;
+ }
+ first_pos = 0;
+ continue;
+ }
+ }
+ if (first_pos) {
+ while (uri[first_pos] == '/') {
+ first_pos++;
+ }
+ }
+
+ /* New segment */
+ APR_ARRAY_PUSH(stack, int) = first_pos ? first_pos : uri_pos;
+ if (alias[alias_pos] != '\0') {
+ if (alias[alias_pos - 1] != '/') {
+ /* Remain in pair with uri segments */
+ do {
+ alias_pos++;
+ } while (alias[alias_pos - 1] != '/' && alias[alias_pos]);
+ }
+ while (alias[alias_pos] == '/') {
+ alias_pos++;
+ }
+ if (alias[alias_pos] != '\0') {
+ alias_depth++;
+ }
+ }
+ }
+
+ if (alias[alias_pos] != '\0') {
+ int *match = &APR_ARRAY_IDX(stack, alias_depth - 1, int);
+ if (*match) {
+ if (alias[alias_pos] != uri[uri_pos]) {
+ /* Current segment does not match */
+ *match = 0;
+ }
+ else if (alias[alias_pos + 1] == '\0'
+ && alias[alias_pos] != '/') {
+ if (uri[uri_pos + 1] == ';') {
+ /* We'll preserve the parameters of the last
+ * segment if it does not end with '/', so mark
+ * the match as negative for below handling.
+ */
+ *match = -(uri_pos + 1);
+ }
+ else if (uri[uri_pos + 1] != '/'
+ && uri[uri_pos + 1] != '\0') {
+ /* Last segment does not match all the way */
+ *match = 0;
+ }
+ }
+ }
+ /* Don't go past the segment if the uri isn't there yet */
+ if (alias[alias_pos] != '/' || uri[uri_pos] == '/') {
+ alias_pos++;
+ }
+ }
+
+ if (uri[uri_pos] == '/') {
+ first_pos = uri_pos + 1;
+ }
+ map[map_pos++] = uri[uri_pos++];
+ map[map_pos] = '\0';
+ }
+
+ /* Can't reach the end of uri before the end of the alias,
+ * for example if uri is "/" and alias is "/examples"
+ */
+ if (alias[alias_pos] != '\0') {
+ return 0;
+ }
+
+ /* Check whether each alias segment matched */
+ for (depth = 0; depth < alias_depth; ++depth) {
+ if (!APR_ARRAY_IDX(stack, depth, int)) {
+ return 0;
+ }
+ }
+
+ /* If alias_depth == stack->nelts we have a full match, i.e.
+ * uri == alias so we can return uri_pos as is (the end of uri)
+ */
+ if (alias_depth < stack->nelts) {
+ /* Return the segment following the alias */
+ uri_pos = APR_ARRAY_IDX(stack, alias_depth, int);
+ if (alias_depth) {
+ /* But if the last segment of the alias does not end with '/'
+ * and the corresponding segment of the uri has parameters,
+ * we want to forward those parameters (see above for the
+ * negative pos trick/mark).
+ */
+ int pos = APR_ARRAY_IDX(stack, alias_depth - 1, int);
+ if (pos < 0) {
+ uri_pos = -pos;
+ }
+ }
+ }
+ /* If the alias lacks a trailing slash, take it from the uri (if any) */
+ if (alias[alias_pos - 1] != '/' && uri[uri_pos - 1] == '/') {
+ uri_pos--;
+ }
+
+ *urip = map;
+ return uri_pos;
+}
+
+/* Detect if an absoluteURI should be proxied or not. Note that we
+ * have to do this during this phase because later phases are
+ * "short-circuiting"... i.e. translate_names will end when the first
+ * module returns OK. So for example, if the request is something like:
+ *
+ * GET http://othervhost/cgi-bin/printenv HTTP/1.0
+ *
+ * mod_alias will notice the /cgi-bin part and ScriptAlias it and
+ * short-circuit the proxy... just because of the ordering in the
+ * configuration file.
+ */
+static int proxy_detect(request_rec *r)
+{
+ void *sconf = r->server->module_config;
+ proxy_server_conf *conf =
+ (proxy_server_conf *) ap_get_module_config(sconf, &proxy_module);
+
+ /* Ick... msvc (perhaps others) promotes ternary short results to int */
+
+ if (conf->req && r->parsed_uri.scheme) {
+ /* but it might be something vhosted */
+ if (!r->parsed_uri.hostname
+ || ap_cstr_casecmp(r->parsed_uri.scheme, ap_http_scheme(r)) != 0
+ || !ap_matches_request_vhost(r, r->parsed_uri.hostname,
+ (apr_port_t)(r->parsed_uri.port_str
+ ? r->parsed_uri.port
+ : ap_default_port(r)))) {
+ r->proxyreq = PROXYREQ_PROXY;
+ r->uri = r->unparsed_uri;
+ r->filename = apr_pstrcat(r->pool, "proxy:", r->uri, NULL);
+ r->handler = "proxy-server";
+ }
+ }
+ /* We need special treatment for CONNECT proxying: it has no scheme part */
+ else if (conf->req && r->method_number == M_CONNECT
+ && r->parsed_uri.hostname
+ && r->parsed_uri.port_str) {
+ r->proxyreq = PROXYREQ_PROXY;
+ r->uri = r->unparsed_uri;
+ r->filename = apr_pstrcat(r->pool, "proxy:", r->uri, NULL);
+ r->handler = "proxy-server";
+ }
+ return DECLINED;
+}
+
+static const char *proxy_interpolate(request_rec *r, const char *str)
+{
+ /* Interpolate an env str in a configuration string
+ * Syntax ${var} --> value_of(var)
+ * Method: replace one var, and recurse on remainder of string
+ * Nothing clever here, and crap like nested vars may do silly things
+ * but we'll at least avoid sending the unwary into a loop
+ */
+ const char *start;
+ const char *end;
+ const char *var;
+ const char *val;
+ const char *firstpart;
+
+ start = ap_strstr_c(str, "${");
+ if (start == NULL) {
+ return str;
+ }
+ end = ap_strchr_c(start+2, '}');
+ if (end == NULL) {
+ return str;
+ }
+ /* OK, this is syntax we want to interpolate. Is there such a var ? */
+ var = apr_pstrmemdup(r->pool, start+2, end-(start+2));
+ val = apr_table_get(r->subprocess_env, var);
+ firstpart = apr_pstrmemdup(r->pool, str, (start-str));
+
+ if (val == NULL) {
+ return apr_pstrcat(r->pool, firstpart,
+ proxy_interpolate(r, end+1), NULL);
+ }
+ else {
+ return apr_pstrcat(r->pool, firstpart, val,
+ proxy_interpolate(r, end+1), NULL);
+ }
+}
+static apr_array_header_t *proxy_vars(request_rec *r,
+ apr_array_header_t *hdr)
+{
+ int i;
+ apr_array_header_t *ret = apr_array_make(r->pool, hdr->nelts,
+ sizeof (struct proxy_alias));
+ struct proxy_alias *old = (struct proxy_alias *) hdr->elts;
+
+ for (i = 0; i < hdr->nelts; ++i) {
+ struct proxy_alias *newcopy = apr_array_push(ret);
+ newcopy->fake = (old[i].flags & PROXYPASS_INTERPOLATE)
+ ? proxy_interpolate(r, old[i].fake) : old[i].fake;
+ newcopy->real = (old[i].flags & PROXYPASS_INTERPOLATE)
+ ? proxy_interpolate(r, old[i].real) : old[i].real;
+ }
+ return ret;
+}
+
+PROXY_DECLARE(int) ap_proxy_trans_match(request_rec *r, struct proxy_alias *ent,
+ proxy_dir_conf *dconf)
+{
+ int len;
+ const char *fake;
+ const char *real;
+ ap_regmatch_t regm[AP_MAX_REG_MATCH];
+ ap_regmatch_t reg1[AP_MAX_REG_MATCH];
+ char *found = NULL;
+ int mismatch = 0;
+ unsigned int nocanon = ent->flags & PROXYPASS_NOCANON;
+ const char *use_uri = nocanon ? r->unparsed_uri : r->uri;
+ const char *servlet_uri = NULL;
+
+ if (dconf && (dconf->interpolate_env == 1) && (ent->flags & PROXYPASS_INTERPOLATE)) {
+ fake = proxy_interpolate(r, ent->fake);
+ real = proxy_interpolate(r, ent->real);
+ }
+ else {
+ fake = ent->fake;
+ real = ent->real;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, APLOGNO(03461)
+ "attempting to match URI path '%s' against %s '%s' for "
+ "proxying", r->uri, (ent->regex ? "pattern" : "prefix"),
+ fake);
+
+ if (ent->regex) {
+ if (!ap_regexec(ent->regex, r->uri, AP_MAX_REG_MATCH, regm, 0)) {
+ if ((real[0] == '!') && (real[1] == '\0')) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(03462)
+ "proxying is explicitly disabled for URI path "
+ "'%s'; declining", r->uri);
+ return DECLINED;
+ }
+ /* test that we haven't reduced the URI */
+ if (nocanon && ap_regexec(ent->regex, r->unparsed_uri,
+ AP_MAX_REG_MATCH, reg1, 0)) {
+ mismatch = 1;
+ use_uri = r->uri;
+ }
+ found = ap_pregsub(r->pool, real, use_uri, AP_MAX_REG_MATCH,
+ (use_uri == r->uri) ? regm : reg1);
+ if (!found) {
+ ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, APLOGNO(01135)
+ "Substitution in regular expression failed. "
+ "Replacement too long?");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* Note: The strcmp() below catches cases where there
+ * was no regex substitution. This is so cases like:
+ *
+ * ProxyPassMatch \.gif balancer://foo
+ *
+ * will work "as expected". The upshot is that the 2
+ * directives below act the exact same way (ie: $1 is implied):
+ *
+ * ProxyPassMatch ^(/.*\.gif)$ balancer://foo
+ * ProxyPassMatch ^(/.*\.gif)$ balancer://foo$1
+ *
+ * which may be confusing.
+ */
+ if (strcmp(found, real) != 0) {
+ found = apr_pstrcat(r->pool, "proxy:", found, NULL);
+ }
+ else {
+ found = apr_pstrcat(r->pool, "proxy:", real, use_uri, NULL);
+ }
+ }
+ }
+ else {
+ if ((ent->flags & PROXYPASS_MAP_SERVLET) == PROXYPASS_MAP_SERVLET) {
+ servlet_uri = r->uri;
+ len = alias_match_servlet(r->pool, &servlet_uri, fake);
+ nocanon = 0; /* ignored since servlet's normalization applies */
+ }
+ else {
+ len = alias_match(r->uri, fake);
+ }
+
+ if (len != 0) {
+ if ((real[0] == '!') && (real[1] == '\0')) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(03463)
+ "proxying is explicitly disabled for URI path "
+ "'%s'; declining", r->uri);
+ return DECLINED;
+ }
+ if (nocanon && len != alias_match(r->unparsed_uri, fake)) {
+ mismatch = 1;
+ use_uri = r->uri;
+ }
+ found = apr_pstrcat(r->pool, "proxy:", real, use_uri + len, NULL);
+ }
+ }
+ if (mismatch) {
+ /* We made a reducing transformation, so we can't safely use
+ * unparsed_uri. Safe fallback is to ignore nocanon.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01136)
+ "Unescaped URL path matched ProxyPass; ignoring unsafe nocanon");
+ }
+
+ if (found) {
+ /* A proxy module is assigned this URL, check whether it's interested
+ * in the request itself (e.g. proxy_wstunnel cares about Upgrade
+ * requests only, and could hand over to proxy_http otherwise).
+ */
+ int rc = proxy_run_check_trans(r, found + 6);
+ if (rc != OK && rc != DECLINED) {
+ return HTTP_CONTINUE;
+ }
+
+ r->filename = found;
+ r->handler = "proxy-server";
+ r->proxyreq = PROXYREQ_REVERSE;
+ if (nocanon && !mismatch) {
+ /* mod_proxy_http needs to be told. Different module. */
+ apr_table_setn(r->notes, "proxy-nocanon", "1");
+ }
+ if (ent->flags & PROXYPASS_NOQUERY) {
+ apr_table_setn(r->notes, "proxy-noquery", "1");
+ }
+
+ if (servlet_uri) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(10248)
+ "Servlet path '%s' (%s) matches proxy handler '%s'",
+ r->uri, servlet_uri, found);
+ /* Apply servlet normalization to r->uri so that <Location> or any
+ * directory context match does not have to handle path parameters.
+ * We change r->uri in-place so that r->parsed_uri.path is updated
+ * too. Since normalized servlet_uri is necessarily shorter than
+ * the original r->uri, strcpy() is fine.
+ */
+ AP_DEBUG_ASSERT(strlen(r->uri) >= strlen(servlet_uri));
+ strcpy(r->uri, servlet_uri);
+ return DONE;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(03464)
+ "URI path '%s' matches proxy handler '%s'", r->uri,
+ found);
+ return OK;
+ }
+
+ return HTTP_CONTINUE;
+}
+
+static int proxy_trans(request_rec *r, int pre_trans)
+{
+ int i, enc;
+ struct proxy_alias *ent;
+ proxy_dir_conf *dconf;
+ proxy_server_conf *conf;
+
+ if (r->proxyreq) {
+ /* someone has already set up the proxy, it was possibly ourselves
+ * in proxy_detect (DONE will prevent further decoding of r->uri,
+ * only if proxyreq is set before pre_trans already).
+ */
+ return pre_trans ? DONE : OK;
+ }
+
+ /* In early pre_trans hook, r->uri was not manipulated yet so we are
+ * compliant with RFC1945 at this point. Otherwise, it probably isn't
+ * an issue because this is a hybrid proxy/origin server.
+ */
+
+ dconf = ap_get_module_config(r->per_dir_config, &proxy_module);
+ conf = (proxy_server_conf *) ap_get_module_config(r->server->module_config,
+ &proxy_module);
+
+ /* Always and only do PROXY_MAP_ENCODED mapping in pre_trans, when
+ * r->uri is still encoded, or we might consider for instance that
+ * a decoded sub-delim is now a delimiter (e.g. "%3B" => ';' for
+ * path parameters), which it's not.
+ */
+ if ((pre_trans && !conf->map_encoded_one)
+ || (!pre_trans && conf->map_encoded_all)) {
+ /* Fast path, nothing at this stage */
+ return DECLINED;
+ }
+
+ if ((r->unparsed_uri[0] == '*' && r->unparsed_uri[1] == '\0')
+ || !r->uri || r->uri[0] != '/') {
+ return DECLINED;
+ }
+
+ if (apr_table_get(r->subprocess_env, "no-proxy")) {
+ return DECLINED;
+ }
+
+ /* short way - this location is reverse proxied? */
+ if (dconf->alias) {
+ enc = (dconf->alias->flags & PROXYPASS_MAP_ENCODED) != 0;
+ if (!(pre_trans ^ enc)) {
+ int rv = ap_proxy_trans_match(r, dconf->alias, dconf);
+ if (rv != HTTP_CONTINUE) {
+ return rv;
+ }
+ }
+ }
+
+ /* long way - walk the list of aliases, find a match */
+ for (i = 0; i < conf->aliases->nelts; i++) {
+ ent = &((struct proxy_alias *)conf->aliases->elts)[i];
+ enc = (ent->flags & PROXYPASS_MAP_ENCODED) != 0;
+ if (!(pre_trans ^ enc)) {
+ int rv = ap_proxy_trans_match(r, ent, dconf);
+ if (rv != HTTP_CONTINUE) {
+ return rv;
+ }
+ }
+ }
+
+ return DECLINED;
+}
+
+static int proxy_pre_translate_name(request_rec *r)
+{
+ return proxy_trans(r, 1);
+}
+
+static int proxy_translate_name(request_rec *r)
+{
+ return proxy_trans(r, 0);
+}
+
+static int proxy_walk(request_rec *r)
+{
+ proxy_server_conf *sconf = ap_get_module_config(r->server->module_config,
+ &proxy_module);
+ ap_conf_vector_t *per_dir_defaults = r->per_dir_config;
+ ap_conf_vector_t **sec_proxy = (ap_conf_vector_t **) sconf->sec_proxy->elts;
+ ap_conf_vector_t *entry_config;
+ proxy_dir_conf *entry_proxy;
+ int num_sec = sconf->sec_proxy->nelts;
+ /* XXX: shouldn't we use URI here? Canonicalize it first?
+ * Pass over "proxy:" prefix
+ */
+ const char *proxyname = r->filename + 6;
+ int j;
+ apr_pool_t *rxpool = NULL;
+
+ for (j = 0; j < num_sec; ++j)
+ {
+ int nmatch = 0;
+ int i;
+ ap_regmatch_t *pmatch = NULL;
+
+ entry_config = sec_proxy[j];
+ entry_proxy = ap_get_module_config(entry_config, &proxy_module);
+
+ if (entry_proxy->r) {
+
+ if (entry_proxy->refs && entry_proxy->refs->nelts) {
+ if (!rxpool) {
+ apr_pool_create(&rxpool, r->pool);
+ apr_pool_tag(rxpool, "proxy_rxpool");
+ }
+ nmatch = entry_proxy->refs->nelts;
+ pmatch = apr_palloc(rxpool, nmatch*sizeof(ap_regmatch_t));
+ }
+
+ if (ap_regexec(entry_proxy->r, proxyname, nmatch, pmatch, 0)) {
+ continue;
+ }
+
+ for (i = 0; i < nmatch; i++) {
+ if (pmatch[i].rm_so >= 0 && pmatch[i].rm_eo >= 0 &&
+ ((const char **)entry_proxy->refs->elts)[i]) {
+ apr_table_setn(r->subprocess_env,
+ ((const char **)entry_proxy->refs->elts)[i],
+ apr_pstrndup(r->pool,
+ proxyname + pmatch[i].rm_so,
+ pmatch[i].rm_eo - pmatch[i].rm_so));
+ }
+ }
+ }
+
+ else if (
+ /* XXX: What about case insensitive matching ???
+ * Compare regex, fnmatch or string as appropriate
+ * If the entry doesn't relate, then continue
+ */
+ entry_proxy->p_is_fnmatch ? apr_fnmatch(entry_proxy->p,
+ proxyname, 0) :
+ strncmp(proxyname, entry_proxy->p,
+ strlen(entry_proxy->p))) {
+ continue;
+ }
+ per_dir_defaults = ap_merge_per_dir_configs(r->pool, per_dir_defaults,
+ entry_config);
+ }
+
+ r->per_dir_config = per_dir_defaults;
+
+ if (rxpool) {
+ apr_pool_destroy(rxpool);
+ }
+
+ return OK;
+}
+
+static int proxy_map_location(request_rec *r)
+{
+ int access_status;
+
+ if (!r->proxyreq || !r->filename || strncmp(r->filename, "proxy:", 6) != 0)
+ return DECLINED;
+
+ /* Don't let the core or mod_http map_to_storage hooks handle this,
+ * We don't need directory/file_walk, and we want to TRACE on our own.
+ */
+ if ((access_status = proxy_walk(r))) {
+ ap_die(access_status, r);
+ return access_status;
+ }
+
+ return OK;
+}
+
+/* -------------------------------------------------------------- */
+/* Fixup the filename */
+
+/*
+ * Canonicalise the URL
+ */
+static int proxy_fixup(request_rec *r)
+{
+ char *url, *p;
+ int access_status;
+ proxy_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
+ &proxy_module);
+
+ if (!r->proxyreq || !r->filename || strncmp(r->filename, "proxy:", 6) != 0)
+ return DECLINED;
+
+ /* XXX: Shouldn't we try this before we run the proxy_walk? */
+ url = &r->filename[6];
+
+ if ((dconf->interpolate_env == 1) && (r->proxyreq == PROXYREQ_REVERSE)) {
+ /* create per-request copy of reverse proxy conf,
+ * and interpolate vars in it
+ */
+ proxy_req_conf *rconf = apr_palloc(r->pool, sizeof(proxy_req_conf));
+ ap_set_module_config(r->request_config, &proxy_module, rconf);
+ rconf->raliases = proxy_vars(r, dconf->raliases);
+ rconf->cookie_paths = proxy_vars(r, dconf->cookie_paths);
+ rconf->cookie_domains = proxy_vars(r, dconf->cookie_domains);
+ }
+
+ /* canonicalise each specific scheme */
+ if ((access_status = proxy_run_canon_handler(r, url))) {
+ return access_status;
+ }
+
+ p = strchr(url, ':');
+ if (p == NULL || p == url)
+ return HTTP_BAD_REQUEST;
+
+ return OK; /* otherwise; we've done the best we can */
+}
+/* Send a redirection if the request contains a hostname which is not */
+/* fully qualified, i.e. doesn't have a domain name appended. Some proxy */
+/* servers like Netscape's allow this and access hosts from the local */
+/* domain in this case. I think it is better to redirect to a FQDN, since */
+/* these will later be found in the bookmarks files. */
+/* The "ProxyDomain" directive determines what domain will be appended */
+static int proxy_needsdomain(request_rec *r, const char *url, const char *domain)
+{
+ char *nuri;
+ const char *ref;
+
+ /* We only want to worry about GETs */
+ if (!r->proxyreq || r->method_number != M_GET || !r->parsed_uri.hostname)
+ return DECLINED;
+
+ /* If host does contain a dot already, or it is "localhost", decline */
+ if (strchr(r->parsed_uri.hostname, '.') != NULL /* has domain, or IPv4 literal */
+ || strchr(r->parsed_uri.hostname, ':') != NULL /* IPv6 literal */
+ || ap_cstr_casecmp(r->parsed_uri.hostname, "localhost") == 0)
+ return DECLINED; /* host name has a dot already */
+
+ ref = apr_table_get(r->headers_in, "Referer");
+
+ /* Reassemble the request, but insert the domain after the host name */
+ /* Note that the domain name always starts with a dot */
+ r->parsed_uri.hostname = apr_pstrcat(r->pool, r->parsed_uri.hostname,
+ domain, NULL);
+ nuri = apr_uri_unparse(r->pool,
+ &r->parsed_uri,
+ APR_URI_UNP_REVEALPASSWORD);
+
+ apr_table_setn(r->headers_out, "Location", nuri);
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01138)
+ "Domain missing: %s sent to %s%s%s", r->uri,
+ apr_uri_unparse(r->pool, &r->parsed_uri,
+ APR_URI_UNP_OMITUSERINFO),
+ ref ? " from " : "", ref ? ref : "");
+
+ return HTTP_MOVED_PERMANENTLY;
+}
+
+/* -------------------------------------------------------------- */
+/* Invoke handler */
+
+static int proxy_handler(request_rec *r)
+{
+ char *uri, *scheme, *p;
+ const char *p2;
+ void *sconf = r->server->module_config;
+ proxy_server_conf *conf = (proxy_server_conf *)
+ ap_get_module_config(sconf, &proxy_module);
+ apr_array_header_t *proxies = conf->proxies;
+ struct proxy_remote *ents = (struct proxy_remote *) proxies->elts;
+ int i, rc, access_status;
+ int direct_connect = 0;
+ const char *str;
+ apr_int64_t maxfwd;
+ proxy_balancer *balancer = NULL;
+ proxy_worker *worker = NULL;
+ int attempts = 0, max_attempts = 0;
+ struct dirconn_entry *list = (struct dirconn_entry *)conf->dirconn->elts;
+ int saved_status;
+
+ /* is this for us? */
+ if (!r->filename) {
+ return DECLINED;
+ }
+
+ if (!r->proxyreq) {
+ /* We may have forced the proxy handler via config or .htaccess */
+ if (r->handler &&
+ strncmp(r->handler, "proxy:", 6) == 0 &&
+ strncmp(r->filename, "proxy:", 6) != 0) {
+ r->proxyreq = PROXYREQ_REVERSE;
+ r->filename = apr_pstrcat(r->pool, r->handler, r->filename, NULL);
+ }
+ else {
+ return DECLINED;
+ }
+ } else if (strncmp(r->filename, "proxy:", 6) != 0) {
+ return DECLINED;
+ }
+
+ /* handle max-forwards / OPTIONS / TRACE */
+ if ((str = apr_table_get(r->headers_in, "Max-Forwards"))) {
+ char *end;
+ maxfwd = apr_strtoi64(str, &end, 10);
+ if (maxfwd < 0 || maxfwd == APR_INT64_MAX || *end) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(10188)
+ "Max-Forwards value '%s' could not be parsed", str);
+ return ap_proxyerror(r, HTTP_BAD_REQUEST,
+ "Max-Forwards request header could not be parsed");
+ }
+ else if (maxfwd == 0) {
+ switch (r->method_number) {
+ case M_TRACE: {
+ int access_status;
+ r->proxyreq = PROXYREQ_NONE;
+ access_status = ap_send_http_trace(r);
+ ap_die(access_status, r);
+ return OK;
+ }
+ case M_OPTIONS: {
+ int access_status;
+ r->proxyreq = PROXYREQ_NONE;
+ access_status = ap_send_http_options(r);
+ ap_die(access_status, r);
+ return OK;
+ }
+ default: {
+ return ap_proxyerror(r, HTTP_BAD_REQUEST,
+ "Max-Forwards has reached zero - proxy loop?");
+ }
+ }
+ }
+ maxfwd = (maxfwd > 0) ? maxfwd - 1 : 0;
+ }
+ else {
+ /* set configured max-forwards */
+ maxfwd = conf->maxfwd;
+ }
+ if (maxfwd >= 0) {
+ apr_table_setn(r->headers_in, "Max-Forwards",
+ apr_psprintf(r->pool, "%" APR_INT64_T_FMT, maxfwd));
+ }
+
+ if (r->method_number == M_TRACE) {
+ core_server_config *coreconf = (core_server_config *)
+ ap_get_core_module_config(sconf);
+
+ if (coreconf->trace_enable == AP_TRACE_DISABLE)
+ {
+ /* Allow "error-notes" string to be printed by ap_send_error_response()
+ * Note; this goes nowhere, canned error response need an overhaul.
+ */
+ apr_table_setn(r->notes, "error-notes",
+ "TRACE forbidden by server configuration");
+ apr_table_setn(r->notes, "verbose-error-to", "*");
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01139)
+ "TRACE forbidden by server configuration");
+ return HTTP_METHOD_NOT_ALLOWED;
+ }
+
+ /* Can't test ap_should_client_block, we aren't ready to send
+ * the client a 100 Continue response till the connection has
+ * been established
+ */
+ if (coreconf->trace_enable != AP_TRACE_EXTENDED
+ && (r->read_length || r->read_chunked || r->remaining))
+ {
+ /* Allow "error-notes" string to be printed by ap_send_error_response()
+ * Note; this goes nowhere, canned error response need an overhaul.
+ */
+ apr_table_setn(r->notes, "error-notes",
+ "TRACE with request body is not allowed");
+ apr_table_setn(r->notes, "verbose-error-to", "*");
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01140)
+ "TRACE with request body is not allowed");
+ return HTTP_REQUEST_ENTITY_TOO_LARGE;
+ }
+ }
+
+ uri = r->filename + 6;
+ p = strchr(uri, ':');
+ if (p == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01141)
+ "proxy_handler no URL in %s", r->filename);
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* If the host doesn't have a domain name, add one and redirect. */
+ if (conf->domain != NULL) {
+ rc = proxy_needsdomain(r, uri, conf->domain);
+ if (ap_is_HTTP_REDIRECT(rc))
+ return HTTP_MOVED_PERMANENTLY;
+ }
+
+ scheme = apr_pstrmemdup(r->pool, uri, p - uri);
+ /* Check URI's destination host against NoProxy hosts */
+ /* Bypass ProxyRemote server lookup if configured as NoProxy */
+ for (direct_connect = i = 0; i < conf->dirconn->nelts &&
+ !direct_connect; i++) {
+ direct_connect = list[i].matcher(&list[i], r);
+ }
+#if DEBUGGING
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ (direct_connect) ? APLOGNO(03231) "NoProxy for %s" : APLOGNO(03232) "UseProxy for %s",
+ r->uri);
+#endif
+
+ do {
+ char *url = uri;
+ /* Try to obtain the most suitable worker */
+ access_status = ap_proxy_pre_request(&worker, &balancer, r, conf, &url);
+ if (access_status != OK) {
+ /*
+ * Only return if access_status is not HTTP_SERVICE_UNAVAILABLE
+ * This gives other modules the chance to hook into the
+ * request_status hook and decide what to do in this situation.
+ */
+ if (access_status != HTTP_SERVICE_UNAVAILABLE)
+ return access_status;
+ /*
+ * Ensure that balancer is NULL if worker is NULL to prevent
+ * potential problems in the post_request hook.
+ */
+ if (!worker)
+ balancer = NULL;
+ goto cleanup;
+ }
+
+ /* Initialise worker if needed, note the shared area must be initialized by the balancer logic */
+ if (balancer) {
+ ap_proxy_initialize_worker(worker, r->server, conf->pool);
+ }
+
+ if (balancer && balancer->s->max_attempts_set && !max_attempts)
+ max_attempts = balancer->s->max_attempts;
+ /* firstly, try a proxy, unless a NoProxy directive is active */
+ if (!direct_connect) {
+ for (i = 0; i < proxies->nelts; i++) {
+ p2 = ap_strchr_c(ents[i].scheme, ':'); /* is it a partial URL? */
+ if (strcmp(ents[i].scheme, "*") == 0 ||
+ (ents[i].use_regex &&
+ ap_regexec(ents[i].regexp, url, 0, NULL, 0) == 0) ||
+ (p2 == NULL && ap_cstr_casecmp(scheme, ents[i].scheme) == 0) ||
+ (p2 != NULL &&
+ ap_cstr_casecmpn(url, ents[i].scheme,
+ strlen(ents[i].scheme)) == 0)) {
+
+ /* handle the scheme */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01142)
+ "Trying to run scheme_handler against proxy");
+ access_status = proxy_run_scheme_handler(r, worker,
+ conf, url,
+ ents[i].hostname,
+ ents[i].port);
+
+ /* Did the scheme handler process the request? */
+ if (access_status != DECLINED) {
+ const char *cl_a;
+ apr_off_t cl;
+
+ /*
+ * An fatal error or success, so no point in
+ * retrying with a direct connection.
+ */
+ if (access_status != HTTP_BAD_GATEWAY) {
+ goto cleanup;
+ }
+
+ cl_a = apr_table_get(r->headers_in, "Content-Length");
+ if (cl_a && (!ap_parse_strict_length(&cl, cl_a)
+ || cl > 0)) {
+ /*
+ * The request body is of length > 0. We cannot
+ * retry with a direct connection since we already
+ * sent (parts of) the request body to the proxy
+ * and do not have any longer.
+ */
+ goto cleanup;
+ }
+ /*
+ * Transfer-Encoding was set as input header, so we had
+ * a request body. We cannot retry with a direct
+ * connection for the same reason as above.
+ */
+ if (apr_table_get(r->headers_in, "Transfer-Encoding")) {
+ goto cleanup;
+ }
+ }
+ }
+ }
+ }
+
+ /* otherwise, try it direct */
+ /* N.B. what if we're behind a firewall, where we must use a proxy or
+ * give up??
+ */
+
+ /* handle the scheme */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01143)
+ "Running scheme %s handler (attempt %d)",
+ scheme, attempts);
+ AP_PROXY_RUN(r, worker, conf, url, attempts);
+ access_status = proxy_run_scheme_handler(r, worker, conf,
+ url, NULL, 0);
+ if (access_status == OK
+ || apr_table_get(r->notes, "proxy-error-override"))
+ break;
+ else if (access_status == HTTP_INTERNAL_SERVER_ERROR) {
+ /* Unrecoverable server error.
+ * We can not failover to another worker.
+ * Mark the worker as unusable if member of load balancer
+ */
+ if (balancer
+ && !(worker->s->status & PROXY_WORKER_IGNORE_ERRORS)) {
+ worker->s->status |= PROXY_WORKER_IN_ERROR;
+ worker->s->error_time = apr_time_now();
+ }
+ break;
+ }
+ else if (access_status == HTTP_SERVICE_UNAVAILABLE) {
+ /* Recoverable server error.
+ * We can failover to another worker
+ * Mark the worker as unusable if member of load balancer
+ */
+ if (balancer
+ && !(worker->s->status & PROXY_WORKER_IGNORE_ERRORS)) {
+ worker->s->status |= PROXY_WORKER_IN_ERROR;
+ worker->s->error_time = apr_time_now();
+ }
+ }
+ else {
+ /* Unrecoverable error.
+ * Return the origin status code to the client.
+ */
+ break;
+ }
+ /* Try again if the worker is unusable and the service is
+ * unavailable.
+ */
+ } while (!PROXY_WORKER_IS_USABLE(worker) &&
+ max_attempts > attempts++);
+
+ if (DECLINED == access_status) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01144)
+ "No protocol handler was valid for the URL %s "
+ "(scheme '%s'). "
+ "If you are using a DSO version of mod_proxy, make sure "
+ "the proxy submodules are included in the configuration "
+ "using LoadModule.", r->uri, scheme);
+ access_status = HTTP_INTERNAL_SERVER_ERROR;
+ goto cleanup;
+ }
+cleanup:
+ /*
+ * Save current r->status and set it to the value of access_status which
+ * might be different (e.g. r->status could be HTTP_OK if e.g. we override
+ * the error page on the proxy or if the error was not generated by the
+ * backend itself but by the proxy e.g. a bad gateway) in order to give
+ * ap_proxy_post_request a chance to act correctly on the status code.
+ * But only do the above if access_status is not OK and not DONE, because
+ * in this case r->status might contain the true status and overwriting
+ * it with OK or DONE would be wrong.
+ */
+ if ((access_status != OK) && (access_status != DONE)) {
+ saved_status = r->status;
+ r->status = access_status;
+ ap_proxy_post_request(worker, balancer, r, conf);
+ /*
+ * Only restore r->status if it has not been changed by
+ * ap_proxy_post_request as we assume that this change was intentional.
+ */
+ if (r->status == access_status) {
+ r->status = saved_status;
+ }
+ }
+ else {
+ ap_proxy_post_request(worker, balancer, r, conf);
+ }
+
+ proxy_run_request_status(&access_status, r);
+ AP_PROXY_RUN_FINISHED(r, attempts, access_status);
+
+ return access_status;
+}
+
+/* -------------------------------------------------------------- */
+/* Setup configurable data */
+
+static void * create_proxy_config(apr_pool_t *p, server_rec *s)
+{
+ proxy_server_conf *ps = apr_pcalloc(p, sizeof(proxy_server_conf));
+
+ ps->sec_proxy = apr_array_make(p, 10, sizeof(ap_conf_vector_t *));
+ ps->proxies = apr_array_make(p, 10, sizeof(struct proxy_remote));
+ ps->aliases = apr_array_make(p, 10, sizeof(struct proxy_alias));
+ ps->noproxies = apr_array_make(p, 10, sizeof(struct noproxy_entry));
+ ps->dirconn = apr_array_make(p, 10, sizeof(struct dirconn_entry));
+ ps->workers = apr_array_make(p, 10, sizeof(proxy_worker));
+ ps->balancers = apr_array_make(p, 10, sizeof(proxy_balancer));
+ ps->forward = NULL;
+ ps->reverse = NULL;
+ ps->domain = NULL;
+ ps->map_encoded_one = 0;
+ ps->map_encoded_all = 1;
+ ps->id = apr_psprintf(p, "p%x", 1); /* simply for storage size */
+ ps->viaopt = via_off; /* initially backward compatible with 1.3.1 */
+ ps->viaopt_set = 0; /* 0 means default */
+ ps->req = 0;
+ ps->max_balancers = 0;
+ ps->bal_persist = 0;
+ ps->inherit = 1;
+ ps->inherit_set = 0;
+ ps->ppinherit = 1;
+ ps->ppinherit_set = 0;
+ ps->bgrowth = 5;
+ ps->bgrowth_set = 0;
+ ps->req_set = 0;
+ ps->recv_buffer_size = 0; /* this default was left unset for some reason */
+ ps->recv_buffer_size_set = 0;
+ ps->io_buffer_size = AP_IOBUFSIZE;
+ ps->io_buffer_size_set = 0;
+ ps->maxfwd = DEFAULT_MAX_FORWARDS;
+ ps->maxfwd_set = 0;
+ ps->timeout = 0;
+ ps->timeout_set = 0;
+ ps->badopt = bad_error;
+ ps->badopt_set = 0;
+ ps->source_address = NULL;
+ ps->source_address_set = 0;
+ apr_pool_create_ex(&ps->pool, p, NULL, NULL);
+ apr_pool_tag(ps->pool, "proxy_server_conf");
+
+ return ps;
+}
+
+static apr_array_header_t *merge_balancers(apr_pool_t *p,
+ apr_array_header_t *base,
+ apr_array_header_t *overrides)
+{
+ proxy_balancer *b1;
+ proxy_balancer *b2;
+ proxy_balancer tmp;
+ int x, y, found;
+ apr_array_header_t *tocopy = apr_array_make(p, 1, sizeof(proxy_balancer));
+
+ /* Check if the balancer is defined in both override and base configs:
+ * a) If it is, Create copy of base balancer and change the configuration
+ * which can be changed by ProxyPass.
+ * b) Otherwise, copy the balancer to tocopy array and merge it later.
+ */
+ b1 = (proxy_balancer *) base->elts;
+ for (y = 0; y < base->nelts; y++) {
+ b2 = (proxy_balancer *) overrides->elts;
+ for (x = 0, found = 0; x < overrides->nelts; x++) {
+ if (b1->hash.def == b2->hash.def && b1->hash.fnv == b2->hash.fnv) {
+ tmp = *b2;
+ *b2 = *b1;
+ b2->s = tmp.s;
+
+ /* For shared memory entries, b2->s belongs to override
+ * balancer, so if some entry is not set there, we have to
+ * update it according to the base balancer. */
+ if (*b2->s->sticky == 0 && *b1->s->sticky) {
+ PROXY_STRNCPY(b2->s->sticky_path, b1->s->sticky_path);
+ PROXY_STRNCPY(b2->s->sticky, b1->s->sticky);
+ }
+ if (!b2->s->sticky_separator_set
+ && b1->s->sticky_separator_set) {
+ b2->s->sticky_separator_set = b1->s->sticky_separator_set;
+ b2->s->sticky_separator = b1->s->sticky_separator;
+ }
+ if (!b2->s->timeout && b1->s->timeout) {
+ b2->s->timeout = b1->s->timeout;
+ }
+ if (!b2->s->max_attempts_set && b1->s->max_attempts_set) {
+ b2->s->max_attempts_set = b1->s->max_attempts_set;
+ b2->s->max_attempts = b1->s->max_attempts;
+ }
+ if (!b2->s->nonce_set && b1->s->nonce_set) {
+ b2->s->nonce_set = b1->s->nonce_set;
+ PROXY_STRNCPY(b2->s->nonce, b1->s->nonce);
+ }
+ if (!b2->s->sticky_force_set && b1->s->sticky_force_set) {
+ b2->s->sticky_force_set = b1->s->sticky_force_set;
+ b2->s->sticky_force = b1->s->sticky_force;
+ }
+ if (!b2->s->scolonsep_set && b1->s->scolonsep_set) {
+ b2->s->scolonsep_set = b1->s->scolonsep_set;
+ b2->s->scolonsep = b1->s->scolonsep;
+ }
+ if (!b2->s->forcerecovery_set && b1->s->forcerecovery_set) {
+ b2->s->forcerecovery_set = b1->s->forcerecovery_set;
+ b2->s->forcerecovery = b1->s->forcerecovery;
+ }
+
+ /* For non-shared memory entries, b2 is copy of b1, so we have
+ * to use tmp copy of b1 to detect changes done in override. */
+ if (tmp.lbmethod_set) {
+ b2->lbmethod_set = tmp.lbmethod_set;
+ b2->lbmethod = tmp.lbmethod;
+ }
+ if (tmp.growth_set) {
+ b2->growth_set = tmp.growth_set;
+ b2->growth = tmp.growth;
+ }
+ if (tmp.failontimeout_set) {
+ b2->failontimeout_set = tmp.failontimeout_set;
+ b2->failontimeout = tmp.failontimeout;
+ }
+ if (!apr_is_empty_array(tmp.errstatuses)) {
+ apr_array_cat(tmp.errstatuses, b2->errstatuses);
+ b2->errstatuses = tmp.errstatuses;
+ }
+
+ found = 1;
+ break;
+ }
+ b2++;
+ }
+ if (!found) {
+ *(proxy_balancer *)apr_array_push(tocopy) = *b1;
+ }
+ b1++;
+ }
+
+ return apr_array_append(p, tocopy, overrides);
+}
+
+static void * merge_proxy_config(apr_pool_t *p, void *basev, void *overridesv)
+{
+ proxy_server_conf *ps = apr_pcalloc(p, sizeof(proxy_server_conf));
+ proxy_server_conf *base = (proxy_server_conf *) basev;
+ proxy_server_conf *overrides = (proxy_server_conf *) overridesv;
+
+ ps->inherit = (overrides->inherit_set == 0) ? base->inherit : overrides->inherit;
+ ps->inherit_set = overrides->inherit_set || base->inherit_set;
+
+ ps->ppinherit = (overrides->ppinherit_set == 0) ? base->ppinherit : overrides->ppinherit;
+ ps->ppinherit_set = overrides->ppinherit_set || base->ppinherit_set;
+
+ if (ps->ppinherit) {
+ ps->proxies = apr_array_append(p, base->proxies, overrides->proxies);
+ }
+ else {
+ ps->proxies = overrides->proxies;
+ }
+ ps->sec_proxy = apr_array_append(p, base->sec_proxy, overrides->sec_proxy);
+ ps->aliases = apr_array_append(p, base->aliases, overrides->aliases);
+ ps->noproxies = apr_array_append(p, base->noproxies, overrides->noproxies);
+ ps->dirconn = apr_array_append(p, base->dirconn, overrides->dirconn);
+ if (ps->inherit || ps->ppinherit) {
+ ps->workers = apr_array_append(p, base->workers, overrides->workers);
+ ps->balancers = merge_balancers(p, base->balancers, overrides->balancers);
+ }
+ else {
+ ps->workers = overrides->workers;
+ ps->balancers = overrides->balancers;
+ }
+ ps->forward = overrides->forward ? overrides->forward : base->forward;
+ ps->reverse = overrides->reverse ? overrides->reverse : base->reverse;
+
+ ps->map_encoded_one = overrides->map_encoded_one || base->map_encoded_one;
+ ps->map_encoded_all = overrides->map_encoded_all && base->map_encoded_all;
+
+ ps->domain = (overrides->domain == NULL) ? base->domain : overrides->domain;
+ ps->id = (overrides->id == NULL) ? base->id : overrides->id;
+ ps->viaopt = (overrides->viaopt_set == 0) ? base->viaopt : overrides->viaopt;
+ ps->viaopt_set = overrides->viaopt_set || base->viaopt_set;
+ ps->req = (overrides->req_set == 0) ? base->req : overrides->req;
+ ps->req_set = overrides->req_set || base->req_set;
+ ps->bgrowth = (overrides->bgrowth_set == 0) ? base->bgrowth : overrides->bgrowth;
+ ps->bgrowth_set = overrides->bgrowth_set || base->bgrowth_set;
+ ps->max_balancers = overrides->max_balancers || base->max_balancers;
+ ps->bal_persist = overrides->bal_persist;
+ ps->recv_buffer_size = (overrides->recv_buffer_size_set == 0) ? base->recv_buffer_size : overrides->recv_buffer_size;
+ ps->recv_buffer_size_set = overrides->recv_buffer_size_set || base->recv_buffer_size_set;
+ ps->io_buffer_size = (overrides->io_buffer_size_set == 0) ? base->io_buffer_size : overrides->io_buffer_size;
+ ps->io_buffer_size_set = overrides->io_buffer_size_set || base->io_buffer_size_set;
+ ps->maxfwd = (overrides->maxfwd_set == 0) ? base->maxfwd : overrides->maxfwd;
+ ps->maxfwd_set = overrides->maxfwd_set || base->maxfwd_set;
+ ps->timeout = (overrides->timeout_set == 0) ? base->timeout : overrides->timeout;
+ ps->timeout_set = overrides->timeout_set || base->timeout_set;
+ ps->badopt = (overrides->badopt_set == 0) ? base->badopt : overrides->badopt;
+ ps->badopt_set = overrides->badopt_set || base->badopt_set;
+ ps->proxy_status = (overrides->proxy_status_set == 0) ? base->proxy_status : overrides->proxy_status;
+ ps->proxy_status_set = overrides->proxy_status_set || base->proxy_status_set;
+ ps->source_address = (overrides->source_address_set == 0) ? base->source_address : overrides->source_address;
+ ps->source_address_set = overrides->source_address_set || base->source_address_set;
+ ps->pool = base->pool;
+ return ps;
+}
+static const char *set_source_address(cmd_parms *parms, void *dummy,
+ const char *arg)
+{
+ proxy_server_conf *psf =
+ ap_get_module_config(parms->server->module_config, &proxy_module);
+ struct apr_sockaddr_t *addr;
+
+ if (APR_SUCCESS == apr_sockaddr_info_get(&addr, arg, APR_UNSPEC, 0, 0,
+ psf->pool)) {
+ psf->source_address = addr;
+ psf->source_address_set = 1;
+ }
+ else {
+ return "ProxySourceAddress invalid value";
+ }
+
+ return NULL;
+}
+
+static void *create_proxy_dir_config(apr_pool_t *p, char *dummy)
+{
+ proxy_dir_conf *new =
+ (proxy_dir_conf *) apr_pcalloc(p, sizeof(proxy_dir_conf));
+
+ /* Filled in by proxysection, when applicable */
+
+ /* Put these in the dir config so they work inside <Location> */
+ new->raliases = apr_array_make(p, 10, sizeof(struct proxy_alias));
+ new->cookie_paths = apr_array_make(p, 10, sizeof(struct proxy_alias));
+ new->cookie_domains = apr_array_make(p, 10, sizeof(struct proxy_alias));
+ new->error_override_codes = apr_array_make(p, 10, sizeof(int));
+ new->preserve_host_set = 0;
+ new->preserve_host = 0;
+ new->interpolate_env = -1; /* unset */
+ new->error_override = 0;
+ new->error_override_set = 0;
+ new->add_forwarded_headers = 1;
+ new->add_forwarded_headers_set = 0;
+ new->forward_100_continue = 1;
+ new->forward_100_continue_set = 0;
+
+ return (void *) new;
+}
+
+static int int_order(const void *i1, const void *i2)
+{
+ return *(const int *)i1 - *(const int *)i2;
+}
+
+static void *merge_proxy_dir_config(apr_pool_t *p, void *basev, void *addv)
+{
+ proxy_dir_conf *new = (proxy_dir_conf *) apr_pcalloc(p, sizeof(proxy_dir_conf));
+ proxy_dir_conf *add = (proxy_dir_conf *) addv;
+ proxy_dir_conf *base = (proxy_dir_conf *) basev;
+
+ new->p = add->p;
+ new->p_is_fnmatch = add->p_is_fnmatch;
+ new->r = add->r;
+ new->refs = add->refs;
+
+ /* Put these in the dir config so they work inside <Location> */
+ new->raliases = apr_array_append(p, base->raliases, add->raliases);
+ new->cookie_paths
+ = apr_array_append(p, base->cookie_paths, add->cookie_paths);
+ new->cookie_domains
+ = apr_array_append(p, base->cookie_domains, add->cookie_domains);
+ new->error_override_codes
+ = apr_array_append(p, base->error_override_codes, add->error_override_codes);
+ /* Keep the array sorted for binary search (since "base" and "add" are
+ * already sorted, it's only needed only if both are merged).
+ */
+ if (base->error_override_codes->nelts
+ && add->error_override_codes->nelts) {
+ qsort(new->error_override_codes->elts,
+ new->error_override_codes->nelts,
+ sizeof(int), int_order);
+ }
+ new->interpolate_env = (add->interpolate_env == -1) ? base->interpolate_env
+ : add->interpolate_env;
+ new->preserve_host = (add->preserve_host_set == 0) ? base->preserve_host
+ : add->preserve_host;
+ new->preserve_host_set = add->preserve_host_set || base->preserve_host_set;
+ new->error_override = (add->error_override_set == 0) ? base->error_override
+ : add->error_override;
+ new->error_override_set = add->error_override_set || base->error_override_set;
+ new->alias = (add->alias_set == 0) ? base->alias : add->alias;
+ new->alias_set = add->alias_set || base->alias_set;
+ new->add_forwarded_headers =
+ (add->add_forwarded_headers_set == 0) ? base->add_forwarded_headers
+ : add->add_forwarded_headers;
+ new->add_forwarded_headers_set = add->add_forwarded_headers_set
+ || base->add_forwarded_headers_set;
+ new->forward_100_continue =
+ (add->forward_100_continue_set == 0) ? base->forward_100_continue
+ : add->forward_100_continue;
+ new->forward_100_continue_set = add->forward_100_continue_set
+ || base->forward_100_continue_set;
+
+ return new;
+}
+
+static const char *
+ add_proxy(cmd_parms *cmd, void *dummy, const char *f1, const char *r1, int regex)
+{
+ server_rec *s = cmd->server;
+ proxy_server_conf *conf =
+ (proxy_server_conf *) ap_get_module_config(s->module_config, &proxy_module);
+ struct proxy_remote *new;
+ char *p, *q;
+ char *r, *f, *scheme;
+ ap_regex_t *reg = NULL;
+ int port;
+
+ r = apr_pstrdup(cmd->pool, r1);
+ scheme = apr_pstrdup(cmd->pool, r1);
+ f = apr_pstrdup(cmd->pool, f1);
+ p = strchr(r, ':');
+ if (p == NULL || p[1] != '/' || p[2] != '/' || p[3] == '\0') {
+ if (regex)
+ return "ProxyRemoteMatch: Bad syntax for a remote proxy server";
+ else
+ return "ProxyRemote: Bad syntax for a remote proxy server";
+ }
+ else {
+ scheme[p-r] = 0;
+ }
+ q = strchr(p + 3, ':');
+ if (q != NULL) {
+ if (sscanf(q + 1, "%u", &port) != 1 || port > 65535) {
+ if (regex)
+ return "ProxyRemoteMatch: Bad syntax for a remote proxy server (bad port number)";
+ else
+ return "ProxyRemote: Bad syntax for a remote proxy server (bad port number)";
+ }
+ *q = '\0';
+ }
+ else
+ port = -1;
+ *p = '\0';
+ if (regex) {
+ reg = ap_pregcomp(cmd->pool, f, AP_REG_EXTENDED);
+ if (!reg)
+ return "Regular expression for ProxyRemoteMatch could not be compiled.";
+ }
+ else
+ if (strchr(f, ':') == NULL)
+ ap_str_tolower(f); /* lowercase scheme */
+ ap_str_tolower(p + 3); /* lowercase hostname */
+
+ if (port == -1) {
+ port = apr_uri_port_of_scheme(scheme);
+ }
+
+ new = apr_array_push(conf->proxies);
+ new->scheme = f;
+ new->protocol = r;
+ new->hostname = p + 3;
+ new->port = port;
+ new->regexp = reg;
+ new->use_regex = regex;
+ return NULL;
+}
+
+static const char *
+ add_proxy_noregex(cmd_parms *cmd, void *dummy, const char *f1, const char *r1)
+{
+ return add_proxy(cmd, dummy, f1, r1, 0);
+}
+
+static const char *
+ add_proxy_regex(cmd_parms *cmd, void *dummy, const char *f1, const char *r1)
+{
+ return add_proxy(cmd, dummy, f1, r1, 1);
+}
+
+PROXY_DECLARE(const char *) ap_proxy_de_socketfy(apr_pool_t *p, const char *url)
+{
+ const char *ptr;
+ /*
+ * We could be passed a URL during the config stage that contains
+ * the UDS path... ignore it
+ */
+ if (!ap_cstr_casecmpn(url, "unix:", 5) &&
+ ((ptr = ap_strchr_c(url + 5, '|')) != NULL)) {
+ /* move past the 'unix:...|' UDS path info */
+ const char *ret, *c;
+
+ ret = ptr + 1;
+ /* special case: "unix:....|scheme:" is OK, expand
+ * to "unix:....|scheme://localhost"
+ * */
+ c = ap_strchr_c(ret, ':');
+ if (c == NULL) {
+ return NULL;
+ }
+ if (c[1] == '\0') {
+ return apr_pstrcat(p, ret, "//localhost", NULL);
+ }
+ else {
+ return ret;
+ }
+ }
+ return url;
+}
+
+static const char *
+ add_pass(cmd_parms *cmd, void *dummy, const char *arg, int is_regex)
+{
+ proxy_dir_conf *dconf = (proxy_dir_conf *)dummy;
+ server_rec *s = cmd->server;
+ proxy_server_conf *conf =
+ (proxy_server_conf *) ap_get_module_config(s->module_config, &proxy_module);
+ struct proxy_alias *new;
+ char *f = cmd->path;
+ char *r = NULL;
+ const char *real;
+ char *word;
+ apr_table_t *params = apr_table_make(cmd->pool, 5);
+ const apr_array_header_t *arr;
+ const apr_table_entry_t *elts;
+ int i;
+ unsigned int worker_type = (is_regex) ? AP_PROXY_WORKER_IS_MATCH
+ : AP_PROXY_WORKER_IS_PREFIX;
+ unsigned int flags = 0;
+ const char *err;
+
+ err = ap_check_cmd_context(cmd, NOT_IN_DIRECTORY|NOT_IN_FILES);
+ if (err) {
+ return err;
+ }
+
+ while (*arg) {
+ word = ap_getword_conf(cmd->pool, &arg);
+ if (!f) {
+ if (!strcmp(word, "~")) {
+ if (is_regex) {
+ return "ProxyPassMatch invalid syntax ('~' usage).";
+ }
+ worker_type = AP_PROXY_WORKER_IS_MATCH;
+ continue;
+ }
+ f = word;
+ }
+ else if (!r) {
+ r = word;
+ }
+ else if (!strcasecmp(word,"nocanon")) {
+ flags |= PROXYPASS_NOCANON;
+ }
+ else if (!strcasecmp(word,"interpolate")) {
+ flags |= PROXYPASS_INTERPOLATE;
+ }
+ else if (!strcasecmp(word,"noquery")) {
+ flags |= PROXYPASS_NOQUERY;
+ }
+ else {
+ char *val = strchr(word, '=');
+ if (!val) {
+ if (cmd->path) {
+ if (*r == '/') {
+ return "ProxyPass|ProxyPassMatch can not have a path when defined in "
+ "a location.";
+ }
+ else {
+ return "Invalid ProxyPass|ProxyPassMatch parameter. Parameter must "
+ "be in the form 'key=value'.";
+ }
+ }
+ else {
+ return "Invalid ProxyPass|ProxyPassMatch parameter. Parameter must be "
+ "in the form 'key=value'.";
+ }
+ }
+ else {
+ *val++ = '\0';
+ }
+ if (!strcasecmp(word, "mapping")) {
+ if (!strcasecmp(val, "encoded")) {
+ flags |= PROXYPASS_MAP_ENCODED;
+ }
+ else if (!strcasecmp(val, "servlet")) {
+ flags |= PROXYPASS_MAP_SERVLET;
+ }
+ else {
+ return "unknown mapping";
+ }
+ }
+ else {
+ apr_table_setn(params, word, val);
+ }
+ }
+ }
+ if (flags & PROXYPASS_MAP_ENCODED) {
+ conf->map_encoded_one = 1;
+ }
+ else {
+ conf->map_encoded_all = 0;
+ }
+
+ if (r == NULL) {
+ return "ProxyPass|ProxyPassMatch needs a path when not defined in a location";
+ }
+ if (!(real = ap_proxy_de_socketfy(cmd->temp_pool, r))) {
+ return "ProxyPass|ProxyPassMatch uses an invalid \"unix:\" URL";
+ }
+
+
+ /* if per directory, save away the single alias */
+ if (cmd->path) {
+ dconf->alias = apr_pcalloc(cmd->pool, sizeof(struct proxy_alias));
+ dconf->alias_set = 1;
+ new = dconf->alias;
+ if (apr_fnmatch_test(f)) {
+ worker_type = AP_PROXY_WORKER_IS_MATCH;
+ }
+ }
+ /* if per server, add to the alias array */
+ else {
+ new = apr_array_push(conf->aliases);
+ }
+
+ new->fake = apr_pstrdup(cmd->pool, f);
+ new->real = apr_pstrdup(cmd->pool, real);
+ new->flags = flags;
+ if (worker_type & AP_PROXY_WORKER_IS_MATCH) {
+ new->regex = ap_pregcomp(cmd->pool, f, AP_REG_EXTENDED);
+ if (new->regex == NULL)
+ return "Regular expression could not be compiled.";
+ }
+ else {
+ new->regex = NULL;
+ }
+
+ if (r[0] == '!' && r[1] == '\0')
+ return NULL;
+
+ arr = apr_table_elts(params);
+ elts = (const apr_table_entry_t *)arr->elts;
+ /* Distinguish the balancer from worker */
+ if (ap_proxy_valid_balancer_name(r, 9)) {
+ proxy_balancer *balancer = ap_proxy_get_balancer(cmd->pool, conf, r, 0);
+ char *fake_copy;
+
+ /*
+ * In the regex case supplying a fake URL doesn't make sense as it
+ * cannot be parsed anyway with apr_uri_parse later on in
+ * ap_proxy_define_balancer / ap_proxy_update_balancer
+ */
+ if (worker_type & AP_PROXY_WORKER_IS_MATCH) {
+ fake_copy = NULL;
+ }
+ else {
+ fake_copy = f;
+ }
+ if (!balancer) {
+ const char *err = ap_proxy_define_balancer(cmd->pool, &balancer, conf, r, fake_copy, 0);
+ if (err)
+ return apr_pstrcat(cmd->temp_pool, "ProxyPass ", err, NULL);
+ }
+ else {
+ ap_proxy_update_balancer(cmd->pool, balancer, fake_copy);
+ }
+ for (i = 0; i < arr->nelts; i++) {
+ const char *err = set_balancer_param(conf, cmd->pool, balancer, elts[i].key,
+ elts[i].val);
+ if (err)
+ return apr_pstrcat(cmd->temp_pool, "ProxyPass ", err, NULL);
+ }
+ new->balancer = balancer;
+ }
+ else {
+ int reuse = 0;
+ proxy_worker *worker = ap_proxy_get_worker_ex(cmd->temp_pool, NULL,
+ conf, new->real,
+ worker_type);
+ if (!worker) {
+ const char *err;
+ err = ap_proxy_define_worker_ex(cmd->pool, &worker, NULL,
+ conf, r, worker_type);
+ if (err)
+ return apr_pstrcat(cmd->temp_pool, "ProxyPass ", err, NULL);
+
+ PROXY_COPY_CONF_PARAMS(worker, conf);
+ }
+ else {
+ reuse = 1;
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, cmd->server, APLOGNO(01145)
+ "Sharing worker '%s' instead of creating new worker '%s'",
+ ap_proxy_worker_name(cmd->pool, worker), new->real);
+ }
+
+ for (i = 0; i < arr->nelts; i++) {
+ if (reuse) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(01146)
+ "Ignoring parameter '%s=%s' for worker '%s' because of worker sharing",
+ elts[i].key, elts[i].val, ap_proxy_worker_name(cmd->pool, worker));
+ } else {
+ const char *err = set_worker_param(cmd->pool, s, worker, elts[i].key,
+ elts[i].val);
+ if (err)
+ return apr_pstrcat(cmd->temp_pool, "ProxyPass ", err, NULL);
+ }
+ }
+ }
+ return NULL;
+}
+
+static const char *
+ add_pass_noregex(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ return add_pass(cmd, dummy, arg, 0);
+}
+
+static const char *
+ add_pass_regex(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ return add_pass(cmd, dummy, arg, 1);
+}
+
+
+static const char * add_pass_reverse(cmd_parms *cmd, void *dconf, const char *f,
+ const char *r, const char *i)
+{
+ proxy_dir_conf *conf = dconf;
+ struct proxy_alias *new;
+ const char *fake;
+ const char *real;
+ const char *interp;
+ const char *err;
+
+ err = ap_check_cmd_context(cmd, NOT_IN_DIRECTORY|NOT_IN_FILES);
+ if (err) {
+ return err;
+ }
+
+ if (cmd->path == NULL) {
+ if (r == NULL || !strcasecmp(r, "interpolate")) {
+ return "ProxyPassReverse needs a path when not defined in a location";
+ }
+ fake = f;
+ real = r;
+ interp = i;
+ }
+ else {
+ if (r && strcasecmp(r, "interpolate")) {
+ return "ProxyPassReverse can not have a path when defined in a location";
+ }
+ fake = cmd->path;
+ real = f;
+ interp = r;
+ }
+
+ new = apr_array_push(conf->raliases);
+ new->fake = fake;
+ new->real = real;
+ new->flags = interp ? PROXYPASS_INTERPOLATE : 0;
+
+ return NULL;
+}
+static const char* cookie_path(cmd_parms *cmd, void *dconf, const char *f,
+ const char *r, const char *interp)
+{
+ proxy_dir_conf *conf = dconf;
+ struct proxy_alias *new;
+
+ new = apr_array_push(conf->cookie_paths);
+ new->fake = f;
+ new->real = r;
+ new->flags = interp ? PROXYPASS_INTERPOLATE : 0;
+
+ return NULL;
+}
+static const char* cookie_domain(cmd_parms *cmd, void *dconf, const char *f,
+ const char *r, const char *interp)
+{
+ proxy_dir_conf *conf = dconf;
+ struct proxy_alias *new;
+
+ new = apr_array_push(conf->cookie_domains);
+ new->fake = f;
+ new->real = r;
+ new->flags = interp ? PROXYPASS_INTERPOLATE : 0;
+ return NULL;
+}
+
+static const char *
+ set_proxy_exclude(cmd_parms *parms, void *dummy, const char *arg)
+{
+ server_rec *s = parms->server;
+ proxy_server_conf *conf =
+ ap_get_module_config(s->module_config, &proxy_module);
+ struct noproxy_entry *new;
+ struct noproxy_entry *list = (struct noproxy_entry *) conf->noproxies->elts;
+ struct apr_sockaddr_t *addr;
+ int found = 0;
+ int i;
+
+ /* Don't duplicate entries */
+ for (i = 0; i < conf->noproxies->nelts; i++) {
+ if (strcasecmp(arg, list[i].name) == 0) { /* ignore case for host names */
+ found = 1;
+ break;
+ }
+ }
+
+ if (!found) {
+ new = apr_array_push(conf->noproxies);
+ new->name = arg;
+ if (APR_SUCCESS == apr_sockaddr_info_get(&addr, new->name, APR_UNSPEC, 0, 0, parms->pool)) {
+ new->addr = addr;
+ }
+ else {
+ new->addr = NULL;
+ }
+ }
+ return NULL;
+}
+
+
+/* Similar to set_proxy_exclude(), but defining directly connected hosts,
+ * which should never be accessed via the configured ProxyRemote servers
+ */
+static const char *
+ set_proxy_dirconn(cmd_parms *parms, void *dummy, const char *arg)
+{
+ server_rec *s = parms->server;
+ proxy_server_conf *conf =
+ ap_get_module_config(s->module_config, &proxy_module);
+ struct dirconn_entry *New;
+ struct dirconn_entry *list = (struct dirconn_entry *) conf->dirconn->elts;
+ int found = 0;
+ int i;
+
+ /* Don't duplicate entries */
+ for (i = 0; i < conf->dirconn->nelts; i++) {
+ if (strcasecmp(arg, list[i].name) == 0) {
+ found = 1;
+ break;
+ }
+ }
+
+ if (!found) {
+ New = apr_array_push(conf->dirconn);
+ New->name = apr_pstrdup(parms->pool, arg);
+ New->hostaddr = NULL;
+
+ if (ap_proxy_is_ipaddr(New, parms->pool)) {
+#if DEBUGGING
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(03018)
+ "Parsed addr %s", inet_ntoa(New->addr));
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(03019)
+ "Parsed mask %s", inet_ntoa(New->mask));
+#endif
+ }
+ else if (ap_proxy_is_domainname(New, parms->pool)) {
+ ap_str_tolower(New->name);
+#if DEBUGGING
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(03020)
+ "Parsed domain %s", New->name);
+#endif
+ }
+ else if (ap_proxy_is_hostname(New, parms->pool)) {
+ ap_str_tolower(New->name);
+#if DEBUGGING
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(03021)
+ "Parsed host %s", New->name);
+#endif
+ }
+ else {
+ ap_proxy_is_word(New, parms->pool);
+#if DEBUGGING
+ fprintf(stderr, "Parsed word %s\n", New->name);
+#endif
+ }
+ }
+ return NULL;
+}
+
+static const char *
+ set_proxy_domain(cmd_parms *parms, void *dummy, const char *arg)
+{
+ proxy_server_conf *psf =
+ ap_get_module_config(parms->server->module_config, &proxy_module);
+
+ if (arg[0] != '.')
+ return "ProxyDomain: domain name must start with a dot.";
+
+ psf->domain = arg;
+ return NULL;
+}
+
+static const char *
+ set_proxy_req(cmd_parms *parms, void *dummy, int flag)
+{
+ proxy_server_conf *psf =
+ ap_get_module_config(parms->server->module_config, &proxy_module);
+
+ psf->req = flag;
+ psf->req_set = 1;
+ return NULL;
+}
+
+static const char *
+ set_proxy_error_override(cmd_parms *parms, void *dconf, const char *arg)
+{
+ proxy_dir_conf *conf = dconf;
+
+ if (strcasecmp(arg, "Off") == 0) {
+ conf->error_override = 0;
+ conf->error_override_set = 1;
+ }
+ else if (strcasecmp(arg, "On") == 0) {
+ conf->error_override = 1;
+ conf->error_override_set = 1;
+ }
+ else if (conf->error_override_set == 1) {
+ int *newcode;
+ int argcode, i;
+ if (!apr_isdigit(arg[0]))
+ return "ProxyErrorOverride: status codes to intercept must be numeric";
+ if (!conf->error_override)
+ return "ProxyErrorOverride: status codes must follow a value of 'on'";
+
+ argcode = strtol(arg, NULL, 10);
+ if (!ap_is_HTTP_ERROR(argcode))
+ return "ProxyErrorOverride: status codes to intercept must be valid HTTP Status Codes >=400 && <600";
+
+ newcode = apr_array_push(conf->error_override_codes);
+ *newcode = argcode;
+
+ /* Keep the array sorted for binary search. */
+ for (i = conf->error_override_codes->nelts - 1; i > 0; --i) {
+ int *oldcode = &((int *)conf->error_override_codes->elts)[i - 1];
+ if (*oldcode <= argcode) {
+ break;
+ }
+ *newcode = *oldcode;
+ *oldcode = argcode;
+ newcode = oldcode;
+ }
+ }
+ else
+ return "ProxyErrorOverride first parameter must be one of: off | on";
+
+ return NULL;
+}
+
+static const char *
+ add_proxy_http_headers(cmd_parms *parms, void *dconf, int flag)
+{
+ proxy_dir_conf *conf = dconf;
+ conf->add_forwarded_headers = flag;
+ conf->add_forwarded_headers_set = 1;
+ return NULL;
+}
+static const char *
+ set_preserve_host(cmd_parms *parms, void *dconf, int flag)
+{
+ proxy_dir_conf *conf = dconf;
+
+ conf->preserve_host = flag;
+ conf->preserve_host_set = 1;
+ return NULL;
+}
+static const char *
+ forward_100_continue(cmd_parms *parms, void *dconf, int flag)
+{
+ proxy_dir_conf *conf = dconf;
+ conf->forward_100_continue = flag;
+ conf->forward_100_continue_set = 1;
+ return NULL;
+}
+
+static const char *
+ set_recv_buffer_size(cmd_parms *parms, void *dummy, const char *arg)
+{
+ proxy_server_conf *psf =
+ ap_get_module_config(parms->server->module_config, &proxy_module);
+ int s = atoi(arg);
+ if (s < 512 && s != 0) {
+ return "ProxyReceiveBufferSize must be >= 512 bytes, or 0 for system default.";
+ }
+
+ psf->recv_buffer_size = s;
+ psf->recv_buffer_size_set = 1;
+ return NULL;
+}
+
+static const char *
+ set_io_buffer_size(cmd_parms *parms, void *dummy, const char *arg)
+{
+ proxy_server_conf *psf =
+ ap_get_module_config(parms->server->module_config, &proxy_module);
+ long s = atol(arg);
+ if (s < 512 && s) {
+ return "ProxyIOBufferSize must be >= 512 bytes, or 0 for system default.";
+ }
+ psf->io_buffer_size = (s ? s : AP_IOBUFSIZE);
+ psf->io_buffer_size_set = 1;
+ return NULL;
+}
+
+static const char *
+ set_max_forwards(cmd_parms *parms, void *dummy, const char *arg)
+{
+ proxy_server_conf *psf =
+ ap_get_module_config(parms->server->module_config, &proxy_module);
+ long s = atol(arg);
+
+ psf->maxfwd = s;
+ psf->maxfwd_set = 1;
+ return NULL;
+}
+static const char*
+ set_proxy_timeout(cmd_parms *parms, void *dummy, const char *arg)
+{
+ proxy_server_conf *psf =
+ ap_get_module_config(parms->server->module_config, &proxy_module);
+ int timeout;
+
+ timeout = atoi(arg);
+ if (timeout<1) {
+ return "Proxy Timeout must be at least 1 second.";
+ }
+ psf->timeout_set = 1;
+ psf->timeout = apr_time_from_sec(timeout);
+
+ return NULL;
+}
+
+static const char*
+ set_via_opt(cmd_parms *parms, void *dummy, const char *arg)
+{
+ proxy_server_conf *psf =
+ ap_get_module_config(parms->server->module_config, &proxy_module);
+
+ if (strcasecmp(arg, "Off") == 0)
+ psf->viaopt = via_off;
+ else if (strcasecmp(arg, "On") == 0)
+ psf->viaopt = via_on;
+ else if (strcasecmp(arg, "Block") == 0)
+ psf->viaopt = via_block;
+ else if (strcasecmp(arg, "Full") == 0)
+ psf->viaopt = via_full;
+ else {
+ return "ProxyVia must be one of: "
+ "off | on | full | block";
+ }
+
+ psf->viaopt_set = 1;
+ return NULL;
+}
+
+static const char*
+ set_bad_opt(cmd_parms *parms, void *dummy, const char *arg)
+{
+ proxy_server_conf *psf =
+ ap_get_module_config(parms->server->module_config, &proxy_module);
+
+ if (strcasecmp(arg, "IsError") == 0)
+ psf->badopt = bad_error;
+ else if (strcasecmp(arg, "Ignore") == 0)
+ psf->badopt = bad_ignore;
+ else if (strcasecmp(arg, "StartBody") == 0)
+ psf->badopt = bad_body;
+ else {
+ return "ProxyBadHeader must be one of: "
+ "IsError | Ignore | StartBody";
+ }
+
+ psf->badopt_set = 1;
+ return NULL;
+}
+
+static const char*
+ set_status_opt(cmd_parms *parms, void *dummy, const char *arg)
+{
+ proxy_server_conf *psf =
+ ap_get_module_config(parms->server->module_config, &proxy_module);
+
+ if (strcasecmp(arg, "Off") == 0)
+ psf->proxy_status = status_off;
+ else if (strcasecmp(arg, "On") == 0)
+ psf->proxy_status = status_on;
+ else if (strcasecmp(arg, "Full") == 0)
+ psf->proxy_status = status_full;
+ else {
+ return "ProxyStatus must be one of: "
+ "off | on | full";
+ }
+
+ psf->proxy_status_set = 1;
+ return NULL;
+}
+
+static const char *set_bgrowth(cmd_parms *parms, void *dummy, const char *arg)
+{
+ proxy_server_conf *psf =
+ ap_get_module_config(parms->server->module_config, &proxy_module);
+
+ int growth = atoi(arg);
+ if (growth < 0 || growth > 1000) {
+ return "BalancerGrowth must be between 0 and 1000";
+ }
+ psf->bgrowth = growth;
+ psf->bgrowth_set = 1;
+
+ return NULL;
+}
+
+static const char *set_persist(cmd_parms *parms, void *dummy, int flag)
+{
+ proxy_server_conf *psf =
+ ap_get_module_config(parms->server->module_config, &proxy_module);
+
+ psf->bal_persist = flag;
+ return NULL;
+}
+
+static const char *set_inherit(cmd_parms *parms, void *dummy, int flag)
+{
+ proxy_server_conf *psf =
+ ap_get_module_config(parms->server->module_config, &proxy_module);
+
+ psf->inherit = flag;
+ psf->inherit_set = 1;
+ return NULL;
+}
+
+static const char *set_ppinherit(cmd_parms *parms, void *dummy, int flag)
+{
+ proxy_server_conf *psf =
+ ap_get_module_config(parms->server->module_config, &proxy_module);
+
+ psf->ppinherit = flag;
+ psf->ppinherit_set = 1;
+ return NULL;
+}
+
+static const char *add_member(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ server_rec *s = cmd->server;
+ proxy_server_conf *conf =
+ ap_get_module_config(s->module_config, &proxy_module);
+ proxy_balancer *balancer;
+ proxy_worker *worker;
+ char *path = cmd->path;
+ char *name = NULL;
+ const char *real;
+ char *word;
+ apr_table_t *params = apr_table_make(cmd->pool, 5);
+ const apr_array_header_t *arr;
+ const apr_table_entry_t *elts;
+ int reuse = 0;
+ int i;
+ /* XXX: Should this be NOT_IN_DIRECTORY|NOT_IN_FILES? */
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_HTACCESS);
+ if (err)
+ return err;
+
+ if (cmd->path)
+ path = apr_pstrdup(cmd->pool, cmd->path);
+
+ while (*arg) {
+ char *val;
+ word = ap_getword_conf(cmd->pool, &arg);
+ val = strchr(word, '=');
+
+ if (!val) {
+ if (!path)
+ path = word;
+ else if (!name)
+ name = word;
+ else {
+ if (cmd->path)
+ return "BalancerMember can not have a balancer name when defined in a location";
+ else
+ return "Invalid BalancerMember parameter. Parameter must "
+ "be in the form 'key=value'";
+ }
+ } else {
+ *val++ = '\0';
+ apr_table_setn(params, word, val);
+ }
+ }
+ if (!path)
+ return "BalancerMember must define balancer name when outside <Proxy > section";
+ if (!name)
+ return "BalancerMember must define remote proxy server";
+ if (!(real = ap_proxy_de_socketfy(cmd->temp_pool, name))) {
+ return "BalancerMember uses an invalid \"unix:\" URL";
+ }
+
+ ap_str_tolower(path); /* lowercase scheme://hostname */
+
+ /* Try to find the balancer */
+ balancer = ap_proxy_get_balancer(cmd->temp_pool, conf, path, 0);
+ if (!balancer) {
+ err = ap_proxy_define_balancer(cmd->pool, &balancer, conf, path, "/", 0);
+ if (err)
+ return apr_pstrcat(cmd->temp_pool, "BalancerMember ", err, NULL);
+ }
+
+ /* Try to find existing worker */
+ worker = ap_proxy_get_worker(cmd->temp_pool, balancer, conf, real);
+ if (!worker) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01147)
+ "Defining worker '%s' for balancer '%s'",
+ name, balancer->s->name);
+ if ((err = ap_proxy_define_worker(cmd->pool, &worker, balancer, conf, name, 0)) != NULL)
+ return apr_pstrcat(cmd->temp_pool, "BalancerMember ", err, NULL);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01148)
+ "Defined worker '%s' for balancer '%s'",
+ ap_proxy_worker_name(cmd->pool, worker), balancer->s->name);
+ PROXY_COPY_CONF_PARAMS(worker, conf);
+ } else {
+ reuse = 1;
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, cmd->server, APLOGNO(01149)
+ "Sharing worker '%s' instead of creating new worker '%s'",
+ ap_proxy_worker_name(cmd->pool, worker), name);
+ }
+ if (!worker->section_config) {
+ worker->section_config = balancer->section_config;
+ }
+
+ arr = apr_table_elts(params);
+ elts = (const apr_table_entry_t *)arr->elts;
+ for (i = 0; i < arr->nelts; i++) {
+ if (reuse) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(01150)
+ "Ignoring parameter '%s=%s' for worker '%s' because of worker sharing",
+ elts[i].key, elts[i].val, ap_proxy_worker_name(cmd->pool, worker));
+ } else {
+ err = set_worker_param(cmd->pool, cmd->server, worker, elts[i].key,
+ elts[i].val);
+ if (err)
+ return apr_pstrcat(cmd->temp_pool, "BalancerMember ", err, NULL);
+ }
+ }
+
+ return NULL;
+}
+
+static const char *
+ set_proxy_param(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ server_rec *s = cmd->server;
+ proxy_server_conf *conf =
+ (proxy_server_conf *) ap_get_module_config(s->module_config, &proxy_module);
+ char *name = NULL;
+ char *word, *val;
+ proxy_balancer *balancer = NULL;
+ proxy_worker *worker = NULL;
+ unsigned int worker_type = 0;
+ int in_proxy_section = 0;
+ /* XXX: Should this be NOT_IN_DIRECTORY|NOT_IN_FILES? */
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_HTACCESS);
+ if (err)
+ return err;
+
+ if (cmd->directive->parent &&
+ strncasecmp(cmd->directive->parent->directive,
+ "<Proxy", 6) == 0) {
+ const char *pargs = cmd->directive->parent->args;
+ /* Directive inside <Proxy section
+ * Parent directive arg is the worker/balancer name.
+ */
+ name = ap_getword_conf(cmd->temp_pool, &pargs);
+ if ((word = ap_strchr(name, '>')))
+ *word = '\0';
+ if (strncasecmp(cmd->directive->parent->directive + 6,
+ "Match", 5) == 0) {
+ worker_type = AP_PROXY_WORKER_IS_MATCH;
+ }
+ else {
+ worker_type = AP_PROXY_WORKER_IS_PREFIX;
+ }
+ in_proxy_section = 1;
+ }
+ else {
+ /* Standard set directive with worker/balancer
+ * name as first param.
+ */
+ name = ap_getword_conf(cmd->temp_pool, &arg);
+ }
+
+ if (ap_proxy_valid_balancer_name(name, 9)) {
+ balancer = ap_proxy_get_balancer(cmd->pool, conf, name, 0);
+ if (!balancer) {
+ if (in_proxy_section) {
+ err = ap_proxy_define_balancer(cmd->pool, &balancer, conf, name, "/", 0);
+ if (err)
+ return apr_pstrcat(cmd->temp_pool, "ProxySet ",
+ err, NULL);
+ }
+ else
+ return apr_pstrcat(cmd->temp_pool, "ProxySet can not find '",
+ name, "' Balancer.", NULL);
+ }
+ }
+ else {
+ const char *real;
+
+ if (!(real = ap_proxy_de_socketfy(cmd->temp_pool, name))) {
+ return "ProxySet uses an invalid \"unix:\" URL";
+ }
+
+ worker = ap_proxy_get_worker_ex(cmd->temp_pool, NULL, conf,
+ real, worker_type);
+ if (!worker) {
+ if (in_proxy_section) {
+ err = ap_proxy_define_worker_ex(cmd->pool, &worker, NULL,
+ conf, name, worker_type);
+ if (err)
+ return apr_pstrcat(cmd->temp_pool, "ProxySet ",
+ err, NULL);
+ }
+ else
+ return apr_pstrcat(cmd->temp_pool, "ProxySet can not find '",
+ name, "' Worker.", NULL);
+ }
+ }
+
+ while (*arg) {
+ word = ap_getword_conf(cmd->pool, &arg);
+ val = strchr(word, '=');
+ if (!val) {
+ return "Invalid ProxySet parameter. Parameter must be "
+ "in the form 'key=value'";
+ }
+ else
+ *val++ = '\0';
+ if (worker)
+ err = set_worker_param(cmd->pool, cmd->server, worker, word, val);
+ else
+ err = set_balancer_param(conf, cmd->pool, balancer, word, val);
+
+ if (err)
+ return apr_pstrcat(cmd->temp_pool, "ProxySet: ", err, " ", word, "=", val, "; ", name, NULL);
+ }
+
+ return NULL;
+}
+
+static void ap_add_per_proxy_conf(server_rec *s, ap_conf_vector_t *dir_config)
+{
+ proxy_server_conf *sconf = ap_get_module_config(s->module_config,
+ &proxy_module);
+ void **new_space = (void **)apr_array_push(sconf->sec_proxy);
+
+ *new_space = dir_config;
+}
+
+static const char *proxysection(cmd_parms *cmd, void *mconfig, const char *arg)
+{
+ const char *errmsg;
+ const char *endp = ap_strrchr_c(arg, '>');
+ int old_overrides = cmd->override;
+ char *old_path = cmd->path;
+ proxy_dir_conf *conf;
+ ap_conf_vector_t *new_dir_conf = ap_create_per_dir_config(cmd->pool);
+ ap_regex_t *r = NULL;
+ const command_rec *thiscmd = cmd->cmd;
+ char *word, *val;
+ proxy_balancer *balancer = NULL;
+ proxy_worker *worker = NULL;
+ unsigned int worker_type = AP_PROXY_WORKER_IS_PREFIX;
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_CONTEXT);
+ proxy_server_conf *sconf =
+ (proxy_server_conf *) ap_get_module_config(cmd->server->module_config, &proxy_module);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ if (endp == NULL) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ "> directive missing closing '>'", NULL);
+ }
+
+ arg = apr_pstrndup(cmd->pool, arg, endp-arg);
+
+ if (!arg) {
+ if (thiscmd->cmd_data)
+ return "<ProxyMatch > block must specify a path";
+ else
+ return "<Proxy > block must specify a path";
+ }
+
+ cmd->path = ap_getword_conf(cmd->pool, &arg);
+ cmd->override = OR_ALL|ACCESS_CONF|PROXY_CONF;
+
+ if (!strncasecmp(cmd->path, "proxy:", 6))
+ cmd->path += 6;
+
+ /* XXX Ignore case? What if we proxy a case-insensitive server?!?
+ * While we are at it, shouldn't we also canonicalize the entire
+ * scheme? See proxy_fixup()
+ */
+ if (thiscmd->cmd_data) { /* <ProxyMatch> */
+ r = ap_pregcomp(cmd->pool, cmd->path, AP_REG_EXTENDED);
+ if (!r) {
+ return "Regex could not be compiled";
+ }
+ worker_type = AP_PROXY_WORKER_IS_MATCH;
+ }
+
+ /* initialize our config and fetch it */
+ conf = ap_set_config_vectors(cmd->server, new_dir_conf, cmd->path,
+ &proxy_module, cmd->pool);
+
+ errmsg = ap_walk_config(cmd->directive->first_child, cmd, new_dir_conf);
+ if (errmsg != NULL)
+ return errmsg;
+
+ conf->r = r;
+ conf->p = cmd->path;
+ conf->p_is_fnmatch = apr_fnmatch_test(conf->p);
+
+ if (r) {
+ conf->refs = apr_array_make(cmd->pool, 8, sizeof(char *));
+ ap_regname(r, conf->refs, AP_REG_MATCH, 1);
+ }
+
+ ap_add_per_proxy_conf(cmd->server, new_dir_conf);
+
+ if (*arg != '\0') {
+ if (thiscmd->cmd_data)
+ return "Multiple <ProxyMatch> arguments not (yet) supported.";
+ if (conf->p_is_fnmatch)
+ return apr_pstrcat(cmd->pool, thiscmd->name,
+ "> arguments are not supported for wildchar url.",
+ NULL);
+ if (!ap_strchr_c(conf->p, ':'))
+ return apr_pstrcat(cmd->pool, thiscmd->name,
+ "> arguments are not supported for non url.",
+ NULL);
+ if (ap_proxy_valid_balancer_name((char *)conf->p, 9)) {
+ balancer = ap_proxy_get_balancer(cmd->pool, sconf, conf->p, 0);
+ if (!balancer) {
+ err = ap_proxy_define_balancer(cmd->pool, &balancer,
+ sconf, conf->p, "/", 0);
+ if (err)
+ return apr_pstrcat(cmd->temp_pool, thiscmd->name,
+ " ", err, NULL);
+ }
+ if (!balancer->section_config) {
+ balancer->section_config = new_dir_conf;
+ }
+ }
+ else {
+ const char *real;
+
+ if (!(real = ap_proxy_de_socketfy(cmd->temp_pool, conf->p))) {
+ return "<Proxy/ProxyMatch > uses an invalid \"unix:\" URL";
+ }
+
+ worker = ap_proxy_get_worker_ex(cmd->temp_pool, NULL, sconf,
+ real, worker_type);
+ if (!worker) {
+ err = ap_proxy_define_worker_ex(cmd->pool, &worker, NULL, sconf,
+ conf->p, worker_type);
+ if (err)
+ return apr_pstrcat(cmd->temp_pool, thiscmd->name,
+ " ", err, NULL);
+ }
+ if (!worker->section_config) {
+ worker->section_config = new_dir_conf;
+ }
+ }
+ if (worker == NULL && balancer == NULL) {
+ return apr_pstrcat(cmd->pool, thiscmd->name,
+ "> arguments are supported only for workers.",
+ NULL);
+ }
+ while (*arg) {
+ word = ap_getword_conf(cmd->pool, &arg);
+ val = strchr(word, '=');
+ if (!val) {
+ return "Invalid Proxy parameter. Parameter must be "
+ "in the form 'key=value'";
+ }
+ else
+ *val++ = '\0';
+ if (worker)
+ err = set_worker_param(cmd->pool, cmd->server, worker, word, val);
+ else
+ err = set_balancer_param(sconf, cmd->pool, balancer,
+ word, val);
+ if (err)
+ return apr_pstrcat(cmd->temp_pool, thiscmd->name, " ", err, " ",
+ word, "=", val, "; ", conf->p, NULL);
+ }
+ }
+
+ cmd->path = old_path;
+ cmd->override = old_overrides;
+
+ return NULL;
+}
+
+static const command_rec proxy_cmds[] =
+{
+ AP_INIT_RAW_ARGS("<Proxy", proxysection, NULL, RSRC_CONF,
+ "Container for directives affecting resources located in the proxied "
+ "location"),
+ AP_INIT_RAW_ARGS("<ProxyMatch", proxysection, (void*)1, RSRC_CONF,
+ "Container for directives affecting resources located in the proxied "
+ "location, in regular expression syntax"),
+ AP_INIT_FLAG("ProxyRequests", set_proxy_req, NULL, RSRC_CONF,
+ "on if the true proxy requests should be accepted"),
+ AP_INIT_TAKE2("ProxyRemote", add_proxy_noregex, NULL, RSRC_CONF,
+ "a scheme, partial URL or '*' and a proxy server"),
+ AP_INIT_TAKE2("ProxyRemoteMatch", add_proxy_regex, NULL, RSRC_CONF,
+ "a regex pattern and a proxy server"),
+ AP_INIT_FLAG("ProxyPassInterpolateEnv", ap_set_flag_slot_char,
+ (void*)APR_OFFSETOF(proxy_dir_conf, interpolate_env),
+ RSRC_CONF|ACCESS_CONF, "Interpolate Env Vars in reverse Proxy") ,
+ AP_INIT_RAW_ARGS("ProxyPass", add_pass_noregex, NULL, RSRC_CONF|ACCESS_CONF,
+ "a virtual path and a URL"),
+ AP_INIT_RAW_ARGS("ProxyPassMatch", add_pass_regex, NULL, RSRC_CONF|ACCESS_CONF,
+ "a virtual path and a URL"),
+ AP_INIT_TAKE123("ProxyPassReverse", add_pass_reverse, NULL, RSRC_CONF|ACCESS_CONF,
+ "a virtual path and a URL for reverse proxy behaviour"),
+ AP_INIT_TAKE23("ProxyPassReverseCookiePath", cookie_path, NULL,
+ RSRC_CONF|ACCESS_CONF, "Path rewrite rule for proxying cookies"),
+ AP_INIT_TAKE23("ProxyPassReverseCookieDomain", cookie_domain, NULL,
+ RSRC_CONF|ACCESS_CONF, "Domain rewrite rule for proxying cookies"),
+ AP_INIT_ITERATE("ProxyBlock", set_proxy_exclude, NULL, RSRC_CONF,
+ "A list of names, hosts or domains to which the proxy will not connect"),
+ AP_INIT_TAKE1("ProxyReceiveBufferSize", set_recv_buffer_size, NULL, RSRC_CONF,
+ "Receive buffer size for outgoing HTTP and FTP connections in bytes"),
+ AP_INIT_TAKE1("ProxyIOBufferSize", set_io_buffer_size, NULL, RSRC_CONF,
+ "IO buffer size for outgoing HTTP and FTP connections in bytes"),
+ AP_INIT_TAKE1("ProxyMaxForwards", set_max_forwards, NULL, RSRC_CONF,
+ "The maximum number of proxies a request may be forwarded through."),
+ AP_INIT_ITERATE("NoProxy", set_proxy_dirconn, NULL, RSRC_CONF,
+ "A list of domains, hosts, or subnets to which the proxy will connect directly"),
+ AP_INIT_TAKE1("ProxyDomain", set_proxy_domain, NULL, RSRC_CONF,
+ "The default intranet domain name (in absence of a domain in the URL)"),
+ AP_INIT_TAKE1("ProxyVia", set_via_opt, NULL, RSRC_CONF,
+ "Configure Via: proxy header header to one of: on | off | block | full"),
+ AP_INIT_ITERATE("ProxyErrorOverride", set_proxy_error_override, NULL, RSRC_CONF|ACCESS_CONF,
+ "use our error handling pages instead of the servers' we are proxying"),
+ AP_INIT_FLAG("ProxyPreserveHost", set_preserve_host, NULL, RSRC_CONF|ACCESS_CONF,
+ "on if we should preserve host header while proxying"),
+ AP_INIT_TAKE1("ProxyTimeout", set_proxy_timeout, NULL, RSRC_CONF,
+ "Set the timeout (in seconds) for a proxied connection. "
+ "This overrides the server timeout"),
+ AP_INIT_TAKE1("ProxyBadHeader", set_bad_opt, NULL, RSRC_CONF,
+ "How to handle bad header line in response: IsError | Ignore | StartBody"),
+ AP_INIT_RAW_ARGS("BalancerMember", add_member, NULL, RSRC_CONF|ACCESS_CONF,
+ "A balancer name and scheme with list of params"),
+ AP_INIT_TAKE1("BalancerGrowth", set_bgrowth, NULL, RSRC_CONF,
+ "Number of additional Balancers that can be added post-config"),
+ AP_INIT_FLAG("BalancerPersist", set_persist, NULL, RSRC_CONF,
+ "on if the balancer should persist changes on reboot/restart made via the Balancer Manager"),
+ AP_INIT_FLAG("BalancerInherit", set_inherit, NULL, RSRC_CONF,
+ "on if this server should inherit Balancers and Workers defined in the main server "
+ "(Setting to off recommended if using the Balancer Manager)"),
+ AP_INIT_FLAG("ProxyPassInherit", set_ppinherit, NULL, RSRC_CONF,
+ "on if this server should inherit all ProxyPass directives defined in the main server "
+ "(Setting to off recommended if using the Balancer Manager)"),
+ AP_INIT_TAKE1("ProxyStatus", set_status_opt, NULL, RSRC_CONF,
+ "Configure Status: proxy status to one of: on | off | full"),
+ AP_INIT_RAW_ARGS("ProxySet", set_proxy_param, NULL, RSRC_CONF|ACCESS_CONF,
+ "A balancer or worker name with list of params"),
+ AP_INIT_TAKE1("ProxySourceAddress", set_source_address, NULL, RSRC_CONF,
+ "Configure local source IP used for request forward"),
+ AP_INIT_FLAG("ProxyAddHeaders", add_proxy_http_headers, NULL, RSRC_CONF|ACCESS_CONF,
+ "on if X-Forwarded-* headers should be added or completed"),
+ AP_INIT_FLAG("Proxy100Continue", forward_100_continue, NULL, RSRC_CONF|ACCESS_CONF,
+ "on if 100-Continue should be forwarded to the origin server, off if the "
+ "proxy should handle it by itself"),
+ {NULL}
+};
+
+static APR_OPTIONAL_FN_TYPE(ssl_proxy_enable) *proxy_ssl_enable = NULL;
+static APR_OPTIONAL_FN_TYPE(ssl_engine_disable) *proxy_ssl_disable = NULL;
+static APR_OPTIONAL_FN_TYPE(ssl_engine_set) *proxy_ssl_engine = NULL;
+
+PROXY_DECLARE(int) ap_proxy_ssl_enable(conn_rec *c)
+{
+ /*
+ * if c == NULL just check if the optional function was imported
+ * else run the optional function so ssl filters are inserted
+ */
+ if (c == NULL) {
+ return ap_ssl_has_outgoing_handlers();
+ }
+ return ap_ssl_bind_outgoing(c, NULL, 1) == OK;
+}
+
+PROXY_DECLARE(int) ap_proxy_ssl_disable(conn_rec *c)
+{
+ return ap_ssl_bind_outgoing(c, NULL, 0) == OK;
+}
+
+PROXY_DECLARE(int) ap_proxy_ssl_engine(conn_rec *c,
+ ap_conf_vector_t *per_dir_config,
+ int enable)
+{
+ /*
+ * if c == NULL just check if the optional function was imported
+ * else run the optional function so ssl filters are inserted
+ */
+ if (c == NULL) {
+ return ap_ssl_has_outgoing_handlers();
+ }
+ return ap_ssl_bind_outgoing(c, per_dir_config, enable) == OK;
+}
+
+PROXY_DECLARE(int) ap_proxy_conn_is_https(conn_rec *c)
+{
+ return ap_ssl_conn_is_ssl(c);
+}
+
+PROXY_DECLARE(const char *) ap_proxy_ssl_val(apr_pool_t *p, server_rec *s,
+ conn_rec *c, request_rec *r,
+ const char *var)
+{
+ return ap_ssl_var_lookup(p, s, c, r, var);
+}
+
+static int proxy_post_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *main_s)
+{
+ server_rec *s = main_s;
+ apr_status_t rv = ap_global_mutex_create(&proxy_mutex, NULL,
+ proxy_id, NULL, s, pconf, 0);
+ if (rv != APR_SUCCESS) {
+ ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog, APLOGNO(02478)
+ "failed to create %s mutex", proxy_id);
+ return rv;
+ }
+
+ proxy_ssl_enable = APR_RETRIEVE_OPTIONAL_FN(ssl_proxy_enable);
+ proxy_ssl_disable = APR_RETRIEVE_OPTIONAL_FN(ssl_engine_disable);
+ proxy_ssl_engine = APR_RETRIEVE_OPTIONAL_FN(ssl_engine_set);
+ ap_proxy_strmatch_path = apr_strmatch_precompile(pconf, "path=", 0);
+ ap_proxy_strmatch_domain = apr_strmatch_precompile(pconf, "domain=", 0);
+
+ for (; s; s = s->next) {
+ int rc, i;
+ proxy_server_conf *sconf =
+ ap_get_module_config(s->module_config, &proxy_module);
+ ap_conf_vector_t **sections =
+ (ap_conf_vector_t **)sconf->sec_proxy->elts;
+
+ for (i = 0; i < sconf->sec_proxy->nelts; ++i) {
+ rc = proxy_run_section_post_config(pconf, ptemp, plog,
+ s, sections[i]);
+ if (rc != OK && rc != DECLINED) {
+ return rc;
+ }
+ }
+ }
+
+ return OK;
+}
+
+/*
+ * proxy Extension to mod_status
+ */
+static int proxy_status_hook(request_rec *r, int flags)
+{
+ int i, n;
+ void *sconf = r->server->module_config;
+ proxy_server_conf *conf = (proxy_server_conf *)
+ ap_get_module_config(sconf, &proxy_module);
+ proxy_balancer *balancer = NULL;
+ proxy_worker **worker = NULL;
+
+ if (conf->balancers->nelts == 0 ||
+ conf->proxy_status == status_off)
+ return OK;
+
+ balancer = (proxy_balancer *)conf->balancers->elts;
+ for (i = 0; i < conf->balancers->nelts; i++) {
+ if (!(flags & AP_STATUS_SHORT)) {
+ ap_rputs("<hr />\n<h1>Proxy LoadBalancer Status for ", r);
+ ap_rvputs(r, balancer->s->name, "</h1>\n\n", NULL);
+ ap_rputs("\n\n<table border=\"0\"><tr>"
+ "<th>SSes</th><th>Timeout</th><th>Method</th>"
+ "</tr>\n<tr>", r);
+ if (*balancer->s->sticky) {
+ if (strcmp(balancer->s->sticky, balancer->s->sticky_path)) {
+ ap_rvputs(r, "<td>", balancer->s->sticky, " | ",
+ balancer->s->sticky_path, NULL);
+ }
+ else {
+ ap_rvputs(r, "<td>", balancer->s->sticky, NULL);
+ }
+ }
+ else {
+ ap_rputs("<td> - ", r);
+ }
+ ap_rprintf(r, "</td><td>%" APR_TIME_T_FMT "</td>",
+ apr_time_sec(balancer->s->timeout));
+ ap_rprintf(r, "<td>%s</td>\n",
+ balancer->lbmethod->name);
+ ap_rputs("</table>\n", r);
+ ap_rputs("\n\n<table border=\"0\"><tr>"
+ "<th>Sch</th><th>Host</th><th>Stat</th>"
+ "<th>Route</th><th>Redir</th>"
+ "<th>F</th><th>Set</th><th>Acc</th><th>Busy</th><th>Wr</th><th>Rd</th>"
+ "</tr>\n", r);
+ }
+ else {
+ ap_rprintf(r, "ProxyBalancer[%d]Name: %s\n", i, balancer->s->name);
+ }
+
+ worker = (proxy_worker **)balancer->workers->elts;
+ for (n = 0; n < balancer->workers->nelts; n++) {
+ char fbuf[50];
+ if (!(flags & AP_STATUS_SHORT)) {
+ ap_rvputs(r, "<tr>\n<td>", (*worker)->s->scheme, "</td>", NULL);
+ ap_rvputs(r, "<td>", (*worker)->s->hostname_ex, "</td><td>", NULL);
+ ap_rvputs(r, ap_proxy_parse_wstatus(r->pool, *worker), NULL);
+ ap_rvputs(r, "</td><td>", (*worker)->s->route, NULL);
+ ap_rvputs(r, "</td><td>", (*worker)->s->redirect, NULL);
+ ap_rprintf(r, "</td><td>%.2f</td>", (float)((*worker)->s->lbfactor)/100.0);
+ ap_rprintf(r, "<td>%d</td>", (*worker)->s->lbset);
+ ap_rprintf(r, "<td>%" APR_SIZE_T_FMT "</td>",
+ (*worker)->s->elected);
+ ap_rprintf(r, "<td>%" APR_SIZE_T_FMT "</td><td>",
+ (*worker)->s->busy);
+ ap_rputs(apr_strfsize((*worker)->s->transferred, fbuf), r);
+ ap_rputs("</td><td>", r);
+ ap_rputs(apr_strfsize((*worker)->s->read, fbuf), r);
+ ap_rputs("</td>\n", r);
+
+ /* TODO: Add the rest of dynamic worker data */
+ ap_rputs("</tr>\n", r);
+ }
+ else {
+ ap_rprintf(r, "ProxyBalancer[%d]Worker[%d]Name: %s\n",
+ i, n, (*worker)->s->name_ex);
+ ap_rprintf(r, "ProxyBalancer[%d]Worker[%d]Status: %s\n",
+ i, n, ap_proxy_parse_wstatus(r->pool, *worker));
+ ap_rprintf(r, "ProxyBalancer[%d]Worker[%d]Elected: %"
+ APR_SIZE_T_FMT "\n",
+ i, n, (*worker)->s->elected);
+ ap_rprintf(r, "ProxyBalancer[%d]Worker[%d]Busy: %"
+ APR_SIZE_T_FMT "\n",
+ i, n, (*worker)->s->busy);
+ ap_rprintf(r, "ProxyBalancer[%d]Worker[%d]Sent: %"
+ APR_OFF_T_FMT "K\n",
+ i, n, (*worker)->s->transferred >> 10);
+ ap_rprintf(r, "ProxyBalancer[%d]Worker[%d]Rcvd: %"
+ APR_OFF_T_FMT "K\n",
+ i, n, (*worker)->s->read >> 10);
+
+ /* TODO: Add the rest of dynamic worker data */
+ }
+
+ ++worker;
+ }
+ if (!(flags & AP_STATUS_SHORT)) {
+ ap_rputs("</table>\n", r);
+ }
+ ++balancer;
+ }
+ if (!(flags & AP_STATUS_SHORT)) {
+ ap_rputs("<hr /><table>\n"
+ "<tr><th>SSes</th><td>Sticky session name</td></tr>\n"
+ "<tr><th>Timeout</th><td>Balancer Timeout</td></tr>\n"
+ "<tr><th>Sch</th><td>Connection scheme</td></tr>\n"
+ "<tr><th>Host</th><td>Backend Hostname</td></tr>\n"
+ "<tr><th>Stat</th><td>Worker status</td></tr>\n"
+ "<tr><th>Route</th><td>Session Route</td></tr>\n"
+ "<tr><th>Redir</th><td>Session Route Redirection</td></tr>\n"
+ "<tr><th>F</th><td>Load Balancer Factor</td></tr>\n"
+ "<tr><th>Acc</th><td>Number of uses</td></tr>\n"
+ "<tr><th>Wr</th><td>Number of bytes transferred</td></tr>\n"
+ "<tr><th>Rd</th><td>Number of bytes read</td></tr>\n"
+ "</table>", r);
+ }
+
+ return OK;
+}
+
+static void child_init(apr_pool_t *p, server_rec *s)
+{
+ proxy_worker *reverse = NULL;
+
+ apr_status_t rv = apr_global_mutex_child_init(&proxy_mutex,
+ apr_global_mutex_lockfile(proxy_mutex),
+ p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(02479)
+ "could not init proxy_mutex in child");
+ exit(1); /* Ugly, but what else? */
+ }
+
+ /* TODO */
+ while (s) {
+ void *sconf = s->module_config;
+ proxy_server_conf *conf;
+ proxy_worker *worker;
+ int i;
+
+ conf = (proxy_server_conf *)ap_get_module_config(sconf, &proxy_module);
+ /*
+ * NOTE: non-balancer members don't use shm at all...
+ * after all, why should they?
+ */
+ worker = (proxy_worker *)conf->workers->elts;
+ for (i = 0; i < conf->workers->nelts; i++, worker++) {
+ ap_proxy_initialize_worker(worker, s, p);
+ }
+ /* Create and initialize forward worker if defined */
+ if (conf->req_set && conf->req) {
+ proxy_worker *forward;
+ ap_proxy_define_worker(conf->pool, &forward, NULL, NULL,
+ "http://www.apache.org", 0);
+ conf->forward = forward;
+ PROXY_STRNCPY(conf->forward->s->name, "proxy:forward");
+ PROXY_STRNCPY(conf->forward->s->name_ex, "proxy:forward");
+ PROXY_STRNCPY(conf->forward->s->hostname, "*"); /* for compatibility */
+ PROXY_STRNCPY(conf->forward->s->hostname_ex, "*");
+ PROXY_STRNCPY(conf->forward->s->scheme, "*");
+ conf->forward->hash.def = conf->forward->s->hash.def =
+ ap_proxy_hashfunc(conf->forward->s->name_ex, PROXY_HASHFUNC_DEFAULT);
+ conf->forward->hash.fnv = conf->forward->s->hash.fnv =
+ ap_proxy_hashfunc(conf->forward->s->name_ex, PROXY_HASHFUNC_FNV);
+ /* Do not disable worker in case of errors */
+ conf->forward->s->status |= PROXY_WORKER_IGNORE_ERRORS;
+ /* Mark as the "generic" worker */
+ conf->forward->s->status |= PROXY_WORKER_GENERIC;
+ ap_proxy_initialize_worker(conf->forward, s, p);
+ /* Disable address cache for generic forward worker */
+ conf->forward->s->is_address_reusable = 0;
+ }
+ if (!reverse) {
+ ap_proxy_define_worker(conf->pool, &reverse, NULL, NULL,
+ "http://www.apache.org", 0);
+ PROXY_STRNCPY(reverse->s->name, "proxy:reverse");
+ PROXY_STRNCPY(reverse->s->name_ex, "proxy:reverse");
+ PROXY_STRNCPY(reverse->s->hostname, "*"); /* for compatibility */
+ PROXY_STRNCPY(reverse->s->hostname_ex, "*");
+ PROXY_STRNCPY(reverse->s->scheme, "*");
+ reverse->hash.def = reverse->s->hash.def =
+ ap_proxy_hashfunc(reverse->s->name_ex, PROXY_HASHFUNC_DEFAULT);
+ reverse->hash.fnv = reverse->s->hash.fnv =
+ ap_proxy_hashfunc(reverse->s->name_ex, PROXY_HASHFUNC_FNV);
+ /* Do not disable worker in case of errors */
+ reverse->s->status |= PROXY_WORKER_IGNORE_ERRORS;
+ /* Mark as the "generic" worker */
+ reverse->s->status |= PROXY_WORKER_GENERIC;
+ conf->reverse = reverse;
+ ap_proxy_initialize_worker(conf->reverse, s, p);
+ /* Disable address cache for generic reverse worker */
+ reverse->s->is_address_reusable = 0;
+ }
+ conf->reverse = reverse;
+ s = s->next;
+ }
+}
+
+/*
+ * This routine is called before the server processes the configuration
+ * files.
+ */
+static int proxy_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp)
+{
+ apr_status_t rv = ap_mutex_register(pconf, proxy_id, NULL,
+ APR_LOCK_DEFAULT, 0);
+ if (rv != APR_SUCCESS) {
+ ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog, APLOGNO(02480)
+ "failed to register %s mutex", proxy_id);
+ return 500; /* An HTTP status would be a misnomer! */
+ }
+
+ APR_OPTIONAL_HOOK(ap, status_hook, proxy_status_hook, NULL, NULL,
+ APR_HOOK_MIDDLE);
+ /* Reset workers count on graceful restart */
+ proxy_lb_workers = 0;
+ set_worker_hc_param_f = APR_RETRIEVE_OPTIONAL_FN(set_worker_hc_param);
+ return OK;
+}
+static void register_hooks(apr_pool_t *p)
+{
+ /* fixup before mod_rewrite, so that the proxied url will not
+ * escaped accidentally by our fixup.
+ */
+ static const char * const aszSucc[] = { "mod_rewrite.c", NULL};
+ /* Only the mpm_winnt has child init hook handler.
+ * make sure that we are called after the mpm
+ * initializes.
+ */
+ static const char *const aszPred[] = { "mpm_winnt.c", "mod_proxy_balancer.c",
+ "mod_proxy_hcheck.c", NULL};
+ /* handler */
+ ap_hook_handler(proxy_handler, NULL, NULL, APR_HOOK_FIRST);
+ /* filename-to-URI translation */
+ ap_hook_pre_translate_name(proxy_pre_translate_name, NULL, NULL,
+ APR_HOOK_MIDDLE);
+ ap_hook_translate_name(proxy_translate_name, aszSucc, NULL,
+ APR_HOOK_FIRST);
+ /* walk <Proxy > entries and suppress default TRACE behavior */
+ ap_hook_map_to_storage(proxy_map_location, NULL,NULL, APR_HOOK_FIRST);
+ /* fixups */
+ ap_hook_fixups(proxy_fixup, NULL, aszSucc, APR_HOOK_FIRST);
+ /* post read_request handling */
+ ap_hook_post_read_request(proxy_detect, NULL, NULL, APR_HOOK_FIRST);
+ /* pre config handling */
+ ap_hook_pre_config(proxy_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
+ /* post config handling */
+ ap_hook_post_config(proxy_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+ /* child init handling */
+ ap_hook_child_init(child_init, aszPred, NULL, APR_HOOK_MIDDLE);
+
+ /* register optional functions within proxy_util.c */
+ proxy_util_register_hooks(p);
+}
+
+AP_DECLARE_MODULE(proxy) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_proxy_dir_config, /* create per-directory config structure */
+ merge_proxy_dir_config, /* merge per-directory config structures */
+ create_proxy_config, /* create per-server config structure */
+ merge_proxy_config, /* merge per-server config structures */
+ proxy_cmds, /* command table */
+ register_hooks
+};
+
+APR_HOOK_STRUCT(
+ APR_HOOK_LINK(scheme_handler)
+ APR_HOOK_LINK(canon_handler)
+ APR_HOOK_LINK(pre_request)
+ APR_HOOK_LINK(post_request)
+ APR_HOOK_LINK(request_status)
+ APR_HOOK_LINK(check_trans)
+)
+
+APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(proxy, PROXY, int, scheme_handler,
+ (request_rec *r, proxy_worker *worker,
+ proxy_server_conf *conf,
+ char *url, const char *proxyhost,
+ apr_port_t proxyport),(r,worker,conf,
+ url,proxyhost,proxyport),DECLINED)
+APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(proxy, PROXY, int, check_trans,
+ (request_rec *r, const char *url),
+ (r, url), DECLINED)
+APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(proxy, PROXY, int, canon_handler,
+ (request_rec *r, char *url),(r,
+ url),DECLINED)
+APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(proxy, PROXY, int, pre_request, (
+ proxy_worker **worker,
+ proxy_balancer **balancer,
+ request_rec *r,
+ proxy_server_conf *conf,
+ char **url),(worker,balancer,
+ r,conf,url),DECLINED)
+APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(proxy, PROXY, int, post_request,
+ (proxy_worker *worker,
+ proxy_balancer *balancer,
+ request_rec *r,
+ proxy_server_conf *conf),(worker,
+ balancer,r,conf),DECLINED)
+APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(proxy, PROXY, int, section_post_config,
+ (apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s,
+ ap_conf_vector_t *section_config),
+ (p, ptemp, plog, s, section_config),
+ OK, DECLINED)
+APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(proxy, PROXY, int, fixups,
+ (request_rec *r), (r),
+ OK, DECLINED)
+APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(proxy, PROXY, int, request_status,
+ (int *status, request_rec *r),
+ (status, r),
+ OK, DECLINED)
diff --git a/modules/proxy/mod_proxy.dep b/modules/proxy/mod_proxy.dep
new file mode 100644
index 0000000..43d8070
--- /dev/null
+++ b/modules/proxy/mod_proxy.dep
@@ -0,0 +1,153 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_proxy.mak
+
+.\mod_proxy.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_slotmem.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_core.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_date.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_reslist.h"\
+ "..\..\srclib\apr-util\include\apr_strmatch.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ "..\generators\mod_status.h"\
+ "..\ssl\mod_ssl.h"\
+ ".\mod_proxy.h"\
+ ".\proxy_util.h"\
+
+
+.\proxy_util.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_mpm.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_slotmem.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_date.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_reslist.h"\
+ "..\..\srclib\apr-util\include\apr_strmatch.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_support.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_version.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\ajp.h"\
+ ".\mod_proxy.h"\
+ ".\proxy_util.h"\
+ ".\scgi.h"\
+
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
diff --git a/modules/proxy/mod_proxy.dsp b/modules/proxy/mod_proxy.dsp
new file mode 100644
index 0000000..30e2a85
--- /dev/null
+++ b/modules/proxy/mod_proxy.dsp
@@ -0,0 +1,127 @@
+# Microsoft Developer Studio Project File - Name="mod_proxy" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_proxy - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy.mak" CFG="mod_proxy - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_proxy - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../ssl" /I "../http2" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../generators" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "PROXY_DECLARE_EXPORT" /Fd"Release\mod_proxy_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_proxy.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_proxy.so" /d LONG_NAME="proxy_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:".\Release\mod_proxy.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /debug /out:".\Release\mod_proxy.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_proxy.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_proxy - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../ssl" /I "../http2" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../generators" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "PROXY_DECLARE_EXPORT" /Fd"Debug\mod_proxy_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_proxy.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_proxy.so" /d LONG_NAME="proxy_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_proxy.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_proxy.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_proxy.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_proxy - Win32 Release"
+# Name "mod_proxy - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\mod_proxy.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\proxy_util.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd"
+# Begin Source File
+
+SOURCE=.\mod_proxy.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/proxy/mod_proxy.h b/modules/proxy/mod_proxy.h
new file mode 100644
index 0000000..5c08e99
--- /dev/null
+++ b/modules/proxy/mod_proxy.h
@@ -0,0 +1,1510 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MOD_PROXY_H
+#define MOD_PROXY_H
+
+/**
+ * @file mod_proxy.h
+ * @brief Proxy Extension Module for Apache
+ *
+ * @defgroup MOD_PROXY mod_proxy
+ * @ingroup APACHE_MODS
+ * @{
+ */
+
+#include "apr_hooks.h"
+#include "apr_optional.h"
+#include "apr.h"
+#include "apr_lib.h"
+#include "apr_strings.h"
+#include "apr_buckets.h"
+#include "apr_md5.h"
+#include "apr_network_io.h"
+#include "apr_pools.h"
+#include "apr_strings.h"
+#include "apr_uri.h"
+#include "apr_date.h"
+#include "apr_strmatch.h"
+#include "apr_fnmatch.h"
+#include "apr_reslist.h"
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+#include "apr_uuid.h"
+#include "util_mutex.h"
+#include "apr_global_mutex.h"
+#include "apr_thread_mutex.h"
+
+#include "httpd.h"
+#include "http_config.h"
+#include "ap_config.h"
+#include "http_core.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "http_vhost.h"
+#include "http_main.h"
+#include "http_log.h"
+#include "http_connection.h"
+#include "http_ssl.h"
+#include "util_filter.h"
+#include "util_ebcdic.h"
+#include "ap_provider.h"
+#include "ap_slotmem.h"
+
+#if APR_HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#if APR_HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+
+/* for proxy_canonenc() */
+enum enctype {
+ enc_path, enc_search, enc_user, enc_fpath, enc_parm
+};
+
+typedef enum {
+ NONE, TCP, OPTIONS, HEAD, GET, CPING, PROVIDER, OPTIONS11, HEAD11, GET11, EOT
+} hcmethod_t;
+
+typedef struct {
+ hcmethod_t method;
+ char *name;
+ int implemented;
+} proxy_hcmethods_t;
+
+typedef struct {
+ unsigned int bit;
+ char flag;
+ const char *name;
+} proxy_wstat_t;
+
+#define BALANCER_PREFIX "balancer://"
+
+#if APR_CHARSET_EBCDIC
+#define CRLF "\r\n"
+#else /*APR_CHARSET_EBCDIC*/
+#define CRLF "\015\012"
+#endif /*APR_CHARSET_EBCDIC*/
+
+/* default Max-Forwards header setting */
+/* Set this to -1, which complies with RFC2616 by not setting
+ * max-forwards if the client didn't send it to us.
+ */
+#define DEFAULT_MAX_FORWARDS -1
+
+typedef struct proxy_balancer proxy_balancer;
+typedef struct proxy_worker proxy_worker;
+typedef struct proxy_conn_pool proxy_conn_pool;
+typedef struct proxy_balancer_method proxy_balancer_method;
+
+/* static information about a remote proxy */
+struct proxy_remote {
+ const char *scheme; /* the schemes handled by this proxy, or '*' */
+ const char *protocol; /* the scheme used to talk to this proxy */
+ const char *hostname; /* the hostname of this proxy */
+ ap_regex_t *regexp; /* compiled regex (if any) for the remote */
+ int use_regex; /* simple boolean. True if we have a regex pattern */
+ apr_port_t port; /* the port for this proxy */
+};
+
+#define PROXYPASS_NOCANON 0x01
+#define PROXYPASS_INTERPOLATE 0x02
+#define PROXYPASS_NOQUERY 0x04
+#define PROXYPASS_MAP_ENCODED 0x08
+#define PROXYPASS_MAP_SERVLET 0x18 /* + MAP_ENCODED */
+struct proxy_alias {
+ const char *real;
+ const char *fake;
+ ap_regex_t *regex;
+ unsigned int flags;
+ proxy_balancer *balancer; /* only valid for reverse-proxys */
+};
+
+struct dirconn_entry {
+ char *name;
+ struct in_addr addr, mask;
+ struct apr_sockaddr_t *hostaddr;
+ int (*matcher) (struct dirconn_entry * This, request_rec *r);
+};
+
+struct noproxy_entry {
+ const char *name;
+ struct apr_sockaddr_t *addr;
+};
+
+typedef struct {
+ apr_array_header_t *proxies;
+ apr_array_header_t *sec_proxy;
+ apr_array_header_t *aliases;
+ apr_array_header_t *noproxies;
+ apr_array_header_t *dirconn;
+ apr_array_header_t *workers; /* non-balancer workers, eg ProxyPass http://example.com */
+ apr_array_header_t *balancers; /* list of balancers @ config time */
+ proxy_worker *forward; /* forward proxy worker */
+ proxy_worker *reverse; /* reverse "module-driven" proxy worker */
+ const char *domain; /* domain name to use in absence of a domain name in the request */
+ const char *id;
+ apr_pool_t *pool; /* Pool used for allocating this struct's elements */
+ int req; /* true if proxy requests are enabled */
+ int max_balancers; /* maximum number of allowed balancers */
+ int bgrowth; /* number of post-config balancers can added */
+ enum {
+ via_off,
+ via_on,
+ via_block,
+ via_full
+ } viaopt; /* how to deal with proxy Via: headers */
+ apr_size_t recv_buffer_size;
+ apr_size_t io_buffer_size;
+ long maxfwd;
+ apr_interval_time_t timeout;
+ enum {
+ bad_error,
+ bad_ignore,
+ bad_body
+ } badopt; /* how to deal with bad headers */
+ enum {
+ status_off,
+ status_on,
+ status_full
+ } proxy_status; /* Status display options */
+ apr_sockaddr_t *source_address;
+ apr_global_mutex_t *mutex; /* global lock, for pool, etc */
+ ap_slotmem_instance_t *bslot; /* balancers shm data - runtime */
+ ap_slotmem_provider_t *storage;
+
+ unsigned int req_set:1;
+ unsigned int viaopt_set:1;
+ unsigned int recv_buffer_size_set:1;
+ unsigned int io_buffer_size_set:1;
+ unsigned int maxfwd_set:1;
+ unsigned int timeout_set:1;
+ unsigned int badopt_set:1;
+ unsigned int proxy_status_set:1;
+ unsigned int source_address_set:1;
+ unsigned int bgrowth_set:1;
+ unsigned int bal_persist:1;
+ unsigned int inherit:1;
+ unsigned int inherit_set:1;
+ unsigned int ppinherit:1;
+ unsigned int ppinherit_set:1;
+ unsigned int map_encoded_one:1;
+ unsigned int map_encoded_all:1;
+} proxy_server_conf;
+
+typedef struct {
+ const char *p; /* The path */
+ ap_regex_t *r; /* Is this a regex? */
+
+/* FIXME
+ * ProxyPassReverse and friends are documented as working inside
+ * <Location>. But in fact they never have done in the case of
+ * more than one <Location>, because the server_conf can't see it.
+ * We need to move them to the per-dir config.
+ * Discussed in February 2005:
+ * http://marc.theaimsgroup.com/?l=apache-httpd-dev&m=110726027118798&w=2
+ */
+ apr_array_header_t *raliases;
+ apr_array_header_t* cookie_paths;
+ apr_array_header_t* cookie_domains;
+ signed char p_is_fnmatch; /* Is the path an fnmatch candidate? */
+ signed char interpolate_env;
+ struct proxy_alias *alias;
+
+ /**
+ * the following setting masks the error page
+ * returned from the 'proxied server' and just
+ * forwards the status code upwards.
+ * This allows the main server (us) to generate
+ * the error page, (so it will look like a error
+ * returned from the rest of the system
+ */
+ unsigned int error_override:1;
+ unsigned int preserve_host:1;
+ unsigned int preserve_host_set:1;
+ unsigned int error_override_set:1;
+ unsigned int alias_set:1;
+ unsigned int add_forwarded_headers:1;
+ unsigned int add_forwarded_headers_set:1;
+
+ /** Named back references */
+ apr_array_header_t *refs;
+
+ unsigned int forward_100_continue:1;
+ unsigned int forward_100_continue_set:1;
+
+ apr_array_header_t *error_override_codes;
+} proxy_dir_conf;
+
+/* if we interpolate env vars per-request, we'll need a per-request
+ * copy of the reverse proxy config
+ */
+typedef struct {
+ apr_array_header_t *raliases;
+ apr_array_header_t* cookie_paths;
+ apr_array_header_t* cookie_domains;
+} proxy_req_conf;
+
+typedef struct {
+ conn_rec *connection;
+ request_rec *r; /* Request record of the backend request
+ * that is used over the backend connection. */
+ proxy_worker *worker; /* Connection pool this connection belongs to */
+ apr_pool_t *pool; /* Subpool for hostname and addr data */
+ const char *hostname;
+ apr_sockaddr_t *addr; /* Preparsed remote address info */
+ apr_pool_t *scpool; /* Subpool used for socket and connection data */
+ apr_socket_t *sock; /* Connection socket */
+ void *data; /* per scheme connection data */
+ void *forward; /* opaque forward proxy data */
+ apr_uint32_t flags; /* Connection flags */
+ apr_port_t port;
+ unsigned int is_ssl:1;
+ unsigned int close:1; /* Close 'this' connection */
+ unsigned int need_flush:1; /* Flag to decide whether we need to flush the
+ * filter chain or not */
+ unsigned int inreslist:1; /* connection in apr_reslist? */
+ const char *uds_path; /* Unix domain socket path */
+ const char *ssl_hostname;/* Hostname (SNI) in use by SSL connection */
+ apr_bucket_brigade *tmp_bb;/* Temporary brigade created with the connection
+ * and its scpool/bucket_alloc (NULL before),
+ * must be left cleaned when used (locally).
+ */
+} proxy_conn_rec;
+
+typedef struct {
+ float cache_completion; /* completion percentage */
+ int content_length; /* length of the content */
+} proxy_completion;
+
+/* Connection pool */
+struct proxy_conn_pool {
+ apr_pool_t *pool; /* The pool used in constructor and destructor calls */
+ apr_sockaddr_t *addr; /* Preparsed remote address info */
+ apr_reslist_t *res; /* Connection resource list */
+ proxy_conn_rec *conn; /* Single connection for prefork mpm */
+ apr_pool_t *dns_pool; /* The pool used for worker scoped DNS resolutions */
+};
+
+#define AP_VOLATILIZE_T(T, x) (*(T volatile *)&(x))
+
+/* worker status bits */
+/*
+ * NOTE: Keep up-to-date w/ proxy_wstat_tbl[]
+ * in mod_proxy.c !
+ */
+#define PROXY_WORKER_INITIALIZED 0x0001
+#define PROXY_WORKER_IGNORE_ERRORS 0x0002
+#define PROXY_WORKER_DRAIN 0x0004
+#define PROXY_WORKER_GENERIC 0x0008
+#define PROXY_WORKER_IN_SHUTDOWN 0x0010
+#define PROXY_WORKER_DISABLED 0x0020
+#define PROXY_WORKER_STOPPED 0x0040
+#define PROXY_WORKER_IN_ERROR 0x0080
+#define PROXY_WORKER_HOT_STANDBY 0x0100
+#define PROXY_WORKER_FREE 0x0200
+#define PROXY_WORKER_HC_FAIL 0x0400
+#define PROXY_WORKER_HOT_SPARE 0x0800
+
+/* worker status flags */
+#define PROXY_WORKER_INITIALIZED_FLAG 'O'
+#define PROXY_WORKER_IGNORE_ERRORS_FLAG 'I'
+#define PROXY_WORKER_DRAIN_FLAG 'N'
+#define PROXY_WORKER_GENERIC_FLAG 'G'
+#define PROXY_WORKER_IN_SHUTDOWN_FLAG 'U'
+#define PROXY_WORKER_DISABLED_FLAG 'D'
+#define PROXY_WORKER_STOPPED_FLAG 'S'
+#define PROXY_WORKER_IN_ERROR_FLAG 'E'
+#define PROXY_WORKER_HOT_STANDBY_FLAG 'H'
+#define PROXY_WORKER_FREE_FLAG 'F'
+#define PROXY_WORKER_HC_FAIL_FLAG 'C'
+#define PROXY_WORKER_HOT_SPARE_FLAG 'R'
+
+#define PROXY_WORKER_NOT_USABLE_BITMAP ( PROXY_WORKER_IN_SHUTDOWN | \
+PROXY_WORKER_DISABLED | PROXY_WORKER_STOPPED | PROXY_WORKER_IN_ERROR | \
+PROXY_WORKER_HC_FAIL )
+
+/* NOTE: these check the shared status */
+#define PROXY_WORKER_IS_INITIALIZED(f) ( (f)->s->status & PROXY_WORKER_INITIALIZED )
+
+#define PROXY_WORKER_IS_STANDBY(f) ( (f)->s->status & PROXY_WORKER_HOT_STANDBY )
+
+#define PROXY_WORKER_IS_SPARE(f) ( (f)->s->status & PROXY_WORKER_HOT_SPARE )
+
+#define PROXY_WORKER_IS_USABLE(f) ( ( !( (f)->s->status & PROXY_WORKER_NOT_USABLE_BITMAP) ) && \
+ PROXY_WORKER_IS_INITIALIZED(f) )
+
+#define PROXY_WORKER_IS_DRAINING(f) ( (f)->s->status & PROXY_WORKER_DRAIN )
+
+#define PROXY_WORKER_IS_GENERIC(f) ( (f)->s->status & PROXY_WORKER_GENERIC )
+
+#define PROXY_WORKER_IS_HCFAILED(f) ( (f)->s->status & PROXY_WORKER_HC_FAIL )
+
+#define PROXY_WORKER_IS_ERROR(f) ( (f)->s->status & PROXY_WORKER_IN_ERROR )
+
+#define PROXY_WORKER_IS(f, b) ( (f)->s->status & (b) )
+
+/* default worker retry timeout in seconds */
+#define PROXY_WORKER_DEFAULT_RETRY 60
+
+/* Some max char string sizes, for shm fields */
+#define PROXY_WORKER_MAX_SCHEME_SIZE 16
+#define PROXY_WORKER_MAX_ROUTE_SIZE 64
+#define PROXY_BALANCER_MAX_ROUTE_SIZE PROXY_WORKER_MAX_ROUTE_SIZE
+#define PROXY_WORKER_MAX_NAME_SIZE 96
+#define PROXY_BALANCER_MAX_NAME_SIZE PROXY_WORKER_MAX_NAME_SIZE
+#define PROXY_WORKER_MAX_HOSTNAME_SIZE 64
+#define PROXY_BALANCER_MAX_HOSTNAME_SIZE PROXY_WORKER_MAX_HOSTNAME_SIZE
+#define PROXY_BALANCER_MAX_STICKY_SIZE 64
+#define PROXY_WORKER_MAX_SECRET_SIZE 64
+
+#define PROXY_RFC1035_HOSTNAME_SIZE 256
+#define PROXY_WORKER_EXT_NAME_SIZE 384
+
+/* RFC-1035 mentions limits of 255 for host-names and 253 for domain-names,
+ * dotted together(?) this would fit the below size (+ trailing NUL).
+ */
+#define PROXY_WORKER_RFC1035_NAME_SIZE 512
+
+#define PROXY_MAX_PROVIDER_NAME_SIZE 16
+
+#define PROXY_STRNCPY(dst, src) ap_proxy_strncpy((dst), (src), (sizeof(dst)))
+
+#define PROXY_COPY_CONF_PARAMS(w, c) \
+do { \
+(w)->s->timeout = (c)->timeout; \
+(w)->s->timeout_set = (c)->timeout_set; \
+(w)->s->recv_buffer_size = (c)->recv_buffer_size; \
+(w)->s->recv_buffer_size_set = (c)->recv_buffer_size_set; \
+(w)->s->io_buffer_size = (c)->io_buffer_size; \
+(w)->s->io_buffer_size_set = (c)->io_buffer_size_set; \
+} while (0)
+
+#define PROXY_SHOULD_PING_100_CONTINUE(w, r) \
+ ((w)->s->ping_timeout_set \
+ && (PROXYREQ_REVERSE == (r)->proxyreq) \
+ && ap_request_has_body((r)))
+
+#define PROXY_DO_100_CONTINUE(w, r) \
+ (PROXY_SHOULD_PING_100_CONTINUE(w, r) \
+ && !apr_table_get((r)->subprocess_env, "force-proxy-request-1.0"))
+
+/* use 2 hashes */
+typedef struct {
+ unsigned int def;
+ unsigned int fnv;
+} proxy_hashes ;
+
+/* Runtime worker status information. Shared in scoreboard */
+/* The addition of member uds_path in 2.4.7 was an incompatible API change. */
+typedef struct {
+ char name[PROXY_WORKER_MAX_NAME_SIZE];
+ char scheme[PROXY_WORKER_MAX_SCHEME_SIZE]; /* scheme to use ajp|http|https */
+ char hostname[PROXY_WORKER_MAX_HOSTNAME_SIZE]; /* remote backend address (deprecated, use hostname_ex below) */
+ char route[PROXY_WORKER_MAX_ROUTE_SIZE]; /* balancing route */
+ char redirect[PROXY_WORKER_MAX_ROUTE_SIZE]; /* temporary balancing redirection route */
+ char flusher[PROXY_WORKER_MAX_SCHEME_SIZE]; /* flush provider used by mod_proxy_fdpass */
+ char uds_path[PROXY_WORKER_MAX_NAME_SIZE]; /* path to worker's unix domain socket if applicable */
+ int lbset; /* load balancer cluster set */
+ int retries; /* number of retries on this worker */
+ int lbstatus; /* Current lbstatus */
+ int lbfactor; /* dynamic lbfactor */
+ int min; /* Desired minimum number of available connections */
+ int smax; /* Soft maximum on the total number of connections */
+ int hmax; /* Hard maximum on the total number of connections */
+ int flush_wait; /* poll wait time in microseconds if flush_auto */
+ int index; /* shm array index */
+ proxy_hashes hash; /* hash of worker name */
+ unsigned int status; /* worker status bitfield */
+ enum {
+ flush_off,
+ flush_on,
+ flush_auto
+ } flush_packets; /* control AJP flushing */
+ apr_time_t updated; /* timestamp of last update for dynamic workers, or queue-time of HC workers */
+ apr_time_t error_time; /* time of the last error */
+ apr_interval_time_t ttl; /* maximum amount of time in seconds a connection
+ * may be available while exceeding the soft limit */
+ apr_interval_time_t retry; /* retry interval */
+ apr_interval_time_t timeout; /* connection timeout */
+ apr_interval_time_t acquire; /* acquire timeout when the maximum number of connections is exceeded */
+ apr_interval_time_t ping_timeout;
+ apr_interval_time_t conn_timeout;
+ apr_size_t recv_buffer_size;
+ apr_size_t io_buffer_size;
+ apr_size_t elected; /* Number of times the worker was elected */
+ apr_size_t busy; /* busyness factor */
+ apr_port_t port;
+ apr_off_t transferred;/* Number of bytes transferred to remote */
+ apr_off_t read; /* Number of bytes read from remote */
+ void *context; /* general purpose storage */
+ unsigned int keepalive:1;
+ unsigned int disablereuse:1;
+ unsigned int is_address_reusable:1;
+ unsigned int retry_set:1;
+ unsigned int timeout_set:1;
+ unsigned int acquire_set:1;
+ unsigned int ping_timeout_set:1;
+ unsigned int conn_timeout_set:1;
+ unsigned int recv_buffer_size_set:1;
+ unsigned int io_buffer_size_set:1;
+ unsigned int keepalive_set:1;
+ unsigned int disablereuse_set:1;
+ unsigned int was_malloced:1;
+ unsigned int is_name_matchable:1;
+ char hcuri[PROXY_WORKER_MAX_ROUTE_SIZE]; /* health check uri */
+ char hcexpr[PROXY_WORKER_MAX_SCHEME_SIZE]; /* name of condition expr for health check */
+ int passes; /* number of successes for check to pass */
+ int pcount; /* current count of passes */
+ int fails; /* number of failures for check to fail */
+ int fcount; /* current count of failures */
+ hcmethod_t method; /* method to use for health check */
+ apr_interval_time_t interval;
+ char upgrade[PROXY_WORKER_MAX_SCHEME_SIZE];/* upgrade protocol used by mod_proxy_wstunnel */
+ char hostname_ex[PROXY_RFC1035_HOSTNAME_SIZE]; /* RFC1035 compliant version of the remote backend address */
+ apr_size_t response_field_size; /* Size of proxy response buffer in bytes. */
+ unsigned int response_field_size_set:1;
+ char secret[PROXY_WORKER_MAX_SECRET_SIZE]; /* authentication secret (e.g. AJP13) */
+ char name_ex[PROXY_WORKER_EXT_NAME_SIZE]; /* Extended name (>96 chars for 2.4.x) */
+} proxy_worker_shared;
+
+#define ALIGNED_PROXY_WORKER_SHARED_SIZE (APR_ALIGN_DEFAULT(sizeof(proxy_worker_shared)))
+
+/* Worker configuration */
+struct proxy_worker {
+ proxy_hashes hash; /* hash of worker name */
+ unsigned int local_status; /* status of per-process worker */
+ proxy_conn_pool *cp; /* Connection pool to use */
+ proxy_worker_shared *s; /* Shared data */
+ proxy_balancer *balancer; /* which balancer am I in? */
+#if APR_HAS_THREADS
+ apr_thread_mutex_t *tmutex; /* Thread lock for updating address cache */
+#endif
+ void *context; /* general purpose storage */
+ ap_conf_vector_t *section_config; /* <Proxy>-section wherein defined */
+};
+
+/* default to health check every 30 seconds */
+#define HCHECK_WATHCHDOG_DEFAULT_INTERVAL (30)
+/* The watchdog runs every 2 seconds, which is also the minimal check */
+#define HCHECK_WATHCHDOG_INTERVAL (2)
+
+/*
+ * Time to wait (in microseconds) to find out if more data is currently
+ * available at the backend.
+ */
+#define PROXY_FLUSH_WAIT 10000
+
+typedef struct {
+ char sticky_path[PROXY_BALANCER_MAX_STICKY_SIZE]; /* URL sticky session identifier */
+ char sticky[PROXY_BALANCER_MAX_STICKY_SIZE]; /* sticky session identifier */
+ char lbpname[PROXY_MAX_PROVIDER_NAME_SIZE]; /* lbmethod provider name */
+ char nonce[APR_UUID_FORMATTED_LENGTH + 1];
+ char name[PROXY_BALANCER_MAX_NAME_SIZE];
+ char sname[PROXY_BALANCER_MAX_NAME_SIZE];
+ char vpath[PROXY_BALANCER_MAX_ROUTE_SIZE];
+ char vhost[PROXY_BALANCER_MAX_HOSTNAME_SIZE];
+ apr_interval_time_t timeout; /* Timeout for waiting on free connection */
+ apr_time_t wupdated; /* timestamp of last change to workers list */
+ int max_attempts; /* Number of attempts before failing */
+ int index; /* shm array index */
+ proxy_hashes hash;
+ unsigned int sticky_force:1; /* Disable failover for sticky sessions */
+ unsigned int scolonsep:1; /* true if ';' seps sticky session paths */
+ unsigned int max_attempts_set:1;
+ unsigned int was_malloced:1;
+ unsigned int need_reset:1;
+ unsigned int vhosted:1;
+ unsigned int inactive:1;
+ unsigned int forcerecovery:1;
+ char sticky_separator; /* separator for sessionid/route */
+ unsigned int forcerecovery_set:1;
+ unsigned int scolonsep_set:1;
+ unsigned int sticky_force_set:1;
+ unsigned int nonce_set:1;
+ unsigned int sticky_separator_set:1;
+} proxy_balancer_shared;
+
+#define ALIGNED_PROXY_BALANCER_SHARED_SIZE (APR_ALIGN_DEFAULT(sizeof(proxy_balancer_shared)))
+
+struct proxy_balancer {
+ apr_array_header_t *workers; /* initially configured workers */
+ apr_array_header_t *errstatuses; /* statuses to force members into error */
+ ap_slotmem_instance_t *wslot; /* worker shm data - runtime */
+ ap_slotmem_provider_t *storage;
+ int growth; /* number of post-config workers can added */
+ int max_workers; /* maximum number of allowed workers */
+ proxy_hashes hash;
+ apr_time_t wupdated; /* timestamp of last change to workers list */
+ proxy_balancer_method *lbmethod;
+ apr_global_mutex_t *gmutex; /* global lock for updating list of workers */
+#if APR_HAS_THREADS
+ apr_thread_mutex_t *tmutex; /* Thread lock for updating shm */
+#endif
+ proxy_server_conf *sconf;
+ void *context; /* general purpose storage */
+ proxy_balancer_shared *s; /* Shared data */
+ int failontimeout; /* Whether to mark a member in Err if IO timeout occurs */
+ unsigned int failontimeout_set:1;
+ unsigned int growth_set:1;
+ unsigned int lbmethod_set:1;
+ ap_conf_vector_t *section_config; /* <Proxy>-section wherein defined */
+};
+
+struct proxy_balancer_method {
+ const char *name; /* name of the load balancer method*/
+ proxy_worker *(*finder)(proxy_balancer *balancer,
+ request_rec *r);
+ void *context; /* general purpose storage */
+ apr_status_t (*reset)(proxy_balancer *balancer, server_rec *s);
+ apr_status_t (*age)(proxy_balancer *balancer, server_rec *s);
+ apr_status_t (*updatelbstatus)(proxy_balancer *balancer, proxy_worker *elected, server_rec *s);
+};
+
+#define PROXY_THREAD_LOCK(x) ( (x) && (x)->tmutex ? apr_thread_mutex_lock((x)->tmutex) : APR_SUCCESS)
+#define PROXY_THREAD_UNLOCK(x) ( (x) && (x)->tmutex ? apr_thread_mutex_unlock((x)->tmutex) : APR_SUCCESS)
+
+#define PROXY_GLOBAL_LOCK(x) ( (x) && (x)->gmutex ? apr_global_mutex_lock((x)->gmutex) : APR_SUCCESS)
+#define PROXY_GLOBAL_UNLOCK(x) ( (x) && (x)->gmutex ? apr_global_mutex_unlock((x)->gmutex) : APR_SUCCESS)
+
+/* hooks */
+
+/* Create a set of PROXY_DECLARE(type), PROXY_DECLARE_NONSTD(type) and
+ * PROXY_DECLARE_DATA with appropriate export and import tags for the platform
+ */
+#if !defined(WIN32)
+#define PROXY_DECLARE(type) type
+#define PROXY_DECLARE_NONSTD(type) type
+#define PROXY_DECLARE_DATA
+#elif defined(PROXY_DECLARE_STATIC)
+#define PROXY_DECLARE(type) type __stdcall
+#define PROXY_DECLARE_NONSTD(type) type
+#define PROXY_DECLARE_DATA
+#elif defined(PROXY_DECLARE_EXPORT)
+#define PROXY_DECLARE(type) __declspec(dllexport) type __stdcall
+#define PROXY_DECLARE_NONSTD(type) __declspec(dllexport) type
+#define PROXY_DECLARE_DATA __declspec(dllexport)
+#else
+#define PROXY_DECLARE(type) __declspec(dllimport) type __stdcall
+#define PROXY_DECLARE_NONSTD(type) __declspec(dllimport) type
+#define PROXY_DECLARE_DATA __declspec(dllimport)
+#endif
+
+/* Using PROXY_DECLARE_OPTIONAL_HOOK instead of
+ * APR_DECLARE_EXTERNAL_HOOK allows build/make_nw_export.awk
+ * to distinguish between hooks that implement
+ * proxy_hook_xx and proxy_hook_get_xx in mod_proxy.c and
+ * those which don't.
+ */
+#define PROXY_DECLARE_OPTIONAL_HOOK APR_DECLARE_EXTERNAL_HOOK
+
+/* These 2 are in mod_proxy.c */
+extern PROXY_DECLARE_DATA proxy_hcmethods_t proxy_hcmethods[];
+extern PROXY_DECLARE_DATA proxy_wstat_t proxy_wstat_tbl[];
+
+/* Following 4 from health check */
+APR_DECLARE_OPTIONAL_FN(void, hc_show_exprs, (request_rec *));
+APR_DECLARE_OPTIONAL_FN(void, hc_select_exprs, (request_rec *, const char *));
+APR_DECLARE_OPTIONAL_FN(int, hc_valid_expr, (request_rec *, const char *));
+APR_DECLARE_OPTIONAL_FN(const char *, set_worker_hc_param,
+ (apr_pool_t *, server_rec *, proxy_worker *,
+ const char *, const char *, void *));
+
+APR_DECLARE_EXTERNAL_HOOK(proxy, PROXY, int, section_post_config,
+ (apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s,
+ ap_conf_vector_t *section_config))
+
+APR_DECLARE_EXTERNAL_HOOK(proxy, PROXY, int, scheme_handler,
+ (request_rec *r, proxy_worker *worker,
+ proxy_server_conf *conf, char *url,
+ const char *proxyhost, apr_port_t proxyport))
+APR_DECLARE_EXTERNAL_HOOK(proxy, PROXY, int, check_trans,
+ (request_rec *r, const char *url))
+APR_DECLARE_EXTERNAL_HOOK(proxy, PROXY, int, canon_handler,
+ (request_rec *r, char *url))
+
+APR_DECLARE_EXTERNAL_HOOK(proxy, PROXY, int, create_req, (request_rec *r, request_rec *pr))
+APR_DECLARE_EXTERNAL_HOOK(proxy, PROXY, int, fixups, (request_rec *r))
+
+
+/**
+ * pre request hook.
+ * It will return the most suitable worker at the moment
+ * and corresponding balancer.
+ * The url is rewritten from balancer://cluster/uri to scheme://host:port/uri
+ * and then the scheme_handler is called.
+ *
+ */
+APR_DECLARE_EXTERNAL_HOOK(proxy, PROXY, int, pre_request, (proxy_worker **worker,
+ proxy_balancer **balancer,
+ request_rec *r,
+ proxy_server_conf *conf, char **url))
+/**
+ * post request hook.
+ * It is called after request for updating runtime balancer status.
+ */
+APR_DECLARE_EXTERNAL_HOOK(proxy, PROXY, int, post_request, (proxy_worker *worker,
+ proxy_balancer *balancer, request_rec *r,
+ proxy_server_conf *conf))
+
+/**
+ * request status hook
+ * It is called after all proxy processing has been done. This gives other
+ * modules a chance to create default content on failure, for example
+ */
+APR_DECLARE_EXTERNAL_HOOK(proxy, PROXY, int, request_status,
+ (int *status, request_rec *r))
+
+/* proxy_util.c */
+
+PROXY_DECLARE(apr_status_t) ap_proxy_strncpy(char *dst, const char *src,
+ apr_size_t dlen);
+PROXY_DECLARE(int) ap_proxy_hex2c(const char *x);
+PROXY_DECLARE(void) ap_proxy_c2hex(int ch, char *x);
+PROXY_DECLARE(char *)ap_proxy_canonenc(apr_pool_t *p, const char *x, int len, enum enctype t,
+ int forcedec, int proxyreq);
+PROXY_DECLARE(char *)ap_proxy_canon_netloc(apr_pool_t *p, char **const urlp, char **userp,
+ char **passwordp, char **hostp, apr_port_t *port);
+PROXY_DECLARE(int) ap_proxyerror(request_rec *r, int statuscode, const char *message);
+PROXY_DECLARE(int) ap_proxy_checkproxyblock(request_rec *r, proxy_server_conf *conf, apr_sockaddr_t *uri_addr);
+
+/** Test whether the hostname/address of the request are blocked by the ProxyBlock
+ * configuration.
+ * @param r request
+ * @param conf server configuration
+ * @param hostname hostname from request URI
+ * @param addr resolved address of hostname, or NULL if not known
+ * @return OK on success, or else an error
+ */
+PROXY_DECLARE(int) ap_proxy_checkproxyblock2(request_rec *r, proxy_server_conf *conf,
+ const char *hostname, apr_sockaddr_t *addr);
+
+PROXY_DECLARE(int) ap_proxy_pre_http_request(conn_rec *c, request_rec *r);
+/* DEPRECATED (will be replaced with ap_proxy_connect_backend */
+PROXY_DECLARE(int) ap_proxy_connect_to_backend(apr_socket_t **, const char *, apr_sockaddr_t *, const char *, proxy_server_conf *, request_rec *);
+/* DEPRECATED (will be replaced with ap_proxy_check_connection */
+PROXY_DECLARE(apr_status_t) ap_proxy_ssl_connection_cleanup(proxy_conn_rec *conn,
+ request_rec *r);
+PROXY_DECLARE(int) ap_proxy_ssl_enable(conn_rec *c);
+PROXY_DECLARE(int) ap_proxy_ssl_disable(conn_rec *c);
+PROXY_DECLARE(int) ap_proxy_ssl_engine(conn_rec *c,
+ ap_conf_vector_t *per_dir_config,
+ int enable);
+PROXY_DECLARE(int) ap_proxy_conn_is_https(conn_rec *c);
+PROXY_DECLARE(const char *) ap_proxy_ssl_val(apr_pool_t *p, server_rec *s, conn_rec *c, request_rec *r, const char *var);
+
+/* Header mapping functions, and a typedef of their signature */
+PROXY_DECLARE(const char *) ap_proxy_location_reverse_map(request_rec *r, proxy_dir_conf *conf, const char *url);
+PROXY_DECLARE(const char *) ap_proxy_cookie_reverse_map(request_rec *r, proxy_dir_conf *conf, const char *str);
+
+#if !defined(WIN32)
+typedef const char *(*ap_proxy_header_reverse_map_fn)(request_rec *,
+ proxy_dir_conf *, const char *);
+#elif defined(PROXY_DECLARE_STATIC)
+typedef const char *(__stdcall *ap_proxy_header_reverse_map_fn)(request_rec *,
+ proxy_dir_conf *, const char *);
+#elif defined(PROXY_DECLARE_EXPORT)
+typedef __declspec(dllexport) const char *
+ (__stdcall *ap_proxy_header_reverse_map_fn)(request_rec *,
+ proxy_dir_conf *, const char *);
+#else
+typedef __declspec(dllimport) const char *
+ (__stdcall *ap_proxy_header_reverse_map_fn)(request_rec *,
+ proxy_dir_conf *, const char *);
+#endif
+
+
+/* Connection pool API */
+/**
+ * Return the user-land, UDS aware worker name
+ * @param p memory pool used for displaying worker name
+ * @param worker the worker
+ * @return name
+ */
+
+PROXY_DECLARE(char *) ap_proxy_worker_name(apr_pool_t *p,
+ proxy_worker *worker);
+
+/**
+ * Return whether a worker upgrade configuration matches Upgrade header
+ * @param p memory pool used for displaying worker name
+ * @param worker the worker
+ * @param upgrade the Upgrade header to match
+ * @param dflt default protocol (NULL for none)
+ * @return 1 (true) or 0 (false)
+ */
+PROXY_DECLARE(int) ap_proxy_worker_can_upgrade(apr_pool_t *p,
+ const proxy_worker *worker,
+ const char *upgrade,
+ const char *dflt);
+
+/* Bitmask for ap_proxy_{define,get}_worker_ex(). */
+#define AP_PROXY_WORKER_IS_PREFIX (1u << 0)
+#define AP_PROXY_WORKER_IS_MATCH (1u << 1)
+#define AP_PROXY_WORKER_IS_MALLOCED (1u << 2)
+#define AP_PROXY_WORKER_NO_UDS (1u << 3)
+
+/**
+ * Get the worker from proxy configuration, looking for either PREFIXED or
+ * MATCHED or both types of workers according to given mask
+ * @param p memory pool used for finding worker
+ * @param balancer the balancer that the worker belongs to
+ * @param conf current proxy server configuration
+ * @param url url to find the worker from
+ * @param mask bitmask of AP_PROXY_WORKER_IS_*
+ * @return proxy_worker or NULL if not found
+ */
+PROXY_DECLARE(proxy_worker *) ap_proxy_get_worker_ex(apr_pool_t *p,
+ proxy_balancer *balancer,
+ proxy_server_conf *conf,
+ const char *url,
+ unsigned int mask);
+
+/**
+ * Get the worker from proxy configuration, both types
+ * @param p memory pool used for finding worker
+ * @param balancer the balancer that the worker belongs to
+ * @param conf current proxy server configuration
+ * @param url url to find the worker from
+ * @return proxy_worker or NULL if not found
+ */
+PROXY_DECLARE(proxy_worker *) ap_proxy_get_worker(apr_pool_t *p,
+ proxy_balancer *balancer,
+ proxy_server_conf *conf,
+ const char *url);
+
+/**
+ * Define and Allocate space for the worker to proxy configuration, of either
+ * PREFIXED or MATCHED type according to given mask
+ * @param p memory pool to allocate worker from
+ * @param worker the new worker
+ * @param balancer the balancer that the worker belongs to
+ * @param conf current proxy server configuration
+ * @param url url containing worker name
+ * @param mask bitmask of AP_PROXY_WORKER_IS_*
+ * @return error message or NULL if successful (*worker is new worker)
+ */
+PROXY_DECLARE(char *) ap_proxy_define_worker_ex(apr_pool_t *p,
+ proxy_worker **worker,
+ proxy_balancer *balancer,
+ proxy_server_conf *conf,
+ const char *url,
+ unsigned int mask);
+
+ /**
+ * Define and Allocate space for the worker to proxy configuration
+ * @param p memory pool to allocate worker from
+ * @param worker the new worker
+ * @param balancer the balancer that the worker belongs to
+ * @param conf current proxy server configuration
+ * @param url url containing worker name
+ * @param do_malloc true if shared struct should be malloced
+ * @return error message or NULL if successful (*worker is new worker)
+ */
+PROXY_DECLARE(char *) ap_proxy_define_worker(apr_pool_t *p,
+ proxy_worker **worker,
+ proxy_balancer *balancer,
+ proxy_server_conf *conf,
+ const char *url,
+ int do_malloc);
+
+/**
+ * Define and Allocate space for the ap_strcmp_match()able worker to proxy
+ * configuration.
+ * @param p memory pool to allocate worker from
+ * @param worker the new worker
+ * @param balancer the balancer that the worker belongs to
+ * @param conf current proxy server configuration
+ * @param url url containing worker name (produces match pattern)
+ * @param do_malloc true if shared struct should be malloced
+ * @return error message or NULL if successful (*worker is new worker)
+ * @deprecated Replaced by ap_proxy_define_worker_ex()
+ */
+PROXY_DECLARE(char *) ap_proxy_define_match_worker(apr_pool_t *p,
+ proxy_worker **worker,
+ proxy_balancer *balancer,
+ proxy_server_conf *conf,
+ const char *url,
+ int do_malloc);
+
+/**
+ * Share a defined proxy worker via shm
+ * @param worker worker to be shared
+ * @param shm location of shared info
+ * @param i index into shm
+ * @return APR_SUCCESS or error code
+ */
+PROXY_DECLARE(apr_status_t) ap_proxy_share_worker(proxy_worker *worker,
+ proxy_worker_shared *shm,
+ int i);
+
+/**
+ * Initialize the worker by setting up worker connection pool and mutex
+ * @param worker worker to initialize
+ * @param s current server record
+ * @param p memory pool used for mutex and connection pool
+ * @return APR_SUCCESS or error code
+ */
+PROXY_DECLARE(apr_status_t) ap_proxy_initialize_worker(proxy_worker *worker,
+ server_rec *s,
+ apr_pool_t *p);
+
+/**
+ * Verifies valid balancer name (eg: balancer://foo)
+ * @param name name to test
+ * @param i number of chars to test; 0 for all.
+ * @return true/false
+ */
+PROXY_DECLARE(int) ap_proxy_valid_balancer_name(char *name, int i);
+
+
+/**
+ * Get the balancer from proxy configuration
+ * @param p memory pool used for temporary storage while finding balancer
+ * @param conf current proxy server configuration
+ * @param url url to find the worker from; must have balancer:// prefix
+ * @param careactive true if we care if the balancer is active or not
+ * @return proxy_balancer or NULL if not found
+ */
+PROXY_DECLARE(proxy_balancer *) ap_proxy_get_balancer(apr_pool_t *p,
+ proxy_server_conf *conf,
+ const char *url,
+ int careactive);
+
+/**
+ * Update the balancer's vhost related fields
+ * @param p memory pool used for temporary storage while finding balancer
+ * @param balancer balancer to be updated
+ * @param url url to find vhost info
+ * @return error string or NULL if OK
+ */
+PROXY_DECLARE(char *) ap_proxy_update_balancer(apr_pool_t *p,
+ proxy_balancer *balancer,
+ const char *url);
+
+/**
+ * Define and Allocate space for the balancer to proxy configuration
+ * @param p memory pool to allocate balancer from
+ * @param balancer the new balancer
+ * @param conf current proxy server configuration
+ * @param url url containing balancer name
+ * @param alias alias/fake-path to this balancer
+ * @param do_malloc true if shared struct should be malloced
+ * @return error message or NULL if successful
+ */
+PROXY_DECLARE(char *) ap_proxy_define_balancer(apr_pool_t *p,
+ proxy_balancer **balancer,
+ proxy_server_conf *conf,
+ const char *url,
+ const char *alias,
+ int do_malloc);
+
+/**
+ * Share a defined proxy balancer via shm
+ * @param balancer balancer to be shared
+ * @param shm location of shared info
+ * @param i index into shm
+ * @return APR_SUCCESS or error code
+ */
+PROXY_DECLARE(apr_status_t) ap_proxy_share_balancer(proxy_balancer *balancer,
+ proxy_balancer_shared *shm,
+ int i);
+
+/**
+ * Initialize the balancer as needed
+ * @param balancer balancer to initialize
+ * @param s current server record
+ * @param p memory pool used for mutex and connection pool
+ * @return APR_SUCCESS or error code
+ */
+PROXY_DECLARE(apr_status_t) ap_proxy_initialize_balancer(proxy_balancer *balancer,
+ server_rec *s,
+ apr_pool_t *p);
+
+typedef int (proxy_is_best_callback_fn_t)(proxy_worker *current, proxy_worker *prev_best, void *baton);
+
+/**
+ * Retrieve the best worker in a balancer for the current request
+ * @param balancer balancer for which to find the best worker
+ * @param r current request record
+ * @param is_best a callback function provide by the lbmethod
+ * that determines if the current worker is best
+ * @param baton an lbmethod-specific context pointer (baton)
+ * passed to the is_best callback
+ * @return the best worker to be used for the request
+ */
+PROXY_DECLARE(proxy_worker *) ap_proxy_balancer_get_best_worker(proxy_balancer *balancer,
+ request_rec *r,
+ proxy_is_best_callback_fn_t *is_best,
+ void *baton);
+/*
+ * Needed by the lb modules.
+ */
+APR_DECLARE_OPTIONAL_FN(proxy_worker *, proxy_balancer_get_best_worker,
+ (proxy_balancer *balancer,
+ request_rec *r,
+ proxy_is_best_callback_fn_t *is_best,
+ void *baton));
+
+/**
+ * Find the shm of the worker as needed
+ * @param storage slotmem provider
+ * @param slot slotmem instance
+ * @param worker worker to find
+ * @param index pointer to index within slotmem of worker
+ * @return pointer to shm of worker, or NULL
+ */
+PROXY_DECLARE(proxy_worker_shared *) ap_proxy_find_workershm(ap_slotmem_provider_t *storage,
+ ap_slotmem_instance_t *slot,
+ proxy_worker *worker,
+ unsigned int *index);
+
+/**
+ * Find the shm of the balancer as needed
+ * @param storage slotmem provider
+ * @param slot slotmem instance
+ * @param balancer balancer of shm to find
+ * @param index pointer to index within slotmem of balancer
+ * @return pointer to shm of balancer, or NULL
+ */
+PROXY_DECLARE(proxy_balancer_shared *) ap_proxy_find_balancershm(ap_slotmem_provider_t *storage,
+ ap_slotmem_instance_t *slot,
+ proxy_balancer *balancer,
+ unsigned int *index);
+
+/**
+ * Get the most suitable worker and/or balancer for the request
+ * @param worker worker used for processing request
+ * @param balancer balancer used for processing request
+ * @param r current request
+ * @param conf current proxy server configuration
+ * @param url request url that balancer can rewrite.
+ * @return OK or HTTP_XXX error
+ * @note It calls balancer pre_request hook if the url starts with balancer://
+ * The balancer then rewrites the url to particular worker, like http://host:port
+ */
+PROXY_DECLARE(int) ap_proxy_pre_request(proxy_worker **worker,
+ proxy_balancer **balancer,
+ request_rec *r,
+ proxy_server_conf *conf,
+ char **url);
+/**
+ * Post request worker and balancer cleanup
+ * @param worker worker used for processing request
+ * @param balancer balancer used for processing request
+ * @param r current request
+ * @param conf current proxy server configuration
+ * @return OK or HTTP_XXX error
+ * @note Whenever the pre_request is called, the post_request has to be
+ * called too.
+ */
+PROXY_DECLARE(int) ap_proxy_post_request(proxy_worker *worker,
+ proxy_balancer *balancer,
+ request_rec *r,
+ proxy_server_conf *conf);
+
+/**
+ * Determine backend hostname and port
+ * @param p memory pool used for processing
+ * @param r current request
+ * @param conf current proxy server configuration
+ * @param worker worker used for processing request
+ * @param conn proxy connection struct
+ * @param uri processed uri
+ * @param url request url
+ * @param proxyname are we connecting directly or via a proxy
+ * @param proxyport proxy host port
+ * @param server_portstr Via headers server port, must be non-NULL
+ * @param server_portstr_size size of the server_portstr buffer; must
+ * be at least one, even if the protocol doesn't use this
+ * @return OK or HTTP_XXX error
+ */
+PROXY_DECLARE(int) ap_proxy_determine_connection(apr_pool_t *p, request_rec *r,
+ proxy_server_conf *conf,
+ proxy_worker *worker,
+ proxy_conn_rec *conn,
+ apr_uri_t *uri,
+ char **url,
+ const char *proxyname,
+ apr_port_t proxyport,
+ char *server_portstr,
+ int server_portstr_size);
+
+/**
+ * Mark a worker for retry
+ * @param proxy_function calling proxy scheme (http, ajp, ...)
+ * @param worker worker used for retrying
+ * @param s current server record
+ * @return OK if marked for retry, DECLINED otherwise
+ * @note The error status of the worker will cleared if the retry interval has
+ * elapsed since the last error.
+ */
+APR_DECLARE_OPTIONAL_FN(int, ap_proxy_retry_worker,
+ (const char *proxy_function, proxy_worker *worker, server_rec *s));
+
+/**
+ * Acquire a connection from worker connection pool
+ * @param proxy_function calling proxy scheme (http, ajp, ...)
+ * @param conn acquired connection
+ * @param worker worker used for obtaining connection
+ * @param s current server record
+ * @return OK or HTTP_XXX error
+ * @note If the connection limit has been reached, the function will
+ * block until a connection becomes available or the timeout has
+ * elapsed.
+ */
+PROXY_DECLARE(int) ap_proxy_acquire_connection(const char *proxy_function,
+ proxy_conn_rec **conn,
+ proxy_worker *worker,
+ server_rec *s);
+/**
+ * Release a connection back to worker connection pool
+ * @param proxy_function calling proxy scheme (http, ajp, ...)
+ * @param conn acquired connection
+ * @param s current server record
+ * @return OK or HTTP_XXX error
+ * @note The connection will be closed if conn->close_on_release is set
+ */
+PROXY_DECLARE(int) ap_proxy_release_connection(const char *proxy_function,
+ proxy_conn_rec *conn,
+ server_rec *s);
+
+#define PROXY_CHECK_CONN_EMPTY (1 << 0)
+/**
+ * Check a connection to the backend
+ * @param scheme calling proxy scheme (http, ajp, ...)
+ * @param conn acquired connection
+ * @param server current server record
+ * @param max_blank_lines how many blank lines to consume,
+ * or zero for none (considered data)
+ * @param flags PROXY_CHECK_* bitmask
+ * @return APR_SUCCESS: connection established,
+ * APR_ENOTEMPTY: connection established with data,
+ * APR_ENOSOCKET: not connected,
+ * APR_EINVAL: worker in error state (unusable),
+ * other: connection closed/aborted (remotely)
+ */
+PROXY_DECLARE(apr_status_t) ap_proxy_check_connection(const char *scheme,
+ proxy_conn_rec *conn,
+ server_rec *server,
+ unsigned max_blank_lines,
+ int flags);
+
+/**
+ * Make a connection to the backend
+ * @param proxy_function calling proxy scheme (http, ajp, ...)
+ * @param conn acquired connection
+ * @param worker connection worker
+ * @param s current server record
+ * @return OK or HTTP_XXX error
+ * @note In case the socket already exists for conn, just check the link
+ * status.
+ */
+PROXY_DECLARE(int) ap_proxy_connect_backend(const char *proxy_function,
+ proxy_conn_rec *conn,
+ proxy_worker *worker,
+ server_rec *s);
+
+/**
+ * Make a connection to a Unix Domain Socket (UDS) path
+ * @param sock UDS to connect
+ * @param uds_path UDS path to connect to
+ * @param p pool to make the sock addr
+ * @return APR_SUCCESS or error status
+ */
+PROXY_DECLARE(apr_status_t) ap_proxy_connect_uds(apr_socket_t *sock,
+ const char *uds_path,
+ apr_pool_t *p);
+/**
+ * Make a connection record for backend connection
+ * @param proxy_function calling proxy scheme (http, ajp, ...)
+ * @param conn acquired connection
+ * @param c client connection record (unused, deprecated)
+ * @param s current server record
+ * @return OK or HTTP_XXX error
+ * @note The function will return immediately if conn->connection
+ * is already set,
+ */
+PROXY_DECLARE(int) ap_proxy_connection_create(const char *proxy_function,
+ proxy_conn_rec *conn,
+ conn_rec *c, server_rec *s);
+
+/**
+ * Make a connection record for backend connection, using request dir config
+ * @param proxy_function calling proxy scheme (http, ajp, ...)
+ * @param conn acquired connection
+ * @param r current request record
+ * @return OK or HTTP_XXX error
+ * @note The function will return immediately if conn->connection
+ * is already set,
+ */
+PROXY_DECLARE(int) ap_proxy_connection_create_ex(const char *proxy_function,
+ proxy_conn_rec *conn,
+ request_rec *r);
+/**
+ * Determine if proxy connection can potentially be reused at the
+ * end of this request.
+ * @param conn proxy connection
+ * @return non-zero if reusable, 0 otherwise
+ * @note Even if this function returns non-zero, the connection may
+ * be subsequently marked for closure.
+ */
+PROXY_DECLARE(int) ap_proxy_connection_reusable(proxy_conn_rec *conn);
+
+/**
+ * Signal the upstream chain that the connection to the backend broke in the
+ * middle of the response. This is done by sending an error bucket with
+ * status HTTP_BAD_GATEWAY and an EOS bucket up the filter chain.
+ * @param r current request record of client request
+ * @param brigade The brigade that is sent through the output filter chain
+ */
+PROXY_DECLARE(void) ap_proxy_backend_broke(request_rec *r,
+ apr_bucket_brigade *brigade);
+
+/**
+ * Return a hash based on the passed string
+ * @param str string to produce hash from
+ * @param method hashing method to use
+ * @return hash as unsigned int
+ */
+
+typedef enum { PROXY_HASHFUNC_DEFAULT, PROXY_HASHFUNC_APR, PROXY_HASHFUNC_FNV } proxy_hash_t;
+
+PROXY_DECLARE(unsigned int) ap_proxy_hashfunc(const char *str, proxy_hash_t method);
+
+
+/**
+ * Set/unset the worker status bitfield depending on flag
+ * @param c flag
+ * @param set set or unset bit
+ * @param w worker to use
+ * @return APR_SUCCESS if valid flag
+ */
+PROXY_DECLARE(apr_status_t) ap_proxy_set_wstatus(char c, int set, proxy_worker *w);
+
+
+/**
+ * Create readable representation of worker status bitfield
+ * @param p pool
+ * @param w worker to use
+ * @return string representation of status
+ */
+PROXY_DECLARE(char *) ap_proxy_parse_wstatus(apr_pool_t *p, proxy_worker *w);
+
+
+/**
+ * Sync balancer and workers based on any updates w/i shm
+ * @param b balancer to check/update member list of
+ * @param s server rec
+ * @param conf config
+ * @return APR_SUCCESS if all goes well
+ */
+PROXY_DECLARE(apr_status_t) ap_proxy_sync_balancer(proxy_balancer *b,
+ server_rec *s,
+ proxy_server_conf *conf);
+
+/**
+ * Configure and create workers (and balancer) in mod_balancer.
+ * @param r request
+ * @param params table with the parameters like b=mycluster etc.
+ * @return 404 when the worker/balancer doesn't exist,
+ * 400 if something is invalid
+ * 200 for success.
+ */
+APR_DECLARE_OPTIONAL_FN(apr_status_t, balancer_manage,
+ (request_rec *, apr_table_t *params));
+
+/**
+ * Find the matched alias for this request and setup for proxy handler
+ * @param r request
+ * @param ent proxy_alias record
+ * @param dconf per-dir config or NULL
+ * @return OK if the alias matched,
+ * DONE if the alias matched and r->uri was normalized so
+ * no further transformation should happen on it,
+ * DECLINED if proxying is disabled for this alias,
+ * HTTP_CONTINUE if the alias did not match
+ */
+PROXY_DECLARE(int) ap_proxy_trans_match(request_rec *r,
+ struct proxy_alias *ent,
+ proxy_dir_conf *dconf);
+
+/**
+ * Create a HTTP request header brigade, old_cl_val and old_te_val as required.
+ * @param p pool
+ * @param header_brigade header brigade to use/fill
+ * @param r request
+ * @param p_conn proxy connection rec
+ * @param worker selected worker
+ * @param conf per-server proxy config
+ * @param uri uri
+ * @param url url
+ * @param server_portstr port as string
+ * @param old_cl_val stored old content-len val
+ * @param old_te_val stored old TE val
+ * @return OK or HTTP_EXPECTATION_FAILED
+ */
+PROXY_DECLARE(int) ap_proxy_create_hdrbrgd(apr_pool_t *p,
+ apr_bucket_brigade *header_brigade,
+ request_rec *r,
+ proxy_conn_rec *p_conn,
+ proxy_worker *worker,
+ proxy_server_conf *conf,
+ apr_uri_t *uri,
+ char *url, char *server_portstr,
+ char **old_cl_val,
+ char **old_te_val);
+
+/**
+ * Prefetch the client request body (in memory), up to a limit.
+ * Read what's in the client pipe. If nonblocking is set and read is EAGAIN,
+ * pass a FLUSH bucket to the backend and read again in blocking mode.
+ * @param r client request
+ * @param backend backend connection
+ * @param input_brigade input brigade to use/fill
+ * @param block blocking or non-blocking mode
+ * @param bytes_read number of bytes read
+ * @param max_read maximum number of bytes to read
+ * @return OK or HTTP_* error code
+ * @note max_read is rounded up to APR_BUCKET_BUFF_SIZE
+ */
+PROXY_DECLARE(int) ap_proxy_prefetch_input(request_rec *r,
+ proxy_conn_rec *backend,
+ apr_bucket_brigade *input_brigade,
+ apr_read_type_e block,
+ apr_off_t *bytes_read,
+ apr_off_t max_read);
+
+/**
+ * Spool the client request body to memory, or disk above given limit.
+ * @param r client request
+ * @param backend backend connection
+ * @param input_brigade input brigade to use/fill
+ * @param bytes_spooled number of bytes spooled
+ * @param max_mem_spool maximum number of in-memory bytes
+ * @return OK or HTTP_* error code
+ */
+PROXY_DECLARE(int) ap_proxy_spool_input(request_rec *r,
+ proxy_conn_rec *backend,
+ apr_bucket_brigade *input_brigade,
+ apr_off_t *bytes_spooled,
+ apr_off_t max_mem_spool);
+
+/**
+ * Read what's in the client pipe. If the read would block (EAGAIN),
+ * pass a FLUSH bucket to the backend and read again in blocking mode.
+ * @param r client request
+ * @param backend backend connection
+ * @param input_brigade brigade to use/fill
+ * @param max_read maximum number of bytes to read
+ * @return OK or HTTP_* error code
+ */
+PROXY_DECLARE(int) ap_proxy_read_input(request_rec *r,
+ proxy_conn_rec *backend,
+ apr_bucket_brigade *input_brigade,
+ apr_off_t max_read);
+
+/**
+ * @param bucket_alloc bucket allocator
+ * @param r request
+ * @param p_conn proxy connection
+ * @param origin connection rec of origin
+ * @param bb brigade to send to origin
+ * @param flush flush
+ * @return status (OK)
+ */
+PROXY_DECLARE(int) ap_proxy_pass_brigade(apr_bucket_alloc_t *bucket_alloc,
+ request_rec *r, proxy_conn_rec *p_conn,
+ conn_rec *origin, apr_bucket_brigade *bb,
+ int flush);
+
+struct proxy_tunnel_conn; /* opaque */
+typedef struct {
+ request_rec *r;
+ const char *scheme;
+ apr_pollset_t *pollset;
+ apr_array_header_t *pfds;
+ apr_interval_time_t timeout;
+ struct proxy_tunnel_conn *client,
+ *origin;
+ apr_size_t read_buf_size;
+ int replied; /* TODO 2.5+: one bit to merge in below bitmask */
+ unsigned int nohalfclose :1;
+} proxy_tunnel_rec;
+
+/**
+ * Create a tunnel, to be activated by ap_proxy_tunnel_run().
+ * @param tunnel tunnel created
+ * @param r client request
+ * @param c_o connection to origin
+ * @param scheme caller proxy scheme (connect, ws(s), http(s), ...)
+ * @return APR_SUCCESS or error status
+ */
+PROXY_DECLARE(apr_status_t) ap_proxy_tunnel_create(proxy_tunnel_rec **tunnel,
+ request_rec *r, conn_rec *c_o,
+ const char *scheme);
+
+/**
+ * Forward anything from either side of the tunnel to the other,
+ * until one end aborts or a polling timeout/error occurs.
+ * @param tunnel tunnel to run
+ * @return OK if completion is full, HTTP_GATEWAY_TIME_OUT on timeout
+ * or another HTTP_ error otherwise.
+ */
+PROXY_DECLARE(int) ap_proxy_tunnel_run(proxy_tunnel_rec *tunnel);
+
+/**
+ * Clear the headers referenced by the Connection header from the given
+ * table, and remove the Connection header.
+ * @param r request
+ * @param headers table of headers to clear
+ * @return 1 if "close" was present, 0 otherwise.
+ */
+APR_DECLARE_OPTIONAL_FN(int, ap_proxy_clear_connection,
+ (request_rec *r, apr_table_t *headers));
+
+/**
+ * Do a AJP CPING and wait for CPONG on the socket
+ *
+ */
+APR_DECLARE_OPTIONAL_FN(apr_status_t, ajp_handle_cping_cpong,
+ (apr_socket_t *sock, request_rec *r,
+ apr_interval_time_t timeout));
+
+
+/**
+ * @param socket socket to test
+ * @return TRUE if socket is connected/active
+ */
+PROXY_DECLARE(int) ap_proxy_is_socket_connected(apr_socket_t *socket);
+
+#define PROXY_LBMETHOD "proxylbmethod"
+
+/* The number of dynamic workers that can be added when reconfiguring.
+ * If this limit is reached you must stop and restart the server.
+ */
+#define PROXY_DYNAMIC_BALANCER_LIMIT 16
+
+/**
+ * Calculate maximum number of workers in scoreboard.
+ * @return number of workers to allocate in the scoreboard
+ */
+int ap_proxy_lb_workers(void);
+
+/**
+ * Returns 1 if a response with the given status should be overridden.
+ *
+ * @param conf proxy directory configuration
+ * @param code http status code
+ * @return 1 if code is considered an error-code, 0 otherwise
+ */
+PROXY_DECLARE(int) ap_proxy_should_override(proxy_dir_conf *conf, int code);
+
+/**
+ * Return the port number of a known scheme (eg: http -> 80).
+ * @param scheme scheme to test
+ * @return port number or 0 if unknown
+ */
+PROXY_DECLARE(apr_port_t) ap_proxy_port_of_scheme(const char *scheme);
+
+/**
+ * Return the name of the health check method (eg: "OPTIONS").
+ * @param method method enum
+ * @return name of method
+ */
+PROXY_DECLARE (const char *) ap_proxy_show_hcmethod(hcmethod_t method);
+
+/**
+ * Strip a unix domain socket (UDS) prefix from the input URL
+ * @param p pool to allocate result from
+ * @param url a URL potentially prefixed with a UDS path
+ * @return URL with the UDS prefix removed
+ */
+PROXY_DECLARE(const char *) ap_proxy_de_socketfy(apr_pool_t *p, const char *url);
+
+/*
+ * Transform buckets from one bucket allocator to another one by creating a
+ * transient bucket for each data bucket and let it use the data read from
+ * the old bucket. Metabuckets are transformed by just recreating them.
+ * Attention: Currently only the following bucket types are handled:
+ *
+ * All data buckets
+ * FLUSH
+ * EOS
+ *
+ * If an other bucket type is found its type is logged as a debug message
+ * and APR_EGENERAL is returned.
+ *
+ * @param r request_rec of the actual request. Used for logging purposes
+ * @param from the bucket brigade to take the buckets from
+ * @param to the bucket brigade to store the transformed buckets
+ * @return apr_status_t of the operation. Either APR_SUCCESS or
+ * APR_EGENERAL
+ */
+PROXY_DECLARE(apr_status_t) ap_proxy_buckets_lifetime_transform(request_rec *r,
+ apr_bucket_brigade *from,
+ apr_bucket_brigade *to);
+
+/*
+ * The flags for ap_proxy_transfer_between_connections(), where for legacy and
+ * compatibility reasons FLUSH_EACH and FLUSH_AFTER are boolean values.
+ */
+#define AP_PROXY_TRANSFER_FLUSH_EACH (0x00)
+#define AP_PROXY_TRANSFER_FLUSH_AFTER (0x01)
+#define AP_PROXY_TRANSFER_YIELD_PENDING (0x02)
+#define AP_PROXY_TRANSFER_YIELD_MAX_READS (0x04)
+
+/*
+ * Sends all data that can be read non blocking from the input filter chain of
+ * c_i and send it down the output filter chain of c_o. For reading it uses
+ * the bucket brigade bb_i which should be created from the bucket allocator
+ * associated with c_i. For sending through the output filter chain it uses
+ * the bucket brigade bb_o which should be created from the bucket allocator
+ * associated with c_o. In order to get the buckets from bb_i to bb_o
+ * ap_proxy_buckets_lifetime_transform is used.
+ *
+ * @param r request_rec of the actual request. Used for logging purposes
+ * @param c_i inbound connection conn_rec
+ * @param c_o outbound connection conn_rec
+ * @param bb_i bucket brigade for pulling data from the inbound connection
+ * @param bb_o bucket brigade for sending data through the outbound connection
+ * @param name string for logging from where data was pulled
+ * @param sent if not NULL will be set to 1 if data was sent through c_o
+ * @param bsize maximum amount of data pulled in one iteration from c_i
+ * @param flags AP_PROXY_TRANSFER_* bitmask
+ * @return apr_status_t of the operation. Could be any error returned from
+ * either the input filter chain of c_i or the output filter chain
+ * of c_o, APR_EPIPE if the outgoing connection was aborted, or
+ * APR_INCOMPLETE if AP_PROXY_TRANSFER_YIELD_PENDING was set and
+ * the output stack gets full before the input stack is exhausted.
+ */
+PROXY_DECLARE(apr_status_t) ap_proxy_transfer_between_connections(
+ request_rec *r,
+ conn_rec *c_i,
+ conn_rec *c_o,
+ apr_bucket_brigade *bb_i,
+ apr_bucket_brigade *bb_o,
+ const char *name,
+ int *sent,
+ apr_off_t bsize,
+ int flags);
+
+extern module PROXY_DECLARE_DATA proxy_module;
+
+#endif /*MOD_PROXY_H*/
+/** @} */
diff --git a/modules/proxy/mod_proxy.mak b/modules/proxy/mod_proxy.mak
new file mode 100644
index 0000000..1fda86c
--- /dev/null
+++ b/modules/proxy/mod_proxy.mak
@@ -0,0 +1,361 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_proxy.dsp
+!IF "$(CFG)" == ""
+CFG=mod_proxy - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_proxy - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_proxy - Win32 Release" && "$(CFG)" != "mod_proxy - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy.mak" CFG="mod_proxy - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_proxy.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_proxy.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_proxy.obj"
+ -@erase "$(INTDIR)\mod_proxy.res"
+ -@erase "$(INTDIR)\mod_proxy_src.idb"
+ -@erase "$(INTDIR)\mod_proxy_src.pdb"
+ -@erase "$(INTDIR)\proxy_util.obj"
+ -@erase "$(OUTDIR)\mod_proxy.exp"
+ -@erase "$(OUTDIR)\mod_proxy.lib"
+ -@erase "$(OUTDIR)\mod_proxy.pdb"
+ -@erase "$(OUTDIR)\mod_proxy.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../ssl" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../generators" /I "../http2" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "PROXY_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_proxy_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_proxy.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_proxy.so" /d LONG_NAME="proxy_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_proxy.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_proxy.pdb" /debug /out:"$(OUTDIR)\mod_proxy.so" /implib:"$(OUTDIR)\mod_proxy.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_proxy.obj" \
+ "$(INTDIR)\proxy_util.obj" \
+ "$(INTDIR)\mod_proxy.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_proxy.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_proxy.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_proxy.so"
+ if exist .\Release\mod_proxy.so.manifest mt.exe -manifest .\Release\mod_proxy.so.manifest -outputresource:.\Release\mod_proxy.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_proxy - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_proxy.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_proxy.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_proxy.obj"
+ -@erase "$(INTDIR)\mod_proxy.res"
+ -@erase "$(INTDIR)\mod_proxy_src.idb"
+ -@erase "$(INTDIR)\mod_proxy_src.pdb"
+ -@erase "$(INTDIR)\proxy_util.obj"
+ -@erase "$(OUTDIR)\mod_proxy.exp"
+ -@erase "$(OUTDIR)\mod_proxy.lib"
+ -@erase "$(OUTDIR)\mod_proxy.pdb"
+ -@erase "$(OUTDIR)\mod_proxy.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../ssl" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../generators" /I "../http2" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "PROXY_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_proxy_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_proxy.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_proxy.so" /d LONG_NAME="proxy_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_proxy.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_proxy.pdb" /debug /out:"$(OUTDIR)\mod_proxy.so" /implib:"$(OUTDIR)\mod_proxy.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_proxy.obj" \
+ "$(INTDIR)\proxy_util.obj" \
+ "$(INTDIR)\mod_proxy.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_proxy.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_proxy.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_proxy.so"
+ if exist .\Debug\mod_proxy.so.manifest mt.exe -manifest .\Debug\mod_proxy.so.manifest -outputresource:.\Debug\mod_proxy.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_proxy.dep")
+!INCLUDE "mod_proxy.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_proxy.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_proxy - Win32 Release" || "$(CFG)" == "mod_proxy - Win32 Debug"
+SOURCE=.\mod_proxy.c
+
+"$(INTDIR)\mod_proxy.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\proxy_util.c
+
+"$(INTDIR)\proxy_util.obj" : $(SOURCE) "$(INTDIR)"
+
+
+!IF "$(CFG)" == "mod_proxy - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\proxy"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\proxy"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\proxy"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\proxy"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\proxy"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\proxy"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\proxy"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_proxy - Win32 Release"
+
+
+"$(INTDIR)\mod_proxy.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_proxy.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_proxy.so" /d LONG_NAME="proxy_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_proxy - Win32 Debug"
+
+
+"$(INTDIR)\mod_proxy.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_proxy.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_proxy.so" /d LONG_NAME="proxy_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+
+!ENDIF
+
diff --git a/modules/proxy/mod_proxy_ajp.c b/modules/proxy/mod_proxy_ajp.c
new file mode 100644
index 0000000..e46bd90
--- /dev/null
+++ b/modules/proxy/mod_proxy_ajp.c
@@ -0,0 +1,874 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* AJP routines for Apache proxy */
+
+#include "mod_proxy.h"
+#include "ajp.h"
+
+module AP_MODULE_DECLARE_DATA proxy_ajp_module;
+
+/*
+ * Canonicalise http-like URLs.
+ * scheme is the scheme for the URL
+ * url is the URL starting with the first '/'
+ * def_port is the default port for this scheme.
+ */
+static int proxy_ajp_canon(request_rec *r, char *url)
+{
+ char *host, *path, sport[7];
+ char *search = NULL;
+ const char *err;
+ apr_port_t port, def_port;
+
+ /* ap_port_of_scheme() */
+ if (ap_cstr_casecmpn(url, "ajp:", 4) == 0) {
+ url += 4;
+ }
+ else {
+ return DECLINED;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "canonicalising URL %s", url);
+
+ /*
+ * do syntactic check.
+ * We break the URL into host, port, path, search
+ */
+ port = def_port = ap_proxy_port_of_scheme("ajp");
+
+ err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00867) "error parsing URL %s: %s",
+ url, err);
+ return HTTP_BAD_REQUEST;
+ }
+
+ /*
+ * now parse path/search args, according to rfc1738:
+ * process the path. With proxy-nocanon set (by
+ * mod_proxy) we use the raw, unparsed uri
+ */
+ if (apr_table_get(r->notes, "proxy-nocanon")) {
+ path = url; /* this is the raw path */
+ }
+ else {
+ path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0,
+ r->proxyreq);
+ search = r->args;
+ if (search && *(ap_scan_vchar_obstext(search))) {
+ /*
+ * We have a raw control character or a ' ' in r->args.
+ * Correct encoding was missed.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10406)
+ "To be forwarded query string contains control "
+ "characters or spaces");
+ return HTTP_FORBIDDEN;
+ }
+ }
+ if (path == NULL)
+ return HTTP_BAD_REQUEST;
+
+ if (port != def_port)
+ apr_snprintf(sport, sizeof(sport), ":%d", port);
+ else
+ sport[0] = '\0';
+
+ if (ap_strchr_c(host, ':')) {
+ /* if literal IPv6 address */
+ host = apr_pstrcat(r->pool, "[", host, "]", NULL);
+ }
+ r->filename = apr_pstrcat(r->pool, "proxy:ajp://", host, sport,
+ "/", path, (search) ? "?" : "",
+ (search) ? search : "", NULL);
+ return OK;
+}
+
+#define METHOD_NON_IDEMPOTENT 0
+#define METHOD_IDEMPOTENT 1
+#define METHOD_IDEMPOTENT_WITH_ARGS 2
+
+static int is_idempotent(request_rec *r)
+{
+ /*
+ * RFC2616 (9.1.2): GET, HEAD, PUT, DELETE, OPTIONS, TRACE are considered
+ * idempotent. Hint: HEAD requests use M_GET as method number as well.
+ */
+ switch (r->method_number) {
+ case M_GET:
+ case M_DELETE:
+ case M_PUT:
+ case M_OPTIONS:
+ case M_TRACE:
+ /*
+ * If the request has arguments it might have side-effects and thus
+ * it might be undesirable to resend it to a backend again
+ * automatically.
+ */
+ if (r->args) {
+ return METHOD_IDEMPOTENT_WITH_ARGS;
+ }
+ return METHOD_IDEMPOTENT;
+ /* Everything else is not considered idempotent. */
+ default:
+ return METHOD_NON_IDEMPOTENT;
+ }
+}
+
+static apr_off_t get_content_length(request_rec * r)
+{
+ apr_off_t len = 0;
+
+ if (r->main == NULL) {
+ const char *clp = apr_table_get(r->headers_in, "Content-Length");
+
+ if (clp && !ap_parse_strict_length(&len, clp)) {
+ len = -1; /* parse error */
+ }
+ }
+
+ return len;
+}
+
+/*
+ * XXX: AJP Auto Flushing
+ *
+ * When processing CMD_AJP13_SEND_BODY_CHUNK AJP messages we will do a poll
+ * with FLUSH_WAIT milliseconds timeout to determine if more data is currently
+ * available at the backend. If there is no more data available, we flush
+ * the data to the client by adding a flush bucket to the brigade we pass
+ * up the filter chain.
+ * This is only a bandaid to fix the AJP/1.3 protocol shortcoming of not
+ * sending (actually not having defined) a flush message, when the data
+ * should be flushed to the client. As soon as this protocol shortcoming is
+ * fixed this code should be removed.
+ *
+ * For further discussion see PR37100.
+ * http://issues.apache.org/bugzilla/show_bug.cgi?id=37100
+ */
+
+/*
+ * process the request and write the response.
+ */
+static int ap_proxy_ajp_request(apr_pool_t *p, request_rec *r,
+ proxy_conn_rec *conn,
+ conn_rec *origin,
+ proxy_dir_conf *conf,
+ apr_uri_t *uri,
+ char *url, char *server_portstr)
+{
+ apr_status_t status;
+ int result;
+ apr_bucket *e;
+ apr_bucket_brigade *input_brigade;
+ apr_bucket_brigade *output_brigade;
+ ajp_msg_t *msg;
+ apr_size_t bufsiz = 0;
+ char *buff;
+ char *send_body_chunk_buff;
+ apr_uint16_t size;
+ apr_byte_t conn_reuse = 0;
+ const char *tenc;
+ int havebody = 1;
+ int client_failed = 0;
+ int backend_failed = 0;
+ apr_off_t bb_len;
+ int data_sent = 0;
+ int request_ended = 0;
+ int headers_sent = 0;
+ int rv = OK;
+ apr_int32_t conn_poll_fd;
+ apr_pollfd_t *conn_poll;
+ proxy_server_conf *psf =
+ ap_get_module_config(r->server->module_config, &proxy_module);
+ apr_size_t maxsize = AJP_MSG_BUFFER_SZ;
+ int send_body = 0;
+ apr_off_t content_length = 0;
+ int original_status = r->status;
+ const char *original_status_line = r->status_line;
+ const char *secret = NULL;
+
+ if (psf->io_buffer_size_set)
+ maxsize = psf->io_buffer_size;
+ if (maxsize > AJP_MAX_BUFFER_SZ)
+ maxsize = AJP_MAX_BUFFER_SZ;
+ else if (maxsize < AJP_MSG_BUFFER_SZ)
+ maxsize = AJP_MSG_BUFFER_SZ;
+ maxsize = APR_ALIGN(maxsize, 1024);
+
+ if (*conn->worker->s->secret)
+ secret = conn->worker->s->secret;
+
+ /*
+ * Send the AJP request to the remote server
+ */
+
+ /* send request headers */
+ status = ajp_send_header(conn->sock, r, maxsize, uri, secret);
+ if (status != APR_SUCCESS) {
+ conn->close = 1;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(00868)
+ "request failed to %pI (%s:%d)",
+ conn->worker->cp->addr,
+ conn->worker->s->hostname_ex,
+ (int)conn->worker->s->port);
+ if (status == AJP_EOVERFLOW)
+ return HTTP_BAD_REQUEST;
+ else if (status == AJP_EBAD_METHOD) {
+ return HTTP_NOT_IMPLEMENTED;
+ } else {
+ /*
+ * This is only non fatal when the method is idempotent. In this
+ * case we can dare to retry it with a different worker if we are
+ * a balancer member.
+ */
+ if (is_idempotent(r) == METHOD_IDEMPOTENT) {
+ return HTTP_SERVICE_UNAVAILABLE;
+ }
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ /* allocate an AJP message to store the data of the buckets */
+ bufsiz = maxsize;
+ status = ajp_alloc_data_msg(r->pool, &buff, &bufsiz, &msg);
+ if (status != APR_SUCCESS) {
+ /* We had a failure: Close connection to backend */
+ conn->close = 1;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00869)
+ "ajp_alloc_data_msg failed");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* read the first block of data */
+ input_brigade = apr_brigade_create(p, r->connection->bucket_alloc);
+ tenc = apr_table_get(r->headers_in, "Transfer-Encoding");
+ if (tenc) {
+ if (ap_cstr_casecmp(tenc, "chunked") == 0) {
+ /* The AJP protocol does not want body data yet */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00870)
+ "request is chunked");
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10396)
+ "%s Transfer-Encoding is not supported",
+ tenc);
+ /* We had a failure: Close connection to backend */
+ conn->close = 1;
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ } else {
+ /* Get client provided Content-Length header */
+ content_length = get_content_length(r);
+ if (content_length < 0) {
+ status = APR_EINVAL;
+ }
+ else {
+ status = ap_get_brigade(r->input_filters, input_brigade,
+ AP_MODE_READBYTES, APR_BLOCK_READ,
+ maxsize - AJP_HEADER_SZ);
+ }
+ if (status != APR_SUCCESS) {
+ /* We had a failure: Close connection to backend */
+ conn->close = 1;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO(00871)
+ "ap_get_brigade failed");
+ apr_brigade_destroy(input_brigade);
+ return ap_map_http_request_error(status, HTTP_BAD_REQUEST);
+ }
+
+ /* have something */
+ if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade))) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00872) "APR_BUCKET_IS_EOS");
+ }
+
+ /* Try to send something */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00873)
+ "data to read (max %" APR_SIZE_T_FMT
+ " at %" APR_SIZE_T_FMT ")", bufsiz, msg->pos);
+
+ status = apr_brigade_flatten(input_brigade, buff, &bufsiz);
+ if (status != APR_SUCCESS) {
+ /* We had a failure: Close connection to backend */
+ conn->close = 1;
+ apr_brigade_destroy(input_brigade);
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(00874)
+ "apr_brigade_flatten");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ apr_brigade_cleanup(input_brigade);
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00875)
+ "got %" APR_SIZE_T_FMT " bytes of data", bufsiz);
+ if (bufsiz > 0) {
+ status = ajp_send_data_msg(conn->sock, msg, bufsiz);
+ ajp_msg_log(r, msg, "First ajp_send_data_msg: ajp_ilink_send packet dump");
+ if (status != APR_SUCCESS) {
+ /* We had a failure: Close connection to backend */
+ conn->close = 1;
+ apr_brigade_destroy(input_brigade);
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(00876)
+ "send failed to %pI (%s:%d)",
+ conn->worker->cp->addr,
+ conn->worker->s->hostname_ex,
+ (int)conn->worker->s->port);
+ /*
+ * It is fatal when we failed to send a (part) of the request
+ * body.
+ */
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ conn->worker->s->transferred += bufsiz;
+ send_body = 1;
+ }
+ else if (content_length > 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(00877)
+ "read zero bytes, expecting"
+ " %" APR_OFF_T_FMT " bytes",
+ content_length);
+ /*
+ * We can only get here if the client closed the connection
+ * to us without sending the body.
+ * Now the connection is in the wrong state on the backend.
+ * Sending an empty data msg doesn't help either as it does
+ * not move this connection to the correct state on the backend
+ * for later resusage by the next request again.
+ * Close it to clean things up.
+ */
+ conn->close = 1;
+ apr_brigade_destroy(input_brigade);
+ return HTTP_BAD_REQUEST;
+ }
+ }
+
+ /* read the response */
+ conn->data = NULL;
+ status = ajp_read_header(conn->sock, r, maxsize,
+ (ajp_msg_t **)&(conn->data));
+ if (status != APR_SUCCESS) {
+ /* We had a failure: Close connection to backend */
+ conn->close = 1;
+ apr_brigade_destroy(input_brigade);
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(00878)
+ "read response failed from %pI (%s:%d)",
+ conn->worker->cp->addr,
+ conn->worker->s->hostname_ex,
+ (int)conn->worker->s->port);
+
+ /* If we had a successful cping/cpong and then a timeout
+ * we assume it is a request that cause a back-end timeout,
+ * but doesn't affect the whole worker.
+ */
+ if (APR_STATUS_IS_TIMEUP(status) &&
+ conn->worker->s->ping_timeout_set) {
+ return HTTP_GATEWAY_TIME_OUT;
+ }
+
+ /*
+ * This is only non fatal when we have not sent (parts) of a possible
+ * request body so far (we do not store it and thus cannot send it
+ * again) and the method is idempotent. In this case we can dare to
+ * retry it with a different worker if we are a balancer member.
+ */
+ if (!send_body && (is_idempotent(r) == METHOD_IDEMPOTENT)) {
+ return HTTP_SERVICE_UNAVAILABLE;
+ }
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ /* parse the response */
+ result = ajp_parse_type(r, conn->data);
+ output_brigade = apr_brigade_create(p, r->connection->bucket_alloc);
+
+ /*
+ * Prepare apr_pollfd_t struct for possible later check if there is currently
+ * data available from the backend (do not flush response to client)
+ * or not (flush response to client)
+ */
+ conn_poll = apr_pcalloc(p, sizeof(apr_pollfd_t));
+ conn_poll->reqevents = APR_POLLIN;
+ conn_poll->desc_type = APR_POLL_SOCKET;
+ conn_poll->desc.s = conn->sock;
+
+ bufsiz = maxsize;
+ for (;;) {
+ switch (result) {
+ case CMD_AJP13_GET_BODY_CHUNK:
+ if (havebody) {
+ if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade))) {
+ /* This is the end */
+ bufsiz = 0;
+ havebody = 0;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO(00879)
+ "APR_BUCKET_IS_EOS");
+ } else {
+ status = ap_get_brigade(r->input_filters, input_brigade,
+ AP_MODE_READBYTES,
+ APR_BLOCK_READ,
+ maxsize - AJP_HEADER_SZ);
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO(00880)
+ "ap_get_brigade failed");
+ if (APR_STATUS_IS_TIMEUP(status)) {
+ rv = HTTP_REQUEST_TIME_OUT;
+ }
+ else if (status == AP_FILTER_ERROR) {
+ rv = AP_FILTER_ERROR;
+ }
+ client_failed = 1;
+ break;
+ }
+ bufsiz = maxsize;
+ status = apr_brigade_flatten(input_brigade, buff,
+ &bufsiz);
+ apr_brigade_cleanup(input_brigade);
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO(00881)
+ "apr_brigade_flatten failed");
+ rv = HTTP_INTERNAL_SERVER_ERROR;
+ client_failed = 1;
+ break;
+ }
+ }
+
+ ajp_msg_reset(msg);
+ /* will go in ajp_send_data_msg */
+ status = ajp_send_data_msg(conn->sock, msg, bufsiz);
+ ajp_msg_log(r, msg, "ajp_send_data_msg after CMD_AJP13_GET_BODY_CHUNK: ajp_ilink_send packet dump");
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO(00882)
+ "ajp_send_data_msg failed");
+ backend_failed = 1;
+ break;
+ }
+ conn->worker->s->transferred += bufsiz;
+ } else {
+ /*
+ * something is wrong TC asks for more body but we are
+ * already at the end of the body data
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00883)
+ "ap_proxy_ajp_request error read after end");
+ backend_failed = 1;
+ }
+ break;
+ case CMD_AJP13_SEND_HEADERS:
+ if (headers_sent) {
+ /* Do not send anything to the client.
+ * Backend already send us the headers.
+ */
+ backend_failed = 1;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00884)
+ "Backend sent headers twice.");
+ break;
+ }
+ /* AJP13_SEND_HEADERS: process them */
+ status = ajp_parse_header(r, conf, conn->data);
+ if (status != APR_SUCCESS) {
+ backend_failed = 1;
+ }
+ else if ((r->status == 401) && conf->error_override) {
+ const char *buf;
+ const char *wa = "WWW-Authenticate";
+ if ((buf = apr_table_get(r->headers_out, wa))) {
+ apr_table_set(r->err_headers_out, wa, buf);
+ } else {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00885)
+ "ap_proxy_ajp_request: origin server "
+ "sent 401 without WWW-Authenticate header");
+ }
+ }
+ headers_sent = 1;
+ break;
+ case CMD_AJP13_SEND_BODY_CHUNK:
+ /* AJP13_SEND_BODY_CHUNK: piece of data */
+ status = ajp_parse_data(r, conn->data, &size, &send_body_chunk_buff);
+ if (status == APR_SUCCESS) {
+ /* If we are overriding the errors, we can't put the content
+ * of the page into the brigade.
+ */
+ if (!ap_proxy_should_override(conf, r->status)) {
+ /* AJP13_SEND_BODY_CHUNK with zero length
+ * is explicit flush message
+ */
+ if (size == 0) {
+ if (headers_sent) {
+ e = apr_bucket_flush_create(r->connection->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(output_brigade, e);
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00886)
+ "Ignoring flush message "
+ "received before headers");
+ }
+ }
+ else {
+ apr_status_t rv;
+
+ /* Handle the case where the error document is itself reverse
+ * proxied and was successful. We must maintain any previous
+ * error status so that an underlying error (eg HTTP_NOT_FOUND)
+ * doesn't become an HTTP_OK.
+ */
+ if (ap_proxy_should_override(conf, original_status)) {
+ r->status = original_status;
+ r->status_line = original_status_line;
+ }
+
+ e = apr_bucket_transient_create(send_body_chunk_buff, size,
+ r->connection->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(output_brigade, e);
+
+ if ((conn->worker->s->flush_packets == flush_on) ||
+ ((conn->worker->s->flush_packets == flush_auto) &&
+ ((rv = apr_poll(conn_poll, 1, &conn_poll_fd,
+ conn->worker->s->flush_wait))
+ != APR_SUCCESS) &&
+ APR_STATUS_IS_TIMEUP(rv))) {
+ e = apr_bucket_flush_create(r->connection->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(output_brigade, e);
+ }
+ apr_brigade_length(output_brigade, 0, &bb_len);
+ if (bb_len != -1)
+ conn->worker->s->read += bb_len;
+ }
+ if (headers_sent) {
+ if (ap_pass_brigade(r->output_filters,
+ output_brigade) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00887)
+ "error processing body.%s",
+ r->connection->aborted ?
+ " Client aborted connection." : "");
+ client_failed = 1;
+ }
+ data_sent = 1;
+ apr_brigade_cleanup(output_brigade);
+ }
+ }
+ }
+ else {
+ backend_failed = 1;
+ }
+ break;
+ case CMD_AJP13_END_RESPONSE:
+ /* If we are overriding the errors, we must not send anything to
+ * the client, especially as the brigade already contains headers.
+ * So do nothing here, and it will be cleaned up below.
+ */
+ status = ajp_parse_reuse(r, conn->data, &conn_reuse);
+ if (status != APR_SUCCESS) {
+ backend_failed = 1;
+ }
+ if (!ap_proxy_should_override(conf, r->status)) {
+ e = apr_bucket_eos_create(r->connection->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(output_brigade, e);
+ if (ap_pass_brigade(r->output_filters,
+ output_brigade) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00888)
+ "error processing end");
+ client_failed = 1;
+ }
+ /* XXX: what about flush here? See mod_jk */
+ data_sent = 1;
+ }
+ request_ended = 1;
+ break;
+ default:
+ backend_failed = 1;
+ break;
+ }
+
+ /*
+ * If connection has been aborted by client: Stop working.
+ * Pretend we are done (data_sent) to avoid further processing.
+ */
+ if (r->connection->aborted) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02821)
+ "client connection aborted");
+ /* no response yet (or ever), set status for access log */
+ if (!headers_sent) {
+ r->status = HTTP_BAD_REQUEST;
+ }
+ client_failed = 1;
+ /* return DONE */
+ data_sent = 1;
+ break;
+ }
+
+ /*
+ * We either have finished successfully or we failed.
+ * So bail out
+ */
+ if ((result == CMD_AJP13_END_RESPONSE)
+ || backend_failed || client_failed)
+ break;
+
+ /* read the response */
+ status = ajp_read_header(conn->sock, r, maxsize,
+ (ajp_msg_t **)&(conn->data));
+ if (status != APR_SUCCESS) {
+ backend_failed = 1;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO(00889)
+ "ajp_read_header failed");
+ break;
+ }
+ result = ajp_parse_type(r, conn->data);
+ }
+ apr_brigade_destroy(input_brigade);
+
+ /*
+ * Clear output_brigade to remove possible buckets that remained there
+ * after an error.
+ */
+ apr_brigade_cleanup(output_brigade);
+
+ if (backend_failed || client_failed) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00890)
+ "Processing of request failed backend: %i, client: %i",
+ backend_failed, client_failed);
+ /* We had a failure: Close connection to backend */
+ conn->close = 1;
+ if (data_sent) {
+ /* Return DONE to avoid error messages being added to the stream */
+ rv = DONE;
+ }
+ }
+ else if (!request_ended) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00891)
+ "Processing of request didn't terminate cleanly");
+ /* We had a failure: Close connection to backend */
+ conn->close = 1;
+ backend_failed = 1;
+ if (data_sent) {
+ /* Return DONE to avoid error messages being added to the stream */
+ rv = DONE;
+ }
+ }
+ else if (!conn_reuse) {
+ /* Our backend signalled connection close */
+ conn->close = 1;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00892)
+ "got response from %pI (%s:%d)",
+ conn->worker->cp->addr,
+ conn->worker->s->hostname_ex,
+ (int)conn->worker->s->port);
+
+ if (ap_proxy_should_override(conf, r->status)) {
+ /* clear r->status for override error, otherwise ErrorDocument
+ * thinks that this is a recursive error, and doesn't find the
+ * custom error page
+ */
+ rv = r->status;
+ r->status = HTTP_OK;
+ /*
+ * prevent proxy_handler() from treating this as an
+ * internal error.
+ */
+ apr_table_setn(r->notes, "proxy-error-override", "1");
+ }
+ else {
+ rv = OK;
+ }
+ }
+
+ if (backend_failed) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(00893)
+ "dialog to %pI (%s:%d) failed",
+ conn->worker->cp->addr,
+ conn->worker->s->hostname_ex,
+ (int)conn->worker->s->port);
+ /*
+ * If we already send data, signal a broken backend connection
+ * upwards in the chain.
+ */
+ if (data_sent) {
+ ap_proxy_backend_broke(r, output_brigade);
+ } else if (!send_body && (is_idempotent(r) == METHOD_IDEMPOTENT)) {
+ /*
+ * This is only non fatal when we have not send (parts) of a possible
+ * request body so far (we do not store it and thus cannot send it
+ * again) and the method is idempotent. In this case we can dare to
+ * retry it with a different worker if we are a balancer member.
+ */
+ rv = HTTP_SERVICE_UNAVAILABLE;
+ } else {
+ /* If we had a successful cping/cpong and then a timeout
+ * we assume it is a request that cause a back-end timeout,
+ * but doesn't affect the whole worker.
+ */
+ if (APR_STATUS_IS_TIMEUP(status) &&
+ conn->worker->s->ping_timeout_set) {
+ apr_table_setn(r->notes, "proxy_timedout", "1");
+ rv = HTTP_GATEWAY_TIME_OUT;
+ }
+ else {
+ rv = HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+ }
+ else if (client_failed) {
+ int level = (r->connection->aborted) ? APLOG_DEBUG : APLOG_ERR;
+ ap_log_rerror(APLOG_MARK, level, status, r, APLOGNO(02822)
+ "dialog with client %pI failed",
+ r->connection->client_addr);
+ if (rv == OK) {
+ rv = HTTP_BAD_REQUEST;
+ }
+ }
+
+ /*
+ * Ensure that we sent an EOS bucket thru the filter chain, if we already
+ * have sent some data. Maybe ap_proxy_backend_broke was called and added
+ * one to the brigade already (no longer making it empty). So we should
+ * not do this in this case.
+ */
+ if (data_sent && !r->eos_sent && !r->connection->aborted
+ && APR_BRIGADE_EMPTY(output_brigade)) {
+ e = apr_bucket_eos_create(r->connection->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(output_brigade, e);
+ }
+
+ /* If we have added something to the brigade above, send it */
+ if (!APR_BRIGADE_EMPTY(output_brigade)
+ && ap_pass_brigade(r->output_filters, output_brigade) != APR_SUCCESS) {
+ rv = AP_FILTER_ERROR;
+ }
+
+ apr_brigade_destroy(output_brigade);
+
+ if (apr_table_get(r->subprocess_env, "proxy-nokeepalive")) {
+ conn->close = 1;
+ }
+
+ return rv;
+}
+
+/*
+ * This handles ajp:// URLs
+ */
+static int proxy_ajp_handler(request_rec *r, proxy_worker *worker,
+ proxy_server_conf *conf,
+ char *url, const char *proxyname,
+ apr_port_t proxyport)
+{
+ int status;
+ char server_portstr[32];
+ conn_rec *origin = NULL;
+ proxy_conn_rec *backend = NULL;
+ const char *scheme = "AJP";
+ int retry;
+ proxy_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
+ &proxy_module);
+ apr_pool_t *p = r->pool;
+ apr_uri_t *uri;
+
+ if (ap_cstr_casecmpn(url, "ajp:", 4) != 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00894) "declining URL %s", url);
+ return DECLINED;
+ }
+
+ uri = apr_palloc(p, sizeof(*uri));
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00895) "serving URL %s", url);
+
+ /* create space for state information */
+ status = ap_proxy_acquire_connection(scheme, &backend, worker,
+ r->server);
+ if (status != OK) {
+ if (backend) {
+ backend->close = 1;
+ ap_proxy_release_connection(scheme, backend, r->server);
+ }
+ return status;
+ }
+
+ backend->is_ssl = 0;
+ backend->close = 0;
+
+ retry = 0;
+ while (retry < 2) {
+ char *locurl = url;
+ /* Step One: Determine Who To Connect To */
+ status = ap_proxy_determine_connection(p, r, conf, worker, backend,
+ uri, &locurl, proxyname, proxyport,
+ server_portstr,
+ sizeof(server_portstr));
+
+ if (status != OK)
+ break;
+
+ /* Step Two: Make the Connection */
+ if (ap_proxy_check_connection(scheme, backend, r->server, 0,
+ PROXY_CHECK_CONN_EMPTY)
+ && ap_proxy_connect_backend(scheme, backend, worker,
+ r->server)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00896)
+ "failed to make connection to backend: %s",
+ backend->hostname);
+ status = HTTP_SERVICE_UNAVAILABLE;
+ break;
+ }
+
+ /* Handle CPING/CPONG */
+ if (worker->s->ping_timeout_set) {
+ status = ajp_handle_cping_cpong(backend->sock, r,
+ worker->s->ping_timeout);
+ /*
+ * In case the CPING / CPONG failed for the first time we might be
+ * just out of luck and got a faulty backend connection, but the
+ * backend might be healthy nevertheless. So ensure that the backend
+ * TCP connection gets closed and try it once again.
+ */
+ if (status != APR_SUCCESS) {
+ backend->close = 1;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(00897)
+ "cping/cpong failed to %pI (%s:%d)",
+ worker->cp->addr, worker->s->hostname_ex,
+ (int)worker->s->port);
+ status = HTTP_SERVICE_UNAVAILABLE;
+ retry++;
+ continue;
+ }
+ }
+ /* Step Three: Process the Request */
+ status = ap_proxy_ajp_request(p, r, backend, origin, dconf, uri, locurl,
+ server_portstr);
+ break;
+ }
+
+ /* Do not close the socket */
+ ap_proxy_release_connection(scheme, backend, r->server);
+ return status;
+}
+
+static void ap_proxy_http_register_hook(apr_pool_t *p)
+{
+ proxy_hook_scheme_handler(proxy_ajp_handler, NULL, NULL, APR_HOOK_FIRST);
+ proxy_hook_canon_handler(proxy_ajp_canon, NULL, NULL, APR_HOOK_FIRST);
+ APR_REGISTER_OPTIONAL_FN(ajp_handle_cping_cpong);
+}
+
+AP_DECLARE_MODULE(proxy_ajp) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ NULL, /* command apr_table_t */
+ ap_proxy_http_register_hook /* register hooks */
+};
+
diff --git a/modules/proxy/mod_proxy_ajp.dep b/modules/proxy/mod_proxy_ajp.dep
new file mode 100644
index 0000000..475859a
--- /dev/null
+++ b/modules/proxy/mod_proxy_ajp.dep
@@ -0,0 +1,356 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_proxy_ajp.mak
+
+.\mod_proxy_ajp.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_slotmem.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_date.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_reslist.h"\
+ "..\..\srclib\apr-util\include\apr_strmatch.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_version.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\ajp.h"\
+ ".\mod_proxy.h"\
+
+
+.\ajp_header.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_slotmem.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_date.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_reslist.h"\
+ "..\..\srclib\apr-util\include\apr_strmatch.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_version.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\ajp.h"\
+ ".\ajp_header.h"\
+ ".\mod_proxy.h"\
+
+
+.\ajp_link.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_slotmem.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_date.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_reslist.h"\
+ "..\..\srclib\apr-util\include\apr_strmatch.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_version.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\ajp.h"\
+ ".\mod_proxy.h"\
+
+
+.\ajp_msg.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_slotmem.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_date.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_reslist.h"\
+ "..\..\srclib\apr-util\include\apr_strmatch.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_version.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\ajp.h"\
+ ".\mod_proxy.h"\
+
+
+.\ajp_utils.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_slotmem.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_date.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_reslist.h"\
+ "..\..\srclib\apr-util\include\apr_strmatch.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_version.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\ajp.h"\
+ ".\mod_proxy.h"\
+
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
diff --git a/modules/proxy/mod_proxy_ajp.dsp b/modules/proxy/mod_proxy_ajp.dsp
new file mode 100644
index 0000000..74d7b30
--- /dev/null
+++ b/modules/proxy/mod_proxy_ajp.dsp
@@ -0,0 +1,151 @@
+# Microsoft Developer Studio Project File - Name="mod_proxy_ajp" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_proxy_ajp - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_ajp.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_ajp.mak" CFG="mod_proxy_ajp - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy_ajp - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy_ajp - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_proxy_ajp - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_proxy_ajp_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_proxy_ajp.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_proxy_ajp.so" /d LONG_NAME="proxy_ajp_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:".\Release\mod_proxy_ajp.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_ajp.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /debug /out:".\Release\mod_proxy_ajp.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_ajp.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_proxy_ajp.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_proxy_ajp - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_proxy_ajp_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_proxy_ajp.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_proxy_ajp.so" /d LONG_NAME="proxy_ajp_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_proxy_ajp.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_ajp.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_proxy_ajp.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_ajp.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_proxy_ajp.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_proxy_ajp - Win32 Release"
+# Name "mod_proxy_ajp - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\mod_proxy_ajp.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter ".h"
+# Begin Source File
+
+SOURCE=.\mod_proxy.h
+# End Source File
+# End Group
+# Begin Group "Ajp Files"
+
+# PROP Default_Filter ""
+# Begin Source File
+
+SOURCE=.\ajp.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\ajp_header.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\ajp_header.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\ajp_link.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\ajp_msg.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\ajp_utils.c
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/proxy/mod_proxy_ajp.mak b/modules/proxy/mod_proxy_ajp.mak
new file mode 100644
index 0000000..b14a569
--- /dev/null
+++ b/modules/proxy/mod_proxy_ajp.mak
@@ -0,0 +1,416 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_proxy_ajp.dsp
+!IF "$(CFG)" == ""
+CFG=mod_proxy_ajp - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_proxy_ajp - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_proxy_ajp - Win32 Release" && "$(CFG)" != "mod_proxy_ajp - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_ajp.mak" CFG="mod_proxy_ajp - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy_ajp - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy_ajp - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_ajp - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_proxy_ajp.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_proxy - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_proxy_ajp.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_proxy - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\ajp_header.obj"
+ -@erase "$(INTDIR)\ajp_link.obj"
+ -@erase "$(INTDIR)\ajp_msg.obj"
+ -@erase "$(INTDIR)\ajp_utils.obj"
+ -@erase "$(INTDIR)\mod_proxy_ajp.obj"
+ -@erase "$(INTDIR)\mod_proxy_ajp.res"
+ -@erase "$(INTDIR)\mod_proxy_ajp_src.idb"
+ -@erase "$(INTDIR)\mod_proxy_ajp_src.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_ajp.exp"
+ -@erase "$(OUTDIR)\mod_proxy_ajp.lib"
+ -@erase "$(OUTDIR)\mod_proxy_ajp.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_ajp.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_proxy_ajp_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_proxy_ajp.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_proxy_ajp.so" /d LONG_NAME="proxy_ajp_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_proxy_ajp.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_proxy_ajp.pdb" /debug /out:"$(OUTDIR)\mod_proxy_ajp.so" /implib:"$(OUTDIR)\mod_proxy_ajp.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_ajp.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_proxy_ajp.obj" \
+ "$(INTDIR)\ajp_header.obj" \
+ "$(INTDIR)\ajp_link.obj" \
+ "$(INTDIR)\ajp_msg.obj" \
+ "$(INTDIR)\ajp_utils.obj" \
+ "$(INTDIR)\mod_proxy_ajp.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib" \
+ "$(OUTDIR)\mod_proxy.lib"
+
+"$(OUTDIR)\mod_proxy_ajp.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_proxy_ajp.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_proxy_ajp.so"
+ if exist .\Release\mod_proxy_ajp.so.manifest mt.exe -manifest .\Release\mod_proxy_ajp.so.manifest -outputresource:.\Release\mod_proxy_ajp.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_proxy_ajp - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_proxy_ajp.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_proxy - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_proxy_ajp.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_proxy - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\ajp_header.obj"
+ -@erase "$(INTDIR)\ajp_link.obj"
+ -@erase "$(INTDIR)\ajp_msg.obj"
+ -@erase "$(INTDIR)\ajp_utils.obj"
+ -@erase "$(INTDIR)\mod_proxy_ajp.obj"
+ -@erase "$(INTDIR)\mod_proxy_ajp.res"
+ -@erase "$(INTDIR)\mod_proxy_ajp_src.idb"
+ -@erase "$(INTDIR)\mod_proxy_ajp_src.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_ajp.exp"
+ -@erase "$(OUTDIR)\mod_proxy_ajp.lib"
+ -@erase "$(OUTDIR)\mod_proxy_ajp.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_ajp.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_proxy_ajp_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_proxy_ajp.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_proxy_ajp.so" /d LONG_NAME="proxy_ajp_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_proxy_ajp.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_proxy_ajp.pdb" /debug /out:"$(OUTDIR)\mod_proxy_ajp.so" /implib:"$(OUTDIR)\mod_proxy_ajp.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_ajp.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_proxy_ajp.obj" \
+ "$(INTDIR)\ajp_header.obj" \
+ "$(INTDIR)\ajp_link.obj" \
+ "$(INTDIR)\ajp_msg.obj" \
+ "$(INTDIR)\ajp_utils.obj" \
+ "$(INTDIR)\mod_proxy_ajp.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib" \
+ "$(OUTDIR)\mod_proxy.lib"
+
+"$(OUTDIR)\mod_proxy_ajp.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_proxy_ajp.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_proxy_ajp.so"
+ if exist .\Debug\mod_proxy_ajp.so.manifest mt.exe -manifest .\Debug\mod_proxy_ajp.so.manifest -outputresource:.\Debug\mod_proxy_ajp.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_proxy_ajp.dep")
+!INCLUDE "mod_proxy_ajp.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_proxy_ajp.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_proxy_ajp - Win32 Release" || "$(CFG)" == "mod_proxy_ajp - Win32 Debug"
+SOURCE=.\mod_proxy_ajp.c
+
+"$(INTDIR)\mod_proxy_ajp.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\ajp_header.c
+
+"$(INTDIR)\ajp_header.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\ajp_link.c
+
+"$(INTDIR)\ajp_link.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\ajp_msg.c
+
+"$(INTDIR)\ajp_msg.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\ajp_utils.c
+
+"$(INTDIR)\ajp_utils.obj" : $(SOURCE) "$(INTDIR)"
+
+
+!IF "$(CFG)" == "mod_proxy_ajp - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\proxy"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_ajp - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\proxy"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_ajp - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\proxy"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_ajp - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\proxy"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_ajp - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\proxy"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_ajp - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\proxy"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_ajp - Win32 Release"
+
+"mod_proxy - Win32 Release" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Release"
+ cd "."
+
+"mod_proxy - Win32 ReleaseCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Release" RECURSE=1 CLEAN
+ cd "."
+
+!ELSEIF "$(CFG)" == "mod_proxy_ajp - Win32 Debug"
+
+"mod_proxy - Win32 Debug" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Debug"
+ cd "."
+
+"mod_proxy - Win32 DebugCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Debug" RECURSE=1 CLEAN
+ cd "."
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_proxy_ajp - Win32 Release"
+
+
+"$(INTDIR)\mod_proxy_ajp.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_proxy_ajp.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_proxy_ajp.so" /d LONG_NAME="proxy_ajp_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_proxy_ajp - Win32 Debug"
+
+
+"$(INTDIR)\mod_proxy_ajp.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_proxy_ajp.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_proxy_ajp.so" /d LONG_NAME="proxy_ajp_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+
+!ENDIF
+
diff --git a/modules/proxy/mod_proxy_balancer.c b/modules/proxy/mod_proxy_balancer.c
new file mode 100644
index 0000000..7f99008
--- /dev/null
+++ b/modules/proxy/mod_proxy_balancer.c
@@ -0,0 +1,2087 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Load balancer module for Apache proxy */
+
+#include "mod_proxy.h"
+#include "scoreboard.h"
+#include "ap_mpm.h"
+#include "apr_version.h"
+#include "ap_hooks.h"
+#include "apr_date.h"
+#include "util_md5.h"
+#include "mod_watchdog.h"
+
+static const char *balancer_mutex_type = "proxy-balancer-shm";
+ap_slotmem_provider_t *storage = NULL;
+
+module AP_MODULE_DECLARE_DATA proxy_balancer_module;
+
+static APR_OPTIONAL_FN_TYPE(set_worker_hc_param) *set_worker_hc_param_f = NULL;
+
+static int (*ap_proxy_retry_worker_fn)(const char *proxy_function,
+ proxy_worker *worker, server_rec *s) = NULL;
+
+static APR_OPTIONAL_FN_TYPE(hc_show_exprs) *hc_show_exprs_f = NULL;
+static APR_OPTIONAL_FN_TYPE(hc_select_exprs) *hc_select_exprs_f = NULL;
+static APR_OPTIONAL_FN_TYPE(hc_valid_expr) *hc_valid_expr_f = NULL;
+
+
+/*
+ * Register our mutex type before the config is read so we
+ * can adjust the mutex settings using the Mutex directive.
+ */
+static int balancer_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp)
+{
+
+ apr_status_t rv;
+
+ rv = ap_mutex_register(pconf, balancer_mutex_type, NULL,
+ APR_LOCK_DEFAULT, 0);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ set_worker_hc_param_f = APR_RETRIEVE_OPTIONAL_FN(set_worker_hc_param);
+ hc_show_exprs_f = APR_RETRIEVE_OPTIONAL_FN(hc_show_exprs);
+ hc_select_exprs_f = APR_RETRIEVE_OPTIONAL_FN(hc_select_exprs);
+ hc_valid_expr_f = APR_RETRIEVE_OPTIONAL_FN(hc_valid_expr);
+ return OK;
+}
+
+#if 0
+extern void proxy_update_members(proxy_balancer **balancer, request_rec *r,
+ proxy_server_conf *conf);
+#endif
+
+static int proxy_balancer_canon(request_rec *r, char *url)
+{
+ char *host, *path;
+ char *search = NULL;
+ const char *err;
+ apr_port_t port = 0;
+
+ /* TODO: offset of BALANCER_PREFIX ?? */
+ if (ap_cstr_casecmpn(url, "balancer:", 9) == 0) {
+ url += 9;
+ }
+ else {
+ return DECLINED;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "canonicalising URL %s", url);
+
+ /* do syntatic check.
+ * We break the URL into host, port, path, search
+ */
+ err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01157)
+ "error parsing URL %s: %s",
+ url, err);
+ return HTTP_BAD_REQUEST;
+ }
+ /*
+ * now parse path/search args, according to rfc1738:
+ * process the path. With proxy-noncanon set (by
+ * mod_proxy) we use the raw, unparsed uri
+ */
+ if (apr_table_get(r->notes, "proxy-nocanon")) {
+ path = url; /* this is the raw path */
+ }
+ else {
+ path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0,
+ r->proxyreq);
+ search = r->args;
+ if (search && *(ap_scan_vchar_obstext(search))) {
+ /*
+ * We have a raw control character or a ' ' in r->args.
+ * Correct encoding was missed.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10407)
+ "To be forwarded query string contains control "
+ "characters or spaces");
+ return HTTP_FORBIDDEN;
+ }
+ }
+ if (path == NULL)
+ return HTTP_BAD_REQUEST;
+
+ r->filename = apr_pstrcat(r->pool, "proxy:" BALANCER_PREFIX, host,
+ "/", path, (search) ? "?" : "", (search) ? search : "", NULL);
+
+ r->path_info = apr_pstrcat(r->pool, "/", path, NULL);
+
+ return OK;
+}
+
+static void init_balancer_members(apr_pool_t *p, server_rec *s,
+ proxy_balancer *balancer)
+{
+ int i;
+ proxy_worker **workers;
+
+ workers = (proxy_worker **)balancer->workers->elts;
+
+ for (i = 0; i < balancer->workers->nelts; i++) {
+ int worker_is_initialized;
+ proxy_worker *worker = *workers;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01158)
+ "Looking at %s -> %s initialized?", balancer->s->name,
+ ap_proxy_worker_name(p, worker));
+ worker_is_initialized = PROXY_WORKER_IS_INITIALIZED(worker);
+ if (!worker_is_initialized) {
+ ap_proxy_initialize_worker(worker, s, p);
+ }
+ ++workers;
+ }
+
+ /* Set default number of attempts to the number of
+ * workers.
+ */
+ if (!balancer->s->max_attempts_set && balancer->workers->nelts > 1) {
+ balancer->s->max_attempts = balancer->workers->nelts - 1;
+ balancer->s->max_attempts_set = 1;
+ }
+}
+
+/* Retrieve the parameter with the given name
+ * Something like 'JSESSIONID=12345...N'
+ */
+static char *get_path_param(apr_pool_t *pool, char *url,
+ const char *name, int scolon_sep)
+{
+ char *path = NULL;
+ char *pathdelims = "?&";
+
+ if (scolon_sep) {
+ pathdelims = ";?&";
+ }
+ for (path = strstr(url, name); path; path = strstr(path + 1, name)) {
+ path += strlen(name);
+ if (*path == '=') {
+ /*
+ * Session path was found, get its value
+ */
+ ++path;
+ if (*path) {
+ char *q;
+ path = apr_strtok(apr_pstrdup(pool, path), pathdelims, &q);
+ return path;
+ }
+ }
+ }
+ return NULL;
+}
+
+static char *get_cookie_param(request_rec *r, const char *name)
+{
+ const char *cookies;
+ const char *start_cookie;
+
+ if ((cookies = apr_table_get(r->headers_in, "Cookie"))) {
+ for (start_cookie = ap_strstr_c(cookies, name); start_cookie;
+ start_cookie = ap_strstr_c(start_cookie + 1, name)) {
+ if (start_cookie == cookies ||
+ start_cookie[-1] == ';' ||
+ start_cookie[-1] == ',' ||
+ isspace(start_cookie[-1])) {
+
+ start_cookie += strlen(name);
+ while(*start_cookie && isspace(*start_cookie))
+ ++start_cookie;
+ if (*start_cookie++ == '=' && *start_cookie) {
+ /*
+ * Session cookie was found, get its value
+ */
+ char *end_cookie, *cookie;
+ cookie = apr_pstrdup(r->pool, start_cookie);
+ if ((end_cookie = strchr(cookie, ';')) != NULL)
+ *end_cookie = '\0';
+ if((end_cookie = strchr(cookie, ',')) != NULL)
+ *end_cookie = '\0';
+ return cookie;
+ }
+ }
+ }
+ }
+ return NULL;
+}
+
+/* Find the worker that has the 'route' defined
+ */
+static proxy_worker *find_route_worker(proxy_balancer *balancer,
+ const char *route, request_rec *r,
+ int recursion)
+{
+ int i;
+ int checking_standby;
+ int checked_standby;
+
+ proxy_worker **workers;
+
+ checking_standby = checked_standby = 0;
+ while (!checked_standby) {
+ workers = (proxy_worker **)balancer->workers->elts;
+ for (i = 0; i < balancer->workers->nelts; i++, workers++) {
+ proxy_worker *worker = *workers;
+ if ( (checking_standby ? !PROXY_WORKER_IS_STANDBY(worker) : PROXY_WORKER_IS_STANDBY(worker)) )
+ continue;
+ if (*(worker->s->route) && strcmp(worker->s->route, route) == 0) {
+ if (PROXY_WORKER_IS_USABLE(worker)) {
+ return worker;
+ } else {
+ /*
+ * If the worker is in error state run
+ * retry on that worker. It will be marked as
+ * operational if the retry timeout is elapsed.
+ * The worker might still be unusable, but we try
+ * anyway.
+ */
+ ap_proxy_retry_worker_fn("BALANCER", worker, r->server);
+ if (PROXY_WORKER_IS_USABLE(worker)) {
+ return worker;
+ } else {
+ /*
+ * We have a worker that is unusable.
+ * It can be in error or disabled, but in case
+ * it has a redirection set use that redirection worker.
+ * This enables to safely remove the member from the
+ * balancer. Of course you will need some kind of
+ * session replication between those two remote.
+ * Also check that we haven't gone thru all the
+ * balancer members by means of redirects.
+ * This should avoid redirect cycles.
+ */
+ if ((*worker->s->redirect)
+ && (recursion < balancer->workers->nelts)) {
+ proxy_worker *rworker = NULL;
+ rworker = find_route_worker(balancer, worker->s->redirect,
+ r, recursion + 1);
+ /* Check if the redirect worker is usable */
+ if (rworker && !PROXY_WORKER_IS_USABLE(rworker)) {
+ /*
+ * If the worker is in error state run
+ * retry on that worker. It will be marked as
+ * operational if the retry timeout is elapsed.
+ * The worker might still be unusable, but we try
+ * anyway.
+ */
+ ap_proxy_retry_worker_fn("BALANCER", rworker, r->server);
+ }
+ if (rworker && PROXY_WORKER_IS_USABLE(rworker))
+ return rworker;
+ }
+ }
+ }
+ }
+ }
+ checked_standby = checking_standby++;
+ }
+ return NULL;
+}
+
+static proxy_worker *find_session_route(proxy_balancer *balancer,
+ request_rec *r,
+ char **route,
+ const char **sticky_used,
+ char **url)
+{
+ proxy_worker *worker = NULL;
+
+ if (!*balancer->s->sticky)
+ return NULL;
+ /* Try to find the sticky route inside url */
+ *route = get_path_param(r->pool, *url, balancer->s->sticky_path, balancer->s->scolonsep);
+ if (*route) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01159)
+ "Found value %s for stickysession %s",
+ *route, balancer->s->sticky_path);
+ *sticky_used = balancer->s->sticky_path;
+ }
+ else {
+ *route = get_cookie_param(r, balancer->s->sticky);
+ if (*route) {
+ *sticky_used = balancer->s->sticky;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01160)
+ "Found value %s for stickysession %s",
+ *route, balancer->s->sticky);
+ }
+ }
+ /*
+ * If we found a value for stickysession, find the first '.' (or whatever
+ * sticky_separator is set to) within. Everything after '.' (if present)
+ * is our route.
+ */
+ if ((*route) && (balancer->s->sticky_separator != 0) && ((*route = strchr(*route, balancer->s->sticky_separator)) != NULL ))
+ (*route)++;
+ if ((*route) && (**route)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01161) "Found route %s", *route);
+ /* We have a route in path or in cookie
+ * Find the worker that has this route defined.
+ */
+ worker = find_route_worker(balancer, *route, r, 1);
+ if (worker && strcmp(*route, worker->s->route)) {
+ /*
+ * Notice that the route of the worker chosen is different from
+ * the route supplied by the client.
+ */
+ apr_table_setn(r->subprocess_env, "BALANCER_ROUTE_CHANGED", "1");
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01162)
+ "Route changed from %s to %s",
+ *route, worker->s->route);
+ }
+ return worker;
+ }
+ else
+ return NULL;
+}
+
+static proxy_worker *find_best_worker(proxy_balancer *balancer,
+ request_rec *r)
+{
+ proxy_worker *candidate = NULL;
+ apr_status_t rv;
+
+#if APR_HAS_THREADS
+ if ((rv = PROXY_THREAD_LOCK(balancer)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01163)
+ "%s: Lock failed for find_best_worker()",
+ balancer->s->name);
+ return NULL;
+ }
+#endif
+
+ candidate = (*balancer->lbmethod->finder)(balancer, r);
+
+ if (candidate)
+ candidate->s->elected++;
+
+#if APR_HAS_THREADS
+ if ((rv = PROXY_THREAD_UNLOCK(balancer)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01164)
+ "%s: Unlock failed for find_best_worker()",
+ balancer->s->name);
+ }
+#endif
+
+ if (candidate == NULL) {
+ /* All the workers are in error state or disabled.
+ * If the balancer has a timeout sleep for a while
+ * and try again to find the worker. The chances are
+ * that some other thread will release a connection.
+ * By default the timeout is not set, and the server
+ * returns SERVER_BUSY.
+ */
+ if (balancer->s->timeout) {
+ /* XXX: This can perhaps be build using some
+ * smarter mechanism, like tread_cond.
+ * But since the statuses can came from
+ * different children, use the provided algo.
+ */
+ apr_interval_time_t timeout = balancer->s->timeout;
+ apr_interval_time_t step, tval = 0;
+ /* Set the timeout to 0 so that we don't
+ * end in infinite loop
+ */
+ balancer->s->timeout = 0;
+ step = timeout / 100;
+ while (tval < timeout) {
+ apr_sleep(step);
+ /* Try again */
+ if ((candidate = find_best_worker(balancer, r)))
+ break;
+ tval += step;
+ }
+ /* restore the timeout */
+ balancer->s->timeout = timeout;
+ }
+ }
+
+ return candidate;
+
+}
+
+static int rewrite_url(request_rec *r, proxy_worker *worker,
+ char **url)
+{
+ const char *scheme = strstr(*url, "://");
+ const char *path = NULL;
+
+ if (scheme)
+ path = ap_strchr_c(scheme + 3, '/');
+
+ /* we break the URL into host, port, uri */
+ if (!worker) {
+ return ap_proxyerror(r, HTTP_BAD_REQUEST, apr_pstrcat(r->pool,
+ "missing worker. URI cannot be parsed: ", *url,
+ NULL));
+ }
+
+ *url = apr_pstrcat(r->pool, worker->s->name_ex, path, NULL);
+
+ return OK;
+}
+
+static void force_recovery(proxy_balancer *balancer, server_rec *s)
+{
+ int i;
+ int ok = 0;
+ proxy_worker **worker;
+
+ worker = (proxy_worker **)balancer->workers->elts;
+ for (i = 0; i < balancer->workers->nelts; i++, worker++) {
+ if (!((*worker)->s->status & PROXY_WORKER_IN_ERROR)) {
+ ok = 1;
+ break;
+ }
+ else {
+ /* Try if we can recover */
+ ap_proxy_retry_worker_fn("BALANCER", *worker, s);
+ if (!((*worker)->s->status & PROXY_WORKER_IN_ERROR)) {
+ ok = 1;
+ break;
+ }
+ }
+ }
+ if (!ok && balancer->s->forcerecovery) {
+ /* If all workers are in error state force the recovery.
+ */
+ worker = (proxy_worker **)balancer->workers->elts;
+ for (i = 0; i < balancer->workers->nelts; i++, worker++) {
+ ++(*worker)->s->retries;
+ (*worker)->s->status &= ~PROXY_WORKER_IN_ERROR;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01165)
+ "%s: Forcing recovery for worker (%s:%d)",
+ balancer->s->name, (*worker)->s->hostname_ex,
+ (int)(*worker)->s->port);
+ }
+ }
+}
+
+static apr_status_t decrement_busy_count(void *worker_)
+{
+ proxy_worker *worker = worker_;
+
+ if (worker->s->busy) {
+ worker->s->busy--;
+ }
+
+ return APR_SUCCESS;
+}
+
+static int proxy_balancer_pre_request(proxy_worker **worker,
+ proxy_balancer **balancer,
+ request_rec *r,
+ proxy_server_conf *conf, char **url)
+{
+ int access_status;
+ proxy_worker *runtime;
+ char *route = NULL;
+ const char *sticky = NULL;
+ apr_status_t rv;
+
+ *worker = NULL;
+ /* Step 1: check if the url is for us
+ * The url we can handle starts with 'balancer://'
+ * If balancer is already provided skip the search
+ * for balancer, because this is failover attempt.
+ */
+ if (!*balancer &&
+ !(*balancer = ap_proxy_get_balancer(r->pool, conf, *url, 1)))
+ return DECLINED;
+
+ /* Step 2: Lock the LoadBalancer
+ * XXX: perhaps we need the process lock here
+ */
+#if APR_HAS_THREADS
+ if ((rv = PROXY_THREAD_LOCK(*balancer)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01166)
+ "%s: Lock failed for pre_request", (*balancer)->s->name);
+ return DECLINED;
+ }
+#endif
+
+ /* Step 3: force recovery */
+ force_recovery(*balancer, r->server);
+
+ /* Step 3.5: Update member list for the balancer */
+ /* TODO: Implement as provider! */
+ ap_proxy_sync_balancer(*balancer, r->server, conf);
+
+ /* Step 4: find the session route */
+ runtime = find_session_route(*balancer, r, &route, &sticky, url);
+ if (runtime) {
+ if ((*balancer)->lbmethod && (*balancer)->lbmethod->updatelbstatus) {
+ /* Call the LB implementation */
+ (*balancer)->lbmethod->updatelbstatus(*balancer, runtime, r->server);
+ }
+ else { /* Use the default one */
+ int i, total_factor = 0;
+ proxy_worker **workers;
+ /* We have a sticky load balancer
+ * Update the workers status
+ * so that even session routes get
+ * into account.
+ */
+ workers = (proxy_worker **)(*balancer)->workers->elts;
+ for (i = 0; i < (*balancer)->workers->nelts; i++) {
+ /* Take into calculation only the workers that are
+ * not in error state or not disabled.
+ */
+ if (PROXY_WORKER_IS_USABLE(*workers)) {
+ (*workers)->s->lbstatus += (*workers)->s->lbfactor;
+ total_factor += (*workers)->s->lbfactor;
+ }
+ workers++;
+ }
+ runtime->s->lbstatus -= total_factor;
+ }
+ runtime->s->elected++;
+
+ *worker = runtime;
+ }
+ else if (route && (*balancer)->s->sticky_force) {
+ int i, member_of = 0;
+ proxy_worker **workers;
+ /*
+ * We have a route provided that doesn't match the
+ * balancer name. See if the provider route is the
+ * member of the same balancer in which case return 503
+ */
+ workers = (proxy_worker **)(*balancer)->workers->elts;
+ for (i = 0; i < (*balancer)->workers->nelts; i++) {
+ if (*((*workers)->s->route) && strcmp((*workers)->s->route, route) == 0) {
+ member_of = 1;
+ break;
+ }
+ workers++;
+ }
+ if (member_of) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01167)
+ "%s: All workers are in error state for route (%s)",
+ (*balancer)->s->name, route);
+#if APR_HAS_THREADS
+ if ((rv = PROXY_THREAD_UNLOCK(*balancer)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01168)
+ "%s: Unlock failed for pre_request",
+ (*balancer)->s->name);
+ }
+#endif
+ return HTTP_SERVICE_UNAVAILABLE;
+ }
+ }
+
+#if APR_HAS_THREADS
+ if ((rv = PROXY_THREAD_UNLOCK(*balancer)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01169)
+ "%s: Unlock failed for pre_request",
+ (*balancer)->s->name);
+ }
+#endif
+ if (!*worker) {
+ runtime = find_best_worker(*balancer, r);
+ if (!runtime) {
+ if ((*balancer)->workers->nelts) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01170)
+ "%s: All workers are in error state",
+ (*balancer)->s->name);
+ } else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01171)
+ "%s: No workers in balancer",
+ (*balancer)->s->name);
+ }
+
+ return HTTP_SERVICE_UNAVAILABLE;
+ }
+ if (*(*balancer)->s->sticky && runtime) {
+ /*
+ * This balancer has sticky sessions and the client either has not
+ * supplied any routing information or all workers for this route
+ * including possible redirect and hotstandby workers are in error
+ * state, but we have found another working worker for this
+ * balancer where we can send the request. Thus notice that we have
+ * changed the route to the backend.
+ */
+ apr_table_setn(r->subprocess_env, "BALANCER_ROUTE_CHANGED", "1");
+ }
+ *worker = runtime;
+ }
+
+ (*worker)->s->busy++;
+ apr_pool_cleanup_register(r->pool, *worker, decrement_busy_count,
+ apr_pool_cleanup_null);
+
+ /* Add balancer/worker info to env. */
+ apr_table_setn(r->subprocess_env,
+ "BALANCER_NAME", (*balancer)->s->name);
+ apr_table_setn(r->subprocess_env,
+ "BALANCER_WORKER_NAME", (*worker)->s->name_ex);
+ apr_table_setn(r->subprocess_env,
+ "BALANCER_WORKER_ROUTE", (*worker)->s->route);
+
+ /* Rewrite the url from 'balancer://url'
+ * to the 'worker_scheme://worker_hostname[:worker_port]/url'
+ * This replaces the balancers fictional name with the
+ * real hostname of the elected worker.
+ */
+ access_status = rewrite_url(r, *worker, url);
+ /* Add the session route to request notes if present */
+ if (route) {
+ apr_table_setn(r->notes, "session-sticky", sticky);
+ apr_table_setn(r->notes, "session-route", route);
+
+ /* Add session info to env. */
+ apr_table_setn(r->subprocess_env,
+ "BALANCER_SESSION_STICKY", sticky);
+ apr_table_setn(r->subprocess_env,
+ "BALANCER_SESSION_ROUTE", route);
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01172)
+ "%s: worker (%s) rewritten to %s",
+ (*balancer)->s->name, (*worker)->s->name_ex, *url);
+
+ return access_status;
+}
+
+static int proxy_balancer_post_request(proxy_worker *worker,
+ proxy_balancer *balancer,
+ request_rec *r,
+ proxy_server_conf *conf)
+{
+
+ apr_status_t rv;
+
+#if APR_HAS_THREADS
+ if ((rv = PROXY_THREAD_LOCK(balancer)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01173)
+ "%s: Lock failed for post_request",
+ balancer->s->name);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+#endif
+
+ if (!apr_is_empty_array(balancer->errstatuses)
+ && !(worker->s->status & PROXY_WORKER_IGNORE_ERRORS)) {
+ int i;
+ for (i = 0; i < balancer->errstatuses->nelts; i++) {
+ int val = ((int *)balancer->errstatuses->elts)[i];
+ if (r->status == val) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01174)
+ "%s: Forcing worker (%s) into error state "
+ "due to status code %d matching 'failonstatus' "
+ "balancer parameter",
+ balancer->s->name, ap_proxy_worker_name(r->pool, worker),
+ val);
+ worker->s->status |= PROXY_WORKER_IN_ERROR;
+ worker->s->error_time = apr_time_now();
+ break;
+ }
+ }
+ }
+
+ if (balancer->failontimeout
+ && !(worker->s->status & PROXY_WORKER_IGNORE_ERRORS)
+ && (apr_table_get(r->notes, "proxy_timedout")) != NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02460)
+ "%s: Forcing worker (%s) into error state "
+ "due to timeout and 'failontimeout' parameter being set",
+ balancer->s->name, ap_proxy_worker_name(r->pool, worker));
+ worker->s->status |= PROXY_WORKER_IN_ERROR;
+ worker->s->error_time = apr_time_now();
+
+ }
+#if APR_HAS_THREADS
+ if ((rv = PROXY_THREAD_UNLOCK(balancer)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01175)
+ "%s: Unlock failed for post_request", balancer->s->name);
+ }
+#endif
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01176)
+ "proxy_balancer_post_request for (%s)", balancer->s->name);
+
+ return OK;
+}
+
+static void recalc_factors(proxy_balancer *balancer)
+{
+ int i;
+ proxy_worker **workers;
+
+
+ /* Recalculate lbfactors */
+ workers = (proxy_worker **)balancer->workers->elts;
+ /* Special case if there is only one worker its
+ * load factor will always be 100
+ */
+ if (balancer->workers->nelts == 1) {
+ (*workers)->s->lbstatus = (*workers)->s->lbfactor = 100;
+ return;
+ }
+ for (i = 0; i < balancer->workers->nelts; i++) {
+ /* Update the status entries */
+ workers[i]->s->lbstatus = workers[i]->s->lbfactor;
+ }
+}
+
+static apr_status_t lock_remove(void *data)
+{
+ int i;
+ proxy_balancer *balancer;
+ server_rec *s = data;
+ void *sconf = s->module_config;
+ proxy_server_conf *conf = (proxy_server_conf *) ap_get_module_config(sconf, &proxy_module);
+
+ balancer = (proxy_balancer *)conf->balancers->elts;
+ for (i = 0; i < conf->balancers->nelts; i++, balancer++) {
+ if (balancer->gmutex) {
+ apr_global_mutex_destroy(balancer->gmutex);
+ balancer->gmutex = NULL;
+ }
+ }
+ return(0);
+}
+
+/*
+ * Compute an ID for a vhost based on what makes it selected by requests.
+ * The second and more Host(s)/IP(s):port(s), and the ServerAlias(es) are
+ * optional (see make_servers_ids() below).
+ */
+ static const char *make_server_id(server_rec *s, apr_pool_t *p, int full)
+{
+ apr_md5_ctx_t md5_ctx;
+ unsigned char md5[APR_MD5_DIGESTSIZE];
+ char id[2 * APR_MD5_DIGESTSIZE + 1];
+ char host_ip[64]; /* for any IPv[46] string */
+ server_addr_rec *sar;
+ int i;
+
+ apr_md5_init(&md5_ctx);
+ for (sar = s->addrs; sar; sar = sar->next) {
+ host_ip[0] = '\0';
+ apr_sockaddr_ip_getbuf(host_ip, sizeof host_ip, sar->host_addr);
+ apr_md5_update(&md5_ctx, (void *)sar->virthost, strlen(sar->virthost));
+ apr_md5_update(&md5_ctx, (void *)host_ip, strlen(host_ip));
+ apr_md5_update(&md5_ctx, (void *)&sar->host_port,
+ sizeof(sar->host_port));
+ if (!full) {
+ break;
+ }
+ }
+ if (s->server_hostname) {
+ apr_md5_update(&md5_ctx, (void *)s->server_hostname,
+ strlen(s->server_hostname));
+ }
+ if (full) {
+ if (s->names) {
+ for (i = 0; i < s->names->nelts; ++i) {
+ const char *name = APR_ARRAY_IDX(s->names, i, char *);
+ apr_md5_update(&md5_ctx, (void *)name, strlen(name));
+ }
+ }
+ if (s->wild_names) {
+ for (i = 0; i < s->wild_names->nelts; ++i) {
+ const char *name = APR_ARRAY_IDX(s->wild_names, i, char *);
+ apr_md5_update(&md5_ctx, (void *)name, strlen(name));
+ }
+ }
+ }
+ apr_md5_final(md5, &md5_ctx);
+ ap_bin2hex(md5, APR_MD5_DIGESTSIZE, id);
+
+ return apr_pstrmemdup(p, id, sizeof(id) - 1);
+}
+
+/*
+ * First try to compute an unique ID for each vhost with minimal criteria,
+ * that is the first Host/IP:port and ServerName. For most cases this should
+ * be enough and avoids changing the ID unnecessarily across restart (or
+ * stop/start w.r.t. persisted files) for things that this module does not
+ * care about.
+ *
+ * But if it's not enough (collisions) do a second pass for the full monty,
+ * that is additionally the other Host(s)/IP(s):port(s) and ServerAlias(es).
+ *
+ * Finally, for pathological configs where this is still not enough, let's
+ * append a counter to duplicates, because we really want that ID to be unique
+ * even if the vhost will never be selected to handle requests at run time, at
+ * load time a duplicate may steal the original slotmems (depending on its
+ * balancers' configurations), see how mod_slotmem_shm reuses slots/files based
+ * solely on this ID and resets them if the sizes don't match.
+ */
+static apr_array_header_t *make_servers_ids(server_rec *main_s, apr_pool_t *p)
+{
+ server_rec *s = main_s;
+ apr_array_header_t *ids = apr_array_make(p, 10, sizeof(const char *));
+ apr_hash_t *dups = apr_hash_make(p);
+ int idx, *dup, full_monty = 0;
+ const char *id;
+
+ for (idx = 0, s = main_s; s; s = s->next, ++idx) {
+ id = make_server_id(s, p, 0);
+ dup = apr_hash_get(dups, id, APR_HASH_KEY_STRING);
+ apr_hash_set(dups, id, APR_HASH_KEY_STRING,
+ apr_pmemdup(p, &idx, sizeof(int)));
+ if (dup) {
+ full_monty = 1;
+ APR_ARRAY_IDX(ids, *dup, const char *) = NULL;
+ APR_ARRAY_PUSH(ids, const char *) = NULL;
+ }
+ else {
+ APR_ARRAY_PUSH(ids, const char *) = id;
+ }
+ }
+ if (full_monty) {
+ apr_hash_clear(dups);
+ for (idx = 0, s = main_s; s; s = s->next, ++idx) {
+ id = APR_ARRAY_IDX(ids, idx, const char *);
+ if (id) {
+ /* Preserve non-duplicates */
+ continue;
+ }
+ id = make_server_id(s, p, 1);
+ if (apr_hash_get(dups, id, APR_HASH_KEY_STRING)) {
+ id = apr_psprintf(p, "%s_%x", id, idx);
+ }
+ else {
+ apr_hash_set(dups, id, APR_HASH_KEY_STRING, (void *)-1);
+ }
+ APR_ARRAY_IDX(ids, idx, const char *) = id;
+ }
+ }
+
+ return ids;
+}
+
+/* post_config hook: */
+static int balancer_post_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ apr_status_t rv;
+ proxy_server_conf *conf;
+ ap_slotmem_instance_t *new = NULL;
+ apr_time_t tstamp;
+ apr_array_header_t *ids;
+ int idx;
+
+ /* balancer_post_config() will be called twice during startup. So, don't
+ * set up the static data the 1st time through. */
+ if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) {
+ return OK;
+ }
+
+ ap_proxy_retry_worker_fn =
+ APR_RETRIEVE_OPTIONAL_FN(ap_proxy_retry_worker);
+ if (!ap_proxy_retry_worker_fn) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02230)
+ "mod_proxy must be loaded for mod_proxy_balancer");
+ return !OK;
+ }
+
+ /*
+ * Get slotmem setups
+ */
+ storage = ap_lookup_provider(AP_SLOTMEM_PROVIDER_GROUP, "shm",
+ AP_SLOTMEM_PROVIDER_VERSION);
+ if (!storage) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(01177)
+ "Failed to lookup provider 'shm' for '%s': is "
+ "mod_slotmem_shm loaded??",
+ AP_SLOTMEM_PROVIDER_GROUP);
+ return !OK;
+ }
+
+ ids = make_servers_ids(s, ptemp);
+
+ tstamp = apr_time_now();
+ /*
+ * Go thru each Vhost and create the shared mem slotmem for
+ * each balancer's workers
+ */
+ for (idx = 0; s; ++idx) {
+ int i,j;
+ const char *id;
+ proxy_balancer *balancer;
+ ap_slotmem_type_t type;
+ void *sconf = s->module_config;
+ conf = (proxy_server_conf *)ap_get_module_config(sconf, &proxy_module);
+ /*
+ * During create_proxy_config() we created a dummy id. Now that
+ * we have identifying info, we can create the real id
+ */
+ id = APR_ARRAY_IDX(ids, idx, const char *);
+ conf->id = apr_psprintf(pconf, "p%x",
+ ap_proxy_hashfunc(id, PROXY_HASHFUNC_DEFAULT));
+ if (conf->bslot) {
+ /* Shared memory already created for this proxy_server_conf.
+ */
+ s = s->next;
+ continue;
+ }
+ if (conf->bal_persist) {
+ type = AP_SLOTMEM_TYPE_PERSIST | AP_SLOTMEM_TYPE_CLEARINUSE;
+ } else {
+ type = 0;
+ }
+ if (conf->balancers->nelts) {
+ conf->max_balancers = conf->balancers->nelts + conf->bgrowth;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01178) "Doing balancers create: %d, %d (%d)",
+ (int)ALIGNED_PROXY_BALANCER_SHARED_SIZE,
+ (int)conf->balancers->nelts, conf->max_balancers);
+
+ rv = storage->create(&new, conf->id,
+ ALIGNED_PROXY_BALANCER_SHARED_SIZE,
+ conf->max_balancers, type, pconf);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s, APLOGNO(01179) "balancer slotmem_create failed");
+ return !OK;
+ }
+ conf->bslot = new;
+ }
+ conf->storage = storage;
+
+ /* Initialize shared scoreboard data */
+ balancer = (proxy_balancer *)conf->balancers->elts;
+ for (i = 0; i < conf->balancers->nelts; i++, balancer++) {
+ proxy_worker **workers;
+ proxy_worker *worker;
+ proxy_balancer_shared *bshm;
+ const char *sname;
+ unsigned int index;
+
+ /* now that we have the right id, we need to redo the sname field */
+ ap_pstr2_alnum(pconf, balancer->s->name + sizeof(BALANCER_PREFIX) - 1,
+ &sname);
+ sname = apr_pstrcat(pconf, conf->id, "_", sname, NULL);
+ PROXY_STRNCPY(balancer->s->sname, sname); /* We know this will succeed */
+
+ balancer->max_workers = balancer->workers->nelts + balancer->growth;
+ /* Create global mutex */
+ rv = ap_global_mutex_create(&(balancer->gmutex), NULL, balancer_mutex_type,
+ balancer->s->sname, s, pconf, 0);
+ if (rv != APR_SUCCESS || !balancer->gmutex) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s, APLOGNO(01180)
+ "mutex creation of %s : %s failed", balancer_mutex_type,
+ balancer->s->sname);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ apr_pool_cleanup_register(pconf, (void *)s, lock_remove,
+ apr_pool_cleanup_null);
+
+ /* setup shm for balancers */
+ bshm = ap_proxy_find_balancershm(storage, conf->bslot, balancer, &index);
+ if (bshm) {
+ if ((rv = storage->fgrab(conf->bslot, index)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s, APLOGNO(02408) "balancer slotmem_fgrab failed");
+ return !OK;
+ }
+ }
+ else {
+ if ((rv = storage->grab(conf->bslot, &index)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s, APLOGNO(01181) "balancer slotmem_grab failed");
+ return !OK;
+ }
+ if ((rv = storage->dptr(conf->bslot, index, (void *)&bshm)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s, APLOGNO(01182) "balancer slotmem_dptr failed");
+ return !OK;
+ }
+ }
+ if ((rv = ap_proxy_share_balancer(balancer, bshm, index)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s, APLOGNO(01183) "Cannot share balancer");
+ return !OK;
+ }
+
+ /* create slotmem slots for workers */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01184) "Doing workers create: %s (%s), %d, %d [%u]",
+ balancer->s->name, balancer->s->sname,
+ (int)ALIGNED_PROXY_WORKER_SHARED_SIZE,
+ (int)balancer->max_workers, i);
+
+ rv = storage->create(&new, balancer->s->sname,
+ ALIGNED_PROXY_WORKER_SHARED_SIZE,
+ balancer->max_workers, type, pconf);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s, APLOGNO(01185) "worker slotmem_create failed");
+ return !OK;
+ }
+ balancer->wslot = new;
+ balancer->storage = storage;
+
+ /* sync all timestamps */
+ balancer->wupdated = balancer->s->wupdated = tstamp;
+
+ /* now go thru each worker */
+ workers = (proxy_worker **)balancer->workers->elts;
+ for (j = 0; j < balancer->workers->nelts; j++, workers++) {
+ proxy_worker_shared *shm;
+
+ worker = *workers;
+
+ shm = ap_proxy_find_workershm(storage, balancer->wslot, worker, &index);
+ if (shm) {
+ if ((rv = storage->fgrab(balancer->wslot, index)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s, APLOGNO(02409) "worker slotmem_fgrab failed");
+ return !OK;
+ }
+ }
+ else {
+ if ((rv = storage->grab(balancer->wslot, &index)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s, APLOGNO(01186) "worker slotmem_grab failed");
+ return !OK;
+
+ }
+ if ((rv = storage->dptr(balancer->wslot, index, (void *)&shm)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s, APLOGNO(01187) "worker slotmem_dptr failed");
+ return !OK;
+ }
+ }
+ if ((rv = ap_proxy_share_worker(worker, shm, index)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s, APLOGNO(01188) "Cannot share worker");
+ return !OK;
+ }
+ worker->s->updated = tstamp;
+ }
+ if (conf->bal_persist) {
+ /* We could have just read-in a persisted config. Force a sync. */
+ balancer->wupdated--;
+ ap_proxy_sync_balancer(balancer, s, conf);
+ }
+ }
+ s = s->next;
+ }
+
+ return OK;
+}
+
+static void create_radio(const char *name, unsigned int flag, request_rec *r)
+{
+ ap_rvputs(r, "<td><label for='", name, "1'>On</label> <input name='", name, "' id='", name, "1' value='1' type=radio", NULL);
+ if (flag)
+ ap_rputs(" checked", r);
+ ap_rvputs(r, "> <br/> <label for='", name, "0'>Off</label> <input name='", name, "' id='", name, "0' value='0' type=radio", NULL);
+ if (!flag)
+ ap_rputs(" checked", r);
+ ap_rputs("></td>\n", r);
+}
+
+static void push2table(const char *input, apr_table_t *params,
+ const char *allowed[], apr_pool_t *p)
+{
+ char *args;
+ char *tok, *val;
+ char *key;
+
+ if (input == NULL) {
+ return;
+ }
+ args = apr_pstrdup(p, input);
+
+ key = apr_strtok(args, "&", &tok);
+ while (key) {
+ val = strchr(key, '=');
+ if (val) {
+ *val++ = '\0';
+ }
+ else {
+ val = "";
+ }
+ ap_unescape_url(key);
+ ap_unescape_url(val);
+ /* hcuri, worker name, balancer name, at least are escaped when building the form, so twice */
+ ap_unescape_url(val);
+ if (allowed == NULL) { /* allow all */
+ apr_table_set(params, key, val);
+ }
+ else {
+ const char **ok = allowed;
+ while (*ok) {
+ if (strcmp(*ok, key) == 0) {
+ apr_table_set(params, key, val);
+ break;
+ }
+ ok++;
+ }
+ }
+ key = apr_strtok(NULL, "&", &tok);
+ }
+}
+
+/* Returns non-zero if the Referer: header value passed matches the
+ * host of the request. */
+static int safe_referer(request_rec *r, const char *ref)
+{
+ apr_uri_t uri;
+
+ if (apr_uri_parse(r->pool, ref, &uri) || !uri.hostname)
+ return 0;
+
+ return strcasecmp(uri.hostname, ap_get_server_name(r)) == 0;
+}
+
+/*
+ * Process the paramters and add or update the worker of the
+ * balancer. Must only be called if the nonce has been validated to
+ * match, to avoid XSS attacks.
+ */
+static int balancer_process_balancer_worker(request_rec *r, proxy_server_conf *conf,
+ proxy_balancer *bsel,
+ proxy_worker *wsel,
+ apr_table_t *params)
+
+{
+ apr_status_t rv;
+ /* First set the params */
+ if (wsel) {
+ const char *val;
+ int was_usable = PROXY_WORKER_IS_USABLE(wsel);
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01192) "settings worker params");
+
+ if ((val = apr_table_get(params, "w_lf"))) {
+ int ival;
+ double fval = atof(val);
+ ival = fval * 100.0;
+ if (ival >= 100 && ival <= 10000) {
+ wsel->s->lbfactor = ival;
+ if (bsel)
+ recalc_factors(bsel);
+ }
+ }
+ if ((val = apr_table_get(params, "w_wr"))) {
+ if (strlen(val) && strlen(val) < sizeof(wsel->s->route))
+ strcpy(wsel->s->route, val);
+ else
+ *wsel->s->route = '\0';
+ }
+ if ((val = apr_table_get(params, "w_rr"))) {
+ if (strlen(val) && strlen(val) < sizeof(wsel->s->redirect))
+ strcpy(wsel->s->redirect, val);
+ else
+ *wsel->s->redirect = '\0';
+ }
+ /*
+ * TODO: Look for all 'w_status_#' keys and then loop thru
+ * on that # character, since the character == the flag
+ */
+ if ((val = apr_table_get(params, "w_status_I"))) {
+ ap_proxy_set_wstatus(PROXY_WORKER_IGNORE_ERRORS_FLAG, atoi(val), wsel);
+ }
+ if ((val = apr_table_get(params, "w_status_N"))) {
+ ap_proxy_set_wstatus(PROXY_WORKER_DRAIN_FLAG, atoi(val), wsel);
+ }
+ if ((val = apr_table_get(params, "w_status_D"))) {
+ ap_proxy_set_wstatus(PROXY_WORKER_DISABLED_FLAG, atoi(val), wsel);
+ }
+ if ((val = apr_table_get(params, "w_status_H"))) {
+ ap_proxy_set_wstatus(PROXY_WORKER_HOT_STANDBY_FLAG, atoi(val), wsel);
+ }
+ if ((val = apr_table_get(params, "w_status_R"))) {
+ ap_proxy_set_wstatus(PROXY_WORKER_HOT_SPARE_FLAG, atoi(val), wsel);
+ }
+ if ((val = apr_table_get(params, "w_status_S"))) {
+ ap_proxy_set_wstatus(PROXY_WORKER_STOPPED_FLAG, atoi(val), wsel);
+ }
+ if ((val = apr_table_get(params, "w_status_C"))) {
+ ap_proxy_set_wstatus(PROXY_WORKER_HC_FAIL_FLAG, atoi(val), wsel);
+ }
+ if ((val = apr_table_get(params, "w_ls"))) {
+ int ival = atoi(val);
+ if (ival >= 0 && ival <= 99) {
+ wsel->s->lbset = ival;
+ }
+ }
+ if ((val = apr_table_get(params, "w_hi"))) {
+ apr_interval_time_t hci;
+ if (ap_timeout_parameter_parse(val, &hci, "ms") == APR_SUCCESS) {
+ if (hci >= AP_WD_TM_SLICE) {
+ wsel->s->interval = hci;
+ }
+ }
+ }
+ if ((val = apr_table_get(params, "w_hp"))) {
+ int ival = atoi(val);
+ if (ival >= 1) {
+ wsel->s->passes = ival;
+ }
+ }
+ if ((val = apr_table_get(params, "w_hf"))) {
+ int ival = atoi(val);
+ if (ival >= 1) {
+ wsel->s->fails = ival;
+ }
+ }
+ if ((val = apr_table_get(params, "w_hm"))) {
+ proxy_hcmethods_t *method = proxy_hcmethods;
+ for (; method->name; method++) {
+ if (!ap_cstr_casecmp(method->name, val) && method->implemented)
+ wsel->s->method = method->method;
+ }
+ }
+ if ((val = apr_table_get(params, "w_hu"))) {
+ if (strlen(val) && strlen(val) < sizeof(wsel->s->hcuri))
+ strcpy(wsel->s->hcuri, val);
+ else
+ *wsel->s->hcuri = '\0';
+ }
+ if (hc_valid_expr_f && (val = apr_table_get(params, "w_he"))) {
+ if (strlen(val) && hc_valid_expr_f(r, val) && strlen(val) < sizeof(wsel->s->hcexpr))
+ strcpy(wsel->s->hcexpr, val);
+ else
+ *wsel->s->hcexpr = '\0';
+ }
+ /* If the health check method doesn't support an expr, then null it */
+ if (wsel->s->method == NONE || wsel->s->method == TCP || wsel->s->method == CPING) {
+ *wsel->s->hcexpr = '\0';
+ }
+ /* if enabling, we need to reset all lb params */
+ if (bsel && !was_usable && PROXY_WORKER_IS_USABLE(wsel)) {
+ bsel->s->need_reset = 1;
+ }
+
+ }
+
+ if (bsel) {
+ const char *val;
+ int ival;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01193)
+ "settings balancer params");
+ if ((val = apr_table_get(params, "b_lbm"))) {
+ if ((strlen(val) < (sizeof(bsel->s->lbpname)-1)) &&
+ strcmp(val, bsel->s->lbpname)) {
+ proxy_balancer_method *lbmethod;
+ lbmethod = ap_lookup_provider(PROXY_LBMETHOD, val, "0");
+ if (lbmethod) {
+ PROXY_STRNCPY(bsel->s->lbpname, val);
+ bsel->lbmethod = lbmethod;
+ bsel->s->wupdated = apr_time_now();
+ bsel->s->need_reset = 1;
+ }
+ }
+ }
+ if ((val = apr_table_get(params, "b_tmo"))) {
+ ival = atoi(val);
+ if (ival >= 0 && ival <= 7200) { /* 2 hrs enuff? */
+ bsel->s->timeout = apr_time_from_sec(ival);
+ }
+ }
+ if ((val = apr_table_get(params, "b_max"))) {
+ ival = atoi(val);
+ if (ival >= 0 && ival <= 99) {
+ bsel->s->max_attempts = ival;
+ }
+ }
+ if ((val = apr_table_get(params, "b_sforce"))) {
+ ival = atoi(val);
+ bsel->s->sticky_force = (ival != 0);
+ }
+ if ((val = apr_table_get(params, "b_ss")) && *val) {
+ if (strlen(val) < (sizeof(bsel->s->sticky_path)-1)) {
+ if (*val == '-' && *(val+1) == '\0')
+ *bsel->s->sticky_path = *bsel->s->sticky = '\0';
+ else {
+ char *path;
+ PROXY_STRNCPY(bsel->s->sticky_path, val);
+ PROXY_STRNCPY(bsel->s->sticky, val);
+
+ if ((path = strchr((char *)bsel->s->sticky, '|'))) {
+ *path++ = '\0';
+ PROXY_STRNCPY(bsel->s->sticky_path, path);
+ }
+ }
+ }
+ }
+ if ((val = apr_table_get(params, "b_wyes")) &&
+ (*val == '1' && *(val+1) == '\0') &&
+ (val = apr_table_get(params, "b_nwrkr"))) {
+ char *ret;
+ proxy_worker *nworker;
+ nworker = ap_proxy_get_worker(r->pool, bsel, conf, val);
+ if (!nworker && storage->num_free_slots(bsel->wslot)) {
+#if APR_HAS_THREADS
+ if ((rv = PROXY_GLOBAL_LOCK(bsel)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01194)
+ "%s: Lock failed for adding worker",
+ bsel->s->name);
+ }
+#endif
+ ret = ap_proxy_define_worker(conf->pool, &nworker, bsel, conf, val, 0);
+ if (!ret) {
+ unsigned int index;
+ proxy_worker_shared *shm;
+ PROXY_COPY_CONF_PARAMS(nworker, conf);
+ if ((rv = storage->grab(bsel->wslot, &index)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_EMERG, rv, r, APLOGNO(01195)
+ "worker slotmem_grab failed");
+#if APR_HAS_THREADS
+ if ((rv = PROXY_GLOBAL_UNLOCK(bsel)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01196)
+ "%s: Unlock failed for adding worker",
+ bsel->s->name);
+ }
+#endif
+ return HTTP_BAD_REQUEST;
+ }
+ if ((rv = storage->dptr(bsel->wslot, index, (void *)&shm)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_EMERG, rv, r, APLOGNO(01197)
+ "worker slotmem_dptr failed");
+#if APR_HAS_THREADS
+ if ((rv = PROXY_GLOBAL_UNLOCK(bsel)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01198)
+ "%s: Unlock failed for adding worker",
+ bsel->s->name);
+ }
+#endif
+ return HTTP_BAD_REQUEST;
+ }
+ if ((rv = ap_proxy_share_worker(nworker, shm, index)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_EMERG, rv, r, APLOGNO(01199)
+ "Cannot share worker");
+#if APR_HAS_THREADS
+ if ((rv = PROXY_GLOBAL_UNLOCK(bsel)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01200)
+ "%s: Unlock failed for adding worker",
+ bsel->s->name);
+ }
+#endif
+ return HTTP_BAD_REQUEST;
+ }
+ if ((rv = ap_proxy_initialize_worker(nworker, r->server, conf->pool)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_EMERG, rv, r, APLOGNO(01201)
+ "Cannot init worker");
+#if APR_HAS_THREADS
+ if ((rv = PROXY_GLOBAL_UNLOCK(bsel)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01202)
+ "%s: Unlock failed for adding worker",
+ bsel->s->name);
+ }
+#endif
+ return HTTP_BAD_REQUEST;
+ }
+ /* sync all timestamps */
+ bsel->wupdated = bsel->s->wupdated = nworker->s->updated = apr_time_now();
+ /* by default, all new workers are disabled */
+ ap_proxy_set_wstatus(PROXY_WORKER_DISABLED_FLAG, 1, nworker);
+ } else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10163)
+ "%s: failed to add worker %s",
+ bsel->s->name, val);
+#if APR_HAS_THREADS
+ PROXY_GLOBAL_UNLOCK(bsel);
+#endif
+ return HTTP_BAD_REQUEST;
+ }
+#if APR_HAS_THREADS
+ if ((rv = PROXY_GLOBAL_UNLOCK(bsel)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01203)
+ "%s: Unlock failed for adding worker",
+ bsel->s->name);
+ }
+#endif
+ } else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10164)
+ "%s: failed to add worker %s",
+ bsel->s->name, val);
+ return HTTP_BAD_REQUEST;
+ }
+
+ }
+
+ }
+ return APR_SUCCESS;
+}
+
+/*
+ * Process a request for balancer or worker management from another module
+ */
+static apr_status_t balancer_manage(request_rec *r, apr_table_t *params)
+{
+ void *sconf;
+ proxy_server_conf *conf;
+ proxy_balancer *bsel = NULL;
+ proxy_worker *wsel = NULL;
+ const char *name;
+ sconf = r->server->module_config;
+ conf = (proxy_server_conf *) ap_get_module_config(sconf, &proxy_module);
+
+ /* Process the parameters */
+ if ((name = apr_table_get(params, "b"))) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "balancer_manage "
+ "balancer: %s", name);
+ bsel = ap_proxy_get_balancer(r->pool, conf,
+ apr_pstrcat(r->pool, BALANCER_PREFIX, name, NULL), 0);
+ }
+
+ if ((name = apr_table_get(params, "w"))) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "balancer_manage "
+ "worker: %s", name);
+ wsel = ap_proxy_get_worker(r->pool, bsel, conf, name);
+ }
+ if (bsel) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "balancer_manage "
+ "balancer: %s", bsel->s->name);
+ return(balancer_process_balancer_worker(r, conf, bsel, wsel, params));
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "balancer_manage failed: "
+ "No balancer!");
+ return HTTP_BAD_REQUEST;
+}
+
+/*
+ * builds the page and links to configure via HTLM or XML.
+ */
+static void balancer_display_page(request_rec *r, proxy_server_conf *conf,
+ proxy_balancer *bsel,
+ proxy_worker *wsel,
+ int usexml)
+{
+ const char *action;
+ proxy_balancer *balancer;
+ proxy_worker *worker;
+ proxy_worker **workers;
+ int i, n;
+ action = ap_construct_url(r->pool, r->uri, r);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01204) "genning page");
+
+ if (usexml) {
+ char date[APR_RFC822_DATE_LEN];
+ ap_set_content_type(r, "text/xml");
+ ap_rputs("<?xml version='1.0' encoding='UTF-8' ?>\n", r);
+ ap_rputs("<httpd:manager xmlns:httpd='http://httpd.apache.org'>\n", r);
+ ap_rputs(" <httpd:balancers>\n", r);
+ balancer = (proxy_balancer *)conf->balancers->elts;
+ for (i = 0; i < conf->balancers->nelts; i++) {
+ ap_rputs(" <httpd:balancer>\n", r);
+ /* Start proxy_balancer */
+ ap_rvputs(r, " <httpd:name>", balancer->s->name, "</httpd:name>\n", NULL);
+ if (*balancer->s->sticky) {
+ ap_rvputs(r, " <httpd:stickysession>", ap_escape_html(r->pool, balancer->s->sticky),
+ "</httpd:stickysession>\n", NULL);
+ ap_rprintf(r,
+ " <httpd:nofailover>%s</httpd:nofailover>\n",
+ (balancer->s->sticky_force ? "On" : "Off"));
+ }
+ ap_rprintf(r,
+ " <httpd:timeout>%" APR_TIME_T_FMT "</httpd:timeout>",
+ apr_time_sec(balancer->s->timeout));
+ if (balancer->s->max_attempts_set) {
+ ap_rprintf(r,
+ " <httpd:maxattempts>%d</httpd:maxattempts>\n",
+ balancer->s->max_attempts);
+ }
+ ap_rvputs(r, " <httpd:lbmethod>", balancer->lbmethod->name,
+ "</httpd:lbmethod>\n", NULL);
+ if (*balancer->s->sticky) {
+ ap_rprintf(r,
+ " <httpd:scolonpathdelim>%s</httpd:scolonpathdelim>\n",
+ (balancer->s->scolonsep ? "On" : "Off"));
+ }
+ /* End proxy_balancer */
+ ap_rputs(" <httpd:workers>\n", r);
+ workers = (proxy_worker **)balancer->workers->elts;
+ for (n = 0; n < balancer->workers->nelts; n++) {
+ worker = *workers;
+ /* Start proxy_worker */
+ ap_rputs(" <httpd:worker>\n", r);
+ ap_rvputs(r, " <httpd:name>", ap_proxy_worker_name(r->pool, worker),
+ "</httpd:name>\n", NULL);
+ ap_rvputs(r, " <httpd:scheme>", worker->s->scheme,
+ "</httpd:scheme>\n", NULL);
+ ap_rvputs(r, " <httpd:hostname>", worker->s->hostname_ex,
+ "</httpd:hostname>\n", NULL);
+ ap_rprintf(r, " <httpd:loadfactor>%.2f</httpd:loadfactor>\n",
+ (float)(worker->s->lbfactor)/100.0);
+ ap_rprintf(r,
+ " <httpd:port>%d</httpd:port>\n",
+ worker->s->port);
+ ap_rprintf(r, " <httpd:min>%d</httpd:min>\n",
+ worker->s->min);
+ ap_rprintf(r, " <httpd:smax>%d</httpd:smax>\n",
+ worker->s->smax);
+ ap_rprintf(r, " <httpd:max>%d</httpd:max>\n",
+ worker->s->hmax);
+ ap_rprintf(r,
+ " <httpd:ttl>%" APR_TIME_T_FMT "</httpd:ttl>\n",
+ apr_time_sec(worker->s->ttl));
+ if (worker->s->timeout_set) {
+ ap_rprintf(r,
+ " <httpd:timeout>%" APR_TIME_T_FMT "</httpd:timeout>\n",
+ apr_time_sec(worker->s->timeout));
+ }
+ if (worker->s->acquire_set) {
+ ap_rprintf(r,
+ " <httpd:acquire>%" APR_TIME_T_FMT "</httpd:acquire>\n",
+ apr_time_msec(worker->s->acquire));
+ }
+ if (worker->s->recv_buffer_size_set) {
+ ap_rprintf(r,
+ " <httpd:recv_buffer_size>%" APR_SIZE_T_FMT "</httpd:recv_buffer_size>\n",
+ worker->s->recv_buffer_size);
+ }
+ if (worker->s->io_buffer_size_set) {
+ ap_rprintf(r,
+ " <httpd:io_buffer_size>%" APR_SIZE_T_FMT "</httpd:io_buffer_size>\n",
+ worker->s->io_buffer_size);
+ }
+ if (worker->s->keepalive_set) {
+ ap_rprintf(r,
+ " <httpd:keepalive>%s</httpd:keepalive>\n",
+ (worker->s->keepalive ? "On" : "Off"));
+ }
+ /* Begin proxy_worker_stat */
+ ap_rputs(" <httpd:status>", r);
+ ap_rputs(ap_proxy_parse_wstatus(r->pool, worker), r);
+ ap_rputs("</httpd:status>\n", r);
+ if ((worker->s->error_time > 0) && apr_rfc822_date(date, worker->s->error_time) == APR_SUCCESS) {
+ ap_rvputs(r, " <httpd:error_time>", date,
+ "</httpd:error_time>\n", NULL);
+ }
+ ap_rprintf(r,
+ " <httpd:retries>%d</httpd:retries>\n",
+ worker->s->retries);
+ ap_rprintf(r,
+ " <httpd:lbstatus>%d</httpd:lbstatus>\n",
+ worker->s->lbstatus);
+ ap_rprintf(r,
+ " <httpd:loadfactor>%.2f</httpd:loadfactor>\n",
+ (float)(worker->s->lbfactor)/100.0);
+ ap_rprintf(r,
+ " <httpd:transferred>%" APR_OFF_T_FMT "</httpd:transferred>\n",
+ worker->s->transferred);
+ ap_rprintf(r,
+ " <httpd:read>%" APR_OFF_T_FMT "</httpd:read>\n",
+ worker->s->read);
+ ap_rprintf(r,
+ " <httpd:elected>%" APR_SIZE_T_FMT "</httpd:elected>\n",
+ worker->s->elected);
+ ap_rvputs(r, " <httpd:route>",
+ ap_escape_html(r->pool, worker->s->route),
+ "</httpd:route>\n", NULL);
+ ap_rvputs(r, " <httpd:redirect>",
+ ap_escape_html(r->pool, worker->s->redirect),
+ "</httpd:redirect>\n", NULL);
+ ap_rprintf(r,
+ " <httpd:busy>%" APR_SIZE_T_FMT "</httpd:busy>\n",
+ worker->s->busy);
+ ap_rprintf(r, " <httpd:lbset>%d</httpd:lbset>\n",
+ worker->s->lbset);
+ /* End proxy_worker_stat */
+ if (!ap_cstr_casecmp(worker->s->scheme, "ajp")) {
+ ap_rputs(" <httpd:flushpackets>", r);
+ switch (worker->s->flush_packets) {
+ case flush_off:
+ ap_rputs("Off", r);
+ break;
+ case flush_on:
+ ap_rputs("On", r);
+ break;
+ case flush_auto:
+ ap_rputs("Auto", r);
+ break;
+ }
+ ap_rputs("</httpd:flushpackets>\n", r);
+ if (worker->s->flush_packets == flush_auto) {
+ ap_rprintf(r,
+ " <httpd:flushwait>%d</httpd:flushwait>\n",
+ worker->s->flush_wait);
+ }
+ if (worker->s->ping_timeout_set) {
+ ap_rprintf(r,
+ " <httpd:ping>%" APR_TIME_T_FMT "</httpd:ping>",
+ apr_time_msec(worker->s->ping_timeout));
+ }
+ }
+ if (worker->s->disablereuse_set) {
+ ap_rprintf(r,
+ " <httpd:disablereuse>%s</httpd:disablereuse>\n",
+ (worker->s->disablereuse ? "On" : "Off"));
+ }
+ if (worker->s->conn_timeout_set) {
+ ap_rprintf(r,
+ " <httpd:connectiontimeout>%" APR_TIME_T_FMT "</httpd:connectiontimeout>\n",
+ apr_time_msec(worker->s->conn_timeout));
+ }
+ if (worker->s->retry_set) {
+ ap_rprintf(r,
+ " <httpd:retry>%" APR_TIME_T_FMT "</httpd:retry>\n",
+ apr_time_sec(worker->s->retry));
+ }
+ ap_rputs(" </httpd:worker>\n", r);
+ ++workers;
+ }
+ ap_rputs(" </httpd:workers>\n", r);
+ ap_rputs(" </httpd:balancer>\n", r);
+ ++balancer;
+ }
+ ap_rputs(" </httpd:balancers>\n", r);
+ ap_rputs("</httpd:manager>", r);
+ }
+ else {
+ ap_set_content_type(r, "text/html; charset=ISO-8859-1");
+ ap_rputs(DOCTYPE_HTML_3_2
+ "<html><head><title>Balancer Manager</title>\n", r);
+ ap_rputs("<style type='text/css'>\n"
+ "table {\n"
+ " border-width: 1px;\n"
+ " border-spacing: 3px;\n"
+ " border-style: solid;\n"
+ " border-color: gray;\n"
+ " border-collapse: collapse;\n"
+ " background-color: white;\n"
+ " text-align: center;\n"
+ "}\n"
+ "th {\n"
+ " border-width: 1px;\n"
+ " padding: 2px;\n"
+ " border-style: dotted;\n"
+ " border-color: gray;\n"
+ " background-color: lightgray;\n"
+ " text-align: center;\n"
+ "}\n"
+ "td {\n"
+ " border-width: 1px;\n"
+ " padding: 2px;\n"
+ " border-style: dotted;\n"
+ " border-color: gray;\n"
+ " background-color: white;\n"
+ " text-align: center;\n"
+ "}\n"
+ "</style>\n</head>\n", r);
+ ap_rputs("<body><h1>Load Balancer Manager for ", r);
+ ap_rvputs(r, ap_escape_html(r->pool, ap_get_server_name(r)),
+ "</h1>\n\n", NULL);
+ ap_rvputs(r, "<dl><dt>Server Version: ",
+ ap_get_server_description(), "</dt>\n", NULL);
+ ap_rvputs(r, "<dt>Server Built: ",
+ ap_get_server_built(), "</dt>\n", NULL);
+ ap_rvputs(r, "<dt>Balancer changes will ", conf->bal_persist ? "" : "NOT ",
+ "be persisted on restart.</dt>", NULL);
+ ap_rvputs(r, "<dt>Balancers are ", conf->inherit ? "" : "NOT ",
+ "inherited from main server.</dt>", NULL);
+ ap_rvputs(r, "<dt>ProxyPass settings are ", conf->ppinherit ? "" : "NOT ",
+ "inherited from main server.</dt>", NULL);
+ ap_rputs("</dl>\n", r);
+ balancer = (proxy_balancer *)conf->balancers->elts;
+ for (i = 0; i < conf->balancers->nelts; i++) {
+
+ ap_rputs("<hr />\n<h3>LoadBalancer Status for ", r);
+ ap_rvputs(r, "<a href=\"", ap_escape_uri(r->pool, r->uri), "?b=",
+ balancer->s->name + sizeof(BALANCER_PREFIX) - 1,
+ "&amp;nonce=", balancer->s->nonce,
+ "\">", NULL);
+ ap_rvputs(r, balancer->s->name, "</a> [",balancer->s->sname, "]</h3>\n", NULL);
+ ap_rputs("\n\n<table><tr>"
+ "<th>MaxMembers</th><th>StickySession</th><th>DisableFailover</th><th>Timeout</th><th>FailoverAttempts</th><th>Method</th>"
+ "<th>Path</th><th>Active</th></tr>\n<tr>", r);
+ /* the below is a safe cast, since the number of slots total will
+ * never be more than max_workers, which is restricted to int */
+ ap_rprintf(r, "<td>%d [%d Used]</td>\n", balancer->max_workers,
+ balancer->max_workers - (int)storage->num_free_slots(balancer->wslot));
+ if (*balancer->s->sticky) {
+ if (strcmp(balancer->s->sticky, balancer->s->sticky_path)) {
+ ap_rvputs(r, "<td>", ap_escape_html(r->pool, balancer->s->sticky), " | ",
+ ap_escape_html(r->pool, balancer->s->sticky_path), NULL);
+ }
+ else {
+ ap_rvputs(r, "<td>", ap_escape_html(r->pool, balancer->s->sticky), NULL);
+ }
+ }
+ else {
+ ap_rputs("<td> (None) ", r);
+ }
+ ap_rprintf(r, "</td><td>%s</td>\n",
+ balancer->s->sticky_force ? "On" : "Off");
+ ap_rprintf(r, "<td>%" APR_TIME_T_FMT "</td>",
+ apr_time_sec(balancer->s->timeout));
+ ap_rprintf(r, "<td>%d</td>\n", balancer->s->max_attempts);
+ ap_rprintf(r, "<td>%s</td>\n",
+ balancer->s->lbpname);
+ ap_rputs("<td>", r);
+ if (*balancer->s->vhost) {
+ ap_rvputs(r, balancer->s->vhost, " -> ", NULL);
+ }
+ ap_rvputs(r, balancer->s->vpath, "</td>\n", NULL);
+ ap_rprintf(r, "<td>%s</td>\n",
+ !balancer->s->inactive ? "Yes" : "No");
+ ap_rputs("</tr>\n</table>\n<br />", r);
+ ap_rputs("\n\n<table><tr>"
+ "<th>Worker URL</th>"
+ "<th>Route</th><th>RouteRedir</th>"
+ "<th>Factor</th><th>Set</th><th>Status</th>"
+ "<th>Elected</th><th>Busy</th><th>Load</th><th>To</th><th>From</th>", r);
+ if (set_worker_hc_param_f) {
+ ap_rputs("<th>HC Method</th><th>HC Interval</th><th>Passes</th><th>Fails</th><th>HC uri</th><th>HC Expr</th>", r);
+ }
+ ap_rputs("</tr>\n", r);
+
+ workers = (proxy_worker **)balancer->workers->elts;
+ for (n = 0; n < balancer->workers->nelts; n++) {
+ char fbuf[50];
+ worker = *workers;
+ ap_rvputs(r, "<tr>\n<td><a href=\"",
+ ap_escape_uri(r->pool, r->uri), "?b=",
+ balancer->s->name + sizeof(BALANCER_PREFIX) - 1, "&amp;w=",
+ ap_escape_uri(r->pool, worker->s->name_ex),
+ "&amp;nonce=", balancer->s->nonce,
+ "\">", NULL);
+ ap_rvputs(r, (*worker->s->uds_path ? "<i>" : ""), ap_proxy_worker_name(r->pool, worker),
+ (*worker->s->uds_path ? "</i>" : ""), "</a></td>", NULL);
+ ap_rvputs(r, "<td>", ap_escape_html(r->pool, worker->s->route),
+ NULL);
+ ap_rvputs(r, "</td><td>",
+ ap_escape_html(r->pool, worker->s->redirect), NULL);
+ ap_rprintf(r, "</td><td>%.2f</td>", (float)(worker->s->lbfactor)/100.0);
+ ap_rprintf(r, "<td>%d</td><td>", worker->s->lbset);
+ ap_rvputs(r, ap_proxy_parse_wstatus(r->pool, worker), NULL);
+ ap_rputs("</td>", r);
+ ap_rprintf(r, "<td>%" APR_SIZE_T_FMT "</td>", worker->s->elected);
+ ap_rprintf(r, "<td>%" APR_SIZE_T_FMT "</td>", worker->s->busy);
+ ap_rprintf(r, "<td>%d</td><td>", worker->s->lbstatus);
+ ap_rputs(apr_strfsize(worker->s->transferred, fbuf), r);
+ ap_rputs("</td><td>", r);
+ ap_rputs(apr_strfsize(worker->s->read, fbuf), r);
+ if (set_worker_hc_param_f) {
+ ap_rprintf(r, "</td><td>%s</td>", ap_proxy_show_hcmethod(worker->s->method));
+ ap_rprintf(r, "<td>%" APR_TIME_T_FMT "ms</td>", apr_time_as_msec(worker->s->interval));
+ ap_rprintf(r, "<td>%d (%d)</td>", worker->s->passes,worker->s->pcount);
+ ap_rprintf(r, "<td>%d (%d)</td>", worker->s->fails, worker->s->fcount);
+ ap_rprintf(r, "<td>%s</td>", ap_escape_html(r->pool, worker->s->hcuri));
+ ap_rprintf(r, "<td>%s", worker->s->hcexpr);
+ }
+ ap_rputs("</td></tr>\n", r);
+
+ ++workers;
+ }
+ ap_rputs("</table>\n", r);
+ ++balancer;
+ }
+ ap_rputs("<hr />\n", r);
+ if (hc_show_exprs_f) {
+ hc_show_exprs_f(r);
+ }
+ if (wsel && bsel) {
+ ap_rputs("<h3>Edit worker settings for ", r);
+ ap_rvputs(r, (*wsel->s->uds_path?"<i>":""), ap_proxy_worker_name(r->pool, wsel), (*wsel->s->uds_path?"</i>":""), "</h3>\n", NULL);
+ ap_rputs("<form method='POST' enctype='application/x-www-form-urlencoded' action=\"", r);
+ ap_rvputs(r, ap_escape_uri(r->pool, action), "\">\n", NULL);
+ ap_rputs("<table><tr><td>Load factor:</td><td><input name='w_lf' id='w_lf' type=text ", r);
+ ap_rprintf(r, "value='%.2f'></td></tr>\n", (float)(wsel->s->lbfactor)/100.0);
+ ap_rputs("<tr><td>LB Set:</td><td><input name='w_ls' id='w_ls' type=text ", r);
+ ap_rprintf(r, "value='%d'></td></tr>\n", wsel->s->lbset);
+ ap_rputs("<tr><td>Route:</td><td><input name='w_wr' id='w_wr' type=text ", r);
+ ap_rvputs(r, "value=\"", ap_escape_html(r->pool, wsel->s->route),
+ NULL);
+ ap_rputs("\"></td></tr>\n", r);
+ ap_rputs("<tr><td>Route Redirect:</td><td><input name='w_rr' id='w_rr' type=text ", r);
+ ap_rvputs(r, "value=\"", ap_escape_html(r->pool, wsel->s->redirect),
+ NULL);
+ ap_rputs("\"></td></tr>\n", r);
+ ap_rputs("<tr><td>Status:</td>", r);
+ ap_rputs("<td><table><tr>"
+ "<th>Ignore Errors</th>"
+ "<th>Draining Mode</th>"
+ "<th>Disabled</th>"
+ "<th>Hot Standby</th>"
+ "<th>Hot Spare</th>", r);
+ if (hc_show_exprs_f) {
+ ap_rputs("<th>HC Fail</th>", r);
+ }
+ ap_rputs("<th>Stopped</th></tr>\n<tr>", r);
+ create_radio("w_status_I", (PROXY_WORKER_IS(wsel, PROXY_WORKER_IGNORE_ERRORS)), r);
+ create_radio("w_status_N", (PROXY_WORKER_IS(wsel, PROXY_WORKER_DRAIN)), r);
+ create_radio("w_status_D", (PROXY_WORKER_IS(wsel, PROXY_WORKER_DISABLED)), r);
+ create_radio("w_status_H", (PROXY_WORKER_IS(wsel, PROXY_WORKER_HOT_STANDBY)), r);
+ create_radio("w_status_R", (PROXY_WORKER_IS(wsel, PROXY_WORKER_HOT_SPARE)), r);
+ if (hc_show_exprs_f) {
+ create_radio("w_status_C", (PROXY_WORKER_IS(wsel, PROXY_WORKER_HC_FAIL)), r);
+ }
+ create_radio("w_status_S", (PROXY_WORKER_IS(wsel, PROXY_WORKER_STOPPED)), r);
+ ap_rputs("</tr></table></td></tr>\n", r);
+ if (hc_select_exprs_f) {
+ proxy_hcmethods_t *method = proxy_hcmethods;
+ ap_rputs("<tr><td colspan='2'>\n<table align='center'><tr><th>Health Check param</th><th>Value</th></tr>\n", r);
+ ap_rputs("<tr><td>Method</td><td><select name='w_hm'>\n", r);
+ for (; method->name; method++) {
+ if (method->implemented) {
+ ap_rprintf(r, "<option value='%s' %s >%s</option>\n",
+ method->name,
+ (wsel->s->method == method->method) ? "selected" : "",
+ method->name);
+ }
+ }
+ ap_rputs("</select>\n</td></tr>\n", r);
+ ap_rputs("<tr><td>Expr</td><td><select name='w_he'>\n", r);
+ hc_select_exprs_f(r, wsel->s->hcexpr);
+ ap_rputs("</select>\n</td></tr>\n", r);
+ ap_rprintf(r, "<tr><td>Interval (ms)</td><td><input name='w_hi' id='w_hi' type='text' "
+ "value='%" APR_TIME_T_FMT "'></td></tr>\n", apr_time_as_msec(wsel->s->interval));
+ ap_rprintf(r, "<tr><td>Passes trigger</td><td><input name='w_hp' id='w_hp' type='text' "
+ "value='%d'></td></tr>\n", wsel->s->passes);
+ ap_rprintf(r, "<tr><td>Fails trigger)</td><td><input name='w_hf' id='w_hf' type='text' "
+ "value='%d'></td></tr>\n", wsel->s->fails);
+ ap_rprintf(r, "<tr><td>HC uri</td><td><input name='w_hu' id='w_hu' type='text' "
+ "value=\"%s\"></td></tr>\n", ap_escape_html(r->pool, wsel->s->hcuri));
+ ap_rputs("</table>\n</td></tr>\n", r);
+ }
+ ap_rputs("<tr><td colspan='2'><input type=submit value='Submit'></td></tr>\n", r);
+ ap_rvputs(r, "</table>\n<input type=hidden name='w' id='w' ", NULL);
+ ap_rvputs(r, "value=\"", ap_escape_uri(r->pool, wsel->s->name_ex), "\">\n", NULL);
+ ap_rvputs(r, "<input type=hidden name='b' id='b' ", NULL);
+ ap_rvputs(r, "value=\"", ap_escape_html(r->pool, bsel->s->name + sizeof(BALANCER_PREFIX) - 1),
+ "\">\n", NULL);
+ ap_rvputs(r, "<input type=hidden name='nonce' id='nonce' value='",
+ bsel->s->nonce, "'>\n", NULL);
+ ap_rputs("</form>\n", r);
+ ap_rputs("<hr />\n", r);
+ } else if (bsel) {
+ const apr_array_header_t *provs;
+ const ap_list_provider_names_t *pname;
+ int i;
+ ap_rputs("<h3>Edit balancer settings for ", r);
+ ap_rvputs(r, ap_escape_html(r->pool, bsel->s->name), "</h3>\n", NULL);
+ ap_rputs("<form method='POST' enctype='application/x-www-form-urlencoded' action=\"", r);
+ ap_rvputs(r, ap_escape_uri(r->pool, action), "\">\n", NULL);
+ ap_rputs("<table>\n", r);
+ provs = ap_list_provider_names(r->pool, PROXY_LBMETHOD, "0");
+ if (provs) {
+ ap_rputs("<tr><td>LBmethod:</td>", r);
+ ap_rputs("<td>\n<select name='b_lbm' id='b_lbm'>", r);
+ pname = (ap_list_provider_names_t *)provs->elts;
+ for (i = 0; i < provs->nelts; i++, pname++) {
+ ap_rvputs(r,"<option value='", pname->provider_name, "'", NULL);
+ if (strcmp(pname->provider_name, bsel->s->lbpname) == 0)
+ ap_rputs(" selected ", r);
+ ap_rvputs(r, ">", pname->provider_name, "\n", NULL);
+ }
+ ap_rputs("</select>\n</td></tr>\n", r);
+ }
+ ap_rputs("<tr><td>Timeout:</td><td><input name='b_tmo' id='b_tmo' type=text ", r);
+ ap_rprintf(r, "value='%" APR_TIME_T_FMT "'></td></tr>\n", apr_time_sec(bsel->s->timeout));
+ ap_rputs("<tr><td>Failover Attempts:</td><td><input name='b_max' id='b_max' type=text ", r);
+ ap_rprintf(r, "value='%d'></td></tr>\n", bsel->s->max_attempts);
+ ap_rputs("<tr><td>Disable Failover:</td>", r);
+ create_radio("b_sforce", bsel->s->sticky_force, r);
+ ap_rputs("</tr>\n", r);
+ ap_rputs("<tr><td>Sticky Session:</td><td><input name='b_ss' id='b_ss' size=64 type=text ", r);
+ if (strcmp(bsel->s->sticky, bsel->s->sticky_path)) {
+ ap_rvputs(r, "value =\"", ap_escape_html(r->pool, bsel->s->sticky), " | ",
+ ap_escape_html(r->pool, bsel->s->sticky_path), NULL);
+ }
+ else {
+ ap_rvputs(r, "value =\"", ap_escape_html(r->pool, bsel->s->sticky), NULL);
+ }
+ ap_rputs("\">&nbsp;&nbsp;&nbsp;&nbsp;(Use '-' to delete)</td></tr>\n", r);
+ if (storage->num_free_slots(bsel->wslot) != 0) {
+ ap_rputs("<tr><td>Add New Worker:</td><td><input name='b_nwrkr' id='b_nwrkr' size=32 type=text>"
+ "&nbsp;&nbsp;&nbsp;&nbsp;Are you sure? <input name='b_wyes' id='b_wyes' type=checkbox value='1'>"
+ "</td></tr>", r);
+ }
+ ap_rputs("<tr><td colspan=2><input type=submit value='Submit'></td></tr>\n", r);
+ ap_rvputs(r, "</table>\n<input type=hidden name='b' id='b' ", NULL);
+ ap_rvputs(r, "value=\"", ap_escape_html(r->pool, bsel->s->name + sizeof(BALANCER_PREFIX) - 1),
+ "\">\n", NULL);
+ ap_rvputs(r, "<input type=hidden name='nonce' id='nonce' value='",
+ bsel->s->nonce, "'>\n", NULL);
+ ap_rputs("</form>\n", r);
+ ap_rputs("<hr />\n", r);
+ }
+ ap_rputs(ap_psignature("",r), r);
+ ap_rputs("</body></html>\n", r);
+ ap_rflush(r);
+ }
+}
+
+/* Manages the loadfactors and member status
+ * The balancer, worker and nonce are obtained from
+ * the request args (?b=...&w=...&nonce=....).
+ * All other params are pulled from any POST
+ * data that exists.
+ * TODO:
+ * /.../<whatever>/balancer/worker/nonce
+ */
+static int balancer_handler(request_rec *r)
+{
+ void *sconf;
+ proxy_server_conf *conf;
+ proxy_balancer *balancer, *bsel = NULL;
+ proxy_worker *wsel = NULL;
+ apr_table_t *params;
+ int i;
+ const char *name, *ref;
+ apr_status_t rv;
+
+ /* is this for us? */
+ if (strcmp(r->handler, "balancer-manager")) {
+ return DECLINED;
+ }
+
+ r->allowed = 0
+ | (AP_METHOD_BIT << M_GET)
+ | (AP_METHOD_BIT << M_POST);
+ if ((r->method_number != M_GET) && (r->method_number != M_POST)) {
+ return DECLINED;
+ }
+
+ sconf = r->server->module_config;
+ conf = (proxy_server_conf *) ap_get_module_config(sconf, &proxy_module);
+ params = apr_table_make(r->pool, 10);
+
+ balancer = (proxy_balancer *)conf->balancers->elts;
+ for (i = 0; i < conf->balancers->nelts; i++, balancer++) {
+#if APR_HAS_THREADS
+ if ((rv = PROXY_THREAD_LOCK(balancer)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01189)
+ "%s: Lock failed for balancer_handler",
+ balancer->s->name);
+ }
+#endif
+ ap_proxy_sync_balancer(balancer, r->server, conf);
+#if APR_HAS_THREADS
+ if ((rv = PROXY_THREAD_UNLOCK(balancer)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01190)
+ "%s: Unlock failed for balancer_handler",
+ balancer->s->name);
+ }
+#endif
+ }
+
+ if (r->args && (r->method_number == M_GET)) {
+ const char *allowed[] = { "w", "b", "nonce", "xml", NULL };
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01191) "parsing r->args");
+
+ push2table(r->args, params, allowed, r->pool);
+ }
+ if (r->method_number == M_POST) {
+ apr_bucket_brigade *ib;
+ apr_size_t len = 1024;
+ char *buf = apr_pcalloc(r->pool, len+1);
+
+ ib = apr_brigade_create(r->connection->pool, r->connection->bucket_alloc);
+ rv = ap_get_brigade(r->input_filters, ib, AP_MODE_READBYTES,
+ APR_BLOCK_READ, len);
+ if (rv != APR_SUCCESS) {
+ return ap_map_http_request_error(rv, HTTP_BAD_REQUEST);
+ }
+ apr_brigade_flatten(ib, buf, &len);
+ buf[len] = '\0';
+ push2table(buf, params, NULL, r->pool);
+ }
+
+ /* Ignore parameters if this looks like XSRF */
+ ref = apr_table_get(r->headers_in, "Referer");
+ if (apr_table_elts(params)
+ && (!ref || !safe_referer(r, ref))) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10187)
+ "ignoring params in balancer-manager cross-site access %s: %s", ref, ap_get_server_name(r));
+ apr_table_clear(params);
+ }
+
+ /* Process the parameters */
+ if ((name = apr_table_get(params, "b")))
+ bsel = ap_proxy_get_balancer(r->pool, conf,
+ apr_pstrcat(r->pool, BALANCER_PREFIX, name, NULL), 0);
+
+ if ((name = apr_table_get(params, "w"))) {
+ wsel = ap_proxy_get_worker(r->pool, bsel, conf, name);
+ }
+
+
+ /* Check that the supplied nonce matches this server's nonce;
+ * otherwise ignore all parameters, to prevent a CSRF
+ * attack. */
+ if (bsel
+ && (*bsel->s->nonce
+ && ((name = apr_table_get(params, "nonce")) != NULL
+ && strcmp(bsel->s->nonce, name) == 0))) {
+ /* Process the parameters and add the worker to the balancer */
+ rv = balancer_process_balancer_worker(r, conf, bsel, wsel, params);
+ if (rv != APR_SUCCESS) {
+ return HTTP_BAD_REQUEST;
+ }
+ }
+
+ /* display the HTML or XML page */
+ if (apr_table_get(params, "xml")) {
+ balancer_display_page(r, conf, bsel, wsel, 1);
+ } else {
+ balancer_display_page(r, conf, bsel, wsel, 0);
+ }
+ return DONE;
+}
+
+static void balancer_child_init(apr_pool_t *p, server_rec *s)
+{
+ while (s) {
+ proxy_balancer *balancer;
+ int i;
+ void *sconf = s->module_config;
+ proxy_server_conf *conf = (proxy_server_conf *)ap_get_module_config(sconf, &proxy_module);
+ apr_status_t rv;
+
+ if (conf->balancers->nelts) {
+ apr_size_t size;
+ unsigned int num;
+ storage->attach(&(conf->bslot), conf->id, &size, &num, p);
+ if (!conf->bslot) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(01205) "slotmem_attach failed");
+ exit(1); /* Ugly, but what else? */
+ }
+ }
+
+ balancer = (proxy_balancer *)conf->balancers->elts;
+ for (i = 0; i < conf->balancers->nelts; i++, balancer++) {
+ rv = ap_proxy_initialize_balancer(balancer, s, p);
+
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(01206)
+ "Failed to init balancer %s in child",
+ balancer->s->name);
+ exit(1); /* Ugly, but what else? */
+ }
+ init_balancer_members(p, s, balancer);
+ }
+ s = s->next;
+ }
+
+}
+
+static void ap_proxy_balancer_register_hook(apr_pool_t *p)
+{
+ /* Only the mpm_winnt has child init hook handler.
+ * make sure that we are called after the mpm
+ * initializes
+ */
+ static const char *const aszPred[] = { "mpm_winnt.c", "mod_slotmem_shm.c", NULL};
+ static const char *const aszPred2[] = { "mod_proxy.c", NULL};
+ /* manager handler */
+ APR_REGISTER_OPTIONAL_FN(balancer_manage);
+ ap_hook_post_config(balancer_post_config, aszPred2, NULL, APR_HOOK_MIDDLE);
+ ap_hook_pre_config(balancer_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_handler(balancer_handler, NULL, NULL, APR_HOOK_FIRST);
+ ap_hook_child_init(balancer_child_init, aszPred, NULL, APR_HOOK_MIDDLE);
+ proxy_hook_pre_request(proxy_balancer_pre_request, NULL, NULL, APR_HOOK_FIRST);
+ proxy_hook_post_request(proxy_balancer_post_request, NULL, NULL, APR_HOOK_FIRST);
+ proxy_hook_canon_handler(proxy_balancer_canon, NULL, NULL, APR_HOOK_FIRST);
+}
+
+AP_DECLARE_MODULE(proxy_balancer) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ NULL, /* command apr_table_t */
+ ap_proxy_balancer_register_hook /* register hooks */
+};
diff --git a/modules/proxy/mod_proxy_balancer.dep b/modules/proxy/mod_proxy_balancer.dep
new file mode 100644
index 0000000..0902194
--- /dev/null
+++ b/modules/proxy/mod_proxy_balancer.dep
@@ -0,0 +1,76 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_proxy_balancer.mak
+
+.\mod_proxy_balancer.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_mpm.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_slotmem.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_date.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_reslist.h"\
+ "..\..\srclib\apr-util\include\apr_strmatch.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_version.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_proxy.h"\
+
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
diff --git a/modules/proxy/mod_proxy_balancer.dsp b/modules/proxy/mod_proxy_balancer.dsp
new file mode 100644
index 0000000..12168e5
--- /dev/null
+++ b/modules/proxy/mod_proxy_balancer.dsp
@@ -0,0 +1,123 @@
+# Microsoft Developer Studio Project File - Name="mod_proxy_balancer" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_proxy_balancer - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_balancer.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_balancer.mak" CFG="mod_proxy_balancer - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy_balancer - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy_balancer - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_proxy_balancer - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_proxy_balancer_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_proxy_balancer.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_proxy_balancer.so" /d LONG_NAME="proxy_balancer_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:".\Release\mod_proxy_balancer.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_balancer.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_proxy_balancer.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_balancer.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_proxy_balancer.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_proxy_balancer - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_proxy_balancer_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_proxy_balancer.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_proxy_balancer.so" /d LONG_NAME="proxy_balancer_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_proxy_balancer.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_balancer.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_proxy_balancer.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_balancer.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_proxy_balancer.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_proxy_balancer - Win32 Release"
+# Name "mod_proxy_balancer - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\mod_proxy_balancer.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter ".h"
+# Begin Source File
+
+SOURCE=.\mod_proxy.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/proxy/mod_proxy_balancer.mak b/modules/proxy/mod_proxy_balancer.mak
new file mode 100644
index 0000000..86d7ec5
--- /dev/null
+++ b/modules/proxy/mod_proxy_balancer.mak
@@ -0,0 +1,380 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_proxy_balancer.dsp
+!IF "$(CFG)" == ""
+CFG=mod_proxy_balancer - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_proxy_balancer - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_proxy_balancer - Win32 Release" && "$(CFG)" != "mod_proxy_balancer - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_balancer.mak" CFG="mod_proxy_balancer - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy_balancer - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy_balancer - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_balancer - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_proxy_balancer.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_proxy - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_proxy_balancer.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_proxy - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_proxy_balancer.obj"
+ -@erase "$(INTDIR)\mod_proxy_balancer.res"
+ -@erase "$(INTDIR)\mod_proxy_balancer_src.idb"
+ -@erase "$(INTDIR)\mod_proxy_balancer_src.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_balancer.exp"
+ -@erase "$(OUTDIR)\mod_proxy_balancer.lib"
+ -@erase "$(OUTDIR)\mod_proxy_balancer.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_balancer.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_proxy_balancer_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_proxy_balancer.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_proxy_balancer.so" /d LONG_NAME="proxy_balancer_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_proxy_balancer.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_proxy_balancer.pdb" /debug /out:"$(OUTDIR)\mod_proxy_balancer.so" /implib:"$(OUTDIR)\mod_proxy_balancer.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_balancer.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_proxy_balancer.obj" \
+ "$(INTDIR)\mod_proxy_balancer.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib" \
+ "$(OUTDIR)\mod_proxy.lib"
+
+"$(OUTDIR)\mod_proxy_balancer.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_proxy_balancer.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_proxy_balancer.so"
+ if exist .\Release\mod_proxy_balancer.so.manifest mt.exe -manifest .\Release\mod_proxy_balancer.so.manifest -outputresource:.\Release\mod_proxy_balancer.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_proxy_balancer - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_proxy_balancer.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_proxy - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_proxy_balancer.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_proxy - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_proxy_balancer.obj"
+ -@erase "$(INTDIR)\mod_proxy_balancer.res"
+ -@erase "$(INTDIR)\mod_proxy_balancer_src.idb"
+ -@erase "$(INTDIR)\mod_proxy_balancer_src.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_balancer.exp"
+ -@erase "$(OUTDIR)\mod_proxy_balancer.lib"
+ -@erase "$(OUTDIR)\mod_proxy_balancer.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_balancer.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_proxy_balancer_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_proxy_balancer.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_proxy_balancer.so" /d LONG_NAME="proxy_balancer_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_proxy_balancer.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_proxy_balancer.pdb" /debug /out:"$(OUTDIR)\mod_proxy_balancer.so" /implib:"$(OUTDIR)\mod_proxy_balancer.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_balancer.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_proxy_balancer.obj" \
+ "$(INTDIR)\mod_proxy_balancer.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib" \
+ "$(OUTDIR)\mod_proxy.lib"
+
+"$(OUTDIR)\mod_proxy_balancer.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_proxy_balancer.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_proxy_balancer.so"
+ if exist .\Debug\mod_proxy_balancer.so.manifest mt.exe -manifest .\Debug\mod_proxy_balancer.so.manifest -outputresource:.\Debug\mod_proxy_balancer.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_proxy_balancer.dep")
+!INCLUDE "mod_proxy_balancer.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_proxy_balancer.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_proxy_balancer - Win32 Release" || "$(CFG)" == "mod_proxy_balancer - Win32 Debug"
+SOURCE=.\mod_proxy_balancer.c
+
+"$(INTDIR)\mod_proxy_balancer.obj" : $(SOURCE) "$(INTDIR)"
+
+
+!IF "$(CFG)" == "mod_proxy_balancer - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\proxy"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_balancer - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\proxy"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_balancer - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\proxy"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_balancer - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\proxy"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_balancer - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\proxy"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_balancer - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\proxy"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_balancer - Win32 Release"
+
+"mod_proxy - Win32 Release" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Release"
+ cd "."
+
+"mod_proxy - Win32 ReleaseCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Release" RECURSE=1 CLEAN
+ cd "."
+
+!ELSEIF "$(CFG)" == "mod_proxy_balancer - Win32 Debug"
+
+"mod_proxy - Win32 Debug" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Debug"
+ cd "."
+
+"mod_proxy - Win32 DebugCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Debug" RECURSE=1 CLEAN
+ cd "."
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_proxy_balancer - Win32 Release"
+
+
+"$(INTDIR)\mod_proxy_balancer.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_proxy_balancer.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_proxy_balancer.so" /d LONG_NAME="proxy_balancer_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_proxy_balancer - Win32 Debug"
+
+
+"$(INTDIR)\mod_proxy_balancer.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_proxy_balancer.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_proxy_balancer.so" /d LONG_NAME="proxy_balancer_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+
+!ENDIF
+
diff --git a/modules/proxy/mod_proxy_connect.c b/modules/proxy/mod_proxy_connect.c
new file mode 100644
index 0000000..5a68135
--- /dev/null
+++ b/modules/proxy/mod_proxy_connect.c
@@ -0,0 +1,404 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* CONNECT method for Apache proxy */
+
+#include "mod_proxy.h"
+#include "apr_poll.h"
+
+#define CONN_BLKSZ AP_IOBUFSIZE
+
+module AP_MODULE_DECLARE_DATA proxy_connect_module;
+
+/*
+ * This handles Netscape CONNECT method secure proxy requests.
+ * A connection is opened to the specified host and data is
+ * passed through between the WWW site and the browser.
+ *
+ * This code is based on the INTERNET-DRAFT document
+ * "Tunneling SSL Through a WWW Proxy" currently at
+ * http://www.mcom.com/newsref/std/tunneling_ssl.html.
+ *
+ * If proxyhost and proxyport are set, we send a CONNECT to
+ * the specified proxy..
+ *
+ * FIXME: this doesn't log the number of bytes sent, but
+ * that may be okay, since the data is supposed to
+ * be transparent. In fact, this doesn't log at all
+ * yet. 8^)
+ * FIXME: doesn't check any headers initially sent from the
+ * client.
+ * FIXME: should allow authentication, but hopefully the
+ * generic proxy authentication is good enough.
+ * FIXME: no check for r->assbackwards, whatever that is.
+ */
+
+typedef struct {
+ apr_array_header_t *allowed_connect_ports;
+} connect_conf;
+
+typedef struct {
+ int first;
+ int last;
+} port_range;
+
+static void *create_config(apr_pool_t *p, server_rec *s)
+{
+ connect_conf *c = apr_pcalloc(p, sizeof(connect_conf));
+ c->allowed_connect_ports = apr_array_make(p, 10, sizeof(port_range));
+ return c;
+}
+
+static void *merge_config(apr_pool_t *p, void *basev, void *overridesv)
+{
+ connect_conf *c = apr_pcalloc(p, sizeof(connect_conf));
+ connect_conf *base = (connect_conf *) basev;
+ connect_conf *overrides = (connect_conf *) overridesv;
+
+ c->allowed_connect_ports = apr_array_append(p,
+ base->allowed_connect_ports,
+ overrides->allowed_connect_ports);
+
+ return c;
+}
+
+
+/*
+ * Set the ports CONNECT can use
+ */
+static const char *
+ set_allowed_ports(cmd_parms *parms, void *dummy, const char *arg)
+{
+ server_rec *s = parms->server;
+ int first, last;
+ connect_conf *conf =
+ ap_get_module_config(s->module_config, &proxy_connect_module);
+ port_range *New;
+ char *endptr;
+ const char *p = arg;
+
+ if (!apr_isdigit(arg[0]))
+ return "AllowCONNECT: port numbers must be numeric";
+
+ first = strtol(p, &endptr, 10);
+ if (*endptr == '-') {
+ p = endptr + 1;
+ last = strtol(p, &endptr, 10);
+ }
+ else {
+ last = first;
+ }
+
+ if (endptr == p || *endptr != '\0') {
+ return apr_psprintf(parms->temp_pool,
+ "Cannot parse '%s' as port number", p);
+ }
+
+ New = apr_array_push(conf->allowed_connect_ports);
+ New->first = first;
+ New->last = last;
+ return NULL;
+}
+
+
+static int allowed_port(connect_conf *conf, int port)
+{
+ int i;
+ port_range *list = (port_range *) conf->allowed_connect_ports->elts;
+
+ if (apr_is_empty_array(conf->allowed_connect_ports)) {
+ return port == APR_URI_HTTPS_DEFAULT_PORT
+ || port == APR_URI_SNEWS_DEFAULT_PORT;
+ }
+
+ for (i = 0; i < conf->allowed_connect_ports->nelts; i++) {
+ if (port >= list[i].first && port <= list[i].last)
+ return 1;
+ }
+ return 0;
+}
+
+/* canonicalise CONNECT URLs. */
+static int proxy_connect_canon(request_rec *r, char *url)
+{
+
+ if (r->method_number != M_CONNECT) {
+ return DECLINED;
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "canonicalising URL %s", url);
+
+ return OK;
+}
+
+/* CONNECT handler */
+static int proxy_connect_handler(request_rec *r, proxy_worker *worker,
+ proxy_server_conf *conf,
+ char *url, const char *proxyname,
+ apr_port_t proxyport)
+{
+ connect_conf *c_conf =
+ ap_get_module_config(r->server->module_config, &proxy_connect_module);
+
+ apr_pool_t *p = r->pool;
+ apr_socket_t *sock;
+ conn_rec *c = r->connection;
+ conn_rec *backconn;
+
+ apr_status_t rv;
+ apr_size_t nbytes;
+ char buffer[HUGE_STRING_LEN];
+
+ apr_bucket_brigade *bb;
+ proxy_tunnel_rec *tunnel;
+ int failed, rc;
+
+ apr_uri_t uri;
+ const char *connectname;
+ apr_port_t connectport = 0;
+ apr_sockaddr_t *nexthop;
+
+ apr_interval_time_t current_timeout;
+
+ /* is this for us? */
+ if (r->method_number != M_CONNECT) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "declining URL %s", url);
+ return DECLINED;
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "serving URL %s", url);
+
+
+ /*
+ * Step One: Determine Who To Connect To
+ *
+ * Break up the URL to determine the host to connect to
+ */
+
+ /* we break the URL into host, port, uri */
+ if (APR_SUCCESS != apr_uri_parse_hostinfo(p, url, &uri)) {
+ return ap_proxyerror(r, HTTP_BAD_REQUEST,
+ apr_pstrcat(p, "URI cannot be parsed: ", url,
+ NULL));
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01019)
+ "connecting %s to %s:%d", url, uri.hostname, uri.port);
+
+ /* Determine host/port of next hop; from request URI or of a proxy. */
+ connectname = proxyname ? proxyname : uri.hostname;
+ connectport = proxyname ? proxyport : uri.port;
+
+ /* Do a DNS lookup for the next hop */
+ rv = apr_sockaddr_info_get(&nexthop, connectname, APR_UNSPEC,
+ connectport, 0, p);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02327)
+ "failed to resolve hostname '%s'", connectname);
+ return ap_proxyerror(r, HTTP_BAD_GATEWAY,
+ apr_pstrcat(p, "DNS lookup failure for: ",
+ connectname, NULL));
+ }
+
+ /* Check ProxyBlock directive on the hostname/address. */
+ if (ap_proxy_checkproxyblock2(r, conf, uri.hostname,
+ proxyname ? NULL : nexthop) != OK) {
+ return ap_proxyerror(r, HTTP_FORBIDDEN,
+ "Connect to remote machine blocked");
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "connecting to remote proxy %s on port %d",
+ connectname, connectport);
+
+ /* Check if it is an allowed port */
+ if (!allowed_port(c_conf, uri.port)) {
+ return ap_proxyerror(r, HTTP_FORBIDDEN,
+ "Connect to remote machine blocked");
+ }
+
+ /*
+ * Step Two: Make the Connection
+ *
+ * We have determined who to connect to. Now make the connection.
+ */
+
+ /*
+ * At this point we have a list of one or more IP addresses of
+ * the machine to connect to. If configured, reorder this
+ * list so that the "best candidate" is first try. "best
+ * candidate" could mean the least loaded server, the fastest
+ * responding server, whatever.
+ *
+ * For now we do nothing, ie we get DNS round robin.
+ * XXX FIXME
+ */
+ failed = ap_proxy_connect_to_backend(&sock, "CONNECT", nexthop,
+ connectname, conf, r);
+
+ /* handle a permanent error from the above loop */
+ if (failed) {
+ if (proxyname) {
+ return DECLINED;
+ }
+ else {
+ return HTTP_SERVICE_UNAVAILABLE;
+ }
+ }
+
+ /*
+ * Step Three: Send the Request
+ *
+ * Send the HTTP/1.1 CONNECT request to the remote server
+ */
+
+ backconn = ap_run_create_connection(c->pool, r->server, sock,
+ c->id, c->sbh, c->bucket_alloc);
+ if (!backconn) {
+ /* peer reset */
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01021)
+ "an error occurred creating a new connection "
+ "to %pI (%s)", nexthop, connectname);
+ apr_socket_close(sock);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ ap_proxy_ssl_engine(backconn, r->per_dir_config, 0);
+
+ /*
+ * save the timeout of the socket because core_pre_connection
+ * will set it to base_server->timeout
+ * (core TimeOut directive).
+ */
+ apr_socket_timeout_get(sock, &current_timeout);
+ rc = ap_run_pre_connection(backconn, sock);
+ if (rc != OK && rc != DONE) {
+ backconn->aborted = 1;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01022)
+ "pre_connection setup failed (%d)", rc);
+ apr_socket_close(sock);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ apr_socket_timeout_set(sock, current_timeout);
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
+ "connection complete to %pI (%s)",
+ nexthop, connectname);
+ apr_table_setn(r->notes, "proxy-source-port", apr_psprintf(r->pool, "%hu",
+ backconn->local_addr->port));
+
+ bb = apr_brigade_create(p, c->bucket_alloc);
+
+ /* If we are connecting through a remote proxy, we need to pass
+ * the CONNECT request on to it.
+ */
+ if (proxyport) {
+ /* FIXME: Error checking ignored.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+ "sending the CONNECT request to the remote proxy");
+ ap_fprintf(backconn->output_filters, bb,
+ "CONNECT %s HTTP/1.0" CRLF, r->uri);
+ ap_fprintf(backconn->output_filters, bb,
+ "Proxy-agent: %s" CRLF CRLF, ap_get_server_banner());
+ ap_fflush(backconn->output_filters, bb);
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "Returning 200 OK");
+ nbytes = apr_snprintf(buffer, sizeof(buffer),
+ "HTTP/1.0 200 Connection Established" CRLF);
+ ap_xlate_proto_to_ascii(buffer, nbytes);
+ ap_fwrite(c->output_filters, bb, buffer, nbytes);
+ nbytes = apr_snprintf(buffer, sizeof(buffer),
+ "Proxy-agent: %s" CRLF CRLF,
+ ap_get_server_banner());
+ ap_xlate_proto_to_ascii(buffer, nbytes);
+ ap_fwrite(c->output_filters, bb, buffer, nbytes);
+ ap_fflush(c->output_filters, bb);
+#if 0
+ /* This is safer code, but it doesn't work yet. I'm leaving it
+ * here so that I can fix it later.
+ */
+ r->status = HTTP_OK;
+ r->header_only = 1;
+ apr_table_set(r->headers_out, "Proxy-agent: %s", ap_get_server_banner());
+ ap_rflush(r);
+#endif
+ }
+ apr_brigade_cleanup(bb);
+
+ /*
+ * Step Four: Handle Data Transfer
+ *
+ * Handle two way transfer of data over the socket (this is a tunnel).
+ */
+
+ /* r->sent_bodyct = 1; */
+
+ rv = ap_proxy_tunnel_create(&tunnel, r, backconn, "CONNECT");
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10208)
+ "can't create tunnel for %pI (%s)",
+ nexthop, connectname);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ rc = ap_proxy_tunnel_run(tunnel);
+ if (ap_is_HTTP_ERROR(rc)) {
+ if (rc == HTTP_GATEWAY_TIME_OUT) {
+ /* ap_proxy_tunnel_run() didn't log this */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10224)
+ "tunnel timed out");
+ }
+ /* Don't send an error page if we sent data already */
+ if (proxyport && !tunnel->replied) {
+ return rc;
+ }
+ }
+
+ /*
+ * Step Five: Clean Up
+ *
+ * Close the socket and clean up
+ */
+
+ if (backconn->aborted)
+ apr_socket_close(sock);
+ else
+ ap_lingering_close(backconn);
+
+ return OK;
+}
+
+static void ap_proxy_connect_register_hook(apr_pool_t *p)
+{
+ proxy_hook_scheme_handler(proxy_connect_handler, NULL, NULL, APR_HOOK_MIDDLE);
+ proxy_hook_canon_handler(proxy_connect_canon, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+static const command_rec cmds[] =
+{
+ AP_INIT_ITERATE("AllowCONNECT", set_allowed_ports, NULL, RSRC_CONF,
+ "A list of ports or port ranges which CONNECT may connect to"),
+ {NULL}
+};
+
+AP_DECLARE_MODULE(proxy_connect) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ create_config, /* create per-server config structure */
+ merge_config, /* merge per-server config structures */
+ cmds, /* command apr_table_t */
+ ap_proxy_connect_register_hook /* register hooks */
+};
diff --git a/modules/proxy/mod_proxy_connect.dep b/modules/proxy/mod_proxy_connect.dep
new file mode 100644
index 0000000..926b6e2
--- /dev/null
+++ b/modules/proxy/mod_proxy_connect.dep
@@ -0,0 +1,73 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_proxy_connect.mak
+
+.\mod_proxy_connect.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_slotmem.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_date.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_reslist.h"\
+ "..\..\srclib\apr-util\include\apr_strmatch.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_proxy.h"\
+
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
diff --git a/modules/proxy/mod_proxy_connect.dsp b/modules/proxy/mod_proxy_connect.dsp
new file mode 100644
index 0000000..24647bf
--- /dev/null
+++ b/modules/proxy/mod_proxy_connect.dsp
@@ -0,0 +1,123 @@
+# Microsoft Developer Studio Project File - Name="mod_proxy_connect" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_proxy_connect - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_connect.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_connect.mak" CFG="mod_proxy_connect - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy_connect - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy_connect - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_proxy_connect - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_proxy_connect_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_proxy_connect.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_proxy_connect.so" /d LONG_NAME="proxy_connect_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:".\Release\mod_proxy_connect.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_connect.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_proxy_connect.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_connect.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_proxy_connect.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_proxy_connect - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_proxy_connect_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_proxy_connect.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_proxy_connect.so" /d LONG_NAME="proxy_connect_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_proxy_connect.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_connect.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_proxy_connect.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_connect.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_proxy_connect.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_proxy_connect - Win32 Release"
+# Name "mod_proxy_connect - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\mod_proxy_connect.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter ".h"
+# Begin Source File
+
+SOURCE=.\mod_proxy.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/proxy/mod_proxy_connect.mak b/modules/proxy/mod_proxy_connect.mak
new file mode 100644
index 0000000..be354db
--- /dev/null
+++ b/modules/proxy/mod_proxy_connect.mak
@@ -0,0 +1,380 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_proxy_connect.dsp
+!IF "$(CFG)" == ""
+CFG=mod_proxy_connect - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_proxy_connect - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_proxy_connect - Win32 Release" && "$(CFG)" != "mod_proxy_connect - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_connect.mak" CFG="mod_proxy_connect - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy_connect - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy_connect - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_connect - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_proxy_connect.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_proxy - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_proxy_connect.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_proxy - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_proxy_connect.obj"
+ -@erase "$(INTDIR)\mod_proxy_connect.res"
+ -@erase "$(INTDIR)\mod_proxy_connect_src.idb"
+ -@erase "$(INTDIR)\mod_proxy_connect_src.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_connect.exp"
+ -@erase "$(OUTDIR)\mod_proxy_connect.lib"
+ -@erase "$(OUTDIR)\mod_proxy_connect.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_connect.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_proxy_connect_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_proxy_connect.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_proxy_connect.so" /d LONG_NAME="proxy_connect_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_proxy_connect.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_proxy_connect.pdb" /debug /out:"$(OUTDIR)\mod_proxy_connect.so" /implib:"$(OUTDIR)\mod_proxy_connect.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_connect.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_proxy_connect.obj" \
+ "$(INTDIR)\mod_proxy_connect.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib" \
+ "$(OUTDIR)\mod_proxy.lib"
+
+"$(OUTDIR)\mod_proxy_connect.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_proxy_connect.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_proxy_connect.so"
+ if exist .\Release\mod_proxy_connect.so.manifest mt.exe -manifest .\Release\mod_proxy_connect.so.manifest -outputresource:.\Release\mod_proxy_connect.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_proxy_connect - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_proxy_connect.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_proxy - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_proxy_connect.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_proxy - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_proxy_connect.obj"
+ -@erase "$(INTDIR)\mod_proxy_connect.res"
+ -@erase "$(INTDIR)\mod_proxy_connect_src.idb"
+ -@erase "$(INTDIR)\mod_proxy_connect_src.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_connect.exp"
+ -@erase "$(OUTDIR)\mod_proxy_connect.lib"
+ -@erase "$(OUTDIR)\mod_proxy_connect.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_connect.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_proxy_connect_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_proxy_connect.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_proxy_connect.so" /d LONG_NAME="proxy_connect_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_proxy_connect.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_proxy_connect.pdb" /debug /out:"$(OUTDIR)\mod_proxy_connect.so" /implib:"$(OUTDIR)\mod_proxy_connect.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_connect.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_proxy_connect.obj" \
+ "$(INTDIR)\mod_proxy_connect.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib" \
+ "$(OUTDIR)\mod_proxy.lib"
+
+"$(OUTDIR)\mod_proxy_connect.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_proxy_connect.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_proxy_connect.so"
+ if exist .\Debug\mod_proxy_connect.so.manifest mt.exe -manifest .\Debug\mod_proxy_connect.so.manifest -outputresource:.\Debug\mod_proxy_connect.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_proxy_connect.dep")
+!INCLUDE "mod_proxy_connect.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_proxy_connect.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_proxy_connect - Win32 Release" || "$(CFG)" == "mod_proxy_connect - Win32 Debug"
+SOURCE=.\mod_proxy_connect.c
+
+"$(INTDIR)\mod_proxy_connect.obj" : $(SOURCE) "$(INTDIR)"
+
+
+!IF "$(CFG)" == "mod_proxy_connect - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\proxy"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_connect - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\proxy"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_connect - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\proxy"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_connect - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\proxy"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_connect - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\proxy"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_connect - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\proxy"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_connect - Win32 Release"
+
+"mod_proxy - Win32 Release" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Release"
+ cd "."
+
+"mod_proxy - Win32 ReleaseCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Release" RECURSE=1 CLEAN
+ cd "."
+
+!ELSEIF "$(CFG)" == "mod_proxy_connect - Win32 Debug"
+
+"mod_proxy - Win32 Debug" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Debug"
+ cd "."
+
+"mod_proxy - Win32 DebugCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Debug" RECURSE=1 CLEAN
+ cd "."
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_proxy_connect - Win32 Release"
+
+
+"$(INTDIR)\mod_proxy_connect.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_proxy_connect.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_proxy_connect.so" /d LONG_NAME="proxy_connect_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_proxy_connect - Win32 Debug"
+
+
+"$(INTDIR)\mod_proxy_connect.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_proxy_connect.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_proxy_connect.so" /d LONG_NAME="proxy_connect_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+
+!ENDIF
+
diff --git a/modules/proxy/mod_proxy_express.c b/modules/proxy/mod_proxy_express.c
new file mode 100644
index 0000000..5d458c4
--- /dev/null
+++ b/modules/proxy/mod_proxy_express.c
@@ -0,0 +1,250 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mod_proxy.h"
+#include "apr_dbm.h"
+
+module AP_MODULE_DECLARE_DATA proxy_express_module;
+
+#include "apr_version.h"
+#if !APR_VERSION_AT_LEAST(2,0,0)
+#include "apu_version.h"
+#endif
+
+static int proxy_available = 0;
+
+typedef struct {
+ const char *dbmfile;
+ const char *dbmtype;
+ int enabled;
+} express_server_conf;
+
+static const char *set_dbmfile(cmd_parms *cmd,
+ void *dconf,
+ const char *arg)
+{
+ express_server_conf *sconf;
+ sconf = ap_get_module_config(cmd->server->module_config, &proxy_express_module);
+
+ if ((sconf->dbmfile = ap_server_root_relative(cmd->pool, arg)) == NULL) {
+ return apr_pstrcat(cmd->pool, "ProxyExpressDBMFile: bad path to file: ",
+ arg, NULL);
+ }
+ return NULL;
+}
+
+static const char *set_dbmtype(cmd_parms *cmd,
+ void *dconf,
+ const char *arg)
+{
+ express_server_conf *sconf;
+ sconf = ap_get_module_config(cmd->server->module_config, &proxy_express_module);
+
+ sconf->dbmtype = arg;
+
+ return NULL;
+}
+
+static const char *set_enabled(cmd_parms *cmd,
+ void *dconf,
+ int flag)
+{
+ express_server_conf *sconf;
+ sconf = ap_get_module_config(cmd->server->module_config, &proxy_express_module);
+
+ sconf->enabled = flag;
+
+ return NULL;
+}
+
+static void *server_create(apr_pool_t *p, server_rec *s)
+{
+ express_server_conf *a;
+
+ a = (express_server_conf *)apr_pcalloc(p, sizeof(express_server_conf));
+
+ a->dbmfile = NULL;
+ a->dbmtype = "default";
+ a->enabled = 0;
+
+ return (void *)a;
+}
+
+static void *server_merge(apr_pool_t *p, void *basev, void *overridesv)
+{
+ express_server_conf *a, *base, *overrides;
+
+ a = (express_server_conf *)apr_pcalloc(p,
+ sizeof(express_server_conf));
+ base = (express_server_conf *)basev;
+ overrides = (express_server_conf *)overridesv;
+
+ a->dbmfile = (overrides->dbmfile) ? overrides->dbmfile : base->dbmfile;
+ a->dbmtype = (overrides->dbmtype) ? overrides->dbmtype : base->dbmtype;
+ a->enabled = (overrides->enabled) ? overrides->enabled : base->enabled;
+
+ return (void *)a;
+}
+
+static int post_config(apr_pool_t *p,
+ apr_pool_t *plog,
+ apr_pool_t *ptemp,
+ server_rec *s)
+{
+ proxy_available = (ap_find_linked_module("mod_proxy.c") != NULL);
+ return OK;
+}
+
+
+static int xlate_name(request_rec *r)
+{
+ int i;
+ const char *name;
+ char *backend = NULL;
+ apr_dbm_t *db;
+ apr_status_t rv;
+ apr_datum_t key, val;
+ struct proxy_alias *ralias;
+ proxy_dir_conf *dconf;
+ express_server_conf *sconf;
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ const apr_dbm_driver_t *driver;
+ const apu_err_t *err;
+#endif
+
+ sconf = ap_get_module_config(r->server->module_config, &proxy_express_module);
+ dconf = ap_get_module_config(r->per_dir_config, &proxy_module);
+
+ if (!sconf->enabled) {
+ return DECLINED;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01001) "proxy_express: Enabled");
+ if (!sconf->dbmfile || (r->filename && strncmp(r->filename, "proxy:", 6) == 0)) {
+ /* it should be go on as an internal proxy request */
+ return DECLINED;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01002)
+ "proxy_express: Opening DBM file: %s (%s)",
+ sconf->dbmfile, sconf->dbmtype);
+
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
+ rv = apr_dbm_get_driver(&driver, sconf->dbmtype, &err, r->pool);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ APLOGNO(10275) "The dbm library '%s' could not be loaded: %s (%s: %d)",
+ sconf->dbmtype, err->msg, err->reason, err->rc);
+ return DECLINED;
+ }
+
+ rv = apr_dbm_open2(&db, driver, sconf->dbmfile, APR_DBM_READONLY,
+ APR_OS_DEFAULT, r->pool);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ APLOGNO(10276) "The '%s' file '%s' could not be loaded",
+ sconf->dbmtype, sconf->dbmfile);
+ return DECLINED;
+ }
+#else
+ rv = apr_dbm_open_ex(&db, sconf->dbmtype, sconf->dbmfile, APR_DBM_READONLY,
+ APR_OS_DEFAULT, r->pool);
+ if (rv != APR_SUCCESS) {
+ return DECLINED;
+ }
+#endif
+
+ name = ap_get_server_name(r);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01003)
+ "proxy_express: looking for %s", name);
+ key.dptr = (char *)name;
+ key.dsize = strlen(key.dptr);
+
+ rv = apr_dbm_fetch(db, key, &val);
+ if (rv == APR_SUCCESS) {
+ backend = apr_pstrmemdup(r->pool, val.dptr, val.dsize);
+ }
+ apr_dbm_close(db);
+ if (rv != APR_SUCCESS || !backend) {
+ return DECLINED;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01004)
+ "proxy_express: found %s -> %s", name, backend);
+ r->filename = apr_pstrcat(r->pool, "proxy:", backend, r->uri, NULL);
+ r->handler = "proxy-server";
+ r->proxyreq = PROXYREQ_REVERSE;
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01005)
+ "proxy_express: rewritten as: %s", r->filename);
+
+ ralias = (struct proxy_alias *)dconf->raliases->elts;
+ /*
+ * See if we have already added a ProxyPassReverse entry
+ * for this host... If so, don't do it again.
+ */
+ /*
+ * NOTE: dconf is process specific so this will only
+ * work as long as we maintain that this process
+ * or thread is handling the backend
+ */
+ for (i = 0; i < dconf->raliases->nelts; i++, ralias++) {
+ if (strcasecmp(backend, ralias->real) == 0) {
+ ralias = NULL;
+ break;
+ }
+ }
+
+ /* Didn't find one... add it */
+ if (!ralias) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01006)
+ "proxy_express: adding PPR entry");
+ ralias = apr_array_push(dconf->raliases);
+ ralias->fake = "/";
+ ralias->real = apr_pstrdup(dconf->raliases->pool, backend);
+ ralias->flags = 0;
+ }
+ return OK;
+}
+
+static const command_rec command_table[] = {
+ AP_INIT_FLAG("ProxyExpressEnable", set_enabled, NULL, OR_FILEINFO,
+ "Enable the ProxyExpress functionality"),
+ AP_INIT_TAKE1("ProxyExpressDBMFile", set_dbmfile, NULL, OR_FILEINFO,
+ "Location of ProxyExpressDBMFile file"),
+ AP_INIT_TAKE1("ProxyExpressDBMType", set_dbmtype, NULL, OR_FILEINFO,
+ "Type of ProxyExpressDBMFile file"),
+ { NULL }
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_hook_post_config(post_config, NULL, NULL, APR_HOOK_LAST);
+ ap_hook_translate_name(xlate_name, NULL, NULL, APR_HOOK_FIRST);
+}
+
+/* the main config structure */
+
+AP_DECLARE_MODULE(proxy_express) =
+{
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-dir config structures */
+ NULL, /* merge per-dir config structures */
+ server_create, /* create per-server config structures */
+ server_merge, /* merge per-server config structures */
+ command_table, /* table of config file commands */
+ register_hooks /* register hooks */
+};
diff --git a/modules/proxy/mod_proxy_express.dep b/modules/proxy/mod_proxy_express.dep
new file mode 100644
index 0000000..f9e5ffb
--- /dev/null
+++ b/modules/proxy/mod_proxy_express.dep
@@ -0,0 +1,74 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_proxy_express.mak
+
+.\mod_proxy_express.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_slotmem.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_date.h"\
+ "..\..\srclib\apr-util\include\apr_dbm.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_reslist.h"\
+ "..\..\srclib\apr-util\include\apr_strmatch.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_proxy.h"\
+
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
diff --git a/modules/proxy/mod_proxy_express.dsp b/modules/proxy/mod_proxy_express.dsp
new file mode 100644
index 0000000..924c676
--- /dev/null
+++ b/modules/proxy/mod_proxy_express.dsp
@@ -0,0 +1,123 @@
+# Microsoft Developer Studio Project File - Name="mod_proxy_express" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_proxy_express - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_express.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_express.mak" CFG="mod_proxy_express - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy_express - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy_express - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_proxy_express - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_proxy_express_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_proxy_express.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_proxy_express.so" /d LONG_NAME="proxy_balancer_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:".\Release\mod_proxy_express.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_express.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_proxy_express.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_express.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_proxy_express.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_proxy_express - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_proxy_express_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_proxy_express.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_proxy_express.so" /d LONG_NAME="proxy_balancer_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_proxy_express.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_express.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_proxy_express.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_express.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_proxy_express.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_proxy_express - Win32 Release"
+# Name "mod_proxy_express - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\mod_proxy_express.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter ".h"
+# Begin Source File
+
+SOURCE=.\mod_proxy.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/proxy/mod_proxy_express.mak b/modules/proxy/mod_proxy_express.mak
new file mode 100644
index 0000000..f656d22
--- /dev/null
+++ b/modules/proxy/mod_proxy_express.mak
@@ -0,0 +1,380 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_proxy_express.dsp
+!IF "$(CFG)" == ""
+CFG=mod_proxy_express - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_proxy_express - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_proxy_express - Win32 Release" && "$(CFG)" != "mod_proxy_express - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_express.mak" CFG="mod_proxy_express - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy_express - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy_express - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_express - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_proxy_express.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_proxy - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_proxy_express.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_proxy - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_proxy_express.obj"
+ -@erase "$(INTDIR)\mod_proxy_express.res"
+ -@erase "$(INTDIR)\mod_proxy_express_src.idb"
+ -@erase "$(INTDIR)\mod_proxy_express_src.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_express.exp"
+ -@erase "$(OUTDIR)\mod_proxy_express.lib"
+ -@erase "$(OUTDIR)\mod_proxy_express.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_express.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_proxy_express_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_proxy_express.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_proxy_express.so" /d LONG_NAME="proxy_balancer_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_proxy_express.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_proxy_express.pdb" /debug /out:"$(OUTDIR)\mod_proxy_express.so" /implib:"$(OUTDIR)\mod_proxy_express.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_express.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_proxy_express.obj" \
+ "$(INTDIR)\mod_proxy_express.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib" \
+ "$(OUTDIR)\mod_proxy.lib"
+
+"$(OUTDIR)\mod_proxy_express.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_proxy_express.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_proxy_express.so"
+ if exist .\Release\mod_proxy_express.so.manifest mt.exe -manifest .\Release\mod_proxy_express.so.manifest -outputresource:.\Release\mod_proxy_express.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_proxy_express - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_proxy_express.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_proxy - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_proxy_express.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_proxy - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_proxy_express.obj"
+ -@erase "$(INTDIR)\mod_proxy_express.res"
+ -@erase "$(INTDIR)\mod_proxy_express_src.idb"
+ -@erase "$(INTDIR)\mod_proxy_express_src.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_express.exp"
+ -@erase "$(OUTDIR)\mod_proxy_express.lib"
+ -@erase "$(OUTDIR)\mod_proxy_express.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_express.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_proxy_express_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_proxy_express.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_proxy_express.so" /d LONG_NAME="proxy_balancer_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_proxy_express.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_proxy_express.pdb" /debug /out:"$(OUTDIR)\mod_proxy_express.so" /implib:"$(OUTDIR)\mod_proxy_express.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_express.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_proxy_express.obj" \
+ "$(INTDIR)\mod_proxy_express.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib" \
+ "$(OUTDIR)\mod_proxy.lib"
+
+"$(OUTDIR)\mod_proxy_express.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_proxy_express.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_proxy_express.so"
+ if exist .\Debug\mod_proxy_express.so.manifest mt.exe -manifest .\Debug\mod_proxy_express.so.manifest -outputresource:.\Debug\mod_proxy_express.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_proxy_express.dep")
+!INCLUDE "mod_proxy_express.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_proxy_express.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_proxy_express - Win32 Release" || "$(CFG)" == "mod_proxy_express - Win32 Debug"
+SOURCE=.\mod_proxy_express.c
+
+"$(INTDIR)\mod_proxy_express.obj" : $(SOURCE) "$(INTDIR)"
+
+
+!IF "$(CFG)" == "mod_proxy_express - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\proxy"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_express - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\proxy"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_express - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\proxy"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_express - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\proxy"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_express - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\proxy"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_express - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\proxy"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_express - Win32 Release"
+
+"mod_proxy - Win32 Release" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Release"
+ cd "."
+
+"mod_proxy - Win32 ReleaseCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Release" RECURSE=1 CLEAN
+ cd "."
+
+!ELSEIF "$(CFG)" == "mod_proxy_express - Win32 Debug"
+
+"mod_proxy - Win32 Debug" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Debug"
+ cd "."
+
+"mod_proxy - Win32 DebugCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Debug" RECURSE=1 CLEAN
+ cd "."
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_proxy_express - Win32 Release"
+
+
+"$(INTDIR)\mod_proxy_express.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_proxy_express.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_proxy_express.so" /d LONG_NAME="proxy_balancer_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_proxy_express - Win32 Debug"
+
+
+"$(INTDIR)\mod_proxy_express.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_proxy_express.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_proxy_express.so" /d LONG_NAME="proxy_balancer_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+
+!ENDIF
+
diff --git a/modules/proxy/mod_proxy_fcgi.c b/modules/proxy/mod_proxy_fcgi.c
new file mode 100644
index 0000000..3382b9b
--- /dev/null
+++ b/modules/proxy/mod_proxy_fcgi.c
@@ -0,0 +1,1330 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mod_proxy.h"
+#include "util_fcgi.h"
+#include "util_script.h"
+#include "ap_expr.h"
+
+module AP_MODULE_DECLARE_DATA proxy_fcgi_module;
+
+typedef struct {
+ ap_expr_info_t *cond;
+ ap_expr_info_t *subst;
+ const char *envname;
+} sei_entry;
+
+typedef struct {
+ int need_dirwalk;
+} fcgi_req_config_t;
+
+/* We will assume FPM, but still differentiate */
+typedef enum {
+ BACKEND_DEFAULT_UNKNOWN = 0,
+ BACKEND_FPM,
+ BACKEND_GENERIC,
+} fcgi_backend_t;
+
+
+#define FCGI_MAY_BE_FPM(dconf) \
+ (dconf && \
+ ((dconf->backend_type == BACKEND_DEFAULT_UNKNOWN) || \
+ (dconf->backend_type == BACKEND_FPM)))
+
+typedef struct {
+ fcgi_backend_t backend_type;
+ apr_array_header_t *env_fixups;
+} fcgi_dirconf_t;
+
+/*
+ * Canonicalise http-like URLs.
+ * scheme is the scheme for the URL
+ * url is the URL starting with the first '/'
+ * def_port is the default port for this scheme.
+ */
+static int proxy_fcgi_canon(request_rec *r, char *url)
+{
+ char *host, sport[7];
+ const char *err;
+ char *path;
+ apr_port_t port, def_port;
+ fcgi_req_config_t *rconf = NULL;
+ const char *pathinfo_type = NULL;
+
+ if (ap_cstr_casecmpn(url, "fcgi:", 5) == 0) {
+ url += 5;
+ }
+ else {
+ return DECLINED;
+ }
+
+ port = def_port = ap_proxy_port_of_scheme("fcgi");
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "canonicalising URL %s", url);
+ err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01059)
+ "error parsing URL %s: %s", url, err);
+ return HTTP_BAD_REQUEST;
+ }
+
+ if (port != def_port)
+ apr_snprintf(sport, sizeof(sport), ":%d", port);
+ else
+ sport[0] = '\0';
+
+ if (ap_strchr_c(host, ':')) {
+ /* if literal IPv6 address */
+ host = apr_pstrcat(r->pool, "[", host, "]", NULL);
+ }
+
+ if (apr_table_get(r->notes, "proxy-nocanon")) {
+ path = url; /* this is the raw path */
+ }
+ else {
+ path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0,
+ r->proxyreq);
+ }
+ if (path == NULL)
+ return HTTP_BAD_REQUEST;
+
+ r->filename = apr_pstrcat(r->pool, "proxy:fcgi://", host, sport, "/",
+ path, NULL);
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01060)
+ "set r->filename to %s", r->filename);
+
+ rconf = ap_get_module_config(r->request_config, &proxy_fcgi_module);
+ if (rconf == NULL) {
+ rconf = apr_pcalloc(r->pool, sizeof(fcgi_req_config_t));
+ ap_set_module_config(r->request_config, &proxy_fcgi_module, rconf);
+ }
+
+ if (NULL != (pathinfo_type = apr_table_get(r->subprocess_env, "proxy-fcgi-pathinfo"))) {
+ /* It has to be on disk for this to work */
+ if (!strcasecmp(pathinfo_type, "full")) {
+ rconf->need_dirwalk = 1;
+ ap_unescape_url_keep2f(path, 0);
+ }
+ else if (!strcasecmp(pathinfo_type, "first-dot")) {
+ char *split = ap_strchr(path, '.');
+ if (split) {
+ char *slash = ap_strchr(split, '/');
+ if (slash) {
+ r->path_info = apr_pstrdup(r->pool, slash);
+ ap_unescape_url_keep2f(r->path_info, 0);
+ *slash = '\0'; /* truncate path */
+ }
+ }
+ }
+ else if (!strcasecmp(pathinfo_type, "last-dot")) {
+ char *split = ap_strrchr(path, '.');
+ if (split) {
+ char *slash = ap_strchr(split, '/');
+ if (slash) {
+ r->path_info = apr_pstrdup(r->pool, slash);
+ ap_unescape_url_keep2f(r->path_info, 0);
+ *slash = '\0'; /* truncate path */
+ }
+ }
+ }
+ else {
+ /* before proxy-fcgi-pathinfo had multi-values. This requires the
+ * the FCGI server to fixup PATH_INFO because it's the entire path
+ */
+ r->path_info = apr_pstrcat(r->pool, "/", path, NULL);
+ if (!strcasecmp(pathinfo_type, "unescape")) {
+ ap_unescape_url_keep2f(r->path_info, 0);
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01061)
+ "set r->path_info to %s", r->path_info);
+ }
+ }
+
+ return OK;
+}
+
+
+/*
+ ProxyFCGISetEnvIf "reqenv('PATH_INFO') =~ m#/foo(\d+)\.php$#" COVENV1 "$1"
+ ProxyFCGISetEnvIf "reqenv('PATH_INFO') =~ m#/foo(\d+)\.php$#" PATH_INFO "/foo.php"
+ ProxyFCGISetEnvIf "reqenv('PATH_TRANSLATED') =~ m#(/.*foo)(\d+)(.*)#" PATH_TRANSLATED "$1$3"
+*/
+static apr_status_t fix_cgivars(request_rec *r, fcgi_dirconf_t *dconf)
+{
+ sei_entry *entries;
+ const char *err, *src;
+ int i = 0, rc = 0;
+ ap_regmatch_t regm[AP_MAX_REG_MATCH];
+
+ entries = (sei_entry *) dconf->env_fixups->elts;
+ for (i = 0; i < dconf->env_fixups->nelts; i++) {
+ sei_entry *entry = &entries[i];
+
+ rc = ap_expr_exec_re(r, entry->cond, AP_MAX_REG_MATCH, regm, &src, &err);
+ if (rc < 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10241)
+ "fix_cgivars: Condition eval returned %d: %s",
+ rc, err);
+ return APR_EGENERAL;
+ }
+ else if (rc == 0) {
+ continue; /* evaluated false */
+ }
+
+ if (entry->envname[0] == '!') {
+ apr_table_unset(r->subprocess_env, entry->envname+1);
+ }
+ else {
+ const char *val = ap_expr_str_exec_re(r, entry->subst, AP_MAX_REG_MATCH, regm, &src, &err);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(03514)
+ "Error evaluating expression for replacement of %s: '%s'",
+ entry->envname, err);
+ continue;
+ }
+ if (APLOGrtrace4(r)) {
+ const char *oldval = apr_table_get(r->subprocess_env, entry->envname);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r,
+ "fix_cgivars: override %s from '%s' to '%s'",
+ entry->envname, oldval, val);
+
+ }
+ apr_table_setn(r->subprocess_env, entry->envname, val);
+ }
+ }
+ return APR_SUCCESS;
+}
+
+/* Wrapper for apr_socket_sendv that handles updating the worker stats. */
+static apr_status_t send_data(proxy_conn_rec *conn,
+ struct iovec *vec,
+ int nvec,
+ apr_size_t *len)
+{
+ apr_status_t rv = APR_SUCCESS;
+ apr_size_t written = 0, to_write = 0;
+ int i, offset;
+ apr_socket_t *s = conn->sock;
+
+ for (i = 0; i < nvec; i++) {
+ to_write += vec[i].iov_len;
+ }
+
+ offset = 0;
+ while (to_write) {
+ apr_size_t n = 0;
+ rv = apr_socket_sendv(s, vec + offset, nvec - offset, &n);
+ if (rv != APR_SUCCESS) {
+ break;
+ }
+ if (n > 0) {
+ written += n;
+ if (written >= to_write)
+ break; /* short circuit out */
+ for (i = offset; i < nvec; ) {
+ if (n >= vec[i].iov_len) {
+ offset++;
+ n -= vec[i++].iov_len;
+ } else {
+ vec[i].iov_len -= n;
+ vec[i].iov_base = (char *) vec[i].iov_base + n;
+ break;
+ }
+ }
+ }
+ }
+
+ conn->worker->s->transferred += written;
+ *len = written;
+
+ return rv;
+}
+
+/* Wrapper for apr_socket_recv that handles updating the worker stats. */
+static apr_status_t get_data(proxy_conn_rec *conn,
+ char *buffer,
+ apr_size_t *buflen)
+{
+ apr_status_t rv = apr_socket_recv(conn->sock, buffer, buflen);
+
+ if (rv == APR_SUCCESS) {
+ conn->worker->s->read += *buflen;
+ }
+
+ return rv;
+}
+
+static apr_status_t get_data_full(proxy_conn_rec *conn,
+ char *buffer,
+ apr_size_t buflen)
+{
+ apr_size_t readlen;
+ apr_size_t cumulative_len = 0;
+ apr_status_t rv;
+
+ do {
+ readlen = buflen - cumulative_len;
+ rv = get_data(conn, buffer + cumulative_len, &readlen);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ cumulative_len += readlen;
+ } while (cumulative_len < buflen);
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t send_begin_request(proxy_conn_rec *conn,
+ apr_uint16_t request_id)
+{
+ struct iovec vec[2];
+ ap_fcgi_header header;
+ unsigned char farray[AP_FCGI_HEADER_LEN];
+ ap_fcgi_begin_request_body brb;
+ unsigned char abrb[AP_FCGI_HEADER_LEN];
+ apr_size_t len;
+
+ ap_fcgi_fill_in_header(&header, AP_FCGI_BEGIN_REQUEST, request_id,
+ sizeof(abrb), 0);
+
+ ap_fcgi_fill_in_request_body(&brb, AP_FCGI_RESPONDER,
+ ap_proxy_connection_reusable(conn)
+ ? AP_FCGI_KEEP_CONN : 0);
+
+ ap_fcgi_header_to_array(&header, farray);
+ ap_fcgi_begin_request_body_to_array(&brb, abrb);
+
+ vec[0].iov_base = (void *)farray;
+ vec[0].iov_len = sizeof(farray);
+ vec[1].iov_base = (void *)abrb;
+ vec[1].iov_len = sizeof(abrb);
+
+ return send_data(conn, vec, 2, &len);
+}
+
+static apr_status_t send_environment(proxy_conn_rec *conn, request_rec *r,
+ apr_pool_t *temp_pool,
+ apr_uint16_t request_id)
+{
+ const apr_array_header_t *envarr;
+ const apr_table_entry_t *elts;
+ struct iovec vec[2];
+ ap_fcgi_header header;
+ unsigned char farray[AP_FCGI_HEADER_LEN];
+ char *body;
+ apr_status_t rv;
+ apr_size_t avail_len, len, required_len;
+ int next_elem, starting_elem;
+ fcgi_req_config_t *rconf = ap_get_module_config(r->request_config, &proxy_fcgi_module);
+ fcgi_dirconf_t *dconf = ap_get_module_config(r->per_dir_config, &proxy_fcgi_module);
+
+ if (rconf) {
+ if (rconf->need_dirwalk) {
+ ap_directory_walk(r);
+ }
+ }
+
+ /* Strip proxy: prefixes */
+ if (r->filename) {
+ char *newfname = NULL;
+
+ if (!strncmp(r->filename, "proxy:balancer://", 17)) {
+ newfname = apr_pstrdup(r->pool, r->filename+17);
+ }
+
+ if (!FCGI_MAY_BE_FPM(dconf)) {
+ if (!strncmp(r->filename, "proxy:fcgi://", 13)) {
+ /* If we strip this under FPM, and any internal redirect occurs
+ * on PATH_INFO, FPM may use PATH_TRANSLATED instead of
+ * SCRIPT_FILENAME (a la mod_fastcgi + Action).
+ */
+ newfname = apr_pstrdup(r->pool, r->filename+13);
+ }
+ /* Query string in environment only */
+ if (newfname && r->args && *r->args) {
+ char *qs = strrchr(newfname, '?');
+ if (qs && !strcmp(qs+1, r->args)) {
+ *qs = '\0';
+ }
+ }
+ }
+
+ if (newfname) {
+ newfname = ap_strchr(newfname, '/');
+ r->filename = newfname;
+ }
+ }
+
+ ap_add_common_vars(r);
+ ap_add_cgi_vars(r);
+
+ /* XXX are there any FastCGI specific env vars we need to send? */
+
+ /* Give admins final option to fine-tune env vars */
+ if (APR_SUCCESS != (rv = fix_cgivars(r, dconf))) {
+ return rv;
+ }
+
+ /* XXX mod_cgi/mod_cgid use ap_create_environment here, which fills in
+ * the TZ value specially. We could use that, but it would mean
+ * parsing the key/value pairs back OUT of the allocated env array,
+ * not to mention allocating a totally useless array in the first
+ * place, which would suck. */
+
+ envarr = apr_table_elts(r->subprocess_env);
+ elts = (const apr_table_entry_t *) envarr->elts;
+
+ if (APLOGrtrace8(r)) {
+ int i;
+
+ for (i = 0; i < envarr->nelts; ++i) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, r, APLOGNO(01062)
+ "sending env var '%s' value '%s'",
+ elts[i].key, elts[i].val);
+ }
+ }
+
+ /* Send envvars over in as many FastCGI records as it takes, */
+ next_elem = 0; /* starting with the first one */
+
+ avail_len = 16 * 1024; /* our limit per record, which could have been up
+ * to AP_FCGI_MAX_CONTENT_LEN
+ */
+
+ while (next_elem < envarr->nelts) {
+ starting_elem = next_elem;
+ required_len = ap_fcgi_encoded_env_len(r->subprocess_env,
+ avail_len,
+ &next_elem);
+
+ if (!required_len) {
+ if (next_elem < envarr->nelts) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+ APLOGNO(02536) "couldn't encode envvar '%s' in %"
+ APR_SIZE_T_FMT " bytes",
+ elts[next_elem].key, avail_len);
+ /* skip this envvar and continue */
+ ++next_elem;
+ continue;
+ }
+ /* only an unused element at the end of the array */
+ break;
+ }
+
+ body = apr_palloc(temp_pool, required_len);
+ rv = ap_fcgi_encode_env(r, r->subprocess_env, body, required_len,
+ &starting_elem);
+ /* we pre-compute, so we can't run out of space */
+ ap_assert(rv == APR_SUCCESS);
+ /* compute and encode must be in sync */
+ ap_assert(starting_elem == next_elem);
+
+ ap_fcgi_fill_in_header(&header, AP_FCGI_PARAMS, request_id,
+ (apr_uint16_t)required_len, 0);
+ ap_fcgi_header_to_array(&header, farray);
+
+ vec[0].iov_base = (void *)farray;
+ vec[0].iov_len = sizeof(farray);
+ vec[1].iov_base = body;
+ vec[1].iov_len = required_len;
+
+ rv = send_data(conn, vec, 2, &len);
+ apr_pool_clear(temp_pool);
+
+ if (rv) {
+ return rv;
+ }
+ }
+
+ /* Envvars sent, so say we're done */
+ ap_fcgi_fill_in_header(&header, AP_FCGI_PARAMS, request_id, 0, 0);
+ ap_fcgi_header_to_array(&header, farray);
+
+ vec[0].iov_base = (void *)farray;
+ vec[0].iov_len = sizeof(farray);
+
+ return send_data(conn, vec, 1, &len);
+}
+
+enum {
+ HDR_STATE_READING_HEADERS,
+ HDR_STATE_GOT_CR,
+ HDR_STATE_GOT_CRLF,
+ HDR_STATE_GOT_CRLFCR,
+ HDR_STATE_GOT_LF,
+ HDR_STATE_DONE_WITH_HEADERS
+};
+
+/* Try to find the end of the script headers in the response from the back
+ * end fastcgi server. STATE holds the current header parsing state for this
+ * request.
+ *
+ * Returns 0 if it can't find the end of the headers, and 1 if it found the
+ * end of the headers. */
+static int handle_headers(request_rec *r, int *state,
+ const char *readbuf, apr_size_t readlen)
+{
+ const char *itr = readbuf;
+
+ while (readlen--) {
+ if (*itr == '\r') {
+ switch (*state) {
+ case HDR_STATE_GOT_CRLF:
+ *state = HDR_STATE_GOT_CRLFCR;
+ break;
+
+ default:
+ *state = HDR_STATE_GOT_CR;
+ break;
+ }
+ }
+ else if (*itr == '\n') {
+ switch (*state) {
+ case HDR_STATE_GOT_LF:
+ *state = HDR_STATE_DONE_WITH_HEADERS;
+ break;
+
+ case HDR_STATE_GOT_CR:
+ *state = HDR_STATE_GOT_CRLF;
+ break;
+
+ case HDR_STATE_GOT_CRLFCR:
+ *state = HDR_STATE_DONE_WITH_HEADERS;
+ break;
+
+ default:
+ *state = HDR_STATE_GOT_LF;
+ break;
+ }
+ }
+ else {
+ *state = HDR_STATE_READING_HEADERS;
+ }
+
+ if (*state == HDR_STATE_DONE_WITH_HEADERS)
+ break;
+
+ ++itr;
+ }
+
+ if (*state == HDR_STATE_DONE_WITH_HEADERS) {
+ return 1;
+ }
+
+ return 0;
+}
+
+static apr_status_t dispatch(proxy_conn_rec *conn, proxy_dir_conf *conf,
+ request_rec *r, apr_pool_t *setaside_pool,
+ apr_uint16_t request_id, const char **err,
+ int *bad_request, int *has_responded,
+ apr_bucket_brigade *input_brigade)
+{
+ apr_bucket_brigade *ib, *ob;
+ int seen_end_of_headers = 0, done = 0, ignore_body = 0;
+ apr_status_t rv = APR_SUCCESS;
+ int script_error_status = HTTP_OK;
+ conn_rec *c = r->connection;
+ struct iovec vec[2];
+ ap_fcgi_header header;
+ unsigned char farray[AP_FCGI_HEADER_LEN];
+ apr_pollfd_t pfd;
+ apr_pollfd_t *flushpoll = NULL;
+ apr_int32_t flushpoll_fd;
+ int header_state = HDR_STATE_READING_HEADERS;
+ char stack_iobuf[AP_IOBUFSIZE];
+ apr_size_t iobuf_size = AP_IOBUFSIZE;
+ char *iobuf = stack_iobuf;
+
+ *err = NULL;
+ if (conn->worker->s->io_buffer_size_set) {
+ iobuf_size = conn->worker->s->io_buffer_size;
+ iobuf = apr_palloc(r->pool, iobuf_size);
+ }
+
+ pfd.desc_type = APR_POLL_SOCKET;
+ pfd.desc.s = conn->sock;
+ pfd.p = r->pool;
+ pfd.reqevents = APR_POLLIN | APR_POLLOUT;
+
+ if (conn->worker->s->flush_packets == flush_auto) {
+ flushpoll = apr_pcalloc(r->pool, sizeof(apr_pollfd_t));
+ flushpoll->reqevents = APR_POLLIN;
+ flushpoll->desc_type = APR_POLL_SOCKET;
+ flushpoll->desc.s = conn->sock;
+ }
+
+ ib = apr_brigade_create(r->pool, c->bucket_alloc);
+ ob = apr_brigade_create(r->pool, c->bucket_alloc);
+
+ while (! done) {
+ apr_interval_time_t timeout;
+ apr_size_t len;
+ int n;
+
+ /* We need SOME kind of timeout here, or virtually anything will
+ * cause timeout errors. */
+ apr_socket_timeout_get(conn->sock, &timeout);
+
+ rv = apr_poll(&pfd, 1, &n, timeout);
+ if (rv != APR_SUCCESS) {
+ if (APR_STATUS_IS_EINTR(rv)) {
+ continue;
+ }
+ *err = "polling";
+ break;
+ }
+
+ if (pfd.rtnevents & APR_POLLOUT) {
+ apr_size_t to_send, writebuflen;
+ int last_stdin = 0;
+ char *iobuf_cursor;
+
+ if (APR_BRIGADE_EMPTY(input_brigade)) {
+ rv = ap_get_brigade(r->input_filters, ib,
+ AP_MODE_READBYTES, APR_BLOCK_READ,
+ iobuf_size);
+ }
+ else {
+ apr_bucket *e;
+ APR_BRIGADE_CONCAT(ib, input_brigade);
+ rv = apr_brigade_partition(ib, iobuf_size, &e);
+ if (rv == APR_SUCCESS) {
+ while (e != APR_BRIGADE_SENTINEL(ib)
+ && APR_BUCKET_IS_METADATA(e)) {
+ e = APR_BUCKET_NEXT(e);
+ }
+ apr_brigade_split_ex(ib, e, input_brigade);
+ }
+ else if (rv == APR_INCOMPLETE) {
+ rv = APR_SUCCESS;
+ }
+ }
+ if (rv != APR_SUCCESS) {
+ *err = "reading input brigade";
+ *bad_request = 1;
+ break;
+ }
+
+ if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(ib))) {
+ last_stdin = 1;
+ }
+
+ writebuflen = iobuf_size;
+
+ rv = apr_brigade_flatten(ib, iobuf, &writebuflen);
+
+ apr_brigade_cleanup(ib);
+
+ if (rv != APR_SUCCESS) {
+ *err = "flattening brigade";
+ break;
+ }
+
+ to_send = writebuflen;
+ iobuf_cursor = iobuf;
+ while (to_send > 0) {
+ int nvec = 0;
+ apr_size_t write_this_time;
+
+ write_this_time =
+ to_send < AP_FCGI_MAX_CONTENT_LEN ? to_send : AP_FCGI_MAX_CONTENT_LEN;
+
+ ap_fcgi_fill_in_header(&header, AP_FCGI_STDIN, request_id,
+ (apr_uint16_t)write_this_time, 0);
+ ap_fcgi_header_to_array(&header, farray);
+
+ vec[nvec].iov_base = (void *)farray;
+ vec[nvec].iov_len = sizeof(farray);
+ ++nvec;
+ if (writebuflen) {
+ vec[nvec].iov_base = iobuf_cursor;
+ vec[nvec].iov_len = write_this_time;
+ ++nvec;
+ }
+
+ rv = send_data(conn, vec, nvec, &len);
+ if (rv != APR_SUCCESS) {
+ *err = "sending stdin";
+ break;
+ }
+
+ to_send -= write_this_time;
+ iobuf_cursor += write_this_time;
+ }
+ if (rv != APR_SUCCESS) {
+ break;
+ }
+
+ if (last_stdin) {
+ pfd.reqevents = APR_POLLIN; /* Done with input data */
+
+ /* signal EOF (empty FCGI_STDIN) */
+ ap_fcgi_fill_in_header(&header, AP_FCGI_STDIN, request_id,
+ 0, 0);
+ ap_fcgi_header_to_array(&header, farray);
+
+ vec[0].iov_base = (void *)farray;
+ vec[0].iov_len = sizeof(farray);
+
+ rv = send_data(conn, vec, 1, &len);
+ if (rv != APR_SUCCESS) {
+ *err = "sending empty stdin";
+ break;
+ }
+ }
+ }
+
+ if (pfd.rtnevents & APR_POLLIN) {
+ apr_size_t readbuflen;
+ apr_uint16_t clen, rid;
+ apr_bucket *b;
+ unsigned char plen;
+ unsigned char type, version;
+ int mayflush = 0;
+
+ /* First, we grab the header... */
+ rv = get_data_full(conn, (char *) farray, AP_FCGI_HEADER_LEN);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01067)
+ "Failed to read FastCGI header");
+ break;
+ }
+
+ ap_log_rdata(APLOG_MARK, APLOG_TRACE8, r, "FastCGI header",
+ farray, AP_FCGI_HEADER_LEN, 0);
+
+ ap_fcgi_header_fields_from_array(&version, &type, &rid,
+ &clen, &plen, farray);
+
+ if (version != AP_FCGI_VERSION_1) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01068)
+ "Got bogus version %d", (int)version);
+ rv = APR_EINVAL;
+ break;
+ }
+
+ if (rid != request_id) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01069)
+ "Got bogus rid %d, expected %d",
+ rid, request_id);
+ rv = APR_EINVAL;
+ break;
+ }
+
+recv_again:
+ if (clen > iobuf_size) {
+ readbuflen = iobuf_size;
+ } else {
+ readbuflen = clen;
+ }
+
+ /* Now get the actual data. Yes it sucks to do this in a second
+ * recv call, this will eventually change when we move to real
+ * nonblocking recv calls. */
+ if (readbuflen != 0) {
+ rv = get_data(conn, iobuf, &readbuflen);
+ if (rv != APR_SUCCESS) {
+ *err = "reading response body";
+ break;
+ }
+ }
+
+ switch (type) {
+ case AP_FCGI_STDOUT:
+ if (clen != 0) {
+ b = apr_bucket_transient_create(iobuf,
+ readbuflen,
+ c->bucket_alloc);
+
+ APR_BRIGADE_INSERT_TAIL(ob, b);
+
+ if (! seen_end_of_headers) {
+ int st = handle_headers(r, &header_state,
+ iobuf, readbuflen);
+
+ if (st == 1) {
+ int status;
+ seen_end_of_headers = 1;
+
+ status = ap_scan_script_header_err_brigade_ex(r, ob,
+ NULL, APLOG_MODULE_INDEX);
+ /* suck in all the rest */
+ if (status != OK) {
+ apr_bucket *tmp_b;
+ apr_brigade_cleanup(ob);
+ tmp_b = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(ob, tmp_b);
+
+ *has_responded = 1;
+ r->status = status;
+ rv = ap_pass_brigade(r->output_filters, ob);
+ if (rv != APR_SUCCESS) {
+ *err = "passing headers brigade to output filters";
+ break;
+ }
+ else if (status == HTTP_NOT_MODIFIED
+ || status == HTTP_PRECONDITION_FAILED) {
+ /* Special 'status' cases handled:
+ * 1) HTTP 304 response MUST NOT contain
+ * a message-body, ignore it.
+ * 2) HTTP 412 response.
+ * The break is not added since there might
+ * be more bytes to read from the FCGI
+ * connection. Even if the message-body is
+ * ignored (and the EOS bucket has already
+ * been sent) we want to avoid subsequent
+ * bogus reads. */
+ ignore_body = 1;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01070)
+ "Error parsing script headers");
+ rv = APR_EINVAL;
+ break;
+ }
+ }
+
+ if (ap_proxy_should_override(conf, r->status) && ap_is_initial_req(r)) {
+ /*
+ * set script_error_status to discard
+ * everything after the headers
+ */
+ script_error_status = r->status;
+ /*
+ * prevent ap_die() from treating this as a
+ * recursive error, initially:
+ */
+ r->status = HTTP_OK;
+ }
+
+ if (script_error_status == HTTP_OK
+ && !APR_BRIGADE_EMPTY(ob) && !ignore_body) {
+ /* Send the part of the body that we read while
+ * reading the headers.
+ */
+ *has_responded = 1;
+ rv = ap_pass_brigade(r->output_filters, ob);
+ if (rv != APR_SUCCESS) {
+ *err = "passing brigade to output filters";
+ break;
+ }
+ mayflush = 1;
+ }
+ apr_brigade_cleanup(ob);
+
+ apr_pool_clear(setaside_pool);
+ }
+ else {
+ /* We're still looking for the end of the
+ * headers, so this part of the data will need
+ * to persist. */
+ apr_bucket_setaside(b, setaside_pool);
+ }
+ } else {
+ /* we've already passed along the headers, so now pass
+ * through the content. we could simply continue to
+ * setaside the content and not pass until we see the
+ * 0 content-length (below, where we append the EOS),
+ * but that could be a huge amount of data; so we pass
+ * along smaller chunks
+ */
+ if (script_error_status == HTTP_OK && !ignore_body) {
+ *has_responded = 1;
+ rv = ap_pass_brigade(r->output_filters, ob);
+ if (rv != APR_SUCCESS) {
+ *err = "passing brigade to output filters";
+ break;
+ }
+ mayflush = 1;
+ }
+ apr_brigade_cleanup(ob);
+ }
+
+ /* If we didn't read all the data, go back and get the
+ * rest of it. */
+ if (clen > readbuflen) {
+ clen -= readbuflen;
+ goto recv_again;
+ }
+ } else {
+ /* XXX what if we haven't seen end of the headers yet? */
+
+ if (script_error_status == HTTP_OK) {
+ b = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(ob, b);
+
+ *has_responded = 1;
+ rv = ap_pass_brigade(r->output_filters, ob);
+ if (rv != APR_SUCCESS) {
+ *err = "passing brigade to output filters";
+ break;
+ }
+ }
+
+ /* XXX Why don't we cleanup here? (logic from AJP) */
+ }
+ break;
+
+ case AP_FCGI_STDERR:
+ /* TODO: Should probably clean up this logging a bit... */
+ if (clen) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01071)
+ "Got error '%.*s'", (int)readbuflen, iobuf);
+ }
+
+ if (clen > readbuflen) {
+ clen -= readbuflen;
+ goto recv_again;
+ }
+ break;
+
+ case AP_FCGI_END_REQUEST:
+ done = 1;
+ break;
+
+ default:
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01072)
+ "Got bogus record %d", type);
+ break;
+ }
+ /* Leave on above switch's inner error. */
+ if (rv != APR_SUCCESS) {
+ break;
+ }
+
+ if (plen) {
+ rv = get_data_full(conn, iobuf, plen);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02537)
+ "Error occurred reading padding");
+ break;
+ }
+ }
+
+ if (mayflush && ((conn->worker->s->flush_packets == flush_on) ||
+ ((conn->worker->s->flush_packets == flush_auto) &&
+ (apr_poll(flushpoll, 1, &flushpoll_fd,
+ conn->worker->s->flush_wait) == APR_TIMEUP)))) {
+ apr_bucket* flush_b = apr_bucket_flush_create(r->connection->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(ob, flush_b);
+ rv = ap_pass_brigade(r->output_filters, ob);
+ if (rv != APR_SUCCESS) {
+ *err = "passing headers brigade to output filters";
+ break;
+ }
+ mayflush = 0;
+ }
+ }
+ }
+
+ apr_brigade_destroy(ib);
+ apr_brigade_destroy(ob);
+
+ if (script_error_status != HTTP_OK) {
+ ap_die(script_error_status, r); /* send ErrorDocument */
+ *has_responded = 1;
+ }
+
+ return rv;
+}
+
+/*
+ * process the request and write the response.
+ */
+static int fcgi_do_request(apr_pool_t *p, request_rec *r,
+ proxy_conn_rec *conn,
+ conn_rec *origin,
+ proxy_dir_conf *conf,
+ apr_uri_t *uri,
+ char *url, char *server_portstr,
+ apr_bucket_brigade *input_brigade)
+{
+ /* Request IDs are arbitrary numbers that we assign to a
+ * single request. This would allow multiplex/pipelining of
+ * multiple requests to the same FastCGI connection, but
+ * we don't support that, and always use a value of '1' to
+ * keep things simple. */
+ apr_uint16_t request_id = 1;
+ apr_status_t rv;
+ apr_pool_t *temp_pool;
+ const char *err;
+ int bad_request = 0,
+ has_responded = 0;
+
+ /* Step 1: Send AP_FCGI_BEGIN_REQUEST */
+ rv = send_begin_request(conn, request_id);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01073)
+ "Failed Writing Request to %s:", server_portstr);
+ conn->close = 1;
+ return HTTP_SERVICE_UNAVAILABLE;
+ }
+
+ apr_pool_create(&temp_pool, r->pool);
+ apr_pool_tag(temp_pool, "proxy_fcgi_do_request");
+
+ /* Step 2: Send Environment via FCGI_PARAMS */
+ rv = send_environment(conn, r, temp_pool, request_id);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01074)
+ "Failed writing Environment to %s:", server_portstr);
+ conn->close = 1;
+ return HTTP_SERVICE_UNAVAILABLE;
+ }
+
+ /* Step 3: Read records from the back end server and handle them. */
+ rv = dispatch(conn, conf, r, temp_pool, request_id,
+ &err, &bad_request, &has_responded,
+ input_brigade);
+ if (rv != APR_SUCCESS) {
+ /* If the client aborted the connection during retrieval or (partially)
+ * sending the response, don't return a HTTP_SERVICE_UNAVAILABLE, since
+ * this is not a backend problem. */
+ if (r->connection->aborted) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r,
+ "The client aborted the connection.");
+ conn->close = 1;
+ return OK;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01075)
+ "Error dispatching request to %s: %s%s%s",
+ server_portstr,
+ err ? "(" : "",
+ err ? err : "",
+ err ? ")" : "");
+ conn->close = 1;
+ if (has_responded) {
+ return AP_FILTER_ERROR;
+ }
+ if (bad_request) {
+ return ap_map_http_request_error(rv, HTTP_BAD_REQUEST);
+ }
+ if (APR_STATUS_IS_TIMEUP(rv)) {
+ return HTTP_GATEWAY_TIME_OUT;
+ }
+ return HTTP_SERVICE_UNAVAILABLE;
+ }
+
+ return OK;
+}
+
+#define FCGI_SCHEME "FCGI"
+
+#define MAX_MEM_SPOOL 16384
+
+/*
+ * This handles fcgi:(dest) URLs
+ */
+static int proxy_fcgi_handler(request_rec *r, proxy_worker *worker,
+ proxy_server_conf *conf,
+ char *url, const char *proxyname,
+ apr_port_t proxyport)
+{
+ int status;
+ char server_portstr[32];
+ conn_rec *origin = NULL;
+ proxy_conn_rec *backend = NULL;
+ apr_bucket_brigade *input_brigade;
+ apr_off_t input_bytes = 0;
+ apr_uri_t *uri;
+
+ proxy_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
+ &proxy_module);
+
+ apr_pool_t *p = r->pool;
+
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01076)
+ "url: %s proxyname: %s proxyport: %d",
+ url, proxyname, proxyport);
+
+ if (ap_cstr_casecmpn(url, "fcgi:", 5) != 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01077) "declining URL %s", url);
+ return DECLINED;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01078) "serving URL %s", url);
+
+ /* Create space for state information */
+ status = ap_proxy_acquire_connection(FCGI_SCHEME, &backend, worker,
+ r->server);
+ if (status != OK) {
+ if (backend) {
+ backend->close = 1;
+ ap_proxy_release_connection(FCGI_SCHEME, backend, r->server);
+ }
+ return status;
+ }
+
+ backend->is_ssl = 0;
+
+ /* Step One: Determine Who To Connect To */
+ uri = apr_palloc(p, sizeof(*uri));
+ status = ap_proxy_determine_connection(p, r, conf, worker, backend,
+ uri, &url, proxyname, proxyport,
+ server_portstr,
+ sizeof(server_portstr));
+ if (status != OK) {
+ goto cleanup;
+ }
+
+ /* We possibly reuse input data prefetched in previous call(s), e.g. for a
+ * balancer fallback scenario.
+ */
+ apr_pool_userdata_get((void **)&input_brigade, "proxy-fcgi-input", p);
+ if (input_brigade == NULL) {
+ const char *old_te = apr_table_get(r->headers_in, "Transfer-Encoding");
+ const char *old_cl = NULL;
+ if (old_te) {
+ apr_table_unset(r->headers_in, "Content-Length");
+ }
+ else {
+ old_cl = apr_table_get(r->headers_in, "Content-Length");
+ }
+
+ input_brigade = apr_brigade_create(p, r->connection->bucket_alloc);
+ apr_pool_userdata_setn(input_brigade, "proxy-fcgi-input", NULL, p);
+
+ /* Prefetch (nonlocking) the request body so to increase the chance
+ * to get the whole (or enough) body and determine Content-Length vs
+ * chunked or spooled. By doing this before connecting or reusing the
+ * backend, we want to minimize the delay between this connection is
+ * considered alive and the first bytes sent (should the client's link
+ * be slow or some input filter retain the data). This is a best effort
+ * to prevent the backend from closing (from under us) what it thinks is
+ * an idle connection, hence to reduce to the minimum the unavoidable
+ * local is_socket_connected() vs remote keepalive race condition.
+ */
+ status = ap_proxy_prefetch_input(r, backend, input_brigade,
+ APR_NONBLOCK_READ, &input_bytes,
+ MAX_MEM_SPOOL);
+ if (status != OK) {
+ goto cleanup;
+ }
+
+ /*
+ * The request body is streamed by default, using either C-L or
+ * chunked T-E, like this:
+ *
+ * The whole body (including no body) was received on prefetch, i.e.
+ * the input brigade ends with EOS => C-L = input_bytes.
+ *
+ * C-L is known and reliable, i.e. only protocol filters in the input
+ * chain thus none should change the body => use C-L from client.
+ *
+ * The administrator has not "proxy-sendcl" which prevents T-E => use
+ * T-E and chunks.
+ *
+ * Otherwise we need to determine and set a content-length, so spool
+ * the entire request body to memory/temporary file (MAX_MEM_SPOOL),
+ * such that we finally know its length => C-L = input_bytes.
+ */
+ if (!APR_BRIGADE_EMPTY(input_brigade)
+ && APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade))) {
+ /* The whole thing fit, so our decision is trivial, use the input
+ * bytes for the Content-Length. If we expected no body, and read
+ * no body, do not set the Content-Length.
+ */
+ if (old_cl || old_te || input_bytes) {
+ apr_table_setn(r->headers_in, "Content-Length",
+ apr_off_t_toa(p, input_bytes));
+ if (old_te) {
+ apr_table_unset(r->headers_in, "Transfer-Encoding");
+ }
+ }
+ }
+ else if (old_cl && r->input_filters == r->proto_input_filters) {
+ /* Streaming is possible by preserving the existing C-L */
+ }
+ else if (!apr_table_get(r->subprocess_env, "proxy-sendcl")) {
+ /* Streaming is possible using T-E: chunked */
+ }
+ else {
+ /* No streaming, C-L is the only option so spool to memory/file */
+ apr_bucket_brigade *tmp_bb;
+ apr_off_t remaining_bytes = 0;
+
+ AP_DEBUG_ASSERT(MAX_MEM_SPOOL >= input_bytes);
+ tmp_bb = apr_brigade_create(p, r->connection->bucket_alloc);
+ status = ap_proxy_spool_input(r, backend, tmp_bb, &remaining_bytes,
+ MAX_MEM_SPOOL - input_bytes);
+ if (status != OK) {
+ goto cleanup;
+ }
+
+ APR_BRIGADE_CONCAT(input_brigade, tmp_bb);
+ input_bytes += remaining_bytes;
+
+ apr_table_setn(r->headers_in, "Content-Length",
+ apr_off_t_toa(p, input_bytes));
+ if (old_te) {
+ apr_table_unset(r->headers_in, "Transfer-Encoding");
+ }
+ }
+ }
+
+ /* This scheme handler does not reuse connections by default, to
+ * avoid tying up a fastcgi that isn't expecting to work on
+ * parallel requests. But if the user went out of their way to
+ * type the default value of disablereuse=off, we'll allow it.
+ */
+ backend->close = 1;
+ if (worker->s->disablereuse_set && !worker->s->disablereuse) {
+ backend->close = 0;
+ }
+
+ /* Step Two: Make the Connection */
+ if (ap_proxy_check_connection(FCGI_SCHEME, backend, r->server, 0,
+ PROXY_CHECK_CONN_EMPTY)
+ && ap_proxy_connect_backend(FCGI_SCHEME, backend, worker,
+ r->server)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01079)
+ "failed to make connection to backend: %s",
+ backend->hostname);
+ status = HTTP_SERVICE_UNAVAILABLE;
+ goto cleanup;
+ }
+
+ /* Step Three: Process the Request */
+ status = fcgi_do_request(p, r, backend, origin, dconf, uri, url,
+ server_portstr, input_brigade);
+
+cleanup:
+ ap_proxy_release_connection(FCGI_SCHEME, backend, r->server);
+ return status;
+}
+
+static void *fcgi_create_dconf(apr_pool_t *p, char *path)
+{
+ fcgi_dirconf_t *a;
+
+ a = (fcgi_dirconf_t *)apr_pcalloc(p, sizeof(fcgi_dirconf_t));
+ a->backend_type = BACKEND_DEFAULT_UNKNOWN;
+ a->env_fixups = apr_array_make(p, 20, sizeof(sei_entry));
+
+ return a;
+}
+
+static void *fcgi_merge_dconf(apr_pool_t *p, void *basev, void *overridesv)
+{
+ fcgi_dirconf_t *a, *base, *over;
+
+ a = (fcgi_dirconf_t *)apr_pcalloc(p, sizeof(fcgi_dirconf_t));
+ base = (fcgi_dirconf_t *)basev;
+ over = (fcgi_dirconf_t *)overridesv;
+
+ a->backend_type = (over->backend_type != BACKEND_DEFAULT_UNKNOWN)
+ ? over->backend_type
+ : base->backend_type;
+ a->env_fixups = apr_array_append(p, base->env_fixups, over->env_fixups);
+ return a;
+}
+
+static const char *cmd_servertype(cmd_parms *cmd, void *in_dconf,
+ const char *val)
+{
+ fcgi_dirconf_t *dconf = in_dconf;
+
+ if (!strcasecmp(val, "GENERIC")) {
+ dconf->backend_type = BACKEND_GENERIC;
+ }
+ else if (!strcasecmp(val, "FPM")) {
+ dconf->backend_type = BACKEND_FPM;
+ }
+ else {
+ return "ProxyFCGIBackendType requires one of the following arguments: "
+ "'GENERIC', 'FPM'";
+ }
+
+ return NULL;
+}
+
+
+static const char *cmd_setenv(cmd_parms *cmd, void *in_dconf,
+ const char *arg1, const char *arg2,
+ const char *arg3)
+{
+ fcgi_dirconf_t *dconf = in_dconf;
+ const char *err;
+ sei_entry *new;
+ const char *envvar = arg2;
+
+ new = apr_array_push(dconf->env_fixups);
+ new->cond = ap_expr_parse_cmd(cmd, arg1, 0, &err, NULL);
+ if (err) {
+ return apr_psprintf(cmd->pool, "Could not parse expression \"%s\": %s",
+ arg1, err);
+ }
+
+ if (envvar[0] == '!') {
+ /* Unset mode. */
+ if (arg3) {
+ return apr_psprintf(cmd->pool, "Third argument (\"%s\") is not "
+ "allowed when using ProxyFCGISetEnvIf's unset "
+ "mode (%s)", arg3, envvar);
+ }
+ else if (!envvar[1]) {
+ /* i.e. someone tried to give us a name of just "!" */
+ return "ProxyFCGISetEnvIf: \"!\" is not a valid variable name";
+ }
+
+ new->subst = NULL;
+ }
+ else {
+ /* Set mode. */
+ if (!arg3) {
+ /* A missing expr-value should be treated as empty. */
+ arg3 = "";
+ }
+
+ new->subst = ap_expr_parse_cmd(cmd, arg3, AP_EXPR_FLAG_STRING_RESULT, &err, NULL);
+ if (err) {
+ return apr_psprintf(cmd->pool, "Could not parse expression \"%s\": %s",
+ arg3, err);
+ }
+ }
+
+ new->envname = envvar;
+
+ return NULL;
+}
+static void register_hooks(apr_pool_t *p)
+{
+ proxy_hook_scheme_handler(proxy_fcgi_handler, NULL, NULL, APR_HOOK_FIRST);
+ proxy_hook_canon_handler(proxy_fcgi_canon, NULL, NULL, APR_HOOK_FIRST);
+}
+
+static const command_rec command_table[] = {
+ AP_INIT_TAKE1("ProxyFCGIBackendType", cmd_servertype, NULL, OR_FILEINFO,
+ "Specify the type of FastCGI server: 'Generic', 'FPM'"),
+ AP_INIT_TAKE23("ProxyFCGISetEnvIf", cmd_setenv, NULL, OR_FILEINFO,
+ "expr-condition env-name expr-value"),
+ { NULL }
+};
+
+AP_DECLARE_MODULE(proxy_fcgi) = {
+ STANDARD20_MODULE_STUFF,
+ fcgi_create_dconf, /* create per-directory config structure */
+ fcgi_merge_dconf, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ command_table, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/proxy/mod_proxy_fcgi.dep b/modules/proxy/mod_proxy_fcgi.dep
new file mode 100644
index 0000000..a2fc7cb
--- /dev/null
+++ b/modules/proxy/mod_proxy_fcgi.dep
@@ -0,0 +1,75 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_proxy_fcgi.mak
+
+.\mod_proxy_fcgi.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_slotmem.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_fcgi.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_date.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_reslist.h"\
+ "..\..\srclib\apr-util\include\apr_strmatch.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_proxy.h"\
+
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
diff --git a/modules/proxy/mod_proxy_fcgi.dsp b/modules/proxy/mod_proxy_fcgi.dsp
new file mode 100644
index 0000000..ac778ed
--- /dev/null
+++ b/modules/proxy/mod_proxy_fcgi.dsp
@@ -0,0 +1,123 @@
+# Microsoft Developer Studio Project File - Name="mod_proxy_fcgi" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_proxy_fcgi - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_fcgi.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_fcgi.mak" CFG="mod_proxy_fcgi - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy_fcgi - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy_fcgi - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_proxy_fcgi - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_proxy_fcgi_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_proxy_fcgi.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_proxy_fcgi.so" /d LONG_NAME="proxy_fcgi_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:".\Release\mod_proxy_fcgi.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_fcgi.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_proxy_fcgi.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_fcgi.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_proxy_fcgi.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_proxy_fcgi - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_proxy_fcgi_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_proxy_fcgi.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_proxy_fcgi.so" /d LONG_NAME="proxy_fcgi_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_proxy_fcgi.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_fcgi.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_proxy_fcgi.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_fcgi.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_proxy_fcgi.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_proxy_fcgi - Win32 Release"
+# Name "mod_proxy_fcgi - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\mod_proxy_fcgi.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter ".h"
+# Begin Source File
+
+SOURCE=.\mod_proxy.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/proxy/mod_proxy_fcgi.mak b/modules/proxy/mod_proxy_fcgi.mak
new file mode 100644
index 0000000..4b15088
--- /dev/null
+++ b/modules/proxy/mod_proxy_fcgi.mak
@@ -0,0 +1,380 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_proxy_fcgi.dsp
+!IF "$(CFG)" == ""
+CFG=mod_proxy_fcgi - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_proxy_fcgi - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_proxy_fcgi - Win32 Release" && "$(CFG)" != "mod_proxy_fcgi - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_fcgi.mak" CFG="mod_proxy_fcgi - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy_fcgi - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy_fcgi - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_fcgi - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_proxy_fcgi.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_proxy - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_proxy_fcgi.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_proxy - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_proxy_fcgi.obj"
+ -@erase "$(INTDIR)\mod_proxy_fcgi.res"
+ -@erase "$(INTDIR)\mod_proxy_fcgi_src.idb"
+ -@erase "$(INTDIR)\mod_proxy_fcgi_src.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_fcgi.exp"
+ -@erase "$(OUTDIR)\mod_proxy_fcgi.lib"
+ -@erase "$(OUTDIR)\mod_proxy_fcgi.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_fcgi.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_proxy_fcgi_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_proxy_fcgi.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_proxy_fcgi.so" /d LONG_NAME="proxy_fcgi_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_proxy_fcgi.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_proxy_fcgi.pdb" /debug /out:"$(OUTDIR)\mod_proxy_fcgi.so" /implib:"$(OUTDIR)\mod_proxy_fcgi.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_fcgi.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_proxy_fcgi.obj" \
+ "$(INTDIR)\mod_proxy_fcgi.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib" \
+ "$(OUTDIR)\mod_proxy.lib"
+
+"$(OUTDIR)\mod_proxy_fcgi.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_proxy_fcgi.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_proxy_fcgi.so"
+ if exist .\Release\mod_proxy_fcgi.so.manifest mt.exe -manifest .\Release\mod_proxy_fcgi.so.manifest -outputresource:.\Release\mod_proxy_fcgi.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_proxy_fcgi - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_proxy_fcgi.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_proxy - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_proxy_fcgi.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_proxy - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_proxy_fcgi.obj"
+ -@erase "$(INTDIR)\mod_proxy_fcgi.res"
+ -@erase "$(INTDIR)\mod_proxy_fcgi_src.idb"
+ -@erase "$(INTDIR)\mod_proxy_fcgi_src.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_fcgi.exp"
+ -@erase "$(OUTDIR)\mod_proxy_fcgi.lib"
+ -@erase "$(OUTDIR)\mod_proxy_fcgi.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_fcgi.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_proxy_fcgi_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_proxy_fcgi.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_proxy_fcgi.so" /d LONG_NAME="proxy_fcgi_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_proxy_fcgi.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_proxy_fcgi.pdb" /debug /out:"$(OUTDIR)\mod_proxy_fcgi.so" /implib:"$(OUTDIR)\mod_proxy_fcgi.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_fcgi.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_proxy_fcgi.obj" \
+ "$(INTDIR)\mod_proxy_fcgi.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib" \
+ "$(OUTDIR)\mod_proxy.lib"
+
+"$(OUTDIR)\mod_proxy_fcgi.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_proxy_fcgi.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_proxy_fcgi.so"
+ if exist .\Debug\mod_proxy_fcgi.so.manifest mt.exe -manifest .\Debug\mod_proxy_fcgi.so.manifest -outputresource:.\Debug\mod_proxy_fcgi.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_proxy_fcgi.dep")
+!INCLUDE "mod_proxy_fcgi.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_proxy_fcgi.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_proxy_fcgi - Win32 Release" || "$(CFG)" == "mod_proxy_fcgi - Win32 Debug"
+SOURCE=.\mod_proxy_fcgi.c
+
+"$(INTDIR)\mod_proxy_fcgi.obj" : $(SOURCE) "$(INTDIR)"
+
+
+!IF "$(CFG)" == "mod_proxy_fcgi - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\proxy"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_fcgi - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\proxy"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_fcgi - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\proxy"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_fcgi - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\proxy"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_fcgi - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\proxy"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_fcgi - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\proxy"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_fcgi - Win32 Release"
+
+"mod_proxy - Win32 Release" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Release"
+ cd "."
+
+"mod_proxy - Win32 ReleaseCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Release" RECURSE=1 CLEAN
+ cd "."
+
+!ELSEIF "$(CFG)" == "mod_proxy_fcgi - Win32 Debug"
+
+"mod_proxy - Win32 Debug" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Debug"
+ cd "."
+
+"mod_proxy - Win32 DebugCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Debug" RECURSE=1 CLEAN
+ cd "."
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_proxy_fcgi - Win32 Release"
+
+
+"$(INTDIR)\mod_proxy_fcgi.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_proxy_fcgi.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_proxy_fcgi.so" /d LONG_NAME="proxy_fcgi_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_proxy_fcgi - Win32 Debug"
+
+
+"$(INTDIR)\mod_proxy_fcgi.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_proxy_fcgi.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_proxy_fcgi.so" /d LONG_NAME="proxy_fcgi_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+
+!ENDIF
+
diff --git a/modules/proxy/mod_proxy_fdpass.c b/modules/proxy/mod_proxy_fdpass.c
new file mode 100644
index 0000000..8f9893d
--- /dev/null
+++ b/modules/proxy/mod_proxy_fdpass.c
@@ -0,0 +1,241 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mod_proxy.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#ifndef CMSG_DATA
+#error This module only works on unix platforms with the correct OS support
+#endif
+
+#include "mod_proxy_fdpass.h"
+
+module AP_MODULE_DECLARE_DATA proxy_fdpass_module;
+
+static int proxy_fdpass_canon(request_rec *r, char *url)
+{
+ const char *path;
+
+ if (ap_cstr_casecmpn(url, "fd://", 5) == 0) {
+ url += 5;
+ }
+ else {
+ return DECLINED;
+ }
+
+ path = ap_server_root_relative(r->pool, url);
+
+ r->filename = apr_pstrcat(r->pool, "proxy:fd://", path, NULL);
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01151)
+ "set r->filename to %s", r->filename);
+ return OK;
+}
+
+static apr_status_t get_socket_from_path(apr_pool_t *p,
+ const char* path,
+ apr_socket_t **out_sock)
+{
+ apr_socket_t *s;
+ apr_status_t rv;
+ *out_sock = NULL;
+
+ rv = apr_socket_create(&s, AF_UNIX, SOCK_STREAM, 0, p);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ rv = ap_proxy_connect_uds(s, path, p);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ *out_sock = s;
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t send_socket(apr_pool_t *p,
+ apr_socket_t *s,
+ apr_socket_t *outbound)
+{
+ apr_status_t rv;
+ apr_os_sock_t rawsock;
+ apr_os_sock_t srawsock;
+ struct msghdr msg;
+ struct cmsghdr *cmsg;
+ struct iovec iov;
+ char b = '\0';
+
+ rv = apr_os_sock_get(&rawsock, outbound);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ rv = apr_os_sock_get(&srawsock, s);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ memset(&msg, 0, sizeof(msg));
+
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ iov.iov_base = &b;
+ iov.iov_len = 1;
+
+ cmsg = apr_palloc(p, sizeof(*cmsg) + sizeof(rawsock));
+ cmsg->cmsg_len = sizeof(*cmsg) + sizeof(rawsock);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+
+ memcpy(CMSG_DATA(cmsg), &rawsock, sizeof(rawsock));
+
+ msg.msg_control = cmsg;
+ msg.msg_controllen = cmsg->cmsg_len;
+
+ rv = sendmsg(srawsock, &msg, 0);
+
+ if (rv == -1) {
+ return errno;
+ }
+
+ return APR_SUCCESS;
+}
+
+static int proxy_fdpass_handler(request_rec *r, proxy_worker *worker,
+ proxy_server_conf *conf,
+ char *url, const char *proxyname,
+ apr_port_t proxyport)
+{
+ apr_status_t rv;
+ apr_socket_t *sock;
+ apr_socket_t *clientsock;
+
+ if (ap_cstr_casecmpn(url, "fd://", 5) == 0) {
+ url += 5;
+ }
+ else {
+ return DECLINED;
+ }
+
+ rv = get_socket_from_path(r->pool, url, &sock);
+
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01152)
+ "Failed to connect to '%s'", url);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ {
+ int status;
+ const char *flush_method = *worker->s->flusher ? worker->s->flusher : "flush";
+
+ proxy_fdpass_flush *flush = ap_lookup_provider(PROXY_FDPASS_FLUSHER,
+ flush_method, "0");
+
+ if (!flush) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01153)
+ "Unable to find configured flush provider '%s'",
+ flush_method);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ status = flush->flusher(r);
+ if (status) {
+ return status;
+ }
+ }
+
+ clientsock = ap_get_conn_socket(r->connection);
+
+ rv = send_socket(r->pool, sock, clientsock);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01154) "send_socket failed:");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ {
+ apr_socket_t *dummy;
+ /* Create a dummy unconnected socket, and set it as the one we were
+ * connected to, so that when the core closes it, it doesn't close
+ * the tcp connection to the client.
+ */
+ rv = apr_socket_create(&dummy, APR_INET, SOCK_STREAM, APR_PROTO_TCP,
+ r->connection->pool);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01155)
+ "failed to create dummy socket");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ ap_set_core_module_config(r->connection->conn_config, dummy);
+ }
+
+ return OK;
+}
+
+static int standard_flush(request_rec *r)
+{
+ int status;
+ apr_bucket_brigade *bb;
+ apr_bucket *e;
+
+ r->connection->keepalive = AP_CONN_CLOSE;
+
+ bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+ e = apr_bucket_flush_create(r->connection->bucket_alloc);
+
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+
+ status = ap_pass_brigade(r->output_filters, bb);
+
+ if (status != OK) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01156)
+ "ap_pass_brigade failed:");
+ return status;
+ }
+
+ return OK;
+}
+
+
+static const proxy_fdpass_flush builtin_flush =
+{
+ "flush",
+ &standard_flush,
+ NULL
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_register_provider(p, PROXY_FDPASS_FLUSHER, "flush", "0", &builtin_flush);
+ proxy_hook_scheme_handler(proxy_fdpass_handler, NULL, NULL, APR_HOOK_FIRST);
+ proxy_hook_canon_handler(proxy_fdpass_canon, NULL, NULL, APR_HOOK_FIRST);
+}
+
+AP_DECLARE_MODULE(proxy_fdpass) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ NULL, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/proxy/mod_proxy_fdpass.h b/modules/proxy/mod_proxy_fdpass.h
new file mode 100644
index 0000000..1a13fed
--- /dev/null
+++ b/modules/proxy/mod_proxy_fdpass.h
@@ -0,0 +1,41 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file mod_proxy_fdpass.h
+ * @brief FD Passing interfaces
+ *
+ * @ingroup APACHE_INTERNAL
+ * @{
+ */
+
+#include "mod_proxy.h"
+
+#ifndef _PROXY_FDPASS_H_
+#define _PROXY_FDPASS_H_
+
+#define PROXY_FDPASS_FLUSHER "proxy_fdpass_flusher"
+
+typedef struct proxy_fdpass_flush proxy_fdpass_flush;
+struct proxy_fdpass_flush {
+ const char *name;
+ int (*flusher)(request_rec *r);
+ void *context;
+};
+
+#endif /* _PROXY_FDPASS_H_ */
+/** @} */
+
diff --git a/modules/proxy/mod_proxy_ftp.c b/modules/proxy/mod_proxy_ftp.c
new file mode 100644
index 0000000..3237a2b
--- /dev/null
+++ b/modules/proxy/mod_proxy_ftp.c
@@ -0,0 +1,2138 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* FTP routines for Apache proxy */
+
+#define APR_WANT_BYTEFUNC
+#include "mod_proxy.h"
+#if APR_HAVE_TIME_H
+#include <time.h>
+#endif
+#include "apr_version.h"
+
+#define AUTODETECT_PWD
+/* Automatic timestamping (Last-Modified header) based on MDTM is used if:
+ * 1) the FTP server supports the MDTM command and
+ * 2) HAVE_TIMEGM (preferred) or HAVE_GMTOFF is available at compile time
+ */
+#define USE_MDTM
+
+
+module AP_MODULE_DECLARE_DATA proxy_ftp_module;
+
+typedef struct {
+ int ftp_list_on_wildcard;
+ int ftp_list_on_wildcard_set;
+ int ftp_escape_wildcards;
+ int ftp_escape_wildcards_set;
+ const char *ftp_directory_charset;
+} proxy_ftp_dir_conf;
+
+static void *create_proxy_ftp_dir_config(apr_pool_t *p, char *dummy)
+{
+ proxy_ftp_dir_conf *new =
+ (proxy_ftp_dir_conf *) apr_pcalloc(p, sizeof(proxy_ftp_dir_conf));
+
+ /* Put these in the dir config so they work inside <Location> */
+ new->ftp_list_on_wildcard = 1;
+ new->ftp_escape_wildcards = 1;
+
+ return (void *) new;
+}
+
+static void *merge_proxy_ftp_dir_config(apr_pool_t *p, void *basev, void *addv)
+{
+ proxy_ftp_dir_conf *new = (proxy_ftp_dir_conf *) apr_pcalloc(p, sizeof(proxy_ftp_dir_conf));
+ proxy_ftp_dir_conf *add = (proxy_ftp_dir_conf *) addv;
+ proxy_ftp_dir_conf *base = (proxy_ftp_dir_conf *) basev;
+
+ /* Put these in the dir config so they work inside <Location> */
+ new->ftp_list_on_wildcard = add->ftp_list_on_wildcard_set ?
+ add->ftp_list_on_wildcard :
+ base->ftp_list_on_wildcard;
+ new->ftp_list_on_wildcard_set = add->ftp_list_on_wildcard_set ?
+ 1 :
+ base->ftp_list_on_wildcard_set;
+ new->ftp_escape_wildcards = add->ftp_escape_wildcards_set ?
+ add->ftp_escape_wildcards :
+ base->ftp_escape_wildcards;
+ new->ftp_escape_wildcards_set = add->ftp_escape_wildcards_set ?
+ 1 :
+ base->ftp_escape_wildcards_set;
+ new->ftp_directory_charset = add->ftp_directory_charset ?
+ add->ftp_directory_charset :
+ base->ftp_directory_charset;
+ return new;
+}
+
+static const char *set_ftp_list_on_wildcard(cmd_parms *cmd, void *dconf,
+ int flag)
+{
+ proxy_ftp_dir_conf *conf = dconf;
+
+ conf->ftp_list_on_wildcard = flag;
+ conf->ftp_list_on_wildcard_set = 1;
+ return NULL;
+}
+
+static const char *set_ftp_escape_wildcards(cmd_parms *cmd, void *dconf,
+ int flag)
+{
+ proxy_ftp_dir_conf *conf = dconf;
+
+ conf->ftp_escape_wildcards = flag;
+ conf->ftp_escape_wildcards_set = 1;
+ return NULL;
+}
+
+static const char *set_ftp_directory_charset(cmd_parms *cmd, void *dconf,
+ const char *arg)
+{
+ proxy_ftp_dir_conf *conf = dconf;
+
+ conf->ftp_directory_charset = arg;
+ return NULL;
+}
+
+/*
+ * Decodes a '%' escaped string, and returns the number of characters
+ */
+static int decodeenc(char *x)
+{
+ int i, j, ch;
+
+ if (x[0] == '\0')
+ return 0; /* special case for no characters */
+ for (i = 0, j = 0; x[i] != '\0'; i++, j++) {
+ /* decode it if not already done */
+ ch = x[i];
+ if (ch == '%' && apr_isxdigit(x[i + 1]) && apr_isxdigit(x[i + 2])) {
+ ch = ap_proxy_hex2c(&x[i + 1]);
+ i += 2;
+ }
+ x[j] = ch;
+ }
+ x[j] = '\0';
+ return j;
+}
+
+/*
+ * Escape the globbing characters in a path used as argument to
+ * the FTP commands (SIZE, CWD, RETR, MDTM, ...).
+ * ftpd assumes '\\' as a quoting character to escape special characters.
+ * Just returns the original string if ProxyFtpEscapeWildcards has been
+ * configured "off".
+ * Returns: escaped string
+ */
+#define FTP_GLOBBING_CHARS "*?[{~"
+static const char *ftp_escape_globbingchars(apr_pool_t *p, const char *path, proxy_ftp_dir_conf *dconf)
+{
+ char *ret;
+ char *d;
+
+ if (!dconf->ftp_escape_wildcards) {
+ return path;
+ }
+
+ ret = apr_palloc(p, 2*strlen(path)+sizeof(""));
+ for (d = ret; *path; ++path) {
+ if (strchr(FTP_GLOBBING_CHARS, *path) != NULL)
+ *d++ = '\\';
+ *d++ = *path;
+ }
+ *d = '\0';
+ return ret;
+}
+
+/*
+ * Check for globbing characters in a path used as argument to
+ * the FTP commands (SIZE, CWD, RETR, MDTM, ...).
+ * ftpd assumes '\\' as a quoting character to escape special characters.
+ * Returns: 0 (no globbing chars, or all globbing chars escaped), 1 (globbing chars)
+ */
+static int ftp_check_globbingchars(const char *path)
+{
+ for ( ; *path; ++path) {
+ if (*path == '\\')
+ ++path;
+ if (*path != '\0' && strchr(FTP_GLOBBING_CHARS, *path) != NULL)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/*
+ * checks an encoded ftp string for bad characters, namely, CR, LF or
+ * non-ascii character
+ */
+static int ftp_check_string(const char *x)
+{
+ int i, ch = 0;
+#if APR_CHARSET_EBCDIC
+ char buf[1];
+#endif
+
+ for (i = 0; x[i] != '\0'; i++) {
+ ch = x[i];
+ if (ch == '%' && apr_isxdigit(x[i + 1]) && apr_isxdigit(x[i + 2])) {
+ ch = ap_proxy_hex2c(&x[i + 1]);
+ i += 2;
+ }
+#if !APR_CHARSET_EBCDIC
+ if (ch == '\015' || ch == '\012' || (ch & 0x80))
+#else /* APR_CHARSET_EBCDIC */
+ if (ch == '\r' || ch == '\n')
+ return 0;
+ buf[0] = ch;
+ ap_xlate_proto_to_ascii(buf, 1);
+ if (buf[0] & 0x80)
+#endif /* APR_CHARSET_EBCDIC */
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * converts a series of buckets into a string
+ * XXX: BillS says this function performs essentially the same function as
+ * ap_rgetline() in protocol.c. Deprecate this function and use ap_rgetline()
+ * instead? I think ftp_string_read() will not work properly on non ASCII
+ * (EBCDIC) machines either.
+ */
+static apr_status_t ftp_string_read(conn_rec *c, apr_bucket_brigade *bb,
+ char *buff, apr_size_t bufflen, int *eos, apr_size_t *outlen)
+{
+ apr_bucket *e;
+ apr_status_t rv;
+ char *pos = buff;
+ char *response;
+ int found = 0;
+ apr_size_t len;
+
+ /* start with an empty string */
+ buff[0] = 0;
+ *eos = 0;
+ *outlen = 0;
+
+ /* loop through each brigade */
+ while (!found) {
+ /* get brigade from network one line at a time */
+ if (APR_SUCCESS != (rv = ap_get_brigade(c->input_filters, bb,
+ AP_MODE_GETLINE,
+ APR_BLOCK_READ,
+ 0))) {
+ return rv;
+ }
+ /* loop through each bucket */
+ while (!found) {
+ if (*eos || APR_BRIGADE_EMPTY(bb)) {
+ /* The connection aborted or timed out */
+ return APR_ECONNABORTED;
+ }
+ e = APR_BRIGADE_FIRST(bb);
+ if (APR_BUCKET_IS_EOS(e)) {
+ *eos = 1;
+ }
+ else {
+ if (APR_SUCCESS != (rv = apr_bucket_read(e,
+ (const char **)&response,
+ &len,
+ APR_BLOCK_READ))) {
+ return rv;
+ }
+ /*
+ * is string LF terminated?
+ * XXX: This check can be made more efficient by simply checking
+ * if the last character in the 'response' buffer is an ASCII_LF.
+ * See ap_rgetline() for an example.
+ */
+ if (memchr(response, APR_ASCII_LF, len)) {
+ found = 1;
+ }
+ /* concat strings until buff is full - then throw the data away */
+ if (len > ((bufflen-1)-(pos-buff))) {
+ len = (bufflen-1)-(pos-buff);
+ }
+ if (len > 0) {
+ memcpy(pos, response, len);
+ pos += len;
+ *outlen += len;
+ }
+ }
+ apr_bucket_delete(e);
+ }
+ *pos = '\0';
+ }
+
+ return APR_SUCCESS;
+}
+
+/*
+ * Canonicalise ftp URLs.
+ */
+static int proxy_ftp_canon(request_rec *r, char *url)
+{
+ char *user, *password, *host, *path, *parms, *strp, sport[7];
+ apr_pool_t *p = r->pool;
+ const char *err;
+ apr_port_t port, def_port;
+
+ /* */
+ if (ap_cstr_casecmpn(url, "ftp:", 4) == 0) {
+ url += 4;
+ }
+ else {
+ return DECLINED;
+ }
+ def_port = apr_uri_port_of_scheme("ftp");
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "canonicalising URL %s", url);
+
+ port = def_port;
+ err = ap_proxy_canon_netloc(p, &url, &user, &password, &host, &port);
+ if (err)
+ return HTTP_BAD_REQUEST;
+ if (user != NULL && !ftp_check_string(user))
+ return HTTP_BAD_REQUEST;
+ if (password != NULL && !ftp_check_string(password))
+ return HTTP_BAD_REQUEST;
+
+ /* now parse path/parameters args, according to rfc1738 */
+ /*
+ * N.B. if this isn't a true proxy request, then the URL path (but not
+ * query args) has already been decoded. This gives rise to the problem
+ * of a ; being decoded into the path.
+ */
+ strp = strchr(url, ';');
+ if (strp != NULL) {
+ *(strp++) = '\0';
+ parms = ap_proxy_canonenc(p, strp, strlen(strp), enc_parm, 0,
+ r->proxyreq);
+ if (parms == NULL)
+ return HTTP_BAD_REQUEST;
+ }
+ else
+ parms = "";
+
+ path = ap_proxy_canonenc(p, url, strlen(url), enc_path, 0, r->proxyreq);
+ if (path == NULL)
+ return HTTP_BAD_REQUEST;
+ if (!ftp_check_string(path))
+ return HTTP_BAD_REQUEST;
+
+ if (r->proxyreq && r->args != NULL) {
+ if (strp != NULL) {
+ strp = ap_proxy_canonenc(p, r->args, strlen(r->args), enc_parm, 1, r->proxyreq);
+ if (strp == NULL)
+ return HTTP_BAD_REQUEST;
+ parms = apr_pstrcat(p, parms, "?", strp, NULL);
+ }
+ else {
+ strp = ap_proxy_canonenc(p, r->args, strlen(r->args), enc_fpath, 1, r->proxyreq);
+ if (strp == NULL)
+ return HTTP_BAD_REQUEST;
+ path = apr_pstrcat(p, path, "?", strp, NULL);
+ }
+ r->args = NULL;
+ }
+
+/* now, rebuild URL */
+
+ if (port != def_port)
+ apr_snprintf(sport, sizeof(sport), ":%d", port);
+ else
+ sport[0] = '\0';
+
+ if (ap_strchr_c(host, ':')) { /* if literal IPv6 address */
+ host = apr_pstrcat(p, "[", host, "]", NULL);
+ }
+ r->filename = apr_pstrcat(p, "proxy:ftp://", (user != NULL) ? user : "",
+ (password != NULL) ? ":" : "",
+ (password != NULL) ? password : "",
+ (user != NULL) ? "@" : "", host, sport, "/", path,
+ (parms[0] != '\0') ? ";" : "", parms, NULL);
+
+ return OK;
+}
+
+/* we chop lines longer than 80 characters */
+#define MAX_LINE_LEN 80
+
+/*
+ * Reads response lines, returns both the ftp status code and
+ * remembers the response message in the supplied buffer
+ */
+static int ftp_getrc_msg(conn_rec *ftp_ctrl, apr_bucket_brigade *bb, char *msgbuf, int msglen)
+{
+ int status;
+ char response[MAX_LINE_LEN];
+ char buff[5];
+ char *mb = msgbuf, *me = &msgbuf[msglen];
+ apr_status_t rv;
+ apr_size_t nread;
+
+ int eos;
+
+ if (APR_SUCCESS != (rv = ftp_string_read(ftp_ctrl, bb, response, sizeof(response), &eos, &nread))) {
+ return -1;
+ }
+/*
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, APLOGNO(03233)
+ "<%s", response);
+*/
+ if (nread < 4) {
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, APLOGNO(10229) "Malformed FTP response '%s'", response);
+ *mb = '\0';
+ return -1;
+ }
+
+ if (!apr_isdigit(response[0]) || !apr_isdigit(response[1]) ||
+ !apr_isdigit(response[2]) || (response[3] != ' ' && response[3] != '-'))
+ status = 0;
+ else
+ status = 100 * response[0] + 10 * response[1] + response[2] - 111 * '0';
+
+ mb = apr_cpystrn(mb, response + 4, me - mb);
+
+ if (response[3] == '-') { /* multi-line reply "123-foo\nbar\n123 baz" */
+ memcpy(buff, response, 3);
+ buff[3] = ' ';
+ do {
+ if (APR_SUCCESS != (rv = ftp_string_read(ftp_ctrl, bb, response, sizeof(response), &eos, &nread))) {
+ return -1;
+ }
+ mb = apr_cpystrn(mb, response + (' ' == response[0] ? 1 : 4), me - mb);
+ } while (memcmp(response, buff, 4) != 0);
+ }
+
+ return status;
+}
+
+/* this is a filter that turns a raw ASCII directory listing into pretty HTML */
+
+/* ideally, mod_proxy should simply send the raw directory list up the filter
+ * stack to mod_autoindex, which in theory should turn the raw ascii into
+ * pretty html along with all the bells and whistles it provides...
+ *
+ * all in good time...! :)
+ */
+
+typedef struct {
+ apr_bucket_brigade *in;
+ char buffer[MAX_STRING_LEN];
+ enum {
+ HEADER, BODY, FOOTER
+ } state;
+} proxy_dir_ctx_t;
+
+/* fallback regex for ls -s1; ($0..$2) == 3 */
+#define LS_REG_PATTERN "^ *([0-9]+) +([^ ]+)$"
+#define LS_REG_MATCH 3
+static ap_regex_t *ls_regex;
+
+static apr_status_t proxy_send_dir_filter(ap_filter_t *f,
+ apr_bucket_brigade *in)
+{
+ request_rec *r = f->r;
+ conn_rec *c = r->connection;
+ apr_pool_t *p = r->pool;
+ apr_bucket_brigade *out = apr_brigade_create(p, c->bucket_alloc);
+ apr_status_t rv;
+
+ int n;
+ char *dir, *path, *reldir, *site, *str, *type;
+
+ const char *pwd = apr_table_get(r->notes, "Directory-PWD");
+ const char *readme = apr_table_get(r->notes, "Directory-README");
+
+ proxy_dir_ctx_t *ctx = f->ctx;
+
+ if (!ctx) {
+ f->ctx = ctx = apr_pcalloc(p, sizeof(*ctx));
+ ctx->in = apr_brigade_create(p, c->bucket_alloc);
+ ctx->buffer[0] = 0;
+ ctx->state = HEADER;
+ }
+
+ /* combine the stored and the new */
+ APR_BRIGADE_CONCAT(ctx->in, in);
+
+ if (HEADER == ctx->state) {
+
+ /* basedir is either "", or "/%2f" for the "squid %2f hack" */
+ const char *basedir = ""; /* By default, path is relative to the $HOME dir */
+ char *wildcard = NULL;
+ const char *escpath;
+
+ /*
+ * In the reverse proxy case we need to construct our site string
+ * via ap_construct_url. For non anonymous sites apr_uri_unparse would
+ * only supply us with 'username@' which leads to the construction of
+ * an invalid base href later on. Losing the username part of the URL
+ * is no problem in the reverse proxy case as the browser sents the
+ * credentials anyway once entered.
+ */
+ if (r->proxyreq == PROXYREQ_REVERSE) {
+ site = ap_construct_url(p, "", r);
+ }
+ else {
+ /* Save "scheme://site" prefix without password */
+ site = apr_uri_unparse(p, &f->r->parsed_uri,
+ APR_URI_UNP_OMITPASSWORD |
+ APR_URI_UNP_OMITPATHINFO);
+ }
+
+ /* ... and path without query args */
+ path = apr_uri_unparse(p, &f->r->parsed_uri, APR_URI_UNP_OMITSITEPART | APR_URI_UNP_OMITQUERY);
+
+ /* If path began with /%2f, change the basedir */
+ if (ap_cstr_casecmpn(path, "/%2f", 4) == 0) {
+ basedir = "/%2f";
+ }
+
+ /* Strip off a type qualifier. It is ignored for dir listings */
+ if ((type = strstr(path, ";type=")) != NULL)
+ *type++ = '\0';
+
+ (void)decodeenc(path);
+
+ while (path[1] == '/') /* collapse multiple leading slashes to one */
+ ++path;
+
+ reldir = strrchr(path, '/');
+ if (reldir != NULL && ftp_check_globbingchars(reldir)) {
+ wildcard = &reldir[1];
+ reldir[0] = '\0'; /* strip off the wildcard suffix */
+ }
+
+ /* Copy path, strip (all except the last) trailing slashes */
+ /* (the trailing slash is needed for the dir component loop below) */
+ path = dir = apr_pstrcat(p, path, "/", NULL);
+ for (n = strlen(path); n > 1 && path[n - 1] == '/' && path[n - 2] == '/'; --n)
+ path[n - 1] = '\0';
+
+ /* Add a link to the root directory (if %2f hack was used) */
+ str = (basedir[0] != '\0') ? "<a href=\"/%2f/\">%2f</a>/" : "";
+
+ /* print "ftp://host/" */
+ escpath = ap_escape_html(p, path);
+ str = apr_psprintf(p, DOCTYPE_HTML_3_2
+ "<html>\n <head>\n <title>%s%s%s</title>\n"
+ "<base href=\"%s%s%s\">\n"
+ " </head>\n"
+ " <body>\n <h2>Directory of "
+ "<a href=\"/\">%s</a>/%s",
+ ap_escape_html(p, site), basedir, escpath,
+ ap_escape_uri(p, site), basedir, escpath,
+ ap_escape_uri(p, site), str);
+
+ APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str, strlen(str),
+ p, c->bucket_alloc));
+
+ for (dir = path+1; (dir = strchr(dir, '/')) != NULL; )
+ {
+ *dir = '\0';
+ if ((reldir = strrchr(path+1, '/'))==NULL) {
+ reldir = path+1;
+ }
+ else
+ ++reldir;
+ /* print "path/" component */
+ str = apr_psprintf(p, "<a href=\"%s%s/\">%s</a>/", basedir,
+ ap_escape_uri(p, path),
+ ap_escape_html(p, reldir));
+ *dir = '/';
+ while (*dir == '/')
+ ++dir;
+ APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str,
+ strlen(str), p,
+ c->bucket_alloc));
+ }
+ if (wildcard != NULL) {
+ wildcard = ap_escape_html(p, wildcard);
+ APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(wildcard,
+ strlen(wildcard), p,
+ c->bucket_alloc));
+ }
+
+ /* If the caller has determined the current directory, and it differs */
+ /* from what the client requested, then show the real name */
+ if (pwd == NULL || strncmp(pwd, path, strlen(pwd)) == 0) {
+ str = apr_psprintf(p, "</h2>\n\n <hr />\n\n<pre>");
+ }
+ else {
+ str = apr_psprintf(p, "</h2>\n\n(%s)\n\n <hr />\n\n<pre>",
+ ap_escape_html(p, pwd));
+ }
+ APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str, strlen(str),
+ p, c->bucket_alloc));
+
+ /* print README */
+ if (readme) {
+ str = apr_psprintf(p, "%s\n</pre>\n\n<hr />\n\n<pre>\n",
+ ap_escape_html(p, readme));
+
+ APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str,
+ strlen(str), p,
+ c->bucket_alloc));
+ }
+
+ /* make sure page intro gets sent out */
+ APR_BRIGADE_INSERT_TAIL(out, apr_bucket_flush_create(c->bucket_alloc));
+ if (APR_SUCCESS != (rv = ap_pass_brigade(f->next, out))) {
+ return rv;
+ }
+ apr_brigade_cleanup(out);
+
+ ctx->state = BODY;
+ }
+
+ /* loop through each line of directory */
+ while (BODY == ctx->state) {
+ char *filename;
+ int found = 0;
+ int eos = 0;
+ ap_regmatch_t re_result[LS_REG_MATCH];
+
+ /* get a complete line */
+ /* if the buffer overruns - throw data away */
+ while (!found && !APR_BRIGADE_EMPTY(ctx->in)) {
+ char *pos, *response;
+ apr_size_t len, max;
+ apr_bucket *e;
+
+ e = APR_BRIGADE_FIRST(ctx->in);
+ if (APR_BUCKET_IS_EOS(e)) {
+ eos = 1;
+ break;
+ }
+ if (APR_SUCCESS != (rv = apr_bucket_read(e, (const char **)&response, &len, APR_BLOCK_READ))) {
+ return rv;
+ }
+ pos = memchr(response, APR_ASCII_LF, len);
+ if (pos != NULL) {
+ if ((response + len) != (pos + 1)) {
+ len = pos - response + 1;
+ apr_bucket_split(e, pos - response + 1);
+ }
+ found = 1;
+ }
+ max = sizeof(ctx->buffer) - strlen(ctx->buffer) - 1;
+ if (len > max) {
+ len = max;
+ }
+
+ /* len+1 to leave space for the trailing nil char */
+ apr_cpystrn(ctx->buffer+strlen(ctx->buffer), response, len+1);
+
+ apr_bucket_delete(e);
+ }
+
+ /* EOS? jump to footer */
+ if (eos) {
+ ctx->state = FOOTER;
+ break;
+ }
+
+ /* not complete? leave and try get some more */
+ if (!found) {
+ return APR_SUCCESS;
+ }
+
+ {
+ apr_size_t n = strlen(ctx->buffer);
+ if (ctx->buffer[n-1] == CRLF[1]) /* strip trailing '\n' */
+ ctx->buffer[--n] = '\0';
+ if (ctx->buffer[n-1] == CRLF[0]) /* strip trailing '\r' if present */
+ ctx->buffer[--n] = '\0';
+ }
+
+ /* a symlink? */
+ if (ctx->buffer[0] == 'l' && (filename = strstr(ctx->buffer, " -> ")) != NULL) {
+ char *link_ptr = filename;
+
+ do {
+ filename--;
+ } while (filename[0] != ' ' && filename > ctx->buffer);
+ if (filename > ctx->buffer)
+ *(filename++) = '\0';
+ *(link_ptr++) = '\0';
+ str = apr_psprintf(p, "%s <a href=\"%s\">%s %s</a>\n",
+ ap_escape_html(p, ctx->buffer),
+ ap_escape_uri(p, filename),
+ ap_escape_html(p, filename),
+ ap_escape_html(p, link_ptr));
+ }
+
+ /* a directory/file? */
+ else if (ctx->buffer[0] == 'd' || ctx->buffer[0] == '-' || ctx->buffer[0] == 'l' || apr_isdigit(ctx->buffer[0])) {
+ int searchidx = 0;
+ char *searchptr = NULL;
+ int firstfile = 1;
+ if (apr_isdigit(ctx->buffer[0])) { /* handle DOS dir */
+ searchptr = strchr(ctx->buffer, '<');
+ if (searchptr != NULL)
+ *searchptr = '[';
+ searchptr = strchr(ctx->buffer, '>');
+ if (searchptr != NULL)
+ *searchptr = ']';
+ }
+
+ filename = strrchr(ctx->buffer, ' ');
+ if (filename == NULL) {
+ /* Line is broken. Ignore it. */
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01034)
+ "proxy_ftp: could not parse line %s",
+ ctx->buffer);
+ /* erase buffer for next time around */
+ ctx->buffer[0] = 0;
+ continue; /* while state is BODY */
+ }
+ *(filename++) = '\0';
+
+ /* handle filenames with spaces in 'em */
+ if (!strcmp(filename, ".") || !strcmp(filename, "..") || firstfile) {
+ firstfile = 0;
+ searchidx = filename - ctx->buffer;
+ }
+ else if (searchidx != 0 && ctx->buffer[searchidx] != 0) {
+ *(--filename) = ' ';
+ ctx->buffer[searchidx - 1] = '\0';
+ filename = &ctx->buffer[searchidx];
+ }
+
+ /* Append a slash to the HREF link for directories */
+ if (!strcmp(filename, ".") || !strcmp(filename, "..") || ctx->buffer[0] == 'd') {
+ str = apr_psprintf(p, "%s <a href=\"%s/\">%s</a>\n",
+ ap_escape_html(p, ctx->buffer),
+ ap_escape_uri(p, filename),
+ ap_escape_html(p, filename));
+ }
+ else {
+ str = apr_psprintf(p, "%s <a href=\"%s\">%s</a>\n",
+ ap_escape_html(p, ctx->buffer),
+ ap_escape_uri(p, filename),
+ ap_escape_html(p, filename));
+ }
+ }
+ /* Try a fallback for listings in the format of "ls -s1" */
+ else if (0 == ap_regexec(ls_regex, ctx->buffer, LS_REG_MATCH, re_result, 0)) {
+ /*
+ * We don't need to check for rm_eo == rm_so == -1 here since ls_regex
+ * is such that $2 cannot be unset if we have a match.
+ */
+ filename = apr_pstrndup(p, &ctx->buffer[re_result[2].rm_so], re_result[2].rm_eo - re_result[2].rm_so);
+
+ str = apr_pstrcat(p, ap_escape_html(p, apr_pstrndup(p, ctx->buffer, re_result[2].rm_so)),
+ "<a href=\"", ap_escape_uri(p, filename), "\">",
+ ap_escape_html(p, filename), "</a>\n", NULL);
+ }
+ else {
+ strcat(ctx->buffer, "\n"); /* re-append the newline */
+ str = ap_escape_html(p, ctx->buffer);
+ }
+
+ /* erase buffer for next time around */
+ ctx->buffer[0] = 0;
+
+ APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str, strlen(str), p,
+ c->bucket_alloc));
+ APR_BRIGADE_INSERT_TAIL(out, apr_bucket_flush_create(c->bucket_alloc));
+ if (APR_SUCCESS != (rv = ap_pass_brigade(f->next, out))) {
+ return rv;
+ }
+ apr_brigade_cleanup(out);
+
+ }
+
+ if (FOOTER == ctx->state) {
+ str = apr_psprintf(p, "</pre>\n\n <hr />\n\n %s\n\n </body>\n</html>\n", ap_psignature("", r));
+ APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str, strlen(str), p,
+ c->bucket_alloc));
+ APR_BRIGADE_INSERT_TAIL(out, apr_bucket_flush_create(c->bucket_alloc));
+ APR_BRIGADE_INSERT_TAIL(out, apr_bucket_eos_create(c->bucket_alloc));
+ if (APR_SUCCESS != (rv = ap_pass_brigade(f->next, out))) {
+ return rv;
+ }
+ apr_brigade_destroy(out);
+ }
+
+ return APR_SUCCESS;
+}
+
+/* Parse EPSV reply and return port, or zero on error. */
+static apr_port_t parse_epsv_reply(const char *reply)
+{
+ const char *p;
+ char *ep;
+ long port;
+
+ /* Reply syntax per RFC 2428: "229 blah blah (|||port|)" where '|'
+ * can be any character in ASCII from 33-126, obscurely. Verify
+ * the syntax. */
+ p = ap_strchr_c(reply, '(');
+ if (p == NULL || !p[1] || p[1] != p[2] || p[1] != p[3]
+ || p[4] == p[1]) {
+ return 0;
+ }
+
+ errno = 0;
+ port = strtol(p + 4, &ep, 10);
+ if (errno || port < 1 || port > 65535 || ep[0] != p[1] || ep[1] != ')') {
+ return 0;
+ }
+
+ return (apr_port_t)port;
+}
+
+/*
+ * Generic "send FTP command to server" routine, using the control socket.
+ * Returns the FTP returncode (3 digit code)
+ * Allows for tracing the FTP protocol (in LogLevel debug)
+ */
+static int
+proxy_ftp_command(const char *cmd, request_rec *r, conn_rec *ftp_ctrl,
+ apr_bucket_brigade *bb, char **pmessage)
+{
+ char *crlf;
+ int rc;
+ char message[HUGE_STRING_LEN];
+
+ /* If cmd == NULL, we retrieve the next ftp response line */
+ if (cmd != NULL) {
+ conn_rec *c = r->connection;
+ APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(cmd, strlen(cmd), r->pool, c->bucket_alloc));
+ APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_flush_create(c->bucket_alloc));
+ ap_pass_brigade(ftp_ctrl->output_filters, bb);
+
+ if (APLOGrtrace2(r)) {
+ /* strip off the CRLF for logging */
+ apr_cpystrn(message, cmd, sizeof(message));
+ if ((crlf = strchr(message, '\r')) != NULL ||
+ (crlf = strchr(message, '\n')) != NULL)
+ *crlf = '\0';
+ if (strncmp(message,"PASS ", 5) == 0)
+ strcpy(&message[5], "****");
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, ">%s", message);
+ }
+ }
+
+ rc = ftp_getrc_msg(ftp_ctrl, bb, message, sizeof(message));
+ if (rc == -1 || rc == 421)
+ strcpy(message,"<unable to read result>");
+ if ((crlf = strchr(message, '\r')) != NULL ||
+ (crlf = strchr(message, '\n')) != NULL)
+ *crlf = '\0';
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "<%3.3u %s", rc, message);
+
+ if (pmessage != NULL)
+ *pmessage = apr_pstrdup(r->pool, message);
+
+ return rc;
+}
+
+/* Set ftp server to TYPE {A,I,E} before transfer of a directory or file */
+static int ftp_set_TYPE(char xfer_type, request_rec *r, conn_rec *ftp_ctrl,
+ apr_bucket_brigade *bb, char **pmessage)
+{
+ char old_type[2] = { 'A', '\0' }; /* After logon, mode is ASCII */
+ int ret = HTTP_OK;
+ int rc;
+
+ /* set desired type */
+ old_type[0] = xfer_type;
+
+ rc = proxy_ftp_command(apr_pstrcat(r->pool, "TYPE ", old_type, CRLF, NULL),
+ r, ftp_ctrl, bb, pmessage);
+/* responses: 200, 421, 500, 501, 504, 530 */
+ /* 200 Command okay. */
+ /* 421 Service not available, closing control connection. */
+ /* 500 Syntax error, command unrecognized. */
+ /* 501 Syntax error in parameters or arguments. */
+ /* 504 Command not implemented for that parameter. */
+ /* 530 Not logged in. */
+ if (rc == -1 || rc == 421) {
+ ret = ap_proxyerror(r, HTTP_BAD_GATEWAY,
+ "Error reading from remote server");
+ }
+ else if (rc != 200 && rc != 504) {
+ ret = ap_proxyerror(r, HTTP_BAD_GATEWAY,
+ "Unable to set transfer type");
+ }
+/* Allow not implemented */
+ else if (rc == 504) {
+ /* ignore it silently */
+ }
+
+ return ret;
+}
+
+
+/* Return the current directory which we have selected on the FTP server, or NULL */
+static char *ftp_get_PWD(request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade *bb)
+{
+ char *cwd = NULL;
+ char *ftpmessage = NULL;
+
+ /* responses: 257, 500, 501, 502, 421, 550 */
+ /* 257 "<directory-name>" <commentary> */
+ /* 421 Service not available, closing control connection. */
+ /* 500 Syntax error, command unrecognized. */
+ /* 501 Syntax error in parameters or arguments. */
+ /* 502 Command not implemented. */
+ /* 550 Requested action not taken. */
+ switch (proxy_ftp_command("PWD" CRLF, r, ftp_ctrl, bb, &ftpmessage)) {
+ case -1:
+ case 421:
+ case 550:
+ ap_proxyerror(r, HTTP_BAD_GATEWAY,
+ "Failed to read PWD on ftp server");
+ break;
+
+ case 257: {
+ const char *dirp = ftpmessage;
+ cwd = ap_getword_conf(r->pool, &dirp);
+ }
+ }
+ return cwd;
+}
+
+
+/* Common routine for failed authorization (i.e., missing or wrong password)
+ * to an ftp service. This causes most browsers to retry the request
+ * with username and password (which was presumably queried from the user)
+ * supplied in the Authorization: header.
+ * Note that we "invent" a realm name which consists of the
+ * ftp://user@host part of the request (sans password -if supplied but invalid-)
+ */
+static int ftp_unauthorized(request_rec *r, int log_it)
+{
+ r->proxyreq = PROXYREQ_NONE;
+ /*
+ * Log failed requests if they supplied a password (log username/password
+ * guessing attempts)
+ */
+ if (log_it)
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01035)
+ "missing or failed auth to %s",
+ apr_uri_unparse(r->pool,
+ &r->parsed_uri, APR_URI_UNP_OMITPATHINFO));
+
+ apr_table_setn(r->err_headers_out, "WWW-Authenticate",
+ apr_pstrcat(r->pool, "Basic realm=\"",
+ apr_uri_unparse(r->pool, &r->parsed_uri,
+ APR_URI_UNP_OMITPASSWORD | APR_URI_UNP_OMITPATHINFO),
+ "\"", NULL));
+
+ return HTTP_UNAUTHORIZED;
+}
+
+static
+apr_status_t proxy_ftp_cleanup(request_rec *r, proxy_conn_rec *backend)
+{
+
+ backend->close = 1;
+ ap_set_module_config(r->connection->conn_config, &proxy_ftp_module, NULL);
+ ap_proxy_release_connection("FTP", backend, r->server);
+
+ return OK;
+}
+
+static
+int ftp_proxyerror(request_rec *r, proxy_conn_rec *conn, int statuscode, const char *message)
+{
+ proxy_ftp_cleanup(r, conn);
+ return ap_proxyerror(r, statuscode, message);
+}
+/*
+ * Handles direct access of ftp:// URLs
+ * Original (Non-PASV) version from
+ * Troy Morrison <spiffnet@zoom.com>
+ * PASV added by Chuck
+ * Filters by [Graham Leggett <minfrin@sharp.fm>]
+ */
+static int proxy_ftp_handler(request_rec *r, proxy_worker *worker,
+ proxy_server_conf *conf, char *url,
+ const char *proxyhost, apr_port_t proxyport)
+{
+ apr_pool_t *p = r->pool;
+ conn_rec *c = r->connection;
+ proxy_conn_rec *backend;
+ apr_socket_t *sock, *local_sock, *data_sock = NULL;
+ apr_sockaddr_t *connect_addr = NULL;
+ apr_status_t rv;
+ conn_rec *origin, *data = NULL;
+ apr_status_t err = APR_SUCCESS;
+#if APR_HAS_THREADS
+ apr_status_t uerr = APR_SUCCESS;
+#endif
+ apr_bucket_brigade *bb;
+ char *buf, *connectname;
+ apr_port_t connectport;
+ char *ftpmessage = NULL;
+ char *path, *strp, *type_suffix, *cwd = NULL;
+ apr_uri_t uri;
+ char *user = NULL;
+/* char *account = NULL; how to supply an account in a URL? */
+ const char *password = NULL;
+ int len, rc;
+ int one = 1;
+ char *size = NULL;
+ char xfer_type = 'A'; /* after ftp login, the default is ASCII */
+ int dirlisting = 0;
+#if defined(USE_MDTM) && (defined(HAVE_TIMEGM) || defined(HAVE_GMTOFF))
+ apr_time_t mtime = 0L;
+#endif
+ proxy_ftp_dir_conf *fdconf = ap_get_module_config(r->per_dir_config,
+ &proxy_ftp_module);
+
+ /* stuff for PASV mode */
+ int connect = 0, use_port = 0;
+ char dates[APR_RFC822_DATE_LEN];
+ int status;
+ apr_pool_t *address_pool;
+
+ /* is this for us? */
+ if (proxyhost) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
+ "declining URL %s - proxyhost %s specified:", url,
+ proxyhost);
+ return DECLINED; /* proxy connections are via HTTP */
+ }
+ if (ap_cstr_casecmpn(url, "ftp:", 4)) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
+ "declining URL %s - not ftp:", url);
+ return DECLINED; /* only interested in FTP */
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, "serving URL %s", url);
+
+
+ /*
+ * I: Who Do I Connect To? -----------------------
+ *
+ * Break up the URL to determine the host to connect to
+ */
+
+ /* we only support GET and HEAD */
+ if (r->method_number != M_GET)
+ return HTTP_NOT_IMPLEMENTED;
+
+ /* We break the URL into host, port, path-search */
+ if (r->parsed_uri.hostname == NULL) {
+ if (APR_SUCCESS != apr_uri_parse(p, url, &uri)) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(10189)
+ "URI cannot be parsed: %s", url);
+ return ap_proxyerror(r, HTTP_BAD_REQUEST, "URI cannot be parsed");
+ }
+ connectname = uri.hostname;
+ connectport = uri.port;
+ path = apr_pstrdup(p, uri.path);
+ }
+ else {
+ connectname = r->parsed_uri.hostname;
+ connectport = r->parsed_uri.port;
+ path = apr_pstrdup(p, r->parsed_uri.path);
+ }
+ if (connectport == 0) {
+ connectport = apr_uri_port_of_scheme("ftp");
+ }
+ path = (path != NULL && path[0] != '\0') ? &path[1] : "";
+
+ type_suffix = strchr(path, ';');
+ if (type_suffix != NULL)
+ *(type_suffix++) = '\0';
+
+ if (type_suffix != NULL && strncmp(type_suffix, "type=", 5) == 0
+ && apr_isalpha(type_suffix[5])) {
+ /* "type=d" forces a dir listing.
+ * The other types (i|a|e) are directly used for the ftp TYPE command
+ */
+ if ( ! (dirlisting = (apr_tolower(type_suffix[5]) == 'd')))
+ xfer_type = apr_toupper(type_suffix[5]);
+
+ /* Check valid types, rather than ignoring invalid types silently: */
+ if (strchr("AEI", xfer_type) == NULL)
+ return ap_proxyerror(r, HTTP_BAD_REQUEST, apr_pstrcat(r->pool,
+ "ftp proxy supports only types 'a', 'i', or 'e': \"",
+ type_suffix, "\" is invalid.", NULL));
+ }
+ else {
+ /* make binary transfers the default */
+ xfer_type = 'I';
+ }
+
+
+ /*
+ * The "Authorization:" header must be checked first. We allow the user
+ * to "override" the URL-coded user [ & password ] in the Browsers'
+ * User&Password Dialog. NOTE that this is only marginally more secure
+ * than having the password travel in plain as part of the URL, because
+ * Basic Auth simply uuencodes the plain text password. But chances are
+ * still smaller that the URL is logged regularly.
+ */
+ if ((password = apr_table_get(r->headers_in, "Authorization")) != NULL
+ && ap_cstr_casecmp(ap_getword(r->pool, &password, ' '), "Basic") == 0
+ && (password = ap_pbase64decode(r->pool, password))[0] != ':') {
+ /* Check the decoded string for special characters. */
+ if (!ftp_check_string(password)) {
+ return ap_proxyerror(r, HTTP_BAD_REQUEST,
+ "user credentials contained invalid character");
+ }
+ /*
+ * Note that this allocation has to be made from r->connection->pool
+ * because it has the lifetime of the connection. The other
+ * allocations are temporary and can be tossed away any time.
+ */
+ user = ap_getword_nulls(r->connection->pool, &password, ':');
+ r->ap_auth_type = "Basic";
+ r->user = r->parsed_uri.user = user;
+ }
+ else if ((user = r->parsed_uri.user) != NULL) {
+ user = apr_pstrdup(p, user);
+ decodeenc(user);
+ if ((password = r->parsed_uri.password) != NULL) {
+ char *tmp = apr_pstrdup(p, password);
+ decodeenc(tmp);
+ password = tmp;
+ }
+ }
+ else {
+ user = "anonymous";
+ password = "apache-proxy@";
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01036)
+ "connecting %s to %s:%d", url, connectname, connectport);
+
+ if (worker->s->is_address_reusable) {
+ if (!worker->cp->addr) {
+#if APR_HAS_THREADS
+ if ((err = PROXY_THREAD_LOCK(worker->balancer)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, err, r, APLOGNO(01037) "lock");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+#endif
+ }
+ connect_addr = AP_VOLATILIZE_T(apr_sockaddr_t *, worker->cp->addr);
+ address_pool = worker->cp->dns_pool;
+ }
+ else
+ address_pool = r->pool;
+
+ /* do a DNS lookup for the destination host */
+ if (!connect_addr)
+ err = apr_sockaddr_info_get(&(connect_addr),
+ connectname, APR_UNSPEC,
+ connectport, 0,
+ address_pool);
+ if (worker->s->is_address_reusable && !worker->cp->addr) {
+ worker->cp->addr = connect_addr;
+#if APR_HAS_THREADS
+ if ((uerr = PROXY_THREAD_UNLOCK(worker->balancer)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, uerr, r, APLOGNO(01038) "unlock");
+ }
+#endif
+ }
+ /*
+ * get all the possible IP addresses for the destname and loop through
+ * them until we get a successful connection
+ */
+ if (APR_SUCCESS != err) {
+ return ap_proxyerror(r, HTTP_BAD_GATEWAY, apr_pstrcat(p,
+ "DNS lookup failure for: ",
+ connectname, NULL));
+ }
+
+ /* check if ProxyBlock directive on this host */
+ if (OK != ap_proxy_checkproxyblock2(r, conf, connectname, connect_addr)) {
+ return ap_proxyerror(r, HTTP_FORBIDDEN,
+ "Connect to remote machine blocked");
+ }
+
+ /* create space for state information */
+ backend = (proxy_conn_rec *) ap_get_module_config(c->conn_config, &proxy_ftp_module);
+ if (!backend) {
+ status = ap_proxy_acquire_connection("FTP", &backend, worker, r->server);
+ if (status != OK) {
+ if (backend) {
+ backend->close = 1;
+ ap_proxy_release_connection("FTP", backend, r->server);
+ }
+ return status;
+ }
+ /* TODO: see if ftp could use determine_connection */
+ backend->addr = connect_addr;
+ ap_set_module_config(c->conn_config, &proxy_ftp_module, backend);
+ }
+
+
+ /*
+ * II: Make the Connection -----------------------
+ *
+ * We have determined who to connect to. Now make the connection.
+ */
+
+
+ if (ap_proxy_connect_backend("FTP", backend, worker, r->server)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01039)
+ "an error occurred creating a new connection to %pI (%s)",
+ connect_addr, connectname);
+ proxy_ftp_cleanup(r, backend);
+ return HTTP_SERVICE_UNAVAILABLE;
+ }
+
+ status = ap_proxy_connection_create_ex("FTP", backend, r);
+ if (status != OK) {
+ proxy_ftp_cleanup(r, backend);
+ return status;
+ }
+
+ /* Use old naming */
+ origin = backend->connection;
+ sock = backend->sock;
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "control connection complete");
+
+
+ /*
+ * III: Send Control Request -------------------------
+ *
+ * Log into the ftp server, send the username & password, change to the
+ * correct directory...
+ */
+
+ bb = apr_brigade_create(p, c->bucket_alloc);
+
+ /* possible results: */
+ /* 120 Service ready in nnn minutes. */
+ /* 220 Service ready for new user. */
+ /* 421 Service not available, closing control connection. */
+ rc = proxy_ftp_command(NULL, r, origin, bb, &ftpmessage);
+ if (rc == -1 || rc == 421) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, "Error reading from remote server");
+ }
+ if (rc == 120) {
+ /*
+ * RFC2616 states: 14.37 Retry-After
+ *
+ * The Retry-After response-header field can be used with a 503 (Service
+ * Unavailable) response to indicate how long the service is expected
+ * to be unavailable to the requesting client. [...] The value of
+ * this field can be either an HTTP-date or an integer number of
+ * seconds (in decimal) after the time of the response. Retry-After
+ * = "Retry-After" ":" ( HTTP-date | delta-seconds )
+ */
+ char *secs_str = ftpmessage;
+ time_t secs;
+
+ /* Look for a number, preceded by whitespace */
+ while (*secs_str)
+ if ((secs_str==ftpmessage || apr_isspace(secs_str[-1])) &&
+ apr_isdigit(secs_str[0]))
+ break;
+ if (*secs_str != '\0') {
+ secs = atol(secs_str);
+ apr_table_addn(r->headers_out, "Retry-After",
+ apr_psprintf(p, "%lu", (unsigned long)(60 * secs)));
+ }
+ return ftp_proxyerror(r, backend, HTTP_SERVICE_UNAVAILABLE, ftpmessage);
+ }
+ if (rc != 220) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
+ }
+
+ rc = proxy_ftp_command(apr_pstrcat(p, "USER ", user, CRLF, NULL),
+ r, origin, bb, &ftpmessage);
+ /* possible results; 230, 331, 332, 421, 500, 501, 530 */
+ /* states: 1 - error, 2 - success; 3 - send password, 4,5 fail */
+ /* 230 User logged in, proceed. */
+ /* 331 User name okay, need password. */
+ /* 332 Need account for login. */
+ /* 421 Service not available, closing control connection. */
+ /* 500 Syntax error, command unrecognized. */
+ /* (This may include errors such as command line too long.) */
+ /* 501 Syntax error in parameters or arguments. */
+ /* 530 Not logged in. */
+ if (rc == -1 || rc == 421) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, "Error reading from remote server");
+ }
+ if (rc == 530) {
+ proxy_ftp_cleanup(r, backend);
+ return ftp_unauthorized(r, 1); /* log it: user name guessing
+ * attempt? */
+ }
+ if (rc != 230 && rc != 331) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
+ }
+
+ if (rc == 331) { /* send password */
+ if (password == NULL) {
+ proxy_ftp_cleanup(r, backend);
+ return ftp_unauthorized(r, 0);
+ }
+
+ rc = proxy_ftp_command(apr_pstrcat(p, "PASS ", password, CRLF, NULL),
+ r, origin, bb, &ftpmessage);
+ /* possible results 202, 230, 332, 421, 500, 501, 503, 530 */
+ /* 230 User logged in, proceed. */
+ /* 332 Need account for login. */
+ /* 421 Service not available, closing control connection. */
+ /* 500 Syntax error, command unrecognized. */
+ /* 501 Syntax error in parameters or arguments. */
+ /* 503 Bad sequence of commands. */
+ /* 530 Not logged in. */
+ if (rc == -1 || rc == 421) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
+ "Error reading from remote server");
+ }
+ if (rc == 332) {
+ return ftp_proxyerror(r, backend, HTTP_UNAUTHORIZED,
+ apr_pstrcat(p, "Need account for login: ", ftpmessage, NULL));
+ }
+ /* @@@ questionable -- we might as well return a 403 Forbidden here */
+ if (rc == 530) {
+ proxy_ftp_cleanup(r, backend);
+ return ftp_unauthorized(r, 1); /* log it: passwd guessing
+ * attempt? */
+ }
+ if (rc != 230 && rc != 202) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
+ }
+ }
+ apr_table_set(r->notes, "Directory-README", ftpmessage);
+
+
+ /* Special handling for leading "%2f": this enforces a "cwd /"
+ * out of the $HOME directory which was the starting point after login
+ */
+ if (ap_cstr_casecmpn(path, "%2f", 3) == 0) {
+ path += 3;
+ while (*path == '/') /* skip leading '/' (after root %2f) */
+ ++path;
+
+ rc = proxy_ftp_command("CWD /" CRLF, r, origin, bb, &ftpmessage);
+ if (rc == -1 || rc == 421)
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
+ "Error reading from remote server");
+ }
+
+ /*
+ * set the directory (walk directory component by component): this is
+ * what we must do if we don't know the OS type of the remote machine
+ */
+ for (;;) {
+ strp = strchr(path, '/');
+ if (strp == NULL)
+ break;
+ *strp = '\0';
+
+ decodeenc(path); /* Note! This decodes a %2f -> "/" */
+
+ if (strchr(path, '/')) { /* are there now any '/' characters? */
+ return ftp_proxyerror(r, backend, HTTP_BAD_REQUEST,
+ "Use of /%2f is only allowed at the base directory");
+ }
+
+ /* NOTE: FTP servers do globbing on the path.
+ * So we need to escape the URI metacharacters.
+ * We use a special glob-escaping routine to escape globbing chars.
+ * We could also have extended gen_test_char.c with a special T_ESCAPE_FTP_PATH
+ */
+ rc = proxy_ftp_command(apr_pstrcat(p, "CWD ",
+ ftp_escape_globbingchars(p, path, fdconf), CRLF, NULL),
+ r, origin, bb, &ftpmessage);
+ *strp = '/';
+ /* responses: 250, 421, 500, 501, 502, 530, 550 */
+ /* 250 Requested file action okay, completed. */
+ /* 421 Service not available, closing control connection. */
+ /* 500 Syntax error, command unrecognized. */
+ /* 501 Syntax error in parameters or arguments. */
+ /* 502 Command not implemented. */
+ /* 530 Not logged in. */
+ /* 550 Requested action not taken. */
+ if (rc == -1 || rc == 421) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
+ "Error reading from remote server");
+ }
+ if (rc == 550) {
+ return ftp_proxyerror(r, backend, HTTP_NOT_FOUND, ftpmessage);
+ }
+ if (rc != 250) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
+ }
+
+ path = strp + 1;
+ }
+
+ /*
+ * IV: Make Data Connection? -------------------------
+ *
+ * Try EPSV, if that fails... try PASV, if that fails... try PORT.
+ */
+/* this temporarily switches off EPSV/PASV */
+/*goto bypass;*/
+
+ /* set up data connection - EPSV */
+ {
+ apr_port_t data_port;
+
+ /*
+ * The EPSV command replaces PASV where both IPV4 and IPV6 is
+ * supported. Only the port is returned, the IP address is always the
+ * same as that on the control connection. Example: Entering Extended
+ * Passive Mode (|||6446|)
+ */
+ rc = proxy_ftp_command("EPSV" CRLF,
+ r, origin, bb, &ftpmessage);
+ /* possible results: 227, 421, 500, 501, 502, 530 */
+ /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */
+ /* 421 Service not available, closing control connection. */
+ /* 500 Syntax error, command unrecognized. */
+ /* 501 Syntax error in parameters or arguments. */
+ /* 502 Command not implemented. */
+ /* 530 Not logged in. */
+ if (rc == -1 || rc == 421) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
+ "Error reading from remote server");
+ }
+ if (rc != 229 && rc != 500 && rc != 501 && rc != 502) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
+ }
+ else if (rc == 229) {
+ /* Parse the port out of the EPSV reply. */
+ data_port = parse_epsv_reply(ftpmessage);
+
+ if (data_port) {
+ apr_sockaddr_t *remote_addr, epsv_addr;
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "EPSV contacting remote host on port %d", data_port);
+
+ /* Retrieve the client's address. */
+ rv = apr_socket_addr_get(&remote_addr, APR_REMOTE, sock);
+ if (rv == APR_SUCCESS) {
+ /* Take a shallow copy of the server address to
+ * modify; the _addr_get function gives back a
+ * pointer to the socket's internal structure.
+ * This is awkward given current APR network
+ * interfaces. */
+ epsv_addr = *remote_addr;
+ epsv_addr.port = data_port;
+#if APR_HAVE_IPV6
+ if (epsv_addr.family == APR_INET6) {
+ epsv_addr.sa.sin6.sin6_port = htons(data_port);
+ }
+ else
+#endif
+ {
+ epsv_addr.sa.sin.sin_port = htons(data_port);
+ }
+ rv = apr_socket_create(&data_sock, epsv_addr.family, SOCK_STREAM, 0, r->pool);
+ }
+
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01040)
+ "could not establish socket for client data connection");
+ proxy_ftp_cleanup(r, backend);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (conf->recv_buffer_size > 0
+ && (rv = apr_socket_opt_set(data_sock, APR_SO_RCVBUF,
+ conf->recv_buffer_size))) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01041)
+ "apr_socket_opt_set(SO_RCVBUF): Failed to "
+ "set ProxyReceiveBufferSize, using default");
+ }
+
+ rv = apr_socket_opt_set(data_sock, APR_TCP_NODELAY, 1);
+ if (rv != APR_SUCCESS && rv != APR_ENOTIMPL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01042)
+ "apr_socket_opt_set(APR_TCP_NODELAY): "
+ "Failed to set");
+ }
+
+ rv = apr_socket_connect(data_sock, &epsv_addr);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01043)
+ "EPSV attempt to connect to %pI failed - "
+ "Firewall/NAT?", &epsv_addr);
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, apr_psprintf(r->pool,
+ "EPSV attempt to connect to %pI failed - firewall/NAT?", &epsv_addr));
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "connected data socket to %pI", &epsv_addr);
+ connect = 1;
+ }
+ }
+ }
+ }
+
+ /* set up data connection - PASV */
+ if (!connect) {
+ rc = proxy_ftp_command("PASV" CRLF,
+ r, origin, bb, &ftpmessage);
+ /* possible results: 227, 421, 500, 501, 502, 530 */
+ /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */
+ /* 421 Service not available, closing control connection. */
+ /* 500 Syntax error, command unrecognized. */
+ /* 501 Syntax error in parameters or arguments. */
+ /* 502 Command not implemented. */
+ /* 530 Not logged in. */
+ if (rc == -1 || rc == 421) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
+ "Error reading from remote server");
+ }
+ if (rc != 227 && rc != 502) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
+ }
+ else if (rc == 227) {
+ unsigned int h0, h1, h2, h3, p0, p1;
+ char *pstr;
+ char *tok_cntx;
+
+/* FIXME: Check PASV against RFC1123 */
+
+ pstr = ftpmessage;
+ pstr = apr_strtok(pstr, " ", &tok_cntx); /* separate result code */
+ if (pstr != NULL) {
+ if (*(pstr + strlen(pstr) + 1) == '=') {
+ pstr += strlen(pstr) + 2;
+ }
+ else {
+ pstr = apr_strtok(NULL, "(", &tok_cntx); /* separate address &
+ * port params */
+ if (pstr != NULL)
+ pstr = apr_strtok(NULL, ")", &tok_cntx);
+ }
+ }
+
+/* FIXME: Only supports IPV4 - fix in RFC2428 */
+
+ if (pstr != NULL && (sscanf(pstr,
+ "%d,%d,%d,%d,%d,%d", &h3, &h2, &h1, &h0, &p1, &p0) == 6)) {
+
+ apr_sockaddr_t *pasv_addr;
+ apr_port_t pasvport = (p1 << 8) + p0;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01044)
+ "PASV contacting host %d.%d.%d.%d:%d",
+ h3, h2, h1, h0, pasvport);
+
+ if ((rv = apr_socket_create(&data_sock, connect_addr->family, SOCK_STREAM, 0, r->pool)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01045)
+ "error creating PASV socket");
+ proxy_ftp_cleanup(r, backend);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (conf->recv_buffer_size > 0
+ && (rv = apr_socket_opt_set(data_sock, APR_SO_RCVBUF,
+ conf->recv_buffer_size))) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01046)
+ "apr_socket_opt_set(SO_RCVBUF): Failed to set ProxyReceiveBufferSize, using default");
+ }
+
+ rv = apr_socket_opt_set(data_sock, APR_TCP_NODELAY, 1);
+ if (rv != APR_SUCCESS && rv != APR_ENOTIMPL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01047)
+ "apr_socket_opt_set(APR_TCP_NODELAY): "
+ "Failed to set");
+ }
+
+ /* make the connection */
+ apr_sockaddr_info_get(&pasv_addr, apr_psprintf(p, "%d.%d.%d.%d", h3, h2, h1, h0), connect_addr->family, pasvport, 0, p);
+ rv = apr_socket_connect(data_sock, pasv_addr);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01048)
+ "PASV attempt to connect to %pI failed - Firewall/NAT?", pasv_addr);
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, apr_psprintf(r->pool,
+ "PASV attempt to connect to %pI failed - firewall/NAT?", pasv_addr));
+ }
+ else {
+ connect = 1;
+ }
+ }
+ }
+ }
+/*bypass:*/
+
+ /* set up data connection - PORT */
+ if (!connect) {
+ apr_sockaddr_t *local_addr;
+ char *local_ip;
+ apr_port_t local_port;
+ unsigned int h0, h1, h2, h3, p0, p1;
+
+ if ((rv = apr_socket_create(&local_sock, connect_addr->family, SOCK_STREAM, 0, r->pool)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01049)
+ "error creating local socket");
+ proxy_ftp_cleanup(r, backend);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ apr_socket_addr_get(&local_addr, APR_LOCAL, sock);
+ local_port = local_addr->port;
+ apr_sockaddr_ip_get(&local_ip, local_addr);
+
+ if ((rv = apr_socket_opt_set(local_sock, APR_SO_REUSEADDR, one))
+ != APR_SUCCESS) {
+#ifndef _OSD_POSIX /* BS2000 has this option "always on" */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01050)
+ "error setting reuseaddr option");
+ proxy_ftp_cleanup(r, backend);
+ return HTTP_INTERNAL_SERVER_ERROR;
+#endif /* _OSD_POSIX */
+ }
+
+ apr_sockaddr_info_get(&local_addr, local_ip, APR_UNSPEC, local_port, 0, r->pool);
+
+ if ((rv = apr_socket_bind(local_sock, local_addr)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01051)
+ "error binding to ftp data socket %pI", local_addr);
+ proxy_ftp_cleanup(r, backend);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* only need a short queue */
+ if ((rv = apr_socket_listen(local_sock, 2)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01052)
+ "error listening to ftp data socket %pI", local_addr);
+ proxy_ftp_cleanup(r, backend);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+/* FIXME: Sent PORT here */
+
+ if (local_ip && (sscanf(local_ip,
+ "%d.%d.%d.%d", &h3, &h2, &h1, &h0) == 4)) {
+ p1 = (local_port >> 8);
+ p0 = (local_port & 0xFF);
+
+ rc = proxy_ftp_command(apr_psprintf(p, "PORT %d,%d,%d,%d,%d,%d" CRLF, h3, h2, h1, h0, p1, p0),
+ r, origin, bb, &ftpmessage);
+ /* possible results: 200, 421, 500, 501, 502, 530 */
+ /* 200 Command okay. */
+ /* 421 Service not available, closing control connection. */
+ /* 500 Syntax error, command unrecognized. */
+ /* 501 Syntax error in parameters or arguments. */
+ /* 502 Command not implemented. */
+ /* 530 Not logged in. */
+ if (rc == -1 || rc == 421) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
+ "Error reading from remote server");
+ }
+ if (rc != 200) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
+ }
+
+ /* signal that we must use the EPRT/PORT loop */
+ use_port = 1;
+ }
+ else {
+/* IPV6 FIXME:
+ * The EPRT command replaces PORT where both IPV4 and IPV6 is supported. The first
+ * number (1,2) indicates the protocol type. Examples:
+ * EPRT |1|132.235.1.2|6275|
+ * EPRT |2|1080::8:800:200C:417A|5282|
+ */
+ return ftp_proxyerror(r, backend, HTTP_NOT_IMPLEMENTED,
+ "Connect to IPV6 ftp server using EPRT not supported. Enable EPSV.");
+ }
+ }
+
+
+ /*
+ * V: Set The Headers -------------------
+ *
+ * Get the size of the request, set up the environment for HTTP.
+ */
+
+ /* set request; "path" holds last path component */
+ len = decodeenc(path);
+
+ if (strchr(path, '/')) { /* are there now any '/' characters? */
+ return ftp_proxyerror(r, backend, HTTP_BAD_REQUEST,
+ "Use of /%2f is only allowed at the base directory");
+ }
+
+ /* If len == 0 then it must be a directory (you can't RETR nothing)
+ * Also, don't allow to RETR by wildcard. Instead, create a dirlisting,
+ * unless ProxyFtpListOnWildcard is off.
+ */
+ if (len == 0 || (ftp_check_globbingchars(path) && fdconf->ftp_list_on_wildcard)) {
+ dirlisting = 1;
+ }
+ else {
+ /* (from FreeBSD ftpd):
+ * SIZE is not in RFC959, but Postel has blessed it and
+ * it will be in the updated RFC.
+ *
+ * Return size of file in a format suitable for
+ * using with RESTART (we just count bytes).
+ */
+ /* from draft-ietf-ftpext-mlst-14.txt:
+ * This value will
+ * change depending on the current STRUcture, MODE and TYPE of the data
+ * connection, or a data connection which would be created were one
+ * created now. Thus, the result of the SIZE command is dependent on
+ * the currently established STRU, MODE and TYPE parameters.
+ */
+ /* Therefore: switch to binary if the user did not specify ";type=a" */
+ ftp_set_TYPE(xfer_type, r, origin, bb, &ftpmessage);
+ rc = proxy_ftp_command(apr_pstrcat(p, "SIZE ",
+ ftp_escape_globbingchars(p, path, fdconf), CRLF, NULL),
+ r, origin, bb, &ftpmessage);
+ if (rc == -1 || rc == 421) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
+ "Error reading from remote server");
+ }
+ else if (rc == 213) {/* Size command ok */
+ int j;
+ for (j = 0; apr_isdigit(ftpmessage[j]); j++)
+ ;
+ ftpmessage[j] = '\0';
+ if (ftpmessage[0] != '\0')
+ size = ftpmessage; /* already pstrdup'ed: no copy necessary */
+ }
+ else if (rc == 550) { /* Not a regular file */
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r,
+ "SIZE shows this is a directory");
+ dirlisting = 1;
+ rc = proxy_ftp_command(apr_pstrcat(p, "CWD ",
+ ftp_escape_globbingchars(p, path, fdconf), CRLF, NULL),
+ r, origin, bb, &ftpmessage);
+ /* possible results: 250, 421, 500, 501, 502, 530, 550 */
+ /* 250 Requested file action okay, completed. */
+ /* 421 Service not available, closing control connection. */
+ /* 500 Syntax error, command unrecognized. */
+ /* 501 Syntax error in parameters or arguments. */
+ /* 502 Command not implemented. */
+ /* 530 Not logged in. */
+ /* 550 Requested action not taken. */
+ if (rc == -1 || rc == 421) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
+ "Error reading from remote server");
+ }
+ if (rc == 550) {
+ return ftp_proxyerror(r, backend, HTTP_NOT_FOUND, ftpmessage);
+ }
+ if (rc != 250) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
+ }
+ path = "";
+ len = 0;
+ }
+ }
+
+ cwd = ftp_get_PWD(r, origin, bb);
+ if (cwd != NULL) {
+ apr_table_set(r->notes, "Directory-PWD", cwd);
+ }
+
+ if (dirlisting) {
+ ftp_set_TYPE('A', r, origin, bb, NULL);
+ /* If the current directory contains no slash, we are talking to
+ * a non-unix ftp system. Try LIST instead of "LIST -lag", it
+ * should return a long listing anyway (unlike NLST).
+ * Some exotic FTP servers might choke on the "-lag" switch.
+ */
+ /* Note that we do not escape the path here, to allow for
+ * queries like: ftp://user@host/apache/src/server/http_*.c
+ */
+ if (len != 0)
+ buf = apr_pstrcat(p, "LIST ", path, CRLF, NULL);
+ else if (cwd == NULL || strchr(cwd, '/') != NULL)
+ buf = "LIST -lag" CRLF;
+ else
+ buf = "LIST" CRLF;
+ }
+ else {
+ /* switch to binary if the user did not specify ";type=a" */
+ ftp_set_TYPE(xfer_type, r, origin, bb, &ftpmessage);
+#if defined(USE_MDTM) && (defined(HAVE_TIMEGM) || defined(HAVE_GMTOFF))
+ /* from draft-ietf-ftpext-mlst-14.txt:
+ * The FTP command, MODIFICATION TIME (MDTM), can be used to determine
+ * when a file in the server NVFS was last modified. <..>
+ * The syntax of a time value is:
+ * time-val = 14DIGIT [ "." 1*DIGIT ] <..>
+ * Symbolically, a time-val may be viewed as
+ * YYYYMMDDHHMMSS.sss
+ * The "." and subsequent digits ("sss") are optional. <..>
+ * Time values are always represented in UTC (GMT)
+ */
+ rc = proxy_ftp_command(apr_pstrcat(p, "MDTM ", ftp_escape_globbingchars(p, path, fdconf), CRLF, NULL),
+ r, origin, bb, &ftpmessage);
+ /* then extract the Last-Modified time from it (YYYYMMDDhhmmss or YYYYMMDDhhmmss.xxx GMT). */
+ if (rc == 213) {
+ struct {
+ char YYYY[4+1];
+ char MM[2+1];
+ char DD[2+1];
+ char hh[2+1];
+ char mm[2+1];
+ char ss[2+1];
+ } time_val;
+ if (6 == sscanf(ftpmessage, "%4[0-9]%2[0-9]%2[0-9]%2[0-9]%2[0-9]%2[0-9]",
+ time_val.YYYY, time_val.MM, time_val.DD, time_val.hh, time_val.mm, time_val.ss)) {
+ struct tm tms;
+ memset (&tms, '\0', sizeof tms);
+ tms.tm_year = atoi(time_val.YYYY) - 1900;
+ tms.tm_mon = atoi(time_val.MM) - 1;
+ tms.tm_mday = atoi(time_val.DD);
+ tms.tm_hour = atoi(time_val.hh);
+ tms.tm_min = atoi(time_val.mm);
+ tms.tm_sec = atoi(time_val.ss);
+#ifdef HAVE_TIMEGM /* Does system have timegm()? */
+ mtime = timegm(&tms);
+ mtime *= APR_USEC_PER_SEC;
+#elif HAVE_GMTOFF /* does struct tm have a member tm_gmtoff? */
+ /* mktime will subtract the local timezone, which is not what we want.
+ * Add it again because the MDTM string is GMT
+ */
+ mtime = mktime(&tms);
+ mtime += tms.tm_gmtoff;
+ mtime *= APR_USEC_PER_SEC;
+#else
+ mtime = 0L;
+#endif
+ }
+ }
+#endif /* USE_MDTM */
+/* FIXME: Handle range requests - send REST */
+ buf = apr_pstrcat(p, "RETR ", ftp_escape_globbingchars(p, path, fdconf), CRLF, NULL);
+ }
+ rc = proxy_ftp_command(buf, r, origin, bb, &ftpmessage);
+ /* rc is an intermediate response for the LIST or RETR commands */
+
+ /*
+ * RETR: 110, 125, 150, 226, 250, 421, 425, 426, 450, 451, 500, 501, 530,
+ * 550 NLST: 125, 150, 226, 250, 421, 425, 426, 450, 451, 500, 501, 502,
+ * 530
+ */
+ /* 110 Restart marker reply. */
+ /* 125 Data connection already open; transfer starting. */
+ /* 150 File status okay; about to open data connection. */
+ /* 226 Closing data connection. */
+ /* 250 Requested file action okay, completed. */
+ /* 421 Service not available, closing control connection. */
+ /* 425 Can't open data connection. */
+ /* 426 Connection closed; transfer aborted. */
+ /* 450 Requested file action not taken. */
+ /* 451 Requested action aborted. Local error in processing. */
+ /* 500 Syntax error, command unrecognized. */
+ /* 501 Syntax error in parameters or arguments. */
+ /* 530 Not logged in. */
+ /* 550 Requested action not taken. */
+ if (rc == -1 || rc == 421) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
+ "Error reading from remote server");
+ }
+ if (rc == 550) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r,
+ "RETR failed, trying LIST instead");
+
+ /* Directory Listings should always be fetched in ASCII mode */
+ dirlisting = 1;
+ ftp_set_TYPE('A', r, origin, bb, NULL);
+
+ rc = proxy_ftp_command(apr_pstrcat(p, "CWD ",
+ ftp_escape_globbingchars(p, path, fdconf), CRLF, NULL),
+ r, origin, bb, &ftpmessage);
+ /* possible results: 250, 421, 500, 501, 502, 530, 550 */
+ /* 250 Requested file action okay, completed. */
+ /* 421 Service not available, closing control connection. */
+ /* 500 Syntax error, command unrecognized. */
+ /* 501 Syntax error in parameters or arguments. */
+ /* 502 Command not implemented. */
+ /* 530 Not logged in. */
+ /* 550 Requested action not taken. */
+ if (rc == -1 || rc == 421) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
+ "Error reading from remote server");
+ }
+ if (rc == 550) {
+ return ftp_proxyerror(r, backend, HTTP_NOT_FOUND, ftpmessage);
+ }
+ if (rc != 250) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
+ }
+
+ /* Update current directory after CWD */
+ cwd = ftp_get_PWD(r, origin, bb);
+ if (cwd != NULL) {
+ apr_table_set(r->notes, "Directory-PWD", cwd);
+ }
+
+ /* See above for the "LIST" vs. "LIST -lag" discussion. */
+ rc = proxy_ftp_command((cwd == NULL || strchr(cwd, '/') != NULL)
+ ? "LIST -lag" CRLF : "LIST" CRLF,
+ r, origin, bb, &ftpmessage);
+
+ /* rc is an intermediate response for the LIST command (125 transfer starting, 150 opening data connection) */
+ if (rc == -1 || rc == 421)
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
+ "Error reading from remote server");
+ }
+ if (rc != 125 && rc != 150 && rc != 226 && rc != 250) {
+ return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
+ }
+
+ r->status = HTTP_OK;
+ r->status_line = "200 OK";
+
+ apr_rfc822_date(dates, r->request_time);
+ apr_table_setn(r->headers_out, "Date", dates);
+ apr_table_setn(r->headers_out, "Server", ap_get_server_description());
+
+ /* set content-type */
+ if (dirlisting) {
+ ap_set_content_type(r, apr_pstrcat(p, "text/html;charset=",
+ fdconf->ftp_directory_charset ?
+ fdconf->ftp_directory_charset :
+ "ISO-8859-1", NULL));
+ }
+ else {
+ if (xfer_type != 'A' && size != NULL) {
+ /* We "trust" the ftp server to really serve (size) bytes... */
+ apr_table_setn(r->headers_out, "Content-Length", size);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
+ "Content-Length set to %s", size);
+ }
+ }
+ if (r->content_type) {
+ apr_table_setn(r->headers_out, "Content-Type", r->content_type);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
+ "Content-Type set to %s", r->content_type);
+ }
+
+#if defined(USE_MDTM) && (defined(HAVE_TIMEGM) || defined(HAVE_GMTOFF))
+ if (mtime != 0L) {
+ char datestr[APR_RFC822_DATE_LEN];
+ apr_rfc822_date(datestr, mtime);
+ apr_table_set(r->headers_out, "Last-Modified", datestr);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
+ "Last-Modified set to %s", datestr);
+ }
+#endif /* USE_MDTM */
+
+ /* If an encoding has been set by mistake, delete it.
+ * @@@ FIXME (e.g., for ftp://user@host/file*.tar.gz,
+ * @@@ the encoding is currently set to x-gzip)
+ */
+ if (dirlisting && r->content_encoding != NULL)
+ r->content_encoding = NULL;
+
+ /* set content-encoding (not for dir listings, they are uncompressed)*/
+ if (r->content_encoding != NULL && r->content_encoding[0] != '\0') {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
+ "Content-Encoding set to %s", r->content_encoding);
+ apr_table_setn(r->headers_out, "Content-Encoding", r->content_encoding);
+ }
+
+ /* wait for connection */
+ if (use_port) {
+ for (;;) {
+ rv = apr_socket_accept(&data_sock, local_sock, r->pool);
+ if (APR_STATUS_IS_EINTR(rv)) {
+ continue;
+ }
+ else if (rv == APR_SUCCESS) {
+ break;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01053)
+ "failed to accept data connection");
+ proxy_ftp_cleanup(r, backend);
+ return HTTP_BAD_GATEWAY;
+ }
+ }
+ }
+
+ /* the transfer socket is now open, create a new connection */
+ data = ap_run_create_connection(p, r->server, data_sock, r->connection->id,
+ r->connection->sbh, c->bucket_alloc);
+ if (!data) {
+ /*
+ * the peer reset the connection already; ap_run_create_connection() closed
+ * the socket
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01054)
+ "an error occurred creating the transfer connection");
+ proxy_ftp_cleanup(r, backend);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /*
+ * We do not do SSL over the data connection, even if the virtual host we
+ * are in might have SSL enabled
+ */
+ ap_proxy_ssl_engine(data, r->per_dir_config, 0);
+ /* set up the connection filters */
+ rc = ap_run_pre_connection(data, data_sock);
+ if (rc != OK && rc != DONE) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01055)
+ "pre_connection setup failed (%d)", rc);
+ data->aborted = 1;
+ proxy_ftp_cleanup(r, backend);
+ return rc;
+ }
+
+ /*
+ * VI: Receive the Response ------------------------
+ *
+ * Get response from the remote ftp socket, and pass it up the filter chain.
+ */
+
+ /* send response */
+ r->sent_bodyct = 1;
+
+ if (dirlisting) {
+ /* insert directory filter */
+ ap_add_output_filter("PROXY_SEND_DIR", NULL, r, r->connection);
+ }
+
+ /* send body */
+ if (!r->header_only) {
+ apr_bucket *e;
+ int finish = FALSE;
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, "start body send");
+
+ /* read the body, pass it to the output filters */
+ while (ap_get_brigade(data->input_filters,
+ bb,
+ AP_MODE_READBYTES,
+ APR_BLOCK_READ,
+ conf->io_buffer_size) == APR_SUCCESS) {
+#if DEBUGGING
+ {
+ apr_off_t readbytes;
+ apr_brigade_length(bb, 0, &readbytes);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, APLOGNO(01056)
+ "proxy: readbytes: %#x", readbytes);
+ }
+#endif
+ /* sanity check */
+ if (APR_BRIGADE_EMPTY(bb)) {
+ break;
+ }
+
+ /* found the last brigade? */
+ if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb))) {
+ /* if this is the last brigade, cleanup the
+ * backend connection first to prevent the
+ * backend server from hanging around waiting
+ * for a slow client to eat these bytes
+ */
+ ap_flush_conn(data);
+ if (data_sock) {
+ apr_socket_close(data_sock);
+ }
+ data_sock = NULL;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01057)
+ "data connection closed");
+ /* signal that we must leave */
+ finish = TRUE;
+ }
+
+ /* if no EOS yet, then we must flush */
+ if (FALSE == finish) {
+ e = apr_bucket_flush_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ }
+
+ /* try send what we read */
+ if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS
+ || c->aborted) {
+ /* Ack! Phbtt! Die! User aborted! */
+ finish = TRUE;
+ }
+
+ /* make sure we always clean up after ourselves */
+ apr_brigade_cleanup(bb);
+
+ /* if we are done, leave */
+ if (TRUE == finish) {
+ break;
+ }
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, "end body send");
+
+ }
+ if (data_sock) {
+ ap_flush_conn(data);
+ apr_socket_close(data_sock);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01058) "data connection closed");
+ }
+
+ /* Retrieve the final response for the RETR or LIST commands */
+ proxy_ftp_command(NULL, r, origin, bb, &ftpmessage);
+ apr_brigade_cleanup(bb);
+
+ /*
+ * VII: Clean Up -------------
+ *
+ * If there are no KeepAlives, or if the connection has been signalled to
+ * close, close the socket and clean up
+ */
+
+ /* finish */
+ proxy_ftp_command("QUIT" CRLF, r, origin, bb, &ftpmessage);
+ /* responses: 221, 500 */
+ /* 221 Service closing control connection. */
+ /* 500 Syntax error, command unrecognized. */
+ ap_flush_conn(origin);
+ proxy_ftp_cleanup(r, backend);
+
+ apr_brigade_destroy(bb);
+ return OK;
+}
+
+static void ap_proxy_ftp_register_hook(apr_pool_t *p)
+{
+ /* hooks */
+ proxy_hook_scheme_handler(proxy_ftp_handler, NULL, NULL, APR_HOOK_MIDDLE);
+ proxy_hook_canon_handler(proxy_ftp_canon, NULL, NULL, APR_HOOK_MIDDLE);
+ /* filters */
+ ap_register_output_filter("PROXY_SEND_DIR", proxy_send_dir_filter,
+ NULL, AP_FTYPE_RESOURCE);
+ /* Compile the output format of "ls -s1" as a fallback for non-unix ftp listings */
+ ls_regex = ap_pregcomp(p, LS_REG_PATTERN, AP_REG_EXTENDED);
+ ap_assert(ls_regex != NULL);
+}
+
+static const command_rec proxy_ftp_cmds[] =
+{
+ AP_INIT_FLAG("ProxyFtpListOnWildcard", set_ftp_list_on_wildcard, NULL,
+ RSRC_CONF|ACCESS_CONF, "Whether wildcard characters in a path cause mod_proxy_ftp to list the files instead of trying to get them. Defaults to on."),
+ AP_INIT_FLAG("ProxyFtpEscapeWildcards", set_ftp_escape_wildcards, NULL,
+ RSRC_CONF|ACCESS_CONF, "Whether the proxy should escape wildcards in paths before sending them to the FTP server. Defaults to on, but most FTP servers will need it turned off if you need to manage paths that contain wildcard characters."),
+ AP_INIT_TAKE1("ProxyFtpDirCharset", set_ftp_directory_charset, NULL,
+ RSRC_CONF|ACCESS_CONF, "Define the character set for proxied FTP listings"),
+ {NULL}
+};
+
+
+AP_DECLARE_MODULE(proxy_ftp) = {
+ STANDARD20_MODULE_STUFF,
+ create_proxy_ftp_dir_config,/* create per-directory config structure */
+ merge_proxy_ftp_dir_config, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ proxy_ftp_cmds, /* command apr_table_t */
+ ap_proxy_ftp_register_hook /* register hooks */
+};
diff --git a/modules/proxy/mod_proxy_ftp.dep b/modules/proxy/mod_proxy_ftp.dep
new file mode 100644
index 0000000..46b78fe
--- /dev/null
+++ b/modules/proxy/mod_proxy_ftp.dep
@@ -0,0 +1,74 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_proxy_ftp.mak
+
+.\mod_proxy_ftp.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_slotmem.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_date.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_reslist.h"\
+ "..\..\srclib\apr-util\include\apr_strmatch.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_version.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_proxy.h"\
+
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
diff --git a/modules/proxy/mod_proxy_ftp.dsp b/modules/proxy/mod_proxy_ftp.dsp
new file mode 100644
index 0000000..2ad1711
--- /dev/null
+++ b/modules/proxy/mod_proxy_ftp.dsp
@@ -0,0 +1,123 @@
+# Microsoft Developer Studio Project File - Name="mod_proxy_ftp" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_proxy_ftp - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_ftp.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_ftp.mak" CFG="mod_proxy_ftp - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy_ftp - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy_ftp - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_proxy_ftp - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_proxy_ftp_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_proxy_ftp.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_proxy_ftp.so" /d LONG_NAME="proxy_ftp_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:".\Release\mod_proxy_ftp.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_ftp.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_proxy_ftp.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_ftp.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_proxy_ftp.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_proxy_ftp - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_proxy_ftp_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_proxy_ftp.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_proxy_ftp.so" /d LONG_NAME="proxy_ftp_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_proxy_ftp.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_ftp.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_proxy_ftp.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_ftp.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_proxy_ftp.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_proxy_ftp - Win32 Release"
+# Name "mod_proxy_ftp - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\mod_proxy_ftp.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter ".h"
+# Begin Source File
+
+SOURCE=.\mod_proxy.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/proxy/mod_proxy_ftp.mak b/modules/proxy/mod_proxy_ftp.mak
new file mode 100644
index 0000000..0b1ca30
--- /dev/null
+++ b/modules/proxy/mod_proxy_ftp.mak
@@ -0,0 +1,380 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_proxy_ftp.dsp
+!IF "$(CFG)" == ""
+CFG=mod_proxy_ftp - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_proxy_ftp - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_proxy_ftp - Win32 Release" && "$(CFG)" != "mod_proxy_ftp - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_ftp.mak" CFG="mod_proxy_ftp - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy_ftp - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy_ftp - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_ftp - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_proxy_ftp.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_proxy - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_proxy_ftp.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_proxy - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_proxy_ftp.obj"
+ -@erase "$(INTDIR)\mod_proxy_ftp.res"
+ -@erase "$(INTDIR)\mod_proxy_ftp_src.idb"
+ -@erase "$(INTDIR)\mod_proxy_ftp_src.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_ftp.exp"
+ -@erase "$(OUTDIR)\mod_proxy_ftp.lib"
+ -@erase "$(OUTDIR)\mod_proxy_ftp.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_ftp.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_proxy_ftp_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_proxy_ftp.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_proxy_ftp.so" /d LONG_NAME="proxy_ftp_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_proxy_ftp.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_proxy_ftp.pdb" /debug /out:"$(OUTDIR)\mod_proxy_ftp.so" /implib:"$(OUTDIR)\mod_proxy_ftp.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_ftp.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_proxy_ftp.obj" \
+ "$(INTDIR)\mod_proxy_ftp.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib" \
+ "$(OUTDIR)\mod_proxy.lib"
+
+"$(OUTDIR)\mod_proxy_ftp.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_proxy_ftp.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_proxy_ftp.so"
+ if exist .\Release\mod_proxy_ftp.so.manifest mt.exe -manifest .\Release\mod_proxy_ftp.so.manifest -outputresource:.\Release\mod_proxy_ftp.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_proxy_ftp - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_proxy_ftp.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_proxy - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_proxy_ftp.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_proxy - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_proxy_ftp.obj"
+ -@erase "$(INTDIR)\mod_proxy_ftp.res"
+ -@erase "$(INTDIR)\mod_proxy_ftp_src.idb"
+ -@erase "$(INTDIR)\mod_proxy_ftp_src.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_ftp.exp"
+ -@erase "$(OUTDIR)\mod_proxy_ftp.lib"
+ -@erase "$(OUTDIR)\mod_proxy_ftp.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_ftp.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_proxy_ftp_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_proxy_ftp.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_proxy_ftp.so" /d LONG_NAME="proxy_ftp_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_proxy_ftp.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_proxy_ftp.pdb" /debug /out:"$(OUTDIR)\mod_proxy_ftp.so" /implib:"$(OUTDIR)\mod_proxy_ftp.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_ftp.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_proxy_ftp.obj" \
+ "$(INTDIR)\mod_proxy_ftp.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib" \
+ "$(OUTDIR)\mod_proxy.lib"
+
+"$(OUTDIR)\mod_proxy_ftp.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_proxy_ftp.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_proxy_ftp.so"
+ if exist .\Debug\mod_proxy_ftp.so.manifest mt.exe -manifest .\Debug\mod_proxy_ftp.so.manifest -outputresource:.\Debug\mod_proxy_ftp.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_proxy_ftp.dep")
+!INCLUDE "mod_proxy_ftp.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_proxy_ftp.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_proxy_ftp - Win32 Release" || "$(CFG)" == "mod_proxy_ftp - Win32 Debug"
+SOURCE=.\mod_proxy_ftp.c
+
+"$(INTDIR)\mod_proxy_ftp.obj" : $(SOURCE) "$(INTDIR)"
+
+
+!IF "$(CFG)" == "mod_proxy_ftp - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\proxy"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_ftp - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\proxy"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_ftp - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\proxy"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_ftp - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\proxy"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_ftp - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\proxy"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_ftp - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\proxy"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_ftp - Win32 Release"
+
+"mod_proxy - Win32 Release" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Release"
+ cd "."
+
+"mod_proxy - Win32 ReleaseCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Release" RECURSE=1 CLEAN
+ cd "."
+
+!ELSEIF "$(CFG)" == "mod_proxy_ftp - Win32 Debug"
+
+"mod_proxy - Win32 Debug" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Debug"
+ cd "."
+
+"mod_proxy - Win32 DebugCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Debug" RECURSE=1 CLEAN
+ cd "."
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_proxy_ftp - Win32 Release"
+
+
+"$(INTDIR)\mod_proxy_ftp.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_proxy_ftp.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_proxy_ftp.so" /d LONG_NAME="proxy_ftp_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_proxy_ftp - Win32 Debug"
+
+
+"$(INTDIR)\mod_proxy_ftp.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_proxy_ftp.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_proxy_ftp.so" /d LONG_NAME="proxy_ftp_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+
+!ENDIF
+
diff --git a/modules/proxy/mod_proxy_hcheck.c b/modules/proxy/mod_proxy_hcheck.c
new file mode 100644
index 0000000..d618b4d
--- /dev/null
+++ b/modules/proxy/mod_proxy_hcheck.c
@@ -0,0 +1,1351 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "mod_proxy.h"
+#include "mod_watchdog.h"
+#include "ap_slotmem.h"
+#include "ap_expr.h"
+#if APR_HAS_THREADS
+#include "apr_thread_pool.h"
+#endif
+#include "http_ssl.h"
+
+module AP_MODULE_DECLARE_DATA proxy_hcheck_module;
+
+#define HCHECK_WATHCHDOG_NAME ("_proxy_hcheck_")
+#define HC_THREADPOOL_SIZE (16)
+
+/* Why? So we can easily set/clear HC_USE_THREADS during dev testing */
+#if APR_HAS_THREADS
+#ifndef HC_USE_THREADS
+#define HC_USE_THREADS 1
+#endif
+#else
+#define HC_USE_THREADS 0
+#endif
+
+typedef struct {
+ char *name;
+ hcmethod_t method;
+ int passes;
+ int fails;
+ apr_interval_time_t interval;
+ char *hurl;
+ char *hcexpr;
+} hc_template_t;
+
+typedef struct {
+ char *expr;
+ ap_expr_info_t *pexpr; /* parsed expression */
+} hc_condition_t;
+
+typedef struct {
+ apr_pool_t *p;
+ apr_array_header_t *templates;
+ apr_table_t *conditions;
+ apr_hash_t *hcworkers;
+ server_rec *s;
+} sctx_t;
+
+/* Used in the HC worker via the context field */
+typedef struct {
+ const char *path; /* The path of the original worker URL */
+ const char *method; /* Method string for the HTTP/AJP request */
+ const char *req; /* pre-formatted HTTP/AJP request */
+ proxy_worker *w; /* Pointer to the actual worker */
+ const char *protocol; /* HTTP 1.0 or 1.1? */
+} wctx_t;
+
+typedef struct {
+ apr_pool_t *ptemp;
+ sctx_t *ctx;
+ proxy_balancer *balancer;
+ proxy_worker *worker;
+ proxy_worker *hc;
+ apr_time_t *now;
+} baton_t;
+
+static APR_OPTIONAL_FN_TYPE(ajp_handle_cping_cpong) *ajp_handle_cping_cpong = NULL;
+
+static void *hc_create_config(apr_pool_t *p, server_rec *s)
+{
+ sctx_t *ctx = apr_pcalloc(p, sizeof(sctx_t));
+ ctx->s = s;
+ apr_pool_create(&ctx->p, p);
+ apr_pool_tag(ctx->p, "proxy_hcheck");
+ ctx->templates = apr_array_make(p, 10, sizeof(hc_template_t));
+ ctx->conditions = apr_table_make(p, 10);
+ ctx->hcworkers = apr_hash_make(p);
+ return ctx;
+}
+
+static ap_watchdog_t *watchdog;
+#if HC_USE_THREADS
+static apr_thread_pool_t *hctp;
+static int tpsize;
+#endif
+
+/*
+ * This serves double duty by not only validating (and creating)
+ * the health-check template, but also ties into set_worker_param()
+ * which does the actual setting of worker params in shm.
+ */
+static const char *set_worker_hc_param(apr_pool_t *p,
+ server_rec *s,
+ proxy_worker *worker,
+ const char *key,
+ const char *val,
+ void *v)
+{
+ int ival;
+ hc_template_t *temp;
+ sctx_t *ctx = (sctx_t *) ap_get_module_config(s->module_config,
+ &proxy_hcheck_module);
+ if (!worker && !v) {
+ return "Bad call to set_worker_hc_param()";
+ }
+ if (!ctx) {
+ ctx = hc_create_config(p, s);
+ ap_set_module_config(s->module_config, &proxy_hcheck_module, ctx);
+ }
+ temp = (hc_template_t *)v;
+ if (!strcasecmp(key, "hctemplate")) {
+ hc_template_t *template;
+ template = (hc_template_t *)ctx->templates->elts;
+ for (ival = 0; ival < ctx->templates->nelts; ival++, template++) {
+ if (!ap_cstr_casecmp(template->name, val)) {
+ if (worker) {
+ worker->s->method = template->method;
+ worker->s->interval = template->interval;
+ worker->s->passes = template->passes;
+ worker->s->fails = template->fails;
+ PROXY_STRNCPY(worker->s->hcuri, template->hurl);
+ PROXY_STRNCPY(worker->s->hcexpr, template->hcexpr);
+ } else {
+ temp->method = template->method;
+ temp->interval = template->interval;
+ temp->passes = template->passes;
+ temp->fails = template->fails;
+ temp->hurl = apr_pstrdup(p, template->hurl);
+ temp->hcexpr = apr_pstrdup(p, template->hcexpr);
+ }
+ return NULL;
+ }
+ }
+ return apr_psprintf(p, "Unknown ProxyHCTemplate name: %s", val);
+ }
+ else if (!strcasecmp(key, "hcmethod")) {
+ proxy_hcmethods_t *method = proxy_hcmethods;
+ for (; method->name; method++) {
+ if (!ap_cstr_casecmp(val, method->name)) {
+ if (!method->implemented) {
+ return apr_psprintf(p, "Health check method %s not (yet) implemented",
+ val);
+ }
+ if (worker) {
+ worker->s->method = method->method;
+ } else {
+ temp->method = method->method;
+ }
+ return NULL;
+ }
+ }
+ return "Unknown method";
+ }
+ else if (!strcasecmp(key, "hcinterval")) {
+ apr_interval_time_t hci;
+ apr_status_t rv;
+ rv = ap_timeout_parameter_parse(val, &hci, "s");
+ if (rv != APR_SUCCESS)
+ return "Unparse-able hcinterval setting";
+ if (hci < AP_WD_TM_SLICE)
+ return apr_psprintf(p, "Interval must be a positive value greater than %"
+ APR_TIME_T_FMT "ms", apr_time_as_msec(AP_WD_TM_SLICE));
+ if (worker) {
+ worker->s->interval = hci;
+ } else {
+ temp->interval = hci;
+ }
+ }
+ else if (!strcasecmp(key, "hcpasses")) {
+ ival = atoi(val);
+ if (ival < 0)
+ return "Passes must be a positive value";
+ if (worker) {
+ worker->s->passes = ival;
+ } else {
+ temp->passes = ival;
+ }
+ }
+ else if (!strcasecmp(key, "hcfails")) {
+ ival = atoi(val);
+ if (ival < 0)
+ return "Fails must be a positive value";
+ if (worker) {
+ worker->s->fails = ival;
+ } else {
+ temp->fails = ival;
+ }
+ }
+ else if (!strcasecmp(key, "hcuri")) {
+ if (strlen(val) >= sizeof(worker->s->hcuri))
+ return apr_psprintf(p, "Health check uri length must be < %d characters",
+ (int)sizeof(worker->s->hcuri));
+ if (worker) {
+ PROXY_STRNCPY(worker->s->hcuri, val);
+ } else {
+ temp->hurl = apr_pstrdup(p, val);
+ }
+ }
+ else if (!strcasecmp(key, "hcexpr")) {
+ hc_condition_t *cond;
+ cond = (hc_condition_t *)apr_table_get(ctx->conditions, val);
+ if (!cond) {
+ return apr_psprintf(p, "Unknown health check condition expr: %s", val);
+ }
+ /* This check is wonky... a known expr can't be this big. Check anyway */
+ if (strlen(val) >= sizeof(worker->s->hcexpr))
+ return apr_psprintf(p, "Health check uri length must be < %d characters",
+ (int)sizeof(worker->s->hcexpr));
+ if (worker) {
+ PROXY_STRNCPY(worker->s->hcexpr, val);
+ } else {
+ temp->hcexpr = apr_pstrdup(p, val);
+ }
+ }
+ else {
+ return "unknown Worker hcheck parameter";
+ }
+ return NULL;
+}
+
+static const char *set_hc_condition(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ char *name = NULL;
+ char *expr;
+ sctx_t *ctx;
+ hc_condition_t *cond;
+
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_HTACCESS);
+ if (err)
+ return err;
+ ctx = (sctx_t *) ap_get_module_config(cmd->server->module_config,
+ &proxy_hcheck_module);
+
+ name = ap_getword_conf(cmd->pool, &arg);
+ if (!*name) {
+ return apr_pstrcat(cmd->temp_pool, "Missing expression name for ",
+ cmd->cmd->name, NULL);
+ }
+ if (strlen(name) > (PROXY_WORKER_MAX_SCHEME_SIZE - 1)) {
+ return apr_psprintf(cmd->temp_pool, "Expression name limited to %d characters",
+ (PROXY_WORKER_MAX_SCHEME_SIZE - 1));
+ }
+ /* get expr. Allow fancy new {...} quoting style */
+ expr = ap_getword_conf2(cmd->temp_pool, &arg);
+ if (!*expr) {
+ return apr_pstrcat(cmd->temp_pool, "Missing expression for ",
+ cmd->cmd->name, NULL);
+ }
+ cond = apr_palloc(cmd->pool, sizeof(hc_condition_t));
+ cond->pexpr = ap_expr_parse_cmd(cmd, expr, 0, &err, NULL);
+ if (err) {
+ return apr_psprintf(cmd->temp_pool, "Could not parse expression \"%s\": %s",
+ expr, err);
+ }
+ cond->expr = apr_pstrdup(cmd->pool, expr);
+ apr_table_setn(ctx->conditions, name, (void *)cond);
+ expr = ap_getword_conf(cmd->temp_pool, &arg);
+ if (*expr) {
+ return "error: extra parameter(s)";
+ }
+ return NULL;
+}
+
+static const char *set_hc_template(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ char *name = NULL;
+ char *word, *val;
+ hc_template_t *template;
+ sctx_t *ctx;
+
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_HTACCESS);
+ if (err)
+ return err;
+ ctx = (sctx_t *) ap_get_module_config(cmd->server->module_config,
+ &proxy_hcheck_module);
+
+ name = ap_getword_conf(cmd->temp_pool, &arg);
+ if (!*name) {
+ return apr_pstrcat(cmd->temp_pool, "Missing template name for ",
+ cmd->cmd->name, NULL);
+ }
+
+ template = (hc_template_t *)apr_array_push(ctx->templates);
+
+ template->name = apr_pstrdup(cmd->pool, name);
+ template->method = template->passes = template->fails = 1;
+ template->interval = apr_time_from_sec(HCHECK_WATHCHDOG_DEFAULT_INTERVAL);
+ template->hurl = NULL;
+ template->hcexpr = NULL;
+ while (*arg) {
+ word = ap_getword_conf(cmd->pool, &arg);
+ val = strchr(word, '=');
+ if (!val) {
+ return "Invalid ProxyHCTemplate parameter. Parameter must be "
+ "in the form 'key=value'";
+ }
+ else
+ *val++ = '\0';
+ err = set_worker_hc_param(cmd->pool, ctx->s, NULL, word, val, template);
+
+ if (err) {
+ /* get rid of recently pushed (bad) template */
+ apr_array_pop(ctx->templates);
+ return apr_pstrcat(cmd->temp_pool, "ProxyHCTemplate: ", err, " ", word, "=", val, "; ", name, NULL);
+ }
+ /* No error means we have a valid template */
+ }
+ return NULL;
+}
+
+#if HC_USE_THREADS
+static const char *set_hc_tpsize (cmd_parms *cmd, void *dummy, const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err)
+ return err;
+
+ tpsize = atoi(arg);
+ if (tpsize < 0)
+ return "Invalid ProxyHCTPsize parameter. Parameter must be "
+ ">= 0";
+ return NULL;
+}
+#endif
+
+/*
+ * Create a dummy request rec, simply so we can use ap_expr.
+ * Use our short-lived pool for bucket_alloc so that we can simply move
+ * buckets and use them after the backend connection is released.
+ */
+static request_rec *create_request_rec(apr_pool_t *p, server_rec *s,
+ proxy_balancer *balancer,
+ const char *method,
+ const char *protocol)
+{
+ request_rec *r;
+
+ r = apr_pcalloc(p, sizeof(request_rec));
+ r->pool = p;
+ r->server = s;
+
+ r->per_dir_config = r->server->lookup_defaults;
+ if (balancer->section_config) {
+ r->per_dir_config = ap_merge_per_dir_configs(r->pool,
+ r->per_dir_config,
+ balancer->section_config);
+ }
+
+ r->proxyreq = PROXYREQ_RESPONSE;
+
+ r->user = NULL;
+ r->ap_auth_type = NULL;
+
+ r->allowed_methods = ap_make_method_list(p, 2);
+
+ r->headers_in = apr_table_make(r->pool, 1);
+ r->trailers_in = apr_table_make(r->pool, 1);
+ r->subprocess_env = apr_table_make(r->pool, 25);
+ r->headers_out = apr_table_make(r->pool, 12);
+ r->err_headers_out = apr_table_make(r->pool, 5);
+ r->trailers_out = apr_table_make(r->pool, 1);
+ r->notes = apr_table_make(r->pool, 5);
+
+ r->request_config = ap_create_request_config(r->pool);
+ /* Must be set before we run create request hook */
+
+ r->sent_bodyct = 0; /* bytect isn't for body */
+
+ r->read_length = 0;
+ r->read_body = REQUEST_NO_BODY;
+
+ r->status = HTTP_OK; /* Until further notice */
+ r->the_request = NULL;
+
+ /* Begin by presuming any module can make its own path_info assumptions,
+ * until some module interjects and changes the value.
+ */
+ r->used_path_info = AP_REQ_DEFAULT_PATH_INFO;
+
+
+ /* Time to populate r with the data we have. */
+ r->method = method;
+ /* Provide quick information about the request method as soon as known */
+ r->method_number = ap_method_number_of(r->method);
+ if (r->method_number == M_OPTIONS
+ || (r->method_number == M_GET && r->method[0] == 'H')) {
+ r->header_only = 1;
+ }
+ else {
+ r->header_only = 0;
+ }
+ r->protocol = "HTTP/1.0";
+ r->proto_num = HTTP_VERSION(1, 0);
+ if ( protocol && (protocol[7] == '1') ) {
+ r->protocol = "HTTP/1.1";
+ r->proto_num = HTTP_VERSION(1, 1);
+ }
+ r->hostname = NULL;
+
+ return r;
+}
+
+static void set_request_connection(request_rec *r, conn_rec *conn)
+{
+ conn->bucket_alloc = apr_bucket_alloc_create(r->pool);
+ r->connection = conn;
+
+ r->kept_body = apr_brigade_create(r->pool, conn->bucket_alloc);
+ r->output_filters = r->proto_output_filters = conn->output_filters;
+ r->input_filters = r->proto_input_filters = conn->input_filters;
+
+ r->useragent_addr = conn->client_addr;
+ r->useragent_ip = conn->client_ip;
+}
+
+static void create_hcheck_req(wctx_t *wctx, proxy_worker *hc,
+ apr_pool_t *p)
+{
+ char *req = NULL;
+ const char *method = NULL;
+ const char *protocol = NULL;
+
+ /* TODO: Fold into switch/case below? This seems more obvious */
+ if ( (hc->s->method == OPTIONS11) || (hc->s->method == HEAD11) || (hc->s->method == GET11) ) {
+ protocol = "HTTP/1.1";
+ } else {
+ protocol = "HTTP/1.0";
+ }
+ switch (hc->s->method) {
+ case OPTIONS:
+ case OPTIONS11:
+ method = "OPTIONS";
+ req = apr_psprintf(p,
+ "OPTIONS * %s\r\n"
+ "Host: %s:%d\r\n"
+ "\r\n", protocol,
+ hc->s->hostname_ex, (int)hc->s->port);
+ break;
+
+ case HEAD:
+ case HEAD11:
+ method = "HEAD";
+ /* fallthru */
+ case GET:
+ case GET11:
+ if (!method) { /* did we fall thru? If not, we are GET */
+ method = "GET";
+ }
+ req = apr_psprintf(p,
+ "%s %s%s%s %s\r\n"
+ "Host: %s:%d\r\n"
+ "\r\n",
+ method,
+ (wctx->path ? wctx->path : ""),
+ (wctx->path && *hc->s->hcuri ? "/" : "" ),
+ (*hc->s->hcuri ? hc->s->hcuri : ""),
+ protocol,
+ hc->s->hostname_ex, (int)hc->s->port);
+ break;
+
+ default:
+ break;
+ }
+ wctx->req = req;
+ wctx->method = method;
+ wctx->protocol = protocol;
+}
+
+static proxy_worker *hc_get_hcworker(sctx_t *ctx, proxy_worker *worker,
+ apr_pool_t *p)
+{
+ proxy_worker *hc = NULL;
+ apr_port_t port;
+
+ hc = (proxy_worker *)apr_hash_get(ctx->hcworkers, &worker, sizeof worker);
+ if (!hc) {
+ apr_uri_t uri;
+ apr_status_t rv;
+ const char *url = worker->s->name_ex;
+ wctx_t *wctx = apr_pcalloc(ctx->p, sizeof(wctx_t));
+
+ port = (worker->s->port ? worker->s->port
+ : ap_proxy_port_of_scheme(worker->s->scheme));
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ctx->s, APLOGNO(03248)
+ "Creating hc worker %pp for %s://%s:%d",
+ worker, worker->s->scheme, worker->s->hostname_ex,
+ (int)port);
+
+ ap_proxy_define_worker(ctx->p, &hc, NULL, NULL, worker->s->name_ex, 0);
+ apr_snprintf(hc->s->name, sizeof hc->s->name, "%pp", worker);
+ apr_snprintf(hc->s->name_ex, sizeof hc->s->name_ex, "%pp", worker);
+ PROXY_STRNCPY(hc->s->hostname, worker->s->hostname); /* for compatibility */
+ PROXY_STRNCPY(hc->s->hostname_ex, worker->s->hostname_ex);
+ PROXY_STRNCPY(hc->s->scheme, worker->s->scheme);
+ PROXY_STRNCPY(hc->s->hcuri, worker->s->hcuri);
+ PROXY_STRNCPY(hc->s->hcexpr, worker->s->hcexpr);
+ hc->hash.def = hc->s->hash.def = ap_proxy_hashfunc(hc->s->name_ex,
+ PROXY_HASHFUNC_DEFAULT);
+ hc->hash.fnv = hc->s->hash.fnv = ap_proxy_hashfunc(hc->s->name_ex,
+ PROXY_HASHFUNC_FNV);
+ hc->s->port = port;
+ hc->s->conn_timeout_set = worker->s->conn_timeout_set;
+ hc->s->conn_timeout = worker->s->conn_timeout;
+ hc->s->ping_timeout_set = worker->s->ping_timeout_set;
+ hc->s->ping_timeout = worker->s->ping_timeout;
+ hc->s->timeout_set = worker->s->timeout_set;
+ hc->s->timeout = worker->s->timeout;
+ /* Do not disable worker in case of errors */
+ hc->s->status |= PROXY_WORKER_IGNORE_ERRORS;
+ /* Mark as the "generic" worker */
+ hc->s->status |= PROXY_WORKER_GENERIC;
+ ap_proxy_initialize_worker(hc, ctx->s, ctx->p);
+ hc->s->is_address_reusable = worker->s->is_address_reusable;
+ hc->s->disablereuse = worker->s->disablereuse;
+ hc->s->method = worker->s->method;
+ rv = apr_uri_parse(p, url, &uri);
+ if (rv == APR_SUCCESS) {
+ wctx->path = apr_pstrdup(ctx->p, uri.path);
+ }
+ wctx->w = worker;
+ create_hcheck_req(wctx, hc, ctx->p);
+ hc->context = wctx;
+ apr_hash_set(ctx->hcworkers, &worker, sizeof worker, hc);
+ }
+ /* This *could* have changed via the Balancer Manager */
+ /* TODO */
+ if (hc->s->method != worker->s->method) {
+ wctx_t *wctx = hc->context;
+ port = (worker->s->port ? worker->s->port
+ : ap_proxy_port_of_scheme(worker->s->scheme));
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ctx->s, APLOGNO(03311)
+ "Updating hc worker %pp for %s://%s:%d",
+ worker, worker->s->scheme, worker->s->hostname_ex,
+ (int)port);
+ hc->s->method = worker->s->method;
+ create_hcheck_req(wctx, hc, ctx->p);
+ }
+ return hc;
+}
+
+static int hc_determine_connection(sctx_t *ctx, proxy_worker *worker,
+ apr_sockaddr_t **addr, apr_pool_t *p)
+{
+ apr_status_t rv = APR_SUCCESS;
+ /*
+ * normally, this is done in ap_proxy_determine_connection().
+ * TODO: Look at using ap_proxy_determine_connection() with a
+ * fake request_rec
+ */
+ if (worker->cp->addr) {
+ *addr = worker->cp->addr;
+ }
+ else {
+ rv = apr_sockaddr_info_get(addr, worker->s->hostname_ex,
+ APR_UNSPEC, worker->s->port, 0, p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ctx->s, APLOGNO(03249)
+ "DNS lookup failure for: %s:%d",
+ worker->s->hostname_ex, (int)worker->s->port);
+ }
+ }
+ return (rv == APR_SUCCESS ? OK : !OK);
+}
+
+static apr_status_t hc_init_worker(sctx_t *ctx, proxy_worker *worker)
+{
+ apr_status_t rv = APR_SUCCESS;
+ /*
+ * Since this is the watchdog, workers never actually handle a
+ * request here, and so the local data isn't initialized (of
+ * course, the shared memory is). So we need to bootstrap
+ * worker->cp. Note, we only need do this once.
+ */
+ if (!worker->cp) {
+ rv = ap_proxy_initialize_worker(worker, ctx->s, ctx->p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ctx->s, APLOGNO(03250) "Cannot init worker");
+ return rv;
+ }
+ if (worker->s->is_address_reusable && !worker->s->disablereuse &&
+ hc_determine_connection(ctx, worker, &worker->cp->addr,
+ worker->cp->pool) != OK) {
+ rv = APR_EGENERAL;
+ }
+ }
+ return rv;
+}
+
+static apr_status_t backend_cleanup(const char *proxy_function, proxy_conn_rec *backend,
+ server_rec *s, int status)
+{
+ if (backend) {
+ backend->close = 1;
+ ap_proxy_release_connection(proxy_function, backend, s);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03251)
+ "Health check %s Status (%d) for %s.",
+ ap_proxy_show_hcmethod(backend->worker->s->method),
+ status,
+ backend->worker->s->name_ex);
+ }
+ if (status != OK) {
+ return APR_EGENERAL;
+ }
+ return APR_SUCCESS;
+}
+
+static int hc_get_backend(const char *proxy_function, proxy_conn_rec **backend,
+ proxy_worker *hc, sctx_t *ctx, apr_pool_t *ptemp)
+{
+ int status;
+ status = ap_proxy_acquire_connection(proxy_function, backend, hc, ctx->s);
+ if (status == OK) {
+ (*backend)->addr = hc->cp->addr;
+ (*backend)->hostname = hc->s->hostname_ex;
+ if (strcmp(hc->s->scheme, "https") == 0 || strcmp(hc->s->scheme, "wss") == 0 ) {
+ if (!ap_ssl_has_outgoing_handlers()) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ctx->s, APLOGNO(03252)
+ "mod_ssl not configured?");
+ return !OK;
+ }
+ (*backend)->is_ssl = 1;
+ }
+
+ }
+ return hc_determine_connection(ctx, hc, &(*backend)->addr, ptemp);
+}
+
+static apr_status_t hc_check_cping(baton_t *baton, apr_thread_t *thread)
+{
+ int status;
+ sctx_t *ctx = baton->ctx;
+ proxy_worker *hc = baton->hc;
+ proxy_conn_rec *backend = NULL;
+ apr_pool_t *ptemp = baton->ptemp;
+ request_rec *r;
+ apr_interval_time_t timeout;
+
+ if (!ajp_handle_cping_cpong) {
+ return APR_ENOTIMPL;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, baton->ctx->s, "HCCPING starting");
+ if ((status = hc_get_backend("HCCPING", &backend, hc, ctx, baton->ptemp)) != OK) {
+ return backend_cleanup("HCCPING", backend, ctx->s, status);
+ }
+ if ((status = ap_proxy_connect_backend("HCCPING", backend, hc, ctx->s)) != OK) {
+ return backend_cleanup("HCCPING", backend, ctx->s, status);
+ }
+ r = create_request_rec(ptemp, ctx->s, baton->balancer, "CPING", NULL);
+ if ((status = ap_proxy_connection_create_ex("HCCPING", backend, r)) != OK) {
+ return backend_cleanup("HCCPING", backend, ctx->s, status);
+ }
+ set_request_connection(r, backend->connection);
+ backend->connection->current_thread = thread;
+
+ if (hc->s->ping_timeout_set) {
+ timeout = hc->s->ping_timeout;
+ } else if ( hc->s->conn_timeout_set) {
+ timeout = hc->s->conn_timeout;
+ } else if ( hc->s->timeout_set) {
+ timeout = hc->s->timeout;
+ } else {
+ /* default to socket timeout */
+ apr_socket_timeout_get(backend->sock, &timeout);
+ }
+ status = ajp_handle_cping_cpong(backend->sock, r, timeout);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, baton->ctx->s, "HCCPING done %d", status);
+ return backend_cleanup("HCCPING", backend, ctx->s, status);
+}
+
+static apr_status_t hc_check_tcp(baton_t *baton)
+{
+ int status;
+ sctx_t *ctx = baton->ctx;
+ proxy_worker *hc = baton->hc;
+ proxy_conn_rec *backend = NULL;
+
+ status = hc_get_backend("HCTCP", &backend, hc, ctx, baton->ptemp);
+ if (status == OK) {
+ status = ap_proxy_connect_backend("HCTCP", backend, hc, ctx->s);
+ /* does an unconditional ap_proxy_is_socket_connected() */
+ }
+ return backend_cleanup("HCTCP", backend, ctx->s, status);
+}
+
+static int hc_send(request_rec *r, const char *out, apr_bucket_brigade *bb)
+{
+ apr_status_t rv;
+ conn_rec *c = r->connection;
+ apr_bucket_alloc_t *ba = c->bucket_alloc;
+ ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, r->server, "%s", out);
+ APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(out, strlen(out),
+ r->pool, ba));
+ APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_flush_create(ba));
+ rv = ap_pass_brigade(c->output_filters, bb);
+ apr_brigade_cleanup(bb);
+ return (rv) ? !OK : OK;
+}
+
+static int hc_read_headers(request_rec *r)
+{
+ char buffer[HUGE_STRING_LEN];
+ int len;
+ const char *ct;
+
+ len = ap_getline(buffer, sizeof(buffer), r, 1);
+ if (len <= 0) {
+ return !OK;
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, APLOGNO(03254)
+ "%.*s", len, buffer);
+ /* for the below, see ap_proxy_http_process_response() */
+ if (apr_date_checkmask(buffer, "HTTP/#.# ###*")) {
+ int major;
+ char keepchar;
+ int proxy_status = OK;
+ const char *proxy_status_line = NULL;
+
+ major = buffer[5] - '0';
+ if ((major != 1) || (len >= sizeof(buffer)-1)) {
+ return !OK;
+ }
+
+ keepchar = buffer[12];
+ buffer[12] = '\0';
+ proxy_status = atoi(&buffer[9]);
+ if (keepchar != '\0') {
+ buffer[12] = keepchar;
+ } else {
+ buffer[12] = ' ';
+ buffer[13] = '\0';
+ }
+ proxy_status_line = apr_pstrdup(r->pool, &buffer[9]);
+ r->status = proxy_status;
+ r->status_line = proxy_status_line;
+ } else {
+ return !OK;
+ }
+
+ /* OK, 1st line is OK... scarf in the headers */
+ while ((len = ap_getline(buffer, sizeof(buffer), r, 1)) > 0) {
+ char *value, *end;
+ ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, r->server, "%.*s",
+ len, buffer);
+ if (!(value = strchr(buffer, ':'))) {
+ return !OK;
+ }
+ *value = '\0';
+ ++value;
+ while (apr_isspace(*value))
+ ++value; /* Skip to start of value */
+ for (end = &value[strlen(value)-1]; end > value && apr_isspace(*end); --end)
+ *end = '\0';
+ apr_table_add(r->headers_out, buffer, value);
+ }
+
+ /* Set the Content-Type for the request if set */
+ if ((ct = apr_table_get(r->headers_out, "Content-Type")) != NULL)
+ ap_set_content_type(r, ct);
+
+ return OK;
+}
+
+static int hc_read_body(request_rec *r, apr_bucket_brigade *bb)
+{
+ apr_status_t rv = APR_SUCCESS;
+ int seen_eos = 0;
+
+ do {
+ apr_size_t len = HUGE_STRING_LEN;
+
+ apr_brigade_cleanup(bb);
+ rv = ap_get_brigade(r->proto_input_filters, bb, AP_MODE_READBYTES,
+ APR_BLOCK_READ, len);
+
+ if (rv != APR_SUCCESS) {
+ if (APR_STATUS_IS_EOF(rv)) {
+ rv = APR_SUCCESS;
+ break;
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, r->server, APLOGNO(03300)
+ "Error reading response body");
+ break;
+ }
+
+ while (!APR_BRIGADE_EMPTY(bb)) {
+ apr_bucket *bucket = APR_BRIGADE_FIRST(bb);
+ if (APR_BUCKET_IS_EOS(bucket)) {
+ seen_eos = 1;
+ break;
+ }
+ if (APR_BUCKET_IS_FLUSH(bucket)) {
+ apr_bucket_delete(bucket);
+ continue;
+ }
+ APR_BUCKET_REMOVE(bucket);
+ APR_BRIGADE_INSERT_TAIL(r->kept_body, bucket);
+ }
+ }
+ while (!seen_eos);
+ apr_brigade_cleanup(bb);
+ return (rv == APR_SUCCESS ? OK : !OK);
+}
+
+/*
+ * Send the HTTP OPTIONS, HEAD or GET request to the backend
+ * server associated w/ worker. If we have Conditions,
+ * then apply those to the resulting response, otherwise
+ * any status code 2xx or 3xx is considered "passing"
+ */
+static apr_status_t hc_check_http(baton_t *baton, apr_thread_t *thread)
+{
+ int status;
+ proxy_conn_rec *backend = NULL;
+ sctx_t *ctx = baton->ctx;
+ proxy_worker *hc = baton->hc;
+ proxy_worker *worker = baton->worker;
+ apr_pool_t *ptemp = baton->ptemp;
+ request_rec *r;
+ wctx_t *wctx;
+ hc_condition_t *cond;
+ apr_bucket_brigade *bb;
+
+ wctx = (wctx_t *)hc->context;
+ if (!wctx->req || !wctx->method) {
+ return APR_ENOTIMPL;
+ }
+
+ if ((status = hc_get_backend("HCOH", &backend, hc, ctx, ptemp)) != OK) {
+ return backend_cleanup("HCOH", backend, ctx->s, status);
+ }
+ if ((status = ap_proxy_connect_backend("HCOH", backend, hc, ctx->s)) != OK) {
+ return backend_cleanup("HCOH", backend, ctx->s, status);
+ }
+
+ r = create_request_rec(ptemp, ctx->s, baton->balancer, wctx->method, wctx->protocol);
+ if ((status = ap_proxy_connection_create_ex("HCOH", backend, r)) != OK) {
+ return backend_cleanup("HCOH", backend, ctx->s, status);
+ }
+ set_request_connection(r, backend->connection);
+ backend->connection->current_thread = thread;
+
+ bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+
+ if ((status = hc_send(r, wctx->req, bb)) != OK) {
+ return backend_cleanup("HCOH", backend, ctx->s, status);
+ }
+ if ((status = hc_read_headers(r)) != OK) {
+ return backend_cleanup("HCOH", backend, ctx->s, status);
+ }
+ if (!r->header_only) {
+ apr_table_t *saved_headers_in = r->headers_in;
+ r->headers_in = apr_table_copy(r->pool, r->headers_out);
+ ap_proxy_pre_http_request(backend->connection, r);
+ status = hc_read_body(r, bb);
+ r->headers_in = saved_headers_in;
+ if (status != OK) {
+ return backend_cleanup("HCOH", backend, ctx->s, status);
+ }
+ r->trailers_out = apr_table_copy(r->pool, r->trailers_in);
+ }
+
+ if (*worker->s->hcexpr &&
+ (cond = (hc_condition_t *)apr_table_get(ctx->conditions, worker->s->hcexpr)) != NULL) {
+ const char *err;
+ int ok = ap_expr_exec(r, cond->pexpr, &err);
+ if (ok > 0) {
+ ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, ctx->s,
+ "Condition %s for %s (%s): passed", worker->s->hcexpr,
+ hc->s->name_ex, worker->s->name_ex);
+ } else if (ok < 0 || err) {
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, ctx->s, APLOGNO(03301)
+ "Error on checking condition %s for %s (%s): %s", worker->s->hcexpr,
+ hc->s->name_ex, worker->s->name_ex, err);
+ status = !OK;
+ } else {
+ ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, ctx->s,
+ "Condition %s for %s (%s) : failed", worker->s->hcexpr,
+ hc->s->name_ex, worker->s->name_ex);
+ status = !OK;
+ }
+ } else if (r->status < 200 || r->status > 399) {
+ ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, ctx->s,
+ "Response status %i for %s (%s): failed", r->status,
+ hc->s->name_ex, worker->s->name_ex);
+ status = !OK;
+ }
+ return backend_cleanup("HCOH", backend, ctx->s, status);
+}
+
+static void * APR_THREAD_FUNC hc_check(apr_thread_t *thread, void *b)
+{
+ baton_t *baton = (baton_t *)b;
+ server_rec *s = baton->ctx->s;
+ proxy_worker *worker = baton->worker;
+ proxy_worker *hc = baton->hc;
+ apr_time_t now;
+ apr_status_t rv;
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03256)
+ "%sHealth checking %s", (thread ? "Threaded " : ""),
+ worker->s->name_ex);
+
+ if (hc->s->method == TCP) {
+ rv = hc_check_tcp(baton);
+ }
+ else if (hc->s->method == CPING) {
+ rv = hc_check_cping(baton, thread);
+ }
+ else {
+ rv = hc_check_http(baton, thread);
+ }
+
+ now = apr_time_now();
+ if (rv == APR_ENOTIMPL) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(03257)
+ "Somehow tried to use unimplemented hcheck method: %d",
+ (int)hc->s->method);
+ }
+ /* what state are we in ? */
+ else if (PROXY_WORKER_IS_HCFAILED(worker) || PROXY_WORKER_IS_ERROR(worker)) {
+ if (rv == APR_SUCCESS) {
+ worker->s->pcount += 1;
+ if (worker->s->pcount >= worker->s->passes) {
+ ap_proxy_set_wstatus(PROXY_WORKER_HC_FAIL_FLAG, 0, worker);
+ ap_proxy_set_wstatus(PROXY_WORKER_IN_ERROR_FLAG, 0, worker);
+ worker->s->pcount = 0;
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(03302)
+ "%sHealth check ENABLING %s", (thread ? "Threaded " : ""),
+ worker->s->name_ex);
+
+ }
+ }
+ }
+ else {
+ if (rv != APR_SUCCESS) {
+ worker->s->error_time = now;
+ worker->s->fcount += 1;
+ if (worker->s->fcount >= worker->s->fails) {
+ ap_proxy_set_wstatus(PROXY_WORKER_HC_FAIL_FLAG, 1, worker);
+ worker->s->fcount = 0;
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(03303)
+ "%sHealth check DISABLING %s", (thread ? "Threaded " : ""),
+ worker->s->name_ex);
+ }
+ }
+ }
+ if (baton->now) {
+ *baton->now = now;
+ }
+ apr_pool_destroy(baton->ptemp);
+ worker->s->updated = now;
+
+ return NULL;
+}
+
+static apr_status_t hc_watchdog_callback(int state, void *data,
+ apr_pool_t *pool)
+{
+ apr_status_t rv = APR_SUCCESS;
+ proxy_balancer *balancer;
+ sctx_t *ctx = (sctx_t *)data;
+ server_rec *s = ctx->s;
+ proxy_server_conf *conf;
+
+ switch (state) {
+ case AP_WATCHDOG_STATE_STARTING:
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03258)
+ "%s watchdog started.",
+ HCHECK_WATHCHDOG_NAME);
+#if HC_USE_THREADS
+ if (tpsize && hctp == NULL) {
+ rv = apr_thread_pool_create(&hctp, tpsize,
+ tpsize, ctx->p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_INFO, rv, s, APLOGNO(03312)
+ "apr_thread_pool_create() with %d threads failed",
+ tpsize);
+ /* we can continue on without the threadpools */
+ hctp = NULL;
+ } else {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(03313)
+ "apr_thread_pool_create() with %d threads succeeded",
+ tpsize);
+ }
+ } else {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(03314)
+ "Skipping apr_thread_pool_create()");
+ hctp = NULL;
+ }
+#endif
+ break;
+
+ case AP_WATCHDOG_STATE_RUNNING:
+ /* loop thru all workers */
+ if (s) {
+ int i;
+ conf = (proxy_server_conf *) ap_get_module_config(s->module_config, &proxy_module);
+ balancer = (proxy_balancer *)conf->balancers->elts;
+ ctx->s = s;
+ for (i = 0; i < conf->balancers->nelts; i++, balancer++) {
+ int n;
+ apr_time_t now;
+ proxy_worker **workers;
+ proxy_worker *worker;
+ /* Have any new balancers or workers been added dynamically? */
+ ap_proxy_sync_balancer(balancer, s, conf);
+ workers = (proxy_worker **)balancer->workers->elts;
+ now = apr_time_now();
+ for (n = 0; n < balancer->workers->nelts; n++) {
+ worker = *workers;
+ if (!PROXY_WORKER_IS(worker, PROXY_WORKER_STOPPED) &&
+ (worker->s->method != NONE) &&
+ (worker->s->updated != 0) &&
+ (now > worker->s->updated + worker->s->interval)) {
+ baton_t *baton;
+ apr_pool_t *ptemp;
+
+ ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s,
+ "Checking %s worker: %s [%d] (%pp)", balancer->s->name,
+ worker->s->name_ex, worker->s->method, worker);
+
+ if ((rv = hc_init_worker(ctx, worker)) != APR_SUCCESS) {
+ worker->s->updated = now;
+ return rv;
+ }
+ worker->s->updated = 0;
+
+ /* This pool has the lifetime of the check */
+ apr_pool_create(&ptemp, ctx->p);
+ apr_pool_tag(ptemp, "hc_request");
+ baton = apr_pcalloc(ptemp, sizeof(baton_t));
+ baton->ctx = ctx;
+ baton->balancer = balancer;
+ baton->worker = worker;
+ baton->ptemp = ptemp;
+ baton->hc = hc_get_hcworker(ctx, worker, ptemp);
+#if HC_USE_THREADS
+ if (hctp) {
+ apr_thread_pool_push(hctp, hc_check, (void *)baton,
+ APR_THREAD_TASK_PRIORITY_NORMAL,
+ NULL);
+ }
+ else
+#endif
+ {
+ baton->now = &now;
+ hc_check(NULL, baton);
+ }
+ }
+ workers++;
+ }
+ }
+ }
+ break;
+
+ case AP_WATCHDOG_STATE_STOPPING:
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03261)
+ "stopping %s watchdog.",
+ HCHECK_WATHCHDOG_NAME);
+#if HC_USE_THREADS
+ if (hctp) {
+ rv = apr_thread_pool_destroy(hctp);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_INFO, rv, s, APLOGNO(03315)
+ "apr_thread_pool_destroy() failed");
+ }
+ hctp = NULL;
+ }
+#endif
+ break;
+ }
+ return rv;
+}
+static int hc_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp)
+{
+#if HC_USE_THREADS
+ hctp = NULL;
+ tpsize = HC_THREADPOOL_SIZE;
+#endif
+
+ ajp_handle_cping_cpong = APR_RETRIEVE_OPTIONAL_FN(ajp_handle_cping_cpong);
+ if (ajp_handle_cping_cpong) {
+ proxy_hcmethods_t *method = proxy_hcmethods;
+ for (; method->name; method++) {
+ if (method->method == CPING) {
+ method->implemented = 1;
+ break;
+ }
+ }
+ }
+
+ return OK;
+}
+static int hc_post_config(apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *main_s)
+{
+ apr_status_t rv;
+ server_rec *s = main_s;
+
+ APR_OPTIONAL_FN_TYPE(ap_watchdog_get_instance) *hc_watchdog_get_instance;
+ APR_OPTIONAL_FN_TYPE(ap_watchdog_register_callback) *hc_watchdog_register_callback;
+
+ if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) {
+ return OK;
+ }
+ hc_watchdog_get_instance = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_get_instance);
+ hc_watchdog_register_callback = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_register_callback);
+ if (!hc_watchdog_get_instance || !hc_watchdog_register_callback) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s, APLOGNO(03262)
+ "mod_watchdog is required");
+ return !OK;
+ }
+ rv = hc_watchdog_get_instance(&watchdog,
+ HCHECK_WATHCHDOG_NAME,
+ 0, 1, p);
+ if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(03263)
+ "Failed to create watchdog instance (%s)",
+ HCHECK_WATHCHDOG_NAME);
+ return !OK;
+ }
+ while (s) {
+ sctx_t *ctx = ap_get_module_config(s->module_config,
+ &proxy_hcheck_module);
+
+ if (s != ctx->s) {
+ ap_log_error(APLOG_MARK, APLOG_TRACE4, 0, s, APLOGNO(10019)
+ "Missing unique per-server context: %s (%pp:%pp) (no hchecks)",
+ s->server_hostname, s, ctx->s);
+ s = s->next;
+ continue;
+ }
+ rv = hc_watchdog_register_callback(watchdog,
+ AP_WD_TM_SLICE,
+ ctx,
+ hc_watchdog_callback);
+ if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(03264)
+ "Failed to register watchdog callback (%s)",
+ HCHECK_WATHCHDOG_NAME);
+ return !OK;
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03265)
+ "watchdog callback registered (%s for %s)", HCHECK_WATHCHDOG_NAME, s->server_hostname);
+ s = s->next;
+ }
+
+ return OK;
+}
+
+static void hc_show_exprs(request_rec *r)
+{
+ const apr_table_entry_t *elts;
+ const apr_array_header_t *hdr;
+ int i;
+ sctx_t *ctx = (sctx_t *) ap_get_module_config(r->server->module_config,
+ &proxy_hcheck_module);
+ if (!ctx)
+ return;
+ if (apr_is_empty_table(ctx->conditions))
+ return;
+
+ ap_rputs("\n\n<table>"
+ "<tr><th colspan='2'>Health check cond. expressions:</th></tr>\n"
+ "<tr><th>Expr name</th><th>Expression</th></tr>\n", r);
+
+ hdr = apr_table_elts(ctx->conditions);
+ elts = (const apr_table_entry_t *) hdr->elts;
+ for (i = 0; i < hdr->nelts; ++i) {
+ hc_condition_t *cond;
+ if (!elts[i].key) {
+ continue;
+ }
+ cond = (hc_condition_t *)elts[i].val;
+ ap_rprintf(r, "<tr><td>%s</td><td>%s</td></tr>\n",
+ ap_escape_html(r->pool, elts[i].key),
+ ap_escape_html(r->pool, cond->expr));
+ }
+ ap_rputs("</table><hr/>\n", r);
+}
+
+static void hc_select_exprs(request_rec *r, const char *expr)
+{
+ const apr_table_entry_t *elts;
+ const apr_array_header_t *hdr;
+ int i;
+ sctx_t *ctx = (sctx_t *) ap_get_module_config(r->server->module_config,
+ &proxy_hcheck_module);
+ if (!ctx)
+ return;
+ if (apr_is_empty_table(ctx->conditions))
+ return;
+
+ hdr = apr_table_elts(ctx->conditions);
+ elts = (const apr_table_entry_t *) hdr->elts;
+ for (i = 0; i < hdr->nelts; ++i) {
+ if (!elts[i].key) {
+ continue;
+ }
+ ap_rprintf(r, "<option value='%s' %s >%s</option>\n",
+ ap_escape_html(r->pool, elts[i].key),
+ (!strcmp(elts[i].key, expr)) ? "selected" : "",
+ ap_escape_html(r->pool, elts[i].key));
+ }
+}
+
+static int hc_valid_expr(request_rec *r, const char *expr)
+{
+ const apr_table_entry_t *elts;
+ const apr_array_header_t *hdr;
+ int i;
+ sctx_t *ctx = (sctx_t *) ap_get_module_config(r->server->module_config,
+ &proxy_hcheck_module);
+ if (!ctx)
+ return 0;
+ if (apr_is_empty_table(ctx->conditions))
+ return 0;
+
+ hdr = apr_table_elts(ctx->conditions);
+ elts = (const apr_table_entry_t *) hdr->elts;
+ for (i = 0; i < hdr->nelts; ++i) {
+ if (!elts[i].key) {
+ continue;
+ }
+ if (!strcmp(elts[i].key, expr))
+ return 1;
+ }
+ return 0;
+}
+
+static const char *hc_get_body(request_rec *r)
+{
+ apr_off_t length;
+ apr_size_t len;
+ apr_status_t rv;
+ char *buf;
+
+ if (!r || !r->kept_body)
+ return "";
+
+ rv = apr_brigade_length(r->kept_body, 1, &length);
+ len = (apr_size_t)length;
+ if (rv != APR_SUCCESS || len == 0)
+ return "";
+
+ buf = apr_palloc(r->pool, len + 1);
+ rv = apr_brigade_flatten(r->kept_body, buf, &len);
+ if (rv != APR_SUCCESS)
+ return "";
+ buf[len] = '\0'; /* ensure */
+ return (const char*)buf;
+}
+
+static const char *hc_expr_var_fn(ap_expr_eval_ctx_t *ctx, const void *data)
+{
+ char *var = (char *)data;
+
+ if (var && *var && ctx->r && ap_cstr_casecmp(var, "BODY") == 0) {
+ return hc_get_body(ctx->r);
+ }
+ return NULL;
+}
+
+static const char *hc_expr_func_fn(ap_expr_eval_ctx_t *ctx, const void *data,
+ const char *arg)
+{
+ char *var = (char *)arg;
+
+ if (var && *var && ctx->r && ap_cstr_casecmp(var, "BODY") == 0) {
+ return hc_get_body(ctx->r);
+ }
+ return NULL;
+}
+
+static int hc_expr_lookup(ap_expr_lookup_parms *parms)
+{
+ switch (parms->type) {
+ case AP_EXPR_FUNC_VAR:
+ /* for now, we just handle everything that starts with HC_.
+ */
+ if (strncasecmp(parms->name, "HC_", 3) == 0) {
+ *parms->func = hc_expr_var_fn;
+ *parms->data = parms->name + 3;
+ return OK;
+ }
+ break;
+ case AP_EXPR_FUNC_STRING:
+ /* Function HC() is implemented by us.
+ */
+ if (strcasecmp(parms->name, "HC") == 0) {
+ *parms->func = hc_expr_func_fn;
+ *parms->data = parms->arg;
+ return OK;
+ }
+ break;
+ }
+ return DECLINED;
+}
+
+static const command_rec command_table[] = {
+ AP_INIT_RAW_ARGS("ProxyHCTemplate", set_hc_template, NULL, OR_FILEINFO,
+ "Health check template"),
+ AP_INIT_RAW_ARGS("ProxyHCExpr", set_hc_condition, NULL, OR_FILEINFO,
+ "Define a health check condition ruleset expression"),
+#if HC_USE_THREADS
+ AP_INIT_TAKE1("ProxyHCTPsize", set_hc_tpsize, NULL, RSRC_CONF,
+ "Set size of health check thread pool"),
+#endif
+ { NULL }
+};
+
+static void hc_register_hooks(apr_pool_t *p)
+{
+ static const char *const aszPre[] = { "mod_proxy_balancer.c", "mod_proxy.c", NULL};
+ static const char *const aszSucc[] = { "mod_watchdog.c", NULL};
+ APR_REGISTER_OPTIONAL_FN(set_worker_hc_param);
+ APR_REGISTER_OPTIONAL_FN(hc_show_exprs);
+ APR_REGISTER_OPTIONAL_FN(hc_select_exprs);
+ APR_REGISTER_OPTIONAL_FN(hc_valid_expr);
+ ap_hook_pre_config(hc_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_post_config(hc_post_config, aszPre, aszSucc, APR_HOOK_LAST);
+ ap_hook_expr_lookup(hc_expr_lookup, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+/* the main config structure */
+
+AP_DECLARE_MODULE(proxy_hcheck) =
+{
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-dir config structures */
+ NULL, /* merge per-dir config structures */
+ hc_create_config, /* create per-server config structures */
+ NULL, /* merge per-server config structures */
+ command_table, /* table of config file commands */
+ hc_register_hooks /* register hooks */
+};
diff --git a/modules/proxy/mod_proxy_hcheck.dep b/modules/proxy/mod_proxy_hcheck.dep
new file mode 100644
index 0000000..008ee64
--- /dev/null
+++ b/modules/proxy/mod_proxy_hcheck.dep
@@ -0,0 +1,5 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_proxy_hcheck.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
diff --git a/modules/proxy/mod_proxy_hcheck.dsp b/modules/proxy/mod_proxy_hcheck.dsp
new file mode 100644
index 0000000..73aaf2b
--- /dev/null
+++ b/modules/proxy/mod_proxy_hcheck.dsp
@@ -0,0 +1,123 @@
+# Microsoft Developer Studio Project File - Name="mod_proxy_hcheck" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_proxy_hcheck - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_hcheck.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_hcheck.mak" CFG="mod_proxy_hcheck - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy_hcheck - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy_hcheck - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_proxy_hcheck - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../core" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_proxy_hcheck_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_proxy_hcheck.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_proxy_hcheck.so" /d LONG_NAME="proxy_hcheck_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:".\Release\mod_proxy_hcheck.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_hcheck.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_proxy_hcheck.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_hcheck.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_proxy_hcheck.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_proxy_hcheck - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../core" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_proxy_hcheck_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_proxy_hcheck.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_proxy_hcheck.so" /d LONG_NAME="proxy_hcheck_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_proxy_hcheck.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_hcheck.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_proxy_hcheck.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_hcheck.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_proxy_hcheck.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_proxy_hcheck - Win32 Release"
+# Name "mod_proxy_hcheck - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\mod_proxy_hcheck.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter ".h"
+# Begin Source File
+
+SOURCE=.\mod_proxy.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/proxy/mod_proxy_hcheck.mak b/modules/proxy/mod_proxy_hcheck.mak
new file mode 100644
index 0000000..dc0d758
--- /dev/null
+++ b/modules/proxy/mod_proxy_hcheck.mak
@@ -0,0 +1,380 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_proxy_hcheck.dsp
+!IF "$(CFG)" == ""
+CFG=mod_proxy_hcheck - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_proxy_hcheck - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_proxy_hcheck - Win32 Release" && "$(CFG)" != "mod_proxy_hcheck - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_hcheck.mak" CFG="mod_proxy_hcheck - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy_hcheck - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy_hcheck - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_hcheck - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_proxy_hcheck.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_proxy - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_proxy_hcheck.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_proxy - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_proxy_hcheck.obj"
+ -@erase "$(INTDIR)\mod_proxy_hcheck.res"
+ -@erase "$(INTDIR)\mod_proxy_hcheck_src.idb"
+ -@erase "$(INTDIR)\mod_proxy_hcheck_src.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_hcheck.exp"
+ -@erase "$(OUTDIR)\mod_proxy_hcheck.lib"
+ -@erase "$(OUTDIR)\mod_proxy_hcheck.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_hcheck.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../core" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_proxy_hcheck_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_proxy_hcheck.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_proxy_hcheck.so" /d LONG_NAME="proxy_hcheck_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_proxy_hcheck.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_proxy_hcheck.pdb" /debug /out:"$(OUTDIR)\mod_proxy_hcheck.so" /implib:"$(OUTDIR)\mod_proxy_hcheck.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_hcheck.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_proxy_hcheck.obj" \
+ "$(INTDIR)\mod_proxy_hcheck.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib" \
+ "$(OUTDIR)\mod_proxy.lib"
+
+"$(OUTDIR)\mod_proxy_hcheck.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_proxy_hcheck.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_proxy_hcheck.so"
+ if exist .\Release\mod_proxy_hcheck.so.manifest mt.exe -manifest .\Release\mod_proxy_hcheck.so.manifest -outputresource:.\Release\mod_proxy_hcheck.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_proxy_hcheck - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_proxy_hcheck.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_proxy - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_proxy_hcheck.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_proxy - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_proxy_hcheck.obj"
+ -@erase "$(INTDIR)\mod_proxy_hcheck.res"
+ -@erase "$(INTDIR)\mod_proxy_hcheck_src.idb"
+ -@erase "$(INTDIR)\mod_proxy_hcheck_src.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_hcheck.exp"
+ -@erase "$(OUTDIR)\mod_proxy_hcheck.lib"
+ -@erase "$(OUTDIR)\mod_proxy_hcheck.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_hcheck.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../core" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_proxy_hcheck_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_proxy_hcheck.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_proxy_hcheck.so" /d LONG_NAME="proxy_hcheck_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_proxy_hcheck.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_proxy_hcheck.pdb" /debug /out:"$(OUTDIR)\mod_proxy_hcheck.so" /implib:"$(OUTDIR)\mod_proxy_hcheck.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_hcheck.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_proxy_hcheck.obj" \
+ "$(INTDIR)\mod_proxy_hcheck.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib" \
+ "$(OUTDIR)\mod_proxy.lib"
+
+"$(OUTDIR)\mod_proxy_hcheck.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_proxy_hcheck.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_proxy_hcheck.so"
+ if exist .\Debug\mod_proxy_hcheck.so.manifest mt.exe -manifest .\Debug\mod_proxy_hcheck.so.manifest -outputresource:.\Debug\mod_proxy_hcheck.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_proxy_hcheck.dep")
+!INCLUDE "mod_proxy_hcheck.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_proxy_hcheck.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_proxy_hcheck - Win32 Release" || "$(CFG)" == "mod_proxy_hcheck - Win32 Debug"
+SOURCE=.\mod_proxy_hcheck.c
+
+"$(INTDIR)\mod_proxy_hcheck.obj" : $(SOURCE) "$(INTDIR)"
+
+
+!IF "$(CFG)" == "mod_proxy_hcheck - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F .\libapr.mak CFG="libapr - Win32 Release"
+ cd "..\..\modules\proxy"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F .\libapr.mak CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_hcheck - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F .\libapr.mak CFG="libapr - Win32 Debug"
+ cd "..\..\modules\proxy"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F .\libapr.mak CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_hcheck - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\proxy"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_hcheck - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\proxy"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_hcheck - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F .\libhttpd.mak CFG="libhttpd - Win32 Release"
+ cd ".\modules\proxy"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F .\libhttpd.mak CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_hcheck - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F .\libhttpd.mak CFG="libhttpd - Win32 Debug"
+ cd ".\modules\proxy"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F .\libhttpd.mak CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_hcheck - Win32 Release"
+
+"mod_proxy - Win32 Release" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F .\mod_proxy.mak CFG="mod_proxy - Win32 Release"
+ cd "."
+
+"mod_proxy - Win32 ReleaseCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F .\mod_proxy.mak CFG="mod_proxy - Win32 Release" RECURSE=1 CLEAN
+ cd "."
+
+!ELSEIF "$(CFG)" == "mod_proxy_hcheck - Win32 Debug"
+
+"mod_proxy - Win32 Debug" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F .\mod_proxy.mak CFG="mod_proxy - Win32 Debug"
+ cd "."
+
+"mod_proxy - Win32 DebugCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F .\mod_proxy.mak CFG="mod_proxy - Win32 Debug" RECURSE=1 CLEAN
+ cd "."
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_proxy_hcheck - Win32 Release"
+
+
+"$(INTDIR)\mod_proxy_hcheck.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_proxy_hcheck.res" /i "../../include" /i "../../srclib/apr/include" /i "\build6\hcheck\build\win32" /d "NDEBUG" /d BIN_NAME="mod_proxy_hcheck.so" /d LONG_NAME="proxy_hcheck_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_proxy_hcheck - Win32 Debug"
+
+
+"$(INTDIR)\mod_proxy_hcheck.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_proxy_hcheck.res" /i "../../include" /i "../../srclib/apr/include" /i "\build6\hcheck\build\win32" /d "_DEBUG" /d BIN_NAME="mod_proxy_hcheck.so" /d LONG_NAME="proxy_hcheck_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+
+!ENDIF
+
diff --git a/modules/proxy/mod_proxy_http.c b/modules/proxy/mod_proxy_http.c
new file mode 100644
index 0000000..51d19a0
--- /dev/null
+++ b/modules/proxy/mod_proxy_http.c
@@ -0,0 +1,2132 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* HTTP routines for Apache proxy */
+
+#include "mod_proxy.h"
+#include "ap_regex.h"
+
+module AP_MODULE_DECLARE_DATA proxy_http_module;
+
+static int (*ap_proxy_clear_connection_fn)(request_rec *r, apr_table_t *headers) =
+ NULL;
+
+static apr_status_t ap_proxy_http_cleanup(const char *scheme,
+ request_rec *r,
+ proxy_conn_rec *backend);
+
+static apr_status_t ap_proxygetline(apr_bucket_brigade *bb, char *s, int n,
+ request_rec *r, int flags, int *read);
+
+static const char *get_url_scheme(const char **url, int *is_ssl)
+{
+ const char *u = *url;
+
+ switch (u[0]) {
+ case 'h':
+ case 'H':
+ if (strncasecmp(u + 1, "ttp", 3) == 0) {
+ if (u[4] == ':') {
+ *is_ssl = 0;
+ *url = u + 5;
+ return "http";
+ }
+ if (apr_tolower(u[4]) == 's' && u[5] == ':') {
+ *is_ssl = 1;
+ *url = u + 6;
+ return "https";
+ }
+ }
+ break;
+
+ case 'w':
+ case 'W':
+ if (apr_tolower(u[1]) == 's') {
+ if (u[2] == ':') {
+ *is_ssl = 0;
+ *url = u + 3;
+ return "ws";
+ }
+ if (apr_tolower(u[2]) == 's' && u[3] == ':') {
+ *is_ssl = 1;
+ *url = u + 4;
+ return "wss";
+ }
+ }
+ break;
+ }
+
+ *is_ssl = 0;
+ return NULL;
+}
+
+/*
+ * Canonicalise http-like URLs.
+ * scheme is the scheme for the URL
+ * url is the URL starting with the first '/'
+ */
+static int proxy_http_canon(request_rec *r, char *url)
+{
+ const char *base_url = url;
+ char *host, *path, sport[7];
+ char *search = NULL;
+ const char *err;
+ const char *scheme;
+ apr_port_t port, def_port;
+ int is_ssl = 0;
+
+ scheme = get_url_scheme((const char **)&url, &is_ssl);
+ if (!scheme) {
+ return DECLINED;
+ }
+ port = def_port = (is_ssl) ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT;
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "HTTP: canonicalising URL %s", base_url);
+
+ /* do syntatic check.
+ * We break the URL into host, port, path, search
+ */
+ err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01083)
+ "error parsing URL %s: %s", base_url, err);
+ return HTTP_BAD_REQUEST;
+ }
+
+ /*
+ * now parse path/search args, according to rfc1738:
+ * process the path.
+ *
+ * In a reverse proxy, our URL has been processed, so canonicalise
+ * unless proxy-nocanon is set to say it's raw
+ * In a forward proxy, we have and MUST NOT MANGLE the original.
+ */
+ switch (r->proxyreq) {
+ default: /* wtf are we doing here? */
+ case PROXYREQ_REVERSE:
+ if (apr_table_get(r->notes, "proxy-nocanon")) {
+ path = url; /* this is the raw path */
+ }
+ else {
+ path = ap_proxy_canonenc(r->pool, url, strlen(url),
+ enc_path, 0, r->proxyreq);
+ search = r->args;
+ if (search && *(ap_scan_vchar_obstext(search))) {
+ /*
+ * We have a raw control character or a ' ' in r->args.
+ * Correct encoding was missed.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10408)
+ "To be forwarded query string contains control "
+ "characters or spaces");
+ return HTTP_FORBIDDEN;
+ }
+ }
+ break;
+ case PROXYREQ_PROXY:
+ path = url;
+ break;
+ }
+
+ if (path == NULL)
+ return HTTP_BAD_REQUEST;
+
+ if (port != def_port)
+ apr_snprintf(sport, sizeof(sport), ":%d", port);
+ else
+ sport[0] = '\0';
+
+ if (ap_strchr_c(host, ':')) { /* if literal IPv6 address */
+ host = apr_pstrcat(r->pool, "[", host, "]", NULL);
+ }
+
+ r->filename = apr_pstrcat(r->pool, "proxy:", scheme, "://", host, sport,
+ "/", path, (search) ? "?" : "", search, NULL);
+ return OK;
+}
+
+/* Clear all connection-based headers from the incoming headers table */
+typedef struct header_dptr {
+ apr_pool_t *pool;
+ apr_table_t *table;
+ apr_time_t time;
+} header_dptr;
+static ap_regex_t *warn_rx;
+static int clean_warning_headers(void *data, const char *key, const char *val)
+{
+ apr_table_t *headers = ((header_dptr*)data)->table;
+ apr_pool_t *pool = ((header_dptr*)data)->pool;
+ char *warning;
+ char *date;
+ apr_time_t warn_time;
+ const int nmatch = 3;
+ ap_regmatch_t pmatch[3];
+
+ if (headers == NULL) {
+ ((header_dptr*)data)->table = headers = apr_table_make(pool, 2);
+ }
+/*
+ * Parse this, suckers!
+ *
+ * Warning = "Warning" ":" 1#warning-value
+ *
+ * warning-value = warn-code SP warn-agent SP warn-text
+ * [SP warn-date]
+ *
+ * warn-code = 3DIGIT
+ * warn-agent = ( host [ ":" port ] ) | pseudonym
+ * ; the name or pseudonym of the server adding
+ * ; the Warning header, for use in debugging
+ * warn-text = quoted-string
+ * warn-date = <"> HTTP-date <">
+ *
+ * Buggrit, use a bloomin' regexp!
+ * (\d{3}\s+\S+\s+\".*?\"(\s+\"(.*?)\")?) --> whole in $1, date in $3
+ */
+ while (!ap_regexec(warn_rx, val, nmatch, pmatch, 0)) {
+ warning = apr_pstrndup(pool, val+pmatch[0].rm_so,
+ pmatch[0].rm_eo - pmatch[0].rm_so);
+ warn_time = 0;
+ if (pmatch[2].rm_eo > pmatch[2].rm_so) {
+ /* OK, we have a date here */
+ date = apr_pstrndup(pool, val+pmatch[2].rm_so,
+ pmatch[2].rm_eo - pmatch[2].rm_so);
+ warn_time = apr_date_parse_http(date);
+ }
+ if (!warn_time || (warn_time == ((header_dptr*)data)->time)) {
+ apr_table_addn(headers, key, warning);
+ }
+ val += pmatch[0].rm_eo;
+ }
+ return 1;
+}
+static apr_table_t *ap_proxy_clean_warnings(apr_pool_t *p, apr_table_t *headers)
+{
+ header_dptr x;
+ x.pool = p;
+ x.table = NULL;
+ x.time = apr_date_parse_http(apr_table_get(headers, "Date"));
+ apr_table_do(clean_warning_headers, &x, headers, "Warning", NULL);
+ if (x.table != NULL) {
+ apr_table_unset(headers, "Warning");
+ return apr_table_overlay(p, headers, x.table);
+ }
+ else {
+ return headers;
+ }
+}
+
+static void add_te_chunked(apr_pool_t *p,
+ apr_bucket_alloc_t *bucket_alloc,
+ apr_bucket_brigade *header_brigade)
+{
+ apr_bucket *e;
+ char *buf;
+ const char te_hdr[] = "Transfer-Encoding: chunked" CRLF;
+
+ buf = apr_pmemdup(p, te_hdr, sizeof(te_hdr)-1);
+ ap_xlate_proto_to_ascii(buf, sizeof(te_hdr)-1);
+
+ e = apr_bucket_pool_create(buf, sizeof(te_hdr)-1, p, bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(header_brigade, e);
+}
+
+static void add_cl(apr_pool_t *p,
+ apr_bucket_alloc_t *bucket_alloc,
+ apr_bucket_brigade *header_brigade,
+ const char *cl_val)
+{
+ apr_bucket *e;
+ char *buf;
+
+ buf = apr_pstrcat(p, "Content-Length: ",
+ cl_val,
+ CRLF,
+ NULL);
+ ap_xlate_proto_to_ascii(buf, strlen(buf));
+ e = apr_bucket_pool_create(buf, strlen(buf), p, bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(header_brigade, e);
+}
+
+#ifndef CRLF_ASCII
+#define CRLF_ASCII "\015\012"
+#endif
+#ifndef ZERO_ASCII
+#define ZERO_ASCII "\060"
+#endif
+
+#define MAX_MEM_SPOOL 16384
+
+typedef enum {
+ RB_INIT = 0,
+ RB_STREAM_CL,
+ RB_STREAM_CHUNKED,
+ RB_SPOOL_CL
+} rb_methods;
+
+typedef struct {
+ apr_pool_t *p;
+ request_rec *r;
+ const char *proto;
+ proxy_worker *worker;
+ proxy_server_conf *sconf;
+
+ char server_portstr[32];
+ proxy_conn_rec *backend;
+ conn_rec *origin;
+
+ apr_bucket_alloc_t *bucket_alloc;
+ apr_bucket_brigade *header_brigade;
+ apr_bucket_brigade *input_brigade;
+ char *old_cl_val, *old_te_val;
+ apr_off_t cl_val;
+
+ rb_methods rb_method;
+
+ const char *upgrade;
+
+ unsigned int do_100_continue :1,
+ prefetch_nonblocking :1,
+ force10 :1;
+} proxy_http_req_t;
+
+static int stream_reqbody(proxy_http_req_t *req)
+{
+ request_rec *r = req->r;
+ int seen_eos = 0, rv = OK;
+ apr_size_t hdr_len;
+ char chunk_hdr[20]; /* must be here due to transient bucket. */
+ conn_rec *origin = req->origin;
+ proxy_conn_rec *p_conn = req->backend;
+ apr_bucket_alloc_t *bucket_alloc = req->bucket_alloc;
+ apr_bucket_brigade *header_brigade = req->header_brigade;
+ apr_bucket_brigade *input_brigade = req->input_brigade;
+ rb_methods rb_method = req->rb_method;
+ apr_off_t bytes, bytes_streamed = 0;
+ apr_bucket *e;
+
+ do {
+ if (APR_BRIGADE_EMPTY(input_brigade)
+ && APR_BRIGADE_EMPTY(header_brigade)) {
+ rv = ap_proxy_read_input(r, p_conn, input_brigade,
+ HUGE_STRING_LEN);
+ if (rv != OK) {
+ return rv;
+ }
+ }
+
+ if (!APR_BRIGADE_EMPTY(input_brigade)) {
+ /* If this brigade contains EOS, remove it and be done. */
+ if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade))) {
+ seen_eos = 1;
+
+ /* We can't pass this EOS to the output_filters. */
+ e = APR_BRIGADE_LAST(input_brigade);
+ apr_bucket_delete(e);
+ }
+
+ apr_brigade_length(input_brigade, 1, &bytes);
+ bytes_streamed += bytes;
+
+ if (rb_method == RB_STREAM_CHUNKED) {
+ if (bytes) {
+ /*
+ * Prepend the size of the chunk
+ */
+ hdr_len = apr_snprintf(chunk_hdr, sizeof(chunk_hdr),
+ "%" APR_UINT64_T_HEX_FMT CRLF,
+ (apr_uint64_t)bytes);
+ ap_xlate_proto_to_ascii(chunk_hdr, hdr_len);
+ e = apr_bucket_transient_create(chunk_hdr, hdr_len,
+ bucket_alloc);
+ APR_BRIGADE_INSERT_HEAD(input_brigade, e);
+
+ /*
+ * Append the end-of-chunk CRLF
+ */
+ e = apr_bucket_immortal_create(CRLF_ASCII, 2, bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(input_brigade, e);
+ }
+ if (seen_eos) {
+ /*
+ * Append the tailing 0-size chunk
+ */
+ e = apr_bucket_immortal_create(ZERO_ASCII CRLF_ASCII
+ /* <trailers> */
+ CRLF_ASCII,
+ 5, bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(input_brigade, e);
+ }
+ }
+ else if (rb_method == RB_STREAM_CL
+ && (bytes_streamed > req->cl_val
+ || (seen_eos && bytes_streamed < req->cl_val))) {
+ /* C-L != bytes streamed?!?
+ *
+ * Prevent HTTP Request/Response Splitting.
+ *
+ * We can't stream more (or less) bytes at the back end since
+ * they could be interpreted in separate requests (more bytes
+ * now would start a new request, less bytes would make the
+ * first bytes of the next request be part of the current one).
+ *
+ * It can't happen from the client connection here thanks to
+ * ap_http_filter(), but some module's filter may be playing
+ * bad games, hence the HTTP_INTERNAL_SERVER_ERROR.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01086)
+ "read %s bytes of request body than expected "
+ "(got %" APR_OFF_T_FMT ", expected "
+ "%" APR_OFF_T_FMT ")",
+ bytes_streamed > req->cl_val ? "more" : "less",
+ bytes_streamed, req->cl_val);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (seen_eos && apr_table_get(r->subprocess_env,
+ "proxy-sendextracrlf")) {
+ e = apr_bucket_immortal_create(CRLF_ASCII, 2, bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(input_brigade, e);
+ }
+ }
+
+ /* If we never sent the header brigade, go ahead and take care of
+ * that now by prepending it (once only since header_brigade will be
+ * empty afterward).
+ */
+ APR_BRIGADE_PREPEND(input_brigade, header_brigade);
+
+ /* Flush here on EOS because we won't ap_proxy_read_input() again. */
+ rv = ap_proxy_pass_brigade(bucket_alloc, r, p_conn, origin,
+ input_brigade, seen_eos);
+ if (rv != OK) {
+ return rv;
+ }
+ } while (!seen_eos);
+
+ return OK;
+}
+
+static void terminate_headers(proxy_http_req_t *req)
+{
+ apr_bucket_alloc_t *bucket_alloc = req->bucket_alloc;
+ apr_bucket *e;
+ char *buf;
+
+ /*
+ * Handle Connection: header if we do HTTP/1.1 request:
+ * If we plan to close the backend connection sent Connection: close
+ * otherwise sent Connection: Keep-Alive.
+ */
+ if (!req->force10) {
+ if (req->upgrade) {
+ buf = apr_pstrdup(req->p, "Connection: Upgrade" CRLF);
+ ap_xlate_proto_to_ascii(buf, strlen(buf));
+ e = apr_bucket_pool_create(buf, strlen(buf), req->p, bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(req->header_brigade, e);
+
+ /* Tell the backend that it can upgrade the connection. */
+ buf = apr_pstrcat(req->p, "Upgrade: ", req->upgrade, CRLF, NULL);
+ }
+ else if (ap_proxy_connection_reusable(req->backend)) {
+ buf = apr_pstrdup(req->p, "Connection: Keep-Alive" CRLF);
+ }
+ else {
+ buf = apr_pstrdup(req->p, "Connection: close" CRLF);
+ }
+ ap_xlate_proto_to_ascii(buf, strlen(buf));
+ e = apr_bucket_pool_create(buf, strlen(buf), req->p, bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(req->header_brigade, e);
+ }
+
+ /* add empty line at the end of the headers */
+ e = apr_bucket_immortal_create(CRLF_ASCII, 2, bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(req->header_brigade, e);
+}
+
+static int ap_proxy_http_prefetch(proxy_http_req_t *req,
+ apr_uri_t *uri, char *url)
+{
+ apr_pool_t *p = req->p;
+ request_rec *r = req->r;
+ conn_rec *c = r->connection;
+ proxy_conn_rec *p_conn = req->backend;
+ apr_bucket_alloc_t *bucket_alloc = req->bucket_alloc;
+ apr_bucket_brigade *header_brigade = req->header_brigade;
+ apr_bucket_brigade *input_brigade = req->input_brigade;
+ apr_bucket *e;
+ apr_off_t bytes_read = 0;
+ apr_off_t bytes;
+ int rv;
+
+ rv = ap_proxy_create_hdrbrgd(p, header_brigade, r, p_conn,
+ req->worker, req->sconf,
+ uri, url, req->server_portstr,
+ &req->old_cl_val, &req->old_te_val);
+ if (rv != OK) {
+ return rv;
+ }
+
+ /* sub-requests never use keepalives, and mustn't pass request bodies.
+ * Because the new logic looks at input_brigade, we will self-terminate
+ * input_brigade and jump past all of the request body logic...
+ * Reading anything with ap_get_brigade is likely to consume the
+ * main request's body or read beyond EOS - which would be unpleasant.
+ *
+ * An exception: when a kept_body is present, then subrequest CAN use
+ * pass request bodies, and we DONT skip the body.
+ */
+ if (!r->kept_body && r->main) {
+ /* XXX: Why DON'T sub-requests use keepalives? */
+ p_conn->close = 1;
+ req->old_te_val = NULL;
+ req->old_cl_val = NULL;
+ req->rb_method = RB_STREAM_CL;
+ e = apr_bucket_eos_create(input_brigade->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(input_brigade, e);
+ goto skip_body;
+ }
+
+ /* WE only understand chunked. Other modules might inject
+ * (and therefore, decode) other flavors but we don't know
+ * that the can and have done so unless they remove
+ * their decoding from the headers_in T-E list.
+ * XXX: Make this extensible, but in doing so, presume the
+ * encoding has been done by the extensions' handler, and
+ * do not modify add_te_chunked's logic
+ */
+ if (req->old_te_val && ap_cstr_casecmp(req->old_te_val, "chunked") != 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01093)
+ "%s Transfer-Encoding is not supported",
+ req->old_te_val);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (req->old_cl_val && req->old_te_val) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01094)
+ "client %s (%s) requested Transfer-Encoding "
+ "chunked body with Content-Length (C-L ignored)",
+ c->client_ip, c->remote_host ? c->remote_host: "");
+ req->old_cl_val = NULL;
+ p_conn->close = 1;
+ }
+
+ rv = ap_proxy_prefetch_input(r, req->backend, input_brigade,
+ req->prefetch_nonblocking ? APR_NONBLOCK_READ
+ : APR_BLOCK_READ,
+ &bytes_read, MAX_MEM_SPOOL);
+ if (rv != OK) {
+ return rv;
+ }
+
+ /* Use chunked request body encoding or send a content-length body?
+ *
+ * Prefer C-L when:
+ *
+ * We have no request body (handled by RB_STREAM_CL)
+ *
+ * We have a request body length <= MAX_MEM_SPOOL
+ *
+ * The administrator has setenv force-proxy-request-1.0
+ *
+ * The client sent a C-L body, and the administrator has
+ * not setenv proxy-sendchunked or has set setenv proxy-sendcl
+ *
+ * The client sent a T-E body, and the administrator has
+ * setenv proxy-sendcl, and not setenv proxy-sendchunked
+ *
+ * If both proxy-sendcl and proxy-sendchunked are set, the
+ * behavior is the same as if neither were set, large bodies
+ * that can't be read will be forwarded in their original
+ * form of C-L, or T-E.
+ *
+ * To ensure maximum compatibility, setenv proxy-sendcl
+ * To reduce server resource use, setenv proxy-sendchunked
+ *
+ * Then address specific servers with conditional setenv
+ * options to restore the default behavior where desirable.
+ *
+ * We have to compute content length by reading the entire request
+ * body; if request body is not small, we'll spool the remaining
+ * input to a temporary file. Chunked is always preferable.
+ *
+ * We can only trust the client-provided C-L if the T-E header
+ * is absent, and the filters are unchanged (the body won't
+ * be resized by another content filter).
+ */
+ if (!APR_BRIGADE_EMPTY(input_brigade)
+ && APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade))) {
+ /* The whole thing fit, so our decision is trivial, use
+ * the filtered bytes read from the client for the request
+ * body Content-Length.
+ *
+ * If we expected no body, and read no body, do not set
+ * the Content-Length.
+ */
+ if (req->old_cl_val || req->old_te_val || bytes_read) {
+ req->old_cl_val = apr_off_t_toa(r->pool, bytes_read);
+ req->cl_val = bytes_read;
+ }
+ req->rb_method = RB_STREAM_CL;
+ }
+ else if (req->old_te_val) {
+ if (req->force10
+ || (apr_table_get(r->subprocess_env, "proxy-sendcl")
+ && !apr_table_get(r->subprocess_env, "proxy-sendchunks")
+ && !apr_table_get(r->subprocess_env, "proxy-sendchunked"))) {
+ req->rb_method = RB_SPOOL_CL;
+ }
+ else {
+ req->rb_method = RB_STREAM_CHUNKED;
+ }
+ }
+ else if (req->old_cl_val) {
+ if (r->input_filters == r->proto_input_filters) {
+ if (!ap_parse_strict_length(&req->cl_val, req->old_cl_val)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01085)
+ "could not parse request Content-Length (%s)",
+ req->old_cl_val);
+ return HTTP_BAD_REQUEST;
+ }
+ req->rb_method = RB_STREAM_CL;
+ }
+ else if (!req->force10
+ && (apr_table_get(r->subprocess_env, "proxy-sendchunks")
+ || apr_table_get(r->subprocess_env, "proxy-sendchunked"))
+ && !apr_table_get(r->subprocess_env, "proxy-sendcl")) {
+ req->rb_method = RB_STREAM_CHUNKED;
+ }
+ else {
+ req->rb_method = RB_SPOOL_CL;
+ }
+ }
+ else {
+ /* This is an appropriate default; very efficient for no-body
+ * requests, and has the behavior that it will not add any C-L
+ * when the old_cl_val is NULL.
+ */
+ req->rb_method = RB_SPOOL_CL;
+ }
+
+ switch (req->rb_method) {
+ case RB_STREAM_CHUNKED:
+ add_te_chunked(req->p, bucket_alloc, header_brigade);
+ break;
+
+ case RB_STREAM_CL:
+ if (req->old_cl_val) {
+ add_cl(req->p, bucket_alloc, header_brigade, req->old_cl_val);
+ }
+ break;
+
+ default: /* => RB_SPOOL_CL */
+ /* If we have to spool the body, do it now, before connecting or
+ * reusing the backend connection.
+ */
+ rv = ap_proxy_spool_input(r, p_conn, input_brigade,
+ &bytes, MAX_MEM_SPOOL);
+ if (rv != OK) {
+ return rv;
+ }
+ if (bytes || req->old_te_val || req->old_cl_val) {
+ add_cl(p, bucket_alloc, header_brigade, apr_off_t_toa(p, bytes));
+ }
+ }
+
+/* Yes I hate gotos. This is the subrequest shortcut */
+skip_body:
+ terminate_headers(req);
+
+ return OK;
+}
+
+static int ap_proxy_http_request(proxy_http_req_t *req)
+{
+ int rv;
+ request_rec *r = req->r;
+
+ /* send the request header/body, if any. */
+ switch (req->rb_method) {
+ case RB_SPOOL_CL:
+ case RB_STREAM_CL:
+ case RB_STREAM_CHUNKED:
+ if (req->do_100_continue) {
+ rv = ap_proxy_pass_brigade(req->bucket_alloc, r, req->backend,
+ req->origin, req->header_brigade, 1);
+ }
+ else {
+ rv = stream_reqbody(req);
+ }
+ break;
+
+ default:
+ /* shouldn't be possible */
+ rv = HTTP_INTERNAL_SERVER_ERROR;
+ break;
+ }
+
+ if (rv != OK) {
+ conn_rec *c = r->connection;
+ /* apr_status_t value has been logged in lower level method */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01097)
+ "pass request body failed to %pI (%s) from %s (%s)",
+ req->backend->addr,
+ req->backend->hostname ? req->backend->hostname: "",
+ c->client_ip, c->remote_host ? c->remote_host: "");
+ return rv;
+ }
+
+ return OK;
+}
+
+/*
+ * If the date is a valid RFC 850 date or asctime() date, then it
+ * is converted to the RFC 1123 format.
+ */
+static const char *date_canon(apr_pool_t *p, const char *date)
+{
+ apr_status_t rv;
+ char* ndate;
+
+ apr_time_t time = apr_date_parse_http(date);
+ if (!time) {
+ return date;
+ }
+
+ ndate = apr_palloc(p, APR_RFC822_DATE_LEN);
+ rv = apr_rfc822_date(ndate, time);
+ if (rv != APR_SUCCESS) {
+ return date;
+ }
+
+ return ndate;
+}
+
+static request_rec *make_fake_req(conn_rec *c, request_rec *r)
+{
+ apr_pool_t *pool;
+ request_rec *rp;
+
+ apr_pool_create(&pool, c->pool);
+ apr_pool_tag(pool, "proxy_http_rp");
+
+ rp = apr_pcalloc(pool, sizeof(*r));
+
+ rp->pool = pool;
+ rp->status = HTTP_OK;
+
+ rp->headers_in = apr_table_make(pool, 50);
+ rp->trailers_in = apr_table_make(pool, 5);
+
+ rp->subprocess_env = apr_table_make(pool, 50);
+ rp->headers_out = apr_table_make(pool, 12);
+ rp->trailers_out = apr_table_make(pool, 5);
+ rp->err_headers_out = apr_table_make(pool, 5);
+ rp->notes = apr_table_make(pool, 5);
+
+ rp->server = r->server;
+ rp->log = r->log;
+ rp->proxyreq = r->proxyreq;
+ rp->request_time = r->request_time;
+ rp->connection = c;
+ rp->output_filters = c->output_filters;
+ rp->input_filters = c->input_filters;
+ rp->proto_output_filters = c->output_filters;
+ rp->proto_input_filters = c->input_filters;
+ rp->useragent_ip = c->client_ip;
+ rp->useragent_addr = c->client_addr;
+
+ rp->request_config = ap_create_request_config(pool);
+ proxy_run_create_req(r, rp);
+
+ return rp;
+}
+
+static void process_proxy_header(request_rec *r, proxy_dir_conf *c,
+ const char *key, const char *value)
+{
+ static const char *date_hdrs[]
+ = { "Date", "Expires", "Last-Modified", NULL };
+ static const struct {
+ const char *name;
+ ap_proxy_header_reverse_map_fn func;
+ } transform_hdrs[] = {
+ { "Location", ap_proxy_location_reverse_map },
+ { "Content-Location", ap_proxy_location_reverse_map },
+ { "URI", ap_proxy_location_reverse_map },
+ { "Destination", ap_proxy_location_reverse_map },
+ { "Set-Cookie", ap_proxy_cookie_reverse_map },
+ { NULL, NULL }
+ };
+ int i;
+ for (i = 0; date_hdrs[i]; ++i) {
+ if (!ap_cstr_casecmp(date_hdrs[i], key)) {
+ apr_table_add(r->headers_out, key,
+ date_canon(r->pool, value));
+ return;
+ }
+ }
+ for (i = 0; transform_hdrs[i].name; ++i) {
+ if (!ap_cstr_casecmp(transform_hdrs[i].name, key)) {
+ apr_table_add(r->headers_out, key,
+ (*transform_hdrs[i].func)(r, c, value));
+ return;
+ }
+ }
+ apr_table_add(r->headers_out, key, value);
+}
+
+/*
+ * Note: pread_len is the length of the response that we've mistakenly
+ * read (assuming that we don't consider that an error via
+ * ProxyBadHeader StartBody). This depends on buffer actually being
+ * local storage to the calling code in order for pread_len to make
+ * any sense at all, since we depend on buffer still containing
+ * what was read by ap_getline() upon return.
+ */
+static apr_status_t ap_proxy_read_headers(request_rec *r, request_rec *rr,
+ char *buffer, int size,
+ conn_rec *c, int *pread_len)
+{
+ int len;
+ char *value, *end;
+ int saw_headers = 0;
+ void *sconf = r->server->module_config;
+ proxy_server_conf *psc;
+ proxy_dir_conf *dconf;
+ apr_status_t rc;
+ apr_bucket_brigade *tmp_bb;
+
+ dconf = ap_get_module_config(r->per_dir_config, &proxy_module);
+ psc = (proxy_server_conf *) ap_get_module_config(sconf, &proxy_module);
+
+ r->headers_out = apr_table_make(r->pool, 20);
+ r->trailers_out = apr_table_make(r->pool, 5);
+ *pread_len = 0;
+
+ /*
+ * Read header lines until we get the empty separator line, a read error,
+ * the connection closes (EOF), or we timeout.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r,
+ "Headers received from backend:");
+
+ tmp_bb = apr_brigade_create(r->pool, c->bucket_alloc);
+ while (1) {
+ rc = ap_proxygetline(tmp_bb, buffer, size, rr,
+ AP_GETLINE_FOLD | AP_GETLINE_NOSPC_EOL, &len);
+
+
+ if (rc != APR_SUCCESS) {
+ if (APR_STATUS_IS_ENOSPC(rc)) {
+ int trunc = (len > 128 ? 128 : len) / 2;
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, rc, r, APLOGNO(10124)
+ "header size is over the limit allowed by "
+ "ResponseFieldSize (%d bytes). "
+ "Bad response header: '%.*s[...]%s'",
+ size, trunc, buffer, buffer + len - trunc);
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, rc, r, APLOGNO(10404)
+ "Error reading headers from backend");
+ }
+ r->headers_out = NULL;
+ return rc;
+ }
+
+ if (len <= 0) {
+ break;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r, "%s", buffer);
+ }
+
+ if (!(value = strchr(buffer, ':'))) { /* Find the colon separator */
+
+ /* We may encounter invalid headers, usually from buggy
+ * MS IIS servers, so we need to determine just how to handle
+ * them. We can either ignore them, assume that they mark the
+ * start-of-body (eg: a missing CRLF) or (the default) mark
+ * the headers as totally bogus and return a 500. The sole
+ * exception is an extra "HTTP/1.0 200, OK" line sprinkled
+ * in between the usual MIME headers, which is a favorite
+ * IIS bug.
+ */
+ /* XXX: The mask check is buggy if we ever see an HTTP/1.10 */
+
+ if (!apr_date_checkmask(buffer, "HTTP/#.# ###*")) {
+ if (psc->badopt == bad_error) {
+ /* Nope, it wasn't even an extra HTTP header. Give up. */
+ r->headers_out = NULL;
+ return APR_EINVAL;
+ }
+ else if (psc->badopt == bad_body) {
+ /* if we've already started loading headers_out, then
+ * return what we've accumulated so far, in the hopes
+ * that they are useful; also note that we likely pre-read
+ * the first line of the response.
+ */
+ if (saw_headers) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01098)
+ "Starting body due to bogus non-header "
+ "in headers returned by %s (%s)",
+ r->uri, r->method);
+ *pread_len = len;
+ return APR_SUCCESS;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01099)
+ "No HTTP headers returned by %s (%s)",
+ r->uri, r->method);
+ return APR_SUCCESS;
+ }
+ }
+ }
+ /* this is the psc->badopt == bad_ignore case */
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01100)
+ "Ignoring bogus HTTP header returned by %s (%s)",
+ r->uri, r->method);
+ continue;
+ }
+
+ *value = '\0';
+ ++value;
+ /* XXX: RFC2068 defines only SP and HT as whitespace, this test is
+ * wrong... and so are many others probably.
+ */
+ while (apr_isspace(*value))
+ ++value; /* Skip to start of value */
+
+ /* should strip trailing whitespace as well */
+ for (end = &value[strlen(value)-1]; end > value && apr_isspace(*end); --end)
+ *end = '\0';
+
+ /* make sure we add so as not to destroy duplicated headers
+ * Modify headers requiring canonicalisation and/or affected
+ * by ProxyPassReverse and family with process_proxy_header
+ */
+ process_proxy_header(r, dconf, buffer, value);
+ saw_headers = 1;
+ }
+ return APR_SUCCESS;
+}
+
+
+
+static int addit_dammit(void *v, const char *key, const char *val)
+{
+ apr_table_addn(v, key, val);
+ return 1;
+}
+
+static apr_status_t ap_proxygetline(apr_bucket_brigade *bb, char *s, int n,
+ request_rec *r, int flags, int *read)
+{
+ apr_status_t rv;
+ apr_size_t len;
+
+ rv = ap_rgetline(&s, n, &len, r, flags, bb);
+ apr_brigade_cleanup(bb);
+
+ if (rv == APR_SUCCESS || APR_STATUS_IS_ENOSPC(rv)) {
+ *read = (int)len;
+ } else {
+ *read = -1;
+ }
+
+ return rv;
+}
+
+/*
+ * Limit the number of interim responses we sent back to the client. Otherwise
+ * we suffer from a memory build up. Besides there is NO sense in sending back
+ * an unlimited number of interim responses to the client. Thus if we cross
+ * this limit send back a 502 (Bad Gateway).
+ */
+#ifndef AP_MAX_INTERIM_RESPONSES
+#define AP_MAX_INTERIM_RESPONSES 10
+#endif
+
+static int add_trailers(void *data, const char *key, const char *val)
+{
+ if (val) {
+ apr_table_add((apr_table_t*)data, key, val);
+ }
+ return 1;
+}
+
+static int send_continue_body(proxy_http_req_t *req)
+{
+ int status;
+
+ /* Send the request body (fully). */
+ switch(req->rb_method) {
+ case RB_SPOOL_CL:
+ case RB_STREAM_CL:
+ case RB_STREAM_CHUNKED:
+ status = stream_reqbody(req);
+ break;
+ default:
+ /* Shouldn't happen */
+ status = HTTP_INTERNAL_SERVER_ERROR;
+ break;
+ }
+ if (status != OK) {
+ conn_rec *c = req->r->connection;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req->r,
+ APLOGNO(10154) "pass request body failed "
+ "to %pI (%s) from %s (%s) with status %i",
+ req->backend->addr,
+ req->backend->hostname ? req->backend->hostname : "",
+ c->client_ip, c->remote_host ? c->remote_host : "",
+ status);
+ req->backend->close = 1;
+ }
+ return status;
+}
+
+static
+int ap_proxy_http_process_response(proxy_http_req_t *req)
+{
+ apr_pool_t *p = req->p;
+ request_rec *r = req->r;
+ conn_rec *c = r->connection;
+ proxy_worker *worker = req->worker;
+ proxy_conn_rec *backend = req->backend;
+ conn_rec *origin = req->origin;
+ int do_100_continue = req->do_100_continue;
+ int status;
+
+ char *buffer;
+ char fixed_buffer[HUGE_STRING_LEN];
+ const char *buf;
+ char keepchar;
+ apr_bucket *e;
+ apr_bucket_brigade *bb;
+ apr_bucket_brigade *pass_bb;
+ int len, backasswards;
+ int interim_response = 0; /* non-zero whilst interim 1xx responses
+ * are being read. */
+ apr_size_t response_field_size = 0;
+ int pread_len = 0;
+ apr_table_t *save_table;
+ int backend_broke = 0;
+ static const char *hop_by_hop_hdrs[] =
+ {"Keep-Alive", "Proxy-Authenticate", "TE", "Trailer", "Upgrade", NULL};
+ int i;
+ const char *te = NULL;
+ int original_status = r->status;
+ int proxy_status = OK;
+ const char *original_status_line = r->status_line;
+ const char *proxy_status_line = NULL;
+ apr_interval_time_t old_timeout = 0;
+ proxy_dir_conf *dconf;
+
+ dconf = ap_get_module_config(r->per_dir_config, &proxy_module);
+
+ bb = apr_brigade_create(p, c->bucket_alloc);
+ pass_bb = apr_brigade_create(p, c->bucket_alloc);
+
+ /* Only use dynamically sized buffer if user specifies ResponseFieldSize */
+ if(backend->worker->s->response_field_size_set) {
+ response_field_size = backend->worker->s->response_field_size;
+
+ if (response_field_size != HUGE_STRING_LEN)
+ buffer = apr_pcalloc(p, response_field_size);
+ else
+ buffer = fixed_buffer;
+ }
+ else {
+ response_field_size = HUGE_STRING_LEN;
+ buffer = fixed_buffer;
+ }
+
+ /* Setup for 100-Continue timeout if appropriate */
+ if (do_100_continue && worker->s->ping_timeout_set) {
+ apr_socket_timeout_get(backend->sock, &old_timeout);
+ if (worker->s->ping_timeout != old_timeout) {
+ apr_status_t rc;
+ rc = apr_socket_timeout_set(backend->sock, worker->s->ping_timeout);
+ if (rc != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(01101)
+ "could not set 100-Continue timeout");
+ }
+ }
+ }
+
+ /* Get response from the remote server, and pass it up the
+ * filter chain
+ */
+
+ backend->r = make_fake_req(origin, r);
+ /* In case anyone needs to know, this is a fake request that is really a
+ * response.
+ */
+ backend->r->proxyreq = PROXYREQ_RESPONSE;
+ apr_table_setn(r->notes, "proxy-source-port", apr_psprintf(r->pool, "%hu",
+ origin->local_addr->port));
+ do {
+ apr_status_t rc;
+ const char *upgrade = NULL;
+ int major = 0, minor = 0;
+ int toclose = 0;
+
+ apr_brigade_cleanup(bb);
+
+ rc = ap_proxygetline(backend->tmp_bb, buffer, response_field_size,
+ backend->r, 0, &len);
+ if (len == 0) {
+ /* handle one potential stray CRLF */
+ rc = ap_proxygetline(backend->tmp_bb, buffer, response_field_size,
+ backend->r, 0, &len);
+ }
+ if (len <= 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(01102)
+ "error reading status line from remote "
+ "server %s:%d", backend->hostname, backend->port);
+ if (APR_STATUS_IS_TIMEUP(rc)) {
+ apr_table_setn(r->notes, "proxy_timedout", "1");
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01103) "read timeout");
+ if (do_100_continue) {
+ return ap_proxyerror(r, HTTP_SERVICE_UNAVAILABLE,
+ "Timeout on 100-Continue");
+ }
+ }
+ /*
+ * If we are a reverse proxy request shutdown the connection
+ * WITHOUT ANY response to trigger a retry by the client
+ * if allowed (as for idempotent requests).
+ * BUT currently we should not do this if the request is the
+ * first request on a keepalive connection as browsers like
+ * seamonkey only display an empty page in this case and do
+ * not do a retry. We should also not do this on a
+ * connection which times out; instead handle as
+ * we normally would handle timeouts
+ */
+ if (r->proxyreq == PROXYREQ_REVERSE && c->keepalives &&
+ !APR_STATUS_IS_TIMEUP(rc)) {
+ apr_bucket *eos;
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01104)
+ "Closing connection to client because"
+ " reading from backend server %s:%d failed."
+ " Number of keepalives %i", backend->hostname,
+ backend->port, c->keepalives);
+ ap_proxy_backend_broke(r, bb);
+ /*
+ * Add an EOC bucket to signal the ap_http_header_filter
+ * that it should get out of our way, BUT ensure that the
+ * EOC bucket is inserted BEFORE an EOS bucket in bb as
+ * some resource filters like mod_deflate pass everything
+ * up to the EOS down the chain immediately and sent the
+ * remainder of the brigade later (or even never). But in
+ * this case the ap_http_header_filter does not get out of
+ * our way soon enough.
+ */
+ e = ap_bucket_eoc_create(c->bucket_alloc);
+ eos = APR_BRIGADE_LAST(bb);
+ while ((APR_BRIGADE_SENTINEL(bb) != eos)
+ && !APR_BUCKET_IS_EOS(eos)) {
+ eos = APR_BUCKET_PREV(eos);
+ }
+ if (eos == APR_BRIGADE_SENTINEL(bb)) {
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ }
+ else {
+ APR_BUCKET_INSERT_BEFORE(eos, e);
+ }
+ ap_pass_brigade(r->output_filters, bb);
+ /* Mark the backend connection for closing */
+ backend->close = 1;
+ if (origin->keepalives) {
+ /* We already had a request on this backend connection and
+ * might just have run into a keepalive race. Hence we
+ * think positive and assume that the backend is fine and
+ * we do not need to signal an error on backend side.
+ */
+ return OK;
+ }
+ /*
+ * This happened on our first request on this connection to the
+ * backend. This indicates something fishy with the backend.
+ * Return HTTP_INTERNAL_SERVER_ERROR to signal an unrecoverable
+ * server error. We do not worry about r->status code and a
+ * possible error response here as the ap_http_outerror_filter
+ * will fix all of this for us.
+ */
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ if (!c->keepalives) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01105)
+ "NOT Closing connection to client"
+ " although reading from backend server %s:%d"
+ " failed.",
+ backend->hostname, backend->port);
+ }
+ return ap_proxyerror(r, HTTP_BAD_GATEWAY,
+ "Error reading from remote server");
+ }
+ /* XXX: Is this a real headers length send from remote? */
+ backend->worker->s->read += len;
+
+ /* Is it an HTTP/1 response?
+ * This is buggy if we ever see an HTTP/1.10
+ */
+ if (apr_date_checkmask(buffer, "HTTP/#.# ###*")) {
+ major = buffer[5] - '0';
+ minor = buffer[7] - '0';
+
+ /* If not an HTTP/1 message or
+ * if the status line was > 8192 bytes
+ */
+ if ((major != 1) || (len >= response_field_size - 1)) {
+ return ap_proxyerror(r, HTTP_BAD_GATEWAY,
+ apr_pstrcat(p, "Corrupt status line returned "
+ "by remote server: ", buffer, NULL));
+ }
+ backasswards = 0;
+
+ keepchar = buffer[12];
+ buffer[12] = '\0';
+ proxy_status = atoi(&buffer[9]);
+ apr_table_setn(r->notes, "proxy-status",
+ apr_pstrdup(r->pool, &buffer[9]));
+
+ if (keepchar != '\0') {
+ buffer[12] = keepchar;
+ } else {
+ /* 2616 requires the space in Status-Line; the origin
+ * server may have sent one but ap_rgetline_core will
+ * have stripped it. */
+ buffer[12] = ' ';
+ buffer[13] = '\0';
+ }
+ proxy_status_line = apr_pstrdup(p, &buffer[9]);
+
+ /* The status out of the front is the same as the status coming in
+ * from the back, until further notice.
+ */
+ r->status = proxy_status;
+ r->status_line = proxy_status_line;
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
+ "Status from backend: %d", proxy_status);
+
+ /* read the headers. */
+ /* N.B. for HTTP/1.0 clients, we have to fold line-wrapped headers*/
+ /* Also, take care with headers with multiple occurrences. */
+
+ /* First, tuck away all already existing cookies */
+ save_table = apr_table_make(r->pool, 2);
+ apr_table_do(addit_dammit, save_table, r->headers_out,
+ "Set-Cookie", NULL);
+
+ /* shove the headers direct into r->headers_out */
+ rc = ap_proxy_read_headers(r, backend->r, buffer, response_field_size,
+ origin, &pread_len);
+
+ if (rc != APR_SUCCESS || r->headers_out == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01106)
+ "bad HTTP/%d.%d header returned by %s (%s)",
+ major, minor, r->uri, r->method);
+ backend->close = 1;
+ /*
+ * ap_send_error relies on a headers_out to be present. we
+ * are in a bad position here.. so force everything we send out
+ * to have nothing to do with the incoming packet
+ */
+ r->headers_out = apr_table_make(r->pool,1);
+ r->status = HTTP_BAD_GATEWAY;
+ r->status_line = "bad gateway";
+ return r->status;
+ }
+
+ /* Now, add in the just read cookies */
+ apr_table_do(addit_dammit, save_table, r->headers_out,
+ "Set-Cookie", NULL);
+
+ /* and now load 'em all in */
+ if (!apr_is_empty_table(save_table)) {
+ apr_table_unset(r->headers_out, "Set-Cookie");
+ r->headers_out = apr_table_overlay(r->pool,
+ r->headers_out,
+ save_table);
+ }
+
+ /*
+ * Save a possible Transfer-Encoding header as we need it later for
+ * ap_http_filter to know where to end.
+ */
+ te = apr_table_get(r->headers_out, "Transfer-Encoding");
+
+ /* can't have both Content-Length and Transfer-Encoding */
+ if (te && apr_table_get(r->headers_out, "Content-Length")) {
+ /*
+ * 2616 section 4.4, point 3: "if both Transfer-Encoding
+ * and Content-Length are received, the latter MUST be
+ * ignored";
+ *
+ * To help mitigate HTTP Splitting, unset Content-Length
+ * and shut down the backend server connection
+ * XXX: We aught to treat such a response as uncachable
+ */
+ apr_table_unset(r->headers_out, "Content-Length");
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01107)
+ "server %s:%d returned Transfer-Encoding"
+ " and Content-Length",
+ backend->hostname, backend->port);
+ backend->close = 1;
+ }
+
+ upgrade = apr_table_get(r->headers_out, "Upgrade");
+ if (proxy_status == HTTP_SWITCHING_PROTOCOLS) {
+ if (!upgrade || !req->upgrade || (strcasecmp(req->upgrade,
+ upgrade) != 0)) {
+ return ap_proxyerror(r, HTTP_BAD_GATEWAY,
+ apr_pstrcat(p, "Unexpected Upgrade: ",
+ upgrade ? upgrade : "n/a",
+ " (expecting ",
+ req->upgrade ? req->upgrade
+ : "n/a", ")",
+ NULL));
+ }
+ backend->close = 1;
+ }
+
+ /* strip connection listed hop-by-hop headers from response */
+ toclose = ap_proxy_clear_connection_fn(r, r->headers_out);
+ if (toclose) {
+ backend->close = 1;
+ if (toclose < 0) {
+ return ap_proxyerror(r, HTTP_BAD_GATEWAY,
+ "Malformed connection header");
+ }
+ }
+
+ if ((buf = apr_table_get(r->headers_out, "Content-Type"))) {
+ ap_set_content_type(r, apr_pstrdup(p, buf));
+ }
+ if (!ap_is_HTTP_INFO(proxy_status)) {
+ ap_proxy_pre_http_request(origin, backend->r);
+ }
+
+ /* Clear hop-by-hop headers */
+ for (i=0; hop_by_hop_hdrs[i]; ++i) {
+ apr_table_unset(r->headers_out, hop_by_hop_hdrs[i]);
+ }
+
+ /* Delete warnings with wrong date */
+ r->headers_out = ap_proxy_clean_warnings(p, r->headers_out);
+
+ /* handle Via header in response */
+ if (req->sconf->viaopt != via_off
+ && req->sconf->viaopt != via_block) {
+ const char *server_name = ap_get_server_name(r);
+ /* If USE_CANONICAL_NAME_OFF was configured for the proxy virtual host,
+ * then the server name returned by ap_get_server_name() is the
+ * origin server name (which does make too much sense with Via: headers)
+ * so we use the proxy vhost's name instead.
+ */
+ if (server_name == r->hostname)
+ server_name = r->server->server_hostname;
+ /* create a "Via:" response header entry and merge it */
+ apr_table_addn(r->headers_out, "Via",
+ (req->sconf->viaopt == via_full)
+ ? apr_psprintf(p, "%d.%d %s%s (%s)",
+ HTTP_VERSION_MAJOR(r->proto_num),
+ HTTP_VERSION_MINOR(r->proto_num),
+ server_name,
+ req->server_portstr,
+ AP_SERVER_BASEVERSION)
+ : apr_psprintf(p, "%d.%d %s%s",
+ HTTP_VERSION_MAJOR(r->proto_num),
+ HTTP_VERSION_MINOR(r->proto_num),
+ server_name,
+ req->server_portstr)
+ );
+ }
+
+ /* cancel keepalive if HTTP/1.0 or less */
+ if ((major < 1) || (minor < 1)) {
+ backend->close = 1;
+ origin->keepalive = AP_CONN_CLOSE;
+ }
+ else {
+ /*
+ * Keep track of the number of keepalives we processed on this
+ * connection.
+ */
+ origin->keepalives++;
+ }
+
+ } else {
+ /* an http/0.9 response */
+ backasswards = 1;
+ r->status = proxy_status = 200;
+ r->status_line = "200 OK";
+ backend->close = 1;
+ }
+
+ if (ap_is_HTTP_INFO(proxy_status)) {
+ const char *policy = NULL;
+
+ /* RFC2616 tells us to forward this.
+ *
+ * OTOH, an interim response here may mean the backend
+ * is playing sillybuggers. The Client didn't ask for
+ * it within the defined HTTP/1.1 mechanisms, and if
+ * it's an extension, it may also be unsupported by us.
+ *
+ * There's also the possibility that changing existing
+ * behaviour here might break something.
+ *
+ * So let's make it configurable.
+ *
+ * We need to force "r->expecting_100 = 1" for RFC behaviour
+ * otherwise ap_send_interim_response() does nothing when
+ * the client did not ask for 100-continue.
+ *
+ * 101 Switching Protocol has its own configuration which
+ * shouldn't be interfered by "proxy-interim-response".
+ */
+ if (proxy_status != HTTP_SWITCHING_PROTOCOLS) {
+ policy = apr_table_get(r->subprocess_env,
+ "proxy-interim-response");
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+ "HTTP: received interim %d response (policy: %s)",
+ r->status, policy ? policy : "n/a");
+ if (!policy
+ || (!strcasecmp(policy, "RFC")
+ && (proxy_status != HTTP_CONTINUE
+ || (r->expecting_100 = 1)))) {
+ switch (proxy_status) {
+ case HTTP_SWITCHING_PROTOCOLS:
+ AP_DEBUG_ASSERT(upgrade != NULL);
+ apr_table_setn(r->headers_out, "Connection", "Upgrade");
+ apr_table_setn(r->headers_out, "Upgrade",
+ apr_pstrdup(p, upgrade));
+ break;
+ }
+ ap_send_interim_response(r, 1);
+ }
+ /* FIXME: refine this to be able to specify per-response-status
+ * policies and maybe also add option to bail out with 502
+ */
+ else if (strcasecmp(policy, "Suppress")) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01108)
+ "undefined proxy interim response policy");
+ }
+ interim_response++;
+ }
+ else {
+ interim_response = 0;
+ }
+
+ /* If we still do 100-continue (end-to-end or ping), either the
+ * current response is the expected "100 Continue" and we are done
+ * with this mode, or this is another interim response and we'll wait
+ * for the next one, or this is a final response and hence the backend
+ * did not honor our expectation.
+ */
+ if (do_100_continue && (!interim_response
+ || proxy_status == HTTP_CONTINUE)) {
+ /* RFC 7231 - Section 5.1.1 - Expect - Requirement for servers
+ * A server that responds with a final status code before
+ * reading the entire message body SHOULD indicate in that
+ * response whether it intends to close the connection or
+ * continue reading and discarding the request message.
+ *
+ * So, if this response is not an interim 100 Continue, we can
+ * avoid sending the request body if the backend responded with
+ * "Connection: close" or HTTP < 1.1, and either let the core
+ * discard it or the caller try another balancer member with the
+ * same body (given status 503, though not implemented yet).
+ */
+ int do_send_body = (proxy_status == HTTP_CONTINUE
+ || (!toclose && major > 0 && minor > 0));
+
+ /* Reset to old timeout iff we've adjusted it. */
+ if (worker->s->ping_timeout_set) {
+ apr_socket_timeout_set(backend->sock, old_timeout);
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(10153)
+ "HTTP: %s100 continue sent by %pI (%s): "
+ "%ssending body (response: HTTP/%i.%i %s)",
+ proxy_status != HTTP_CONTINUE ? "no " : "",
+ backend->addr,
+ backend->hostname ? backend->hostname : "",
+ do_send_body ? "" : "not ",
+ major, minor, proxy_status_line);
+
+ if (do_send_body) {
+ status = send_continue_body(req);
+ if (status != OK) {
+ return status;
+ }
+ }
+ else {
+ /* If we don't read the client connection any further, since
+ * there are pending data it should be "Connection: close"d to
+ * prevent reuse. We don't exactly c->keepalive = AP_CONN_CLOSE
+ * here though, because error_override or a potential retry on
+ * another backend could finally read that data and finalize
+ * the request processing, making keep-alive possible. So what
+ * we do is leaving r->expecting_100 alone, ap_set_keepalive()
+ * will do the right thing according to the final response and
+ * any later update of r->expecting_100.
+ */
+ }
+
+ /* Once only! */
+ do_100_continue = 0;
+ }
+
+ if (proxy_status == HTTP_SWITCHING_PROTOCOLS) {
+ apr_status_t rv;
+ proxy_tunnel_rec *tunnel;
+ apr_interval_time_t client_timeout = -1,
+ backend_timeout = -1;
+
+ /* If we didn't send the full body yet, do it now */
+ if (do_100_continue) {
+ r->expecting_100 = 0;
+ status = send_continue_body(req);
+ if (status != OK) {
+ return status;
+ }
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(10239)
+ "HTTP: tunneling protocol %s", upgrade);
+
+ rv = ap_proxy_tunnel_create(&tunnel, r, origin, upgrade);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10240)
+ "can't create tunnel for %s", upgrade);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* Set timeout to the highest configured for client or backend */
+ apr_socket_timeout_get(backend->sock, &backend_timeout);
+ apr_socket_timeout_get(ap_get_conn_socket(c), &client_timeout);
+ if (backend_timeout >= 0 && backend_timeout > client_timeout) {
+ tunnel->timeout = backend_timeout;
+ }
+ else {
+ tunnel->timeout = client_timeout;
+ }
+
+ /* Let proxy tunnel forward everything */
+ status = ap_proxy_tunnel_run(tunnel);
+
+ /* We are done with both connections */
+ return DONE;
+ }
+
+ if (interim_response) {
+ /* Already forwarded above, read next response */
+ continue;
+ }
+
+ /* Moved the fixups of Date headers and those affected by
+ * ProxyPassReverse/etc from here to ap_proxy_read_headers
+ */
+
+ /* PR 41646: get HEAD right with ProxyErrorOverride */
+ if (ap_proxy_should_override(dconf, proxy_status)) {
+ if (proxy_status == HTTP_UNAUTHORIZED) {
+ const char *buf;
+ const char *wa = "WWW-Authenticate";
+ if ((buf = apr_table_get(r->headers_out, wa))) {
+ apr_table_set(r->err_headers_out, wa, buf);
+ } else {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01109)
+ "origin server sent 401 without "
+ "WWW-Authenticate header");
+ }
+ }
+
+ /* clear r->status for override error, otherwise ErrorDocument
+ * thinks that this is a recursive error, and doesn't find the
+ * custom error page
+ */
+ r->status = HTTP_OK;
+ /* Discard body, if one is expected */
+ if (!r->header_only && !AP_STATUS_IS_HEADER_ONLY(proxy_status)) {
+ const char *tmp;
+ /* Add minimal headers needed to allow http_in filter
+ * detecting end of body without waiting for a timeout. */
+ if ((tmp = apr_table_get(r->headers_out, "Transfer-Encoding"))) {
+ apr_table_set(backend->r->headers_in, "Transfer-Encoding", tmp);
+ }
+ else if ((tmp = apr_table_get(r->headers_out, "Content-Length"))) {
+ apr_table_set(backend->r->headers_in, "Content-Length", tmp);
+ }
+ else if (te) {
+ apr_table_set(backend->r->headers_in, "Transfer-Encoding", te);
+ }
+ ap_discard_request_body(backend->r);
+ }
+ /*
+ * prevent proxy_handler() from treating this as an
+ * internal error.
+ */
+ apr_table_setn(r->notes, "proxy-error-override", "1");
+ return proxy_status;
+ }
+
+ /* Forward back Upgrade header if it matches the configured one(s), it
+ * may be an HTTP_UPGRADE_REQUIRED response or some other status where
+ * Upgrade makes sense to negotiate the protocol by other means.
+ */
+ if (upgrade && ap_proxy_worker_can_upgrade(p, worker, upgrade,
+ (*req->proto == 'w')
+ ? "WebSocket" : NULL)) {
+ apr_table_setn(r->headers_out, "Connection", "Upgrade");
+ apr_table_setn(r->headers_out, "Upgrade", apr_pstrdup(p, upgrade));
+ }
+
+ r->sent_bodyct = 1;
+ /*
+ * Is it an HTTP/0.9 response or did we maybe preread the 1st line of
+ * the response? If so, load the extra data. These are 2 mutually
+ * exclusive possibilities, that just happen to require very
+ * similar behavior.
+ */
+ if (backasswards || pread_len) {
+ apr_ssize_t cntr = (apr_ssize_t)pread_len;
+ if (backasswards) {
+ /*@@@FIXME:
+ * At this point in response processing of a 0.9 response,
+ * we don't know yet whether data is binary or not.
+ * mod_charset_lite will get control later on, so it cannot
+ * decide on the conversion of this buffer full of data.
+ * However, chances are that we are not really talking to an
+ * HTTP/0.9 server, but to some different protocol, therefore
+ * the best guess IMHO is to always treat the buffer as "text/x":
+ */
+ ap_xlate_proto_to_ascii(buffer, len);
+ cntr = (apr_ssize_t)len;
+ }
+ e = apr_bucket_heap_create(buffer, cntr, NULL, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ }
+
+ /* send body - but only if a body is expected */
+ if ((!r->header_only) && /* not HEAD request */
+ (proxy_status != HTTP_NO_CONTENT) && /* not 204 */
+ (proxy_status != HTTP_NOT_MODIFIED)) { /* not 304 */
+ apr_read_type_e mode;
+ int finish;
+
+ /* We need to copy the output headers and treat them as input
+ * headers as well. BUT, we need to do this before we remove
+ * TE, so that they are preserved accordingly for
+ * ap_http_filter to know where to end.
+ */
+ backend->r->headers_in = apr_table_clone(backend->r->pool, r->headers_out);
+ /*
+ * Restore Transfer-Encoding header from response if we saved
+ * one before and there is none left. We need it for the
+ * ap_http_filter. See above.
+ */
+ if (te && !apr_table_get(backend->r->headers_in, "Transfer-Encoding")) {
+ apr_table_add(backend->r->headers_in, "Transfer-Encoding", te);
+ }
+
+ apr_table_unset(r->headers_out,"Transfer-Encoding");
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, "start body send");
+
+ /* read the body, pass it to the output filters */
+
+ /* Handle the case where the error document is itself reverse
+ * proxied and was successful. We must maintain any previous
+ * error status so that an underlying error (eg HTTP_NOT_FOUND)
+ * doesn't become an HTTP_OK.
+ */
+ if (ap_proxy_should_override(dconf, original_status)) {
+ r->status = original_status;
+ r->status_line = original_status_line;
+ }
+
+ mode = APR_NONBLOCK_READ;
+ finish = FALSE;
+ do {
+ apr_off_t readbytes;
+ apr_status_t rv;
+
+ rv = ap_get_brigade(backend->r->input_filters, bb,
+ AP_MODE_READBYTES, mode,
+ req->sconf->io_buffer_size);
+
+ /* ap_get_brigade will return success with an empty brigade
+ * for a non-blocking read which would block: */
+ if (mode == APR_NONBLOCK_READ
+ && (APR_STATUS_IS_EAGAIN(rv)
+ || (rv == APR_SUCCESS && APR_BRIGADE_EMPTY(bb)))) {
+ /* flush to the client and switch to blocking mode */
+ e = apr_bucket_flush_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ if (ap_pass_brigade(r->output_filters, bb)
+ || c->aborted) {
+ backend->close = 1;
+ break;
+ }
+ apr_brigade_cleanup(bb);
+ mode = APR_BLOCK_READ;
+ continue;
+ }
+ else if (rv == APR_EOF) {
+ backend->close = 1;
+ break;
+ }
+ else if (rv != APR_SUCCESS) {
+ /* In this case, we are in real trouble because
+ * our backend bailed on us. Pass along a 502 error
+ * error bucket
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01110)
+ "error reading response");
+ apr_brigade_cleanup(bb);
+ ap_proxy_backend_broke(r, bb);
+ ap_pass_brigade(r->output_filters, bb);
+ backend_broke = 1;
+ backend->close = 1;
+ break;
+ }
+ /* next time try a non-blocking read */
+ mode = APR_NONBLOCK_READ;
+
+ if (!apr_is_empty_table(backend->r->trailers_in)) {
+ apr_table_do(add_trailers, r->trailers_out,
+ backend->r->trailers_in, NULL);
+ apr_table_clear(backend->r->trailers_in);
+ }
+
+ apr_brigade_length(bb, 0, &readbytes);
+ backend->worker->s->read += readbytes;
+#if DEBUGGING
+ {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01111)
+ "readbytes: %#x", readbytes);
+ }
+#endif
+ /* sanity check */
+ if (APR_BRIGADE_EMPTY(bb)) {
+ break;
+ }
+
+ /* Switch the allocator lifetime of the buckets */
+ ap_proxy_buckets_lifetime_transform(r, bb, pass_bb);
+
+ /* found the last brigade? */
+ if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(pass_bb))) {
+
+ /* signal that we must leave */
+ finish = TRUE;
+
+ /* the brigade may contain transient buckets that contain
+ * data that lives only as long as the backend connection.
+ * Force a setaside so these transient buckets become heap
+ * buckets that live as long as the request.
+ */
+ for (e = APR_BRIGADE_FIRST(pass_bb); e
+ != APR_BRIGADE_SENTINEL(pass_bb); e
+ = APR_BUCKET_NEXT(e)) {
+ apr_bucket_setaside(e, r->pool);
+ }
+
+ /* finally it is safe to clean up the brigade from the
+ * connection pool, as we have forced a setaside on all
+ * buckets.
+ */
+ apr_brigade_cleanup(bb);
+
+ /* make sure we release the backend connection as soon
+ * as we know we are done, so that the backend isn't
+ * left waiting for a slow client to eventually
+ * acknowledge the data.
+ */
+ ap_proxy_release_connection(backend->worker->s->scheme,
+ backend, r->server);
+ /* Ensure that the backend is not reused */
+ req->backend = NULL;
+
+ }
+
+ /* try send what we read */
+ if (ap_pass_brigade(r->output_filters, pass_bb) != APR_SUCCESS
+ || c->aborted) {
+ /* Ack! Phbtt! Die! User aborted! */
+ /* Only close backend if we haven't got all from the
+ * backend. Furthermore if req->backend is NULL it is no
+ * longer safe to fiddle around with backend as it might
+ * be already in use by another thread.
+ */
+ if (req->backend) {
+ /* this causes socket close below */
+ req->backend->close = 1;
+ }
+ finish = TRUE;
+ }
+
+ /* make sure we always clean up after ourselves */
+ apr_brigade_cleanup(pass_bb);
+ apr_brigade_cleanup(bb);
+
+ } while (!finish);
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "end body send");
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "header only");
+
+ /* make sure we release the backend connection as soon
+ * as we know we are done, so that the backend isn't
+ * left waiting for a slow client to eventually
+ * acknowledge the data.
+ */
+ ap_proxy_release_connection(backend->worker->s->scheme,
+ backend, r->server);
+ /* Ensure that the backend is not reused */
+ req->backend = NULL;
+
+ /* Pass EOS bucket down the filter chain. */
+ e = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ ap_pass_brigade(r->output_filters, bb);
+
+ apr_brigade_cleanup(bb);
+ }
+ } while (interim_response && (interim_response < AP_MAX_INTERIM_RESPONSES));
+
+ /* We have to cleanup bb brigade, because buckets inserted to it could be
+ * created from scpool and this pool can be freed before this brigade. */
+ apr_brigade_cleanup(bb);
+
+ /* See define of AP_MAX_INTERIM_RESPONSES for why */
+ if (interim_response >= AP_MAX_INTERIM_RESPONSES) {
+ return ap_proxyerror(r, HTTP_BAD_GATEWAY,
+ apr_psprintf(p,
+ "Too many (%d) interim responses from origin server",
+ interim_response));
+ }
+
+ /* If our connection with the client is to be aborted, return DONE. */
+ if (c->aborted || backend_broke) {
+ return DONE;
+ }
+
+ return OK;
+}
+
+static
+apr_status_t ap_proxy_http_cleanup(const char *scheme, request_rec *r,
+ proxy_conn_rec *backend)
+{
+ ap_proxy_release_connection(scheme, backend, r->server);
+ return OK;
+}
+
+/*
+ * This handles http:// URLs, and other URLs using a remote proxy over http
+ * If proxyhost is NULL, then contact the server directly, otherwise
+ * go via the proxy.
+ * Note that if a proxy is used, then URLs other than http: can be accessed,
+ * also, if we have trouble which is clearly specific to the proxy, then
+ * we return DECLINED so that we can try another proxy. (Or the direct
+ * route.)
+ */
+static int proxy_http_handler(request_rec *r, proxy_worker *worker,
+ proxy_server_conf *conf,
+ char *url, const char *proxyname,
+ apr_port_t proxyport)
+{
+ int status;
+ const char *scheme;
+ const char *u = url;
+ proxy_http_req_t *req = NULL;
+ proxy_conn_rec *backend = NULL;
+ apr_bucket_brigade *input_brigade = NULL;
+ int is_ssl = 0;
+ conn_rec *c = r->connection;
+ proxy_dir_conf *dconf;
+ int retry = 0;
+ char *locurl = url;
+ int toclose = 0;
+ /*
+ * Use a shorter-lived pool to reduce memory usage
+ * and avoid a memory leak
+ */
+ apr_pool_t *p = r->pool;
+ apr_uri_t *uri;
+
+ scheme = get_url_scheme(&u, &is_ssl);
+ if (!scheme && proxyname && strncasecmp(url, "ftp:", 4) == 0) {
+ u = url + 4;
+ scheme = "ftp";
+ is_ssl = 0;
+ }
+ if (!scheme || u[0] != '/' || u[1] != '/' || u[2] == '\0') {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01113)
+ "HTTP: declining URL %s", url);
+ return DECLINED; /* only interested in HTTP, WS or FTP via proxy */
+ }
+ if (is_ssl && !ap_ssl_has_outgoing_handlers()) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01112)
+ "HTTP: declining URL %s (mod_ssl not configured?)", url);
+ return DECLINED;
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "HTTP: serving URL %s", url);
+
+ /* create space for state information */
+ if ((status = ap_proxy_acquire_connection(scheme, &backend,
+ worker, r->server)) != OK) {
+ return status;
+ }
+
+ backend->is_ssl = is_ssl;
+
+ req = apr_pcalloc(p, sizeof(*req));
+ req->p = p;
+ req->r = r;
+ req->sconf = conf;
+ req->worker = worker;
+ req->backend = backend;
+ req->proto = scheme;
+ req->bucket_alloc = c->bucket_alloc;
+ req->rb_method = RB_INIT;
+
+ dconf = ap_get_module_config(r->per_dir_config, &proxy_module);
+
+ if (apr_table_get(r->subprocess_env, "force-proxy-request-1.0")) {
+ req->force10 = 1;
+ }
+ else if (*worker->s->upgrade || *req->proto == 'w') {
+ /* Forward Upgrade header if it matches the configured one(s),
+ * the default being "WebSocket" for ws[s] schemes.
+ */
+ const char *upgrade = apr_table_get(r->headers_in, "Upgrade");
+ if (upgrade && ap_proxy_worker_can_upgrade(p, worker, upgrade,
+ (*req->proto == 'w')
+ ? "WebSocket" : NULL)) {
+ req->upgrade = upgrade;
+ }
+ }
+
+ /* We possibly reuse input data prefetched in previous call(s), e.g. for a
+ * balancer fallback scenario, and in this case the 100 continue settings
+ * should be consistent between balancer members. If not, we need to ignore
+ * Proxy100Continue on=>off once we tried to prefetch already, otherwise
+ * the HTTP_IN filter won't send 100 Continue for us anymore, and we might
+ * deadlock with the client waiting for each other. Note that off=>on is
+ * not an issue because in this case r->expecting_100 is false (the 100
+ * Continue is out already), but we make sure that prefetch will be
+ * nonblocking to avoid passing more time there.
+ */
+ apr_pool_userdata_get((void **)&input_brigade, "proxy-req-input", p);
+
+ /* Should we handle end-to-end or ping 100-continue? */
+ if (!req->force10
+ && ((r->expecting_100 && (dconf->forward_100_continue || input_brigade))
+ || PROXY_SHOULD_PING_100_CONTINUE(worker, r))) {
+ /* Tell ap_proxy_create_hdrbrgd() to preserve/add the Expect header */
+ apr_table_setn(r->notes, "proxy-100-continue", "1");
+ req->do_100_continue = 1;
+ }
+
+ /* Should we block while prefetching the body or try nonblocking and flush
+ * data to the backend ASAP?
+ */
+ if (input_brigade
+ || req->do_100_continue
+ || apr_table_get(r->subprocess_env,
+ "proxy-prefetch-nonblocking")) {
+ req->prefetch_nonblocking = 1;
+ }
+
+ /*
+ * In the case that we are handling a reverse proxy connection and this
+ * is not a request that is coming over an already kept alive connection
+ * with the client, do NOT reuse the connection to the backend, because
+ * we cannot forward a failure to the client in this case as the client
+ * does NOT expect this in this situation.
+ * Yes, this creates a performance penalty.
+ */
+ if ((r->proxyreq == PROXYREQ_REVERSE) && (!c->keepalives)
+ && (apr_table_get(r->subprocess_env, "proxy-initial-not-pooled"))) {
+ backend->close = 1;
+ }
+
+ /* Step One: Determine Who To Connect To */
+ uri = apr_palloc(p, sizeof(*uri));
+ if ((status = ap_proxy_determine_connection(p, r, conf, worker, backend,
+ uri, &locurl, proxyname,
+ proxyport, req->server_portstr,
+ sizeof(req->server_portstr))))
+ goto cleanup;
+
+ /* The header is always (re-)built since it depends on worker settings,
+ * but the body can be fetched only once (even partially), so it's saved
+ * in between proxy_http_handler() calls should we come back here.
+ */
+ req->header_brigade = apr_brigade_create(p, req->bucket_alloc);
+ if (input_brigade == NULL) {
+ input_brigade = apr_brigade_create(p, req->bucket_alloc);
+ apr_pool_userdata_setn(input_brigade, "proxy-req-input", NULL, p);
+ }
+ req->input_brigade = input_brigade;
+
+ /* Prefetch (nonlocking) the request body so to increase the chance to get
+ * the whole (or enough) body and determine Content-Length vs chunked or
+ * spooled. By doing this before connecting or reusing the backend, we want
+ * to minimize the delay between this connection is considered alive and
+ * the first bytes sent (should the client's link be slow or some input
+ * filter retain the data). This is a best effort to prevent the backend
+ * from closing (from under us) what it thinks is an idle connection, hence
+ * to reduce to the minimum the unavoidable local is_socket_connected() vs
+ * remote keepalive race condition.
+ */
+ if ((status = ap_proxy_http_prefetch(req, uri, locurl)) != OK)
+ goto cleanup;
+
+ /* We need to reset backend->close now, since ap_proxy_http_prefetch() set
+ * it to disable the reuse of the connection *after* this request (no keep-
+ * alive), not to close any reusable connection before this request. However
+ * assure what is expected later by using a local flag and do the right thing
+ * when ap_proxy_connect_backend() below provides the connection to close.
+ */
+ toclose = backend->close;
+ backend->close = 0;
+
+ while (retry < 2) {
+ if (retry) {
+ char *newurl = url;
+
+ /* Step One (again): (Re)Determine Who To Connect To */
+ if ((status = ap_proxy_determine_connection(p, r, conf, worker,
+ backend, uri, &newurl, proxyname, proxyport,
+ req->server_portstr, sizeof(req->server_portstr))))
+ break;
+
+ /* The code assumes locurl is not changed during the loop, or
+ * ap_proxy_http_prefetch() would have to be called every time,
+ * and header_brigade be changed accordingly...
+ */
+ AP_DEBUG_ASSERT(strcmp(newurl, locurl) == 0);
+ }
+
+ /* Step Two: Make the Connection */
+ if (ap_proxy_check_connection(scheme, backend, r->server, 1,
+ PROXY_CHECK_CONN_EMPTY)
+ && ap_proxy_connect_backend(scheme, backend, worker,
+ r->server)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01114)
+ "HTTP: failed to make connection to backend: %s",
+ backend->hostname);
+ status = HTTP_SERVICE_UNAVAILABLE;
+ break;
+ }
+
+ /* Step Three: Create conn_rec */
+ if ((status = ap_proxy_connection_create_ex(scheme, backend, r)) != OK)
+ break;
+ req->origin = backend->connection;
+
+ /* Don't recycle the connection if prefetch (above) told not to do so */
+ if (toclose) {
+ backend->close = 1;
+ req->origin->keepalive = AP_CONN_CLOSE;
+ }
+
+ /* Step Four: Send the Request
+ * On the off-chance that we forced a 100-Continue as a
+ * kinda HTTP ping test, allow for retries
+ */
+ status = ap_proxy_http_request(req);
+ if (status != OK) {
+ if (req->do_100_continue && status == HTTP_SERVICE_UNAVAILABLE) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, status, r, APLOGNO(01115)
+ "HTTP: 100-Continue failed to %pI (%s:%d)",
+ worker->cp->addr, worker->s->hostname_ex,
+ (int)worker->s->port);
+ backend->close = 1;
+ retry++;
+ continue;
+ }
+ break;
+ }
+
+ /* Step Five: Receive the Response... Fall thru to cleanup */
+ status = ap_proxy_http_process_response(req);
+
+ break;
+ }
+
+ /* Step Six: Clean Up */
+cleanup:
+ if (req->backend) {
+ if (status != OK)
+ req->backend->close = 1;
+ ap_proxy_http_cleanup(scheme, r, req->backend);
+ }
+ return status;
+}
+
+/* post_config hook: */
+static int proxy_http_post_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+
+ /* proxy_http_post_config() will be called twice during startup. So, don't
+ * set up the static data the 1st time through. */
+ if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) {
+ return OK;
+ }
+
+ ap_proxy_clear_connection_fn =
+ APR_RETRIEVE_OPTIONAL_FN(ap_proxy_clear_connection);
+ if (!ap_proxy_clear_connection_fn) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02477)
+ "mod_proxy must be loaded for mod_proxy_http");
+ return !OK;
+ }
+
+ return OK;
+}
+
+static void ap_proxy_http_register_hook(apr_pool_t *p)
+{
+ ap_hook_post_config(proxy_http_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+ proxy_hook_scheme_handler(proxy_http_handler, NULL, NULL, APR_HOOK_FIRST);
+ proxy_hook_canon_handler(proxy_http_canon, NULL, NULL, APR_HOOK_FIRST);
+ warn_rx = ap_pregcomp(p, "[0-9]{3}[ \t]+[^ \t]+[ \t]+\"[^\"]*\"([ \t]+\"([^\"]+)\")?", 0);
+}
+
+AP_DECLARE_MODULE(proxy_http) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ NULL, /* command apr_table_t */
+ ap_proxy_http_register_hook/* register hooks */
+};
+
diff --git a/modules/proxy/mod_proxy_http.dep b/modules/proxy/mod_proxy_http.dep
new file mode 100644
index 0000000..35c91b7
--- /dev/null
+++ b/modules/proxy/mod_proxy_http.dep
@@ -0,0 +1,73 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_proxy_http.mak
+
+.\mod_proxy_http.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_slotmem.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_date.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_reslist.h"\
+ "..\..\srclib\apr-util\include\apr_strmatch.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_proxy.h"\
+
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
diff --git a/modules/proxy/mod_proxy_http.dsp b/modules/proxy/mod_proxy_http.dsp
new file mode 100644
index 0000000..7d48815
--- /dev/null
+++ b/modules/proxy/mod_proxy_http.dsp
@@ -0,0 +1,123 @@
+# Microsoft Developer Studio Project File - Name="mod_proxy_http" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_proxy_http - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_http.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_http.mak" CFG="mod_proxy_http - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy_http - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy_http - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_proxy_http - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_proxy_http_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_proxy_http.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_proxy_http.so" /d LONG_NAME="proxy_http_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:".\Release\mod_proxy_http.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_http.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_proxy_http.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_http.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_proxy_http.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_proxy_http - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_proxy_http_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_proxy_http.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_proxy_http.so" /d LONG_NAME="proxy_http_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_proxy_http.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_http.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_proxy_http.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_http.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_proxy_http.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_proxy_http - Win32 Release"
+# Name "mod_proxy_http - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\mod_proxy_http.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter ".h"
+# Begin Source File
+
+SOURCE=.\mod_proxy.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/proxy/mod_proxy_http.mak b/modules/proxy/mod_proxy_http.mak
new file mode 100644
index 0000000..c381187
--- /dev/null
+++ b/modules/proxy/mod_proxy_http.mak
@@ -0,0 +1,380 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_proxy_http.dsp
+!IF "$(CFG)" == ""
+CFG=mod_proxy_http - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_proxy_http - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_proxy_http - Win32 Release" && "$(CFG)" != "mod_proxy_http - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_http.mak" CFG="mod_proxy_http - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy_http - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy_http - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_http - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_proxy_http.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_proxy - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_proxy_http.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_proxy - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_proxy_http.obj"
+ -@erase "$(INTDIR)\mod_proxy_http.res"
+ -@erase "$(INTDIR)\mod_proxy_http_src.idb"
+ -@erase "$(INTDIR)\mod_proxy_http_src.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_http.exp"
+ -@erase "$(OUTDIR)\mod_proxy_http.lib"
+ -@erase "$(OUTDIR)\mod_proxy_http.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_http.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_proxy_http_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_proxy_http.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_proxy_http.so" /d LONG_NAME="proxy_http_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_proxy_http.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_proxy_http.pdb" /debug /out:"$(OUTDIR)\mod_proxy_http.so" /implib:"$(OUTDIR)\mod_proxy_http.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_http.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_proxy_http.obj" \
+ "$(INTDIR)\mod_proxy_http.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib" \
+ "$(OUTDIR)\mod_proxy.lib"
+
+"$(OUTDIR)\mod_proxy_http.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_proxy_http.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_proxy_http.so"
+ if exist .\Release\mod_proxy_http.so.manifest mt.exe -manifest .\Release\mod_proxy_http.so.manifest -outputresource:.\Release\mod_proxy_http.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_proxy_http - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_proxy_http.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_proxy - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_proxy_http.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_proxy - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_proxy_http.obj"
+ -@erase "$(INTDIR)\mod_proxy_http.res"
+ -@erase "$(INTDIR)\mod_proxy_http_src.idb"
+ -@erase "$(INTDIR)\mod_proxy_http_src.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_http.exp"
+ -@erase "$(OUTDIR)\mod_proxy_http.lib"
+ -@erase "$(OUTDIR)\mod_proxy_http.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_http.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_proxy_http_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_proxy_http.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_proxy_http.so" /d LONG_NAME="proxy_http_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_proxy_http.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_proxy_http.pdb" /debug /out:"$(OUTDIR)\mod_proxy_http.so" /implib:"$(OUTDIR)\mod_proxy_http.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_http.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_proxy_http.obj" \
+ "$(INTDIR)\mod_proxy_http.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib" \
+ "$(OUTDIR)\mod_proxy.lib"
+
+"$(OUTDIR)\mod_proxy_http.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_proxy_http.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_proxy_http.so"
+ if exist .\Debug\mod_proxy_http.so.manifest mt.exe -manifest .\Debug\mod_proxy_http.so.manifest -outputresource:.\Debug\mod_proxy_http.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_proxy_http.dep")
+!INCLUDE "mod_proxy_http.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_proxy_http.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_proxy_http - Win32 Release" || "$(CFG)" == "mod_proxy_http - Win32 Debug"
+SOURCE=.\mod_proxy_http.c
+
+"$(INTDIR)\mod_proxy_http.obj" : $(SOURCE) "$(INTDIR)"
+
+
+!IF "$(CFG)" == "mod_proxy_http - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\proxy"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_http - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\proxy"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_http - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\proxy"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_http - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\proxy"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_http - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\proxy"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_http - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\proxy"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_http - Win32 Release"
+
+"mod_proxy - Win32 Release" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Release"
+ cd "."
+
+"mod_proxy - Win32 ReleaseCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Release" RECURSE=1 CLEAN
+ cd "."
+
+!ELSEIF "$(CFG)" == "mod_proxy_http - Win32 Debug"
+
+"mod_proxy - Win32 Debug" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Debug"
+ cd "."
+
+"mod_proxy - Win32 DebugCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Debug" RECURSE=1 CLEAN
+ cd "."
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_proxy_http - Win32 Release"
+
+
+"$(INTDIR)\mod_proxy_http.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_proxy_http.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_proxy_http.so" /d LONG_NAME="proxy_http_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_proxy_http - Win32 Debug"
+
+
+"$(INTDIR)\mod_proxy_http.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_proxy_http.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_proxy_http.so" /d LONG_NAME="proxy_http_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+
+!ENDIF
+
diff --git a/modules/proxy/mod_proxy_scgi.c b/modules/proxy/mod_proxy_scgi.c
new file mode 100644
index 0000000..493757d
--- /dev/null
+++ b/modules/proxy/mod_proxy_scgi.c
@@ -0,0 +1,674 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * mod_proxy_scgi.c
+ * Proxy backend module for the SCGI protocol
+ * (http://python.ca/scgi/protocol.txt)
+ *
+ * Andr� Malo (nd/perlig.de), August 2007
+ */
+
+#define APR_WANT_MEMFUNC
+#define APR_WANT_STRFUNC
+#include "apr_strings.h"
+#include "ap_hooks.h"
+#include "apr_optional_hooks.h"
+#include "apr_buckets.h"
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "util_script.h"
+
+#include "mod_proxy.h"
+#include "scgi.h"
+
+
+#define SCHEME "scgi"
+#define PROXY_FUNCTION "SCGI"
+#define SCGI_MAGIC "SCGI"
+#define SCGI_PROTOCOL_VERSION "1"
+
+/* just protect from typos */
+#define CONTENT_LENGTH "CONTENT_LENGTH"
+#define GATEWAY_INTERFACE "GATEWAY_INTERFACE"
+
+module AP_MODULE_DECLARE_DATA proxy_scgi_module;
+
+
+typedef enum {
+ scgi_internal_redirect,
+ scgi_sendfile
+} scgi_request_type;
+
+typedef struct {
+ const char *location; /* target URL */
+ scgi_request_type type; /* type of request */
+} scgi_request_config;
+
+const char *scgi_sendfile_off = "off";
+const char *scgi_sendfile_on = "X-Sendfile";
+const char *scgi_internal_redirect_off = "off";
+const char *scgi_internal_redirect_on = "Location";
+
+typedef struct {
+ const char *sendfile;
+ const char *internal_redirect;
+} scgi_config;
+
+
+/*
+ * We create our own bucket type, which is actually derived (c&p) from the
+ * socket bucket.
+ * Maybe some time this should be made more abstract (like passing an
+ * interception function to read or something) and go into the ap_ or
+ * even apr_ namespace.
+ */
+
+typedef struct {
+ apr_socket_t *sock;
+ apr_off_t *counter;
+} socket_ex_data;
+
+static apr_bucket *bucket_socket_ex_create(socket_ex_data *data,
+ apr_bucket_alloc_t *list);
+
+
+static apr_status_t bucket_socket_ex_read(apr_bucket *a, const char **str,
+ apr_size_t *len,
+ apr_read_type_e block)
+{
+ socket_ex_data *data = a->data;
+ apr_socket_t *p = data->sock;
+ char *buf;
+ apr_status_t rv;
+ apr_interval_time_t timeout;
+
+ if (block == APR_NONBLOCK_READ) {
+ apr_socket_timeout_get(p, &timeout);
+ apr_socket_timeout_set(p, 0);
+ }
+
+ *str = NULL;
+ *len = APR_BUCKET_BUFF_SIZE;
+ buf = apr_bucket_alloc(*len, a->list);
+
+ rv = apr_socket_recv(p, buf, len);
+
+ if (block == APR_NONBLOCK_READ) {
+ apr_socket_timeout_set(p, timeout);
+ }
+
+ if (rv != APR_SUCCESS && rv != APR_EOF) {
+ apr_bucket_free(buf);
+ return rv;
+ }
+
+ if (*len > 0) {
+ apr_bucket_heap *h;
+
+ /* count for stats */
+ *data->counter += *len;
+
+ /* Change the current bucket to refer to what we read */
+ a = apr_bucket_heap_make(a, buf, *len, apr_bucket_free);
+ h = a->data;
+ h->alloc_len = APR_BUCKET_BUFF_SIZE; /* note the real buffer size */
+ *str = buf;
+ APR_BUCKET_INSERT_AFTER(a, bucket_socket_ex_create(data, a->list));
+ }
+ else {
+ apr_bucket_free(buf);
+ a = apr_bucket_immortal_make(a, "", 0);
+ *str = a->data;
+ }
+ return APR_SUCCESS;
+}
+
+static const apr_bucket_type_t bucket_type_socket_ex = {
+ "SOCKET_EX", 5, APR_BUCKET_DATA,
+ apr_bucket_destroy_noop,
+ bucket_socket_ex_read,
+ apr_bucket_setaside_notimpl,
+ apr_bucket_split_notimpl,
+ apr_bucket_copy_notimpl
+};
+
+static apr_bucket *bucket_socket_ex_make(apr_bucket *b, socket_ex_data *data)
+{
+ b->type = &bucket_type_socket_ex;
+ b->length = (apr_size_t)(-1);
+ b->start = -1;
+ b->data = data;
+ return b;
+}
+
+static apr_bucket *bucket_socket_ex_create(socket_ex_data *data,
+ apr_bucket_alloc_t *list)
+{
+ apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
+
+ APR_BUCKET_INIT(b);
+ b->free = apr_bucket_free;
+ b->list = list;
+ return bucket_socket_ex_make(b, data);
+}
+
+
+/*
+ * Canonicalize scgi-like URLs.
+ */
+static int scgi_canon(request_rec *r, char *url)
+{
+ char *host, sport[sizeof(":65535")];
+ const char *err, *path;
+ apr_port_t port, def_port;
+
+ if (ap_cstr_casecmpn(url, SCHEME "://", sizeof(SCHEME) + 2)) {
+ return DECLINED;
+ }
+ url += sizeof(SCHEME); /* Keep slashes */
+
+ port = def_port = SCGI_DEF_PORT;
+
+ err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00857)
+ "error parsing URL %s: %s", url, err);
+ return HTTP_BAD_REQUEST;
+ }
+
+ if (port != def_port) {
+ apr_snprintf(sport, sizeof(sport), ":%u", port);
+ }
+ else {
+ sport[0] = '\0';
+ }
+
+ if (ap_strchr(host, ':')) { /* if literal IPv6 address */
+ host = apr_pstrcat(r->pool, "[", host, "]", NULL);
+ }
+
+ path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0,
+ r->proxyreq);
+ if (!path) {
+ return HTTP_BAD_REQUEST;
+ }
+
+ r->filename = apr_pstrcat(r->pool, "proxy:" SCHEME "://", host, sport, "/",
+ path, NULL);
+
+ if (apr_table_get(r->subprocess_env, "proxy-scgi-pathinfo")) {
+ r->path_info = apr_pstrcat(r->pool, "/", path, NULL);
+ }
+
+ return OK;
+}
+
+
+/*
+ * Send a block of data, ensure, everything is sent
+ */
+static int sendall(proxy_conn_rec *conn, const char *buf, apr_size_t length,
+ request_rec *r)
+{
+ apr_status_t rv;
+ apr_size_t written;
+
+ while (length > 0) {
+ written = length;
+ if ((rv = apr_socket_send(conn->sock, buf, &written)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00858)
+ "sending data to %s:%u failed",
+ conn->hostname, conn->port);
+ return HTTP_SERVICE_UNAVAILABLE;
+ }
+
+ /* count for stats */
+ conn->worker->s->transferred += written;
+ buf += written;
+ length -= written;
+ }
+
+ return OK;
+}
+
+
+/*
+ * Send SCGI header block
+ */
+static int send_headers(request_rec *r, proxy_conn_rec *conn)
+{
+ char *buf, *cp, *bodylen;
+ const char *ns_len;
+ const apr_array_header_t *env_table;
+ const apr_table_entry_t *env;
+ int j;
+ apr_size_t len, bodylen_size;
+ apr_size_t headerlen = sizeof(CONTENT_LENGTH)
+ + sizeof(SCGI_MAGIC)
+ + sizeof(SCGI_PROTOCOL_VERSION);
+
+ ap_add_common_vars(r);
+ ap_add_cgi_vars(r);
+
+ /*
+ * The header blob basically takes the environment and concatenates
+ * keys and values using 0 bytes. There are special treatments here:
+ * - GATEWAY_INTERFACE and SCGI_MAGIC are dropped
+ * - CONTENT_LENGTH is always set and must be sent as the very first
+ * variable
+ *
+ * Additionally it's wrapped into a so-called netstring (see SCGI spec)
+ */
+ env_table = apr_table_elts(r->subprocess_env);
+ env = (apr_table_entry_t *)env_table->elts;
+ for (j = 0; j < env_table->nelts; ++j) {
+ if ( (!strcmp(env[j].key, GATEWAY_INTERFACE))
+ || (!strcmp(env[j].key, CONTENT_LENGTH))
+ || (!strcmp(env[j].key, SCGI_MAGIC))) {
+ continue;
+ }
+ headerlen += strlen(env[j].key) + strlen(env[j].val) + 2;
+ }
+ bodylen = apr_psprintf(r->pool, "%" APR_OFF_T_FMT, r->remaining);
+ bodylen_size = strlen(bodylen) + 1;
+ headerlen += bodylen_size;
+
+ ns_len = apr_psprintf(r->pool, "%" APR_SIZE_T_FMT ":", headerlen);
+ len = strlen(ns_len);
+ headerlen += len + 1; /* 1 == , */
+ cp = buf = apr_palloc(r->pool, headerlen);
+ memcpy(cp, ns_len, len);
+ cp += len;
+
+ memcpy(cp, CONTENT_LENGTH, sizeof(CONTENT_LENGTH));
+ cp += sizeof(CONTENT_LENGTH);
+ memcpy(cp, bodylen, bodylen_size);
+ cp += bodylen_size;
+ memcpy(cp, SCGI_MAGIC, sizeof(SCGI_MAGIC));
+ cp += sizeof(SCGI_MAGIC);
+ memcpy(cp, SCGI_PROTOCOL_VERSION, sizeof(SCGI_PROTOCOL_VERSION));
+ cp += sizeof(SCGI_PROTOCOL_VERSION);
+
+ for (j = 0; j < env_table->nelts; ++j) {
+ if ( (!strcmp(env[j].key, GATEWAY_INTERFACE))
+ || (!strcmp(env[j].key, CONTENT_LENGTH))
+ || (!strcmp(env[j].key, SCGI_MAGIC))) {
+ continue;
+ }
+ len = strlen(env[j].key) + 1;
+ memcpy(cp, env[j].key, len);
+ cp += len;
+ len = strlen(env[j].val) + 1;
+ memcpy(cp, env[j].val, len);
+ cp += len;
+ }
+ *cp++ = ',';
+
+ return sendall(conn, buf, headerlen, r);
+}
+
+
+/*
+ * Send request body (if any)
+ */
+static int send_request_body(request_rec *r, proxy_conn_rec *conn)
+{
+ if (ap_should_client_block(r)) {
+ char *buf = apr_palloc(r->pool, AP_IOBUFSIZE);
+ int status;
+ long readlen;
+
+ readlen = ap_get_client_block(r, buf, AP_IOBUFSIZE);
+ while (readlen > 0) {
+ status = sendall(conn, buf, (apr_size_t)readlen, r);
+ if (status != OK) {
+ return HTTP_SERVICE_UNAVAILABLE;
+ }
+ readlen = ap_get_client_block(r, buf, AP_IOBUFSIZE);
+ }
+ if (readlen == -1) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00859)
+ "receiving request body failed");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ return OK;
+}
+
+
+/*
+ * Fetch response from backend and pass back to the front
+ */
+static int pass_response(request_rec *r, proxy_conn_rec *conn)
+{
+ apr_bucket_brigade *bb;
+ apr_bucket *b;
+ const char *location;
+ scgi_config *conf;
+ socket_ex_data *sock_data;
+ int status;
+
+ sock_data = apr_palloc(r->pool, sizeof(*sock_data));
+ sock_data->sock = conn->sock;
+ sock_data->counter = &conn->worker->s->read;
+
+ bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+ b = bucket_socket_ex_create(sock_data, r->connection->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ b = apr_bucket_eos_create(r->connection->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+
+ status = ap_scan_script_header_err_brigade_ex(r, bb, NULL,
+ APLOG_MODULE_INDEX);
+ if (status != OK) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00860)
+ "error reading response headers from %s:%u",
+ conn->hostname, conn->port);
+ r->status_line = NULL;
+ apr_brigade_destroy(bb);
+ return status;
+ }
+
+ conf = ap_get_module_config(r->per_dir_config, &proxy_scgi_module);
+ if (conf->sendfile && conf->sendfile != scgi_sendfile_off) {
+ short err = 1;
+
+ location = apr_table_get(r->err_headers_out, conf->sendfile);
+ if (!location) {
+ err = 0;
+ location = apr_table_get(r->headers_out, conf->sendfile);
+ }
+ if (location) {
+ scgi_request_config *req_conf = apr_palloc(r->pool,
+ sizeof(*req_conf));
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00861)
+ "Found %s: %s - preparing subrequest.",
+ conf->sendfile, location);
+
+ if (err) {
+ apr_table_unset(r->err_headers_out, conf->sendfile);
+ }
+ else {
+ apr_table_unset(r->headers_out, conf->sendfile);
+ }
+ req_conf->location = location;
+ req_conf->type = scgi_sendfile;
+ ap_set_module_config(r->request_config, &proxy_scgi_module,
+ req_conf);
+ apr_brigade_destroy(bb);
+ return OK;
+ }
+ }
+
+ if (r->status == HTTP_OK
+ && (!conf->internal_redirect /* default === On */
+ || conf->internal_redirect != scgi_internal_redirect_off)) {
+ short err = 1;
+ const char *location_header = conf->internal_redirect ?
+ conf->internal_redirect : scgi_internal_redirect_on;
+
+ location = apr_table_get(r->err_headers_out, location_header);
+ if (!location) {
+ err = 0;
+ location = apr_table_get(r->headers_out, location_header);
+ }
+ if (location && *location == '/') {
+ scgi_request_config *req_conf = apr_palloc(r->pool,
+ sizeof(*req_conf));
+ if (ap_cstr_casecmp(location_header, "Location")) {
+ if (err) {
+ apr_table_unset(r->err_headers_out, location_header);
+ }
+ else {
+ apr_table_unset(r->headers_out, location_header);
+ }
+ }
+ req_conf->location = location;
+ req_conf->type = scgi_internal_redirect;
+ ap_set_module_config(r->request_config, &proxy_scgi_module,
+ req_conf);
+ apr_brigade_destroy(bb);
+ return OK;
+ }
+ }
+
+ if (ap_pass_brigade(r->output_filters, bb)) {
+ return AP_FILTER_ERROR;
+ }
+
+ return OK;
+}
+
+/*
+ * Internal redirect / subrequest handler, working on request_status hook
+ */
+static int scgi_request_status(int *status, request_rec *r)
+{
+ scgi_request_config *req_conf;
+
+ if ( (*status == OK)
+ && (req_conf = ap_get_module_config(r->request_config,
+ &proxy_scgi_module))) {
+ switch (req_conf->type) {
+ case scgi_internal_redirect:
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00862)
+ "Internal redirect to %s", req_conf->location);
+
+ r->status_line = NULL;
+ if (r->method_number != M_GET) {
+ /* keep HEAD, which is passed around as M_GET, too */
+ r->method = "GET";
+ r->method_number = M_GET;
+ }
+ apr_table_unset(r->headers_in, "Content-Length");
+ ap_internal_redirect_handler(req_conf->location, r);
+ return OK;
+ /* break; */
+
+ case scgi_sendfile:
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00863)
+ "File subrequest to %s", req_conf->location);
+ do {
+ request_rec *rr;
+
+ rr = ap_sub_req_lookup_file(req_conf->location, r,
+ r->output_filters);
+ if (rr->status == HTTP_OK && rr->finfo.filetype != APR_NOFILE) {
+ /*
+ * We don't touch Content-Length here. It might be
+ * borked (there's plenty of room for a race condition).
+ * Either the backend sets it or it's gonna be chunked.
+ */
+ ap_run_sub_req(rr);
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00864)
+ "Subrequest to file '%s' not possible. "
+ "(rr->status=%d, rr->finfo.filetype=%d)",
+ req_conf->location, rr->status,
+ rr->finfo.filetype);
+ *status = HTTP_INTERNAL_SERVER_ERROR;
+ return *status;
+ }
+ } while (0);
+
+ return OK;
+ /* break; */
+ }
+ }
+
+ return DECLINED;
+}
+
+
+/*
+ * This handles scgi:(dest) URLs
+ */
+static int scgi_handler(request_rec *r, proxy_worker *worker,
+ proxy_server_conf *conf, char *url,
+ const char *proxyname, apr_port_t proxyport)
+{
+ int status;
+ proxy_conn_rec *backend = NULL;
+ apr_pool_t *p = r->pool;
+ apr_uri_t *uri;
+ char dummy;
+
+ if (ap_cstr_casecmpn(url, SCHEME "://", sizeof(SCHEME) + 2)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00865)
+ "declining URL %s", url);
+ return DECLINED;
+ }
+
+ /* Create space for state information */
+ status = ap_proxy_acquire_connection(PROXY_FUNCTION, &backend, worker,
+ r->server);
+ if (status != OK) {
+ goto cleanup;
+ }
+ backend->is_ssl = 0;
+
+ /* Step One: Determine Who To Connect To */
+ uri = apr_palloc(p, sizeof(*uri));
+ status = ap_proxy_determine_connection(p, r, conf, worker, backend,
+ uri, &url, proxyname, proxyport,
+ &dummy, 1);
+ if (status != OK) {
+ goto cleanup;
+ }
+
+ /* Step Two: Make the Connection */
+ if (ap_proxy_connect_backend(PROXY_FUNCTION, backend, worker, r->server)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00866)
+ "failed to make connection to backend: %s:%u",
+ backend->hostname, backend->port);
+ status = HTTP_SERVICE_UNAVAILABLE;
+ goto cleanup;
+ }
+
+ /* Step Three: Process the Request */
+ if ( ((status = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR)) != OK)
+ || ((status = send_headers(r, backend)) != OK)
+ || ((status = send_request_body(r, backend)) != OK)
+ || ((status = pass_response(r, backend)) != OK)) {
+ goto cleanup;
+ }
+
+cleanup:
+ if (backend) {
+ backend->close = 1; /* always close the socket */
+ ap_proxy_release_connection(PROXY_FUNCTION, backend, r->server);
+ }
+ return status;
+}
+
+
+static void *create_scgi_config(apr_pool_t *p, char *dummy)
+{
+ scgi_config *conf=apr_palloc(p, sizeof(*conf));
+
+ conf->sendfile = NULL; /* === default (off) */
+ conf->internal_redirect = NULL; /* === default (on) */
+
+ return conf;
+}
+
+
+static void *merge_scgi_config(apr_pool_t *p, void *base_, void *add_)
+{
+ scgi_config *base=base_, *add=add_, *conf=apr_palloc(p, sizeof(*conf));
+
+ conf->sendfile = add->sendfile ? add->sendfile: base->sendfile;
+ conf->internal_redirect = add->internal_redirect
+ ? add->internal_redirect
+ : base->internal_redirect;
+ return conf;
+}
+
+
+static const char *scgi_set_send_file(cmd_parms *cmd, void *mconfig,
+ const char *arg)
+{
+ scgi_config *conf=mconfig;
+
+ if (!strcasecmp(arg, "Off")) {
+ conf->sendfile = scgi_sendfile_off;
+ }
+ else if (!strcasecmp(arg, "On")) {
+ conf->sendfile = scgi_sendfile_on;
+ }
+ else {
+ conf->sendfile = arg;
+ }
+ return NULL;
+}
+
+
+static const char *scgi_set_internal_redirect(cmd_parms *cmd, void *mconfig,
+ const char *arg)
+{
+ scgi_config *conf = mconfig;
+
+ if (!strcasecmp(arg, "Off")) {
+ conf->internal_redirect = scgi_internal_redirect_off;
+ }
+ else if (!strcasecmp(arg, "On")) {
+ conf->internal_redirect = scgi_internal_redirect_on;
+ }
+ else {
+ conf->internal_redirect = arg;
+ }
+ return NULL;
+}
+
+
+static const command_rec scgi_cmds[] =
+{
+ AP_INIT_TAKE1("ProxySCGISendfile", scgi_set_send_file, NULL,
+ RSRC_CONF|ACCESS_CONF,
+ "The name of the X-Sendfile pseudo response header or "
+ "On or Off"),
+ AP_INIT_TAKE1("ProxySCGIInternalRedirect", scgi_set_internal_redirect, NULL,
+ RSRC_CONF|ACCESS_CONF,
+ "The name of the pseudo response header or On or Off"),
+ {NULL}
+};
+
+
+static void register_hooks(apr_pool_t *p)
+{
+ proxy_hook_scheme_handler(scgi_handler, NULL, NULL, APR_HOOK_FIRST);
+ proxy_hook_canon_handler(scgi_canon, NULL, NULL, APR_HOOK_FIRST);
+ APR_OPTIONAL_HOOK(proxy, request_status, scgi_request_status, NULL, NULL,
+ APR_HOOK_MIDDLE);
+}
+
+
+AP_DECLARE_MODULE(proxy_scgi) = {
+ STANDARD20_MODULE_STUFF,
+ create_scgi_config, /* create per-directory config structure */
+ merge_scgi_config, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ scgi_cmds, /* command table */
+ register_hooks /* register hooks */
+};
diff --git a/modules/proxy/mod_proxy_scgi.dep b/modules/proxy/mod_proxy_scgi.dep
new file mode 100644
index 0000000..1b20db8
--- /dev/null
+++ b/modules/proxy/mod_proxy_scgi.dep
@@ -0,0 +1,75 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_proxy_scgi.mak
+
+.\mod_proxy_scgi.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_slotmem.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_date.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_reslist.h"\
+ "..\..\srclib\apr-util\include\apr_strmatch.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_proxy.h"\
+ ".\scgi.h"\
+
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
diff --git a/modules/proxy/mod_proxy_scgi.dsp b/modules/proxy/mod_proxy_scgi.dsp
new file mode 100644
index 0000000..8aee15e
--- /dev/null
+++ b/modules/proxy/mod_proxy_scgi.dsp
@@ -0,0 +1,123 @@
+# Microsoft Developer Studio Project File - Name="mod_proxy_scgi" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_proxy_scgi - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_scgi.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_scgi.mak" CFG="mod_proxy_scgi - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy_scgi - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy_scgi - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_proxy_scgi - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_proxy_scgi_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_proxy_scgi.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_proxy_scgi.so" /d LONG_NAME="proxy_scgi_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:".\Release\mod_proxy_scgi.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_scgi.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_proxy_scgi.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_scgi.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_proxy_scgi.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_proxy_scgi - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_proxy_scgi_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_proxy_scgi.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_proxy_scgi.so" /d LONG_NAME="proxy_scgi_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_proxy_scgi.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_scgi.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_proxy_scgi.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_scgi.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_proxy_scgi.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_proxy_scgi - Win32 Release"
+# Name "mod_proxy_scgi - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\mod_proxy_scgi.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter ".h"
+# Begin Source File
+
+SOURCE=.\mod_proxy.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/proxy/mod_proxy_scgi.mak b/modules/proxy/mod_proxy_scgi.mak
new file mode 100644
index 0000000..7ffb248
--- /dev/null
+++ b/modules/proxy/mod_proxy_scgi.mak
@@ -0,0 +1,380 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_proxy_scgi.dsp
+!IF "$(CFG)" == ""
+CFG=mod_proxy_scgi - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_proxy_scgi - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_proxy_scgi - Win32 Release" && "$(CFG)" != "mod_proxy_scgi - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_scgi.mak" CFG="mod_proxy_scgi - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy_scgi - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy_scgi - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_scgi - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_proxy_scgi.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_proxy - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_proxy_scgi.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_proxy - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_proxy_scgi.obj"
+ -@erase "$(INTDIR)\mod_proxy_scgi.res"
+ -@erase "$(INTDIR)\mod_proxy_scgi_src.idb"
+ -@erase "$(INTDIR)\mod_proxy_scgi_src.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_scgi.exp"
+ -@erase "$(OUTDIR)\mod_proxy_scgi.lib"
+ -@erase "$(OUTDIR)\mod_proxy_scgi.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_scgi.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_proxy_scgi_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_proxy_scgi.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_proxy_scgi.so" /d LONG_NAME="proxy_scgi_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_proxy_scgi.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_proxy_scgi.pdb" /debug /out:"$(OUTDIR)\mod_proxy_scgi.so" /implib:"$(OUTDIR)\mod_proxy_scgi.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_scgi.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_proxy_scgi.obj" \
+ "$(INTDIR)\mod_proxy_scgi.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib" \
+ "$(OUTDIR)\mod_proxy.lib"
+
+"$(OUTDIR)\mod_proxy_scgi.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_proxy_scgi.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_proxy_scgi.so"
+ if exist .\Release\mod_proxy_scgi.so.manifest mt.exe -manifest .\Release\mod_proxy_scgi.so.manifest -outputresource:.\Release\mod_proxy_scgi.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_proxy_scgi - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_proxy_scgi.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_proxy - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_proxy_scgi.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_proxy - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_proxy_scgi.obj"
+ -@erase "$(INTDIR)\mod_proxy_scgi.res"
+ -@erase "$(INTDIR)\mod_proxy_scgi_src.idb"
+ -@erase "$(INTDIR)\mod_proxy_scgi_src.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_scgi.exp"
+ -@erase "$(OUTDIR)\mod_proxy_scgi.lib"
+ -@erase "$(OUTDIR)\mod_proxy_scgi.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_scgi.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_proxy_scgi_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_proxy_scgi.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_proxy_scgi.so" /d LONG_NAME="proxy_scgi_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_proxy_scgi.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_proxy_scgi.pdb" /debug /out:"$(OUTDIR)\mod_proxy_scgi.so" /implib:"$(OUTDIR)\mod_proxy_scgi.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_scgi.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_proxy_scgi.obj" \
+ "$(INTDIR)\mod_proxy_scgi.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib" \
+ "$(OUTDIR)\mod_proxy.lib"
+
+"$(OUTDIR)\mod_proxy_scgi.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_proxy_scgi.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_proxy_scgi.so"
+ if exist .\Debug\mod_proxy_scgi.so.manifest mt.exe -manifest .\Debug\mod_proxy_scgi.so.manifest -outputresource:.\Debug\mod_proxy_scgi.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_proxy_scgi.dep")
+!INCLUDE "mod_proxy_scgi.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_proxy_scgi.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_proxy_scgi - Win32 Release" || "$(CFG)" == "mod_proxy_scgi - Win32 Debug"
+SOURCE=.\mod_proxy_scgi.c
+
+"$(INTDIR)\mod_proxy_scgi.obj" : $(SOURCE) "$(INTDIR)"
+
+
+!IF "$(CFG)" == "mod_proxy_scgi - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\proxy"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_scgi - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\proxy"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_scgi - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\proxy"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_scgi - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\proxy"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_scgi - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\proxy"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_scgi - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\proxy"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_scgi - Win32 Release"
+
+"mod_proxy - Win32 Release" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Release"
+ cd "."
+
+"mod_proxy - Win32 ReleaseCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Release" RECURSE=1 CLEAN
+ cd "."
+
+!ELSEIF "$(CFG)" == "mod_proxy_scgi - Win32 Debug"
+
+"mod_proxy - Win32 Debug" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Debug"
+ cd "."
+
+"mod_proxy - Win32 DebugCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Debug" RECURSE=1 CLEAN
+ cd "."
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_proxy_scgi - Win32 Release"
+
+
+"$(INTDIR)\mod_proxy_scgi.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_proxy_scgi.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_proxy_scgi.so" /d LONG_NAME="proxy_scgi_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_proxy_scgi - Win32 Debug"
+
+
+"$(INTDIR)\mod_proxy_scgi.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_proxy_scgi.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_proxy_scgi.so" /d LONG_NAME="proxy_scgi_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+
+!ENDIF
+
diff --git a/modules/proxy/mod_proxy_uwsgi.c b/modules/proxy/mod_proxy_uwsgi.c
new file mode 100644
index 0000000..92e153c
--- /dev/null
+++ b/modules/proxy/mod_proxy_uwsgi.c
@@ -0,0 +1,576 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+
+*** mod_proxy_uwsgi ***
+
+Copyright 2009-2017 Unbit S.a.s. <info@unbit.it>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+*/
+
+#define APR_WANT_MEMFUNC
+#define APR_WANT_STRFUNC
+#include "apr_strings.h"
+#include "apr_hooks.h"
+#include "apr_optional_hooks.h"
+#include "apr_buckets.h"
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "util_script.h"
+
+#include "mod_proxy.h"
+
+
+#define UWSGI_SCHEME "uwsgi"
+#define UWSGI_DEFAULT_PORT 3031
+
+module AP_MODULE_DECLARE_DATA proxy_uwsgi_module;
+
+
+static int uwsgi_canon(request_rec *r, char *url)
+{
+ char *host, sport[sizeof(":65535")];
+ const char *err, *path;
+ apr_port_t port = UWSGI_DEFAULT_PORT;
+
+ if (ap_cstr_casecmpn(url, UWSGI_SCHEME "://", sizeof(UWSGI_SCHEME) + 2)) {
+ return DECLINED;
+ }
+ url += sizeof(UWSGI_SCHEME); /* Keep slashes */
+
+ err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10097)
+ "error parsing URL %s: %s", url, err);
+ return HTTP_BAD_REQUEST;
+ }
+
+ if (port != UWSGI_DEFAULT_PORT)
+ apr_snprintf(sport, sizeof(sport), ":%u", port);
+ else
+ sport[0] = '\0';
+
+ if (ap_strchr(host, ':')) { /* if literal IPv6 address */
+ host = apr_pstrcat(r->pool, "[", host, "]", NULL);
+ }
+
+ path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0,
+ r->proxyreq);
+ if (!path) {
+ return HTTP_BAD_REQUEST;
+ }
+
+ r->filename =
+ apr_pstrcat(r->pool, "proxy:" UWSGI_SCHEME "://", host, sport, "/",
+ path, NULL);
+
+ return OK;
+}
+
+
+static int uwsgi_send(proxy_conn_rec * conn, const char *buf,
+ apr_size_t length, request_rec *r)
+{
+ apr_status_t rv;
+ apr_size_t written;
+
+ while (length > 0) {
+ written = length;
+ if ((rv = apr_socket_send(conn->sock, buf, &written)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10098)
+ "sending data to %s:%u failed",
+ conn->hostname, conn->port);
+ return HTTP_SERVICE_UNAVAILABLE;
+ }
+
+ /* count for stats */
+ conn->worker->s->transferred += written;
+ buf += written;
+ length -= written;
+ }
+
+ return OK;
+}
+
+
+/*
+ * Send uwsgi header block
+ */
+static int uwsgi_send_headers(request_rec *r, proxy_conn_rec * conn)
+{
+ char *buf, *ptr;
+
+ const apr_array_header_t *env_table;
+ const apr_table_entry_t *env;
+
+ int j;
+
+ apr_size_t headerlen = 4;
+ apr_size_t pktsize, keylen, vallen;
+ const char *script_name;
+ const char *path_info;
+ const char *auth;
+
+ ap_add_common_vars(r);
+ ap_add_cgi_vars(r);
+
+ /*
+ this is not a security problem (in Linux) as uWSGI destroy the env memory area readable in /proc
+ and generally if you host untrusted apps in your server and allows them to read others uid /proc/<pid>
+ files you have higher problems...
+ */
+ auth = apr_table_get(r->headers_in, "Authorization");
+ if (auth) {
+ apr_table_setn(r->subprocess_env, "HTTP_AUTHORIZATION", auth);
+ }
+
+ script_name = apr_table_get(r->subprocess_env, "SCRIPT_NAME");
+ path_info = apr_table_get(r->subprocess_env, "PATH_INFO");
+
+ if (script_name && path_info) {
+ if (strcmp(path_info, "/")) {
+ apr_table_set(r->subprocess_env, "SCRIPT_NAME",
+ apr_pstrndup(r->pool, script_name,
+ strlen(script_name) -
+ strlen(path_info)));
+ }
+ else {
+ if (!strcmp(script_name, "/")) {
+ apr_table_setn(r->subprocess_env, "SCRIPT_NAME", "");
+ }
+ }
+ }
+
+ env_table = apr_table_elts(r->subprocess_env);
+ env = (apr_table_entry_t *) env_table->elts;
+
+ for (j = 0; j < env_table->nelts; ++j) {
+ headerlen += 2 + strlen(env[j].key) + 2 + (env[j].val ? strlen(env[j].val) : 0);
+ }
+
+ pktsize = headerlen - 4;
+ if (pktsize > APR_UINT16_MAX) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10259)
+ "can't send headers to %s:%u: packet size too "
+ "large (%" APR_SIZE_T_FMT ")",
+ conn->hostname, conn->port, pktsize);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ptr = buf = apr_palloc(r->pool, headerlen);
+
+ ptr += 4;
+
+ for (j = 0; j < env_table->nelts; ++j) {
+ keylen = strlen(env[j].key);
+ *ptr++ = (apr_byte_t) (keylen & 0xff);
+ *ptr++ = (apr_byte_t) ((keylen >> 8) & 0xff);
+ memcpy(ptr, env[j].key, keylen);
+ ptr += keylen;
+
+ vallen = env[j].val ? strlen(env[j].val) : 0;
+ *ptr++ = (apr_byte_t) (vallen & 0xff);
+ *ptr++ = (apr_byte_t) ((vallen >> 8) & 0xff);
+ if (env[j].val) {
+ memcpy(ptr, env[j].val, vallen);
+ }
+ ptr += vallen;
+ }
+
+ buf[0] = 0;
+ buf[1] = (apr_byte_t) (pktsize & 0xff);
+ buf[2] = (apr_byte_t) ((pktsize >> 8) & 0xff);
+ buf[3] = 0;
+
+ return uwsgi_send(conn, buf, headerlen, r);
+}
+
+
+static int uwsgi_send_body(request_rec *r, proxy_conn_rec * conn)
+{
+ if (ap_should_client_block(r)) {
+ char *buf = apr_palloc(r->pool, AP_IOBUFSIZE);
+ int status;
+ long readlen;
+
+ readlen = ap_get_client_block(r, buf, AP_IOBUFSIZE);
+ while (readlen > 0) {
+ status = uwsgi_send(conn, buf, (apr_size_t)readlen, r);
+ if (status != OK) {
+ return HTTP_SERVICE_UNAVAILABLE;
+ }
+ readlen = ap_get_client_block(r, buf, AP_IOBUFSIZE);
+ }
+ if (readlen == -1) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10099)
+ "receiving request body failed");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ return OK;
+}
+
+static request_rec *make_fake_req(conn_rec *c, request_rec *r)
+{
+ apr_pool_t *pool;
+ request_rec *rp;
+
+ apr_pool_create(&pool, c->pool);
+ apr_pool_tag(pool, "proxy_uwsgi_rp");
+
+ rp = apr_pcalloc(pool, sizeof(*r));
+
+ rp->pool = pool;
+ rp->status = HTTP_OK;
+
+ rp->headers_in = apr_table_make(pool, 50);
+ rp->subprocess_env = apr_table_make(pool, 50);
+ rp->headers_out = apr_table_make(pool, 12);
+ rp->err_headers_out = apr_table_make(pool, 5);
+ rp->notes = apr_table_make(pool, 5);
+
+ rp->server = r->server;
+ rp->log = r->log;
+ rp->proxyreq = r->proxyreq;
+ rp->request_time = r->request_time;
+ rp->connection = c;
+ rp->output_filters = c->output_filters;
+ rp->input_filters = c->input_filters;
+ rp->proto_output_filters = c->output_filters;
+ rp->proto_input_filters = c->input_filters;
+ rp->useragent_ip = c->client_ip;
+ rp->useragent_addr = c->client_addr;
+
+ rp->request_config = ap_create_request_config(pool);
+ proxy_run_create_req(r, rp);
+
+ return rp;
+}
+
+static int uwsgi_response(request_rec *r, proxy_conn_rec * backend,
+ proxy_server_conf * conf)
+{
+
+ char buffer[HUGE_STRING_LEN];
+ const char *buf;
+ char *value, *end;
+ char keepchar;
+ int len;
+ int backend_broke = 0;
+ int status_start;
+ int status_end;
+ int finish = 0;
+ conn_rec *c = r->connection;
+ apr_off_t readbytes;
+ apr_status_t rv;
+ apr_bucket *e;
+ apr_read_type_e mode = APR_NONBLOCK_READ;
+ apr_bucket_brigade *pass_bb;
+ apr_bucket_brigade *bb;
+ proxy_dir_conf *dconf;
+
+ request_rec *rp = make_fake_req(backend->connection, r);
+ rp->proxyreq = PROXYREQ_RESPONSE;
+
+ bb = apr_brigade_create(r->pool, c->bucket_alloc);
+ pass_bb = apr_brigade_create(r->pool, c->bucket_alloc);
+
+ len = ap_getline(buffer, sizeof(buffer), rp, 1);
+ if (len <= 0) {
+ /* invalid or empty */
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ backend->worker->s->read += len;
+ if ((apr_size_t)len >= sizeof(buffer)) {
+ /* too long */
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* Position of http status code */
+ if (apr_date_checkmask(buffer, "HTTP/#.# ###*")) {
+ status_start = 9;
+ }
+ else if (apr_date_checkmask(buffer, "HTTP/# ###*")) {
+ status_start = 7;
+ }
+ else {
+ /* not HTTP */
+ return HTTP_BAD_GATEWAY;
+ }
+ status_end = status_start + 3;
+
+ keepchar = buffer[status_end];
+ buffer[status_end] = '\0';
+ r->status = atoi(&buffer[status_start]);
+
+ if (keepchar != '\0') {
+ buffer[status_end] = keepchar;
+ }
+ else {
+ /* 2616 requires the space in Status-Line; the origin
+ * server may have sent one but ap_rgetline_core will
+ * have stripped it. */
+ buffer[status_end] = ' ';
+ buffer[status_end + 1] = '\0';
+ }
+ r->status_line = apr_pstrdup(r->pool, &buffer[status_start]);
+
+ /* parse headers */
+ while ((len = ap_getline(buffer, sizeof(buffer), rp, 1)) > 0) {
+ if ((apr_size_t)len >= sizeof(buffer)) {
+ /* too long */
+ len = -1;
+ break;
+ }
+ value = strchr(buffer, ':');
+ if (!value) {
+ /* invalid header */
+ len = -1;
+ break;
+ }
+ *value++ = '\0';
+ if (*ap_scan_http_token(buffer)) {
+ /* invalid name */
+ len = -1;
+ break;
+ }
+ while (apr_isspace(*value))
+ ++value;
+ for (end = &value[strlen(value) - 1];
+ end > value && apr_isspace(*end); --end)
+ *end = '\0';
+ if (*ap_scan_http_field_content(value)) {
+ /* invalid value */
+ len = -1;
+ break;
+ }
+ apr_table_add(r->headers_out, buffer, value);
+ }
+ if (len < 0) {
+ /* Reset headers, but not to NULL because things below the chain expect
+ * this to be non NULL e.g. the ap_content_length_filter.
+ */
+ r->headers_out = apr_table_make(r->pool, 1);
+ return HTTP_BAD_GATEWAY;
+ }
+
+ if ((buf = apr_table_get(r->headers_out, "Content-Type"))) {
+ ap_set_content_type(r, apr_pstrdup(r->pool, buf));
+ }
+
+ /* honor ProxyErrorOverride and ErrorDocument */
+#if AP_MODULE_MAGIC_AT_LEAST(20101106,0)
+ dconf =
+ ap_get_module_config(r->per_dir_config, &proxy_module);
+ if (ap_proxy_should_override(dconf, r->status)) {
+#else
+ if (ap_proxy_should_override(conf, r->status)) {
+#endif
+ int status = r->status;
+ r->status = HTTP_OK;
+ r->status_line = NULL;
+
+ apr_brigade_cleanup(bb);
+ apr_brigade_cleanup(pass_bb);
+
+ return status;
+ }
+
+ while (!finish) {
+ rv = ap_get_brigade(rp->input_filters, bb,
+ AP_MODE_READBYTES, mode, conf->io_buffer_size);
+ if (APR_STATUS_IS_EAGAIN(rv)
+ || (rv == APR_SUCCESS && APR_BRIGADE_EMPTY(bb))) {
+ e = apr_bucket_flush_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ if (ap_pass_brigade(r->output_filters, bb) || c->aborted) {
+ break;
+ }
+ apr_brigade_cleanup(bb);
+ mode = APR_BLOCK_READ;
+ continue;
+ }
+ else if (rv == APR_EOF) {
+ break;
+ }
+ else if (rv != APR_SUCCESS) {
+ ap_proxy_backend_broke(r, bb);
+ ap_pass_brigade(r->output_filters, bb);
+ backend_broke = 1;
+ break;
+ }
+
+ mode = APR_NONBLOCK_READ;
+ apr_brigade_length(bb, 0, &readbytes);
+ backend->worker->s->read += readbytes;
+
+ if (APR_BRIGADE_EMPTY(bb)) {
+ apr_brigade_cleanup(bb);
+ break;
+ }
+
+ ap_proxy_buckets_lifetime_transform(r, bb, pass_bb);
+
+ /* found the last brigade? */
+ if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb)))
+ finish = 1;
+
+ /* do not pass chunk if it is zero_sized */
+ apr_brigade_length(pass_bb, 0, &readbytes);
+
+ if ((readbytes > 0
+ && ap_pass_brigade(r->output_filters, pass_bb) != APR_SUCCESS)
+ || c->aborted) {
+ finish = 1;
+ }
+
+ apr_brigade_cleanup(bb);
+ apr_brigade_cleanup(pass_bb);
+ }
+
+ e = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ ap_pass_brigade(r->output_filters, bb);
+
+ apr_brigade_cleanup(bb);
+
+ if (c->aborted || backend_broke) {
+ return DONE;
+ }
+
+ return OK;
+}
+
+static int uwsgi_handler(request_rec *r, proxy_worker * worker,
+ proxy_server_conf * conf, char *url,
+ const char *proxyname, apr_port_t proxyport)
+{
+ int status;
+ proxy_conn_rec *backend = NULL;
+ apr_pool_t *p = r->pool;
+ char server_portstr[32];
+ char *u_path_info;
+ apr_uri_t *uri;
+
+ if (ap_cstr_casecmpn(url, UWSGI_SCHEME "://", sizeof(UWSGI_SCHEME) + 2)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "declining URL %s", url);
+ return DECLINED;
+ }
+
+ uri = apr_palloc(r->pool, sizeof(*uri));
+
+ /* ADD PATH_INFO (unescaped) */
+ u_path_info = ap_strchr(url + sizeof(UWSGI_SCHEME) + 2, '/');
+ if (!u_path_info) {
+ u_path_info = apr_pstrdup(r->pool, "/");
+ }
+ else if (ap_unescape_url(u_path_info) != OK) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10100)
+ "unable to decode uwsgi uri: %s", url);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ else {
+ /* Remove duplicate slashes at the beginning of PATH_INFO */
+ while (u_path_info[1] == '/') {
+ u_path_info++;
+ }
+ }
+ apr_table_add(r->subprocess_env, "PATH_INFO", u_path_info);
+
+ /* Create space for state information */
+ status = ap_proxy_acquire_connection(UWSGI_SCHEME, &backend, worker,
+ r->server);
+ if (status != OK) {
+ goto cleanup;
+ }
+ backend->is_ssl = 0;
+
+ /* Step One: Determine Who To Connect To */
+ status = ap_proxy_determine_connection(p, r, conf, worker, backend,
+ uri, &url, proxyname, proxyport,
+ server_portstr,
+ sizeof(server_portstr));
+ if (status != OK) {
+ goto cleanup;
+ }
+
+
+ /* Step Two: Make the Connection */
+ if (ap_proxy_connect_backend(UWSGI_SCHEME, backend, worker, r->server)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10101)
+ "failed to make connection to backend: %s:%u",
+ backend->hostname, backend->port);
+ status = HTTP_SERVICE_UNAVAILABLE;
+ goto cleanup;
+ }
+
+ /* Step Three: Create conn_rec */
+ if ((status = ap_proxy_connection_create(UWSGI_SCHEME, backend,
+ r->connection,
+ r->server)) != OK)
+ goto cleanup;
+
+ /* Step Four: Process the Request */
+ if (((status = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR)) != OK)
+ || ((status = uwsgi_send_headers(r, backend)) != OK)
+ || ((status = uwsgi_send_body(r, backend)) != OK)
+ || ((status = uwsgi_response(r, backend, conf)) != OK)) {
+ goto cleanup;
+ }
+
+ cleanup:
+ if (backend) {
+ backend->close = 1; /* always close the socket */
+ ap_proxy_release_connection(UWSGI_SCHEME, backend, r->server);
+ }
+ return status;
+}
+
+
+static void register_hooks(apr_pool_t * p)
+{
+ proxy_hook_scheme_handler(uwsgi_handler, NULL, NULL, APR_HOOK_FIRST);
+ proxy_hook_canon_handler(uwsgi_canon, NULL, NULL, APR_HOOK_FIRST);
+}
+
+
+module AP_MODULE_DECLARE_DATA proxy_uwsgi_module = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ NULL, /* command table */
+ register_hooks /* register hooks */
+};
diff --git a/modules/proxy/mod_proxy_uwsgi.dep b/modules/proxy/mod_proxy_uwsgi.dep
new file mode 100644
index 0000000..6513378
--- /dev/null
+++ b/modules/proxy/mod_proxy_uwsgi.dep
@@ -0,0 +1,75 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_proxy_scgi.mak
+
+.\mod_proxy_uwsgi.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_slotmem.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_date.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_reslist.h"\
+ "..\..\srclib\apr-util\include\apr_strmatch.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_proxy.h"\
+ ".\scgi.h"\
+
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
diff --git a/modules/proxy/mod_proxy_uwsgi.dsp b/modules/proxy/mod_proxy_uwsgi.dsp
new file mode 100644
index 0000000..00c3034
--- /dev/null
+++ b/modules/proxy/mod_proxy_uwsgi.dsp
@@ -0,0 +1,123 @@
+# Microsoft Developer Studio Project File - Name="mod_proxy_uwsgi" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_proxy_uwsgi - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_uwsgi.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_uwsgi.mak" CFG="mod_proxy_uwsgi - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy_uwsgi - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy_uwsgi - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_proxy_uwsgi - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_proxy_uwsgi_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_proxy_uwsgi.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_proxy_uwsgi.so" /d LONG_NAME="proxy_uwsgi_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:".\Release\mod_proxy_uwsgi.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_uwsgi.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_proxy_uwsgi.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_uwsgi.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_proxy_uwsgi.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_proxy_uwsgi - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_proxy_uwsgi_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_proxy_uwsgi.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_proxy_uwsgi.so" /d LONG_NAME="proxy_uwsgi_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_proxy_uwsgi.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_uwsgi.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_proxy_uwsgi.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_uwsgi.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_proxy_uwsgi.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_proxy_uwsgi - Win32 Release"
+# Name "mod_proxy_uwsgi - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\mod_proxy_uwsgi.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter ".h"
+# Begin Source File
+
+SOURCE=.\mod_proxy.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/proxy/mod_proxy_uwsgi.mak b/modules/proxy/mod_proxy_uwsgi.mak
new file mode 100644
index 0000000..6db95e5
--- /dev/null
+++ b/modules/proxy/mod_proxy_uwsgi.mak
@@ -0,0 +1,380 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_proxy_uwsgi.dsp
+!IF "$(CFG)" == ""
+CFG=mod_proxy_uwsgi - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_proxy_uwsgi - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_proxy_uwsgi - Win32 Release" && "$(CFG)" != "mod_proxy_uwsgi - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_uwsgi.mak" CFG="mod_proxy_uwsgi - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy_uwsgi - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy_uwsgi - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_uwsgi - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_proxy_uwsgi.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_proxy - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_proxy_uwsgi.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_proxy - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_proxy_uwsgi.obj"
+ -@erase "$(INTDIR)\mod_proxy_uwsgi.res"
+ -@erase "$(INTDIR)\mod_proxy_uwsgi_src.idb"
+ -@erase "$(INTDIR)\mod_proxy_uwsgi_src.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_uwsgi.exp"
+ -@erase "$(OUTDIR)\mod_proxy_uwsgi.lib"
+ -@erase "$(OUTDIR)\mod_proxy_uwsgi.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_uwsgi.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_proxy_uwsgi_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_proxy_uwsgi.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_proxy_uwsgi.so" /d LONG_NAME="proxy_uwsgi_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_proxy_uwsgi.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_proxy_uwsgi.pdb" /debug /out:"$(OUTDIR)\mod_proxy_uwsgi.so" /implib:"$(OUTDIR)\mod_proxy_uwsgi.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_uwsgi.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_proxy_uwsgi.obj" \
+ "$(INTDIR)\mod_proxy_uwsgi.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib" \
+ "$(OUTDIR)\mod_proxy.lib"
+
+"$(OUTDIR)\mod_proxy_uwsgi.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_proxy_uwsgi.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_proxy_uwsgi.so"
+ if exist .\Release\mod_proxy_uwsgi.so.manifest mt.exe -manifest .\Release\mod_proxy_uwsgi.so.manifest -outputresource:.\Release\mod_proxy_uwsgi.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_proxy_uwsgi - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_proxy_uwsgi.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_proxy - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_proxy_uwsgi.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_proxy - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_proxy_uwsgi.obj"
+ -@erase "$(INTDIR)\mod_proxy_uwsgi.res"
+ -@erase "$(INTDIR)\mod_proxy_uwsgi_src.idb"
+ -@erase "$(INTDIR)\mod_proxy_uwsgi_src.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_uwsgi.exp"
+ -@erase "$(OUTDIR)\mod_proxy_uwsgi.lib"
+ -@erase "$(OUTDIR)\mod_proxy_uwsgi.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_uwsgi.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_proxy_uwsgi_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_proxy_uwsgi.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_proxy_uwsgi.so" /d LONG_NAME="proxy_uwsgi_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_proxy_uwsgi.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_proxy_uwsgi.pdb" /debug /out:"$(OUTDIR)\mod_proxy_uwsgi.so" /implib:"$(OUTDIR)\mod_proxy_uwsgi.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_uwsgi.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_proxy_uwsgi.obj" \
+ "$(INTDIR)\mod_proxy_uwsgi.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib" \
+ "$(OUTDIR)\mod_proxy.lib"
+
+"$(OUTDIR)\mod_proxy_uwsgi.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_proxy_uwsgi.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_proxy_uwsgi.so"
+ if exist .\Debug\mod_proxy_uwsgi.so.manifest mt.exe -manifest .\Debug\mod_proxy_uwsgi.so.manifest -outputresource:.\Debug\mod_proxy_uwsgi.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_proxy_uwsgi.dep")
+!INCLUDE "mod_proxy_uwsgi.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_proxy_uwsgi.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_proxy_uwsgi - Win32 Release" || "$(CFG)" == "mod_proxy_uwsgi - Win32 Debug"
+SOURCE=.\mod_proxy_uwsgi.c
+
+"$(INTDIR)\mod_proxy_uwsgi.obj" : $(SOURCE) "$(INTDIR)"
+
+
+!IF "$(CFG)" == "mod_proxy_uwsgi - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\proxy"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_uwsgi - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\proxy"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_uwsgi - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\proxy"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_uwsgi - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\proxy"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_uwsgi - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\proxy"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_uwsgi - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\proxy"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_uwsgi - Win32 Release"
+
+"mod_proxy - Win32 Release" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Release"
+ cd "."
+
+"mod_proxy - Win32 ReleaseCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Release" RECURSE=1 CLEAN
+ cd "."
+
+!ELSEIF "$(CFG)" == "mod_proxy_uwsgi - Win32 Debug"
+
+"mod_proxy - Win32 Debug" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Debug"
+ cd "."
+
+"mod_proxy - Win32 DebugCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Debug" RECURSE=1 CLEAN
+ cd "."
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_proxy_uwsgi - Win32 Release"
+
+
+"$(INTDIR)\mod_proxy_uwsgi.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_proxy_uwsgi.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_proxy_uwsgi.so" /d LONG_NAME="proxy_uwsgi_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_proxy_uwsgi - Win32 Debug"
+
+
+"$(INTDIR)\mod_proxy_uwsgi.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_proxy_uwsgi.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_proxy_uwsgi.so" /d LONG_NAME="proxy_uwsgi_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+
+!ENDIF
+
diff --git a/modules/proxy/mod_proxy_wstunnel.c b/modules/proxy/mod_proxy_wstunnel.c
new file mode 100644
index 0000000..88f86a4
--- /dev/null
+++ b/modules/proxy/mod_proxy_wstunnel.c
@@ -0,0 +1,499 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mod_proxy.h"
+#include "http_config.h"
+
+module AP_MODULE_DECLARE_DATA proxy_wstunnel_module;
+
+typedef struct {
+ unsigned int fallback_to_proxy_http :1,
+ fallback_to_proxy_http_set :1;
+} proxyws_dir_conf;
+
+static int can_fallback_to_proxy_http;
+
+static int proxy_wstunnel_check_trans(request_rec *r, const char *url)
+{
+ proxyws_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
+ &proxy_wstunnel_module);
+
+ if (can_fallback_to_proxy_http && dconf->fallback_to_proxy_http) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "check_trans fallback");
+ return DECLINED;
+ }
+
+ if (ap_cstr_casecmpn(url, "ws:", 3) != 0
+ && ap_cstr_casecmpn(url, "wss:", 4) != 0) {
+ return DECLINED;
+ }
+
+ if (!apr_table_get(r->headers_in, "Upgrade")) {
+ /* No Upgrade, let mod_proxy_http handle it (for instance).
+ * Note: anything but OK/DECLINED will do (i.e. bypass wstunnel w/o
+ * aborting the request), HTTP_UPGRADE_REQUIRED is documentary...
+ */
+ return HTTP_UPGRADE_REQUIRED;
+ }
+
+ return OK;
+}
+
+/*
+ * Canonicalise http-like URLs.
+ * scheme is the scheme for the URL
+ * url is the URL starting with the first '/'
+ * def_port is the default port for this scheme.
+ */
+static int proxy_wstunnel_canon(request_rec *r, char *url)
+{
+ proxyws_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
+ &proxy_wstunnel_module);
+ char *host, *path, sport[7];
+ char *search = NULL;
+ const char *err;
+ char *scheme;
+ apr_port_t port, def_port;
+
+ if (can_fallback_to_proxy_http && dconf->fallback_to_proxy_http) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "canon fallback");
+ return DECLINED;
+ }
+
+ /* ap_port_of_scheme() */
+ if (ap_cstr_casecmpn(url, "ws:", 3) == 0) {
+ url += 3;
+ scheme = "ws:";
+ def_port = apr_uri_port_of_scheme("http");
+ }
+ else if (ap_cstr_casecmpn(url, "wss:", 4) == 0) {
+ url += 4;
+ scheme = "wss:";
+ def_port = apr_uri_port_of_scheme("https");
+ }
+ else {
+ return DECLINED;
+ }
+
+ port = def_port;
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "canonicalising URL %s", url);
+
+ /*
+ * do syntactic check.
+ * We break the URL into host, port, path, search
+ */
+ err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02439) "error parsing URL %s: %s",
+ url, err);
+ return HTTP_BAD_REQUEST;
+ }
+
+ /*
+ * now parse path/search args, according to rfc1738:
+ * process the path. With proxy-nocanon set (by
+ * mod_proxy) we use the raw, unparsed uri
+ */
+ if (apr_table_get(r->notes, "proxy-nocanon")) {
+ path = url; /* this is the raw path */
+ }
+ else {
+ path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0,
+ r->proxyreq);
+ search = r->args;
+ if (search && *(ap_scan_vchar_obstext(search))) {
+ /*
+ * We have a raw control character or a ' ' in r->args.
+ * Correct encoding was missed.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10409)
+ "To be forwarded query string contains control "
+ "characters or spaces");
+ return HTTP_FORBIDDEN;
+ }
+ }
+ if (path == NULL)
+ return HTTP_BAD_REQUEST;
+
+ if (port != def_port)
+ apr_snprintf(sport, sizeof(sport), ":%d", port);
+ else
+ sport[0] = '\0';
+
+ if (ap_strchr_c(host, ':')) {
+ /* if literal IPv6 address */
+ host = apr_pstrcat(r->pool, "[", host, "]", NULL);
+ }
+ r->filename = apr_pstrcat(r->pool, "proxy:", scheme, "//", host, sport,
+ "/", path, (search) ? "?" : "",
+ (search) ? search : "", NULL);
+ return OK;
+}
+
+/*
+ * process the request and write the response.
+ */
+static int proxy_wstunnel_request(apr_pool_t *p, request_rec *r,
+ proxy_conn_rec *conn,
+ proxy_worker *worker,
+ proxy_server_conf *conf,
+ apr_uri_t *uri,
+ char *url, char *server_portstr)
+{
+ apr_status_t rv;
+ apr_pollset_t *pollset;
+ apr_pollfd_t pollfd;
+ const apr_pollfd_t *signalled;
+ apr_int32_t pollcnt, pi;
+ apr_int16_t pollevent;
+ conn_rec *c = r->connection;
+ apr_socket_t *sock = conn->sock;
+ conn_rec *backconn = conn->connection;
+ char *buf;
+ apr_bucket_brigade *header_brigade;
+ apr_bucket *e;
+ char *old_cl_val = NULL;
+ char *old_te_val = NULL;
+ apr_bucket_brigade *bb = apr_brigade_create(p, c->bucket_alloc);
+ apr_socket_t *client_socket = ap_get_conn_socket(c);
+ int done = 0, replied = 0;
+ const char *upgrade_method = *worker->s->upgrade ? worker->s->upgrade : "WebSocket";
+
+ header_brigade = apr_brigade_create(p, backconn->bucket_alloc);
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "sending request");
+
+ rv = ap_proxy_create_hdrbrgd(p, header_brigade, r, conn,
+ worker, conf, uri, url, server_portstr,
+ &old_cl_val, &old_te_val);
+ if (rv != OK) {
+ return rv;
+ }
+
+ if (ap_cstr_casecmp(upgrade_method, "NONE") == 0) {
+ buf = apr_pstrdup(p, "Upgrade: WebSocket" CRLF "Connection: Upgrade" CRLF CRLF);
+ } else if (ap_cstr_casecmp(upgrade_method, "ANY") == 0) {
+ const char *upgrade;
+ upgrade = apr_table_get(r->headers_in, "Upgrade");
+ buf = apr_pstrcat(p, "Upgrade: ", upgrade, CRLF "Connection: Upgrade" CRLF CRLF, NULL);
+ } else {
+ buf = apr_pstrcat(p, "Upgrade: ", upgrade_method, CRLF "Connection: Upgrade" CRLF CRLF, NULL);
+ }
+ ap_xlate_proto_to_ascii(buf, strlen(buf));
+ e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(header_brigade, e);
+
+ if ((rv = ap_proxy_pass_brigade(backconn->bucket_alloc, r, conn, backconn,
+ header_brigade, 1)) != OK)
+ return rv;
+
+ apr_brigade_cleanup(header_brigade);
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "setting up poll()");
+
+ if ((rv = apr_pollset_create(&pollset, 2, p, 0)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02443)
+ "error apr_pollset_create()");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+#if 0
+ apr_socket_opt_set(sock, APR_SO_NONBLOCK, 1);
+ apr_socket_opt_set(sock, APR_SO_KEEPALIVE, 1);
+ apr_socket_opt_set(client_socket, APR_SO_NONBLOCK, 1);
+ apr_socket_opt_set(client_socket, APR_SO_KEEPALIVE, 1);
+#endif
+
+ pollfd.p = p;
+ pollfd.desc_type = APR_POLL_SOCKET;
+ pollfd.reqevents = APR_POLLIN | APR_POLLHUP;
+ pollfd.desc.s = sock;
+ pollfd.client_data = NULL;
+ apr_pollset_add(pollset, &pollfd);
+
+ pollfd.desc.s = client_socket;
+ apr_pollset_add(pollset, &pollfd);
+
+ ap_remove_input_filter_byhandle(c->input_filters, "reqtimeout");
+
+ r->output_filters = c->output_filters;
+ r->proto_output_filters = c->output_filters;
+ r->input_filters = c->input_filters;
+ r->proto_input_filters = c->input_filters;
+
+ /* This handler should take care of the entire connection; make it so that
+ * nothing else is attempted on the connection after returning. */
+ c->keepalive = AP_CONN_CLOSE;
+
+ do { /* Loop until done (one side closes the connection, or an error) */
+ rv = apr_pollset_poll(pollset, -1, &pollcnt, &signalled);
+ if (rv != APR_SUCCESS) {
+ if (APR_STATUS_IS_EINTR(rv)) {
+ continue;
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02444) "error apr_poll()");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(02445)
+ "woke from poll(), i=%d", pollcnt);
+
+ for (pi = 0; pi < pollcnt; pi++) {
+ const apr_pollfd_t *cur = &signalled[pi];
+
+ if (cur->desc.s == sock) {
+ pollevent = cur->rtnevents;
+ if (pollevent & (APR_POLLIN | APR_POLLHUP)) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(02446)
+ "sock was readable");
+ done |= ap_proxy_transfer_between_connections(r, backconn,
+ c,
+ header_brigade,
+ bb, "sock",
+ &replied,
+ AP_IOBUFSIZE,
+ 0)
+ != APR_SUCCESS;
+ }
+ else if (pollevent & APR_POLLERR) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, APLOGNO(02447)
+ "error on backconn");
+ backconn->aborted = 1;
+ done = 1;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, APLOGNO(02605)
+ "unknown event on backconn %d", pollevent);
+ done = 1;
+ }
+ }
+ else if (cur->desc.s == client_socket) {
+ pollevent = cur->rtnevents;
+ if (pollevent & (APR_POLLIN | APR_POLLHUP)) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(02448)
+ "client was readable");
+ done |= ap_proxy_transfer_between_connections(r, c,
+ backconn, bb,
+ header_brigade,
+ "client",
+ NULL,
+ AP_IOBUFSIZE,
+ 0)
+ != APR_SUCCESS;
+ }
+ else if (pollevent & APR_POLLERR) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(02607)
+ "error on client conn");
+ c->aborted = 1;
+ done = 1;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, APLOGNO(02606)
+ "unknown event on client conn %d", pollevent);
+ done = 1;
+ }
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02449)
+ "unknown socket in pollset");
+ done = 1;
+ }
+
+ }
+ } while (!done);
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+ "finished with poll() - cleaning up");
+
+ if (!replied) {
+ return HTTP_BAD_GATEWAY;
+ }
+ else {
+ return OK;
+ }
+
+ return OK;
+}
+
+/*
+ */
+static int proxy_wstunnel_handler(request_rec *r, proxy_worker *worker,
+ proxy_server_conf *conf,
+ char *url, const char *proxyname,
+ apr_port_t proxyport)
+{
+ proxyws_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
+ &proxy_wstunnel_module);
+ int status;
+ char server_portstr[32];
+ proxy_conn_rec *backend = NULL;
+ const char *upgrade;
+ char *scheme;
+ apr_pool_t *p = r->pool;
+ char *locurl = url;
+ apr_uri_t *uri;
+ int is_ssl = 0;
+
+ if (can_fallback_to_proxy_http && dconf->fallback_to_proxy_http) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "handler fallback");
+ return DECLINED;
+ }
+
+ if (ap_cstr_casecmpn(url, "wss:", 4) == 0) {
+ scheme = "WSS";
+ is_ssl = 1;
+ }
+ else if (ap_cstr_casecmpn(url, "ws:", 3) == 0) {
+ scheme = "WS";
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02450)
+ "declining URL %s", url);
+ return DECLINED;
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "serving URL %s", url);
+
+ upgrade = apr_table_get(r->headers_in, "Upgrade");
+ if (!upgrade || !ap_proxy_worker_can_upgrade(p, worker, upgrade,
+ "WebSocket")) {
+ const char *worker_upgrade = *worker->s->upgrade ? worker->s->upgrade
+ : "WebSocket";
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02900)
+ "require upgrade for URL %s "
+ "(Upgrade header is %s, expecting %s)",
+ url, upgrade ? upgrade : "missing", worker_upgrade);
+ apr_table_setn(r->err_headers_out, "Connection", "Upgrade");
+ apr_table_setn(r->err_headers_out, "Upgrade", worker_upgrade);
+ return HTTP_UPGRADE_REQUIRED;
+ }
+
+ uri = apr_palloc(p, sizeof(*uri));
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02451) "serving URL %s", url);
+
+ /* create space for state information */
+ status = ap_proxy_acquire_connection(scheme, &backend, worker, r->server);
+ if (status != OK) {
+ goto cleanup;
+ }
+
+ backend->is_ssl = is_ssl;
+ backend->close = 0;
+
+ /* Step One: Determine Who To Connect To */
+ status = ap_proxy_determine_connection(p, r, conf, worker, backend,
+ uri, &locurl, proxyname, proxyport,
+ server_portstr,
+ sizeof(server_portstr));
+ if (status != OK) {
+ goto cleanup;
+ }
+
+ /* Step Two: Make the Connection */
+ if (ap_proxy_connect_backend(scheme, backend, worker, r->server)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02452)
+ "failed to make connection to backend: %s",
+ backend->hostname);
+ status = HTTP_SERVICE_UNAVAILABLE;
+ goto cleanup;
+ }
+
+ /* Step Three: Create conn_rec */
+ status = ap_proxy_connection_create_ex(scheme, backend, r);
+ if (status != OK) {
+ goto cleanup;
+ }
+
+ /* Step Four: Process the Request */
+ status = proxy_wstunnel_request(p, r, backend, worker, conf, uri, locurl,
+ server_portstr);
+
+cleanup:
+ /* Do not close the socket */
+ if (backend) {
+ backend->close = 1;
+ ap_proxy_release_connection(scheme, backend, r->server);
+ }
+ return status;
+}
+
+static void *create_proxyws_dir_config(apr_pool_t *p, char *dummy)
+{
+ proxyws_dir_conf *new =
+ (proxyws_dir_conf *) apr_pcalloc(p, sizeof(proxyws_dir_conf));
+
+ new->fallback_to_proxy_http = 1;
+
+ return (void *) new;
+}
+
+static void *merge_proxyws_dir_config(apr_pool_t *p, void *vbase, void *vadd)
+{
+ proxyws_dir_conf *new = apr_pcalloc(p, sizeof(proxyws_dir_conf)),
+ *add = vadd, *base = vbase;
+
+ new->fallback_to_proxy_http = (add->fallback_to_proxy_http_set)
+ ? add->fallback_to_proxy_http
+ : base->fallback_to_proxy_http;
+ new->fallback_to_proxy_http_set = (add->fallback_to_proxy_http_set
+ || base->fallback_to_proxy_http_set);
+
+ return new;
+}
+
+static const char * proxyws_fallback_to_proxy_http(cmd_parms *cmd, void *conf, int arg)
+{
+ proxyws_dir_conf *dconf = conf;
+ dconf->fallback_to_proxy_http = !!arg;
+ dconf->fallback_to_proxy_http_set = 1;
+ return NULL;
+}
+
+static int proxy_wstunnel_post_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ can_fallback_to_proxy_http =
+ (ap_find_linked_module("mod_proxy_http.c") != NULL);
+
+ return OK;
+}
+
+static const command_rec ws_proxy_cmds[] =
+{
+ AP_INIT_FLAG("ProxyWebsocketFallbackToProxyHttp",
+ proxyws_fallback_to_proxy_http, NULL, RSRC_CONF|ACCESS_CONF,
+ "whether to let mod_proxy_http handle the upgrade and tunneling, "
+ "On by default"),
+
+ {NULL}
+};
+
+static void ws_proxy_hooks(apr_pool_t *p)
+{
+ static const char * const aszSucc[] = { "mod_proxy_http.c", NULL};
+ ap_hook_post_config(proxy_wstunnel_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+ proxy_hook_scheme_handler(proxy_wstunnel_handler, NULL, aszSucc, APR_HOOK_FIRST);
+ proxy_hook_check_trans(proxy_wstunnel_check_trans, NULL, aszSucc, APR_HOOK_MIDDLE);
+ proxy_hook_canon_handler(proxy_wstunnel_canon, NULL, aszSucc, APR_HOOK_FIRST);
+}
+
+AP_DECLARE_MODULE(proxy_wstunnel) = {
+ STANDARD20_MODULE_STUFF,
+ create_proxyws_dir_config, /* create per-directory config structure */
+ merge_proxyws_dir_config, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ ws_proxy_cmds, /* command apr_table_t */
+ ws_proxy_hooks /* register hooks */
+};
diff --git a/modules/proxy/mod_proxy_wstunnel.dep b/modules/proxy/mod_proxy_wstunnel.dep
new file mode 100644
index 0000000..5692e63
--- /dev/null
+++ b/modules/proxy/mod_proxy_wstunnel.dep
@@ -0,0 +1,73 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_proxy_wstunnel.mak
+
+.\mod_proxy_wstunnel.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_slotmem.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_date.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_reslist.h"\
+ "..\..\srclib\apr-util\include\apr_strmatch.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_proxy.h"\
+
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
diff --git a/modules/proxy/mod_proxy_wstunnel.dsp b/modules/proxy/mod_proxy_wstunnel.dsp
new file mode 100644
index 0000000..60fd062
--- /dev/null
+++ b/modules/proxy/mod_proxy_wstunnel.dsp
@@ -0,0 +1,123 @@
+# Microsoft Developer Studio Project File - Name="mod_proxy_wstunnel" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_proxy_wstunnel - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_wstunnel.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_wstunnel.mak" CFG="mod_proxy_wstunnel - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy_wstunnel - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy_wstunnel - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_proxy_wstunnel - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_proxy_wstunnel_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_proxy_wstunnel.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_proxy_wstunnel.so" /d LONG_NAME="proxy_wstunnel_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:".\Release\mod_proxy_wstunnel.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_wstunnel.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_proxy_wstunnel.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_wstunnel.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_proxy_wstunnel.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_proxy_wstunnel - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_proxy_wstunnel_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_proxy_wstunnel.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_proxy_wstunnel.so" /d LONG_NAME="proxy_wstunnel_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_proxy_wstunnel.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_wstunnel.so
+# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_proxy_wstunnel.so" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_wstunnel.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_proxy_wstunnel.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_proxy_wstunnel - Win32 Release"
+# Name "mod_proxy_wstunnel - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90"
+# Begin Source File
+
+SOURCE=.\mod_proxy_wstunnel.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter ".h"
+# Begin Source File
+
+SOURCE=.\mod_proxy.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/proxy/mod_proxy_wstunnel.mak b/modules/proxy/mod_proxy_wstunnel.mak
new file mode 100644
index 0000000..530715f
--- /dev/null
+++ b/modules/proxy/mod_proxy_wstunnel.mak
@@ -0,0 +1,380 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_proxy_wstunnel.dsp
+!IF "$(CFG)" == ""
+CFG=mod_proxy_wstunnel - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_proxy_wstunnel - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_proxy_wstunnel - Win32 Release" && "$(CFG)" != "mod_proxy_wstunnel - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_proxy_wstunnel.mak" CFG="mod_proxy_wstunnel - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_proxy_wstunnel - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_proxy_wstunnel - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_wstunnel - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_proxy_wstunnel.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_proxy - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_proxy_wstunnel.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_proxy - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_proxy_wstunnel.obj"
+ -@erase "$(INTDIR)\mod_proxy_wstunnel.res"
+ -@erase "$(INTDIR)\mod_proxy_wstunnel_src.idb"
+ -@erase "$(INTDIR)\mod_proxy_wstunnel_src.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_wstunnel.exp"
+ -@erase "$(OUTDIR)\mod_proxy_wstunnel.lib"
+ -@erase "$(OUTDIR)\mod_proxy_wstunnel.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_wstunnel.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_proxy_wstunnel_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_proxy_wstunnel.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_proxy_wstunnel.so" /d LONG_NAME="proxy_wstunnel_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_proxy_wstunnel.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_proxy_wstunnel.pdb" /debug /out:"$(OUTDIR)\mod_proxy_wstunnel.so" /implib:"$(OUTDIR)\mod_proxy_wstunnel.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_wstunnel.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_proxy_wstunnel.obj" \
+ "$(INTDIR)\mod_proxy_wstunnel.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib" \
+ "$(OUTDIR)\mod_proxy.lib"
+
+"$(OUTDIR)\mod_proxy_wstunnel.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_proxy_wstunnel.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_proxy_wstunnel.so"
+ if exist .\Release\mod_proxy_wstunnel.so.manifest mt.exe -manifest .\Release\mod_proxy_wstunnel.so.manifest -outputresource:.\Release\mod_proxy_wstunnel.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_proxy_wstunnel - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_proxy_wstunnel.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_proxy - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_proxy_wstunnel.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_proxy - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_proxy_wstunnel.obj"
+ -@erase "$(INTDIR)\mod_proxy_wstunnel.res"
+ -@erase "$(INTDIR)\mod_proxy_wstunnel_src.idb"
+ -@erase "$(INTDIR)\mod_proxy_wstunnel_src.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_wstunnel.exp"
+ -@erase "$(OUTDIR)\mod_proxy_wstunnel.lib"
+ -@erase "$(OUTDIR)\mod_proxy_wstunnel.pdb"
+ -@erase "$(OUTDIR)\mod_proxy_wstunnel.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_proxy_wstunnel_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_proxy_wstunnel.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_proxy_wstunnel.so" /d LONG_NAME="proxy_wstunnel_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_proxy_wstunnel.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_proxy_wstunnel.pdb" /debug /out:"$(OUTDIR)\mod_proxy_wstunnel.so" /implib:"$(OUTDIR)\mod_proxy_wstunnel.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_proxy_wstunnel.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_proxy_wstunnel.obj" \
+ "$(INTDIR)\mod_proxy_wstunnel.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib" \
+ "$(OUTDIR)\mod_proxy.lib"
+
+"$(OUTDIR)\mod_proxy_wstunnel.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_proxy_wstunnel.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_proxy_wstunnel.so"
+ if exist .\Debug\mod_proxy_wstunnel.so.manifest mt.exe -manifest .\Debug\mod_proxy_wstunnel.so.manifest -outputresource:.\Debug\mod_proxy_wstunnel.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_proxy_wstunnel.dep")
+!INCLUDE "mod_proxy_wstunnel.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_proxy_wstunnel.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_proxy_wstunnel - Win32 Release" || "$(CFG)" == "mod_proxy_wstunnel - Win32 Debug"
+SOURCE=.\mod_proxy_wstunnel.c
+
+"$(INTDIR)\mod_proxy_wstunnel.obj" : $(SOURCE) "$(INTDIR)"
+
+
+!IF "$(CFG)" == "mod_proxy_wstunnel - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\proxy"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_wstunnel - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\proxy"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_wstunnel - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\proxy"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_wstunnel - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\proxy"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_wstunnel - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\proxy"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\proxy"
+
+!ELSEIF "$(CFG)" == "mod_proxy_wstunnel - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\proxy"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\proxy"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_proxy_wstunnel - Win32 Release"
+
+"mod_proxy - Win32 Release" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Release"
+ cd "."
+
+"mod_proxy - Win32 ReleaseCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Release" RECURSE=1 CLEAN
+ cd "."
+
+!ELSEIF "$(CFG)" == "mod_proxy_wstunnel - Win32 Debug"
+
+"mod_proxy - Win32 Debug" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Debug"
+ cd "."
+
+"mod_proxy - Win32 DebugCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_proxy.mak" CFG="mod_proxy - Win32 Debug" RECURSE=1 CLEAN
+ cd "."
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_proxy_wstunnel - Win32 Release"
+
+
+"$(INTDIR)\mod_proxy_wstunnel.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_proxy_wstunnel.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_proxy_wstunnel.so" /d LONG_NAME="proxy_wstunnel_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_proxy_wstunnel - Win32 Debug"
+
+
+"$(INTDIR)\mod_proxy_wstunnel.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_proxy_wstunnel.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_proxy_wstunnel.so" /d LONG_NAME="proxy_wstunnel_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+
+!ENDIF
+
diff --git a/modules/proxy/proxy_util.c b/modules/proxy/proxy_util.c
new file mode 100644
index 0000000..8267f1b
--- /dev/null
+++ b/modules/proxy/proxy_util.c
@@ -0,0 +1,5080 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Utility routines for Apache proxy */
+#include "mod_proxy.h"
+#include "ap_mpm.h"
+#include "scoreboard.h"
+#include "apr_version.h"
+#include "apr_strings.h"
+#include "apr_hash.h"
+#include "proxy_util.h"
+#include "ajp.h"
+#include "scgi.h"
+
+#include "mod_http2.h" /* for http2_get_num_workers() */
+
+#if APR_HAVE_UNISTD_H
+#include <unistd.h> /* for getpid() */
+#endif
+
+#if APR_HAVE_SYS_UN_H
+#include <sys/un.h>
+#endif
+#if (APR_MAJOR_VERSION < 2)
+#include "apr_support.h" /* for apr_wait_for_io_or_timeout() */
+#endif
+
+APLOG_USE_MODULE(proxy);
+
+/*
+ * Opaque structure containing target server info when
+ * using a forward proxy.
+ * Up to now only used in combination with HTTP CONNECT.
+ */
+typedef struct {
+ int use_http_connect; /* Use SSL Tunneling via HTTP CONNECT */
+ const char *target_host; /* Target hostname */
+ apr_port_t target_port; /* Target port */
+ const char *proxy_auth; /* Proxy authorization */
+} forward_info;
+
+/* Global balancer counter */
+int PROXY_DECLARE_DATA proxy_lb_workers = 0;
+static int lb_workers_limit = 0;
+const apr_strmatch_pattern PROXY_DECLARE_DATA *ap_proxy_strmatch_path;
+const apr_strmatch_pattern PROXY_DECLARE_DATA *ap_proxy_strmatch_domain;
+
+extern apr_global_mutex_t *proxy_mutex;
+
+static int proxy_match_ipaddr(struct dirconn_entry *This, request_rec *r);
+static int proxy_match_domainname(struct dirconn_entry *This, request_rec *r);
+static int proxy_match_hostname(struct dirconn_entry *This, request_rec *r);
+static int proxy_match_word(struct dirconn_entry *This, request_rec *r);
+static int ap_proxy_retry_worker(const char *proxy_function, proxy_worker *worker, server_rec *s);
+static proxy_worker *proxy_balancer_get_best_worker(proxy_balancer *balancer,
+ request_rec *r,
+ proxy_is_best_callback_fn_t *is_best,
+ void *baton);
+
+APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(proxy, PROXY, int, create_req,
+ (request_rec *r, request_rec *pr), (r, pr),
+ OK, DECLINED)
+
+PROXY_DECLARE(apr_status_t) ap_proxy_strncpy(char *dst, const char *src,
+ apr_size_t dlen)
+{
+ char *thenil;
+ apr_size_t thelen;
+
+ /* special case handling */
+ if (!dlen) {
+ /* XXX: APR_ENOSPACE would be better */
+ return APR_EGENERAL;
+ }
+ if (!src) {
+ *dst = '\0';
+ return APR_SUCCESS;
+ }
+ thenil = apr_cpystrn(dst, src, dlen);
+ thelen = thenil - dst;
+ if (src[thelen] == '\0') {
+ return APR_SUCCESS;
+ }
+ return APR_EGENERAL;
+}
+
+/* already called in the knowledge that the characters are hex digits */
+PROXY_DECLARE(int) ap_proxy_hex2c(const char *x)
+{
+ int i;
+
+#if !APR_CHARSET_EBCDIC
+ int ch = x[0];
+
+ if (apr_isdigit(ch)) {
+ i = ch - '0';
+ }
+ else if (apr_isupper(ch)) {
+ i = ch - ('A' - 10);
+ }
+ else {
+ i = ch - ('a' - 10);
+ }
+ i <<= 4;
+
+ ch = x[1];
+ if (apr_isdigit(ch)) {
+ i += ch - '0';
+ }
+ else if (apr_isupper(ch)) {
+ i += ch - ('A' - 10);
+ }
+ else {
+ i += ch - ('a' - 10);
+ }
+ return i;
+#else /*APR_CHARSET_EBCDIC*/
+ /*
+ * we assume that the hex value refers to an ASCII character
+ * so convert to EBCDIC so that it makes sense locally;
+ *
+ * example:
+ *
+ * client specifies %20 in URL to refer to a space char;
+ * at this point we're called with EBCDIC "20"; after turning
+ * EBCDIC "20" into binary 0x20, we then need to assume that 0x20
+ * represents an ASCII char and convert 0x20 to EBCDIC, yielding
+ * 0x40
+ */
+ char buf[1];
+
+ if (1 == sscanf(x, "%2x", &i)) {
+ buf[0] = i & 0xFF;
+ ap_xlate_proto_from_ascii(buf, 1);
+ return buf[0];
+ }
+ else {
+ return 0;
+ }
+#endif /*APR_CHARSET_EBCDIC*/
+}
+
+PROXY_DECLARE(void) ap_proxy_c2hex(int ch, char *x)
+{
+#if !APR_CHARSET_EBCDIC
+ int i;
+
+ x[0] = '%';
+ i = (ch & 0xF0) >> 4;
+ if (i >= 10) {
+ x[1] = ('A' - 10) + i;
+ }
+ else {
+ x[1] = '0' + i;
+ }
+
+ i = ch & 0x0F;
+ if (i >= 10) {
+ x[2] = ('A' - 10) + i;
+ }
+ else {
+ x[2] = '0' + i;
+ }
+#else /*APR_CHARSET_EBCDIC*/
+ static const char ntoa[] = { "0123456789ABCDEF" };
+ char buf[1];
+
+ ch &= 0xFF;
+
+ buf[0] = ch;
+ ap_xlate_proto_to_ascii(buf, 1);
+
+ x[0] = '%';
+ x[1] = ntoa[(buf[0] >> 4) & 0x0F];
+ x[2] = ntoa[buf[0] & 0x0F];
+ x[3] = '\0';
+#endif /*APR_CHARSET_EBCDIC*/
+}
+
+/*
+ * canonicalise a URL-encoded string
+ */
+
+/*
+ * Convert a URL-encoded string to canonical form.
+ * It decodes characters which need not be encoded,
+ * and encodes those which must be encoded, and does not touch
+ * those which must not be touched.
+ */
+PROXY_DECLARE(char *)ap_proxy_canonenc(apr_pool_t *p, const char *x, int len,
+ enum enctype t, int forcedec,
+ int proxyreq)
+{
+ int i, j, ch;
+ char *y;
+ char *allowed; /* characters which should not be encoded */
+ char *reserved; /* characters which much not be en/de-coded */
+
+/*
+ * N.B. in addition to :@&=, this allows ';' in an http path
+ * and '?' in an ftp path -- this may be revised
+ *
+ * Also, it makes a '+' character in a search string reserved, as
+ * it may be form-encoded. (Although RFC 1738 doesn't allow this -
+ * it only permits ; / ? : @ = & as reserved chars.)
+ */
+ if (t == enc_path) {
+ allowed = "~$-_.+!*'(),;:@&=";
+ }
+ else if (t == enc_search) {
+ allowed = "$-_.!*'(),;:@&=";
+ }
+ else if (t == enc_user) {
+ allowed = "$-_.+!*'(),;@&=";
+ }
+ else if (t == enc_fpath) {
+ allowed = "$-_.+!*'(),?:@&=";
+ }
+ else { /* if (t == enc_parm) */
+ allowed = "$-_.+!*'(),?/:@&=";
+ }
+
+ if (t == enc_path) {
+ reserved = "/";
+ }
+ else if (t == enc_search) {
+ reserved = "+";
+ }
+ else {
+ reserved = "";
+ }
+
+ y = apr_palloc(p, 3 * len + 1);
+
+ for (i = 0, j = 0; i < len; i++, j++) {
+/* always handle '/' first */
+ ch = x[i];
+ if (strchr(reserved, ch)) {
+ y[j] = ch;
+ continue;
+ }
+/*
+ * decode it if not already done. do not decode reverse proxied URLs
+ * unless specifically forced
+ */
+ if ((forcedec || (proxyreq && proxyreq != PROXYREQ_REVERSE)) && ch == '%') {
+ if (!apr_isxdigit(x[i + 1]) || !apr_isxdigit(x[i + 2])) {
+ return NULL;
+ }
+ ch = ap_proxy_hex2c(&x[i + 1]);
+ i += 2;
+ if (ch != 0 && strchr(reserved, ch)) { /* keep it encoded */
+ ap_proxy_c2hex(ch, &y[j]);
+ j += 2;
+ continue;
+ }
+ }
+/* recode it, if necessary */
+ if (!apr_isalnum(ch) && !strchr(allowed, ch)) {
+ ap_proxy_c2hex(ch, &y[j]);
+ j += 2;
+ }
+ else {
+ y[j] = ch;
+ }
+ }
+ y[j] = '\0';
+ return y;
+}
+
+/*
+ * Parses network-location.
+ * urlp on input the URL; on output the path, after the leading /
+ * user NULL if no user/password permitted
+ * password holder for password
+ * host holder for host
+ * port port number; only set if one is supplied.
+ *
+ * Returns an error string.
+ */
+PROXY_DECLARE(char *)
+ ap_proxy_canon_netloc(apr_pool_t *p, char **const urlp, char **userp,
+ char **passwordp, char **hostp, apr_port_t *port)
+{
+ char *addr, *scope_id, *strp, *host, *url = *urlp;
+ char *user = NULL, *password = NULL;
+ apr_port_t tmp_port;
+ apr_status_t rv;
+
+ if (url[0] != '/' || url[1] != '/') {
+ return "Malformed URL";
+ }
+ host = url + 2;
+ url = strchr(host, '/');
+ if (url == NULL) {
+ url = "";
+ }
+ else {
+ *(url++) = '\0'; /* skip separating '/' */
+ }
+
+ /* find _last_ '@' since it might occur in user/password part */
+ strp = strrchr(host, '@');
+
+ if (strp != NULL) {
+ *strp = '\0';
+ user = host;
+ host = strp + 1;
+
+/* find password */
+ strp = strchr(user, ':');
+ if (strp != NULL) {
+ *strp = '\0';
+ password = ap_proxy_canonenc(p, strp + 1, strlen(strp + 1), enc_user, 1, 0);
+ if (password == NULL) {
+ return "Bad %-escape in URL (password)";
+ }
+ }
+
+ user = ap_proxy_canonenc(p, user, strlen(user), enc_user, 1, 0);
+ if (user == NULL) {
+ return "Bad %-escape in URL (username)";
+ }
+ }
+ if (userp != NULL) {
+ *userp = user;
+ }
+ if (passwordp != NULL) {
+ *passwordp = password;
+ }
+
+ /*
+ * Parse the host string to separate host portion from optional port.
+ * Perform range checking on port.
+ */
+ rv = apr_parse_addr_port(&addr, &scope_id, &tmp_port, host, p);
+ if (rv != APR_SUCCESS || addr == NULL || scope_id != NULL) {
+ return "Invalid host/port";
+ }
+ if (tmp_port != 0) { /* only update caller's port if port was specified */
+ *port = tmp_port;
+ }
+
+ ap_str_tolower(addr); /* DNS names are case-insensitive */
+
+ *urlp = url;
+ *hostp = addr;
+
+ return NULL;
+}
+
+PROXY_DECLARE(int) ap_proxyerror(request_rec *r, int statuscode, const char *message)
+{
+ apr_table_setn(r->notes, "error-notes",
+ apr_pstrcat(r->pool,
+ "The proxy server could not handle the request<p>"
+ "Reason: <strong>", ap_escape_html(r->pool, message),
+ "</strong></p>",
+ NULL));
+
+ /* Allow "error-notes" string to be printed by ap_send_error_response() */
+ apr_table_setn(r->notes, "verbose-error-to", "*");
+
+ r->status_line = apr_psprintf(r->pool, "%3.3u Proxy Error", statuscode);
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00898) "%s returned by %s", message,
+ r->uri);
+ return statuscode;
+}
+
+static const char *
+ proxy_get_host_of_request(request_rec *r)
+{
+ char *url, *user = NULL, *password = NULL, *err, *host = NULL;
+ apr_port_t port;
+
+ if (r->hostname != NULL) {
+ return r->hostname;
+ }
+
+ /* Set url to the first char after "scheme://" */
+ if ((url = strchr(r->uri, ':')) == NULL || url[1] != '/' || url[2] != '/') {
+ return NULL;
+ }
+
+ url = apr_pstrdup(r->pool, &url[1]); /* make it point to "//", which is what proxy_canon_netloc expects */
+
+ err = ap_proxy_canon_netloc(r->pool, &url, &user, &password, &host, &port);
+
+ if (err != NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00899) "%s", err);
+ }
+
+ r->hostname = host;
+
+ return host; /* ought to return the port, too */
+}
+
+/* Return TRUE if addr represents an IP address (or an IP network address) */
+PROXY_DECLARE(int) ap_proxy_is_ipaddr(struct dirconn_entry *This, apr_pool_t *p)
+{
+ const char *addr = This->name;
+ long ip_addr[4];
+ int i, quads;
+ long bits;
+
+ /*
+ * if the address is given with an explicit netmask, use that
+ * Due to a deficiency in apr_inet_addr(), it is impossible to parse
+ * "partial" addresses (with less than 4 quads) correctly, i.e.
+ * 192.168.123 is parsed as 192.168.0.123, which is not what I want.
+ * I therefore have to parse the IP address manually:
+ * if (proxy_readmask(This->name, &This->addr.s_addr, &This->mask.s_addr) == 0)
+ * addr and mask were set by proxy_readmask()
+ * return 1;
+ */
+
+ /*
+ * Parse IP addr manually, optionally allowing
+ * abbreviated net addresses like 192.168.
+ */
+
+ /* Iterate over up to 4 (dotted) quads. */
+ for (quads = 0; quads < 4 && *addr != '\0'; ++quads) {
+ char *tmp;
+
+ if (*addr == '/' && quads > 0) { /* netmask starts here. */
+ break;
+ }
+
+ if (!apr_isdigit(*addr)) {
+ return 0; /* no digit at start of quad */
+ }
+
+ ip_addr[quads] = strtol(addr, &tmp, 0);
+
+ if (tmp == addr) { /* expected a digit, found something else */
+ return 0;
+ }
+
+ if (ip_addr[quads] < 0 || ip_addr[quads] > 255) {
+ /* invalid octet */
+ return 0;
+ }
+
+ addr = tmp;
+
+ if (*addr == '.' && quads != 3) {
+ ++addr; /* after the 4th quad, a dot would be illegal */
+ }
+ }
+
+ for (This->addr.s_addr = 0, i = 0; i < quads; ++i) {
+ This->addr.s_addr |= htonl(ip_addr[i] << (24 - 8 * i));
+ }
+
+ if (addr[0] == '/' && apr_isdigit(addr[1])) { /* net mask follows: */
+ char *tmp;
+
+ ++addr;
+
+ bits = strtol(addr, &tmp, 0);
+
+ if (tmp == addr) { /* expected a digit, found something else */
+ return 0;
+ }
+
+ addr = tmp;
+
+ if (bits < 0 || bits > 32) { /* netmask must be between 0 and 32 */
+ return 0;
+ }
+
+ }
+ else {
+ /*
+ * Determine (i.e., "guess") netmask by counting the
+ * number of trailing .0's; reduce #quads appropriately
+ * (so that 192.168.0.0 is equivalent to 192.168.)
+ */
+ while (quads > 0 && ip_addr[quads - 1] == 0) {
+ --quads;
+ }
+
+ /* "IP Address should be given in dotted-quad form, optionally followed by a netmask (e.g., 192.168.111.0/24)"; */
+ if (quads < 1) {
+ return 0;
+ }
+
+ /* every zero-byte counts as 8 zero-bits */
+ bits = 8 * quads;
+
+ if (bits != 32) { /* no warning for fully qualified IP address */
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00900)
+ "Warning: NetMask not supplied with IP-Addr; guessing: %s/%ld",
+ inet_ntoa(This->addr), bits);
+ }
+ }
+
+ This->mask.s_addr = htonl(APR_INADDR_NONE << (32 - bits));
+
+ if (*addr == '\0' && (This->addr.s_addr & ~This->mask.s_addr) != 0) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00901)
+ "Warning: NetMask and IP-Addr disagree in %s/%ld",
+ inet_ntoa(This->addr), bits);
+ This->addr.s_addr &= This->mask.s_addr;
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00902)
+ " Set to %s/%ld", inet_ntoa(This->addr), bits);
+ }
+
+ if (*addr == '\0') {
+ This->matcher = proxy_match_ipaddr;
+ return 1;
+ }
+ else {
+ return (*addr == '\0'); /* okay iff we've parsed the whole string */
+ }
+}
+
+/* Return TRUE if addr represents an IP address (or an IP network address) */
+static int proxy_match_ipaddr(struct dirconn_entry *This, request_rec *r)
+{
+ int i, ip_addr[4];
+ struct in_addr addr, *ip;
+ const char *host = proxy_get_host_of_request(r);
+
+ if (host == NULL) { /* oops! */
+ return 0;
+ }
+
+ memset(&addr, '\0', sizeof addr);
+ memset(ip_addr, '\0', sizeof ip_addr);
+
+ if (4 == sscanf(host, "%d.%d.%d.%d", &ip_addr[0], &ip_addr[1], &ip_addr[2], &ip_addr[3])) {
+ for (addr.s_addr = 0, i = 0; i < 4; ++i) {
+ /* ap_proxy_is_ipaddr() already confirmed that we have
+ * a valid octet in ip_addr[i]
+ */
+ addr.s_addr |= htonl(ip_addr[i] << (24 - 8 * i));
+ }
+
+ if (This->addr.s_addr == (addr.s_addr & This->mask.s_addr)) {
+#if DEBUGGING
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00903)
+ "1)IP-Match: %s[%s] <-> ", host, inet_ntoa(addr));
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00904)
+ "%s/", inet_ntoa(This->addr));
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00905)
+ "%s", inet_ntoa(This->mask));
+#endif
+ return 1;
+ }
+#if DEBUGGING
+ else {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00906)
+ "1)IP-NoMatch: %s[%s] <-> ", host, inet_ntoa(addr));
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00907)
+ "%s/", inet_ntoa(This->addr));
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00908)
+ "%s", inet_ntoa(This->mask));
+ }
+#endif
+ }
+ else {
+ struct apr_sockaddr_t *reqaddr;
+
+ if (apr_sockaddr_info_get(&reqaddr, host, APR_UNSPEC, 0, 0, r->pool)
+ != APR_SUCCESS) {
+#if DEBUGGING
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00909)
+ "2)IP-NoMatch: hostname=%s msg=Host not found", host);
+#endif
+ return 0;
+ }
+
+ /* Try to deal with multiple IP addr's for a host */
+ /* FIXME: This needs to be able to deal with IPv6 */
+ while (reqaddr) {
+ ip = (struct in_addr *) reqaddr->ipaddr_ptr;
+ if (This->addr.s_addr == (ip->s_addr & This->mask.s_addr)) {
+#if DEBUGGING
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00910)
+ "3)IP-Match: %s[%s] <-> ", host, inet_ntoa(*ip));
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00911)
+ "%s/", inet_ntoa(This->addr));
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00912)
+ "%s", inet_ntoa(This->mask));
+#endif
+ return 1;
+ }
+#if DEBUGGING
+ else {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00913)
+ "3)IP-NoMatch: %s[%s] <-> ", host, inet_ntoa(*ip));
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00914)
+ "%s/", inet_ntoa(This->addr));
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00915)
+ "%s", inet_ntoa(This->mask));
+ }
+#endif
+ reqaddr = reqaddr->next;
+ }
+ }
+
+ return 0;
+}
+
+/* Return TRUE if addr represents a domain name */
+PROXY_DECLARE(int) ap_proxy_is_domainname(struct dirconn_entry *This, apr_pool_t *p)
+{
+ char *addr = This->name;
+ int i;
+
+ /* Domain name must start with a '.' */
+ if (addr[0] != '.') {
+ return 0;
+ }
+
+ /* rfc1035 says DNS names must consist of "[-a-zA-Z0-9]" and '.' */
+ for (i = 0; apr_isalnum(addr[i]) || addr[i] == '-' || addr[i] == '.'; ++i) {
+ continue;
+ }
+
+#if 0
+ if (addr[i] == ':') {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(03234)
+ "@@@@ handle optional port in proxy_is_domainname()");
+ /* @@@@ handle optional port */
+ }
+#endif
+
+ if (addr[i] != '\0') {
+ return 0;
+ }
+
+ /* Strip trailing dots */
+ for (i = strlen(addr) - 1; i > 0 && addr[i] == '.'; --i) {
+ addr[i] = '\0';
+ }
+
+ This->matcher = proxy_match_domainname;
+ return 1;
+}
+
+/* Return TRUE if host "host" is in domain "domain" */
+static int proxy_match_domainname(struct dirconn_entry *This, request_rec *r)
+{
+ const char *host = proxy_get_host_of_request(r);
+ int d_len = strlen(This->name), h_len;
+
+ if (host == NULL) { /* some error was logged already */
+ return 0;
+ }
+
+ h_len = strlen(host);
+
+ /* @@@ do this within the setup? */
+ /* Ignore trailing dots in domain comparison: */
+ while (d_len > 0 && This->name[d_len - 1] == '.') {
+ --d_len;
+ }
+ while (h_len > 0 && host[h_len - 1] == '.') {
+ --h_len;
+ }
+ return h_len > d_len
+ && strncasecmp(&host[h_len - d_len], This->name, d_len) == 0;
+}
+
+/* Return TRUE if host represents a host name */
+PROXY_DECLARE(int) ap_proxy_is_hostname(struct dirconn_entry *This, apr_pool_t *p)
+{
+ struct apr_sockaddr_t *addr;
+ char *host = This->name;
+ int i;
+
+ /* Host names must not start with a '.' */
+ if (host[0] == '.') {
+ return 0;
+ }
+ /* rfc1035 says DNS names must consist of "[-a-zA-Z0-9]" and '.' */
+ for (i = 0; apr_isalnum(host[i]) || host[i] == '-' || host[i] == '.'; ++i);
+
+ if (host[i] != '\0' || apr_sockaddr_info_get(&addr, host, APR_UNSPEC, 0, 0, p) != APR_SUCCESS) {
+ return 0;
+ }
+
+ This->hostaddr = addr;
+
+ /* Strip trailing dots */
+ for (i = strlen(host) - 1; i > 0 && host[i] == '.'; --i) {
+ host[i] = '\0';
+ }
+
+ This->matcher = proxy_match_hostname;
+ return 1;
+}
+
+/* Return TRUE if host "host" is equal to host2 "host2" */
+static int proxy_match_hostname(struct dirconn_entry *This, request_rec *r)
+{
+ char *host = This->name;
+ const char *host2 = proxy_get_host_of_request(r);
+ int h2_len;
+ int h1_len;
+
+ if (host == NULL || host2 == NULL) {
+ return 0; /* oops! */
+ }
+
+ h2_len = strlen(host2);
+ h1_len = strlen(host);
+
+#if 0
+ struct apr_sockaddr_t *addr = *This->hostaddr;
+
+ /* Try to deal with multiple IP addr's for a host */
+ while (addr) {
+ if (addr->ipaddr_ptr == ? ? ? ? ? ? ? ? ? ? ? ? ?)
+ return 1;
+ addr = addr->next;
+ }
+#endif
+
+ /* Ignore trailing dots in host2 comparison: */
+ while (h2_len > 0 && host2[h2_len - 1] == '.') {
+ --h2_len;
+ }
+ while (h1_len > 0 && host[h1_len - 1] == '.') {
+ --h1_len;
+ }
+ return h1_len == h2_len
+ && strncasecmp(host, host2, h1_len) == 0;
+}
+
+/* Return TRUE if addr is to be matched as a word */
+PROXY_DECLARE(int) ap_proxy_is_word(struct dirconn_entry *This, apr_pool_t *p)
+{
+ This->matcher = proxy_match_word;
+ return 1;
+}
+
+/* Return TRUE if string "str2" occurs literally in "str1" */
+static int proxy_match_word(struct dirconn_entry *This, request_rec *r)
+{
+ const char *host = proxy_get_host_of_request(r);
+ return host != NULL && ap_strstr_c(host, This->name) != NULL;
+}
+
+/* Backwards-compatible interface. */
+PROXY_DECLARE(int) ap_proxy_checkproxyblock(request_rec *r, proxy_server_conf *conf,
+ apr_sockaddr_t *uri_addr)
+{
+ return ap_proxy_checkproxyblock2(r, conf, uri_addr->hostname, uri_addr);
+}
+
+#define MAX_IP_STR_LEN (46)
+
+PROXY_DECLARE(int) ap_proxy_checkproxyblock2(request_rec *r, proxy_server_conf *conf,
+ const char *hostname, apr_sockaddr_t *addr)
+{
+ int j;
+
+ /* XXX FIXME: conf->noproxies->elts is part of an opaque structure */
+ for (j = 0; j < conf->noproxies->nelts; j++) {
+ struct noproxy_entry *npent = (struct noproxy_entry *) conf->noproxies->elts;
+ struct apr_sockaddr_t *conf_addr;
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+ "checking remote machine [%s] against [%s]",
+ hostname, npent[j].name);
+ if (ap_strstr_c(hostname, npent[j].name) || npent[j].name[0] == '*') {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(00916)
+ "connect to remote machine %s blocked: name %s "
+ "matched", hostname, npent[j].name);
+ return HTTP_FORBIDDEN;
+ }
+
+ /* No IP address checks if no IP address was passed in,
+ * i.e. the forward address proxy case, where this server does
+ * not resolve the hostname. */
+ if (!addr)
+ continue;
+
+ for (conf_addr = npent[j].addr; conf_addr; conf_addr = conf_addr->next) {
+ char caddr[MAX_IP_STR_LEN], uaddr[MAX_IP_STR_LEN];
+ apr_sockaddr_t *uri_addr;
+
+ if (apr_sockaddr_ip_getbuf(caddr, sizeof caddr, conf_addr))
+ continue;
+
+ for (uri_addr = addr; uri_addr; uri_addr = uri_addr->next) {
+ if (apr_sockaddr_ip_getbuf(uaddr, sizeof uaddr, uri_addr))
+ continue;
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+ "ProxyBlock comparing %s and %s", caddr, uaddr);
+ if (!strcmp(caddr, uaddr)) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(00917)
+ "connect to remote machine %s blocked: "
+ "IP %s matched", hostname, caddr);
+ return HTTP_FORBIDDEN;
+ }
+ }
+ }
+ }
+
+ return OK;
+}
+
+/* set up the minimal filter set */
+PROXY_DECLARE(int) ap_proxy_pre_http_request(conn_rec *c, request_rec *r)
+{
+ ap_add_input_filter("HTTP_IN", NULL, r, c);
+ return OK;
+}
+
+PROXY_DECLARE(const char *) ap_proxy_location_reverse_map(request_rec *r,
+ proxy_dir_conf *conf, const char *url)
+{
+ proxy_req_conf *rconf;
+ struct proxy_alias *ent;
+ int i, l1, l1_orig, l2;
+ char *u;
+
+ /*
+ * XXX FIXME: Make sure this handled the ambiguous case of the :<PORT>
+ * after the hostname
+ * XXX FIXME: Ensure the /uri component is a case sensitive match
+ */
+ if (r->proxyreq != PROXYREQ_REVERSE) {
+ return url;
+ }
+
+ l1_orig = strlen(url);
+ if (conf->interpolate_env == 1) {
+ rconf = ap_get_module_config(r->request_config, &proxy_module);
+ ent = (struct proxy_alias *)rconf->raliases->elts;
+ }
+ else {
+ ent = (struct proxy_alias *)conf->raliases->elts;
+ }
+ for (i = 0; i < conf->raliases->nelts; i++) {
+ proxy_server_conf *sconf = (proxy_server_conf *)
+ ap_get_module_config(r->server->module_config, &proxy_module);
+ proxy_balancer *balancer;
+ const char *real = ent[i].real;
+
+ /* Restore the url length, if it had been changed by the code below */
+ l1 = l1_orig;
+
+ /*
+ * First check if mapping against a balancer and see
+ * if we have such a entity. If so, then we need to
+ * find the particulars of the actual worker which may
+ * or may not be the right one... basically, we need
+ * to find which member actually handled this request.
+ */
+ if (ap_proxy_valid_balancer_name((char *)real, 0) &&
+ (balancer = ap_proxy_get_balancer(r->pool, sconf, real, 1))) {
+ int n, l3 = 0;
+ proxy_worker **worker = (proxy_worker **)balancer->workers->elts;
+ const char *urlpart = ap_strchr_c(real + sizeof(BALANCER_PREFIX) - 1, '/');
+ if (urlpart) {
+ if (!urlpart[1])
+ urlpart = NULL;
+ else
+ l3 = strlen(urlpart);
+ }
+ /* The balancer comparison is a bit trickier. Given the context
+ * BalancerMember balancer://alias http://example.com/foo
+ * ProxyPassReverse /bash balancer://alias/bar
+ * translate url http://example.com/foo/bar/that to /bash/that
+ */
+ for (n = 0; n < balancer->workers->nelts; n++) {
+ l2 = strlen((*worker)->s->name_ex);
+ if (urlpart) {
+ /* urlpart (l3) assuredly starts with its own '/' */
+ if ((*worker)->s->name_ex[l2 - 1] == '/')
+ --l2;
+ if (l1 >= l2 + l3
+ && strncasecmp((*worker)->s->name_ex, url, l2) == 0
+ && strncmp(urlpart, url + l2, l3) == 0) {
+ u = apr_pstrcat(r->pool, ent[i].fake, &url[l2 + l3],
+ NULL);
+ return ap_is_url(u) ? u : ap_construct_url(r->pool, u, r);
+ }
+ }
+ else if (l1 >= l2 && strncasecmp((*worker)->s->name_ex, url, l2) == 0) {
+ /* edge case where fake is just "/"... avoid double slash */
+ if ((ent[i].fake[0] == '/') && (ent[i].fake[1] == 0) && (url[l2] == '/')) {
+ u = apr_pstrdup(r->pool, &url[l2]);
+ } else {
+ u = apr_pstrcat(r->pool, ent[i].fake, &url[l2], NULL);
+ }
+ return ap_is_url(u) ? u : ap_construct_url(r->pool, u, r);
+ }
+ worker++;
+ }
+ }
+ else {
+ const char *part = url;
+ l2 = strlen(real);
+ if (real[0] == '/') {
+ part = ap_strstr_c(url, "://");
+ if (part) {
+ part = ap_strchr_c(part+3, '/');
+ if (part) {
+ l1 = strlen(part);
+ }
+ else {
+ part = url;
+ }
+ }
+ else {
+ part = url;
+ }
+ }
+ if (l2 > 0 && l1 >= l2 && strncasecmp(real, part, l2) == 0) {
+ u = apr_pstrcat(r->pool, ent[i].fake, &part[l2], NULL);
+ return ap_is_url(u) ? u : ap_construct_url(r->pool, u, r);
+ }
+ }
+ }
+
+ return url;
+}
+
+/*
+ * Cookies are a bit trickier to match: we've got two substrings to worry
+ * about, and we can't just find them with strstr 'cos of case. Regexp
+ * matching would be an easy fix, but for better consistency with all the
+ * other matches we'll refrain and use apr_strmatch to find path=/domain=
+ * and stick to plain strings for the config values.
+ */
+PROXY_DECLARE(const char *) ap_proxy_cookie_reverse_map(request_rec *r,
+ proxy_dir_conf *conf, const char *str)
+{
+ proxy_req_conf *rconf = ap_get_module_config(r->request_config,
+ &proxy_module);
+ struct proxy_alias *ent;
+ apr_size_t len = strlen(str);
+ const char *newpath = NULL;
+ const char *newdomain = NULL;
+ const char *pathp;
+ const char *domainp;
+ const char *pathe = NULL;
+ const char *domaine = NULL;
+ apr_size_t l1, l2, poffs = 0, doffs = 0;
+ int i;
+ int ddiff = 0;
+ int pdiff = 0;
+ char *tmpstr, *tmpstr_orig, *token, *last, *ret;
+
+ if (r->proxyreq != PROXYREQ_REVERSE) {
+ return str;
+ }
+
+ /*
+ * Find the match and replacement, but save replacing until we've done
+ * both path and domain so we know the new strlen
+ */
+ tmpstr_orig = tmpstr = apr_pstrdup(r->pool, str);
+ while ((token = apr_strtok(tmpstr, ";", &last))) {
+ /* skip leading spaces */
+ while (apr_isspace(*token)) {
+ ++token;
+ }
+
+ if (ap_cstr_casecmpn("path=", token, 5) == 0) {
+ pathp = token + 5;
+ poffs = pathp - tmpstr_orig;
+ l1 = strlen(pathp);
+ pathe = str + poffs + l1;
+ if (conf->interpolate_env == 1) {
+ ent = (struct proxy_alias *)rconf->cookie_paths->elts;
+ }
+ else {
+ ent = (struct proxy_alias *)conf->cookie_paths->elts;
+ }
+ for (i = 0; i < conf->cookie_paths->nelts; i++) {
+ l2 = strlen(ent[i].fake);
+ if (l1 >= l2 && strncmp(ent[i].fake, pathp, l2) == 0) {
+ newpath = ent[i].real;
+ pdiff = strlen(newpath) - l1;
+ break;
+ }
+ }
+ }
+ else if (ap_cstr_casecmpn("domain=", token, 7) == 0) {
+ domainp = token + 7;
+ doffs = domainp - tmpstr_orig;
+ l1 = strlen(domainp);
+ domaine = str + doffs + l1;
+ if (conf->interpolate_env == 1) {
+ ent = (struct proxy_alias *)rconf->cookie_domains->elts;
+ }
+ else {
+ ent = (struct proxy_alias *)conf->cookie_domains->elts;
+ }
+ for (i = 0; i < conf->cookie_domains->nelts; i++) {
+ l2 = strlen(ent[i].fake);
+ if (l1 >= l2 && strncasecmp(ent[i].fake, domainp, l2) == 0) {
+ newdomain = ent[i].real;
+ ddiff = strlen(newdomain) - l1;
+ break;
+ }
+ }
+ }
+
+ /* Iterate the remaining tokens using apr_strtok(NULL, ...) */
+ tmpstr = NULL;
+ }
+
+ if (newpath) {
+ ret = apr_palloc(r->pool, len + pdiff + ddiff + 1);
+ l1 = strlen(newpath);
+ if (newdomain) {
+ l2 = strlen(newdomain);
+ if (doffs > poffs) {
+ memcpy(ret, str, poffs);
+ memcpy(ret + poffs, newpath, l1);
+ memcpy(ret + poffs + l1, pathe, str + doffs - pathe);
+ memcpy(ret + doffs + pdiff, newdomain, l2);
+ strcpy(ret + doffs + pdiff + l2, domaine);
+ }
+ else {
+ memcpy(ret, str, doffs) ;
+ memcpy(ret + doffs, newdomain, l2);
+ memcpy(ret + doffs + l2, domaine, str + poffs - domaine);
+ memcpy(ret + poffs + ddiff, newpath, l1);
+ strcpy(ret + poffs + ddiff + l1, pathe);
+ }
+ }
+ else {
+ memcpy(ret, str, poffs);
+ memcpy(ret + poffs, newpath, l1);
+ strcpy(ret + poffs + l1, pathe);
+ }
+ }
+ else if (newdomain) {
+ ret = apr_palloc(r->pool, len + ddiff + 1);
+ l2 = strlen(newdomain);
+ memcpy(ret, str, doffs);
+ memcpy(ret + doffs, newdomain, l2);
+ strcpy(ret + doffs + l2, domaine);
+ }
+ else {
+ ret = (char *)str; /* no change */
+ }
+
+ return ret;
+}
+
+/*
+ * BALANCER related...
+ */
+
+/*
+ * verifies that the balancer name conforms to standards.
+ */
+PROXY_DECLARE(int) ap_proxy_valid_balancer_name(char *name, int i)
+{
+ if (!i)
+ i = sizeof(BALANCER_PREFIX)-1;
+ return (!ap_cstr_casecmpn(name, BALANCER_PREFIX, i));
+}
+
+
+PROXY_DECLARE(proxy_balancer *) ap_proxy_get_balancer(apr_pool_t *p,
+ proxy_server_conf *conf,
+ const char *url,
+ int care)
+{
+ proxy_balancer *balancer;
+ char *c, *uri = apr_pstrdup(p, url);
+ int i;
+ proxy_hashes hash;
+
+ c = strchr(uri, ':');
+ if (c == NULL || c[1] != '/' || c[2] != '/' || c[3] == '\0') {
+ return NULL;
+ }
+ /* remove path from uri */
+ if ((c = strchr(c + 3, '/'))) {
+ *c = '\0';
+ }
+ ap_str_tolower(uri);
+ hash.def = ap_proxy_hashfunc(uri, PROXY_HASHFUNC_DEFAULT);
+ hash.fnv = ap_proxy_hashfunc(uri, PROXY_HASHFUNC_FNV);
+ balancer = (proxy_balancer *)conf->balancers->elts;
+ for (i = 0; i < conf->balancers->nelts; i++) {
+ if (balancer->hash.def == hash.def && balancer->hash.fnv == hash.fnv) {
+ if (!care || !balancer->s->inactive) {
+ return balancer;
+ }
+ }
+ balancer++;
+ }
+ return NULL;
+}
+
+
+PROXY_DECLARE(char *) ap_proxy_update_balancer(apr_pool_t *p,
+ proxy_balancer *balancer,
+ const char *url)
+{
+ apr_uri_t puri;
+ if (!url) {
+ return NULL;
+ }
+ if (apr_uri_parse(p, url, &puri) != APR_SUCCESS) {
+ return apr_psprintf(p, "unable to parse: %s", url);
+ }
+ if (puri.path && PROXY_STRNCPY(balancer->s->vpath, puri.path) != APR_SUCCESS) {
+ return apr_psprintf(p, "balancer %s front-end virtual-path (%s) too long",
+ balancer->s->name, puri.path);
+ }
+ if (puri.hostname && PROXY_STRNCPY(balancer->s->vhost, puri.hostname) != APR_SUCCESS) {
+ return apr_psprintf(p, "balancer %s front-end vhost name (%s) too long",
+ balancer->s->name, puri.hostname);
+ }
+ return NULL;
+}
+
+#define PROXY_UNSET_NONCE '\n'
+
+PROXY_DECLARE(char *) ap_proxy_define_balancer(apr_pool_t *p,
+ proxy_balancer **balancer,
+ proxy_server_conf *conf,
+ const char *url,
+ const char *alias,
+ int do_malloc)
+{
+ proxy_balancer_method *lbmethod;
+ proxy_balancer_shared *bshared;
+ char *c, *q, *uri = apr_pstrdup(p, url);
+ const char *sname;
+
+ /* We should never get here without a valid BALANCER_PREFIX... */
+
+ c = strchr(uri, ':');
+ if (c == NULL || c[1] != '/' || c[2] != '/' || c[3] == '\0')
+ return apr_psprintf(p, "Bad syntax for a balancer name (%s)", uri);
+ /* remove path from uri */
+ if ((q = strchr(c + 3, '/')))
+ *q = '\0';
+
+ ap_str_tolower(uri);
+ *balancer = apr_array_push(conf->balancers);
+ memset(*balancer, 0, sizeof(proxy_balancer));
+
+ /*
+ * NOTE: The default method is byrequests - if it doesn't
+ * exist, that's OK at this time. We check when we share and sync
+ */
+ lbmethod = ap_lookup_provider(PROXY_LBMETHOD, "byrequests", "0");
+ (*balancer)->lbmethod = lbmethod;
+
+ (*balancer)->workers = apr_array_make(p, 5, sizeof(proxy_worker *));
+#if APR_HAS_THREADS
+ (*balancer)->gmutex = NULL;
+ (*balancer)->tmutex = NULL;
+#endif
+
+ if (do_malloc)
+ bshared = ap_malloc(sizeof(proxy_balancer_shared));
+ else
+ bshared = apr_palloc(p, sizeof(proxy_balancer_shared));
+
+ memset(bshared, 0, sizeof(proxy_balancer_shared));
+
+ bshared->was_malloced = (do_malloc != 0);
+ PROXY_STRNCPY(bshared->lbpname, "byrequests");
+ if (PROXY_STRNCPY(bshared->name, uri) != APR_SUCCESS) {
+ if (do_malloc) free(bshared);
+ return apr_psprintf(p, "balancer name (%s) too long", uri);
+ }
+ (*balancer)->lbmethod_set = 1;
+
+ /*
+ * We do the below for verification. The real sname will be
+ * done post_config
+ */
+ ap_pstr2_alnum(p, bshared->name + sizeof(BALANCER_PREFIX) - 1,
+ &sname);
+ sname = apr_pstrcat(p, conf->id, "_", sname, NULL);
+ if (PROXY_STRNCPY(bshared->sname, sname) != APR_SUCCESS) {
+ if (do_malloc) free(bshared);
+ return apr_psprintf(p, "balancer safe-name (%s) too long", sname);
+ }
+ bshared->hash.def = ap_proxy_hashfunc(bshared->name, PROXY_HASHFUNC_DEFAULT);
+ bshared->hash.fnv = ap_proxy_hashfunc(bshared->name, PROXY_HASHFUNC_FNV);
+ (*balancer)->hash = bshared->hash;
+
+ bshared->forcerecovery = 1;
+ bshared->sticky_separator = '.';
+ *bshared->nonce = PROXY_UNSET_NONCE; /* impossible valid input */
+
+ (*balancer)->s = bshared;
+ (*balancer)->sconf = conf;
+
+ return ap_proxy_update_balancer(p, *balancer, alias);
+}
+
+/*
+ * Create an already defined balancer and free up memory.
+ */
+PROXY_DECLARE(apr_status_t) ap_proxy_share_balancer(proxy_balancer *balancer,
+ proxy_balancer_shared *shm,
+ int i)
+{
+ apr_status_t rv = APR_SUCCESS;
+ proxy_balancer_method *lbmethod;
+ char *action = "copying";
+ if (!shm || !balancer->s)
+ return APR_EINVAL;
+
+ if ((balancer->s->hash.def != shm->hash.def) ||
+ (balancer->s->hash.fnv != shm->hash.fnv)) {
+ memcpy(shm, balancer->s, sizeof(proxy_balancer_shared));
+ if (balancer->s->was_malloced)
+ free(balancer->s);
+ } else {
+ action = "re-using";
+ }
+ balancer->s = shm;
+ balancer->s->index = i;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(02337)
+ "%s shm[%d] (0x%pp) for %s", action, i, (void *)shm,
+ balancer->s->name);
+ /* the below should always succeed */
+ lbmethod = ap_lookup_provider(PROXY_LBMETHOD, balancer->s->lbpname, "0");
+ if (lbmethod) {
+ balancer->lbmethod = lbmethod;
+ balancer->lbmethod_set = 1;
+ } else {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, ap_server_conf, APLOGNO(02432)
+ "Cannot find LB Method: %s", balancer->s->lbpname);
+ return APR_EINVAL;
+ }
+ if (*balancer->s->nonce == PROXY_UNSET_NONCE) {
+ char nonce[APR_UUID_FORMATTED_LENGTH + 1];
+ apr_uuid_t uuid;
+
+ /* Generate a pseudo-UUID from the PRNG to use as a nonce for
+ * the lifetime of the process. uuid.data is a char array so
+ * this is an adequate substitute for apr_uuid_get(). */
+ ap_random_insecure_bytes(uuid.data, sizeof uuid.data);
+ apr_uuid_format(nonce, &uuid);
+ rv = PROXY_STRNCPY(balancer->s->nonce, nonce);
+ }
+ return rv;
+}
+
+PROXY_DECLARE(apr_status_t) ap_proxy_initialize_balancer(proxy_balancer *balancer, server_rec *s, apr_pool_t *p)
+{
+#if APR_HAS_THREADS
+ apr_status_t rv = APR_SUCCESS;
+#endif
+ ap_slotmem_provider_t *storage = balancer->storage;
+ apr_size_t size;
+ unsigned int num;
+
+ if (!storage) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s, APLOGNO(00918)
+ "no provider for %s", balancer->s->name);
+ return APR_EGENERAL;
+ }
+ /*
+ * for each balancer we need to init the global
+ * mutex and then attach to the shared worker shm
+ */
+ if (!balancer->gmutex) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s, APLOGNO(00919)
+ "no mutex %s", balancer->s->name);
+ return APR_EGENERAL;
+ }
+
+ /* Re-open the mutex for the child. */
+ rv = apr_global_mutex_child_init(&(balancer->gmutex),
+ apr_global_mutex_lockfile(balancer->gmutex),
+ p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00920)
+ "Failed to reopen mutex %s in child",
+ balancer->s->name);
+ return rv;
+ }
+
+ /* now attach */
+ storage->attach(&(balancer->wslot), balancer->s->sname, &size, &num, p);
+ if (!balancer->wslot) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s, APLOGNO(00921) "slotmem_attach failed");
+ return APR_EGENERAL;
+ }
+ if (balancer->lbmethod && balancer->lbmethod->reset)
+ balancer->lbmethod->reset(balancer, s);
+
+#if APR_HAS_THREADS
+ if (balancer->tmutex == NULL) {
+ rv = apr_thread_mutex_create(&(balancer->tmutex), APR_THREAD_MUTEX_DEFAULT, p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s, APLOGNO(00922)
+ "can not create balancer thread mutex");
+ return rv;
+ }
+ }
+#endif
+ return APR_SUCCESS;
+}
+
+static proxy_worker *proxy_balancer_get_best_worker(proxy_balancer *balancer,
+ request_rec *r,
+ proxy_is_best_callback_fn_t *is_best,
+ void *baton)
+{
+ int i = 0;
+ int cur_lbset = 0;
+ int max_lbset = 0;
+ int unusable_workers = 0;
+ apr_pool_t *tpool = NULL;
+ apr_array_header_t *spares = NULL;
+ apr_array_header_t *standbys = NULL;
+ proxy_worker *worker = NULL;
+ proxy_worker *best_worker = NULL;
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, APLOGNO(10122)
+ "proxy: Entering %s for BALANCER (%s)",
+ balancer->lbmethod->name, balancer->s->name);
+
+ apr_pool_create(&tpool, r->pool);
+ apr_pool_tag(tpool, "proxy_lb_best");
+
+ spares = apr_array_make(tpool, 1, sizeof(proxy_worker*));
+ standbys = apr_array_make(tpool, 1, sizeof(proxy_worker*));
+
+ /* Process lbsets in order, only replacing unusable workers in a given lbset
+ * with available spares from the same lbset. Hot standbys will be used as a
+ * last resort when all other workers and spares are unavailable.
+ */
+ for (cur_lbset = 0; !best_worker && (cur_lbset <= max_lbset); cur_lbset++) {
+ unusable_workers = 0;
+ apr_array_clear(spares);
+ apr_array_clear(standbys);
+
+ for (i = 0; i < balancer->workers->nelts; i++) {
+ worker = APR_ARRAY_IDX(balancer->workers, i, proxy_worker *);
+
+ if (worker->s->lbset > max_lbset) {
+ max_lbset = worker->s->lbset;
+ }
+
+ if (worker->s->lbset != cur_lbset) {
+ continue;
+ }
+
+ /* A draining worker that is neither a spare nor a standby should be
+ * considered unusable to be replaced by spares.
+ */
+ if (PROXY_WORKER_IS_DRAINING(worker)) {
+ if (!PROXY_WORKER_IS_SPARE(worker) && !PROXY_WORKER_IS_STANDBY(worker)) {
+ unusable_workers++;
+ }
+
+ continue;
+ }
+
+ /* If the worker is in error state run retry on that worker. It will
+ * be marked as operational if the retry timeout is elapsed. The
+ * worker might still be unusable, but we try anyway.
+ */
+ if (!PROXY_WORKER_IS_USABLE(worker)) {
+ ap_proxy_retry_worker("BALANCER", worker, r->server);
+ }
+
+ if (PROXY_WORKER_IS_SPARE(worker)) {
+ if (PROXY_WORKER_IS_USABLE(worker)) {
+ APR_ARRAY_PUSH(spares, proxy_worker *) = worker;
+ }
+ }
+ else if (PROXY_WORKER_IS_STANDBY(worker)) {
+ if (PROXY_WORKER_IS_USABLE(worker)) {
+ APR_ARRAY_PUSH(standbys, proxy_worker *) = worker;
+ }
+ }
+ else if (PROXY_WORKER_IS_USABLE(worker)) {
+ if (is_best(worker, best_worker, baton)) {
+ best_worker = worker;
+ }
+ }
+ else {
+ unusable_workers++;
+ }
+ }
+
+ /* Check if any spares are best. */
+ for (i = 0; (i < spares->nelts) && (i < unusable_workers); i++) {
+ worker = APR_ARRAY_IDX(spares, i, proxy_worker *);
+
+ if (is_best(worker, best_worker, baton)) {
+ best_worker = worker;
+ }
+ }
+
+ /* If no workers are available, use the standbys. */
+ if (!best_worker) {
+ for (i = 0; i < standbys->nelts; i++) {
+ worker = APR_ARRAY_IDX(standbys, i, proxy_worker *);
+
+ if (is_best(worker, best_worker, baton)) {
+ best_worker = worker;
+ }
+ }
+ }
+ }
+
+ apr_pool_destroy(tpool);
+
+ if (best_worker) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, APLOGNO(10123)
+ "proxy: %s selected worker \"%s\" : busy %" APR_SIZE_T_FMT " : lbstatus %d",
+ balancer->lbmethod->name, best_worker->s->name_ex,
+ best_worker->s->busy, best_worker->s->lbstatus);
+ }
+
+ return best_worker;
+}
+
+PROXY_DECLARE(proxy_worker *) ap_proxy_balancer_get_best_worker(proxy_balancer *balancer,
+ request_rec *r,
+ proxy_is_best_callback_fn_t *is_best,
+ void *baton)
+{
+ return proxy_balancer_get_best_worker(balancer, r, is_best, baton);
+}
+
+/*
+ * CONNECTION related...
+ */
+
+static void socket_cleanup(proxy_conn_rec *conn)
+{
+ conn->sock = NULL;
+ conn->tmp_bb = NULL;
+ conn->connection = NULL;
+ conn->ssl_hostname = NULL;
+ apr_pool_clear(conn->scpool);
+}
+
+static apr_status_t conn_pool_cleanup(void *theworker)
+{
+ ((proxy_worker *)theworker)->cp = NULL;
+ return APR_SUCCESS;
+}
+
+static void init_conn_pool(apr_pool_t *p, proxy_worker *worker)
+{
+ apr_pool_t *pool;
+ apr_pool_t *dns_pool;
+ proxy_conn_pool *cp;
+
+ /*
+ * Create a connection pool's subpool.
+ * This pool is used for connection recycling.
+ * Once the worker is added it is never removed but
+ * it can be disabled.
+ */
+ apr_pool_create(&pool, p);
+ apr_pool_tag(pool, "proxy_worker_cp");
+ /*
+ * Create a subpool of the connection pool for worker
+ * scoped DNS resolutions. This is needed to avoid race
+ * conditions in using the connection pool by multiple
+ * threads during ramp up.
+ */
+ apr_pool_create(&dns_pool, pool);
+ apr_pool_tag(dns_pool, "proxy_worker_dns");
+ /*
+ * Alloc from the same pool as worker.
+ * proxy_conn_pool is permanently attached to the worker.
+ */
+ cp = (proxy_conn_pool *)apr_pcalloc(p, sizeof(proxy_conn_pool));
+ cp->pool = pool;
+ cp->dns_pool = dns_pool;
+ worker->cp = cp;
+
+ apr_pool_pre_cleanup_register(p, worker, conn_pool_cleanup);
+}
+
+PROXY_DECLARE(int) ap_proxy_connection_reusable(proxy_conn_rec *conn)
+{
+ proxy_worker *worker = conn->worker;
+
+ return ! (conn->close || !worker->s->is_address_reusable || worker->s->disablereuse);
+}
+
+static apr_status_t connection_cleanup(void *theconn)
+{
+ proxy_conn_rec *conn = (proxy_conn_rec *)theconn;
+ proxy_worker *worker = conn->worker;
+
+ if (conn->r) {
+ apr_pool_destroy(conn->r->pool);
+ conn->r = NULL;
+ }
+
+ /* Sanity check: Did we already return the pooled connection? */
+ if (conn->inreslist) {
+ ap_log_perror(APLOG_MARK, APLOG_ERR, 0, conn->pool, APLOGNO(00923)
+ "Pooled connection 0x%pp for worker %s has been"
+ " already returned to the connection pool.", conn,
+ ap_proxy_worker_name(conn->pool, worker));
+ return APR_SUCCESS;
+ }
+
+ /* determine if the connection need to be closed */
+ if (!worker->s->is_address_reusable || worker->s->disablereuse) {
+ apr_pool_t *p = conn->pool;
+ apr_pool_clear(p);
+ conn = apr_pcalloc(p, sizeof(proxy_conn_rec));
+ conn->pool = p;
+ conn->worker = worker;
+ apr_pool_create(&(conn->scpool), p);
+ apr_pool_tag(conn->scpool, "proxy_conn_scpool");
+ }
+ else if (conn->close
+ || (conn->connection
+ && conn->connection->keepalive == AP_CONN_CLOSE)) {
+ socket_cleanup(conn);
+ conn->close = 0;
+ }
+ else if (conn->is_ssl) {
+ /* Unbind/reset the SSL connection dir config (sslconn->dc) from
+ * r->per_dir_config, r will likely get destroyed before this proxy
+ * conn is reused.
+ */
+ ap_proxy_ssl_engine(conn->connection, worker->section_config, 1);
+ }
+
+ if (worker->s->hmax && worker->cp->res) {
+ conn->inreslist = 1;
+ apr_reslist_release(worker->cp->res, (void *)conn);
+ }
+ else
+ {
+ worker->cp->conn = conn;
+ }
+
+ /* Always return the SUCCESS */
+ return APR_SUCCESS;
+}
+
+/* DEPRECATED */
+PROXY_DECLARE(apr_status_t) ap_proxy_ssl_connection_cleanup(proxy_conn_rec *conn,
+ request_rec *r)
+{
+ apr_status_t rv;
+
+ /*
+ * If we have an existing SSL connection it might be possible that the
+ * server sent some SSL message we have not read so far (e.g. an SSL
+ * shutdown message if the server closed the keepalive connection while
+ * the connection was held unused in our pool).
+ * So ensure that if present (=> APR_NONBLOCK_READ) it is read and
+ * processed. We don't expect any data to be in the returned brigade.
+ */
+ if (conn->sock && conn->connection) {
+ rv = ap_get_brigade(conn->connection->input_filters, conn->tmp_bb,
+ AP_MODE_READBYTES, APR_NONBLOCK_READ,
+ HUGE_STRING_LEN);
+ if (!APR_BRIGADE_EMPTY(conn->tmp_bb)) {
+ apr_off_t len;
+
+ rv = apr_brigade_length(conn->tmp_bb, 0, &len);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, rv, r,
+ "SSL cleanup brigade contained %"
+ APR_OFF_T_FMT " bytes of data.", len);
+ apr_brigade_cleanup(conn->tmp_bb);
+ }
+ if ((rv != APR_SUCCESS) && !APR_STATUS_IS_EAGAIN(rv)) {
+ socket_cleanup(conn);
+ }
+ }
+ return APR_SUCCESS;
+}
+
+/* reslist constructor */
+static apr_status_t connection_constructor(void **resource, void *params,
+ apr_pool_t *pool)
+{
+ apr_pool_t *ctx;
+ apr_pool_t *scpool;
+ proxy_conn_rec *conn;
+ proxy_worker *worker = (proxy_worker *)params;
+
+ /*
+ * Create the subpool for each connection
+ * This keeps the memory consumption constant
+ * when disconnecting from backend.
+ */
+ apr_pool_create(&ctx, pool);
+ apr_pool_tag(ctx, "proxy_conn_pool");
+ /*
+ * Create another subpool that manages the data for the
+ * socket and the connection member of the proxy_conn_rec struct as we
+ * destroy this data more frequently than other data in the proxy_conn_rec
+ * struct like hostname and addr (at least in the case where we have
+ * keepalive connections that timed out).
+ */
+ apr_pool_create(&scpool, ctx);
+ apr_pool_tag(scpool, "proxy_conn_scpool");
+ conn = apr_pcalloc(ctx, sizeof(proxy_conn_rec));
+
+ conn->pool = ctx;
+ conn->scpool = scpool;
+ conn->worker = worker;
+ conn->inreslist = 1;
+ *resource = conn;
+
+ return APR_SUCCESS;
+}
+
+/* reslist destructor */
+static apr_status_t connection_destructor(void *resource, void *params,
+ apr_pool_t *pool)
+{
+ proxy_worker *worker = params;
+
+ /* Destroy the pool only if not called from reslist_destroy */
+ if (worker->cp) {
+ proxy_conn_rec *conn = resource;
+ apr_pool_destroy(conn->pool);
+ }
+
+ return APR_SUCCESS;
+}
+
+/*
+ * WORKER related...
+ */
+
+PROXY_DECLARE(char *) ap_proxy_worker_name(apr_pool_t *p,
+ proxy_worker *worker)
+{
+ if (!(*worker->s->uds_path) || !p) {
+ /* just in case */
+ return worker->s->name_ex;
+ }
+ return apr_pstrcat(p, "unix:", worker->s->uds_path, "|", worker->s->name_ex, NULL);
+}
+
+PROXY_DECLARE(int) ap_proxy_worker_can_upgrade(apr_pool_t *p,
+ const proxy_worker *worker,
+ const char *upgrade,
+ const char *dflt)
+{
+ /* Find in worker->s->upgrade list (if any) */
+ const char *worker_upgrade = worker->s->upgrade;
+ if (*worker_upgrade) {
+ return (strcmp(worker_upgrade, "*") == 0
+ || ap_cstr_casecmp(worker_upgrade, upgrade) == 0
+ || ap_find_token(p, worker_upgrade, upgrade));
+ }
+
+ /* Compare to the provided default (if any) */
+ return (dflt && ap_cstr_casecmp(dflt, upgrade) == 0);
+}
+
+/*
+ * Taken from ap_strcmp_match() :
+ * Match = 0, NoMatch = 1, Abort = -1, Inval = -2
+ * Based loosely on sections of wildmat.c by Rich Salz
+ * Hmmm... shouldn't this really go component by component?
+ *
+ * Adds handling of the "\<any>" => "<any>" unescaping.
+ */
+static int ap_proxy_strcmp_ematch(const char *str, const char *expected)
+{
+ apr_size_t x, y;
+
+ for (x = 0, y = 0; expected[y]; ++y, ++x) {
+ if (expected[y] == '$' && apr_isdigit(expected[y + 1])) {
+ do {
+ y += 2;
+ } while (expected[y] == '$' && apr_isdigit(expected[y + 1]));
+ if (!expected[y])
+ return 0;
+ while (str[x]) {
+ int ret;
+ if ((ret = ap_proxy_strcmp_ematch(&str[x++], &expected[y])) != 1)
+ return ret;
+ }
+ return -1;
+ }
+ else if (!str[x]) {
+ return -1;
+ }
+ else if (expected[y] == '\\' && !expected[++y]) {
+ /* NUL is an invalid char! */
+ return -2;
+ }
+ if (str[x] != expected[y])
+ return 1;
+ }
+ /* We got all the way through the worker path without a difference */
+ return 0;
+}
+
+PROXY_DECLARE(proxy_worker *) ap_proxy_get_worker_ex(apr_pool_t *p,
+ proxy_balancer *balancer,
+ proxy_server_conf *conf,
+ const char *url,
+ unsigned int mask)
+{
+ proxy_worker *worker;
+ proxy_worker *max_worker = NULL;
+ int max_match = 0;
+ int url_length;
+ int min_match;
+ int worker_name_length;
+ const char *c;
+ char *url_copy;
+ int i;
+
+ if (!url) {
+ return NULL;
+ }
+
+ if (!(mask & AP_PROXY_WORKER_NO_UDS)) {
+ url = ap_proxy_de_socketfy(p, url);
+ if (!url) {
+ return NULL;
+ }
+ }
+
+ c = ap_strchr_c(url, ':');
+ if (c == NULL || c[1] != '/' || c[2] != '/' || c[3] == '\0') {
+ return NULL;
+ }
+
+ url_length = strlen(url);
+ url_copy = apr_pstrmemdup(p, url, url_length);
+
+ /* Default to lookup for both _PREFIX and _MATCH workers */
+ if (!(mask & (AP_PROXY_WORKER_IS_PREFIX | AP_PROXY_WORKER_IS_MATCH))) {
+ mask |= AP_PROXY_WORKER_IS_PREFIX | AP_PROXY_WORKER_IS_MATCH;
+ }
+
+ /*
+ * We need to find the start of the path and
+ * therefore we know the length of the scheme://hostname/
+ * part to we can force-lowercase everything up to
+ * the start of the path.
+ */
+ c = ap_strchr_c(c+3, '/');
+ if (c) {
+ char *pathstart;
+ pathstart = url_copy + (c - url);
+ *pathstart = '\0';
+ ap_str_tolower(url_copy);
+ min_match = strlen(url_copy);
+ *pathstart = '/';
+ }
+ else {
+ ap_str_tolower(url_copy);
+ min_match = strlen(url_copy);
+ }
+ /*
+ * Do a "longest match" on the worker name to find the worker that
+ * fits best to the URL, but keep in mind that we must have at least
+ * a minimum matching of length min_match such that
+ * scheme://hostname[:port] matches between worker and url.
+ */
+
+ if (balancer) {
+ proxy_worker **workers = (proxy_worker **)balancer->workers->elts;
+ for (i = 0; i < balancer->workers->nelts; i++, workers++) {
+ worker = *workers;
+ if ( ((worker_name_length = strlen(worker->s->name_ex)) <= url_length)
+ && (worker_name_length >= min_match)
+ && (worker_name_length > max_match)
+ && (worker->s->is_name_matchable
+ || ((mask & AP_PROXY_WORKER_IS_PREFIX)
+ && strncmp(url_copy, worker->s->name_ex,
+ worker_name_length) == 0))
+ && (!worker->s->is_name_matchable
+ || ((mask & AP_PROXY_WORKER_IS_MATCH)
+ && ap_proxy_strcmp_ematch(url_copy,
+ worker->s->name_ex) == 0)) ) {
+ max_worker = worker;
+ max_match = worker_name_length;
+ }
+ }
+ } else {
+ worker = (proxy_worker *)conf->workers->elts;
+ for (i = 0; i < conf->workers->nelts; i++, worker++) {
+ if ( ((worker_name_length = strlen(worker->s->name_ex)) <= url_length)
+ && (worker_name_length >= min_match)
+ && (worker_name_length > max_match)
+ && (worker->s->is_name_matchable
+ || ((mask & AP_PROXY_WORKER_IS_PREFIX)
+ && strncmp(url_copy, worker->s->name_ex,
+ worker_name_length) == 0))
+ && (!worker->s->is_name_matchable
+ || ((mask & AP_PROXY_WORKER_IS_MATCH)
+ && ap_proxy_strcmp_ematch(url_copy,
+ worker->s->name_ex) == 0)) ) {
+ max_worker = worker;
+ max_match = worker_name_length;
+ }
+ }
+ }
+
+ return max_worker;
+}
+
+PROXY_DECLARE(proxy_worker *) ap_proxy_get_worker(apr_pool_t *p,
+ proxy_balancer *balancer,
+ proxy_server_conf *conf,
+ const char *url)
+{
+ return ap_proxy_get_worker_ex(p, balancer, conf, url, 0);
+}
+
+/*
+ * To create a worker from scratch first we define the
+ * specifics of the worker; this is all local data.
+ * We then allocate space for it if data needs to be
+ * shared. This allows for dynamic addition during
+ * config and runtime.
+ */
+PROXY_DECLARE(char *) ap_proxy_define_worker_ex(apr_pool_t *p,
+ proxy_worker **worker,
+ proxy_balancer *balancer,
+ proxy_server_conf *conf,
+ const char *url,
+ unsigned int mask)
+{
+ apr_status_t rv;
+ proxy_worker_shared *wshared;
+ const char *ptr = NULL, *sockpath = NULL, *pdollars = NULL;
+ apr_port_t port_of_scheme;
+ apr_uri_t uri;
+
+ /*
+ * Look to see if we are using UDS:
+ * require format: unix:/path/foo/bar.sock|http://ignored/path2/
+ * This results in talking http to the socket at /path/foo/bar.sock
+ */
+ if (!ap_cstr_casecmpn(url, "unix:", 5)
+ && (ptr = ap_strchr_c(url + 5, '|'))) {
+ rv = apr_uri_parse(p, apr_pstrmemdup(p, url, ptr - url), &uri);
+ if (rv == APR_SUCCESS) {
+ sockpath = ap_runtime_dir_relative(p, uri.path);;
+ ptr++; /* so we get the scheme for the uds */
+ }
+ else {
+ ptr = url;
+ }
+ }
+ else {
+ ptr = url;
+ }
+
+ if (mask & AP_PROXY_WORKER_IS_MATCH) {
+ /* apr_uri_parse() will accept the '$' sign anywhere in the URL but
+ * in the :port part, and we don't want scheme://host:port$1$2/path
+ * to fail (e.g. "ProxyPassMatch ^/(a|b)(/.*)? http://host:port$2").
+ * So we trim all the $n from the :port and prepend them in uri.path
+ * afterward for apr_uri_unparse() to restore the original URL below.
+ */
+#define IS_REF(x) (x[0] == '$' && apr_isdigit(x[1]))
+ const char *pos = ap_strstr_c(ptr, "://");
+ if (pos) {
+ pos += 3;
+ while (*pos && *pos != ':' && *pos != '/') {
+ pos++;
+ }
+ if (*pos == ':') {
+ pos++;
+ while (*pos && !IS_REF(pos) && *pos != '/') {
+ pos++;
+ }
+ if (IS_REF(pos)) {
+ struct iovec vec[2];
+ const char *path = pos + 2;
+ while (*path && *path != '/') {
+ path++;
+ }
+ pdollars = apr_pstrmemdup(p, pos, path - pos);
+ vec[0].iov_base = (void *)ptr;
+ vec[0].iov_len = pos - ptr;
+ vec[1].iov_base = (void *)path;
+ vec[1].iov_len = strlen(path);
+ ptr = apr_pstrcatv(p, vec, 2, NULL);
+ }
+ }
+ }
+#undef IS_REF
+ }
+
+ /* Normalize the url (worker name) */
+ rv = apr_uri_parse(p, ptr, &uri);
+ if (rv != APR_SUCCESS) {
+ return apr_pstrcat(p, "Unable to parse URL: ", url, NULL);
+ }
+ if (!uri.scheme) {
+ return apr_pstrcat(p, "URL must be absolute!: ", url, NULL);
+ }
+ if (!uri.hostname) {
+ if (sockpath) {
+ /* allow for unix:/path|http: */
+ uri.hostname = "localhost";
+ }
+ else {
+ return apr_pstrcat(p, "URL must be absolute!: ", url, NULL);
+ }
+ }
+ else {
+ ap_str_tolower(uri.hostname);
+ }
+ ap_str_tolower(uri.scheme);
+ port_of_scheme = ap_proxy_port_of_scheme(uri.scheme);
+ if (uri.port && uri.port == port_of_scheme) {
+ uri.port = 0;
+ }
+ if (pdollars) {
+ /* Restore/prepend pdollars into the path. */
+ uri.path = apr_pstrcat(p, pdollars, uri.path, NULL);
+ }
+ ptr = apr_uri_unparse(p, &uri, APR_URI_UNP_REVEALPASSWORD);
+
+ /*
+ * Workers can be associated w/ balancers or on their
+ * own; ie: the generic reverse-proxy or a worker
+ * in a simple ProxyPass statement. eg:
+ *
+ * ProxyPass / http://www.example.com
+ *
+ * in which case the worker goes in the conf slot.
+ */
+ if (balancer) {
+ proxy_worker **runtime;
+ /* recall that we get a ptr to the ptr here */
+ runtime = apr_array_push(balancer->workers);
+ *worker = *runtime = apr_palloc(p, sizeof(proxy_worker)); /* right to left baby */
+ /* we've updated the list of workers associated with
+ * this balancer *locally* */
+ balancer->wupdated = apr_time_now();
+ } else if (conf) {
+ *worker = apr_array_push(conf->workers);
+ } else {
+ /* we need to allocate space here */
+ *worker = apr_palloc(p, sizeof(proxy_worker));
+ }
+ memset(*worker, 0, sizeof(proxy_worker));
+
+ /* right here we just want to tuck away the worker info.
+ * if called during config, we don't have shm setup yet,
+ * so just note the info for later. */
+ if (mask & AP_PROXY_WORKER_IS_MALLOCED)
+ wshared = ap_malloc(sizeof(proxy_worker_shared)); /* will be freed ap_proxy_share_worker */
+ else
+ wshared = apr_palloc(p, sizeof(proxy_worker_shared));
+ memset(wshared, 0, sizeof(proxy_worker_shared));
+
+ if (PROXY_STRNCPY(wshared->name_ex, ptr) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(10366)
+ "Alert! worker name (%s) too long; truncated to: %s", ptr, wshared->name_ex);
+ }
+ if (PROXY_STRNCPY(wshared->name, ptr) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(010118)
+ "worker name (%s) too long; truncated for legacy modules that do not use "
+ "proxy_worker_shared->name_ex: %s", ptr, wshared->name);
+ }
+ if (PROXY_STRNCPY(wshared->scheme, uri.scheme) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(010117)
+ "Alert! worker scheme (%s) too long; truncated to: %s", uri.scheme, wshared->scheme);
+ }
+ if (PROXY_STRNCPY(wshared->hostname_ex, uri.hostname) != APR_SUCCESS) {
+ return apr_psprintf(p, "worker hostname (%s) too long", uri.hostname);
+ }
+ if (PROXY_STRNCPY(wshared->hostname, uri.hostname) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(010118)
+ "worker hostname (%s) too long; truncated for legacy modules that do not use "
+ "proxy_worker_shared->hostname_ex: %s", uri.hostname, wshared->hostname);
+ }
+ wshared->port = (uri.port) ? uri.port : port_of_scheme;
+ wshared->flush_packets = flush_off;
+ wshared->flush_wait = PROXY_FLUSH_WAIT;
+ wshared->is_address_reusable = 1;
+ wshared->lbfactor = 100;
+ wshared->passes = 1;
+ wshared->fails = 1;
+ wshared->interval = apr_time_from_sec(HCHECK_WATHCHDOG_DEFAULT_INTERVAL);
+ wshared->smax = -1;
+ wshared->hash.def = ap_proxy_hashfunc(wshared->name_ex, PROXY_HASHFUNC_DEFAULT);
+ wshared->hash.fnv = ap_proxy_hashfunc(wshared->name_ex, PROXY_HASHFUNC_FNV);
+ wshared->was_malloced = (mask & AP_PROXY_WORKER_IS_MALLOCED) != 0;
+ wshared->is_name_matchable = 0;
+ if (sockpath) {
+ if (PROXY_STRNCPY(wshared->uds_path, sockpath) != APR_SUCCESS) {
+ return apr_psprintf(p, "worker uds path (%s) too long", sockpath);
+ }
+
+ }
+ else {
+ *wshared->uds_path = '\0';
+ }
+ if (!balancer) {
+ wshared->status |= PROXY_WORKER_IGNORE_ERRORS;
+ }
+
+ (*worker)->hash = wshared->hash;
+ (*worker)->context = NULL;
+ (*worker)->cp = NULL;
+ (*worker)->balancer = balancer;
+ (*worker)->s = wshared;
+
+ if (mask & AP_PROXY_WORKER_IS_MATCH) {
+ (*worker)->s->is_name_matchable = 1;
+ if (ap_strchr_c((*worker)->s->name_ex, '$')) {
+ /* Before AP_PROXY_WORKER_IS_MATCH (< 2.4.47), a regex worker
+ * with dollar substitution was never matched against the actual
+ * URL thus the request fell through the generic worker. To avoid
+ * dns and connection reuse compat issues, let's disable connection
+ * reuse by default, it can still be overwritten by an explicit
+ * enablereuse=on.
+ */
+ (*worker)->s->disablereuse = 1;
+ }
+ }
+
+ return NULL;
+}
+
+PROXY_DECLARE(char *) ap_proxy_define_worker(apr_pool_t *p,
+ proxy_worker **worker,
+ proxy_balancer *balancer,
+ proxy_server_conf *conf,
+ const char *url,
+ int do_malloc)
+{
+ return ap_proxy_define_worker_ex(p, worker, balancer, conf, url,
+ AP_PROXY_WORKER_IS_PREFIX |
+ (do_malloc ? AP_PROXY_WORKER_IS_MALLOCED
+ : 0));
+}
+
+/* DEPRECATED */
+PROXY_DECLARE(char *) ap_proxy_define_match_worker(apr_pool_t *p,
+ proxy_worker **worker,
+ proxy_balancer *balancer,
+ proxy_server_conf *conf,
+ const char *url,
+ int do_malloc)
+{
+ return ap_proxy_define_worker_ex(p, worker, balancer, conf, url,
+ AP_PROXY_WORKER_IS_MATCH |
+ (do_malloc ? AP_PROXY_WORKER_IS_MALLOCED
+ : 0));
+}
+
+/*
+ * Create an already defined worker and free up memory
+ */
+PROXY_DECLARE(apr_status_t) ap_proxy_share_worker(proxy_worker *worker, proxy_worker_shared *shm,
+ int i)
+{
+ char *action = "copying";
+ if (!shm || !worker->s)
+ return APR_EINVAL;
+
+ if ((worker->s->hash.def != shm->hash.def) ||
+ (worker->s->hash.fnv != shm->hash.fnv)) {
+ memcpy(shm, worker->s, sizeof(proxy_worker_shared));
+ if (worker->s->was_malloced)
+ free(worker->s); /* was malloced in ap_proxy_define_worker */
+ } else {
+ action = "re-using";
+ }
+ worker->s = shm;
+ worker->s->index = i;
+
+ if (APLOGdebug(ap_server_conf)) {
+ apr_pool_t *pool;
+ apr_pool_create(&pool, ap_server_conf->process->pool);
+ apr_pool_tag(pool, "proxy_worker_name");
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(02338)
+ "%s shm[%d] (0x%pp) for worker: %s", action, i, (void *)shm,
+ ap_proxy_worker_name(pool, worker));
+ if (pool) {
+ apr_pool_destroy(pool);
+ }
+ }
+ return APR_SUCCESS;
+}
+
+PROXY_DECLARE(apr_status_t) ap_proxy_initialize_worker(proxy_worker *worker, server_rec *s, apr_pool_t *p)
+{
+ APR_OPTIONAL_FN_TYPE(http2_get_num_workers) *get_h2_num_workers;
+ apr_status_t rv = APR_SUCCESS;
+ int max_threads, minw, maxw;
+
+ if (worker->s->status & PROXY_WORKER_INITIALIZED) {
+ /* The worker is already initialized */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00924)
+ "worker %s shared already initialized",
+ ap_proxy_worker_name(p, worker));
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00925)
+ "initializing worker %s shared",
+ ap_proxy_worker_name(p, worker));
+ /* Set default parameters */
+ if (!worker->s->retry_set) {
+ worker->s->retry = apr_time_from_sec(PROXY_WORKER_DEFAULT_RETRY);
+ }
+ /* By default address is reusable unless DisableReuse is set */
+ if (worker->s->disablereuse) {
+ worker->s->is_address_reusable = 0;
+ }
+ else {
+ worker->s->is_address_reusable = 1;
+ }
+
+ /*
+ * When mod_http2 is loaded we might have more threads since it has
+ * its own pool of processing threads.
+ */
+ ap_mpm_query(AP_MPMQ_MAX_THREADS, &max_threads);
+ get_h2_num_workers = APR_RETRIEVE_OPTIONAL_FN(http2_get_num_workers);
+ if (get_h2_num_workers) {
+ get_h2_num_workers(s, &minw, &maxw);
+ /* So now the max is:
+ * max_threads-1 threads for HTTP/1 each requiring one connection
+ * + one thread for HTTP/2 requiring maxw connections
+ */
+ max_threads = max_threads - 1 + maxw;
+ }
+ if (max_threads > 1) {
+ /* Default hmax is max_threads to scale with the load and never
+ * wait for an idle connection to proceed.
+ */
+ if (worker->s->hmax == 0) {
+ worker->s->hmax = max_threads;
+ }
+ if (worker->s->smax == -1 || worker->s->smax > worker->s->hmax) {
+ worker->s->smax = worker->s->hmax;
+ }
+ /* Set min to be lower than smax */
+ if (worker->s->min > worker->s->smax) {
+ worker->s->min = worker->s->smax;
+ }
+ }
+ else {
+ /* This will suppress the apr_reslist creation */
+ worker->s->min = worker->s->smax = worker->s->hmax = 0;
+ }
+ }
+
+ /* What if local is init'ed and shm isn't?? Even possible? */
+ if (worker->local_status & PROXY_WORKER_INITIALIZED) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00926)
+ "worker %s local already initialized",
+ ap_proxy_worker_name(p, worker));
+ }
+ else {
+ apr_global_mutex_lock(proxy_mutex);
+ /* Check again after we got the lock if we are still uninitialized */
+ if (!(AP_VOLATILIZE_T(unsigned int, worker->local_status) & PROXY_WORKER_INITIALIZED)) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00927)
+ "initializing worker %s local",
+ ap_proxy_worker_name(p, worker));
+ /* Now init local worker data */
+#if APR_HAS_THREADS
+ if (worker->tmutex == NULL) {
+ rv = apr_thread_mutex_create(&(worker->tmutex), APR_THREAD_MUTEX_DEFAULT, p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00928)
+ "can not create worker thread mutex");
+ apr_global_mutex_unlock(proxy_mutex);
+ return rv;
+ }
+ }
+#endif
+ if (worker->cp == NULL)
+ init_conn_pool(p, worker);
+ if (worker->cp == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00929)
+ "can not create connection pool");
+ apr_global_mutex_unlock(proxy_mutex);
+ return APR_EGENERAL;
+ }
+
+ if (worker->s->hmax) {
+ rv = apr_reslist_create(&(worker->cp->res),
+ worker->s->min, worker->s->smax,
+ worker->s->hmax, worker->s->ttl,
+ connection_constructor, connection_destructor,
+ worker, worker->cp->pool);
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00930)
+ "initialized pool in child %" APR_PID_T_FMT " for (%s:%d) min=%d max=%d smax=%d",
+ getpid(), worker->s->hostname_ex, (int)worker->s->port,
+ worker->s->min, worker->s->hmax, worker->s->smax);
+
+ /* Set the acquire timeout */
+ if (rv == APR_SUCCESS && worker->s->acquire_set) {
+ apr_reslist_timeout_set(worker->cp->res, worker->s->acquire);
+ }
+
+ }
+ else {
+ void *conn;
+
+ rv = connection_constructor(&conn, worker, worker->cp->pool);
+ worker->cp->conn = conn;
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(00931)
+ "initialized single connection worker in child %" APR_PID_T_FMT " for (%s:%d)",
+ getpid(), worker->s->hostname_ex,
+ (int)worker->s->port);
+ }
+ if (rv == APR_SUCCESS) {
+ worker->local_status |= (PROXY_WORKER_INITIALIZED);
+ }
+ }
+ apr_global_mutex_unlock(proxy_mutex);
+
+ }
+ if (rv == APR_SUCCESS) {
+ worker->s->status |= (PROXY_WORKER_INITIALIZED);
+ }
+ return rv;
+}
+
+static int ap_proxy_retry_worker(const char *proxy_function, proxy_worker *worker,
+ server_rec *s)
+{
+ if (worker->s->status & PROXY_WORKER_IN_ERROR) {
+ if (PROXY_WORKER_IS(worker, PROXY_WORKER_STOPPED)) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(3305)
+ "%s: Won't retry worker (%s:%d): stopped",
+ proxy_function, worker->s->hostname_ex,
+ (int)worker->s->port);
+ return DECLINED;
+ }
+ if ((worker->s->status & PROXY_WORKER_IGNORE_ERRORS)
+ || apr_time_now() > worker->s->error_time + worker->s->retry) {
+ ++worker->s->retries;
+ worker->s->status &= ~PROXY_WORKER_IN_ERROR;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00932)
+ "%s: worker for (%s:%d) has been marked for retry",
+ proxy_function, worker->s->hostname_ex,
+ (int)worker->s->port);
+ return OK;
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00933)
+ "%s: too soon to retry worker for (%s:%d)",
+ proxy_function, worker->s->hostname_ex,
+ (int)worker->s->port);
+ return DECLINED;
+ }
+ }
+ else {
+ return OK;
+ }
+}
+
+/*
+ * In the case of the reverse proxy, we need to see if we
+ * were passed a UDS url (eg: from mod_proxy) and adjust uds_path
+ * as required.
+ */
+static int fix_uds_filename(request_rec *r, char **url)
+{
+ char *uds_url = r->filename + 6, *origin_url;
+
+ if (!strncmp(r->filename, "proxy:", 6) &&
+ !ap_cstr_casecmpn(uds_url, "unix:", 5) &&
+ (origin_url = ap_strchr(uds_url + 5, '|'))) {
+ char *uds_path = NULL;
+ apr_size_t url_len;
+ apr_uri_t urisock;
+ apr_status_t rv;
+
+ *origin_url = '\0';
+ rv = apr_uri_parse(r->pool, uds_url, &urisock);
+ *origin_url++ = '|';
+
+ if (rv == APR_SUCCESS && urisock.path && (!urisock.hostname
+ || !urisock.hostname[0])) {
+ uds_path = ap_runtime_dir_relative(r->pool, urisock.path);
+ }
+ if (!uds_path) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10292)
+ "Invalid proxy UDS filename (%s)", r->filename);
+ return 0;
+ }
+ apr_table_setn(r->notes, "uds_path", uds_path);
+
+ /* Remove the UDS path from *url and r->filename */
+ url_len = strlen(origin_url);
+ *url = apr_pstrmemdup(r->pool, origin_url, url_len);
+ memcpy(uds_url, *url, url_len + 1);
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+ "*: rewrite of url due to UDS(%s): %s (%s)",
+ uds_path, *url, r->filename);
+ }
+ return 1;
+}
+
+PROXY_DECLARE(int) ap_proxy_pre_request(proxy_worker **worker,
+ proxy_balancer **balancer,
+ request_rec *r,
+ proxy_server_conf *conf, char **url)
+{
+ int access_status;
+
+ access_status = proxy_run_pre_request(worker, balancer, r, conf, url);
+ if (access_status == DECLINED && *balancer == NULL) {
+ const int forward = (r->proxyreq == PROXYREQ_PROXY);
+ *worker = ap_proxy_get_worker_ex(r->pool, NULL, conf, *url,
+ forward ? AP_PROXY_WORKER_NO_UDS : 0);
+ if (*worker) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+ "%s: found worker %s for %s",
+ (*worker)->s->scheme, (*worker)->s->name_ex, *url);
+ if (!forward && !fix_uds_filename(r, url)) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ access_status = OK;
+ }
+ else if (forward) {
+ if (conf->forward) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+ "*: found forward proxy worker for %s", *url);
+ *worker = conf->forward;
+ access_status = OK;
+ /*
+ * The forward worker does not keep connections alive, so
+ * ensure that mod_proxy_http does the correct thing
+ * regarding the Connection header in the request.
+ */
+ apr_table_setn(r->subprocess_env, "proxy-nokeepalive", "1");
+ }
+ }
+ else if (r->proxyreq == PROXYREQ_REVERSE) {
+ if (conf->reverse) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+ "*: using default reverse proxy worker for %s "
+ "(no keepalive)", *url);
+ *worker = conf->reverse;
+ access_status = OK;
+ /*
+ * The reverse worker does not keep connections alive, so
+ * ensure that mod_proxy_http does the correct thing
+ * regarding the Connection header in the request.
+ */
+ apr_table_setn(r->subprocess_env, "proxy-nokeepalive", "1");
+ if (!fix_uds_filename(r, url)) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+ }
+ }
+ else if (access_status == DECLINED && *balancer != NULL) {
+ /* All the workers are busy */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00934)
+ "all workers are busy. Unable to serve %s", *url);
+ access_status = HTTP_SERVICE_UNAVAILABLE;
+ }
+ return access_status;
+}
+
+PROXY_DECLARE(int) ap_proxy_post_request(proxy_worker *worker,
+ proxy_balancer *balancer,
+ request_rec *r,
+ proxy_server_conf *conf)
+{
+ int access_status = OK;
+ if (balancer) {
+ access_status = proxy_run_post_request(worker, balancer, r, conf);
+ if (access_status == DECLINED) {
+ access_status = OK; /* no post_request handler available */
+ /* TODO: recycle direct worker */
+ }
+ }
+
+ return access_status;
+}
+
+/* DEPRECATED */
+PROXY_DECLARE(int) ap_proxy_connect_to_backend(apr_socket_t **newsock,
+ const char *proxy_function,
+ apr_sockaddr_t *backend_addr,
+ const char *backend_name,
+ proxy_server_conf *conf,
+ request_rec *r)
+{
+ apr_status_t rv;
+ int connected = 0;
+ int loglevel;
+
+ while (backend_addr && !connected) {
+ if ((rv = apr_socket_create(newsock, backend_addr->family,
+ SOCK_STREAM, 0, r->pool)) != APR_SUCCESS) {
+ loglevel = backend_addr->next ? APLOG_DEBUG : APLOG_ERR;
+ ap_log_rerror(APLOG_MARK, loglevel, rv, r, APLOGNO(00935)
+ "%s: error creating fam %d socket for target %s",
+ proxy_function, backend_addr->family, backend_name);
+ /*
+ * this could be an IPv6 address from the DNS but the
+ * local machine won't give us an IPv6 socket; hopefully the
+ * DNS returned an additional address to try
+ */
+ backend_addr = backend_addr->next;
+ continue;
+ }
+
+ if (conf->recv_buffer_size > 0 &&
+ (rv = apr_socket_opt_set(*newsock, APR_SO_RCVBUF,
+ conf->recv_buffer_size))) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00936)
+ "apr_socket_opt_set(SO_RCVBUF): Failed to set "
+ "ProxyReceiveBufferSize, using default");
+ }
+
+ rv = apr_socket_opt_set(*newsock, APR_TCP_NODELAY, 1);
+ if (rv != APR_SUCCESS && rv != APR_ENOTIMPL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00937)
+ "apr_socket_opt_set(APR_TCP_NODELAY): "
+ "Failed to set");
+ }
+
+ /* Set a timeout on the socket */
+ if (conf->timeout_set) {
+ apr_socket_timeout_set(*newsock, conf->timeout);
+ }
+ else {
+ apr_socket_timeout_set(*newsock, r->server->timeout);
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+ "%s: fam %d socket created to connect to %s",
+ proxy_function, backend_addr->family, backend_name);
+
+ if (conf->source_address) {
+ apr_sockaddr_t *local_addr;
+ /* Make a copy since apr_socket_bind() could change
+ * conf->source_address, which we don't want.
+ */
+ local_addr = apr_pmemdup(r->pool, conf->source_address,
+ sizeof(apr_sockaddr_t));
+ local_addr->pool = r->pool;
+ rv = apr_socket_bind(*newsock, local_addr);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00938)
+ "%s: failed to bind socket to local address",
+ proxy_function);
+ }
+ }
+
+ /* make the connection out of the socket */
+ rv = apr_socket_connect(*newsock, backend_addr);
+
+ /* if an error occurred, loop round and try again */
+ if (rv != APR_SUCCESS) {
+ apr_socket_close(*newsock);
+ loglevel = backend_addr->next ? APLOG_DEBUG : APLOG_ERR;
+ ap_log_rerror(APLOG_MARK, loglevel, rv, r, APLOGNO(00939)
+ "%s: attempt to connect to %pI (%s) failed",
+ proxy_function, backend_addr, backend_name);
+ backend_addr = backend_addr->next;
+ continue;
+ }
+ connected = 1;
+ }
+ return connected ? 0 : 1;
+}
+
+PROXY_DECLARE(int) ap_proxy_acquire_connection(const char *proxy_function,
+ proxy_conn_rec **conn,
+ proxy_worker *worker,
+ server_rec *s)
+{
+ apr_status_t rv;
+
+ if (!PROXY_WORKER_IS_USABLE(worker)) {
+ /* Retry the worker */
+ ap_proxy_retry_worker(proxy_function, worker, s);
+
+ if (!PROXY_WORKER_IS_USABLE(worker)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00940)
+ "%s: disabled connection for (%s:%d)",
+ proxy_function, worker->s->hostname_ex,
+ (int)worker->s->port);
+ return HTTP_SERVICE_UNAVAILABLE;
+ }
+ }
+
+ if (worker->s->hmax && worker->cp->res) {
+ rv = apr_reslist_acquire(worker->cp->res, (void **)conn);
+ }
+ else {
+ /* create the new connection if the previous was destroyed */
+ if (!worker->cp->conn) {
+ rv = connection_constructor((void **)conn, worker, worker->cp->pool);
+ }
+ else {
+ *conn = worker->cp->conn;
+ worker->cp->conn = NULL;
+ rv = APR_SUCCESS;
+ }
+ }
+
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00941)
+ "%s: failed to acquire connection for (%s:%d)",
+ proxy_function, worker->s->hostname_ex,
+ (int)worker->s->port);
+ return HTTP_SERVICE_UNAVAILABLE;
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00942)
+ "%s: has acquired connection for (%s:%d)",
+ proxy_function, worker->s->hostname_ex,
+ (int)worker->s->port);
+
+ (*conn)->worker = worker;
+ (*conn)->close = 0;
+ (*conn)->inreslist = 0;
+
+ return OK;
+}
+
+PROXY_DECLARE(int) ap_proxy_release_connection(const char *proxy_function,
+ proxy_conn_rec *conn,
+ server_rec *s)
+{
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00943)
+ "%s: has released connection for (%s:%d)",
+ proxy_function, conn->worker->s->hostname_ex,
+ (int)conn->worker->s->port);
+ connection_cleanup(conn);
+
+ return OK;
+}
+
+PROXY_DECLARE(int)
+ap_proxy_determine_connection(apr_pool_t *p, request_rec *r,
+ proxy_server_conf *conf,
+ proxy_worker *worker,
+ proxy_conn_rec *conn,
+ apr_uri_t *uri,
+ char **url,
+ const char *proxyname,
+ apr_port_t proxyport,
+ char *server_portstr,
+ int server_portstr_size)
+{
+ int server_port;
+ apr_status_t err = APR_SUCCESS;
+#if APR_HAS_THREADS
+ apr_status_t uerr = APR_SUCCESS;
+#endif
+ const char *uds_path;
+
+ /*
+ * Break up the URL to determine the host to connect to
+ */
+
+ /* we break the URL into host, port, uri */
+ if (APR_SUCCESS != apr_uri_parse(p, *url, uri)) {
+ return ap_proxyerror(r, HTTP_BAD_REQUEST,
+ apr_pstrcat(p,"URI cannot be parsed: ", *url,
+ NULL));
+ }
+ if (!uri->port) {
+ uri->port = ap_proxy_port_of_scheme(uri->scheme);
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00944)
+ "connecting %s to %s:%d", *url, uri->hostname, uri->port);
+
+ /*
+ * allocate these out of the specified connection pool
+ * The scheme handler decides if this is permanent or
+ * short living pool.
+ */
+ /* Unless we are connecting the backend via a (forward Proxy)Remote, we
+ * have to use the original form of the URI (non absolute), but this is
+ * also the case via a remote proxy using the CONNECT method since the
+ * original request (and URI) is to be embedded in the body.
+ */
+ if (!proxyname || conn->is_ssl) {
+ *url = apr_pstrcat(p, uri->path, uri->query ? "?" : "",
+ uri->query ? uri->query : "",
+ uri->fragment ? "#" : "",
+ uri->fragment ? uri->fragment : "", NULL);
+ }
+ /*
+ * Figure out if our passed in proxy_conn_rec has a usable
+ * address cached.
+ *
+ * TODO: Handle this much better...
+ *
+ * XXX: If generic workers are ever address-reusable, we need
+ * to check host and port on the conn and be careful about
+ * spilling the cached addr from the worker.
+ */
+ uds_path = (*worker->s->uds_path ? worker->s->uds_path : apr_table_get(r->notes, "uds_path"));
+ if (uds_path) {
+ if (conn->uds_path == NULL) {
+ /* use (*conn)->pool instead of worker->cp->pool to match lifetime */
+ conn->uds_path = apr_pstrdup(conn->pool, uds_path);
+ }
+ if (conn->uds_path) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02545)
+ "%s: has determined UDS as %s",
+ uri->scheme, conn->uds_path);
+ }
+ else {
+ /* should never happen */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02546)
+ "%s: cannot determine UDS (%s)",
+ uri->scheme, uds_path);
+
+ }
+ /*
+ * In UDS cases, some structs are NULL. Protect from de-refs
+ * and provide info for logging at the same time.
+ */
+ if (!conn->addr) {
+ apr_sockaddr_t *sa;
+ apr_sockaddr_info_get(&sa, NULL, APR_UNSPEC, 0, 0, conn->pool);
+ conn->addr = sa;
+ }
+ conn->hostname = "httpd-UDS";
+ conn->port = 0;
+ }
+ else {
+ int will_reuse = worker->s->is_address_reusable && !worker->s->disablereuse;
+ if (!conn->hostname || !will_reuse) {
+ if (proxyname) {
+ conn->hostname = apr_pstrdup(conn->pool, proxyname);
+ conn->port = proxyport;
+ /*
+ * If we have a forward proxy and the protocol is HTTPS,
+ * then we need to prepend a HTTP CONNECT request before
+ * sending our actual HTTPS requests.
+ * Save our real backend data for using it later during HTTP CONNECT.
+ */
+ if (conn->is_ssl) {
+ const char *proxy_auth;
+
+ forward_info *forward = apr_pcalloc(conn->pool, sizeof(forward_info));
+ conn->forward = forward;
+ forward->use_http_connect = 1;
+ forward->target_host = apr_pstrdup(conn->pool, uri->hostname);
+ forward->target_port = uri->port;
+ /* Do we want to pass Proxy-Authorization along?
+ * If we haven't used it, then YES
+ * If we have used it then MAYBE: RFC2616 says we MAY propagate it.
+ * So let's make it configurable by env.
+ * The logic here is the same used in mod_proxy_http.
+ */
+ proxy_auth = apr_table_get(r->headers_in, "Proxy-Authorization");
+ if (proxy_auth != NULL &&
+ proxy_auth[0] != '\0' &&
+ r->user == NULL && /* we haven't yet authenticated */
+ apr_table_get(r->subprocess_env, "Proxy-Chain-Auth")) {
+ forward->proxy_auth = apr_pstrdup(conn->pool, proxy_auth);
+ }
+ }
+ }
+ else {
+ conn->hostname = apr_pstrdup(conn->pool, uri->hostname);
+ conn->port = uri->port;
+ }
+ if (!will_reuse) {
+ /*
+ * Only do a lookup if we should not reuse the backend address.
+ * Otherwise we will look it up once for the worker.
+ */
+ err = apr_sockaddr_info_get(&(conn->addr),
+ conn->hostname, APR_UNSPEC,
+ conn->port, 0,
+ conn->pool);
+ }
+ socket_cleanup(conn);
+ conn->close = 0;
+ }
+ if (will_reuse) {
+ /*
+ * Looking up the backend address for the worker only makes sense if
+ * we can reuse the address.
+ */
+ if (!worker->cp->addr) {
+#if APR_HAS_THREADS
+ if ((err = PROXY_THREAD_LOCK(worker)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, err, r, APLOGNO(00945) "lock");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+#endif
+
+ /*
+ * Recheck addr after we got the lock. This may have changed
+ * while waiting for the lock.
+ */
+ if (!AP_VOLATILIZE_T(apr_sockaddr_t *, worker->cp->addr)) {
+
+ apr_sockaddr_t *addr;
+
+ /*
+ * Worker can have the single constant backend address.
+ * The single DNS lookup is used once per worker.
+ * If dynamic change is needed then set the addr to NULL
+ * inside dynamic config to force the lookup.
+ */
+ err = apr_sockaddr_info_get(&addr,
+ conn->hostname, APR_UNSPEC,
+ conn->port, 0,
+ worker->cp->dns_pool);
+ worker->cp->addr = addr;
+ }
+ conn->addr = worker->cp->addr;
+#if APR_HAS_THREADS
+ if ((uerr = PROXY_THREAD_UNLOCK(worker)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, uerr, r, APLOGNO(00946) "unlock");
+ }
+#endif
+ }
+ else {
+ conn->addr = worker->cp->addr;
+ }
+ }
+ }
+ /* Close a possible existing socket if we are told to do so */
+ if (conn->close) {
+ socket_cleanup(conn);
+ conn->close = 0;
+ }
+
+ if (err != APR_SUCCESS) {
+ return ap_proxyerror(r, HTTP_BAD_GATEWAY,
+ apr_pstrcat(p, "DNS lookup failure for: ",
+ conn->hostname, NULL));
+ }
+
+ /* Get the server port for the Via headers */
+ server_port = ap_get_server_port(r);
+ AP_DEBUG_ASSERT(server_portstr_size > 0);
+ if (ap_is_default_port(server_port, r)) {
+ server_portstr[0] = '\0';
+ }
+ else {
+ apr_snprintf(server_portstr, server_portstr_size, ":%d",
+ server_port);
+ }
+
+ /* check if ProxyBlock directive on this host */
+ if (OK != ap_proxy_checkproxyblock2(r, conf, uri->hostname,
+ proxyname ? NULL : conn->addr)) {
+ return ap_proxyerror(r, HTTP_FORBIDDEN,
+ "Connect to remote machine blocked");
+ }
+ /*
+ * When SSL is configured, determine the hostname (SNI) for the request
+ * and save it in conn->ssl_hostname. Close any reused connection whose
+ * SNI differs.
+ */
+ if (conn->is_ssl) {
+ proxy_dir_conf *dconf;
+ const char *ssl_hostname;
+ /*
+ * In the case of ProxyPreserveHost on use the hostname of
+ * the request if present otherwise use the one from the
+ * backend request URI.
+ */
+ dconf = ap_get_module_config(r->per_dir_config, &proxy_module);
+ if (dconf->preserve_host) {
+ ssl_hostname = r->hostname;
+ }
+ else if (conn->forward
+ && ((forward_info *)(conn->forward))->use_http_connect) {
+ ssl_hostname = ((forward_info *)conn->forward)->target_host;
+ }
+ else {
+ ssl_hostname = conn->hostname;
+ }
+ /*
+ * Close if a SNI is in use but this request requires no or
+ * a different one, or no SNI is in use but one is required.
+ */
+ if ((conn->ssl_hostname && (!ssl_hostname ||
+ strcasecmp(conn->ssl_hostname,
+ ssl_hostname) != 0)) ||
+ (!conn->ssl_hostname && ssl_hostname && conn->sock)) {
+ socket_cleanup(conn);
+ }
+ if (conn->ssl_hostname == NULL) {
+ conn->ssl_hostname = apr_pstrdup(conn->scpool, ssl_hostname);
+ }
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00947)
+ "connected %s to %s:%d", *url, conn->hostname, conn->port);
+ return OK;
+}
+
+#define USE_ALTERNATE_IS_CONNECTED 1
+
+#if !defined(APR_MSG_PEEK) && defined(MSG_PEEK)
+#define APR_MSG_PEEK MSG_PEEK
+#endif
+
+#if USE_ALTERNATE_IS_CONNECTED && defined(APR_MSG_PEEK)
+PROXY_DECLARE(int) ap_proxy_is_socket_connected(apr_socket_t *socket)
+{
+ apr_pollfd_t pfds[1];
+ apr_status_t status;
+ apr_int32_t nfds;
+
+ pfds[0].reqevents = APR_POLLIN;
+ pfds[0].desc_type = APR_POLL_SOCKET;
+ pfds[0].desc.s = socket;
+
+ do {
+ status = apr_poll(&pfds[0], 1, &nfds, 0);
+ } while (APR_STATUS_IS_EINTR(status));
+
+ if (status == APR_SUCCESS && nfds == 1 &&
+ pfds[0].rtnevents == APR_POLLIN) {
+ apr_sockaddr_t unused;
+ apr_size_t len = 1;
+ char buf[1];
+ /* The socket might be closed in which case
+ * the poll will return POLLIN.
+ * If there is no data available the socket
+ * is closed.
+ */
+ status = apr_socket_recvfrom(&unused, socket, APR_MSG_PEEK,
+ &buf[0], &len);
+ if (status == APR_SUCCESS && len)
+ return 1;
+ else
+ return 0;
+ }
+ else if (APR_STATUS_IS_EAGAIN(status) || APR_STATUS_IS_TIMEUP(status)) {
+ return 1;
+ }
+ return 0;
+
+}
+#else
+PROXY_DECLARE(int) ap_proxy_is_socket_connected(apr_socket_t *sock)
+
+{
+ apr_size_t buffer_len = 1;
+ char test_buffer[1];
+ apr_status_t socket_status;
+ apr_interval_time_t current_timeout;
+
+ /* save timeout */
+ apr_socket_timeout_get(sock, &current_timeout);
+ /* set no timeout */
+ apr_socket_timeout_set(sock, 0);
+ socket_status = apr_socket_recv(sock, test_buffer, &buffer_len);
+ /* put back old timeout */
+ apr_socket_timeout_set(sock, current_timeout);
+ if (APR_STATUS_IS_EOF(socket_status)
+ || APR_STATUS_IS_ECONNRESET(socket_status)) {
+ return 0;
+ }
+ else {
+ return 1;
+ }
+}
+#endif /* USE_ALTERNATE_IS_CONNECTED */
+
+
+/*
+ * Send a HTTP CONNECT request to a forward proxy.
+ * The proxy is given by "backend", the target server
+ * is contained in the "forward" member of "backend".
+ */
+static apr_status_t send_http_connect(proxy_conn_rec *backend,
+ server_rec *s)
+{
+ int status;
+ apr_size_t nbytes;
+ apr_size_t left;
+ int complete = 0;
+ char buffer[HUGE_STRING_LEN];
+ char drain_buffer[HUGE_STRING_LEN];
+ forward_info *forward = (forward_info *)backend->forward;
+ int len = 0;
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00948)
+ "CONNECT: sending the CONNECT request for %s:%d "
+ "to the remote proxy %pI (%s)",
+ forward->target_host, forward->target_port,
+ backend->addr, backend->hostname);
+ /* Create the CONNECT request */
+ nbytes = apr_snprintf(buffer, sizeof(buffer),
+ "CONNECT %s:%d HTTP/1.0" CRLF,
+ forward->target_host, forward->target_port);
+ /* Add proxy authorization from the initial request if necessary */
+ if (forward->proxy_auth != NULL) {
+ nbytes += apr_snprintf(buffer + nbytes, sizeof(buffer) - nbytes,
+ "Proxy-Authorization: %s" CRLF,
+ forward->proxy_auth);
+ }
+ /* Set a reasonable agent and send everything */
+ nbytes += apr_snprintf(buffer + nbytes, sizeof(buffer) - nbytes,
+ "Proxy-agent: %s" CRLF CRLF,
+ ap_get_server_banner());
+ ap_xlate_proto_to_ascii(buffer, nbytes);
+ apr_socket_send(backend->sock, buffer, &nbytes);
+
+ /* Receive the whole CONNECT response */
+ left = sizeof(buffer) - 1;
+ /* Read until we find the end of the headers or run out of buffer */
+ do {
+ nbytes = left;
+ status = apr_socket_recv(backend->sock, buffer + len, &nbytes);
+ len += nbytes;
+ left -= nbytes;
+ buffer[len] = '\0';
+ if (strstr(buffer + len - nbytes, CRLF_ASCII CRLF_ASCII) != NULL) {
+ ap_xlate_proto_from_ascii(buffer, len);
+ complete = 1;
+ break;
+ }
+ } while (status == APR_SUCCESS && left > 0);
+ /* Drain what's left */
+ if (!complete) {
+ nbytes = sizeof(drain_buffer) - 1;
+ while (status == APR_SUCCESS && nbytes) {
+ status = apr_socket_recv(backend->sock, drain_buffer, &nbytes);
+ drain_buffer[nbytes] = '\0';
+ nbytes = sizeof(drain_buffer) - 1;
+ if (strstr(drain_buffer, CRLF_ASCII CRLF_ASCII) != NULL) {
+ break;
+ }
+ }
+ }
+
+ /* Check for HTTP_OK response status */
+ if (status == APR_SUCCESS) {
+ unsigned int major, minor;
+ /* Only scan for three character status code */
+ char code_str[4];
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00949)
+ "send_http_connect: response from the forward proxy: %s",
+ buffer);
+
+ /* Extract the returned code */
+ if (sscanf(buffer, "HTTP/%u.%u %3s", &major, &minor, code_str) == 3) {
+ status = atoi(code_str);
+ if (status == HTTP_OK) {
+ status = APR_SUCCESS;
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00950)
+ "send_http_connect: the forward proxy returned code is '%s'",
+ code_str);
+ status = APR_INCOMPLETE;
+ }
+ }
+ }
+
+ return(status);
+}
+
+
+/* TODO: In APR 2.x: Extend apr_sockaddr_t to possibly be a path !!! */
+PROXY_DECLARE(apr_status_t) ap_proxy_connect_uds(apr_socket_t *sock,
+ const char *uds_path,
+ apr_pool_t *p)
+{
+#if APR_HAVE_SYS_UN_H
+ apr_status_t rv;
+ apr_os_sock_t rawsock;
+ apr_interval_time_t t;
+ struct sockaddr_un *sa;
+ apr_socklen_t addrlen, pathlen;
+
+ rv = apr_os_sock_get(&rawsock, sock);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ rv = apr_socket_timeout_get(sock, &t);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ pathlen = strlen(uds_path);
+ /* copy the UDS path (including NUL) to the sockaddr_un */
+ addrlen = APR_OFFSETOF(struct sockaddr_un, sun_path) + pathlen;
+ sa = (struct sockaddr_un *)apr_palloc(p, addrlen + 1);
+ memcpy(sa->sun_path, uds_path, pathlen + 1);
+ sa->sun_family = AF_UNIX;
+
+ do {
+ rv = connect(rawsock, (struct sockaddr*)sa, addrlen);
+ } while (rv == -1 && (rv = errno) == EINTR);
+
+ if (rv && rv != EISCONN) {
+ if ((rv == EINPROGRESS || rv == EALREADY) && (t > 0)) {
+#if APR_MAJOR_VERSION < 2
+ rv = apr_wait_for_io_or_timeout(NULL, sock, 0);
+#else
+ rv = apr_socket_wait(sock, APR_WAIT_WRITE);
+#endif
+ }
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+
+ return APR_SUCCESS;
+#else
+ return APR_ENOTIMPL;
+#endif
+}
+
+PROXY_DECLARE(apr_status_t) ap_proxy_check_connection(const char *scheme,
+ proxy_conn_rec *conn,
+ server_rec *server,
+ unsigned max_blank_lines,
+ int flags)
+{
+ apr_status_t rv = APR_SUCCESS;
+ proxy_worker *worker = conn->worker;
+
+ if (!PROXY_WORKER_IS_USABLE(worker)) {
+ /*
+ * The worker is in error likely done by a different thread / process
+ * e.g. for a timeout or bad status. We should respect this and should
+ * not continue with a connection via this worker even if we got one.
+ */
+ rv = APR_EINVAL;
+ }
+ else if (conn->connection) {
+ /* We have a conn_rec, check the full filter stack for things like
+ * SSL alert/shutdown, filters aside data...
+ */
+ rv = ap_check_pipeline(conn->connection, conn->tmp_bb,
+ max_blank_lines);
+ apr_brigade_cleanup(conn->tmp_bb);
+ if (rv == APR_SUCCESS) {
+ /* Some data available, the caller might not want them. */
+ if (flags & PROXY_CHECK_CONN_EMPTY) {
+ rv = APR_ENOTEMPTY;
+ }
+ }
+ else if (APR_STATUS_IS_EAGAIN(rv)) {
+ /* Filter chain is OK and empty, yet we can't determine from
+ * ap_check_pipeline (actually ap_core_input_filter) whether
+ * an empty non-blocking read is EAGAIN or EOF on the socket
+ * side (it's always SUCCESS), so check it explicitly here.
+ */
+ if (ap_proxy_is_socket_connected(conn->sock)) {
+ rv = APR_SUCCESS;
+ }
+ else {
+ rv = APR_EPIPE;
+ }
+ }
+ }
+ else if (conn->sock) {
+ /* For modules working with sockets directly, check it. */
+ if (!ap_proxy_is_socket_connected(conn->sock)) {
+ rv = APR_EPIPE;
+ }
+ }
+ else {
+ rv = APR_ENOSOCKET;
+ }
+
+ if (rv == APR_SUCCESS) {
+ if (APLOGtrace2(server)) {
+ apr_sockaddr_t *local_addr = NULL;
+ apr_socket_addr_get(&local_addr, APR_LOCAL, conn->sock);
+ ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, server,
+ "%s: reusing backend connection %pI<>%pI",
+ scheme, local_addr, conn->addr);
+ }
+ }
+ else if (conn->sock) {
+ /* This clears conn->scpool (and associated data), so backup and
+ * restore any ssl_hostname for this connection set earlier by
+ * ap_proxy_determine_connection().
+ */
+ char ssl_hostname[PROXY_WORKER_RFC1035_NAME_SIZE];
+ if (rv == APR_EINVAL
+ || !conn->ssl_hostname
+ || PROXY_STRNCPY(ssl_hostname, conn->ssl_hostname)) {
+ ssl_hostname[0] = '\0';
+ }
+
+ socket_cleanup(conn);
+ if (rv != APR_ENOTEMPTY) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, APLOGNO(00951)
+ "%s: backend socket is disconnected.", scheme);
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, server, APLOGNO(03408)
+ "%s: reusable backend connection is not empty: "
+ "forcibly closed", scheme);
+ }
+
+ if (ssl_hostname[0]) {
+ conn->ssl_hostname = apr_pstrdup(conn->scpool, ssl_hostname);
+ }
+ }
+
+ return rv;
+}
+
+PROXY_DECLARE(int) ap_proxy_connect_backend(const char *proxy_function,
+ proxy_conn_rec *conn,
+ proxy_worker *worker,
+ server_rec *s)
+{
+ apr_status_t rv;
+ int loglevel;
+ apr_sockaddr_t *backend_addr = conn->addr;
+ /* the local address to use for the outgoing connection */
+ apr_sockaddr_t *local_addr;
+ apr_socket_t *newsock;
+ void *sconf = s->module_config;
+ proxy_server_conf *conf =
+ (proxy_server_conf *) ap_get_module_config(sconf, &proxy_module);
+
+ rv = ap_proxy_check_connection(proxy_function, conn, s, 0, 0);
+ if (rv == APR_EINVAL) {
+ return DECLINED;
+ }
+
+ while (rv != APR_SUCCESS && (backend_addr || conn->uds_path)) {
+#if APR_HAVE_SYS_UN_H
+ if (conn->uds_path)
+ {
+ rv = apr_socket_create(&newsock, AF_UNIX, SOCK_STREAM, 0,
+ conn->scpool);
+ if (rv != APR_SUCCESS) {
+ loglevel = APLOG_ERR;
+ ap_log_error(APLOG_MARK, loglevel, rv, s, APLOGNO(02453)
+ "%s: error creating Unix domain socket for "
+ "target %s:%d",
+ proxy_function,
+ worker->s->hostname_ex,
+ (int)worker->s->port);
+ break;
+ }
+ conn->connection = NULL;
+
+ rv = ap_proxy_connect_uds(newsock, conn->uds_path, conn->scpool);
+ if (rv != APR_SUCCESS) {
+ apr_socket_close(newsock);
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(02454)
+ "%s: attempt to connect to Unix domain socket "
+ "%s (%s:%d) failed",
+ proxy_function,
+ conn->uds_path,
+ worker->s->hostname_ex,
+ (int)worker->s->port);
+ break;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02823)
+ "%s: connection established with Unix domain socket "
+ "%s (%s:%d)",
+ proxy_function,
+ conn->uds_path,
+ worker->s->hostname_ex,
+ (int)worker->s->port);
+ }
+ else
+#endif
+ {
+ if ((rv = apr_socket_create(&newsock, backend_addr->family,
+ SOCK_STREAM, APR_PROTO_TCP,
+ conn->scpool)) != APR_SUCCESS) {
+ loglevel = backend_addr->next ? APLOG_DEBUG : APLOG_ERR;
+ ap_log_error(APLOG_MARK, loglevel, rv, s, APLOGNO(00952)
+ "%s: error creating fam %d socket for "
+ "target %s:%d",
+ proxy_function,
+ backend_addr->family,
+ worker->s->hostname_ex,
+ (int)worker->s->port);
+ /*
+ * this could be an IPv6 address from the DNS but the
+ * local machine won't give us an IPv6 socket; hopefully the
+ * DNS returned an additional address to try
+ */
+ backend_addr = backend_addr->next;
+ continue;
+ }
+ conn->connection = NULL;
+
+ if (worker->s->recv_buffer_size > 0 &&
+ (rv = apr_socket_opt_set(newsock, APR_SO_RCVBUF,
+ worker->s->recv_buffer_size))) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00953)
+ "apr_socket_opt_set(SO_RCVBUF): Failed to set "
+ "ProxyReceiveBufferSize, using default");
+ }
+
+ rv = apr_socket_opt_set(newsock, APR_TCP_NODELAY, 1);
+ if (rv != APR_SUCCESS && rv != APR_ENOTIMPL) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00954)
+ "apr_socket_opt_set(APR_TCP_NODELAY): "
+ "Failed to set");
+ }
+
+ /* Set a timeout for connecting to the backend on the socket */
+ if (worker->s->conn_timeout_set) {
+ apr_socket_timeout_set(newsock, worker->s->conn_timeout);
+ }
+ else if (worker->s->timeout_set) {
+ apr_socket_timeout_set(newsock, worker->s->timeout);
+ }
+ else if (conf->timeout_set) {
+ apr_socket_timeout_set(newsock, conf->timeout);
+ }
+ else {
+ apr_socket_timeout_set(newsock, s->timeout);
+ }
+ /* Set a keepalive option */
+ if (worker->s->keepalive) {
+ if ((rv = apr_socket_opt_set(newsock,
+ APR_SO_KEEPALIVE, 1)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00955)
+ "apr_socket_opt_set(SO_KEEPALIVE): Failed to set"
+ " Keepalive");
+ }
+ }
+ ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, s,
+ "%s: fam %d socket created to connect to %s:%d",
+ proxy_function, backend_addr->family,
+ worker->s->hostname_ex, (int)worker->s->port);
+
+ if (conf->source_address_set) {
+ local_addr = apr_pmemdup(conn->scpool, conf->source_address,
+ sizeof(apr_sockaddr_t));
+ local_addr->pool = conn->scpool;
+ rv = apr_socket_bind(newsock, local_addr);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00956)
+ "%s: failed to bind socket to local address",
+ proxy_function);
+ }
+ }
+
+ /* make the connection out of the socket */
+ rv = apr_socket_connect(newsock, backend_addr);
+
+ /* if an error occurred, loop round and try again */
+ if (rv != APR_SUCCESS) {
+ apr_socket_close(newsock);
+ loglevel = backend_addr->next ? APLOG_DEBUG : APLOG_ERR;
+ ap_log_error(APLOG_MARK, loglevel, rv, s, APLOGNO(00957)
+ "%s: attempt to connect to %pI (%s:%d) failed",
+ proxy_function,
+ backend_addr,
+ worker->s->hostname_ex,
+ (int)worker->s->port);
+ backend_addr = backend_addr->next;
+ continue;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02824)
+ "%s: connection established with %pI (%s:%d)",
+ proxy_function,
+ backend_addr,
+ worker->s->hostname_ex,
+ (int)worker->s->port);
+ }
+
+ /* Set a timeout on the socket */
+ if (worker->s->timeout_set) {
+ apr_socket_timeout_set(newsock, worker->s->timeout);
+ }
+ else if (conf->timeout_set) {
+ apr_socket_timeout_set(newsock, conf->timeout);
+ }
+ else {
+ apr_socket_timeout_set(newsock, s->timeout);
+ }
+
+ conn->sock = newsock;
+
+ if (!conn->uds_path && conn->forward) {
+ forward_info *forward = (forward_info *)conn->forward;
+ /*
+ * For HTTP CONNECT we need to prepend CONNECT request before
+ * sending our actual HTTPS requests.
+ */
+ if (forward->use_http_connect) {
+ rv = send_http_connect(conn, s);
+ /* If an error occurred, loop round and try again */
+ if (rv != APR_SUCCESS) {
+ conn->sock = NULL;
+ apr_socket_close(newsock);
+ loglevel = backend_addr->next ? APLOG_DEBUG : APLOG_ERR;
+ ap_log_error(APLOG_MARK, loglevel, rv, s, APLOGNO(00958)
+ "%s: attempt to connect to %s:%d "
+ "via http CONNECT through %pI (%s:%d) failed",
+ proxy_function,
+ forward->target_host, forward->target_port,
+ backend_addr, worker->s->hostname_ex,
+ (int)worker->s->port);
+ backend_addr = backend_addr->next;
+ continue;
+ }
+ }
+ }
+ }
+
+ if (PROXY_WORKER_IS_USABLE(worker)) {
+ /*
+ * Put the entire worker to error state if
+ * the PROXY_WORKER_IGNORE_ERRORS flag is not set.
+ * Although some connections may be alive
+ * no further connections to the worker could be made
+ */
+ if (rv != APR_SUCCESS) {
+ if (!(worker->s->status & PROXY_WORKER_IGNORE_ERRORS)) {
+ worker->s->error_time = apr_time_now();
+ worker->s->status |= PROXY_WORKER_IN_ERROR;
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00959)
+ "ap_proxy_connect_backend disabling worker for (%s:%d) for %"
+ APR_TIME_T_FMT "s",
+ worker->s->hostname_ex, (int)worker->s->port,
+ apr_time_sec(worker->s->retry));
+ }
+ }
+ else {
+ if (worker->s->retries) {
+ /*
+ * A worker came back. So here is where we need to
+ * either reset all params to initial conditions or
+ * apply some sort of aging
+ */
+ }
+ worker->s->error_time = 0;
+ worker->s->retries = 0;
+ }
+ }
+ else {
+ /*
+ * The worker is in error likely done by a different thread / process
+ * e.g. for a timeout or bad status. We should respect this and should
+ * not continue with a connection via this worker even if we got one.
+ */
+ if (rv == APR_SUCCESS) {
+ socket_cleanup(conn);
+ }
+ rv = APR_EINVAL;
+ }
+
+ return rv == APR_SUCCESS ? OK : DECLINED;
+}
+
+static apr_status_t connection_shutdown(void *theconn)
+{
+ proxy_conn_rec *conn = (proxy_conn_rec *)theconn;
+ conn_rec *c = conn->connection;
+ if (c) {
+ if (!c->aborted) {
+ apr_interval_time_t saved_timeout = 0;
+ apr_socket_timeout_get(conn->sock, &saved_timeout);
+ if (saved_timeout) {
+ apr_socket_timeout_set(conn->sock, 0);
+ }
+
+ (void)ap_shutdown_conn(c, 0);
+ c->aborted = 1;
+
+ if (saved_timeout) {
+ apr_socket_timeout_set(conn->sock, saved_timeout);
+ }
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(02642)
+ "proxy: connection shutdown");
+ }
+ return APR_SUCCESS;
+}
+
+
+static int proxy_connection_create(const char *proxy_function,
+ proxy_conn_rec *conn,
+ request_rec *r, server_rec *s)
+{
+ ap_conf_vector_t *per_dir_config = (r) ? r->per_dir_config
+ : conn->worker->section_config;
+ apr_sockaddr_t *backend_addr = conn->addr;
+ int rc;
+ apr_interval_time_t current_timeout;
+ apr_bucket_alloc_t *bucket_alloc;
+
+ if (conn->connection) {
+ if (conn->is_ssl) {
+ /* on reuse, reinit the SSL connection dir config with the current
+ * r->per_dir_config, the previous one was reset on release.
+ */
+ ap_proxy_ssl_engine(conn->connection, per_dir_config, 1);
+ }
+ return OK;
+ }
+
+ bucket_alloc = apr_bucket_alloc_create(conn->scpool);
+ conn->tmp_bb = apr_brigade_create(conn->scpool, bucket_alloc);
+ /*
+ * The socket is now open, create a new backend server connection
+ */
+ conn->connection = ap_run_create_connection(conn->scpool, s, conn->sock,
+ 0, NULL,
+ bucket_alloc);
+
+ if (!conn->connection) {
+ /*
+ * the peer reset the connection already; ap_run_create_connection()
+ * closed the socket
+ */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0,
+ s, APLOGNO(00960) "%s: an error occurred creating a "
+ "new connection to %pI (%s)", proxy_function,
+ backend_addr, conn->hostname);
+ /* XXX: Will be closed when proxy_conn is closed */
+ socket_cleanup(conn);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* For ssl connection to backend */
+ if (conn->is_ssl) {
+ if (!ap_proxy_ssl_engine(conn->connection, per_dir_config, 1)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0,
+ s, APLOGNO(00961) "%s: failed to enable ssl support "
+ "for %pI (%s)", proxy_function,
+ backend_addr, conn->hostname);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ if (conn->ssl_hostname) {
+ /* Set a note on the connection about what CN is requested,
+ * such that mod_ssl can check if it is requested to do so.
+ */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, conn->connection,
+ "%s: set SNI to %s for (%s)", proxy_function,
+ conn->ssl_hostname, conn->hostname);
+ apr_table_setn(conn->connection->notes, "proxy-request-hostname",
+ conn->ssl_hostname);
+ }
+ }
+ else {
+ /* TODO: See if this will break FTP */
+ ap_proxy_ssl_engine(conn->connection, per_dir_config, 0);
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00962)
+ "%s: connection complete to %pI (%s)",
+ proxy_function, backend_addr, conn->hostname);
+
+ /*
+ * save the timeout of the socket because core_pre_connection
+ * will set it to base_server->timeout
+ * (core TimeOut directive).
+ */
+ apr_socket_timeout_get(conn->sock, &current_timeout);
+ /* set up the connection filters */
+ rc = ap_run_pre_connection(conn->connection, conn->sock);
+ if (rc != OK && rc != DONE) {
+ conn->connection->aborted = 1;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00963)
+ "%s: pre_connection setup failed (%d)",
+ proxy_function, rc);
+ return rc;
+ }
+ apr_socket_timeout_set(conn->sock, current_timeout);
+
+ /* Shutdown the connection before closing it (eg. SSL connections
+ * need to be close-notify-ed).
+ */
+ apr_pool_pre_cleanup_register(conn->scpool, conn, connection_shutdown);
+
+ return OK;
+}
+
+PROXY_DECLARE(int) ap_proxy_connection_create_ex(const char *proxy_function,
+ proxy_conn_rec *conn,
+ request_rec *r)
+{
+ return proxy_connection_create(proxy_function, conn, r, r->server);
+}
+
+PROXY_DECLARE(int) ap_proxy_connection_create(const char *proxy_function,
+ proxy_conn_rec *conn,
+ conn_rec *c, server_rec *s)
+{
+ (void) c; /* unused */
+ return proxy_connection_create(proxy_function, conn, NULL, s);
+}
+
+int ap_proxy_lb_workers(void)
+{
+ /*
+ * Since we can't resize the scoreboard when reconfiguring, we
+ * have to impose a limit on the number of workers, we are
+ * able to reconfigure to.
+ */
+ if (!lb_workers_limit)
+ lb_workers_limit = proxy_lb_workers + PROXY_DYNAMIC_BALANCER_LIMIT;
+ return lb_workers_limit;
+}
+
+static APR_INLINE int error_code_overridden(const int *elts, int nelts,
+ int code)
+{
+ int min = 0;
+ int max = nelts - 1;
+ AP_DEBUG_ASSERT(max >= 0);
+
+ while (min < max) {
+ int mid = (min + max) / 2;
+ int val = elts[mid];
+
+ if (val < code) {
+ min = mid + 1;
+ }
+ else if (val > code) {
+ max = mid - 1;
+ }
+ else {
+ return 1;
+ }
+ }
+
+ return elts[min] == code;
+}
+
+PROXY_DECLARE(int) ap_proxy_should_override(proxy_dir_conf *conf, int code)
+{
+ if (!conf->error_override)
+ return 0;
+
+ if (apr_is_empty_array(conf->error_override_codes))
+ return ap_is_HTTP_ERROR(code);
+
+ /* Since error_override_codes is sorted, apply binary search. */
+ return error_code_overridden((int *)conf->error_override_codes->elts,
+ conf->error_override_codes->nelts,
+ code);
+}
+
+PROXY_DECLARE(void) ap_proxy_backend_broke(request_rec *r,
+ apr_bucket_brigade *brigade)
+{
+ apr_bucket *e;
+ conn_rec *c = r->connection;
+
+ r->no_cache = 1;
+ /*
+ * If this is a subrequest, then prevent also caching of the main
+ * request.
+ */
+ if (r->main)
+ r->main->no_cache = 1;
+ e = ap_bucket_error_create(HTTP_BAD_GATEWAY, NULL, c->pool,
+ c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(brigade, e);
+ e = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(brigade, e);
+}
+
+/*
+ * Provide a string hashing function for the proxy.
+ * We offer 2 methods: one is the APR model but we
+ * also provide our own, based on either FNV or SDBM.
+ * The reason is in case we want to use both to ensure no
+ * collisions.
+ */
+PROXY_DECLARE(unsigned int)
+ap_proxy_hashfunc(const char *str, proxy_hash_t method)
+{
+ if (method == PROXY_HASHFUNC_APR) {
+ apr_ssize_t slen = strlen(str);
+ return apr_hashfunc_default(str, &slen);
+ }
+ else if (method == PROXY_HASHFUNC_FNV) {
+ /* FNV model */
+ unsigned int hash;
+ const unsigned int fnv_prime = 0x811C9DC5;
+ for (hash = 0; *str; str++) {
+ hash *= fnv_prime;
+ hash ^= (*str);
+ }
+ return hash;
+ }
+ else { /* method == PROXY_HASHFUNC_DEFAULT */
+ /* SDBM model */
+ unsigned int hash;
+ for (hash = 0; *str; str++) {
+ hash = (*str) + (hash << 6) + (hash << 16) - hash;
+ }
+ return hash;
+ }
+}
+
+PROXY_DECLARE(apr_status_t) ap_proxy_set_wstatus(char c, int set, proxy_worker *w)
+{
+ unsigned int *status = &w->s->status;
+ char flag = toupper(c);
+ proxy_wstat_t *pwt = proxy_wstat_tbl;
+ while (pwt->bit) {
+ if (flag == pwt->flag) {
+ if (set)
+ *status |= pwt->bit;
+ else
+ *status &= ~(pwt->bit);
+ return APR_SUCCESS;
+ }
+ pwt++;
+ }
+ return APR_EINVAL;
+}
+
+PROXY_DECLARE(char *) ap_proxy_parse_wstatus(apr_pool_t *p, proxy_worker *w)
+{
+ char *ret = "";
+ unsigned int status = w->s->status;
+ proxy_wstat_t *pwt = proxy_wstat_tbl;
+ while (pwt->bit) {
+ if (status & pwt->bit)
+ ret = apr_pstrcat(p, ret, pwt->name, NULL);
+ pwt++;
+ }
+ if (!*ret) {
+ ret = "??? ";
+ }
+ if (PROXY_WORKER_IS_USABLE(w))
+ ret = apr_pstrcat(p, ret, "Ok ", NULL);
+ return ret;
+}
+
+PROXY_DECLARE(apr_status_t) ap_proxy_sync_balancer(proxy_balancer *b, server_rec *s,
+ proxy_server_conf *conf)
+{
+ proxy_worker **workers;
+ int i;
+ int index;
+ proxy_worker_shared *shm;
+ proxy_balancer_method *lbmethod;
+ ap_slotmem_provider_t *storage = b->storage;
+
+ if (b->s->wupdated <= b->wupdated)
+ return APR_SUCCESS;
+ /* balancer sync */
+ lbmethod = ap_lookup_provider(PROXY_LBMETHOD, b->s->lbpname, "0");
+ if (lbmethod) {
+ b->lbmethod = lbmethod;
+ } else {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s, APLOGNO(02433)
+ "Cannot find LB Method: %s", b->s->lbpname);
+ return APR_EINVAL;
+ }
+
+ /* worker sync */
+
+ /*
+ * Look thru the list of workers in shm
+ * and see which one(s) we are lacking...
+ * again, the cast to unsigned int is safe
+ * since our upper limit is always max_workers
+ * which is int.
+ */
+ for (index = 0; index < b->max_workers; index++) {
+ int found;
+ apr_status_t rv;
+ if ((rv = storage->dptr(b->wslot, (unsigned int)index, (void *)&shm)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s, APLOGNO(00965) "worker slotmem_dptr failed");
+ return APR_EGENERAL;
+ }
+ /* account for possible "holes" in the slotmem
+ * (eg: slots 0-2 are used, but 3 isn't, but 4-5 is)
+ */
+ if (!shm->hash.def || !shm->hash.fnv)
+ continue;
+ found = 0;
+ workers = (proxy_worker **)b->workers->elts;
+ for (i = 0; i < b->workers->nelts; i++, workers++) {
+ proxy_worker *worker = *workers;
+ if (worker->hash.def == shm->hash.def && worker->hash.fnv == shm->hash.fnv) {
+ found = 1;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02402)
+ "re-grabbing shm[%d] (0x%pp) for worker: %s", i, (void *)shm,
+ ap_proxy_worker_name(conf->pool, worker));
+ break;
+ }
+ }
+ if (!found) {
+ proxy_worker **runtime;
+ /* XXX: a thread mutex is maybe enough here */
+ apr_global_mutex_lock(proxy_mutex);
+ runtime = apr_array_push(b->workers);
+ *runtime = apr_pcalloc(conf->pool, sizeof(proxy_worker));
+ apr_global_mutex_unlock(proxy_mutex);
+ (*runtime)->hash = shm->hash;
+ (*runtime)->balancer = b;
+ (*runtime)->s = shm;
+
+ rv = ap_proxy_initialize_worker(*runtime, s, conf->pool);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s, APLOGNO(00966) "Cannot init worker");
+ return rv;
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02403)
+ "grabbing shm[%d] (0x%pp) for worker: %s", i, (void *)shm,
+ (*runtime)->s->name_ex);
+ }
+ }
+ if (b->s->need_reset) {
+ if (b->lbmethod && b->lbmethod->reset)
+ b->lbmethod->reset(b, s);
+ b->s->need_reset = 0;
+ }
+ b->wupdated = b->s->wupdated;
+ return APR_SUCCESS;
+}
+
+PROXY_DECLARE(proxy_worker_shared *) ap_proxy_find_workershm(ap_slotmem_provider_t *storage,
+ ap_slotmem_instance_t *slot,
+ proxy_worker *worker,
+ unsigned int *index)
+{
+ proxy_worker_shared *shm;
+ unsigned int i, limit;
+ limit = storage->num_slots(slot);
+ for (i = 0; i < limit; i++) {
+ if (storage->dptr(slot, i, (void *)&shm) != APR_SUCCESS) {
+ return NULL;
+ }
+ if ((worker->s->hash.def == shm->hash.def) &&
+ (worker->s->hash.fnv == shm->hash.fnv)) {
+ *index = i;
+ return shm;
+ }
+ }
+ return NULL;
+}
+
+PROXY_DECLARE(proxy_balancer_shared *) ap_proxy_find_balancershm(ap_slotmem_provider_t *storage,
+ ap_slotmem_instance_t *slot,
+ proxy_balancer *balancer,
+ unsigned int *index)
+{
+ proxy_balancer_shared *shm;
+ unsigned int i, limit;
+ limit = storage->num_slots(slot);
+ for (i = 0; i < limit; i++) {
+ if (storage->dptr(slot, i, (void *)&shm) != APR_SUCCESS) {
+ return NULL;
+ }
+ if ((balancer->s->hash.def == shm->hash.def) &&
+ (balancer->s->hash.fnv == shm->hash.fnv)) {
+ *index = i;
+ return shm;
+ }
+ }
+ return NULL;
+}
+
+typedef struct header_connection {
+ apr_pool_t *pool;
+ apr_array_header_t *array;
+ const char *first;
+ unsigned int closed:1;
+} header_connection;
+
+static int find_conn_headers(void *data, const char *key, const char *val)
+{
+ header_connection *x = data;
+ const char *name;
+
+ do {
+ while (*val == ',' || *val == ';') {
+ val++;
+ }
+ name = ap_get_token(x->pool, &val, 0);
+ if (!strcasecmp(name, "close")) {
+ x->closed = 1;
+ }
+ if (!x->first) {
+ x->first = name;
+ }
+ else {
+ const char **elt;
+ if (!x->array) {
+ x->array = apr_array_make(x->pool, 4, sizeof(char *));
+ }
+ elt = apr_array_push(x->array);
+ *elt = name;
+ }
+ } while (*val);
+
+ return 1;
+}
+
+/**
+ * Remove all headers referred to by the Connection header.
+ */
+static int ap_proxy_clear_connection(request_rec *r, apr_table_t *headers)
+{
+ const char **name;
+ header_connection x;
+
+ x.pool = r->pool;
+ x.array = NULL;
+ x.first = NULL;
+ x.closed = 0;
+
+ apr_table_unset(headers, "Proxy-Connection");
+
+ apr_table_do(find_conn_headers, &x, headers, "Connection", NULL);
+ if (x.first) {
+ /* fast path - no memory allocated for one header */
+ apr_table_unset(headers, "Connection");
+ apr_table_unset(headers, x.first);
+ }
+ if (x.array) {
+ /* two or more headers */
+ while ((name = apr_array_pop(x.array))) {
+ apr_table_unset(headers, *name);
+ }
+ }
+
+ return x.closed;
+}
+
+PROXY_DECLARE(int) ap_proxy_create_hdrbrgd(apr_pool_t *p,
+ apr_bucket_brigade *header_brigade,
+ request_rec *r,
+ proxy_conn_rec *p_conn,
+ proxy_worker *worker,
+ proxy_server_conf *conf,
+ apr_uri_t *uri,
+ char *url, char *server_portstr,
+ char **old_cl_val,
+ char **old_te_val)
+{
+ int rc = OK;
+ conn_rec *c = r->connection;
+ int counter;
+ char *buf;
+ apr_table_t *saved_headers_in = r->headers_in;
+ const char *saved_host = apr_table_get(saved_headers_in, "Host");
+ const apr_array_header_t *headers_in_array;
+ const apr_table_entry_t *headers_in;
+ apr_bucket *e;
+ int force10 = 0, do_100_continue = 0;
+ conn_rec *origin = p_conn->connection;
+ const char *host, *val;
+ proxy_dir_conf *dconf = ap_get_module_config(r->per_dir_config, &proxy_module);
+
+ /*
+ * HTTP "Ping" test? Easiest is 100-Continue. However:
+ * To be compliant, we only use 100-Continue for requests with bodies.
+ * We also make sure we won't be talking HTTP/1.0 as well.
+ */
+ if (apr_table_get(r->subprocess_env, "force-proxy-request-1.0")) {
+ force10 = 1;
+ }
+ else if (apr_table_get(r->notes, "proxy-100-continue")
+ || PROXY_SHOULD_PING_100_CONTINUE(worker, r)) {
+ do_100_continue = 1;
+ }
+ if (force10 || apr_table_get(r->subprocess_env, "proxy-nokeepalive")) {
+ if (origin) {
+ origin->keepalive = AP_CONN_CLOSE;
+ }
+ p_conn->close = 1;
+ }
+
+ if (force10) {
+ buf = apr_pstrcat(p, r->method, " ", url, " HTTP/1.0" CRLF, NULL);
+ }
+ else {
+ buf = apr_pstrcat(p, r->method, " ", url, " HTTP/1.1" CRLF, NULL);
+ }
+ ap_xlate_proto_to_ascii(buf, strlen(buf));
+ e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(header_brigade, e);
+
+ /*
+ * Make a copy on r->headers_in for the request we make to the backend,
+ * modify the copy in place according to our configuration and connection
+ * handling, use it to fill in the forwarded headers' brigade, and finally
+ * restore the saved/original ones in r->headers_in.
+ *
+ * Note: We need to take r->pool for apr_table_copy as the key / value
+ * pairs in r->headers_in have been created out of r->pool and
+ * p might be (and actually is) a longer living pool.
+ * This would trigger the bad pool ancestry abort in apr_table_copy if
+ * apr is compiled with APR_POOL_DEBUG.
+ *
+ * icing: if p indeed lives longer than r->pool, we should allocate
+ * all new header values from r->pool as well and avoid leakage.
+ */
+ r->headers_in = apr_table_copy(r->pool, saved_headers_in);
+
+ /* Return the original Transfer-Encoding and/or Content-Length values
+ * then drop the headers, they must be set by the proxy handler based
+ * on the actual body being forwarded.
+ */
+ if ((*old_te_val = (char *)apr_table_get(r->headers_in,
+ "Transfer-Encoding"))) {
+ apr_table_unset(r->headers_in, "Transfer-Encoding");
+ }
+ if ((*old_cl_val = (char *)apr_table_get(r->headers_in,
+ "Content-Length"))) {
+ apr_table_unset(r->headers_in, "Content-Length");
+ }
+
+ /* Clear out hop-by-hop request headers not to forward */
+ if (ap_proxy_clear_connection(r, r->headers_in) < 0) {
+ rc = HTTP_BAD_REQUEST;
+ goto cleanup;
+ }
+
+ /* RFC2616 13.5.1 says we should strip these */
+ apr_table_unset(r->headers_in, "Keep-Alive");
+ apr_table_unset(r->headers_in, "Upgrade");
+ apr_table_unset(r->headers_in, "Trailer");
+ apr_table_unset(r->headers_in, "TE");
+
+ /* Compute Host header */
+ if (dconf->preserve_host == 0) {
+ if (ap_strchr_c(uri->hostname, ':')) { /* if literal IPv6 address */
+ if (uri->port_str && uri->port != DEFAULT_HTTP_PORT) {
+ host = apr_pstrcat(r->pool, "[", uri->hostname, "]:",
+ uri->port_str, NULL);
+ } else {
+ host = apr_pstrcat(r->pool, "[", uri->hostname, "]", NULL);
+ }
+ } else {
+ if (uri->port_str && uri->port != DEFAULT_HTTP_PORT) {
+ host = apr_pstrcat(r->pool, uri->hostname, ":",
+ uri->port_str, NULL);
+ } else {
+ host = uri->hostname;
+ }
+ }
+ apr_table_setn(r->headers_in, "Host", host);
+ }
+ else {
+ /* don't want to use r->hostname as the incoming header might have a
+ * port attached, let's use the original header.
+ */
+ host = saved_host;
+ if (!host) {
+ host = r->server->server_hostname;
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01092)
+ "no HTTP 0.9 request (with no host line) "
+ "on incoming request and preserve host set "
+ "forcing hostname to be %s for uri %s",
+ host, r->uri);
+ apr_table_setn(r->headers_in, "Host", host);
+ }
+ }
+
+ /* handle Via */
+ if (conf->viaopt == via_block) {
+ /* Block all outgoing Via: headers */
+ apr_table_unset(r->headers_in, "Via");
+ } else if (conf->viaopt != via_off) {
+ const char *server_name = ap_get_server_name(r);
+ /* If USE_CANONICAL_NAME_OFF was configured for the proxy virtual host,
+ * then the server name returned by ap_get_server_name() is the
+ * origin server name (which does make too much sense with Via: headers)
+ * so we use the proxy vhost's name instead.
+ */
+ if (server_name == r->hostname)
+ server_name = r->server->server_hostname;
+ /* Create a "Via:" request header entry and merge it */
+ /* Generate outgoing Via: header with/without server comment: */
+ apr_table_mergen(r->headers_in, "Via",
+ (conf->viaopt == via_full)
+ ? apr_psprintf(p, "%d.%d %s%s (%s)",
+ HTTP_VERSION_MAJOR(r->proto_num),
+ HTTP_VERSION_MINOR(r->proto_num),
+ server_name, server_portstr,
+ AP_SERVER_BASEVERSION)
+ : apr_psprintf(p, "%d.%d %s%s",
+ HTTP_VERSION_MAJOR(r->proto_num),
+ HTTP_VERSION_MINOR(r->proto_num),
+ server_name, server_portstr)
+ );
+ }
+
+ /* Use HTTP/1.1 100-Continue as quick "HTTP ping" test
+ * to backend
+ */
+ if (do_100_continue) {
+ /* Add the Expect header if not already there. */
+ if (!(val = apr_table_get(r->headers_in, "Expect"))
+ || (ap_cstr_casecmp(val, "100-Continue") != 0 /* fast path */
+ && !ap_find_token(r->pool, val, "100-Continue"))) {
+ apr_table_mergen(r->headers_in, "Expect", "100-Continue");
+ }
+ }
+ else {
+ /* XXX: we should strip the 100-continue token only from the
+ * Expect header, but are there others actually used anywhere?
+ */
+ apr_table_unset(r->headers_in, "Expect");
+ }
+
+ /* X-Forwarded-*: handling
+ *
+ * XXX Privacy Note:
+ * -----------------
+ *
+ * These request headers are only really useful when the mod_proxy
+ * is used in a reverse proxy configuration, so that useful info
+ * about the client can be passed through the reverse proxy and on
+ * to the backend server, which may require the information to
+ * function properly.
+ *
+ * In a forward proxy situation, these options are a potential
+ * privacy violation, as information about clients behind the proxy
+ * are revealed to arbitrary servers out there on the internet.
+ *
+ * The HTTP/1.1 Via: header is designed for passing client
+ * information through proxies to a server, and should be used in
+ * a forward proxy configuration instead of X-Forwarded-*. See the
+ * ProxyVia option for details.
+ */
+ if (dconf->add_forwarded_headers) {
+ if (PROXYREQ_REVERSE == r->proxyreq) {
+ /* Add X-Forwarded-For: so that the upstream has a chance to
+ * determine, where the original request came from.
+ */
+ apr_table_mergen(r->headers_in, "X-Forwarded-For",
+ r->useragent_ip);
+
+ /* Add X-Forwarded-Host: so that upstream knows what the
+ * original request hostname was.
+ */
+ if (saved_host) {
+ apr_table_mergen(r->headers_in, "X-Forwarded-Host",
+ saved_host);
+ }
+
+ /* Add X-Forwarded-Server: so that upstream knows what the
+ * name of this proxy server is (if there are more than one)
+ * XXX: This duplicates Via: - do we strictly need it?
+ */
+ apr_table_mergen(r->headers_in, "X-Forwarded-Server",
+ r->server->server_hostname);
+ }
+ }
+
+ /* Do we want to strip Proxy-Authorization ?
+ * If we haven't used it, then NO
+ * If we have used it then MAYBE: RFC2616 says we MAY propagate it.
+ * So let's make it configurable by env.
+ */
+ if (r->user != NULL /* we've authenticated */
+ && !apr_table_get(r->subprocess_env, "Proxy-Chain-Auth")) {
+ apr_table_unset(r->headers_in, "Proxy-Authorization");
+ }
+
+ /* for sub-requests, ignore freshness/expiry headers */
+ if (r->main) {
+ apr_table_unset(r->headers_in, "If-Match");
+ apr_table_unset(r->headers_in, "If-Modified-Since");
+ apr_table_unset(r->headers_in, "If-Range");
+ apr_table_unset(r->headers_in, "If-Unmodified-Since");
+ apr_table_unset(r->headers_in, "If-None-Match");
+ }
+
+ /* run hook to fixup the request we are about to send */
+ proxy_run_fixups(r);
+
+ /* We used to send `Host: ` always first, so let's keep it that
+ * way. No telling which legacy backend is relying on this.
+ * If proxy_run_fixups() changed the value, use it (though removal
+ * is ignored).
+ */
+ val = apr_table_get(r->headers_in, "Host");
+ if (val) {
+ apr_table_unset(r->headers_in, "Host");
+ host = val;
+ }
+ buf = apr_pstrcat(p, "Host: ", host, CRLF, NULL);
+ ap_xlate_proto_to_ascii(buf, strlen(buf));
+ e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(header_brigade, e);
+
+ /* Append the (remaining) headers to the brigade */
+ headers_in_array = apr_table_elts(r->headers_in);
+ headers_in = (const apr_table_entry_t *) headers_in_array->elts;
+ for (counter = 0; counter < headers_in_array->nelts; counter++) {
+ if (headers_in[counter].key == NULL
+ || headers_in[counter].val == NULL) {
+ continue;
+ }
+
+ buf = apr_pstrcat(p, headers_in[counter].key, ": ",
+ headers_in[counter].val, CRLF,
+ NULL);
+ ap_xlate_proto_to_ascii(buf, strlen(buf));
+ e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(header_brigade, e);
+ }
+
+cleanup:
+ r->headers_in = saved_headers_in;
+ return rc;
+}
+
+PROXY_DECLARE(int) ap_proxy_prefetch_input(request_rec *r,
+ proxy_conn_rec *backend,
+ apr_bucket_brigade *input_brigade,
+ apr_read_type_e block,
+ apr_off_t *bytes_read,
+ apr_off_t max_read)
+{
+ apr_pool_t *p = r->pool;
+ conn_rec *c = r->connection;
+ apr_bucket_brigade *temp_brigade;
+ apr_status_t status;
+ apr_off_t bytes;
+
+ *bytes_read = 0;
+ if (max_read < APR_BUCKET_BUFF_SIZE) {
+ max_read = APR_BUCKET_BUFF_SIZE;
+ }
+
+ /* Prefetch max_read bytes
+ *
+ * This helps us avoid any election of C-L v.s. T-E
+ * request bodies, since we are willing to keep in
+ * memory this much data, in any case. This gives
+ * us an instant C-L election if the body is of some
+ * reasonable size.
+ */
+ temp_brigade = apr_brigade_create(p, input_brigade->bucket_alloc);
+
+ /* Account for saved input, if any. */
+ apr_brigade_length(input_brigade, 0, bytes_read);
+
+ /* Ensure we don't hit a wall where we have a buffer too small for
+ * ap_get_brigade's filters to fetch us another bucket, surrender
+ * once we hit 80 bytes (an arbitrary value) less than max_read.
+ */
+ while (*bytes_read < max_read - 80
+ && (APR_BRIGADE_EMPTY(input_brigade)
+ || !APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade)))) {
+ status = ap_get_brigade(r->input_filters, temp_brigade,
+ AP_MODE_READBYTES, block,
+ max_read - *bytes_read);
+ /* ap_get_brigade may return success with an empty brigade
+ * for a non-blocking read which would block
+ */
+ if (block == APR_NONBLOCK_READ
+ && ((status == APR_SUCCESS && APR_BRIGADE_EMPTY(temp_brigade))
+ || APR_STATUS_IS_EAGAIN(status))) {
+ break;
+ }
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01095)
+ "prefetch request body failed to %pI (%s)"
+ " from %s (%s)", backend->addr,
+ backend->hostname ? backend->hostname : "",
+ c->client_ip, c->remote_host ? c->remote_host : "");
+ return ap_map_http_request_error(status, HTTP_BAD_REQUEST);
+ }
+
+ apr_brigade_length(temp_brigade, 1, &bytes);
+ *bytes_read += bytes;
+
+ /*
+ * Save temp_brigade in input_brigade. (At least) in the SSL case
+ * temp_brigade contains transient buckets whose data would get
+ * overwritten during the next call of ap_get_brigade in the loop.
+ * ap_save_brigade ensures these buckets to be set aside.
+ * Calling ap_save_brigade with NULL as filter is OK, because
+ * input_brigade already has been created and does not need to get
+ * created by ap_save_brigade.
+ */
+ status = ap_save_brigade(NULL, &input_brigade, &temp_brigade, p);
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01096)
+ "processing prefetched request body failed"
+ " to %pI (%s) from %s (%s)", backend->addr,
+ backend->hostname ? backend->hostname : "",
+ c->client_ip, c->remote_host ? c->remote_host : "");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ return OK;
+}
+
+PROXY_DECLARE(int) ap_proxy_read_input(request_rec *r,
+ proxy_conn_rec *backend,
+ apr_bucket_brigade *bb,
+ apr_off_t max_read)
+{
+ apr_bucket_alloc_t *bucket_alloc = bb->bucket_alloc;
+ apr_read_type_e block = (backend->connection) ? APR_NONBLOCK_READ
+ : APR_BLOCK_READ;
+ apr_status_t status;
+ int rv;
+
+ for (;;) {
+ apr_brigade_cleanup(bb);
+ status = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES,
+ block, max_read);
+ if (block == APR_BLOCK_READ
+ || (!(status == APR_SUCCESS && APR_BRIGADE_EMPTY(bb))
+ && !APR_STATUS_IS_EAGAIN(status))) {
+ break;
+ }
+
+ /* Flush and retry (blocking) */
+ apr_brigade_cleanup(bb);
+ rv = ap_proxy_pass_brigade(bucket_alloc, r, backend,
+ backend->connection, bb, 1);
+ if (rv != OK) {
+ return rv;
+ }
+ block = APR_BLOCK_READ;
+ }
+
+ if (status != APR_SUCCESS) {
+ conn_rec *c = r->connection;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02608)
+ "read request body failed to %pI (%s)"
+ " from %s (%s)", backend->addr,
+ backend->hostname ? backend->hostname : "",
+ c->client_ip, c->remote_host ? c->remote_host : "");
+ return ap_map_http_request_error(status, HTTP_BAD_REQUEST);
+ }
+
+ return OK;
+}
+
+PROXY_DECLARE(int) ap_proxy_spool_input(request_rec *r,
+ proxy_conn_rec *backend,
+ apr_bucket_brigade *input_brigade,
+ apr_off_t *bytes_spooled,
+ apr_off_t max_mem_spool)
+{
+ apr_pool_t *p = r->pool;
+ int seen_eos = 0, rv = OK;
+ apr_status_t status = APR_SUCCESS;
+ apr_bucket_alloc_t *bucket_alloc = input_brigade->bucket_alloc;
+ apr_bucket_brigade *body_brigade;
+ apr_bucket *e;
+ apr_off_t bytes, fsize = 0;
+ apr_file_t *tmpfile = NULL;
+
+ *bytes_spooled = 0;
+ body_brigade = apr_brigade_create(p, bucket_alloc);
+
+ do {
+ if (APR_BRIGADE_EMPTY(input_brigade)) {
+ rv = ap_proxy_read_input(r, backend, input_brigade,
+ HUGE_STRING_LEN);
+ if (rv != OK) {
+ return rv;
+ }
+ }
+
+ /* If this brigade contains EOS, either stop or remove it. */
+ if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade))) {
+ seen_eos = 1;
+ }
+
+ apr_brigade_length(input_brigade, 1, &bytes);
+
+ if (*bytes_spooled + bytes > max_mem_spool) {
+ /* can't spool any more in memory; write latest brigade to disk */
+ if (tmpfile == NULL) {
+ const char *temp_dir;
+ char *template;
+
+ status = apr_temp_dir_get(&temp_dir, p);
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01089)
+ "search for temporary directory failed");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ apr_filepath_merge(&template, temp_dir,
+ "modproxy.tmp.XXXXXX",
+ APR_FILEPATH_NATIVE, p);
+ status = apr_file_mktemp(&tmpfile, template, 0, p);
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01090)
+ "creation of temporary file in directory "
+ "%s failed", temp_dir);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+ for (e = APR_BRIGADE_FIRST(input_brigade);
+ e != APR_BRIGADE_SENTINEL(input_brigade);
+ e = APR_BUCKET_NEXT(e)) {
+ const char *data;
+ apr_size_t bytes_read, bytes_written;
+
+ apr_bucket_read(e, &data, &bytes_read, APR_BLOCK_READ);
+ status = apr_file_write_full(tmpfile, data, bytes_read, &bytes_written);
+ if (status != APR_SUCCESS) {
+ const char *tmpfile_name;
+
+ if (apr_file_name_get(&tmpfile_name, tmpfile) != APR_SUCCESS) {
+ tmpfile_name = "(unknown)";
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01091)
+ "write to temporary file %s failed",
+ tmpfile_name);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ AP_DEBUG_ASSERT(bytes_read == bytes_written);
+ fsize += bytes_written;
+ }
+ apr_brigade_cleanup(input_brigade);
+ }
+ else {
+
+ /*
+ * Save input_brigade in body_brigade. (At least) in the SSL case
+ * input_brigade contains transient buckets whose data would get
+ * overwritten during the next call of ap_get_brigade in the loop.
+ * ap_save_brigade ensures these buckets to be set aside.
+ * Calling ap_save_brigade with NULL as filter is OK, because
+ * body_brigade already has been created and does not need to get
+ * created by ap_save_brigade.
+ */
+ status = ap_save_brigade(NULL, &body_brigade, &input_brigade, p);
+ if (status != APR_SUCCESS) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ }
+
+ *bytes_spooled += bytes;
+ } while (!seen_eos);
+
+ APR_BRIGADE_CONCAT(input_brigade, body_brigade);
+ if (tmpfile) {
+ apr_brigade_insert_file(input_brigade, tmpfile, 0, fsize, p);
+ }
+ if (apr_table_get(r->subprocess_env, "proxy-sendextracrlf")) {
+ e = apr_bucket_immortal_create(CRLF_ASCII, 2, bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(input_brigade, e);
+ }
+ if (tmpfile) {
+ /* We dropped metadata buckets when spooling to tmpfile,
+ * terminate with EOS to allow for flushing in a one go.
+ */
+ e = apr_bucket_eos_create(bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(input_brigade, e);
+ }
+ return OK;
+}
+
+PROXY_DECLARE(int) ap_proxy_pass_brigade(apr_bucket_alloc_t *bucket_alloc,
+ request_rec *r, proxy_conn_rec *p_conn,
+ conn_rec *origin, apr_bucket_brigade *bb,
+ int flush)
+{
+ apr_status_t status;
+ apr_off_t transferred;
+
+ if (flush) {
+ apr_bucket *e = apr_bucket_flush_create(bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ }
+ apr_brigade_length(bb, 0, &transferred);
+ if (transferred != -1)
+ p_conn->worker->s->transferred += transferred;
+ status = ap_pass_brigade(origin->output_filters, bb);
+ /* Cleanup the brigade now to avoid buckets lifetime
+ * issues in case of error returned below. */
+ apr_brigade_cleanup(bb);
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(01084)
+ "pass request body failed to %pI (%s)",
+ p_conn->addr, p_conn->hostname);
+ if (origin->aborted) {
+ const char *ssl_note;
+
+ if (((ssl_note = apr_table_get(origin->notes, "SSL_connect_rv"))
+ != NULL) && (strcmp(ssl_note, "err") == 0)) {
+ return ap_proxyerror(r, HTTP_INTERNAL_SERVER_ERROR,
+ "Error during SSL Handshake with"
+ " remote server");
+ }
+ return APR_STATUS_IS_TIMEUP(status) ? HTTP_GATEWAY_TIME_OUT : HTTP_BAD_GATEWAY;
+ }
+ else {
+ return HTTP_BAD_REQUEST;
+ }
+ }
+ return OK;
+}
+
+/* Fill in unknown schemes from apr_uri_port_of_scheme() */
+
+typedef struct proxy_schemes_t {
+ const char *name;
+ apr_port_t default_port;
+} proxy_schemes_t ;
+
+static proxy_schemes_t pschemes[] =
+{
+ {"fcgi", 8000},
+ {"ajp", AJP13_DEF_PORT},
+ {"scgi", SCGI_DEF_PORT},
+ {"h2c", DEFAULT_HTTP_PORT},
+ {"h2", DEFAULT_HTTPS_PORT},
+ {"ws", DEFAULT_HTTP_PORT},
+ {"wss", DEFAULT_HTTPS_PORT},
+ { NULL, 0xFFFF } /* unknown port */
+};
+
+PROXY_DECLARE(apr_port_t) ap_proxy_port_of_scheme(const char *scheme)
+{
+ if (scheme) {
+ apr_port_t port;
+ if ((port = apr_uri_port_of_scheme(scheme)) != 0) {
+ return port;
+ } else {
+ proxy_schemes_t *pscheme;
+ for (pscheme = pschemes; pscheme->name != NULL; ++pscheme) {
+ if (ap_cstr_casecmp(scheme, pscheme->name) == 0) {
+ return pscheme->default_port;
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+static APR_INLINE int ap_filter_should_yield(ap_filter_t *f)
+{
+ return f->c->data_in_output_filters;
+}
+
+static APR_INLINE int ap_filter_output_pending(conn_rec *c)
+{
+ ap_filter_t *f = c->output_filters;
+ while (f->next) {
+ f = f->next;
+ }
+ if (f->frec->filter_func.out_func(f, NULL)) {
+ return AP_FILTER_ERROR;
+ }
+ return c->data_in_output_filters ? OK : DECLINED;
+}
+
+PROXY_DECLARE(apr_status_t) ap_proxy_buckets_lifetime_transform(request_rec *r,
+ apr_bucket_brigade *from,
+ apr_bucket_brigade *to)
+{
+ apr_bucket *e;
+ apr_bucket *new;
+ const char *data;
+ apr_size_t bytes;
+ apr_status_t rv = APR_SUCCESS;
+ apr_bucket_alloc_t *bucket_alloc = to->bucket_alloc;
+
+ apr_brigade_cleanup(to);
+ for (e = APR_BRIGADE_FIRST(from);
+ e != APR_BRIGADE_SENTINEL(from);
+ e = APR_BUCKET_NEXT(e)) {
+ if (!APR_BUCKET_IS_METADATA(e)) {
+ apr_bucket_read(e, &data, &bytes, APR_BLOCK_READ);
+ new = apr_bucket_transient_create(data, bytes, bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(to, new);
+ }
+ else if (APR_BUCKET_IS_FLUSH(e)) {
+ new = apr_bucket_flush_create(bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(to, new);
+ }
+ else if (APR_BUCKET_IS_EOS(e)) {
+ new = apr_bucket_eos_create(bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(to, new);
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(03304)
+ "Unhandled bucket type of type %s in"
+ " ap_proxy_buckets_lifetime_transform", e->type->name);
+ rv = APR_EGENERAL;
+ }
+ }
+ return rv;
+}
+
+/* An arbitrary large value to address pathological case where we keep
+ * reading from one side only, without scheduling the other direction for
+ * too long. This can happen with large MTU and small read buffers, like
+ * micro-benchmarking huge files bidirectional transfer with client, proxy
+ * and backend on localhost for instance. Though we could just ignore the
+ * case and let the sender stop by itself at some point when/if it needs to
+ * receive data, or the receiver stop when/if it needs to send...
+ */
+#define PROXY_TRANSFER_MAX_READS 10000
+
+PROXY_DECLARE(apr_status_t) ap_proxy_transfer_between_connections(
+ request_rec *r,
+ conn_rec *c_i,
+ conn_rec *c_o,
+ apr_bucket_brigade *bb_i,
+ apr_bucket_brigade *bb_o,
+ const char *name,
+ int *sent,
+ apr_off_t bsize,
+ int flags)
+{
+ apr_status_t rv;
+ int flush_each = 0;
+ unsigned int num_reads = 0;
+#ifdef DEBUGGING
+ apr_off_t len;
+#endif
+
+ /*
+ * Compat: since FLUSH_EACH is default (and zero) for legacy reasons, we
+ * pretend it's no FLUSH_AFTER nor YIELD_PENDING flags, the latter because
+ * flushing would defeat the purpose of checking for pending data (hence
+ * determine whether or not the output chain/stack is full for stopping).
+ */
+ if (!(flags & (AP_PROXY_TRANSFER_FLUSH_AFTER |
+ AP_PROXY_TRANSFER_YIELD_PENDING))) {
+ flush_each = 1;
+ }
+
+ for (;;) {
+ apr_brigade_cleanup(bb_i);
+ rv = ap_get_brigade(c_i->input_filters, bb_i, AP_MODE_READBYTES,
+ APR_NONBLOCK_READ, bsize);
+ if (rv != APR_SUCCESS) {
+ if (!APR_STATUS_IS_EAGAIN(rv) && !APR_STATUS_IS_EOF(rv)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(03308)
+ "ap_proxy_transfer_between_connections: "
+ "error on %s - ap_get_brigade",
+ name);
+ if (rv == APR_INCOMPLETE) {
+ /* Don't return APR_INCOMPLETE, it'd mean "should yield"
+ * for the caller, while it means "incomplete body" here
+ * from ap_http_filter(), which is an error.
+ */
+ rv = APR_EGENERAL;
+ }
+ }
+ break;
+ }
+
+ if (c_o->aborted) {
+ apr_brigade_cleanup(bb_i);
+ flags &= ~AP_PROXY_TRANSFER_FLUSH_AFTER;
+ rv = APR_EPIPE;
+ break;
+ }
+ if (APR_BRIGADE_EMPTY(bb_i)) {
+ break;
+ }
+#ifdef DEBUGGING
+ len = -1;
+ apr_brigade_length(bb_i, 0, &len);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03306)
+ "ap_proxy_transfer_between_connections: "
+ "read %" APR_OFF_T_FMT
+ " bytes from %s", len, name);
+#endif
+ if (sent) {
+ *sent = 1;
+ }
+ ap_proxy_buckets_lifetime_transform(r, bb_i, bb_o);
+ if (flush_each) {
+ apr_bucket *b;
+ /*
+ * Do not use ap_fflush here since this would cause the flush
+ * bucket to be sent in a separate brigade afterwards which
+ * causes some filters to set aside the buckets from the first
+ * brigade and process them when FLUSH arrives in the second
+ * brigade. As set asides of our transformed buckets involve
+ * memory copying we try to avoid this. If we have the flush
+ * bucket in the first brigade they directly process the
+ * buckets without setting them aside.
+ */
+ b = apr_bucket_flush_create(bb_o->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb_o, b);
+ }
+ rv = ap_pass_brigade(c_o->output_filters, bb_o);
+ apr_brigade_cleanup(bb_o);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(03307)
+ "ap_proxy_transfer_between_connections: "
+ "error on %s - ap_pass_brigade",
+ name);
+ flags &= ~AP_PROXY_TRANSFER_FLUSH_AFTER;
+ break;
+ }
+
+ /* Yield if the output filters stack is full? This is to avoid
+ * blocking and give the caller a chance to POLLOUT async.
+ */
+ if ((flags & AP_PROXY_TRANSFER_YIELD_PENDING)
+ && ap_filter_should_yield(c_o->output_filters)) {
+ int rc = ap_filter_output_pending(c_o);
+ if (rc == OK) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+ "ap_proxy_transfer_between_connections: "
+ "yield (output pending)");
+ rv = APR_INCOMPLETE;
+ break;
+ }
+ if (rc != DECLINED) {
+ rv = AP_FILTER_ERROR;
+ break;
+ }
+ }
+
+ /* Yield if we keep hold of the thread for too long? This gives
+ * the caller a chance to schedule the other direction too.
+ */
+ if ((flags & AP_PROXY_TRANSFER_YIELD_MAX_READS)
+ && ++num_reads > PROXY_TRANSFER_MAX_READS) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+ "ap_proxy_transfer_between_connections: "
+ "yield (max reads)");
+ rv = APR_SUCCESS;
+ break;
+ }
+ }
+
+ if (flags & AP_PROXY_TRANSFER_FLUSH_AFTER) {
+ ap_fflush(c_o->output_filters, bb_o);
+ apr_brigade_cleanup(bb_o);
+ }
+ apr_brigade_cleanup(bb_i);
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, rv, r,
+ "ap_proxy_transfer_between_connections complete (%s %pI)",
+ (c_i == r->connection) ? "to" : "from",
+ (c_i == r->connection) ? c_o->client_addr
+ : c_i->client_addr);
+
+ if (APR_STATUS_IS_EAGAIN(rv)) {
+ rv = APR_SUCCESS;
+ }
+ return rv;
+}
+
+struct proxy_tunnel_conn {
+ /* the other side of the tunnel */
+ struct proxy_tunnel_conn *other;
+
+ conn_rec *c;
+ const char *name;
+
+ apr_pollfd_t *pfd;
+ apr_bucket_brigade *bb;
+
+ unsigned int down_in:1,
+ down_out:1;
+};
+
+PROXY_DECLARE(apr_status_t) ap_proxy_tunnel_create(proxy_tunnel_rec **ptunnel,
+ request_rec *r, conn_rec *c_o,
+ const char *scheme)
+{
+ apr_status_t rv;
+ conn_rec *c_i = r->connection;
+ apr_interval_time_t timeout = -1;
+ proxy_tunnel_rec *tunnel;
+
+ *ptunnel = NULL;
+
+ tunnel = apr_pcalloc(r->pool, sizeof(*tunnel));
+
+ rv = apr_pollset_create(&tunnel->pollset, 2, r->pool, APR_POLLSET_NOCOPY);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ tunnel->r = r;
+ tunnel->scheme = apr_pstrdup(r->pool, scheme);
+ tunnel->client = apr_pcalloc(r->pool, sizeof(struct proxy_tunnel_conn));
+ tunnel->origin = apr_pcalloc(r->pool, sizeof(struct proxy_tunnel_conn));
+ tunnel->pfds = apr_array_make(r->pool, 2, sizeof(apr_pollfd_t));
+ tunnel->read_buf_size = ap_get_read_buf_size(r);
+ tunnel->client->other = tunnel->origin;
+ tunnel->origin->other = tunnel->client;
+ tunnel->timeout = -1;
+
+ tunnel->client->c = c_i;
+ tunnel->client->name = "client";
+ tunnel->client->bb = apr_brigade_create(c_i->pool, c_i->bucket_alloc);
+ tunnel->client->pfd = &APR_ARRAY_PUSH(tunnel->pfds, apr_pollfd_t);
+ tunnel->client->pfd->p = r->pool;
+ tunnel->client->pfd->desc_type = APR_POLL_SOCKET;
+ tunnel->client->pfd->desc.s = ap_get_conn_socket(c_i);
+ tunnel->client->pfd->client_data = tunnel->client;
+
+ tunnel->origin->c = c_o;
+ tunnel->origin->name = "origin";
+ tunnel->origin->bb = apr_brigade_create(c_o->pool, c_o->bucket_alloc);
+ tunnel->origin->pfd = &APR_ARRAY_PUSH(tunnel->pfds, apr_pollfd_t);
+ tunnel->origin->pfd->p = r->pool;
+ tunnel->origin->pfd->desc_type = APR_POLL_SOCKET;
+ tunnel->origin->pfd->desc.s = ap_get_conn_socket(c_o);
+ tunnel->origin->pfd->client_data = tunnel->origin;
+
+ /* Defaults to the biggest timeout of both connections */
+ apr_socket_timeout_get(tunnel->client->pfd->desc.s, &timeout);
+ apr_socket_timeout_get(tunnel->origin->pfd->desc.s, &tunnel->timeout);
+ if (timeout >= 0 && (tunnel->timeout < 0 || tunnel->timeout < timeout)) {
+ tunnel->timeout = timeout;
+ }
+
+ /* We should be nonblocking from now on the sockets */
+ apr_socket_opt_set(tunnel->client->pfd->desc.s, APR_SO_NONBLOCK, 1);
+ apr_socket_opt_set(tunnel->origin->pfd->desc.s, APR_SO_NONBLOCK, 1);
+
+ /* No coalescing filters */
+ ap_remove_output_filter_byhandle(c_i->output_filters,
+ "SSL/TLS Coalescing Filter");
+ ap_remove_output_filter_byhandle(c_o->output_filters,
+ "SSL/TLS Coalescing Filter");
+
+ /* Bidirectional non-HTTP stream will confuse mod_reqtimeoout */
+ ap_remove_input_filter_byhandle(c_i->input_filters, "reqtimeout");
+
+ /* The input/output filter stacks should contain connection filters only */
+ r->input_filters = r->proto_input_filters = c_i->input_filters;
+ r->output_filters = r->proto_output_filters = c_i->output_filters;
+
+ /* Won't be reused after tunneling */
+ c_i->keepalive = AP_CONN_CLOSE;
+ c_o->keepalive = AP_CONN_CLOSE;
+
+ /* Disable half-close forwarding for this request? */
+ if (apr_table_get(r->subprocess_env, "proxy-nohalfclose")) {
+ tunnel->nohalfclose = 1;
+ }
+
+ /* Start with POLLOUT and let ap_proxy_tunnel_run() schedule both
+ * directions when there are no output data pending (anymore).
+ */
+ tunnel->client->pfd->reqevents = APR_POLLOUT | APR_POLLERR;
+ tunnel->origin->pfd->reqevents = APR_POLLOUT | APR_POLLERR;
+ if ((rv = apr_pollset_add(tunnel->pollset, tunnel->client->pfd))
+ || (rv = apr_pollset_add(tunnel->pollset, tunnel->origin->pfd))) {
+ return rv;
+ }
+
+ *ptunnel = tunnel;
+ return APR_SUCCESS;
+}
+
+static void add_pollset(apr_pollset_t *pollset, apr_pollfd_t *pfd,
+ apr_int16_t events)
+{
+ apr_status_t rv;
+
+ AP_DEBUG_ASSERT((pfd->reqevents & events) == 0);
+
+ if (pfd->reqevents) {
+ rv = apr_pollset_remove(pollset, pfd);
+ if (rv != APR_SUCCESS) {
+ AP_DEBUG_ASSERT(1);
+ }
+ }
+
+ if (events & APR_POLLIN) {
+ events |= APR_POLLHUP;
+ }
+ pfd->reqevents |= events | APR_POLLERR;
+ rv = apr_pollset_add(pollset, pfd);
+ if (rv != APR_SUCCESS) {
+ AP_DEBUG_ASSERT(1);
+ }
+}
+
+static void del_pollset(apr_pollset_t *pollset, apr_pollfd_t *pfd,
+ apr_int16_t events)
+{
+ apr_status_t rv;
+
+ AP_DEBUG_ASSERT((pfd->reqevents & events) != 0);
+
+ rv = apr_pollset_remove(pollset, pfd);
+ if (rv != APR_SUCCESS) {
+ AP_DEBUG_ASSERT(0);
+ return;
+ }
+
+ if (events & APR_POLLIN) {
+ events |= APR_POLLHUP;
+ }
+ if (pfd->reqevents & ~(events | APR_POLLERR)) {
+ pfd->reqevents &= ~events;
+ rv = apr_pollset_add(pollset, pfd);
+ if (rv != APR_SUCCESS) {
+ AP_DEBUG_ASSERT(0);
+ return;
+ }
+ }
+ else {
+ pfd->reqevents = 0;
+ }
+}
+
+static int proxy_tunnel_forward(proxy_tunnel_rec *tunnel,
+ struct proxy_tunnel_conn *in)
+{
+ struct proxy_tunnel_conn *out = in->other;
+ apr_status_t rv;
+ int sent = 0;
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, tunnel->r,
+ "proxy: %s: %s input ready",
+ tunnel->scheme, in->name);
+
+ rv = ap_proxy_transfer_between_connections(tunnel->r,
+ in->c, out->c,
+ in->bb, out->bb,
+ in->name, &sent,
+ tunnel->read_buf_size,
+ AP_PROXY_TRANSFER_YIELD_PENDING |
+ AP_PROXY_TRANSFER_YIELD_MAX_READS);
+ if (sent && out == tunnel->client) {
+ tunnel->replied = 1;
+ }
+ if (rv != APR_SUCCESS) {
+ if (APR_STATUS_IS_INCOMPLETE(rv)) {
+ /* Pause POLLIN while waiting for POLLOUT on the other
+ * side, hence avoid filling the output filters even
+ * more to avoid blocking there.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, tunnel->r,
+ "proxy: %s: %s wait writable",
+ tunnel->scheme, out->name);
+ }
+ else if (APR_STATUS_IS_EOF(rv)) {
+ /* Stop POLLIN and wait for POLLOUT (flush) on the
+ * other side to shut it down.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, tunnel->r,
+ "proxy: %s: %s read shutdown",
+ tunnel->scheme, in->name);
+ if (tunnel->nohalfclose) {
+ /* No half-close forwarding, we are done both ways as
+ * soon as one side shuts down.
+ */
+ return DONE;
+ }
+ in->down_in = 1;
+ }
+ else {
+ /* Real failure, bail out */
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ del_pollset(tunnel->pollset, in->pfd, APR_POLLIN);
+ add_pollset(tunnel->pollset, out->pfd, APR_POLLOUT);
+ }
+
+ return OK;
+}
+
+PROXY_DECLARE(int) ap_proxy_tunnel_run(proxy_tunnel_rec *tunnel)
+{
+ int status = OK, rc;
+ request_rec *r = tunnel->r;
+ apr_pollset_t *pollset = tunnel->pollset;
+ struct proxy_tunnel_conn *client = tunnel->client,
+ *origin = tunnel->origin;
+ apr_interval_time_t timeout = tunnel->timeout >= 0 ? tunnel->timeout : -1;
+ const char *scheme = tunnel->scheme;
+ apr_status_t rv;
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(10212)
+ "proxy: %s: tunnel running (timeout %lf)",
+ scheme, timeout >= 0 ? (double)timeout / APR_USEC_PER_SEC
+ : (double)-1.0);
+
+ /* Loop until both directions of the connection are closed,
+ * or a failure occurs.
+ */
+ do {
+ const apr_pollfd_t *results;
+ apr_int32_t nresults, i;
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, r,
+ "proxy: %s: polling (client=%hx, origin=%hx)",
+ scheme, client->pfd->reqevents, origin->pfd->reqevents);
+ do {
+ rv = apr_pollset_poll(pollset, timeout, &nresults, &results);
+ } while (APR_STATUS_IS_EINTR(rv));
+
+ if (rv != APR_SUCCESS) {
+ if (APR_STATUS_IS_TIMEUP(rv)) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, APLOGNO(10213)
+ "proxy: %s: polling timed out "
+ "(client=%hx, origin=%hx)",
+ scheme, client->pfd->reqevents,
+ origin->pfd->reqevents);
+ status = HTTP_GATEWAY_TIME_OUT;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10214)
+ "proxy: %s: polling failed", scheme);
+ status = HTTP_INTERNAL_SERVER_ERROR;
+ }
+ goto done;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, r, APLOGNO(10215)
+ "proxy: %s: woken up, %i result(s)", scheme, nresults);
+
+ for (i = 0; i < nresults; i++) {
+ const apr_pollfd_t *pfd = &results[i];
+ struct proxy_tunnel_conn *tc = pfd->client_data;
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, r,
+ "proxy: %s: #%i: %s: %hx/%hx", scheme, i,
+ tc->name, pfd->rtnevents, tc->pfd->reqevents);
+
+ /* sanity check */
+ if (pfd->desc.s != client->pfd->desc.s
+ && pfd->desc.s != origin->pfd->desc.s) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10222)
+ "proxy: %s: unknown socket in pollset", scheme);
+ status = HTTP_INTERNAL_SERVER_ERROR;
+ goto done;
+ }
+
+ if (!(pfd->rtnevents & (APR_POLLIN | APR_POLLOUT |
+ APR_POLLHUP | APR_POLLERR))) {
+ /* this catches POLLNVAL etc.. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10220)
+ "proxy: %s: polling events error (%x)",
+ scheme, pfd->rtnevents);
+ status = HTTP_INTERNAL_SERVER_ERROR;
+ goto done;
+ }
+
+ /* We want to write if we asked for POLLOUT and got:
+ * - POLLOUT: the socket is ready for write;
+ * - !POLLIN: the socket is in error state (POLLERR) so we let
+ * the user know by failing the write and log, OR the socket
+ * is shutdown for read already (POLLHUP) so we have to
+ * shutdown for write.
+ */
+ if ((tc->pfd->reqevents & APR_POLLOUT)
+ && ((pfd->rtnevents & APR_POLLOUT)
+ || !(tc->pfd->reqevents & APR_POLLIN)
+ || !(pfd->rtnevents & (APR_POLLIN | APR_POLLHUP)))) {
+ struct proxy_tunnel_conn *out = tc, *in = tc->other;
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, r,
+ "proxy: %s: %s output ready",
+ scheme, out->name);
+
+ rc = ap_filter_output_pending(out->c);
+ if (rc == OK) {
+ /* Keep polling out (only) */
+ continue;
+ }
+ if (rc != DECLINED) {
+ /* Real failure, bail out */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10221)
+ "proxy: %s: %s flushing failed (%i)",
+ scheme, out->name, rc);
+ status = rc;
+ goto done;
+ }
+
+ /* No more pending data. If the other side is not readable
+ * anymore it's time to shutdown for write (this direction
+ * is over). Otherwise back to normal business.
+ */
+ del_pollset(pollset, out->pfd, APR_POLLOUT);
+ if (in->down_in) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
+ "proxy: %s: %s write shutdown",
+ scheme, out->name);
+ apr_socket_shutdown(out->pfd->desc.s, 1);
+ out->down_out = 1;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r,
+ "proxy: %s: %s resume writable",
+ scheme, out->name);
+ add_pollset(pollset, in->pfd, APR_POLLIN);
+
+ /* Flush any pending input data now, we don't know when
+ * the next POLLIN will trigger and retaining data might
+ * deadlock the underlying protocol. We don't check for
+ * pending data first with ap_filter_input_pending() since
+ * the read from proxy_tunnel_forward() is nonblocking
+ * anyway and returning OK if there's no data.
+ */
+ rc = proxy_tunnel_forward(tunnel, in);
+ if (rc != OK) {
+ status = rc;
+ goto done;
+ }
+ }
+ }
+
+ /* We want to read if we asked for POLLIN|HUP and got:
+ * - POLLIN|HUP: the socket is ready for read or EOF (POLLHUP);
+ * - !POLLOUT: the socket is in error state (POLLERR) so we let
+ * the user know by failing the read and log.
+ */
+ if ((tc->pfd->reqevents & APR_POLLIN)
+ && ((pfd->rtnevents & (APR_POLLIN | APR_POLLHUP))
+ || !(pfd->rtnevents & APR_POLLOUT))) {
+ rc = proxy_tunnel_forward(tunnel, tc);
+ if (rc != OK) {
+ status = rc;
+ goto done;
+ }
+ }
+ }
+ } while (!client->down_out || !origin->down_out);
+
+done:
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(10223)
+ "proxy: %s: tunneling returns (%i)", scheme, status);
+ if (status == DONE) {
+ status = OK;
+ }
+ return status;
+}
+
+PROXY_DECLARE (const char *) ap_proxy_show_hcmethod(hcmethod_t method)
+{
+ proxy_hcmethods_t *m = proxy_hcmethods;
+ for (; m->name; m++) {
+ if (m->method == method) {
+ return m->name;
+ }
+ }
+ return "???";
+}
+
+void proxy_util_register_hooks(apr_pool_t *p)
+{
+ APR_REGISTER_OPTIONAL_FN(ap_proxy_retry_worker);
+ APR_REGISTER_OPTIONAL_FN(ap_proxy_clear_connection);
+ APR_REGISTER_OPTIONAL_FN(proxy_balancer_get_best_worker);
+}
diff --git a/modules/proxy/proxy_util.h b/modules/proxy/proxy_util.h
new file mode 100644
index 0000000..bc131da
--- /dev/null
+++ b/modules/proxy/proxy_util.h
@@ -0,0 +1,45 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef PROXY_UTIL_H_
+#define PROXY_UTIL_H_
+
+/**
+ * @file proxy_util.h
+ * @brief Internal interfaces private to mod_proxy.
+ *
+ * @defgroup MOD_PROXY_PRIVATE Private
+ * @ingroup MOD_PROXY
+ * @{
+ */
+
+PROXY_DECLARE(int) ap_proxy_is_ipaddr(struct dirconn_entry *This, apr_pool_t *p);
+PROXY_DECLARE(int) ap_proxy_is_domainname(struct dirconn_entry *This, apr_pool_t *p);
+PROXY_DECLARE(int) ap_proxy_is_hostname(struct dirconn_entry *This, apr_pool_t *p);
+PROXY_DECLARE(int) ap_proxy_is_word(struct dirconn_entry *This, apr_pool_t *p);
+
+extern PROXY_DECLARE_DATA int proxy_lb_workers;
+extern PROXY_DECLARE_DATA const apr_strmatch_pattern *ap_proxy_strmatch_path;
+extern PROXY_DECLARE_DATA const apr_strmatch_pattern *ap_proxy_strmatch_domain;
+
+/**
+ * Register optional functions declared within proxy_util.c.
+ */
+void proxy_util_register_hooks(apr_pool_t *p);
+
+/** @} */
+
+#endif /* PROXY_UTIL_H_ */
diff --git a/modules/proxy/scgi.h b/modules/proxy/scgi.h
new file mode 100644
index 0000000..a2e5e96
--- /dev/null
+++ b/modules/proxy/scgi.h
@@ -0,0 +1,36 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file scgi.h
+ * @brief Shared SCGI-related definitions
+ *
+ * @ingroup APACHE_INTERNAL
+ * @{
+ */
+
+#ifndef SCGI_H
+#define SCGI_H
+
+/* This is not defined by the protocol. It is a convention
+ * of mod_proxy_scgi, and mod_proxy utility routines must
+ * use the same value as mod_proxy_scgi.
+ */
+#define SCGI_DEF_PORT 4000
+
+/** @} */
+
+#endif /* SCGI_H */
diff --git a/modules/session/Makefile.in b/modules/session/Makefile.in
new file mode 100644
index 0000000..fb9dacf
--- /dev/null
+++ b/modules/session/Makefile.in
@@ -0,0 +1,4 @@
+# a modules Makefile has no explicit targets -- they will be defined by
+# whatever modules are enabled. just grab special.mk to deal with this.
+include $(top_srcdir)/build/special.mk
+
diff --git a/modules/session/NWGNUmakefile b/modules/session/NWGNUmakefile
new file mode 100644
index 0000000..85f3a31
--- /dev/null
+++ b/modules/session/NWGNUmakefile
@@ -0,0 +1,257 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+ifneq "$(MAKECMDGOALS)" "clean"
+ifneq "$(findstring clobber_,$(MAKECMDGOALS))" "clobber_"
+APU_HAVE_CRYPTO = $(shell $(AWK) '/^\#define APU_HAVE_CRYPTO/{print $$3}' $(APRUTIL)/include/apu.h)
+endif
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME =
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION =
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME =
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE =
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/session.nlm \
+ $(OBJDIR)/session_cookie.nlm \
+ $(OBJDIR)/session_dbd.nlm \
+ $(EOLIST)
+
+# If the APU library has cryptp API then build the mod_session_crypto module
+ifeq "$(APU_HAVE_CRYPTO)" "1"
+TARGET_nlm += $(OBJDIR)/session_crypto.nlm
+endif
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/session/NWGNUsession b/modules/session/NWGNUsession
new file mode 100644
index 0000000..635e8c1
--- /dev/null
+++ b/modules/session/NWGNUsession
@@ -0,0 +1,254 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(SRC)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = session
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Session Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Session Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_session.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ session_module \
+ ap_hook_session_decode \
+ ap_hook_session_encode \
+ ap_hook_session_load \
+ ap_hook_session_save \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+vpath %.c ../arch/netware
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/session/NWGNUsession_cookie b/modules/session/NWGNUsession_cookie
new file mode 100644
index 0000000..93f9200
--- /dev/null
+++ b/modules/session/NWGNUsession_cookie
@@ -0,0 +1,253 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(SRC)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = session_cookie
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Session Cookie Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = SessionCookie Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_session_cookie.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ session \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ ap_hook_session_load \
+ ap_hook_session_save \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ session_cookie_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+vpath %.c ../arch/netware
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/session/NWGNUsession_crypto b/modules/session/NWGNUsession_crypto
new file mode 100644
index 0000000..825065f
--- /dev/null
+++ b/modules/session/NWGNUsession_crypto
@@ -0,0 +1,255 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(SRC)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = session_crypto
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Session Crypto Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = SessionCrypto Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_session_crypto.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ session \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ ap_hook_session_decode \
+ ap_hook_session_encode \
+ ap_hook_session_load \
+ ap_hook_session_save \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ session_crypto_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+vpath %.c ../arch/netware
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/session/NWGNUsession_dbd b/modules/session/NWGNUsession_dbd
new file mode 100644
index 0000000..9473d63
--- /dev/null
+++ b/modules/session/NWGNUsession_dbd
@@ -0,0 +1,253 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(SRC)/include \
+ $(STDMOD)/database \
+ $(NWOS) \
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = session_dbd
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Session Cookie Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = SessionDbd Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_session_dbd.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ session \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ ap_hook_session_load \
+ ap_hook_session_save \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ session_dbd_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+vpath %.c ../arch/netware
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/session/config.m4 b/modules/session/config.m4
new file mode 100644
index 0000000..7a38185
--- /dev/null
+++ b/modules/session/config.m4
@@ -0,0 +1,68 @@
+dnl modules enabled in this directory by default
+
+if test -z "$enable_session" ; then
+ session_mods_enable=most
+else
+ session_mods_enable=$enable_session
+fi
+
+dnl Session
+
+dnl APACHE_MODULE(name, helptext[, objects[, structname[, default[, config]]]])
+
+APACHE_MODPATH_INIT(session)
+
+dnl Session modules; modules that are capable of storing key value pairs in
+dnl various places, such as databases, LDAP, or cookies.
+dnl
+session_cookie_objects='mod_session_cookie.lo'
+session_crypto_objects='mod_session_crypto.lo'
+session_dbd_objects='mod_session_dbd.lo'
+
+case "$host" in
+ *os2*)
+ # OS/2 DLLs must resolve all symbols at build time
+ # and we need some from main session module
+ session_cookie_objects="$session_cookie_objects mod_session.la"
+ session_crypto_objects="$session_crypto_objects mod_session.la"
+ session_dbd_objects="$session_dbd_objects mod_session.la"
+ ;;
+esac
+
+APACHE_MODULE(session, session module, , , most)
+APACHE_MODULE(session_cookie, session cookie module, $session_cookie_objects, , $session_mods_enable,,session)
+
+if test "$enable_session_crypto" != ""; then
+ session_mods_enable_crypto=$enable_session_crypto
+else
+ session_mods_enable_crypto=$session_mods_enable
+fi
+if test "$session_mods_enable_crypto" != "no"; then
+ saved_CPPFLAGS="$CPPFLAGS"
+ CPPFLAGS="$CPPFLAGS $APR_INCLUDES $APU_INCLUDES"
+ AC_TRY_COMPILE([#include <apr_crypto.h>],[
+#if APU_HAVE_CRYPTO == 0
+#error no crypto support
+#endif
+ ], [ap_HAVE_APR_CRYPTO="yes"], [ap_HAVE_APR_CRYPTO="no"])
+ CPPFLAGS="$saved_CPPFLAGS"
+ if test $ap_HAVE_APR_CRYPTO = "no"; then
+ AC_MSG_WARN([Your APR does not include SSL/EVP support. To enable it: configure --with-crypto])
+ if test "$enable_session_crypto" != "" -a "$enable_session_crypto" != "no"; then
+ AC_MSG_ERROR([mod_session_crypto cannot be enabled])
+ fi
+ session_mods_enable_crypto="no"
+ fi
+fi
+APACHE_MODULE(session_crypto, session crypto module, $session_crypto_objects, , $session_mods_enable_crypto, [
+if test "$session_mods_enable_crypto" = "no" ; then
+ enable_session_crypto=no
+fi
+],session)
+
+APACHE_MODULE(session_dbd, session dbd module, $session_dbd_objects, , $session_mods_enable,,session)
+
+APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/$modpath_current])
+
+APACHE_MODPATH_FINISH
+
diff --git a/modules/session/mod_session.c b/modules/session/mod_session.c
new file mode 100644
index 0000000..fa8d406
--- /dev/null
+++ b/modules/session/mod_session.c
@@ -0,0 +1,711 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mod_session.h"
+#include "apr_lib.h"
+#include "apr_strings.h"
+#include "util_filter.h"
+#include "http_log.h"
+#include "http_request.h"
+#include "http_protocol.h"
+
+#define SESSION_EXPIRY "expiry"
+#define HTTP_SESSION "HTTP_SESSION"
+
+APR_HOOK_STRUCT(
+ APR_HOOK_LINK(session_load)
+ APR_HOOK_LINK(session_save)
+ APR_HOOK_LINK(session_encode)
+ APR_HOOK_LINK(session_decode)
+)
+APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(ap, SESSION, int, session_load,
+ (request_rec * r, session_rec ** z), (r, z), DECLINED)
+APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(ap, SESSION, int, session_save,
+ (request_rec * r, session_rec * z), (r, z), DECLINED)
+APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ap, SESSION, int, session_encode,
+ (request_rec * r, session_rec * z), (r, z), OK, DECLINED)
+APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ap, SESSION, int, session_decode,
+ (request_rec * r, session_rec * z), (r, z), OK, DECLINED)
+
+static int session_identity_encode(request_rec * r, session_rec * z);
+static int session_identity_decode(request_rec * r, session_rec * z);
+static int session_fixups(request_rec * r);
+
+/**
+ * Should the session be included within this URL.
+ *
+ * This function tests whether a session is valid for this URL. It uses the
+ * include and exclude arrays to determine whether they should be included.
+ */
+static int session_included(request_rec * r, session_dir_conf * conf)
+{
+
+ const char **includes = (const char **) conf->includes->elts;
+ const char **excludes = (const char **) conf->excludes->elts;
+ int included = 1; /* defaults to included */
+ int i;
+
+ if (conf->includes->nelts) {
+ included = 0;
+ for (i = 0; !included && i < conf->includes->nelts; i++) {
+ const char *include = includes[i];
+ if (strncmp(r->uri, include, strlen(include)) == 0) {
+ included = 1;
+ }
+ }
+ }
+
+ if (conf->excludes->nelts) {
+ for (i = 0; included && i < conf->excludes->nelts; i++) {
+ const char *exclude = excludes[i];
+ if (strncmp(r->uri, exclude, strlen(exclude)) == 0) {
+ included = 0;
+ }
+ }
+ }
+
+ return included;
+}
+
+/**
+ * Load the session.
+ *
+ * If the session doesn't exist, a blank one will be created.
+ *
+ * @param r The request
+ * @param z A pointer to where the session will be written.
+ */
+static apr_status_t ap_session_load(request_rec * r, session_rec ** z)
+{
+
+ session_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
+ &session_module);
+ apr_time_t now;
+ session_rec *zz = NULL;
+ int rv = 0;
+
+ /* is the session enabled? */
+ if (!dconf || !dconf->enabled) {
+ return APR_SUCCESS;
+ }
+
+ /* should the session be loaded at all? */
+ if (!session_included(r, dconf)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01814)
+ "excluded by configuration for: %s", r->uri);
+ return APR_SUCCESS;
+ }
+
+ /* load the session from the session hook */
+ rv = ap_run_session_load(r, &zz);
+ if (DECLINED == rv) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01815)
+ "session is enabled but no session modules have been configured, "
+ "session not loaded: %s", r->uri);
+ return APR_EGENERAL;
+ }
+ else if (OK != rv) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01816)
+ "error while loading the session, "
+ "session not loaded: %s", r->uri);
+ return rv;
+ }
+
+ /* found a session that hasn't expired? */
+ now = apr_time_now();
+
+ if (zz) {
+ /* load the session attributes */
+ rv = ap_run_session_decode(r, zz);
+
+ /* having a session we cannot decode is just as good as having
+ none at all */
+ if (OK != rv) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01817)
+ "error while decoding the session, "
+ "session not loaded: %s", r->uri);
+ zz = NULL;
+ }
+
+ /* invalidate session if session is expired */
+ if (zz && zz->expiry && zz->expiry < now) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "session is expired");
+ zz = NULL;
+ }
+ }
+
+ /* no luck, create a blank session */
+ if (!zz) {
+ zz = (session_rec *) apr_pcalloc(r->pool, sizeof(session_rec));
+ zz->pool = r->pool;
+ zz->entries = apr_table_make(zz->pool, 10);
+ }
+
+ /* make sure the expiry and maxage are set, if present */
+ if (dconf->maxage) {
+ if (!zz->expiry) {
+ zz->expiry = now + dconf->maxage * APR_USEC_PER_SEC;
+ }
+ zz->maxage = dconf->maxage;
+ }
+
+ *z = zz;
+
+ return APR_SUCCESS;
+
+}
+
+/**
+ * Save the session.
+ *
+ * In most implementations the session is only saved if the dirty flag is
+ * true. This prevents the session being saved unnecessarily.
+ *
+ * @param r The request
+ * @param z A pointer to where the session will be written.
+ */
+static apr_status_t ap_session_save(request_rec * r, session_rec * z)
+{
+ if (z) {
+ apr_time_t now = apr_time_now();
+ apr_time_t initialExpiry = z->expiry;
+ int rv = 0;
+
+ session_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
+ &session_module);
+
+ /* sanity checks, should we try save at all? */
+ if (z->written) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01818)
+ "attempt made to save the session twice, "
+ "session not saved: %s", r->uri);
+ return APR_EGENERAL;
+ }
+ if (z->expiry && z->expiry < now) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01819)
+ "attempt made to save a session when the session had already expired, "
+ "session not saved: %s", r->uri);
+ return APR_EGENERAL;
+ }
+
+ /* reset the expiry back to maxage, if the expiry is present */
+ if (dconf->maxage) {
+ z->expiry = now + dconf->maxage * APR_USEC_PER_SEC;
+ z->maxage = dconf->maxage;
+ }
+
+ /* reset the expiry before saving if present */
+ if (z->dirty && z->maxage) {
+ z->expiry = now + z->maxage * APR_USEC_PER_SEC;
+ }
+
+ /* don't save if the only change is the expiry by a small amount */
+ if (!z->dirty && dconf->expiry_update_time
+ && (z->expiry - initialExpiry < dconf->expiry_update_time)) {
+ return APR_SUCCESS;
+ }
+
+ /* also don't save sessions that didn't change at all */
+ if (!z->dirty && !z->maxage) {
+ return APR_SUCCESS;
+ }
+
+ /* encode the session */
+ rv = ap_run_session_encode(r, z);
+ if (OK != rv) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01820)
+ "error while encoding the session, "
+ "session not saved: %s", r->uri);
+ return rv;
+ }
+
+ /* try the save */
+ rv = ap_run_session_save(r, z);
+ if (DECLINED == rv) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01821)
+ "session is enabled but no session modules have been configured, "
+ "session not saved: %s", r->uri);
+ return APR_EGENERAL;
+ }
+ else if (OK != rv) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01822)
+ "error while saving the session, "
+ "session not saved: %s", r->uri);
+ return rv;
+ }
+ else {
+ z->written = 1;
+ }
+ }
+
+ return APR_SUCCESS;
+
+}
+
+/**
+ * Get a particular value from the session.
+ * @param r The current request.
+ * @param z The current session. If this value is NULL, the session will be
+ * looked up in the request, created if necessary, and saved to the request
+ * notes.
+ * @param key The key to get.
+ * @param value The buffer to write the value to.
+ */
+static apr_status_t ap_session_get(request_rec * r, session_rec * z,
+ const char *key, const char **value)
+{
+ if (!z) {
+ apr_status_t rv;
+ rv = ap_session_load(r, &z);
+ if (APR_SUCCESS != rv) {
+ return rv;
+ }
+ }
+ if (z && z->entries) {
+ *value = apr_table_get(z->entries, key);
+ }
+
+ return OK;
+}
+
+/**
+ * Set a particular value to the session.
+ *
+ * Using this method ensures that the dirty flag is set correctly, so that
+ * the session can be saved efficiently.
+ * @param r The current request.
+ * @param z The current session. If this value is NULL, the session will be
+ * looked up in the request, created if necessary, and saved to the request
+ * notes.
+ * @param key The key to set. The existing key value will be replaced.
+ * @param value The value to set.
+ */
+static apr_status_t ap_session_set(request_rec * r, session_rec * z,
+ const char *key, const char *value)
+{
+ if (!z) {
+ apr_status_t rv;
+ rv = ap_session_load(r, &z);
+ if (APR_SUCCESS != rv) {
+ return rv;
+ }
+ }
+ if (z) {
+ if (value) {
+ apr_table_set(z->entries, key, value);
+ }
+ else {
+ apr_table_unset(z->entries, key);
+ }
+ z->dirty = 1;
+ }
+ return APR_SUCCESS;
+}
+
+static int identity_count(void *v, const char *key, const char *val)
+{
+ apr_size_t *count = v;
+
+ *count += strlen(key) * 3 + strlen(val) * 3 + 2;
+ return 1;
+}
+
+static int identity_concat(void *v, const char *key, const char *val)
+{
+ char *slider = v;
+ apr_size_t length = strlen(slider);
+
+ slider += length;
+ if (length) {
+ *slider = '&';
+ slider++;
+ }
+ ap_escape_urlencoded_buffer(slider, key);
+ slider += strlen(slider);
+ *slider = '=';
+ slider++;
+ ap_escape_urlencoded_buffer(slider, val);
+ return 1;
+}
+
+/**
+ * Default identity encoding for the session.
+ *
+ * By default, the name value pairs in the session are URLEncoded, separated
+ * by equals, and then in turn separated by ampersand, in the format of an
+ * html form.
+ *
+ * This was chosen to make it easy for external code to unpack a session,
+ * should there be a need to do so.
+ *
+ * @param r The request pointer.
+ * @param z A pointer to where the session will be written.
+ */
+static apr_status_t session_identity_encode(request_rec * r, session_rec * z)
+{
+ char *buffer = NULL;
+ apr_size_t length = 0;
+
+ if (z->expiry) {
+ char *expiry = apr_psprintf(z->pool, "%" APR_INT64_T_FMT, z->expiry);
+ apr_table_setn(z->entries, SESSION_EXPIRY, expiry);
+ }
+ apr_table_do(identity_count, &length, z->entries, NULL);
+ buffer = apr_pcalloc(r->pool, length + 1);
+ apr_table_do(identity_concat, buffer, z->entries, NULL);
+ z->encoded = buffer;
+ return OK;
+
+}
+
+/**
+ * Default identity decoding for the session.
+ *
+ * By default, the name value pairs in the session are URLEncoded, separated
+ * by equals, and then in turn separated by ampersand, in the format of an
+ * html form.
+ *
+ * This was chosen to make it easy for external code to unpack a session,
+ * should there be a need to do so.
+ *
+ * This function reverses that process, and populates the session table.
+ *
+ * Name / value pairs that are not encoded properly are ignored.
+ *
+ * @param r The request pointer.
+ * @param z A pointer to where the session will be written.
+ */
+static apr_status_t session_identity_decode(request_rec * r, session_rec * z)
+{
+
+ char *last = NULL;
+ char *encoded, *pair;
+ const char *sep = "&";
+
+ /* sanity check - anything to decode? */
+ if (!z->encoded) {
+ return OK;
+ }
+
+ /* decode what we have */
+ encoded = apr_pstrdup(r->pool, z->encoded);
+ pair = apr_strtok(encoded, sep, &last);
+ while (pair && pair[0]) {
+ char *plast = NULL;
+ const char *psep = "=";
+ char *key = apr_strtok(pair, psep, &plast);
+ if (key && *key) {
+ char *val = apr_strtok(NULL, sep, &plast);
+ if (!val || !*val) {
+ apr_table_unset(z->entries, key);
+ }
+ else if (!ap_unescape_urlencoded(key) && !ap_unescape_urlencoded(val)) {
+ if (!strcmp(SESSION_EXPIRY, key)) {
+ z->expiry = (apr_time_t) apr_atoi64(val);
+ }
+ else {
+ apr_table_set(z->entries, key, val);
+ }
+ }
+ }
+ pair = apr_strtok(NULL, sep, &last);
+ }
+ z->encoded = NULL;
+ return OK;
+
+}
+
+/**
+ * Ensure any changes to the session are committed.
+ *
+ * This is done in an output filter so that our options for where to
+ * store the session can include storing the session within a cookie:
+ * As an HTTP header, the cookie must be set before the output is
+ * written, but after the handler is run.
+ *
+ * NOTE: It is possible for internal redirects to cause more than one
+ * request to be present, and each request might have a session
+ * defined. We need to go through each session in turn, and save each
+ * one.
+ *
+ * The same session might appear in more than one request. The first
+ * attempt to save the session will be called
+ */
+static apr_status_t session_output_filter(ap_filter_t * f,
+ apr_bucket_brigade * in)
+{
+
+ /* save all the sessions in all the requests */
+ request_rec *r = f->r->main;
+ if (!r) {
+ r = f->r;
+ }
+ while (r) {
+ session_rec *z = NULL;
+ session_dir_conf *conf = ap_get_module_config(r->per_dir_config,
+ &session_module);
+
+ /* load the session, or create one if necessary */
+ /* when unset or on error, z will be NULL */
+ ap_session_load(r, &z);
+ if (!z || z->written) {
+ r = r->next;
+ continue;
+ }
+
+ /* if a header was specified, insert the new values from the header */
+ if (conf->header_set) {
+ const char *override = apr_table_get(r->err_headers_out, conf->header);
+ if (!override) {
+ override = apr_table_get(r->headers_out, conf->header);
+ }
+ if (override) {
+ apr_table_unset(r->err_headers_out, conf->header);
+ apr_table_unset(r->headers_out, conf->header);
+ z->encoded = override;
+ z->dirty = 1;
+ session_identity_decode(r, z);
+ }
+ }
+
+ /* save away the session, and we're done */
+ /* when unset or on error, we've complained to the log */
+ ap_session_save(r, z);
+
+ r = r->next;
+ }
+
+ /* remove ourselves from the filter chain */
+ ap_remove_output_filter(f);
+
+ /* send the data up the stack */
+ return ap_pass_brigade(f->next, in);
+
+}
+
+/**
+ * Insert the output filter.
+ */
+static void session_insert_output_filter(request_rec * r)
+{
+ ap_add_output_filter("MOD_SESSION_OUT", NULL, r, r->connection);
+}
+
+/**
+ * Fixups hook.
+ *
+ * Load the session within a fixup - this ensures that the session is
+ * properly loaded prior to the handler being called.
+ *
+ * The fixup is also responsible for injecting the session into the CGI
+ * environment, should the admin have configured it so.
+ *
+ * @param r The request
+ */
+static int session_fixups(request_rec * r)
+{
+ session_dir_conf *conf = ap_get_module_config(r->per_dir_config,
+ &session_module);
+
+ session_rec *z = NULL;
+
+ /* if an error occurs or no session has been configured, we ignore
+ * the broken session and allow it to be recreated from scratch on save
+ * if necessary.
+ */
+ ap_session_load(r, &z);
+
+ if (conf->env) {
+ if (z) {
+ session_identity_encode(r, z);
+ if (z->encoded) {
+ apr_table_set(r->subprocess_env, HTTP_SESSION, z->encoded);
+ z->encoded = NULL;
+ }
+ }
+ apr_table_unset(r->headers_in, "Session");
+ }
+
+ return OK;
+
+}
+
+
+static void *create_session_dir_config(apr_pool_t * p, char *dummy)
+{
+ session_dir_conf *new =
+ (session_dir_conf *) apr_pcalloc(p, sizeof(session_dir_conf));
+
+ new->includes = apr_array_make(p, 10, sizeof(const char **));
+ new->excludes = apr_array_make(p, 10, sizeof(const char **));
+
+ return (void *) new;
+}
+
+static void *merge_session_dir_config(apr_pool_t * p, void *basev, void *addv)
+{
+ session_dir_conf *new = (session_dir_conf *) apr_pcalloc(p, sizeof(session_dir_conf));
+ session_dir_conf *add = (session_dir_conf *) addv;
+ session_dir_conf *base = (session_dir_conf *) basev;
+
+ new->enabled = (add->enabled_set == 0) ? base->enabled : add->enabled;
+ new->enabled_set = add->enabled_set || base->enabled_set;
+ new->maxage = (add->maxage_set == 0) ? base->maxage : add->maxage;
+ new->maxage_set = add->maxage_set || base->maxage_set;
+ new->header = (add->header_set == 0) ? base->header : add->header;
+ new->header_set = add->header_set || base->header_set;
+ new->env = (add->env_set == 0) ? base->env : add->env;
+ new->env_set = add->env_set || base->env_set;
+ new->includes = apr_array_append(p, base->includes, add->includes);
+ new->excludes = apr_array_append(p, base->excludes, add->excludes);
+ new->expiry_update_time = (add->expiry_update_set == 0)
+ ? base->expiry_update_time
+ : add->expiry_update_time;
+ new->expiry_update_set = add->expiry_update_set || base->expiry_update_set;
+
+ return new;
+}
+
+
+static const char *
+ set_session_enable(cmd_parms * parms, void *dconf, int flag)
+{
+ session_dir_conf *conf = dconf;
+
+ conf->enabled = flag;
+ conf->enabled_set = 1;
+
+ return NULL;
+}
+
+static const char *
+ set_session_maxage(cmd_parms * parms, void *dconf, const char *arg)
+{
+ session_dir_conf *conf = dconf;
+
+ conf->maxage = atol(arg);
+ conf->maxage_set = 1;
+
+ return NULL;
+}
+
+static const char *
+ set_session_header(cmd_parms * parms, void *dconf, const char *arg)
+{
+ session_dir_conf *conf = dconf;
+
+ conf->header = arg;
+ conf->header_set = 1;
+
+ return NULL;
+}
+
+static const char *
+ set_session_env(cmd_parms * parms, void *dconf, int flag)
+{
+ session_dir_conf *conf = dconf;
+
+ conf->env = flag;
+ conf->env_set = 1;
+
+ return NULL;
+}
+
+static const char *add_session_include(cmd_parms * cmd, void *dconf, const char *f)
+{
+ session_dir_conf *conf = dconf;
+
+ const char **new = apr_array_push(conf->includes);
+ *new = f;
+
+ return NULL;
+}
+
+static const char *add_session_exclude(cmd_parms * cmd, void *dconf, const char *f)
+{
+ session_dir_conf *conf = dconf;
+
+ const char **new = apr_array_push(conf->excludes);
+ *new = f;
+
+ return NULL;
+}
+
+static const char *
+ set_session_expiry_update(cmd_parms * parms, void *dconf, const char *arg)
+{
+ session_dir_conf *conf = dconf;
+
+ conf->expiry_update_time = atoi(arg);
+ if (conf->expiry_update_time < 0) {
+ return "SessionExpiryUpdateInterval must be zero (disable) or a positive value";
+ }
+ conf->expiry_update_time = apr_time_from_sec(conf->expiry_update_time);
+ conf->expiry_update_set = 1;
+
+ return NULL;
+}
+
+
+static const command_rec session_cmds[] =
+{
+ AP_INIT_FLAG("Session", set_session_enable, NULL, RSRC_CONF|OR_AUTHCFG,
+ "on if a session should be maintained for these URLs"),
+ AP_INIT_TAKE1("SessionMaxAge", set_session_maxage, NULL, RSRC_CONF|OR_AUTHCFG,
+ "length of time for which a session should be valid. Zero to disable"),
+ AP_INIT_TAKE1("SessionHeader", set_session_header, NULL, RSRC_CONF|OR_AUTHCFG,
+ "output header, if present, whose contents will be injected into the session."),
+ AP_INIT_FLAG("SessionEnv", set_session_env, NULL, RSRC_CONF|OR_AUTHCFG,
+ "on if a session should be written to the CGI environment. Defaults to off"),
+ AP_INIT_TAKE1("SessionInclude", add_session_include, NULL, RSRC_CONF|OR_AUTHCFG,
+ "URL prefixes to include in the session. Defaults to all URLs"),
+ AP_INIT_TAKE1("SessionExclude", add_session_exclude, NULL, RSRC_CONF|OR_AUTHCFG,
+ "URL prefixes to exclude from the session. Defaults to no URLs"),
+ AP_INIT_TAKE1("SessionExpiryUpdateInterval", set_session_expiry_update, NULL, RSRC_CONF|OR_AUTHCFG,
+ "time interval for which a session's expiry time may change "
+ "without having to be rewritten. Zero to disable"),
+ {NULL}
+};
+
+static void register_hooks(apr_pool_t * p)
+{
+ ap_register_output_filter("MOD_SESSION_OUT", session_output_filter,
+ NULL, AP_FTYPE_CONTENT_SET);
+ ap_hook_insert_filter(session_insert_output_filter, NULL, NULL,
+ APR_HOOK_MIDDLE);
+ ap_hook_insert_error_filter(session_insert_output_filter,
+ NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_fixups(session_fixups, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_session_encode(session_identity_encode, NULL, NULL,
+ APR_HOOK_REALLY_FIRST);
+ ap_hook_session_decode(session_identity_decode, NULL, NULL,
+ APR_HOOK_REALLY_LAST);
+ APR_REGISTER_OPTIONAL_FN(ap_session_get);
+ APR_REGISTER_OPTIONAL_FN(ap_session_set);
+ APR_REGISTER_OPTIONAL_FN(ap_session_load);
+ APR_REGISTER_OPTIONAL_FN(ap_session_save);
+}
+
+AP_DECLARE_MODULE(session) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_session_dir_config, /* dir config creater */
+ merge_session_dir_config, /* dir merger --- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ session_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/session/mod_session.dep b/modules/session/mod_session.dep
new file mode 100644
index 0000000..d773317
--- /dev/null
+++ b/modules/session/mod_session.dep
@@ -0,0 +1,56 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_session.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_session.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_session.h"\
+
diff --git a/modules/session/mod_session.dsp b/modules/session/mod_session.dsp
new file mode 100644
index 0000000..69dca0f
--- /dev/null
+++ b/modules/session/mod_session.dsp
@@ -0,0 +1,115 @@
+# Microsoft Developer Studio Project File - Name="mod_session" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_session - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_session.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_session.mak" CFG="mod_session - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_session - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_session - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_session - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "SESSION_DECLARE_EXPORT" /Fd"Release\mod_session_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_session.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_session.so" /d LONG_NAME="session_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_session.so" /base:@..\..\os\win32\BaseAddr.ref,mod_session.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_session.so" /base:@..\..\os\win32\BaseAddr.ref,mod_session.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_session.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_session - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "SESSION_DECLARE_EXPORT" /Fd"Debug\mod_session_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_session.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_session.so" /d LONG_NAME="session_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_session.so" /base:@..\..\os\win32\BaseAddr.ref,mod_session.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_session.so" /base:@..\..\os\win32\BaseAddr.ref,mod_session.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_session.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_session - Win32 Release"
+# Name "mod_session - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_session.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\mod_session.h
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/session/mod_session.h b/modules/session/mod_session.h
new file mode 100644
index 0000000..bdeb532
--- /dev/null
+++ b/modules/session/mod_session.h
@@ -0,0 +1,189 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MOD_SESSION_H
+#define MOD_SESSION_H
+
+/* Create a set of SESSION_DECLARE(type), SESSION_DECLARE_NONSTD(type) and
+ * SESSION_DECLARE_DATA with appropriate export and import tags for the platform
+ */
+#if !defined(WIN32)
+#define SESSION_DECLARE(type) type
+#define SESSION_DECLARE_NONSTD(type) type
+#define SESSION_DECLARE_DATA
+#elif defined(SESSION_DECLARE_STATIC)
+#define SESSION_DECLARE(type) type __stdcall
+#define SESSION_DECLARE_NONSTD(type) type
+#define SESSION_DECLARE_DATA
+#elif defined(SESSION_DECLARE_EXPORT)
+#define SESSION_DECLARE(type) __declspec(dllexport) type __stdcall
+#define SESSION_DECLARE_NONSTD(type) __declspec(dllexport) type
+#define SESSION_DECLARE_DATA __declspec(dllexport)
+#else
+#define SESSION_DECLARE(type) __declspec(dllimport) type __stdcall
+#define SESSION_DECLARE_NONSTD(type) __declspec(dllimport) type
+#define SESSION_DECLARE_DATA __declspec(dllimport)
+#endif
+
+/**
+ * @file mod_session.h
+ * @brief Session Module for Apache
+ *
+ * @defgroup MOD_SESSION mod_session
+ * @ingroup APACHE_MODS
+ * @{
+ */
+
+#include "apr_hooks.h"
+#include "apr_optional.h"
+#include "apr_tables.h"
+#include "apr_uuid.h"
+#include "apr_pools.h"
+#include "apr_time.h"
+
+#include "httpd.h"
+#include "http_config.h"
+#include "ap_config.h"
+
+#define MOD_SESSION_NOTES_KEY "mod_session_key"
+
+/**
+ * Define the name of a username stored in the session, so that modules interested
+ * in the username can find it in a standard place.
+ */
+#define MOD_SESSION_USER "user"
+
+/**
+ * Define the name of a password stored in the session, so that modules interested
+ * in the password can find it in a standard place.
+ */
+#define MOD_SESSION_PW "pw"
+
+/**
+ * A session structure.
+ *
+ * At the core of the session is a set of name value pairs making up the
+ * session.
+ *
+ * The session might be uniquely identified by an anonymous uuid, or
+ * a remote_user value, or both.
+ */
+typedef struct {
+ apr_pool_t *pool; /* pool to be used for this session */
+ apr_uuid_t *uuid; /* anonymous uuid of this particular session */
+ const char *remote_user; /* user who owns this particular session */
+ apr_table_t *entries; /* key value pairs */
+ const char *encoded; /* the encoded version of the key value pairs */
+ apr_time_t expiry; /* if > 0, the time of expiry of this session */
+ long maxage; /* if > 0, the maxage of the session, from
+ * which expiry is calculated */
+ int dirty; /* dirty flag */
+ int cached; /* true if this session was loaded from a
+ * cache of some kind */
+ int written; /* true if this session has already been
+ * written */
+} session_rec;
+
+/**
+ * Structure to carry the per-dir session config.
+ */
+typedef struct {
+ int enabled; /* whether the session has been enabled for
+ * this directory */
+ int enabled_set;
+ long maxage; /* seconds until session expiry */
+ int maxage_set;
+ const char *header; /* header to inject session */
+ int header_set;
+ int env; /* whether the session has been enabled for
+ * this directory */
+ int env_set;
+ apr_array_header_t *includes; /* URL prefixes to be included. All
+ * URLs included if empty */
+ apr_array_header_t *excludes; /* URL prefixes to be excluded. No
+ * URLs excluded if empty */
+ apr_time_t expiry_update_time; /* seconds the session expiry may change and
+ * not have to be rewritten */
+ int expiry_update_set;
+} session_dir_conf;
+
+/**
+ * Hook to load the session.
+ *
+ * If the session doesn't exist, a blank one will be created.
+ *
+ * @param r The request
+ * @param z A pointer to where the session will be written.
+ */
+APR_DECLARE_EXTERNAL_HOOK(ap, SESSION, apr_status_t, session_load,
+ (request_rec * r, session_rec ** z))
+
+/**
+ * Hook to save the session.
+ *
+ * In most implementations the session is only saved if the dirty flag is
+ * true. This prevents the session being saved unnecessarily.
+ *
+ * @param r The request
+ * @param z A pointer to where the session will be written.
+ */
+APR_DECLARE_EXTERNAL_HOOK(ap, SESSION, apr_status_t, session_save,
+ (request_rec * r, session_rec * z))
+
+/**
+ * Hook to encode the session.
+ *
+ * In the default implementation, the key value pairs are encoded using
+ * key value pairs separated by equals, in turn separated by ampersand,
+ * like a web form.
+ *
+ * @param r The request
+ * @param z A pointer to where the session will be written.
+ */
+APR_DECLARE_EXTERNAL_HOOK(ap, SESSION, apr_status_t, session_encode,
+ (request_rec * r, session_rec * z))
+
+/**
+ * Hook to decode the session.
+ *
+ * In the default implementation, the key value pairs are encoded using
+ * key value pairs separated by equals, in turn separated by ampersand,
+ * like a web form.
+ *
+ * @param r The request
+ * @param z A pointer to where the session will be written.
+ */
+APR_DECLARE_EXTERNAL_HOOK(ap, SESSION, apr_status_t, session_decode,
+ (request_rec * r, session_rec * z))
+
+APR_DECLARE_OPTIONAL_FN(
+ apr_status_t,
+ ap_session_get,
+ (request_rec * r, session_rec * z, const char *key, const char **value));
+APR_DECLARE_OPTIONAL_FN(apr_status_t, ap_session_set,
+ (request_rec * r, session_rec * z, const char *key, const char *value));
+APR_DECLARE_OPTIONAL_FN(apr_status_t, ap_session_load,
+ (request_rec *, session_rec **));
+APR_DECLARE_OPTIONAL_FN(apr_status_t, ap_session_save,
+ (request_rec *, session_rec *));
+
+/**
+ * The name of the module.
+ */
+extern module AP_MODULE_DECLARE_DATA session_module;
+
+#endif /* MOD_SESSION_H */
+/** @} */
diff --git a/modules/session/mod_session.mak b/modules/session/mod_session.mak
new file mode 100644
index 0000000..ce92fa6
--- /dev/null
+++ b/modules/session/mod_session.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_session.dsp
+!IF "$(CFG)" == ""
+CFG=mod_session - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_session - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_session - Win32 Release" && "$(CFG)" != "mod_session - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_session.mak" CFG="mod_session - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_session - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_session - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_session - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_session.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_session.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_session.obj"
+ -@erase "$(INTDIR)\mod_session.res"
+ -@erase "$(INTDIR)\mod_session_src.idb"
+ -@erase "$(INTDIR)\mod_session_src.pdb"
+ -@erase "$(OUTDIR)\mod_session.exp"
+ -@erase "$(OUTDIR)\mod_session.lib"
+ -@erase "$(OUTDIR)\mod_session.pdb"
+ -@erase "$(OUTDIR)\mod_session.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "SESSION_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_session_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_session.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_session.so" /d LONG_NAME="session_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_session.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_session.pdb" /debug /out:"$(OUTDIR)\mod_session.so" /implib:"$(OUTDIR)\mod_session.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_session.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_session.obj" \
+ "$(INTDIR)\mod_session.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_session.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_session.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_session.so"
+ if exist .\Release\mod_session.so.manifest mt.exe -manifest .\Release\mod_session.so.manifest -outputresource:.\Release\mod_session.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_session - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_session.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_session.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_session.obj"
+ -@erase "$(INTDIR)\mod_session.res"
+ -@erase "$(INTDIR)\mod_session_src.idb"
+ -@erase "$(INTDIR)\mod_session_src.pdb"
+ -@erase "$(OUTDIR)\mod_session.exp"
+ -@erase "$(OUTDIR)\mod_session.lib"
+ -@erase "$(OUTDIR)\mod_session.pdb"
+ -@erase "$(OUTDIR)\mod_session.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "SESSION_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_session_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_session.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_session.so" /d LONG_NAME="session_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_session.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_session.pdb" /debug /out:"$(OUTDIR)\mod_session.so" /implib:"$(OUTDIR)\mod_session.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_session.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_session.obj" \
+ "$(INTDIR)\mod_session.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_session.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_session.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_session.so"
+ if exist .\Debug\mod_session.so.manifest mt.exe -manifest .\Debug\mod_session.so.manifest -outputresource:.\Debug\mod_session.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_session.dep")
+!INCLUDE "mod_session.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_session.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_session - Win32 Release" || "$(CFG)" == "mod_session - Win32 Debug"
+
+!IF "$(CFG)" == "mod_session - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\session"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\session"
+
+!ELSEIF "$(CFG)" == "mod_session - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\session"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\session"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_session - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\session"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\session"
+
+!ELSEIF "$(CFG)" == "mod_session - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\session"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\session"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_session - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\session"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\session"
+
+!ELSEIF "$(CFG)" == "mod_session - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\session"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\session"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_session - Win32 Release"
+
+
+"$(INTDIR)\mod_session.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_session.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_session.so" /d LONG_NAME="session_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_session - Win32 Debug"
+
+
+"$(INTDIR)\mod_session.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_session.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_session.so" /d LONG_NAME="session_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_session.c
+
+"$(INTDIR)\mod_session.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/session/mod_session_cookie.c b/modules/session/mod_session_cookie.c
new file mode 100644
index 0000000..36168b7
--- /dev/null
+++ b/modules/session/mod_session_cookie.c
@@ -0,0 +1,284 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mod_session.h"
+#include "apr_lib.h"
+#include "apr_strings.h"
+#include "http_log.h"
+#include "util_cookies.h"
+
+#define MOD_SESSION_COOKIE "mod_session_cookie"
+
+module AP_MODULE_DECLARE_DATA session_cookie_module;
+
+/**
+ * Structure to carry the per-dir session config.
+ */
+typedef struct {
+ const char *name;
+ int name_set;
+ const char *name_attrs;
+ const char *name2;
+ int name2_set;
+ const char *name2_attrs;
+ int remove;
+ int remove_set;
+} session_cookie_dir_conf;
+
+/**
+ * Set the cookie and embed the session within it.
+ *
+ * This function adds an RFC2109 compliant Set-Cookie header for
+ * the cookie specified in SessionCookieName, and an RFC2965 compliant
+ * Set-Cookie2 header for the cookie specified in SessionCookieName2.
+ *
+ * If specified, the optional cookie attributes will be added to
+ * each cookie. If defaults are not specified, DEFAULT_ATTRS
+ * will be used.
+ *
+ * On success, this method will return APR_SUCCESS.
+ *
+ * @param r The request pointer.
+ * @param z A pointer to where the session will be written.
+ */
+static apr_status_t session_cookie_save(request_rec * r, session_rec * z)
+{
+
+ session_cookie_dir_conf *conf = ap_get_module_config(r->per_dir_config,
+ &session_cookie_module);
+
+ /* create RFC2109 compliant cookie */
+ if (conf->name_set) {
+ if (z->encoded && z->encoded[0]) {
+ ap_cookie_write(r, conf->name, z->encoded, conf->name_attrs,
+ z->maxage, r->err_headers_out,
+ NULL);
+ }
+ else {
+ ap_cookie_remove(r, conf->name, conf->name_attrs, r->headers_out,
+ r->err_headers_out, NULL);
+ }
+ }
+
+ /* create RFC2965 compliant cookie */
+ if (conf->name2_set) {
+ if (z->encoded && z->encoded[0]) {
+ ap_cookie_write2(r, conf->name2, z->encoded, conf->name2_attrs,
+ z->maxage, r->err_headers_out,
+ NULL);
+ }
+ else {
+ ap_cookie_remove2(r, conf->name2, conf->name2_attrs,
+ r->headers_out, r->err_headers_out, NULL);
+ }
+ }
+
+ if (conf->name_set || conf->name2_set) {
+ return OK;
+ }
+ return DECLINED;
+
+}
+
+/**
+ * Isolate the cookie with the name "name", and if present, extract
+ * the payload from the cookie.
+ *
+ * If the cookie is found, the cookie and any other cookies with the
+ * same name are removed from the cookies passed in the request, so
+ * that credentials are not leaked to a backend server or process.
+ *
+ * A missing or malformed cookie will cause this function to return
+ * APR_EGENERAL.
+ *
+ * On success, this returns APR_SUCCESS.
+ */
+static apr_status_t session_cookie_load(request_rec * r, session_rec ** z)
+{
+
+ session_cookie_dir_conf *conf = ap_get_module_config(r->per_dir_config,
+ &session_cookie_module);
+
+ session_rec *zz = NULL;
+ const char *val = NULL;
+ const char *note = NULL;
+ const char *name = NULL;
+ request_rec *m = r;
+
+ /* find the first redirect */
+ while (m->prev) {
+ m = m->prev;
+ }
+ /* find the main request */
+ while (m->main) {
+ m = m->main;
+ }
+
+ /* is our session in a cookie? */
+ if (conf->name2_set) {
+ name = conf->name2;
+ }
+ else if (conf->name_set) {
+ name = conf->name;
+ }
+ else {
+ return DECLINED;
+ }
+
+ /* first look in the notes */
+ note = apr_pstrcat(m->pool, MOD_SESSION_COOKIE, name, NULL);
+ zz = (session_rec *)apr_table_get(m->notes, note);
+ if (zz) {
+ *z = zz;
+ return OK;
+ }
+
+ /* otherwise, try parse the cookie */
+ ap_cookie_read(r, name, &val, conf->remove);
+
+ /* create a new session and return it */
+ zz = (session_rec *) apr_pcalloc(m->pool, sizeof(session_rec));
+ zz->pool = m->pool;
+ zz->entries = apr_table_make(m->pool, 10);
+ zz->encoded = val;
+ *z = zz;
+
+ /* put the session in the notes so we don't have to parse it again */
+ apr_table_setn(m->notes, note, (char *)zz);
+
+ /* don't cache auth protected pages */
+ apr_table_addn(r->headers_out, "Cache-Control", "no-cache, private");
+
+ return OK;
+
+}
+
+
+
+static void *create_session_cookie_dir_config(apr_pool_t * p, char *dummy)
+{
+ session_cookie_dir_conf *new =
+ (session_cookie_dir_conf *) apr_pcalloc(p, sizeof(session_cookie_dir_conf));
+
+ return (void *) new;
+}
+
+static void *merge_session_cookie_dir_config(apr_pool_t * p, void *basev,
+ void *addv)
+{
+ session_cookie_dir_conf *new = (session_cookie_dir_conf *)
+ apr_pcalloc(p, sizeof(session_cookie_dir_conf));
+ session_cookie_dir_conf *add = (session_cookie_dir_conf *) addv;
+ session_cookie_dir_conf *base = (session_cookie_dir_conf *) basev;
+
+ new->name = (add->name_set == 0) ? base->name : add->name;
+ new->name_attrs = (add->name_set == 0) ? base->name_attrs : add->name_attrs;
+ new->name_set = add->name_set || base->name_set;
+ new->name2 = (add->name2_set == 0) ? base->name2 : add->name2;
+ new->name2_attrs = (add->name2_set == 0) ? base->name2_attrs : add->name2_attrs;
+ new->name2_set = add->name2_set || base->name2_set;
+ new->remove = (add->remove_set == 0) ? base->remove : add->remove;
+ new->remove_set = add->remove_set || base->remove_set;
+
+ return new;
+}
+
+/**
+ * Sanity check a given string that it exists, is not empty,
+ * and does not contain special characters.
+ */
+static const char *check_string(cmd_parms * cmd, const char *string)
+{
+ if (!string || !*string || ap_strchr_c(string, '=') || ap_strchr_c(string, '&')) {
+ return apr_pstrcat(cmd->pool, cmd->directive->directive,
+ " cannot be empty, or contain '=' or '&'.",
+ NULL);
+ }
+ return NULL;
+}
+
+static const char *set_cookie_name(cmd_parms * cmd, void *config,
+ const char *args)
+{
+ char *last;
+ char *line = apr_pstrdup(cmd->pool, args);
+ session_cookie_dir_conf *conf = (session_cookie_dir_conf *) config;
+ char *cookie = apr_strtok(line, " \t", &last);
+ conf->name = cookie;
+ conf->name_set = 1;
+ while (apr_isspace(*last)) {
+ last++;
+ }
+ conf->name_attrs = last;
+ return check_string(cmd, cookie);
+}
+
+static const char *set_cookie_name2(cmd_parms * cmd, void *config,
+ const char *args)
+{
+ char *last;
+ char *line = apr_pstrdup(cmd->pool, args);
+ session_cookie_dir_conf *conf = (session_cookie_dir_conf *) config;
+ char *cookie = apr_strtok(line, " \t", &last);
+ conf->name2 = cookie;
+ conf->name2_set = 1;
+ while (apr_isspace(*last)) {
+ last++;
+ }
+ conf->name2_attrs = last;
+ return check_string(cmd, cookie);
+}
+
+static const char *
+ set_remove(cmd_parms * parms, void *dconf, int flag)
+{
+ session_cookie_dir_conf *conf = dconf;
+
+ conf->remove = flag;
+ conf->remove_set = 1;
+
+ return NULL;
+}
+
+static const command_rec session_cookie_cmds[] =
+{
+ AP_INIT_RAW_ARGS("SessionCookieName", set_cookie_name, NULL, RSRC_CONF|OR_AUTHCFG,
+ "The name of the RFC2109 cookie carrying the session"),
+ AP_INIT_RAW_ARGS("SessionCookieName2", set_cookie_name2, NULL, RSRC_CONF|OR_AUTHCFG,
+ "The name of the RFC2965 cookie carrying the session"),
+ AP_INIT_FLAG("SessionCookieRemove", set_remove, NULL, RSRC_CONF|OR_AUTHCFG,
+ "Set to 'On' to remove the session cookie from the headers "
+ "and hide the cookie from a backend server or process"),
+ {NULL}
+};
+
+static void register_hooks(apr_pool_t * p)
+{
+ ap_hook_session_load(session_cookie_load, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_session_save(session_cookie_save, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(session_cookie) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_session_cookie_dir_config, /* dir config creater */
+ merge_session_cookie_dir_config, /* dir merger --- default is to
+ * override */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ session_cookie_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/session/mod_session_cookie.dep b/modules/session/mod_session_cookie.dep
new file mode 100644
index 0000000..23d8727
--- /dev/null
+++ b/modules/session/mod_session_cookie.dep
@@ -0,0 +1,49 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_session_cookie.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_session_cookie.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_cookies.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_session.h"\
+
diff --git a/modules/session/mod_session_cookie.dsp b/modules/session/mod_session_cookie.dsp
new file mode 100644
index 0000000..3a0c86a
--- /dev/null
+++ b/modules/session/mod_session_cookie.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_session_cookie" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_session_cookie - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_session_cookie.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_session_cookie.mak" CFG="mod_session_cookie - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_session_cookie - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_session_cookie - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_session_cookie - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_session_cookie_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_session_cookie.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_session_cookie.so" /d LONG_NAME="session_cookie_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_session_cookie.so" /base:@..\..\os\win32\BaseAddr.ref,mod_session_cookie.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_session_cookie.so" /base:@..\..\os\win32\BaseAddr.ref,mod_session_cookie.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_session_cookie.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_session_cookie - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_session_cookie_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_session_cookie.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_session_cookie.so" /d LONG_NAME="session_cookie_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_session_cookie.so" /base:@..\..\os\win32\BaseAddr.ref,mod_session_cookie.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_session_cookie.so" /base:@..\..\os\win32\BaseAddr.ref,mod_session_cookie.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_session_cookie.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_session_cookie - Win32 Release"
+# Name "mod_session_cookie - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_session_cookie.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/session/mod_session_cookie.mak b/modules/session/mod_session_cookie.mak
new file mode 100644
index 0000000..014f1e3
--- /dev/null
+++ b/modules/session/mod_session_cookie.mak
@@ -0,0 +1,381 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_session_cookie.dsp
+!IF "$(CFG)" == ""
+CFG=mod_session_cookie - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_session_cookie - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_session_cookie - Win32 Release" && "$(CFG)" != "mod_session_cookie - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_session_cookie.mak" CFG="mod_session_cookie - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_session_cookie - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_session_cookie - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_session_cookie - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_session_cookie.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_session - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_session_cookie.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_session - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_session_cookie.obj"
+ -@erase "$(INTDIR)\mod_session_cookie.res"
+ -@erase "$(INTDIR)\mod_session_cookie_src.idb"
+ -@erase "$(INTDIR)\mod_session_cookie_src.pdb"
+ -@erase "$(OUTDIR)\mod_session_cookie.exp"
+ -@erase "$(OUTDIR)\mod_session_cookie.lib"
+ -@erase "$(OUTDIR)\mod_session_cookie.pdb"
+ -@erase "$(OUTDIR)\mod_session_cookie.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_session_cookie_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_session_cookie.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_session_cookie.so" /d LONG_NAME="session_cookie_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_session_cookie.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_session_cookie.pdb" /debug /out:"$(OUTDIR)\mod_session_cookie.so" /implib:"$(OUTDIR)\mod_session_cookie.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_session_cookie.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_session_cookie.obj" \
+ "$(INTDIR)\mod_session_cookie.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib" \
+ "$(OUTDIR)\mod_session.lib"
+
+"$(OUTDIR)\mod_session_cookie.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_session_cookie.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_session_cookie.so"
+ if exist .\Release\mod_session_cookie.so.manifest mt.exe -manifest .\Release\mod_session_cookie.so.manifest -outputresource:.\Release\mod_session_cookie.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_session_cookie - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_session_cookie.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_session - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_session_cookie.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_session - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_session_cookie.obj"
+ -@erase "$(INTDIR)\mod_session_cookie.res"
+ -@erase "$(INTDIR)\mod_session_cookie_src.idb"
+ -@erase "$(INTDIR)\mod_session_cookie_src.pdb"
+ -@erase "$(OUTDIR)\mod_session_cookie.exp"
+ -@erase "$(OUTDIR)\mod_session_cookie.lib"
+ -@erase "$(OUTDIR)\mod_session_cookie.pdb"
+ -@erase "$(OUTDIR)\mod_session_cookie.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_session_cookie_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_session_cookie.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_session_cookie.so" /d LONG_NAME="session_cookie_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_session_cookie.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_session_cookie.pdb" /debug /out:"$(OUTDIR)\mod_session_cookie.so" /implib:"$(OUTDIR)\mod_session_cookie.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_session_cookie.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_session_cookie.obj" \
+ "$(INTDIR)\mod_session_cookie.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib" \
+ "$(OUTDIR)\mod_session.lib"
+
+"$(OUTDIR)\mod_session_cookie.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_session_cookie.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_session_cookie.so"
+ if exist .\Debug\mod_session_cookie.so.manifest mt.exe -manifest .\Debug\mod_session_cookie.so.manifest -outputresource:.\Debug\mod_session_cookie.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_session_cookie.dep")
+!INCLUDE "mod_session_cookie.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_session_cookie.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_session_cookie - Win32 Release" || "$(CFG)" == "mod_session_cookie - Win32 Debug"
+
+!IF "$(CFG)" == "mod_session_cookie - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\session"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\session"
+
+!ELSEIF "$(CFG)" == "mod_session_cookie - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\session"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\session"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_session_cookie - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\session"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\session"
+
+!ELSEIF "$(CFG)" == "mod_session_cookie - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\session"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\session"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_session_cookie - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\session"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\session"
+
+!ELSEIF "$(CFG)" == "mod_session_cookie - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\session"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\session"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_session_cookie - Win32 Release"
+
+"mod_session - Win32 Release" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_session.mak" CFG="mod_session - Win32 Release"
+ cd "."
+
+"mod_session - Win32 ReleaseCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_session.mak" CFG="mod_session - Win32 Release" RECURSE=1 CLEAN
+ cd "."
+
+!ELSEIF "$(CFG)" == "mod_session_cookie - Win32 Debug"
+
+"mod_session - Win32 Debug" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_session.mak" CFG="mod_session - Win32 Debug"
+ cd "."
+
+"mod_session - Win32 DebugCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_session.mak" CFG="mod_session - Win32 Debug" RECURSE=1 CLEAN
+ cd "."
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_session_cookie - Win32 Release"
+
+
+"$(INTDIR)\mod_session_cookie.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_session_cookie.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_session_cookie.so" /d LONG_NAME="session_cookie_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_session_cookie - Win32 Debug"
+
+
+"$(INTDIR)\mod_session_cookie.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_session_cookie.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_session_cookie.so" /d LONG_NAME="session_cookie_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_session_cookie.c
+
+"$(INTDIR)\mod_session_cookie.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/session/mod_session_crypto.c b/modules/session/mod_session_crypto.c
new file mode 100644
index 0000000..fe39f2c
--- /dev/null
+++ b/modules/session/mod_session_crypto.c
@@ -0,0 +1,807 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mod_session.h"
+#include "apu_version.h"
+#include "apr_base64.h" /* for apr_base64_decode et al */
+#include "apr_lib.h"
+#include "apr_md5.h"
+#include "apr_strings.h"
+#include "http_log.h"
+#include "http_core.h"
+
+#if APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION < 4
+
+#error session_crypto_module requires APU v1.4.0 or later
+
+#elif APU_HAVE_CRYPTO == 0
+
+#error Crypto support must be enabled in APR
+
+#else
+
+#include "apr_crypto.h" /* for apr_*_crypt et al */
+
+#define CRYPTO_KEY "session_crypto_context"
+
+module AP_MODULE_DECLARE_DATA session_crypto_module;
+
+/**
+ * Structure to carry the per-dir session config.
+ */
+typedef struct {
+ apr_array_header_t *passphrases;
+ int passphrases_set;
+ const char *cipher;
+ int cipher_set;
+} session_crypto_dir_conf;
+
+/**
+ * Structure to carry the server wide session config.
+ */
+typedef struct {
+ const char *library;
+ const char *params;
+ int library_set;
+} session_crypto_conf;
+
+/* Wrappers around apr_siphash24() and apr_crypto_equals(),
+ * available in APU-1.6/APR-2.0 only.
+ */
+#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 6)
+
+#include "apr_siphash.h"
+
+#define AP_SIPHASH_DSIZE APR_SIPHASH_DSIZE
+#define AP_SIPHASH_KSIZE APR_SIPHASH_KSIZE
+#define ap_siphash24_auth apr_siphash24_auth
+
+#define ap_crypto_equals apr_crypto_equals
+
+#else
+
+#define AP_SIPHASH_DSIZE 8
+#define AP_SIPHASH_KSIZE 16
+
+#define ROTL64(x, n) (((x) << (n)) | ((x) >> (64 - (n))))
+
+#define U8TO64_LE(p) \
+ (((apr_uint64_t)((p)[0]) ) | \
+ ((apr_uint64_t)((p)[1]) << 8) | \
+ ((apr_uint64_t)((p)[2]) << 16) | \
+ ((apr_uint64_t)((p)[3]) << 24) | \
+ ((apr_uint64_t)((p)[4]) << 32) | \
+ ((apr_uint64_t)((p)[5]) << 40) | \
+ ((apr_uint64_t)((p)[6]) << 48) | \
+ ((apr_uint64_t)((p)[7]) << 56))
+
+#define U64TO8_LE(p, v) \
+do { \
+ (p)[0] = (unsigned char)((v) ); \
+ (p)[1] = (unsigned char)((v) >> 8); \
+ (p)[2] = (unsigned char)((v) >> 16); \
+ (p)[3] = (unsigned char)((v) >> 24); \
+ (p)[4] = (unsigned char)((v) >> 32); \
+ (p)[5] = (unsigned char)((v) >> 40); \
+ (p)[6] = (unsigned char)((v) >> 48); \
+ (p)[7] = (unsigned char)((v) >> 56); \
+} while (0)
+
+#define SIPROUND() \
+do { \
+ v0 += v1; v1=ROTL64(v1,13); v1 ^= v0; v0=ROTL64(v0,32); \
+ v2 += v3; v3=ROTL64(v3,16); v3 ^= v2; \
+ v0 += v3; v3=ROTL64(v3,21); v3 ^= v0; \
+ v2 += v1; v1=ROTL64(v1,17); v1 ^= v2; v2=ROTL64(v2,32); \
+} while(0)
+
+static apr_uint64_t ap_siphash24(const void *src, apr_size_t len,
+ const unsigned char key[AP_SIPHASH_KSIZE])
+{
+ const unsigned char *ptr, *end;
+ apr_uint64_t v0, v1, v2, v3, m;
+ apr_uint64_t k0, k1;
+ unsigned int rem;
+
+ k0 = U8TO64_LE(key + 0);
+ k1 = U8TO64_LE(key + 8);
+ v3 = k1 ^ (apr_uint64_t)0x7465646279746573ULL;
+ v2 = k0 ^ (apr_uint64_t)0x6c7967656e657261ULL;
+ v1 = k1 ^ (apr_uint64_t)0x646f72616e646f6dULL;
+ v0 = k0 ^ (apr_uint64_t)0x736f6d6570736575ULL;
+
+ rem = (unsigned int)(len & 0x7);
+ for (ptr = src, end = ptr + len - rem; ptr < end; ptr += 8) {
+ m = U8TO64_LE(ptr);
+ v3 ^= m;
+ SIPROUND();
+ SIPROUND();
+ v0 ^= m;
+ }
+ m = (apr_uint64_t)(len & 0xff) << 56;
+ switch (rem) {
+ case 7: m |= (apr_uint64_t)ptr[6] << 48;
+ case 6: m |= (apr_uint64_t)ptr[5] << 40;
+ case 5: m |= (apr_uint64_t)ptr[4] << 32;
+ case 4: m |= (apr_uint64_t)ptr[3] << 24;
+ case 3: m |= (apr_uint64_t)ptr[2] << 16;
+ case 2: m |= (apr_uint64_t)ptr[1] << 8;
+ case 1: m |= (apr_uint64_t)ptr[0];
+ case 0: break;
+ }
+ v3 ^= m;
+ SIPROUND();
+ SIPROUND();
+ v0 ^= m;
+
+ v2 ^= 0xff;
+ SIPROUND();
+ SIPROUND();
+ SIPROUND();
+ SIPROUND();
+
+ return v0 ^ v1 ^ v2 ^ v3;
+}
+
+static void ap_siphash24_auth(unsigned char out[AP_SIPHASH_DSIZE],
+ const void *src, apr_size_t len,
+ const unsigned char key[AP_SIPHASH_KSIZE])
+{
+ apr_uint64_t h;
+ h = ap_siphash24(src, len, key);
+ U64TO8_LE(out, h);
+}
+
+static int ap_crypto_equals(const void *buf1, const void *buf2,
+ apr_size_t size)
+{
+ const unsigned char *p1 = buf1;
+ const unsigned char *p2 = buf2;
+ unsigned char diff = 0;
+ apr_size_t i;
+
+ for (i = 0; i < size; ++i) {
+ diff |= p1[i] ^ p2[i];
+ }
+
+ return 1 & ((diff - 1) >> 8);
+}
+
+#endif
+
+static void compute_auth(const void *src, apr_size_t len,
+ const char *passphrase, apr_size_t passlen,
+ unsigned char auth[AP_SIPHASH_DSIZE])
+{
+ unsigned char key[APR_MD5_DIGESTSIZE];
+
+ /* XXX: if we had a way to get the raw bytes from an apr_crypto_key_t
+ * we could use them directly (not available in APR-1.5.x).
+ * MD5 is 128bit too, so use it to get a suitable siphash key
+ * from the passphrase.
+ */
+ apr_md5(key, passphrase, passlen);
+
+ ap_siphash24_auth(auth, src, len, key);
+}
+
+/**
+ * Initialise the encryption as per the current config.
+ *
+ * Returns APR_SUCCESS if successful.
+ */
+static apr_status_t crypt_init(request_rec *r,
+ const apr_crypto_t *f, apr_crypto_block_key_type_e **cipher,
+ session_crypto_dir_conf * dconf)
+{
+ apr_status_t res;
+ apr_hash_t *ciphers;
+
+ res = apr_crypto_get_block_key_types(&ciphers, f);
+ if (APR_SUCCESS != res) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, APLOGNO(01823)
+ "no ciphers returned by APR. "
+ "session encryption not possible");
+ return res;
+ }
+
+ *cipher = apr_hash_get(ciphers, dconf->cipher, APR_HASH_KEY_STRING);
+ if (!(*cipher)) {
+ apr_hash_index_t *hi;
+ const void *key;
+ apr_ssize_t klen;
+ int sum = 0;
+ int offset = 0;
+ char *options = NULL;
+
+ for (hi = apr_hash_first(r->pool, ciphers); hi; hi = apr_hash_next(hi)) {
+ apr_hash_this(hi, NULL, &klen, NULL);
+ sum += klen + 2;
+ }
+ for (hi = apr_hash_first(r->pool, ciphers); hi; hi = apr_hash_next(hi)) {
+ apr_hash_this(hi, &key, &klen, NULL);
+ if (!options) {
+ options = apr_palloc(r->pool, sum + 1);
+ }
+ else {
+ options[offset++] = ',';
+ options[offset++] = ' ';
+ }
+ strncpy(options + offset, key, klen);
+ offset += klen;
+ }
+ options[offset] = 0;
+
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, APLOGNO(01824)
+ "cipher '%s' not recognised by crypto driver. "
+ "session encryption not possible, options: %s", dconf->cipher, options);
+
+ return APR_EGENERAL;
+ }
+
+ return APR_SUCCESS;
+}
+
+/**
+ * Encrypt the string given as per the current config.
+ *
+ * Returns APR_SUCCESS if successful.
+ */
+static apr_status_t encrypt_string(request_rec * r, const apr_crypto_t *f,
+ session_crypto_dir_conf *dconf, const char *in, char **out)
+{
+ apr_status_t res;
+ apr_crypto_key_t *key = NULL;
+ apr_size_t ivSize = 0;
+ apr_crypto_block_t *block = NULL;
+ unsigned char *encrypt = NULL;
+ unsigned char *combined = NULL;
+ apr_size_t encryptlen, tlen, combinedlen;
+ char *base64;
+ apr_size_t blockSize = 0;
+ const unsigned char *iv = NULL;
+ apr_uuid_t salt;
+ apr_crypto_block_key_type_e *cipher;
+ const char *passphrase;
+ apr_size_t passlen;
+
+ /* use a uuid as a salt value, and prepend it to our result */
+ apr_uuid_get(&salt);
+ res = crypt_init(r, f, &cipher, dconf);
+ if (res != APR_SUCCESS) {
+ return res;
+ }
+
+ /* encrypt using the first passphrase in the list */
+ passphrase = APR_ARRAY_IDX(dconf->passphrases, 0, const char *);
+ passlen = strlen(passphrase);
+ res = apr_crypto_passphrase(&key, &ivSize, passphrase, passlen,
+ (unsigned char *) (&salt), sizeof(apr_uuid_t),
+ *cipher, APR_MODE_CBC, 1, 4096, f, r->pool);
+ if (APR_STATUS_IS_ENOKEY(res)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, APLOGNO(01825)
+ "failure generating key from passphrase");
+ }
+ if (APR_STATUS_IS_EPADDING(res)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, APLOGNO(01826)
+ "padding is not supported for cipher");
+ }
+ if (APR_STATUS_IS_EKEYTYPE(res)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, APLOGNO(01827)
+ "the key type is not known");
+ }
+ if (APR_SUCCESS != res) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, APLOGNO(01828)
+ "encryption could not be configured.");
+ return res;
+ }
+
+ res = apr_crypto_block_encrypt_init(&block, &iv, key, &blockSize, r->pool);
+ if (APR_SUCCESS != res) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, APLOGNO(01829)
+ "apr_crypto_block_encrypt_init failed");
+ return res;
+ }
+
+ /* encrypt the given string */
+ res = apr_crypto_block_encrypt(&encrypt, &encryptlen,
+ (const unsigned char *)in, strlen(in),
+ block);
+ if (APR_SUCCESS != res) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, APLOGNO(01830)
+ "apr_crypto_block_encrypt failed");
+ return res;
+ }
+ res = apr_crypto_block_encrypt_finish(encrypt + encryptlen, &tlen, block);
+ if (APR_SUCCESS != res) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, APLOGNO(01831)
+ "apr_crypto_block_encrypt_finish failed");
+ return res;
+ }
+ encryptlen += tlen;
+
+ /* prepend the salt and the iv to the result (keep room for the MAC) */
+ combinedlen = AP_SIPHASH_DSIZE + sizeof(apr_uuid_t) + ivSize + encryptlen;
+ combined = apr_palloc(r->pool, combinedlen);
+ memcpy(combined + AP_SIPHASH_DSIZE, &salt, sizeof(apr_uuid_t));
+ memcpy(combined + AP_SIPHASH_DSIZE + sizeof(apr_uuid_t), iv, ivSize);
+ memcpy(combined + AP_SIPHASH_DSIZE + sizeof(apr_uuid_t) + ivSize,
+ encrypt, encryptlen);
+ /* authenticate the whole salt+IV+ciphertext with a leading MAC */
+ compute_auth(combined + AP_SIPHASH_DSIZE, combinedlen - AP_SIPHASH_DSIZE,
+ passphrase, passlen, combined);
+
+ /* base64 encode the result (APR handles the trailing '\0') */
+ base64 = apr_palloc(r->pool, apr_base64_encode_len(combinedlen));
+ apr_base64_encode(base64, (const char *) combined, combinedlen);
+ *out = base64;
+
+ return res;
+
+}
+
+/**
+ * Decrypt the string given as per the current config.
+ *
+ * Returns APR_SUCCESS if successful.
+ */
+static apr_status_t decrypt_string(request_rec * r, const apr_crypto_t *f,
+ session_crypto_dir_conf *dconf, const char *in, char **out)
+{
+ apr_status_t res;
+ apr_crypto_key_t *key = NULL;
+ apr_size_t ivSize = 0;
+ apr_crypto_block_t *block = NULL;
+ unsigned char *decrypted = NULL;
+ apr_size_t decryptedlen, tlen;
+ apr_size_t decodedlen;
+ char *decoded;
+ apr_size_t blockSize = 0;
+ apr_crypto_block_key_type_e *cipher;
+ unsigned char auth[AP_SIPHASH_DSIZE];
+ int i = 0;
+
+ /* strip base64 from the string */
+ decoded = apr_palloc(r->pool, apr_base64_decode_len(in));
+ decodedlen = apr_base64_decode(decoded, in);
+ decoded[decodedlen] = '\0';
+
+ /* sanity check - decoded too short? */
+ if (decodedlen < (AP_SIPHASH_DSIZE + sizeof(apr_uuid_t))) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(10005)
+ "too short to decrypt, aborting");
+ return APR_ECRYPT;
+ }
+
+ res = crypt_init(r, f, &cipher, dconf);
+ if (res != APR_SUCCESS) {
+ return res;
+ }
+
+ res = APR_ECRYPT; /* in case we exhaust all passphrases */
+
+ /* try each passphrase in turn */
+ for (; i < dconf->passphrases->nelts; i++) {
+ const char *passphrase = APR_ARRAY_IDX(dconf->passphrases, i, char *);
+ apr_size_t passlen = strlen(passphrase);
+ apr_size_t len = decodedlen - AP_SIPHASH_DSIZE;
+ unsigned char *slider = (unsigned char *)decoded + AP_SIPHASH_DSIZE;
+
+ /* Verify authentication of the whole salt+IV+ciphertext by computing
+ * the MAC and comparing it (timing safe) with the one in the payload.
+ */
+ compute_auth(slider, len, passphrase, passlen, auth);
+ if (!ap_crypto_equals(auth, decoded, AP_SIPHASH_DSIZE)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, res, r, APLOGNO(10006)
+ "auth does not match, skipping");
+ continue;
+ }
+
+ /* encrypt using the first passphrase in the list */
+ res = apr_crypto_passphrase(&key, &ivSize, passphrase, passlen,
+ slider, sizeof(apr_uuid_t),
+ *cipher, APR_MODE_CBC, 1, 4096,
+ f, r->pool);
+ if (APR_STATUS_IS_ENOKEY(res)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, res, r, APLOGNO(01832)
+ "failure generating key from passphrase");
+ continue;
+ }
+ else if (APR_STATUS_IS_EPADDING(res)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, res, r, APLOGNO(01833)
+ "padding is not supported for cipher");
+ continue;
+ }
+ else if (APR_STATUS_IS_EKEYTYPE(res)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, res, r, APLOGNO(01834)
+ "the key type is not known");
+ continue;
+ }
+ else if (APR_SUCCESS != res) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, res, r, APLOGNO(01835)
+ "encryption could not be configured.");
+ continue;
+ }
+
+ /* sanity check - decoded too short? */
+ if (len < (sizeof(apr_uuid_t) + ivSize)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(01836)
+ "too short to decrypt, skipping");
+ res = APR_ECRYPT;
+ continue;
+ }
+
+ /* bypass the salt at the start of the decoded block */
+ slider += sizeof(apr_uuid_t);
+ len -= sizeof(apr_uuid_t);
+
+ res = apr_crypto_block_decrypt_init(&block, &blockSize, slider, key,
+ r->pool);
+ if (APR_SUCCESS != res) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, res, r, APLOGNO(01837)
+ "apr_crypto_block_decrypt_init failed");
+ continue;
+ }
+
+ /* bypass the iv at the start of the decoded block */
+ slider += ivSize;
+ len -= ivSize;
+
+ /* decrypt the given string */
+ res = apr_crypto_block_decrypt(&decrypted, &decryptedlen,
+ slider, len, block);
+ if (res) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, res, r, APLOGNO(01838)
+ "apr_crypto_block_decrypt failed");
+ continue;
+ }
+ *out = (char *) decrypted;
+
+ res = apr_crypto_block_decrypt_finish(decrypted + decryptedlen, &tlen, block);
+ if (APR_SUCCESS != res) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, res, r, APLOGNO(01839)
+ "apr_crypto_block_decrypt_finish failed");
+ continue;
+ }
+ decryptedlen += tlen;
+ decrypted[decryptedlen] = 0;
+
+ break;
+ }
+
+ if (APR_SUCCESS != res) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, res, r, APLOGNO(01840)
+ "decryption failed");
+ }
+
+ return res;
+
+}
+
+/**
+ * Crypto encoding for the session.
+ *
+ * @param r The request pointer.
+ * @param z A pointer to where the session will be written.
+ */
+static apr_status_t session_crypto_encode(request_rec * r, session_rec * z)
+{
+
+ char *encoded = NULL;
+ apr_status_t res;
+ const apr_crypto_t *f = NULL;
+ session_crypto_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
+ &session_crypto_module);
+
+ if (dconf->passphrases_set && z->encoded && *z->encoded) {
+ apr_pool_userdata_get((void **)&f, CRYPTO_KEY, r->server->process->pconf);
+ res = encrypt_string(r, f, dconf, z->encoded, &encoded);
+ if (res != OK) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, res, r, APLOGNO(01841)
+ "encrypt session failed");
+ return res;
+ }
+ z->encoded = encoded;
+ }
+
+ return OK;
+
+}
+
+/**
+ * Crypto decoding for the session.
+ *
+ * @param r The request pointer.
+ * @param z A pointer to where the session will be written.
+ */
+static apr_status_t session_crypto_decode(request_rec * r,
+ session_rec * z)
+{
+
+ char *encoded = NULL;
+ apr_status_t res;
+ const apr_crypto_t *f = NULL;
+ session_crypto_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
+ &session_crypto_module);
+
+ if ((dconf->passphrases_set) && z->encoded && *z->encoded) {
+ apr_pool_userdata_get((void **)&f, CRYPTO_KEY,
+ r->server->process->pconf);
+ res = decrypt_string(r, f, dconf, z->encoded, &encoded);
+ if (res != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, APLOGNO(01842)
+ "decrypt session failed, wrong passphrase?");
+ return res;
+ }
+ z->encoded = encoded;
+ }
+
+ return OK;
+
+}
+
+/**
+ * Initialise the SSL in the post_config hook.
+ */
+static int session_crypto_init(apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ const apr_crypto_driver_t *driver = NULL;
+ apr_crypto_t *f = NULL;
+
+ session_crypto_conf *conf = ap_get_module_config(s->module_config,
+ &session_crypto_module);
+
+ /* session_crypto_init() will be called twice. Don't bother
+ * going through all of the initialization on the first call
+ * because it will just be thrown away.*/
+ if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) {
+ return OK;
+ }
+
+ if (conf->library) {
+
+ const apu_err_t *err = NULL;
+ apr_status_t rv;
+
+ rv = apr_crypto_init(p);
+ if (APR_SUCCESS != rv) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(01843)
+ "APR crypto could not be initialised");
+ return rv;
+ }
+
+ rv = apr_crypto_get_driver(&driver, conf->library, conf->params, &err, p);
+ if (APR_EREINIT == rv) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rv, s, APLOGNO(01844)
+ "warning: crypto for '%s' was already initialised, "
+ "using existing configuration", conf->library);
+ rv = APR_SUCCESS;
+ }
+ if (APR_SUCCESS != rv && err) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(01845)
+ "The crypto library '%s' could not be loaded: %s (%s: %d)", conf->library, err->msg, err->reason, err->rc);
+ return rv;
+ }
+ if (APR_ENOTIMPL == rv) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(01846)
+ "The crypto library '%s' could not be found",
+ conf->library);
+ return rv;
+ }
+ if (APR_SUCCESS != rv || !driver) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(01847)
+ "The crypto library '%s' could not be loaded",
+ conf->library);
+ return rv;
+ }
+
+ rv = apr_crypto_make(&f, driver, conf->params, p);
+ if (APR_SUCCESS != rv) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(01848)
+ "The crypto library '%s' could not be initialised",
+ conf->library);
+ return rv;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_INFO, rv, s, APLOGNO(01849)
+ "The crypto library '%s' was loaded successfully",
+ conf->library);
+
+ apr_pool_userdata_set((const void *)f, CRYPTO_KEY,
+ apr_pool_cleanup_null, s->process->pconf);
+
+ }
+
+ return OK;
+}
+
+static void *create_session_crypto_config(apr_pool_t * p, server_rec *s)
+{
+ session_crypto_conf *new =
+ (session_crypto_conf *) apr_pcalloc(p, sizeof(session_crypto_conf));
+
+ /* if no library has been configured, set the recommended library
+ * as a sensible default.
+ */
+#ifdef APU_CRYPTO_RECOMMENDED_DRIVER
+ new->library = APU_CRYPTO_RECOMMENDED_DRIVER;
+#endif
+
+ return (void *) new;
+}
+
+static void *create_session_crypto_dir_config(apr_pool_t * p, char *dummy)
+{
+ session_crypto_dir_conf *new =
+ (session_crypto_dir_conf *) apr_pcalloc(p, sizeof(session_crypto_dir_conf));
+
+ new->passphrases = apr_array_make(p, 10, sizeof(char *));
+
+ /* default cipher AES256-SHA */
+ new->cipher = "aes256";
+
+ return (void *) new;
+}
+
+static void *merge_session_crypto_dir_config(apr_pool_t * p, void *basev, void *addv)
+{
+ session_crypto_dir_conf *new = (session_crypto_dir_conf *) apr_pcalloc(p, sizeof(session_crypto_dir_conf));
+ session_crypto_dir_conf *add = (session_crypto_dir_conf *) addv;
+ session_crypto_dir_conf *base = (session_crypto_dir_conf *) basev;
+
+ new->passphrases = (add->passphrases_set == 0) ? base->passphrases : add->passphrases;
+ new->passphrases_set = add->passphrases_set || base->passphrases_set;
+ new->cipher = (add->cipher_set == 0) ? base->cipher : add->cipher;
+ new->cipher_set = add->cipher_set || base->cipher_set;
+
+ return new;
+}
+
+static const char *set_crypto_driver(cmd_parms * cmd, void *config, const char *arg)
+{
+ session_crypto_conf *conf =
+ (session_crypto_conf *)ap_get_module_config(cmd->server->module_config,
+ &session_crypto_module);
+
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ conf->library = ap_getword_conf(cmd->pool, &arg);
+ conf->params = arg;
+ conf->library_set = 1;
+
+ return NULL;
+}
+
+static const char *set_crypto_passphrase(cmd_parms * cmd, void *config, const char *arg)
+{
+ int arglen = strlen(arg);
+ char **argv;
+ char *result;
+ const char **passphrase;
+ session_crypto_dir_conf *dconf = (session_crypto_dir_conf *) config;
+
+ passphrase = apr_array_push(dconf->passphrases);
+
+ if ((arglen > 5) && strncmp(arg, "exec:", 5) == 0) {
+ if (apr_tokenize_to_argv(arg+5, &argv, cmd->temp_pool) != APR_SUCCESS) {
+ return apr_pstrcat(cmd->pool,
+ "Unable to parse exec arguments from ",
+ arg+5, NULL);
+ }
+ argv[0] = ap_server_root_relative(cmd->temp_pool, argv[0]);
+
+ if (!argv[0]) {
+ return apr_pstrcat(cmd->pool,
+ "Invalid SessionCryptoPassphrase exec location:",
+ arg+5, NULL);
+ }
+ result = ap_get_exec_line(cmd->pool,
+ (const char*)argv[0], (const char * const *)argv);
+
+ if(!result) {
+ return apr_pstrcat(cmd->pool,
+ "Unable to get bind password from exec of ",
+ arg+5, NULL);
+ }
+ *passphrase = result;
+ }
+ else {
+ *passphrase = arg;
+ }
+
+ dconf->passphrases_set = 1;
+
+ return NULL;
+}
+
+static const char *set_crypto_passphrase_file(cmd_parms *cmd, void *config,
+ const char *filename)
+{
+ char buffer[MAX_STRING_LEN];
+ char *arg;
+ const char *args;
+ ap_configfile_t *file;
+ apr_status_t rv;
+
+ filename = ap_server_root_relative(cmd->temp_pool, filename);
+ rv = ap_pcfg_openfile(&file, cmd->temp_pool, filename);
+ if (rv != APR_SUCCESS) {
+ return apr_psprintf(cmd->pool, "%s: Could not open file %s: %pm",
+ cmd->cmd->name, filename, &rv);
+ }
+
+ while (!(ap_cfg_getline(buffer, sizeof(buffer), file))) {
+ args = buffer;
+ while (*(arg = ap_getword_conf(cmd->pool, &args)) != '\0') {
+ if (*arg == '#') {
+ break;
+ }
+ set_crypto_passphrase(cmd, config, arg);
+ }
+ }
+
+ ap_cfg_closefile(file);
+
+ return NULL;
+}
+
+static const char *set_crypto_cipher(cmd_parms * cmd, void *config, const char *cipher)
+{
+ session_crypto_dir_conf *dconf = (session_crypto_dir_conf *) config;
+
+ dconf->cipher = cipher;
+ dconf->cipher_set = 1;
+
+ return NULL;
+}
+
+static const command_rec session_crypto_cmds[] =
+{
+ AP_INIT_ITERATE("SessionCryptoPassphrase", set_crypto_passphrase, NULL, RSRC_CONF|OR_AUTHCFG,
+ "The passphrase(s) used to encrypt the session. First will be used for encryption, all phrases will be accepted for decryption"),
+ AP_INIT_TAKE1("SessionCryptoPassphraseFile", set_crypto_passphrase_file, NULL, RSRC_CONF|ACCESS_CONF,
+ "File containing passphrase(s) used to encrypt the session, one per line. First will be used for encryption, all phrases will be accepted for decryption"),
+ AP_INIT_TAKE1("SessionCryptoCipher", set_crypto_cipher, NULL, RSRC_CONF|OR_AUTHCFG,
+ "The underlying crypto cipher to use"),
+ AP_INIT_RAW_ARGS("SessionCryptoDriver", set_crypto_driver, NULL, RSRC_CONF,
+ "The underlying crypto library driver to use"),
+ { NULL }
+};
+
+static void register_hooks(apr_pool_t * p)
+{
+ ap_hook_session_encode(session_crypto_encode, NULL, NULL, APR_HOOK_LAST);
+ ap_hook_session_decode(session_crypto_decode, NULL, NULL, APR_HOOK_FIRST);
+ ap_hook_post_config(session_crypto_init, NULL, NULL, APR_HOOK_LAST);
+}
+
+AP_DECLARE_MODULE(session_crypto) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_session_crypto_dir_config, /* dir config creater */
+ merge_session_crypto_dir_config, /* dir merger --- default is to override */
+ create_session_crypto_config, /* server config */
+ NULL, /* merge server config */
+ session_crypto_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
+
+#endif
diff --git a/modules/session/mod_session_crypto.dep b/modules/session/mod_session_crypto.dep
new file mode 100644
index 0000000..fff2fa5
--- /dev/null
+++ b/modules/session/mod_session_crypto.dep
@@ -0,0 +1,57 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_session_crypto.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_session_crypto.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\srclib\apr-util\include\apr_base64.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_crypto.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr-util\include\apu_errno.h"\
+ "..\..\srclib\apr-util\include\apu_version.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_version.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_session.h"\
+
diff --git a/modules/session/mod_session_crypto.dsp b/modules/session/mod_session_crypto.dsp
new file mode 100644
index 0000000..37aba02
--- /dev/null
+++ b/modules/session/mod_session_crypto.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_session_crypto" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_session_crypto - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_session_crypto.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_session_crypto.mak" CFG="mod_session_crypto - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_session_crypto - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_session_crypto - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_session_crypto - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_session_crypto_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_session_crypto.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_session_crypto.so" /d LONG_NAME="session_crypto_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_session_crypto.so" /base:@..\..\os\win32\BaseAddr.ref,mod_session_crypto.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_session_crypto.so" /base:@..\..\os\win32\BaseAddr.ref,mod_session_crypto.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_session_crypto.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_session_crypto - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_session_crypto_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_session_crypto.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_session_crypto.so" /d LONG_NAME="session_crypto_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_session_crypto.so" /base:@..\..\os\win32\BaseAddr.ref,mod_session_crypto.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_session_crypto.so" /base:@..\..\os\win32\BaseAddr.ref,mod_session_crypto.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_session_crypto.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_session_crypto - Win32 Release"
+# Name "mod_session_crypto - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_session_crypto.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/session/mod_session_crypto.mak b/modules/session/mod_session_crypto.mak
new file mode 100644
index 0000000..13a4c67
--- /dev/null
+++ b/modules/session/mod_session_crypto.mak
@@ -0,0 +1,381 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_session_crypto.dsp
+!IF "$(CFG)" == ""
+CFG=mod_session_crypto - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_session_crypto - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_session_crypto - Win32 Release" && "$(CFG)" != "mod_session_crypto - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_session_crypto.mak" CFG="mod_session_crypto - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_session_crypto - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_session_crypto - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_session_crypto - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_session_crypto.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_session - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_session_crypto.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_session - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_session_crypto.obj"
+ -@erase "$(INTDIR)\mod_session_crypto.res"
+ -@erase "$(INTDIR)\mod_session_crypto_src.idb"
+ -@erase "$(INTDIR)\mod_session_crypto_src.pdb"
+ -@erase "$(OUTDIR)\mod_session_crypto.exp"
+ -@erase "$(OUTDIR)\mod_session_crypto.lib"
+ -@erase "$(OUTDIR)\mod_session_crypto.pdb"
+ -@erase "$(OUTDIR)\mod_session_crypto.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_session_crypto_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_session_crypto.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_session_crypto.so" /d LONG_NAME="session_crypto_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_session_crypto.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_session_crypto.pdb" /debug /out:"$(OUTDIR)\mod_session_crypto.so" /implib:"$(OUTDIR)\mod_session_crypto.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_session_crypto.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_session_crypto.obj" \
+ "$(INTDIR)\mod_session_crypto.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib" \
+ "$(OUTDIR)\mod_session.lib"
+
+"$(OUTDIR)\mod_session_crypto.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_session_crypto.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_session_crypto.so"
+ if exist .\Release\mod_session_crypto.so.manifest mt.exe -manifest .\Release\mod_session_crypto.so.manifest -outputresource:.\Release\mod_session_crypto.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_session_crypto - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_session_crypto.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_session - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_session_crypto.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_session - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_session_crypto.obj"
+ -@erase "$(INTDIR)\mod_session_crypto.res"
+ -@erase "$(INTDIR)\mod_session_crypto_src.idb"
+ -@erase "$(INTDIR)\mod_session_crypto_src.pdb"
+ -@erase "$(OUTDIR)\mod_session_crypto.exp"
+ -@erase "$(OUTDIR)\mod_session_crypto.lib"
+ -@erase "$(OUTDIR)\mod_session_crypto.pdb"
+ -@erase "$(OUTDIR)\mod_session_crypto.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_session_crypto_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_session_crypto.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_session_crypto.so" /d LONG_NAME="session_crypto_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_session_crypto.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_session_crypto.pdb" /debug /out:"$(OUTDIR)\mod_session_crypto.so" /implib:"$(OUTDIR)\mod_session_crypto.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_session_crypto.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_session_crypto.obj" \
+ "$(INTDIR)\mod_session_crypto.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib" \
+ "$(OUTDIR)\mod_session.lib"
+
+"$(OUTDIR)\mod_session_crypto.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_session_crypto.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_session_crypto.so"
+ if exist .\Debug\mod_session_crypto.so.manifest mt.exe -manifest .\Debug\mod_session_crypto.so.manifest -outputresource:.\Debug\mod_session_crypto.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_session_crypto.dep")
+!INCLUDE "mod_session_crypto.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_session_crypto.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_session_crypto - Win32 Release" || "$(CFG)" == "mod_session_crypto - Win32 Debug"
+
+!IF "$(CFG)" == "mod_session_crypto - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\session"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\session"
+
+!ELSEIF "$(CFG)" == "mod_session_crypto - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\session"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\session"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_session_crypto - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\session"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\session"
+
+!ELSEIF "$(CFG)" == "mod_session_crypto - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\session"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\session"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_session_crypto - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\session"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\session"
+
+!ELSEIF "$(CFG)" == "mod_session_crypto - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\session"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\session"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_session_crypto - Win32 Release"
+
+"mod_session - Win32 Release" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_session.mak" CFG="mod_session - Win32 Release"
+ cd "."
+
+"mod_session - Win32 ReleaseCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_session.mak" CFG="mod_session - Win32 Release" RECURSE=1 CLEAN
+ cd "."
+
+!ELSEIF "$(CFG)" == "mod_session_crypto - Win32 Debug"
+
+"mod_session - Win32 Debug" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_session.mak" CFG="mod_session - Win32 Debug"
+ cd "."
+
+"mod_session - Win32 DebugCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_session.mak" CFG="mod_session - Win32 Debug" RECURSE=1 CLEAN
+ cd "."
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_session_crypto - Win32 Release"
+
+
+"$(INTDIR)\mod_session_crypto.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_session_crypto.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_session_crypto.so" /d LONG_NAME="session_crypto_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_session_crypto - Win32 Debug"
+
+
+"$(INTDIR)\mod_session_crypto.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_session_crypto.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_session_crypto.so" /d LONG_NAME="session_crypto_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_session_crypto.c
+
+"$(INTDIR)\mod_session_crypto.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/session/mod_session_dbd.c b/modules/session/mod_session_dbd.c
new file mode 100644
index 0000000..f683da2
--- /dev/null
+++ b/modules/session/mod_session_dbd.c
@@ -0,0 +1,640 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mod_session.h"
+#include "apr_lib.h"
+#include "apr_strings.h"
+#include "http_log.h"
+#include "util_cookies.h"
+#include "apr_dbd.h"
+#include "mod_dbd.h"
+#include "mpm_common.h"
+
+#define MOD_SESSION_DBD "mod_session_dbd"
+
+module AP_MODULE_DECLARE_DATA session_dbd_module;
+
+/**
+ * Structure to carry the per-dir session config.
+ */
+typedef struct {
+ const char *name;
+ int name_set;
+ const char *name_attrs;
+ const char *name2;
+ int name2_set;
+ const char *name2_attrs;
+ int peruser;
+ int peruser_set;
+ int remove;
+ int remove_set;
+ const char *selectlabel;
+ const char *insertlabel;
+ const char *updatelabel;
+ const char *deletelabel;
+} session_dbd_dir_conf;
+
+/* optional function - look it up once in post_config */
+static ap_dbd_t *(*session_dbd_acquire_fn) (request_rec *) = NULL;
+static void (*session_dbd_prepare_fn) (server_rec *, const char *, const char *) = NULL;
+
+/**
+ * Initialise the database.
+ *
+ * If the mod_dbd module is missing, this method will return APR_EGENERAL.
+ */
+static apr_status_t dbd_init(request_rec *r, const char *query, ap_dbd_t **dbdp,
+ apr_dbd_prepared_t **statementp)
+{
+ ap_dbd_t *dbd;
+ apr_dbd_prepared_t *statement;
+
+ if (!session_dbd_prepare_fn || !session_dbd_acquire_fn) {
+ session_dbd_prepare_fn = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_prepare);
+ session_dbd_acquire_fn = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_acquire);
+ if (!session_dbd_prepare_fn || !session_dbd_acquire_fn) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01850)
+ "You must load mod_dbd to enable AuthDBD functions");
+ return APR_EGENERAL;
+ }
+ }
+
+ dbd = session_dbd_acquire_fn(r);
+ if (!dbd) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01851)
+ "failed to acquire database connection");
+ return APR_EGENERAL;
+ }
+
+ statement = apr_hash_get(dbd->prepared, query, APR_HASH_KEY_STRING);
+ if (!statement) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01852)
+ "failed to find the prepared statement called '%s'", query);
+ return APR_EGENERAL;
+ }
+
+ *dbdp = dbd;
+ *statementp = statement;
+
+ return APR_SUCCESS;
+}
+
+/**
+ * Load the session by the key specified.
+ *
+ * The session value is allocated using the passed apr_pool_t.
+ */
+static apr_status_t dbd_load(apr_pool_t *p, request_rec * r,
+ const char *key, const char **val)
+{
+
+ apr_status_t rv;
+ ap_dbd_t *dbd = NULL;
+ apr_dbd_prepared_t *statement = NULL;
+ apr_dbd_results_t *res = NULL;
+ apr_dbd_row_t *row = NULL;
+ apr_int64_t expiry = (apr_int64_t) apr_time_now();
+
+ session_dbd_dir_conf *conf = ap_get_module_config(r->per_dir_config,
+ &session_dbd_module);
+
+ if (conf->selectlabel == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01853)
+ "no SessionDBDselectlabel has been specified");
+ return APR_EGENERAL;
+ }
+
+ rv = dbd_init(r, conf->selectlabel, &dbd, &statement);
+ if (rv) {
+ return rv;
+ }
+ rv = apr_dbd_pvbselect(dbd->driver, r->pool, dbd->handle, &res, statement,
+ 0, key, &expiry, NULL);
+ if (rv) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01854)
+ "query execution error saving session '%s' "
+ "in database using query '%s': %s", key, conf->selectlabel,
+ apr_dbd_error(dbd->driver, dbd->handle, rv));
+ return APR_EGENERAL;
+ }
+ for (rv = apr_dbd_get_row(dbd->driver, r->pool, res, &row, -1);
+ rv != -1;
+ rv = apr_dbd_get_row(dbd->driver, r->pool, res, &row, -1)) {
+ if (rv != 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01855)
+ "error retrieving results while saving '%s' "
+ "in database using query '%s': %s", key, conf->selectlabel,
+ apr_dbd_error(dbd->driver, dbd->handle, rv));
+ return APR_EGENERAL;
+ }
+ if (*val == NULL) {
+ *val = apr_pstrdup(p, apr_dbd_get_entry(dbd->driver, row, 0));
+ }
+ /* we can't break out here or row won't get cleaned up */
+ }
+
+ return APR_SUCCESS;
+
+}
+
+/**
+ * Load the session by firing off a dbd query.
+ *
+ * If the session is anonymous, the session key will be extracted from
+ * the cookie specified. Failing that, the session key will be extracted
+ * from the GET parameters.
+ *
+ * If the session is keyed by the username, the session will be extracted
+ * by that.
+ *
+ * If no session is found, an empty session will be created.
+ *
+ * On success, this returns OK.
+ */
+static apr_status_t session_dbd_load(request_rec * r, session_rec ** z)
+{
+
+ session_dbd_dir_conf *conf = ap_get_module_config(r->per_dir_config,
+ &session_dbd_module);
+
+ apr_status_t ret = APR_SUCCESS;
+ session_rec *zz = NULL;
+ const char *name = NULL;
+ const char *note = NULL;
+ const char *val = NULL;
+ const char *key = NULL;
+ request_rec *m = r->main ? r->main : r;
+
+ /* is our session in a cookie? */
+ if (conf->name2_set) {
+ name = conf->name2;
+ }
+ else if (conf->name_set) {
+ name = conf->name;
+ }
+ else if (conf->peruser_set && r->user) {
+ name = r->user;
+ }
+ else {
+ return DECLINED;
+ }
+
+ /* first look in the notes */
+ note = apr_pstrcat(m->pool, MOD_SESSION_DBD, name, NULL);
+ zz = (session_rec *)apr_table_get(m->notes, note);
+ if (zz) {
+ *z = zz;
+ return OK;
+ }
+
+ /* load anonymous sessions */
+ if (conf->name_set || conf->name2_set) {
+
+ /* load an RFC2109 or RFC2965 compliant cookie */
+ ap_cookie_read(r, name, &key, conf->remove);
+ if (key) {
+ ret = dbd_load(m->pool, r, key, &val);
+ if (ret != APR_SUCCESS) {
+ return ret;
+ }
+ }
+
+ }
+
+ /* load named session */
+ else if (conf->peruser) {
+ if (r->user) {
+ ret = dbd_load(m->pool, r, r->user, &val);
+ if (ret != APR_SUCCESS) {
+ return ret;
+ }
+ }
+ }
+
+ /* otherwise not for us */
+ else {
+ return DECLINED;
+ }
+
+ /* create a new session and return it */
+ zz = (session_rec *) apr_pcalloc(m->pool, sizeof(session_rec));
+ zz->pool = m->pool;
+ zz->entries = apr_table_make(zz->pool, 10);
+ if (key && val) {
+ apr_uuid_t *uuid = apr_pcalloc(zz->pool, sizeof(apr_uuid_t));
+ if (APR_SUCCESS == apr_uuid_parse(uuid, key)) {
+ zz->uuid = uuid;
+ }
+ }
+ zz->encoded = val;
+ *z = zz;
+
+ /* put the session in the notes so we don't have to parse it again */
+ apr_table_setn(m->notes, note, (char *)zz);
+
+ /* don't cache pages with a session */
+ apr_table_addn(r->headers_out, "Cache-Control", "no-cache, private");
+
+ return OK;
+
+}
+
+/**
+ * Save the session by the key specified.
+ */
+static apr_status_t dbd_save(request_rec * r, const char *oldkey,
+ const char *newkey, const char *val, apr_int64_t expiry)
+{
+
+ apr_status_t rv;
+ ap_dbd_t *dbd = NULL;
+ apr_dbd_prepared_t *statement;
+ int rows = 0;
+
+ session_dbd_dir_conf *conf = ap_get_module_config(r->per_dir_config,
+ &session_dbd_module);
+
+ if (conf->updatelabel == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01856)
+ "no SessionDBDupdatelabel has been specified");
+ return APR_EGENERAL;
+ }
+
+ rv = dbd_init(r, conf->updatelabel, &dbd, &statement);
+ if (rv) {
+ return rv;
+ }
+
+ if (oldkey) {
+ rv = apr_dbd_pvbquery(dbd->driver, r->pool, dbd->handle, &rows,
+ statement, val, &expiry, newkey, oldkey, NULL);
+ if (rv) {
+ ap_log_rerror(
+ APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01857) "query execution error updating session '%s' "
+ "using database query '%s': %s/%s", oldkey, newkey, conf->updatelabel, apr_dbd_error(dbd->driver, dbd->handle, rv));
+ return APR_EGENERAL;
+ }
+
+ /*
+ * if some rows were updated it means a session existed and was updated,
+ * so we are done.
+ */
+ if (rows != 0) {
+ return APR_SUCCESS;
+ }
+ }
+
+ if (conf->insertlabel == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01858)
+ "no SessionDBDinsertlabel has been specified");
+ return APR_EGENERAL;
+ }
+
+ rv = dbd_init(r, conf->insertlabel, &dbd, &statement);
+ if (rv) {
+ return rv;
+ }
+ rv = apr_dbd_pvbquery(dbd->driver, r->pool, dbd->handle, &rows, statement,
+ val, &expiry, newkey, NULL);
+ if (rv) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01859)
+ "query execution error inserting session '%s' "
+ "in database with '%s': %s", newkey, conf->insertlabel,
+ apr_dbd_error(dbd->driver, dbd->handle, rv));
+ return APR_EGENERAL;
+ }
+
+ /*
+ * if some rows were inserted it means a session was inserted, so we are
+ * done.
+ */
+ if (rows != 0) {
+ return APR_SUCCESS;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01860)
+ "the session insert query did not cause any rows to be added "
+ "to the database for session '%s', session not inserted", newkey);
+
+ return APR_EGENERAL;
+
+}
+
+/**
+ * Remove the session by the key specified.
+ */
+static apr_status_t dbd_remove(request_rec * r, const char *key)
+{
+
+ apr_status_t rv;
+ ap_dbd_t *dbd;
+ apr_dbd_prepared_t *statement;
+ int rows = 0;
+
+ session_dbd_dir_conf *conf = ap_get_module_config(r->per_dir_config,
+ &session_dbd_module);
+
+ if (conf->deletelabel == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01862)
+ "no SessionDBDdeletelabel has been specified");
+ return APR_EGENERAL;
+ }
+
+ rv = dbd_init(r, conf->deletelabel, &dbd, &statement);
+ if (rv != APR_SUCCESS) {
+ /* No need to do additional error logging here, it has already
+ been done in dbd_init if needed */
+ return rv;
+ }
+
+ rv = apr_dbd_pvbquery(dbd->driver, r->pool, dbd->handle, &rows, statement,
+ key, NULL);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01864)
+ "query execution error removing session '%s' "
+ "from database", key);
+ return rv;
+ }
+
+ return APR_SUCCESS;
+
+}
+
+/**
+ * Clean out expired sessions.
+ *
+ * TODO: We need to figure out a way to clean out expired sessions from the database.
+ * The monitor hook doesn't help us that much, as we have no handle into the
+ * server, and so we need to come up with a way to do this safely.
+ */
+static apr_status_t dbd_clean(apr_pool_t *p, server_rec *s)
+{
+
+ return APR_ENOTIMPL;
+
+}
+
+/**
+ * Save the session by firing off a dbd query.
+ *
+ * If the session is anonymous, save the session and write a cookie
+ * containing the uuid.
+ *
+ * If the session is keyed to the username, save the session using
+ * the username as a key.
+ *
+ * On success, this method will return APR_SUCCESS.
+ *
+ * @param r The request pointer.
+ * @param z A pointer to where the session will be written.
+ */
+static apr_status_t session_dbd_save(request_rec * r, session_rec * z)
+{
+
+ apr_status_t ret = APR_SUCCESS;
+ session_dbd_dir_conf *conf = ap_get_module_config(r->per_dir_config,
+ &session_dbd_module);
+
+ /* support anonymous sessions */
+ if (conf->name_set || conf->name2_set) {
+ char *oldkey = NULL, *newkey = NULL;
+
+ /* if the session is new or changed, make a new session ID */
+ if (z->uuid) {
+ oldkey = apr_pcalloc(r->pool, APR_UUID_FORMATTED_LENGTH + 1);
+ apr_uuid_format(oldkey, z->uuid);
+ }
+ if (z->dirty || !oldkey) {
+ z->uuid = apr_pcalloc(z->pool, sizeof(apr_uuid_t));
+ apr_uuid_get(z->uuid);
+ newkey = apr_pcalloc(r->pool, APR_UUID_FORMATTED_LENGTH + 1);
+ apr_uuid_format(newkey, z->uuid);
+ }
+ else {
+ newkey = oldkey;
+ }
+
+ /* save the session with the uuid as key */
+ if (z->encoded && z->encoded[0]) {
+ ret = dbd_save(r, oldkey, newkey, z->encoded, z->expiry);
+ }
+ else {
+ ret = dbd_remove(r, oldkey);
+ }
+ if (ret != APR_SUCCESS) {
+ return ret;
+ }
+
+ /* create RFC2109 compliant cookie */
+ if (conf->name_set) {
+ ap_cookie_write(r, conf->name, newkey, conf->name_attrs, z->maxage,
+ r->headers_out, r->err_headers_out, NULL);
+ }
+
+ /* create RFC2965 compliant cookie */
+ if (conf->name2_set) {
+ ap_cookie_write2(r, conf->name2, newkey, conf->name2_attrs, z->maxage,
+ r->headers_out, r->err_headers_out, NULL);
+ }
+
+ return OK;
+
+ }
+
+ /* save named session */
+ else if (conf->peruser) {
+
+ /* don't cache pages with a session */
+ apr_table_addn(r->headers_out, "Cache-Control", "no-cache, private");
+
+ if (r->user) {
+ ret = dbd_save(r, r->user, r->user, z->encoded, z->expiry);
+ if (ret != APR_SUCCESS) {
+ return ret;
+ }
+ return OK;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01865)
+ "peruser sessions can only be saved if a user is logged in, "
+ "session not saved: %s", r->uri);
+ }
+ }
+
+ return DECLINED;
+
+}
+
+/**
+ * This function performs housekeeping on the database, deleting expired
+ * sessions.
+ */
+static int session_dbd_monitor(apr_pool_t *p, server_rec *s)
+{
+ /* TODO handle housekeeping */
+ dbd_clean(p, s);
+ return OK;
+}
+
+
+static void *create_session_dbd_dir_config(apr_pool_t * p, char *dummy)
+{
+ session_dbd_dir_conf *new =
+ (session_dbd_dir_conf *) apr_pcalloc(p, sizeof(session_dbd_dir_conf));
+
+ new->remove = 1;
+
+ new->selectlabel = "selectsession";
+ new->insertlabel = "insertsession";
+ new->updatelabel = "updatesession";
+ new->deletelabel = "deletesession";
+
+ return (void *) new;
+}
+
+static void *merge_session_dbd_dir_config(apr_pool_t * p, void *basev, void *addv)
+{
+ session_dbd_dir_conf *new = (session_dbd_dir_conf *) apr_pcalloc(p, sizeof(session_dbd_dir_conf));
+ session_dbd_dir_conf *add = (session_dbd_dir_conf *) addv;
+ session_dbd_dir_conf *base = (session_dbd_dir_conf *) basev;
+
+ new->name = (add->name_set == 0) ? base->name : add->name;
+ new->name_attrs = (add->name_set == 0) ? base->name_attrs : add->name_attrs;
+ new->name_set = add->name_set || base->name_set;
+ new->name2 = (add->name2_set == 0) ? base->name2 : add->name2;
+ new->name2_attrs = (add->name2_set == 0) ? base->name2_attrs : add->name2_attrs;
+ new->name2_set = add->name2_set || base->name2_set;
+ new->peruser = (add->peruser_set == 0) ? base->peruser : add->peruser;
+ new->peruser_set = add->peruser_set || base->peruser_set;
+ new->remove = (add->remove_set == 0) ? base->remove : add->remove;
+ new->remove_set = add->remove_set || base->remove_set;
+ new->selectlabel = (!add->selectlabel) ? base->selectlabel : add->selectlabel;
+ new->updatelabel = (!add->updatelabel) ? base->updatelabel : add->updatelabel;
+ new->insertlabel = (!add->insertlabel) ? base->insertlabel : add->insertlabel;
+ new->deletelabel = (!add->deletelabel) ? base->deletelabel : add->deletelabel;
+
+ return new;
+}
+
+/**
+ * Sanity check a given string that it exists, is not empty,
+ * and does not contain special characters.
+ */
+static const char *check_string(cmd_parms * cmd, const char *string)
+{
+ if (APR_SUCCESS != ap_cookie_check_string(string)) {
+ return apr_pstrcat(cmd->pool, cmd->directive->directive,
+ " cannot be empty, or contain '=', ';' or '&'.",
+ NULL);
+ }
+ return NULL;
+}
+
+static const char *
+ set_dbd_peruser(cmd_parms * parms, void *dconf, int flag)
+{
+ session_dbd_dir_conf *conf = dconf;
+
+ conf->peruser = flag;
+ conf->peruser_set = 1;
+
+ return NULL;
+}
+
+static const char *
+ set_dbd_cookie_remove(cmd_parms * parms, void *dconf, int flag)
+{
+ session_dbd_dir_conf *conf = dconf;
+
+ conf->remove = flag;
+ conf->remove_set = 1;
+
+ return NULL;
+}
+
+static const char *set_cookie_name(cmd_parms * cmd, void *config, const char *args)
+{
+ char *last;
+ char *line = apr_pstrdup(cmd->pool, args);
+ session_dbd_dir_conf *conf = (session_dbd_dir_conf *) config;
+ char *cookie = apr_strtok(line, " \t", &last);
+ conf->name = cookie;
+ conf->name_set = 1;
+ while (apr_isspace(*last)) {
+ last++;
+ }
+ conf->name_attrs = last;
+ return check_string(cmd, cookie);
+}
+
+static const char *set_cookie_name2(cmd_parms * cmd, void *config, const char *args)
+{
+ char *last;
+ char *line = apr_pstrdup(cmd->pool, args);
+ session_dbd_dir_conf *conf = (session_dbd_dir_conf *) config;
+ char *cookie = apr_strtok(line, " \t", &last);
+ conf->name2 = cookie;
+ conf->name2_set = 1;
+ while (apr_isspace(*last)) {
+ last++;
+ }
+ conf->name2_attrs = last;
+ return check_string(cmd, cookie);
+}
+
+static const command_rec session_dbd_cmds[] =
+{
+ AP_INIT_TAKE1("SessionDBDSelectLabel", ap_set_string_slot,
+ (void *) APR_OFFSETOF(session_dbd_dir_conf, selectlabel), RSRC_CONF|OR_AUTHCFG,
+ "Query label used to select a new session"),
+ AP_INIT_TAKE1("SessionDBDInsertLabel", ap_set_string_slot,
+ (void *) APR_OFFSETOF(session_dbd_dir_conf, insertlabel), RSRC_CONF|OR_AUTHCFG,
+ "Query label used to insert a new session"),
+ AP_INIT_TAKE1("SessionDBDUpdateLabel", ap_set_string_slot,
+ (void *) APR_OFFSETOF(session_dbd_dir_conf, updatelabel), RSRC_CONF|OR_AUTHCFG,
+ "Query label used to update an existing session"),
+ AP_INIT_TAKE1("SessionDBDDeleteLabel", ap_set_string_slot,
+ (void *) APR_OFFSETOF(session_dbd_dir_conf, deletelabel), RSRC_CONF|OR_AUTHCFG,
+ "Query label used to delete an existing session"),
+ AP_INIT_FLAG("SessionDBDPerUser", set_dbd_peruser, NULL, RSRC_CONF|OR_AUTHCFG,
+ "Save the session per user"),
+ AP_INIT_FLAG("SessionDBDCookieRemove", set_dbd_cookie_remove, NULL, RSRC_CONF|OR_AUTHCFG,
+ "Remove the session cookie after session load. On by default."),
+ AP_INIT_RAW_ARGS("SessionDBDCookieName", set_cookie_name, NULL, RSRC_CONF|OR_AUTHCFG,
+ "The name of the RFC2109 cookie carrying the session key"),
+ AP_INIT_RAW_ARGS("SessionDBDCookieName2", set_cookie_name2, NULL, RSRC_CONF|OR_AUTHCFG,
+ "The name of the RFC2965 cookie carrying the session key"),
+ {NULL}
+};
+
+static void register_hooks(apr_pool_t * p)
+{
+ ap_hook_session_load(session_dbd_load, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_session_save(session_dbd_save, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_monitor(session_dbd_monitor, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(session_dbd) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_session_dbd_dir_config, /* dir config creater */
+ merge_session_dbd_dir_config, /* dir merger --- default is to
+ * override */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ session_dbd_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/session/mod_session_dbd.dep b/modules/session/mod_session_dbd.dep
new file mode 100644
index 0000000..9cb6808
--- /dev/null
+++ b/modules/session/mod_session_dbd.dep
@@ -0,0 +1,60 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_session_dbd.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_session_dbd.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_mpm.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mpm_common.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_cookies.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_dbd.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_uuid.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ "..\database\mod_dbd.h"\
+ ".\mod_session.h"\
+
diff --git a/modules/session/mod_session_dbd.dsp b/modules/session/mod_session_dbd.dsp
new file mode 100644
index 0000000..055b4e5
--- /dev/null
+++ b/modules/session/mod_session_dbd.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_session_dbd" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_session_dbd - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_session_dbd.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_session_dbd.mak" CFG="mod_session_dbd - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_session_dbd - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_session_dbd - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_session_dbd - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../database" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_session_dbd_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_session_dbd.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_session_dbd.so" /d LONG_NAME="session_dbd_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_session_dbd.so" /base:@..\..\os\win32\BaseAddr.ref,mod_session_dbd.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_session_dbd.so" /base:@..\..\os\win32\BaseAddr.ref,mod_session_dbd.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_session_dbd.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_session_dbd - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../database" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_session_dbd_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_session_dbd.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_session_dbd.so" /d LONG_NAME="session_dbd_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_session_dbd.so" /base:@..\..\os\win32\BaseAddr.ref,mod_session_dbd.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_session_dbd.so" /base:@..\..\os\win32\BaseAddr.ref,mod_session_dbd.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_session_dbd.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_session_dbd - Win32 Release"
+# Name "mod_session_dbd - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_session_dbd.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/session/mod_session_dbd.mak b/modules/session/mod_session_dbd.mak
new file mode 100644
index 0000000..e16c61e
--- /dev/null
+++ b/modules/session/mod_session_dbd.mak
@@ -0,0 +1,409 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_session_dbd.dsp
+!IF "$(CFG)" == ""
+CFG=mod_session_dbd - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_session_dbd - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_session_dbd - Win32 Release" && "$(CFG)" != "mod_session_dbd - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_session_dbd.mak" CFG="mod_session_dbd - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_session_dbd - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_session_dbd - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_session_dbd - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_session_dbd.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_session - Win32 Release" "mod_dbd - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_session_dbd.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_dbd - Win32 ReleaseCLEAN" "mod_session - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_session_dbd.obj"
+ -@erase "$(INTDIR)\mod_session_dbd.res"
+ -@erase "$(INTDIR)\mod_session_dbd_src.idb"
+ -@erase "$(INTDIR)\mod_session_dbd_src.pdb"
+ -@erase "$(OUTDIR)\mod_session_dbd.exp"
+ -@erase "$(OUTDIR)\mod_session_dbd.lib"
+ -@erase "$(OUTDIR)\mod_session_dbd.pdb"
+ -@erase "$(OUTDIR)\mod_session_dbd.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../database" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_session_dbd_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_session_dbd.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_session_dbd.so" /d LONG_NAME="session_dbd_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_session_dbd.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_session_dbd.pdb" /debug /out:"$(OUTDIR)\mod_session_dbd.so" /implib:"$(OUTDIR)\mod_session_dbd.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_session_dbd.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_session_dbd.obj" \
+ "$(INTDIR)\mod_session_dbd.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib" \
+ "..\database\Release\mod_dbd.lib" \
+ "$(OUTDIR)\mod_session.lib"
+
+"$(OUTDIR)\mod_session_dbd.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_session_dbd.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_session_dbd.so"
+ if exist .\Release\mod_session_dbd.so.manifest mt.exe -manifest .\Release\mod_session_dbd.so.manifest -outputresource:.\Release\mod_session_dbd.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_session_dbd - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_session_dbd.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "mod_session - Win32 Debug" "mod_dbd - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_session_dbd.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_dbd - Win32 DebugCLEAN" "mod_session - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_session_dbd.obj"
+ -@erase "$(INTDIR)\mod_session_dbd.res"
+ -@erase "$(INTDIR)\mod_session_dbd_src.idb"
+ -@erase "$(INTDIR)\mod_session_dbd_src.pdb"
+ -@erase "$(OUTDIR)\mod_session_dbd.exp"
+ -@erase "$(OUTDIR)\mod_session_dbd.lib"
+ -@erase "$(OUTDIR)\mod_session_dbd.pdb"
+ -@erase "$(OUTDIR)\mod_session_dbd.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../database" /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_session_dbd_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_session_dbd.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_session_dbd.so" /d LONG_NAME="session_dbd_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_session_dbd.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_session_dbd.pdb" /debug /out:"$(OUTDIR)\mod_session_dbd.so" /implib:"$(OUTDIR)\mod_session_dbd.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_session_dbd.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_session_dbd.obj" \
+ "$(INTDIR)\mod_session_dbd.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib" \
+ "..\database\Debug\mod_dbd.lib" \
+ "$(OUTDIR)\mod_session.lib"
+
+"$(OUTDIR)\mod_session_dbd.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_session_dbd.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_session_dbd.so"
+ if exist .\Debug\mod_session_dbd.so.manifest mt.exe -manifest .\Debug\mod_session_dbd.so.manifest -outputresource:.\Debug\mod_session_dbd.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_session_dbd.dep")
+!INCLUDE "mod_session_dbd.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_session_dbd.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_session_dbd - Win32 Release" || "$(CFG)" == "mod_session_dbd - Win32 Debug"
+
+!IF "$(CFG)" == "mod_session_dbd - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\session"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\session"
+
+!ELSEIF "$(CFG)" == "mod_session_dbd - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\session"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\session"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_session_dbd - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\session"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\session"
+
+!ELSEIF "$(CFG)" == "mod_session_dbd - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\session"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\session"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_session_dbd - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\session"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\session"
+
+!ELSEIF "$(CFG)" == "mod_session_dbd - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\session"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\session"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_session_dbd - Win32 Release"
+
+"mod_dbd - Win32 Release" :
+ cd ".\..\database"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_dbd.mak" CFG="mod_dbd - Win32 Release"
+ cd "..\session"
+
+"mod_dbd - Win32 ReleaseCLEAN" :
+ cd ".\..\database"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_dbd.mak" CFG="mod_dbd - Win32 Release" RECURSE=1 CLEAN
+ cd "..\session"
+
+!ELSEIF "$(CFG)" == "mod_session_dbd - Win32 Debug"
+
+"mod_dbd - Win32 Debug" :
+ cd ".\..\database"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_dbd.mak" CFG="mod_dbd - Win32 Debug"
+ cd "..\session"
+
+"mod_dbd - Win32 DebugCLEAN" :
+ cd ".\..\database"
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_dbd.mak" CFG="mod_dbd - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\session"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_session_dbd - Win32 Release"
+
+"mod_session - Win32 Release" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_session.mak" CFG="mod_session - Win32 Release"
+ cd "."
+
+"mod_session - Win32 ReleaseCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_session.mak" CFG="mod_session - Win32 Release" RECURSE=1 CLEAN
+ cd "."
+
+!ELSEIF "$(CFG)" == "mod_session_dbd - Win32 Debug"
+
+"mod_session - Win32 Debug" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_session.mak" CFG="mod_session - Win32 Debug"
+ cd "."
+
+"mod_session - Win32 DebugCLEAN" :
+ cd "."
+ $(MAKE) /$(MAKEFLAGS) /F ".\mod_session.mak" CFG="mod_session - Win32 Debug" RECURSE=1 CLEAN
+ cd "."
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_session_dbd - Win32 Release"
+
+
+"$(INTDIR)\mod_session_dbd.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_session_dbd.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_session_dbd.so" /d LONG_NAME="session_dbd_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_session_dbd - Win32 Debug"
+
+
+"$(INTDIR)\mod_session_dbd.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_session_dbd.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_session_dbd.so" /d LONG_NAME="session_dbd_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_session_dbd.c
+
+"$(INTDIR)\mod_session_dbd.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/slotmem/Makefile.in b/modules/slotmem/Makefile.in
new file mode 100644
index 0000000..7c5c149
--- /dev/null
+++ b/modules/slotmem/Makefile.in
@@ -0,0 +1,3 @@
+# a modules Makefile has no explicit targets -- they will be defined by
+# whatever modules are enabled. just grab special.mk to deal with this.
+include $(top_srcdir)/build/special.mk
diff --git a/modules/slotmem/NWGNUmakefile b/modules/slotmem/NWGNUmakefile
new file mode 100644
index 0000000..bdde37d
--- /dev/null
+++ b/modules/slotmem/NWGNUmakefile
@@ -0,0 +1,246 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME =
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION =
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME =
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE =
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/slotmem_plain.nlm \
+ $(OBJDIR)/slotmem_shm.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/slotmem/NWGNUslotmem_plain b/modules/slotmem/NWGNUslotmem_plain
new file mode 100644
index 0000000..a66504a
--- /dev/null
+++ b/modules/slotmem/NWGNUslotmem_plain
@@ -0,0 +1,250 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = slotmem_plain
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Slotmem Plain Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Slotmem Plain Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_slotmem_plain.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ slotmem_plain_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+vpath %.c ../arch/netware
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/slotmem/NWGNUslotmem_shm b/modules/slotmem/NWGNUslotmem_shm
new file mode 100644
index 0000000..5155693
--- /dev/null
+++ b/modules/slotmem/NWGNUslotmem_shm
@@ -0,0 +1,250 @@
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# Get the 'head' of the build environment if necessary. This includes default
+# targets and paths to tools
+#
+
+ifndef EnvironmentDefined
+include $(AP_WORK)/build/NWGNUhead.inc
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = slotmem_shm
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) Slotmem SHM Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = Slotmem SHM Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_slotmem_shm.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ slotmem_shm_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+vpath %.c ../arch/netware
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/slotmem/config.m4 b/modules/slotmem/config.m4
new file mode 100644
index 0000000..82d13bb
--- /dev/null
+++ b/modules/slotmem/config.m4
@@ -0,0 +1,10 @@
+dnl modules enabled in this directory by default
+
+dnl APACHE_MODULE(name, helptext[, objects[, structname[, default[, config]]]])
+
+APACHE_MODPATH_INIT(slotmem)
+
+APACHE_MODULE(slotmem_shm, slotmem provider that uses shared memory, , , most)
+APACHE_MODULE(slotmem_plain, slotmem provider that uses plain memory, , , )
+
+APACHE_MODPATH_FINISH
diff --git a/modules/slotmem/mod_slotmem_plain.c b/modules/slotmem/mod_slotmem_plain.c
new file mode 100644
index 0000000..4c2b19b
--- /dev/null
+++ b/modules/slotmem/mod_slotmem_plain.c
@@ -0,0 +1,343 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Memory handler for a plain memory divided in slot.
+ * This one uses plain memory.
+ */
+
+#include "ap_slotmem.h"
+
+#define AP_SLOTMEM_IS_PREGRAB(t) (t->type & AP_SLOTMEM_TYPE_PREGRAB)
+
+struct ap_slotmem_instance_t {
+ char *name; /* per segment name */
+ void *base; /* data set start */
+ apr_size_t size; /* size of each memory slot */
+ unsigned int num; /* number of mem slots */
+ apr_pool_t *gpool; /* per segment global pool */
+ char *inuse; /* in-use flag table*/
+ ap_slotmem_type_t type; /* type-specific flags */
+ struct ap_slotmem_instance_t *next; /* location of next allocated segment */
+};
+
+
+/* global pool and list of slotmem we are handling */
+static struct ap_slotmem_instance_t *globallistmem = NULL;
+static apr_pool_t *gpool = NULL;
+
+static apr_status_t slotmem_do(ap_slotmem_instance_t *mem, ap_slotmem_callback_fn_t *func, void *data, apr_pool_t *pool)
+{
+ unsigned int i;
+ char *ptr;
+ char *inuse;
+ apr_status_t retval = APR_SUCCESS;
+
+
+ if (!mem)
+ return APR_ENOSHMAVAIL;
+
+ ptr = (char *)mem->base;
+ inuse = mem->inuse;
+ for (i = 0; i < mem->num; i++, inuse++) {
+ if (!AP_SLOTMEM_IS_PREGRAB(mem) ||
+ (AP_SLOTMEM_IS_PREGRAB(mem) && *inuse)) {
+ retval = func((void *) ptr, data, pool);
+ if (retval != APR_SUCCESS)
+ break;
+ }
+ ptr += mem->size;
+ }
+ return retval;
+}
+
+static apr_status_t slotmem_create(ap_slotmem_instance_t **new, const char *name, apr_size_t item_size, unsigned int item_num, ap_slotmem_type_t type, apr_pool_t *pool)
+{
+ ap_slotmem_instance_t *res;
+ ap_slotmem_instance_t *next = globallistmem;
+ apr_size_t basesize = (item_size * item_num);
+
+ const char *fname;
+
+ if (name) {
+ if (name[0] == ':')
+ fname = name;
+ else
+ fname = ap_runtime_dir_relative(pool, name);
+
+ /* first try to attach to existing slotmem */
+ if (next) {
+ for (;;) {
+ if (strcmp(next->name, fname) == 0) {
+ /* we already have it */
+ *new = next;
+ return APR_SUCCESS;
+ }
+ if (!next->next) {
+ break;
+ }
+ next = next->next;
+ }
+ }
+ }
+ else
+ fname = "anonymous";
+
+ /* create the memory using the gpool */
+ res = (ap_slotmem_instance_t *) apr_pcalloc(gpool, sizeof(ap_slotmem_instance_t));
+ res->base = apr_pcalloc(gpool, basesize + (item_num * sizeof(char)));
+ if (!res->base)
+ return APR_ENOSHMAVAIL;
+
+ /* For the chained slotmem stuff */
+ res->name = apr_pstrdup(gpool, fname);
+ res->size = item_size;
+ res->num = item_num;
+ res->next = NULL;
+ res->type = type;
+ res->inuse = (char *)res->base + basesize;
+ if (globallistmem == NULL)
+ globallistmem = res;
+ else
+ next->next = res;
+
+ *new = res;
+ return APR_SUCCESS;
+}
+
+static apr_status_t slotmem_attach(ap_slotmem_instance_t **new, const char *name, apr_size_t *item_size, unsigned int *item_num, apr_pool_t *pool)
+{
+ ap_slotmem_instance_t *next = globallistmem;
+ const char *fname;
+
+ if (name) {
+ if (name[0] == ':')
+ fname = name;
+ else
+ fname = ap_runtime_dir_relative(pool, name);
+ }
+ else
+ return APR_ENOSHMAVAIL;
+
+ /* first try to attach to existing slotmem */
+ while (next) {
+ if (strcmp(next->name, fname) == 0) {
+ /* we already have it */
+ *new = next;
+ *item_size = next->size;
+ *item_num = next->num;
+ return APR_SUCCESS;
+ }
+ next = next->next;
+ }
+
+ return APR_ENOSHMAVAIL;
+}
+
+static apr_status_t slotmem_dptr(ap_slotmem_instance_t *score, unsigned int id, void **mem)
+{
+
+ char *ptr;
+
+ if (!score)
+ return APR_ENOSHMAVAIL;
+ if (id >= score->num)
+ return APR_EINVAL;
+
+ ptr = (char *)score->base + score->size * id;
+ if (!ptr)
+ return APR_ENOSHMAVAIL;
+ *mem = ptr;
+ return APR_SUCCESS;
+}
+
+static apr_status_t slotmem_get(ap_slotmem_instance_t *slot, unsigned int id, unsigned char *dest, apr_size_t dest_len)
+{
+ void *ptr;
+ char *inuse;
+ apr_status_t ret;
+
+ if (!slot) {
+ return APR_ENOSHMAVAIL;
+ }
+
+ inuse = slot->inuse + id;
+ if (id >= slot->num) {
+ return APR_EINVAL;
+ }
+ if (AP_SLOTMEM_IS_PREGRAB(slot) && !*inuse) {
+ return APR_NOTFOUND;
+ }
+ ret = slotmem_dptr(slot, id, &ptr);
+ if (ret != APR_SUCCESS) {
+ return ret;
+ }
+ *inuse=1;
+ memcpy(dest, ptr, dest_len); /* bounds check? */
+ return APR_SUCCESS;
+}
+
+static apr_status_t slotmem_put(ap_slotmem_instance_t *slot, unsigned int id, unsigned char *src, apr_size_t src_len)
+{
+ void *ptr;
+ char *inuse;
+ apr_status_t ret;
+
+ if (!slot) {
+ return APR_ENOSHMAVAIL;
+ }
+
+ inuse = slot->inuse + id;
+ if (id >= slot->num) {
+ return APR_EINVAL;
+ }
+ if (AP_SLOTMEM_IS_PREGRAB(slot) && !*inuse) {
+ return APR_NOTFOUND;
+ }
+ ret = slotmem_dptr(slot, id, &ptr);
+ if (ret != APR_SUCCESS) {
+ return ret;
+ }
+ *inuse=1;
+ memcpy(ptr, src, src_len); /* bounds check? */
+ return APR_SUCCESS;
+}
+
+static unsigned int slotmem_num_slots(ap_slotmem_instance_t *slot)
+{
+ return slot->num;
+}
+
+static unsigned int slotmem_num_free_slots(ap_slotmem_instance_t *slot)
+{
+ unsigned int i, counter=0;
+ char *inuse = slot->inuse;
+ for (i = 0; i < slot->num; i++, inuse++) {
+ if (!*inuse)
+ counter++;
+ }
+ return counter;
+}
+
+static apr_size_t slotmem_slot_size(ap_slotmem_instance_t *slot)
+{
+ return slot->size;
+}
+
+/*
+ * XXXX: if !AP_SLOTMEM_IS_PREGRAB, then still worry about
+ * inuse for grab and return?
+ */
+static apr_status_t slotmem_grab(ap_slotmem_instance_t *slot, unsigned int *id)
+{
+ unsigned int i;
+ char *inuse;
+
+ if (!slot) {
+ return APR_ENOSHMAVAIL;
+ }
+
+ inuse = slot->inuse;
+
+ for (i = 0; i < slot->num; i++, inuse++) {
+ if (!*inuse) {
+ break;
+ }
+ }
+ if (i >= slot->num) {
+ return APR_EINVAL;
+ }
+ *inuse = 1;
+ *id = i;
+ return APR_SUCCESS;
+}
+
+static apr_status_t slotmem_fgrab(ap_slotmem_instance_t *slot, unsigned int id)
+{
+ char *inuse;
+
+ if (!slot) {
+ return APR_ENOSHMAVAIL;
+ }
+
+ if (id >= slot->num) {
+ return APR_EINVAL;
+ }
+ inuse = slot->inuse + id;
+ *inuse = 1;
+ return APR_SUCCESS;
+}
+
+static apr_status_t slotmem_release(ap_slotmem_instance_t *slot, unsigned int id)
+{
+ char *inuse;
+
+ if (!slot) {
+ return APR_ENOSHMAVAIL;
+ }
+
+ inuse = slot->inuse;
+
+ if (id >= slot->num) {
+ return APR_EINVAL;
+ }
+ if (!inuse[id] ) {
+ return APR_NOTFOUND;
+ }
+ inuse[id] = 0;
+ return APR_SUCCESS;
+}
+
+static const ap_slotmem_provider_t storage = {
+ "plainmem",
+ &slotmem_do,
+ &slotmem_create,
+ &slotmem_attach,
+ &slotmem_dptr,
+ &slotmem_get,
+ &slotmem_put,
+ &slotmem_num_slots,
+ &slotmem_num_free_slots,
+ &slotmem_slot_size,
+ &slotmem_grab,
+ &slotmem_release,
+ &slotmem_fgrab
+};
+
+static int pre_config(apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp)
+{
+ gpool = p;
+ return OK;
+}
+
+static void ap_slotmem_plain_register_hook(apr_pool_t *p)
+{
+ /* XXX: static const char * const prePos[] = { "mod_slotmem.c", NULL }; */
+ ap_register_provider(p, AP_SLOTMEM_PROVIDER_GROUP, "plain",
+ AP_SLOTMEM_PROVIDER_VERSION, &storage);
+ ap_hook_pre_config(pre_config, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(slotmem_plain) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ NULL, /* command apr_table_t */
+ ap_slotmem_plain_register_hook /* register hooks */
+};
+
diff --git a/modules/slotmem/mod_slotmem_plain.dep b/modules/slotmem/mod_slotmem_plain.dep
new file mode 100644
index 0000000..274535e
--- /dev/null
+++ b/modules/slotmem/mod_slotmem_plain.dep
@@ -0,0 +1,51 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_slotmem_plain.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_slotmem_plain.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_slotmem.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/slotmem/mod_slotmem_plain.dsp b/modules/slotmem/mod_slotmem_plain.dsp
new file mode 100644
index 0000000..023e31c
--- /dev/null
+++ b/modules/slotmem/mod_slotmem_plain.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_slotmem_plain" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_slotmem_plain - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_slotmem_plain.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_slotmem_plain.mak" CFG="mod_slotmem_plain - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_slotmem_plain - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_slotmem_plain - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_slotmem_plain - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_slotmem_plain_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_slotmem_plain.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_slotmem_plain.so" /d LONG_NAME="slotmem_plain_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_slotmem_plain.so" /base:@..\..\os\win32\BaseAddr.ref,mod_slotmem_plain.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_slotmem_plain.so" /base:@..\..\os\win32\BaseAddr.ref,mod_slotmem_plain.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_slotmem_plain.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_slotmem_plain - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_slotmem_plain_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_slotmem_plain.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_slotmem_plain.so" /d LONG_NAME="slotmem_plain_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_slotmem_plain.so" /base:@..\..\os\win32\BaseAddr.ref,mod_slotmem_plain.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_slotmem_plain.so" /base:@..\..\os\win32\BaseAddr.ref,mod_slotmem_plain.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_slotmem_plain.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_slotmem_plain - Win32 Release"
+# Name "mod_slotmem_plain - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_slotmem_plain.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/slotmem/mod_slotmem_plain.mak b/modules/slotmem/mod_slotmem_plain.mak
new file mode 100644
index 0000000..4e7891a
--- /dev/null
+++ b/modules/slotmem/mod_slotmem_plain.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_slotmem_plain.dsp
+!IF "$(CFG)" == ""
+CFG=mod_slotmem_plain - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_slotmem_plain - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_slotmem_plain - Win32 Release" && "$(CFG)" != "mod_slotmem_plain - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_slotmem_plain.mak" CFG="mod_slotmem_plain - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_slotmem_plain - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_slotmem_plain - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_slotmem_plain - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_slotmem_plain.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_slotmem_plain.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_slotmem_plain.obj"
+ -@erase "$(INTDIR)\mod_slotmem_plain.res"
+ -@erase "$(INTDIR)\mod_slotmem_plain_src.idb"
+ -@erase "$(INTDIR)\mod_slotmem_plain_src.pdb"
+ -@erase "$(OUTDIR)\mod_slotmem_plain.exp"
+ -@erase "$(OUTDIR)\mod_slotmem_plain.lib"
+ -@erase "$(OUTDIR)\mod_slotmem_plain.pdb"
+ -@erase "$(OUTDIR)\mod_slotmem_plain.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_slotmem_plain_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_slotmem_plain.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_slotmem_plain.so" /d LONG_NAME="slotmem_plain_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_slotmem_plain.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_slotmem_plain.pdb" /debug /out:"$(OUTDIR)\mod_slotmem_plain.so" /implib:"$(OUTDIR)\mod_slotmem_plain.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_slotmem_plain.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_slotmem_plain.obj" \
+ "$(INTDIR)\mod_slotmem_plain.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_slotmem_plain.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_slotmem_plain.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_slotmem_plain.so"
+ if exist .\Release\mod_slotmem_plain.so.manifest mt.exe -manifest .\Release\mod_slotmem_plain.so.manifest -outputresource:.\Release\mod_slotmem_plain.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_slotmem_plain - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_slotmem_plain.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_slotmem_plain.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_slotmem_plain.obj"
+ -@erase "$(INTDIR)\mod_slotmem_plain.res"
+ -@erase "$(INTDIR)\mod_slotmem_plain_src.idb"
+ -@erase "$(INTDIR)\mod_slotmem_plain_src.pdb"
+ -@erase "$(OUTDIR)\mod_slotmem_plain.exp"
+ -@erase "$(OUTDIR)\mod_slotmem_plain.lib"
+ -@erase "$(OUTDIR)\mod_slotmem_plain.pdb"
+ -@erase "$(OUTDIR)\mod_slotmem_plain.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_slotmem_plain_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_slotmem_plain.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_slotmem_plain.so" /d LONG_NAME="slotmem_plain_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_slotmem_plain.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_slotmem_plain.pdb" /debug /out:"$(OUTDIR)\mod_slotmem_plain.so" /implib:"$(OUTDIR)\mod_slotmem_plain.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_slotmem_plain.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_slotmem_plain.obj" \
+ "$(INTDIR)\mod_slotmem_plain.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_slotmem_plain.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_slotmem_plain.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_slotmem_plain.so"
+ if exist .\Debug\mod_slotmem_plain.so.manifest mt.exe -manifest .\Debug\mod_slotmem_plain.so.manifest -outputresource:.\Debug\mod_slotmem_plain.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_slotmem_plain.dep")
+!INCLUDE "mod_slotmem_plain.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_slotmem_plain.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_slotmem_plain - Win32 Release" || "$(CFG)" == "mod_slotmem_plain - Win32 Debug"
+
+!IF "$(CFG)" == "mod_slotmem_plain - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\slotmem"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\slotmem"
+
+!ELSEIF "$(CFG)" == "mod_slotmem_plain - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\slotmem"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\slotmem"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_slotmem_plain - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\slotmem"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\slotmem"
+
+!ELSEIF "$(CFG)" == "mod_slotmem_plain - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\slotmem"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\slotmem"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_slotmem_plain - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\slotmem"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\slotmem"
+
+!ELSEIF "$(CFG)" == "mod_slotmem_plain - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\slotmem"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\slotmem"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_slotmem_plain - Win32 Release"
+
+
+"$(INTDIR)\mod_slotmem_plain.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_slotmem_plain.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_slotmem_plain.so" /d LONG_NAME="slotmem_plain_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_slotmem_plain - Win32 Debug"
+
+
+"$(INTDIR)\mod_slotmem_plain.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_slotmem_plain.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_slotmem_plain.so" /d LONG_NAME="slotmem_plain_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_slotmem_plain.c
+
+"$(INTDIR)\mod_slotmem_plain.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/slotmem/mod_slotmem_shm.c b/modules/slotmem/mod_slotmem_shm.c
new file mode 100644
index 0000000..f4eaa84
--- /dev/null
+++ b/modules/slotmem/mod_slotmem_shm.c
@@ -0,0 +1,788 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Memory handler for a shared memory divided in slot.
+ * This one uses shared memory.
+ *
+ * Shared memory is cleaned-up for each restart, graceful or
+ * otherwise.
+ */
+
+#include "ap_slotmem.h"
+
+#include "httpd.h"
+#include "http_main.h"
+#include "ap_mpm.h" /* for ap_mpm_query() */
+
+#define AP_SLOTMEM_IS_PREGRAB(t) (t->desc->type & AP_SLOTMEM_TYPE_PREGRAB)
+#define AP_SLOTMEM_IS_PERSIST(t) (t->desc->type & AP_SLOTMEM_TYPE_PERSIST)
+#define AP_SLOTMEM_IS_CLEARINUSE(t) (t->desc->type & AP_SLOTMEM_TYPE_CLEARINUSE)
+
+/* The description of the slots to reuse the slotmem */
+typedef struct {
+ apr_size_t size; /* size of each memory slot */
+ unsigned int num; /* number of mem slots */
+ ap_slotmem_type_t type; /* type-specific flags */
+} sharedslotdesc_t;
+
+#define AP_SLOTMEM_OFFSET (APR_ALIGN_DEFAULT(sizeof(sharedslotdesc_t)))
+#define AP_UNSIGNEDINT_OFFSET (APR_ALIGN_DEFAULT(sizeof(unsigned int)))
+
+struct ap_slotmem_instance_t {
+ char *name; /* file based SHM path/name */
+ char *pname; /* persisted file path/name */
+ int fbased; /* filebased? */
+ void *shm; /* ptr to memory segment (apr_shm_t *) */
+ void *base; /* data set start */
+ apr_pool_t *gpool; /* per segment pool (generation cleared) */
+ char *inuse; /* in-use flag table*/
+ unsigned int *num_free; /* slot free count for this instance */
+ void *persist; /* persist dataset start */
+ const sharedslotdesc_t *desc; /* per slot desc */
+ struct ap_slotmem_instance_t *next; /* location of next allocated segment */
+};
+
+/*
+ * Layout for SHM and persisted file :
+ *
+ * +-------------------------------------------------------------+~>
+ * | desc | num_free | base (slots) | inuse (array) | md5 | desc | compat..
+ * +------+-----------------------------------------+------------+~>
+ * ^ ^ ^ \ / ^ :
+ * |______|_____________ SHM (mem->@) ______________| | _____|__/
+ * | |/ |
+ * | ^ v |
+ * |_____________________ File (mem->persist + [meta]) __|
+ */
+
+/* global pool and list of slotmem we are handling */
+static struct ap_slotmem_instance_t *globallistmem = NULL;
+static apr_pool_t *gpool = NULL;
+
+#define DEFAULT_SLOTMEM_PREFIX "slotmem-shm-"
+#define DEFAULT_SLOTMEM_SUFFIX ".shm"
+#define DEFAULT_SLOTMEM_PERSIST_SUFFIX ".persist"
+
+/*
+ * Persist the slotmem in a file
+ * slotmem name and file name.
+ * none : no persistent data
+ * rel_name : $server_root/rel_name
+ * /abs_name : $abs_name
+ *
+ */
+static int slotmem_filenames(apr_pool_t *pool,
+ const char *slotname,
+ const char **filename,
+ const char **persistname)
+{
+ const char *fname = NULL, *pname = NULL;
+
+ if (slotname && *slotname && strcasecmp(slotname, "none") != 0) {
+ if (slotname[0] != '/') {
+ /* Each generation needs its own file name. */
+ int generation = 0;
+ ap_mpm_query(AP_MPMQ_GENERATION, &generation);
+ fname = apr_psprintf(pool, "%s%s_%x%s", DEFAULT_SLOTMEM_PREFIX,
+ slotname, generation, DEFAULT_SLOTMEM_SUFFIX);
+ fname = ap_runtime_dir_relative(pool, fname);
+ }
+ else {
+ /* Don't mangle the file name if given an absolute path, it's
+ * up to the caller to provide a unique name when necessary.
+ */
+ fname = slotname;
+ }
+
+ if (persistname) {
+ /* Persisted file names are immutable... */
+ if (slotname[0] != '/') {
+ pname = apr_pstrcat(pool, DEFAULT_SLOTMEM_PREFIX,
+ slotname, DEFAULT_SLOTMEM_SUFFIX,
+ DEFAULT_SLOTMEM_PERSIST_SUFFIX,
+ NULL);
+ pname = ap_runtime_dir_relative(pool, pname);
+ }
+ else {
+ pname = apr_pstrcat(pool, slotname,
+ DEFAULT_SLOTMEM_PERSIST_SUFFIX,
+ NULL);
+ }
+ }
+ }
+
+ *filename = fname;
+ if (persistname) {
+ *persistname = pname;
+ }
+ return (fname != NULL);
+}
+
+static void slotmem_clearinuse(ap_slotmem_instance_t *slot)
+{
+ unsigned int i;
+ char *inuse;
+
+ if (!slot) {
+ return;
+ }
+
+ inuse = slot->inuse;
+
+ for (i = 0; i < slot->desc->num; i++, inuse++) {
+ if (*inuse) {
+ *inuse = 0;
+ (*slot->num_free)++;
+ }
+ }
+}
+
+static void store_slotmem(ap_slotmem_instance_t *slotmem)
+{
+ apr_file_t *fp;
+ apr_status_t rv;
+ apr_size_t nbytes;
+ unsigned char digest[APR_MD5_DIGESTSIZE];
+ const char *storename = slotmem->pname;
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(02334)
+ "storing %s", storename);
+
+ if (storename) {
+ rv = apr_file_open(&fp, storename, APR_CREATE | APR_READ | APR_WRITE,
+ APR_OS_DEFAULT, slotmem->gpool);
+ if (APR_STATUS_IS_EEXIST(rv)) {
+ apr_file_remove(storename, slotmem->gpool);
+ rv = apr_file_open(&fp, storename, APR_CREATE | APR_READ | APR_WRITE,
+ APR_OS_DEFAULT, slotmem->gpool);
+ }
+ if (rv != APR_SUCCESS) {
+ return;
+ }
+ if (AP_SLOTMEM_IS_CLEARINUSE(slotmem)) {
+ slotmem_clearinuse(slotmem);
+ }
+ nbytes = (slotmem->desc->size * slotmem->desc->num) +
+ (slotmem->desc->num * sizeof(char)) + AP_UNSIGNEDINT_OFFSET;
+ apr_md5(digest, slotmem->persist, nbytes);
+ rv = apr_file_write_full(fp, slotmem->persist, nbytes, NULL);
+ if (rv == APR_SUCCESS) {
+ rv = apr_file_write_full(fp, digest, APR_MD5_DIGESTSIZE, NULL);
+ }
+ if (rv == APR_SUCCESS) {
+ rv = apr_file_write_full(fp, slotmem->desc, AP_SLOTMEM_OFFSET,
+ NULL);
+ }
+ apr_file_close(fp);
+ if (rv != APR_SUCCESS) {
+ apr_file_remove(storename, slotmem->gpool);
+ }
+ }
+}
+
+static apr_status_t restore_slotmem(sharedslotdesc_t *desc,
+ const char *storename, apr_size_t size,
+ apr_pool_t *pool)
+{
+ apr_file_t *fp;
+ apr_status_t rv = APR_ENOTIMPL;
+ void *ptr = (char *)desc + AP_SLOTMEM_OFFSET;
+ apr_size_t nbytes = size - AP_SLOTMEM_OFFSET;
+ unsigned char digest[APR_MD5_DIGESTSIZE];
+ unsigned char digest2[APR_MD5_DIGESTSIZE];
+ char desc_buf[AP_SLOTMEM_OFFSET];
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(02335)
+ "restoring %s", storename);
+
+ if (storename) {
+ rv = apr_file_open(&fp, storename, APR_READ | APR_WRITE, APR_OS_DEFAULT,
+ pool);
+ if (rv == APR_SUCCESS) {
+ rv = apr_file_read_full(fp, ptr, nbytes, NULL);
+ if (rv == APR_SUCCESS || rv == APR_EOF) {
+ /*
+ * if at EOF, don't bother checking md5
+ * - backwards compatibility
+ * */
+ if (apr_file_eof(fp) != APR_EOF) {
+ rv = apr_file_read_full(fp, digest, APR_MD5_DIGESTSIZE, NULL);
+ if (rv == APR_SUCCESS || rv == APR_EOF) {
+ apr_md5(digest2, ptr, nbytes);
+ if (memcmp(digest, digest2, APR_MD5_DIGESTSIZE)) {
+ rv = APR_EMISMATCH;
+ }
+ /*
+ * if at EOF, don't bother checking desc
+ * - backwards compatibility
+ * */
+ else if (apr_file_eof(fp) != APR_EOF) {
+ rv = apr_file_read_full(fp, desc_buf, sizeof(desc_buf), NULL);
+ if (rv == APR_SUCCESS || rv == APR_EOF) {
+ if (memcmp(desc, desc_buf, sizeof(desc_buf))) {
+ rv = APR_EMISMATCH;
+ }
+ else {
+ rv = APR_SUCCESS;
+ }
+ }
+ else {
+ rv = APR_INCOMPLETE;
+ }
+ }
+ else {
+ rv = APR_EOF;
+ }
+ }
+ else {
+ rv = APR_INCOMPLETE;
+ }
+ }
+ else {
+ rv = APR_EOF;
+ }
+ if (rv == APR_EMISMATCH) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(02551)
+ "persisted slotmem md5/desc mismatch");
+ }
+ else if (rv == APR_EOF) {
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(02552)
+ "persisted slotmem at EOF... bypassing md5/desc match check "
+ "(old persist file?)");
+ rv = APR_SUCCESS;
+ }
+ }
+ else {
+ rv = APR_INCOMPLETE;
+ }
+ if (rv == APR_INCOMPLETE) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(02553)
+ "persisted slotmem read had unexpected size");
+ }
+ apr_file_close(fp);
+ }
+ }
+ return rv;
+}
+
+/*
+ * Whether the module is called from a MPM that re-enter main() and
+ * pre/post_config phases.
+ */
+static APR_INLINE int is_child_process(void)
+{
+#ifdef WIN32
+ return getenv("AP_PARENT_PID") != NULL;
+#else
+ return 0;
+#endif
+}
+
+static apr_status_t cleanup_slotmem(void *param)
+{
+ int is_child = is_child_process();
+ ap_slotmem_instance_t *next = globallistmem;
+
+ while (next) {
+ if (!is_child && AP_SLOTMEM_IS_PERSIST(next)) {
+ store_slotmem(next);
+ }
+ apr_shm_destroy(next->shm);
+ apr_shm_remove(next->name, next->gpool);
+ next = next->next;
+ }
+
+ globallistmem = NULL;
+ return APR_SUCCESS;
+}
+
+static apr_status_t slotmem_doall(ap_slotmem_instance_t *mem,
+ ap_slotmem_callback_fn_t *func,
+ void *data, apr_pool_t *pool)
+{
+ unsigned int i;
+ char *ptr;
+ char *inuse;
+ apr_status_t retval = APR_SUCCESS;
+
+ if (!mem) {
+ return APR_ENOSHMAVAIL;
+ }
+
+ ptr = (char *)mem->base;
+ inuse = mem->inuse;
+ for (i = 0; i < mem->desc->num; i++, inuse++) {
+ if (!AP_SLOTMEM_IS_PREGRAB(mem) || *inuse) {
+ retval = func((void *) ptr, data, pool);
+ if (retval != APR_SUCCESS)
+ break;
+ }
+ ptr += mem->desc->size;
+ }
+ return retval;
+}
+
+static apr_status_t slotmem_create(ap_slotmem_instance_t **new,
+ const char *name, apr_size_t item_size,
+ unsigned int item_num,
+ ap_slotmem_type_t type, apr_pool_t *pool)
+{
+ int fbased = 1;
+ int restored = 0;
+ char *ptr;
+ sharedslotdesc_t *desc;
+ ap_slotmem_instance_t *res;
+ ap_slotmem_instance_t *next = globallistmem;
+ const char *fname, *pname = NULL;
+ apr_shm_t *shm;
+ apr_size_t basesize = (item_size * item_num);
+ apr_size_t size = AP_SLOTMEM_OFFSET + AP_UNSIGNEDINT_OFFSET +
+ (item_num * sizeof(char)) + basesize;
+ int persist = (type & AP_SLOTMEM_TYPE_PERSIST) != 0;
+ apr_status_t rv;
+
+ *new = NULL;
+ if (gpool == NULL) {
+ return APR_ENOSHMAVAIL;
+ }
+ if (slotmem_filenames(pool, name, &fname, persist ? &pname : NULL)) {
+ /* first try to attach to existing slotmem */
+ if (next) {
+ for (;;) {
+ if (strcmp(next->name, fname) == 0) {
+ /* we already have it */
+ *new = next;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(02603)
+ "create found %s in global list", fname);
+ return APR_SUCCESS;
+ }
+ if (!next->next) {
+ break;
+ }
+ next = next->next;
+ }
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(02602)
+ "create didn't find %s in global list", fname);
+ }
+ else {
+ fbased = 0;
+ fname = "none";
+ }
+
+ /* first try to attach to existing shared memory */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(02300)
+ "create %s: %"APR_SIZE_T_FMT"/%u", fname, item_size,
+ item_num);
+
+ {
+ /* For MPMs that run pre/post_config() phases in both the parent
+ * and children processes (e.g. winnt), SHMs created by the
+ * parent exist in the children already; attach them.
+ */
+ if (fbased) {
+ if (is_child_process()) {
+ rv = apr_shm_attach(&shm, fname, gpool);
+ }
+ else {
+ apr_shm_remove(fname, pool);
+ rv = apr_shm_create(&shm, size, fname, gpool);
+ }
+ }
+ else {
+ rv = apr_shm_create(&shm, size, NULL, gpool);
+ }
+ ap_log_error(APLOG_MARK, rv == APR_SUCCESS ? APLOG_DEBUG : APLOG_ERR,
+ rv, ap_server_conf, APLOGNO(02611)
+ "create: apr_shm_%s(%s) %s",
+ fbased && is_child_process() ? "attach" : "create",
+ fname, rv == APR_SUCCESS ? "succeeded" : "failed");
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ desc = (sharedslotdesc_t *)apr_shm_baseaddr_get(shm);
+ memset(desc, 0, size);
+ desc->size = item_size;
+ desc->num = item_num;
+ desc->type = type;
+
+ /*
+ * TODO: Error check the below... What error makes
+ * sense if the restore fails? Any?
+ * For now, we continue with a fresh new slotmem,
+ * but NOTICE in the log.
+ */
+ if (persist) {
+ rv = restore_slotmem(desc, pname, size, pool);
+ if (rv == APR_SUCCESS) {
+ restored = 1;
+ }
+ else {
+ /* just in case, re-zero */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf,
+ APLOGNO(02554) "could not restore %s", fname);
+ memset((char *)desc + AP_SLOTMEM_OFFSET, 0,
+ size - AP_SLOTMEM_OFFSET);
+ }
+ }
+ }
+
+ ptr = (char *)desc + AP_SLOTMEM_OFFSET;
+
+ /* For the chained slotmem stuff */
+ res = apr_pcalloc(gpool, sizeof(ap_slotmem_instance_t));
+ res->name = apr_pstrdup(gpool, fname);
+ res->pname = apr_pstrdup(gpool, pname);
+ res->fbased = fbased;
+ res->shm = shm;
+ res->persist = (void *)ptr;
+ res->num_free = (unsigned int *)ptr;
+ ptr += AP_UNSIGNEDINT_OFFSET;
+ if (!restored) {
+ *res->num_free = item_num;
+ }
+ res->base = (void *)ptr;
+ res->desc = desc;
+ res->gpool = gpool;
+ res->next = NULL;
+ res->inuse = ptr + basesize;
+ if (fbased) {
+ if (globallistmem == NULL) {
+ globallistmem = res;
+ }
+ else {
+ next->next = res;
+ }
+ }
+
+ *new = res;
+ return APR_SUCCESS;
+}
+
+static apr_status_t slotmem_attach(ap_slotmem_instance_t **new,
+ const char *name, apr_size_t *item_size,
+ unsigned int *item_num, apr_pool_t *pool)
+{
+ char *ptr;
+ ap_slotmem_instance_t *res;
+ ap_slotmem_instance_t *next = globallistmem;
+ sharedslotdesc_t *desc;
+ const char *fname;
+ apr_shm_t *shm;
+ apr_status_t rv;
+
+ if (gpool == NULL) {
+ return APR_ENOSHMAVAIL;
+ }
+ if (!slotmem_filenames(pool, name, &fname, NULL)) {
+ return APR_ENOSHMAVAIL;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(02301)
+ "attach looking for %s", fname);
+
+ /* first try to attach to existing slotmem */
+ if (next) {
+ for (;;) {
+ if (strcmp(next->name, fname) == 0) {
+ /* we already have it */
+ *new = next;
+ *item_size = next->desc->size;
+ *item_num = next->desc->num;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf,
+ APLOGNO(02302)
+ "attach found %s: %"APR_SIZE_T_FMT"/%u", fname,
+ *item_size, *item_num);
+ return APR_SUCCESS;
+ }
+ if (!next->next) {
+ break;
+ }
+ next = next->next;
+ }
+ }
+
+ /* next try to attach to existing shared memory */
+ rv = apr_shm_attach(&shm, fname, gpool);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ /* Read the description of the slotmem */
+ desc = (sharedslotdesc_t *)apr_shm_baseaddr_get(shm);
+ ptr = (char *)desc + AP_SLOTMEM_OFFSET;
+
+ /* For the chained slotmem stuff */
+ res = apr_pcalloc(gpool, sizeof(ap_slotmem_instance_t));
+ res->name = apr_pstrdup(gpool, fname);
+ res->fbased = 1;
+ res->shm = shm;
+ res->persist = (void *)ptr;
+ res->num_free = (unsigned int *)ptr;
+ ptr += AP_UNSIGNEDINT_OFFSET;
+ res->base = (void *)ptr;
+ res->desc = desc;
+ res->gpool = gpool;
+ res->inuse = ptr + (desc->size * desc->num);
+ res->next = NULL;
+
+ *new = res;
+ *item_size = desc->size;
+ *item_num = desc->num;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf,
+ APLOGNO(02303)
+ "attach found %s: %"APR_SIZE_T_FMT"/%u", fname,
+ *item_size, *item_num);
+ return APR_SUCCESS;
+}
+
+static apr_status_t slotmem_dptr(ap_slotmem_instance_t *slot,
+ unsigned int id, void **mem)
+{
+ char *ptr;
+
+ if (!slot) {
+ return APR_ENOSHMAVAIL;
+ }
+ if (id >= slot->desc->num) {
+ return APR_EINVAL;
+ }
+
+ ptr = (char *)slot->base + slot->desc->size * id;
+ if (!ptr) {
+ return APR_ENOSHMAVAIL;
+ }
+ *mem = (void *)ptr;
+ return APR_SUCCESS;
+}
+
+static apr_status_t slotmem_get(ap_slotmem_instance_t *slot, unsigned int id,
+ unsigned char *dest, apr_size_t dest_len)
+{
+ void *ptr;
+ char *inuse;
+ apr_status_t ret;
+
+ if (!slot) {
+ return APR_ENOSHMAVAIL;
+ }
+
+ inuse = slot->inuse + id;
+ if (id >= slot->desc->num) {
+ return APR_EINVAL;
+ }
+ if (AP_SLOTMEM_IS_PREGRAB(slot) && !*inuse) {
+ return APR_NOTFOUND;
+ }
+ ret = slotmem_dptr(slot, id, &ptr);
+ if (ret != APR_SUCCESS) {
+ return ret;
+ }
+ *inuse = 1;
+ memcpy(dest, ptr, dest_len); /* bounds check? */
+ return APR_SUCCESS;
+}
+
+static apr_status_t slotmem_put(ap_slotmem_instance_t *slot, unsigned int id,
+ unsigned char *src, apr_size_t src_len)
+{
+ void *ptr;
+ char *inuse;
+ apr_status_t ret;
+
+ if (!slot) {
+ return APR_ENOSHMAVAIL;
+ }
+
+ inuse = slot->inuse + id;
+ if (id >= slot->desc->num) {
+ return APR_EINVAL;
+ }
+ if (AP_SLOTMEM_IS_PREGRAB(slot) && !*inuse) {
+ return APR_NOTFOUND;
+ }
+ ret = slotmem_dptr(slot, id, &ptr);
+ if (ret != APR_SUCCESS) {
+ return ret;
+ }
+ *inuse=1;
+ memcpy(ptr, src, src_len); /* bounds check? */
+ return APR_SUCCESS;
+}
+
+static unsigned int slotmem_num_slots(ap_slotmem_instance_t *slot)
+{
+ return slot->desc->num;
+}
+
+static unsigned int slotmem_num_free_slots(ap_slotmem_instance_t *slot)
+{
+ if (AP_SLOTMEM_IS_PREGRAB(slot))
+ return *slot->num_free;
+ else {
+ unsigned int i, counter=0;
+ char *inuse = slot->inuse;
+ for (i=0; i<slot->desc->num; i++, inuse++) {
+ if (!*inuse)
+ counter++;
+ }
+ return counter;
+ }
+}
+
+static apr_size_t slotmem_slot_size(ap_slotmem_instance_t *slot)
+{
+ return slot->desc->size;
+}
+
+static apr_status_t slotmem_grab(ap_slotmem_instance_t *slot, unsigned int *id)
+{
+ unsigned int i;
+ char *inuse;
+
+ if (!slot) {
+ return APR_ENOSHMAVAIL;
+ }
+
+ inuse = slot->inuse;
+
+ for (i = 0; i < slot->desc->num; i++, inuse++) {
+ if (!*inuse) {
+ break;
+ }
+ }
+ if (i >= slot->desc->num) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(02293)
+ "slotmem(%s) grab failed. Num %u/num_free %u",
+ slot->name, slotmem_num_slots(slot),
+ slotmem_num_free_slots(slot));
+ return APR_EINVAL;
+ }
+ *inuse = 1;
+ *id = i;
+ (*slot->num_free)--;
+ return APR_SUCCESS;
+}
+
+static apr_status_t slotmem_fgrab(ap_slotmem_instance_t *slot, unsigned int id)
+{
+ char *inuse;
+
+ if (!slot) {
+ return APR_ENOSHMAVAIL;
+ }
+
+ if (id >= slot->desc->num) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(02397)
+ "slotmem(%s) fgrab failed. Num %u/num_free %u",
+ slot->name, slotmem_num_slots(slot),
+ slotmem_num_free_slots(slot));
+ return APR_EINVAL;
+ }
+ inuse = slot->inuse + id;
+
+ if (!*inuse) {
+ *inuse = 1;
+ (*slot->num_free)--;
+ }
+ return APR_SUCCESS;
+}
+
+static apr_status_t slotmem_release(ap_slotmem_instance_t *slot,
+ unsigned int id)
+{
+ char *inuse;
+
+ if (!slot) {
+ return APR_ENOSHMAVAIL;
+ }
+
+ inuse = slot->inuse;
+
+ if (id >= slot->desc->num || !inuse[id] ) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(02294)
+ "slotmem(%s) release failed. Num %u/inuse[%u] %d",
+ slot->name, slotmem_num_slots(slot),
+ id, (int)inuse[id]);
+ if (id >= slot->desc->num) {
+ return APR_EINVAL;
+ } else {
+ return APR_NOTFOUND;
+ }
+ }
+ inuse[id] = 0;
+ (*slot->num_free)++;
+ return APR_SUCCESS;
+}
+
+static const ap_slotmem_provider_t storage = {
+ "sharedmem",
+ &slotmem_doall,
+ &slotmem_create,
+ &slotmem_attach,
+ &slotmem_dptr,
+ &slotmem_get,
+ &slotmem_put,
+ &slotmem_num_slots,
+ &slotmem_num_free_slots,
+ &slotmem_slot_size,
+ &slotmem_grab,
+ &slotmem_release,
+ &slotmem_fgrab
+};
+
+/* make the storage usable from outside */
+static const ap_slotmem_provider_t *slotmem_shm_getstorage(void)
+{
+ return (&storage);
+}
+
+/*
+ * Make sure the shared memory is cleaned
+ */
+static int post_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp,
+ server_rec *s)
+{
+ apr_pool_cleanup_register(p, NULL, cleanup_slotmem, apr_pool_cleanup_null);
+ return OK;
+}
+
+static int pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp)
+{
+ gpool = p;
+ globallistmem = NULL;
+ return OK;
+}
+
+static void ap_slotmem_shm_register_hook(apr_pool_t *p)
+{
+ const ap_slotmem_provider_t *storage = slotmem_shm_getstorage();
+ ap_register_provider(p, AP_SLOTMEM_PROVIDER_GROUP, "shm",
+ AP_SLOTMEM_PROVIDER_VERSION, storage);
+ ap_hook_post_config(post_config, NULL, NULL, APR_HOOK_LAST);
+ ap_hook_pre_config(pre_config, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(slotmem_shm) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ NULL, /* command apr_table_t */
+ ap_slotmem_shm_register_hook /* register hooks */
+};
diff --git a/modules/slotmem/mod_slotmem_shm.dep b/modules/slotmem/mod_slotmem_shm.dep
new file mode 100644
index 0000000..e3320b8
--- /dev/null
+++ b/modules/slotmem/mod_slotmem_shm.dep
@@ -0,0 +1,57 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_slotmem_shm.mak
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
+
+.\mod_slotmem_shm.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_mpm.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_slotmem.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+
diff --git a/modules/slotmem/mod_slotmem_shm.dsp b/modules/slotmem/mod_slotmem_shm.dsp
new file mode 100644
index 0000000..615ddd4
--- /dev/null
+++ b/modules/slotmem/mod_slotmem_shm.dsp
@@ -0,0 +1,111 @@
+# Microsoft Developer Studio Project File - Name="mod_slotmem_shm" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_slotmem_shm - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_slotmem_shm.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_slotmem_shm.mak" CFG="mod_slotmem_shm - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_slotmem_shm - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_slotmem_shm - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_slotmem_shm - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_slotmem_shm_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_slotmem_shm.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_slotmem_shm.so" /d LONG_NAME="slotmem_shm_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_slotmem_shm.so" /base:@..\..\os\win32\BaseAddr.ref,mod_slotmem_shm.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_slotmem_shm.so" /base:@..\..\os\win32\BaseAddr.ref,mod_slotmem_shm.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_slotmem_shm.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_slotmem_shm - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_slotmem_shm_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_slotmem_shm.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_slotmem_shm.so" /d LONG_NAME="slotmem_shm_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_slotmem_shm.so" /base:@..\..\os\win32\BaseAddr.ref,mod_slotmem_shm.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_slotmem_shm.so" /base:@..\..\os\win32\BaseAddr.ref,mod_slotmem_shm.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_slotmem_shm.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_slotmem_shm - Win32 Release"
+# Name "mod_slotmem_shm - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_slotmem_shm.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/slotmem/mod_slotmem_shm.mak b/modules/slotmem/mod_slotmem_shm.mak
new file mode 100644
index 0000000..e7e64b8
--- /dev/null
+++ b/modules/slotmem/mod_slotmem_shm.mak
@@ -0,0 +1,353 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_slotmem_shm.dsp
+!IF "$(CFG)" == ""
+CFG=mod_slotmem_shm - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_slotmem_shm - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_slotmem_shm - Win32 Release" && "$(CFG)" != "mod_slotmem_shm - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_slotmem_shm.mak" CFG="mod_slotmem_shm - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_slotmem_shm - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_slotmem_shm - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "mod_slotmem_shm - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_slotmem_shm.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_slotmem_shm.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_slotmem_shm.obj"
+ -@erase "$(INTDIR)\mod_slotmem_shm.res"
+ -@erase "$(INTDIR)\mod_slotmem_shm_src.idb"
+ -@erase "$(INTDIR)\mod_slotmem_shm_src.pdb"
+ -@erase "$(OUTDIR)\mod_slotmem_shm.exp"
+ -@erase "$(OUTDIR)\mod_slotmem_shm.lib"
+ -@erase "$(OUTDIR)\mod_slotmem_shm.pdb"
+ -@erase "$(OUTDIR)\mod_slotmem_shm.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_slotmem_shm_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_slotmem_shm.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_slotmem_shm.so" /d LONG_NAME="slotmem_shm_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_slotmem_shm.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_slotmem_shm.pdb" /debug /out:"$(OUTDIR)\mod_slotmem_shm.so" /implib:"$(OUTDIR)\mod_slotmem_shm.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_slotmem_shm.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_slotmem_shm.obj" \
+ "$(INTDIR)\mod_slotmem_shm.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_slotmem_shm.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_slotmem_shm.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_slotmem_shm.so"
+ if exist .\Release\mod_slotmem_shm.so.manifest mt.exe -manifest .\Release\mod_slotmem_shm.so.manifest -outputresource:.\Release\mod_slotmem_shm.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_slotmem_shm - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_slotmem_shm.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_slotmem_shm.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_slotmem_shm.obj"
+ -@erase "$(INTDIR)\mod_slotmem_shm.res"
+ -@erase "$(INTDIR)\mod_slotmem_shm_src.idb"
+ -@erase "$(INTDIR)\mod_slotmem_shm_src.pdb"
+ -@erase "$(OUTDIR)\mod_slotmem_shm.exp"
+ -@erase "$(OUTDIR)\mod_slotmem_shm.lib"
+ -@erase "$(OUTDIR)\mod_slotmem_shm.pdb"
+ -@erase "$(OUTDIR)\mod_slotmem_shm.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_slotmem_shm_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_slotmem_shm.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_slotmem_shm.so" /d LONG_NAME="slotmem_shm_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_slotmem_shm.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_slotmem_shm.pdb" /debug /out:"$(OUTDIR)\mod_slotmem_shm.so" /implib:"$(OUTDIR)\mod_slotmem_shm.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_slotmem_shm.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_slotmem_shm.obj" \
+ "$(INTDIR)\mod_slotmem_shm.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_slotmem_shm.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_slotmem_shm.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_slotmem_shm.so"
+ if exist .\Debug\mod_slotmem_shm.so.manifest mt.exe -manifest .\Debug\mod_slotmem_shm.so.manifest -outputresource:.\Debug\mod_slotmem_shm.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_slotmem_shm.dep")
+!INCLUDE "mod_slotmem_shm.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_slotmem_shm.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_slotmem_shm - Win32 Release" || "$(CFG)" == "mod_slotmem_shm - Win32 Debug"
+
+!IF "$(CFG)" == "mod_slotmem_shm - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\slotmem"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\slotmem"
+
+!ELSEIF "$(CFG)" == "mod_slotmem_shm - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\slotmem"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\slotmem"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_slotmem_shm - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\slotmem"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\slotmem"
+
+!ELSEIF "$(CFG)" == "mod_slotmem_shm - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\slotmem"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\slotmem"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_slotmem_shm - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\slotmem"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\slotmem"
+
+!ELSEIF "$(CFG)" == "mod_slotmem_shm - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\slotmem"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\slotmem"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_slotmem_shm - Win32 Release"
+
+
+"$(INTDIR)\mod_slotmem_shm.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_slotmem_shm.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_slotmem_shm.so" /d LONG_NAME="slotmem_shm_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_slotmem_shm - Win32 Debug"
+
+
+"$(INTDIR)\mod_slotmem_shm.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_slotmem_shm.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_slotmem_shm.so" /d LONG_NAME="slotmem_shm_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+SOURCE=.\mod_slotmem_shm.c
+
+"$(INTDIR)\mod_slotmem_shm.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/modules/ssl/Makefile.in b/modules/ssl/Makefile.in
new file mode 100644
index 0000000..4395bc3
--- /dev/null
+++ b/modules/ssl/Makefile.in
@@ -0,0 +1,20 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# standard stuff
+#
+
+include $(top_srcdir)/build/special.mk
diff --git a/modules/ssl/NWGNUmakefile b/modules/ssl/NWGNUmakefile
new file mode 100644
index 0000000..933180f
--- /dev/null
+++ b/modules/ssl/NWGNUmakefile
@@ -0,0 +1,327 @@
+#
+# This Makefile requires the environment var OSSLSDK
+# pointing to the base directory of your OpenSSL SDK.
+# If you want to use the Novell NTLS SDK instead then
+# define NTLSSDK pointing to the base directory of the
+# SDK, and also set USE_NTLS=1
+#
+
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+#
+# Make sure all needed macro's are defined
+#
+
+ifeq "$(USE_NTLS)" "1"
+SSL_INC = $(NTLSSDK)/inc
+SSL_LIB = $(NTLSSDK)/imp
+SSL_BIN = $(NTLSSDK)/bin
+SSL_APP = $(NTLSSDK)/apps
+ifneq "$(wildcard $(SSL_INC)/openssl/opensslv.h)" "$(SSL_INC)/openssl/opensslv.h"
+$(error '$(NTLSSDK)' does NOT point to a valid NTLS SDK!)
+endif
+else
+SSL_INC = $(OSSLSDK)/outinc_nw_libc
+SSL_LIB = $(OSSLSDK)/out_nw_libc
+SSL_BIN = $(OSSLSDK)/out_nw_libc
+SSL_APP = $(OSSLSDK)/apps
+ifneq "$(wildcard $(SSL_INC)/openssl/opensslv.h)" "$(SSL_INC)/openssl/opensslv.h"
+$(error '$(OSSLSDK)' does NOT point to a valid OpenSSL SDK!)
+endif
+endif
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(SSL_INC) \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(AP_WORK)/modules/cache \
+ $(AP_WORK)/modules/generators \
+ $(AP_WORK)/server/mpm/NetWare \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ -DHAVE_OPENSSL \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ -l $(SSL_LIB) \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = mod_ssl
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+ifeq "$(USE_NTLS)" "1"
+NLM_DESCRIPTION = Apache $(VERSION_STR) SSL module (NTLS)
+else
+NLM_DESCRIPTION = Apache $(VERSION_STR) SSL module (OpenSSL)
+endif
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = $(NLM_NAME)
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If this is specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# Declare all target files (you must add your files here)
+#
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs := $(patsubst %.c,$(OBJDIR)/%.o,$(wildcard *.c))
+
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+ifneq "$(USE_NTLS)" "1"
+FILES_nlm_libs += \
+ $(SSL_LIB)/crypto.lib \
+ $(SSL_LIB)/ssl.lib \
+ $(EOLIST)
+endif
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ Apache2 \
+ Libc \
+ $(EOLIST)
+
+ifeq "$(USE_NTLS)" "1"
+FILES_nlm_modules += ntls \
+ $(EOLIST)
+endif
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @libc.imp \
+ @aprlib.imp \
+ @httpd.imp \
+ $(EOLIST)
+
+# Don't link with Winsock if standard sockets are being used
+ifneq "$(USE_STDSOCKETS)" "1"
+FILES_nlm_Ximports += @ws2nlm.imp \
+ $(EOLIST)
+endif
+
+ifeq "$(USE_NTLS)" "1"
+FILES_nlm_Ximports += @ntls.imp \
+ $(EOLIST)
+else
+FILES_nlm_Ximports += \
+ GetProcessSwitchCount \
+ RunningProcess \
+ GetSuperHighResolutionTimer \
+ $(EOLIST)
+endif
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ ssl_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+ $(call COPY,$(SSL_BIN)/openssl.nlm, $(INSTALLBASE)/bin/)
+ $(call COPY,$(SSL_APP)/openssl.cnf, $(INSTALLBASE)/bin/)
+
+#
+# Any specialized rules here
+#
+vpath %.c $(AP_WORK)/modules/arch/netware
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/ssl/README b/modules/ssl/README
new file mode 100644
index 0000000..5fc7312
--- /dev/null
+++ b/modules/ssl/README
@@ -0,0 +1,106 @@
+SYNOPSIS
+
+ This Apache module provides strong cryptography for the Apache 2 webserver
+ via the Secure Sockets Layer (SSL v2/v3) and Transport Layer Security (TLS
+ v1) protocols by the help of the SSL/TLS implementation library OpenSSL which
+ is based on SSLeay from Eric A. Young and Tim J. Hudson.
+
+ The mod_ssl package was created in April 1998 by Ralf S. Engelschall
+ and was originally derived from software developed by Ben Laurie for
+ use in the Apache-SSL HTTP server project. The mod_ssl implementation
+ for Apache 1.3 continues to be supported by the modssl project
+ <http://www.modssl.org/>.
+
+SOURCES
+
+ See the top-level LAYOUT file for file descriptions.
+
+ The source files are written in clean ANSI C and pass the ``gcc -O -g
+ -ggdb3 -Wall -Wshadow -Wpointer-arith -Wcast-align -Wmissing-prototypes
+ -Wmissing-declarations -Wnested-externs -Winline'' compiler test
+ (assuming `gcc' is GCC 2.95.2 or newer) without any complains. When
+ you make changes or additions make sure the source still passes this
+ compiler test.
+
+FUNCTIONS
+
+ Inside the source code you will be confronted with the following types of
+ functions which can be identified by their prefixes:
+
+ ap_xxxx() ............... Apache API function
+ ssl_xxxx() .............. mod_ssl function
+ SSL_xxxx() .............. OpenSSL function (SSL library)
+ OpenSSL_xxxx() .......... OpenSSL function (SSL library)
+ X509_xxxx() ............. OpenSSL function (Crypto library)
+ PEM_xxxx() .............. OpenSSL function (Crypto library)
+ EVP_xxxx() .............. OpenSSL function (Crypto library)
+ RSA_xxxx() .............. OpenSSL function (Crypto library)
+
+DATA STRUCTURES
+
+ Inside the source code you will be confronted with the following
+ data structures:
+
+ server_rec .............. Apache (Virtual) Server
+ conn_rec ................ Apache Connection
+ request_rec ............. Apache Request
+ SSLModConfig ............ mod_ssl (Global) Module Configuration
+ SSLSrvConfig ............ mod_ssl (Virtual) Server Configuration
+ SSLDirConfig ............ mod_ssl Directory Configuration
+ SSLConnConfig ........... mod_ssl Connection Configuration
+ SSLFilterRec ............ mod_ssl Filter Context
+ SSL_CTX ................. OpenSSL Context
+ SSL_METHOD .............. OpenSSL Protocol Method
+ SSL_CIPHER .............. OpenSSL Cipher
+ SSL_SESSION ............. OpenSSL Session
+ SSL ..................... OpenSSL Connection
+ BIO ..................... OpenSSL Connection Buffer
+
+ For an overview how these are related and chained together have a look at the
+ page in README.dsov.{fig,ps}. It contains overview diagrams for those data
+ structures. It's designed for DIN A4 paper size, but you can easily generate
+ a smaller version inside XFig by specifying a magnification on the Export
+ panel.
+
+INCOMPATIBILITIES
+
+ The following intentional incompatibilities exist between mod_ssl 2.x
+ from Apache 1.3 and this mod_ssl version for Apache 2:
+
+ o The complete EAPI-based SSL_VENDOR stuff was removed.
+ o The complete EAPI-based SSL_COMPAT stuff was removed.
+ o The <IfDefine> variable MOD_SSL is no longer provided automatically
+
+MAJOR CHANGES
+
+ For a complete history of changes for Apache 2 mod_ssl, see the
+ CHANGES file in the top-level directory. The following
+ is a condensed summary of the major changes were made between
+ mod_ssl 2.x from Apache 1.3 and this mod_ssl version for Apache 2:
+
+ o The DBM based session cache is now based on APR's DBM API only.
+ o The shared memory based session cache is now based on APR's APIs.
+ o SSL I/O is now implemented in terms of filters rather than BUFF
+ o Eliminated ap_global_ctx. Storing Persistent information in
+ process_rec->pool->user_data. The ssl_pphrase_Handle_CB() and
+ ssl_config_global_* () functions have an extra parameter now -
+ "server_rec *" - which is used to retrieve the SSLModConfigRec.
+ o Properly support restarts, allowing mod_ssl to be added to a server
+ that is already running and to change server certs/keys on restart
+ o Various performance enhancements
+ o proxy support is no longer an "extension", much of the mod_ssl core
+ was re-written (ssl_engine_{init,kernel,config}.c) to be generic so
+ it could be re-used in proxy mode.
+ - the optional function ssl_proxy_enable is provide for mod_proxy
+ to enable proxy support
+ - proxy support now requires 'SSLProxyEngine on' to be configured
+ - proxy now supports SSLProxyCARevocation{Path,File} in addition to
+ the original SSLProxy* directives
+ o per-directory SSLCACertificate{File,Path} is now thread-safe but
+ requires SSL_set_cert_store patch to OpenSSL
+ o the ssl_engine_{ds,ext}.c source files are obsolete and no longer
+ exist
+
+TODO
+
+ See the top-level STATUS file for current efforts and goals.
diff --git a/modules/ssl/README.dsov.fig b/modules/ssl/README.dsov.fig
new file mode 100644
index 0000000..77cd2ca
--- /dev/null
+++ b/modules/ssl/README.dsov.fig
@@ -0,0 +1,346 @@
+#FIG 3.2
+Landscape
+Center
+Metric
+Letter
+100.00
+Single
+-2
+1200 2
+0 32 #616561
+0 33 #b6b2b6
+0 34 #f7f3f7
+0 35 #cfcfcf
+0 36 #ffffff
+6 6345 2835 7155 3150
+6 6345 2970 7110 3150
+4 0 0 200 0 20 8 0.0000 4 120 585 6345 3105 "ssl_module")\001
+-6
+4 0 0 200 0 20 8 0.0000 4 120 660 6345 2970 ap_ctx_get(...,\001
+-6
+6 10800 2610 12240 3060
+4 0 0 200 0 20 8 0.0000 4 120 1170 10800 2745 ap_get_module_config(...\001
+4 0 0 200 0 20 8 0.0000 4 120 795 10800 2880 ->per_dir_config,\001
+4 0 0 200 0 20 8 0.0000 4 120 585 10800 3015 &ssl_module)\001
+-6
+6 7920 4770 9135 4995
+2 4 0 1 35 35 200 0 20 0.000 0 0 4 0 0 5
+ 9135 4995 7920 4995 7920 4770 9135 4770 9135 4995
+4 0 0 100 0 18 12 0.0000 4 180 1065 8010 4950 request_rec\001
+-6
+2 1 0 1 0 34 200 0 20 0.000 0 0 -1 1 0 2
+ 1 1 1.00 60.00 120.00
+ 6975 3330 7425 2520
+2 1 0 1 0 34 200 0 20 0.000 0 0 -1 1 0 2
+ 1 1 1.00 60.00 120.00
+ 7200 4230 9450 2520
+2 1 0 1 0 34 200 0 20 0.000 0 0 -1 1 0 2
+ 1 1 1.00 60.00 120.00
+ 7875 4905 7200 5220
+2 1 0 1 0 34 200 0 20 0.000 0 0 -1 1 0 2
+ 1 1 1.00 60.00 120.00
+ 6750 5130 6750 4545
+2 1 0 1 0 34 200 0 20 0.000 0 0 -1 1 0 2
+ 1 1 1.00 60.00 120.00
+ 6705 5445 7155 6120
+2 1 0 1 0 34 200 0 20 0.000 0 0 -1 1 0 2
+ 1 1 1.00 60.00 120.00
+ 7875 4815 7200 4590
+2 1 0 1 0 34 200 0 20 0.000 0 0 -1 1 0 2
+ 1 1 1.00 60.00 120.00
+ 9585 2565 11475 4230
+2 1 0 1 0 34 200 0 20 0.000 0 0 -1 1 0 2
+ 1 1 1.00 60.00 120.00
+ 10170 5130 11835 4545
+2 1 0 1 0 34 200 0 20 0.000 0 0 -1 1 0 2
+ 1 1 1.00 60.00 120.00
+ 7920 6075 9855 5400
+2 1 0 1 0 34 200 0 20 0.000 0 0 -1 1 0 2
+ 1 1 1.00 60.00 120.00
+ 9990 5445 10935 5625
+2 1 0 1 0 34 200 0 20 0.000 0 0 -1 1 0 2
+ 1 1 1.00 60.00 120.00
+ 10215 5310 10935 5310
+2 1 0 1 0 34 200 0 20 0.000 0 0 -1 1 0 2
+ 1 1 1.00 60.00 120.00
+ 11925 4590 11925 5085
+2 1 0 1 0 34 200 0 20 0.000 0 0 -1 1 0 2
+ 1 1 1.00 60.00 120.00
+ 9810 5490 9810 6840
+2 1 0 1 0 34 200 0 20 0.000 0 0 -1 1 0 2
+ 1 1 1.00 60.00 120.00
+ 9945 5445 10935 6030
+2 1 0 1 0 34 200 0 20 0.000 0 0 -1 1 0 2
+ 1 1 1.00 60.00 120.00
+ 8865 4725 10800 2565
+2 1 0 3 0 34 200 0 20 0.000 0 0 -1 0 0 2
+ 675 6075 5850 6075
+2 1 2 1 0 34 200 0 20 1.000 0 0 -1 1 0 2
+ 1 1 1.00 60.00 120.00
+ 675 6525 675 6075
+2 1 2 1 0 34 200 0 20 1.000 0 0 -1 1 0 2
+ 1 0 1.00 60.00 120.00
+ 5850 6075 5850 6525
+2 1 0 3 0 34 200 0 20 0.000 0 0 -1 0 0 2
+ 900 5625 5625 5625
+2 1 0 3 0 34 200 0 20 0.000 0 0 -1 0 0 2
+ 1125 5175 5400 5175
+2 1 0 3 0 34 200 0 20 0.000 0 0 -1 0 0 2
+ 1350 4725 5175 4725
+2 1 0 3 0 34 200 0 20 0.000 0 0 -1 0 0 2
+ 1575 4275 4950 4275
+2 1 0 3 0 34 200 0 20 0.000 0 0 -1 0 0 2
+ 1800 3825 4725 3825
+2 1 0 3 0 34 200 0 20 0.000 0 0 -1 0 0 2
+ 2025 3375 4500 3375
+2 1 0 3 0 34 200 0 20 0.000 0 0 -1 0 0 2
+ 2250 2925 4275 2925
+2 1 0 3 0 34 200 0 20 0.000 0 0 -1 0 0 2
+ 2475 2475 4050 2475
+2 1 0 3 0 34 200 0 20 0.000 0 0 -1 0 0 2
+ 2700 2025 3825 2025
+2 1 0 3 0 34 200 0 20 0.000 0 0 -1 0 0 2
+ 2925 1575 3600 1575
+2 1 2 1 0 34 200 0 20 1.000 0 0 -1 1 0 2
+ 1 1 1.00 60.00 120.00
+ 900 6075 900 5625
+2 1 2 1 0 34 200 0 20 1.000 0 0 -1 1 0 2
+ 1 1 1.00 60.00 120.00
+ 1125 6525 1125 5175
+2 1 2 1 0 34 200 0 20 1.000 0 0 -1 1 0 2
+ 1 1 1.00 60.00 120.00
+ 1350 5175 1350 4725
+2 1 2 1 0 34 200 0 20 1.000 0 0 -1 1 0 2
+ 1 1 1.00 60.00 120.00
+ 1575 4725 1575 4275
+2 1 2 1 0 34 200 0 20 1.000 0 0 -1 1 0 2
+ 1 1 1.00 60.00 120.00
+ 1800 6525 1800 3825
+2 1 2 1 0 34 200 0 20 1.000 0 0 -1 1 0 2
+ 1 1 1.00 60.00 120.00
+ 2025 3825 2025 3375
+2 1 2 1 0 34 200 0 20 1.000 0 0 -1 1 0 2
+ 1 1 1.00 60.00 120.00
+ 2250 3375 2250 2925
+2 1 2 1 0 34 200 0 20 1.000 0 0 -1 1 0 2
+ 1 1 1.00 60.00 120.00
+ 2475 2925 2475 2475
+2 1 2 1 0 34 200 0 20 1.000 0 0 -1 1 0 2
+ 1 0 1.00 60.00 120.00
+ 5625 5625 5625 6075
+2 1 2 1 0 34 200 0 20 1.000 0 0 -1 1 0 2
+ 1 0 1.00 60.00 120.00
+ 5400 5175 5400 6525
+2 1 2 1 0 34 200 0 20 1.000 0 0 -1 1 0 2
+ 1 0 1.00 60.00 120.00
+ 5175 4725 5175 5175
+2 1 2 1 0 34 200 0 20 1.000 0 0 -1 1 0 2
+ 1 0 1.00 60.00 120.00
+ 4950 4275 4950 4725
+2 1 2 1 0 34 200 0 20 1.000 0 0 -1 1 0 2
+ 1 0 1.00 60.00 120.00
+ 4725 3825 4725 6525
+2 1 2 1 0 34 200 0 20 1.000 0 0 -1 1 0 2
+ 1 0 1.00 60.00 120.00
+ 4500 3375 4500 3825
+2 1 2 1 0 34 200 0 20 1.000 0 0 -1 1 0 2
+ 1 0 1.00 60.00 120.00
+ 4275 2925 4275 3375
+2 1 2 1 0 34 200 0 20 1.000 0 0 -1 1 0 2
+ 1 0 1.00 60.00 120.00
+ 4050 2475 4050 2925
+2 1 2 1 0 34 200 0 20 1.000 0 0 -1 1 0 2
+ 1 1 1.00 60.00 120.00
+ 2700 6525 2700 2025
+2 1 2 1 0 34 200 0 20 1.000 0 0 -1 1 0 2
+ 1 0 1.00 60.00 120.00
+ 3825 2025 3825 6525
+2 1 2 1 0 34 200 0 20 1.000 0 0 -1 1 0 2
+ 1 0 1.00 60.00 120.00
+ 3600 1575 3600 2025
+2 1 2 1 0 34 200 0 20 1.000 0 0 -1 1 0 2
+ 1 1 1.00 60.00 120.00
+ 2925 2025 2925 1575
+2 1 0 4 0 0 200 0 20 0.000 0 0 -1 1 0 2
+ 1 1 4.00 60.00 120.00
+ 540 6525 6300 6525
+2 3 0 1 7 7 800 0 20 0.000 0 0 -1 0 0 9
+ 675 6525 5850 6525 5850 6075 5625 6075 5625 5625 900 5625
+ 900 6075 675 6075 675 6525
+2 3 0 1 34 34 700 0 20 0.000 0 0 -1 0 0 13
+ 1125 6525 5355 6525 5400 5175 5175 5175 5175 4725 4950 4725
+ 4950 4275 1575 4275 1575 4725 1350 4725 1350 5175 1125 5175
+ 1125 6525
+2 3 0 1 35 35 500 0 20 0.000 0 0 -1 0 0 17
+ 1800 6525 4725 6525 4725 3825 4500 3825 4500 3375 4275 3375
+ 4275 2925 4050 2925 4050 2475 2475 2475 2475 2925 2250 2925
+ 2250 3375 2025 3375 2025 3825 1800 3825 1800 6525
+2 3 0 1 33 33 400 0 20 0.000 0 0 -1 0 0 9
+ 2700 6525 3825 6525 3825 2025 3600 2025 3600 1575 2925 1575
+ 2925 2025 2700 2025 2700 6525
+2 1 0 1 0 34 200 0 20 0.000 0 0 -1 1 1 2
+ 2 0 1.00 60.00 120.00
+ 2 0 1.00 60.00 120.00
+ 2700 6750 3825 6750
+2 1 0 1 0 34 200 0 20 0.000 0 0 -1 1 1 2
+ 2 0 1.00 60.00 120.00
+ 2 0 1.00 60.00 120.00
+ 1125 7200 5400 7200
+2 1 0 1 0 34 200 0 20 0.000 0 0 -1 1 1 2
+ 2 0 1.00 60.00 120.00
+ 2 0 1.00 60.00 120.00
+ 1800 6975 4725 6975
+2 1 0 1 0 34 200 0 20 0.000 0 0 -1 1 1 2
+ 2 0 1.00 60.00 120.00
+ 2 0 1.00 60.00 120.00
+ 675 7425 5850 7425
+2 1 2 1 0 34 200 0 20 3.000 0 1 -1 0 0 2
+ 675 6570 675 7650
+2 1 2 1 0 34 200 0 20 3.000 0 1 -1 0 0 2
+ 1125 6570 1125 7650
+2 1 2 1 0 34 200 0 20 3.000 0 1 -1 0 0 2
+ 1800 6570 1800 7650
+2 1 2 1 0 34 200 0 20 3.000 0 1 -1 0 0 2
+ 2700 6570 2700 7650
+2 1 2 1 0 34 200 0 20 3.000 0 1 -1 0 0 2
+ 3825 6570 3825 7650
+2 1 2 1 0 34 200 0 20 3.000 0 1 -1 0 0 2
+ 4725 6570 4725 7650
+2 1 2 1 0 34 200 0 20 3.000 0 1 -1 0 0 2
+ 5400 6570 5400 7650
+2 1 2 1 0 34 200 0 20 3.000 0 1 -1 0 0 2
+ 5850 6570 5850 7650
+2 4 0 2 0 7 100 0 -1 0.000 0 0 20 0 0 5
+ 12600 8550 450 8550 450 225 12600 225 12600 8550
+2 4 0 1 0 34 200 0 20 0.000 0 0 20 0 0 5
+ 12600 1350 450 1350 450 225 12600 225 12600 1350
+2 4 0 1 35 35 200 0 20 0.000 0 0 4 0 0 5
+ 10170 2475 8775 2475 8775 2250 10170 2250 10170 2475
+2 4 0 1 35 35 200 0 20 0.000 0 0 4 0 0 5
+ 11925 2475 10575 2475 10575 2250 11925 2250 11925 2475
+2 4 0 1 35 35 200 0 20 0.000 0 0 4 0 0 5
+ 12375 4500 11430 4500 11430 4275 12375 4275 12375 4500
+2 4 0 1 35 35 200 0 20 0.000 0 0 4 0 0 5
+ 12375 5400 10980 5400 10980 5175 12375 5175 12375 5400
+2 4 0 1 35 35 200 0 20 0.000 0 0 4 0 0 5
+ 10170 5400 9675 5400 9675 5175 10170 5175 10170 5400
+2 4 0 1 35 35 200 0 20 0.000 0 0 4 0 0 5
+ 7875 6300 7200 6300 7200 6075 7875 6075 7875 6300
+2 4 0 1 35 35 200 0 20 0.000 0 0 4 0 0 5
+ 8190 2475 6750 2475 6750 2250 8190 2250 8190 2475
+2 4 0 1 35 35 200 0 20 0.000 0 0 4 0 0 5
+ 7605 3600 6300 3600 6300 3375 7605 3375 7605 3600
+2 4 0 1 35 35 200 0 20 0.000 0 0 4 0 0 5
+ 7335 4500 6300 4500 6300 4275 7335 4275 7335 4500
+2 4 0 1 35 35 200 0 20 0.000 0 0 4 0 0 5
+ 7200 5400 6300 5400 6300 5175 7200 5175 7200 5400
+2 1 0 6 7 7 600 0 -1 0.000 0 0 -1 0 0 2
+ 9450 4500 6075 1935
+2 1 0 6 7 7 600 0 -1 0.000 0 0 4 0 0 2
+ 9450 4500 12465 2205
+2 1 0 6 7 7 600 0 -1 0.000 0 0 4 0 0 2
+ 9450 4500 9450 7785
+2 1 0 1 0 34 200 0 20 0.000 0 0 -1 1 0 2
+ 1 1 1.00 60.00 120.00
+ 9630 5310 7245 5310
+2 1 0 1 0 34 200 0 20 0.000 0 0 -1 1 0 2
+ 1 1 1.00 60.00 120.00
+ 11385 4365 7380 4365
+2 4 0 1 35 35 200 0 20 0.000 0 0 4 0 0 5
+ 12240 5805 10980 5805 10980 5580 12240 5580 12240 5805
+2 4 0 1 35 35 200 0 20 0.000 0 0 4 0 0 5
+ 12375 6210 10980 6210 10980 5985 12375 5985 12375 6210
+2 1 0 1 0 34 200 0 20 0.000 0 0 -1 1 0 2
+ 1 1 1.00 60.00 120.00
+ 11205 6885 9900 5445
+2 4 0 1 35 35 200 0 20 0.000 0 0 4 0 0 5
+ 12285 7155 10530 7155 10530 6930 12285 6930 12285 7155
+2 4 0 1 35 35 200 0 20 0.000 0 0 4 0 0 5
+ 10170 7155 9630 7155 9630 6930 10170 6930 10170 7155
+2 1 0 6 7 7 600 0 -1 0.000 0 0 4 0 0 2
+ 12510 6435 9450 6435
+2 1 0 1 0 34 300 0 20 0.000 0 0 7 1 0 4
+ 1 1 1.00 60.00 120.00
+ 12375 4455 12510 4635 12510 6210 11970 6885
+2 1 2 1 0 34 200 0 20 1.000 0 0 -1 1 0 2
+ 1 1 1.00 60.00 120.00
+ 9850 5143 9175 4918
+3 1 0 1 34 34 800 0 20 0.000 0 0 0 41
+ 7380 1710 6390 2115 5535 2115 6075 3015 5670 3465 6165 3915
+ 5715 4410 6030 5040 6030 5310 6480 5715 6390 6255 6975 6300
+ 7065 6975 7965 6750 8100 7560 8955 7290 9360 7740 9720 7560
+ 10755 8145 12060 8280 12375 7650 12420 7200 12510 7065 12330 6660
+ 12510 6390 12420 5940 12375 5400 12510 5220 12510 4725 12600 4275
+ 12375 3645 12105 3240 12150 2745 12375 2700 12330 1980 11790 1575
+ 11250 1935 10125 1485 8955 2070 7785 1620 7695 1575
+ 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
+ 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
+ 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
+ 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
+ 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
+ 1.000
+4 0 0 100 0 0 12 0.0000 4 180 1440 10575 675 Ralf S. Engelschall\001
+4 0 0 100 0 18 20 0.0000 4 270 3840 4275 675 Apache+mod_ssl+OpenSSL\001
+4 0 0 100 0 0 10 0.0000 4 135 1320 10575 855 rse@engelschall.com\001
+4 0 0 100 0 0 10 0.0000 4 135 1410 10575 1035 www.engelschall.com\001
+4 0 0 100 0 0 12 0.0000 4 135 870 900 675 Version 1.3\001
+4 0 0 100 0 0 12 0.0000 4 180 1035 900 855 12-Apr-1999\001
+4 0 0 200 0 20 8 0.0000 4 60 390 6210 4680 ->server\001
+4 0 0 200 0 20 8 0.0000 4 120 855 8280 6120 ap_ctx_get(...,"ssl")\001
+4 0 0 200 0 20 8 0.0000 4 120 1170 7740 2700 ap_get_module_config(...\001
+4 0 0 200 0 20 8 0.0000 4 120 810 7740 2835 ->module_config,\001
+4 0 0 200 0 20 8 0.0000 4 120 585 7740 2970 &ssl_module)\001
+4 0 0 100 0 18 20 0.0000 4 270 1200 9000 8100 Chaining\001
+4 0 0 100 0 18 20 0.0000 4 210 1095 2745 8100 Lifetime\001
+4 0 0 100 0 18 12 0.0000 4 180 1215 810 6255 ap_global_ctx\001
+4 0 0 100 0 18 12 0.0000 4 180 1305 990 5805 SSLModConfig\001
+4 0 0 100 0 18 12 0.0000 4 180 840 4050 4455 SSL_CTX\001
+4 0 0 100 0 18 12 0.0000 4 150 975 4455 5355 server_rec\001
+4 0 0 100 0 18 12 0.0000 4 180 1260 3870 4905 SSLSrvConfig\001
+4 0 0 100 0 18 12 0.0000 4 135 480 1845 4005 BUFF\001
+4 0 0 100 0 18 12 0.0000 4 150 810 2070 3555 conn_rec\001
+4 0 0 100 0 18 12 0.0000 4 135 345 2295 3105 BIO\001
+4 0 0 100 0 18 12 0.0000 4 135 375 2565 2655 SSL\001
+4 0 0 100 0 18 12 0.0000 4 180 1185 3645 1620 SSLDirConfig\001
+4 0 0 100 0 18 12 0.0000 4 180 1065 3915 2070 request_rec\001
+4 0 0 200 0 0 8 0.0000 4 120 1440 900 7560 Startup, Runtime, Shutdown\001
+4 0 0 200 0 0 8 0.0000 4 105 975 1350 7335 Configuration Time\001
+4 0 0 200 0 0 8 0.0000 4 90 1050 2025 7110 Connection Duration\001
+4 0 0 200 0 0 8 0.0000 4 120 885 2835 6885 Request Duration\001
+4 0 0 200 0 18 20 0.0000 4 195 90 6345 6795 t\001
+4 0 0 200 0 20 8 0.0000 4 90 345 7110 5985 ->client\001
+4 0 0 100 0 18 12 0.0000 4 180 1305 6795 2430 SSLModConfig\001
+4 0 0 100 0 18 12 0.0000 4 180 1260 8865 2430 SSLSrvConfig\001
+4 0 0 100 0 18 12 0.0000 4 180 1215 6345 3555 ap_global_ctx\001
+4 0 0 100 0 18 12 0.0000 4 150 975 6345 4455 server_rec\001
+4 0 0 100 0 18 12 0.0000 4 150 810 6345 5355 conn_rec\001
+4 0 0 100 0 18 12 0.0000 4 135 375 9720 5355 SSL\001
+4 0 0 100 0 18 12 0.0000 4 180 1185 10665 2430 SSLDirConfig\001
+4 0 0 100 0 18 12 0.0000 4 135 480 7290 6255 BUFF\001
+4 0 0 100 0 18 12 0.0000 4 180 1305 11025 5355 SSL_METHOD\001
+4 0 0 100 0 18 12 0.0000 4 180 840 11475 4455 SSL_CTX\001
+4 0 0 100 0 18 24 0.0000 4 285 4365 3915 1080 Data Structure Overview\001
+4 0 0 200 0 20 8 0.0000 4 90 615 7065 5085 ->connection\001
+4 0 0 200 0 20 8 0.0000 4 60 390 7065 4770 ->server\001
+4 0 0 200 0 20 8 0.0000 4 120 960 8010 5445 SSL_get_app_data()\001
+4 0 0 200 0 20 8 0.0000 4 120 510 10530 4050 ->pSSLCtx\001
+4 0 0 200 0 20 8 0.0000 4 120 1215 7875 4275 SSL_CTX_get_app_data()\001
+4 0 0 200 0 20 8 0.0000 4 120 1155 10305 5535 SSL_get_current_cipher()\001
+4 0 0 100 0 18 12 0.0000 4 180 1170 11025 5760 SSL_CIPHER\001
+4 0 0 100 0 18 12 0.0000 4 180 1350 10980 6165 SSL_SESSION\001
+4 0 0 200 0 20 8 0.0000 4 120 840 10440 5940 SSL_get_session()\001
+4 0 0 100 0 18 12 0.0000 4 180 1665 10575 7110 X509_STORE_CTX\001
+4 0 0 100 0 18 12 0.0000 4 135 345 9720 7110 BIO\001
+4 0 0 200 0 20 8 0.0000 4 120 840 9540 7335 SSL_get_{r,w}bio()\001
+4 0 0 100 0 18 20 0.0000 4 270 1170 8730 3465 mod_ssl\001
+4 0 0 100 0 18 20 0.0000 4 270 1050 8145 6750 Apache\001
+4 0 0 200 0 20 8 0.0000 4 120 945 10125 4680 SSL_get_SSL_CTX()\001
+4 0 0 200 0 20 8 0.0000 4 120 1170 10350 5175 SSL_get_SSL_METHOD()\001
+4 0 0 200 0 20 8 0.0000 4 90 465 11745 4770 ->method\001
+4 0 0 200 0 20 8 0.0000 4 120 1665 9945 6480 X509_STORE_CTX_get_app_data()\001
+4 0 0 200 0 20 8 0.0000 4 120 1215 10980 6705 SSL_CTX_get_cert_store()\001
+4 0 0 200 0 20 8 0.0000 4 120 1020 8280 5130 modssl_get_app_data2()\001
+4 0 0 100 0 18 20 0.0000 4 270 1290 10710 7605 OpenSSL\001
+4 0 0 100 0 18 12 0.0000 4 180 720 10710 7785 [Crypto]\001
+4 0 0 100 0 18 20 0.0000 4 270 1290 10935 3645 OpenSSL\001
+4 0 0 100 0 18 12 0.0000 4 180 495 10935 3825 [SSL]\001
diff --git a/modules/ssl/README.dsov.ps b/modules/ssl/README.dsov.ps
new file mode 100644
index 0000000..bcbf268
--- /dev/null
+++ b/modules/ssl/README.dsov.ps
@@ -0,0 +1,1138 @@
+%!PS-Adobe-2.0
+%%Title: README.dsov.ps
+%%Creator: fig2dev Version 3.2 Patchlevel 1
+%%CreationDate: Mon Apr 12 17:09:11 1999
+%%For: rse@en1.engelschall.com (Ralf S. Engelschall)
+%%Orientation: Landscape
+%%BoundingBox: 59 37 553 755
+%%Pages: 1
+%%BeginSetup
+%%IncludeFeature: *PageSize Letter
+%%EndSetup
+%%Magnification: 0.9340
+%%EndComments
+/$F2psDict 200 dict def
+$F2psDict begin
+$F2psDict /mtrx matrix put
+/col-1 {0 setgray} bind def
+/col0 {0.000 0.000 0.000 srgb} bind def
+/col1 {0.000 0.000 1.000 srgb} bind def
+/col2 {0.000 1.000 0.000 srgb} bind def
+/col3 {0.000 1.000 1.000 srgb} bind def
+/col4 {1.000 0.000 0.000 srgb} bind def
+/col5 {1.000 0.000 1.000 srgb} bind def
+/col6 {1.000 1.000 0.000 srgb} bind def
+/col7 {1.000 1.000 1.000 srgb} bind def
+/col8 {0.000 0.000 0.560 srgb} bind def
+/col9 {0.000 0.000 0.690 srgb} bind def
+/col10 {0.000 0.000 0.820 srgb} bind def
+/col11 {0.530 0.810 1.000 srgb} bind def
+/col12 {0.000 0.560 0.000 srgb} bind def
+/col13 {0.000 0.690 0.000 srgb} bind def
+/col14 {0.000 0.820 0.000 srgb} bind def
+/col15 {0.000 0.560 0.560 srgb} bind def
+/col16 {0.000 0.690 0.690 srgb} bind def
+/col17 {0.000 0.820 0.820 srgb} bind def
+/col18 {0.560 0.000 0.000 srgb} bind def
+/col19 {0.690 0.000 0.000 srgb} bind def
+/col20 {0.820 0.000 0.000 srgb} bind def
+/col21 {0.560 0.000 0.560 srgb} bind def
+/col22 {0.690 0.000 0.690 srgb} bind def
+/col23 {0.820 0.000 0.820 srgb} bind def
+/col24 {0.500 0.190 0.000 srgb} bind def
+/col25 {0.630 0.250 0.000 srgb} bind def
+/col26 {0.750 0.380 0.000 srgb} bind def
+/col27 {1.000 0.500 0.500 srgb} bind def
+/col28 {1.000 0.630 0.630 srgb} bind def
+/col29 {1.000 0.750 0.750 srgb} bind def
+/col30 {1.000 0.880 0.880 srgb} bind def
+/col31 {1.000 0.840 0.000 srgb} bind def
+/col32 {0.380 0.396 0.380 srgb} bind def
+/col33 {0.714 0.698 0.714 srgb} bind def
+/col34 {0.969 0.953 0.969 srgb} bind def
+/col35 {0.812 0.812 0.812 srgb} bind def
+/col36 {1.000 1.000 1.000 srgb} bind def
+
+end
+save
+48.0 12.0 translate
+ 90 rotate
+1 -1 scale
+
+/cp {closepath} bind def
+/ef {eofill} bind def
+/gr {grestore} bind def
+/gs {gsave} bind def
+/sa {save} bind def
+/rs {restore} bind def
+/l {lineto} bind def
+/m {moveto} bind def
+/rm {rmoveto} bind def
+/n {newpath} bind def
+/s {stroke} bind def
+/sh {show} bind def
+/slc {setlinecap} bind def
+/slj {setlinejoin} bind def
+/slw {setlinewidth} bind def
+/srgb {setrgbcolor} bind def
+/rot {rotate} bind def
+/sc {scale} bind def
+/sd {setdash} bind def
+/ff {findfont} bind def
+/sf {setfont} bind def
+/scf {scalefont} bind def
+/sw {stringwidth} bind def
+/tr {translate} bind def
+/tnt {dup dup currentrgbcolor
+ 4 -2 roll dup 1 exch sub 3 -1 roll mul add
+ 4 -2 roll dup 1 exch sub 3 -1 roll mul add
+ 4 -2 roll dup 1 exch sub 3 -1 roll mul add srgb}
+ bind def
+/shd {dup dup currentrgbcolor 4 -2 roll mul 4 -2 roll mul
+ 4 -2 roll mul srgb} bind def
+/reencdict 12 dict def /ReEncode { reencdict begin
+/newcodesandnames exch def /newfontname exch def /basefontname exch def
+/basefontdict basefontname findfont def /newfont basefontdict maxlength dict def
+basefontdict { exch dup /FID ne { dup /Encoding eq
+{ exch dup length array copy newfont 3 1 roll put }
+{ exch newfont 3 1 roll put } ifelse } { pop pop } ifelse } forall
+newfont /FontName newfontname put newcodesandnames aload pop
+128 1 255 { newfont /Encoding get exch /.notdef put } for
+newcodesandnames length 2 idiv { newfont /Encoding get 3 1 roll put } repeat
+newfontname newfont definefont pop end } def
+/isovec [
+8#200 /grave 8#201 /acute 8#202 /circumflex 8#203 /tilde
+8#204 /macron 8#205 /breve 8#206 /dotaccent 8#207 /dieresis
+8#210 /ring 8#211 /cedilla 8#212 /hungarumlaut 8#213 /ogonek 8#214 /caron
+8#220 /dotlessi 8#230 /oe 8#231 /OE
+8#240 /space 8#241 /exclamdown 8#242 /cent 8#243 /sterling
+8#244 /currency 8#245 /yen 8#246 /brokenbar 8#247 /section 8#250 /dieresis
+8#251 /copyright 8#252 /ordfeminine 8#253 /guillemotleft 8#254 /logicalnot
+8#255 /endash 8#256 /registered 8#257 /macron 8#260 /degree 8#261 /plusminus
+8#262 /twosuperior 8#263 /threesuperior 8#264 /acute 8#265 /mu 8#266 /paragraph
+8#267 /periodcentered 8#270 /cedilla 8#271 /onesuperior 8#272 /ordmasculine
+8#273 /guillemotright 8#274 /onequarter 8#275 /onehalf
+8#276 /threequarters 8#277 /questiondown 8#300 /Agrave 8#301 /Aacute
+8#302 /Acircumflex 8#303 /Atilde 8#304 /Adieresis 8#305 /Aring
+8#306 /AE 8#307 /Ccedilla 8#310 /Egrave 8#311 /Eacute
+8#312 /Ecircumflex 8#313 /Edieresis 8#314 /Igrave 8#315 /Iacute
+8#316 /Icircumflex 8#317 /Idieresis 8#320 /Eth 8#321 /Ntilde 8#322 /Ograve
+8#323 /Oacute 8#324 /Ocircumflex 8#325 /Otilde 8#326 /Odieresis 8#327 /multiply
+8#330 /Oslash 8#331 /Ugrave 8#332 /Uacute 8#333 /Ucircumflex
+8#334 /Udieresis 8#335 /Yacute 8#336 /Thorn 8#337 /germandbls 8#340 /agrave
+8#341 /aacute 8#342 /acircumflex 8#343 /atilde 8#344 /adieresis 8#345 /aring
+8#346 /ae 8#347 /ccedilla 8#350 /egrave 8#351 /eacute
+8#352 /ecircumflex 8#353 /edieresis 8#354 /igrave 8#355 /iacute
+8#356 /icircumflex 8#357 /idieresis 8#360 /eth 8#361 /ntilde 8#362 /ograve
+8#363 /oacute 8#364 /ocircumflex 8#365 /otilde 8#366 /odieresis 8#367 /divide
+8#370 /oslash 8#371 /ugrave 8#372 /uacute 8#373 /ucircumflex
+8#374 /udieresis 8#375 /yacute 8#376 /thorn 8#377 /ydieresis] def
+/Times-Roman /Times-Roman-iso isovec ReEncode
+/Helvetica-Bold /Helvetica-Bold-iso isovec ReEncode
+/Helvetica-Narrow /Helvetica-Narrow-iso isovec ReEncode
+/$F2psBegin {$F2psDict begin /$F2psEnteredState save def} def
+/$F2psEnd {$F2psEnteredState restore end} def
+%%EndProlog
+
+$F2psBegin
+10 setmiterlimit
+n -1000 9572 m -1000 -1000 l 13622 -1000 l 13622 9572 l cp clip
+ 0.05883 0.05883 sc
+%%Page: 1 1
+% Polyline
+7.500 slw
+n 6413 2048 m 6380 2054 l 6348 2061 l 6315 2067 l 6283 2073 l 6250 2079 l
+ 6217 2084 l 6185 2090 l 6152 2095 l 6120 2101 l 6088 2107 l
+ 6057 2113 l 6027 2120 l 5998 2126 l 5970 2134 l 5943 2141 l
+ 5918 2149 l 5894 2158 l 5873 2167 l 5853 2177 l 5835 2187 l
+ 5819 2198 l 5805 2210 l 5793 2222 l 5782 2235 l 5774 2250 l
+ 5768 2265 l 5763 2281 l 5760 2299 l 5759 2318 l 5759 2339 l
+ 5761 2360 l 5764 2383 l 5768 2408 l 5774 2433 l 5780 2460 l
+ 5788 2488 l 5797 2516 l 5806 2546 l 5815 2575 l 5825 2606 l
+ 5836 2636 l 5846 2666 l 5856 2696 l 5866 2726 l 5875 2755 l
+ 5884 2784 l 5892 2812 l 5899 2839 l 5905 2866 l 5910 2891 l
+ 5915 2916 l 5918 2940 l 5919 2968 l 5920 2995 l 5919 3022 l
+ 5916 3048 l 5912 3075 l 5908 3101 l 5902 3127 l 5895 3153 l
+ 5887 3179 l 5880 3205 l 5871 3230 l 5863 3254 l 5855 3278 l
+ 5848 3302 l 5841 3324 l 5834 3346 l 5829 3367 l 5824 3388 l
+ 5821 3408 l 5819 3427 l 5819 3446 l 5820 3465 l 5823 3484 l
+ 5827 3503 l 5833 3522 l 5840 3542 l 5848 3562 l 5858 3582 l
+ 5868 3603 l 5880 3625 l 5891 3647 l 5904 3669 l 5916 3691 l
+ 5929 3713 l 5941 3736 l 5953 3758 l 5964 3779 l 5974 3801 l
+ 5983 3822 l 5991 3843 l 5997 3863 l 6002 3883 l 6006 3903 l
+ 6008 3923 l 6008 3942 l 6006 3962 l 6003 3983 l 5998 4004 l
+ 5992 4025 l 5985 4048 l 5977 4070 l 5968 4094 l 5958 4118 l
+ 5947 4142 l 5936 4167 l 5925 4192 l 5913 4216 l 5902 4241 l
+ 5892 4266 l 5882 4291 l 5872 4315 l 5864 4339 l 5857 4362 l
+ 5851 4386 l 5846 4409 l 5843 4433 l 5840 4456 l 5840 4480 l
+ 5840 4505 l 5842 4530 l 5845 4556 l 5849 4582 l 5854 4609 l
+ 5860 4636 l 5867 4664 l 5875 4692 l 5883 4720 l 5892 4747 l
+ 5901 4774 l 5910 4801 l 5920 4827 l 5929 4852 l 5938 4875 l
+ 5947 4898 l 5955 4920 l 5963 4941 l 5971 4961 l 5978 4980 l
+ 5985 5002 l 5992 5024 l 5999 5046 l 6005 5067 l 6010 5088 l
+ 6016 5109 l 6022 5129 l 6027 5150 l 6033 5170 l 6039 5190 l
+ 6045 5209 l 6052 5228 l 6059 5246 l 6067 5264 l 6075 5281 l
+ 6084 5298 l 6094 5315 l 6105 5333 l 6115 5347 l 6125 5361 l
+ 6137 5376 l 6149 5392 l 6162 5408 l 6176 5425 l 6191 5443 l
+ 6206 5461 l 6221 5480 l 6237 5499 l 6253 5519 l 6269 5539 l
+ 6284 5559 l 6299 5579 l 6313 5599 l 6327 5619 l 6340 5639 l
+ 6352 5659 l 6363 5679 l 6373 5698 l 6382 5718 l 6390 5738 l
+ 6398 5759 l 6404 5782 l 6410 5805 l 6415 5828 l 6420 5852 l
+ 6424 5877 l 6428 5902 l 6431 5927 l 6435 5952 l 6438 5977 l
+ 6442 6001 l 6446 6025 l 6450 6048 l 6455 6069 l 6461 6090 l
+ 6467 6109 l 6474 6127 l 6483 6143 l 6492 6159 l 6503 6173 l
+ 6515 6185 l 6528 6197 l 6543 6209 l 6560 6220 l 6578 6230 l
+ 6598 6240 l 6619 6250 l 6641 6260 l 6663 6270 l 6687 6281 l
+ 6710 6291 l 6733 6302 l 6757 6312 l 6779 6324 l 6801 6335 l
+ 6821 6348 l 6841 6361 l 6859 6374 l 6876 6389 l 6893 6405 l
+ 6906 6421 l 6919 6437 l 6932 6455 l 6944 6475 l 6955 6495 l
+ 6967 6516 l 6979 6538 l 6991 6561 l 7003 6584 l 7015 6608 l
+ 7027 6631 l 7040 6654 l 7053 6677 l 7067 6699 l 7081 6720 l
+ 7096 6739 l 7111 6758 l 7127 6774 l 7144 6789 l 7161 6803 l
+ 7180 6815 l 7200 6825 l 7220 6833 l 7240 6840 l 7263 6845 l
+ 7286 6850 l 7311 6854 l 7338 6857 l 7365 6859 l 7394 6861 l
+ 7424 6862 l 7454 6864 l 7485 6865 l 7516 6866 l 7547 6867 l
+ 7578 6868 l 7609 6870 l 7639 6872 l 7668 6875 l 7696 6879 l
+ 7723 6883 l 7748 6889 l 7773 6895 l 7795 6903 l 7817 6912 l
+ 7838 6923 l 7857 6934 l 7875 6948 l 7892 6963 l 7909 6980 l
+ 7926 6998 l 7941 7017 l 7957 7038 l 7972 7060 l 7987 7083 l
+ 8002 7106 l 8017 7130 l 8031 7154 l 8046 7178 l 8061 7202 l
+ 8075 7225 l 8090 7247 l 8105 7269 l 8120 7289 l 8135 7308 l
+ 8151 7326 l 8167 7342 l 8184 7356 l 8202 7369 l 8220 7380 l
+ 8239 7390 l 8260 7397 l 8282 7404 l 8305 7409 l 8330 7413 l
+ 8356 7416 l 8383 7418 l 8412 7420 l 8441 7420 l 8471 7419 l
+ 8502 7418 l 8534 7417 l 8565 7415 l 8597 7413 l 8629 7411 l
+ 8660 7409 l 8690 7407 l 8720 7405 l 8749 7404 l 8777 7404 l
+ 8804 7404 l 8830 7405 l 8856 7407 l 8880 7410 l 8906 7414 l
+ 8931 7420 l 8956 7427 l 8981 7435 l 9005 7444 l 9029 7455 l
+ 9053 7466 l 9077 7478 l 9100 7491 l 9123 7504 l 9146 7517 l
+ 9168 7531 l 9190 7544 l 9210 7557 l 9230 7570 l 9250 7582 l
+ 9268 7593 l 9286 7604 l 9304 7613 l 9320 7621 l 9336 7629 l
+ 9353 7635 l 9370 7641 l 9388 7645 l 9406 7648 l 9425 7650 l
+ 9444 7652 l 9464 7653 l 9485 7653 l 9508 7653 l 9531 7653 l
+ 9555 7653 l 9579 7653 l 9605 7654 l 9631 7655 l 9658 7656 l
+ 9685 7659 l 9713 7662 l 9742 7666 l 9771 7672 l 9801 7679 l
+ 9833 7688 l 9853 7694 l 9874 7700 l 9895 7708 l 9918 7716 l
+ 9941 7725 l 9966 7734 l 9991 7745 l 10017 7755 l 10045 7767 l
+ 10073 7779 l 10102 7791 l 10132 7804 l 10163 7818 l 10194 7831 l
+ 10227 7845 l 10259 7860 l 10293 7874 l 10326 7889 l 10360 7903 l
+ 10394 7918 l 10429 7932 l 10463 7947 l 10497 7961 l 10531 7974 l
+ 10565 7988 l 10599 8001 l 10633 8013 l 10667 8025 l 10700 8037 l
+ 10733 8049 l 10767 8059 l 10800 8070 l 10834 8080 l 10868 8090 l
+ 10902 8099 l 10937 8108 l 10973 8117 l 11009 8125 l 11045 8133 l
+ 11083 8141 l 11120 8148 l 11158 8155 l 11197 8161 l 11236 8167 l
+ 11275 8172 l 11313 8177 l 11352 8181 l 11391 8184 l 11429 8187 l
+ 11467 8190 l 11504 8191 l 11540 8192 l 11576 8192 l 11610 8192 l
+ 11644 8191 l 11676 8189 l 11707 8187 l 11738 8184 l 11767 8180 l
+ 11794 8176 l 11821 8171 l 11847 8165 l 11871 8159 l 11895 8153 l
+ 11923 8143 l 11950 8133 l 11976 8122 l 12001 8109 l 12025 8096 l
+ 12048 8081 l 12071 8065 l 12092 8048 l 12113 8031 l 12133 8012 l
+ 12153 7992 l 12171 7972 l 12188 7951 l 12205 7930 l 12220 7909 l
+ 12235 7887 l 12248 7865 l 12260 7843 l 12272 7822 l 12282 7800 l
+ 12292 7779 l 12301 7759 l 12309 7739 l 12316 7719 l 12323 7699 l
+ 12330 7680 l 12338 7655 l 12345 7631 l 12352 7607 l 12359 7582 l
+ 12365 7558 l 12371 7533 l 12377 7508 l 12382 7484 l 12388 7460 l
+ 12392 7436 l 12397 7414 l 12401 7391 l 12405 7370 l 12409 7350 l
+ 12412 7331 l 12415 7313 l 12418 7297 l 12421 7281 l 12424 7266 l
+ 12428 7253 l 12432 7234 l 12437 7216 l 12442 7199 l 12446 7183 l
+ 12451 7166 l 12456 7150 l 12460 7134 l 12463 7117 l 12466 7101 l
+ 12468 7086 l 12469 7070 l 12469 7054 l 12467 7037 l 12465 7020 l
+ 12462 7006 l 12459 6991 l 12455 6975 l 12450 6958 l 12445 6940 l
+ 12440 6921 l 12434 6901 l 12428 6880 l 12422 6859 l 12416 6838 l
+ 12411 6817 l 12406 6796 l 12401 6776 l 12397 6756 l 12394 6736 l
+ 12392 6718 l 12390 6700 l 12390 6683 l 12390 6665 l 12392 6649 l
+ 12394 6631 l 12397 6614 l 12401 6597 l 12406 6579 l 12411 6561 l
+ 12416 6542 l 12422 6524 l 12428 6505 l 12434 6487 l 12440 6468 l
+ 12445 6450 l 12450 6432 l 12455 6414 l 12459 6396 l 12462 6378 l
+ 12465 6360 l 12467 6343 l 12468 6326 l 12469 6308 l 12469 6289 l
+ 12468 6269 l 12468 6249 l 12466 6227 l 12464 6205 l 12462 6182 l
+ 12460 6159 l 12457 6135 l 12454 6111 l 12451 6087 l 12447 6063 l
+ 12444 6040 l 12441 6016 l 12437 5993 l 12434 5970 l 12431 5948 l
+ 12428 5925 l 12424 5902 l 12421 5879 l 12419 5855 l 12416 5831 l
+ 12413 5806 l 12411 5781 l 12408 5755 l 12406 5729 l 12404 5702 l
+ 12403 5676 l 12401 5651 l 12400 5625 l 12400 5601 l 12399 5578 l
+ 12399 5555 l 12400 5534 l 12401 5514 l 12402 5495 l 12403 5477 l
+ 12405 5460 l 12408 5440 l 12411 5421 l 12416 5402 l 12420 5384 l
+ 12426 5365 l 12431 5347 l 12437 5329 l 12444 5311 l 12450 5293 l
+ 12456 5275 l 12462 5258 l 12468 5240 l 12474 5222 l 12479 5205 l
+ 12483 5186 l 12488 5168 l 12490 5152 l 12493 5135 l 12496 5117 l
+ 12498 5099 l 12500 5079 l 12502 5058 l 12504 5036 l 12506 5014 l
+ 12507 4990 l 12509 4966 l 12510 4942 l 12512 4918 l 12513 4893 l
+ 12515 4869 l 12516 4845 l 12518 4822 l 12520 4799 l 12521 4776 l
+ 12523 4754 l 12525 4733 l 12527 4713 l 12529 4693 l 12531 4673 l
+ 12534 4653 l 12536 4632 l 12539 4610 l 12541 4588 l 12543 4566 l
+ 12546 4543 l 12548 4520 l 12550 4497 l 12552 4473 l 12553 4450 l
+ 12554 4426 l 12555 4403 l 12555 4380 l 12555 4357 l 12555 4334 l
+ 12554 4312 l 12552 4290 l 12550 4267 l 12548 4245 l 12545 4224 l
+ 12541 4203 l 12537 4181 l 12533 4159 l 12528 4136 l 12523 4112 l
+ 12517 4088 l 12510 4064 l 12503 4038 l 12496 4013 l 12488 3987 l
+ 12479 3961 l 12471 3935 l 12462 3909 l 12452 3884 l 12443 3859 l
+ 12434 3835 l 12424 3811 l 12415 3788 l 12405 3766 l 12396 3744 l
+ 12386 3723 l 12377 3702 l 12368 3683 l 12357 3661 l 12347 3640 l
+ 12336 3619 l 12325 3598 l 12314 3576 l 12303 3555 l 12291 3533 l
+ 12280 3511 l 12269 3489 l 12257 3467 l 12246 3446 l 12235 3424 l
+ 12225 3402 l 12215 3381 l 12206 3360 l 12197 3340 l 12189 3320 l
+ 12181 3301 l 12174 3281 l 12168 3262 l 12162 3244 l 12158 3225 l
+ 12153 3204 l 12149 3183 l 12145 3162 l 12142 3139 l 12140 3117 l
+ 12138 3094 l 12137 3071 l 12137 3047 l 12138 3024 l 12139 3001 l
+ 12141 2978 l 12143 2956 l 12146 2935 l 12150 2915 l 12154 2896 l
+ 12158 2879 l 12163 2862 l 12168 2847 l 12174 2833 l 12180 2820 l
+ 12188 2805 l 12197 2792 l 12206 2779 l 12216 2766 l 12227 2754 l
+ 12238 2742 l 12249 2730 l 12260 2717 l 12272 2704 l 12282 2691 l
+ 12292 2676 l 12302 2661 l 12310 2645 l 12318 2627 l 12324 2608 l
+ 12330 2588 l 12334 2571 l 12336 2553 l 12339 2534 l 12341 2513 l
+ 12342 2491 l 12343 2467 l 12343 2442 l 12342 2416 l 12340 2389 l
+ 12338 2360 l 12335 2332 l 12331 2303 l 12326 2273 l 12320 2244 l
+ 12314 2215 l 12307 2187 l 12299 2159 l 12290 2132 l 12280 2106 l
+ 12270 2081 l 12259 2056 l 12248 2033 l 12236 2011 l 12224 1990 l
+ 12210 1970 l 12196 1949 l 12181 1929 l 12164 1910 l 12147 1890 l
+ 12129 1871 l 12110 1853 l 12090 1835 l 12070 1818 l 12049 1802 l
+ 12027 1787 l 12005 1773 l 11983 1761 l 11961 1749 l 11939 1739 l
+ 11917 1730 l 11895 1722 l 11874 1716 l 11852 1710 l 11831 1707 l
+ 11811 1704 l 11790 1703 l 11769 1702 l 11748 1703 l 11727 1705 l
+ 11706 1708 l 11683 1711 l 11660 1716 l 11636 1721 l 11612 1727 l
+ 11587 1733 l 11560 1740 l 11534 1747 l 11506 1754 l 11479 1761 l
+ 11450 1768 l 11422 1774 l 11393 1780 l 11364 1786 l 11334 1791 l
+ 11305 1795 l 11275 1798 l 11245 1800 l 11215 1801 l 11184 1801 l
+ 11153 1800 l 11128 1798 l 11104 1796 l 11078 1793 l 11052 1790 l
+ 11025 1785 l 10997 1781 l 10968 1776 l 10939 1770 l 10908 1764 l
+ 10877 1758 l 10844 1751 l 10811 1744 l 10778 1737 l 10743 1730 l
+ 10708 1722 l 10673 1715 l 10637 1708 l 10601 1701 l 10565 1695 l
+ 10530 1688 l 10494 1682 l 10458 1677 l 10422 1672 l 10387 1668 l
+ 10352 1664 l 10318 1661 l 10284 1658 l 10250 1657 l 10216 1656 l
+ 10183 1655 l 10150 1656 l 10118 1658 l 10087 1660 l 10055 1663 l
+ 10024 1666 l 9992 1671 l 9960 1676 l 9927 1682 l 9894 1688 l
+ 9861 1695 l 9827 1703 l 9792 1711 l 9757 1720 l 9721 1729 l
+ 9685 1738 l 9649 1748 l 9613 1757 l 9576 1767 l 9539 1778 l
+ 9502 1788 l 9465 1798 l 9429 1807 l 9392 1817 l 9356 1826 l
+ 9320 1835 l 9285 1844 l 9250 1852 l 9216 1860 l 9182 1867 l
+ 9148 1873 l 9115 1879 l 9082 1884 l 9050 1889 l 9018 1892 l
+ 8987 1895 l 8955 1898 l 8919 1899 l 8883 1900 l 8847 1899 l
+ 8811 1898 l 8774 1896 l 8737 1893 l 8699 1889 l 8661 1884 l
+ 8623 1878 l 8585 1872 l 8546 1865 l 8508 1857 l 8470 1849 l
+ 8432 1840 l 8395 1830 l 8358 1821 l 8322 1811 l 8287 1801 l
+ 8254 1790 l 8221 1780 l 8189 1770 l 8159 1760 l 8130 1750 l
+ 8102 1740 l 8076 1730 l 8051 1721 l 8028 1712 l 8006 1703 l
+ 7985 1695 l 7965 1688 l 7931 1674 l 7899 1662 l 7871 1650 l
+ 7844 1640 l 7820 1631 l 7798 1623 l 7778 1617 l 7760 1611 l
+ 7743 1607 l 7728 1603 l 7715 1601 l 7702 1600 l 7691 1600 l
+ 7680 1601 l 7669 1603 l 7658 1605 l 7648 1607 l 7638 1610 l
+ 7627 1613 l 7615 1617 l 7601 1621 l 7587 1626 l 7571 1632 l
+ 7554 1638 l 7536 1645 l 7517 1653 l 7496 1661 l 7474 1670 l
+ 7452 1679 l 7428 1689 l 7403 1699 l 7378 1709 l 7352 1720 l
+ 7325 1731 l 7297 1743 l 7268 1755 l 7247 1763 l 7226 1772 l
+ 7204 1781 l 7182 1790 l 7158 1800 l 7133 1810 l 7108 1820 l
+ 7081 1831 l 7053 1842 l 7025 1853 l 6996 1864 l 6966 1875 l
+ 6935 1886 l 6904 1898 l 6873 1909 l 6841 1921 l 6809 1932 l
+ 6776 1943 l 6744 1954 l 6712 1964 l 6680 1974 l 6649 1984 l
+ 6618 1994 l 6587 2003 l 6557 2011 l 6527 2019 l 6498 2027 l
+ 6469 2034 l 6441 2041 l cp gs col34 1.00 shd ef gr gs col34 s gr
+% Polyline
+n 675 6525 m 5850 6525 l 5850 6075 l 5625 6075 l 5625 5625 l 900 5625 l
+ 900 6075 l 675 6075 l cp gs col7 1.00 shd ef gr gs col7 s gr
+% Polyline
+n 1125 6525 m 5355 6525 l 5400 5175 l 5175 5175 l 5175 4725 l 4950 4725 l
+ 4950 4275 l 1575 4275 l 1575 4725 l 1350 4725 l 1350 5175 l
+ 1125 5175 l cp gs col34 1.00 shd ef gr gs col34 s gr
+% Polyline
+75.000 slw
+n 9450 4500 m 12465 2205 l gs col7 s gr
+% Polyline
+n 9450 4500 m 9450 7785 l gs col7 s gr
+% Polyline
+n 9450 4500 m 6075 1935 l gs col7 s gr
+% Polyline
+n 12510 6435 m 9450 6435 l gs col7 s gr
+% Polyline
+7.500 slw
+n 1800 6525 m 4725 6525 l 4725 3825 l 4500 3825 l 4500 3375 l 4275 3375 l
+ 4275 2925 l 4050 2925 l 4050 2475 l 2475 2475 l 2475 2925 l
+ 2250 2925 l 2250 3375 l 2025 3375 l 2025 3825 l 1800 3825 l
+ cp gs col35 1.00 shd ef gr gs col35 s gr
+% Polyline
+n 2700 6525 m 3825 6525 l 3825 2025 l 3600 2025 l 3600 1575 l 2925 1575 l
+ 2925 2025 l 2700 2025 l cp gs col33 1.00 shd ef gr gs col33 s gr
+% Polyline
+gs clippath
+12068 6810 m 11970 6885 l 12022 6773 l 11937 6878 l 11984 6915 l cp
+clip
+n 12375 4455 m 12510 4635 l 12510 6210 l 11970 6885 l gs col34 1.00 shd ef gr gs col0 s gr gr
+
+% arrowhead
+n 12068 6810 m 11970 6885 l 12022 6773 l 12045 6791 l 12068 6810 l cp gs 0.00 setgray ef gr col0 s
+% Polyline
+gs clippath
+7113 6004 m 7155 6120 l 7063 6037 l 7138 6149 l 7188 6116 l cp
+clip
+n 6705 5445 m 7155 6120 l gs col34 1.00 shd ef gr gs col0 s gr gr
+
+% arrowhead
+n 7113 6004 m 7155 6120 l 7063 6037 l 7088 6020 l 7113 6004 l cp gs 0.00 setgray ef gr col0 s
+% Polyline
+gs clippath
+7304 4656 m 7200 4590 l 7323 4599 l 7195 4557 l 7176 4614 l cp
+clip
+n 7875 4815 m 7200 4590 l gs col34 1.00 shd ef gr gs col0 s gr gr
+
+% arrowhead
+n 7304 4656 m 7200 4590 l 7323 4599 l 7314 4628 l 7304 4656 l cp gs 0.00 setgray ef gr col0 s
+% Polyline
+gs clippath
+11405 4128 m 11475 4230 l 11365 4173 l 11466 4262 l 11506 4217 l cp
+clip
+n 9585 2565 m 11475 4230 l gs col34 1.00 shd ef gr gs col0 s gr gr
+
+% arrowhead
+n 11405 4128 m 11475 4230 l 11365 4173 l 11385 4151 l 11405 4128 l cp gs 0.00 setgray ef gr col0 s
+% Polyline
+gs clippath
+11712 4556 m 11835 4545 l 11732 4613 l 11859 4568 l 11839 4512 l cp
+clip
+n 10170 5130 m 11835 4545 l gs col34 1.00 shd ef gr gs col0 s gr gr
+
+% arrowhead
+n 11712 4556 m 11835 4545 l 11732 4613 l 11722 4585 l 11712 4556 l cp gs 0.00 setgray ef gr col0 s
+% Polyline
+gs clippath
+9732 5411 m 9855 5400 l 9752 5468 l 9879 5423 l 9859 5367 l cp
+clip
+n 7920 6075 m 9855 5400 l gs col34 1.00 shd ef gr gs col0 s gr gr
+
+% arrowhead
+n 9732 5411 m 9855 5400 l 9752 5468 l 9742 5440 l 9732 5411 l cp gs 0.00 setgray ef gr col0 s
+% Polyline
+gs clippath
+10823 5573 m 10935 5625 l 10812 5632 l 10944 5657 l 10955 5598 l cp
+clip
+n 9990 5445 m 10935 5625 l gs col34 1.00 shd ef gr gs col0 s gr gr
+
+% arrowhead
+n 10823 5573 m 10935 5625 l 10812 5632 l 10817 5603 l 10823 5573 l cp gs 0.00 setgray ef gr col0 s
+% Polyline
+gs clippath
+10815 5280 m 10935 5310 l 10815 5340 l 10950 5340 l 10950 5280 l cp
+clip
+n 10215 5310 m 10935 5310 l gs col34 1.00 shd ef gr gs col0 s gr gr
+
+% arrowhead
+n 10815 5280 m 10935 5310 l 10815 5340 l 10815 5310 l 10815 5280 l cp gs 0.00 setgray ef gr col0 s
+% Polyline
+gs clippath
+11955 4965 m 11925 5085 l 11895 4965 l 11895 5100 l 11955 5100 l cp
+clip
+n 11925 4590 m 11925 5085 l gs col34 1.00 shd ef gr gs col0 s gr gr
+
+% arrowhead
+n 11955 4965 m 11925 5085 l 11895 4965 l 11925 4965 l 11955 4965 l cp gs 0.00 setgray ef gr col0 s
+% Polyline
+gs clippath
+9840 6720 m 9810 6840 l 9780 6720 l 9780 6855 l 9840 6855 l cp
+clip
+n 9810 5490 m 9810 6840 l gs col34 1.00 shd ef gr gs col0 s gr gr
+
+% arrowhead
+n 9840 6720 m 9810 6840 l 9780 6720 l 9810 6720 l 9840 6720 l cp gs 0.00 setgray ef gr col0 s
+% Polyline
+gs clippath
+10847 5943 m 10935 6030 l 10816 5995 l 10933 6063 l 10963 6012 l cp
+clip
+n 9945 5445 m 10935 6030 l gs col34 1.00 shd ef gr gs col0 s gr gr
+
+% arrowhead
+n 10847 5943 m 10935 6030 l 10816 5995 l 10832 5969 l 10847 5943 l cp gs 0.00 setgray ef gr col0 s
+% Polyline
+gs clippath
+10698 2634 m 10800 2565 l 10742 2674 l 10832 2574 l 10788 2534 l cp
+clip
+n 8865 4725 m 10800 2565 l gs col34 1.00 shd ef gr gs col0 s gr gr
+
+% arrowhead
+n 10698 2634 m 10800 2565 l 10742 2674 l 10720 2654 l 10698 2634 l cp gs 0.00 setgray ef gr col0 s
+% Polyline
+30.000 slw
+n 675 6075 m 5850 6075 l gs col34 1.00 shd ef gr gs col0 s gr
+% Polyline
+7.500 slw
+ [15 15] 15 sd
+gs clippath
+645 6195 m 675 6075 l 705 6195 l 705 6060 l 645 6060 l cp
+clip
+n 675 6525 m 675 6075 l gs col34 1.00 shd ef gr gs col0 s gr gr
+ [] 0 sd
+% arrowhead
+n 645 6195 m 675 6075 l 705 6195 l 675 6195 l 645 6195 l cp gs 0.00 setgray ef gr col0 s
+% Polyline
+ [15 15] 15 sd
+gs clippath
+5880 6405 m 5850 6525 l 5820 6405 l 5820 6540 l 5880 6540 l cp
+clip
+n 5850 6075 m 5850 6525 l gs col34 1.00 shd ef gr gs col0 s gr gr
+ [] 0 sd
+% arrowhead
+n 5880 6405 m 5850 6525 l 5820 6405 l 5850 6405 l 5880 6405 l cp gs col7 1.00 shd ef gr col0 s
+% Polyline
+30.000 slw
+n 900 5625 m 5625 5625 l gs col34 1.00 shd ef gr gs col0 s gr
+% Polyline
+n 1125 5175 m 5400 5175 l gs col34 1.00 shd ef gr gs col0 s gr
+% Polyline
+n 1350 4725 m 5175 4725 l gs col34 1.00 shd ef gr gs col0 s gr
+% Polyline
+n 1575 4275 m 4950 4275 l gs col34 1.00 shd ef gr gs col0 s gr
+% Polyline
+n 1800 3825 m 4725 3825 l gs col34 1.00 shd ef gr gs col0 s gr
+% Polyline
+n 2025 3375 m 4500 3375 l gs col34 1.00 shd ef gr gs col0 s gr
+% Polyline
+n 2250 2925 m 4275 2925 l gs col34 1.00 shd ef gr gs col0 s gr
+% Polyline
+n 2475 2475 m 4050 2475 l gs col34 1.00 shd ef gr gs col0 s gr
+% Polyline
+n 2700 2025 m 3825 2025 l gs col34 1.00 shd ef gr gs col0 s gr
+% Polyline
+n 2925 1575 m 3600 1575 l gs col34 1.00 shd ef gr gs col0 s gr
+% Polyline
+7.500 slw
+ [15 15] 15 sd
+gs clippath
+870 5745 m 900 5625 l 930 5745 l 930 5610 l 870 5610 l cp
+clip
+n 900 6075 m 900 5625 l gs col34 1.00 shd ef gr gs col0 s gr gr
+ [] 0 sd
+% arrowhead
+n 870 5745 m 900 5625 l 930 5745 l 900 5745 l 870 5745 l cp gs 0.00 setgray ef gr col0 s
+% Polyline
+ [15 15] 15 sd
+gs clippath
+1095 5295 m 1125 5175 l 1155 5295 l 1155 5160 l 1095 5160 l cp
+clip
+n 1125 6525 m 1125 5175 l gs col34 1.00 shd ef gr gs col0 s gr gr
+ [] 0 sd
+% arrowhead
+n 1095 5295 m 1125 5175 l 1155 5295 l 1125 5295 l 1095 5295 l cp gs 0.00 setgray ef gr col0 s
+% Polyline
+ [15 15] 15 sd
+gs clippath
+1320 4845 m 1350 4725 l 1380 4845 l 1380 4710 l 1320 4710 l cp
+clip
+n 1350 5175 m 1350 4725 l gs col34 1.00 shd ef gr gs col0 s gr gr
+ [] 0 sd
+% arrowhead
+n 1320 4845 m 1350 4725 l 1380 4845 l 1350 4845 l 1320 4845 l cp gs 0.00 setgray ef gr col0 s
+% Polyline
+ [15 15] 15 sd
+gs clippath
+1545 4395 m 1575 4275 l 1605 4395 l 1605 4260 l 1545 4260 l cp
+clip
+n 1575 4725 m 1575 4275 l gs col34 1.00 shd ef gr gs col0 s gr gr
+ [] 0 sd
+% arrowhead
+n 1545 4395 m 1575 4275 l 1605 4395 l 1575 4395 l 1545 4395 l cp gs 0.00 setgray ef gr col0 s
+% Polyline
+ [15 15] 15 sd
+gs clippath
+1770 3945 m 1800 3825 l 1830 3945 l 1830 3810 l 1770 3810 l cp
+clip
+n 1800 6525 m 1800 3825 l gs col34 1.00 shd ef gr gs col0 s gr gr
+ [] 0 sd
+% arrowhead
+n 1770 3945 m 1800 3825 l 1830 3945 l 1800 3945 l 1770 3945 l cp gs 0.00 setgray ef gr col0 s
+% Polyline
+ [15 15] 15 sd
+gs clippath
+1995 3495 m 2025 3375 l 2055 3495 l 2055 3360 l 1995 3360 l cp
+clip
+n 2025 3825 m 2025 3375 l gs col34 1.00 shd ef gr gs col0 s gr gr
+ [] 0 sd
+% arrowhead
+n 1995 3495 m 2025 3375 l 2055 3495 l 2025 3495 l 1995 3495 l cp gs 0.00 setgray ef gr col0 s
+% Polyline
+ [15 15] 15 sd
+gs clippath
+2220 3045 m 2250 2925 l 2280 3045 l 2280 2910 l 2220 2910 l cp
+clip
+n 2250 3375 m 2250 2925 l gs col34 1.00 shd ef gr gs col0 s gr gr
+ [] 0 sd
+% arrowhead
+n 2220 3045 m 2250 2925 l 2280 3045 l 2250 3045 l 2220 3045 l cp gs 0.00 setgray ef gr col0 s
+% Polyline
+ [15 15] 15 sd
+gs clippath
+2445 2595 m 2475 2475 l 2505 2595 l 2505 2460 l 2445 2460 l cp
+clip
+n 2475 2925 m 2475 2475 l gs col34 1.00 shd ef gr gs col0 s gr gr
+ [] 0 sd
+% arrowhead
+n 2445 2595 m 2475 2475 l 2505 2595 l 2475 2595 l 2445 2595 l cp gs 0.00 setgray ef gr col0 s
+% Polyline
+ [15 15] 15 sd
+gs clippath
+5655 5955 m 5625 6075 l 5595 5955 l 5595 6090 l 5655 6090 l cp
+clip
+n 5625 5625 m 5625 6075 l gs col34 1.00 shd ef gr gs col0 s gr gr
+ [] 0 sd
+% arrowhead
+n 5655 5955 m 5625 6075 l 5595 5955 l 5625 5955 l 5655 5955 l cp gs col7 1.00 shd ef gr col0 s
+% Polyline
+ [15 15] 15 sd
+gs clippath
+5430 6405 m 5400 6525 l 5370 6405 l 5370 6540 l 5430 6540 l cp
+clip
+n 5400 5175 m 5400 6525 l gs col34 1.00 shd ef gr gs col0 s gr gr
+ [] 0 sd
+% arrowhead
+n 5430 6405 m 5400 6525 l 5370 6405 l 5400 6405 l 5430 6405 l cp gs col7 1.00 shd ef gr col0 s
+% Polyline
+ [15 15] 15 sd
+gs clippath
+5205 5055 m 5175 5175 l 5145 5055 l 5145 5190 l 5205 5190 l cp
+clip
+n 5175 4725 m 5175 5175 l gs col34 1.00 shd ef gr gs col0 s gr gr
+ [] 0 sd
+% arrowhead
+n 5205 5055 m 5175 5175 l 5145 5055 l 5175 5055 l 5205 5055 l cp gs col7 1.00 shd ef gr col0 s
+% Polyline
+ [15 15] 15 sd
+gs clippath
+4980 4605 m 4950 4725 l 4920 4605 l 4920 4740 l 4980 4740 l cp
+clip
+n 4950 4275 m 4950 4725 l gs col34 1.00 shd ef gr gs col0 s gr gr
+ [] 0 sd
+% arrowhead
+n 4980 4605 m 4950 4725 l 4920 4605 l 4950 4605 l 4980 4605 l cp gs col7 1.00 shd ef gr col0 s
+% Polyline
+ [15 15] 15 sd
+gs clippath
+4755 6405 m 4725 6525 l 4695 6405 l 4695 6540 l 4755 6540 l cp
+clip
+n 4725 3825 m 4725 6525 l gs col34 1.00 shd ef gr gs col0 s gr gr
+ [] 0 sd
+% arrowhead
+n 4755 6405 m 4725 6525 l 4695 6405 l 4725 6405 l 4755 6405 l cp gs col7 1.00 shd ef gr col0 s
+% Polyline
+ [15 15] 15 sd
+gs clippath
+4530 3705 m 4500 3825 l 4470 3705 l 4470 3840 l 4530 3840 l cp
+clip
+n 4500 3375 m 4500 3825 l gs col34 1.00 shd ef gr gs col0 s gr gr
+ [] 0 sd
+% arrowhead
+n 4530 3705 m 4500 3825 l 4470 3705 l 4500 3705 l 4530 3705 l cp gs col7 1.00 shd ef gr col0 s
+% Polyline
+ [15 15] 15 sd
+gs clippath
+4305 3255 m 4275 3375 l 4245 3255 l 4245 3390 l 4305 3390 l cp
+clip
+n 4275 2925 m 4275 3375 l gs col34 1.00 shd ef gr gs col0 s gr gr
+ [] 0 sd
+% arrowhead
+n 4305 3255 m 4275 3375 l 4245 3255 l 4275 3255 l 4305 3255 l cp gs col7 1.00 shd ef gr col0 s
+% Polyline
+ [15 15] 15 sd
+gs clippath
+4080 2805 m 4050 2925 l 4020 2805 l 4020 2940 l 4080 2940 l cp
+clip
+n 4050 2475 m 4050 2925 l gs col34 1.00 shd ef gr gs col0 s gr gr
+ [] 0 sd
+% arrowhead
+n 4080 2805 m 4050 2925 l 4020 2805 l 4050 2805 l 4080 2805 l cp gs col7 1.00 shd ef gr col0 s
+% Polyline
+ [15 15] 15 sd
+gs clippath
+2670 2145 m 2700 2025 l 2730 2145 l 2730 2010 l 2670 2010 l cp
+clip
+n 2700 6525 m 2700 2025 l gs col34 1.00 shd ef gr gs col0 s gr gr
+ [] 0 sd
+% arrowhead
+n 2670 2145 m 2700 2025 l 2730 2145 l 2700 2145 l 2670 2145 l cp gs 0.00 setgray ef gr col0 s
+% Polyline
+ [15 15] 15 sd
+gs clippath
+3855 6405 m 3825 6525 l 3795 6405 l 3795 6540 l 3855 6540 l cp
+clip
+n 3825 2025 m 3825 6525 l gs col34 1.00 shd ef gr gs col0 s gr gr
+ [] 0 sd
+% arrowhead
+n 3855 6405 m 3825 6525 l 3795 6405 l 3825 6405 l 3855 6405 l cp gs col7 1.00 shd ef gr col0 s
+% Polyline
+ [15 15] 15 sd
+gs clippath
+3630 1905 m 3600 2025 l 3570 1905 l 3570 2040 l 3630 2040 l cp
+clip
+n 3600 1575 m 3600 2025 l gs col34 1.00 shd ef gr gs col0 s gr gr
+ [] 0 sd
+% arrowhead
+n 3630 1905 m 3600 2025 l 3570 1905 l 3600 1905 l 3630 1905 l cp gs col7 1.00 shd ef gr col0 s
+% Polyline
+ [15 15] 15 sd
+gs clippath
+2895 1695 m 2925 1575 l 2955 1695 l 2955 1560 l 2895 1560 l cp
+clip
+n 2925 2025 m 2925 1575 l gs col34 1.00 shd ef gr gs col0 s gr gr
+ [] 0 sd
+% arrowhead
+n 2895 1695 m 2925 1575 l 2955 1695 l 2925 1695 l 2895 1695 l cp gs 0.00 setgray ef gr col0 s
+% Polyline
+45.000 slw
+gs clippath
+6087 6495 m 6207 6525 l 6087 6555 l 6360 6555 l 6360 6495 l cp
+clip
+n 540 6525 m 6300 6525 l gs 0.00 setgray ef gr gs col0 s gr gr
+
+% arrowhead
+n 6087 6495 m 6207 6525 l 6087 6555 l 6087 6525 l 6087 6495 l cp gs 0.00 setgray ef gr col0 s
+% Polyline
+7.500 slw
+gs clippath
+3681 6720 m 3825 6750 l 3681 6780 l 3840 6780 l 3840 6720 l cp
+2844 6780 m 2700 6750 l 2844 6720 l 2685 6720 l 2685 6780 l cp
+clip
+n 2700 6750 m 3825 6750 l gs col34 1.00 shd ef gr gs col0 s gr gr
+
+% arrowhead
+n 2844 6780 m 2700 6750 l 2844 6720 l 2820 6750 l 2844 6780 l cp gs col7 1.00 shd ef gr col0 s
+% arrowhead
+n 3681 6720 m 3825 6750 l 3681 6780 l 3705 6750 l 3681 6720 l cp gs col7 1.00 shd ef gr col0 s
+% Polyline
+gs clippath
+5256 7170 m 5400 7200 l 5256 7230 l 5415 7230 l 5415 7170 l cp
+1269 7230 m 1125 7200 l 1269 7170 l 1110 7170 l 1110 7230 l cp
+clip
+n 1125 7200 m 5400 7200 l gs col34 1.00 shd ef gr gs col0 s gr gr
+
+% arrowhead
+n 1269 7230 m 1125 7200 l 1269 7170 l 1245 7200 l 1269 7230 l cp gs col7 1.00 shd ef gr col0 s
+% arrowhead
+n 5256 7170 m 5400 7200 l 5256 7230 l 5280 7200 l 5256 7170 l cp gs col7 1.00 shd ef gr col0 s
+% Polyline
+gs clippath
+4581 6945 m 4725 6975 l 4581 7005 l 4740 7005 l 4740 6945 l cp
+1944 7005 m 1800 6975 l 1944 6945 l 1785 6945 l 1785 7005 l cp
+clip
+n 1800 6975 m 4725 6975 l gs col34 1.00 shd ef gr gs col0 s gr gr
+
+% arrowhead
+n 1944 7005 m 1800 6975 l 1944 6945 l 1920 6975 l 1944 7005 l cp gs col7 1.00 shd ef gr col0 s
+% arrowhead
+n 4581 6945 m 4725 6975 l 4581 7005 l 4605 6975 l 4581 6945 l cp gs col7 1.00 shd ef gr col0 s
+% Polyline
+gs clippath
+5706 7395 m 5850 7425 l 5706 7455 l 5865 7455 l 5865 7395 l cp
+819 7455 m 675 7425 l 819 7395 l 660 7395 l 660 7455 l cp
+clip
+n 675 7425 m 5850 7425 l gs col34 1.00 shd ef gr gs col0 s gr gr
+
+% arrowhead
+n 819 7455 m 675 7425 l 819 7395 l 795 7425 l 819 7455 l cp gs col7 1.00 shd ef gr col0 s
+% arrowhead
+n 5706 7395 m 5850 7425 l 5706 7455 l 5730 7425 l 5706 7395 l cp gs col7 1.00 shd ef gr col0 s
+% Polyline
+1 slc
+ [15 45] 45 sd
+n 675 6570 m 675 7650 l gs col34 1.00 shd ef gr gs col0 s gr [] 0 sd
+% Polyline
+ [15 45] 45 sd
+n 1125 6570 m 1125 7650 l gs col34 1.00 shd ef gr gs col0 s gr [] 0 sd
+% Polyline
+ [15 45] 45 sd
+n 1800 6570 m 1800 7650 l gs col34 1.00 shd ef gr gs col0 s gr [] 0 sd
+% Polyline
+ [15 45] 45 sd
+n 2700 6570 m 2700 7650 l gs col34 1.00 shd ef gr gs col0 s gr [] 0 sd
+% Polyline
+ [15 45] 45 sd
+n 3825 6570 m 3825 7650 l gs col34 1.00 shd ef gr gs col0 s gr [] 0 sd
+% Polyline
+ [15 45] 45 sd
+n 4725 6570 m 4725 7650 l gs col34 1.00 shd ef gr gs col0 s gr [] 0 sd
+% Polyline
+ [15 45] 45 sd
+n 5400 6570 m 5400 7650 l gs col34 1.00 shd ef gr gs col0 s gr [] 0 sd
+% Polyline
+ [15 45] 45 sd
+n 5850 6570 m 5850 7650 l gs col34 1.00 shd ef gr gs col0 s gr [] 0 sd
+% Polyline
+0 slc
+n 750 225 m 450 225 450 1050 300 arcto 4 {pop} repeat
+ 450 1350 12300 1350 300 arcto 4 {pop} repeat
+ 12600 1350 12600 525 300 arcto 4 {pop} repeat
+ 12600 225 750 225 300 arcto 4 {pop} repeat
+ cp gs col34 1.00 shd ef gr gs col0 s gr
+% Polyline
+n 8835 2250 m 8775 2250 8775 2415 60 arcto 4 {pop} repeat
+ 8775 2475 10110 2475 60 arcto 4 {pop} repeat
+ 10170 2475 10170 2310 60 arcto 4 {pop} repeat
+ 10170 2250 8835 2250 60 arcto 4 {pop} repeat
+ cp gs col35 1.00 shd ef gr gs col35 s gr
+% Polyline
+n 10635 2250 m 10575 2250 10575 2415 60 arcto 4 {pop} repeat
+ 10575 2475 11865 2475 60 arcto 4 {pop} repeat
+ 11925 2475 11925 2310 60 arcto 4 {pop} repeat
+ 11925 2250 10635 2250 60 arcto 4 {pop} repeat
+ cp gs col35 1.00 shd ef gr gs col35 s gr
+% Polyline
+n 11490 4275 m 11430 4275 11430 4440 60 arcto 4 {pop} repeat
+ 11430 4500 12315 4500 60 arcto 4 {pop} repeat
+ 12375 4500 12375 4335 60 arcto 4 {pop} repeat
+ 12375 4275 11490 4275 60 arcto 4 {pop} repeat
+ cp gs col35 1.00 shd ef gr gs col35 s gr
+% Polyline
+n 11040 5175 m 10980 5175 10980 5340 60 arcto 4 {pop} repeat
+ 10980 5400 12315 5400 60 arcto 4 {pop} repeat
+ 12375 5400 12375 5235 60 arcto 4 {pop} repeat
+ 12375 5175 11040 5175 60 arcto 4 {pop} repeat
+ cp gs col35 1.00 shd ef gr gs col35 s gr
+% Polyline
+n 9735 5175 m 9675 5175 9675 5340 60 arcto 4 {pop} repeat
+ 9675 5400 10110 5400 60 arcto 4 {pop} repeat
+ 10170 5400 10170 5235 60 arcto 4 {pop} repeat
+ 10170 5175 9735 5175 60 arcto 4 {pop} repeat
+ cp gs col35 1.00 shd ef gr gs col35 s gr
+% Polyline
+n 7260 6075 m 7200 6075 7200 6240 60 arcto 4 {pop} repeat
+ 7200 6300 7815 6300 60 arcto 4 {pop} repeat
+ 7875 6300 7875 6135 60 arcto 4 {pop} repeat
+ 7875 6075 7260 6075 60 arcto 4 {pop} repeat
+ cp gs col35 1.00 shd ef gr gs col35 s gr
+% Polyline
+n 6810 2250 m 6750 2250 6750 2415 60 arcto 4 {pop} repeat
+ 6750 2475 8130 2475 60 arcto 4 {pop} repeat
+ 8190 2475 8190 2310 60 arcto 4 {pop} repeat
+ 8190 2250 6810 2250 60 arcto 4 {pop} repeat
+ cp gs col35 1.00 shd ef gr gs col35 s gr
+% Polyline
+n 6360 3375 m 6300 3375 6300 3540 60 arcto 4 {pop} repeat
+ 6300 3600 7545 3600 60 arcto 4 {pop} repeat
+ 7605 3600 7605 3435 60 arcto 4 {pop} repeat
+ 7605 3375 6360 3375 60 arcto 4 {pop} repeat
+ cp gs col35 1.00 shd ef gr gs col35 s gr
+% Polyline
+n 6360 4275 m 6300 4275 6300 4440 60 arcto 4 {pop} repeat
+ 6300 4500 7275 4500 60 arcto 4 {pop} repeat
+ 7335 4500 7335 4335 60 arcto 4 {pop} repeat
+ 7335 4275 6360 4275 60 arcto 4 {pop} repeat
+ cp gs col35 1.00 shd ef gr gs col35 s gr
+% Polyline
+n 6360 5175 m 6300 5175 6300 5340 60 arcto 4 {pop} repeat
+ 6300 5400 7140 5400 60 arcto 4 {pop} repeat
+ 7200 5400 7200 5235 60 arcto 4 {pop} repeat
+ 7200 5175 6360 5175 60 arcto 4 {pop} repeat
+ cp gs col35 1.00 shd ef gr gs col35 s gr
+% Polyline
+gs clippath
+7365 5340 m 7245 5310 l 7365 5280 l 7230 5280 l 7230 5340 l cp
+clip
+n 9630 5310 m 7245 5310 l gs col34 1.00 shd ef gr gs col0 s gr gr
+
+% arrowhead
+n 7365 5340 m 7245 5310 l 7365 5280 l 7365 5310 l 7365 5340 l cp gs 0.00 setgray ef gr col0 s
+% Polyline
+gs clippath
+7500 4395 m 7380 4365 l 7500 4335 l 7365 4335 l 7365 4395 l cp
+clip
+n 11385 4365 m 7380 4365 l gs col34 1.00 shd ef gr gs col0 s gr gr
+
+% arrowhead
+n 7500 4395 m 7380 4365 l 7500 4335 l 7500 4365 l 7500 4395 l cp gs 0.00 setgray ef gr col0 s
+% Polyline
+n 11040 5580 m 10980 5580 10980 5745 60 arcto 4 {pop} repeat
+ 10980 5805 12180 5805 60 arcto 4 {pop} repeat
+ 12240 5805 12240 5640 60 arcto 4 {pop} repeat
+ 12240 5580 11040 5580 60 arcto 4 {pop} repeat
+ cp gs col35 1.00 shd ef gr gs col35 s gr
+% Polyline
+n 11040 5985 m 10980 5985 10980 6150 60 arcto 4 {pop} repeat
+ 10980 6210 12315 6210 60 arcto 4 {pop} repeat
+ 12375 6210 12375 6045 60 arcto 4 {pop} repeat
+ 12375 5985 11040 5985 60 arcto 4 {pop} repeat
+ cp gs col35 1.00 shd ef gr gs col35 s gr
+% Polyline
+gs clippath
+9958 5554 m 9900 5445 l 10003 5514 l 9912 5414 l 9868 5454 l cp
+clip
+n 11205 6885 m 9900 5445 l gs col34 1.00 shd ef gr gs col0 s gr gr
+
+% arrowhead
+n 9958 5554 m 9900 5445 l 10003 5514 l 9981 5534 l 9958 5554 l cp gs 0.00 setgray ef gr col0 s
+% Polyline
+n 10590 6930 m 10530 6930 10530 7095 60 arcto 4 {pop} repeat
+ 10530 7155 12225 7155 60 arcto 4 {pop} repeat
+ 12285 7155 12285 6990 60 arcto 4 {pop} repeat
+ 12285 6930 10590 6930 60 arcto 4 {pop} repeat
+ cp gs col35 1.00 shd ef gr gs col35 s gr
+% Polyline
+n 9690 6930 m 9630 6930 9630 7095 60 arcto 4 {pop} repeat
+ 9630 7155 10110 7155 60 arcto 4 {pop} repeat
+ 10170 7155 10170 6990 60 arcto 4 {pop} repeat
+ 10170 6930 9690 6930 60 arcto 4 {pop} repeat
+ cp gs col35 1.00 shd ef gr gs col35 s gr
+/Times-Roman-iso ff 120.00 scf sf
+900 7560 m
+gs 1 -1 sc (Startup, Runtime, Shutdown) col0 sh gr
+/Helvetica-Narrow-iso ff 120.00 scf sf
+6345 2970 m
+gs 1 -1 sc (ap_ctx_get\(...,) col0 sh gr
+/Helvetica-Narrow-iso ff 120.00 scf sf
+10800 2745 m
+gs 1 -1 sc (ap_get_module_config\(...) col0 sh gr
+/Helvetica-Narrow-iso ff 120.00 scf sf
+10800 2880 m
+gs 1 -1 sc (->per_dir_config,) col0 sh gr
+/Helvetica-Narrow-iso ff 120.00 scf sf
+10800 3015 m
+gs 1 -1 sc (&ssl_module\)) col0 sh gr
+% Polyline
+n 7980 4770 m 7920 4770 7920 4935 60 arcto 4 {pop} repeat
+ 7920 4995 9075 4995 60 arcto 4 {pop} repeat
+ 9135 4995 9135 4830 60 arcto 4 {pop} repeat
+ 9135 4770 7980 4770 60 arcto 4 {pop} repeat
+ cp gs col35 1.00 shd ef gr gs col35 s gr
+% Polyline
+gs clippath
+7340 2610 m 7425 2520 l 7393 2639 l 7459 2521 l 7406 2492 l cp
+clip
+n 6975 3330 m 7425 2520 l gs col34 1.00 shd ef gr gs col0 s gr gr
+
+% arrowhead
+n 7340 2610 m 7425 2520 l 7393 2639 l 7367 2625 l 7340 2610 l cp gs 0.00 setgray ef gr col0 s
+% Polyline
+gs clippath
+9336 2569 m 9450 2520 l 9373 2616 l 9480 2535 l 9444 2487 l cp
+clip
+n 7200 4230 m 9450 2520 l gs col34 1.00 shd ef gr gs col0 s gr gr
+
+% arrowhead
+n 9336 2569 m 9450 2520 l 9373 2616 l 9354 2593 l 9336 2569 l cp gs 0.00 setgray ef gr col0 s
+% Polyline
+gs clippath
+7321 5196 m 7200 5220 l 7296 5142 l 7174 5199 l 7199 5254 l cp
+clip
+n 7875 4905 m 7200 5220 l gs col34 1.00 shd ef gr gs col0 s gr gr
+
+% arrowhead
+n 7321 5196 m 7200 5220 l 7296 5142 l 7309 5169 l 7321 5196 l cp gs 0.00 setgray ef gr col0 s
+% Polyline
+gs clippath
+6720 4665 m 6750 4545 l 6780 4665 l 6780 4530 l 6720 4530 l cp
+clip
+n 6750 5130 m 6750 4545 l gs col34 1.00 shd ef gr gs col0 s gr gr
+
+% arrowhead
+n 6720 4665 m 6750 4545 l 6780 4665 l 6750 4665 l 6720 4665 l cp gs 0.00 setgray ef gr col0 s
+% Polyline
+ [15 15] 15 sd
+gs clippath
+9279 4984 m 9175 4918 l 9298 4927 l 9170 4885 l 9151 4942 l cp
+clip
+n 9850 5143 m 9175 4918 l gs col34 1.00 shd ef gr gs col0 s gr gr
+ [] 0 sd
+% arrowhead
+n 9279 4984 m 9175 4918 l 9298 4927 l 9289 4956 l 9279 4984 l cp gs 0.00 setgray ef gr col0 s
+/Helvetica-Narrow-iso ff 120.00 scf sf
+6210 4680 m
+gs 1 -1 sc (->server) col0 sh gr
+/Helvetica-Narrow-iso ff 120.00 scf sf
+8280 6120 m
+gs 1 -1 sc (ap_ctx_get\(...,"ssl"\)) col0 sh gr
+/Helvetica-Narrow-iso ff 120.00 scf sf
+7740 2700 m
+gs 1 -1 sc (ap_get_module_config\(...) col0 sh gr
+/Helvetica-Narrow-iso ff 120.00 scf sf
+7740 2835 m
+gs 1 -1 sc (->module_config,) col0 sh gr
+/Helvetica-Narrow-iso ff 120.00 scf sf
+7740 2970 m
+gs 1 -1 sc (&ssl_module\)) col0 sh gr
+/Helvetica-Narrow-iso ff 120.00 scf sf
+6345 3105 m
+gs 1 -1 sc ("ssl_module"\)) col0 sh gr
+/Times-Roman-iso ff 120.00 scf sf
+1350 7335 m
+gs 1 -1 sc (Configuration Time) col0 sh gr
+/Times-Roman-iso ff 120.00 scf sf
+2025 7110 m
+gs 1 -1 sc (Connection Duration) col0 sh gr
+/Times-Roman-iso ff 120.00 scf sf
+2835 6885 m
+gs 1 -1 sc (Request Duration) col0 sh gr
+/Helvetica-Bold-iso ff 300.00 scf sf
+6345 6795 m
+gs 1 -1 sc (t) col0 sh gr
+/Helvetica-Narrow-iso ff 120.00 scf sf
+7110 5985 m
+gs 1 -1 sc (->client) col0 sh gr
+/Helvetica-Narrow-iso ff 120.00 scf sf
+7065 5085 m
+gs 1 -1 sc (->connection) col0 sh gr
+/Helvetica-Narrow-iso ff 120.00 scf sf
+7065 4770 m
+gs 1 -1 sc (->server) col0 sh gr
+/Helvetica-Narrow-iso ff 120.00 scf sf
+8010 5445 m
+gs 1 -1 sc (SSL_get_app_data\(\)) col0 sh gr
+/Helvetica-Narrow-iso ff 120.00 scf sf
+10530 4050 m
+gs 1 -1 sc (->pSSLCtx) col0 sh gr
+/Helvetica-Narrow-iso ff 120.00 scf sf
+7875 4275 m
+gs 1 -1 sc (SSL_CTX_get_app_data\(\)) col0 sh gr
+/Helvetica-Narrow-iso ff 120.00 scf sf
+10305 5535 m
+gs 1 -1 sc (SSL_get_current_cipher\(\)) col0 sh gr
+/Helvetica-Narrow-iso ff 120.00 scf sf
+10440 5940 m
+gs 1 -1 sc (SSL_get_session\(\)) col0 sh gr
+/Helvetica-Narrow-iso ff 120.00 scf sf
+9540 7335 m
+gs 1 -1 sc (SSL_get_{r,w}bio\(\)) col0 sh gr
+/Helvetica-Narrow-iso ff 120.00 scf sf
+10125 4680 m
+gs 1 -1 sc (SSL_get_SSL_CTX\(\)) col0 sh gr
+/Helvetica-Narrow-iso ff 120.00 scf sf
+10350 5175 m
+gs 1 -1 sc (SSL_get_SSL_METHOD\(\)) col0 sh gr
+/Helvetica-Narrow-iso ff 120.00 scf sf
+11745 4770 m
+gs 1 -1 sc (->method) col0 sh gr
+/Helvetica-Narrow-iso ff 120.00 scf sf
+9945 6480 m
+gs 1 -1 sc (X509_STORE_CTX_get_app_data\(\)) col0 sh gr
+/Helvetica-Narrow-iso ff 120.00 scf sf
+10980 6705 m
+gs 1 -1 sc (SSL_CTX_get_cert_store\(\)) col0 sh gr
+/Helvetica-Narrow-iso ff 120.00 scf sf
+8280 5130 m
+gs 1 -1 sc (modssl_get_app_data2\(\)) col0 sh gr
+/Helvetica-Bold-iso ff 180.00 scf sf
+3645 1620 m
+gs 1 -1 sc (SSLDirConfig) col0 sh gr
+/Helvetica-Bold-iso ff 300.00 scf sf
+10935 3645 m
+gs 1 -1 sc (OpenSSL) col0 sh gr
+/Helvetica-Bold-iso ff 180.00 scf sf
+10935 3825 m
+gs 1 -1 sc ([SSL]) col0 sh gr
+/Helvetica-Bold-iso ff 180.00 scf sf
+11025 5760 m
+gs 1 -1 sc (SSL_CIPHER) col0 sh gr
+/Helvetica-Bold-iso ff 180.00 scf sf
+10980 6165 m
+gs 1 -1 sc (SSL_SESSION) col0 sh gr
+/Helvetica-Bold-iso ff 300.00 scf sf
+10710 7605 m
+gs 1 -1 sc (OpenSSL) col0 sh gr
+/Helvetica-Bold-iso ff 180.00 scf sf
+10575 7110 m
+gs 1 -1 sc (X509_STORE_CTX) col0 sh gr
+/Helvetica-Bold-iso ff 180.00 scf sf
+6795 2430 m
+gs 1 -1 sc (SSLModConfig) col0 sh gr
+/Helvetica-Bold-iso ff 180.00 scf sf
+8865 2430 m
+gs 1 -1 sc (SSLSrvConfig) col0 sh gr
+/Helvetica-Bold-iso ff 180.00 scf sf
+6345 3555 m
+gs 1 -1 sc (ap_global_ctx) col0 sh gr
+/Helvetica-Bold-iso ff 180.00 scf sf
+6345 4455 m
+gs 1 -1 sc (server_rec) col0 sh gr
+/Helvetica-Bold-iso ff 180.00 scf sf
+6345 5355 m
+gs 1 -1 sc (conn_rec) col0 sh gr
+/Helvetica-Bold-iso ff 180.00 scf sf
+9720 5355 m
+gs 1 -1 sc (SSL) col0 sh gr
+/Helvetica-Bold-iso ff 180.00 scf sf
+10665 2430 m
+gs 1 -1 sc (SSLDirConfig) col0 sh gr
+/Helvetica-Bold-iso ff 180.00 scf sf
+7290 6255 m
+gs 1 -1 sc (BUFF) col0 sh gr
+/Helvetica-Bold-iso ff 180.00 scf sf
+11025 5355 m
+gs 1 -1 sc (SSL_METHOD) col0 sh gr
+% Polyline
+15.000 slw
+n 750 225 m 450 225 450 8250 300 arcto 4 {pop} repeat
+ 450 8550 12300 8550 300 arcto 4 {pop} repeat
+ 12600 8550 12600 525 300 arcto 4 {pop} repeat
+ 12600 225 750 225 300 arcto 4 {pop} repeat
+ cp gs col0 s gr
+/Helvetica-Bold-iso ff 180.00 scf sf
+11475 4455 m
+gs 1 -1 sc (SSL_CTX) col0 sh gr
+/Helvetica-Bold-iso ff 180.00 scf sf
+8010 4950 m
+gs 1 -1 sc (request_rec) col0 sh gr
+/Times-Roman-iso ff 180.00 scf sf
+10575 675 m
+gs 1 -1 sc (Ralf S. Engelschall) col0 sh gr
+/Helvetica-Bold-iso ff 300.00 scf sf
+4275 675 m
+gs 1 -1 sc (Apache+mod_ssl+OpenSSL) col0 sh gr
+/Times-Roman-iso ff 150.00 scf sf
+10575 855 m
+gs 1 -1 sc (rse@engelschall.com) col0 sh gr
+/Times-Roman-iso ff 150.00 scf sf
+10575 1035 m
+gs 1 -1 sc (www.engelschall.com) col0 sh gr
+/Times-Roman-iso ff 180.00 scf sf
+900 675 m
+gs 1 -1 sc (Version 1.3) col0 sh gr
+/Times-Roman-iso ff 180.00 scf sf
+900 855 m
+gs 1 -1 sc (12-Apr-1999) col0 sh gr
+/Helvetica-Bold-iso ff 360.00 scf sf
+3915 1080 m
+gs 1 -1 sc (Data Structure Overview) col0 sh gr
+/Helvetica-Bold-iso ff 180.00 scf sf
+9720 7110 m
+gs 1 -1 sc (BIO) col0 sh gr
+/Helvetica-Bold-iso ff 180.00 scf sf
+10710 7785 m
+gs 1 -1 sc ([Crypto]) col0 sh gr
+/Helvetica-Bold-iso ff 300.00 scf sf
+8730 3465 m
+gs 1 -1 sc (mod_ssl) col0 sh gr
+/Helvetica-Bold-iso ff 300.00 scf sf
+8145 6750 m
+gs 1 -1 sc (Apache) col0 sh gr
+/Helvetica-Bold-iso ff 300.00 scf sf
+9000 8100 m
+gs 1 -1 sc (Chaining) col0 sh gr
+/Helvetica-Bold-iso ff 300.00 scf sf
+2745 8100 m
+gs 1 -1 sc (Lifetime) col0 sh gr
+/Helvetica-Bold-iso ff 180.00 scf sf
+810 6255 m
+gs 1 -1 sc (ap_global_ctx) col0 sh gr
+/Helvetica-Bold-iso ff 180.00 scf sf
+990 5805 m
+gs 1 -1 sc (SSLModConfig) col0 sh gr
+/Helvetica-Bold-iso ff 180.00 scf sf
+4050 4455 m
+gs 1 -1 sc (SSL_CTX) col0 sh gr
+/Helvetica-Bold-iso ff 180.00 scf sf
+4455 5355 m
+gs 1 -1 sc (server_rec) col0 sh gr
+/Helvetica-Bold-iso ff 180.00 scf sf
+3870 4905 m
+gs 1 -1 sc (SSLSrvConfig) col0 sh gr
+/Helvetica-Bold-iso ff 180.00 scf sf
+1845 4005 m
+gs 1 -1 sc (BUFF) col0 sh gr
+/Helvetica-Bold-iso ff 180.00 scf sf
+2070 3555 m
+gs 1 -1 sc (conn_rec) col0 sh gr
+/Helvetica-Bold-iso ff 180.00 scf sf
+2295 3105 m
+gs 1 -1 sc (BIO) col0 sh gr
+/Helvetica-Bold-iso ff 180.00 scf sf
+2565 2655 m
+gs 1 -1 sc (SSL) col0 sh gr
+/Helvetica-Bold-iso ff 180.00 scf sf
+3915 2070 m
+gs 1 -1 sc (request_rec) col0 sh gr
+$F2psEnd
+rs
+showpage
diff --git a/modules/ssl/config.m4 b/modules/ssl/config.m4
new file mode 100644
index 0000000..45eeb43
--- /dev/null
+++ b/modules/ssl/config.m4
@@ -0,0 +1,57 @@
+dnl Licensed to the Apache Software Foundation (ASF) under one or more
+dnl contributor license agreements. See the NOTICE file distributed with
+dnl this work for additional information regarding copyright ownership.
+dnl The ASF licenses this file to You under the Apache License, Version 2.0
+dnl (the "License"); you may not use this file except in compliance with
+dnl the License. You may obtain a copy of the License at
+dnl
+dnl http://www.apache.org/licenses/LICENSE-2.0
+dnl
+dnl Unless required by applicable law or agreed to in writing, software
+dnl distributed under the License is distributed on an "AS IS" BASIS,
+dnl WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+dnl See the License for the specific language governing permissions and
+dnl limitations under the License.
+
+dnl # start of module specific part
+APACHE_MODPATH_INIT(ssl)
+
+dnl # list of module object files
+ssl_objs="dnl
+mod_ssl.lo dnl
+ssl_engine_config.lo dnl
+ssl_engine_init.lo dnl
+ssl_engine_io.lo dnl
+ssl_engine_kernel.lo dnl
+ssl_engine_log.lo dnl
+ssl_engine_mutex.lo dnl
+ssl_engine_pphrase.lo dnl
+ssl_engine_rand.lo dnl
+ssl_engine_vars.lo dnl
+ssl_scache.lo dnl
+ssl_util_stapling.lo dnl
+ssl_util.lo dnl
+ssl_util_ssl.lo dnl
+ssl_engine_ocsp.lo dnl
+ssl_util_ocsp.lo dnl
+"
+dnl # hook module into the Autoconf mechanism (--enable-ssl option)
+APACHE_MODULE(ssl, [SSL/TLS support (mod_ssl)], $ssl_objs, , most, [
+ APACHE_CHECK_OPENSSL
+ if test "$ac_cv_openssl" = "yes" ; then
+ if test "x$enable_ssl" = "xshared"; then
+ # The only symbol which needs to be exported is the module
+ # structure, so ask libtool to hide everything else:
+ APR_ADDTO(MOD_SSL_LDADD, [-export-symbols-regex ssl_module])
+ fi
+ else
+ enable_ssl=no
+ fi
+])
+
+# Ensure that other modules can pick up mod_ssl.h
+APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/$modpath_current])
+
+dnl # end of module specific part
+APACHE_MODPATH_FINISH
+
diff --git a/modules/ssl/mod_ssl.c b/modules/ssl/mod_ssl.c
new file mode 100644
index 0000000..5b8c4d5
--- /dev/null
+++ b/modules/ssl/mod_ssl.c
@@ -0,0 +1,775 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* _ _
+ * _ __ ___ ___ __| | ___ ___| | mod_ssl
+ * | '_ ` _ \ / _ \ / _` | / __/ __| | Apache Interface to OpenSSL
+ * | | | | | | (_) | (_| | \__ \__ \ |
+ * |_| |_| |_|\___/ \__,_|___|___/___/_|
+ * |_____|
+ * mod_ssl.c
+ * Apache API interface structures
+ */
+
+#include "ssl_private.h"
+#include "mod_ssl.h"
+#include "mod_ssl_openssl.h"
+#include "util_md5.h"
+#include "util_mutex.h"
+#include "ap_provider.h"
+#include "http_config.h"
+
+#include "mod_proxy.h" /* for proxy_hook_section_post_config() */
+
+#include <assert.h>
+
+static int modssl_running_statically = 0;
+
+APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(ssl, SSL, int, pre_handshake,
+ (conn_rec *c,SSL *ssl,int is_proxy),
+ (c,ssl,is_proxy), OK, DECLINED);
+
+/*
+ * the table of configuration directives we provide
+ */
+
+#define SSL_CMD_ALL(name, args, desc) \
+ AP_INIT_##args("SSL"#name, ssl_cmd_SSL##name, \
+ NULL, RSRC_CONF|OR_AUTHCFG, desc),
+
+#define SSL_CMD_SRV(name, args, desc) \
+ AP_INIT_##args("SSL"#name, ssl_cmd_SSL##name, \
+ NULL, RSRC_CONF, desc),
+
+#define SSL_CMD_PXY(name, args, desc) \
+ AP_INIT_##args("SSL"#name, ssl_cmd_SSL##name, \
+ NULL, RSRC_CONF|PROXY_CONF, desc),
+
+#define SSL_CMD_DIR(name, type, args, desc) \
+ AP_INIT_##args("SSL"#name, ssl_cmd_SSL##name, \
+ NULL, OR_##type, desc),
+
+#define AP_END_CMD { NULL }
+
+static const command_rec ssl_config_cmds[] = {
+ /*
+ * Global (main-server) context configuration directives
+ */
+ SSL_CMD_SRV(PassPhraseDialog, TAKE1,
+ "SSL dialog mechanism for the pass phrase query "
+ "('builtin', '|/path/to/pipe_program', "
+ "or 'exec:/path/to/cgi_program')")
+ SSL_CMD_SRV(SessionCache, TAKE1,
+ "SSL Session Cache storage "
+ "('none', 'nonenotnull', 'dbm:/path/to/file')")
+#if defined(HAVE_OPENSSL_ENGINE_H) && defined(HAVE_ENGINE_INIT)
+ SSL_CMD_SRV(CryptoDevice, TAKE1,
+ "SSL external Crypto Device usage "
+ "('builtin', '...')")
+#endif
+ SSL_CMD_SRV(RandomSeed, TAKE23,
+ "SSL Pseudo Random Number Generator (PRNG) seeding source "
+ "('startup|connect builtin|file:/path|exec:/path [bytes]')")
+
+ /*
+ * Per-server context configuration directives
+ */
+ SSL_CMD_SRV(Engine, TAKE1,
+ "SSL switch for the protocol engine "
+ "('on', 'off')")
+ SSL_CMD_SRV(FIPS, FLAG,
+ "Enable FIPS-140 mode "
+ "(`on', `off')")
+ SSL_CMD_ALL(CipherSuite, TAKE12,
+ "Colon-delimited list of permitted SSL Ciphers, optional preceded "
+ "by protocol identifier ('XXX:...:XXX' - see manual)")
+ SSL_CMD_SRV(CertificateFile, TAKE1,
+ "SSL Server Certificate file "
+ "('/path/to/file' - PEM or DER encoded)")
+ SSL_CMD_SRV(CertificateKeyFile, TAKE1,
+ "SSL Server Private Key file "
+ "('/path/to/file' - PEM or DER encoded)")
+ SSL_CMD_SRV(CertificateChainFile, TAKE1,
+ "SSL Server CA Certificate Chain file "
+ "('/path/to/file' - PEM encoded)")
+#ifdef HAVE_TLS_SESSION_TICKETS
+ SSL_CMD_SRV(SessionTicketKeyFile, TAKE1,
+ "TLS session ticket encryption/decryption key file (RFC 5077) "
+ "('/path/to/file' - file with 48 bytes of random data)")
+#endif
+ SSL_CMD_ALL(CACertificatePath, TAKE1,
+ "SSL CA Certificate path "
+ "('/path/to/dir' - contains PEM encoded files)")
+ SSL_CMD_ALL(CACertificateFile, TAKE1,
+ "SSL CA Certificate file "
+ "('/path/to/file' - PEM encoded)")
+ SSL_CMD_SRV(CADNRequestPath, TAKE1,
+ "SSL CA Distinguished Name path "
+ "('/path/to/dir' - symlink hashes to PEM of acceptable CA names to request)")
+ SSL_CMD_SRV(CADNRequestFile, TAKE1,
+ "SSL CA Distinguished Name file "
+ "('/path/to/file' - PEM encoded to derive acceptable CA names to request)")
+ SSL_CMD_SRV(CARevocationPath, TAKE1,
+ "SSL CA Certificate Revocation List (CRL) path "
+ "('/path/to/dir' - contains PEM encoded files)")
+ SSL_CMD_SRV(CARevocationFile, TAKE1,
+ "SSL CA Certificate Revocation List (CRL) file "
+ "('/path/to/file' - PEM encoded)")
+ SSL_CMD_SRV(CARevocationCheck, RAW_ARGS,
+ "SSL CA Certificate Revocation List (CRL) checking mode")
+ SSL_CMD_ALL(VerifyClient, TAKE1,
+ "SSL Client verify type "
+ "('none', 'optional', 'require', 'optional_no_ca')")
+ SSL_CMD_ALL(VerifyDepth, TAKE1,
+ "SSL Client verify depth "
+ "('N' - number of intermediate certificates)")
+ SSL_CMD_SRV(SessionCacheTimeout, TAKE1,
+ "SSL Session Cache object lifetime "
+ "('N' - number of seconds)")
+#ifdef OPENSSL_NO_SSL3
+#define SSLv3_PROTO_PREFIX ""
+#else
+#define SSLv3_PROTO_PREFIX "SSLv3|"
+#endif
+#ifdef HAVE_TLSV1_X
+#define SSL_PROTOCOLS SSLv3_PROTO_PREFIX "TLSv1|TLSv1.1|TLSv1.2"
+#else
+#define SSL_PROTOCOLS SSLv3_PROTO_PREFIX "TLSv1"
+#endif
+ SSL_CMD_SRV(Protocol, RAW_ARGS,
+ "Enable or disable various SSL protocols "
+ "('[+-][" SSL_PROTOCOLS "] ...' - see manual)")
+ SSL_CMD_SRV(HonorCipherOrder, FLAG,
+ "Use the server's cipher ordering preference")
+ SSL_CMD_SRV(Compression, FLAG,
+ "Enable SSL level compression "
+ "(`on', `off')")
+ SSL_CMD_SRV(SessionTickets, FLAG,
+ "Enable or disable TLS session tickets"
+ "(`on', `off')")
+ SSL_CMD_SRV(InsecureRenegotiation, FLAG,
+ "Enable support for insecure renegotiation")
+ SSL_CMD_ALL(UserName, TAKE1,
+ "Set user name to SSL variable value")
+ SSL_CMD_SRV(StrictSNIVHostCheck, FLAG,
+ "Strict SNI virtual host checking")
+
+#ifdef HAVE_SRP
+ SSL_CMD_SRV(SRPVerifierFile, TAKE1,
+ "SRP verifier file "
+ "('/path/to/file' - created by srptool)")
+ SSL_CMD_SRV(SRPUnknownUserSeed, TAKE1,
+ "SRP seed for unknown users (to avoid leaking a user's existence) "
+ "('some secret text')")
+#endif
+
+ /*
+ * Proxy configuration for remote SSL connections
+ */
+ SSL_CMD_PXY(ProxyEngine, FLAG,
+ "SSL switch for the proxy protocol engine "
+ "('on', 'off')")
+ SSL_CMD_PXY(ProxyProtocol, RAW_ARGS,
+ "SSL Proxy: enable or disable SSL protocol flavors "
+ "('[+-][" SSL_PROTOCOLS "] ...' - see manual)")
+ SSL_CMD_PXY(ProxyCipherSuite, TAKE12,
+ "SSL Proxy: colon-delimited list of permitted SSL ciphers "
+ ", optionally preceded by protocol specifier ('XXX:...:XXX' - see manual)")
+ SSL_CMD_PXY(ProxyVerify, TAKE1,
+ "SSL Proxy: whether to verify the remote certificate "
+ "('on' or 'off')")
+ SSL_CMD_PXY(ProxyVerifyDepth, TAKE1,
+ "SSL Proxy: maximum certificate verification depth "
+ "('N' - number of intermediate certificates)")
+ SSL_CMD_PXY(ProxyCACertificateFile, TAKE1,
+ "SSL Proxy: file containing server certificates "
+ "('/path/to/file' - PEM encoded certificates)")
+ SSL_CMD_PXY(ProxyCACertificatePath, TAKE1,
+ "SSL Proxy: directory containing server certificates "
+ "('/path/to/dir' - contains PEM encoded certificates)")
+ SSL_CMD_PXY(ProxyCARevocationPath, TAKE1,
+ "SSL Proxy: CA Certificate Revocation List (CRL) path "
+ "('/path/to/dir' - contains PEM encoded files)")
+ SSL_CMD_PXY(ProxyCARevocationFile, TAKE1,
+ "SSL Proxy: CA Certificate Revocation List (CRL) file "
+ "('/path/to/file' - PEM encoded)")
+ SSL_CMD_PXY(ProxyCARevocationCheck, RAW_ARGS,
+ "SSL Proxy: CA Certificate Revocation List (CRL) checking mode")
+ SSL_CMD_PXY(ProxyMachineCertificateFile, TAKE1,
+ "SSL Proxy: file containing client certificates "
+ "('/path/to/file' - PEM encoded certificates)")
+ SSL_CMD_PXY(ProxyMachineCertificatePath, TAKE1,
+ "SSL Proxy: directory containing client certificates "
+ "('/path/to/dir' - contains PEM encoded certificates)")
+ SSL_CMD_PXY(ProxyMachineCertificateChainFile, TAKE1,
+ "SSL Proxy: file containing issuing certificates "
+ "of the client certificate "
+ "(`/path/to/file' - PEM encoded certificates)")
+ SSL_CMD_PXY(ProxyCheckPeerExpire, FLAG,
+ "SSL Proxy: check the peer certificate's expiration date")
+ SSL_CMD_PXY(ProxyCheckPeerCN, FLAG,
+ "SSL Proxy: check the peer certificate's CN")
+ SSL_CMD_PXY(ProxyCheckPeerName, FLAG,
+ "SSL Proxy: check the peer certificate's name "
+ "(must be present in subjectAltName extension or CN")
+
+ /*
+ * Per-directory context configuration directives
+ */
+ SSL_CMD_DIR(Options, OPTIONS, RAW_ARGS,
+ "Set one or more options to configure the SSL engine"
+ "('[+-]option[=value] ...' - see manual)")
+ SSL_CMD_DIR(RequireSSL, AUTHCFG, NO_ARGS,
+ "Require the SSL protocol for the per-directory context "
+ "(no arguments)")
+ SSL_CMD_DIR(Require, AUTHCFG, RAW_ARGS,
+ "Require a boolean expression to evaluate to true for granting access"
+ "(arbitrary complex boolean expression - see manual)")
+ SSL_CMD_DIR(RenegBufferSize, AUTHCFG, TAKE1,
+ "Configure the amount of memory that will be used for buffering the "
+ "request body if a per-location SSL renegotiation is required due to "
+ "changed access control requirements")
+
+ SSL_CMD_SRV(OCSPEnable, RAW_ARGS,
+ "Enable use of OCSP to verify certificate revocation mode ('on', 'leaf', 'off')")
+ SSL_CMD_SRV(OCSPDefaultResponder, TAKE1,
+ "URL of the default OCSP Responder")
+ SSL_CMD_SRV(OCSPOverrideResponder, FLAG,
+ "Force use of the default responder URL ('on', 'off')")
+ SSL_CMD_SRV(OCSPResponseTimeSkew, TAKE1,
+ "Maximum time difference in OCSP responses")
+ SSL_CMD_SRV(OCSPResponseMaxAge, TAKE1,
+ "Maximum age of OCSP responses")
+ SSL_CMD_SRV(OCSPResponderTimeout, TAKE1,
+ "OCSP responder query timeout")
+ SSL_CMD_SRV(OCSPUseRequestNonce, FLAG,
+ "Whether OCSP queries use a nonce or not ('on', 'off')")
+ SSL_CMD_SRV(OCSPProxyURL, TAKE1,
+ "Proxy URL to use for OCSP requests")
+
+/* Define OCSP Responder Certificate Verification Directive */
+ SSL_CMD_SRV(OCSPNoVerify, FLAG,
+ "Do not verify OCSP Responder certificate ('on', 'off')")
+/* Define OCSP Responder File Configuration Directive */
+ SSL_CMD_SRV(OCSPResponderCertificateFile, TAKE1,
+ "Trusted OCSP responder certificates"
+ "(`/path/to/file' - PEM encoded certificates)")
+
+#ifdef HAVE_OCSP_STAPLING
+ /*
+ * OCSP Stapling options
+ */
+ SSL_CMD_SRV(StaplingCache, TAKE1,
+ "SSL Stapling Response Cache storage "
+ "(`dbm:/path/to/file')")
+ SSL_CMD_SRV(UseStapling, FLAG,
+ "SSL switch for the OCSP Stapling protocol " "(`on', `off')")
+ SSL_CMD_SRV(StaplingResponseTimeSkew, TAKE1,
+ "SSL stapling option for maximum time difference in OCSP responses")
+ SSL_CMD_SRV(StaplingResponderTimeout, TAKE1,
+ "SSL stapling option for OCSP responder timeout")
+ SSL_CMD_SRV(StaplingResponseMaxAge, TAKE1,
+ "SSL stapling option for maximum age of OCSP responses")
+ SSL_CMD_SRV(StaplingStandardCacheTimeout, TAKE1,
+ "SSL stapling option for normal OCSP Response Cache Lifetime")
+ SSL_CMD_SRV(StaplingReturnResponderErrors, FLAG,
+ "SSL stapling switch to return Status Errors Back to Client"
+ "(`on', `off')")
+ SSL_CMD_SRV(StaplingFakeTryLater, FLAG,
+ "SSL stapling switch to send tryLater response to client on error "
+ "(`on', `off')")
+ SSL_CMD_SRV(StaplingErrorCacheTimeout, TAKE1,
+ "SSL stapling option for OCSP Response Error Cache Lifetime")
+ SSL_CMD_SRV(StaplingForceURL, TAKE1,
+ "SSL stapling option to Force the OCSP Stapling URL")
+#endif
+
+#ifdef HAVE_SSL_CONF_CMD
+ SSL_CMD_SRV(OpenSSLConfCmd, TAKE2,
+ "OpenSSL configuration command")
+#endif
+
+ /* Deprecated directives. */
+ AP_INIT_RAW_ARGS("SSLLog", ap_set_deprecated, NULL, OR_ALL,
+ "SSLLog directive is no longer supported - use ErrorLog."),
+ AP_INIT_RAW_ARGS("SSLLogLevel", ap_set_deprecated, NULL, OR_ALL,
+ "SSLLogLevel directive is no longer supported - use LogLevel."),
+
+ AP_END_CMD
+};
+
+/*
+ * the various processing hooks
+ */
+static int modssl_is_prelinked(void)
+{
+ apr_size_t i = 0;
+ const module *mod;
+ while ((mod = ap_prelinked_modules[i++])) {
+ if (strcmp(mod->name, "mod_ssl.c") == 0) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static apr_status_t ssl_cleanup_pre_config(void *data)
+{
+#if HAVE_OPENSSL_INIT_SSL || (OPENSSL_VERSION_NUMBER >= 0x10100000L && \
+ !defined(LIBRESSL_VERSION_NUMBER))
+ /* Openssl v1.1+ handles all termination automatically from
+ * OPENSSL_init_ssl(). Do nothing in this case.
+ */
+
+#else
+ /* Termination below is for legacy Openssl versions v1.0.x and
+ * older.
+ */
+
+ /* Corresponds to OBJ_create()s */
+ OBJ_cleanup();
+ /* Corresponds to OPENSSL_load_builtin_modules() */
+ CONF_modules_free();
+ /* Corresponds to SSL_library_init: */
+ EVP_cleanup();
+#if HAVE_ENGINE_LOAD_BUILTIN_ENGINES
+ ENGINE_cleanup();
+#endif
+#if OPENSSL_VERSION_NUMBER >= 0x1000200fL
+#ifndef OPENSSL_NO_COMP
+ SSL_COMP_free_compression_methods();
+#endif
+#endif
+
+ /* Usually needed per thread, but this parent process is single-threaded */
+#if MODSSL_USE_OPENSSL_PRE_1_1_API
+#if OPENSSL_VERSION_NUMBER >= 0x1000000fL
+ ERR_remove_thread_state(NULL);
+#else
+ ERR_remove_state(0);
+#endif
+#endif
+
+ /* Don't call ERR_free_strings in earlier versions, ERR_load_*_strings only
+ * actually loaded the error strings once per process due to static
+ * variable abuse in OpenSSL. */
+#if (OPENSSL_VERSION_NUMBER >= 0x00090805f)
+ ERR_free_strings();
+#endif
+
+ /* Also don't call CRYPTO_cleanup_all_ex_data when linked statically here;
+ * any registered ex_data indices may have been cached in static variables
+ * in OpenSSL; removing them may cause havoc. Notably, with OpenSSL
+ * versions >= 0.9.8f, COMP_CTX cleanups would not be run, which
+ * could result in a per-connection memory leak (!). */
+ if (!modssl_running_statically) {
+ CRYPTO_cleanup_all_ex_data();
+ }
+#endif
+
+ /*
+ * TODO: determine somewhere we can safely shove out diagnostics
+ * (when enabled) at this late stage in the game:
+ * CRYPTO_mem_leaks_fp(stderr);
+ */
+
+ return APR_SUCCESS;
+}
+
+static int ssl_hook_pre_config(apr_pool_t *pconf,
+ apr_pool_t *plog,
+ apr_pool_t *ptemp)
+{
+ modssl_running_statically = modssl_is_prelinked();
+
+#if HAVE_OPENSSL_INIT_SSL || (OPENSSL_VERSION_NUMBER >= 0x10100000L && \
+ !defined(LIBRESSL_VERSION_NUMBER))
+ /* Openssl v1.1+ handles all initialisation automatically, apart
+ * from hints as to how we want to use the library.
+ *
+ * We tell openssl we want to include engine support.
+ */
+ OPENSSL_init_ssl(OPENSSL_INIT_ENGINE_ALL_BUILTIN, NULL);
+
+#else
+ /* Configuration below is for legacy versions Openssl v1.0 and
+ * older.
+ */
+
+#if APR_HAS_THREADS && MODSSL_USE_OPENSSL_PRE_1_1_API
+ ssl_util_thread_id_setup(pconf);
+#endif
+#if MODSSL_USE_OPENSSL_PRE_1_1_API || defined(LIBRESSL_VERSION_NUMBER)
+ (void)CRYPTO_malloc_init();
+#else
+ OPENSSL_malloc_init();
+#endif
+ ERR_load_crypto_strings();
+ SSL_load_error_strings();
+ SSL_library_init();
+#if HAVE_ENGINE_LOAD_BUILTIN_ENGINES
+ ENGINE_load_builtin_engines();
+#endif
+ OpenSSL_add_all_algorithms();
+ OPENSSL_load_builtin_modules();
+#endif
+
+ if (OBJ_txt2nid("id-on-dnsSRV") == NID_undef) {
+ (void)OBJ_create("1.3.6.1.5.5.7.8.7", "id-on-dnsSRV",
+ "SRVName otherName form");
+ }
+
+ /* Start w/o errors (e.g. OBJ_txt2nid() above) */
+ ERR_clear_error();
+
+ /*
+ * Let us cleanup the ssl library when the module is unloaded
+ */
+ apr_pool_cleanup_register(pconf, NULL, ssl_cleanup_pre_config,
+ apr_pool_cleanup_null);
+
+ /* Register us to handle mod_log_config %c/%x variables */
+ ssl_var_log_config_register(pconf);
+
+ /* Register to handle mod_status status page generation */
+ ssl_scache_status_register(pconf);
+
+ /* Register mutex type names so they can be configured with Mutex */
+ ap_mutex_register(pconf, SSL_CACHE_MUTEX_TYPE, NULL, APR_LOCK_DEFAULT, 0);
+#ifdef HAVE_OCSP_STAPLING
+ ap_mutex_register(pconf, SSL_STAPLING_CACHE_MUTEX_TYPE, NULL,
+ APR_LOCK_DEFAULT, 0);
+ ap_mutex_register(pconf, SSL_STAPLING_REFRESH_MUTEX_TYPE, NULL,
+ APR_LOCK_DEFAULT, 0);
+#endif
+
+ return OK;
+}
+
+static SSLConnRec *ssl_init_connection_ctx(conn_rec *c,
+ ap_conf_vector_t *per_dir_config,
+ int reinit)
+{
+ SSLConnRec *sslconn = myConnConfig(c);
+ int need_setup = 0;
+
+ /* mod_proxy's (r->)per_dir_config has the lifetime of the request, thus
+ * it uses ssl_engine_set() to reset sslconn->dc when reusing SSL backend
+ * connections, so we must fall through here. But in the case where we are
+ * called from ssl_init_ssl_connection() with no per_dir_config (which also
+ * includes mod_proxy's later run_pre_connection call), sslconn->dc should
+ * be preserved if it's already set.
+ */
+ if (!sslconn) {
+ sslconn = apr_pcalloc(c->pool, sizeof(*sslconn));
+ need_setup = 1;
+ }
+ else if (!reinit) {
+ return sslconn;
+ }
+
+ /* Reinit dc in any case because it may be r->per_dir_config scoped
+ * and thus a caller like mod_proxy needs to update it per request.
+ */
+ if (per_dir_config) {
+ sslconn->dc = ap_get_module_config(per_dir_config, &ssl_module);
+ }
+ else {
+ sslconn->dc = ap_get_module_config(c->base_server->lookup_defaults,
+ &ssl_module);
+ }
+
+ if (need_setup) {
+ sslconn->server = c->base_server;
+ sslconn->verify_depth = UNSET;
+ if (c->outgoing) {
+ sslconn->cipher_suite = sslconn->dc->proxy->auth.cipher_suite;
+ }
+ else {
+ SSLSrvConfigRec *sc = mySrvConfig(c->base_server);
+ sslconn->cipher_suite = sc->server->auth.cipher_suite;
+ }
+
+ myConnConfigSet(c, sslconn);
+ }
+
+ return sslconn;
+}
+
+static int ssl_engine_status(conn_rec *c, SSLConnRec *sslconn)
+{
+ if (c->master) {
+ return DECLINED;
+ }
+ if (sslconn) {
+ /* This connection has already been configured. Check what applies. */
+ if (sslconn->disabled) {
+ return SUSPENDED;
+ }
+ if (c->outgoing) {
+ if (!sslconn->dc->proxy_enabled) {
+ return DECLINED;
+ }
+ }
+ else {
+ if (mySrvConfig(sslconn->server)->enabled != SSL_ENABLED_TRUE) {
+ return DECLINED;
+ }
+ }
+ }
+ else {
+ /* we decline by default for outgoing connections and for incoming
+ * where the base_server is not enabled. */
+ if (c->outgoing || mySrvConfig(c->base_server)->enabled != SSL_ENABLED_TRUE) {
+ return DECLINED;
+ }
+ }
+ return OK;
+}
+
+static int ssl_hook_ssl_bind_outgoing(conn_rec *c,
+ ap_conf_vector_t *per_dir_config,
+ int enable_ssl)
+{
+ SSLConnRec *sslconn;
+ int status;
+
+ sslconn = ssl_init_connection_ctx(c, per_dir_config, 1);
+ if (sslconn->ssl) {
+ /* we are already bound to this connection. We have rebound
+ * or removed the reference to a previous per_dir_config,
+ * there is nothing more to do. */
+ return OK;
+ }
+
+ status = ssl_engine_status(c, sslconn);
+ if (enable_ssl) {
+ if (status != OK) {
+ SSLSrvConfigRec *sc = mySrvConfig(sslconn->server);
+ sslconn->disabled = 1;
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10272)
+ "SSL Proxy requested for %s but not enabled for us.",
+ sc->vhost_id);
+ }
+ else {
+ sslconn->disabled = 0;
+ return OK;
+ }
+ }
+ else {
+ sslconn->disabled = 1;
+ }
+ return DECLINED;
+}
+
+int ssl_init_ssl_connection(conn_rec *c, request_rec *r)
+{
+ SSLSrvConfigRec *sc;
+ SSL *ssl;
+ SSLConnRec *sslconn;
+ char *vhost_md5;
+ int rc;
+ modssl_ctx_t *mctx;
+ server_rec *server;
+
+ /*
+ * Create or retrieve SSL context
+ */
+ sslconn = ssl_init_connection_ctx(c, r ? r->per_dir_config : NULL, 0);
+ server = sslconn->server;
+ sc = mySrvConfig(server);
+
+ /*
+ * Seed the Pseudo Random Number Generator (PRNG)
+ */
+ ssl_rand_seed(server, c->pool, SSL_RSCTX_CONNECT,
+ c->outgoing ? "Proxy: " : "Server: ");
+
+ mctx = myConnCtxConfig(c, sc);
+
+ /*
+ * Create a new SSL connection with the configured server SSL context and
+ * attach this to the socket. Additionally we register this attachment
+ * so we can detach later.
+ */
+ if (!(sslconn->ssl = ssl = SSL_new(mctx->ssl_ctx))) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(01962)
+ "Unable to create a new SSL connection from the SSL "
+ "context");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, server);
+
+ c->aborted = 1;
+
+ return DECLINED; /* XXX */
+ }
+
+ rc = ssl_run_pre_handshake(c, ssl, c->outgoing ? 1 : 0);
+ if (rc != OK && rc != DECLINED) {
+ return rc;
+ }
+
+ vhost_md5 = ap_md5_binary(c->pool, (unsigned char *)sc->vhost_id,
+ sc->vhost_id_len);
+
+ if (!SSL_set_session_id_context(ssl, (unsigned char *)vhost_md5,
+ APR_MD5_DIGESTSIZE*2))
+ {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(01963)
+ "Unable to set session id context to '%s'", vhost_md5);
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, server);
+
+ c->aborted = 1;
+
+ return DECLINED; /* XXX */
+ }
+
+ SSL_set_app_data(ssl, c);
+ modssl_set_app_data2(ssl, NULL); /* will be request_rec */
+
+ SSL_set_verify_result(ssl, X509_V_OK);
+
+ ssl_io_filter_init(c, r, ssl);
+
+ return APR_SUCCESS;
+}
+
+static const char *ssl_hook_http_scheme(const request_rec *r)
+{
+ return modssl_request_is_tls(r, NULL) ? "https" : NULL;
+}
+
+static apr_port_t ssl_hook_default_port(const request_rec *r)
+{
+ return modssl_request_is_tls(r, NULL) ? 443 : 0;
+}
+
+static int ssl_hook_pre_connection(conn_rec *c, void *csd)
+{
+ SSLSrvConfigRec *sc;
+ SSLConnRec *sslconn = myConnConfig(c);
+
+ /*
+ * Immediately stop processing if SSL is disabled for this connection
+ */
+ if (ssl_engine_status(c, sslconn) != OK) {
+ return DECLINED;
+ }
+
+ if (sslconn) {
+ sc = mySrvConfig(sslconn->server);
+ }
+ else {
+ sc = mySrvConfig(c->base_server);
+ }
+
+ /*
+ * Remember the connection information for
+ * later access inside callback functions
+ */
+
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(01964)
+ "Connection to child %ld established "
+ "(server %s)", c->id, sc->vhost_id);
+
+ return ssl_init_ssl_connection(c, NULL);
+}
+
+static int ssl_hook_process_connection(conn_rec* c)
+{
+ SSLConnRec *sslconn = myConnConfig(c);
+
+ if (sslconn && !sslconn->disabled) {
+ /* On an active SSL connection, let the input filters initialize
+ * themselves which triggers the handshake, which again triggers
+ * all kinds of useful things such as SNI and ALPN.
+ */
+ apr_bucket_brigade* temp;
+
+ temp = apr_brigade_create(c->pool, c->bucket_alloc);
+ ap_get_brigade(c->input_filters, temp,
+ AP_MODE_INIT, APR_BLOCK_READ, 0);
+ apr_brigade_destroy(temp);
+ }
+
+ return DECLINED;
+}
+
+/*
+ * the module registration phase
+ */
+
+static void ssl_register_hooks(apr_pool_t *p)
+{
+ /* ssl_hook_ReadReq needs to use the BrowserMatch settings so must
+ * run after mod_setenvif's post_read_request hook. */
+ static const char *pre_prr[] = { "mod_setenvif.c", NULL };
+ /* The ssl_init_Module post_config hook should run before mod_proxy's
+ * for the ssl proxy main configs to be merged with vhosts' before being
+ * themselves merged with mod_proxy's in proxy_hook_section_post_config.
+ */
+ static const char *b_pc[] = { "mod_proxy.c", NULL};
+
+
+ ssl_io_filter_register(p);
+
+ ap_hook_pre_connection(ssl_hook_pre_connection,NULL,NULL, APR_HOOK_MIDDLE);
+ ap_hook_process_connection(ssl_hook_process_connection,
+ NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_test_config (ssl_hook_ConfigTest, NULL,NULL, APR_HOOK_MIDDLE);
+ ap_hook_post_config (ssl_init_Module, NULL,b_pc, APR_HOOK_MIDDLE);
+ ap_hook_http_scheme (ssl_hook_http_scheme, NULL,NULL, APR_HOOK_MIDDLE);
+ ap_hook_default_port (ssl_hook_default_port, NULL,NULL, APR_HOOK_MIDDLE);
+ ap_hook_pre_config (ssl_hook_pre_config, NULL,NULL, APR_HOOK_MIDDLE);
+ ap_hook_child_init (ssl_init_Child, NULL,NULL, APR_HOOK_MIDDLE);
+ ap_hook_check_authn (ssl_hook_UserCheck, NULL,NULL, APR_HOOK_FIRST,
+ AP_AUTH_INTERNAL_PER_CONF);
+ ap_hook_fixups (ssl_hook_Fixup, NULL,NULL, APR_HOOK_MIDDLE);
+ ap_hook_check_access (ssl_hook_Access, NULL,NULL, APR_HOOK_MIDDLE,
+ AP_AUTH_INTERNAL_PER_CONF);
+ ap_hook_check_authz (ssl_hook_Auth, NULL,NULL, APR_HOOK_MIDDLE,
+ AP_AUTH_INTERNAL_PER_CONF);
+ ap_hook_post_read_request(ssl_hook_ReadReq, pre_prr,NULL, APR_HOOK_MIDDLE);
+
+ APR_OPTIONAL_HOOK(proxy, section_post_config,
+ ssl_proxy_section_post_config, NULL, NULL,
+ APR_HOOK_MIDDLE);
+
+ ssl_var_register(p);
+ ap_hook_ssl_bind_outgoing (ssl_hook_ssl_bind_outgoing, NULL, NULL, APR_HOOK_MIDDLE);
+
+ ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "ssl",
+ AUTHZ_PROVIDER_VERSION,
+ &ssl_authz_provider_require_ssl,
+ AP_AUTH_INTERNAL_PER_CONF);
+
+ ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "ssl-verify-client",
+ AUTHZ_PROVIDER_VERSION,
+ &ssl_authz_provider_verify_client,
+ AP_AUTH_INTERNAL_PER_CONF);
+}
+
+module AP_MODULE_DECLARE_DATA ssl_module = {
+ STANDARD20_MODULE_STUFF,
+ ssl_config_perdir_create, /* create per-dir config structures */
+ ssl_config_perdir_merge, /* merge per-dir config structures */
+ ssl_config_server_create, /* create per-server config structures */
+ ssl_config_server_merge, /* merge per-server config structures */
+ ssl_config_cmds, /* table of configuration directives */
+ ssl_register_hooks /* register hooks */
+#if defined(AP_MODULE_HAS_FLAGS)
+ ,AP_MODULE_FLAG_ALWAYS_MERGE /* flags */
+#endif
+};
diff --git a/modules/ssl/mod_ssl.dep b/modules/ssl/mod_ssl.dep
new file mode 100644
index 0000000..323d0f6
--- /dev/null
+++ b/modules/ssl/mod_ssl.dep
@@ -0,0 +1,1086 @@
+# Microsoft Developer Studio Generated Dependency File, included by mod_ssl.mak
+
+.\mod_ssl.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_socache.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_md5.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_ssl.h"\
+ ".\mod_ssl_openssl.h"\
+ ".\ssl_private.h"\
+ ".\ssl_util_ssl.h"\
+
+
+.\ssl_engine_config.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_socache.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\ssl_private.h"\
+ ".\ssl_util_ssl.h"\
+
+
+.\ssl_engine_init.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_mpm.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_socache.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\mpm_common.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_ssl.h"\
+ ".\mod_ssl_openssl.h"\
+ ".\ssl_private.h"\
+ ".\ssl_util_ssl.h"\
+
+
+.\ssl_engine_io.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_socache.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_date.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_ssl.h"\
+ ".\mod_ssl_openssl.h"\
+ ".\ssl_private.h"\
+ ".\ssl_util_ssl.h"\
+
+
+.\ssl_engine_kernel.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_socache.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_md5.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_md5.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\mod_ssl.h"\
+ ".\ssl_private.h"\
+ ".\ssl_util_ssl.h"\
+
+
+.\ssl_engine_log.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_socache.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\ssl_private.h"\
+ ".\ssl_util_ssl.h"\
+
+
+.\ssl_engine_mutex.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_socache.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\ssl_private.h"\
+ ".\ssl_util_ssl.h"\
+
+
+.\ssl_engine_ocsp.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_socache.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_base64.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\ssl_private.h"\
+ ".\ssl_util_ssl.h"\
+
+
+.\ssl_engine_pphrase.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_socache.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\ssl_private.h"\
+ ".\ssl_util_ssl.h"\
+
+
+.\ssl_engine_rand.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_socache.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\ssl_private.h"\
+ ".\ssl_util_ssl.h"\
+
+
+.\ssl_engine_vars.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_socache.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ "..\loggers\mod_log_config.h"\
+ ".\mod_ssl.h"\
+ ".\ssl_private.h"\
+ ".\ssl_util_ssl.h"\
+
+
+.\ssl_scache.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_socache.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ "..\generators\mod_status.h"\
+ ".\ssl_private.h"\
+ ".\ssl_util_ssl.h"\
+
+
+.\ssl_util.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_mpm.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_socache.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\ssl_private.h"\
+ ".\ssl_util_ssl.h"\
+
+
+.\ssl_util_ocsp.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_socache.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\ssl_private.h"\
+ ".\ssl_util_ssl.h"\
+
+
+.\ssl_util_ssl.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_socache.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\ssl_private.h"\
+ ".\ssl_util_ssl.h"\
+
+
+.\ssl_util_stapling.c : \
+ "..\..\include\ap_config.h"\
+ "..\..\include\ap_config_layout.h"\
+ "..\..\include\ap_expr.h"\
+ "..\..\include\ap_hooks.h"\
+ "..\..\include\ap_mmn.h"\
+ "..\..\include\ap_mpm.h"\
+ "..\..\include\ap_provider.h"\
+ "..\..\include\ap_regex.h"\
+ "..\..\include\ap_release.h"\
+ "..\..\include\ap_socache.h"\
+ "..\..\include\apache_noprobes.h"\
+ "..\..\include\http_config.h"\
+ "..\..\include\http_connection.h"\
+ "..\..\include\http_core.h"\
+ "..\..\include\http_log.h"\
+ "..\..\include\http_main.h"\
+ "..\..\include\http_protocol.h"\
+ "..\..\include\http_request.h"\
+ "..\..\include\http_vhost.h"\
+ "..\..\include\httpd.h"\
+ "..\..\include\mod_auth.h"\
+ "..\..\include\os.h"\
+ "..\..\include\scoreboard.h"\
+ "..\..\include\util_cfgtree.h"\
+ "..\..\include\util_charset.h"\
+ "..\..\include\util_ebcdic.h"\
+ "..\..\include\util_filter.h"\
+ "..\..\include\util_mutex.h"\
+ "..\..\include\util_script.h"\
+ "..\..\srclib\apr-util\include\apr_buckets.h"\
+ "..\..\srclib\apr-util\include\apr_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_optional.h"\
+ "..\..\srclib\apr-util\include\apr_optional_hooks.h"\
+ "..\..\srclib\apr-util\include\apr_uri.h"\
+ "..\..\srclib\apr-util\include\apr_xlate.h"\
+ "..\..\srclib\apr-util\include\apu.h"\
+ "..\..\srclib\apr\include\apr.h"\
+ "..\..\srclib\apr\include\apr_allocator.h"\
+ "..\..\srclib\apr\include\apr_dso.h"\
+ "..\..\srclib\apr\include\apr_errno.h"\
+ "..\..\srclib\apr\include\apr_file_info.h"\
+ "..\..\srclib\apr\include\apr_file_io.h"\
+ "..\..\srclib\apr\include\apr_fnmatch.h"\
+ "..\..\srclib\apr\include\apr_general.h"\
+ "..\..\srclib\apr\include\apr_global_mutex.h"\
+ "..\..\srclib\apr\include\apr_hash.h"\
+ "..\..\srclib\apr\include\apr_inherit.h"\
+ "..\..\srclib\apr\include\apr_lib.h"\
+ "..\..\srclib\apr\include\apr_mmap.h"\
+ "..\..\srclib\apr\include\apr_network_io.h"\
+ "..\..\srclib\apr\include\apr_poll.h"\
+ "..\..\srclib\apr\include\apr_pools.h"\
+ "..\..\srclib\apr\include\apr_portable.h"\
+ "..\..\srclib\apr\include\apr_proc_mutex.h"\
+ "..\..\srclib\apr\include\apr_ring.h"\
+ "..\..\srclib\apr\include\apr_shm.h"\
+ "..\..\srclib\apr\include\apr_strings.h"\
+ "..\..\srclib\apr\include\apr_tables.h"\
+ "..\..\srclib\apr\include\apr_thread_mutex.h"\
+ "..\..\srclib\apr\include\apr_thread_proc.h"\
+ "..\..\srclib\apr\include\apr_time.h"\
+ "..\..\srclib\apr\include\apr_user.h"\
+ "..\..\srclib\apr\include\apr_want.h"\
+ ".\ssl_private.h"\
+ ".\ssl_util_ssl.h"\
+
+
+..\..\build\win32\httpd.rc : \
+ "..\..\include\ap_release.h"\
+
diff --git a/modules/ssl/mod_ssl.dsp b/modules/ssl/mod_ssl.dsp
new file mode 100644
index 0000000..65b554d
--- /dev/null
+++ b/modules/ssl/mod_ssl.dsp
@@ -0,0 +1,195 @@
+# Microsoft Developer Studio Project File - Name="mod_ssl" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_ssl - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_ssl.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_ssl.mak" CFG="mod_ssl - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_ssl - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_ssl - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_ssl - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../generators" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/openssl/inc32" /I "../md" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "WIN32_LEAN_AND_MEAN" /D "NO_IDEA" /D "NO_RC5" /D "NO_MDC2" /D "OPENSSL_NO_IDEA" /D "OPENSSL_NO_RC5" /D "OPENSSL_NO_MDC2" /D "HAVE_OPENSSL" /D "HAVE_SSL_SET_STATE" /D "HAVE_OPENSSL_ENGINE_H" /D "HAVE_ENGINE_INIT" /D "HAVE_ENGINE_LOAD_BUILTIN_ENGINES" /D "SSL_DECLARE_EXPORT" /Fd"Release\mod_ssl_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_ssl.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_ssl.so" /d LONG_NAME="proxy_ssl_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_ssl.so" /base:@..\..\os\win32\BaseAddr.ref,mod_ssl.so
+# ADD LINK32 kernel32.lib user32.lib wsock32.lib ws2_32.lib advapi32.lib gdi32.lib libeay32.lib ssleay32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_ssl.so" /libpath:"../../srclib/openssl/out32dll" /libpath:"../../srclib/openssl/out32" /base:@..\..\os\win32\BaseAddr.ref,mod_ssl.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_ssl.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_ssl - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../generators" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../../srclib/openssl/inc32" /I "../md" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "WIN32_LEAN_AND_MEAN" /D "NO_IDEA" /D "NO_RC5" /D "NO_MDC2" /D "OPENSSL_NO_IDEA" /D "OPENSSL_NO_RC5" /D "OPENSSL_NO_MDC2" /D "HAVE_OPENSSL" /D "HAVE_SSL_SET_STATE" /D "HAVE_OPENSSL_ENGINE_H" /D "HAVE_ENGINE_INIT" /D "HAVE_ENGINE_LOAD_BUILTIN_ENGINES" /D "SSL_DECLARE_EXPORT" /Fd"Debug\mod_ssl_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_ssl.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_ssl.so" /d LONG_NAME="proxy_ssl_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_ssl.so" /base:@..\..\os\win32\BaseAddr.ref,mod_ssl.so
+# ADD LINK32 kernel32.lib user32.lib wsock32.lib ws2_32.lib advapi32.lib gdi32.lib libeay32.lib ssleay32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_ssl.so" /libpath:"../../srclib/openssl/out32dll.dbg" /libpath:"../../srclib/openssl/out32.dbg" /libpath:"../../srclib/openssl/out32dll" /libpath:"../../srclib/openssl/out32" /base:@..\..\os\win32\BaseAddr.ref,mod_ssl.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_ssl.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_ssl - Win32 Release"
+# Name "mod_ssl - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "*.c"
+# Begin Source File
+
+SOURCE=.\mod_ssl.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\ssl_engine_config.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\ssl_engine_init.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\ssl_engine_io.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\ssl_engine_kernel.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\ssl_engine_log.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\ssl_engine_mutex.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\ssl_engine_pphrase.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\ssl_engine_rand.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\ssl_engine_vars.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\ssl_engine_ocsp.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\ssl_util_ocsp.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\ssl_scache.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\ssl_util_stapling.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\ssl_util.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\ssl_util_ssl.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "*.h"
+# Begin Source File
+
+SOURCE=.\mod_ssl.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\ssl_private.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\ssl_util_ssl.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\ssl_util_table.h
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/ssl/mod_ssl.h b/modules/ssl/mod_ssl.h
new file mode 100644
index 0000000..a360911
--- /dev/null
+++ b/modules/ssl/mod_ssl.h
@@ -0,0 +1,120 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file mod_ssl.h
+ * @brief SSL extension module for Apache
+ *
+ * @defgroup MOD_SSL mod_ssl
+ * @ingroup APACHE_MODS
+ * @{
+ */
+
+#ifndef __MOD_SSL_H__
+#define __MOD_SSL_H__
+
+#include "httpd.h"
+#include "http_config.h"
+#include "apr_optional.h"
+#include "apr_tables.h" /* for apr_array_header_t */
+
+/* Create a set of SSL_DECLARE(type), SSL_DECLARE_NONSTD(type) and
+ * SSL_DECLARE_DATA with appropriate export and import tags for the platform
+ */
+#if !defined(WIN32)
+#define SSL_DECLARE(type) type
+#define SSL_DECLARE_NONSTD(type) type
+#define SSL_DECLARE_DATA
+#elif defined(SSL_DECLARE_STATIC)
+#define SSL_DECLARE(type) type __stdcall
+#define SSL_DECLARE_NONSTD(type) type
+#define SSL_DECLARE_DATA
+#elif defined(SSL_DECLARE_EXPORT)
+#define SSL_DECLARE(type) __declspec(dllexport) type __stdcall
+#define SSL_DECLARE_NONSTD(type) __declspec(dllexport) type
+#define SSL_DECLARE_DATA __declspec(dllexport)
+#else
+#define SSL_DECLARE(type) __declspec(dllimport) type __stdcall
+#define SSL_DECLARE_NONSTD(type) __declspec(dllimport) type
+#define SSL_DECLARE_DATA __declspec(dllimport)
+#endif
+
+/** The ssl_var_lookup() optional function retrieves SSL environment
+ * variables. */
+APR_DECLARE_OPTIONAL_FN(char *, ssl_var_lookup,
+ (apr_pool_t *, server_rec *,
+ conn_rec *, request_rec *,
+ char *));
+
+/** The ssl_ext_list() optional function attempts to build an array
+ * of all the values contained in the named X.509 extension. The
+ * returned array will be created in the supplied pool.
+ * The client certificate is used if peer is non-zero; the server
+ * certificate is used otherwise.
+ * Extension specifies the extensions to use as a string. This can be
+ * one of the "known" long or short names, or a numeric OID,
+ * e.g. "1.2.3.4", 'nsComment' and 'DN' are all valid.
+ * A pointer to an apr_array_header_t structure is returned if at
+ * least one matching extension is found, NULL otherwise.
+ */
+APR_DECLARE_OPTIONAL_FN(apr_array_header_t *, ssl_ext_list,
+ (apr_pool_t *p, conn_rec *c, int peer,
+ const char *extension));
+
+/** An optional function which returns non-zero if the given connection
+ * is using SSL/TLS. */
+APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec *));
+
+/** The ssl_proxy_enable() and ssl_engine_{set,disable}() optional
+ * functions are used by mod_proxy to enable use of SSL for outgoing
+ * connections. */
+
+APR_DECLARE_OPTIONAL_FN(int, ssl_proxy_enable, (conn_rec *));
+APR_DECLARE_OPTIONAL_FN(int, ssl_engine_disable, (conn_rec *));
+APR_DECLARE_OPTIONAL_FN(int, ssl_engine_set, (conn_rec *,
+ ap_conf_vector_t *,
+ int proxy, int enable));
+
+/* Check for availability of new hooks */
+#define SSL_CERT_HOOKS
+#ifdef SSL_CERT_HOOKS
+
+/** Lets others add certificate and key files to the given server.
+ * For each cert a key must also be added.
+ * @param cert_file and array of const char* with the path to the certificate chain
+ * @param key_file and array of const char* with the path to the private key file
+ */
+APR_DECLARE_EXTERNAL_HOOK(ssl, SSL, int, add_cert_files,
+ (server_rec *s, apr_pool_t *p,
+ apr_array_header_t *cert_files,
+ apr_array_header_t *key_files))
+
+/** In case no certificates are available for a server, this
+ * lets other modules add a fallback certificate for the time
+ * being. Regular requests against this server will be answered
+ * with a 503.
+ * @param cert_file and array of const char* with the path to the certificate chain
+ * @param key_file and array of const char* with the path to the private key file
+ */
+APR_DECLARE_EXTERNAL_HOOK(ssl, SSL, int, add_fallback_cert_files,
+ (server_rec *s, apr_pool_t *p,
+ apr_array_header_t *cert_files,
+ apr_array_header_t *key_files))
+
+#endif /* SSL_CERT_HOOKS */
+
+#endif /* __MOD_SSL_H__ */
+/** @} */
diff --git a/modules/ssl/mod_ssl.mak b/modules/ssl/mod_ssl.mak
new file mode 100644
index 0000000..ea77195
--- /dev/null
+++ b/modules/ssl/mod_ssl.mak
@@ -0,0 +1,500 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on mod_ssl.dsp
+!IF "$(CFG)" == ""
+CFG=mod_ssl - Win32 Release
+!MESSAGE No configuration specified. Defaulting to mod_ssl - Win32 Release.
+!ENDIF
+
+!IF "$(CFG)" != "mod_ssl - Win32 Release" && "$(CFG)" != "mod_ssl - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_ssl.mak" CFG="mod_ssl - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_ssl - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_ssl - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(_HAVE_OSSL110)" == "1"
+SSLCRP=libcrypto
+SSLLIB=libssl
+SSLINC=/I ../../srclib/openssl/include
+SSLBIN=../../srclib/openssl
+!ELSE
+SSLCRP=libeay32
+SSLLIB=ssleay32
+SSLINC=/I ../../srclib/openssl/inc32
+SSLBIN=../../srclib/openssl/out32dll
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_ssl - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_ssl.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_ssl.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_ssl.obj"
+ -@erase "$(INTDIR)\mod_ssl.res"
+ -@erase "$(INTDIR)\mod_ssl_src.idb"
+ -@erase "$(INTDIR)\mod_ssl_src.pdb"
+ -@erase "$(INTDIR)\ssl_engine_config.obj"
+ -@erase "$(INTDIR)\ssl_engine_init.obj"
+ -@erase "$(INTDIR)\ssl_engine_io.obj"
+ -@erase "$(INTDIR)\ssl_engine_kernel.obj"
+ -@erase "$(INTDIR)\ssl_engine_log.obj"
+ -@erase "$(INTDIR)\ssl_engine_mutex.obj"
+ -@erase "$(INTDIR)\ssl_engine_ocsp.obj"
+ -@erase "$(INTDIR)\ssl_engine_pphrase.obj"
+ -@erase "$(INTDIR)\ssl_engine_rand.obj"
+ -@erase "$(INTDIR)\ssl_engine_vars.obj"
+ -@erase "$(INTDIR)\ssl_scache.obj"
+ -@erase "$(INTDIR)\ssl_util.obj"
+ -@erase "$(INTDIR)\ssl_util_ocsp.obj"
+ -@erase "$(INTDIR)\ssl_util_ssl.obj"
+ -@erase "$(INTDIR)\ssl_util_stapling.obj"
+ -@erase "$(OUTDIR)\mod_ssl.exp"
+ -@erase "$(OUTDIR)\mod_ssl.lib"
+ -@erase "$(OUTDIR)\mod_ssl.pdb"
+ -@erase "$(OUTDIR)\mod_ssl.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../generators" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../md" $(SSLINC) /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "WIN32_LEAN_AND_MEAN" /D "NO_IDEA" /D "NO_RC5" /D "NO_MDC2" /D "OPENSSL_NO_IDEA" /D "OPENSSL_NO_RC5" /D "OPENSSL_NO_MDC2" /D "HAVE_OPENSSL" /D "HAVE_SSL_SET_STATE" /D "HAVE_OPENSSL_ENGINE_H" /D "HAVE_ENGINE_INIT" /D "HAVE_ENGINE_LOAD_BUILTIN_ENGINES" /D "SSL_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_ssl_src" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_ssl.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_ssl.so" /d LONG_NAME="proxy_ssl_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_ssl.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib user32.lib wsock32.lib ws2_32.lib advapi32.lib gdi32.lib $(SSLCRP).lib $(SSLLIB).lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_ssl.pdb" /debug /out:"$(OUTDIR)\mod_ssl.so" /implib:"$(OUTDIR)\mod_ssl.lib" /libpath:"$(SSLBIN)" /libpath:"../../srclib/openssl/out32" /base:@..\..\os\win32\BaseAddr.ref,mod_ssl.so /opt:ref
+LINK32_OBJS= \
+ "$(INTDIR)\mod_ssl.obj" \
+ "$(INTDIR)\ssl_engine_config.obj" \
+ "$(INTDIR)\ssl_engine_init.obj" \
+ "$(INTDIR)\ssl_engine_io.obj" \
+ "$(INTDIR)\ssl_engine_kernel.obj" \
+ "$(INTDIR)\ssl_engine_log.obj" \
+ "$(INTDIR)\ssl_engine_mutex.obj" \
+ "$(INTDIR)\ssl_engine_pphrase.obj" \
+ "$(INTDIR)\ssl_engine_rand.obj" \
+ "$(INTDIR)\ssl_engine_vars.obj" \
+ "$(INTDIR)\ssl_engine_ocsp.obj" \
+ "$(INTDIR)\ssl_util_ocsp.obj" \
+ "$(INTDIR)\ssl_scache.obj" \
+ "$(INTDIR)\ssl_util_stapling.obj" \
+ "$(INTDIR)\ssl_util.obj" \
+ "$(INTDIR)\ssl_util_ssl.obj" \
+ "$(INTDIR)\mod_ssl.res" \
+ "..\..\srclib\apr\Release\libapr-1.lib" \
+ "..\..\srclib\apr-util\Release\libaprutil-1.lib" \
+ "..\..\Release\libhttpd.lib"
+
+"$(OUTDIR)\mod_ssl.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Release\mod_ssl.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_ssl.so"
+ if exist .\Release\mod_ssl.so.manifest mt.exe -manifest .\Release\mod_ssl.so.manifest -outputresource:.\Release\mod_ssl.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ELSEIF "$(CFG)" == "mod_ssl - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+!IF "$(RECURSE)" == "0"
+
+ALL : "$(OUTDIR)\mod_ssl.so" "$(DS_POSTBUILD_DEP)"
+
+!ELSE
+
+ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_ssl.so" "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+!IF "$(RECURSE)" == "1"
+CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN"
+!ELSE
+CLEAN :
+!ENDIF
+ -@erase "$(INTDIR)\mod_ssl.obj"
+ -@erase "$(INTDIR)\mod_ssl.res"
+ -@erase "$(INTDIR)\mod_ssl_src.idb"
+ -@erase "$(INTDIR)\mod_ssl_src.pdb"
+ -@erase "$(INTDIR)\ssl_engine_config.obj"
+ -@erase "$(INTDIR)\ssl_engine_init.obj"
+ -@erase "$(INTDIR)\ssl_engine_io.obj"
+ -@erase "$(INTDIR)\ssl_engine_kernel.obj"
+ -@erase "$(INTDIR)\ssl_engine_log.obj"
+ -@erase "$(INTDIR)\ssl_engine_mutex.obj"
+ -@erase "$(INTDIR)\ssl_engine_ocsp.obj"
+ -@erase "$(INTDIR)\ssl_engine_pphrase.obj"
+ -@erase "$(INTDIR)\ssl_engine_rand.obj"
+ -@erase "$(INTDIR)\ssl_engine_vars.obj"
+ -@erase "$(INTDIR)\ssl_scache.obj"
+ -@erase "$(INTDIR)\ssl_util.obj"
+ -@erase "$(INTDIR)\ssl_util_ocsp.obj"
+ -@erase "$(INTDIR)\ssl_util_ssl.obj"
+ -@erase "$(INTDIR)\ssl_util_stapling.obj"
+ -@erase "$(OUTDIR)\mod_ssl.exp"
+ -@erase "$(OUTDIR)\mod_ssl.lib"
+ -@erase "$(OUTDIR)\mod_ssl.pdb"
+ -@erase "$(OUTDIR)\mod_ssl.so"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../generators" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /I "../md" $(SSLINC) /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "WIN32_LEAN_AND_MEAN" /D "NO_IDEA" /D "NO_RC5" /D "NO_MDC2" /D "OPENSSL_NO_IDEA" /D "OPENSSL_NO_RC5" /D "OPENSSL_NO_MDC2" /D "HAVE_OPENSSL" /D "HAVE_SSL_SET_STATE" /D "HAVE_OPENSSL_ENGINE_H" /D "HAVE_ENGINE_INIT" /D "HAVE_ENGINE_LOAD_BUILTIN_ENGINES" /D "SSL_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_ssl_src" /FD /EHsc /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_ssl.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_ssl.so" /d LONG_NAME="proxy_ssl_module for Apache"
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_ssl.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib user32.lib wsock32.lib ws2_32.lib advapi32.lib gdi32.lib $(SSLCRP).lib $(SSLLIB).lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_ssl.pdb" /debug /out:"$(OUTDIR)\mod_ssl.so" /implib:"$(OUTDIR)\mod_ssl.lib" /libpath:"../../srclib/openssl/out32dll.dbg" /libpath:"../../srclib/openssl/out32.dbg" /libpath:"$(SSLBIN)" /libpath:"../../srclib/openssl/out32" /base:@..\..\os\win32\BaseAddr.ref,mod_ssl.so
+LINK32_OBJS= \
+ "$(INTDIR)\mod_ssl.obj" \
+ "$(INTDIR)\ssl_engine_config.obj" \
+ "$(INTDIR)\ssl_engine_init.obj" \
+ "$(INTDIR)\ssl_engine_io.obj" \
+ "$(INTDIR)\ssl_engine_kernel.obj" \
+ "$(INTDIR)\ssl_engine_log.obj" \
+ "$(INTDIR)\ssl_engine_mutex.obj" \
+ "$(INTDIR)\ssl_engine_pphrase.obj" \
+ "$(INTDIR)\ssl_engine_rand.obj" \
+ "$(INTDIR)\ssl_engine_vars.obj" \
+ "$(INTDIR)\ssl_engine_ocsp.obj" \
+ "$(INTDIR)\ssl_util_ocsp.obj" \
+ "$(INTDIR)\ssl_scache.obj" \
+ "$(INTDIR)\ssl_util_stapling.obj" \
+ "$(INTDIR)\ssl_util.obj" \
+ "$(INTDIR)\ssl_util_ssl.obj" \
+ "$(INTDIR)\mod_ssl.res" \
+ "..\..\srclib\apr\Debug\libapr-1.lib" \
+ "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \
+ "..\..\Debug\libhttpd.lib"
+
+"$(OUTDIR)\mod_ssl.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+TargetPath=.\Debug\mod_ssl.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep
+
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_ssl.so"
+ if exist .\Debug\mod_ssl.so.manifest mt.exe -manifest .\Debug\mod_ssl.so.manifest -outputresource:.\Debug\mod_ssl.so;2
+ echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)"
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("mod_ssl.dep")
+!INCLUDE "mod_ssl.dep"
+!ELSE
+!MESSAGE Warning: cannot find "mod_ssl.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "mod_ssl - Win32 Release" || "$(CFG)" == "mod_ssl - Win32 Debug"
+SOURCE=.\mod_ssl.c
+
+"$(INTDIR)\mod_ssl.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\ssl_engine_config.c
+
+"$(INTDIR)\ssl_engine_config.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\ssl_engine_init.c
+
+"$(INTDIR)\ssl_engine_init.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\ssl_engine_io.c
+
+"$(INTDIR)\ssl_engine_io.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\ssl_engine_kernel.c
+
+"$(INTDIR)\ssl_engine_kernel.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\ssl_engine_log.c
+
+"$(INTDIR)\ssl_engine_log.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\ssl_engine_mutex.c
+
+"$(INTDIR)\ssl_engine_mutex.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\ssl_engine_ocsp.c
+
+"$(INTDIR)\ssl_engine_ocsp.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\ssl_engine_pphrase.c
+
+"$(INTDIR)\ssl_engine_pphrase.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\ssl_engine_rand.c
+
+"$(INTDIR)\ssl_engine_rand.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\ssl_engine_vars.c
+
+"$(INTDIR)\ssl_engine_vars.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\ssl_scache.c
+
+"$(INTDIR)\ssl_scache.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\ssl_util.c
+
+"$(INTDIR)\ssl_util.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\ssl_util_ocsp.c
+
+"$(INTDIR)\ssl_util_ocsp.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\ssl_util_ssl.c
+
+"$(INTDIR)\ssl_util_ssl.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\ssl_util_stapling.c
+
+"$(INTDIR)\ssl_util_stapling.obj" : $(SOURCE) "$(INTDIR)"
+
+
+!IF "$(CFG)" == "mod_ssl - Win32 Release"
+
+"libapr - Win32 Release" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release"
+ cd "..\..\modules\ssl"
+
+"libapr - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\ssl"
+
+!ELSEIF "$(CFG)" == "mod_ssl - Win32 Debug"
+
+"libapr - Win32 Debug" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug"
+ cd "..\..\modules\ssl"
+
+"libapr - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\ssl"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_ssl - Win32 Release"
+
+"libaprutil - Win32 Release" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release"
+ cd "..\..\modules\ssl"
+
+"libaprutil - Win32 ReleaseCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN
+ cd "..\..\modules\ssl"
+
+!ELSEIF "$(CFG)" == "mod_ssl - Win32 Debug"
+
+"libaprutil - Win32 Debug" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug"
+ cd "..\..\modules\ssl"
+
+"libaprutil - Win32 DebugCLEAN" :
+ cd ".\..\..\srclib\apr-util"
+ $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN
+ cd "..\..\modules\ssl"
+
+!ENDIF
+
+!IF "$(CFG)" == "mod_ssl - Win32 Release"
+
+"libhttpd - Win32 Release" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release"
+ cd ".\modules\ssl"
+
+"libhttpd - Win32 ReleaseCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN
+ cd ".\modules\ssl"
+
+!ELSEIF "$(CFG)" == "mod_ssl - Win32 Debug"
+
+"libhttpd - Win32 Debug" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug"
+ cd ".\modules\ssl"
+
+"libhttpd - Win32 DebugCLEAN" :
+ cd ".\..\.."
+ $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN
+ cd ".\modules\ssl"
+
+!ENDIF
+
+SOURCE=..\..\build\win32\httpd.rc
+
+!IF "$(CFG)" == "mod_ssl - Win32 Release"
+
+
+"$(INTDIR)\mod_ssl.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_ssl.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_ssl.so" /d LONG_NAME="proxy_ssl_module for Apache" $(SOURCE)
+
+
+!ELSEIF "$(CFG)" == "mod_ssl - Win32 Debug"
+
+
+"$(INTDIR)\mod_ssl.res" : $(SOURCE) "$(INTDIR)"
+ $(RSC) /l 0x409 /fo"$(INTDIR)\mod_ssl.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_ssl.so" /d LONG_NAME="proxy_ssl_module for Apache" $(SOURCE)
+
+
+!ENDIF
+
+
+!ENDIF
+
diff --git a/modules/ssl/mod_ssl_openssl.h b/modules/ssl/mod_ssl_openssl.h
new file mode 100644
index 0000000..d4f684f
--- /dev/null
+++ b/modules/ssl/mod_ssl_openssl.h
@@ -0,0 +1,113 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file mod_ssl_openssl.h
+ * @brief Interface to OpenSSL-specific APIs provided by mod_ssl
+ *
+ * @defgroup MOD_SSL mod_ssl_openssl
+ * @ingroup APACHE_MODS
+ * @{
+ */
+
+#ifndef __MOD_SSL_OPENSSL_H__
+#define __MOD_SSL_OPENSSL_H__
+
+#include "mod_ssl.h"
+
+/* OpenSSL headers */
+
+#ifndef SSL_PRIVATE_H
+#include <openssl/opensslv.h>
+#if (OPENSSL_VERSION_NUMBER >= 0x10001000)
+/* must be defined before including ssl.h */
+#define OPENSSL_NO_SSL_INTERN
+#endif
+#include <openssl/ssl.h>
+#endif
+
+/**
+ * init_server hook -- allow SSL_CTX-specific initialization to be performed by
+ * a module for each SSL-enabled server (one at a time)
+ * @param s SSL-enabled [virtual] server
+ * @param p pconf pool
+ * @param is_proxy 1 if this server supports backend connections
+ * over SSL/TLS, 0 if it supports client connections over SSL/TLS
+ * @param ctx OpenSSL SSL Context for the server
+ */
+APR_DECLARE_EXTERNAL_HOOK(ssl, SSL, int, init_server,
+ (server_rec *s, apr_pool_t *p, int is_proxy, SSL_CTX *ctx))
+
+/**
+ * pre_handshake hook
+ * @param c conn_rec for new connection from client or to backend server
+ * @param ssl OpenSSL SSL Connection for the client or backend server
+ * @param is_proxy 1 if this handshake is for a backend connection, 0 otherwise
+ */
+APR_DECLARE_EXTERNAL_HOOK(ssl, SSL, int, pre_handshake,
+ (conn_rec *c, SSL *ssl, int is_proxy))
+
+/**
+ * proxy_post_handshake hook -- allow module to abort after successful
+ * handshake with backend server and subsequent peer checks
+ * @param c conn_rec for connection to backend server
+ * @param ssl OpenSSL SSL Connection for the client or backend server
+ */
+APR_DECLARE_EXTERNAL_HOOK(ssl, SSL, int, proxy_post_handshake,
+ (conn_rec *c, SSL *ssl))
+
+/** On TLS connections that do not relate to a configured virtual host,
+ * allow other modules to provide a X509 certificate and EVP_PKEY to
+ * be used on the connection. This first hook which does not
+ * return DECLINED will determine the outcome. */
+APR_DECLARE_EXTERNAL_HOOK(ssl, SSL, int, answer_challenge,
+ (conn_rec *c, const char *server_name,
+ X509 **pcert, EVP_PKEY **pkey))
+
+/** During post_config phase, ask around if someone wants to provide
+ * OCSP stapling status information for the given cert (with the also
+ * provided issuer certificate). The first hook which does not
+ * return DECLINED promises to take responsibility (and respond
+ * in later calls via hook ssl_get_stapling_status).
+ * If no hook takes over, mod_ssl's own stapling implementation will
+ * be applied (if configured).
+ */
+APR_DECLARE_EXTERNAL_HOOK(ssl, SSL, int, init_stapling_status,
+ (server_rec *s, apr_pool_t *p,
+ X509 *cert, X509 *issuer))
+
+/** Anyone answering positive to ssl_init_stapling_status for a
+ * certificate, needs to register here and supply the actual OCSP stapling
+ * status data (OCSP_RESP) for a new connection.
+ * A hook supplying the response data must return APR_SUCCESS.
+ * The data is returned in DER encoded bytes via pder and pderlen. The
+ * returned pointer may be NULL, which indicates that data is (currently)
+ * unavailable.
+ * If DER data is returned, it MUST come from a response with
+ * status OCSP_RESPONSE_STATUS_SUCCESSFUL and V_OCSP_CERTSTATUS_GOOD
+ * or V_OCSP_CERTSTATUS_REVOKED, not V_OCSP_CERTSTATUS_UNKNOWN. This means
+ * errors in OCSP retrieval are to be handled/logged by the hook and
+ * are not done by mod_ssl.
+ * Any DER bytes returned MUST be allocated via malloc() and ownership
+ * passes to mod_ssl. Meaning, the hook must return a malloced copy of
+ * the data it has. mod_ssl (or OpenSSL) will free it.
+ */
+APR_DECLARE_EXTERNAL_HOOK(ssl, SSL, int, get_stapling_status,
+ (unsigned char **pder, int *pderlen,
+ conn_rec *c, server_rec *s, X509 *cert))
+
+#endif /* __MOD_SSL_OPENSSL_H__ */
+/** @} */
diff --git a/modules/ssl/ssl_engine_config.c b/modules/ssl/ssl_engine_config.c
new file mode 100644
index 0000000..de18b8f
--- /dev/null
+++ b/modules/ssl/ssl_engine_config.c
@@ -0,0 +1,2158 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* _ _
+ * _ __ ___ ___ __| | ___ ___| | mod_ssl
+ * | '_ ` _ \ / _ \ / _` | / __/ __| | Apache Interface to OpenSSL
+ * | | | | | | (_) | (_| | \__ \__ \ |
+ * |_| |_| |_|\___/ \__,_|___|___/___/_|
+ * |_____|
+ * ssl_engine_config.c
+ * Apache Configuration Directives
+ */
+ /* ``Damned if you do,
+ damned if you don't.''
+ -- Unknown */
+#include "ssl_private.h"
+#include "util_mutex.h"
+#include "ap_provider.h"
+
+/* _________________________________________________________________
+**
+** Support for Global Configuration
+** _________________________________________________________________
+*/
+
+#define SSL_MOD_CONFIG_KEY "ssl_module"
+
+SSLModConfigRec *ssl_config_global_create(server_rec *s)
+{
+ apr_pool_t *pool = s->process->pool;
+ SSLModConfigRec *mc;
+ void *vmc;
+
+ apr_pool_userdata_get(&vmc, SSL_MOD_CONFIG_KEY, pool);
+ if (vmc) {
+ return vmc; /* reused for lifetime of the server */
+ }
+
+ /*
+ * allocate an own subpool which survives server restarts
+ */
+ mc = (SSLModConfigRec *)apr_palloc(pool, sizeof(*mc));
+ mc->pPool = pool;
+ mc->bFixed = FALSE;
+
+ /*
+ * initialize per-module configuration
+ */
+ mc->sesscache_mode = SSL_SESS_CACHE_OFF;
+ mc->sesscache = NULL;
+ mc->pMutex = NULL;
+ mc->aRandSeed = apr_array_make(pool, 4,
+ sizeof(ssl_randseed_t));
+ mc->tVHostKeys = apr_hash_make(pool);
+ mc->tPrivateKey = apr_hash_make(pool);
+#if defined(HAVE_OPENSSL_ENGINE_H) && defined(HAVE_ENGINE_INIT)
+ mc->szCryptoDevice = NULL;
+#endif
+#ifdef HAVE_OCSP_STAPLING
+ mc->stapling_cache = NULL;
+ mc->stapling_cache_mutex = NULL;
+ mc->stapling_refresh_mutex = NULL;
+#endif
+
+#ifdef HAVE_OPENSSL_KEYLOG
+ mc->keylog_file = NULL;
+#endif
+#ifdef HAVE_FIPS
+ mc->fips = UNSET;
+#endif
+
+ apr_pool_userdata_set(mc, SSL_MOD_CONFIG_KEY,
+ apr_pool_cleanup_null,
+ pool);
+
+ return mc;
+}
+
+void ssl_config_global_fix(SSLModConfigRec *mc)
+{
+ mc->bFixed = TRUE;
+}
+
+BOOL ssl_config_global_isfixed(SSLModConfigRec *mc)
+{
+ return mc->bFixed;
+}
+
+/* _________________________________________________________________
+**
+** Configuration handling
+** _________________________________________________________________
+*/
+
+#ifdef HAVE_SSL_CONF_CMD
+static apr_status_t modssl_ctx_config_cleanup(void *ctx)
+{
+ SSL_CONF_CTX_free(ctx);
+ return APR_SUCCESS;
+}
+#endif
+
+static void modssl_ctx_init(modssl_ctx_t *mctx, apr_pool_t *p)
+{
+ mctx->sc = NULL; /* set during module init */
+
+ mctx->ssl_ctx = NULL; /* set during module init */
+
+ mctx->pks = NULL;
+ mctx->pkp = NULL;
+
+#ifdef HAVE_TLS_SESSION_TICKETS
+ mctx->ticket_key = NULL;
+#endif
+
+ mctx->protocol = SSL_PROTOCOL_DEFAULT;
+ mctx->protocol_set = 0;
+
+ mctx->pphrase_dialog_type = SSL_PPTYPE_UNSET;
+ mctx->pphrase_dialog_path = NULL;
+
+ mctx->cert_chain = NULL;
+
+ mctx->crl_path = NULL;
+ mctx->crl_file = NULL;
+ mctx->crl_check_mask = UNSET;
+
+ mctx->auth.ca_cert_path = NULL;
+ mctx->auth.ca_cert_file = NULL;
+ mctx->auth.cipher_suite = NULL;
+ mctx->auth.verify_depth = UNSET;
+ mctx->auth.verify_mode = SSL_CVERIFY_UNSET;
+ mctx->auth.tls13_ciphers = NULL;
+
+ mctx->ocsp_mask = UNSET;
+ mctx->ocsp_force_default = UNSET;
+ mctx->ocsp_responder = NULL;
+ mctx->ocsp_resptime_skew = UNSET;
+ mctx->ocsp_resp_maxage = UNSET;
+ mctx->ocsp_responder_timeout = UNSET;
+ mctx->ocsp_use_request_nonce = UNSET;
+ mctx->proxy_uri = NULL;
+
+/* Set OCSP Responder Certificate Verification variable */
+ mctx->ocsp_noverify = UNSET;
+/* Set OCSP Responder File variables */
+ mctx->ocsp_verify_flags = 0;
+ mctx->ocsp_certs_file = NULL;
+ mctx->ocsp_certs = NULL;
+
+#ifdef HAVE_OCSP_STAPLING
+ mctx->stapling_enabled = UNSET;
+ mctx->stapling_resptime_skew = UNSET;
+ mctx->stapling_resp_maxage = UNSET;
+ mctx->stapling_cache_timeout = UNSET;
+ mctx->stapling_return_errors = UNSET;
+ mctx->stapling_fake_trylater = UNSET;
+ mctx->stapling_errcache_timeout = UNSET;
+ mctx->stapling_responder_timeout = UNSET;
+ mctx->stapling_force_url = NULL;
+#endif
+
+#ifdef HAVE_SRP
+ mctx->srp_vfile = NULL;
+ mctx->srp_unknown_user_seed = NULL;
+ mctx->srp_vbase = NULL;
+#endif
+#ifdef HAVE_SSL_CONF_CMD
+ mctx->ssl_ctx_config = SSL_CONF_CTX_new();
+ apr_pool_cleanup_register(p, mctx->ssl_ctx_config,
+ modssl_ctx_config_cleanup,
+ apr_pool_cleanup_null);
+ SSL_CONF_CTX_set_flags(mctx->ssl_ctx_config, SSL_CONF_FLAG_FILE);
+ SSL_CONF_CTX_set_flags(mctx->ssl_ctx_config, SSL_CONF_FLAG_SERVER);
+ SSL_CONF_CTX_set_flags(mctx->ssl_ctx_config, SSL_CONF_FLAG_CERTIFICATE);
+ mctx->ssl_ctx_param = apr_array_make(p, 5, sizeof(ssl_ctx_param_t));
+#endif
+
+ mctx->ssl_check_peer_cn = UNSET;
+ mctx->ssl_check_peer_name = UNSET;
+ mctx->ssl_check_peer_expire = UNSET;
+}
+
+static void modssl_ctx_init_server(SSLSrvConfigRec *sc,
+ apr_pool_t *p)
+{
+ modssl_ctx_t *mctx;
+
+ mctx = sc->server = apr_palloc(p, sizeof(*sc->server));
+
+ modssl_ctx_init(mctx, p);
+
+ mctx->pks = apr_pcalloc(p, sizeof(*mctx->pks));
+
+ mctx->pks->cert_files = apr_array_make(p, 3, sizeof(char *));
+ mctx->pks->key_files = apr_array_make(p, 3, sizeof(char *));
+
+#ifdef HAVE_TLS_SESSION_TICKETS
+ mctx->ticket_key = apr_pcalloc(p, sizeof(*mctx->ticket_key));
+#endif
+}
+
+static SSLSrvConfigRec *ssl_config_server_new(apr_pool_t *p)
+{
+ SSLSrvConfigRec *sc = apr_palloc(p, sizeof(*sc));
+
+ sc->mc = NULL;
+ sc->enabled = SSL_ENABLED_UNSET;
+ sc->vhost_id = NULL; /* set during module init */
+ sc->vhost_id_len = 0; /* set during module init */
+ sc->session_cache_timeout = UNSET;
+ sc->cipher_server_pref = UNSET;
+ sc->insecure_reneg = UNSET;
+#ifdef HAVE_TLSEXT
+ sc->strict_sni_vhost_check = SSL_ENABLED_UNSET;
+#endif
+#ifndef OPENSSL_NO_COMP
+ sc->compression = UNSET;
+#endif
+ sc->session_tickets = UNSET;
+
+ modssl_ctx_init_server(sc, p);
+
+ return sc;
+}
+
+/*
+ * Create per-server SSL configuration
+ */
+void *ssl_config_server_create(apr_pool_t *p, server_rec *s)
+{
+ SSLSrvConfigRec *sc = ssl_config_server_new(p);
+
+ sc->mc = ssl_config_global_create(s);
+
+ return sc;
+}
+
+#define cfgMerge(el,unset) mrg->el = (add->el == (unset)) ? base->el : add->el
+#define cfgMergeArray(el) mrg->el = apr_array_append(p, base->el, add->el)
+#define cfgMergeString(el) cfgMerge(el, NULL)
+#define cfgMergeBool(el) cfgMerge(el, UNSET)
+#define cfgMergeInt(el) cfgMerge(el, UNSET)
+
+/*
+ * Merge per-server SSL configurations
+ */
+
+static void modssl_ctx_cfg_merge(apr_pool_t *p,
+ modssl_ctx_t *base,
+ modssl_ctx_t *add,
+ modssl_ctx_t *mrg)
+{
+ if (add->protocol_set) {
+ mrg->protocol_set = 1;
+ mrg->protocol = add->protocol;
+ }
+ else {
+ mrg->protocol_set = base->protocol_set;
+ mrg->protocol = base->protocol;
+ }
+
+ cfgMerge(pphrase_dialog_type, SSL_PPTYPE_UNSET);
+ cfgMergeString(pphrase_dialog_path);
+
+ cfgMergeString(cert_chain);
+
+ cfgMerge(crl_path, NULL);
+ cfgMerge(crl_file, NULL);
+ cfgMergeInt(crl_check_mask);
+
+ cfgMergeString(auth.ca_cert_path);
+ cfgMergeString(auth.ca_cert_file);
+ cfgMergeString(auth.cipher_suite);
+ cfgMergeInt(auth.verify_depth);
+ cfgMerge(auth.verify_mode, SSL_CVERIFY_UNSET);
+ cfgMergeString(auth.tls13_ciphers);
+
+ cfgMergeInt(ocsp_mask);
+ cfgMergeBool(ocsp_force_default);
+ cfgMerge(ocsp_responder, NULL);
+ cfgMergeInt(ocsp_resptime_skew);
+ cfgMergeInt(ocsp_resp_maxage);
+ cfgMergeInt(ocsp_responder_timeout);
+ cfgMergeBool(ocsp_use_request_nonce);
+ cfgMerge(proxy_uri, NULL);
+
+/* Set OCSP Responder Certificate Verification directive */
+ cfgMergeBool(ocsp_noverify);
+/* Set OCSP Responder File directive for importing */
+ cfgMerge(ocsp_certs_file, NULL);
+
+#ifdef HAVE_OCSP_STAPLING
+ cfgMergeBool(stapling_enabled);
+ cfgMergeInt(stapling_resptime_skew);
+ cfgMergeInt(stapling_resp_maxage);
+ cfgMergeInt(stapling_cache_timeout);
+ cfgMergeBool(stapling_return_errors);
+ cfgMergeBool(stapling_fake_trylater);
+ cfgMergeInt(stapling_errcache_timeout);
+ cfgMergeInt(stapling_responder_timeout);
+ cfgMerge(stapling_force_url, NULL);
+#endif
+
+#ifdef HAVE_SRP
+ cfgMergeString(srp_vfile);
+ cfgMergeString(srp_unknown_user_seed);
+#endif
+
+#ifdef HAVE_SSL_CONF_CMD
+ cfgMergeArray(ssl_ctx_param);
+#endif
+
+ cfgMergeBool(ssl_check_peer_cn);
+ cfgMergeBool(ssl_check_peer_name);
+ cfgMergeBool(ssl_check_peer_expire);
+}
+
+static void modssl_ctx_cfg_merge_certkeys_array(apr_pool_t *p,
+ apr_array_header_t *base,
+ apr_array_header_t *add,
+ apr_array_header_t *mrg)
+{
+ int i;
+
+ /*
+ * pick up to CERTKEYS_IDX_MAX+1 entries from "add" (in which case they
+ * they "knock out" their corresponding entries in "base", emulating
+ * the behavior with cfgMergeString in releases up to 2.4.7)
+ */
+ for (i = 0; i < add->nelts && i <= CERTKEYS_IDX_MAX; i++) {
+ APR_ARRAY_PUSH(mrg, const char *) = APR_ARRAY_IDX(add, i, const char *);
+ }
+
+ /* add remaining ones from "base" */
+ while (i < base->nelts) {
+ APR_ARRAY_PUSH(mrg, const char *) = APR_ARRAY_IDX(base, i, const char *);
+ i++;
+ }
+
+ /* and finally, append the rest of "add" (if there are any) */
+ for (i = CERTKEYS_IDX_MAX+1; i < add->nelts; i++) {
+ APR_ARRAY_PUSH(mrg, const char *) = APR_ARRAY_IDX(add, i, const char *);
+ }
+}
+
+static void modssl_ctx_cfg_merge_server(apr_pool_t *p,
+ modssl_ctx_t *base,
+ modssl_ctx_t *add,
+ modssl_ctx_t *mrg)
+{
+ modssl_ctx_cfg_merge(p, base, add, mrg);
+
+ /*
+ * For better backwards compatibility with releases up to 2.4.7,
+ * merging global and vhost-level SSLCertificateFile and
+ * SSLCertificateKeyFile directives needs special treatment.
+ * See also PR 56306 and 56353.
+ */
+ modssl_ctx_cfg_merge_certkeys_array(p, base->pks->cert_files,
+ add->pks->cert_files,
+ mrg->pks->cert_files);
+ modssl_ctx_cfg_merge_certkeys_array(p, base->pks->key_files,
+ add->pks->key_files,
+ mrg->pks->key_files);
+
+ cfgMergeString(pks->ca_name_path);
+ cfgMergeString(pks->ca_name_file);
+
+#ifdef HAVE_TLS_SESSION_TICKETS
+ cfgMergeString(ticket_key->file_path);
+#endif
+}
+
+void *ssl_config_server_merge(apr_pool_t *p, void *basev, void *addv)
+{
+ SSLSrvConfigRec *base = (SSLSrvConfigRec *)basev;
+ SSLSrvConfigRec *add = (SSLSrvConfigRec *)addv;
+ SSLSrvConfigRec *mrg = ssl_config_server_new(p);
+
+ cfgMerge(mc, NULL);
+ cfgMerge(enabled, SSL_ENABLED_UNSET);
+ cfgMergeInt(session_cache_timeout);
+ cfgMergeBool(cipher_server_pref);
+ cfgMergeBool(insecure_reneg);
+#ifdef HAVE_TLSEXT
+ cfgMerge(strict_sni_vhost_check, SSL_ENABLED_UNSET);
+#endif
+#ifndef OPENSSL_NO_COMP
+ cfgMergeBool(compression);
+#endif
+ cfgMergeBool(session_tickets);
+
+ modssl_ctx_cfg_merge_server(p, base->server, add->server, mrg->server);
+
+ return mrg;
+}
+
+/*
+ * Create per-directory SSL configuration
+ */
+
+static void modssl_ctx_init_proxy(SSLDirConfigRec *dc,
+ apr_pool_t *p)
+{
+ modssl_ctx_t *mctx;
+
+ mctx = dc->proxy = apr_palloc(p, sizeof(*dc->proxy));
+
+ modssl_ctx_init(mctx, p);
+
+ mctx->pkp = apr_palloc(p, sizeof(*mctx->pkp));
+
+ mctx->pkp->cert_file = NULL;
+ mctx->pkp->cert_path = NULL;
+ mctx->pkp->ca_cert_file = NULL;
+ mctx->pkp->certs = NULL;
+ mctx->pkp->ca_certs = NULL;
+}
+
+void *ssl_config_perdir_create(apr_pool_t *p, char *dir)
+{
+ SSLDirConfigRec *dc = apr_palloc(p, sizeof(*dc));
+
+ dc->bSSLRequired = FALSE;
+ dc->aRequirement = apr_array_make(p, 4, sizeof(ssl_require_t));
+ dc->nOptions = SSL_OPT_NONE|SSL_OPT_RELSET;
+ dc->nOptionsAdd = SSL_OPT_NONE;
+ dc->nOptionsDel = SSL_OPT_NONE;
+
+ dc->szCipherSuite = NULL;
+ dc->nVerifyClient = SSL_CVERIFY_UNSET;
+ dc->nVerifyDepth = UNSET;
+
+ dc->szUserName = NULL;
+
+ dc->nRenegBufferSize = UNSET;
+
+ dc->proxy_enabled = UNSET;
+ modssl_ctx_init_proxy(dc, p);
+ dc->proxy_post_config = FALSE;
+
+ return dc;
+}
+
+/*
+ * Merge per-directory SSL configurations
+ */
+
+static void modssl_ctx_cfg_merge_proxy(apr_pool_t *p,
+ modssl_ctx_t *base,
+ modssl_ctx_t *add,
+ modssl_ctx_t *mrg)
+{
+ modssl_ctx_cfg_merge(p, base, add, mrg);
+
+ cfgMergeString(pkp->cert_file);
+ cfgMergeString(pkp->cert_path);
+ cfgMergeString(pkp->ca_cert_file);
+ cfgMergeString(pkp->certs);
+ cfgMergeString(pkp->ca_certs);
+}
+
+void *ssl_config_perdir_merge(apr_pool_t *p, void *basev, void *addv)
+{
+ SSLDirConfigRec *base = (SSLDirConfigRec *)basev;
+ SSLDirConfigRec *add = (SSLDirConfigRec *)addv;
+ SSLDirConfigRec *mrg = (SSLDirConfigRec *)apr_palloc(p, sizeof(*mrg));
+
+ cfgMerge(bSSLRequired, FALSE);
+ cfgMergeArray(aRequirement);
+
+ if (add->nOptions & SSL_OPT_RELSET) {
+ mrg->nOptionsAdd =
+ (base->nOptionsAdd & ~(add->nOptionsDel)) | add->nOptionsAdd;
+ mrg->nOptionsDel =
+ (base->nOptionsDel & ~(add->nOptionsAdd)) | add->nOptionsDel;
+ mrg->nOptions =
+ (base->nOptions & ~(mrg->nOptionsDel)) | mrg->nOptionsAdd;
+ }
+ else {
+ mrg->nOptions = add->nOptions;
+ mrg->nOptionsAdd = add->nOptionsAdd;
+ mrg->nOptionsDel = add->nOptionsDel;
+ }
+
+ cfgMergeString(szCipherSuite);
+ cfgMerge(nVerifyClient, SSL_CVERIFY_UNSET);
+ cfgMergeInt(nVerifyDepth);
+
+ cfgMergeString(szUserName);
+
+ cfgMergeInt(nRenegBufferSize);
+
+ mrg->proxy_post_config = add->proxy_post_config;
+ if (!mrg->proxy_post_config) {
+ cfgMergeBool(proxy_enabled);
+ modssl_ctx_init_proxy(mrg, p);
+ modssl_ctx_cfg_merge_proxy(p, base->proxy, add->proxy, mrg->proxy);
+
+ /* Since ssl_proxy_section_post_config() hook won't be called if there
+ * is no SSLProxy* in this dir config, the ssl_ctx may still be NULL
+ * here at runtime. Merging it is either a no-op (NULL => NULL) because
+ * we are still before post config, or we really want to reuse the one
+ * from the upper/server context (outside of <Proxy> sections).
+ */
+ cfgMerge(proxy->ssl_ctx, NULL);
+ }
+ else {
+ /* The post_config hook has already merged and initialized the
+ * proxy context, use it.
+ */
+ mrg->proxy_enabled = add->proxy_enabled;
+ mrg->proxy = add->proxy;
+ }
+
+ return mrg;
+}
+
+/* Simply merge conf with base into conf, no third party. */
+void ssl_config_proxy_merge(apr_pool_t *p,
+ SSLDirConfigRec *base,
+ SSLDirConfigRec *conf)
+{
+ if (conf->proxy_enabled == UNSET) {
+ conf->proxy_enabled = base->proxy_enabled;
+ }
+ modssl_ctx_cfg_merge_proxy(p, base->proxy, conf->proxy, conf->proxy);
+}
+
+/*
+ * Configuration functions for particular directives
+ */
+
+const char *ssl_cmd_SSLPassPhraseDialog(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ const char *err;
+ int arglen = strlen(arg);
+
+ if ((err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
+ return err;
+ }
+
+ if (strcEQ(arg, "builtin")) {
+ sc->server->pphrase_dialog_type = SSL_PPTYPE_BUILTIN;
+ sc->server->pphrase_dialog_path = NULL;
+ }
+ else if ((arglen > 5) && strEQn(arg, "exec:", 5)) {
+ sc->server->pphrase_dialog_type = SSL_PPTYPE_FILTER;
+ sc->server->pphrase_dialog_path =
+ ap_server_root_relative(cmd->pool, arg+5);
+ if (!sc->server->pphrase_dialog_path) {
+ return apr_pstrcat(cmd->pool,
+ "Invalid SSLPassPhraseDialog exec: path ",
+ arg+5, NULL);
+ }
+ if (!ssl_util_path_check(SSL_PCM_EXISTS,
+ sc->server->pphrase_dialog_path,
+ cmd->pool))
+ {
+ return apr_pstrcat(cmd->pool,
+ "SSLPassPhraseDialog: file '",
+ sc->server->pphrase_dialog_path,
+ "' does not exist", NULL);
+ }
+
+ }
+ else if ((arglen > 1) && (arg[0] == '|')) {
+ sc->server->pphrase_dialog_type = SSL_PPTYPE_PIPE;
+ sc->server->pphrase_dialog_path = arg + 1;
+ }
+ else {
+ return "SSLPassPhraseDialog: Invalid argument";
+ }
+
+ return NULL;
+}
+
+#if defined(HAVE_OPENSSL_ENGINE_H) && defined(HAVE_ENGINE_INIT)
+const char *ssl_cmd_SSLCryptoDevice(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg)
+{
+ SSLModConfigRec *mc = myModConfig(cmd->server);
+ const char *err;
+ ENGINE *e;
+
+ if ((err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
+ return err;
+ }
+
+ if (strcEQ(arg, "builtin")) {
+ mc->szCryptoDevice = NULL;
+ }
+ else if ((e = ENGINE_by_id(arg))) {
+ mc->szCryptoDevice = arg;
+ ENGINE_free(e);
+ }
+ else {
+ err = "SSLCryptoDevice: Invalid argument; must be one of: "
+ "'builtin' (none)";
+ e = ENGINE_get_first();
+ while (e) {
+ err = apr_pstrcat(cmd->pool, err, ", '", ENGINE_get_id(e),
+ "' (", ENGINE_get_name(e), ")", NULL);
+ /* Iterate; this call implicitly decrements the refcount
+ * on the 'old' e, per the docs in engine.h. */
+ e = ENGINE_get_next(e);
+ }
+ return err;
+ }
+
+ return NULL;
+}
+#endif
+
+const char *ssl_cmd_SSLRandomSeed(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg1,
+ const char *arg2,
+ const char *arg3)
+{
+ SSLModConfigRec *mc = myModConfig(cmd->server);
+ const char *err;
+ ssl_randseed_t *seed;
+ int arg2len = strlen(arg2);
+
+ if ((err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
+ return err;
+ }
+
+ if (ssl_config_global_isfixed(mc)) {
+ return NULL;
+ }
+
+ seed = apr_array_push(mc->aRandSeed);
+
+ if (strcEQ(arg1, "startup")) {
+ seed->nCtx = SSL_RSCTX_STARTUP;
+ }
+ else if (strcEQ(arg1, "connect")) {
+ seed->nCtx = SSL_RSCTX_CONNECT;
+ }
+ else {
+ return apr_pstrcat(cmd->pool, "SSLRandomSeed: "
+ "invalid context: `", arg1, "'",
+ NULL);
+ }
+
+ if ((arg2len > 5) && strEQn(arg2, "file:", 5)) {
+ seed->nSrc = SSL_RSSRC_FILE;
+ seed->cpPath = ap_server_root_relative(mc->pPool, arg2+5);
+ }
+ else if ((arg2len > 5) && strEQn(arg2, "exec:", 5)) {
+ seed->nSrc = SSL_RSSRC_EXEC;
+ seed->cpPath = ap_server_root_relative(mc->pPool, arg2+5);
+ }
+ else if ((arg2len > 4) && strEQn(arg2, "egd:", 4)) {
+#ifdef HAVE_RAND_EGD
+ seed->nSrc = SSL_RSSRC_EGD;
+ seed->cpPath = ap_server_root_relative(mc->pPool, arg2+4);
+#else
+ return apr_pstrcat(cmd->pool, "Invalid SSLRandomSeed entropy source `",
+ arg2, "': This version of " MODSSL_LIBRARY_NAME
+ " does not support the Entropy Gathering Daemon "
+ "(EGD).", NULL);
+#endif
+ }
+ else if (strcEQ(arg2, "builtin")) {
+ seed->nSrc = SSL_RSSRC_BUILTIN;
+ seed->cpPath = NULL;
+ }
+ else {
+ seed->nSrc = SSL_RSSRC_FILE;
+ seed->cpPath = ap_server_root_relative(mc->pPool, arg2);
+ }
+
+ if (seed->nSrc != SSL_RSSRC_BUILTIN) {
+ if (!seed->cpPath) {
+ return apr_pstrcat(cmd->pool,
+ "Invalid SSLRandomSeed path ",
+ arg2, NULL);
+ }
+ if (!ssl_util_path_check(SSL_PCM_EXISTS, seed->cpPath, cmd->pool)) {
+ return apr_pstrcat(cmd->pool,
+ "SSLRandomSeed: source path '",
+ seed->cpPath, "' does not exist", NULL);
+ }
+ }
+
+ if (!arg3) {
+ seed->nBytes = 0; /* read whole file */
+ }
+ else {
+ if (seed->nSrc == SSL_RSSRC_BUILTIN) {
+ return "SSLRandomSeed: byte specification not "
+ "allowed for builtin seed source";
+ }
+
+ seed->nBytes = atoi(arg3);
+
+ if (seed->nBytes < 0) {
+ return "SSLRandomSeed: invalid number of bytes specified";
+ }
+ }
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLEngine(cmd_parms *cmd, void *dcfg, const char *arg)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+
+ if (!strcasecmp(arg, "On")) {
+ sc->enabled = SSL_ENABLED_TRUE;
+ return NULL;
+ }
+ else if (!strcasecmp(arg, "Off")) {
+ sc->enabled = SSL_ENABLED_FALSE;
+ return NULL;
+ }
+ else if (!strcasecmp(arg, "Optional")) {
+ sc->enabled = SSL_ENABLED_OPTIONAL;
+ return NULL;
+ }
+
+ return "Argument must be On, Off, or Optional";
+}
+
+const char *ssl_cmd_SSLFIPS(cmd_parms *cmd, void *dcfg, int flag)
+{
+#ifdef HAVE_FIPS
+ SSLModConfigRec *mc = myModConfig(cmd->server);
+#endif
+ const char *err;
+
+ if ((err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
+ return err;
+ }
+
+#ifdef HAVE_FIPS
+ if ((mc->fips != UNSET) && (mc->fips != (BOOL)(flag ? TRUE : FALSE)))
+ return "Conflicting SSLFIPS options, cannot be both On and Off";
+ mc->fips = flag ? TRUE : FALSE;
+#else
+ if (flag)
+ return "SSLFIPS invalid, rebuild httpd and openssl compiled for FIPS";
+#endif
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLCipherSuite(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg1, const char *arg2)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ SSLDirConfigRec *dc = (SSLDirConfigRec *)dcfg;
+
+ if (arg2 == NULL) {
+ arg2 = arg1;
+ arg1 = "SSL";
+ }
+
+ if (!strcmp("SSL", arg1)) {
+ /* always disable null and export ciphers */
+ arg2 = apr_pstrcat(cmd->pool, arg2, ":!aNULL:!eNULL:!EXP", NULL);
+ if (cmd->path) {
+ dc->szCipherSuite = arg2;
+ }
+ else {
+ sc->server->auth.cipher_suite = arg2;
+ }
+ return NULL;
+ }
+#if SSL_HAVE_PROTOCOL_TLSV1_3
+ else if (!strcmp("TLSv1.3", arg1)) {
+ if (cmd->path) {
+ return "TLSv1.3 ciphers cannot be set inside a directory context";
+ }
+ sc->server->auth.tls13_ciphers = arg2;
+ return NULL;
+ }
+#endif
+ return apr_pstrcat(cmd->pool, "protocol '", arg1, "' not supported", NULL);
+}
+
+#define SSL_FLAGS_CHECK_FILE \
+ (SSL_PCM_EXISTS|SSL_PCM_ISREG|SSL_PCM_ISNONZERO)
+
+#define SSL_FLAGS_CHECK_DIR \
+ (SSL_PCM_EXISTS|SSL_PCM_ISDIR)
+
+static const char *ssl_cmd_check_file(cmd_parms *parms,
+ const char **file)
+{
+ const char *filepath;
+
+ /* If only dumping the config, don't verify the paths */
+ if (ap_state_query(AP_SQ_RUN_MODE) == AP_SQ_RM_CONFIG_DUMP) {
+ return NULL;
+ }
+
+ filepath = ap_server_root_relative(parms->pool, *file);
+ if (!filepath) {
+ return apr_pstrcat(parms->pool, parms->cmd->name,
+ ": Invalid file path ", *file, NULL);
+ }
+ *file = filepath;
+
+ if (ssl_util_path_check(SSL_FLAGS_CHECK_FILE, *file, parms->pool)) {
+ return NULL;
+ }
+
+ return apr_pstrcat(parms->pool, parms->cmd->name,
+ ": file '", *file,
+ "' does not exist or is empty", NULL);
+
+}
+
+const char *ssl_cmd_SSLCompression(cmd_parms *cmd, void *dcfg, int flag)
+{
+#if !defined(OPENSSL_NO_COMP)
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+#ifndef SSL_OP_NO_COMPRESSION
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err)
+ return "This version of OpenSSL does not support enabling "
+ "SSLCompression within <VirtualHost> sections.";
+#endif
+ if (flag) {
+ /* Some (packaged) versions of OpenSSL do not support
+ * compression by default. Enabling this directive would not
+ * have the desired effect, so fail with an error. */
+ STACK_OF(SSL_COMP) *meths = SSL_COMP_get_compression_methods();
+
+ if (sk_SSL_COMP_num(meths) == 0) {
+ return "This version of OpenSSL does not have any compression methods "
+ "available, cannot enable SSLCompression.";
+ }
+ }
+ sc->compression = flag ? TRUE : FALSE;
+ return NULL;
+#else
+ return "Setting Compression mode unsupported; not implemented by the SSL library";
+#endif
+}
+
+const char *ssl_cmd_SSLHonorCipherOrder(cmd_parms *cmd, void *dcfg, int flag)
+{
+#ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ sc->cipher_server_pref = flag?TRUE:FALSE;
+ return NULL;
+#else
+ return "SSLHonorCipherOrder unsupported; not implemented by the SSL library";
+#endif
+}
+
+const char *ssl_cmd_SSLSessionTickets(cmd_parms *cmd, void *dcfg, int flag)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+#ifndef SSL_OP_NO_TICKET
+ return "This version of OpenSSL does not support using "
+ "SSLSessionTickets.";
+#endif
+ sc->session_tickets = flag ? TRUE : FALSE;
+ return NULL;
+}
+
+const char *ssl_cmd_SSLInsecureRenegotiation(cmd_parms *cmd, void *dcfg, int flag)
+{
+#ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ sc->insecure_reneg = flag?TRUE:FALSE;
+ return NULL;
+#else
+ return "The SSLInsecureRenegotiation directive is not available "
+ "with this SSL library";
+#endif
+}
+
+
+static const char *ssl_cmd_check_dir(cmd_parms *parms,
+ const char **dir)
+{
+ const char *dirpath = ap_server_root_relative(parms->pool, *dir);
+
+ if (!dirpath) {
+ return apr_pstrcat(parms->pool, parms->cmd->name,
+ ": Invalid dir path ", *dir, NULL);
+ }
+ *dir = dirpath;
+
+ if (ssl_util_path_check(SSL_FLAGS_CHECK_DIR, *dir, parms->pool)) {
+ return NULL;
+ }
+
+ return apr_pstrcat(parms->pool, parms->cmd->name,
+ ": directory '", *dir,
+ "' does not exist", NULL);
+
+}
+
+const char *ssl_cmd_SSLCertificateFile(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ const char *err;
+
+ /* Only check for non-ENGINE based certs. */
+ if (!modssl_is_engine_id(arg)
+ && (err = ssl_cmd_check_file(cmd, &arg))) {
+ return err;
+ }
+
+ *(const char **)apr_array_push(sc->server->pks->cert_files) = arg;
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLCertificateKeyFile(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ const char *err;
+
+ /* Check keyfile exists for non-ENGINE keys. */
+ if (!modssl_is_engine_id(arg)
+ && (err = ssl_cmd_check_file(cmd, &arg))) {
+ return err;
+ }
+
+ *(const char **)apr_array_push(sc->server->pks->key_files) = arg;
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLCertificateChainFile(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ const char *err;
+
+ if ((err = ssl_cmd_check_file(cmd, &arg))) {
+ return err;
+ }
+
+ sc->server->cert_chain = arg;
+
+ return NULL;
+}
+
+#ifdef HAVE_TLS_SESSION_TICKETS
+const char *ssl_cmd_SSLSessionTicketKeyFile(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ const char *err;
+
+ if ((err = ssl_cmd_check_file(cmd, &arg))) {
+ return err;
+ }
+
+ sc->server->ticket_key->file_path = arg;
+
+ return NULL;
+}
+#endif
+
+#define NO_PER_DIR_SSL_CA \
+ "Your SSL library does not have support for per-directory CA"
+
+const char *ssl_cmd_SSLCACertificatePath(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg)
+{
+ /*SSLDirConfigRec *dc = (SSLDirConfigRec *)dcfg;*/
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ const char *err;
+
+ if ((err = ssl_cmd_check_dir(cmd, &arg))) {
+ return err;
+ }
+
+ if (cmd->path) {
+ return NO_PER_DIR_SSL_CA;
+ }
+
+ /* XXX: bring back per-dir */
+ sc->server->auth.ca_cert_path = arg;
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLCACertificateFile(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg)
+{
+ /*SSLDirConfigRec *dc = (SSLDirConfigRec *)dcfg;*/
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ const char *err;
+
+ if ((err = ssl_cmd_check_file(cmd, &arg))) {
+ return err;
+ }
+
+ if (cmd->path) {
+ return NO_PER_DIR_SSL_CA;
+ }
+
+ /* XXX: bring back per-dir */
+ sc->server->auth.ca_cert_file = arg;
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLCADNRequestPath(cmd_parms *cmd, void *dcfg,
+ const char *arg)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ const char *err;
+
+ if ((err = ssl_cmd_check_dir(cmd, &arg))) {
+ return err;
+ }
+
+ sc->server->pks->ca_name_path = arg;
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLCADNRequestFile(cmd_parms *cmd, void *dcfg,
+ const char *arg)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ const char *err;
+
+ if ((err = ssl_cmd_check_file(cmd, &arg))) {
+ return err;
+ }
+
+ sc->server->pks->ca_name_file = arg;
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLCARevocationPath(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ const char *err;
+
+ if ((err = ssl_cmd_check_dir(cmd, &arg))) {
+ return err;
+ }
+
+ sc->server->crl_path = arg;
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLCARevocationFile(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ const char *err;
+
+ if ((err = ssl_cmd_check_file(cmd, &arg))) {
+ return err;
+ }
+
+ sc->server->crl_file = arg;
+
+ return NULL;
+}
+
+static const char *ssl_cmd_crlcheck_parse(cmd_parms *parms,
+ const char *arg,
+ int *mask)
+{
+ const char *w;
+
+ w = ap_getword_conf(parms->temp_pool, &arg);
+ if (strcEQ(w, "none")) {
+ *mask = SSL_CRLCHECK_NONE;
+ }
+ else if (strcEQ(w, "leaf")) {
+ *mask = SSL_CRLCHECK_LEAF;
+ }
+ else if (strcEQ(w, "chain")) {
+ *mask = SSL_CRLCHECK_CHAIN;
+ }
+ else {
+ return apr_pstrcat(parms->temp_pool, parms->cmd->name,
+ ": Invalid argument '", w, "'",
+ NULL);
+ }
+
+ while (*arg) {
+ w = ap_getword_conf(parms->temp_pool, &arg);
+ if (strcEQ(w, "no_crl_for_cert_ok")) {
+ *mask |= SSL_CRLCHECK_NO_CRL_FOR_CERT_OK;
+ }
+ else {
+ return apr_pstrcat(parms->temp_pool, parms->cmd->name,
+ ": Invalid argument '", w, "'",
+ NULL);
+ }
+ }
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLCARevocationCheck(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+
+ return ssl_cmd_crlcheck_parse(cmd, arg, &sc->server->crl_check_mask);
+}
+
+static const char *ssl_cmd_verify_parse(cmd_parms *parms,
+ const char *arg,
+ ssl_verify_t *id)
+{
+ if (strcEQ(arg, "none") || strcEQ(arg, "off")) {
+ *id = SSL_CVERIFY_NONE;
+ }
+ else if (strcEQ(arg, "optional")) {
+ *id = SSL_CVERIFY_OPTIONAL;
+ }
+ else if (strcEQ(arg, "require") || strcEQ(arg, "on")) {
+ *id = SSL_CVERIFY_REQUIRE;
+ }
+ else if (strcEQ(arg, "optional_no_ca")) {
+ *id = SSL_CVERIFY_OPTIONAL_NO_CA;
+ }
+ else {
+ return apr_pstrcat(parms->temp_pool, parms->cmd->name,
+ ": Invalid argument '", arg, "'",
+ NULL);
+ }
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLVerifyClient(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg)
+{
+ SSLDirConfigRec *dc = (SSLDirConfigRec *)dcfg;
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ ssl_verify_t mode = SSL_CVERIFY_NONE;
+ const char *err;
+
+ if ((err = ssl_cmd_verify_parse(cmd, arg, &mode))) {
+ return err;
+ }
+
+ if (cmd->path) {
+ dc->nVerifyClient = mode;
+ }
+ else {
+ sc->server->auth.verify_mode = mode;
+ }
+
+ return NULL;
+}
+
+static const char *ssl_cmd_verify_depth_parse(cmd_parms *parms,
+ const char *arg,
+ int *depth)
+{
+ if ((*depth = atoi(arg)) >= 0) {
+ return NULL;
+ }
+
+ return apr_pstrcat(parms->temp_pool, parms->cmd->name,
+ ": Invalid argument '", arg, "'",
+ NULL);
+}
+
+const char *ssl_cmd_SSLVerifyDepth(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg)
+{
+ SSLDirConfigRec *dc = (SSLDirConfigRec *)dcfg;
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ int depth;
+ const char *err;
+
+ if ((err = ssl_cmd_verify_depth_parse(cmd, arg, &depth))) {
+ return err;
+ }
+
+ if (cmd->path) {
+ dc->nVerifyDepth = depth;
+ }
+ else {
+ sc->server->auth.verify_depth = depth;
+ }
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLSessionCache(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg)
+{
+ SSLModConfigRec *mc = myModConfig(cmd->server);
+ const char *err, *sep, *name;
+ long enabled_flags;
+
+ if ((err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
+ return err;
+ }
+
+ /* The OpenSSL session cache mode must have both the flags
+ * SSL_SESS_CACHE_SERVER and SSL_SESS_CACHE_NO_INTERNAL set if a
+ * session cache is configured; NO_INTERNAL prevents the
+ * OpenSSL-internal session cache being used in addition to the
+ * "external" (mod_ssl-provided) cache, which otherwise causes
+ * additional memory consumption. */
+ enabled_flags = SSL_SESS_CACHE_SERVER | SSL_SESS_CACHE_NO_INTERNAL;
+
+ if (strcEQ(arg, "none")) {
+ /* Nothing to do; session cache will be off. */
+ }
+ else if (strcEQ(arg, "nonenotnull")) {
+ /* ### Having a separate mode for this seems logically
+ * unnecessary; the stated purpose of sending non-empty
+ * session IDs would be better fixed in OpenSSL or simply
+ * doing it by default if "none" is used. */
+ mc->sesscache_mode = enabled_flags;
+ }
+ else {
+ /* Argument is of form 'name:args' or just 'name'. */
+ sep = ap_strchr_c(arg, ':');
+ if (sep) {
+ name = apr_pstrmemdup(cmd->pool, arg, sep - arg);
+ sep++;
+ }
+ else {
+ name = arg;
+ }
+
+ /* Find the provider of given name. */
+ mc->sesscache = ap_lookup_provider(AP_SOCACHE_PROVIDER_GROUP,
+ name,
+ AP_SOCACHE_PROVIDER_VERSION);
+ if (mc->sesscache) {
+ /* Cache found; create it, passing anything beyond the colon. */
+ mc->sesscache_mode = enabled_flags;
+ err = mc->sesscache->create(&mc->sesscache_context, sep,
+ cmd->temp_pool, cmd->pool);
+ }
+ else {
+ apr_array_header_t *name_list;
+ const char *all_names;
+
+ /* Build a comma-separated list of all registered provider
+ * names: */
+ name_list = ap_list_provider_names(cmd->pool,
+ AP_SOCACHE_PROVIDER_GROUP,
+ AP_SOCACHE_PROVIDER_VERSION);
+ all_names = apr_array_pstrcat(cmd->pool, name_list, ',');
+
+ err = apr_psprintf(cmd->pool, "'%s' session cache not supported "
+ "(known names: %s). Maybe you need to load the "
+ "appropriate socache module (mod_socache_%s?).",
+ name, all_names, name);
+ }
+ }
+
+ if (err) {
+ return apr_psprintf(cmd->pool, "SSLSessionCache: %s", err);
+ }
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLSessionCacheTimeout(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+
+ sc->session_cache_timeout = atoi(arg);
+
+ if (sc->session_cache_timeout < 0) {
+ return "SSLSessionCacheTimeout: Invalid argument";
+ }
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLOptions(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg)
+{
+ SSLDirConfigRec *dc = (SSLDirConfigRec *)dcfg;
+ ssl_opt_t opt;
+ int first = TRUE;
+ char action, *w;
+
+ while (*arg) {
+ w = ap_getword_conf(cmd->temp_pool, &arg);
+ action = NUL;
+
+ if ((*w == '+') || (*w == '-')) {
+ action = *(w++);
+ }
+ else if (first) {
+ dc->nOptions = SSL_OPT_NONE;
+ first = FALSE;
+ }
+
+ if (strcEQ(w, "StdEnvVars")) {
+ opt = SSL_OPT_STDENVVARS;
+ }
+ else if (strcEQ(w, "ExportCertData")) {
+ opt = SSL_OPT_EXPORTCERTDATA;
+ }
+ else if (strcEQ(w, "FakeBasicAuth")) {
+ opt = SSL_OPT_FAKEBASICAUTH;
+ }
+ else if (strcEQ(w, "StrictRequire")) {
+ opt = SSL_OPT_STRICTREQUIRE;
+ }
+ else if (strcEQ(w, "OptRenegotiate")) {
+ opt = SSL_OPT_OPTRENEGOTIATE;
+ }
+ else if (strcEQ(w, "LegacyDNStringFormat")) {
+ opt = SSL_OPT_LEGACYDNFORMAT;
+ }
+ else {
+ return apr_pstrcat(cmd->pool,
+ "SSLOptions: Illegal option '", w, "'",
+ NULL);
+ }
+
+ if (action == '-') {
+ dc->nOptionsAdd &= ~opt;
+ dc->nOptionsDel |= opt;
+ dc->nOptions &= ~opt;
+ }
+ else if (action == '+') {
+ dc->nOptionsAdd |= opt;
+ dc->nOptionsDel &= ~opt;
+ dc->nOptions |= opt;
+ }
+ else {
+ dc->nOptions = opt;
+ dc->nOptionsAdd = opt;
+ dc->nOptionsDel = SSL_OPT_NONE;
+ }
+ }
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLRequireSSL(cmd_parms *cmd, void *dcfg)
+{
+ SSLDirConfigRec *dc = (SSLDirConfigRec *)dcfg;
+
+ dc->bSSLRequired = TRUE;
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLRequire(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg)
+{
+ SSLDirConfigRec *dc = (SSLDirConfigRec *)dcfg;
+ ap_expr_info_t *info = apr_pcalloc(cmd->pool, sizeof(ap_expr_info_t));
+ ssl_require_t *require;
+ const char *errstring;
+
+ info->flags = AP_EXPR_FLAG_SSL_EXPR_COMPAT;
+ info->filename = cmd->directive->filename;
+ info->line_number = cmd->directive->line_num;
+ info->module_index = APLOG_MODULE_INDEX;
+ errstring = ap_expr_parse(cmd->pool, cmd->temp_pool, info, arg, NULL);
+ if (errstring) {
+ return apr_pstrcat(cmd->pool, "SSLRequire: ", errstring, NULL);
+ }
+
+ require = apr_array_push(dc->aRequirement);
+ require->cpExpr = arg;
+ require->mpExpr = info;
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLRenegBufferSize(cmd_parms *cmd, void *dcfg, const char *arg)
+{
+ SSLDirConfigRec *dc = dcfg;
+ int val;
+
+ val = atoi(arg);
+ if (val < 0) {
+ return apr_pstrcat(cmd->pool, "Invalid size for SSLRenegBufferSize: ",
+ arg, NULL);
+ }
+ dc->nRenegBufferSize = val;
+
+ return NULL;
+}
+
+static const char *ssl_cmd_protocol_parse(cmd_parms *parms,
+ const char *arg,
+ ssl_proto_t *options)
+{
+ ssl_proto_t thisopt;
+
+ *options = SSL_PROTOCOL_NONE;
+
+ while (*arg) {
+ char *w = ap_getword_conf(parms->temp_pool, &arg);
+ char action = '\0';
+
+ if ((*w == '+') || (*w == '-')) {
+ action = *(w++);
+ }
+
+ if (strcEQ(w, "SSLv2")) {
+ if (action == '-') {
+ continue;
+ }
+ else {
+ return "SSLProtocol: SSLv2 is no longer supported";
+ }
+ }
+ else if (strcEQ(w, "SSLv3")) {
+#ifdef OPENSSL_NO_SSL3
+ if (action != '-') {
+ return "SSLv3 not supported by this version of OpenSSL";
+ }
+ /* Nothing to do, the flag is not present to be toggled */
+ continue;
+#else
+ thisopt = SSL_PROTOCOL_SSLV3;
+#endif
+ }
+ else if (strcEQ(w, "TLSv1")) {
+ thisopt = SSL_PROTOCOL_TLSV1;
+ }
+#ifdef HAVE_TLSV1_X
+ else if (strcEQ(w, "TLSv1.1")) {
+ thisopt = SSL_PROTOCOL_TLSV1_1;
+ }
+ else if (strcEQ(w, "TLSv1.2")) {
+ thisopt = SSL_PROTOCOL_TLSV1_2;
+ }
+ else if (SSL_HAVE_PROTOCOL_TLSV1_3 && strcEQ(w, "TLSv1.3")) {
+ thisopt = SSL_PROTOCOL_TLSV1_3;
+ }
+#endif
+ else if (strcEQ(w, "all")) {
+ thisopt = SSL_PROTOCOL_ALL;
+ }
+ else {
+ return apr_pstrcat(parms->temp_pool,
+ parms->cmd->name,
+ ": Illegal protocol '", w, "'", NULL);
+ }
+
+ if (action == '-') {
+ *options &= ~thisopt;
+ }
+ else if (action == '+') {
+ *options |= thisopt;
+ }
+ else {
+ if (*options != SSL_PROTOCOL_NONE) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, parms->server, APLOGNO(02532)
+ "%s: Protocol '%s' overrides already set parameter(s). "
+ "Check if a +/- prefix is missing.",
+ parms->cmd->name, w);
+ }
+ *options = thisopt;
+ }
+ }
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLProtocol(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+
+ sc->server->protocol_set = 1;
+ return ssl_cmd_protocol_parse(cmd, arg, &sc->server->protocol);
+}
+
+const char *ssl_cmd_SSLProxyEngine(cmd_parms *cmd, void *dcfg, int flag)
+{
+ SSLDirConfigRec *dc = (SSLDirConfigRec *)dcfg;
+
+ dc->proxy_enabled = flag ? TRUE : FALSE;
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLProxyProtocol(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg)
+{
+ SSLDirConfigRec *dc = (SSLDirConfigRec *)dcfg;
+
+ dc->proxy->protocol_set = 1;
+ return ssl_cmd_protocol_parse(cmd, arg, &dc->proxy->protocol);
+}
+
+const char *ssl_cmd_SSLProxyCipherSuite(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg1, const char *arg2)
+{
+ SSLDirConfigRec *dc = (SSLDirConfigRec *)dcfg;
+
+ if (arg2 == NULL) {
+ arg2 = arg1;
+ arg1 = "SSL";
+ }
+
+ if (!strcmp("SSL", arg1)) {
+ /* always disable null and export ciphers */
+ arg2 = apr_pstrcat(cmd->pool, arg2, ":!aNULL:!eNULL:!EXP", NULL);
+ dc->proxy->auth.cipher_suite = arg2;
+ return NULL;
+ }
+#if SSL_HAVE_PROTOCOL_TLSV1_3
+ else if (!strcmp("TLSv1.3", arg1)) {
+ dc->proxy->auth.tls13_ciphers = arg2;
+ return NULL;
+ }
+#endif
+ return apr_pstrcat(cmd->pool, "protocol '", arg1, "' not supported", NULL);
+}
+
+const char *ssl_cmd_SSLProxyVerify(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg)
+{
+ SSLDirConfigRec *dc = (SSLDirConfigRec *)dcfg;
+ ssl_verify_t mode = SSL_CVERIFY_NONE;
+ const char *err;
+
+ if ((err = ssl_cmd_verify_parse(cmd, arg, &mode))) {
+ return err;
+ }
+
+ dc->proxy->auth.verify_mode = mode;
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLProxyVerifyDepth(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg)
+{
+ SSLDirConfigRec *dc = (SSLDirConfigRec *)dcfg;
+ int depth;
+ const char *err;
+
+ if ((err = ssl_cmd_verify_depth_parse(cmd, arg, &depth))) {
+ return err;
+ }
+
+ dc->proxy->auth.verify_depth = depth;
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLProxyCACertificateFile(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg)
+{
+ SSLDirConfigRec *dc = (SSLDirConfigRec *)dcfg;
+ const char *err;
+
+ if ((err = ssl_cmd_check_file(cmd, &arg))) {
+ return err;
+ }
+
+ dc->proxy->auth.ca_cert_file = arg;
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLProxyCACertificatePath(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg)
+{
+ SSLDirConfigRec *dc = (SSLDirConfigRec *)dcfg;
+ const char *err;
+
+ if ((err = ssl_cmd_check_dir(cmd, &arg))) {
+ return err;
+ }
+
+ dc->proxy->auth.ca_cert_path = arg;
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLProxyCARevocationPath(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg)
+{
+ SSLDirConfigRec *dc = (SSLDirConfigRec *)dcfg;
+ const char *err;
+
+ if ((err = ssl_cmd_check_dir(cmd, &arg))) {
+ return err;
+ }
+
+ dc->proxy->crl_path = arg;
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLProxyCARevocationFile(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg)
+{
+ SSLDirConfigRec *dc = (SSLDirConfigRec *)dcfg;
+ const char *err;
+
+ if ((err = ssl_cmd_check_file(cmd, &arg))) {
+ return err;
+ }
+
+ dc->proxy->crl_file = arg;
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLProxyCARevocationCheck(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg)
+{
+ SSLDirConfigRec *dc = (SSLDirConfigRec *)dcfg;
+
+ return ssl_cmd_crlcheck_parse(cmd, arg, &dc->proxy->crl_check_mask);
+}
+
+const char *ssl_cmd_SSLProxyMachineCertificateFile(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg)
+{
+ SSLDirConfigRec *dc = (SSLDirConfigRec *)dcfg;
+ const char *err;
+
+ if ((err = ssl_cmd_check_file(cmd, &arg))) {
+ return err;
+ }
+
+ dc->proxy->pkp->cert_file = arg;
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLProxyMachineCertificatePath(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg)
+{
+ SSLDirConfigRec *dc = (SSLDirConfigRec *)dcfg;
+ const char *err;
+
+ if ((err = ssl_cmd_check_dir(cmd, &arg))) {
+ return err;
+ }
+
+ dc->proxy->pkp->cert_path = arg;
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLProxyMachineCertificateChainFile(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg)
+{
+ SSLDirConfigRec *dc = (SSLDirConfigRec *)dcfg;
+ const char *err;
+
+ if ((err = ssl_cmd_check_file(cmd, &arg))) {
+ return err;
+ }
+
+ dc->proxy->pkp->ca_cert_file = arg;
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLUserName(cmd_parms *cmd, void *dcfg,
+ const char *arg)
+{
+ SSLDirConfigRec *dc = (SSLDirConfigRec *)dcfg;
+ dc->szUserName = arg;
+ return NULL;
+}
+
+static const char *ssl_cmd_ocspcheck_parse(cmd_parms *parms,
+ const char *arg,
+ int *mask)
+{
+ const char *w;
+
+ w = ap_getword_conf(parms->temp_pool, &arg);
+ if (strcEQ(w, "off")) {
+ *mask = SSL_OCSPCHECK_NONE;
+ }
+ else if (strcEQ(w, "leaf")) {
+ *mask = SSL_OCSPCHECK_LEAF;
+ }
+ else if (strcEQ(w, "on")) {
+ *mask = SSL_OCSPCHECK_CHAIN;
+ }
+ else {
+ return apr_pstrcat(parms->temp_pool, parms->cmd->name,
+ ": Invalid argument '", w, "'",
+ NULL);
+ }
+
+ while (*arg) {
+ w = ap_getword_conf(parms->temp_pool, &arg);
+ if (strcEQ(w, "no_ocsp_for_cert_ok")) {
+ *mask |= SSL_OCSPCHECK_NO_OCSP_FOR_CERT_OK;
+ }
+ else {
+ return apr_pstrcat(parms->temp_pool, parms->cmd->name,
+ ": Invalid argument '", w, "'",
+ NULL);
+ }
+ }
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLOCSPEnable(cmd_parms *cmd, void *dcfg, const char *arg)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+
+#ifdef OPENSSL_NO_OCSP
+ if (flag) {
+ return "OCSP support disabled in SSL library; cannot enable "
+ "OCSP validation";
+ }
+#endif
+
+ return ssl_cmd_ocspcheck_parse(cmd, arg, &sc->server->ocsp_mask);
+}
+
+const char *ssl_cmd_SSLOCSPOverrideResponder(cmd_parms *cmd, void *dcfg, int flag)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+
+ sc->server->ocsp_force_default = flag ? TRUE : FALSE;
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLOCSPDefaultResponder(cmd_parms *cmd, void *dcfg, const char *arg)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+
+ sc->server->ocsp_responder = arg;
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLOCSPResponseTimeSkew(cmd_parms *cmd, void *dcfg, const char *arg)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ sc->server->ocsp_resptime_skew = atoi(arg);
+ if (sc->server->ocsp_resptime_skew < 0) {
+ return "SSLOCSPResponseTimeSkew: invalid argument";
+ }
+ return NULL;
+}
+
+const char *ssl_cmd_SSLOCSPResponseMaxAge(cmd_parms *cmd, void *dcfg, const char *arg)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ sc->server->ocsp_resp_maxage = atoi(arg);
+ if (sc->server->ocsp_resp_maxage < 0) {
+ return "SSLOCSPResponseMaxAge: invalid argument";
+ }
+ return NULL;
+}
+
+const char *ssl_cmd_SSLOCSPResponderTimeout(cmd_parms *cmd, void *dcfg, const char *arg)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ sc->server->ocsp_responder_timeout = apr_time_from_sec(atoi(arg));
+ if (sc->server->ocsp_responder_timeout < 0) {
+ return "SSLOCSPResponderTimeout: invalid argument";
+ }
+ return NULL;
+}
+
+const char *ssl_cmd_SSLOCSPUseRequestNonce(cmd_parms *cmd, void *dcfg, int flag)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+
+ sc->server->ocsp_use_request_nonce = flag ? TRUE : FALSE;
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLOCSPProxyURL(cmd_parms *cmd, void *dcfg,
+ const char *arg)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ sc->server->proxy_uri = apr_palloc(cmd->pool, sizeof(apr_uri_t));
+ if (apr_uri_parse(cmd->pool, arg, sc->server->proxy_uri) != APR_SUCCESS) {
+ return apr_psprintf(cmd->pool,
+ "SSLOCSPProxyURL: Cannot parse URL %s", arg);
+ }
+ return NULL;
+}
+
+/* Set OCSP responder certificate verification directive */
+const char *ssl_cmd_SSLOCSPNoVerify(cmd_parms *cmd, void *dcfg, int flag)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+
+ sc->server->ocsp_noverify = flag ? TRUE : FALSE;
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLProxyCheckPeerExpire(cmd_parms *cmd, void *dcfg, int flag)
+{
+ SSLDirConfigRec *dc = (SSLDirConfigRec *)dcfg;
+
+ dc->proxy->ssl_check_peer_expire = flag ? TRUE : FALSE;
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLProxyCheckPeerCN(cmd_parms *cmd, void *dcfg, int flag)
+{
+ SSLDirConfigRec *dc = (SSLDirConfigRec *)dcfg;
+
+ dc->proxy->ssl_check_peer_cn = flag ? TRUE : FALSE;
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLProxyCheckPeerName(cmd_parms *cmd, void *dcfg, int flag)
+{
+ SSLDirConfigRec *dc = (SSLDirConfigRec *)dcfg;
+
+ dc->proxy->ssl_check_peer_name = flag ? TRUE : FALSE;
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLStrictSNIVHostCheck(cmd_parms *cmd, void *dcfg, int flag)
+{
+#ifdef HAVE_TLSEXT
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+
+ sc->strict_sni_vhost_check = flag ? SSL_ENABLED_TRUE : SSL_ENABLED_FALSE;
+
+ return NULL;
+#else
+ return "SSLStrictSNIVHostCheck failed; OpenSSL is not built with support "
+ "for TLS extensions and SNI indication. Refer to the "
+ "documentation, and build a compatible version of OpenSSL.";
+#endif
+}
+
+#ifdef HAVE_OCSP_STAPLING
+
+const char *ssl_cmd_SSLStaplingCache(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg)
+{
+ SSLModConfigRec *mc = myModConfig(cmd->server);
+ const char *err, *sep, *name;
+
+ if ((err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
+ return err;
+ }
+
+ /* Argument is of form 'name:args' or just 'name'. */
+ sep = ap_strchr_c(arg, ':');
+ if (sep) {
+ name = apr_pstrmemdup(cmd->pool, arg, sep - arg);
+ sep++;
+ }
+ else {
+ name = arg;
+ }
+
+ /* Find the provider of given name. */
+ mc->stapling_cache = ap_lookup_provider(AP_SOCACHE_PROVIDER_GROUP,
+ name,
+ AP_SOCACHE_PROVIDER_VERSION);
+ if (mc->stapling_cache) {
+ /* Cache found; create it, passing anything beyond the colon. */
+ err = mc->stapling_cache->create(&mc->stapling_cache_context,
+ sep, cmd->temp_pool,
+ cmd->pool);
+ }
+ else {
+ apr_array_header_t *name_list;
+ const char *all_names;
+
+ /* Build a comma-separated list of all registered provider
+ * names: */
+ name_list = ap_list_provider_names(cmd->pool,
+ AP_SOCACHE_PROVIDER_GROUP,
+ AP_SOCACHE_PROVIDER_VERSION);
+ all_names = apr_array_pstrcat(cmd->pool, name_list, ',');
+
+ err = apr_psprintf(cmd->pool, "'%s' stapling cache not supported "
+ "(known names: %s) Maybe you need to load the "
+ "appropriate socache module (mod_socache_%s?)",
+ name, all_names, name);
+ }
+
+ if (err) {
+ return apr_psprintf(cmd->pool, "SSLStaplingCache: %s", err);
+ }
+
+ return NULL;
+}
+
+const char *ssl_cmd_SSLUseStapling(cmd_parms *cmd, void *dcfg, int flag)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ sc->server->stapling_enabled = flag ? TRUE : FALSE;
+ return NULL;
+}
+
+const char *ssl_cmd_SSLStaplingResponseTimeSkew(cmd_parms *cmd, void *dcfg,
+ const char *arg)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ sc->server->stapling_resptime_skew = atoi(arg);
+ if (sc->server->stapling_resptime_skew < 0) {
+ return "SSLStaplingResponseTimeSkew: invalid argument";
+ }
+ return NULL;
+}
+
+const char *ssl_cmd_SSLStaplingResponseMaxAge(cmd_parms *cmd, void *dcfg,
+ const char *arg)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ sc->server->stapling_resp_maxage = atoi(arg);
+ if (sc->server->stapling_resp_maxage < 0) {
+ return "SSLStaplingResponseMaxAge: invalid argument";
+ }
+ return NULL;
+}
+
+const char *ssl_cmd_SSLStaplingStandardCacheTimeout(cmd_parms *cmd, void *dcfg,
+ const char *arg)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ sc->server->stapling_cache_timeout = atoi(arg);
+ if (sc->server->stapling_cache_timeout < 0) {
+ return "SSLStaplingStandardCacheTimeout: invalid argument";
+ }
+ return NULL;
+}
+
+const char *ssl_cmd_SSLStaplingErrorCacheTimeout(cmd_parms *cmd, void *dcfg,
+ const char *arg)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ sc->server->stapling_errcache_timeout = atoi(arg);
+ if (sc->server->stapling_errcache_timeout < 0) {
+ return "SSLStaplingErrorCacheTimeout: invalid argument";
+ }
+ return NULL;
+}
+
+const char *ssl_cmd_SSLStaplingReturnResponderErrors(cmd_parms *cmd,
+ void *dcfg, int flag)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ sc->server->stapling_return_errors = flag ? TRUE : FALSE;
+ return NULL;
+}
+
+const char *ssl_cmd_SSLStaplingFakeTryLater(cmd_parms *cmd,
+ void *dcfg, int flag)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ sc->server->stapling_fake_trylater = flag ? TRUE : FALSE;
+ return NULL;
+}
+
+const char *ssl_cmd_SSLStaplingResponderTimeout(cmd_parms *cmd, void *dcfg,
+ const char *arg)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ sc->server->stapling_responder_timeout = atoi(arg);
+ sc->server->stapling_responder_timeout *= APR_USEC_PER_SEC;
+ if (sc->server->stapling_responder_timeout < 0) {
+ return "SSLStaplingResponderTimeout: invalid argument";
+ }
+ return NULL;
+}
+
+const char *ssl_cmd_SSLStaplingForceURL(cmd_parms *cmd, void *dcfg,
+ const char *arg)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ sc->server->stapling_force_url = arg;
+ return NULL;
+}
+
+#endif /* HAVE_OCSP_STAPLING */
+
+#ifdef HAVE_SSL_CONF_CMD
+const char *ssl_cmd_SSLOpenSSLConfCmd(cmd_parms *cmd, void *dcfg,
+ const char *arg1, const char *arg2)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ SSL_CONF_CTX *cctx = sc->server->ssl_ctx_config;
+ int value_type = SSL_CONF_cmd_value_type(cctx, arg1);
+ const char *err;
+ ssl_ctx_param_t *param;
+
+ if (value_type == SSL_CONF_TYPE_UNKNOWN) {
+ return apr_psprintf(cmd->pool,
+ "'%s': invalid OpenSSL configuration command",
+ arg1);
+ }
+
+ if (value_type == SSL_CONF_TYPE_FILE) {
+ if ((err = ssl_cmd_check_file(cmd, &arg2)))
+ return err;
+ }
+ else if (value_type == SSL_CONF_TYPE_DIR) {
+ if ((err = ssl_cmd_check_dir(cmd, &arg2)))
+ return err;
+ }
+
+ if (strcEQ(arg1, "CipherString")) {
+ /* always disable null and export ciphers */
+ arg2 = apr_pstrcat(cmd->pool, arg2, ":!aNULL:!eNULL:!EXP", NULL);
+ }
+
+ param = apr_array_push(sc->server->ssl_ctx_param);
+ param->name = arg1;
+ param->value = arg2;
+ return NULL;
+}
+#endif
+
+#ifdef HAVE_SRP
+
+const char *ssl_cmd_SSLSRPVerifierFile(cmd_parms *cmd, void *dcfg,
+ const char *arg)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ const char *err;
+
+ if ((err = ssl_cmd_check_file(cmd, &arg)))
+ return err;
+ /* SRP_VBASE_init takes char*, not const char* */
+ sc->server->srp_vfile = apr_pstrdup(cmd->pool, arg);
+ return NULL;
+}
+
+const char *ssl_cmd_SSLSRPUnknownUserSeed(cmd_parms *cmd, void *dcfg,
+ const char *arg)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ /* SRP_VBASE_new takes char*, not const char* */
+ sc->server->srp_unknown_user_seed = apr_pstrdup(cmd->pool, arg);
+ return NULL;
+}
+
+#endif /* HAVE_SRP */
+
+/* OCSP Responder File Function to read in value */
+const char *ssl_cmd_SSLOCSPResponderCertificateFile(cmd_parms *cmd, void *dcfg,
+ const char *arg)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ const char *err;
+
+ if ((err = ssl_cmd_check_file(cmd, &arg))) {
+ return err;
+ }
+
+ sc->server->ocsp_certs_file = arg;
+ return NULL;
+}
+
+void ssl_hook_ConfigTest(apr_pool_t *pconf, server_rec *s)
+{
+ apr_file_t *out = NULL;
+ if (!ap_exists_config_define("DUMP_CERTS")) {
+ return;
+ }
+ apr_file_open_stdout(&out, pconf);
+ apr_file_printf(out, "Server certificates:\n");
+
+ /* Dump the filenames of all configured server certificates to
+ * stdout. */
+ while (s) {
+ SSLSrvConfigRec *sc = mySrvConfig(s);
+
+ if (sc && sc->server && sc->server->pks) {
+ modssl_pk_server_t *const pks = sc->server->pks;
+ int i;
+
+ for (i = 0; (i < pks->cert_files->nelts) &&
+ APR_ARRAY_IDX(pks->cert_files, i, const char *);
+ i++) {
+ apr_file_printf(out, " %s\n",
+ APR_ARRAY_IDX(pks->cert_files,
+ i, const char *));
+ }
+ }
+
+ s = s->next;
+ }
+
+}
+
diff --git a/modules/ssl/ssl_engine_init.c b/modules/ssl/ssl_engine_init.c
new file mode 100644
index 0000000..825621d
--- /dev/null
+++ b/modules/ssl/ssl_engine_init.c
@@ -0,0 +1,2367 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* _ _
+ * _ __ ___ ___ __| | ___ ___| | mod_ssl
+ * | '_ ` _ \ / _ \ / _` | / __/ __| | Apache Interface to OpenSSL
+ * | | | | | | (_) | (_| | \__ \__ \ |
+ * |_| |_| |_|\___/ \__,_|___|___/___/_|
+ * |_____|
+ * ssl_engine_init.c
+ * Initialization of Servers
+ */
+ /* ``Recursive, adj.;
+ see Recursive.''
+ -- Unknown */
+#include "ssl_private.h"
+#include "mod_ssl.h"
+#include "mod_ssl_openssl.h"
+#include "mpm_common.h"
+#include "mod_md.h"
+
+static apr_status_t ssl_init_ca_cert_path(server_rec *, apr_pool_t *, const char *,
+ STACK_OF(X509_NAME) *, STACK_OF(X509_INFO) *);
+
+APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(ssl, SSL, int, init_server,
+ (server_rec *s,apr_pool_t *p,int is_proxy,SSL_CTX *ctx),
+ (s,p,is_proxy,ctx), OK, DECLINED)
+
+APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(ssl, SSL, int, add_cert_files,
+ (server_rec *s, apr_pool_t *p,
+ apr_array_header_t *cert_files, apr_array_header_t *key_files),
+ (s, p, cert_files, key_files),
+ OK, DECLINED)
+
+APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(ssl, SSL, int, add_fallback_cert_files,
+ (server_rec *s, apr_pool_t *p,
+ apr_array_header_t *cert_files, apr_array_header_t *key_files),
+ (s, p, cert_files, key_files),
+ OK, DECLINED)
+
+APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(ssl, SSL, int, answer_challenge,
+ (conn_rec *c, const char *server_name,
+ X509 **pcert, EVP_PKEY **pkey),
+ (c, server_name, pcert, pkey),
+ DECLINED, DECLINED)
+
+
+/* _________________________________________________________________
+**
+** Module Initialization
+** _________________________________________________________________
+*/
+
+#ifdef HAVE_ECC
+#define KEYTYPES "RSA, DSA or ECC"
+#else
+#define KEYTYPES "RSA or DSA"
+#endif
+
+#if MODSSL_USE_OPENSSL_PRE_1_1_API
+/* OpenSSL Pre-1.1.0 compatibility */
+/* Taken from OpenSSL 1.1.0 snapshot 20160410 */
+static int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g)
+{
+ /* q is optional */
+ if (p == NULL || g == NULL)
+ return 0;
+ BN_free(dh->p);
+ BN_free(dh->q);
+ BN_free(dh->g);
+ dh->p = p;
+ dh->q = q;
+ dh->g = g;
+
+ if (q != NULL) {
+ dh->length = BN_num_bits(q);
+ }
+
+ return 1;
+}
+
+/*
+ * Grab well-defined DH parameters from OpenSSL, see the BN_get_rfc*
+ * functions in <openssl/bn.h> for all available primes.
+ */
+static DH *make_dh_params(BIGNUM *(*prime)(BIGNUM *))
+{
+ DH *dh = DH_new();
+ BIGNUM *p, *g;
+
+ if (!dh) {
+ return NULL;
+ }
+ p = prime(NULL);
+ g = BN_new();
+ if (g != NULL) {
+ BN_set_word(g, 2);
+ }
+ if (!p || !g || !DH_set0_pqg(dh, p, NULL, g)) {
+ DH_free(dh);
+ BN_free(p);
+ BN_free(g);
+ return NULL;
+ }
+ return dh;
+}
+
+/* Storage and initialization for DH parameters. */
+static struct dhparam {
+ BIGNUM *(*const prime)(BIGNUM *); /* function to generate... */
+ DH *dh; /* ...this, used for keys.... */
+ const unsigned int min; /* ...of length >= this. */
+} dhparams[] = {
+ { BN_get_rfc3526_prime_8192, NULL, 6145 },
+ { BN_get_rfc3526_prime_6144, NULL, 4097 },
+ { BN_get_rfc3526_prime_4096, NULL, 3073 },
+ { BN_get_rfc3526_prime_3072, NULL, 2049 },
+ { BN_get_rfc3526_prime_2048, NULL, 1025 },
+ { BN_get_rfc2409_prime_1024, NULL, 0 }
+};
+
+static void init_dh_params(void)
+{
+ unsigned n;
+
+ for (n = 0; n < sizeof(dhparams)/sizeof(dhparams[0]); n++)
+ dhparams[n].dh = make_dh_params(dhparams[n].prime);
+}
+
+static void free_dh_params(void)
+{
+ unsigned n;
+
+ /* DH_free() is a noop for a NULL parameter, so these are harmless
+ * in the (unexpected) case where these variables are already
+ * NULL. */
+ for (n = 0; n < sizeof(dhparams)/sizeof(dhparams[0]); n++) {
+ DH_free(dhparams[n].dh);
+ dhparams[n].dh = NULL;
+ }
+}
+
+/* Hand out the same DH structure though once generated as we leak
+ * memory otherwise and freeing the structure up after use would be
+ * hard to track and in fact is not needed at all as it is safe to
+ * use the same parameters over and over again security wise (in
+ * contrast to the keys itself) and code safe as the returned structure
+ * is duplicated by OpenSSL anyway. Hence no modification happens
+ * to our copy. */
+DH *modssl_get_dh_params(unsigned keylen)
+{
+ unsigned n;
+
+ for (n = 0; n < sizeof(dhparams)/sizeof(dhparams[0]); n++)
+ if (keylen >= dhparams[n].min)
+ return dhparams[n].dh;
+
+ return NULL; /* impossible to reach. */
+}
+#endif
+
+static void ssl_add_version_components(apr_pool_t *ptemp, apr_pool_t *pconf,
+ server_rec *s)
+{
+ char *modver = ssl_var_lookup(ptemp, s, NULL, NULL, "SSL_VERSION_INTERFACE");
+ char *libver = ssl_var_lookup(ptemp, s, NULL, NULL, "SSL_VERSION_LIBRARY");
+ char *incver = ssl_var_lookup(ptemp, s, NULL, NULL,
+ "SSL_VERSION_LIBRARY_INTERFACE");
+
+ ap_add_version_component(pconf, libver);
+
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(01876)
+ "%s compiled against Server: %s, Library: %s",
+ modver, AP_SERVER_BASEVERSION, incver);
+}
+
+/* _________________________________________________________________
+**
+** Let other answer special connection attempts.
+** Used in ACME challenge handling by mod_md.
+** _________________________________________________________________
+*/
+
+int ssl_is_challenge(conn_rec *c, const char *servername,
+ X509 **pcert, EVP_PKEY **pkey,
+ const char **pcert_pem, const char **pkey_pem)
+{
+ *pcert = NULL;
+ *pkey = NULL;
+ *pcert_pem = *pkey_pem = NULL;
+ if (ap_ssl_answer_challenge(c, servername, pcert_pem, pkey_pem)) {
+ return 1;
+ }
+ else if (OK == ssl_run_answer_challenge(c, servername, pcert, pkey)) {
+ return 1;
+ }
+ return 0;
+}
+
+#ifdef HAVE_FIPS
+static apr_status_t modssl_fips_cleanup(void *data)
+{
+ modssl_fips_enable(0);
+ return APR_SUCCESS;
+}
+#endif
+
+/*
+ * Per-module initialization
+ */
+apr_status_t ssl_init_Module(apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp,
+ server_rec *base_server)
+{
+ SSLModConfigRec *mc = myModConfig(base_server);
+ SSLSrvConfigRec *sc;
+ server_rec *s;
+ apr_status_t rv;
+ apr_array_header_t *pphrases;
+
+ if (SSLeay() < MODSSL_LIBRARY_VERSION) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, base_server, APLOGNO(01882)
+ "Init: this version of mod_ssl was compiled against "
+ "a newer library (%s, version currently loaded is %s)"
+ " - may result in undefined or erroneous behavior",
+ MODSSL_LIBRARY_TEXT, MODSSL_LIBRARY_DYNTEXT);
+ }
+
+ /* We initialize mc->pid per-process in the child init,
+ * but it should be initialized for startup before we
+ * call ssl_rand_seed() below.
+ */
+ mc->pid = getpid();
+
+ /*
+ * Let us cleanup on restarts and exits
+ */
+ apr_pool_cleanup_register(p, base_server,
+ ssl_init_ModuleKill,
+ apr_pool_cleanup_null);
+
+ /*
+ * Any init round fixes the global config
+ */
+ ssl_config_global_create(base_server); /* just to avoid problems */
+ ssl_config_global_fix(mc);
+
+ /*
+ * try to fix the configuration and open the dedicated SSL
+ * logfile as early as possible
+ */
+ for (s = base_server; s; s = s->next) {
+ sc = mySrvConfig(s);
+
+ if (sc->server) {
+ sc->server->sc = sc;
+ }
+
+ /*
+ * Create the server host:port string because we need it a lot
+ */
+ if (sc->vhost_id) {
+ /* already set. This should only happen if this config rec is
+ * shared with another server. Argh! */
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(10104)
+ "%s, SSLSrvConfigRec shared from %s",
+ ssl_util_vhostid(p, s), sc->vhost_id);
+ }
+ sc->vhost_id = ssl_util_vhostid(p, s);
+ sc->vhost_id_len = strlen(sc->vhost_id);
+
+ /* Default to enabled if SSLEngine is not set explicitly, and
+ * the protocol is https. */
+ if (ap_get_server_protocol(s)
+ && strcmp("https", ap_get_server_protocol(s)) == 0
+ && sc->enabled == SSL_ENABLED_UNSET
+ && (!apr_is_empty_array(sc->server->pks->cert_files))) {
+ sc->enabled = SSL_ENABLED_TRUE;
+ }
+
+ /* Fix up stuff that may not have been set. If sc->enabled is
+ * UNSET, then SSL is disabled on this vhost. */
+ if (sc->enabled == SSL_ENABLED_UNSET) {
+ sc->enabled = SSL_ENABLED_FALSE;
+ }
+
+ if (sc->session_cache_timeout == UNSET) {
+ sc->session_cache_timeout = SSL_SESSION_CACHE_TIMEOUT;
+ }
+
+ if (sc->server && sc->server->pphrase_dialog_type == SSL_PPTYPE_UNSET) {
+ sc->server->pphrase_dialog_type = SSL_PPTYPE_BUILTIN;
+ }
+ }
+
+#if APR_HAS_THREADS && MODSSL_USE_OPENSSL_PRE_1_1_API
+ ssl_util_thread_setup(p);
+#endif
+
+ /*
+ * SSL external crypto device ("engine") support
+ */
+#if defined(HAVE_OPENSSL_ENGINE_H) && defined(HAVE_ENGINE_INIT)
+ if ((rv = ssl_init_Engine(base_server, p)) != APR_SUCCESS) {
+ return rv;
+ }
+#endif
+
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, base_server, APLOGNO(01883)
+ "Init: Initialized %s library", MODSSL_LIBRARY_NAME);
+
+ /*
+ * Seed the Pseudo Random Number Generator (PRNG)
+ * only need ptemp here; nothing inside allocated from the pool
+ * needs to live once we return from ssl_rand_seed().
+ */
+ ssl_rand_seed(base_server, ptemp, SSL_RSCTX_STARTUP, "Init: ");
+
+#ifdef HAVE_FIPS
+ if (!modssl_fips_is_enabled() && mc->fips == TRUE) {
+ if (!modssl_fips_enable(1)) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, base_server, APLOGNO(01885)
+ "Could not enable FIPS mode");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, base_server);
+ return ssl_die(base_server);
+ }
+
+ apr_pool_cleanup_register(p, NULL, modssl_fips_cleanup,
+ apr_pool_cleanup_null);
+ }
+
+ /* Log actual FIPS mode which the SSL library is operating under,
+ * which may have been set outside of the mod_ssl
+ * configuration. */
+ if (modssl_fips_is_enabled()) {
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, base_server, APLOGNO(01884)
+ MODSSL_LIBRARY_NAME " has FIPS mode enabled");
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO(01886)
+ MODSSL_LIBRARY_NAME " has FIPS mode disabled");
+ }
+#endif
+
+ /*
+ * initialize the mutex handling
+ */
+ if (!ssl_mutex_init(base_server, p)) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+#ifdef HAVE_OCSP_STAPLING
+ ssl_stapling_certinfo_hash_init(p);
+#endif
+
+ /*
+ * initialize session caching
+ */
+ if ((rv = ssl_scache_init(base_server, p)) != APR_SUCCESS) {
+ return rv;
+ }
+
+ pphrases = apr_array_make(ptemp, 2, sizeof(char *));
+
+ /*
+ * initialize servers
+ */
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, base_server, APLOGNO(01887)
+ "Init: Initializing (virtual) servers for SSL");
+
+ for (s = base_server; s; s = s->next) {
+ sc = mySrvConfig(s);
+ /*
+ * Either now skip this server when SSL is disabled for
+ * it or give out some information about what we're
+ * configuring.
+ */
+
+ /*
+ * Read the server certificate and key
+ */
+ if ((rv = ssl_init_ConfigureServer(s, p, ptemp, sc, pphrases))
+ != APR_SUCCESS) {
+ return rv;
+ }
+ }
+
+ if (pphrases->nelts > 0) {
+ memset(pphrases->elts, 0, pphrases->elt_size * pphrases->nelts);
+ pphrases->nelts = 0;
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(02560)
+ "Init: Wiped out the queried pass phrases from memory");
+ }
+
+ /*
+ * Configuration consistency checks
+ */
+ if ((rv = ssl_init_CheckServers(base_server, ptemp)) != APR_SUCCESS) {
+ return rv;
+ }
+
+ for (s = base_server; s; s = s->next) {
+ SSLDirConfigRec *sdc = ap_get_module_config(s->lookup_defaults,
+ &ssl_module);
+
+ sc = mySrvConfig(s);
+ if (sc->enabled == SSL_ENABLED_TRUE || sc->enabled == SSL_ENABLED_OPTIONAL) {
+ if ((rv = ssl_run_init_server(s, p, 0, sc->server->ssl_ctx)) != APR_SUCCESS) {
+ return rv;
+ }
+ }
+
+ if (sdc->proxy_enabled) {
+ rv = ssl_run_init_server(s, p, 1, sdc->proxy->ssl_ctx);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+ }
+
+ /*
+ * Announce mod_ssl and SSL library in HTTP Server field
+ * as ``mod_ssl/X.X.X OpenSSL/X.X.X''
+ */
+ ssl_add_version_components(ptemp, p, base_server);
+
+ modssl_init_app_data2_idx(); /* for modssl_get_app_data2() at request time */
+
+#if MODSSL_USE_OPENSSL_PRE_1_1_API
+ init_dh_params();
+#else
+ init_bio_methods();
+#endif
+
+#ifdef HAVE_OPENSSL_KEYLOG
+ {
+ const char *logfn = getenv("SSLKEYLOGFILE");
+
+ if (logfn) {
+ rv = apr_file_open(&mc->keylog_file, logfn,
+ APR_FOPEN_CREATE|APR_FOPEN_WRITE|APR_FOPEN_APPEND|APR_FOPEN_LARGEFILE,
+ APR_FPROT_UREAD|APR_FPROT_UWRITE,
+ mc->pPool);
+ if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, rv, s, APLOGNO(10226)
+ "Could not open log file '%s' configured via SSLKEYLOGFILE",
+ logfn);
+ return rv;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, APLOGNO(10227)
+ "Init: Logging SSL private key material to %s", logfn);
+ }
+ }
+#endif
+
+ return OK;
+}
+
+/*
+ * Support for external a Crypto Device ("engine"), usually
+ * a hardware accelerator card for crypto operations.
+ */
+#if defined(HAVE_OPENSSL_ENGINE_H) && defined(HAVE_ENGINE_INIT)
+apr_status_t ssl_init_Engine(server_rec *s, apr_pool_t *p)
+{
+ SSLModConfigRec *mc = myModConfig(s);
+ ENGINE *e;
+
+ if (mc->szCryptoDevice) {
+ if (!(e = ENGINE_by_id(mc->szCryptoDevice))) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(01888)
+ "Init: Failed to load Crypto Device API `%s'",
+ mc->szCryptoDevice);
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+ return ssl_die(s);
+ }
+
+#ifdef ENGINE_CTRL_CHIL_SET_FORKCHECK
+ if (strEQ(mc->szCryptoDevice, "chil")) {
+ ENGINE_ctrl(e, ENGINE_CTRL_CHIL_SET_FORKCHECK, 1, 0, 0);
+ }
+#endif
+
+ if (!ENGINE_set_default(e, ENGINE_METHOD_ALL)) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(01889)
+ "Init: Failed to enable Crypto Device API `%s'",
+ mc->szCryptoDevice);
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+ return ssl_die(s);
+ }
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(01890)
+ "Init: loaded Crypto Device API `%s'",
+ mc->szCryptoDevice);
+
+ ENGINE_free(e);
+ }
+
+ return APR_SUCCESS;
+}
+#endif
+
+#ifdef HAVE_TLSEXT
+static apr_status_t ssl_init_ctx_tls_extensions(server_rec *s,
+ apr_pool_t *p,
+ apr_pool_t *ptemp,
+ modssl_ctx_t *mctx)
+{
+ apr_status_t rv;
+
+ /*
+ * Configure TLS extensions support
+ */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01893)
+ "Configuring TLS extension handling");
+
+ /*
+ * The Server Name Indication (SNI) provided by the ClientHello can be
+ * used to select the right (name-based-)vhost and its SSL configuration
+ * before the handshake takes place.
+ */
+ if (!SSL_CTX_set_tlsext_servername_callback(mctx->ssl_ctx,
+ ssl_callback_ServerNameIndication) ||
+ !SSL_CTX_set_tlsext_servername_arg(mctx->ssl_ctx, mctx)) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(01894)
+ "Unable to initialize TLS servername extension "
+ "callback (incompatible OpenSSL version?)");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+ return ssl_die(s);
+ }
+
+#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER)
+ /*
+ * The ClientHello callback also allows to retrieve the SNI, but since it
+ * runs at the earliest possible connection stage we can even set the TLS
+ * protocol version(s) according to the selected (name-based-)vhost, which
+ * is not possible at the SNI callback stage (due to OpenSSL internals).
+ */
+ SSL_CTX_set_client_hello_cb(mctx->ssl_ctx, ssl_callback_ClientHello, NULL);
+#endif
+
+#ifdef HAVE_OCSP_STAPLING
+ /*
+ * OCSP Stapling support, status_request extension
+ */
+ if ((mctx->pkp == FALSE) && (mctx->stapling_enabled == TRUE)) {
+ if ((rv = modssl_init_stapling(s, p, ptemp, mctx)) != APR_SUCCESS) {
+ return rv;
+ }
+ }
+#endif
+
+#ifdef HAVE_SRP
+ /*
+ * TLS-SRP support
+ */
+ if (mctx->srp_vfile != NULL) {
+ int err;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02308)
+ "Using SRP verifier file [%s]", mctx->srp_vfile);
+
+ if (!(mctx->srp_vbase = SRP_VBASE_new(mctx->srp_unknown_user_seed))) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02309)
+ "Unable to initialize SRP verifier structure "
+ "[%s seed]",
+ mctx->srp_unknown_user_seed ? "with" : "without");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+ return ssl_die(s);
+ }
+
+ err = SRP_VBASE_init(mctx->srp_vbase, mctx->srp_vfile);
+ if (err != SRP_NO_ERROR) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02310)
+ "Unable to load SRP verifier file [error %d]", err);
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+ return ssl_die(s);
+ }
+
+ SSL_CTX_set_srp_username_callback(mctx->ssl_ctx,
+ ssl_callback_SRPServerParams);
+ SSL_CTX_set_srp_cb_arg(mctx->ssl_ctx, mctx);
+ }
+#endif
+ return APR_SUCCESS;
+}
+#endif
+
+static apr_status_t ssl_init_ctx_protocol(server_rec *s,
+ apr_pool_t *p,
+ apr_pool_t *ptemp,
+ modssl_ctx_t *mctx)
+{
+ SSL_CTX *ctx = NULL;
+ MODSSL_SSL_METHOD_CONST SSL_METHOD *method = NULL;
+ char *cp;
+ int protocol = mctx->protocol;
+ SSLSrvConfigRec *sc = mySrvConfig(s);
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ int prot;
+#endif
+
+ /*
+ * Create the new per-server SSL context
+ */
+ if (protocol == SSL_PROTOCOL_NONE) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02231)
+ "No SSL protocols available [hint: SSLProtocol]");
+ return ssl_die(s);
+ }
+
+ cp = apr_pstrcat(p,
+#ifndef OPENSSL_NO_SSL3
+ (protocol & SSL_PROTOCOL_SSLV3 ? "SSLv3, " : ""),
+#endif
+ (protocol & SSL_PROTOCOL_TLSV1 ? "TLSv1, " : ""),
+#ifdef HAVE_TLSV1_X
+ (protocol & SSL_PROTOCOL_TLSV1_1 ? "TLSv1.1, " : ""),
+ (protocol & SSL_PROTOCOL_TLSV1_2 ? "TLSv1.2, " : ""),
+#if SSL_HAVE_PROTOCOL_TLSV1_3
+ (protocol & SSL_PROTOCOL_TLSV1_3 ? "TLSv1.3, " : ""),
+#endif
+#endif
+ NULL);
+ cp[strlen(cp)-2] = NUL;
+
+ ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s,
+ "Creating new SSL context (protocols: %s)", cp);
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+#ifndef OPENSSL_NO_SSL3
+ if (protocol == SSL_PROTOCOL_SSLV3) {
+ method = mctx->pkp ?
+ SSLv3_client_method() : /* proxy */
+ SSLv3_server_method(); /* server */
+ }
+ else
+#endif
+ if (protocol == SSL_PROTOCOL_TLSV1) {
+ method = mctx->pkp ?
+ TLSv1_client_method() : /* proxy */
+ TLSv1_server_method(); /* server */
+ }
+#ifdef HAVE_TLSV1_X
+ else if (protocol == SSL_PROTOCOL_TLSV1_1) {
+ method = mctx->pkp ?
+ TLSv1_1_client_method() : /* proxy */
+ TLSv1_1_server_method(); /* server */
+ }
+ else if (protocol == SSL_PROTOCOL_TLSV1_2) {
+ method = mctx->pkp ?
+ TLSv1_2_client_method() : /* proxy */
+ TLSv1_2_server_method(); /* server */
+ }
+#if SSL_HAVE_PROTOCOL_TLSV1_3
+ else if (protocol == SSL_PROTOCOL_TLSV1_3) {
+ method = mctx->pkp ?
+ TLSv1_3_client_method() : /* proxy */
+ TLSv1_3_server_method(); /* server */
+ }
+#endif
+#endif
+ else { /* For multiple protocols, we need a flexible method */
+ method = mctx->pkp ?
+ SSLv23_client_method() : /* proxy */
+ SSLv23_server_method(); /* server */
+ }
+#else
+ method = mctx->pkp ?
+ TLS_client_method() : /* proxy */
+ TLS_server_method(); /* server */
+#endif
+ ctx = SSL_CTX_new(method);
+
+ mctx->ssl_ctx = ctx;
+
+ SSL_CTX_set_options(ctx, SSL_OP_ALL);
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
+ (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20800000L)
+ /* always disable SSLv2, as per RFC 6176 */
+ SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2);
+
+#ifndef OPENSSL_NO_SSL3
+ if (!(protocol & SSL_PROTOCOL_SSLV3)) {
+ SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv3);
+ }
+#endif
+
+ if (!(protocol & SSL_PROTOCOL_TLSV1)) {
+ SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1);
+ }
+
+#ifdef HAVE_TLSV1_X
+ if (!(protocol & SSL_PROTOCOL_TLSV1_1)) {
+ SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_1);
+ }
+
+ if (!(protocol & SSL_PROTOCOL_TLSV1_2)) {
+ SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_2);
+ }
+#if SSL_HAVE_PROTOCOL_TLSV1_3
+ ssl_set_ctx_protocol_option(s, ctx, SSL_OP_NO_TLSv1_3,
+ protocol & SSL_PROTOCOL_TLSV1_3, "TLSv1.3");
+#endif
+#endif
+
+#else /* #if OPENSSL_VERSION_NUMBER < 0x10100000L */
+ /* We first determine the maximum protocol version we should provide */
+#if SSL_HAVE_PROTOCOL_TLSV1_3
+ if (protocol & SSL_PROTOCOL_TLSV1_3) {
+ prot = TLS1_3_VERSION;
+ } else
+#endif
+ if (protocol & SSL_PROTOCOL_TLSV1_2) {
+ prot = TLS1_2_VERSION;
+ } else if (protocol & SSL_PROTOCOL_TLSV1_1) {
+ prot = TLS1_1_VERSION;
+ } else if (protocol & SSL_PROTOCOL_TLSV1) {
+ prot = TLS1_VERSION;
+#ifndef OPENSSL_NO_SSL3
+ } else if (protocol & SSL_PROTOCOL_SSLV3) {
+ prot = SSL3_VERSION;
+#endif
+ } else {
+ SSL_CTX_free(ctx);
+ mctx->ssl_ctx = NULL;
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(03378)
+ "No SSL protocols available [hint: SSLProtocol]");
+ return ssl_die(s);
+ }
+ SSL_CTX_set_max_proto_version(ctx, prot);
+
+ /* Next we scan for the minimal protocol version we should provide,
+ * but we do not allow holes between max and min */
+#if SSL_HAVE_PROTOCOL_TLSV1_3
+ if (prot == TLS1_3_VERSION && protocol & SSL_PROTOCOL_TLSV1_2) {
+ prot = TLS1_2_VERSION;
+ }
+#endif
+ if (prot == TLS1_2_VERSION && protocol & SSL_PROTOCOL_TLSV1_1) {
+ prot = TLS1_1_VERSION;
+ }
+ if (prot == TLS1_1_VERSION && protocol & SSL_PROTOCOL_TLSV1) {
+ prot = TLS1_VERSION;
+ }
+#ifndef OPENSSL_NO_SSL3
+ if (prot == TLS1_VERSION && protocol & SSL_PROTOCOL_SSLV3) {
+ prot = SSL3_VERSION;
+ }
+#endif
+ SSL_CTX_set_min_proto_version(ctx, prot);
+#endif /* if OPENSSL_VERSION_NUMBER < 0x10100000L */
+
+#ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
+ if (sc->cipher_server_pref == TRUE) {
+ SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
+ }
+#endif
+
+
+#ifndef OPENSSL_NO_COMP
+ if (sc->compression != TRUE) {
+#ifdef SSL_OP_NO_COMPRESSION
+ /* OpenSSL >= 1.0 only */
+ SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION);
+#else
+ sk_SSL_COMP_zero(SSL_COMP_get_compression_methods());
+#endif
+ }
+#endif
+
+#ifdef SSL_OP_NO_TICKET
+ /*
+ * Configure using RFC 5077 TLS session tickets
+ * for session resumption.
+ */
+ if (sc->session_tickets == FALSE) {
+ SSL_CTX_set_options(ctx, SSL_OP_NO_TICKET);
+ }
+#endif
+
+#ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION
+ if (sc->insecure_reneg == TRUE) {
+ SSL_CTX_set_options(ctx, SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION);
+ }
+#endif
+
+ SSL_CTX_set_app_data(ctx, s);
+
+ /*
+ * Configure additional context ingredients
+ */
+ SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE);
+#ifdef HAVE_ECC
+ SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE);
+#endif
+
+#ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
+ /*
+ * Disallow a session from being resumed during a renegotiation,
+ * so that an acceptable cipher suite can be negotiated.
+ */
+ SSL_CTX_set_options(ctx, SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
+#endif
+
+#ifdef SSL_MODE_RELEASE_BUFFERS
+ /* If httpd is configured to reduce mem usage, ask openssl to do so, too */
+ if (ap_max_mem_free != APR_ALLOCATOR_MAX_FREE_UNLIMITED)
+ SSL_CTX_set_mode(ctx, SSL_MODE_RELEASE_BUFFERS);
+#endif
+
+#if OPENSSL_VERSION_NUMBER >= 0x1010100fL
+ /* For OpenSSL >=1.1.1, disable auto-retry mode so it's possible
+ * to consume handshake records without blocking for app-data.
+ * https://github.com/openssl/openssl/issues/7178 */
+ SSL_CTX_clear_mode(ctx, SSL_MODE_AUTO_RETRY);
+#endif
+
+#ifdef HAVE_OPENSSL_KEYLOG
+ if (mctx->sc->mc->keylog_file) {
+ SSL_CTX_set_keylog_callback(ctx, modssl_callback_keylog);
+ }
+#endif
+
+ return APR_SUCCESS;
+}
+
+static void ssl_init_ctx_session_cache(server_rec *s,
+ apr_pool_t *p,
+ apr_pool_t *ptemp,
+ modssl_ctx_t *mctx)
+{
+ SSL_CTX *ctx = mctx->ssl_ctx;
+ SSLModConfigRec *mc = myModConfig(s);
+
+ SSL_CTX_set_session_cache_mode(ctx, mc->sesscache_mode);
+
+ if (mc->sesscache) {
+ SSL_CTX_sess_set_new_cb(ctx, ssl_callback_NewSessionCacheEntry);
+ SSL_CTX_sess_set_get_cb(ctx, ssl_callback_GetSessionCacheEntry);
+ SSL_CTX_sess_set_remove_cb(ctx, ssl_callback_DelSessionCacheEntry);
+ }
+}
+
+static void ssl_init_ctx_callbacks(server_rec *s,
+ apr_pool_t *p,
+ apr_pool_t *ptemp,
+ modssl_ctx_t *mctx)
+{
+ SSL_CTX *ctx = mctx->ssl_ctx;
+
+#if MODSSL_USE_OPENSSL_PRE_1_1_API
+ /* Note that for OpenSSL>=1.1, auto selection is enabled via
+ * SSL_CTX_set_dh_auto(,1) if no parameter is configured. */
+ SSL_CTX_set_tmp_dh_callback(ctx, ssl_callback_TmpDH);
+#endif
+
+ SSL_CTX_set_info_callback(ctx, ssl_callback_Info);
+
+#ifdef HAVE_TLS_ALPN
+ SSL_CTX_set_alpn_select_cb(ctx, ssl_callback_alpn_select, NULL);
+#endif
+}
+
+static APR_INLINE
+int modssl_CTX_load_verify_locations(SSL_CTX *ctx,
+ const char *file,
+ const char *path)
+{
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+ if (!SSL_CTX_load_verify_locations(ctx, file, path))
+ return 0;
+#else
+ if (file && !SSL_CTX_load_verify_file(ctx, file))
+ return 0;
+ if (path && !SSL_CTX_load_verify_dir(ctx, path))
+ return 0;
+#endif
+ return 1;
+}
+
+static apr_status_t ssl_init_ctx_verify(server_rec *s,
+ apr_pool_t *p,
+ apr_pool_t *ptemp,
+ modssl_ctx_t *mctx)
+{
+ SSL_CTX *ctx = mctx->ssl_ctx;
+
+ int verify = SSL_VERIFY_NONE;
+ STACK_OF(X509_NAME) *ca_list;
+
+ if (mctx->auth.verify_mode == SSL_CVERIFY_UNSET) {
+ mctx->auth.verify_mode = SSL_CVERIFY_NONE;
+ }
+
+ if (mctx->auth.verify_depth == UNSET) {
+ mctx->auth.verify_depth = 1;
+ }
+
+ /*
+ * Configure callbacks for SSL context
+ */
+ if (mctx->auth.verify_mode == SSL_CVERIFY_REQUIRE) {
+ verify |= SSL_VERIFY_PEER_STRICT;
+ }
+
+ if ((mctx->auth.verify_mode == SSL_CVERIFY_OPTIONAL) ||
+ (mctx->auth.verify_mode == SSL_CVERIFY_OPTIONAL_NO_CA))
+ {
+ verify |= SSL_VERIFY_PEER;
+ }
+
+ SSL_CTX_set_verify(ctx, verify, ssl_callback_SSLVerify);
+
+ /*
+ * Configure Client Authentication details
+ */
+ if (mctx->auth.ca_cert_file || mctx->auth.ca_cert_path) {
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s,
+ "Configuring client authentication");
+
+ if (!modssl_CTX_load_verify_locations(ctx, mctx->auth.ca_cert_file,
+ mctx->auth.ca_cert_path)) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(01895)
+ "Unable to configure verify locations "
+ "for client authentication");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+ return ssl_die(s);
+ }
+
+ if (mctx->pks && (mctx->pks->ca_name_file || mctx->pks->ca_name_path)) {
+ ca_list = ssl_init_FindCAList(s, ptemp,
+ mctx->pks->ca_name_file,
+ mctx->pks->ca_name_path);
+ } else
+ ca_list = ssl_init_FindCAList(s, ptemp,
+ mctx->auth.ca_cert_file,
+ mctx->auth.ca_cert_path);
+ if (sk_X509_NAME_num(ca_list) <= 0) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(01896)
+ "Unable to determine list of acceptable "
+ "CA certificates for client authentication");
+ return ssl_die(s);
+ }
+
+ SSL_CTX_set_client_CA_list(ctx, ca_list);
+ }
+
+ /*
+ * Give a warning when no CAs were configured but client authentication
+ * should take place. This cannot work.
+ */
+ if (mctx->auth.verify_mode == SSL_CVERIFY_REQUIRE) {
+ ca_list = SSL_CTX_get_client_CA_list(ctx);
+
+ if (sk_X509_NAME_num(ca_list) == 0) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(01897)
+ "Init: Oops, you want to request client "
+ "authentication, but no CAs are known for "
+ "verification!? [Hint: SSLCACertificate*]");
+ }
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t ssl_init_ctx_cipher_suite(server_rec *s,
+ apr_pool_t *p,
+ apr_pool_t *ptemp,
+ modssl_ctx_t *mctx)
+{
+ SSL_CTX *ctx = mctx->ssl_ctx;
+ const char *suite;
+
+ /*
+ * Configure SSL Cipher Suite. Always disable NULL and export ciphers,
+ * see also ssl_engine_config.c:ssl_cmd_SSLCipherSuite().
+ * OpenSSL's SSL_DEFAULT_CIPHER_LIST includes !aNULL:!eNULL from 0.9.8f,
+ * and !EXP from 0.9.8zf/1.0.1m/1.0.2a, so append them while we support
+ * earlier versions.
+ */
+ suite = mctx->auth.cipher_suite ? mctx->auth.cipher_suite :
+ apr_pstrcat(ptemp, SSL_DEFAULT_CIPHER_LIST, ":!aNULL:!eNULL:!EXP",
+ NULL);
+
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s,
+ "Configuring permitted SSL ciphers [%s]",
+ suite);
+
+ if (!SSL_CTX_set_cipher_list(ctx, suite)) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(01898)
+ "Unable to configure permitted SSL ciphers");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+ return ssl_die(s);
+ }
+#if SSL_HAVE_PROTOCOL_TLSV1_3
+ if (mctx->auth.tls13_ciphers
+ && !SSL_CTX_set_ciphersuites(ctx, mctx->auth.tls13_ciphers)) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10127)
+ "Unable to configure permitted TLSv1.3 ciphers");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+ return ssl_die(s);
+ }
+#endif
+ return APR_SUCCESS;
+}
+
+static APR_INLINE
+int modssl_X509_STORE_load_locations(X509_STORE *store,
+ const char *file,
+ const char *path)
+{
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+ if (!X509_STORE_load_locations(store, file, path))
+ return 0;
+#else
+ if (file && !X509_STORE_load_file(store, file))
+ return 0;
+ if (path && !X509_STORE_load_path(store, path))
+ return 0;
+#endif
+ return 1;
+}
+
+static apr_status_t ssl_init_ctx_crl(server_rec *s,
+ apr_pool_t *p,
+ apr_pool_t *ptemp,
+ modssl_ctx_t *mctx)
+{
+ X509_STORE *store = SSL_CTX_get_cert_store(mctx->ssl_ctx);
+ unsigned long crlflags = 0;
+ char *cfgp = mctx->pkp ? "SSLProxy" : "SSL";
+ int crl_check_mode;
+
+ if (mctx->ocsp_mask == UNSET) {
+ mctx->ocsp_mask = SSL_OCSPCHECK_NONE;
+ }
+
+ if (mctx->crl_check_mask == UNSET) {
+ mctx->crl_check_mask = SSL_CRLCHECK_NONE;
+ }
+ crl_check_mode = mctx->crl_check_mask & ~SSL_CRLCHECK_FLAGS;
+
+ /*
+ * Configure Certificate Revocation List (CRL) Details
+ */
+
+ if (!(mctx->crl_file || mctx->crl_path)) {
+ if (crl_check_mode == SSL_CRLCHECK_LEAF ||
+ crl_check_mode == SSL_CRLCHECK_CHAIN) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(01899)
+ "Host %s: CRL checking has been enabled, but "
+ "neither %sCARevocationFile nor %sCARevocationPath "
+ "is configured", mctx->sc->vhost_id, cfgp, cfgp);
+ return ssl_die(s);
+ }
+ return APR_SUCCESS;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01900)
+ "Configuring certificate revocation facility");
+
+ if (!store || !modssl_X509_STORE_load_locations(store, mctx->crl_file,
+ mctx->crl_path)) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(01901)
+ "Host %s: unable to configure X.509 CRL storage "
+ "for certificate revocation", mctx->sc->vhost_id);
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+ return ssl_die(s);
+ }
+
+ switch (crl_check_mode) {
+ case SSL_CRLCHECK_LEAF:
+ crlflags = X509_V_FLAG_CRL_CHECK;
+ break;
+ case SSL_CRLCHECK_CHAIN:
+ crlflags = X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL;
+ break;
+ default:
+ crlflags = 0;
+ }
+
+ if (crlflags) {
+ X509_STORE_set_flags(store, crlflags);
+ } else {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(01902)
+ "Host %s: X.509 CRL storage locations configured, "
+ "but CRL checking (%sCARevocationCheck) is not "
+ "enabled", mctx->sc->vhost_id, cfgp);
+ }
+
+ return APR_SUCCESS;
+}
+
+/*
+ * Read a file that optionally contains the server certificate in PEM
+ * format, possibly followed by a sequence of CA certificates that
+ * should be sent to the peer in the SSL Certificate message.
+ */
+static int use_certificate_chain(
+ SSL_CTX *ctx, char *file, int skipfirst, pem_password_cb *cb)
+{
+ BIO *bio;
+ X509 *x509;
+ unsigned long err;
+ int n;
+
+ if ((bio = BIO_new(BIO_s_file())) == NULL)
+ return -1;
+ if (BIO_read_filename(bio, file) <= 0) {
+ BIO_free(bio);
+ return -1;
+ }
+ /* optionally skip a leading server certificate */
+ if (skipfirst) {
+ if ((x509 = PEM_read_bio_X509(bio, NULL, cb, NULL)) == NULL) {
+ BIO_free(bio);
+ return -1;
+ }
+ X509_free(x509);
+ }
+ /* free a perhaps already configured extra chain */
+#ifdef OPENSSL_NO_SSL_INTERN
+ SSL_CTX_clear_extra_chain_certs(ctx);
+#else
+ if (ctx->extra_certs != NULL) {
+ sk_X509_pop_free((STACK_OF(X509) *)ctx->extra_certs, X509_free);
+ ctx->extra_certs = NULL;
+ }
+#endif
+
+ /* create new extra chain by loading the certs */
+ n = 0;
+ ERR_clear_error();
+ while ((x509 = PEM_read_bio_X509(bio, NULL, cb, NULL)) != NULL) {
+ if (!SSL_CTX_add_extra_chain_cert(ctx, x509)) {
+ X509_free(x509);
+ BIO_free(bio);
+ return -1;
+ }
+ n++;
+ }
+ /* Make sure that only the error is just an EOF */
+ if ((err = ERR_peek_error()) > 0) {
+ if (!( ERR_GET_LIB(err) == ERR_LIB_PEM
+ && ERR_GET_REASON(err) == PEM_R_NO_START_LINE)) {
+ BIO_free(bio);
+ return -1;
+ }
+ while (ERR_get_error() > 0) ;
+ }
+ BIO_free(bio);
+ return n;
+}
+
+static apr_status_t ssl_init_ctx_cert_chain(server_rec *s,
+ apr_pool_t *p,
+ apr_pool_t *ptemp,
+ modssl_ctx_t *mctx)
+{
+ BOOL skip_first = FALSE;
+ int i, n;
+ const char *chain = mctx->cert_chain;
+
+ /*
+ * Optionally configure extra server certificate chain certificates.
+ * This is usually done by OpenSSL automatically when one of the
+ * server cert issuers are found under SSLCACertificatePath or in
+ * SSLCACertificateFile. But because these are intended for client
+ * authentication it can conflict. For instance when you use a
+ * Global ID server certificate you've to send out the intermediate
+ * CA certificate, too. When you would just configure this with
+ * SSLCACertificateFile and also use client authentication mod_ssl
+ * would accept all clients also issued by this CA. Obviously this
+ * isn't what we want in this situation. So this feature here exists
+ * to allow one to explicitly configure CA certificates which are
+ * used only for the server certificate chain.
+ */
+ if (!chain) {
+ return APR_SUCCESS;
+ }
+
+ for (i = 0; (i < mctx->pks->cert_files->nelts) &&
+ APR_ARRAY_IDX(mctx->pks->cert_files, i, const char *); i++) {
+ if (strEQ(APR_ARRAY_IDX(mctx->pks->cert_files, i, const char *), chain)) {
+ skip_first = TRUE;
+ break;
+ }
+ }
+
+ n = use_certificate_chain(mctx->ssl_ctx, (char *)chain, skip_first, NULL);
+ if (n < 0) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(01903)
+ "Failed to configure CA certificate chain!");
+ return ssl_die(s);
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01904)
+ "Configuring server certificate chain "
+ "(%d CA certificate%s)",
+ n, n == 1 ? "" : "s");
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t ssl_init_ctx(server_rec *s,
+ apr_pool_t *p,
+ apr_pool_t *ptemp,
+ modssl_ctx_t *mctx)
+{
+ apr_status_t rv;
+
+ if ((rv = ssl_init_ctx_protocol(s, p, ptemp, mctx)) != APR_SUCCESS) {
+ return rv;
+ }
+
+ ssl_init_ctx_session_cache(s, p, ptemp, mctx);
+
+ ssl_init_ctx_callbacks(s, p, ptemp, mctx);
+
+ if ((rv = ssl_init_ctx_verify(s, p, ptemp, mctx)) != APR_SUCCESS) {
+ return rv;
+ }
+
+ if ((rv = ssl_init_ctx_cipher_suite(s, p, ptemp, mctx)) != APR_SUCCESS) {
+ return rv;
+ }
+
+ if ((rv = ssl_init_ctx_crl(s, p, ptemp, mctx)) != APR_SUCCESS) {
+ return rv;
+ }
+
+ if (mctx->pks) {
+ /* XXX: proxy support? */
+ if ((rv = ssl_init_ctx_cert_chain(s, p, ptemp, mctx)) != APR_SUCCESS) {
+ return rv;
+ }
+#ifdef HAVE_TLSEXT
+ if ((rv = ssl_init_ctx_tls_extensions(s, p, ptemp, mctx)) !=
+ APR_SUCCESS) {
+ return rv;
+ }
+#endif
+ }
+
+ return APR_SUCCESS;
+}
+
+static void ssl_check_public_cert(server_rec *s,
+ apr_pool_t *ptemp,
+ X509 *cert,
+ const char *key_id)
+{
+ int is_ca, pathlen;
+
+ if (!cert) {
+ return;
+ }
+
+ /*
+ * Some information about the certificate(s)
+ */
+
+ if (modssl_X509_getBC(cert, &is_ca, &pathlen)) {
+ if (is_ca) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(01906)
+ "%s server certificate is a CA certificate "
+ "(BasicConstraints: CA == TRUE !?)", key_id);
+ }
+
+ if (pathlen > 0) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(01907)
+ "%s server certificate is not a leaf certificate "
+ "(BasicConstraints: pathlen == %d > 0 !?)",
+ key_id, pathlen);
+ }
+ }
+
+ if (modssl_X509_match_name(ptemp, cert, (const char *)s->server_hostname,
+ TRUE, s) == FALSE) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(01909)
+ "%s server certificate does NOT include an ID "
+ "which matches the server name", key_id);
+ }
+}
+
+/* prevent OpenSSL from showing its "Enter PEM pass phrase:" prompt */
+static int ssl_no_passwd_prompt_cb(char *buf, int size, int rwflag,
+ void *userdata) {
+ return 0;
+}
+
+static APR_INLINE int modssl_DH_bits(DH *dh)
+{
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+ return DH_bits(dh);
+#else
+ return BN_num_bits(DH_get0_p(dh));
+#endif
+}
+
+/* SSL_CTX_use_PrivateKey_file() can fail either because the private
+ * key was encrypted, or due to a mismatch between an already-loaded
+ * cert and the key - a common misconfiguration - from calling
+ * X509_check_private_key(). This macro is passed the last error code
+ * off the OpenSSL stack and evaluates to true only for the first
+ * case. With OpenSSL < 3 the second case is identifiable by the
+ * function code, but function codes are not used from 3.0. */
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+#define CHECK_PRIVKEY_ERROR(ec) (ERR_GET_FUNC(ec) != X509_F_X509_CHECK_PRIVATE_KEY)
+#else
+#define CHECK_PRIVKEY_ERROR(ec) (ERR_GET_LIB(ec) != ERR_LIB_X509 \
+ || (ERR_GET_REASON(ec) != X509_R_KEY_TYPE_MISMATCH \
+ && ERR_GET_REASON(ec) != X509_R_KEY_VALUES_MISMATCH \
+ && ERR_GET_REASON(ec) != X509_R_UNKNOWN_KEY_TYPE))
+#endif
+
+static apr_status_t ssl_init_server_certs(server_rec *s,
+ apr_pool_t *p,
+ apr_pool_t *ptemp,
+ modssl_ctx_t *mctx,
+ apr_array_header_t *pphrases)
+{
+ SSLModConfigRec *mc = myModConfig(s);
+ const char *vhost_id = mctx->sc->vhost_id, *key_id, *certfile, *keyfile;
+ int i;
+ X509 *cert;
+ DH *dh;
+#ifdef HAVE_ECC
+ EC_GROUP *ecparams = NULL;
+ int nid;
+ EC_KEY *eckey = NULL;
+#endif
+#ifndef HAVE_SSL_CONF_CMD
+ SSL *ssl;
+#endif
+
+ /* no OpenSSL default prompts for any of the SSL_CTX_use_* calls, please */
+ SSL_CTX_set_default_passwd_cb(mctx->ssl_ctx, ssl_no_passwd_prompt_cb);
+
+ /* Iterate over the SSLCertificateFile array */
+ for (i = 0; (i < mctx->pks->cert_files->nelts) &&
+ (certfile = APR_ARRAY_IDX(mctx->pks->cert_files, i,
+ const char *));
+ i++) {
+ EVP_PKEY *pkey;
+ const char *engine_certfile = NULL;
+
+ key_id = apr_psprintf(ptemp, "%s:%d", vhost_id, i);
+
+ ERR_clear_error();
+
+ /* first the certificate (public key) */
+ if (modssl_is_engine_id(certfile)) {
+ engine_certfile = certfile;
+ }
+ else if (mctx->cert_chain) {
+ if ((SSL_CTX_use_certificate_file(mctx->ssl_ctx, certfile,
+ SSL_FILETYPE_PEM) < 1)) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02561)
+ "Failed to configure certificate %s, check %s",
+ key_id, certfile);
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+ return APR_EGENERAL;
+ }
+ } else {
+ if ((SSL_CTX_use_certificate_chain_file(mctx->ssl_ctx,
+ certfile) < 1)) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02562)
+ "Failed to configure certificate %s (with chain),"
+ " check %s", key_id, certfile);
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+ return APR_EGENERAL;
+ }
+ }
+
+ /* and second, the private key */
+ if (i < mctx->pks->key_files->nelts) {
+ keyfile = APR_ARRAY_IDX(mctx->pks->key_files, i, const char *);
+ } else {
+ keyfile = certfile;
+ }
+
+ ERR_clear_error();
+
+ if (modssl_is_engine_id(keyfile)) {
+ apr_status_t rv;
+
+ cert = NULL;
+
+ if ((rv = modssl_load_engine_keypair(s, ptemp, vhost_id,
+ engine_certfile, keyfile,
+ &cert, &pkey))) {
+ return rv;
+ }
+
+ if (cert) {
+ if (SSL_CTX_use_certificate(mctx->ssl_ctx, cert) < 1) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10137)
+ "Failed to configure engine certificate %s, check %s",
+ key_id, certfile);
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+ return APR_EGENERAL;
+ }
+
+ /* SSL_CTX now owns the cert. */
+ X509_free(cert);
+ }
+
+ if (SSL_CTX_use_PrivateKey(mctx->ssl_ctx, pkey) < 1) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10130)
+ "Failed to configure private key %s from engine",
+ keyfile);
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+ return APR_EGENERAL;
+ }
+
+ /* SSL_CTX now owns the key */
+ EVP_PKEY_free(pkey);
+ }
+ else if ((SSL_CTX_use_PrivateKey_file(mctx->ssl_ctx, keyfile,
+ SSL_FILETYPE_PEM) < 1)
+ && CHECK_PRIVKEY_ERROR(ERR_peek_last_error())) {
+ ssl_asn1_t *asn1;
+ const unsigned char *ptr;
+
+ ERR_clear_error();
+
+ /* perhaps it's an encrypted private key, so try again */
+ ssl_load_encrypted_pkey(s, ptemp, i, keyfile, &pphrases);
+
+ if (!(asn1 = ssl_asn1_table_get(mc->tPrivateKey, key_id)) ||
+ !(ptr = asn1->cpData) ||
+ !(pkey = d2i_AutoPrivateKey(NULL, &ptr, asn1->nData)) ||
+ (SSL_CTX_use_PrivateKey(mctx->ssl_ctx, pkey) < 1)) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02564)
+ "Failed to configure encrypted (?) private key %s,"
+ " check %s", key_id, keyfile);
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+ return APR_EGENERAL;
+ }
+ }
+
+ if (SSL_CTX_check_private_key(mctx->ssl_ctx) < 1) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02565)
+ "Certificate and private key %s from %s and %s "
+ "do not match", key_id, certfile, keyfile);
+ return APR_EGENERAL;
+ }
+
+#ifdef HAVE_SSL_CONF_CMD
+ /*
+ * workaround for those OpenSSL versions where SSL_CTX_get0_certificate
+ * is not yet available: create an SSL struct which we dispose of
+ * as soon as we no longer need access to the cert. (Strictly speaking,
+ * SSL_CTX_get0_certificate does not depend on the SSL_CONF stuff,
+ * but there's no reliable way to check for its existence, so we
+ * assume that if SSL_CONF is available, it's OpenSSL 1.0.2 or later,
+ * and SSL_CTX_get0_certificate is implemented.)
+ */
+ if (!(cert = SSL_CTX_get0_certificate(mctx->ssl_ctx))) {
+#else
+ ssl = SSL_new(mctx->ssl_ctx);
+ if (ssl) {
+ /* Workaround bug in SSL_get_certificate in OpenSSL 0.9.8y */
+ SSL_set_connect_state(ssl);
+ cert = SSL_get_certificate(ssl);
+ }
+ if (!ssl || !cert) {
+#endif
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(02566)
+ "Unable to retrieve certificate %s", key_id);
+#ifndef HAVE_SSL_CONF_CMD
+ if (ssl)
+ SSL_free(ssl);
+#endif
+ return APR_EGENERAL;
+ }
+
+ /* warn about potential cert issues */
+ ssl_check_public_cert(s, ptemp, cert, key_id);
+
+#if defined(HAVE_OCSP_STAPLING) && !defined(SSL_CTRL_SET_CURRENT_CERT)
+ /*
+ * OpenSSL up to 1.0.1: configure stapling as we go. In 1.0.2
+ * and later, there's SSL_CTX_set_current_cert, which allows
+ * iterating over all certs in an SSL_CTX (including those possibly
+ * loaded via SSLOpenSSLConfCmd Certificate), so for 1.0.2 and
+ * later, we defer to the code in ssl_init_server_ctx.
+ */
+ if (!ssl_stapling_init_cert(s, p, ptemp, mctx, cert)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(02567)
+ "Unable to configure certificate %s for stapling",
+ key_id);
+ }
+#endif
+
+#ifndef HAVE_SSL_CONF_CMD
+ SSL_free(ssl);
+#endif
+
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(02568)
+ "Certificate and private key %s configured from %s and %s",
+ key_id, certfile, keyfile);
+ }
+
+ /*
+ * Try to read DH parameters from the (first) SSLCertificateFile
+ */
+ certfile = APR_ARRAY_IDX(mctx->pks->cert_files, 0, const char *);
+ if (certfile && !modssl_is_engine_id(certfile)
+ && (dh = ssl_dh_GetParamFromFile(certfile))) {
+ /* ### This should be replaced with SSL_CTX_set0_tmp_dh_pkey()
+ * for OpenSSL 3.0+. */
+ SSL_CTX_set_tmp_dh(mctx->ssl_ctx, dh);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02540)
+ "Custom DH parameters (%d bits) for %s loaded from %s",
+ modssl_DH_bits(dh), vhost_id, certfile);
+ DH_free(dh);
+ }
+#if !MODSSL_USE_OPENSSL_PRE_1_1_API
+ else {
+ /* If no parameter is manually configured, enable auto
+ * selection. */
+ SSL_CTX_set_dh_auto(mctx->ssl_ctx, 1);
+ }
+#endif
+
+#ifdef HAVE_ECC
+ /*
+ * Similarly, try to read the ECDH curve name from SSLCertificateFile...
+ */
+ if (certfile && !modssl_is_engine_id(certfile)
+ && (ecparams = ssl_ec_GetParamFromFile(certfile))
+ && (nid = EC_GROUP_get_curve_name(ecparams))
+ && (eckey = EC_KEY_new_by_curve_name(nid))) {
+ SSL_CTX_set_tmp_ecdh(mctx->ssl_ctx, eckey);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02541)
+ "ECDH curve %s for %s specified in %s",
+ OBJ_nid2sn(nid), vhost_id, certfile);
+ }
+ /*
+ * ...otherwise, enable auto curve selection (OpenSSL 1.0.2)
+ * or configure NIST P-256 (required to enable ECDHE for earlier versions)
+ * ECDH is always enabled in 1.1.0 unless excluded from SSLCipherList
+ */
+#if MODSSL_USE_OPENSSL_PRE_1_1_API
+ else {
+#if defined(SSL_CTX_set_ecdh_auto)
+ SSL_CTX_set_ecdh_auto(mctx->ssl_ctx, 1);
+#else
+ eckey = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
+ SSL_CTX_set_tmp_ecdh(mctx->ssl_ctx, eckey);
+#endif
+ }
+#endif
+ /* OpenSSL assures us that _free() is NULL-safe */
+ EC_KEY_free(eckey);
+ EC_GROUP_free(ecparams);
+#endif
+
+ return APR_SUCCESS;
+}
+
+#ifdef HAVE_TLS_SESSION_TICKETS
+static apr_status_t ssl_init_ticket_key(server_rec *s,
+ apr_pool_t *p,
+ apr_pool_t *ptemp,
+ modssl_ctx_t *mctx)
+{
+ apr_status_t rv;
+ apr_file_t *fp;
+ apr_size_t len;
+ char buf[TLSEXT_TICKET_KEY_LEN];
+ char *path;
+ modssl_ticket_key_t *ticket_key = mctx->ticket_key;
+ int res;
+
+ if (!ticket_key->file_path) {
+ return APR_SUCCESS;
+ }
+
+ path = ap_server_root_relative(p, ticket_key->file_path);
+
+ rv = apr_file_open(&fp, path, APR_READ|APR_BINARY,
+ APR_OS_DEFAULT, ptemp);
+
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02286)
+ "Failed to open ticket key file %s: (%d) %pm",
+ path, rv, &rv);
+ return ssl_die(s);
+ }
+
+ rv = apr_file_read_full(fp, &buf[0], TLSEXT_TICKET_KEY_LEN, &len);
+
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02287)
+ "Failed to read %d bytes from %s: (%d) %pm",
+ TLSEXT_TICKET_KEY_LEN, path, rv, &rv);
+ return ssl_die(s);
+ }
+
+ memcpy(ticket_key->key_name, buf, 16);
+ memcpy(ticket_key->aes_key, buf + 32, 16);
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+ memcpy(ticket_key->hmac_secret, buf + 16, 16);
+ res = SSL_CTX_set_tlsext_ticket_key_cb(mctx->ssl_ctx,
+ ssl_callback_SessionTicket);
+#else
+ ticket_key->mac_params[0] =
+ OSSL_PARAM_construct_octet_string(OSSL_MAC_PARAM_KEY, buf + 16, 16);
+ ticket_key->mac_params[1] =
+ OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST, "sha256", 0);
+ ticket_key->mac_params[2] =
+ OSSL_PARAM_construct_end();
+ res = SSL_CTX_set_tlsext_ticket_key_evp_cb(mctx->ssl_ctx,
+ ssl_callback_SessionTicket);
+#endif
+ if (!res) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(01913)
+ "Unable to initialize TLS session ticket key callback "
+ "(incompatible OpenSSL version?)");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+ return ssl_die(s);
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(02288)
+ "TLS session ticket key for %s successfully loaded from %s",
+ (mySrvConfig(s))->vhost_id, path);
+
+ return APR_SUCCESS;
+}
+#endif
+
+static BOOL load_x509_info(apr_pool_t *ptemp,
+ STACK_OF(X509_INFO) *sk,
+ const char *filename)
+{
+ BIO *in;
+
+ if (!(in = BIO_new(BIO_s_file()))) {
+ return FALSE;
+ }
+
+ if (BIO_read_filename(in, filename) <= 0) {
+ BIO_free(in);
+ return FALSE;
+ }
+
+ ERR_clear_error();
+
+ PEM_X509_INFO_read_bio(in, sk, NULL, NULL);
+
+ BIO_free(in);
+
+ return TRUE;
+}
+
+static apr_status_t ssl_init_proxy_certs(server_rec *s,
+ apr_pool_t *p,
+ apr_pool_t *ptemp,
+ modssl_ctx_t *mctx)
+{
+ int n, ncerts = 0;
+ STACK_OF(X509_INFO) *sk;
+ modssl_pk_proxy_t *pkp = mctx->pkp;
+ STACK_OF(X509) *chain;
+ X509_STORE_CTX *sctx;
+ X509_STORE *store = SSL_CTX_get_cert_store(mctx->ssl_ctx);
+
+#if OPENSSL_VERSION_NUMBER >= 0x1010100fL
+ /* For OpenSSL >=1.1.1, turn on client cert support which is
+ * otherwise turned off by default (by design).
+ * https://github.com/openssl/openssl/issues/6933 */
+ SSL_CTX_set_post_handshake_auth(mctx->ssl_ctx, 1);
+#endif
+
+ SSL_CTX_set_client_cert_cb(mctx->ssl_ctx,
+ ssl_callback_proxy_cert);
+
+ if (!(pkp->cert_file || pkp->cert_path)) {
+ return APR_SUCCESS;
+ }
+
+ sk = sk_X509_INFO_new_null();
+
+ if (pkp->cert_file) {
+ load_x509_info(ptemp, sk, pkp->cert_file);
+ }
+
+ if (pkp->cert_path) {
+ ssl_init_ca_cert_path(s, ptemp, pkp->cert_path, NULL, sk);
+ }
+
+ if ((ncerts = sk_X509_INFO_num(sk)) <= 0) {
+ sk_X509_INFO_free(sk);
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(02206)
+ "no client certs found for SSL proxy");
+ return APR_SUCCESS;
+ }
+
+ /* Check that all client certs have got certificates and private
+ * keys. */
+ for (n = 0; n < ncerts; n++) {
+ X509_INFO *inf = sk_X509_INFO_value(sk, n);
+
+ if (!inf->x509 || !inf->x_pkey || !inf->x_pkey->dec_pkey ||
+ inf->enc_data) {
+ sk_X509_INFO_free(sk);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, s, APLOGNO(02252)
+ "incomplete client cert configured for SSL proxy "
+ "(missing or encrypted private key?)");
+ return ssl_die(s);
+ }
+
+ if (X509_check_private_key(inf->x509, inf->x_pkey->dec_pkey) != 1) {
+ ssl_log_xerror(SSLLOG_MARK, APLOG_STARTUP, 0, ptemp, s, inf->x509,
+ APLOGNO(02326) "proxy client certificate and "
+ "private key do not match");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, s);
+ return ssl_die(s);
+ }
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02207)
+ "loaded %d client certs for SSL proxy",
+ ncerts);
+ pkp->certs = sk;
+
+
+ if (!pkp->ca_cert_file || !store) {
+ return APR_SUCCESS;
+ }
+
+ /* If SSLProxyMachineCertificateChainFile is configured, load all
+ * the CA certs and have OpenSSL attempt to construct a full chain
+ * from each configured end-entity cert up to a root. This will
+ * allow selection of the correct cert given a list of root CA
+ * names in the certificate request from the server. */
+ pkp->ca_certs = (STACK_OF(X509) **) apr_pcalloc(p, ncerts * sizeof(sk));
+ sctx = X509_STORE_CTX_new();
+
+ if (!sctx) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02208)
+ "SSL proxy client cert initialization failed");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+ return ssl_die(s);
+ }
+
+ modssl_X509_STORE_load_locations(store, pkp->ca_cert_file, NULL);
+
+ for (n = 0; n < ncerts; n++) {
+ int i;
+
+ X509_INFO *inf = sk_X509_INFO_value(pkp->certs, n);
+ X509_STORE_CTX_init(sctx, store, inf->x509, NULL);
+
+ /* Attempt to verify the client cert */
+ if (X509_verify_cert(sctx) != 1) {
+ int err = X509_STORE_CTX_get_error(sctx);
+ ssl_log_xerror(SSLLOG_MARK, APLOG_WARNING, 0, ptemp, s, inf->x509,
+ APLOGNO(02270) "SSL proxy client cert chain "
+ "verification failed: %s :",
+ X509_verify_cert_error_string(err));
+ }
+
+ /* Clear X509_verify_cert errors */
+ ERR_clear_error();
+
+ /* Obtain a copy of the verified chain */
+ chain = X509_STORE_CTX_get1_chain(sctx);
+
+ if (chain != NULL) {
+ /* Discard end entity cert from the chain */
+ X509_free(sk_X509_shift(chain));
+
+ if ((i = sk_X509_num(chain)) > 0) {
+ /* Store the chain for later use */
+ pkp->ca_certs[n] = chain;
+ }
+ else {
+ /* Discard empty chain */
+ sk_X509_pop_free(chain, X509_free);
+ pkp->ca_certs[n] = NULL;
+ }
+
+ ssl_log_xerror(SSLLOG_MARK, APLOG_DEBUG, 0, ptemp, s, inf->x509,
+ APLOGNO(02271)
+ "loaded %i intermediate CA%s for cert %i: ",
+ i, i == 1 ? "" : "s", n);
+ if (i > 0) {
+ int j;
+ for (j = 0; j < i; j++) {
+ ssl_log_xerror(SSLLOG_MARK, APLOG_DEBUG, 0, ptemp, s,
+ sk_X509_value(chain, j), APLOGNO(03039)
+ "%i:", j);
+ }
+ }
+ }
+
+ /* get ready for next X509_STORE_CTX_init */
+ X509_STORE_CTX_cleanup(sctx);
+ }
+
+ X509_STORE_CTX_free(sctx);
+
+ return APR_SUCCESS;
+}
+
+#define MODSSL_CFG_ITEM_FREE(func, item) \
+ if (item) { \
+ func(item); \
+ item = NULL; \
+ }
+
+static void ssl_init_ctx_cleanup(modssl_ctx_t *mctx)
+{
+ MODSSL_CFG_ITEM_FREE(SSL_CTX_free, mctx->ssl_ctx);
+
+#ifdef HAVE_SRP
+ if (mctx->srp_vbase != NULL) {
+ SRP_VBASE_free(mctx->srp_vbase);
+ mctx->srp_vbase = NULL;
+ }
+#endif
+}
+
+static apr_status_t ssl_cleanup_proxy_ctx(void *data)
+{
+ modssl_ctx_t *mctx = data;
+
+ ssl_init_ctx_cleanup(mctx);
+
+ if (mctx->pkp->certs) {
+ int i = 0;
+ int ncerts = sk_X509_INFO_num(mctx->pkp->certs);
+
+ if (mctx->pkp->ca_certs) {
+ for (i = 0; i < ncerts; i++) {
+ if (mctx->pkp->ca_certs[i] != NULL) {
+ sk_X509_pop_free(mctx->pkp->ca_certs[i], X509_free);
+ }
+ }
+ }
+
+ sk_X509_INFO_pop_free(mctx->pkp->certs, X509_INFO_free);
+ mctx->pkp->certs = NULL;
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t ssl_init_proxy_ctx(server_rec *s,
+ apr_pool_t *p,
+ apr_pool_t *ptemp,
+ modssl_ctx_t *proxy)
+{
+ apr_status_t rv;
+
+ if (proxy->ssl_ctx) {
+ /* Merged/initialized already */
+ return APR_SUCCESS;
+ }
+
+ apr_pool_cleanup_register(p, proxy,
+ ssl_cleanup_proxy_ctx,
+ apr_pool_cleanup_null);
+
+ if ((rv = ssl_init_ctx(s, p, ptemp, proxy)) != APR_SUCCESS) {
+ return rv;
+ }
+
+ if ((rv = ssl_init_proxy_certs(s, p, ptemp, proxy)) != APR_SUCCESS) {
+ return rv;
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t ssl_init_server_ctx(server_rec *s,
+ apr_pool_t *p,
+ apr_pool_t *ptemp,
+ SSLSrvConfigRec *sc,
+ apr_array_header_t *pphrases)
+{
+ apr_status_t rv;
+ modssl_pk_server_t *pks;
+#ifdef HAVE_SSL_CONF_CMD
+ ssl_ctx_param_t *param = (ssl_ctx_param_t *)sc->server->ssl_ctx_param->elts;
+ SSL_CONF_CTX *cctx = sc->server->ssl_ctx_config;
+ int i;
+#endif
+ int n;
+
+ /*
+ * Check for problematic re-initializations
+ */
+ if (sc->server->ssl_ctx) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02569)
+ "Illegal attempt to re-initialise SSL for server "
+ "(SSLEngine On should go in the VirtualHost, not in global scope.)");
+ return APR_EGENERAL;
+ }
+
+ /* Allow others to provide certificate files */
+ pks = sc->server->pks;
+ n = pks->cert_files->nelts;
+ ap_ssl_add_cert_files(s, p, pks->cert_files, pks->key_files);
+ ssl_run_add_cert_files(s, p, pks->cert_files, pks->key_files);
+
+ if (apr_is_empty_array(pks->cert_files)) {
+ /* does someone propose a certiciate to fall back on here? */
+ ap_ssl_add_fallback_cert_files(s, p, pks->cert_files, pks->key_files);
+ ssl_run_add_fallback_cert_files(s, p, pks->cert_files, pks->key_files);
+ if (n < pks->cert_files->nelts) {
+ pks->service_unavailable = 1;
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(10085)
+ "Init: %s will respond with '503 Service Unavailable' for now. There "
+ "are no SSL certificates configured and no other module contributed any.",
+ ssl_util_vhostid(p, s));
+ }
+ }
+
+ if (n < pks->cert_files->nelts) {
+ /* additionally installed certs overrides any old chain configuration */
+ sc->server->cert_chain = NULL;
+ }
+
+ if ((rv = ssl_init_ctx(s, p, ptemp, sc->server)) != APR_SUCCESS) {
+ return rv;
+ }
+
+ if ((rv = ssl_init_server_certs(s, p, ptemp, sc->server, pphrases))
+ != APR_SUCCESS) {
+ return rv;
+ }
+
+#ifdef HAVE_SSL_CONF_CMD
+ SSL_CONF_CTX_set_ssl_ctx(cctx, sc->server->ssl_ctx);
+ for (i = 0; i < sc->server->ssl_ctx_param->nelts; i++, param++) {
+ ERR_clear_error();
+ if (SSL_CONF_cmd(cctx, param->name, param->value) <= 0) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02407)
+ "\"SSLOpenSSLConfCmd %s %s\" failed for %s",
+ param->name, param->value, sc->vhost_id);
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+ return ssl_die(s);
+ } else {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02556)
+ "\"SSLOpenSSLConfCmd %s %s\" applied to %s",
+ param->name, param->value, sc->vhost_id);
+ }
+ }
+
+ if (SSL_CONF_CTX_finish(cctx) == 0) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02547)
+ "SSL_CONF_CTX_finish() failed");
+ SSL_CONF_CTX_free(cctx);
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+ return ssl_die(s);
+ }
+#endif
+
+ if (SSL_CTX_check_private_key(sc->server->ssl_ctx) != 1) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02572)
+ "Failed to configure at least one certificate and key "
+ "for %s", sc->vhost_id);
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+ return ssl_die(s);
+ }
+
+#if defined(HAVE_OCSP_STAPLING) && defined(SSL_CTRL_SET_CURRENT_CERT)
+ /*
+ * OpenSSL 1.0.2 and later allows iterating over all SSL_CTX certs
+ * by means of SSL_CTX_set_current_cert. Enabling stapling at this
+ * (late) point makes sure that we catch both certificates loaded
+ * via SSLCertificateFile and SSLOpenSSLConfCmd Certificate.
+ */
+ do {
+ X509 *cert;
+ int i = 0;
+ int ret = SSL_CTX_set_current_cert(sc->server->ssl_ctx,
+ SSL_CERT_SET_FIRST);
+ while (ret) {
+ cert = SSL_CTX_get0_certificate(sc->server->ssl_ctx);
+ if (!cert || !ssl_stapling_init_cert(s, p, ptemp, sc->server,
+ cert)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(02604)
+ "Unable to configure certificate %s:%d "
+ "for stapling", sc->vhost_id, i);
+ }
+ ret = SSL_CTX_set_current_cert(sc->server->ssl_ctx,
+ SSL_CERT_SET_NEXT);
+ i++;
+ }
+ } while(0);
+#endif
+
+#ifdef HAVE_TLS_SESSION_TICKETS
+ if ((rv = ssl_init_ticket_key(s, p, ptemp, sc->server)) != APR_SUCCESS) {
+ return rv;
+ }
+#endif
+
+ SSL_CTX_set_timeout(sc->server->ssl_ctx,
+ sc->session_cache_timeout == UNSET ?
+ SSL_SESSION_CACHE_TIMEOUT : sc->session_cache_timeout);
+
+ return APR_SUCCESS;
+}
+
+/*
+ * Configure a particular server
+ */
+apr_status_t ssl_init_ConfigureServer(server_rec *s,
+ apr_pool_t *p,
+ apr_pool_t *ptemp,
+ SSLSrvConfigRec *sc,
+ apr_array_header_t *pphrases)
+{
+ SSLDirConfigRec *sdc = ap_get_module_config(s->lookup_defaults,
+ &ssl_module);
+ apr_status_t rv;
+
+ /* Initialize the server if SSL is enabled or optional.
+ */
+ if ((sc->enabled == SSL_ENABLED_TRUE) || (sc->enabled == SSL_ENABLED_OPTIONAL)) {
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(01914)
+ "Configuring server %s for SSL protocol", sc->vhost_id);
+ if ((rv = ssl_init_server_ctx(s, p, ptemp, sc, pphrases))
+ != APR_SUCCESS) {
+ return rv;
+ }
+
+ /* Initialize OCSP Responder certificate if OCSP enabled */
+ #ifndef OPENSSL_NO_OCSP
+ ssl_init_ocsp_certificates(s, sc->server);
+ #endif
+
+ }
+
+ sdc->proxy->sc = sc;
+ if (sdc->proxy_enabled == TRUE) {
+ rv = ssl_init_proxy_ctx(s, p, ptemp, sdc->proxy);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+ else {
+ sdc->proxy_enabled = FALSE;
+ }
+ sdc->proxy_post_config = 1;
+
+ return APR_SUCCESS;
+}
+
+apr_status_t ssl_init_CheckServers(server_rec *base_server, apr_pool_t *p)
+{
+ server_rec *s;
+ SSLSrvConfigRec *sc;
+#ifndef HAVE_TLSEXT
+ server_rec *ps;
+ apr_hash_t *table;
+ const char *key;
+ apr_ssize_t klen;
+
+ BOOL conflict = FALSE;
+#endif
+
+ /*
+ * Give out warnings when a server has HTTPS configured
+ * for the HTTP port or vice versa
+ */
+ for (s = base_server; s; s = s->next) {
+ sc = mySrvConfig(s);
+
+ if ((sc->enabled == SSL_ENABLED_TRUE) && (s->port == DEFAULT_HTTP_PORT)) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0,
+ base_server, APLOGNO(01915)
+ "Init: (%s) You configured HTTPS(%d) "
+ "on the standard HTTP(%d) port!",
+ ssl_util_vhostid(p, s),
+ DEFAULT_HTTPS_PORT, DEFAULT_HTTP_PORT);
+ }
+
+ if ((sc->enabled == SSL_ENABLED_FALSE) && (s->port == DEFAULT_HTTPS_PORT)) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0,
+ base_server, APLOGNO(01916)
+ "Init: (%s) You configured HTTP(%d) "
+ "on the standard HTTPS(%d) port!",
+ ssl_util_vhostid(p, s),
+ DEFAULT_HTTP_PORT, DEFAULT_HTTPS_PORT);
+ }
+ }
+
+#ifndef HAVE_TLSEXT
+ /*
+ * Give out warnings when more than one SSL-aware virtual server uses the
+ * same IP:port and an OpenSSL version without support for TLS extensions
+ * (SNI in particular) is used.
+ */
+ table = apr_hash_make(p);
+
+ for (s = base_server; s; s = s->next) {
+ char *addr;
+
+ sc = mySrvConfig(s);
+
+ if (!((sc->enabled == SSL_ENABLED_TRUE) && s->addrs)) {
+ continue;
+ }
+
+ apr_sockaddr_ip_get(&addr, s->addrs->host_addr);
+ key = apr_psprintf(p, "%s:%u", addr, s->addrs->host_port);
+ klen = strlen(key);
+
+ if ((ps = (server_rec *)apr_hash_get(table, key, klen))) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, base_server, APLOGNO(02662)
+ "Init: SSL server IP/port conflict: "
+ "%s (%s:%d) vs. %s (%s:%d)",
+ ssl_util_vhostid(p, s),
+ (s->defn_name ? s->defn_name : "unknown"),
+ s->defn_line_number,
+ ssl_util_vhostid(p, ps),
+ (ps->defn_name ? ps->defn_name : "unknown"),
+ ps->defn_line_number);
+ conflict = TRUE;
+ continue;
+ }
+
+ apr_hash_set(table, key, klen, s);
+ }
+
+ if (conflict) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, base_server, APLOGNO(01917)
+ "Init: Name-based SSL virtual hosts require "
+ "an OpenSSL version with support for TLS extensions "
+ "(RFC 6066 - Server Name Indication / SNI), "
+ "but the currently used library version (%s) is "
+ "lacking this feature", MODSSL_LIBRARY_DYNTEXT);
+ }
+#endif
+
+ return APR_SUCCESS;
+}
+
+int ssl_proxy_section_post_config(apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s,
+ ap_conf_vector_t *section_config)
+{
+ SSLDirConfigRec *sdc = ap_get_module_config(s->lookup_defaults,
+ &ssl_module);
+ SSLDirConfigRec *pdc = ap_get_module_config(section_config,
+ &ssl_module);
+ if (pdc) {
+ pdc->proxy->sc = mySrvConfig(s);
+ ssl_config_proxy_merge(p, sdc, pdc);
+ if (pdc->proxy_enabled) {
+ apr_status_t rv;
+
+ rv = ssl_init_proxy_ctx(s, p, ptemp, pdc->proxy);
+ if (rv != APR_SUCCESS) {
+ return !OK;
+ }
+
+ rv = ssl_run_init_server(s, p, 1, pdc->proxy->ssl_ctx);
+ if (rv != APR_SUCCESS) {
+ return !OK;
+ }
+ }
+ pdc->proxy_post_config = 1;
+ }
+ return OK;
+}
+
+static int ssl_init_FindCAList_X509NameCmp(const X509_NAME * const *a,
+ const X509_NAME * const *b)
+{
+ return(X509_NAME_cmp(*a, *b));
+}
+
+static void ssl_init_PushCAList(STACK_OF(X509_NAME) *ca_list,
+ server_rec *s, apr_pool_t *ptemp,
+ const char *file)
+{
+ int n;
+ STACK_OF(X509_NAME) *sk;
+
+ sk = (STACK_OF(X509_NAME) *)
+ SSL_load_client_CA_file(file);
+
+ if (!sk) {
+ return;
+ }
+
+ for (n = 0; n < sk_X509_NAME_num(sk); n++) {
+ X509_NAME *name = sk_X509_NAME_value(sk, n);
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02209)
+ "CA certificate: %s",
+ modssl_X509_NAME_to_string(ptemp, name, 0));
+
+ /*
+ * note that SSL_load_client_CA_file() checks for duplicates,
+ * but since we call it multiple times when reading a directory
+ * we must also check for duplicates ourselves.
+ */
+
+ if (sk_X509_NAME_find(ca_list, name) < 0) {
+ /* this will be freed when ca_list is */
+ sk_X509_NAME_push(ca_list, name);
+ }
+ else {
+ /* need to free this ourselves, else it will leak */
+ X509_NAME_free(name);
+ }
+ }
+
+ sk_X509_NAME_free(sk);
+}
+
+static apr_status_t ssl_init_ca_cert_path(server_rec *s,
+ apr_pool_t *ptemp,
+ const char *path,
+ STACK_OF(X509_NAME) *ca_list,
+ STACK_OF(X509_INFO) *xi_list)
+{
+ apr_dir_t *dir;
+ apr_finfo_t direntry;
+ apr_int32_t finfo_flags = APR_FINFO_TYPE|APR_FINFO_NAME;
+
+ if (!path || (!ca_list && !xi_list) ||
+ (apr_dir_open(&dir, path, ptemp) != APR_SUCCESS)) {
+ return APR_EGENERAL;
+ }
+
+ while ((apr_dir_read(&direntry, finfo_flags, dir)) == APR_SUCCESS) {
+ const char *file;
+ if (direntry.filetype == APR_DIR) {
+ continue; /* don't try to load directories */
+ }
+ file = apr_pstrcat(ptemp, path, "/", direntry.name, NULL);
+ if (ca_list) {
+ ssl_init_PushCAList(ca_list, s, ptemp, file);
+ }
+ if (xi_list) {
+ load_x509_info(ptemp, xi_list, file);
+ }
+ }
+
+ apr_dir_close(dir);
+
+ return APR_SUCCESS;
+}
+
+STACK_OF(X509_NAME) *ssl_init_FindCAList(server_rec *s,
+ apr_pool_t *ptemp,
+ const char *ca_file,
+ const char *ca_path)
+{
+ STACK_OF(X509_NAME) *ca_list;
+
+ /*
+ * Start with a empty stack/list where new
+ * entries get added in sorted order.
+ */
+ ca_list = sk_X509_NAME_new(ssl_init_FindCAList_X509NameCmp);
+
+ /*
+ * Process CA certificate bundle file
+ */
+ if (ca_file) {
+ ssl_init_PushCAList(ca_list, s, ptemp, ca_file);
+ /*
+ * If ca_list is still empty after trying to load ca_file
+ * then the file failed to load, and users should hear about that.
+ */
+ if (sk_X509_NAME_num(ca_list) == 0) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(02210)
+ "Failed to load SSLCACertificateFile: %s", ca_file);
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, s);
+ }
+ }
+
+ /*
+ * Process CA certificate path files
+ */
+ if (ca_path &&
+ ssl_init_ca_cert_path(s, ptemp,
+ ca_path, ca_list, NULL) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02211)
+ "Failed to open Certificate Path `%s'", ca_path);
+ sk_X509_NAME_pop_free(ca_list, X509_NAME_free);
+ return NULL;
+ }
+
+ /*
+ * Cleanup
+ */
+ (void) sk_X509_NAME_set_cmp_func(ca_list, NULL);
+
+ return ca_list;
+}
+
+void ssl_init_Child(apr_pool_t *p, server_rec *s)
+{
+ SSLModConfigRec *mc = myModConfig(s);
+ mc->pid = getpid(); /* only call getpid() once per-process */
+
+ /* XXX: there should be an ap_srand() function */
+ srand((unsigned int)time(NULL));
+
+ /* open the mutex lockfile */
+ ssl_mutex_reinit(s, p);
+#ifdef HAVE_OCSP_STAPLING
+ ssl_stapling_mutex_reinit(s, p);
+#endif
+}
+
+apr_status_t ssl_init_ModuleKill(void *data)
+{
+ SSLSrvConfigRec *sc;
+ server_rec *base_server = (server_rec *)data;
+ server_rec *s;
+
+ /*
+ * Drop the session cache and mutex
+ */
+ ssl_scache_kill(base_server);
+
+ /*
+ * Free the non-pool allocated structures
+ * in the per-server configurations
+ */
+ for (s = base_server; s; s = s->next) {
+ sc = mySrvConfig(s);
+
+ ssl_init_ctx_cleanup(sc->server);
+
+ /* Not Sure but possibly clear X509 trusted cert file */
+ #ifndef OPENSSL_NO_OCSP
+ sk_X509_pop_free(sc->server->ocsp_certs, X509_free);
+ #endif
+
+ }
+
+#if MODSSL_USE_OPENSSL_PRE_1_1_API
+ free_dh_params();
+#else
+ free_bio_methods();
+#endif
+
+ return APR_SUCCESS;
+}
diff --git a/modules/ssl/ssl_engine_io.c b/modules/ssl/ssl_engine_io.c
new file mode 100644
index 0000000..f14fc9b
--- /dev/null
+++ b/modules/ssl/ssl_engine_io.c
@@ -0,0 +1,2419 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* _ _
+ * _ __ ___ ___ __| | ___ ___| | mod_ssl
+ * | '_ ` _ \ / _ \ / _` | / __/ __| | Apache Interface to OpenSSL
+ * | | | | | | (_) | (_| | \__ \__ \ |
+ * |_| |_| |_|\___/ \__,_|___|___/___/_|
+ * |_____|
+ * ssl_engine_io.c
+ * I/O Functions
+ */
+ /* ``MY HACK: This universe.
+ Just one little problem:
+ core keeps dumping.''
+ -- Unknown */
+#include "ssl_private.h"
+#include "mod_ssl.h"
+#include "mod_ssl_openssl.h"
+#include "apr_date.h"
+
+APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(ssl, SSL, int, proxy_post_handshake,
+ (conn_rec *c,SSL *ssl),
+ (c,ssl),OK,DECLINED);
+
+/* _________________________________________________________________
+**
+** I/O Hooks
+** _________________________________________________________________
+*/
+
+/* This file is designed to be the bridge between OpenSSL and httpd.
+ * However, we really don't expect anyone (let alone ourselves) to
+ * remember what is in this file. So, first, a quick overview.
+ *
+ * In this file, you will find:
+ * - ssl_io_filter_input (Apache input filter)
+ * - ssl_io_filter_output (Apache output filter)
+ *
+ * - bio_filter_in_* (OpenSSL input filter)
+ * - bio_filter_out_* (OpenSSL output filter)
+ *
+ * The input chain is roughly:
+ *
+ * ssl_io_filter_input->ssl_io_input_read->SSL_read->...
+ * ...->bio_filter_in_read->ap_get_brigade/next-httpd-filter
+ *
+ * In mortal terminology, we do the following:
+ * - Receive a request for data to the SSL input filter
+ * - Call a helper function once we know we should perform a read
+ * - Call OpenSSL's SSL_read()
+ * - SSL_read() will then call bio_filter_in_read
+ * - bio_filter_in_read will then try to fetch data from the next httpd filter
+ * - bio_filter_in_read will flatten that data and return it to SSL_read
+ * - SSL_read will then decrypt the data
+ * - ssl_io_input_read will then receive decrypted data as a char* and
+ * ensure that there were no read errors
+ * - The char* is placed in a brigade and returned
+ *
+ * Since connection-level input filters in httpd need to be able to
+ * handle AP_MODE_GETLINE calls (namely identifying LF-terminated strings),
+ * ssl_io_input_getline which will handle this special case.
+ *
+ * Due to AP_MODE_GETLINE and AP_MODE_SPECULATIVE, we may sometimes have
+ * 'leftover' decoded data which must be setaside for the next read. That
+ * is currently handled by the char_buffer_{read|write} functions. So,
+ * ssl_io_input_read may be able to fulfill reads without invoking
+ * SSL_read().
+ *
+ * Note that the filter context of ssl_io_filter_input and bio_filter_in_*
+ * are shared as bio_filter_in_ctx_t.
+ *
+ * Note that the filter is by choice limited to reading at most
+ * AP_IOBUFSIZE (8192 bytes) per call.
+ *
+ */
+
+/* this custom BIO allows us to hook SSL_write directly into
+ * an apr_bucket_brigade and use transient buckets with the SSL
+ * malloc-ed buffer, rather than copying into a mem BIO.
+ * also allows us to pass the brigade as data is being written
+ * rather than buffering up the entire response in the mem BIO.
+ *
+ * when SSL needs to flush (e.g. SSL_accept()), it will call BIO_flush()
+ * which will trigger a call to bio_filter_out_ctrl() -> bio_filter_out_flush().
+ * so we only need to flush the output ourselves if we receive an
+ * EOS or FLUSH bucket. this was not possible with the mem BIO where we
+ * had to flush all over the place not really knowing when it was required
+ * to do so.
+ */
+
+typedef struct {
+ SSL *pssl;
+ BIO *pbioRead;
+ BIO *pbioWrite;
+ ap_filter_t *pInputFilter;
+ ap_filter_t *pOutputFilter;
+ SSLConnRec *config;
+} ssl_filter_ctx_t;
+
+typedef struct {
+ ssl_filter_ctx_t *filter_ctx;
+ conn_rec *c;
+ apr_bucket_brigade *bb; /* Brigade used as a buffer. */
+ apr_status_t rc;
+} bio_filter_out_ctx_t;
+
+static bio_filter_out_ctx_t *bio_filter_out_ctx_new(ssl_filter_ctx_t *filter_ctx,
+ conn_rec *c)
+{
+ bio_filter_out_ctx_t *outctx = apr_palloc(c->pool, sizeof(*outctx));
+
+ outctx->filter_ctx = filter_ctx;
+ outctx->c = c;
+ outctx->bb = apr_brigade_create(c->pool, c->bucket_alloc);
+
+ return outctx;
+}
+
+/* Pass an output brigade down the filter stack; returns 1 on success
+ * or -1 on failure. */
+static int bio_filter_out_pass(bio_filter_out_ctx_t *outctx)
+{
+ AP_DEBUG_ASSERT(!APR_BRIGADE_EMPTY(outctx->bb));
+
+ outctx->rc = ap_pass_brigade(outctx->filter_ctx->pOutputFilter->next,
+ outctx->bb);
+ /* Fail if the connection was reset: */
+ if (outctx->rc == APR_SUCCESS && outctx->c->aborted) {
+ outctx->rc = APR_ECONNRESET;
+ }
+ return (outctx->rc == APR_SUCCESS) ? 1 : -1;
+}
+
+/* Send a FLUSH bucket down the output filter stack; returns 1 on
+ * success, -1 on failure. */
+static int bio_filter_out_flush(BIO *bio)
+{
+ bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)BIO_get_data(bio);
+ apr_bucket *e;
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, outctx->c,
+ "bio_filter_out_write: flush");
+
+ AP_DEBUG_ASSERT(APR_BRIGADE_EMPTY(outctx->bb));
+
+ e = apr_bucket_flush_create(outctx->bb->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(outctx->bb, e);
+
+ return bio_filter_out_pass(outctx);
+}
+
+static int bio_filter_create(BIO *bio)
+{
+ BIO_set_shutdown(bio, 1);
+ BIO_set_init(bio, 1);
+#if MODSSL_USE_OPENSSL_PRE_1_1_API
+ /* No setter method for OpenSSL 1.1.0 available,
+ * but I can't find any functional use of the
+ * "num" field there either.
+ */
+ bio->num = -1;
+#endif
+ BIO_set_data(bio, NULL);
+
+ return 1;
+}
+
+static int bio_filter_destroy(BIO *bio)
+{
+ if (bio == NULL) {
+ return 0;
+ }
+
+ /* nothing to free here.
+ * apache will destroy the bucket brigade for us
+ */
+ return 1;
+}
+
+static int bio_filter_out_read(BIO *bio, char *out, int outl)
+{
+ /* this is never called */
+ bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)BIO_get_data(bio);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, outctx->c,
+ "BUG: %s() should not be called", "bio_filter_out_read");
+ AP_DEBUG_ASSERT(0);
+ return -1;
+}
+
+static int bio_filter_out_write(BIO *bio, const char *in, int inl)
+{
+ bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)BIO_get_data(bio);
+ apr_bucket *e;
+ int need_flush;
+
+ BIO_clear_retry_flags(bio);
+
+ /* Abort early if the client has initiated a renegotiation. */
+ if (outctx->filter_ctx->config->reneg_state == RENEG_ABORT) {
+ outctx->rc = APR_ECONNABORTED;
+ return -1;
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, outctx->c,
+ "bio_filter_out_write: %i bytes", inl);
+
+ /* Use a transient bucket for the output data - any downstream
+ * filter must setaside if necessary. */
+ e = apr_bucket_transient_create(in, inl, outctx->bb->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(outctx->bb, e);
+
+ /* In theory, OpenSSL should flush as necessary, but it is known
+ * not to do so correctly in some cases (< 0.9.8m; see PR 46952),
+ * or on the proxy/client side (after ssl23_client_hello(), e.g.
+ * ssl/proxy.t test suite).
+ *
+ * Historically, this flush call was performed only for an SSLv2
+ * connection or for a proxy connection. Calling _out_flush can
+ * be expensive in cases where requests/responses are pipelined,
+ * so limit the performance impact to handshake time.
+ */
+#if OPENSSL_VERSION_NUMBER < 0x0009080df
+ need_flush = !SSL_is_init_finished(outctx->filter_ctx->pssl);
+#else
+ need_flush = SSL_in_connect_init(outctx->filter_ctx->pssl);
+#endif
+ if (need_flush) {
+ e = apr_bucket_flush_create(outctx->bb->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(outctx->bb, e);
+ }
+
+ if (bio_filter_out_pass(outctx) < 0) {
+ return -1;
+ }
+
+ return inl;
+}
+
+static long bio_filter_out_ctrl(BIO *bio, int cmd, long num, void *ptr)
+{
+ long ret = 1;
+ bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)BIO_get_data(bio);
+
+ switch (cmd) {
+ case BIO_CTRL_RESET:
+ case BIO_CTRL_EOF:
+ case BIO_C_SET_BUF_MEM_EOF_RETURN:
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, outctx->c,
+ "output bio: unhandled control %d", cmd);
+ ret = 0;
+ break;
+ case BIO_CTRL_WPENDING:
+ case BIO_CTRL_PENDING:
+ case BIO_CTRL_INFO:
+ ret = 0;
+ break;
+ case BIO_CTRL_GET_CLOSE:
+ ret = (long)BIO_get_shutdown(bio);
+ break;
+ case BIO_CTRL_SET_CLOSE:
+ BIO_set_shutdown(bio, (int)num);
+ break;
+ case BIO_CTRL_FLUSH:
+ ret = bio_filter_out_flush(bio);
+ break;
+ case BIO_CTRL_DUP:
+ ret = 1;
+ break;
+ /* N/A */
+ case BIO_C_SET_BUF_MEM:
+ case BIO_C_GET_BUF_MEM_PTR:
+ /* we don't care */
+ case BIO_CTRL_PUSH:
+ case BIO_CTRL_POP:
+ default:
+ ret = 0;
+ break;
+ }
+
+ return ret;
+}
+
+static int bio_filter_out_gets(BIO *bio, char *buf, int size)
+{
+ /* this is never called */
+ bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)BIO_get_data(bio);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, outctx->c,
+ "BUG: %s() should not be called", "bio_filter_out_gets");
+ AP_DEBUG_ASSERT(0);
+ return -1;
+}
+
+static int bio_filter_out_puts(BIO *bio, const char *str)
+{
+ /* this is never called */
+ bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)BIO_get_data(bio);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, outctx->c,
+ "BUG: %s() should not be called", "bio_filter_out_puts");
+ AP_DEBUG_ASSERT(0);
+ return -1;
+}
+
+typedef struct {
+ int length;
+ char *value;
+} char_buffer_t;
+
+typedef struct {
+ SSL *ssl;
+ BIO *bio_out;
+ ap_filter_t *f;
+ apr_status_t rc;
+ ap_input_mode_t mode;
+ apr_read_type_e block;
+ apr_bucket_brigade *bb;
+ char_buffer_t cbuf;
+ apr_pool_t *pool;
+ char buffer[AP_IOBUFSIZE];
+ ssl_filter_ctx_t *filter_ctx;
+} bio_filter_in_ctx_t;
+
+/*
+ * this char_buffer api might seem silly, but we don't need to copy
+ * any of this data and we need to remember the length.
+ */
+
+/* Copy up to INL bytes from the char_buffer BUFFER into IN. Note
+ * that due to the strange way this API is designed/used, the
+ * char_buffer object is used to cache a segment of inctx->buffer, and
+ * then this function called to copy (part of) that segment to the
+ * beginning of inctx->buffer. So the segments to copy cannot be
+ * presumed to be non-overlapping, and memmove must be used. */
+static int char_buffer_read(char_buffer_t *buffer, char *in, int inl)
+{
+ if (!buffer->length) {
+ return 0;
+ }
+
+ if (buffer->length > inl) {
+ /* we have enough to fill the caller's buffer */
+ memmove(in, buffer->value, inl);
+ buffer->value += inl;
+ buffer->length -= inl;
+ }
+ else {
+ /* swallow remainder of the buffer */
+ memmove(in, buffer->value, buffer->length);
+ inl = buffer->length;
+ buffer->value = NULL;
+ buffer->length = 0;
+ }
+
+ return inl;
+}
+
+static int char_buffer_write(char_buffer_t *buffer, char *in, int inl)
+{
+ buffer->value = in;
+ buffer->length = inl;
+ return inl;
+}
+
+/* This function will read from a brigade and discard the read buckets as it
+ * proceeds. It will read at most *len bytes.
+ */
+static apr_status_t brigade_consume(apr_bucket_brigade *bb,
+ apr_read_type_e block,
+ char *c, apr_size_t *len)
+{
+ apr_size_t actual = 0;
+ apr_status_t status = APR_SUCCESS;
+
+ while (!APR_BRIGADE_EMPTY(bb)) {
+ apr_bucket *b = APR_BRIGADE_FIRST(bb);
+ const char *str;
+ apr_size_t str_len;
+ apr_size_t consume;
+
+ /* Justin points out this is an http-ism that might
+ * not fit if brigade_consume is added to APR. Perhaps
+ * apr_bucket_read(eos_bucket) should return APR_EOF?
+ * Then this becomes mainline instead of a one-off.
+ */
+ if (APR_BUCKET_IS_EOS(b)) {
+ status = APR_EOF;
+ break;
+ }
+
+ /* The reason I'm not offering brigade_consume yet
+ * across to apr-util is that the following call
+ * illustrates how borked that API really is. For
+ * this sort of case (caller provided buffer) it
+ * would be much more trivial for apr_bucket_consume
+ * to do all the work that follows, based on the
+ * particular characteristics of the bucket we are
+ * consuming here.
+ */
+ status = apr_bucket_read(b, &str, &str_len, block);
+
+ if (status != APR_SUCCESS) {
+ if (APR_STATUS_IS_EOF(status)) {
+ /* This stream bucket was consumed */
+ apr_bucket_delete(b);
+ continue;
+ }
+ break;
+ }
+
+ if (str_len > 0) {
+ /* Do not block once some data has been consumed */
+ block = APR_NONBLOCK_READ;
+
+ /* Assure we don't overflow. */
+ consume = (str_len + actual > *len) ? *len - actual : str_len;
+
+ memcpy(c, str, consume);
+
+ c += consume;
+ actual += consume;
+
+ if (consume >= b->length) {
+ /* This physical bucket was consumed */
+ apr_bucket_delete(b);
+ }
+ else {
+ /* Only part of this physical bucket was consumed */
+ b->start += consume;
+ b->length -= consume;
+ }
+ }
+ else if (b->length == 0) {
+ apr_bucket_delete(b);
+ }
+
+ /* This could probably be actual == *len, but be safe from stray
+ * photons. */
+ if (actual >= *len) {
+ break;
+ }
+ }
+
+ *len = actual;
+ return status;
+}
+
+/*
+ * this is the function called by SSL_read()
+ */
+static int bio_filter_in_read(BIO *bio, char *in, int inlen)
+{
+ apr_size_t inl = inlen;
+ bio_filter_in_ctx_t *inctx = (bio_filter_in_ctx_t *)BIO_get_data(bio);
+ apr_read_type_e block = inctx->block;
+
+ inctx->rc = APR_SUCCESS;
+
+ /* OpenSSL catches this case, so should we. */
+ if (!in)
+ return 0;
+
+ BIO_clear_retry_flags(bio);
+
+ /* Abort early if the client has initiated a renegotiation. */
+ if (inctx->filter_ctx->config->reneg_state == RENEG_ABORT) {
+ inctx->rc = APR_ECONNABORTED;
+ return -1;
+ }
+
+ if (!inctx->bb) {
+ inctx->rc = APR_EOF;
+ return -1;
+ }
+
+ if (APR_BRIGADE_EMPTY(inctx->bb)) {
+
+ inctx->rc = ap_get_brigade(inctx->f->next, inctx->bb,
+ AP_MODE_READBYTES, block,
+ inl);
+
+ /* If the read returns EAGAIN or success with an empty
+ * brigade, return an error after setting the retry flag;
+ * SSL_read() will then return -1, and SSL_get_error() will
+ * indicate SSL_ERROR_WANT_READ. */
+ if (APR_STATUS_IS_EAGAIN(inctx->rc) || APR_STATUS_IS_EINTR(inctx->rc)
+ || (inctx->rc == APR_SUCCESS && APR_BRIGADE_EMPTY(inctx->bb))) {
+ BIO_set_retry_read(bio);
+ return -1;
+ }
+
+ if (block == APR_BLOCK_READ
+ && APR_STATUS_IS_TIMEUP(inctx->rc)
+ && APR_BRIGADE_EMPTY(inctx->bb)) {
+ /* don't give up, just return the timeout */
+ return -1;
+ }
+ if (inctx->rc != APR_SUCCESS) {
+ /* Unexpected errors discard the brigade */
+ apr_brigade_cleanup(inctx->bb);
+ inctx->bb = NULL;
+ return -1;
+ }
+ }
+
+ inctx->rc = brigade_consume(inctx->bb, block, in, &inl);
+
+ if (inctx->rc == APR_SUCCESS) {
+ return (int)inl;
+ }
+
+ if (APR_STATUS_IS_EAGAIN(inctx->rc)
+ || APR_STATUS_IS_EINTR(inctx->rc)) {
+ BIO_set_retry_read(bio);
+ return (int)inl;
+ }
+
+ /* Unexpected errors and APR_EOF clean out the brigade.
+ * Subsequent calls will return APR_EOF.
+ */
+ apr_brigade_cleanup(inctx->bb);
+ inctx->bb = NULL;
+
+ if (APR_STATUS_IS_EOF(inctx->rc) && inl) {
+ /* Provide the results of this read pass,
+ * without resetting the BIO retry_read flag
+ */
+ return (int)inl;
+ }
+
+ return -1;
+}
+
+static int bio_filter_in_write(BIO *bio, const char *in, int inl)
+{
+ bio_filter_in_ctx_t *inctx = (bio_filter_in_ctx_t *)BIO_get_data(bio);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, inctx->f->c,
+ "BUG: %s() should not be called", "bio_filter_in_write");
+ AP_DEBUG_ASSERT(0);
+ return -1;
+}
+
+static int bio_filter_in_puts(BIO *bio, const char *str)
+{
+ bio_filter_in_ctx_t *inctx = (bio_filter_in_ctx_t *)BIO_get_data(bio);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, inctx->f->c,
+ "BUG: %s() should not be called", "bio_filter_in_puts");
+ AP_DEBUG_ASSERT(0);
+ return -1;
+}
+
+static int bio_filter_in_gets(BIO *bio, char *buf, int size)
+{
+ bio_filter_in_ctx_t *inctx = (bio_filter_in_ctx_t *)BIO_get_data(bio);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, inctx->f->c,
+ "BUG: %s() should not be called", "bio_filter_in_gets");
+ AP_DEBUG_ASSERT(0);
+ return -1;
+}
+
+static long bio_filter_in_ctrl(BIO *bio, int cmd, long num, void *ptr)
+{
+ bio_filter_in_ctx_t *inctx = (bio_filter_in_ctx_t *)BIO_get_data(bio);
+ switch (cmd) {
+#ifdef BIO_CTRL_EOF
+ case BIO_CTRL_EOF:
+ return inctx->rc == APR_EOF;
+#endif
+ default:
+ break;
+ }
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, inctx->f->c,
+ "BUG: bio_filter_in_ctrl() should not be called with cmd=%i",
+ cmd);
+ return 0;
+}
+
+#if MODSSL_USE_OPENSSL_PRE_1_1_API
+
+static BIO_METHOD bio_filter_out_method = {
+ BIO_TYPE_MEM,
+ "APR output filter",
+ bio_filter_out_write,
+ bio_filter_out_read, /* read is never called */
+ bio_filter_out_puts, /* puts is never called */
+ bio_filter_out_gets, /* gets is never called */
+ bio_filter_out_ctrl,
+ bio_filter_create,
+ bio_filter_destroy,
+ NULL
+};
+
+static BIO_METHOD bio_filter_in_method = {
+ BIO_TYPE_MEM,
+ "APR input filter",
+ bio_filter_in_write, /* write is never called */
+ bio_filter_in_read,
+ bio_filter_in_puts, /* puts is never called */
+ bio_filter_in_gets, /* gets is never called */
+ bio_filter_in_ctrl, /* ctrl is called for EOF check */
+ bio_filter_create,
+ bio_filter_destroy,
+ NULL
+};
+
+#else
+
+static BIO_METHOD *bio_filter_out_method = NULL;
+static BIO_METHOD *bio_filter_in_method = NULL;
+
+void init_bio_methods(void)
+{
+ bio_filter_out_method = BIO_meth_new(BIO_TYPE_MEM, "APR output filter");
+ BIO_meth_set_write(bio_filter_out_method, &bio_filter_out_write);
+ BIO_meth_set_read(bio_filter_out_method, &bio_filter_out_read); /* read is never called */
+ BIO_meth_set_puts(bio_filter_out_method, &bio_filter_out_puts); /* puts is never called */
+ BIO_meth_set_gets(bio_filter_out_method, &bio_filter_out_gets); /* gets is never called */
+ BIO_meth_set_ctrl(bio_filter_out_method, &bio_filter_out_ctrl);
+ BIO_meth_set_create(bio_filter_out_method, &bio_filter_create);
+ BIO_meth_set_destroy(bio_filter_out_method, &bio_filter_destroy);
+
+ bio_filter_in_method = BIO_meth_new(BIO_TYPE_MEM, "APR input filter");
+ BIO_meth_set_write(bio_filter_in_method, &bio_filter_in_write); /* write is never called */
+ BIO_meth_set_read(bio_filter_in_method, &bio_filter_in_read);
+ BIO_meth_set_puts(bio_filter_in_method, &bio_filter_in_puts); /* puts is never called */
+ BIO_meth_set_gets(bio_filter_in_method, &bio_filter_in_gets); /* gets is never called */
+ BIO_meth_set_ctrl(bio_filter_in_method, &bio_filter_in_ctrl); /* ctrl is never called */
+ BIO_meth_set_create(bio_filter_in_method, &bio_filter_create);
+ BIO_meth_set_destroy(bio_filter_in_method, &bio_filter_destroy);
+}
+
+void free_bio_methods(void)
+{
+ BIO_meth_free(bio_filter_out_method);
+ BIO_meth_free(bio_filter_in_method);
+}
+#endif
+
+static apr_status_t ssl_io_input_read(bio_filter_in_ctx_t *inctx,
+ char *buf,
+ apr_size_t *len)
+{
+ apr_size_t wanted = *len;
+ apr_size_t bytes = 0;
+ int rc;
+
+ *len = 0;
+
+ /* If we have something leftover from last time, try that first. */
+ if ((bytes = char_buffer_read(&inctx->cbuf, buf, wanted))) {
+ *len = bytes;
+ if (inctx->mode == AP_MODE_SPECULATIVE) {
+ /* We want to rollback this read. */
+ if (inctx->cbuf.length > 0) {
+ inctx->cbuf.value -= bytes;
+ inctx->cbuf.length += bytes;
+ } else {
+ char_buffer_write(&inctx->cbuf, buf, (int)bytes);
+ }
+ return APR_SUCCESS;
+ }
+ /* This could probably be *len == wanted, but be safe from stray
+ * photons.
+ */
+ if (*len >= wanted) {
+ return APR_SUCCESS;
+ }
+ if (inctx->mode == AP_MODE_GETLINE) {
+ if (memchr(buf, APR_ASCII_LF, *len)) {
+ return APR_SUCCESS;
+ }
+ }
+ else {
+ /* Down to a nonblock pattern as we have some data already
+ */
+ inctx->block = APR_NONBLOCK_READ;
+ }
+ }
+
+ while (1) {
+
+ if (!inctx->filter_ctx->pssl) {
+ /* Ensure a non-zero error code is returned */
+ if (inctx->rc == APR_SUCCESS) {
+ inctx->rc = APR_EGENERAL;
+ }
+ break;
+ }
+
+ /* We rely on SSL_get_error() after the read, which requires an empty
+ * error queue before the read in order to work properly.
+ */
+ ERR_clear_error();
+
+ /* SSL_read may not read because we haven't taken enough data
+ * from the stack. This is where we want to consider all of
+ * the blocking and SPECULATIVE semantics
+ */
+ rc = SSL_read(inctx->filter_ctx->pssl, buf + bytes, wanted - bytes);
+
+ if (rc > 0) {
+ *len += rc;
+ if (inctx->mode == AP_MODE_SPECULATIVE) {
+ /* We want to rollback this read. */
+ char_buffer_write(&inctx->cbuf, buf, rc);
+ }
+ return inctx->rc;
+ }
+ else /* (rc <= 0) */ {
+ int ssl_err;
+ conn_rec *c;
+ if (rc == 0) {
+ /* If EAGAIN, we will loop given a blocking read,
+ * otherwise consider ourselves at EOF.
+ */
+ if (APR_STATUS_IS_EAGAIN(inctx->rc)
+ || APR_STATUS_IS_EINTR(inctx->rc)) {
+ /* Already read something, return APR_SUCCESS instead.
+ * On win32 in particular, but perhaps on other kernels,
+ * a blocking call isn't 'always' blocking.
+ */
+ if (*len > 0) {
+ inctx->rc = APR_SUCCESS;
+ break;
+ }
+ if (inctx->block == APR_NONBLOCK_READ) {
+ break;
+ }
+ }
+ else {
+ if (*len > 0) {
+ inctx->rc = APR_SUCCESS;
+ break;
+ }
+ }
+ }
+ ssl_err = SSL_get_error(inctx->filter_ctx->pssl, rc);
+ c = (conn_rec*)SSL_get_app_data(inctx->filter_ctx->pssl);
+
+ if (ssl_err == SSL_ERROR_WANT_READ) {
+ /*
+ * If OpenSSL wants to read more, and we were nonblocking,
+ * report as an EAGAIN. Otherwise loop, pulling more
+ * data from network filter.
+ *
+ * (This is usually the case when the client forces an SSL
+ * renegotiation which is handled implicitly by OpenSSL.)
+ */
+ inctx->rc = APR_EAGAIN;
+
+ if (*len > 0) {
+ inctx->rc = APR_SUCCESS;
+ break;
+ }
+ if (inctx->block == APR_NONBLOCK_READ) {
+ break;
+ }
+ continue; /* Blocking and nothing yet? Try again. */
+ }
+ else if (ssl_err == SSL_ERROR_SYSCALL) {
+ if (APR_STATUS_IS_EAGAIN(inctx->rc)
+ || APR_STATUS_IS_EINTR(inctx->rc)) {
+ /* Already read something, return APR_SUCCESS instead. */
+ if (*len > 0) {
+ inctx->rc = APR_SUCCESS;
+ break;
+ }
+ if (inctx->block == APR_NONBLOCK_READ) {
+ break;
+ }
+ continue; /* Blocking and nothing yet? Try again. */
+ }
+ else if (APR_STATUS_IS_TIMEUP(inctx->rc)) {
+ /* just return it, the calling layer might be fine with it,
+ and we do not want to bloat the log. */
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, inctx->rc, c, APLOGNO(01991)
+ "SSL input filter read failed.");
+ }
+ }
+ else if (rc == 0 && ssl_err == SSL_ERROR_ZERO_RETURN) {
+ inctx->rc = APR_EOF;
+ break;
+ }
+ else /* if (ssl_err == SSL_ERROR_SSL) */ {
+ /*
+ * Log SSL errors and any unexpected conditions.
+ */
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, inctx->rc, c, APLOGNO(01992)
+ "SSL library error %d reading data", ssl_err);
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_INFO, mySrvFromConn(c));
+
+ }
+ if (rc == 0) {
+ inctx->rc = APR_EOF;
+ break;
+ }
+ if (inctx->rc == APR_SUCCESS) {
+ inctx->rc = APR_EGENERAL;
+ }
+ break;
+ }
+ }
+ return inctx->rc;
+}
+
+/* Read a line of input from the SSL input layer into buffer BUF of
+ * length *LEN; updating *len to reflect the length of the line
+ * including the LF character. */
+static apr_status_t ssl_io_input_getline(bio_filter_in_ctx_t *inctx,
+ char *buf,
+ apr_size_t *len)
+{
+ const char *pos = NULL;
+ apr_status_t status;
+ apr_size_t tmplen = *len, buflen = *len, offset = 0;
+
+ *len = 0;
+
+ /*
+ * in most cases we get all the headers on the first SSL_read.
+ * however, in certain cases SSL_read will only get a partial
+ * chunk of the headers, so we try to read until LF is seen.
+ */
+
+ while (tmplen > 0) {
+ status = ssl_io_input_read(inctx, buf + offset, &tmplen);
+
+ if (status != APR_SUCCESS) {
+ if (APR_STATUS_IS_EAGAIN(status) && (*len > 0)) {
+ /* Save the part of the line we already got */
+ char_buffer_write(&inctx->cbuf, buf, *len);
+ }
+ return status;
+ }
+
+ *len += tmplen;
+
+ if ((pos = memchr(buf, APR_ASCII_LF, *len))) {
+ break;
+ }
+
+ offset += tmplen;
+ tmplen = buflen - offset;
+ }
+
+ if (pos) {
+ char *value;
+ int length;
+ apr_size_t bytes = pos - buf;
+
+ bytes += 1;
+ value = buf + bytes;
+ length = *len - bytes;
+
+ char_buffer_write(&inctx->cbuf, value, length);
+
+ *len = bytes;
+ }
+
+ return APR_SUCCESS;
+}
+
+
+static apr_status_t ssl_filter_write(ap_filter_t *f,
+ const char *data,
+ apr_size_t len)
+{
+ ssl_filter_ctx_t *filter_ctx = f->ctx;
+ bio_filter_out_ctx_t *outctx;
+ int res;
+
+ /* write SSL */
+ if (filter_ctx->pssl == NULL) {
+ return APR_EGENERAL;
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, f->c,
+ "ssl_filter_write: %"APR_SIZE_T_FMT" bytes", len);
+
+ /* We rely on SSL_get_error() after the write, which requires an empty error
+ * queue before the write in order to work properly.
+ */
+ ERR_clear_error();
+
+ outctx = (bio_filter_out_ctx_t *)BIO_get_data(filter_ctx->pbioWrite);
+ res = SSL_write(filter_ctx->pssl, (unsigned char *)data, len);
+
+ if (res < 0) {
+ int ssl_err = SSL_get_error(filter_ctx->pssl, res);
+ conn_rec *c = (conn_rec*)SSL_get_app_data(outctx->filter_ctx->pssl);
+
+ if (ssl_err == SSL_ERROR_WANT_WRITE) {
+ /*
+ * If OpenSSL wants to write more, and we were nonblocking,
+ * report as an EAGAIN. Otherwise loop, pushing more
+ * data at the network filter.
+ *
+ * (This is usually the case when the client forces an SSL
+ * renegotiation which is handled implicitly by OpenSSL.)
+ */
+ outctx->rc = APR_EAGAIN;
+ }
+ else if (ssl_err == SSL_ERROR_WANT_READ) {
+ /*
+ * If OpenSSL wants to read during write, and we were
+ * nonblocking, set the sense explicitly to read and
+ * report as an EAGAIN.
+ *
+ * (This is usually the case when the client forces an SSL
+ * renegotiation which is handled implicitly by OpenSSL.)
+ */
+ outctx->c->cs->sense = CONN_SENSE_WANT_READ;
+ outctx->rc = APR_EAGAIN;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, outctx->c,
+ "Want read during nonblocking write");
+ }
+ else if (ssl_err == SSL_ERROR_SYSCALL) {
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, outctx->rc, c, APLOGNO(01993)
+ "SSL output filter write failed.");
+ }
+ else /* if (ssl_err == SSL_ERROR_SSL) */ {
+ /*
+ * Log SSL errors
+ */
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, outctx->rc, c, APLOGNO(01994)
+ "SSL library error %d writing data", ssl_err);
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_INFO, mySrvFromConn(c));
+ }
+ if (outctx->rc == APR_SUCCESS) {
+ outctx->rc = APR_EGENERAL;
+ }
+ }
+ else if ((apr_size_t)res != len) {
+ conn_rec *c = f->c;
+ char *reason = "reason unknown";
+
+ /* XXX: probably a better way to determine this */
+ if (SSL_total_renegotiations(filter_ctx->pssl)) {
+ reason = "likely due to failed renegotiation";
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, outctx->rc, c, APLOGNO(01995)
+ "failed to write %" APR_SSIZE_T_FMT
+ " of %" APR_SIZE_T_FMT " bytes (%s)",
+ len - (apr_size_t)res, len, reason);
+
+ outctx->rc = APR_EGENERAL;
+ }
+ return outctx->rc;
+}
+
+/* Just use a simple request. Any request will work for this, because
+ * we use a flag in the conn_rec->conn_vector now. The fake request just
+ * gets the request back to the Apache core so that a response can be sent.
+ * Since we use an HTTP/1.x request, we also have to inject the empty line
+ * that terminates the headers, or the core will read more data from the
+ * socket.
+ */
+#define HTTP_ON_HTTPS_PORT \
+ "GET / HTTP/1.0" CRLF
+
+#define HTTP_ON_HTTPS_PORT_BUCKET(alloc) \
+ apr_bucket_immortal_create(HTTP_ON_HTTPS_PORT, \
+ sizeof(HTTP_ON_HTTPS_PORT) - 1, \
+ alloc)
+
+/* Custom apr_status_t error code, used when a plain HTTP request is
+ * received on an SSL port. */
+#define MODSSL_ERROR_HTTP_ON_HTTPS (APR_OS_START_USERERR + 0)
+
+/* Custom apr_status_t error code, used when the proxy cannot
+ * establish an outgoing SSL connection. */
+#define MODSSL_ERROR_BAD_GATEWAY (APR_OS_START_USERERR + 1)
+
+static void ssl_io_filter_disable(SSLConnRec *sslconn,
+ bio_filter_in_ctx_t *inctx)
+{
+ SSL_free(inctx->ssl);
+ sslconn->ssl = NULL;
+ inctx->ssl = NULL;
+ inctx->filter_ctx->pssl = NULL;
+}
+
+static apr_status_t ssl_io_filter_error(bio_filter_in_ctx_t *inctx,
+ apr_bucket_brigade *bb,
+ apr_status_t status,
+ int is_init)
+{
+ ap_filter_t *f = inctx->f;
+ SSLConnRec *sslconn = myConnConfig(f->c);
+ apr_bucket *bucket;
+ int send_eos = 1;
+
+ switch (status) {
+ case MODSSL_ERROR_HTTP_ON_HTTPS:
+ /* log the situation */
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, f->c, APLOGNO(01996)
+ "SSL handshake failed: HTTP spoken on HTTPS port; "
+ "trying to send HTML error page");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_INFO, sslconn->server);
+
+ ssl_io_filter_disable(sslconn, inctx);
+ f->c->keepalive = AP_CONN_CLOSE;
+ if (is_init) {
+ sslconn->non_ssl_request = NON_SSL_SEND_REQLINE;
+ return APR_EGENERAL;
+ }
+ sslconn->non_ssl_request = NON_SSL_SEND_HDR_SEP;
+
+ /* fake the request line */
+ bucket = HTTP_ON_HTTPS_PORT_BUCKET(f->c->bucket_alloc);
+ send_eos = 0;
+ break;
+
+ case MODSSL_ERROR_BAD_GATEWAY:
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, f->c, APLOGNO(01997)
+ "SSL handshake failed: sending 502");
+ f->c->aborted = 1;
+ return APR_EGENERAL;
+
+ default:
+ return status;
+ }
+
+ APR_BRIGADE_INSERT_TAIL(bb, bucket);
+ if (send_eos) {
+ bucket = apr_bucket_eos_create(f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, bucket);
+ }
+ return APR_SUCCESS;
+}
+
+static const char ssl_io_filter[] = "SSL/TLS Filter";
+static const char ssl_io_buffer[] = "SSL/TLS Buffer";
+static const char ssl_io_coalesce[] = "SSL/TLS Coalescing Filter";
+
+/*
+ * Close the SSL part of the socket connection
+ * (called immediately _before_ the socket is closed)
+ * or called with
+ */
+static void ssl_filter_io_shutdown(ssl_filter_ctx_t *filter_ctx,
+ conn_rec *c, int abortive)
+{
+ SSL *ssl = filter_ctx->pssl;
+ const char *type = "";
+ SSLConnRec *sslconn = myConnConfig(c);
+ int shutdown_type;
+ int loglevel = APLOG_DEBUG;
+ const char *logno;
+
+ if (!ssl) {
+ return;
+ }
+
+ /*
+ * Now close the SSL layer of the connection. We've to take
+ * the TLSv1 standard into account here:
+ *
+ * | 7.2.1. Closure alerts
+ * |
+ * | The client and the server must share knowledge that the connection is
+ * | ending in order to avoid a truncation attack. Either party may
+ * | initiate the exchange of closing messages.
+ * |
+ * | close_notify
+ * | This message notifies the recipient that the sender will not send
+ * | any more messages on this connection. The session becomes
+ * | unresumable if any connection is terminated without proper
+ * | close_notify messages with level equal to warning.
+ * |
+ * | Either party may initiate a close by sending a close_notify alert.
+ * | Any data received after a closure alert is ignored.
+ * |
+ * | Each party is required to send a close_notify alert before closing
+ * | the write side of the connection. It is required that the other party
+ * | respond with a close_notify alert of its own and close down the
+ * | connection immediately, discarding any pending writes. It is not
+ * | required for the initiator of the close to wait for the responding
+ * | close_notify alert before closing the read side of the connection.
+ *
+ * This means we've to send a close notify message, but haven't to wait
+ * for the close notify of the client. Actually we cannot wait for the
+ * close notify of the client because some clients (including Netscape
+ * 4.x) don't send one, so we would hang.
+ */
+
+ /*
+ * exchange close notify messages, but allow the user
+ * to force the type of handshake via SetEnvIf directive
+ */
+ if (abortive) {
+ shutdown_type = SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN;
+ type = "abortive";
+ logno = APLOGNO(01998);
+ loglevel = APLOG_INFO;
+ }
+ else switch (sslconn->shutdown_type) {
+ case SSL_SHUTDOWN_TYPE_UNCLEAN:
+ /* perform no close notify handshake at all
+ (violates the SSL/TLS standard!) */
+ shutdown_type = SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN;
+ type = "unclean";
+ logno = APLOGNO(01999);
+ break;
+ case SSL_SHUTDOWN_TYPE_ACCURATE:
+ /* send close notify and wait for clients close notify
+ (standard compliant, but usually causes connection hangs) */
+ shutdown_type = 0;
+ type = "accurate";
+ logno = APLOGNO(02000);
+ break;
+ default:
+ /*
+ * case SSL_SHUTDOWN_TYPE_UNSET:
+ * case SSL_SHUTDOWN_TYPE_STANDARD:
+ */
+ /* send close notify, but don't wait for clients close notify
+ (standard compliant and safe, so it's the DEFAULT!) */
+ shutdown_type = SSL_RECEIVED_SHUTDOWN;
+ type = "standard";
+ logno = APLOGNO(02001);
+ break;
+ }
+
+ SSL_set_shutdown(ssl, shutdown_type);
+ modssl_smart_shutdown(ssl);
+
+ /* and finally log the fact that we've closed the connection */
+ if (APLOG_CS_IS_LEVEL(c, mySrvFromConn(c), loglevel)) {
+ /* Intentional no APLOGNO */
+ /* logno provides APLOGNO */
+ ap_log_cserror(APLOG_MARK, loglevel, 0, c, mySrvFromConn(c),
+ "%sConnection closed to child %ld with %s shutdown "
+ "(server %s)",
+ logno, c->id, type,
+ ssl_util_vhostid(c->pool, mySrvFromConn(c)));
+ }
+
+ /* deallocate the SSL connection */
+ if (sslconn->client_cert) {
+ X509_free(sslconn->client_cert);
+ sslconn->client_cert = NULL;
+ }
+ SSL_free(ssl);
+ sslconn->ssl = NULL;
+ filter_ctx->pssl = NULL; /* so filters know we've been shutdown */
+
+ if (abortive) {
+ /* prevent any further I/O */
+ c->aborted = 1;
+ }
+}
+
+static apr_status_t ssl_io_filter_cleanup(void *data)
+{
+ ssl_filter_ctx_t *filter_ctx = data;
+
+ if (filter_ctx->pssl) {
+ conn_rec *c = (conn_rec *)SSL_get_app_data(filter_ctx->pssl);
+ SSLConnRec *sslconn = myConnConfig(c);
+
+ SSL_free(filter_ctx->pssl);
+ sslconn->ssl = filter_ctx->pssl = NULL;
+ }
+
+ return APR_SUCCESS;
+}
+
+/*
+ * The hook is NOT registered with ap_hook_process_connection. Instead, it is
+ * called manually from the churn () before it tries to read any data.
+ * There is some problem if I accept conn_rec *. Still investigating..
+ * Adv. if conn_rec * can be accepted is we can hook this function using the
+ * ap_hook_process_connection hook.
+ */
+
+/* Perform the SSL handshake (whether in client or server mode), if
+ * necessary, for the given connection. */
+static apr_status_t ssl_io_filter_handshake(ssl_filter_ctx_t *filter_ctx)
+{
+ conn_rec *c = (conn_rec *)SSL_get_app_data(filter_ctx->pssl);
+ SSLConnRec *sslconn = myConnConfig(c);
+ SSLSrvConfigRec *sc;
+ X509 *cert;
+ int n;
+ int ssl_err;
+ long verify_result;
+ server_rec *server;
+
+ if (SSL_is_init_finished(filter_ctx->pssl)) {
+ return APR_SUCCESS;
+ }
+
+ server = sslconn->server;
+ if (c->outgoing) {
+#ifdef HAVE_TLSEXT
+ apr_ipsubnet_t *ip;
+#ifdef HAVE_TLS_ALPN
+ const char *alpn_note;
+ apr_array_header_t *alpn_proposed = NULL;
+ int alpn_empty_ok = 1;
+#endif
+#endif
+ const char *hostname_note = apr_table_get(c->notes,
+ "proxy-request-hostname");
+ BOOL proxy_ssl_check_peer_ok = TRUE;
+ int post_handshake_rc = OK;
+ SSLDirConfigRec *dc;
+
+ dc = sslconn->dc;
+ sc = mySrvConfig(server);
+
+#ifdef HAVE_TLSEXT
+#ifdef HAVE_TLS_ALPN
+ alpn_note = apr_table_get(c->notes, "proxy-request-alpn-protos");
+ if (alpn_note) {
+ char *protos, *s, *p, *last, *proto;
+ apr_size_t len;
+
+ /* Transform the note into a protocol formatted byte array:
+ * (len-byte proto-char+)*
+ * We need the remote server to agree on one of these, unless 'http/1.1'
+ * is also among our proposals. Because pre-ALPN remotes will speak this.
+ */
+ alpn_proposed = apr_array_make(c->pool, 3, sizeof(const char*));
+ alpn_empty_ok = 0;
+ s = protos = apr_pcalloc(c->pool, strlen(alpn_note)+1);
+ p = apr_pstrdup(c->pool, alpn_note);
+ while ((p = apr_strtok(p, ", ", &last))) {
+ len = last - p - (*last? 1 : 0);
+ if (len > 255) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(03309)
+ "ALPN proxy protocol identifier too long: %s",
+ p);
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, server);
+ return APR_EGENERAL;
+ }
+ proto = apr_pstrndup(c->pool, p, len);
+ APR_ARRAY_PUSH(alpn_proposed, const char*) = proto;
+ if (!strcmp("http/1.1", proto)) {
+ alpn_empty_ok = 1;
+ }
+ *s++ = (unsigned char)len;
+ while (len--) {
+ *s++ = *p++;
+ }
+ p = NULL;
+ }
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "setting alpn protos from '%s', protolen=%d",
+ alpn_note, (int)(s - protos));
+ if (protos != s && SSL_set_alpn_protos(filter_ctx->pssl,
+ (unsigned char *)protos,
+ s - protos)) {
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c, APLOGNO(03310)
+ "error setting alpn protos from '%s'", alpn_note);
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_WARNING, server);
+ /* If ALPN was requested and we cannot do it, we must fail */
+ return MODSSL_ERROR_BAD_GATEWAY;
+ }
+ }
+#endif /* defined HAVE_TLS_ALPN */
+ /*
+ * Enable SNI for backend requests. Make sure we don't do it for
+ * pure SSLv3 connections, and also prevent IP addresses
+ * from being included in the SNI extension. (OpenSSL would simply
+ * pass them on, but RFC 6066 is quite clear on this: "Literal
+ * IPv4 and IPv6 addresses are not permitted".)
+ */
+ if (hostname_note &&
+#ifndef OPENSSL_NO_SSL3
+ dc->proxy->protocol != SSL_PROTOCOL_SSLV3 &&
+#endif
+ apr_ipsubnet_create(&ip, hostname_note, NULL,
+ c->pool) != APR_SUCCESS) {
+ if (SSL_set_tlsext_host_name(filter_ctx->pssl, hostname_note)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c,
+ "SNI extension for SSL Proxy request set to '%s'",
+ hostname_note);
+ } else {
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c, APLOGNO(02002)
+ "Failed to set SNI extension for SSL Proxy "
+ "request to '%s'", hostname_note);
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_WARNING, server);
+ }
+ }
+#endif /* defined HAVE_TLSEXT */
+
+ if ((n = SSL_connect(filter_ctx->pssl)) <= 0) {
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02003)
+ "SSL Proxy connect failed");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_INFO, server);
+ /* ensure that the SSL structures etc are freed, etc: */
+ ssl_filter_io_shutdown(filter_ctx, c, 1);
+ apr_table_setn(c->notes, "SSL_connect_rv", "err");
+ return MODSSL_ERROR_BAD_GATEWAY;
+ }
+
+ cert = SSL_get_peer_certificate(filter_ctx->pssl);
+
+ if (dc->proxy->ssl_check_peer_expire != FALSE) {
+ if (!cert
+ || (X509_cmp_current_time(
+ X509_get_notBefore(cert)) >= 0)
+ || (X509_cmp_current_time(
+ X509_get_notAfter(cert)) <= 0)) {
+ proxy_ssl_check_peer_ok = FALSE;
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02004)
+ "SSL Proxy: Peer certificate is expired");
+ }
+ }
+ if ((dc->proxy->ssl_check_peer_name != FALSE) &&
+ ((dc->proxy->ssl_check_peer_cn != FALSE) ||
+ (dc->proxy->ssl_check_peer_name == TRUE)) &&
+ hostname_note) {
+ if (!cert
+ || modssl_X509_match_name(c->pool, cert, hostname_note,
+ TRUE, server) == FALSE) {
+ proxy_ssl_check_peer_ok = FALSE;
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02411)
+ "SSL Proxy: Peer certificate does not match "
+ "for hostname %s", hostname_note);
+ }
+ }
+ else if ((dc->proxy->ssl_check_peer_cn == TRUE) &&
+ hostname_note) {
+ const char *hostname;
+ int match = 0;
+
+ hostname = ssl_var_lookup(NULL, server, c, NULL,
+ "SSL_CLIENT_S_DN_CN");
+
+ /* Do string match or simplest wildcard match if that
+ * fails. */
+ match = strcasecmp(hostname, hostname_note) == 0;
+ if (!match && strncmp(hostname, "*.", 2) == 0) {
+ const char *p = ap_strchr_c(hostname_note, '.');
+
+ match = p && strcasecmp(p, hostname + 1) == 0;
+ }
+
+ if (!match) {
+ proxy_ssl_check_peer_ok = FALSE;
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02005)
+ "SSL Proxy: Peer certificate CN mismatch:"
+ " Certificate CN: %s Requested hostname: %s",
+ hostname, hostname_note);
+ }
+ }
+
+#ifdef HAVE_TLS_ALPN
+ /* If we proposed ALPN protocol(s), we need to check if the server
+ * agreed to one of them. While <https://www.rfc-editor.org/rfc/rfc7301.txt>
+ * chapter 3.2 says the server SHALL error the handshake in such a case,
+ * the reality is that some servers fall back to their default, e.g. http/1.1.
+ * (we also do this right now)
+ * We need to treat this as an error for security reasons.
+ */
+ if (alpn_proposed && alpn_proposed->nelts > 0) {
+ const char *selected;
+ unsigned int slen;
+
+ SSL_get0_alpn_selected(filter_ctx->pssl, (const unsigned char**)&selected, &slen);
+ if (!selected || !slen) {
+ /* No ALPN selection reported by the remote server. This could mean
+ * it does not support ALPN (old server) or that it does not support
+ * any of our proposals (Apache itself up to 2.4.48 at least did that). */
+ if (!alpn_empty_ok) {
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(10273)
+ "SSL Proxy: Peer did not select any of our ALPN protocols [%s].",
+ alpn_note);
+ proxy_ssl_check_peer_ok = FALSE;
+ }
+ }
+ else {
+ const char *proto;
+ int i, found = 0;
+ for (i = 0; !found && i < alpn_proposed->nelts; ++i) {
+ proto = APR_ARRAY_IDX(alpn_proposed, i, const char *);
+ found = !strncmp(selected, proto, slen);
+ }
+ if (!found) {
+ /* From a conforming peer, this should never happen,
+ * but life always finds a way... */
+ proto = apr_pstrndup(c->pool, selected, slen);
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(10274)
+ "SSL Proxy: Peer proposed ALPN protocol %s which is none "
+ "of our proposals [%s].", proto, alpn_note);
+ proxy_ssl_check_peer_ok = FALSE;
+ }
+ }
+ }
+#endif
+
+ if (proxy_ssl_check_peer_ok == TRUE) {
+ /* another chance to fail */
+ post_handshake_rc = ssl_run_proxy_post_handshake(c, filter_ctx->pssl);
+ }
+
+ if (cert) {
+ X509_free(cert);
+ }
+
+ if (proxy_ssl_check_peer_ok != TRUE
+ || (post_handshake_rc != OK && post_handshake_rc != DECLINED)) {
+ /* ensure that the SSL structures etc are freed, etc: */
+ ssl_filter_io_shutdown(filter_ctx, c, 1);
+ apr_table_setn(c->notes, "SSL_connect_rv", "err");
+ return MODSSL_ERROR_BAD_GATEWAY;
+ }
+
+ apr_table_setn(c->notes, "SSL_connect_rv", "ok");
+ return APR_SUCCESS;
+ }
+
+ /* We rely on SSL_get_error() after the accept, which requires an empty
+ * error queue before the accept in order to work properly.
+ */
+ ERR_clear_error();
+
+ if ((n = SSL_accept(filter_ctx->pssl)) <= 0) {
+ bio_filter_in_ctx_t *inctx = (bio_filter_in_ctx_t *)
+ BIO_get_data(filter_ctx->pbioRead);
+ bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)
+ BIO_get_data(filter_ctx->pbioWrite);
+ apr_status_t rc = inctx->rc ? inctx->rc : outctx->rc ;
+ ssl_err = SSL_get_error(filter_ctx->pssl, n);
+
+ if (ssl_err == SSL_ERROR_ZERO_RETURN) {
+ /*
+ * The case where the connection was closed before any data
+ * was transferred. That's not a real error and can occur
+ * sporadically with some clients.
+ */
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, rc, c, APLOGNO(02006)
+ "SSL handshake stopped: connection was closed");
+ }
+ else if (ssl_err == SSL_ERROR_WANT_READ) {
+ /*
+ * This is in addition to what was present earlier. It is
+ * borrowed from openssl_state_machine.c [mod_tls].
+ * TBD.
+ */
+ outctx->rc = APR_EAGAIN;
+ return APR_EAGAIN;
+ }
+ else if (ERR_GET_LIB(ERR_peek_error()) == ERR_LIB_SSL &&
+ ERR_GET_REASON(ERR_peek_error()) == SSL_R_HTTP_REQUEST) {
+ /*
+ * The case where OpenSSL has recognized a HTTP request:
+ * This means the client speaks plain HTTP on our HTTPS port.
+ * ssl_io_filter_error will disable the ssl filters when it
+ * sees this status code.
+ */
+ return MODSSL_ERROR_HTTP_ON_HTTPS;
+ }
+ else if (ssl_err == SSL_ERROR_SYSCALL) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rc, c, APLOGNO(02007)
+ "SSL handshake interrupted by system "
+ "[Hint: Stop button pressed in browser?!]");
+ }
+ else /* if (ssl_err == SSL_ERROR_SSL) */ {
+ /*
+ * Log SSL errors and any unexpected conditions.
+ */
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, rc, c, APLOGNO(02008)
+ "SSL library error %d in handshake "
+ "(server %s)", ssl_err,
+ ssl_util_vhostid(c->pool, server));
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_INFO, server);
+
+ }
+ if (inctx->rc == APR_SUCCESS) {
+ inctx->rc = APR_EGENERAL;
+ }
+
+ ssl_filter_io_shutdown(filter_ctx, c, 1);
+ return inctx->rc;
+ }
+ sc = mySrvConfig(sslconn->server);
+
+ /*
+ * Check for failed client authentication
+ */
+ verify_result = SSL_get_verify_result(filter_ctx->pssl);
+
+ if ((verify_result != X509_V_OK) ||
+ sslconn->verify_error)
+ {
+ if (ssl_verify_error_is_optional(verify_result) &&
+ (sc->server->auth.verify_mode == SSL_CVERIFY_OPTIONAL_NO_CA))
+ {
+ /* leaving this log message as an error for the moment,
+ * according to the mod_ssl docs:
+ * "level optional_no_ca is actually against the idea
+ * of authentication (but can be used to establish
+ * SSL test pages, etc.)"
+ * optional_no_ca doesn't appear to work as advertised
+ * in 1.x
+ */
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02009)
+ "SSL client authentication failed, "
+ "accepting certificate based on "
+ "\"SSLVerifyClient optional_no_ca\" "
+ "configuration");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_INFO, server);
+ }
+ else {
+ const char *error = sslconn->verify_error ?
+ sslconn->verify_error :
+ X509_verify_cert_error_string(verify_result);
+
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02010)
+ "SSL client authentication failed: %s",
+ error ? error : "unknown");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_INFO, server);
+
+ ssl_filter_io_shutdown(filter_ctx, c, 1);
+ return APR_ECONNABORTED;
+ }
+ }
+
+ /*
+ * Remember the peer certificate's DN
+ */
+ if ((cert = SSL_get_peer_certificate(filter_ctx->pssl))) {
+ if (sslconn->client_cert) {
+ X509_free(sslconn->client_cert);
+ }
+ sslconn->client_cert = cert;
+ sslconn->client_dn = NULL;
+ }
+
+ /*
+ * Make really sure that when a peer certificate
+ * is required we really got one... (be paranoid)
+ */
+ if ((sc->server->auth.verify_mode == SSL_CVERIFY_REQUIRE) &&
+ !sslconn->client_cert)
+ {
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02011)
+ "No acceptable peer certificate available");
+
+ ssl_filter_io_shutdown(filter_ctx, c, 1);
+ return APR_ECONNABORTED;
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t ssl_io_filter_input(ap_filter_t *f,
+ apr_bucket_brigade *bb,
+ ap_input_mode_t mode,
+ apr_read_type_e block,
+ apr_off_t readbytes)
+{
+ apr_status_t status;
+ bio_filter_in_ctx_t *inctx = f->ctx;
+ const char *start = inctx->buffer; /* start of block to return */
+ apr_size_t len = sizeof(inctx->buffer); /* length of block to return */
+ int is_init = (mode == AP_MODE_INIT);
+ apr_bucket *bucket;
+
+ if (f->c->aborted) {
+ /* XXX: Ok, if we aborted, we ARE at the EOS. We also have
+ * aborted. This 'double protection' is probably redundant,
+ * but also effective against just about anything.
+ */
+ bucket = apr_bucket_eos_create(f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, bucket);
+ return APR_ECONNABORTED;
+ }
+
+ if (!inctx->ssl) {
+ SSLConnRec *sslconn = myConnConfig(f->c);
+ if (sslconn->non_ssl_request == NON_SSL_SEND_REQLINE) {
+ bucket = HTTP_ON_HTTPS_PORT_BUCKET(f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, bucket);
+ if (mode != AP_MODE_SPECULATIVE) {
+ sslconn->non_ssl_request = NON_SSL_SEND_HDR_SEP;
+ }
+ return APR_SUCCESS;
+ }
+ if (sslconn->non_ssl_request == NON_SSL_SEND_HDR_SEP) {
+ bucket = apr_bucket_immortal_create(CRLF, 2, f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, bucket);
+ if (mode != AP_MODE_SPECULATIVE) {
+ sslconn->non_ssl_request = NON_SSL_SET_ERROR_MSG;
+ }
+ return APR_SUCCESS;
+ }
+ return ap_get_brigade(f->next, bb, mode, block, readbytes);
+ }
+
+ /* XXX: we don't currently support anything other than these modes. */
+ if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE &&
+ mode != AP_MODE_SPECULATIVE && mode != AP_MODE_INIT) {
+ return APR_ENOTIMPL;
+ }
+
+ inctx->mode = mode;
+ inctx->block = block;
+
+ /* XXX: we could actually move ssl_io_filter_handshake to an
+ * ap_hook_process_connection but would still need to call it for
+ * AP_MODE_INIT for protocols that may upgrade the connection
+ * rather than have SSLEngine On configured.
+ */
+ if ((status = ssl_io_filter_handshake(inctx->filter_ctx)) != APR_SUCCESS) {
+ return ssl_io_filter_error(inctx, bb, status, is_init);
+ }
+
+ if (is_init) {
+ /* protocol module needs to handshake before sending
+ * data to client (e.g. NNTP or FTP)
+ */
+ return APR_SUCCESS;
+ }
+
+ if (inctx->mode == AP_MODE_READBYTES ||
+ inctx->mode == AP_MODE_SPECULATIVE) {
+ /* Protected from truncation, readbytes < MAX_SIZE_T
+ * FIXME: No, it's *not* protected. -- jre */
+ if (readbytes < len) {
+ len = (apr_size_t)readbytes;
+ }
+ status = ssl_io_input_read(inctx, inctx->buffer, &len);
+ }
+ else if (inctx->mode == AP_MODE_GETLINE) {
+ const char *pos;
+
+ /* Satisfy the read directly out of the buffer if possible;
+ * invoking ssl_io_input_getline will mean the entire buffer
+ * is copied once (unnecessarily) for each GETLINE call. */
+ if (inctx->cbuf.length
+ && (pos = memchr(inctx->cbuf.value, APR_ASCII_LF,
+ inctx->cbuf.length)) != NULL) {
+ start = inctx->cbuf.value;
+ len = 1 + pos - start; /* +1 to include LF */
+ /* Buffer contents now consumed. */
+ inctx->cbuf.value += len;
+ inctx->cbuf.length -= len;
+ status = APR_SUCCESS;
+ }
+ else {
+ /* Otherwise fall back to the hard way. */
+ status = ssl_io_input_getline(inctx, inctx->buffer, &len);
+ }
+ }
+ else {
+ /* We have no idea what you are talking about, so return an error. */
+ status = APR_ENOTIMPL;
+ }
+
+ /* It is possible for mod_ssl's BIO to be used outside of the
+ * direct control of mod_ssl's input or output filter -- notably,
+ * when mod_ssl initiates a renegotiation. Switching the BIO mode
+ * back to "blocking" here ensures such operations don't fail with
+ * SSL_ERROR_WANT_READ. */
+ inctx->block = APR_BLOCK_READ;
+
+ /* Handle custom errors. */
+ if (status != APR_SUCCESS) {
+ return ssl_io_filter_error(inctx, bb, status, 0);
+ }
+
+ /* Create a transient bucket out of the decrypted data. */
+ if (len > 0) {
+ bucket =
+ apr_bucket_transient_create(start, len, f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, bucket);
+ }
+
+ return APR_SUCCESS;
+}
+
+
+/* ssl_io_filter_output() produces one SSL/TLS record per bucket
+ * passed down the output filter stack. This results in a high
+ * overhead (more network packets & TLS processing) for any output
+ * comprising many small buckets. SSI output passed through the HTTP
+ * chunk filter, for example, may produce many brigades containing
+ * small buckets - [chunk-size CRLF] [chunk-data] [CRLF].
+ *
+ * Sending HTTP response headers as a separate TLS record to the
+ * response body also reveals information to a network observer (the
+ * size of headers) which can be significant.
+ *
+ * The coalescing filter merges data buckets with the aim of producing
+ * fewer, larger TLS records - without copying/buffering all content
+ * and introducing unnecessary overhead.
+ *
+ * ### This buffering could be probably be done more comprehensively
+ * ### in ssl_io_filter_output itself.
+ *
+ * ### Another possible performance optimisation in particular for the
+ * ### [HEAP] [FILE] HTTP response case is using a brigade rather than
+ * ### a char array to buffer; using apr_brigade_write() to append
+ * ### will use already-allocated memory from the HEAP, reducing # of
+ * ### copies.
+ */
+
+#define COALESCE_BYTES (AP_IOBUFSIZE)
+
+struct coalesce_ctx {
+ char buffer[COALESCE_BYTES];
+ apr_size_t bytes; /* number of bytes of buffer used. */
+};
+
+static apr_status_t ssl_io_filter_coalesce(ap_filter_t *f,
+ apr_bucket_brigade *bb)
+{
+ apr_bucket *e, *upto;
+ apr_size_t bytes = 0;
+ struct coalesce_ctx *ctx = f->ctx;
+ apr_size_t buffered = ctx ? ctx->bytes : 0; /* space used on entry */
+ unsigned count = 0;
+
+ /* The brigade consists of zero-or-more small data buckets which
+ * can be coalesced (referred to as the "prefix"), followed by the
+ * remainder of the brigade.
+ *
+ * Find the last bucket - if any - of that prefix. count gives
+ * the number of buckets in the prefix. The "prefix" must contain
+ * only data buckets with known length, and must be of a total
+ * size which fits into the buffer.
+ *
+ * N.B.: The process here could be repeated throughout the brigade
+ * (coalesce any run of consecutive data buckets) but this would
+ * add significant complexity, particularly to memory
+ * management. */
+ for (e = APR_BRIGADE_FIRST(bb);
+ e != APR_BRIGADE_SENTINEL(bb)
+ && !APR_BUCKET_IS_METADATA(e)
+ && e->length != (apr_size_t)-1
+ && e->length <= COALESCE_BYTES
+ && (buffered + bytes + e->length) <= COALESCE_BYTES;
+ e = APR_BUCKET_NEXT(e)) {
+ /* don't count zero-length buckets */
+ if (e->length) {
+ bytes += e->length;
+ count++;
+ }
+ }
+
+ /* If there is room remaining and the next bucket is a data
+ * bucket, try to include it in the prefix to coalesce. For a
+ * typical [HEAP] [FILE] HTTP response brigade, this handles
+ * merging the headers and the start of the body into a single TLS
+ * record. */
+ if (bytes + buffered > 0
+ && bytes + buffered < COALESCE_BYTES
+ && e != APR_BRIGADE_SENTINEL(bb)
+ && !APR_BUCKET_IS_METADATA(e)) {
+ apr_status_t rv = APR_SUCCESS;
+
+ /* For an indeterminate length bucket (PIPE/CGI/...), try a
+ * non-blocking read to have it morph into a HEAP. If the
+ * read fails with EAGAIN, it is harmless to try a split
+ * anyway, split is ENOTIMPL for most PIPE-like buckets. */
+ if (e->length == (apr_size_t)-1) {
+ const char *discard;
+ apr_size_t ignore;
+
+ rv = apr_bucket_read(e, &discard, &ignore, APR_NONBLOCK_READ);
+ if (rv != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(rv)) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, f->c, APLOGNO(10232)
+ "coalesce failed to read from %s bucket",
+ e->type->name);
+ return AP_FILTER_ERROR;
+ }
+ }
+
+ if (rv == APR_SUCCESS) {
+ /* If the read above made the bucket morph, it may now fit
+ * entirely within the buffer. Otherwise, split it so it does
+ * fit. */
+ if (e->length > COALESCE_BYTES
+ || e->length + buffered + bytes > COALESCE_BYTES) {
+ rv = apr_bucket_split(e, COALESCE_BYTES - (buffered + bytes));
+ }
+
+ if (rv == APR_SUCCESS && e->length == 0) {
+ /* As above, don't count in the prefix if the bucket is
+ * now zero-length. */
+ }
+ else if (rv == APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, f->c,
+ "coalesce: adding %" APR_SIZE_T_FMT " bytes "
+ "from split %s bucket, total %" APR_SIZE_T_FMT,
+ e->length, e->type->name, bytes + buffered);
+
+ count++;
+ bytes += e->length;
+ e = APR_BUCKET_NEXT(e);
+ }
+ else if (rv != APR_ENOTIMPL) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, f->c, APLOGNO(10233)
+ "coalesce: failed to split data bucket");
+ return AP_FILTER_ERROR;
+ }
+ }
+ }
+
+ /* The prefix is zero or more buckets. upto now points to the
+ * bucket AFTER the end of the prefix, which may be the brigade
+ * sentinel. */
+ upto = e;
+
+ /* Coalesce the prefix, if any of the following are true:
+ *
+ * a) the prefix is more than one bucket
+ * OR
+ * b) the prefix is the entire brigade, which is a single bucket
+ * AND the prefix length is smaller than the buffer size,
+ * OR
+ * c) the prefix is a single bucket
+ * AND there is buffered data from a previous pass.
+ *
+ * The aim with (b) is to buffer a small bucket so it can be
+ * coalesced with future invocations of this filter. e.g. three
+ * calls each with a single 100 byte HEAP bucket should get
+ * coalesced together. But an invocation with a 8192 byte HEAP
+ * should pass through untouched.
+ */
+ if (bytes > 0
+ && (count > 1
+ || (upto == APR_BRIGADE_SENTINEL(bb)
+ && bytes < COALESCE_BYTES)
+ || (ctx && ctx->bytes > 0))) {
+ /* If coalescing some bytes, ensure a context has been
+ * created. */
+ if (!ctx) {
+ f->ctx = ctx = apr_palloc(f->c->pool, sizeof *ctx);
+ ctx->bytes = 0;
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, f->c,
+ "coalesce: have %" APR_SIZE_T_FMT " bytes, "
+ "adding %" APR_SIZE_T_FMT " more (buckets=%u)",
+ ctx->bytes, bytes, count);
+
+ /* Iterate through the prefix segment. For non-fatal errors
+ * in this loop it is safe to break out and fall back to the
+ * normal path of sending the buffer + remaining buckets in
+ * brigade. */
+ e = APR_BRIGADE_FIRST(bb);
+ while (e != upto) {
+ apr_size_t len;
+ const char *data;
+ apr_bucket *next;
+
+ if (APR_BUCKET_IS_METADATA(e)
+ || e->length == (apr_size_t)-1) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, f->c, APLOGNO(02012)
+ "unexpected %s bucket during coalesce",
+ e->type->name);
+ break; /* non-fatal error; break out */
+ }
+
+ if (e->length) {
+ apr_status_t rv;
+
+ /* A blocking read should be fine here for a
+ * known-length data bucket, rather than the usual
+ * non-block/flush/block. */
+ rv = apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
+ if (rv) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, f->c, APLOGNO(02013)
+ "coalesce failed to read from data bucket");
+ return AP_FILTER_ERROR;
+ }
+
+ /* Be paranoid. */
+ if (len > sizeof ctx->buffer
+ || (len + ctx->bytes > sizeof ctx->buffer)) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, f->c, APLOGNO(02014)
+ "unexpected coalesced bucket data length");
+ break; /* non-fatal error; break out */
+ }
+
+ memcpy(ctx->buffer + ctx->bytes, data, len);
+ ctx->bytes += len;
+ }
+
+ next = APR_BUCKET_NEXT(e);
+ apr_bucket_delete(e);
+ e = next;
+ }
+ }
+
+ if (APR_BRIGADE_EMPTY(bb)) {
+ /* If the brigade is now empty, our work here is done. */
+ return APR_SUCCESS;
+ }
+
+ /* If anything remains in the brigade, it must now be passed down
+ * the filter stack, first prepending anything that has been
+ * coalesced. */
+ if (ctx && ctx->bytes) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, f->c,
+ "coalesce: passing on %" APR_SIZE_T_FMT " bytes", ctx->bytes);
+
+ e = apr_bucket_transient_create(ctx->buffer, ctx->bytes, bb->bucket_alloc);
+ APR_BRIGADE_INSERT_HEAD(bb, e);
+ ctx->bytes = 0; /* buffer now emptied. */
+ }
+
+ return ap_pass_brigade(f->next, bb);
+}
+
+static apr_status_t ssl_io_filter_output(ap_filter_t *f,
+ apr_bucket_brigade *bb)
+{
+ apr_status_t status = APR_SUCCESS;
+ ssl_filter_ctx_t *filter_ctx = f->ctx;
+ bio_filter_in_ctx_t *inctx;
+ bio_filter_out_ctx_t *outctx;
+ apr_read_type_e rblock = APR_NONBLOCK_READ;
+
+ if (f->c->aborted) {
+ apr_brigade_cleanup(bb);
+ return APR_ECONNABORTED;
+ }
+
+ if (!filter_ctx->pssl) {
+ /* ssl_filter_io_shutdown was called */
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ inctx = (bio_filter_in_ctx_t *)BIO_get_data(filter_ctx->pbioRead);
+ outctx = (bio_filter_out_ctx_t *)BIO_get_data(filter_ctx->pbioWrite);
+
+ /* When we are the writer, we must initialize the inctx
+ * mode so that we block for any required ssl input, because
+ * output filtering is always nonblocking.
+ */
+ inctx->mode = AP_MODE_READBYTES;
+ inctx->block = APR_BLOCK_READ;
+
+ if ((status = ssl_io_filter_handshake(filter_ctx)) != APR_SUCCESS) {
+ return ssl_io_filter_error(inctx, bb, status, 0);
+ }
+
+ while (!APR_BRIGADE_EMPTY(bb) && status == APR_SUCCESS) {
+ apr_bucket *bucket = APR_BRIGADE_FIRST(bb);
+
+ if (APR_BUCKET_IS_METADATA(bucket)) {
+ /* Pass through metadata buckets untouched. EOC is
+ * special; terminate the SSL layer first. */
+ if (AP_BUCKET_IS_EOC(bucket)) {
+ ssl_filter_io_shutdown(filter_ctx, f->c, 0);
+ }
+ AP_DEBUG_ASSERT(APR_BRIGADE_EMPTY(outctx->bb));
+
+ /* Metadata buckets are passed one per brigade; it might
+ * be more efficient (but also more complex) to use
+ * outctx->bb as a true buffer and interleave these with
+ * data buckets. */
+ APR_BUCKET_REMOVE(bucket);
+ APR_BRIGADE_INSERT_HEAD(outctx->bb, bucket);
+ status = ap_pass_brigade(f->next, outctx->bb);
+ if (status == APR_SUCCESS && f->c->aborted)
+ status = APR_ECONNRESET;
+ apr_brigade_cleanup(outctx->bb);
+ }
+ else {
+ /* Filter a data bucket. */
+ const char *data;
+ apr_size_t len;
+
+ status = apr_bucket_read(bucket, &data, &len, rblock);
+
+ if (APR_STATUS_IS_EAGAIN(status)) {
+ /* No data available: flush... */
+ if (bio_filter_out_flush(filter_ctx->pbioWrite) < 0) {
+ status = outctx->rc;
+ break;
+ }
+ rblock = APR_BLOCK_READ;
+ /* and try again with a blocking read. */
+ status = APR_SUCCESS;
+ continue;
+ }
+
+ rblock = APR_NONBLOCK_READ;
+
+ if (!APR_STATUS_IS_EOF(status) && (status != APR_SUCCESS)) {
+ break;
+ }
+
+ status = ssl_filter_write(f, data, len);
+ apr_bucket_delete(bucket);
+ }
+
+ }
+
+ return status;
+}
+
+struct modssl_buffer_ctx {
+ apr_bucket_brigade *bb;
+};
+
+int ssl_io_buffer_fill(request_rec *r, apr_size_t maxlen)
+{
+ conn_rec *c = r->connection;
+ struct modssl_buffer_ctx *ctx;
+ apr_bucket_brigade *tempb;
+ apr_off_t total = 0; /* total length buffered */
+ int eos = 0; /* non-zero once EOS is seen */
+
+ /* Create the context which will be passed to the input filter;
+ * containing a setaside pool and a brigade which constrain the
+ * lifetime of the buffered data. */
+ ctx = apr_palloc(r->pool, sizeof *ctx);
+ ctx->bb = apr_brigade_create(r->pool, c->bucket_alloc);
+
+ /* ... and a temporary brigade. */
+ tempb = apr_brigade_create(r->pool, c->bucket_alloc);
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, c, "filling buffer, max size "
+ "%" APR_SIZE_T_FMT " bytes", maxlen);
+
+ do {
+ apr_status_t rv;
+ apr_bucket *e, *next;
+
+ /* The request body is read from the protocol-level input
+ * filters; the buffering filter will reinject it from that
+ * level, allowing content/resource filters to run later, if
+ * necessary. */
+
+ rv = ap_get_brigade(r->proto_input_filters, tempb, AP_MODE_READBYTES,
+ APR_BLOCK_READ, 8192);
+ if (rv) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02015)
+ "could not read request body for SSL buffer");
+ return ap_map_http_request_error(rv, HTTP_INTERNAL_SERVER_ERROR);
+ }
+
+ /* Iterate through the returned brigade: setaside each bucket
+ * into the context's pool and move it into the brigade. */
+ for (e = APR_BRIGADE_FIRST(tempb);
+ e != APR_BRIGADE_SENTINEL(tempb) && !eos; e = next) {
+ const char *data;
+ apr_size_t len;
+
+ next = APR_BUCKET_NEXT(e);
+
+ if (APR_BUCKET_IS_EOS(e)) {
+ eos = 1;
+ } else if (!APR_BUCKET_IS_METADATA(e)) {
+ rv = apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02016)
+ "could not read bucket for SSL buffer");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ total += len;
+ }
+
+ rv = apr_bucket_setaside(e, r->pool);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02017)
+ "could not setaside bucket for SSL buffer");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, c,
+ "total of %" APR_OFF_T_FMT " bytes in buffer, eos=%d",
+ total, eos);
+
+ /* Fail if this exceeds the maximum buffer size. */
+ if (total > maxlen) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02018)
+ "request body exceeds maximum size (%" APR_SIZE_T_FMT
+ ") for SSL buffer", maxlen);
+ return HTTP_REQUEST_ENTITY_TOO_LARGE;
+ }
+
+ } while (!eos);
+
+ apr_brigade_destroy(tempb);
+
+ /* After consuming all protocol-level input, remove all protocol-level
+ * filters. It should strictly only be necessary to remove filters
+ * at exactly ftype == AP_FTYPE_PROTOCOL, since this filter will
+ * precede all > AP_FTYPE_PROTOCOL anyway. */
+ while (r->proto_input_filters->frec->ftype < AP_FTYPE_CONNECTION) {
+ ap_remove_input_filter(r->proto_input_filters);
+ }
+
+ /* Insert the filter which will supply the buffered content. */
+ ap_add_input_filter(ssl_io_buffer, ctx, r, c);
+
+ return 0;
+}
+
+/* This input filter supplies the buffered request body to the caller
+ * from the brigade stored in f->ctx. Note that the placement of this
+ * filter in the filter stack is important; it must be the first
+ * r->proto_input_filter; lower-typed filters will not be preserved
+ * across internal redirects (see PR 43738). */
+static apr_status_t ssl_io_filter_buffer(ap_filter_t *f,
+ apr_bucket_brigade *bb,
+ ap_input_mode_t mode,
+ apr_read_type_e block,
+ apr_off_t bytes)
+{
+ struct modssl_buffer_ctx *ctx = f->ctx;
+ apr_status_t rv;
+ apr_bucket *e, *d;
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, f->c,
+ "read from buffered SSL brigade, mode %d, "
+ "%" APR_OFF_T_FMT " bytes",
+ mode, bytes);
+
+ if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE) {
+ return APR_ENOTIMPL;
+ }
+
+ if (APR_BRIGADE_EMPTY(ctx->bb)) {
+ /* Surprisingly (and perhaps, wrongly), the request body can be
+ * pulled from the input filter stack more than once; a
+ * handler may read it, and ap_discard_request_body() will
+ * attempt to do so again after *every* request. So input
+ * filters must be prepared to give up an EOS if invoked after
+ * initially reading the request. The HTTP_IN filter does this
+ * with its ->eos_sent flag. */
+
+ APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_eos_create(f->c->bucket_alloc));
+ return APR_SUCCESS;
+ }
+
+ if (mode == AP_MODE_READBYTES) {
+ /* Partition the buffered brigade. */
+ rv = apr_brigade_partition(ctx->bb, bytes, &e);
+ if (rv && rv != APR_INCOMPLETE) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, f->c, APLOGNO(02019)
+ "could not partition buffered SSL brigade");
+ ap_remove_input_filter(f);
+ return rv;
+ }
+
+ /* If the buffered brigade contains less then the requested
+ * length, just pass it all back. */
+ if (rv == APR_INCOMPLETE) {
+ APR_BRIGADE_CONCAT(bb, ctx->bb);
+ } else {
+ d = APR_BRIGADE_FIRST(ctx->bb);
+
+ e = APR_BUCKET_PREV(e);
+
+ /* Unsplice the partitioned segment and move it into the
+ * passed-in brigade; no convenient way to do this with
+ * the APR_BRIGADE_* macros. */
+ APR_RING_UNSPLICE(d, e, link);
+ APR_RING_SPLICE_HEAD(&bb->list, d, e, apr_bucket, link);
+
+ APR_BRIGADE_CHECK_CONSISTENCY(bb);
+ APR_BRIGADE_CHECK_CONSISTENCY(ctx->bb);
+ }
+ }
+ else {
+ /* Split a line into the passed-in brigade. */
+ rv = apr_brigade_split_line(bb, ctx->bb, block, bytes);
+
+ if (rv) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, f->c, APLOGNO(02020)
+ "could not split line from buffered SSL brigade");
+ ap_remove_input_filter(f);
+ return rv;
+ }
+ }
+
+ if (APR_BRIGADE_EMPTY(ctx->bb)) {
+ e = APR_BRIGADE_LAST(bb);
+
+ /* Ensure that the brigade is terminated by an EOS if the
+ * buffered request body has been entirely consumed. */
+ if (e == APR_BRIGADE_SENTINEL(bb) || !APR_BUCKET_IS_EOS(e)) {
+ e = apr_bucket_eos_create(f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, f->c,
+ "buffered SSL brigade exhausted");
+ /* Note that the filter must *not* be removed here; it may be
+ * invoked again, see comment above. */
+ }
+
+ return APR_SUCCESS;
+}
+
+/* The request_rec pointer is passed in here only to ensure that the
+ * filter chain is modified correctly when doing a TLS upgrade. It
+ * must *not* be used otherwise. */
+static void ssl_io_input_add_filter(ssl_filter_ctx_t *filter_ctx, conn_rec *c,
+ request_rec *r, SSL *ssl)
+{
+ bio_filter_in_ctx_t *inctx;
+
+ inctx = apr_palloc(c->pool, sizeof(*inctx));
+
+ filter_ctx->pInputFilter = ap_add_input_filter(ssl_io_filter, inctx, r, c);
+
+#if MODSSL_USE_OPENSSL_PRE_1_1_API
+ filter_ctx->pbioRead = BIO_new(&bio_filter_in_method);
+#else
+ filter_ctx->pbioRead = BIO_new(bio_filter_in_method);
+#endif
+ BIO_set_data(filter_ctx->pbioRead, (void *)inctx);
+
+ inctx->ssl = ssl;
+ inctx->bio_out = filter_ctx->pbioWrite;
+ inctx->f = filter_ctx->pInputFilter;
+ inctx->rc = APR_SUCCESS;
+ inctx->mode = AP_MODE_READBYTES;
+ inctx->cbuf.length = 0;
+ inctx->bb = apr_brigade_create(c->pool, c->bucket_alloc);
+ inctx->block = APR_BLOCK_READ;
+ inctx->pool = c->pool;
+ inctx->filter_ctx = filter_ctx;
+}
+
+/* The request_rec pointer is passed in here only to ensure that the
+ * filter chain is modified correctly when doing a TLS upgrade. It
+ * must *not* be used otherwise. */
+void ssl_io_filter_init(conn_rec *c, request_rec *r, SSL *ssl)
+{
+ ssl_filter_ctx_t *filter_ctx;
+
+ filter_ctx = apr_palloc(c->pool, sizeof(ssl_filter_ctx_t));
+
+ filter_ctx->config = myConnConfig(c);
+
+ ap_add_output_filter(ssl_io_coalesce, NULL, r, c);
+
+ filter_ctx->pOutputFilter = ap_add_output_filter(ssl_io_filter,
+ filter_ctx, r, c);
+
+#if MODSSL_USE_OPENSSL_PRE_1_1_API
+ filter_ctx->pbioWrite = BIO_new(&bio_filter_out_method);
+#else
+ filter_ctx->pbioWrite = BIO_new(bio_filter_out_method);
+#endif
+ BIO_set_data(filter_ctx->pbioWrite, (void *)bio_filter_out_ctx_new(filter_ctx, c));
+
+ /* write is non blocking for the benefit of async mpm */
+ if (c->cs) {
+ BIO_set_nbio(filter_ctx->pbioWrite, 1);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, c,
+ "Enabling non-blocking writes");
+ }
+
+ ssl_io_input_add_filter(filter_ctx, c, r, ssl);
+
+ SSL_set_bio(ssl, filter_ctx->pbioRead, filter_ctx->pbioWrite);
+ filter_ctx->pssl = ssl;
+
+ apr_pool_cleanup_register(c->pool, (void*)filter_ctx,
+ ssl_io_filter_cleanup, apr_pool_cleanup_null);
+
+ if (APLOG_CS_IS_LEVEL(c, mySrvFromConn(c), APLOG_TRACE4)) {
+ BIO *rbio = SSL_get_rbio(ssl),
+ *wbio = SSL_get_wbio(ssl);
+ BIO_set_callback(rbio, ssl_io_data_cb);
+ BIO_set_callback_arg(rbio, (void *)ssl);
+ if (wbio && wbio != rbio) {
+ BIO_set_callback(wbio, ssl_io_data_cb);
+ BIO_set_callback_arg(wbio, (void *)ssl);
+ }
+ }
+
+ return;
+}
+
+void ssl_io_filter_register(apr_pool_t *p)
+{
+ ap_register_input_filter (ssl_io_filter, ssl_io_filter_input, NULL, AP_FTYPE_CONNECTION + 5);
+ ap_register_output_filter (ssl_io_coalesce, ssl_io_filter_coalesce, NULL, AP_FTYPE_CONNECTION + 4);
+ ap_register_output_filter (ssl_io_filter, ssl_io_filter_output, NULL, AP_FTYPE_CONNECTION + 5);
+
+ ap_register_input_filter (ssl_io_buffer, ssl_io_filter_buffer, NULL, AP_FTYPE_PROTOCOL);
+
+ return;
+}
+
+/* _________________________________________________________________
+**
+** I/O Data Debugging
+** _________________________________________________________________
+*/
+
+#define DUMP_WIDTH 16
+
+static void ssl_io_data_dump(conn_rec *c, server_rec *s,
+ const char *b, long len)
+{
+ char buf[256];
+ int i, j, rows, trunc, pos;
+ unsigned char ch;
+
+ trunc = 0;
+ for (; (len > 0) && ((b[len-1] == ' ') || (b[len-1] == '\0')); len--)
+ trunc++;
+ rows = (len / DUMP_WIDTH);
+ if ((rows * DUMP_WIDTH) < len)
+ rows++;
+ ap_log_cserror(APLOG_MARK, APLOG_TRACE7, 0, c, s,
+ "+-------------------------------------------------------------------------+");
+ for (i = 0 ; i < rows; i++) {
+#if APR_CHARSET_EBCDIC
+ char ebcdic_text[DUMP_WIDTH];
+ j = DUMP_WIDTH;
+ if ((i * DUMP_WIDTH + j) > len)
+ j = len % DUMP_WIDTH;
+ if (j == 0)
+ j = DUMP_WIDTH;
+ memcpy(ebcdic_text,(char *)(b) + i * DUMP_WIDTH, j);
+ ap_xlate_proto_from_ascii(ebcdic_text, j);
+#endif /* APR_CHARSET_EBCDIC */
+ pos = 0;
+ pos += apr_snprintf(buf, sizeof(buf)-pos, "| %04x: ", i * DUMP_WIDTH);
+ for (j = 0; j < DUMP_WIDTH; j++) {
+ if (((i * DUMP_WIDTH) + j) >= len)
+ pos += apr_snprintf(buf+pos, sizeof(buf)-pos, " ");
+ else {
+ ch = ((unsigned char)*((char *)(b) + i * DUMP_WIDTH + j)) & 0xff;
+ pos += apr_snprintf(buf+pos, sizeof(buf)-pos, "%02x%c", ch , j==7 ? '-' : ' ');
+ }
+ }
+ pos += apr_snprintf(buf+pos, sizeof(buf)-pos, " ");
+ for (j = 0; j < DUMP_WIDTH; j++) {
+ if (((i * DUMP_WIDTH) + j) >= len)
+ pos += apr_snprintf(buf+pos, sizeof(buf)-pos, " ");
+ else {
+ ch = ((unsigned char)*((char *)(b) + i * DUMP_WIDTH + j)) & 0xff;
+#if APR_CHARSET_EBCDIC
+ pos += apr_snprintf(buf+pos, sizeof(buf)-pos, "%c", (ch >= 0x20 && ch <= 0x7F) ? ebcdic_text[j] : '.');
+#else /* APR_CHARSET_EBCDIC */
+ pos += apr_snprintf(buf+pos, sizeof(buf)-pos, "%c", ((ch >= ' ') && (ch <= '~')) ? ch : '.');
+#endif /* APR_CHARSET_EBCDIC */
+ }
+ }
+ pos += apr_snprintf(buf+pos, sizeof(buf)-pos, " |");
+ ap_log_cserror(APLOG_MARK, APLOG_TRACE7, 0, c, s, "%s", buf);
+ }
+ if (trunc > 0)
+ ap_log_cserror(APLOG_MARK, APLOG_TRACE7, 0, c, s,
+ "| %04ld - <SPACES/NULS>", len + trunc);
+ ap_log_cserror(APLOG_MARK, APLOG_TRACE7, 0, c, s,
+ "+-------------------------------------------------------------------------+");
+}
+
+long ssl_io_data_cb(BIO *bio, int cmd,
+ const char *argp,
+ int argi, long argl, long rc)
+{
+ SSL *ssl;
+ conn_rec *c;
+ server_rec *s;
+
+ if ((ssl = (SSL *)BIO_get_callback_arg(bio)) == NULL)
+ return rc;
+ if ((c = (conn_rec *)SSL_get_app_data(ssl)) == NULL)
+ return rc;
+ s = mySrvFromConn(c);
+
+ if ( cmd == (BIO_CB_WRITE|BIO_CB_RETURN)
+ || cmd == (BIO_CB_READ |BIO_CB_RETURN) ) {
+ if (rc >= 0) {
+ const char *dump = "";
+ if (APLOG_CS_IS_LEVEL(c, s, APLOG_TRACE7)) {
+ if (argp != NULL)
+ dump = "(BIO dump follows)";
+ else
+ dump = "(Oops, no memory buffer?)";
+ }
+ ap_log_cserror(APLOG_MARK, APLOG_TRACE4, 0, c, s,
+ "%s: %s %ld/%d bytes %s BIO#%pp [mem: %pp] %s",
+ MODSSL_LIBRARY_NAME,
+ (cmd == (BIO_CB_WRITE|BIO_CB_RETURN) ? "write" : "read"),
+ rc, argi, (cmd == (BIO_CB_WRITE|BIO_CB_RETURN) ? "to" : "from"),
+ bio, argp, dump);
+ if (*dump != '\0' && argp != NULL)
+ ssl_io_data_dump(c, s, argp, rc);
+ }
+ else {
+ ap_log_cserror(APLOG_MARK, APLOG_TRACE4, 0, c, s,
+ "%s: I/O error, %d bytes expected to %s on BIO#%pp [mem: %pp]",
+ MODSSL_LIBRARY_NAME, argi,
+ (cmd == (BIO_CB_WRITE|BIO_CB_RETURN) ? "write" : "read"),
+ bio, argp);
+ }
+ }
+ return rc;
+}
diff --git a/modules/ssl/ssl_engine_kernel.c b/modules/ssl/ssl_engine_kernel.c
new file mode 100644
index 0000000..591f6ae
--- /dev/null
+++ b/modules/ssl/ssl_engine_kernel.c
@@ -0,0 +1,2855 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* _ _
+ * _ __ ___ ___ __| | ___ ___| | mod_ssl
+ * | '_ ` _ \ / _ \ / _` | / __/ __| | Apache Interface to OpenSSL
+ * | | | | | | (_) | (_| | \__ \__ \ |
+ * |_| |_| |_|\___/ \__,_|___|___/___/_|
+ * |_____|
+ * ssl_engine_kernel.c
+ * The SSL engine kernel
+ */
+ /* ``It took me fifteen years to discover
+ I had no talent for programming, but
+ I couldn't give it up because by that
+ time I was too famous.''
+ -- Unknown */
+#include "ssl_private.h"
+#include "mod_ssl.h"
+#include "util_md5.h"
+#include "scoreboard.h"
+
+static void ssl_configure_env(request_rec *r, SSLConnRec *sslconn);
+#ifdef HAVE_TLSEXT
+static int ssl_find_vhost(void *servername, conn_rec *c, server_rec *s);
+#endif
+
+#define SWITCH_STATUS_LINE "HTTP/1.1 101 Switching Protocols"
+#define UPGRADE_HEADER "Upgrade: TLS/1.0, HTTP/1.1"
+#define CONNECTION_HEADER "Connection: Upgrade"
+
+/* Perform an upgrade-to-TLS for the given request, per RFC 2817. */
+static apr_status_t upgrade_connection(request_rec *r)
+{
+ struct conn_rec *conn = r->connection;
+ apr_bucket_brigade *bb;
+ SSLConnRec *sslconn;
+ apr_status_t rv;
+ SSL *ssl;
+
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02028)
+ "upgrading connection to TLS");
+
+ bb = apr_brigade_create(r->pool, conn->bucket_alloc);
+
+ rv = ap_fputs(conn->output_filters, bb, SWITCH_STATUS_LINE CRLF
+ UPGRADE_HEADER CRLF CONNECTION_HEADER CRLF CRLF);
+ if (rv == APR_SUCCESS) {
+ APR_BRIGADE_INSERT_TAIL(bb,
+ apr_bucket_flush_create(conn->bucket_alloc));
+ rv = ap_pass_brigade(conn->output_filters, bb);
+ }
+
+ if (rv) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02029)
+ "failed to send 101 interim response for connection "
+ "upgrade");
+ return rv;
+ }
+
+ ssl_init_ssl_connection(conn, r);
+
+ sslconn = myConnConfig(conn);
+ ssl = sslconn->ssl;
+
+ /* Perform initial SSL handshake. */
+ SSL_set_accept_state(ssl);
+ SSL_do_handshake(ssl);
+
+ if (!SSL_is_init_finished(ssl)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02030)
+ "TLS upgrade handshake failed");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, r->server);
+
+ return APR_ECONNABORTED;
+ }
+
+ return APR_SUCCESS;
+}
+
+/* Perform a speculative (and non-blocking) read from the connection
+ * filters for the given request, to determine whether there is any
+ * pending data to read. Return non-zero if there is, else zero. */
+static int has_buffered_data(request_rec *r)
+{
+ apr_bucket_brigade *bb;
+ apr_off_t len;
+ apr_status_t rv;
+ int result;
+
+ bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+
+ rv = ap_get_brigade(r->connection->input_filters, bb, AP_MODE_SPECULATIVE,
+ APR_NONBLOCK_READ, 1);
+ result = rv == APR_SUCCESS
+ && apr_brigade_length(bb, 1, &len) == APR_SUCCESS
+ && len > 0;
+
+ apr_brigade_destroy(bb);
+
+ return result;
+}
+
+/* If a renegotiation is required for the location, and the request
+ * includes a message body (and the client has not requested a "100
+ * Continue" response), then the client will be streaming the request
+ * body over the wire already. In that case, it is not possible to
+ * stop and perform a new SSL handshake immediately; once the SSL
+ * library moves to the "accept" state, it will reject the SSL packets
+ * which the client is sending for the request body.
+ *
+ * To allow authentication to complete in the hook, the solution used
+ * here is to fill a (bounded) buffer with the request body, and then
+ * to reinject that request body later.
+ *
+ * This function is called to fill the renegotiation buffer for the
+ * location as required, or fail. Returns zero on success or HTTP_
+ * error code on failure.
+ */
+static int fill_reneg_buffer(request_rec *r, SSLDirConfigRec *dc)
+{
+ int rv;
+ apr_size_t rsize;
+
+ /* ### this is HTTP/1.1 specific, special case for protocol? */
+ if (r->expecting_100 || !ap_request_has_body(r)) {
+ return 0;
+ }
+
+ rsize = dc->nRenegBufferSize == UNSET ? DEFAULT_RENEG_BUFFER_SIZE : dc->nRenegBufferSize;
+ if (rsize > 0) {
+ /* Fill the I/O buffer with the request body if possible. */
+ rv = ssl_io_buffer_fill(r, rsize);
+ }
+ else {
+ /* If the reneg buffer size is set to zero, just fail. */
+ rv = HTTP_REQUEST_ENTITY_TOO_LARGE;
+ }
+
+ return rv;
+}
+
+#ifdef HAVE_TLSEXT
+static int ap_array_same_str_set(apr_array_header_t *s1, apr_array_header_t *s2)
+{
+ int i;
+ const char *c;
+
+ if (s1 == s2) {
+ return 1;
+ }
+ else if (!s1 || !s2 || (s1->nelts != s2->nelts)) {
+ return 0;
+ }
+
+ for (i = 0; i < s1->nelts; i++) {
+ c = APR_ARRAY_IDX(s1, i, const char *);
+ if (!c || !ap_array_str_contains(s2, c)) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static int ssl_pk_server_compatible(modssl_pk_server_t *pks1,
+ modssl_pk_server_t *pks2)
+{
+ if (!pks1 || !pks2) {
+ return 0;
+ }
+ /* both have the same certificates? */
+ if ((pks1->ca_name_path != pks2->ca_name_path)
+ && (!pks1->ca_name_path || !pks2->ca_name_path
+ || strcmp(pks1->ca_name_path, pks2->ca_name_path))) {
+ return 0;
+ }
+ if ((pks1->ca_name_file != pks2->ca_name_file)
+ && (!pks1->ca_name_file || !pks2->ca_name_file
+ || strcmp(pks1->ca_name_file, pks2->ca_name_file))) {
+ return 0;
+ }
+ if (!ap_array_same_str_set(pks1->cert_files, pks2->cert_files)
+ || !ap_array_same_str_set(pks1->key_files, pks2->key_files)) {
+ return 0;
+ }
+ return 1;
+}
+
+static int ssl_auth_compatible(modssl_auth_ctx_t *a1,
+ modssl_auth_ctx_t *a2)
+{
+ if (!a1 || !a2) {
+ return 0;
+ }
+ /* both have the same verification */
+ if ((a1->verify_depth != a2->verify_depth)
+ || (a1->verify_mode != a2->verify_mode)) {
+ return 0;
+ }
+ /* both have the same ca path/file */
+ if ((a1->ca_cert_path != a2->ca_cert_path)
+ && (!a1->ca_cert_path || !a2->ca_cert_path
+ || strcmp(a1->ca_cert_path, a2->ca_cert_path))) {
+ return 0;
+ }
+ if ((a1->ca_cert_file != a2->ca_cert_file)
+ && (!a1->ca_cert_file || !a2->ca_cert_file
+ || strcmp(a1->ca_cert_file, a2->ca_cert_file))) {
+ return 0;
+ }
+ /* both have the same ca cipher suite string */
+ if ((a1->cipher_suite != a2->cipher_suite)
+ && (!a1->cipher_suite || !a2->cipher_suite
+ || strcmp(a1->cipher_suite, a2->cipher_suite))) {
+ return 0;
+ }
+ /* both have the same ca cipher suite string */
+ if ((a1->tls13_ciphers != a2->tls13_ciphers)
+ && (!a1->tls13_ciphers || !a2->tls13_ciphers
+ || strcmp(a1->tls13_ciphers, a2->tls13_ciphers))) {
+ return 0;
+ }
+ return 1;
+}
+
+static int ssl_ctx_compatible(modssl_ctx_t *ctx1,
+ modssl_ctx_t *ctx2)
+{
+ if (!ctx1 || !ctx2
+ || (ctx1->protocol != ctx2->protocol)
+ || !ssl_auth_compatible(&ctx1->auth, &ctx2->auth)
+ || !ssl_pk_server_compatible(ctx1->pks, ctx2->pks)) {
+ return 0;
+ }
+ return 1;
+}
+
+static int ssl_server_compatible(server_rec *s1, server_rec *s2)
+{
+ SSLSrvConfigRec *sc1 = s1? mySrvConfig(s1) : NULL;
+ SSLSrvConfigRec *sc2 = s2? mySrvConfig(s2) : NULL;
+
+ /* both use the same TLS protocol? */
+ if (!sc1 || !sc2
+ || !ssl_ctx_compatible(sc1->server, sc2->server)) {
+ return 0;
+ }
+
+ return 1;
+}
+#endif
+
+/*
+ * Post Read Request Handler
+ */
+int ssl_hook_ReadReq(request_rec *r)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(r->server);
+ SSLConnRec *sslconn;
+ const char *upgrade;
+#ifdef HAVE_TLSEXT
+ const char *servername;
+#endif
+ SSL *ssl;
+
+ /* Perform TLS upgrade here if "SSLEngine optional" is configured,
+ * SSL is not already set up for this connection, and the client
+ * has sent a suitable Upgrade header. */
+ if (sc->enabled == SSL_ENABLED_OPTIONAL && !myConnConfig(r->connection)
+ && (upgrade = apr_table_get(r->headers_in, "Upgrade")) != NULL
+ && ap_find_token(r->pool, upgrade, "TLS/1.0")) {
+ if (upgrade_connection(r)) {
+ return AP_FILTER_ERROR;
+ }
+ }
+
+ /* If we are on a slave connection, we do not expect to have an SSLConnRec,
+ * but our master connection might. */
+ sslconn = myConnConfig(r->connection);
+ if (!(sslconn && sslconn->ssl) && r->connection->master) {
+ sslconn = myConnConfig(r->connection->master);
+ }
+
+ /* If "SSLEngine optional" is configured, this is not an SSL
+ * connection, and this isn't a subrequest, send an Upgrade
+ * response header. Note this must happen before map_to_storage
+ * and OPTIONS * request processing is completed.
+ */
+ if (sc->enabled == SSL_ENABLED_OPTIONAL && !(sslconn && sslconn->ssl)
+ && !r->main) {
+ apr_table_setn(r->headers_out, "Upgrade", "TLS/1.0, HTTP/1.1");
+ apr_table_mergen(r->headers_out, "Connection", "upgrade");
+ }
+
+ if (!sslconn) {
+ return DECLINED;
+ }
+
+ if (sslconn->service_unavailable) {
+ /* This is set when the SSL properties of this connection are
+ * incomplete or if this connection was made to challenge a
+ * particular hostname (ACME). We never serve any request on
+ * such a connection. */
+ /* TODO: a retry-after indicator would be nice here */
+ return HTTP_SERVICE_UNAVAILABLE;
+ }
+
+ if (sslconn->non_ssl_request == NON_SSL_SET_ERROR_MSG) {
+ apr_table_setn(r->notes, "error-notes",
+ "Reason: You're speaking plain HTTP to an SSL-enabled "
+ "server port.<br />\n Instead use the HTTPS scheme to "
+ "access this URL, please.<br />\n");
+
+ /* Now that we have caught this error, forget it. we are done
+ * with using SSL on this request.
+ */
+ sslconn->non_ssl_request = NON_SSL_OK;
+
+ return HTTP_BAD_REQUEST;
+ }
+
+ /*
+ * Get the SSL connection structure and perform the
+ * delayed interlinking from SSL back to request_rec
+ */
+ ssl = sslconn->ssl;
+ if (!ssl) {
+ return DECLINED;
+ }
+#ifdef HAVE_TLSEXT
+ /*
+ * Perform SNI checks only on the initial request. In particular,
+ * if these checks detect a problem, the checks shouldn't return an
+ * error again when processing an ErrorDocument redirect for the
+ * original problem.
+ */
+ if (r->proxyreq != PROXYREQ_PROXY && ap_is_initial_req(r)) {
+ server_rec *handshakeserver = sslconn->server;
+ SSLSrvConfigRec *hssc = mySrvConfig(handshakeserver);
+
+ if ((servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name))) {
+ /*
+ * The SNI extension supplied a hostname. So don't accept requests
+ * with either no hostname or a hostname that selected a different
+ * virtual host than the one used for the handshake, causing
+ * different SSL parameters to be applied, such as SSLProtocol,
+ * SSLCACertificateFile/Path and SSLCADNRequestFile/Path which
+ * cannot be renegotiated (SSLCA* due to current limitations in
+ * OpenSSL, see:
+ * http://mail-archives.apache.org/mod_mbox/httpd-dev/200806.mbox/%3C48592955.2090303@velox.ch%3E
+ * and
+ * http://mail-archives.apache.org/mod_mbox/httpd-dev/201312.mbox/%3CCAKQ1sVNpOrdiBm-UPw1hEdSN7YQXRRjeaT-MCWbW_7mN%3DuFiOw%40mail.gmail.com%3E
+ * )
+ */
+ if (!r->hostname) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02031)
+ "Hostname %s provided via SNI, but no hostname"
+ " provided in HTTP request", servername);
+ return HTTP_BAD_REQUEST;
+ }
+ if (r->server != handshakeserver
+ && !ssl_server_compatible(sslconn->server, r->server)) {
+ /*
+ * The request does not select the virtual host that was
+ * selected by the SNI and its SSL parameters are different
+ */
+
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02032)
+ "Hostname %s provided via SNI and hostname %s provided"
+ " via HTTP have no compatible SSL setup",
+ servername, r->hostname);
+ return HTTP_MISDIRECTED_REQUEST;
+ }
+ }
+ else if (((sc->strict_sni_vhost_check == SSL_ENABLED_TRUE)
+ || hssc->strict_sni_vhost_check == SSL_ENABLED_TRUE)
+ && r->connection->vhost_lookup_data) {
+ /*
+ * We are using a name based configuration here, but no hostname was
+ * provided via SNI. Don't allow that if are requested to do strict
+ * checking. Check whether this strict checking was set up either in the
+ * server config we used for handshaking or in our current server.
+ * This should avoid insecure configuration by accident.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02033)
+ "No hostname was provided via SNI for a name based"
+ " virtual host");
+ apr_table_setn(r->notes, "error-notes",
+ "Reason: The client software did not provide a "
+ "hostname using Server Name Indication (SNI), "
+ "which is required to access this server.<br />\n");
+ return HTTP_FORBIDDEN;
+ }
+ }
+#endif
+ modssl_set_app_data2(ssl, r);
+
+ /*
+ * Log information about incoming HTTPS requests
+ */
+ if (APLOGrinfo(r) && ap_is_initial_req(r)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02034)
+ "%s HTTPS request received for child %ld (server %s)",
+ (r->connection->keepalives <= 0 ?
+ "Initial (No.1)" :
+ apr_psprintf(r->pool, "Subsequent (No.%d)",
+ r->connection->keepalives+1)),
+ r->connection->id,
+ ssl_util_vhostid(r->pool, r->server));
+ }
+
+ /* SetEnvIf ssl-*-shutdown flags can only be per-server,
+ * so they won't change across keepalive requests
+ */
+ if (sslconn->shutdown_type == SSL_SHUTDOWN_TYPE_UNSET) {
+ ssl_configure_env(r, sslconn);
+ }
+
+ return DECLINED;
+}
+
+/*
+ * Move SetEnvIf information from request_rec to conn_rec/BUFF
+ * to allow the close connection handler to use them.
+ */
+
+static void ssl_configure_env(request_rec *r, SSLConnRec *sslconn)
+{
+ int i;
+ const apr_array_header_t *arr = apr_table_elts(r->subprocess_env);
+ const apr_table_entry_t *elts = (const apr_table_entry_t *)arr->elts;
+
+ sslconn->shutdown_type = SSL_SHUTDOWN_TYPE_STANDARD;
+
+ for (i = 0; i < arr->nelts; i++) {
+ const char *key = elts[i].key;
+
+ switch (*key) {
+ case 's':
+ /* being case-sensitive here.
+ * and not checking for the -shutdown since these are the only
+ * SetEnvIf "flags" we support
+ */
+ if (!strncmp(key+1, "sl-", 3)) {
+ key += 4;
+ if (!strncmp(key, "unclean", 7)) {
+ sslconn->shutdown_type = SSL_SHUTDOWN_TYPE_UNCLEAN;
+ }
+ else if (!strncmp(key, "accurate", 8)) {
+ sslconn->shutdown_type = SSL_SHUTDOWN_TYPE_ACCURATE;
+ }
+ return; /* should only ever be one ssl-*-shutdown */
+ }
+ break;
+ }
+ }
+}
+
+static int ssl_check_post_client_verify(request_rec *r, SSLSrvConfigRec *sc,
+ SSLDirConfigRec *dc, SSLConnRec *sslconn,
+ SSL *ssl)
+{
+ X509 *cert;
+
+ /*
+ * Remember the peer certificate's DN
+ */
+ if ((cert = SSL_get_peer_certificate(ssl))) {
+ if (sslconn->client_cert) {
+ X509_free(sslconn->client_cert);
+ }
+ sslconn->client_cert = cert;
+ sslconn->client_dn = NULL;
+ }
+
+ /*
+ * Finally check for acceptable renegotiation results
+ */
+ if ((dc->nVerifyClient != SSL_CVERIFY_NONE) ||
+ (sc->server->auth.verify_mode != SSL_CVERIFY_NONE)) {
+ BOOL do_verify = ((dc->nVerifyClient == SSL_CVERIFY_REQUIRE) ||
+ (sc->server->auth.verify_mode == SSL_CVERIFY_REQUIRE));
+
+ if (do_verify && (SSL_get_verify_result(ssl) != X509_V_OK)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02262)
+ "Re-negotiation handshake failed: "
+ "Client verification failed");
+
+ return HTTP_FORBIDDEN;
+ }
+
+ if (do_verify) {
+ if (cert == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02263)
+ "Re-negotiation handshake failed: "
+ "Client certificate missing");
+
+ return HTTP_FORBIDDEN;
+ }
+ }
+ }
+ return OK;
+}
+
+/*
+ * Access Handler, classic flavour, for SSL/TLS up to v1.2
+ * where everything can be renegotiated and no one is happy.
+ */
+static int ssl_hook_Access_classic(request_rec *r, SSLSrvConfigRec *sc, SSLDirConfigRec *dc,
+ SSLConnRec *sslconn, SSL *ssl)
+{
+ server_rec *handshakeserver = sslconn ? sslconn->server : NULL;
+ SSLSrvConfigRec *hssc = handshakeserver? mySrvConfig(handshakeserver) : NULL;
+ SSL_CTX *ctx = ssl ? SSL_get_SSL_CTX(ssl) : NULL;
+ BOOL renegotiate = FALSE, renegotiate_quick = FALSE;
+ X509 *peercert;
+ X509_STORE *cert_store = NULL;
+ X509_STORE_CTX *cert_store_ctx;
+ STACK_OF(SSL_CIPHER) *cipher_list_old = NULL, *cipher_list = NULL;
+ const SSL_CIPHER *cipher = NULL;
+ int depth, verify_old, verify, n, rc;
+ const char *ncipher_suite;
+
+#ifdef HAVE_SRP
+ /*
+ * Support for per-directory reconfigured SSL connection parameters
+ *
+ * We do not force any renegotiation if the user is already authenticated
+ * via SRP.
+ *
+ */
+ if (SSL_get_srp_username(ssl)) {
+ return DECLINED;
+ }
+#endif
+
+ /*
+ * Support for per-directory reconfigured SSL connection parameters.
+ *
+ * This is implemented by forcing an SSL renegotiation with the
+ * reconfigured parameter suite. But Apache's internal API processing
+ * makes our life very hard here, because when internal sub-requests occur
+ * we nevertheless should avoid multiple unnecessary SSL handshakes (they
+ * require extra network I/O and especially time to perform).
+ *
+ * But the optimization for filtering out the unnecessary handshakes isn't
+ * obvious and trivial. Especially because while Apache is in its
+ * sub-request processing the client could force additional handshakes,
+ * too. And these take place perhaps without our notice. So the only
+ * possibility is to explicitly _ask_ OpenSSL whether the renegotiation
+ * has to be performed or not. It has to performed when some parameters
+ * which were previously known (by us) are not those we've now
+ * reconfigured (as known by OpenSSL) or (in optimized way) at least when
+ * the reconfigured parameter suite is stronger (more restrictions) than
+ * the currently active one.
+ */
+
+ /*
+ * Override of SSLCipherSuite
+ *
+ * We provide two options here:
+ *
+ * o The paranoid and default approach where we force a renegotiation when
+ * the cipher suite changed in _any_ way (which is straight-forward but
+ * often forces renegotiations too often and is perhaps not what the
+ * user actually wanted).
+ *
+ * o The optimized and still secure way where we force a renegotiation
+ * only if the currently active cipher is no longer contained in the
+ * reconfigured/new cipher suite. Any other changes are not important
+ * because it's the servers choice to select a cipher from the ones the
+ * client supports. So as long as the current cipher is still in the new
+ * cipher suite we're happy. Because we can assume we would have
+ * selected it again even when other (better) ciphers exists now in the
+ * new cipher suite. This approach is fine because the user explicitly
+ * has to enable this via ``SSLOptions +OptRenegotiate''. So we do no
+ * implicit optimizations.
+ */
+ ncipher_suite = (dc->szCipherSuite?
+ dc->szCipherSuite : (r->server != handshakeserver)?
+ sc->server->auth.cipher_suite : NULL);
+
+ if (ncipher_suite && (!sslconn->cipher_suite
+ || strcmp(ncipher_suite, sslconn->cipher_suite))) {
+ /* remember old state */
+
+ if (dc->nOptions & SSL_OPT_OPTRENEGOTIATE) {
+ cipher = SSL_get_current_cipher(ssl);
+ }
+ else {
+ cipher_list_old = (STACK_OF(SSL_CIPHER) *)SSL_get_ciphers(ssl);
+
+ if (cipher_list_old) {
+ cipher_list_old = sk_SSL_CIPHER_dup(cipher_list_old);
+ }
+ }
+
+ /* configure new state */
+ if (r->connection->master) {
+ /* TODO: this categorically fails changed cipher suite settings
+ * on slave connections. We could do better by
+ * - create a new SSL* from our SSL_CTX and set cipher suite there,
+ * and retrieve ciphers, free afterwards
+ * Modifying the SSL on a slave connection is no good.
+ */
+ apr_table_setn(r->notes, "ssl-renegotiate-forbidden", "cipher-suite");
+ return HTTP_FORBIDDEN;
+ }
+
+ if (!SSL_set_cipher_list(ssl, ncipher_suite)) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02253)
+ "Unable to reconfigure (per-directory) "
+ "permitted SSL ciphers");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, r->server);
+
+ if (cipher_list_old) {
+ sk_SSL_CIPHER_free(cipher_list_old);
+ }
+
+ return HTTP_FORBIDDEN;
+ }
+
+ /* determine whether a renegotiation has to be forced */
+ cipher_list = (STACK_OF(SSL_CIPHER) *)SSL_get_ciphers(ssl);
+
+ if (dc->nOptions & SSL_OPT_OPTRENEGOTIATE) {
+ /* optimized way */
+ if ((!cipher && cipher_list) ||
+ (cipher && !cipher_list))
+ {
+ renegotiate = TRUE;
+ }
+ else if (cipher && cipher_list &&
+ (sk_SSL_CIPHER_find(cipher_list, cipher) < 0))
+ {
+ renegotiate = TRUE;
+ }
+ }
+ else {
+ /* paranoid way */
+ if ((!cipher_list_old && cipher_list) ||
+ (cipher_list_old && !cipher_list))
+ {
+ renegotiate = TRUE;
+ }
+ else if (cipher_list_old && cipher_list) {
+ for (n = 0;
+ !renegotiate && (n < sk_SSL_CIPHER_num(cipher_list));
+ n++)
+ {
+ const SSL_CIPHER *value = sk_SSL_CIPHER_value(cipher_list, n);
+
+ if (sk_SSL_CIPHER_find(cipher_list_old, value) < 0) {
+ renegotiate = TRUE;
+ }
+ }
+
+ for (n = 0;
+ !renegotiate && (n < sk_SSL_CIPHER_num(cipher_list_old));
+ n++)
+ {
+ const SSL_CIPHER *value = sk_SSL_CIPHER_value(cipher_list_old, n);
+
+ if (sk_SSL_CIPHER_find(cipher_list, value) < 0) {
+ renegotiate = TRUE;
+ }
+ }
+ }
+ }
+
+ /* cleanup */
+ if (cipher_list_old) {
+ sk_SSL_CIPHER_free(cipher_list_old);
+ }
+
+ if (renegotiate) {
+ if (r->connection->master) {
+ /* The request causes renegotiation on a slave connection.
+ * This is not allowed since we might have concurrent requests
+ * on this connection.
+ */
+ apr_table_setn(r->notes, "ssl-renegotiate-forbidden", "cipher-suite");
+ return HTTP_FORBIDDEN;
+ }
+
+#ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
+ if (sc->cipher_server_pref == TRUE) {
+ SSL_set_options(ssl, SSL_OP_CIPHER_SERVER_PREFERENCE);
+ }
+#endif
+ /* tracing */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02220)
+ "Reconfigured cipher suite will force renegotiation");
+ }
+ }
+
+ /*
+ * override of SSLVerifyClient
+ *
+ * We force a renegotiation if the reconfigured/new verify type is
+ * stronger than the currently active verify type.
+ *
+ * The order is: none << optional_no_ca << optional << require
+ *
+ * Additionally the following optimization is possible here: When the
+ * currently active verify type is "none" but a client certificate is
+ * already known/present, it's enough to manually force a client
+ * verification but at least skip the I/O-intensive renegotiation
+ * handshake.
+ */
+ if ((dc->nVerifyClient != SSL_CVERIFY_UNSET) ||
+ (sc->server->auth.verify_mode != SSL_CVERIFY_UNSET)) {
+
+ /* remember old state */
+ verify_old = SSL_get_verify_mode(ssl);
+ /* configure new state */
+ verify = SSL_VERIFY_NONE;
+
+ if ((dc->nVerifyClient == SSL_CVERIFY_REQUIRE) ||
+ (sc->server->auth.verify_mode == SSL_CVERIFY_REQUIRE)) {
+ verify |= SSL_VERIFY_PEER_STRICT;
+ }
+
+ if ((dc->nVerifyClient == SSL_CVERIFY_OPTIONAL) ||
+ (dc->nVerifyClient == SSL_CVERIFY_OPTIONAL_NO_CA) ||
+ (sc->server->auth.verify_mode == SSL_CVERIFY_OPTIONAL) ||
+ (sc->server->auth.verify_mode == SSL_CVERIFY_OPTIONAL_NO_CA))
+ {
+ verify |= SSL_VERIFY_PEER;
+ }
+
+ /* TODO: this seems premature since we do not know if there
+ * are any changes required.
+ */
+ SSL_set_verify(ssl, verify, ssl_callback_SSLVerify);
+ SSL_set_verify_result(ssl, X509_V_OK);
+
+ /* determine whether we've to force a renegotiation */
+ if (!renegotiate && verify != verify_old) {
+ if (((verify_old == SSL_VERIFY_NONE) &&
+ (verify != SSL_VERIFY_NONE)) ||
+
+ (!(verify_old & SSL_VERIFY_PEER) &&
+ (verify & SSL_VERIFY_PEER)) ||
+
+ (!(verify_old & SSL_VERIFY_FAIL_IF_NO_PEER_CERT) &&
+ (verify & SSL_VERIFY_FAIL_IF_NO_PEER_CERT)))
+ {
+ renegotiate = TRUE;
+ if (r->connection->master) {
+ /* The request causes renegotiation on a slave connection.
+ * This is not allowed since we might have concurrent requests
+ * on this connection.
+ */
+ apr_table_setn(r->notes, "ssl-renegotiate-forbidden", "verify-client");
+ SSL_set_verify(ssl, verify_old, ssl_callback_SSLVerify);
+ return HTTP_FORBIDDEN;
+ }
+ /* optimization */
+
+ if ((dc->nOptions & SSL_OPT_OPTRENEGOTIATE) &&
+ (verify_old == SSL_VERIFY_NONE) &&
+ ((peercert = SSL_get_peer_certificate(ssl)) != NULL))
+ {
+ renegotiate_quick = TRUE;
+ X509_free(peercert);
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02255)
+ "Changed client verification type will force "
+ "%srenegotiation",
+ renegotiate_quick ? "quick " : "");
+ }
+ else if (verify != SSL_VERIFY_NONE) {
+ /*
+ * override of SSLVerifyDepth
+ *
+ * The depth checks are handled by us manually inside the
+ * verify callback function and not by OpenSSL internally
+ * (and our function is aware of both the per-server and
+ * per-directory contexts). So we cannot ask OpenSSL about
+ * the currently verify depth. Instead we remember it in our
+ * SSLConnRec attached to the SSL* of OpenSSL. We've to force
+ * the renegotiation if the reconfigured/new verify depth is
+ * less than the currently active/remembered verify depth
+ * (because this means more restriction on the certificate
+ * chain).
+ */
+ n = (sslconn->verify_depth != UNSET)
+ ? sslconn->verify_depth
+ : hssc->server->auth.verify_depth;
+ /* determine the new depth */
+ sslconn->verify_depth = (dc->nVerifyDepth != UNSET)
+ ? dc->nVerifyDepth
+ : sc->server->auth.verify_depth;
+ if (sslconn->verify_depth < n) {
+ renegotiate = TRUE;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02254)
+ "Reduced client verification depth will "
+ "force renegotiation");
+ }
+ }
+ }
+ /* If we're handling a request for a vhost other than the default one,
+ * then we need to make sure that client authentication is properly
+ * enforced. For clients supplying an SNI extension, the peer
+ * certificate verification has happened in the handshake already
+ * (and r->server == handshakeserver). For non-SNI requests,
+ * an additional check is needed here. If client authentication
+ * is configured as mandatory, then we can only proceed if the
+ * CA list doesn't have to be changed (OpenSSL doesn't provide
+ * an option to change the list for an existing session).
+ */
+ if ((r->server != handshakeserver)
+ && renegotiate
+ && ((verify & SSL_VERIFY_PEER) ||
+ (verify & SSL_VERIFY_FAIL_IF_NO_PEER_CERT))) {
+#define MODSSL_CFG_CA_NE(f, sc1, sc2) \
+ (sc1->server->auth.f && \
+ (!sc2->server->auth.f || \
+ strNE(sc1->server->auth.f, sc2->server->auth.f)))
+
+ if (MODSSL_CFG_CA_NE(ca_cert_file, sc, hssc) ||
+ MODSSL_CFG_CA_NE(ca_cert_path, sc, hssc)) {
+ if (verify & SSL_VERIFY_FAIL_IF_NO_PEER_CERT) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02256)
+ "Non-default virtual host with SSLVerify set to "
+ "'require' and VirtualHost-specific CA certificate "
+ "list is only available to clients with TLS server "
+ "name indication (SNI) support");
+ SSL_set_verify(ssl, verify_old, NULL);
+ return HTTP_FORBIDDEN;
+ } else
+ /* let it pass, possibly with an "incorrect" peer cert,
+ * so make sure the SSL_CLIENT_VERIFY environment variable
+ * will indicate partial success only, later on.
+ */
+ sslconn->verify_info = "GENEROUS";
+ }
+ }
+ }
+
+ /* Fill reneg buffer if required. */
+ if (renegotiate && !renegotiate_quick) {
+ rc = fill_reneg_buffer(r, dc);
+ if (rc) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02257)
+ "could not buffer message body to allow "
+ "SSL renegotiation to proceed");
+ return rc;
+ }
+ }
+
+ /*
+ * now do the renegotiation if anything was actually reconfigured
+ */
+ if (renegotiate) {
+ /*
+ * Now we force the SSL renegotiation by sending the Hello Request
+ * message to the client. Here we have to do a workaround: Actually
+ * OpenSSL returns immediately after sending the Hello Request (the
+ * intent AFAIK is because the SSL/TLS protocol says it's not a must
+ * that the client replies to a Hello Request). But because we insist
+ * on a reply (anything else is an error for us) we have to go to the
+ * ACCEPT state manually. Using SSL_set_accept_state() doesn't work
+ * here because it resets too much of the connection. So we set the
+ * state explicitly and continue the handshake manually.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02221)
+ "Requesting connection re-negotiation");
+
+ if (renegotiate_quick) {
+ STACK_OF(X509) *cert_stack;
+ X509 *cert;
+
+ /* perform just a manual re-verification of the peer */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02258)
+ "Performing quick renegotiation: "
+ "just re-verifying the peer");
+
+ cert_stack = (STACK_OF(X509) *)SSL_get_peer_cert_chain(ssl);
+
+ cert = SSL_get_peer_certificate(ssl);
+
+ if (!cert_stack || (sk_X509_num(cert_stack) == 0)) {
+ if (!cert) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02222)
+ "Cannot find peer certificate chain");
+
+ return HTTP_FORBIDDEN;
+ }
+
+ /* client cert is in the session cache, but there is
+ * no chain, since ssl3_get_client_certificate()
+ * sk_X509_shift-ed the peer cert out of the chain.
+ * we put it back here for the purpose of quick_renegotiation.
+ */
+ cert_stack = sk_X509_new_null();
+ sk_X509_push(cert_stack, cert);
+ }
+
+ if (!(cert_store ||
+ (cert_store = SSL_CTX_get_cert_store(ctx))))
+ {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02223)
+ "Cannot find certificate storage");
+
+ return HTTP_FORBIDDEN;
+ }
+
+ if (!cert) {
+ cert = sk_X509_value(cert_stack, 0);
+ }
+
+ cert_store_ctx = X509_STORE_CTX_new();
+ X509_STORE_CTX_init(cert_store_ctx, cert_store, cert, cert_stack);
+ depth = SSL_get_verify_depth(ssl);
+
+ if (depth >= 0) {
+ X509_STORE_CTX_set_depth(cert_store_ctx, depth);
+ }
+
+ X509_STORE_CTX_set_ex_data(cert_store_ctx,
+ SSL_get_ex_data_X509_STORE_CTX_idx(),
+ (char *)ssl);
+
+ if (!X509_verify_cert(cert_store_ctx)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02224)
+ "Re-negotiation verification step failed");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, r->server);
+ }
+
+ SSL_set_verify_result(ssl, X509_STORE_CTX_get_error(cert_store_ctx));
+ X509_STORE_CTX_cleanup(cert_store_ctx);
+ X509_STORE_CTX_free(cert_store_ctx);
+
+ if (cert_stack != SSL_get_peer_cert_chain(ssl)) {
+ /* we created this ourselves, so free it */
+ sk_X509_pop_free(cert_stack, X509_free);
+ }
+ }
+ else {
+ char peekbuf[1];
+ const char *reneg_support;
+ request_rec *id = r->main ? r->main : r;
+
+ /* Additional mitigation for CVE-2009-3555: At this point,
+ * before renegotiating, an (entire) request has been read
+ * from the connection. An attacker may have sent further
+ * data to "prefix" any subsequent request by the victim's
+ * client after the renegotiation; this data may already
+ * have been read and buffered. Forcing a connection
+ * closure after the response ensures such data will be
+ * discarded. Legimately pipelined HTTP requests will be
+ * retried anyway with this approach. */
+ if (has_buffered_data(r)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02259)
+ "insecure SSL re-negotiation required, but "
+ "a pipelined request is present; keepalive "
+ "disabled");
+ r->connection->keepalive = AP_CONN_CLOSE;
+ }
+
+#if defined(SSL_get_secure_renegotiation_support)
+ reneg_support = SSL_get_secure_renegotiation_support(ssl) ?
+ "client does" : "client does not";
+#else
+ reneg_support = "server does not";
+#endif
+ /* Perform a full renegotiation. */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02260)
+ "Performing full renegotiation: complete handshake "
+ "protocol (%s support secure renegotiation)",
+ reneg_support);
+
+ SSL_set_session_id_context(ssl,
+ (unsigned char *)&id,
+ sizeof(id));
+
+ /* Toggle the renegotiation state to allow the new
+ * handshake to proceed. */
+ sslconn->reneg_state = RENEG_ALLOW;
+
+ SSL_renegotiate(ssl);
+ SSL_do_handshake(ssl);
+
+ if (!SSL_is_init_finished(ssl)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02225)
+ "Re-negotiation request failed");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, r->server);
+
+ r->connection->keepalive = AP_CONN_CLOSE;
+ return HTTP_FORBIDDEN;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02226)
+ "Awaiting re-negotiation handshake");
+
+ /* XXX: Should replace setting state with SSL_renegotiate(ssl);
+ * However, this causes failures in perl-framework currently,
+ * perhaps pre-test if we have already negotiated?
+ */
+ /* Need to trigger renegotiation handshake by reading.
+ * Peeking 0 bytes actually works.
+ * See: http://marc.info/?t=145493359200002&r=1&w=2
+ */
+ SSL_peek(ssl, peekbuf, 0);
+
+ sslconn->reneg_state = RENEG_REJECT;
+
+ if (!SSL_is_init_finished(ssl)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02261)
+ "Re-negotiation handshake failed");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, r->server);
+
+ r->connection->keepalive = AP_CONN_CLOSE;
+ return HTTP_FORBIDDEN;
+ }
+
+ /* Full renegotiation successful, we now have handshaken with
+ * this server's parameters.
+ */
+ sslconn->server = r->server;
+ }
+
+ /*
+ * Finally check for acceptable renegotiation results
+ */
+ if (OK != (rc = ssl_check_post_client_verify(r, sc, dc, sslconn, ssl))) {
+ return rc;
+ }
+
+ /*
+ * Also check that SSLCipherSuite has been enforced as expected.
+ */
+ if (cipher_list) {
+ cipher = SSL_get_current_cipher(ssl);
+ if (sk_SSL_CIPHER_find(cipher_list, cipher) < 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02264)
+ "SSL cipher suite not renegotiated: "
+ "access to %s denied using cipher %s",
+ r->filename,
+ SSL_CIPHER_get_name(cipher));
+ return HTTP_FORBIDDEN;
+ }
+ }
+ /* remember any new cipher suite used in renegotiation */
+ if (ncipher_suite) {
+ sslconn->cipher_suite = ncipher_suite;
+ }
+ }
+
+ return DECLINED;
+}
+
+#if SSL_HAVE_PROTOCOL_TLSV1_3
+/*
+ * Access Handler, modern flavour, for SSL/TLS v1.3 and onward.
+ * Only client certificates can be requested, everything else stays.
+ */
+static int ssl_hook_Access_modern(request_rec *r, SSLSrvConfigRec *sc, SSLDirConfigRec *dc,
+ SSLConnRec *sslconn, SSL *ssl)
+{
+ if ((dc->nVerifyClient != SSL_CVERIFY_UNSET) ||
+ (sc->server->auth.verify_mode != SSL_CVERIFY_UNSET)) {
+ int vmode_inplace, vmode_needed;
+ int change_vmode = FALSE;
+ int old_state, n, rc;
+
+ vmode_inplace = SSL_get_verify_mode(ssl);
+ vmode_needed = SSL_VERIFY_NONE;
+
+ if ((dc->nVerifyClient == SSL_CVERIFY_REQUIRE) ||
+ (sc->server->auth.verify_mode == SSL_CVERIFY_REQUIRE)) {
+ vmode_needed |= SSL_VERIFY_PEER_STRICT;
+ }
+
+ if ((dc->nVerifyClient == SSL_CVERIFY_OPTIONAL) ||
+ (dc->nVerifyClient == SSL_CVERIFY_OPTIONAL_NO_CA) ||
+ (sc->server->auth.verify_mode == SSL_CVERIFY_OPTIONAL) ||
+ (sc->server->auth.verify_mode == SSL_CVERIFY_OPTIONAL_NO_CA))
+ {
+ vmode_needed |= SSL_VERIFY_PEER;
+ }
+
+ if (vmode_needed == SSL_VERIFY_NONE) {
+ return DECLINED;
+ }
+
+ vmode_needed |= SSL_VERIFY_CLIENT_ONCE;
+ if (vmode_inplace != vmode_needed) {
+ /* Need to change, if new setting is more restrictive than existing one */
+
+ if ((vmode_inplace == SSL_VERIFY_NONE)
+ || (!(vmode_inplace & SSL_VERIFY_PEER)
+ && (vmode_needed & SSL_VERIFY_PEER))
+ || (!(vmode_inplace & SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
+ && (vmode_needed & SSL_VERIFY_FAIL_IF_NO_PEER_CERT))) {
+ /* need to change the effective verify mode */
+ change_vmode = TRUE;
+ }
+ else {
+ /* FIXME: does this work with TLSv1.3? Is this more than re-inspecting
+ * the certificate we should already have? */
+ /*
+ * override of SSLVerifyDepth
+ *
+ * The depth checks are handled by us manually inside the
+ * verify callback function and not by OpenSSL internally
+ * (and our function is aware of both the per-server and
+ * per-directory contexts). So we cannot ask OpenSSL about
+ * the currently verify depth. Instead we remember it in our
+ * SSLConnRec attached to the SSL* of OpenSSL. We've to force
+ * the renegotiation if the reconfigured/new verify depth is
+ * less than the currently active/remembered verify depth
+ * (because this means more restriction on the certificate
+ * chain).
+ */
+ n = (sslconn->verify_depth != UNSET)?
+ sslconn->verify_depth : sc->server->auth.verify_depth;
+ /* determine the new depth */
+ sslconn->verify_depth = (dc->nVerifyDepth != UNSET)
+ ? dc->nVerifyDepth
+ : sc->server->auth.verify_depth;
+ if (sslconn->verify_depth < n) {
+ change_vmode = TRUE;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(10128)
+ "Reduced client verification depth will "
+ "force renegotiation");
+ }
+ }
+ }
+
+ /* Fill reneg buffer if required. */
+ if (change_vmode) {
+ char peekbuf[1];
+
+ if (r->connection->master) {
+ /* FIXME: modifying the SSL on a slave connection is no good.
+ * We would need to push this back to the master connection
+ * somehow.
+ */
+ apr_table_setn(r->notes, "ssl-renegotiate-forbidden", "verify-client");
+ return HTTP_FORBIDDEN;
+ }
+
+ rc = fill_reneg_buffer(r, dc);
+ if (rc) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10228)
+ "could not buffer message body to allow "
+ "TLS Post-Handshake Authentication to proceed");
+ return rc;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(10129)
+ "verify client post handshake");
+
+ SSL_set_verify(ssl, vmode_needed, ssl_callback_SSLVerify);
+
+ if (SSL_verify_client_post_handshake(ssl) != 1) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10158)
+ "cannot perform post-handshake authentication");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, r->server);
+ apr_table_setn(r->notes, "error-notes",
+ "Reason: Cannot perform Post-Handshake Authentication.<br />");
+ SSL_set_verify(ssl, vmode_inplace, NULL);
+ return HTTP_FORBIDDEN;
+ }
+
+ old_state = sslconn->reneg_state;
+ sslconn->reneg_state = RENEG_ALLOW;
+ modssl_set_app_data2(ssl, r);
+
+ SSL_do_handshake(ssl);
+ /* Need to trigger renegotiation handshake by reading.
+ * Peeking 0 bytes actually works.
+ * See: http://marc.info/?t=145493359200002&r=1&w=2
+ */
+ SSL_peek(ssl, peekbuf, 0);
+
+ sslconn->reneg_state = old_state;
+ modssl_set_app_data2(ssl, NULL);
+
+ /*
+ * Finally check for acceptable renegotiation results
+ */
+ if (OK != (rc = ssl_check_post_client_verify(r, sc, dc, sslconn, ssl))) {
+ SSL_set_verify(ssl, vmode_inplace, NULL);
+ return rc;
+ }
+ }
+ }
+
+ return DECLINED;
+}
+#endif
+
+int ssl_hook_Access(request_rec *r)
+{
+ SSLDirConfigRec *dc = myDirConfig(r);
+ SSLSrvConfigRec *sc = mySrvConfig(r->server);
+ SSLConnRec *sslconn = myConnConfig(r->connection);
+ SSL *ssl = sslconn ? sslconn->ssl : NULL;
+ apr_array_header_t *requires;
+ ssl_require_t *ssl_requires;
+ int ok, i, ret;
+
+ /* On a slave connection, we do not expect to have an SSLConnRec, but
+ * our master connection might have one. */
+ if (!(sslconn && ssl) && r->connection->master) {
+ sslconn = myConnConfig(r->connection->master);
+ ssl = sslconn ? sslconn->ssl : NULL;
+ }
+
+ /*
+ * We should have handshaken here, otherwise we are being
+ * redirected (ErrorDocument) from a renegotiation failure below.
+ * The access is still forbidden in the latter case, let ap_die() handle
+ * this recursive (same) error.
+ */
+ if (ssl && !SSL_is_init_finished(ssl)) {
+ return HTTP_FORBIDDEN;
+ }
+
+ /*
+ * Support for SSLRequireSSL directive
+ */
+ if (dc->bSSLRequired && !ssl) {
+ if ((sc->enabled == SSL_ENABLED_OPTIONAL) && !r->connection->master) {
+ /* This vhost was configured for optional SSL, just tell the
+ * client that we need to upgrade.
+ */
+ apr_table_setn(r->err_headers_out, "Upgrade", "TLS/1.0, HTTP/1.1");
+ apr_table_setn(r->err_headers_out, "Connection", "Upgrade");
+
+ return HTTP_UPGRADE_REQUIRED;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02219)
+ "access to %s failed, reason: %s",
+ r->filename, "SSL connection required");
+
+ /* remember forbidden access for strict require option */
+ apr_table_setn(r->notes, "ssl-access-forbidden", "1");
+
+ return HTTP_FORBIDDEN;
+ }
+
+ /*
+ * Check to see whether SSL is in use; if it's not, then no
+ * further access control checks are relevant. (the test for
+ * sc->enabled is probably strictly unnecessary)
+ */
+ if (sc->enabled == SSL_ENABLED_FALSE || !ssl) {
+ return DECLINED;
+ }
+
+#if SSL_HAVE_PROTOCOL_TLSV1_3
+ /* TLSv1.3+ is less complicated here. Branch off into a new codeline
+ * and avoid messing with the past. */
+ if (SSL_version(ssl) >= TLS1_3_VERSION) {
+ ret = ssl_hook_Access_modern(r, sc, dc, sslconn, ssl);
+ }
+ else
+#endif
+ {
+ ret = ssl_hook_Access_classic(r, sc, dc, sslconn, ssl);
+ }
+
+ if (ret != DECLINED) {
+ return ret;
+ }
+
+ /* If we're trying to have the user name set from a client
+ * certificate then we need to set it here. This should be safe as
+ * the user name probably isn't important from an auth checking point
+ * of view as the certificate supplied acts in that capacity.
+ * However, if FakeAuth is being used then this isn't the case so
+ * we need to postpone setting the username until later.
+ */
+ if ((dc->nOptions & SSL_OPT_FAKEBASICAUTH) == 0 && dc->szUserName) {
+ char *val = ssl_var_lookup(r->pool, r->server, r->connection,
+ r, (char *)dc->szUserName);
+ if (val && val[0])
+ r->user = val;
+ else
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02227)
+ "Failed to set r->user to '%s'", dc->szUserName);
+ }
+
+ /*
+ * Check SSLRequire boolean expressions
+ */
+ requires = dc->aRequirement;
+ ssl_requires = (ssl_require_t *)requires->elts;
+
+ for (i = 0; i < requires->nelts; i++) {
+ ssl_require_t *req = &ssl_requires[i];
+ const char *errstring;
+ ok = ap_expr_exec(r, req->mpExpr, &errstring);
+
+ if (ok < 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02265)
+ "access to %s failed, reason: Failed to execute "
+ "SSL requirement expression: %s",
+ r->filename, errstring);
+
+ /* remember forbidden access for strict require option */
+ apr_table_setn(r->notes, "ssl-access-forbidden", "1");
+
+ return HTTP_FORBIDDEN;
+ }
+
+ if (ok != 1) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02266)
+ "Access to %s denied for %s "
+ "(requirement expression not fulfilled)",
+ r->filename, r->useragent_ip);
+
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02228)
+ "Failed expression: %s", req->cpExpr);
+
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02229)
+ "access to %s failed, reason: %s",
+ r->filename,
+ "SSL requirement expression not fulfilled");
+
+ /* remember forbidden access for strict require option */
+ apr_table_setn(r->notes, "ssl-access-forbidden", "1");
+
+ return HTTP_FORBIDDEN;
+ }
+ }
+
+ /*
+ * Else access is granted from our point of view (except vendor
+ * handlers override). But we have to return DECLINED here instead
+ * of OK, because mod_auth and other modules still might want to
+ * deny access.
+ */
+
+ return DECLINED;
+}
+
+/*
+ * Authentication Handler:
+ * Fake a Basic authentication from the X509 client certificate.
+ *
+ * This must be run fairly early on to prevent a real authentication from
+ * occurring, in particular it must be run before anything else that
+ * authenticates a user. This means that the Module statement for this
+ * module should be LAST in the Configuration file.
+ */
+int ssl_hook_UserCheck(request_rec *r)
+{
+ SSLConnRec *sslconn;
+ SSLDirConfigRec *dc = myDirConfig(r);
+ char *clientdn;
+ const char *auth_line, *username, *password;
+
+ /*
+ * Additionally forbid access (again)
+ * when strict require option is used.
+ */
+ if ((dc->nOptions & SSL_OPT_STRICTREQUIRE) &&
+ (apr_table_get(r->notes, "ssl-access-forbidden")))
+ {
+ return HTTP_FORBIDDEN;
+ }
+
+ /*
+ * We decline when we are in a subrequest. The Authorization header
+ * would already be present if it was added in the main request.
+ */
+ if (!ap_is_initial_req(r)) {
+ return DECLINED;
+ }
+
+ /*
+ * Make sure the user is not able to fake the client certificate
+ * based authentication by just entering an X.509 Subject DN
+ * ("/XX=YYY/XX=YYY/..") as the username and "password" as the
+ * password.
+ */
+ if ((auth_line = apr_table_get(r->headers_in, "Authorization"))) {
+ if (strcEQ(ap_getword(r->pool, &auth_line, ' '), "Basic")) {
+ while ((*auth_line == ' ') || (*auth_line == '\t')) {
+ auth_line++;
+ }
+
+ auth_line = ap_pbase64decode(r->pool, auth_line);
+ username = ap_getword_nulls(r->pool, &auth_line, ':');
+ password = auth_line;
+
+ if ((username[0] == '/') && strEQ(password, "password")) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02035)
+ "Encountered FakeBasicAuth spoof: %s", username);
+ return HTTP_FORBIDDEN;
+ }
+ }
+ }
+
+ /*
+ * We decline operation in various situations...
+ * - TLS not enabled
+ * - client did not present a certificate
+ * - SSLOptions +FakeBasicAuth not configured
+ * - r->user already authenticated
+ */
+ if (!modssl_request_is_tls(r, &sslconn)
+ || !sslconn->client_cert
+ || !(dc->nOptions & SSL_OPT_FAKEBASICAUTH)
+ || r->user) {
+ return DECLINED;
+ }
+
+ if (!sslconn->client_dn) {
+ X509_NAME *name = X509_get_subject_name(sslconn->client_cert);
+ char *cp = X509_NAME_oneline(name, NULL, 0);
+ sslconn->client_dn = apr_pstrdup(r->connection->pool, cp);
+ OPENSSL_free(cp);
+ }
+
+ clientdn = (char *)sslconn->client_dn;
+
+ /*
+ * Fake a password - which one would be immaterial, as, it seems, an empty
+ * password in the users file would match ALL incoming passwords, if only
+ * we were using the standard crypt library routine. Unfortunately, OpenSSL
+ * "fixes" a "bug" in crypt and thus prevents blank passwords from
+ * working. (IMHO what they really fix is a bug in the users of the code
+ * - failing to program correctly for shadow passwords). We need,
+ * therefore, to provide a password. This password can be matched by
+ * adding the string "xxj31ZMTZzkVA" as the password in the user file.
+ * This is just the crypted variant of the word "password" ;-)
+ */
+ auth_line = apr_pstrcat(r->pool, "Basic ",
+ ap_pbase64encode(r->pool,
+ apr_pstrcat(r->pool, clientdn,
+ ":password", NULL)),
+ NULL);
+ apr_table_setn(r->headers_in, "Authorization", auth_line);
+
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02036)
+ "Faking HTTP Basic Auth header: \"Authorization: %s\"",
+ auth_line);
+
+ return DECLINED;
+}
+
+/* authorization phase */
+int ssl_hook_Auth(request_rec *r)
+{
+ SSLDirConfigRec *dc = myDirConfig(r);
+
+ /*
+ * Additionally forbid access (again)
+ * when strict require option is used.
+ */
+ if ((dc->nOptions & SSL_OPT_STRICTREQUIRE) &&
+ (apr_table_get(r->notes, "ssl-access-forbidden")))
+ {
+ return HTTP_FORBIDDEN;
+ }
+
+ return DECLINED;
+}
+
+/*
+ * Fixup Handler
+ */
+
+static const char *const ssl_hook_Fixup_vars[] = {
+ "SSL_VERSION_INTERFACE",
+ "SSL_VERSION_LIBRARY",
+ "SSL_PROTOCOL",
+ "SSL_SECURE_RENEG",
+ "SSL_COMPRESS_METHOD",
+ "SSL_CIPHER",
+ "SSL_CIPHER_EXPORT",
+ "SSL_CIPHER_USEKEYSIZE",
+ "SSL_CIPHER_ALGKEYSIZE",
+ "SSL_CLIENT_VERIFY",
+ "SSL_CLIENT_M_VERSION",
+ "SSL_CLIENT_M_SERIAL",
+ "SSL_CLIENT_V_START",
+ "SSL_CLIENT_V_END",
+ "SSL_CLIENT_V_REMAIN",
+ "SSL_CLIENT_S_DN",
+ "SSL_CLIENT_I_DN",
+ "SSL_CLIENT_A_KEY",
+ "SSL_CLIENT_A_SIG",
+ "SSL_CLIENT_CERT_RFC4523_CEA",
+ "SSL_SERVER_M_VERSION",
+ "SSL_SERVER_M_SERIAL",
+ "SSL_SERVER_V_START",
+ "SSL_SERVER_V_END",
+ "SSL_SERVER_S_DN",
+ "SSL_SERVER_I_DN",
+ "SSL_SERVER_A_KEY",
+ "SSL_SERVER_A_SIG",
+ "SSL_SESSION_ID",
+ "SSL_SESSION_RESUMED",
+#ifdef HAVE_SRP
+ "SSL_SRP_USER",
+ "SSL_SRP_USERINFO",
+#endif
+ NULL
+};
+
+int ssl_hook_Fixup(request_rec *r)
+{
+ SSLDirConfigRec *dc = myDirConfig(r);
+ apr_table_t *env = r->subprocess_env;
+ char *var, *val = "";
+#ifdef HAVE_TLSEXT
+ const char *servername;
+#endif
+ STACK_OF(X509) *peer_certs;
+ SSLConnRec *sslconn;
+ SSL *ssl;
+ int i;
+
+ if (!modssl_request_is_tls(r, &sslconn)) {
+ return DECLINED;
+ }
+ ssl = sslconn->ssl;
+
+ /*
+ * Annotate the SSI/CGI environment with standard SSL information
+ */
+ /* the always present HTTPS (=HTTP over SSL) flag! */
+ apr_table_setn(env, "HTTPS", "on");
+
+#ifdef HAVE_TLSEXT
+ /* add content of SNI TLS extension (if supplied with ClientHello) */
+ if ((servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name))) {
+ apr_table_set(env, "SSL_TLS_SNI", servername);
+ }
+#endif
+
+ /* standard SSL environment variables */
+ if (dc->nOptions & SSL_OPT_STDENVVARS) {
+ modssl_var_extract_dns(env, ssl, r->pool);
+ modssl_var_extract_san_entries(env, ssl, r->pool);
+
+ for (i = 0; ssl_hook_Fixup_vars[i]; i++) {
+ var = (char *)ssl_hook_Fixup_vars[i];
+ val = ssl_var_lookup(r->pool, r->server, r->connection, r, var);
+ if (!strIsEmpty(val)) {
+ apr_table_setn(env, var, val);
+ }
+ }
+ }
+
+ /*
+ * On-demand bloat up the SSI/CGI environment with certificate data
+ */
+ if (dc->nOptions & SSL_OPT_EXPORTCERTDATA) {
+ val = ssl_var_lookup(r->pool, r->server, r->connection,
+ r, "SSL_SERVER_CERT");
+
+ apr_table_setn(env, "SSL_SERVER_CERT", val);
+
+ val = ssl_var_lookup(r->pool, r->server, r->connection,
+ r, "SSL_CLIENT_CERT");
+
+ apr_table_setn(env, "SSL_CLIENT_CERT", val);
+
+ if ((peer_certs = (STACK_OF(X509) *)SSL_get_peer_cert_chain(ssl))) {
+ for (i = 0; i < sk_X509_num(peer_certs); i++) {
+ var = apr_psprintf(r->pool, "SSL_CLIENT_CERT_CHAIN_%d", i);
+ val = ssl_var_lookup(r->pool, r->server, r->connection,
+ r, var);
+ if (val) {
+ apr_table_setn(env, var, val);
+ }
+ }
+ }
+ }
+
+
+#ifdef SSL_get_secure_renegotiation_support
+ apr_table_setn(r->notes, "ssl-secure-reneg",
+ SSL_get_secure_renegotiation_support(ssl) ? "1" : "0");
+#endif
+
+ return DECLINED;
+}
+
+/* _________________________________________________________________
+**
+** Authz providers for use with mod_authz_core
+** _________________________________________________________________
+*/
+
+static authz_status ssl_authz_require_ssl_check(request_rec *r,
+ const char *require_line,
+ const void *parsed)
+{
+ if (modssl_request_is_tls(r, NULL))
+ return AUTHZ_GRANTED;
+ else
+ return AUTHZ_DENIED;
+}
+
+static const char *ssl_authz_require_ssl_parse(cmd_parms *cmd,
+ const char *require_line,
+ const void **parsed)
+{
+ if (require_line && require_line[0])
+ return "'Require ssl' does not take arguments";
+
+ return NULL;
+}
+
+const authz_provider ssl_authz_provider_require_ssl =
+{
+ &ssl_authz_require_ssl_check,
+ &ssl_authz_require_ssl_parse,
+};
+
+static authz_status ssl_authz_verify_client_check(request_rec *r,
+ const char *require_line,
+ const void *parsed)
+{
+ SSLConnRec *sslconn = myConnConfig(r->connection);
+ SSL *ssl = sslconn ? sslconn->ssl : NULL;
+
+ if (!ssl)
+ return AUTHZ_DENIED;
+
+ if (sslconn->verify_error == NULL &&
+ sslconn->verify_info == NULL &&
+ SSL_get_verify_result(ssl) == X509_V_OK)
+ {
+ X509 *xs = SSL_get_peer_certificate(ssl);
+
+ if (xs) {
+ X509_free(xs);
+ return AUTHZ_GRANTED;
+ }
+ else {
+ X509_free(xs);
+ }
+ }
+
+ return AUTHZ_DENIED;
+}
+
+static const char *ssl_authz_verify_client_parse(cmd_parms *cmd,
+ const char *require_line,
+ const void **parsed)
+{
+ if (require_line && require_line[0])
+ return "'Require ssl-verify-client' does not take arguments";
+
+ return NULL;
+}
+
+const authz_provider ssl_authz_provider_verify_client =
+{
+ &ssl_authz_verify_client_check,
+ &ssl_authz_verify_client_parse,
+};
+
+
+
+/* _________________________________________________________________
+**
+** OpenSSL Callback Functions
+** _________________________________________________________________
+*/
+
+#if MODSSL_USE_OPENSSL_PRE_1_1_API
+/*
+ * Hand out standard DH parameters, based on the authentication strength
+ */
+DH *ssl_callback_TmpDH(SSL *ssl, int export, int keylen)
+{
+ conn_rec *c = (conn_rec *)SSL_get_app_data(ssl);
+ EVP_PKEY *pkey;
+ int type;
+
+#ifdef SSL_CERT_SET_SERVER
+ /*
+ * When multiple certs/keys are configured for the SSL_CTX: make sure
+ * that we get the private key which is indeed used for the current
+ * SSL connection (available in OpenSSL 1.0.2 or later only)
+ */
+ SSL_set_current_cert(ssl, SSL_CERT_SET_SERVER);
+#endif
+ pkey = SSL_get_privatekey(ssl);
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ type = pkey ? EVP_PKEY_type(pkey->type) : EVP_PKEY_NONE;
+#else
+ type = pkey ? EVP_PKEY_base_id(pkey) : EVP_PKEY_NONE;
+#endif
+
+ /*
+ * OpenSSL will call us with either keylen == 512 or keylen == 1024
+ * (see the definition of SSL_EXPORT_PKEYLENGTH in ssl_locl.h).
+ * Adjust the DH parameter length according to the size of the
+ * RSA/DSA private key used for the current connection, and always
+ * use at least 1024-bit parameters.
+ * Note: This may cause interoperability issues with implementations
+ * which limit their DH support to 1024 bit - e.g. Java 7 and earlier.
+ * In this case, SSLCertificateFile can be used to specify fixed
+ * 1024-bit DH parameters (with the effect that OpenSSL skips this
+ * callback).
+ */
+ if ((type == EVP_PKEY_RSA) || (type == EVP_PKEY_DSA)) {
+ keylen = EVP_PKEY_bits(pkey);
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
+ "handing out built-in DH parameters for %d-bit authenticated connection", keylen);
+
+ return modssl_get_dh_params(keylen);
+}
+#endif
+
+/*
+ * This OpenSSL callback function is called when OpenSSL
+ * does client authentication and verifies the certificate chain.
+ */
+int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx)
+{
+ /* Get Apache context back through OpenSSL context */
+ SSL *ssl = X509_STORE_CTX_get_ex_data(ctx,
+ SSL_get_ex_data_X509_STORE_CTX_idx());
+ conn_rec *conn = (conn_rec *)SSL_get_app_data(ssl);
+ request_rec *r = (request_rec *)modssl_get_app_data2(ssl);
+ server_rec *s = r ? r->server : mySrvFromConn(conn);
+
+ SSLSrvConfigRec *sc = mySrvConfig(s);
+ SSLConnRec *sslconn = myConnConfig(conn);
+ SSLDirConfigRec *dc = r ? myDirConfig(r) : sslconn->dc;
+ modssl_ctx_t *mctx = myConnCtxConfig(conn, sc);
+ int crl_check_mode = mctx->crl_check_mask & ~SSL_CRLCHECK_FLAGS;
+
+ /* Get verify ingredients */
+ int errnum = X509_STORE_CTX_get_error(ctx);
+ int errdepth = X509_STORE_CTX_get_error_depth(ctx);
+ int depth = UNSET;
+ int verify = SSL_CVERIFY_UNSET;
+
+ /*
+ * Log verification information
+ */
+ ssl_log_cxerror(SSLLOG_MARK, APLOG_DEBUG, 0, conn,
+ X509_STORE_CTX_get_current_cert(ctx), APLOGNO(02275)
+ "Certificate Verification, depth %d, "
+ "CRL checking mode: %s (%x)", errdepth,
+ crl_check_mode == SSL_CRLCHECK_CHAIN ? "chain" :
+ crl_check_mode == SSL_CRLCHECK_LEAF ? "leaf" : "none",
+ mctx->crl_check_mask);
+
+ /*
+ * Check for optionally acceptable non-verifiable issuer situation
+ */
+ if (dc) {
+ if (conn->outgoing) {
+ verify = dc->proxy->auth.verify_mode;
+ }
+ else {
+ verify = dc->nVerifyClient;
+ }
+ }
+ if (!dc || (verify == SSL_CVERIFY_UNSET)) {
+ verify = mctx->auth.verify_mode;
+ }
+
+ if (verify == SSL_CVERIFY_NONE) {
+ /*
+ * SSLProxyVerify is either not configured or set to "none".
+ * (this callback doesn't happen in the server context if SSLVerify
+ * is not configured or set to "none")
+ */
+ return TRUE;
+ }
+
+ if (ssl_verify_error_is_optional(errnum) &&
+ (verify == SSL_CVERIFY_OPTIONAL_NO_CA))
+ {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, conn, APLOGNO(02037)
+ "Certificate Verification: Verifiable Issuer is "
+ "configured as optional, therefore we're accepting "
+ "the certificate");
+
+ sslconn->verify_info = "GENEROUS";
+ ok = TRUE;
+ }
+
+ /*
+ * Expired certificates vs. "expired" CRLs: by default, OpenSSL
+ * turns X509_V_ERR_CRL_HAS_EXPIRED into a "certificate_expired(45)"
+ * SSL alert, but that's not really the message we should convey to the
+ * peer (at the very least, it's confusing, and in many cases, it's also
+ * inaccurate, as the certificate itself may very well not have expired
+ * yet). We set the X509_STORE_CTX error to something which OpenSSL's
+ * s3_both.c:ssl_verify_alarm_type() maps to SSL_AD_CERTIFICATE_UNKNOWN,
+ * i.e. the peer will receive a "certificate_unknown(46)" alert.
+ * We do not touch errnum, though, so that later on we will still log
+ * the "real" error, as returned by OpenSSL.
+ */
+ if (!ok && errnum == X509_V_ERR_CRL_HAS_EXPIRED) {
+ X509_STORE_CTX_set_error(ctx, -1);
+ }
+
+ if (!ok && errnum == X509_V_ERR_UNABLE_TO_GET_CRL
+ && (mctx->crl_check_mask & SSL_CRLCHECK_NO_CRL_FOR_CERT_OK)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, conn,
+ "Certificate Verification: Temporary error (%d): %s: "
+ "optional therefore we're accepting the certificate",
+ errnum, X509_verify_cert_error_string(errnum));
+ X509_STORE_CTX_set_error(ctx, X509_V_OK);
+ errnum = X509_V_OK;
+ ok = TRUE;
+ }
+
+#ifndef OPENSSL_NO_OCSP
+ /*
+ * Perform OCSP-based revocation checks
+ */
+ if (ok && ((mctx->ocsp_mask & SSL_OCSPCHECK_CHAIN) ||
+ (errdepth == 0 && (mctx->ocsp_mask & SSL_OCSPCHECK_LEAF)))) {
+ /* If there was an optional verification error, it's not
+ * possible to perform OCSP validation since the issuer may be
+ * missing/untrusted. Fail in that case. */
+ if (ssl_verify_error_is_optional(errnum)) {
+ X509_STORE_CTX_set_error(ctx, X509_V_ERR_APPLICATION_VERIFICATION);
+ errnum = X509_V_ERR_APPLICATION_VERIFICATION;
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, conn, APLOGNO(02038)
+ "cannot perform OCSP validation for cert "
+ "if issuer has not been verified "
+ "(optional_no_ca configured)");
+ ok = FALSE;
+ } else {
+ ok = modssl_verify_ocsp(ctx, sc, s, conn, conn->pool);
+ if (!ok) {
+ errnum = X509_STORE_CTX_get_error(ctx);
+ }
+ }
+ }
+#endif
+
+ /*
+ * If we already know it's not ok, log the real reason
+ */
+ if (!ok) {
+ if (APLOGcinfo(conn)) {
+ ssl_log_cxerror(SSLLOG_MARK, APLOG_INFO, 0, conn,
+ X509_STORE_CTX_get_current_cert(ctx), APLOGNO(02276)
+ "Certificate Verification: Error (%d): %s",
+ errnum, X509_verify_cert_error_string(errnum));
+ } else {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, conn, APLOGNO(02039)
+ "Certificate Verification: Error (%d): %s",
+ errnum, X509_verify_cert_error_string(errnum));
+ }
+
+ if (sslconn->client_cert) {
+ X509_free(sslconn->client_cert);
+ sslconn->client_cert = NULL;
+ }
+ sslconn->client_dn = NULL;
+ sslconn->verify_error = X509_verify_cert_error_string(errnum);
+ }
+
+ /*
+ * Finally check the depth of the certificate verification
+ */
+ if (dc) {
+ if (conn->outgoing) {
+ depth = dc->proxy->auth.verify_depth;
+ }
+ else {
+ depth = dc->nVerifyDepth;
+ }
+ }
+ if (!dc || (depth == UNSET)) {
+ depth = mctx->auth.verify_depth;
+ }
+
+ if (errdepth > depth) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, conn, APLOGNO(02040)
+ "Certificate Verification: Certificate Chain too long "
+ "(chain has %d certificates, but maximum allowed are "
+ "only %d)",
+ errdepth, depth);
+
+ errnum = X509_V_ERR_CERT_CHAIN_TOO_LONG;
+ sslconn->verify_error = X509_verify_cert_error_string(errnum);
+
+ ok = FALSE;
+ }
+
+ /*
+ * And finally signal OpenSSL the (perhaps changed) state
+ */
+ return ok;
+}
+
+#define SSLPROXY_CERT_CB_LOG_FMT \
+ "Proxy client certificate callback: (%s) "
+
+static void modssl_proxy_info_log(conn_rec *c,
+ X509_INFO *info,
+ const char *msg)
+{
+ ssl_log_cxerror(SSLLOG_MARK, APLOG_DEBUG, 0, c, info->x509, APLOGNO(02277)
+ SSLPROXY_CERT_CB_LOG_FMT "%s, sending",
+ (mySrvConfigFromConn(c))->vhost_id, msg);
+}
+
+/*
+ * caller will decrement the cert and key reference
+ * so we need to increment here to prevent them from
+ * being freed.
+ */
+#if MODSSL_USE_OPENSSL_PRE_1_1_API
+#define modssl_set_cert_info(info, cert, pkey) \
+ *cert = info->x509; \
+ CRYPTO_add(&(*cert)->references, +1, CRYPTO_LOCK_X509); \
+ *pkey = info->x_pkey->dec_pkey; \
+ CRYPTO_add(&(*pkey)->references, +1, CRYPTO_LOCK_EVP_PKEY)
+#else
+#define modssl_set_cert_info(info, cert, pkey) \
+ *cert = info->x509; \
+ X509_up_ref(*cert); \
+ *pkey = info->x_pkey->dec_pkey; \
+ EVP_PKEY_up_ref(*pkey);
+#endif
+
+int ssl_callback_proxy_cert(SSL *ssl, X509 **x509, EVP_PKEY **pkey)
+{
+ conn_rec *c = (conn_rec *)SSL_get_app_data(ssl);
+ server_rec *s = mySrvFromConn(c);
+ SSLSrvConfigRec *sc = mySrvConfig(s);
+ SSLDirConfigRec *dc = myDirConfigFromConn(c);
+ X509_NAME *ca_name, *issuer, *ca_issuer;
+ X509_INFO *info;
+ X509 *ca_cert;
+ STACK_OF(X509_NAME) *ca_list;
+ STACK_OF(X509_INFO) *certs;
+ STACK_OF(X509) *ca_certs;
+ STACK_OF(X509) **ca_cert_chains;
+ int i, j, k;
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02267)
+ SSLPROXY_CERT_CB_LOG_FMT "entered",
+ sc->vhost_id);
+
+ certs = (dc && dc->proxy) ? dc->proxy->pkp->certs : NULL;
+ if (!certs || (sk_X509_INFO_num(certs) <= 0)) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(02268)
+ SSLPROXY_CERT_CB_LOG_FMT
+ "downstream server wanted client certificate "
+ "but none are configured", sc->vhost_id);
+ return FALSE;
+ }
+
+ ca_list = SSL_get_client_CA_list(ssl);
+
+ if (!ca_list || (sk_X509_NAME_num(ca_list) <= 0)) {
+ /*
+ * downstream server didn't send us a list of acceptable CA certs,
+ * so we send the first client cert in the list.
+ */
+ info = sk_X509_INFO_value(certs, 0);
+
+ modssl_proxy_info_log(c, info, APLOGNO(02278) "no acceptable CA list");
+
+ modssl_set_cert_info(info, x509, pkey);
+
+ return TRUE;
+ }
+
+ ca_cert_chains = dc->proxy->pkp->ca_certs;
+ for (i = 0; i < sk_X509_NAME_num(ca_list); i++) {
+ ca_name = sk_X509_NAME_value(ca_list, i);
+
+ for (j = 0; j < sk_X509_INFO_num(certs); j++) {
+ info = sk_X509_INFO_value(certs, j);
+ issuer = X509_get_issuer_name(info->x509);
+
+ /* Search certs (by issuer name) one by one*/
+ if (X509_NAME_cmp(issuer, ca_name) == 0) {
+ modssl_proxy_info_log(c, info, APLOGNO(02279)
+ "found acceptable cert");
+
+ modssl_set_cert_info(info, x509, pkey);
+
+ return TRUE;
+ }
+
+ if (ca_cert_chains) {
+ /*
+ * Failed to find direct issuer - search intermediates
+ * (by issuer name), if provided.
+ */
+ ca_certs = ca_cert_chains[j];
+ for (k = 0; k < sk_X509_num(ca_certs); k++) {
+ ca_cert = sk_X509_value(ca_certs, k);
+ ca_issuer = X509_get_issuer_name(ca_cert);
+
+ if(X509_NAME_cmp(ca_issuer, ca_name) == 0 ) {
+ modssl_proxy_info_log(c, info, APLOGNO(02280)
+ "found acceptable cert by intermediate CA");
+
+ modssl_set_cert_info(info, x509, pkey);
+
+ return TRUE;
+ }
+ } /* end loop through chained certs */
+ }
+ } /* end loop through available certs */
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02269)
+ SSLPROXY_CERT_CB_LOG_FMT
+ "no client certificate found!?", sc->vhost_id);
+
+ return FALSE;
+}
+
+static void ssl_session_log(server_rec *s,
+ const char *request,
+ IDCONST unsigned char *id,
+ unsigned int idlen,
+ const char *status,
+ const char *result,
+ long timeout)
+{
+ char buf[MODSSL_SESSION_ID_STRING_LEN];
+ char timeout_str[56] = {'\0'};
+
+ if (!APLOGdebug(s)) {
+ return;
+ }
+
+ if (timeout) {
+ apr_snprintf(timeout_str, sizeof(timeout_str),
+ "timeout=%lds ", timeout);
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, s,
+ "Inter-Process Session Cache: "
+ "request=%s status=%s id=%s %s(session %s)",
+ request, status,
+ modssl_SSL_SESSION_id2sz(id, idlen, buf, sizeof(buf)),
+ timeout_str, result);
+}
+
+/*
+ * This callback function is executed by OpenSSL whenever a new SSL_SESSION is
+ * added to the internal OpenSSL session cache. We use this hook to spread the
+ * SSL_SESSION also to the inter-process disk-cache to make share it with our
+ * other Apache pre-forked server processes.
+ */
+int ssl_callback_NewSessionCacheEntry(SSL *ssl, SSL_SESSION *session)
+{
+ /* Get Apache context back through OpenSSL context */
+ conn_rec *conn = (conn_rec *)SSL_get_app_data(ssl);
+ server_rec *s = mySrvFromConn(conn);
+ SSLSrvConfigRec *sc = mySrvConfig(s);
+ long timeout = sc->session_cache_timeout;
+ BOOL rc;
+ IDCONST unsigned char *id;
+ unsigned int idlen;
+
+ /*
+ * Set the timeout also for the internal OpenSSL cache, because this way
+ * our inter-process cache is consulted only when it's really necessary.
+ */
+ SSL_set_timeout(session, timeout);
+
+ /*
+ * Store the SSL_SESSION in the inter-process cache with the
+ * same expire time, so it expires automatically there, too.
+ */
+#ifdef OPENSSL_NO_SSL_INTERN
+ id = (unsigned char *)SSL_SESSION_get_id(session, &idlen);
+#else
+ id = session->session_id;
+ idlen = session->session_id_length;
+#endif
+
+ rc = ssl_scache_store(s, id, idlen,
+ apr_time_from_sec(SSL_SESSION_get_time(session)
+ + timeout),
+ session, conn->pool);
+
+ ssl_session_log(s, "SET", id, idlen,
+ rc == TRUE ? "OK" : "BAD",
+ "caching", timeout);
+
+ /*
+ * return 0 which means to OpenSSL that the session is still
+ * valid and was not freed by us with SSL_SESSION_free().
+ */
+ return 0;
+}
+
+/*
+ * This callback function is executed by OpenSSL whenever a
+ * SSL_SESSION is looked up in the internal OpenSSL cache and it
+ * was not found. We use this to lookup the SSL_SESSION in the
+ * inter-process disk-cache where it was perhaps stored by one
+ * of our other Apache pre-forked server processes.
+ */
+SSL_SESSION *ssl_callback_GetSessionCacheEntry(SSL *ssl,
+ IDCONST unsigned char *id,
+ int idlen, int *do_copy)
+{
+ /* Get Apache context back through OpenSSL context */
+ conn_rec *conn = (conn_rec *)SSL_get_app_data(ssl);
+ server_rec *s = mySrvFromConn(conn);
+ SSL_SESSION *session;
+
+ /*
+ * Try to retrieve the SSL_SESSION from the inter-process cache
+ */
+ session = ssl_scache_retrieve(s, id, idlen, conn->pool);
+
+ ssl_session_log(s, "GET", id, idlen,
+ session ? "FOUND" : "MISSED",
+ session ? "reuse" : "renewal", 0);
+
+ /*
+ * Return NULL or the retrieved SSL_SESSION. But indicate (by
+ * setting do_copy to 0) that the reference count on the
+ * SSL_SESSION should not be incremented by the SSL library,
+ * because we will no longer hold a reference to it ourself.
+ */
+ *do_copy = 0;
+
+ return session;
+}
+
+/*
+ * This callback function is executed by OpenSSL whenever a
+ * SSL_SESSION is removed from the internal OpenSSL cache.
+ * We use this to remove the SSL_SESSION in the inter-process
+ * disk-cache, too.
+ */
+void ssl_callback_DelSessionCacheEntry(SSL_CTX *ctx,
+ SSL_SESSION *session)
+{
+ server_rec *s;
+ SSLSrvConfigRec *sc;
+ IDCONST unsigned char *id;
+ unsigned int idlen;
+
+ /*
+ * Get Apache context back through OpenSSL context
+ */
+ if (!(s = (server_rec *)SSL_CTX_get_app_data(ctx))) {
+ return; /* on server shutdown Apache is already gone */
+ }
+
+ sc = mySrvConfig(s);
+
+ /*
+ * Remove the SSL_SESSION from the inter-process cache
+ */
+#ifdef OPENSSL_NO_SSL_INTERN
+ id = (unsigned char *)SSL_SESSION_get_id(session, &idlen);
+#else
+ id = session->session_id;
+ idlen = session->session_id_length;
+#endif
+
+ /* TODO: Do we need a temp pool here, or are we always shutting down? */
+ ssl_scache_remove(s, id, idlen, sc->mc->pPool);
+
+ ssl_session_log(s, "REM", id, idlen,
+ "OK", "dead", 0);
+
+ return;
+}
+
+/* Dump debugginfo trace to the log file. */
+static void log_tracing_state(const SSL *ssl, conn_rec *c,
+ server_rec *s, int where, int rc)
+{
+ /*
+ * create the various trace messages
+ */
+ if (where & SSL_CB_HANDSHAKE_START) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c,
+ "%s: Handshake: start", MODSSL_LIBRARY_NAME);
+ }
+ else if (where & SSL_CB_HANDSHAKE_DONE) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c,
+ "%s: Handshake: done", MODSSL_LIBRARY_NAME);
+ }
+ else if (where & SSL_CB_LOOP) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c,
+ "%s: Loop: %s",
+ MODSSL_LIBRARY_NAME, SSL_state_string_long(ssl));
+ }
+ else if (where & SSL_CB_READ) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c,
+ "%s: Read: %s",
+ MODSSL_LIBRARY_NAME, SSL_state_string_long(ssl));
+ }
+ else if (where & SSL_CB_WRITE) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c,
+ "%s: Write: %s",
+ MODSSL_LIBRARY_NAME, SSL_state_string_long(ssl));
+ }
+ else if (where & SSL_CB_ALERT) {
+ char *str = (where & SSL_CB_READ) ? "read" : "write";
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c,
+ "%s: Alert: %s:%s:%s",
+ MODSSL_LIBRARY_NAME, str,
+ SSL_alert_type_string_long(rc),
+ SSL_alert_desc_string_long(rc));
+ }
+ else if (where & SSL_CB_EXIT) {
+ if (rc == 0) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c,
+ "%s: Exit: failed in %s",
+ MODSSL_LIBRARY_NAME, SSL_state_string_long(ssl));
+ }
+ else if (rc < 0) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c,
+ "%s: Exit: error in %s",
+ MODSSL_LIBRARY_NAME, SSL_state_string_long(ssl));
+ }
+ }
+
+ /*
+ * Because SSL renegotiations can happen at any time (not only after
+ * SSL_accept()), the best way to log the current connection details is
+ * right after a finished handshake.
+ */
+ if (where & SSL_CB_HANDSHAKE_DONE) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(02041)
+ "Protocol: %s, Cipher: %s (%s/%s bits)",
+ ssl_var_lookup(NULL, s, c, NULL, "SSL_PROTOCOL"),
+ ssl_var_lookup(NULL, s, c, NULL, "SSL_CIPHER"),
+ ssl_var_lookup(NULL, s, c, NULL, "SSL_CIPHER_USEKEYSIZE"),
+ ssl_var_lookup(NULL, s, c, NULL, "SSL_CIPHER_ALGKEYSIZE"));
+ }
+}
+
+/*
+ * This callback function is executed while OpenSSL processes the SSL
+ * handshake and does SSL record layer stuff. It's used to trap
+ * client-initiated renegotiations, and for dumping everything to the
+ * log.
+ */
+void ssl_callback_Info(const SSL *ssl, int where, int rc)
+{
+ conn_rec *c;
+ server_rec *s;
+
+ /* Retrieve the conn_rec and the associated SSLConnRec. */
+ if ((c = (conn_rec *)SSL_get_app_data((SSL *)ssl)) == NULL) {
+ return;
+ }
+
+ /* With TLS 1.3 this callback may be called multiple times on the first
+ * negotiation, so the below logic to detect renegotiations can't work.
+ * Fortunately renegotiations are forbidden starting with TLS 1.3, and
+ * this is enforced by OpenSSL so there's nothing to be done here.
+ */
+#if SSL_HAVE_PROTOCOL_TLSV1_3
+ if (SSL_version(ssl) < TLS1_3_VERSION)
+#endif
+ {
+ SSLConnRec *sslconn;
+
+ if ((sslconn = myConnConfig(c)) == NULL) {
+ return;
+ }
+
+ /* If the reneg state is to reject renegotiations, check the SSL
+ * state machine and move to ABORT if a Client Hello is being
+ * read. */
+ if (!c->outgoing &&
+ (where & SSL_CB_HANDSHAKE_START) &&
+ sslconn->reneg_state == RENEG_REJECT) {
+ sslconn->reneg_state = RENEG_ABORT;
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02042)
+ "rejecting client initiated renegotiation");
+ }
+ /* If the first handshake is complete, change state to reject any
+ * subsequent client-initiated renegotiation. */
+ else if ((where & SSL_CB_HANDSHAKE_DONE)
+ && sslconn->reneg_state == RENEG_INIT) {
+ sslconn->reneg_state = RENEG_REJECT;
+ }
+ }
+
+ s = mySrvFromConn(c);
+ if (s && APLOGdebug(s)) {
+ log_tracing_state(ssl, c, s, where, rc);
+ }
+}
+
+#ifdef HAVE_TLSEXT
+
+static apr_status_t set_challenge_creds(conn_rec *c, const char *servername,
+ SSL *ssl, X509 *cert, EVP_PKEY *key,
+ const char *cert_pem, const char *key_pem)
+{
+ SSLConnRec *sslcon = myConnConfig(c);
+ apr_status_t rv = APR_SUCCESS;
+ int our_data = 0;
+
+ sslcon->service_unavailable = 1;
+ if (cert_pem) {
+ cert = NULL;
+ key = NULL;
+ our_data = 1;
+
+ rv = modssl_read_cert(c->pool, cert_pem, key_pem, NULL, NULL, &cert, &key);
+ if (rv != APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c, APLOGNO(10266)
+ "Failed to parse PEM of challenge certificate %s",
+ servername);
+ goto cleanup;
+ }
+ }
+
+ if ((SSL_use_certificate(ssl, cert) < 1)) {
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c, APLOGNO(10086)
+ "Failed to configure challenge certificate %s",
+ servername);
+ rv = APR_EGENERAL;
+ goto cleanup;
+ }
+
+ if (!SSL_use_PrivateKey(ssl, key)) {
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c, APLOGNO(10087)
+ "error '%s' using Challenge key: %s",
+ ERR_error_string(ERR_peek_last_error(), NULL),
+ servername);
+ rv = APR_EGENERAL;
+ goto cleanup;
+ }
+
+ if (SSL_check_private_key(ssl) < 1) {
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c, APLOGNO(10088)
+ "Challenge certificate and private key %s "
+ "do not match", servername);
+ rv = APR_EGENERAL;
+ goto cleanup;
+ }
+
+cleanup:
+ if (our_data && cert) X509_free(cert);
+ if (our_data && key) EVP_PKEY_free(key);
+ return APR_SUCCESS;
+}
+
+/*
+ * This function sets the virtual host from an extended
+ * client hello with a server name indication extension ("SNI", cf. RFC 6066).
+ */
+static apr_status_t init_vhost(conn_rec *c, SSL *ssl, const char *servername)
+{
+ if (c) {
+ SSLConnRec *sslcon = myConnConfig(c);
+
+ if (sslcon->vhost_found) {
+ /* already found the vhost? */
+ return sslcon->vhost_found > 0 ? APR_SUCCESS : APR_NOTFOUND;
+ }
+ sslcon->vhost_found = -1;
+
+ if (!servername) {
+ servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+ }
+ if (servername) {
+ if (ap_vhost_iterate_given_conn(c, ssl_find_vhost,
+ (void *)servername)) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(02043)
+ "SSL virtual host for servername %s found",
+ servername);
+
+ sslcon->vhost_found = +1;
+ return APR_SUCCESS;
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(02044)
+ "No matching SSL virtual host for servername "
+ "%s found (using default/first virtual host)",
+ servername);
+ /*
+ * RFC 6066 section 3 says "It is NOT RECOMMENDED to send
+ * a warning-level unrecognized_name(112) alert, because
+ * the client's behavior in response to warning-level alerts
+ * is unpredictable."
+ *
+ * To maintain backwards compatibility in mod_ssl, we
+ * no longer send any alert (neither warning- nor fatal-level),
+ * i.e. we take the second action suggested in RFC 6066:
+ * "If the server understood the ClientHello extension but
+ * does not recognize the server name, the server SHOULD take
+ * one of two actions: either abort the handshake by sending
+ * a fatal-level unrecognized_name(112) alert or continue
+ * the handshake."
+ */
+ }
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(02645)
+ "Server name not provided via TLS extension "
+ "(using default/first virtual host)");
+ }
+ }
+
+ return APR_NOTFOUND;
+}
+
+/*
+ * This callback function is executed when OpenSSL encounters an extended
+ * client hello with a server name indication extension ("SNI", cf. RFC 6066).
+ */
+int ssl_callback_ServerNameIndication(SSL *ssl, int *al, modssl_ctx_t *mctx)
+{
+ conn_rec *c = (conn_rec *)SSL_get_app_data(ssl);
+ apr_status_t status = init_vhost(c, ssl, NULL);
+
+ return (status == APR_SUCCESS)? SSL_TLSEXT_ERR_OK : SSL_TLSEXT_ERR_NOACK;
+}
+
+#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER)
+/*
+ * This callback function is called when the ClientHello is received.
+ */
+int ssl_callback_ClientHello(SSL *ssl, int *al, void *arg)
+{
+ char *servername = NULL;
+ conn_rec *c = (conn_rec *)SSL_get_app_data(ssl);
+ const unsigned char *pos;
+ size_t len, remaining;
+ (void)arg;
+
+ /* We can't use SSL_get_servername() at this earliest OpenSSL connection
+ * stage, and there is no SSL_client_hello_get0_servername() provided as
+ * of OpenSSL 1.1.1. So the code below, that extracts the SNI from the
+ * ClientHello's TLS extensions, is taken from some test code in OpenSSL,
+ * i.e. client_hello_select_server_ctx() in "test/handshake_helper.c".
+ */
+
+ /*
+ * The server_name extension was given too much extensibility when it
+ * was written, so parsing the normal case is a bit complex.
+ */
+ if (!SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_server_name, &pos,
+ &remaining)
+ || remaining <= 2)
+ goto give_up;
+
+ /* Extract the length of the supplied list of names. */
+ len = (*(pos++) << 8);
+ len += *(pos++);
+ if (len + 2 != remaining)
+ goto give_up;
+ remaining = len;
+
+ /*
+ * The list in practice only has a single element, so we only consider
+ * the first one.
+ */
+ if (remaining <= 3 || *pos++ != TLSEXT_NAMETYPE_host_name)
+ goto give_up;
+ remaining--;
+
+ /* Now we can finally pull out the byte array with the actual hostname. */
+ len = (*(pos++) << 8);
+ len += *(pos++);
+ if (len + 2 != remaining)
+ goto give_up;
+
+ /* Use the SNI to switch to the relevant vhost, should it differ from
+ * c->base_server.
+ */
+ servername = apr_pstrmemdup(c->pool, (const char *)pos, len);
+
+give_up:
+ init_vhost(c, ssl, servername);
+ return SSL_CLIENT_HELLO_SUCCESS;
+}
+#endif /* OPENSSL_VERSION_NUMBER < 0x10101000L */
+
+/*
+ * Find a (name-based) SSL virtual host where either the ServerName
+ * or one of the ServerAliases matches the supplied name (to be used
+ * with ap_vhost_iterate_given_conn())
+ */
+static int ssl_find_vhost(void *servername, conn_rec *c, server_rec *s)
+{
+ SSLSrvConfigRec *sc;
+ SSL *ssl;
+ BOOL found;
+ SSLConnRec *sslcon;
+
+ found = ssl_util_vhost_matches(servername, s);
+
+ /* set SSL_CTX (if matched) */
+ sslcon = myConnConfig(c);
+ if (found && (ssl = sslcon->ssl) &&
+ (sc = mySrvConfig(s))) {
+ SSL_CTX *ctx = SSL_set_SSL_CTX(ssl, sc->server->ssl_ctx);
+
+ /*
+ * SSL_set_SSL_CTX() only deals with the server cert,
+ * so we need to duplicate a few additional settings
+ * from the ctx by hand
+ */
+ SSL_set_options(ssl, SSL_CTX_get_options(ctx));
+#if OPENSSL_VERSION_NUMBER >= 0x1010007fL \
+ && (!defined(LIBRESSL_VERSION_NUMBER) \
+ || LIBRESSL_VERSION_NUMBER >= 0x20800000L)
+ /*
+ * Don't switch the protocol if none is configured for this vhost,
+ * the default in this case is still the base server's SSLProtocol.
+ */
+ if (myConnCtxConfig(c, sc)->protocol_set) {
+ SSL_set_min_proto_version(ssl, SSL_CTX_get_min_proto_version(ctx));
+ SSL_set_max_proto_version(ssl, SSL_CTX_get_max_proto_version(ctx));
+ }
+#endif
+ if ((SSL_get_verify_mode(ssl) == SSL_VERIFY_NONE) ||
+ (SSL_num_renegotiations(ssl) == 0)) {
+ /*
+ * Only initialize the verification settings from the ctx
+ * if they are not yet set, or if we're called when a new
+ * SSL connection is set up (num_renegotiations == 0).
+ * Otherwise, we would possibly reset a per-directory
+ * configuration which was put into effect by ssl_hook_Access.
+ */
+ SSL_set_verify(ssl, SSL_CTX_get_verify_mode(ctx),
+ SSL_CTX_get_verify_callback(ctx));
+ }
+
+ /*
+ * Adjust the session id context. ssl_init_ssl_connection()
+ * always picks the configuration of the first vhost when
+ * calling SSL_new(), but we want to tie the session to the
+ * vhost we have just switched to. Again, we have to make sure
+ * that we're not overwriting a session id context which was
+ * possibly set in ssl_hook_Access(), before triggering
+ * a renegotiation.
+ */
+ if (SSL_num_renegotiations(ssl) == 0) {
+ unsigned char *sid_ctx =
+ (unsigned char *)ap_md5_binary(c->pool,
+ (unsigned char *)sc->vhost_id,
+ sc->vhost_id_len);
+ SSL_set_session_id_context(ssl, sid_ctx, APR_MD5_DIGESTSIZE*2);
+ }
+
+ /*
+ * Save the found server into our SSLConnRec for later
+ * retrieval
+ */
+ sslcon->server = s;
+ sslcon->cipher_suite = sc->server->auth.cipher_suite;
+ sslcon->service_unavailable = sc->server->pks?
+ sc->server->pks->service_unavailable : 0;
+
+ ap_update_child_status_from_server(c->sbh, SERVER_BUSY_READ, c, s);
+ /*
+ * There is one special filter callback, which is set
+ * very early depending on the base_server's log level.
+ * If this is not the first vhost we're now selecting
+ * (and the first vhost doesn't use APLOG_TRACE4), then
+ * we need to set that callback here.
+ */
+ if (APLOGtrace4(s)) {
+ BIO *rbio = SSL_get_rbio(ssl),
+ *wbio = SSL_get_wbio(ssl);
+ BIO_set_callback(rbio, ssl_io_data_cb);
+ BIO_set_callback_arg(rbio, (void *)ssl);
+ if (wbio && wbio != rbio) {
+ BIO_set_callback(wbio, ssl_io_data_cb);
+ BIO_set_callback_arg(wbio, (void *)ssl);
+ }
+ }
+
+ return 1;
+ }
+
+ return 0;
+}
+#endif /* HAVE_TLSEXT */
+
+#ifdef HAVE_TLS_SESSION_TICKETS
+/*
+ * This callback function is executed when OpenSSL needs a key for encrypting/
+ * decrypting a TLS session ticket (RFC 5077) and a ticket key file has been
+ * configured through SSLSessionTicketKeyFile.
+ */
+int ssl_callback_SessionTicket(SSL *ssl,
+ unsigned char *keyname,
+ unsigned char *iv,
+ EVP_CIPHER_CTX *cipher_ctx,
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+ HMAC_CTX *hmac_ctx,
+#else
+ EVP_MAC_CTX *mac_ctx,
+#endif
+ int mode)
+{
+ conn_rec *c = (conn_rec *)SSL_get_app_data(ssl);
+ server_rec *s = mySrvFromConn(c);
+ SSLSrvConfigRec *sc = mySrvConfig(s);
+ modssl_ctx_t *mctx = myConnCtxConfig(c, sc);
+ modssl_ticket_key_t *ticket_key = mctx->ticket_key;
+
+ if (mode == 1) {
+ /*
+ * OpenSSL is asking for a key for encrypting a ticket,
+ * see s3_srvr.c:ssl3_send_newsession_ticket()
+ */
+
+ if (ticket_key == NULL) {
+ /* should never happen, but better safe than sorry */
+ return -1;
+ }
+
+ memcpy(keyname, ticket_key->key_name, 16);
+ if (RAND_bytes(iv, EVP_MAX_IV_LENGTH) != 1) {
+ return -1;
+ }
+ EVP_EncryptInit_ex(cipher_ctx, EVP_aes_128_cbc(), NULL,
+ ticket_key->aes_key, iv);
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+ HMAC_Init_ex(hmac_ctx, ticket_key->hmac_secret, 16,
+ tlsext_tick_md(), NULL);
+#else
+ EVP_MAC_CTX_set_params(mac_ctx, ticket_key->mac_params);
+#endif
+
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(02289)
+ "TLS session ticket key for %s successfully set, "
+ "creating new session ticket", sc->vhost_id);
+
+ return 1;
+ }
+ else if (mode == 0) {
+ /*
+ * OpenSSL is asking for the decryption key,
+ * see t1_lib.c:tls_decrypt_ticket()
+ */
+
+ /* check key name */
+ if (ticket_key == NULL || memcmp(keyname, ticket_key->key_name, 16)) {
+ return 0;
+ }
+
+ EVP_DecryptInit_ex(cipher_ctx, EVP_aes_128_cbc(), NULL,
+ ticket_key->aes_key, iv);
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+ HMAC_Init_ex(hmac_ctx, ticket_key->hmac_secret, 16,
+ tlsext_tick_md(), NULL);
+#else
+ EVP_MAC_CTX_set_params(mac_ctx, ticket_key->mac_params);
+#endif
+
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(02290)
+ "TLS session ticket key for %s successfully set, "
+ "decrypting existing session ticket", sc->vhost_id);
+
+ return 1;
+ }
+
+ /* OpenSSL is not expected to call us with modes other than 1 or 0 */
+ return -1;
+}
+#endif /* HAVE_TLS_SESSION_TICKETS */
+
+#ifdef HAVE_TLS_ALPN
+
+/*
+ * This callback function is executed when the TLS Application-Layer
+ * Protocol Negotiation Extension (ALPN, RFC 7301) is triggered by the Client
+ * Hello, giving a list of desired protocol names (in descending preference)
+ * to the server.
+ * The callback has to select a protocol name or return an error if none of
+ * the clients preferences is supported.
+ * The selected protocol does not have to be on the client list, according
+ * to RFC 7301, so no checks are performed.
+ * The client protocol list is serialized as length byte followed by ASCII
+ * characters (not null-terminated), followed by the next protocol name.
+ */
+int ssl_callback_alpn_select(SSL *ssl,
+ const unsigned char **out, unsigned char *outlen,
+ const unsigned char *in, unsigned int inlen,
+ void *arg)
+{
+ conn_rec *c = (conn_rec*)SSL_get_app_data(ssl);
+ SSLConnRec *sslconn;
+ apr_array_header_t *client_protos;
+ const char *proposed;
+ size_t len;
+ int i;
+
+ /* If the connection object is not available,
+ * then there's nothing for us to do. */
+ if (c == NULL) {
+ return SSL_TLSEXT_ERR_OK;
+ }
+ sslconn = myConnConfig(c);
+
+ if (inlen == 0) {
+ /* someone tries to trick us? */
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02837)
+ "ALPN client protocol list empty");
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+ }
+
+ client_protos = apr_array_make(c->pool, 0, sizeof(char *));
+ for (i = 0; i < inlen; /**/) {
+ unsigned int plen = in[i++];
+ if (plen + i > inlen) {
+ /* someone tries to trick us? */
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02838)
+ "ALPN protocol identifier too long");
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+ }
+ APR_ARRAY_PUSH(client_protos, char *) =
+ apr_pstrndup(c->pool, (const char *)in+i, plen);
+ i += plen;
+ }
+
+ /* The order the callbacks are invoked from TLS extensions is, unfortunately
+ * not defined and older openssl versions do call ALPN selection before
+ * they callback the SNI. We need to make sure that we know which vhost
+ * we are dealing with so we respect the correct protocols.
+ */
+ init_vhost(c, ssl, NULL);
+
+ proposed = ap_select_protocol(c, NULL, sslconn->server, client_protos);
+ if (!proposed) {
+ proposed = ap_get_protocol(c);
+ }
+
+ len = strlen(proposed);
+ if (len > 255) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02840)
+ "ALPN negotiated protocol name too long");
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+ }
+ *out = (const unsigned char *)proposed;
+ *outlen = (unsigned char)len;
+
+ if (strcmp(proposed, ap_get_protocol(c))) {
+ apr_status_t status;
+
+ status = ap_switch_protocol(c, NULL, sslconn->server, proposed);
+ if (status != APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, status, c,
+ APLOGNO(02908) "protocol switch to '%s' failed",
+ proposed);
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+ }
+
+ /* protocol was switched, this could be a challenge protocol such as "acme-tls/1".
+ * For that to work, we need to allow overrides to our ssl certificate.
+ * However, exclude challenge checks on our best known traffic protocol.
+ * (http/1.1 is the default, we never switch to it anyway.)
+ */
+ if (strcmp("h2", proposed)) {
+ const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+ X509 *cert;
+ EVP_PKEY *key;
+ const char *cert_pem, *key_pem;
+
+ if (ssl_is_challenge(c, servername, &cert, &key, &cert_pem, &key_pem)) {
+ if (set_challenge_creds(c, servername, ssl, cert, key,
+ cert_pem, key_pem) != APR_SUCCESS) {
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+ }
+ SSL_set_verify(ssl, SSL_VERIFY_NONE, ssl_callback_SSLVerify);
+ }
+ }
+ }
+
+ return SSL_TLSEXT_ERR_OK;
+}
+#endif /* HAVE_TLS_ALPN */
+
+#ifdef HAVE_SRP
+
+int ssl_callback_SRPServerParams(SSL *ssl, int *ad, void *arg)
+{
+ modssl_ctx_t *mctx = (modssl_ctx_t *)arg;
+ char *username = SSL_get_srp_username(ssl);
+ SRP_user_pwd *u;
+
+ if (username == NULL
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ || (u = SRP_VBASE_get_by_user(mctx->srp_vbase, username)) == NULL) {
+#else
+ || (u = SRP_VBASE_get1_by_user(mctx->srp_vbase, username)) == NULL) {
+#endif
+ *ad = SSL_AD_UNKNOWN_PSK_IDENTITY;
+ return SSL3_AL_FATAL;
+ }
+
+ if (SSL_set_srp_server_param(ssl, u->N, u->g, u->s, u->v, u->info) < 0) {
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ SRP_user_pwd_free(u);
+#endif
+ *ad = SSL_AD_INTERNAL_ERROR;
+ return SSL3_AL_FATAL;
+ }
+
+ /* reset all other options */
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ SRP_user_pwd_free(u);
+#endif
+ SSL_set_verify(ssl, SSL_VERIFY_NONE, ssl_callback_SSLVerify);
+ return SSL_ERROR_NONE;
+}
+
+#endif /* HAVE_SRP */
+
+
+#ifdef HAVE_OPENSSL_KEYLOG
+/* Callback used with SSL_CTX_set_keylog_callback. */
+void modssl_callback_keylog(const SSL *ssl, const char *line)
+{
+ conn_rec *conn = SSL_get_app_data(ssl);
+ SSLSrvConfigRec *sc = mySrvConfig(conn->base_server);
+
+ if (sc && sc->mc->keylog_file) {
+ apr_file_printf(sc->mc->keylog_file, "%s\n", line);
+ }
+}
+#endif
diff --git a/modules/ssl/ssl_engine_log.c b/modules/ssl/ssl_engine_log.c
new file mode 100644
index 0000000..3b3ceac
--- /dev/null
+++ b/modules/ssl/ssl_engine_log.c
@@ -0,0 +1,246 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* _ _
+ * _ __ ___ ___ __| | ___ ___| | mod_ssl
+ * | '_ ` _ \ / _ \ / _` | / __/ __| | Apache Interface to OpenSSL
+ * | | | | | | (_) | (_| | \__ \__ \ |
+ * |_| |_| |_|\___/ \__,_|___|___/___/_|
+ * |_____|
+ * ssl_engine_log.c
+ * Logging Facility
+ */
+ /* ``The difference between a computer
+ industry job and open-source software
+ hacking is about 30 hours a week.''
+ -- Ralf S. Engelschall */
+#include "ssl_private.h"
+
+/* _________________________________________________________________
+**
+** Logfile Support
+** _________________________________________________________________
+*/
+
+static const struct {
+ const char *cpPattern;
+ const char *cpAnnotation;
+} ssl_log_annotate[] = {
+ { "*envelope*bad*decrypt*", "wrong pass phrase!?" },
+ { "*CLIENT_HELLO*unknown*protocol*", "speaking not SSL to HTTPS port!?" },
+ { "*CLIENT_HELLO*http*request*", "speaking HTTP to HTTPS port!?" },
+ { "*SSL3_READ_BYTES:sslv3*alert*bad*certificate*", "Subject CN in certificate not server name or identical to CA!?" },
+ { "*self signed certificate in certificate chain*", "Client certificate signed by CA not known to server?" },
+ { "*peer did not return a certificate*", "No CAs known to server for verification?" },
+ { "*no shared cipher*", "Too restrictive SSLCipherSuite or using DSA server certificate?" },
+ { "*no start line*", "Bad file contents or format - or even just a forgotten SSLCertificateKeyFile?" },
+ { "*bad password read*", "You entered an incorrect pass phrase!?" },
+ { "*bad mac decode*", "Browser still remembered details of a re-created server certificate?" },
+ { NULL, NULL }
+};
+
+static const char *ssl_log_annotation(const char *error)
+{
+ int i = 0;
+
+ while (ssl_log_annotate[i].cpPattern != NULL
+ && ap_strcmp_match(error, ssl_log_annotate[i].cpPattern) != 0)
+ i++;
+
+ return ssl_log_annotate[i].cpAnnotation;
+}
+
+apr_status_t ssl_die(server_rec *s)
+{
+ if (s != NULL && s->is_virtual && s->error_fname != NULL)
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, NULL, APLOGNO(02311)
+ "Fatal error initialising mod_ssl, exiting. "
+ "See %s for more information",
+ ap_server_root_relative(s->process->pool,
+ s->error_fname));
+ else
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, NULL, APLOGNO(02312)
+ "Fatal error initialising mod_ssl, exiting.");
+
+ return APR_EGENERAL;
+}
+
+static APR_INLINE
+unsigned long modssl_ERR_peek_error_data(const char **data, int *flags)
+{
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+ return ERR_peek_error_line_data(NULL, NULL, data, flags);
+#else
+ return ERR_peek_error_data(data, flags);
+#endif
+}
+
+/*
+ * Prints the SSL library error information.
+ */
+void ssl_log_ssl_error(const char *file, int line, int level, server_rec *s)
+{
+ unsigned long e;
+ const char *data;
+ int flags;
+
+ while ((e = modssl_ERR_peek_error_data(&data, &flags))) {
+ const char *annotation;
+ char err[256];
+
+ if (!(flags & ERR_TXT_STRING)) {
+ data = NULL;
+ }
+
+ ERR_error_string_n(e, err, sizeof err);
+ annotation = ssl_log_annotation(err);
+
+ ap_log_error(file, line, APLOG_MODULE_INDEX, level, 0, s,
+ "SSL Library Error: %s%s%s%s%s%s",
+ /* %s */
+ err,
+ /* %s%s%s */
+ data ? " (" : "", data ? data : "", data ? ")" : "",
+ /* %s%s */
+ annotation ? " -- " : "",
+ annotation ? annotation : "");
+
+ /* Pop the error off the stack: */
+ ERR_get_error();
+ }
+}
+
+static void ssl_log_cert_error(const char *file, int line, int level,
+ apr_status_t rv, const server_rec *s,
+ const conn_rec *c, const request_rec *r,
+ apr_pool_t *p, X509 *cert, const char *format,
+ va_list ap)
+{
+ char buf[HUGE_STRING_LEN];
+ int msglen, n;
+ char *name;
+
+ msglen = apr_vsnprintf(buf, sizeof buf, format, ap);
+
+ if (cert) {
+ BIO *bio = BIO_new(BIO_s_mem());
+
+ if (bio) {
+ /*
+ * Limit the maximum length of the subject and issuer DN strings
+ * in the log message. 300 characters should always be sufficient
+ * for holding both the timestamp, module name, pid etc. stuff
+ * at the beginning of the line and the trailing information about
+ * serial, notbefore and notafter.
+ */
+ int maxdnlen = (HUGE_STRING_LEN - msglen - 300) / 2;
+
+ BIO_puts(bio, " [subject: ");
+ name = modssl_X509_NAME_to_string(p, X509_get_subject_name(cert),
+ maxdnlen);
+ if (!strIsEmpty(name)) {
+ BIO_puts(bio, name);
+ } else {
+ BIO_puts(bio, "-empty-");
+ }
+
+ BIO_puts(bio, " / issuer: ");
+ name = modssl_X509_NAME_to_string(p, X509_get_issuer_name(cert),
+ maxdnlen);
+ if (!strIsEmpty(name)) {
+ BIO_puts(bio, name);
+ } else {
+ BIO_puts(bio, "-empty-");
+ }
+
+ BIO_puts(bio, " / serial: ");
+ if (i2a_ASN1_INTEGER(bio, X509_get_serialNumber(cert)) == -1)
+ BIO_puts(bio, "(ERROR)");
+
+ BIO_puts(bio, " / notbefore: ");
+ ASN1_TIME_print(bio, X509_get_notBefore(cert));
+
+ BIO_puts(bio, " / notafter: ");
+ ASN1_TIME_print(bio, X509_get_notAfter(cert));
+
+ BIO_puts(bio, "]");
+
+ n = BIO_read(bio, buf + msglen, sizeof buf - msglen - 1);
+ if (n > 0)
+ buf[msglen + n] = '\0';
+
+ BIO_free(bio);
+ }
+ }
+ else {
+ apr_snprintf(buf + msglen, sizeof buf - msglen,
+ " [certificate: -not available-]");
+ }
+
+ if (r) {
+ ap_log_rerror(file, line, APLOG_MODULE_INDEX, level, rv, r, "%s", buf);
+ }
+ else if (c) {
+ ap_log_cerror(file, line, APLOG_MODULE_INDEX, level, rv, c, "%s", buf);
+ }
+ else if (s) {
+ ap_log_error(file, line, APLOG_MODULE_INDEX, level, rv, s, "%s", buf);
+ }
+
+}
+
+/*
+ * Wrappers for ap_log_error/ap_log_cerror/ap_log_rerror which log additional
+ * details of the X509 cert. For ssl_log_xerror, a pool needs to be passed in
+ * as well (for temporary allocation of the cert's subject/issuer name strings,
+ * in the other cases we use the connection and request pool, respectively).
+ */
+void ssl_log_xerror(const char *file, int line, int level, apr_status_t rv,
+ apr_pool_t *ptemp, server_rec *s, X509 *cert,
+ const char *fmt, ...)
+{
+ if (APLOG_IS_LEVEL(s,level)) {
+ va_list ap;
+ va_start(ap, fmt);
+ ssl_log_cert_error(file, line, level, rv, s, NULL, NULL, ptemp,
+ cert, fmt, ap);
+ va_end(ap);
+ }
+}
+
+void ssl_log_cxerror(const char *file, int line, int level, apr_status_t rv,
+ conn_rec *c, X509 *cert, const char *fmt, ...)
+{
+ if (APLOG_IS_LEVEL(mySrvFromConn(c),level)) {
+ va_list ap;
+ va_start(ap, fmt);
+ ssl_log_cert_error(file, line, level, rv, NULL, c, NULL, c->pool,
+ cert, fmt, ap);
+ va_end(ap);
+ }
+}
+
+void ssl_log_rxerror(const char *file, int line, int level, apr_status_t rv,
+ request_rec *r, X509 *cert, const char *fmt, ...)
+{
+ if (APLOG_R_IS_LEVEL(r,level)) {
+ va_list ap;
+ va_start(ap, fmt);
+ ssl_log_cert_error(file, line, level, rv, NULL, NULL, r, r->pool,
+ cert, fmt, ap);
+ va_end(ap);
+ }
+}
diff --git a/modules/ssl/ssl_engine_mutex.c b/modules/ssl/ssl_engine_mutex.c
new file mode 100644
index 0000000..e915a16
--- /dev/null
+++ b/modules/ssl/ssl_engine_mutex.c
@@ -0,0 +1,111 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* _ _
+ * _ __ ___ ___ __| | ___ ___| | mod_ssl
+ * | '_ ` _ \ / _ \ / _` | / __/ __| | Apache Interface to OpenSSL
+ * | | | | | | (_) | (_| | \__ \__ \ |
+ * |_| |_| |_|\___/ \__,_|___|___/___/_|
+ * |_____|
+ * ssl_engine_mutex.c
+ * Semaphore for Mutual Exclusion
+ */
+ /* ``Real programmers confuse
+ Christmas and Halloween
+ because DEC 25 = OCT 31.''
+ -- Unknown */
+
+#include "ssl_private.h"
+
+int ssl_mutex_init(server_rec *s, apr_pool_t *p)
+{
+ SSLModConfigRec *mc = myModConfig(s);
+ apr_status_t rv;
+
+ /* A mutex is only needed if a session cache is configured, and
+ * the provider used is not internally multi-process/thread
+ * safe. */
+ if (!mc->sesscache
+ || (mc->sesscache->flags & AP_SOCACHE_FLAG_NOTMPSAFE) == 0) {
+ return TRUE;
+ }
+
+ if (mc->pMutex) {
+ return TRUE;
+ }
+
+ if ((rv = ap_global_mutex_create(&mc->pMutex, NULL, SSL_CACHE_MUTEX_TYPE,
+ NULL, s, s->process->pool, 0))
+ != APR_SUCCESS) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+int ssl_mutex_reinit(server_rec *s, apr_pool_t *p)
+{
+ SSLModConfigRec *mc = myModConfig(s);
+ apr_status_t rv;
+ const char *lockfile;
+
+ if (mc->pMutex == NULL || !mc->sesscache
+ || (mc->sesscache->flags & AP_SOCACHE_FLAG_NOTMPSAFE) == 0) {
+ return TRUE;
+ }
+
+ lockfile = apr_global_mutex_lockfile(mc->pMutex);
+ if ((rv = apr_global_mutex_child_init(&mc->pMutex,
+ lockfile,
+ p)) != APR_SUCCESS) {
+ if (lockfile)
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(02024)
+ "Cannot reinit %s mutex with file `%s'",
+ SSL_CACHE_MUTEX_TYPE, lockfile);
+ else
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rv, s, APLOGNO(02025)
+ "Cannot reinit %s mutex", SSL_CACHE_MUTEX_TYPE);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+int ssl_mutex_on(server_rec *s)
+{
+ SSLModConfigRec *mc = myModConfig(s);
+ apr_status_t rv;
+
+ if ((rv = apr_global_mutex_lock(mc->pMutex)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rv, s, APLOGNO(02026)
+ "Failed to acquire SSL session cache lock");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+int ssl_mutex_off(server_rec *s)
+{
+ SSLModConfigRec *mc = myModConfig(s);
+ apr_status_t rv;
+
+ if ((rv = apr_global_mutex_unlock(mc->pMutex)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rv, s, APLOGNO(02027)
+ "Failed to release SSL session cache lock");
+ return FALSE;
+ }
+ return TRUE;
+}
+
diff --git a/modules/ssl/ssl_engine_ocsp.c b/modules/ssl/ssl_engine_ocsp.c
new file mode 100644
index 0000000..5e04512
--- /dev/null
+++ b/modules/ssl/ssl_engine_ocsp.c
@@ -0,0 +1,312 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ssl_private.h"
+
+#ifndef OPENSSL_NO_OCSP
+#include "apr_base64.h"
+
+/* Return the responder URI specified in the given certificate, or
+ * NULL if none specified. */
+static const char *extract_responder_uri(X509 *cert, apr_pool_t *pool)
+{
+ STACK_OF(ACCESS_DESCRIPTION) *values;
+ char *result = NULL;
+ int j;
+
+ values = X509_get_ext_d2i(cert, NID_info_access, NULL, NULL);
+ if (!values) {
+ return NULL;
+ }
+
+ for (j = 0; j < sk_ACCESS_DESCRIPTION_num(values) && !result; j++) {
+ ACCESS_DESCRIPTION *value = sk_ACCESS_DESCRIPTION_value(values, j);
+
+ /* Name found in extension, and is a URI: */
+ if (OBJ_obj2nid(value->method) == NID_ad_OCSP
+ && value->location->type == GEN_URI) {
+ result = apr_pstrdup(pool,
+ (char *)value->location->d.uniformResourceIdentifier->data);
+ }
+ }
+
+ AUTHORITY_INFO_ACCESS_free(values);
+
+ return result;
+}
+
+/* Return the responder URI object which should be used in the given
+ * configuration for the given certificate, or NULL if none can be
+ * determined. */
+static apr_uri_t *determine_responder_uri(SSLSrvConfigRec *sc, X509 *cert,
+ conn_rec *c, apr_pool_t *p)
+{
+ apr_uri_t *u = apr_palloc(p, sizeof *u);
+ const char *s;
+ apr_status_t rv;
+
+ /* Use default responder URL if forced by configuration, else use
+ * certificate-specified responder, falling back to default if
+ * necessary and possible. */
+ if (sc->server->ocsp_force_default == TRUE) {
+ s = sc->server->ocsp_responder;
+ }
+ else {
+ s = extract_responder_uri(cert, p);
+
+ if (s == NULL && sc->server->ocsp_responder) {
+ s = sc->server->ocsp_responder;
+ }
+ }
+
+ if (s == NULL) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(01918)
+ "no OCSP responder specified in certificate and "
+ "no default configured");
+ return NULL;
+ }
+
+ rv = apr_uri_parse(p, s, u);
+ if (rv || !u->hostname) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c, APLOGNO(01919)
+ "failed to parse OCSP responder URI '%s'", s);
+ return NULL;
+ }
+
+ if (ap_cstr_casecmp(u->scheme, "http") != 0) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c, APLOGNO(01920)
+ "cannot handle OCSP responder URI '%s'", s);
+ return NULL;
+ }
+
+ if (!u->port) {
+ u->port = apr_uri_port_of_scheme(u->scheme);
+ }
+
+ return u;
+}
+
+/* Create an OCSP request for the given certificate; returning the
+ * certificate ID in *certid and *issuer on success. Returns the
+ * request object on success, or NULL on error. */
+static OCSP_REQUEST *create_request(X509_STORE_CTX *ctx, X509 *cert,
+ OCSP_CERTID **certid,
+ server_rec *s, apr_pool_t *p,
+ SSLSrvConfigRec *sc)
+{
+ OCSP_REQUEST *req = OCSP_REQUEST_new();
+
+ *certid = OCSP_cert_to_id(NULL, cert, X509_STORE_CTX_get0_current_issuer(ctx));
+ if (!*certid || !OCSP_request_add0_id(req, *certid)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(01921)
+ "could not retrieve certificate id");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, s);
+ return NULL;
+ }
+
+ if (sc->server->ocsp_use_request_nonce != FALSE) {
+ OCSP_request_add1_nonce(req, 0, -1);
+ }
+
+ return req;
+}
+
+/* Verify the OCSP status of given certificate. Returns
+ * V_OCSP_CERTSTATUS_* result code. */
+static int verify_ocsp_status(X509 *cert, X509_STORE_CTX *ctx, conn_rec *c,
+ SSLSrvConfigRec *sc, server_rec *s,
+ apr_pool_t *pool)
+{
+ int rc = V_OCSP_CERTSTATUS_GOOD;
+ OCSP_RESPONSE *response = NULL;
+ OCSP_BASICRESP *basicResponse = NULL;
+ OCSP_REQUEST *request = NULL;
+ OCSP_CERTID *certID = NULL;
+ apr_uri_t *ruri;
+
+ ruri = determine_responder_uri(sc, cert, c, pool);
+ if (!ruri) {
+ if (sc->server->ocsp_mask & SSL_OCSPCHECK_NO_OCSP_FOR_CERT_OK) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
+ "Skipping OCSP check for certificate cos no OCSP URL"
+ " found and no_ocsp_for_cert_ok is set");
+ return V_OCSP_CERTSTATUS_GOOD;
+ } else {
+ return V_OCSP_CERTSTATUS_UNKNOWN;
+ }
+ }
+
+ request = create_request(ctx, cert, &certID, s, pool, sc);
+ if (request) {
+ apr_interval_time_t to = sc->server->ocsp_responder_timeout == UNSET ?
+ apr_time_from_sec(DEFAULT_OCSP_TIMEOUT) :
+ sc->server->ocsp_responder_timeout;
+ response = modssl_dispatch_ocsp_request(ruri, to, request, c, pool);
+ }
+
+ if (!request || !response) {
+ rc = V_OCSP_CERTSTATUS_UNKNOWN;
+ }
+
+ if (rc == V_OCSP_CERTSTATUS_GOOD) {
+ int r = OCSP_response_status(response);
+
+ if (r != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(01922)
+ "OCSP response not successful: %d", r);
+ rc = V_OCSP_CERTSTATUS_UNKNOWN;
+ }
+ }
+
+ if (rc == V_OCSP_CERTSTATUS_GOOD) {
+ basicResponse = OCSP_response_get1_basic(response);
+ if (!basicResponse) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(01923)
+ "could not retrieve OCSP basic response");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, s);
+ rc = V_OCSP_CERTSTATUS_UNKNOWN;
+ }
+ }
+
+ if (rc == V_OCSP_CERTSTATUS_GOOD &&
+ sc->server->ocsp_use_request_nonce != FALSE &&
+ OCSP_check_nonce(request, basicResponse) != 1) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(01924)
+ "Bad OCSP responder answer (bad nonce)");
+ rc = V_OCSP_CERTSTATUS_UNKNOWN;
+ }
+
+ if (rc == V_OCSP_CERTSTATUS_GOOD) {
+ /* Check if OCSP certificate verification required */
+ if (sc->server->ocsp_noverify != TRUE) {
+ /* Modify OCSP response verification to include OCSP Responder cert */
+ if (OCSP_basic_verify(basicResponse, sc->server->ocsp_certs, X509_STORE_CTX_get0_store(ctx),
+ sc->server->ocsp_verify_flags) != 1) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(01925)
+ "failed to verify the OCSP response");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, s);
+ rc = V_OCSP_CERTSTATUS_UNKNOWN;
+ }
+ }
+ }
+
+ if (rc == V_OCSP_CERTSTATUS_GOOD) {
+ int reason = -1, status;
+ ASN1_GENERALIZEDTIME *thisup = NULL, *nextup = NULL;
+
+ rc = OCSP_resp_find_status(basicResponse, certID, &status,
+ &reason, NULL, &thisup, &nextup);
+ if (rc != 1) {
+ ssl_log_cxerror(SSLLOG_MARK, APLOG_ERR, 0, c, cert, APLOGNO(02272)
+ "failed to retrieve OCSP response status");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, s);
+ rc = V_OCSP_CERTSTATUS_UNKNOWN;
+ }
+ else {
+ rc = status;
+ }
+
+ /* Check whether the response is inside the defined validity
+ * period; otherwise fail. */
+ if (rc != V_OCSP_CERTSTATUS_UNKNOWN) {
+ long resptime_skew = sc->server->ocsp_resptime_skew == UNSET ?
+ DEFAULT_OCSP_MAX_SKEW : sc->server->ocsp_resptime_skew;
+ /* oscp_resp_maxage can be passed verbatim - UNSET (-1) means
+ * that responses can be of any age as long as nextup is in the
+ * future. */
+ int vrc = OCSP_check_validity(thisup, nextup, resptime_skew,
+ sc->server->ocsp_resp_maxage);
+ if (vrc != 1) {
+ ssl_log_cxerror(SSLLOG_MARK, APLOG_ERR, 0, c, cert, APLOGNO(02273)
+ "OCSP response outside validity period");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, s);
+ rc = V_OCSP_CERTSTATUS_UNKNOWN;
+ }
+ }
+
+ {
+ int level =
+ (status == V_OCSP_CERTSTATUS_GOOD) ? APLOG_INFO : APLOG_ERR;
+ const char *result =
+ status == V_OCSP_CERTSTATUS_GOOD ? "good" :
+ (status == V_OCSP_CERTSTATUS_REVOKED ? "revoked" : "unknown");
+
+ ssl_log_cxerror(SSLLOG_MARK, level, 0, c, cert, APLOGNO(03239)
+ "OCSP validation completed, "
+ "certificate status: %s (%d, %d)",
+ result, status, reason);
+ }
+ }
+
+ if (request) OCSP_REQUEST_free(request);
+ if (response) OCSP_RESPONSE_free(response);
+ if (basicResponse) OCSP_BASICRESP_free(basicResponse);
+ /* certID is freed when the request is freed */
+
+ return rc;
+}
+
+int modssl_verify_ocsp(X509_STORE_CTX *ctx, SSLSrvConfigRec *sc,
+ server_rec *s, conn_rec *c, apr_pool_t *pool)
+{
+ X509 *cert = X509_STORE_CTX_get_current_cert(ctx);
+ apr_pool_t *vpool;
+ int rv;
+
+ if (!cert) {
+ /* starting with OpenSSL 1.0, X509_STORE_CTX_get_current_cert()
+ * may yield NULL. Return early, but leave the ctx error as is. */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
+ "No cert available to check with OCSP");
+ return 1;
+ }
+ else if (X509_check_issued(cert,cert) == X509_V_OK) {
+ /* don't do OCSP checking for valid self-issued certs */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
+ "Skipping OCSP check for valid self-issued cert");
+ X509_STORE_CTX_set_error(ctx, X509_V_OK);
+ return 1;
+ }
+
+ /* Create a temporary pool to constrain memory use (the passed-in
+ * pool may be e.g. a connection pool). */
+ apr_pool_create(&vpool, pool);
+ apr_pool_tag(vpool, "modssl_verify_ocsp");
+
+ rv = verify_ocsp_status(cert, ctx, c, sc, s, vpool);
+
+ apr_pool_destroy(vpool);
+
+ /* Propagate the verification status back to the passed-in
+ * context. */
+ switch (rv) {
+ case V_OCSP_CERTSTATUS_GOOD:
+ X509_STORE_CTX_set_error(ctx, X509_V_OK);
+ break;
+
+ case V_OCSP_CERTSTATUS_REVOKED:
+ X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_REVOKED);
+ break;
+
+ case V_OCSP_CERTSTATUS_UNKNOWN:
+ /* correct error code for application errors? */
+ X509_STORE_CTX_set_error(ctx, X509_V_ERR_APPLICATION_VERIFICATION);
+ break;
+ }
+
+ return rv == V_OCSP_CERTSTATUS_GOOD;
+}
+#endif /* HAVE_OCSP */
diff --git a/modules/ssl/ssl_engine_pphrase.c b/modules/ssl/ssl_engine_pphrase.c
new file mode 100644
index 0000000..d1859f7
--- /dev/null
+++ b/modules/ssl/ssl_engine_pphrase.c
@@ -0,0 +1,911 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* _ _
+ * _ __ ___ ___ __| | ___ ___| | mod_ssl
+ * | '_ ` _ \ / _ \ / _` | / __/ __| | Apache Interface to OpenSSL
+ * | | | | | | (_) | (_| | \__ \__ \ |
+ * |_| |_| |_|\___/ \__,_|___|___/___/_|
+ * |_____|
+ * ssl_engine_pphrase.c
+ * Pass Phrase Dialog
+ */
+ /* ``Treat your password like your
+ toothbrush. Don't let anybody
+ else use it, and get a new one
+ every six months.''
+ -- Clifford Stoll */
+#include "ssl_private.h"
+
+typedef struct {
+ server_rec *s;
+ apr_pool_t *p;
+ apr_array_header_t *aPassPhrase;
+ int nPassPhraseCur;
+ char *cpPassPhraseCur;
+ int nPassPhraseDialog;
+ int nPassPhraseDialogCur;
+ BOOL bPassPhraseDialogOnce;
+ const char *key_id;
+ const char *pkey_file;
+} pphrase_cb_arg_t;
+
+#ifdef HAVE_ECC
+static const char *key_types[] = {"RSA", "DSA", "ECC"};
+#else
+static const char *key_types[] = {"RSA", "DSA"};
+#endif
+
+/*
+ * Return true if the named file exists and is readable
+ */
+
+static apr_status_t exists_and_readable(const char *fname, apr_pool_t *pool,
+ apr_time_t *mtime)
+{
+ apr_status_t stat;
+ apr_finfo_t sbuf;
+ apr_file_t *fd;
+
+ if ((stat = apr_stat(&sbuf, fname, APR_FINFO_MIN, pool)) != APR_SUCCESS)
+ return stat;
+
+ if (sbuf.filetype != APR_REG)
+ return APR_EGENERAL;
+
+ if ((stat = apr_file_open(&fd, fname, APR_READ, 0, pool)) != APR_SUCCESS)
+ return stat;
+
+ if (mtime) {
+ *mtime = sbuf.mtime;
+ }
+
+ apr_file_close(fd);
+ return APR_SUCCESS;
+}
+
+/*
+ * reuse vhost keys for asn1 tables where keys are allocated out
+ * of s->process->pool to prevent "leaking" each time we format
+ * a vhost key. since the key is stored in a table with lifetime
+ * of s->process->pool, the key needs to have the same lifetime.
+ *
+ * XXX: probably seems silly to use a hash table with keys and values
+ * being the same, but it is easier than doing a linear search
+ * and will make it easier to remove keys if needed in the future.
+ * also have the problem with apr_array_header_t that if we
+ * underestimate the number of vhost keys when we apr_array_make(),
+ * the array will get resized when we push past the initial number
+ * of elts. this resizing in the s->process->pool means "leaking"
+ * since apr_array_push() will apr_alloc arr->nalloc * 2 elts,
+ * leaving the original arr->elts to waste.
+ */
+static const char *asn1_table_vhost_key(SSLModConfigRec *mc, apr_pool_t *p,
+ const char *id, int i)
+{
+ /* 'p' pool used here is cleared on restarts (or sooner) */
+ char *key = apr_psprintf(p, "%s:%d", id, i);
+ void *keyptr = apr_hash_get(mc->tVHostKeys, key,
+ APR_HASH_KEY_STRING);
+
+ if (!keyptr) {
+ /* make a copy out of s->process->pool */
+ keyptr = apr_pstrdup(mc->pPool, key);
+ apr_hash_set(mc->tVHostKeys, keyptr,
+ APR_HASH_KEY_STRING, keyptr);
+ }
+
+ return (char *)keyptr;
+}
+
+/* _________________________________________________________________
+**
+** Pass Phrase and Private Key Handling
+** _________________________________________________________________
+*/
+
+#define BUILTIN_DIALOG_BACKOFF 2
+#define BUILTIN_DIALOG_RETRIES 5
+
+static apr_file_t *writetty = NULL;
+static apr_file_t *readtty = NULL;
+
+int ssl_pphrase_Handle_CB(char *, int, int, void *);
+
+static char *pphrase_array_get(apr_array_header_t *arr, int idx)
+{
+ if ((idx < 0) || (idx >= arr->nelts)) {
+ return NULL;
+ }
+
+ return ((char **)arr->elts)[idx];
+}
+
+apr_status_t ssl_load_encrypted_pkey(server_rec *s, apr_pool_t *p, int idx,
+ const char *pkey_file,
+ apr_array_header_t **pphrases)
+{
+ SSLModConfigRec *mc = myModConfig(s);
+ SSLSrvConfigRec *sc = mySrvConfig(s);
+ const char *key_id = asn1_table_vhost_key(mc, p, sc->vhost_id, idx);
+ EVP_PKEY *pPrivateKey = NULL;
+ ssl_asn1_t *asn1;
+ int nPassPhrase = (*pphrases)->nelts;
+ int nPassPhraseRetry = 0;
+ apr_time_t pkey_mtime = 0;
+ apr_status_t rv;
+ pphrase_cb_arg_t ppcb_arg;
+
+ if (!pkey_file) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02573)
+ "Init: No private key specified for %s", key_id);
+ return ssl_die(s);
+ }
+ else if ((rv = exists_and_readable(pkey_file, p, &pkey_mtime))
+ != APR_SUCCESS ) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s, APLOGNO(02574)
+ "Init: Can't open server private key file %s", pkey_file);
+ return ssl_die(s);
+ }
+
+ ppcb_arg.s = s;
+ ppcb_arg.p = p;
+ ppcb_arg.aPassPhrase = *pphrases;
+ ppcb_arg.nPassPhraseCur = 0;
+ ppcb_arg.cpPassPhraseCur = NULL;
+ ppcb_arg.nPassPhraseDialog = 0;
+ ppcb_arg.nPassPhraseDialogCur = 0;
+ ppcb_arg.bPassPhraseDialogOnce = TRUE;
+ ppcb_arg.key_id = key_id;
+ ppcb_arg.pkey_file = pkey_file;
+
+ /*
+ * if the private key is encrypted and SSLPassPhraseDialog
+ * is configured to "builtin" it isn't possible to prompt for
+ * a password after httpd has detached from the tty.
+ * in this case if we already have a private key and the
+ * file name/mtime hasn't changed, then reuse the existing key.
+ * we also reuse existing private keys that were encrypted for
+ * exec: and pipe: dialogs to minimize chances to snoop the
+ * password. that and pipe: dialogs might prompt the user
+ * for password, which on win32 for example could happen 4
+ * times at startup. twice for each child and twice within
+ * each since apache "restarts itself" on startup.
+ * of course this will not work for the builtin dialog if
+ * the server was started without LoadModule ssl_module
+ * configured, then restarted with it configured.
+ * but we fall through with a chance of success if the key
+ * is not encrypted or can be handled via exec or pipe dialog.
+ * and in the case of fallthrough, pkey_mtime and isatty()
+ * are used to give a better idea as to what failed.
+ */
+ if (pkey_mtime) {
+ ssl_asn1_t *asn1 = ssl_asn1_table_get(mc->tPrivateKey, key_id);
+ if (asn1 && (asn1->source_mtime == pkey_mtime)) {
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(02575)
+ "Reusing existing private key from %s on restart",
+ ppcb_arg.pkey_file);
+ return APR_SUCCESS;
+ }
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(02576)
+ "Attempting to load encrypted (?) private key %s", key_id);
+
+ for (;;) {
+ /*
+ * Try to read the private key file with the help of
+ * the callback function which serves the pass
+ * phrases to OpenSSL
+ */
+
+ ppcb_arg.cpPassPhraseCur = NULL;
+
+ /* Ensure that the error stack is empty; some SSL
+ * functions will fail spuriously if the error stack
+ * is not empty. */
+ ERR_clear_error();
+
+ pPrivateKey = modssl_read_privatekey(ppcb_arg.pkey_file,
+ ssl_pphrase_Handle_CB, &ppcb_arg);
+ /* If the private key was successfully read, nothing more to
+ do here. */
+ if (pPrivateKey != NULL)
+ break;
+
+ /*
+ * when we have more remembered pass phrases
+ * try to reuse these first.
+ */
+ if (ppcb_arg.nPassPhraseCur < nPassPhrase) {
+ ppcb_arg.nPassPhraseCur++;
+ continue;
+ }
+
+ /*
+ * else it's not readable and we have no more
+ * remembered pass phrases. Then this has to mean
+ * that the callback function popped up the dialog
+ * but a wrong pass phrase was entered. We give the
+ * user (but not the dialog program) a few more
+ * chances...
+ */
+#ifndef WIN32
+ if ((sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN
+ || sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE)
+#else
+ if (sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE
+#endif
+ && ppcb_arg.cpPassPhraseCur != NULL
+ && nPassPhraseRetry < BUILTIN_DIALOG_RETRIES ) {
+ apr_file_printf(writetty, "Apache:mod_ssl:Error: Pass phrase incorrect "
+ "(%d more retr%s permitted).\n",
+ (BUILTIN_DIALOG_RETRIES-nPassPhraseRetry),
+ (BUILTIN_DIALOG_RETRIES-nPassPhraseRetry) == 1 ? "y" : "ies");
+ nPassPhraseRetry++;
+ if (nPassPhraseRetry > BUILTIN_DIALOG_BACKOFF)
+ apr_sleep((nPassPhraseRetry-BUILTIN_DIALOG_BACKOFF)
+ * 5 * APR_USEC_PER_SEC);
+ continue;
+ }
+#ifdef WIN32
+ if (sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02577)
+ "Init: SSLPassPhraseDialog builtin is not "
+ "supported on Win32 (key file "
+ "%s)", ppcb_arg.pkey_file);
+ return ssl_die(s);
+ }
+#endif /* WIN32 */
+
+ /*
+ * Ok, anything else now means a fatal error.
+ */
+ if (ppcb_arg.cpPassPhraseCur == NULL) {
+ if (ppcb_arg.nPassPhraseDialogCur && pkey_mtime &&
+ !isatty(fileno(stdout))) /* XXX: apr_isatty() */
+ {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0,
+ s, APLOGNO(02578)
+ "Init: Unable to read pass phrase "
+ "[Hint: key introduced or changed "
+ "before restart?]");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, s);
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0,
+ s, APLOGNO(02579) "Init: Private key not found");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, s);
+ }
+ if (writetty) {
+ apr_file_printf(writetty, "Apache:mod_ssl:Error: Private key not found.\n");
+ apr_file_printf(writetty, "**Stopped\n");
+ }
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02580)
+ "Init: Pass phrase incorrect for key %s",
+ key_id);
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+
+ if (writetty) {
+ apr_file_printf(writetty, "Apache:mod_ssl:Error: Pass phrase incorrect.\n");
+ apr_file_printf(writetty, "**Stopped\n");
+ }
+ }
+ return ssl_die(s);
+ }
+
+ if (pPrivateKey == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02581)
+ "Init: Unable to read server private key from file %s",
+ ppcb_arg.pkey_file);
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+ return ssl_die(s);
+ }
+
+ /*
+ * Log the type of reading
+ */
+ if (ppcb_arg.nPassPhraseDialogCur == 0) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02582)
+ "unencrypted %s private key - pass phrase not "
+ "required", key_id);
+ }
+ else {
+ if (ppcb_arg.cpPassPhraseCur != NULL) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0,
+ s, APLOGNO(02583)
+ "encrypted %s private key - pass phrase "
+ "requested", key_id);
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0,
+ s, APLOGNO(02584)
+ "encrypted %s private key - pass phrase"
+ " reused", key_id);
+ }
+ }
+
+ /*
+ * Ok, when we have one more pass phrase store it
+ */
+ if (ppcb_arg.cpPassPhraseCur != NULL) {
+ *(const char **)apr_array_push(ppcb_arg.aPassPhrase) =
+ ppcb_arg.cpPassPhraseCur;
+ nPassPhrase++;
+ }
+
+ /* Cache the private key in the global module configuration so it
+ * can be used after subsequent reloads. */
+ asn1 = ssl_asn1_table_set(mc->tPrivateKey, key_id, pPrivateKey);
+
+ if (ppcb_arg.nPassPhraseDialogCur != 0) {
+ /* remember mtime of encrypted keys */
+ asn1->source_mtime = pkey_mtime;
+ }
+
+ /*
+ * Free the private key structure
+ */
+ EVP_PKEY_free(pPrivateKey);
+
+ /*
+ * Let the user know when we're successful.
+ */
+ if ((ppcb_arg.nPassPhraseDialog > 0) &&
+ (ppcb_arg.cpPassPhraseCur != NULL)) {
+ if (writetty) {
+ apr_file_printf(writetty, "\n"
+ "OK: Pass Phrase Dialog successful.\n");
+ }
+ }
+
+ /* Close the pipes if they were opened
+ */
+ if (readtty) {
+ apr_file_close(readtty);
+ apr_file_close(writetty);
+ readtty = writetty = NULL;
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t ssl_pipe_child_create(apr_pool_t *p, const char *progname)
+{
+ /* Child process code for 'ErrorLog "|..."';
+ * may want a common framework for this, since I expect it will
+ * be common for other foo-loggers to want this sort of thing...
+ */
+ apr_status_t rc;
+ apr_procattr_t *procattr;
+ apr_proc_t *procnew;
+
+ if (((rc = apr_procattr_create(&procattr, p)) == APR_SUCCESS) &&
+ ((rc = apr_procattr_io_set(procattr,
+ APR_FULL_BLOCK,
+ APR_FULL_BLOCK,
+ APR_NO_PIPE)) == APR_SUCCESS)) {
+ char **args;
+
+ apr_tokenize_to_argv(progname, &args, p);
+ procnew = (apr_proc_t *)apr_pcalloc(p, sizeof(*procnew));
+ rc = apr_proc_create(procnew, args[0], (const char * const *)args,
+ NULL, procattr, p);
+ if (rc == APR_SUCCESS) {
+ /* XXX: not sure if we aught to...
+ * apr_pool_note_subprocess(p, procnew, APR_KILL_AFTER_TIMEOUT);
+ */
+ writetty = procnew->in;
+ readtty = procnew->out;
+ }
+ }
+
+ return rc;
+}
+
+static int pipe_get_passwd_cb(char *buf, int length, char *prompt, int verify)
+{
+ apr_status_t rc;
+ char *p;
+
+ apr_file_puts(prompt, writetty);
+
+ buf[0]='\0';
+ rc = apr_file_gets(buf, length, readtty);
+ apr_file_puts(APR_EOL_STR, writetty);
+
+ if (rc != APR_SUCCESS || apr_file_eof(readtty)) {
+ memset(buf, 0, length);
+ return 1; /* failure */
+ }
+ if ((p = strchr(buf, '\n')) != NULL) {
+ *p = '\0';
+ }
+#ifdef WIN32
+ /* XXX: apr_sometest */
+ if ((p = strchr(buf, '\r')) != NULL) {
+ *p = '\0';
+ }
+#endif
+ return 0;
+}
+
+int ssl_pphrase_Handle_CB(char *buf, int bufsize, int verify, void *srv)
+{
+ pphrase_cb_arg_t *ppcb_arg = (pphrase_cb_arg_t *)srv;
+ SSLSrvConfigRec *sc = mySrvConfig(ppcb_arg->s);
+ char *cpp;
+ int len = -1;
+
+ ppcb_arg->nPassPhraseDialog++;
+ ppcb_arg->nPassPhraseDialogCur++;
+
+ /*
+ * When remembered pass phrases are available use them...
+ */
+ if ((cpp = pphrase_array_get(ppcb_arg->aPassPhrase,
+ ppcb_arg->nPassPhraseCur)) != NULL) {
+ apr_cpystrn(buf, cpp, bufsize);
+ len = strlen(buf);
+ return len;
+ }
+
+ /*
+ * Builtin or Pipe dialog
+ */
+ if (sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN
+ || sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE) {
+ char *prompt;
+ int i;
+
+ if (sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE) {
+ if (!readtty) {
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, ppcb_arg->s,
+ APLOGNO(01965)
+ "Init: Creating pass phrase dialog pipe child "
+ "'%s'", sc->server->pphrase_dialog_path);
+ if (ssl_pipe_child_create(ppcb_arg->p,
+ sc->server->pphrase_dialog_path)
+ != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, ppcb_arg->s,
+ APLOGNO(01966)
+ "Init: Failed to create pass phrase pipe '%s'",
+ sc->server->pphrase_dialog_path);
+ PEMerr(PEM_F_PEM_DEF_CALLBACK,
+ PEM_R_PROBLEMS_GETTING_PASSWORD);
+ memset(buf, 0, (unsigned int)bufsize);
+ return (-1);
+ }
+ }
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, ppcb_arg->s, APLOGNO(01967)
+ "Init: Requesting pass phrase via piped dialog");
+ }
+ else { /* sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN */
+#ifdef WIN32
+ PEMerr(PEM_F_PEM_DEF_CALLBACK, PEM_R_PROBLEMS_GETTING_PASSWORD);
+ memset(buf, 0, (unsigned int)bufsize);
+ return (-1);
+#else
+ /*
+ * stderr has already been redirected to the error_log.
+ * rather than attempting to temporarily rehook it to the terminal,
+ * we print the prompt to stdout before EVP_read_pw_string turns
+ * off tty echo
+ */
+ apr_file_open_stdout(&writetty, ppcb_arg->p);
+
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, ppcb_arg->s, APLOGNO(01968)
+ "Init: Requesting pass phrase via builtin terminal "
+ "dialog");
+#endif
+ }
+
+ /*
+ * The first time display a header to inform the user about what
+ * program he actually speaks to, which module is responsible for
+ * this terminal dialog and why to the hell he has to enter
+ * something...
+ */
+ if (ppcb_arg->nPassPhraseDialog == 1) {
+ apr_file_printf(writetty, "%s mod_ssl (Pass Phrase Dialog)\n",
+ AP_SERVER_BASEVERSION);
+ apr_file_printf(writetty, "Some of your private key files are encrypted for security reasons.\n");
+ apr_file_printf(writetty, "In order to read them you have to provide the pass phrases.\n");
+ }
+ if (ppcb_arg->bPassPhraseDialogOnce) {
+ ppcb_arg->bPassPhraseDialogOnce = FALSE;
+ apr_file_printf(writetty, "\n");
+ apr_file_printf(writetty, "Private key %s (%s)\n",
+ ppcb_arg->key_id, ppcb_arg->pkey_file);
+ }
+
+ /*
+ * Emulate the OpenSSL internal pass phrase dialog
+ * (see crypto/pem/pem_lib.c:def_callback() for details)
+ */
+ prompt = "Enter pass phrase:";
+
+ for (;;) {
+ apr_file_puts(prompt, writetty);
+ if (sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE) {
+ i = pipe_get_passwd_cb(buf, bufsize, "", FALSE);
+ }
+ else { /* sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN */
+ i = EVP_read_pw_string(buf, bufsize, "", FALSE);
+ }
+ if (i != 0) {
+ PEMerr(PEM_F_PEM_DEF_CALLBACK,PEM_R_PROBLEMS_GETTING_PASSWORD);
+ memset(buf, 0, (unsigned int)bufsize);
+ return (-1);
+ }
+ len = strlen(buf);
+ if (len < 1)
+ apr_file_printf(writetty, "Apache:mod_ssl:Error: Pass phrase empty (needs to be at least 1 character).\n");
+ else
+ break;
+ }
+ }
+
+ /*
+ * Filter program
+ */
+ else if (sc->server->pphrase_dialog_type == SSL_PPTYPE_FILTER) {
+ const char *cmd = sc->server->pphrase_dialog_path;
+ const char **argv = apr_palloc(ppcb_arg->p, sizeof(char *) * 4);
+ const char *idx = ap_strrchr_c(ppcb_arg->key_id, ':') + 1;
+ char *result;
+ int i;
+
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, ppcb_arg->s, APLOGNO(01969)
+ "Init: Requesting pass phrase from dialog filter "
+ "program (%s)", cmd);
+
+ argv[0] = cmd;
+ argv[1] = apr_pstrndup(ppcb_arg->p, ppcb_arg->key_id,
+ idx-1 - ppcb_arg->key_id);
+ if ((i = atoi(idx)) < CERTKEYS_IDX_MAX+1) {
+ /*
+ * For compatibility with existing 2.4.x configurations, use
+ * "RSA", "DSA" and "ECC" strings for the first two/three keys
+ */
+ argv[2] = key_types[i];
+ } else {
+ /* Four and above: use the integer index */
+ argv[2] = apr_pstrdup(ppcb_arg->p, idx);
+ }
+ argv[3] = NULL;
+
+ result = ssl_util_readfilter(ppcb_arg->s, ppcb_arg->p, cmd, argv);
+ apr_cpystrn(buf, result, bufsize);
+ len = strlen(buf);
+ }
+
+ /*
+ * Ok, we now have the pass phrase, so give it back
+ */
+ ppcb_arg->cpPassPhraseCur = apr_pstrdup(ppcb_arg->p, buf);
+
+ /*
+ * And return its length to OpenSSL...
+ */
+ return (len);
+}
+
+
+#if defined(HAVE_OPENSSL_ENGINE_H) && defined(HAVE_ENGINE_INIT)
+
+/* OpenSSL UI implementation for passphrase entry; largely duplicated
+ * from ssl_pphrase_Handle_CB but adjusted for UI API. TODO: Might be
+ * worth trying to shift pphrase handling over to the UI API
+ * completely. */
+static int passphrase_ui_open(UI *ui)
+{
+ pphrase_cb_arg_t *ppcb = UI_get0_user_data(ui);
+ SSLSrvConfigRec *sc = mySrvConfig(ppcb->s);
+
+ ppcb->nPassPhraseDialog++;
+ ppcb->nPassPhraseDialogCur++;
+
+ /*
+ * Builtin or Pipe dialog
+ */
+ if (sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN
+ || sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE) {
+ if (sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE) {
+ if (!readtty) {
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, ppcb->s,
+ APLOGNO(10143)
+ "Init: Creating pass phrase dialog pipe child "
+ "'%s'", sc->server->pphrase_dialog_path);
+ if (ssl_pipe_child_create(ppcb->p,
+ sc->server->pphrase_dialog_path)
+ != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, ppcb->s,
+ APLOGNO(10144)
+ "Init: Failed to create pass phrase pipe '%s'",
+ sc->server->pphrase_dialog_path);
+ return 0;
+ }
+ }
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, ppcb->s, APLOGNO(10145)
+ "Init: Requesting pass phrase via piped dialog");
+ }
+ else { /* sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN */
+#ifdef WIN32
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, ppcb->s, APLOGNO(10146)
+ "Init: Failed to create pass phrase pipe '%s'",
+ sc->server->pphrase_dialog_path);
+ return 0;
+#else
+ /*
+ * stderr has already been redirected to the error_log.
+ * rather than attempting to temporarily rehook it to the terminal,
+ * we print the prompt to stdout before EVP_read_pw_string turns
+ * off tty echo
+ */
+ apr_file_open_stdout(&writetty, ppcb->p);
+
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, ppcb->s, APLOGNO(10147)
+ "Init: Requesting pass phrase via builtin terminal "
+ "dialog");
+#endif
+ }
+
+ /*
+ * The first time display a header to inform the user about what
+ * program he actually speaks to, which module is responsible for
+ * this terminal dialog and why to the hell he has to enter
+ * something...
+ */
+ if (ppcb->nPassPhraseDialog == 1) {
+ apr_file_printf(writetty, "%s mod_ssl (Pass Phrase Dialog)\n",
+ AP_SERVER_BASEVERSION);
+ apr_file_printf(writetty,
+ "A pass phrase is required to access the private key.\n");
+ }
+ if (ppcb->bPassPhraseDialogOnce) {
+ ppcb->bPassPhraseDialogOnce = FALSE;
+ apr_file_printf(writetty, "\n");
+ apr_file_printf(writetty, "Private key %s (%s)\n",
+ ppcb->key_id, ppcb->pkey_file);
+ }
+ }
+
+ return 1;
+}
+
+static int passphrase_ui_read(UI *ui, UI_STRING *uis)
+{
+ pphrase_cb_arg_t *ppcb = UI_get0_user_data(ui);
+ SSLSrvConfigRec *sc = mySrvConfig(ppcb->s);
+ const char *prompt;
+ int i;
+ int bufsize;
+ int len;
+ char *buf;
+
+ prompt = UI_get0_output_string(uis);
+ if (prompt == NULL) {
+ prompt = "Enter pass phrase:";
+ }
+
+ /*
+ * Get the maximum expected size and allocate the buffer
+ */
+ bufsize = UI_get_result_maxsize(uis);
+ buf = apr_pcalloc(ppcb->p, bufsize);
+
+ if (sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN
+ || sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE) {
+ /*
+ * Get the pass phrase through a callback.
+ * Empty input is not accepted.
+ */
+ for (;;) {
+ if (sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE) {
+ i = pipe_get_passwd_cb(buf, bufsize, "", FALSE);
+ }
+ else { /* sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN */
+ i = EVP_read_pw_string(buf, bufsize, "", FALSE);
+ }
+ if (i != 0) {
+ OPENSSL_cleanse(buf, bufsize);
+ return 0;
+ }
+ len = strlen(buf);
+ if (len < 1){
+ apr_file_printf(writetty, "Apache:mod_ssl:Error: Pass phrase"
+ "empty (needs to be at least 1 character).\n");
+ apr_file_puts(prompt, writetty);
+ }
+ else {
+ break;
+ }
+ }
+ }
+ /*
+ * Filter program
+ */
+ else if (sc->server->pphrase_dialog_type == SSL_PPTYPE_FILTER) {
+ const char *cmd = sc->server->pphrase_dialog_path;
+ const char **argv = apr_palloc(ppcb->p, sizeof(char *) * 3);
+ char *result;
+
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, ppcb->s, APLOGNO(10148)
+ "Init: Requesting pass phrase from dialog filter "
+ "program (%s)", cmd);
+
+ argv[0] = cmd;
+ argv[1] = ppcb->key_id;
+ argv[2] = NULL;
+
+ result = ssl_util_readfilter(ppcb->s, ppcb->p, cmd, argv);
+ apr_cpystrn(buf, result, bufsize);
+ len = strlen(buf);
+ }
+
+ /*
+ * Ok, we now have the pass phrase, so give it back
+ */
+ ppcb->cpPassPhraseCur = apr_pstrdup(ppcb->p, buf);
+ UI_set_result(ui, uis, buf);
+
+ /* Clear sensitive data. */
+ OPENSSL_cleanse(buf, bufsize);
+ return 1;
+}
+
+static int passphrase_ui_write(UI *ui, UI_STRING *uis)
+{
+ pphrase_cb_arg_t *ppcb = UI_get0_user_data(ui);
+ SSLSrvConfigRec *sc;
+ const char *prompt;
+
+ sc = mySrvConfig(ppcb->s);
+
+ if (sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN
+ || sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE) {
+ prompt = UI_get0_output_string(uis);
+ apr_file_puts(prompt, writetty);
+ }
+
+ return 1;
+}
+
+static int passphrase_ui_close(UI *ui)
+{
+ /*
+ * Close the pipes if they were opened
+ */
+ if (readtty) {
+ apr_file_close(readtty);
+ apr_file_close(writetty);
+ readtty = writetty = NULL;
+ }
+ return 1;
+}
+
+static apr_status_t pp_ui_method_cleanup(void *uip)
+{
+ UI_METHOD *uim = uip;
+
+ UI_destroy_method(uim);
+
+ return APR_SUCCESS;
+}
+
+static UI_METHOD *get_passphrase_ui(apr_pool_t *p)
+{
+ UI_METHOD *ui_method = UI_create_method("Passphrase UI");
+
+ UI_method_set_opener(ui_method, passphrase_ui_open);
+ UI_method_set_reader(ui_method, passphrase_ui_read);
+ UI_method_set_writer(ui_method, passphrase_ui_write);
+ UI_method_set_closer(ui_method, passphrase_ui_close);
+
+ apr_pool_cleanup_register(p, ui_method, pp_ui_method_cleanup,
+ pp_ui_method_cleanup);
+
+ return ui_method;
+}
+#endif
+
+
+apr_status_t modssl_load_engine_keypair(server_rec *s, apr_pool_t *p,
+ const char *vhostid,
+ const char *certid, const char *keyid,
+ X509 **pubkey, EVP_PKEY **privkey)
+{
+#if defined(HAVE_OPENSSL_ENGINE_H) && defined(HAVE_ENGINE_INIT)
+ const char *c, *scheme;
+ ENGINE *e;
+ UI_METHOD *ui_method = get_passphrase_ui(p);
+ pphrase_cb_arg_t ppcb;
+
+ memset(&ppcb, 0, sizeof ppcb);
+ ppcb.s = s;
+ ppcb.p = p;
+ ppcb.bPassPhraseDialogOnce = TRUE;
+ ppcb.key_id = vhostid;
+ ppcb.pkey_file = keyid;
+
+ c = ap_strchr_c(keyid, ':');
+ if (!c || c == keyid) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10131)
+ "Init: Unrecognized private key identifier `%s'",
+ keyid);
+ return ssl_die(s);
+ }
+
+ scheme = apr_pstrmemdup(p, keyid, c - keyid);
+ if (!(e = ENGINE_by_id(scheme))) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10132)
+ "Init: Failed to load engine for private key %s",
+ keyid);
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+ return ssl_die(s);
+ }
+
+ if (!ENGINE_init(e)) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10149)
+ "Init: Failed to initialize engine %s for private key %s",
+ scheme, keyid);
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+ return ssl_die(s);
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+ "Init: Initialized engine %s for private key %s",
+ scheme, keyid);
+
+ if (APLOGdebug(s)) {
+ ENGINE_ctrl_cmd_string(e, "VERBOSE", NULL, 0);
+ }
+
+ if (certid) {
+ struct {
+ const char *cert_id;
+ X509 *cert;
+ } params = { certid, NULL };
+
+ if (!ENGINE_ctrl_cmd(e, "LOAD_CERT_CTRL", 0, &params, NULL, 1)) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10136)
+ "Init: Unable to get the certificate");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+ return ssl_die(s);
+ }
+
+ *pubkey = params.cert;
+ }
+
+ *privkey = ENGINE_load_private_key(e, keyid, ui_method, &ppcb);
+ if (*privkey == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10133)
+ "Init: Unable to get the private key");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+ return ssl_die(s);
+ }
+
+ ENGINE_finish(e);
+ ENGINE_free(e);
+
+ return APR_SUCCESS;
+#else
+ return APR_ENOTIMPL;
+#endif
+}
diff --git a/modules/ssl/ssl_engine_rand.c b/modules/ssl/ssl_engine_rand.c
new file mode 100644
index 0000000..4e1a9c1
--- /dev/null
+++ b/modules/ssl/ssl_engine_rand.c
@@ -0,0 +1,177 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* _ _
+ * _ __ ___ ___ __| | ___ ___| | mod_ssl
+ * | '_ ` _ \ / _ \ / _` | / __/ __| | Apache Interface to OpenSSL
+ * | | | | | | (_) | (_| | \__ \__ \ |
+ * |_| |_| |_|\___/ \__,_|___|___/___/_|
+ * |_____|
+ * ssl_engine_rand.c
+ * Random Number Generator Seeding
+ */
+ /* ``The generation of random
+ numbers is too important
+ to be left to chance.'' */
+
+#include "ssl_private.h"
+
+/* _________________________________________________________________
+**
+** Support for better seeding of SSL library's RNG
+** _________________________________________________________________
+*/
+
+static int ssl_rand_choosenum(int, int);
+static int ssl_rand_feedfp(apr_pool_t *, apr_file_t *, int);
+
+int ssl_rand_seed(server_rec *s, apr_pool_t *p, ssl_rsctx_t nCtx, char *prefix)
+{
+ SSLModConfigRec *mc;
+ apr_array_header_t *apRandSeed;
+ ssl_randseed_t *pRandSeeds;
+ ssl_randseed_t *pRandSeed;
+ unsigned char stackdata[256];
+ int nDone;
+ apr_file_t *fp;
+ int i, n, l;
+
+ mc = myModConfig(s);
+ nDone = 0;
+ apRandSeed = mc->aRandSeed;
+ pRandSeeds = (ssl_randseed_t *)apRandSeed->elts;
+ for (i = 0; i < apRandSeed->nelts; i++) {
+ pRandSeed = &pRandSeeds[i];
+ if (pRandSeed->nCtx == nCtx) {
+ if (pRandSeed->nSrc == SSL_RSSRC_FILE) {
+ /*
+ * seed in contents of an external file
+ */
+ if (apr_file_open(&fp, pRandSeed->cpPath,
+ APR_READ, APR_OS_DEFAULT, p) != APR_SUCCESS)
+ continue;
+ nDone += ssl_rand_feedfp(p, fp, pRandSeed->nBytes);
+ apr_file_close(fp);
+ }
+ else if (pRandSeed->nSrc == SSL_RSSRC_EXEC) {
+ const char *cmd = pRandSeed->cpPath;
+ const char **argv = apr_palloc(p, sizeof(char *) * 3);
+ /*
+ * seed in contents generated by an external program
+ */
+ argv[0] = cmd;
+ argv[1] = apr_itoa(p, pRandSeed->nBytes);
+ argv[2] = NULL;
+
+ if ((fp = ssl_util_ppopen(s, p, cmd, argv)) == NULL)
+ continue;
+ nDone += ssl_rand_feedfp(p, fp, pRandSeed->nBytes);
+ ssl_util_ppclose(s, p, fp);
+ }
+#ifdef HAVE_RAND_EGD
+ else if (pRandSeed->nSrc == SSL_RSSRC_EGD) {
+ /*
+ * seed in contents provided by the external
+ * Entropy Gathering Daemon (EGD)
+ */
+ if ((n = RAND_egd(pRandSeed->cpPath)) == -1)
+ continue;
+ nDone += n;
+ }
+#endif
+ else if (pRandSeed->nSrc == SSL_RSSRC_BUILTIN) {
+ struct {
+ time_t t;
+ pid_t pid;
+ } my_seed;
+
+ /*
+ * seed in the current time (usually just 4 bytes)
+ */
+ my_seed.t = time(NULL);
+
+ /*
+ * seed in the current process id (usually just 4 bytes)
+ */
+ my_seed.pid = mc->pid;
+
+ l = sizeof(my_seed);
+ RAND_seed((unsigned char *)&my_seed, l);
+ nDone += l;
+
+ /*
+ * seed in some current state of the run-time stack (128 bytes)
+ */
+ n = ssl_rand_choosenum(0, sizeof(stackdata)-128-1);
+ RAND_seed(stackdata+n, 128);
+ nDone += 128;
+
+ }
+ }
+ }
+ ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, s,
+ "%sSeeding PRNG with %d bytes of entropy", prefix, nDone);
+
+ if (RAND_status() == 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(01990)
+ "%sPRNG still contains insufficient entropy!", prefix);
+
+ return nDone;
+}
+
+#define BUFSIZE 8192
+
+static int ssl_rand_feedfp(apr_pool_t *p, apr_file_t *fp, int nReq)
+{
+ apr_size_t nDone;
+ unsigned char caBuf[BUFSIZE];
+ apr_size_t nBuf;
+ apr_size_t nRead;
+ apr_size_t nTodo;
+
+ nDone = 0;
+ nRead = BUFSIZE;
+ nTodo = nReq;
+ while (1) {
+ if (nReq > 0)
+ nRead = (nTodo < BUFSIZE ? nTodo : BUFSIZE);
+ nBuf = nRead;
+ if (apr_file_read(fp, caBuf, &nBuf) != APR_SUCCESS)
+ break;
+ RAND_seed(caBuf, nBuf);
+ nDone += nBuf;
+ if (nReq > 0) {
+ nTodo -= nBuf;
+ if (nTodo <= 0)
+ break;
+ }
+ }
+ return nDone;
+}
+
+static int ssl_rand_choosenum(int l, int h)
+{
+ int i;
+ char buf[50];
+
+ apr_snprintf(buf, sizeof(buf), "%.0f",
+ (((double)(rand()%RAND_MAX)/RAND_MAX)*(h-l)));
+ i = atoi(buf)+1;
+ if (i < l) i = l;
+ if (i > h) i = h;
+ return i;
+}
+
diff --git a/modules/ssl/ssl_engine_vars.c b/modules/ssl/ssl_engine_vars.c
new file mode 100644
index 0000000..418d849
--- /dev/null
+++ b/modules/ssl/ssl_engine_vars.c
@@ -0,0 +1,1230 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* _ _
+ * _ __ ___ ___ __| | ___ ___| | mod_ssl
+ * | '_ ` _ \ / _ \ / _` | / __/ __| | Apache Interface to OpenSSL
+ * | | | | | | (_) | (_| | \__ \__ \ |
+ * |_| |_| |_|\___/ \__,_|___|___/___/_|
+ * |_____|
+ * ssl_engine_vars.c
+ * Variable Lookup Facility
+ */
+ /* ``Those of you who think they
+ know everything are very annoying
+ to those of us who do.''
+ -- Unknown */
+#include "ssl_private.h"
+#include "mod_ssl.h"
+#include "ap_expr.h"
+
+#include "apr_time.h"
+
+/* _________________________________________________________________
+**
+** Variable Lookup
+** _________________________________________________________________
+*/
+
+static char *ssl_var_lookup_ssl(apr_pool_t *p, SSLConnRec *sslconn, request_rec *r, char *var);
+static char *ssl_var_lookup_ssl_cert(apr_pool_t *p, request_rec *r, X509 *xs, char *var);
+static char *ssl_var_lookup_ssl_cert_dn(apr_pool_t *p, X509_NAME *xsname, const char *var);
+static char *ssl_var_lookup_ssl_cert_san(apr_pool_t *p, X509 *xs, char *var);
+static char *ssl_var_lookup_ssl_cert_valid(apr_pool_t *p, ASN1_TIME *tm);
+static char *ssl_var_lookup_ssl_cert_remain(apr_pool_t *p, ASN1_TIME *tm);
+static char *ssl_var_lookup_ssl_cert_serial(apr_pool_t *p, X509 *xs);
+static char *ssl_var_lookup_ssl_cert_chain(apr_pool_t *p, STACK_OF(X509) *sk, char *var);
+static char *ssl_var_lookup_ssl_cert_rfc4523_cea(apr_pool_t *p, SSL *ssl);
+static char *ssl_var_lookup_ssl_cert_PEM(apr_pool_t *p, X509 *xs);
+static char *ssl_var_lookup_ssl_cert_verify(apr_pool_t *p, SSLConnRec *sslconn);
+static char *ssl_var_lookup_ssl_cipher(apr_pool_t *p, SSLConnRec *sslconn, char *var);
+static void ssl_var_lookup_ssl_cipher_bits(SSL *ssl, int *usekeysize, int *algkeysize);
+static char *ssl_var_lookup_ssl_version(apr_pool_t *p, char *var);
+static char *ssl_var_lookup_ssl_compress_meth(SSL *ssl);
+
+static SSLConnRec *ssl_get_effective_config(conn_rec *c)
+{
+ SSLConnRec *sslconn = myConnConfig(c);
+ if (!(sslconn && sslconn->ssl) && c->master) {
+ /* use master connection if no SSL defined here */
+ sslconn = myConnConfig(c->master);
+ }
+ return sslconn;
+}
+
+static int ssl_conn_is_ssl(conn_rec *c)
+{
+ const SSLConnRec *sslconn = ssl_get_effective_config(c);
+ return (sslconn && sslconn->ssl)? OK : DECLINED;
+}
+
+static const char var_interface[] = "mod_ssl/" AP_SERVER_BASEREVISION;
+static char var_library_interface[] = MODSSL_LIBRARY_TEXT;
+static char *var_library = NULL;
+
+static apr_array_header_t *expr_peer_ext_list_fn(ap_expr_eval_ctx_t *ctx,
+ const void *dummy,
+ const char *arg)
+{
+ return ssl_ext_list(ctx->p, ctx->c, 1, arg);
+}
+
+static const char *expr_var_fn(ap_expr_eval_ctx_t *ctx, const void *data)
+{
+ char *var = (char *)data;
+ SSLConnRec *sslconn = ssl_get_effective_config(ctx->c);
+
+ return sslconn ? ssl_var_lookup_ssl(ctx->p, sslconn, ctx->r, var) : NULL;
+}
+
+static const char *expr_func_fn(ap_expr_eval_ctx_t *ctx, const void *data,
+ const char *arg)
+{
+ char *var = (char *)arg;
+
+ return var ? ssl_var_lookup(ctx->p, ctx->s, ctx->c, ctx->r, var) : NULL;
+}
+
+static int ssl_expr_lookup(ap_expr_lookup_parms *parms)
+{
+ switch (parms->type) {
+ case AP_EXPR_FUNC_VAR:
+ /* for now, we just handle everything that starts with SSL_, but
+ * register our hook as APR_HOOK_LAST
+ * XXX: This can be optimized
+ */
+ if (strcEQn(parms->name, "SSL_", 4)) {
+ *parms->func = expr_var_fn;
+ *parms->data = parms->name + 4;
+ return OK;
+ }
+ break;
+ case AP_EXPR_FUNC_STRING:
+ /* Function SSL() is implemented by us.
+ */
+ if (strcEQ(parms->name, "SSL")) {
+ *parms->func = expr_func_fn;
+ *parms->data = NULL;
+ return OK;
+ }
+ break;
+ case AP_EXPR_FUNC_LIST:
+ if (strcEQ(parms->name, "PeerExtList")) {
+ *parms->func = expr_peer_ext_list_fn;
+ *parms->data = "PeerExtList";
+ return OK;
+ }
+ break;
+ }
+ return DECLINED;
+}
+
+
+void ssl_var_register(apr_pool_t *p)
+{
+ char *cp, *cp2;
+
+ ap_hook_ssl_conn_is_ssl(ssl_conn_is_ssl, NULL, NULL, APR_HOOK_MIDDLE);
+ APR_REGISTER_OPTIONAL_FN(ssl_var_lookup);
+ APR_REGISTER_OPTIONAL_FN(ssl_ext_list);
+
+ /* Perform once-per-process library version determination: */
+ var_library = apr_pstrdup(p, MODSSL_LIBRARY_DYNTEXT);
+
+ if ((cp = strchr(var_library, ' ')) != NULL) {
+ *cp = '/';
+ if ((cp2 = strchr(cp, ' ')) != NULL)
+ *cp2 = NUL;
+ }
+
+ if ((cp = strchr(var_library_interface, ' ')) != NULL) {
+ *cp = '/';
+ if ((cp2 = strchr(cp, ' ')) != NULL)
+ *cp2 = NUL;
+ }
+
+ ap_hook_expr_lookup(ssl_expr_lookup, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+/* This function must remain safe to use for a non-SSL connection. */
+char *ssl_var_lookup(apr_pool_t *p, server_rec *s, conn_rec *c, request_rec *r, char *var)
+{
+ SSLModConfigRec *mc = myModConfig(s);
+ const char *result;
+ BOOL resdup;
+ apr_time_exp_t tm;
+
+ result = NULL;
+ resdup = TRUE;
+
+ /*
+ * When no pool is given try to find one
+ */
+ if (p == NULL) {
+ if (r != NULL)
+ p = r->pool;
+ else if (c != NULL)
+ p = c->pool;
+ else
+ p = mc->pPool;
+ }
+
+ /*
+ * Request dependent stuff
+ */
+ if (r != NULL) {
+ switch (var[0]) {
+ case 'H':
+ case 'h':
+ if (strcEQ(var, "HTTP_USER_AGENT"))
+ result = apr_table_get(r->headers_in, "User-Agent");
+ else if (strcEQ(var, "HTTP_REFERER"))
+ result = apr_table_get(r->headers_in, "Referer");
+ else if (strcEQ(var, "HTTP_COOKIE"))
+ result = apr_table_get(r->headers_in, "Cookie");
+ else if (strcEQ(var, "HTTP_FORWARDED"))
+ result = apr_table_get(r->headers_in, "Forwarded");
+ else if (strcEQ(var, "HTTP_HOST"))
+ result = apr_table_get(r->headers_in, "Host");
+ else if (strcEQ(var, "HTTP_PROXY_CONNECTION"))
+ result = apr_table_get(r->headers_in, "Proxy-Connection");
+ else if (strcEQ(var, "HTTP_ACCEPT"))
+ result = apr_table_get(r->headers_in, "Accept");
+ else if (strlen(var) > 5 && strcEQn(var, "HTTP:", 5))
+ /* all other headers from which we are still not know about */
+ result = apr_table_get(r->headers_in, var+5);
+ break;
+
+ case 'R':
+ case 'r':
+ if (strcEQ(var, "REQUEST_METHOD"))
+ result = r->method;
+ else if (strcEQ(var, "REQUEST_SCHEME"))
+ result = ap_http_scheme(r);
+ else if (strcEQ(var, "REQUEST_URI"))
+ result = r->uri;
+ else if (strcEQ(var, "REQUEST_FILENAME"))
+ result = r->filename;
+ else if (strcEQ(var, "REMOTE_ADDR"))
+ result = r->useragent_ip;
+ else if (strcEQ(var, "REMOTE_HOST"))
+ result = ap_get_useragent_host(r, REMOTE_NAME, NULL);
+ else if (strcEQ(var, "REMOTE_IDENT"))
+ result = ap_get_remote_logname(r);
+ else if (strcEQ(var, "REMOTE_USER"))
+ result = r->user;
+ break;
+
+ case 'S':
+ case 's':
+ if (strcEQn(var, "SSL", 3)) break; /* shortcut common case */
+
+ if (strcEQ(var, "SERVER_ADMIN"))
+ result = r->server->server_admin;
+ else if (strcEQ(var, "SERVER_NAME"))
+ result = ap_get_server_name_for_url(r);
+ else if (strcEQ(var, "SERVER_PORT"))
+ result = apr_psprintf(p, "%u", ap_get_server_port(r));
+ else if (strcEQ(var, "SERVER_PROTOCOL"))
+ result = r->protocol;
+ else if (strcEQ(var, "SCRIPT_FILENAME"))
+ result = r->filename;
+ break;
+
+ default:
+ if (strcEQ(var, "PATH_INFO"))
+ result = r->path_info;
+ else if (strcEQ(var, "QUERY_STRING"))
+ result = r->args;
+ else if (strcEQ(var, "IS_SUBREQ"))
+ result = (r->main != NULL ? "true" : "false");
+ else if (strcEQ(var, "DOCUMENT_ROOT"))
+ result = ap_document_root(r);
+ else if (strcEQ(var, "AUTH_TYPE"))
+ result = r->ap_auth_type;
+ else if (strcEQ(var, "THE_REQUEST"))
+ result = r->the_request;
+ else if (strlen(var) > 4 && strcEQn(var, "ENV:", 4)) {
+ result = apr_table_get(r->notes, var+4);
+ if (result == NULL)
+ result = apr_table_get(r->subprocess_env, var+4);
+ }
+ break;
+ }
+ }
+
+ /*
+ * Connection stuff
+ */
+ if (result == NULL && c != NULL) {
+ SSLConnRec *sslconn = ssl_get_effective_config(c);
+ if (strlen(var) > 4 && strcEQn(var, "SSL_", 4)
+ && sslconn && sslconn->ssl)
+ result = ssl_var_lookup_ssl(p, sslconn, r, var+4);
+ else if (strcEQ(var, "HTTPS")) {
+ if (sslconn && sslconn->ssl)
+ result = "on";
+ else
+ result = "off";
+ }
+ }
+
+ /*
+ * Totally independent stuff
+ */
+ if (result == NULL) {
+ if (strlen(var) > 12 && strcEQn(var, "SSL_VERSION_", 12))
+ result = ssl_var_lookup_ssl_version(p, var+12);
+ else if (strcEQ(var, "SERVER_SOFTWARE"))
+ result = ap_get_server_banner();
+ else if (strcEQ(var, "API_VERSION")) {
+ result = apr_itoa(p, MODULE_MAGIC_NUMBER_MAJOR);
+ resdup = FALSE;
+ }
+ else if (strcEQ(var, "TIME_YEAR")) {
+ apr_time_exp_lt(&tm, apr_time_now());
+ result = apr_psprintf(p, "%02d%02d",
+ (tm.tm_year / 100) + 19, tm.tm_year % 100);
+ resdup = FALSE;
+ }
+#define MKTIMESTR(format, tmfield) \
+ apr_time_exp_lt(&tm, apr_time_now()); \
+ result = apr_psprintf(p, format, tm.tmfield); \
+ resdup = FALSE;
+ else if (strcEQ(var, "TIME_MON")) {
+ MKTIMESTR("%02d", tm_mon+1)
+ }
+ else if (strcEQ(var, "TIME_DAY")) {
+ MKTIMESTR("%02d", tm_mday)
+ }
+ else if (strcEQ(var, "TIME_HOUR")) {
+ MKTIMESTR("%02d", tm_hour)
+ }
+ else if (strcEQ(var, "TIME_MIN")) {
+ MKTIMESTR("%02d", tm_min)
+ }
+ else if (strcEQ(var, "TIME_SEC")) {
+ MKTIMESTR("%02d", tm_sec)
+ }
+ else if (strcEQ(var, "TIME_WDAY")) {
+ MKTIMESTR("%d", tm_wday)
+ }
+ else if (strcEQ(var, "TIME")) {
+ apr_time_exp_lt(&tm, apr_time_now());
+ result = apr_psprintf(p,
+ "%02d%02d%02d%02d%02d%02d%02d", (tm.tm_year / 100) + 19,
+ (tm.tm_year % 100), tm.tm_mon+1, tm.tm_mday,
+ tm.tm_hour, tm.tm_min, tm.tm_sec);
+ resdup = FALSE;
+ }
+ /* all other env-variables from the parent Apache process */
+ else if (strlen(var) > 4 && strcEQn(var, "ENV:", 4)) {
+ result = getenv(var+4);
+ }
+ }
+
+ if (result != NULL && resdup)
+ result = apr_pstrdup(p, result);
+ if (result == NULL)
+ result = "";
+ return (char *)result;
+}
+
+static char *ssl_var_lookup_ssl(apr_pool_t *p, SSLConnRec *sslconn,
+ request_rec *r, char *var)
+{
+ char *result;
+ X509 *xs;
+ STACK_OF(X509) *sk;
+ SSL *ssl;
+
+ result = NULL;
+
+ ssl = sslconn->ssl;
+ if (strlen(var) > 8 && strcEQn(var, "VERSION_", 8)) {
+ result = ssl_var_lookup_ssl_version(p, var+8);
+ }
+ else if (ssl != NULL && strcEQ(var, "PROTOCOL")) {
+ result = (char *)SSL_get_version(ssl);
+ }
+ else if (ssl != NULL && strcEQ(var, "SESSION_ID")) {
+ char buf[MODSSL_SESSION_ID_STRING_LEN];
+ SSL_SESSION *pSession = SSL_get_session(ssl);
+ if (pSession) {
+ IDCONST unsigned char *id;
+ unsigned int idlen;
+
+#ifdef OPENSSL_NO_SSL_INTERN
+ id = (unsigned char *)SSL_SESSION_get_id(pSession, &idlen);
+#else
+ id = pSession->session_id;
+ idlen = pSession->session_id_length;
+#endif
+
+ result = apr_pstrdup(p, modssl_SSL_SESSION_id2sz(id, idlen,
+ buf, sizeof(buf)));
+ }
+ }
+ else if(ssl != NULL && strcEQ(var, "SESSION_RESUMED")) {
+ if (SSL_session_reused(ssl) == 1)
+ result = "Resumed";
+ else
+ result = "Initial";
+ }
+ else if (ssl != NULL && strlen(var) >= 6 && strcEQn(var, "CIPHER", 6)) {
+ result = ssl_var_lookup_ssl_cipher(p, sslconn, var+6);
+ }
+ else if (ssl != NULL && strlen(var) > 18 && strcEQn(var, "CLIENT_CERT_CHAIN_", 18)) {
+ sk = SSL_get_peer_cert_chain(ssl);
+ result = ssl_var_lookup_ssl_cert_chain(p, sk, var+18);
+ }
+ else if (ssl != NULL && strcEQ(var, "CLIENT_CERT_RFC4523_CEA")) {
+ result = ssl_var_lookup_ssl_cert_rfc4523_cea(p, ssl);
+ }
+ else if (ssl != NULL && strcEQ(var, "CLIENT_VERIFY")) {
+ result = ssl_var_lookup_ssl_cert_verify(p, sslconn);
+ }
+ else if (ssl != NULL && strlen(var) > 7 && strcEQn(var, "CLIENT_", 7)) {
+ if ((xs = SSL_get_peer_certificate(ssl)) != NULL) {
+ result = ssl_var_lookup_ssl_cert(p, r, xs, var+7);
+ X509_free(xs);
+ }
+ }
+ else if (ssl != NULL && strlen(var) > 7 && strcEQn(var, "SERVER_", 7)) {
+ if ((xs = SSL_get_certificate(ssl)) != NULL) {
+ result = ssl_var_lookup_ssl_cert(p, r, xs, var+7);
+ /* SSL_get_certificate is different from SSL_get_peer_certificate.
+ * No need to X509_free(xs).
+ */
+ }
+ }
+ else if (ssl != NULL && strcEQ(var, "COMPRESS_METHOD")) {
+ result = ssl_var_lookup_ssl_compress_meth(ssl);
+ }
+#ifdef HAVE_TLSEXT
+ else if (ssl != NULL && strcEQ(var, "TLS_SNI")) {
+ result = apr_pstrdup(p, SSL_get_servername(ssl,
+ TLSEXT_NAMETYPE_host_name));
+ }
+#endif
+ else if (ssl != NULL && strcEQ(var, "SECURE_RENEG")) {
+ int flag = 0;
+#ifdef SSL_get_secure_renegotiation_support
+ flag = SSL_get_secure_renegotiation_support(ssl);
+#endif
+ result = apr_pstrdup(p, flag ? "true" : "false");
+ }
+#ifdef HAVE_SRP
+ else if (ssl != NULL && strcEQ(var, "SRP_USER")) {
+ if ((result = SSL_get_srp_username(ssl)) != NULL) {
+ result = apr_pstrdup(p, result);
+ }
+ }
+ else if (ssl != NULL && strcEQ(var, "SRP_USERINFO")) {
+ if ((result = SSL_get_srp_userinfo(ssl)) != NULL) {
+ result = apr_pstrdup(p, result);
+ }
+ }
+#endif
+
+ return result;
+}
+
+static char *ssl_var_lookup_ssl_cert_dn_oneline(apr_pool_t *p, request_rec *r,
+ X509_NAME *xsname)
+{
+ char *result = NULL;
+ SSLDirConfigRec *dc;
+ int legacy_format = 0;
+ if (r) {
+ dc = myDirConfig(r);
+ legacy_format = dc->nOptions & SSL_OPT_LEGACYDNFORMAT;
+ }
+ if (legacy_format) {
+ char *cp = X509_NAME_oneline(xsname, NULL, 0);
+ result = apr_pstrdup(p, cp);
+ OPENSSL_free(cp);
+ }
+ else {
+ BIO* bio;
+ unsigned long flags = XN_FLAG_RFC2253 & ~ASN1_STRFLGS_ESC_MSB;
+
+ if ((bio = BIO_new(BIO_s_mem())) == NULL)
+ return NULL;
+ X509_NAME_print_ex(bio, xsname, 0, flags);
+
+ result = modssl_bio_free_read(p, bio);
+ }
+ return result;
+}
+
+static char *ssl_var_lookup_ssl_cert(apr_pool_t *p, request_rec *r, X509 *xs,
+ char *var)
+{
+ char *result;
+ BOOL resdup;
+ X509_NAME *xsname;
+ int nid;
+
+ result = NULL;
+ resdup = TRUE;
+
+ if (strcEQ(var, "M_VERSION")) {
+ result = apr_psprintf(p, "%lu", X509_get_version(xs)+1);
+ resdup = FALSE;
+ }
+ else if (strcEQ(var, "M_SERIAL")) {
+ result = ssl_var_lookup_ssl_cert_serial(p, xs);
+ }
+ else if (strcEQ(var, "V_START")) {
+ result = ssl_var_lookup_ssl_cert_valid(p, X509_get_notBefore(xs));
+ }
+ else if (strcEQ(var, "V_END")) {
+ result = ssl_var_lookup_ssl_cert_valid(p, X509_get_notAfter(xs));
+ }
+ else if (strcEQ(var, "V_REMAIN")) {
+ result = ssl_var_lookup_ssl_cert_remain(p, X509_get_notAfter(xs));
+ resdup = FALSE;
+ }
+ else if (*var && strcEQ(var+1, "_DN")) {
+ if (*var == 'S')
+ xsname = X509_get_subject_name(xs);
+ else if (*var == 'I')
+ xsname = X509_get_issuer_name(xs);
+ else
+ return NULL;
+ result = ssl_var_lookup_ssl_cert_dn_oneline(p, r, xsname);
+ resdup = FALSE;
+ }
+ else if (strlen(var) > 5 && strcEQn(var+1, "_DN_", 4)) {
+ if (*var == 'S')
+ xsname = X509_get_subject_name(xs);
+ else if (*var == 'I')
+ xsname = X509_get_issuer_name(xs);
+ else
+ return NULL;
+ result = ssl_var_lookup_ssl_cert_dn(p, xsname, var+5);
+ resdup = FALSE;
+ }
+ else if (strlen(var) > 4 && strcEQn(var, "SAN_", 4)) {
+ result = ssl_var_lookup_ssl_cert_san(p, xs, var+4);
+ resdup = FALSE;
+ }
+ else if (strcEQ(var, "A_SIG")) {
+#if MODSSL_USE_OPENSSL_PRE_1_1_API
+ nid = OBJ_obj2nid((ASN1_OBJECT *)(xs->cert_info->signature->algorithm));
+#else
+ const ASN1_OBJECT *paobj;
+ X509_ALGOR_get0(&paobj, NULL, NULL, X509_get0_tbs_sigalg(xs));
+ nid = OBJ_obj2nid(paobj);
+#endif
+ result = apr_pstrdup(p,
+ (nid == NID_undef) ? "UNKNOWN" : OBJ_nid2ln(nid));
+ resdup = FALSE;
+ }
+ else if (strcEQ(var, "A_KEY")) {
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ nid = OBJ_obj2nid((ASN1_OBJECT *)(xs->cert_info->key->algor->algorithm));
+#else
+ ASN1_OBJECT *paobj;
+ X509_PUBKEY_get0_param(&paobj, NULL, 0, NULL, X509_get_X509_PUBKEY(xs));
+ nid = OBJ_obj2nid(paobj);
+#endif
+ result = apr_pstrdup(p,
+ (nid == NID_undef) ? "UNKNOWN" : OBJ_nid2ln(nid));
+ resdup = FALSE;
+ }
+ else if (strcEQ(var, "CERT")) {
+ result = ssl_var_lookup_ssl_cert_PEM(p, xs);
+ }
+
+ if (resdup)
+ result = apr_pstrdup(p, result);
+ return result;
+}
+
+/* In this table, .extract is non-zero if RDNs using the NID should be
+ * extracted to for the SSL_{CLIENT,SERVER}_{I,S}_DN_* environment
+ * variables. */
+static const struct {
+ char *name;
+ int nid;
+ int extract;
+} ssl_var_lookup_ssl_cert_dn_rec[] = {
+ { "C", NID_countryName, 1 },
+ { "ST", NID_stateOrProvinceName, 1 }, /* officially (RFC2156) */
+ { "SP", NID_stateOrProvinceName, 0 }, /* compatibility (SSLeay) */
+ { "L", NID_localityName, 1 },
+ { "O", NID_organizationName, 1 },
+ { "OU", NID_organizationalUnitName, 1 },
+ { "CN", NID_commonName, 1 },
+ { "T", NID_title, 1 },
+ { "I", NID_initials, 1 },
+ { "G", NID_givenName, 1 },
+ { "S", NID_surname, 1 },
+ { "D", NID_description, 1 },
+#ifdef NID_userId
+ { "UID", NID_userId, 1 },
+#endif
+ { "Email", NID_pkcs9_emailAddress, 1 },
+ { NULL, 0, 0 }
+};
+
+static char *ssl_var_lookup_ssl_cert_dn(apr_pool_t *p, X509_NAME *xsname,
+ const char *var)
+{
+ const char *ptr;
+ char *result;
+ X509_NAME_ENTRY *xsne;
+ int i, j, n, idx = 0, raw = 0;
+ apr_size_t varlen;
+
+ ptr = ap_strrchr_c(var, '_');
+ if (ptr && ptr > var && strcmp(ptr + 1, "RAW") == 0) {
+ var = apr_pstrmemdup(p, var, ptr - var);
+ raw = 1;
+ }
+
+ /* if an _N suffix is used, find the Nth attribute of given name */
+ ptr = ap_strchr_c(var, '_');
+ if (ptr != NULL && strspn(ptr + 1, "0123456789") == strlen(ptr + 1)) {
+ idx = atoi(ptr + 1);
+ varlen = ptr - var;
+ } else {
+ varlen = strlen(var);
+ }
+
+ result = NULL;
+
+ for (i = 0; ssl_var_lookup_ssl_cert_dn_rec[i].name != NULL; i++) {
+ if (strEQn(var, ssl_var_lookup_ssl_cert_dn_rec[i].name, varlen)
+ && strlen(ssl_var_lookup_ssl_cert_dn_rec[i].name) == varlen) {
+ for (j = 0; j < X509_NAME_entry_count(xsname); j++) {
+ xsne = X509_NAME_get_entry(xsname, j);
+
+ n =OBJ_obj2nid((ASN1_OBJECT *)X509_NAME_ENTRY_get_object(xsne));
+
+ if (n == ssl_var_lookup_ssl_cert_dn_rec[i].nid && idx-- == 0) {
+ result = modssl_X509_NAME_ENTRY_to_string(p, xsne, raw);
+ break;
+ }
+ }
+ break;
+ }
+ }
+ return result;
+}
+
+static char *ssl_var_lookup_ssl_cert_san(apr_pool_t *p, X509 *xs, char *var)
+{
+ int type;
+ apr_size_t numlen;
+ const char *onf = NULL;
+ apr_array_header_t *entries;
+
+ if (strcEQn(var, "Email_", 6)) {
+ type = GEN_EMAIL;
+ var += 6;
+ }
+ else if (strcEQn(var, "DNS_", 4)) {
+ type = GEN_DNS;
+ var += 4;
+ }
+ else if (strcEQn(var, "OTHER_", 6)) {
+ type = GEN_OTHERNAME;
+ var += 6;
+ if (strEQn(var, "msUPN_", 6)) {
+ var += 6;
+ onf = "msUPN";
+ }
+ else if (strEQn(var, "dnsSRV_", 7)) {
+ var += 7;
+ onf = "id-on-dnsSRV";
+ }
+ else
+ return NULL;
+ }
+ else
+ return NULL;
+
+ /* sanity check: number must be between 1 and 4 digits */
+ numlen = strspn(var, "0123456789");
+ if ((numlen < 1) || (numlen > 4) || (numlen != strlen(var)))
+ return NULL;
+
+ if (modssl_X509_getSAN(p, xs, type, onf, atoi(var), &entries))
+ /* return the first entry from this 1-element array */
+ return APR_ARRAY_IDX(entries, 0, char *);
+ else
+ return NULL;
+}
+
+static char *ssl_var_lookup_ssl_cert_valid(apr_pool_t *p, ASN1_TIME *tm)
+{
+ BIO* bio;
+
+ if ((bio = BIO_new(BIO_s_mem())) == NULL)
+ return NULL;
+ ASN1_TIME_print(bio, tm);
+
+ return modssl_bio_free_read(p, bio);
+}
+
+#define DIGIT2NUM(x) (((x)[0] - '0') * 10 + (x)[1] - '0')
+
+/* Return a string giving the number of days remaining until 'tm', or
+ * "0" if this can't be determined. */
+static char *ssl_var_lookup_ssl_cert_remain(apr_pool_t *p, ASN1_TIME *tm)
+{
+ apr_time_t then, now = apr_time_now();
+ apr_time_exp_t exp = {0};
+ long diff;
+ unsigned char *dp;
+
+ /* Fail if the time isn't a valid ASN.1 TIME; RFC3280 mandates
+ * that the seconds digits are present even though ASN.1
+ * doesn't. */
+ if ((tm->type == V_ASN1_UTCTIME && tm->length < 11) ||
+ (tm->type == V_ASN1_GENERALIZEDTIME && tm->length < 13) ||
+ !ASN1_TIME_check(tm)) {
+ return apr_pstrdup(p, "0");
+ }
+
+ if (tm->type == V_ASN1_UTCTIME) {
+ exp.tm_year = DIGIT2NUM(tm->data);
+ if (exp.tm_year <= 50) exp.tm_year += 100;
+ dp = tm->data + 2;
+ } else {
+ exp.tm_year = DIGIT2NUM(tm->data) * 100 + DIGIT2NUM(tm->data + 2) - 1900;
+ dp = tm->data + 4;
+ }
+
+ exp.tm_mon = DIGIT2NUM(dp) - 1;
+ exp.tm_mday = DIGIT2NUM(dp + 2) + 1;
+ exp.tm_hour = DIGIT2NUM(dp + 4);
+ exp.tm_min = DIGIT2NUM(dp + 6);
+ exp.tm_sec = DIGIT2NUM(dp + 8);
+
+ if (apr_time_exp_gmt_get(&then, &exp) != APR_SUCCESS) {
+ return apr_pstrdup(p, "0");
+ }
+
+ diff = (long)((apr_time_sec(then) - apr_time_sec(now)) / (60*60*24));
+
+ return diff > 0 ? apr_ltoa(p, diff) : apr_pstrdup(p, "0");
+}
+
+static char *ssl_var_lookup_ssl_cert_serial(apr_pool_t *p, X509 *xs)
+{
+ BIO *bio;
+
+ if ((bio = BIO_new(BIO_s_mem())) == NULL)
+ return NULL;
+ i2a_ASN1_INTEGER(bio, X509_get_serialNumber(xs));
+
+ return modssl_bio_free_read(p, bio);
+}
+
+static char *ssl_var_lookup_ssl_cert_chain(apr_pool_t *p, STACK_OF(X509) *sk, char *var)
+{
+ char *result;
+ X509 *xs;
+ int n;
+
+ result = NULL;
+
+ if (strspn(var, "0123456789") == strlen(var)) {
+ n = atoi(var);
+ if (n < sk_X509_num(sk)) {
+ xs = sk_X509_value(sk, n);
+ result = ssl_var_lookup_ssl_cert_PEM(p, xs);
+ }
+ }
+
+ return result;
+}
+
+static char *ssl_var_lookup_ssl_cert_rfc4523_cea(apr_pool_t *p, SSL *ssl)
+{
+ char *result;
+ X509 *xs;
+
+ ASN1_INTEGER *serialNumber;
+
+ if (!(xs = SSL_get_peer_certificate(ssl))) {
+ return NULL;
+ }
+
+ result = NULL;
+
+ serialNumber = X509_get_serialNumber(xs);
+ if (serialNumber) {
+ X509_NAME *issuer = X509_get_issuer_name(xs);
+ if (issuer) {
+ BIGNUM *bn = ASN1_INTEGER_to_BN(serialNumber, NULL);
+ char *decimal = BN_bn2dec(bn);
+ result = apr_pstrcat(p, "{ serialNumber ", decimal,
+ ", issuer rdnSequence:\"",
+ modssl_X509_NAME_to_string(p, issuer, 0), "\" }", NULL);
+ OPENSSL_free(decimal);
+ BN_free(bn);
+ }
+ }
+
+ X509_free(xs);
+ return result;
+}
+
+static char *ssl_var_lookup_ssl_cert_PEM(apr_pool_t *p, X509 *xs)
+{
+ BIO *bio;
+
+ if ((bio = BIO_new(BIO_s_mem())) == NULL)
+ return NULL;
+ PEM_write_bio_X509(bio, xs);
+
+ return modssl_bio_free_read(p, bio);
+}
+
+static char *ssl_var_lookup_ssl_cert_verify(apr_pool_t *p, SSLConnRec *sslconn)
+{
+ char *result;
+ long vrc;
+ const char *verr;
+ const char *vinfo;
+ SSL *ssl;
+ X509 *xs;
+
+ result = NULL;
+ ssl = sslconn->ssl;
+ verr = sslconn->verify_error;
+ vinfo = sslconn->verify_info;
+ vrc = SSL_get_verify_result(ssl);
+ xs = SSL_get_peer_certificate(ssl);
+
+ if (vrc == X509_V_OK && verr == NULL && xs == NULL)
+ /* no client verification done at all */
+ result = "NONE";
+ else if (vrc == X509_V_OK && verr == NULL && vinfo == NULL && xs != NULL)
+ /* client verification done successful */
+ result = "SUCCESS";
+ else if (vrc == X509_V_OK && vinfo != NULL && strEQ(vinfo, "GENEROUS"))
+ /* client verification done in generous way */
+ result = "GENEROUS";
+ else
+ /* client verification failed */
+ result = apr_psprintf(p, "FAILED:%s",
+ verr ? verr : X509_verify_cert_error_string(vrc));
+
+ if (xs)
+ X509_free(xs);
+ return result;
+}
+
+static char *ssl_var_lookup_ssl_cipher(apr_pool_t *p, SSLConnRec *sslconn, char *var)
+{
+ char *result;
+ BOOL resdup;
+ int usekeysize, algkeysize;
+ SSL *ssl;
+
+ result = NULL;
+ resdup = TRUE;
+
+ ssl = sslconn->ssl;
+ ssl_var_lookup_ssl_cipher_bits(ssl, &usekeysize, &algkeysize);
+
+ if (ssl && strEQ(var, "")) {
+ MODSSL_SSL_CIPHER_CONST SSL_CIPHER *cipher = SSL_get_current_cipher(ssl);
+ result = (cipher != NULL ? (char *)SSL_CIPHER_get_name(cipher) : NULL);
+ }
+ else if (strcEQ(var, "_EXPORT"))
+ result = (usekeysize < 56 ? "true" : "false");
+ else if (strcEQ(var, "_USEKEYSIZE")) {
+ result = apr_itoa(p, usekeysize);
+ resdup = FALSE;
+ }
+ else if (strcEQ(var, "_ALGKEYSIZE")) {
+ result = apr_itoa(p, algkeysize);
+ resdup = FALSE;
+ }
+
+ if (result != NULL && resdup)
+ result = apr_pstrdup(p, result);
+ return result;
+}
+
+static void ssl_var_lookup_ssl_cipher_bits(SSL *ssl, int *usekeysize, int *algkeysize)
+{
+ MODSSL_SSL_CIPHER_CONST SSL_CIPHER *cipher;
+
+ *usekeysize = 0;
+ *algkeysize = 0;
+ if (ssl != NULL)
+ if ((cipher = SSL_get_current_cipher(ssl)) != NULL)
+ *usekeysize = SSL_CIPHER_get_bits(cipher, algkeysize);
+ return;
+}
+
+static char *ssl_var_lookup_ssl_version(apr_pool_t *p, char *var)
+{
+ if (strEQ(var, "INTERFACE")) {
+ return apr_pstrdup(p, var_interface);
+ }
+ else if (strEQ(var, "LIBRARY_INTERFACE")) {
+ return apr_pstrdup(p, var_library_interface);
+ }
+ else if (strEQ(var, "LIBRARY")) {
+ return apr_pstrdup(p, var_library);
+ }
+ return NULL;
+}
+
+/* Add each RDN in 'xn' to the table 't' where the NID is present in
+ * 'nids', using key prefix 'pfx'. */
+static void extract_dn(apr_table_t *t, apr_hash_t *nids, const char *pfx,
+ X509_NAME *xn, apr_pool_t *p)
+{
+ X509_NAME_ENTRY *xsne;
+ apr_hash_t *count;
+ int i, nid;
+
+ /* Hash of (int) NID -> (int *) counter to count each time an RDN
+ * with the given NID has been seen. */
+ count = apr_hash_make(p);
+
+ /* For each RDN... */
+ for (i = 0; i < X509_NAME_entry_count(xn); i++) {
+ const char *tag;
+ xsne = X509_NAME_get_entry(xn, i);
+
+ /* Retrieve the nid, and check whether this is one of the nids
+ * which are to be extracted. */
+ nid = OBJ_obj2nid((ASN1_OBJECT *)X509_NAME_ENTRY_get_object(xsne));
+
+ tag = apr_hash_get(nids, &nid, sizeof nid);
+ if (tag) {
+ const char *key;
+ int *dup;
+ char *value;
+
+ /* Check whether a variable with this nid was already
+ * been used; if so, use the foo_N=bar syntax. */
+ dup = apr_hash_get(count, &nid, sizeof nid);
+ if (dup) {
+ key = apr_psprintf(p, "%s%s_%d", pfx, tag, ++(*dup));
+ }
+ else {
+ /* Otherwise, use the plain foo=bar syntax. */
+ dup = apr_pcalloc(p, sizeof *dup);
+ apr_hash_set(count, &nid, sizeof nid, dup);
+ key = apr_pstrcat(p, pfx, tag, NULL);
+ }
+ value = modssl_X509_NAME_ENTRY_to_string(p, xsne, 0);
+ apr_table_setn(t, key, value);
+ }
+ }
+}
+
+void modssl_var_extract_dns(apr_table_t *t, SSL *ssl, apr_pool_t *p)
+{
+ apr_hash_t *nids;
+ unsigned n;
+ X509 *xs;
+
+ /* Build up a hash table of (int *)NID->(char *)short-name for all
+ * the tags which are to be extracted: */
+ nids = apr_hash_make(p);
+ for (n = 0; ssl_var_lookup_ssl_cert_dn_rec[n].name; n++) {
+ if (ssl_var_lookup_ssl_cert_dn_rec[n].extract) {
+ apr_hash_set(nids, &ssl_var_lookup_ssl_cert_dn_rec[n].nid,
+ sizeof(ssl_var_lookup_ssl_cert_dn_rec[0].nid),
+ ssl_var_lookup_ssl_cert_dn_rec[n].name);
+ }
+ }
+
+ /* Extract the server cert DNS -- note that the refcount does NOT
+ * increase: */
+ xs = SSL_get_certificate(ssl);
+ if (xs) {
+ extract_dn(t, nids, "SSL_SERVER_S_DN_", X509_get_subject_name(xs), p);
+ extract_dn(t, nids, "SSL_SERVER_I_DN_", X509_get_issuer_name(xs), p);
+ }
+
+ /* Extract the client cert DNs -- note that the refcount DOES
+ * increase: */
+ xs = SSL_get_peer_certificate(ssl);
+ if (xs) {
+ extract_dn(t, nids, "SSL_CLIENT_S_DN_", X509_get_subject_name(xs), p);
+ extract_dn(t, nids, "SSL_CLIENT_I_DN_", X509_get_issuer_name(xs), p);
+ X509_free(xs);
+ }
+}
+
+static void extract_san_array(apr_table_t *t, const char *pfx,
+ apr_array_header_t *entries, apr_pool_t *p)
+{
+ int i;
+
+ for (i = 0; i < entries->nelts; i++) {
+ const char *key = apr_psprintf(p, "%s_%d", pfx, i);
+ apr_table_setn(t, key, APR_ARRAY_IDX(entries, i, const char *));
+ }
+}
+
+void modssl_var_extract_san_entries(apr_table_t *t, SSL *ssl, apr_pool_t *p)
+{
+ X509 *xs;
+ apr_array_header_t *entries;
+
+ /* subjectAltName entries of the server certificate */
+ xs = SSL_get_certificate(ssl);
+ if (xs) {
+ if (modssl_X509_getSAN(p, xs, GEN_EMAIL, NULL, -1, &entries)) {
+ extract_san_array(t, "SSL_SERVER_SAN_Email", entries, p);
+ }
+ if (modssl_X509_getSAN(p, xs, GEN_DNS, NULL, -1, &entries)) {
+ extract_san_array(t, "SSL_SERVER_SAN_DNS", entries, p);
+ }
+ if (modssl_X509_getSAN(p, xs, GEN_OTHERNAME, "id-on-dnsSRV", -1,
+ &entries)) {
+ extract_san_array(t, "SSL_SERVER_SAN_OTHER_dnsSRV", entries, p);
+ }
+ /* no need to free xs (refcount does not increase) */
+ }
+
+ /* subjectAltName entries of the client certificate */
+ xs = SSL_get_peer_certificate(ssl);
+ if (xs) {
+ if (modssl_X509_getSAN(p, xs, GEN_EMAIL, NULL, -1, &entries)) {
+ extract_san_array(t, "SSL_CLIENT_SAN_Email", entries, p);
+ }
+ if (modssl_X509_getSAN(p, xs, GEN_DNS, NULL, -1, &entries)) {
+ extract_san_array(t, "SSL_CLIENT_SAN_DNS", entries, p);
+ }
+ if (modssl_X509_getSAN(p, xs, GEN_OTHERNAME, "msUPN", -1, &entries)) {
+ extract_san_array(t, "SSL_CLIENT_SAN_OTHER_msUPN", entries, p);
+ }
+ X509_free(xs);
+ }
+}
+
+/* For an extension type which OpenSSL does not recognize, attempt to
+ * parse the extension type as a primitive string. This will fail for
+ * any structured extension type per the docs. Returns non-zero on
+ * success and writes the string to the given bio. */
+static int dump_extn_value(BIO *bio, ASN1_OCTET_STRING *str)
+{
+ const unsigned char *pp = str->data;
+ ASN1_STRING *ret = ASN1_STRING_new();
+ int rv = 0;
+
+ /* This allows UTF8String, IA5String, VisibleString, or BMPString;
+ * conversion to UTF-8 is forced. */
+ if (d2i_DISPLAYTEXT(&ret, &pp, str->length)) {
+ ASN1_STRING_print_ex(bio, ret, ASN1_STRFLGS_UTF8_CONVERT);
+ rv = 1;
+ }
+
+ ASN1_STRING_free(ret);
+ return rv;
+}
+
+apr_array_header_t *ssl_ext_list(apr_pool_t *p, conn_rec *c, int peer,
+ const char *extension)
+{
+ SSLConnRec *sslconn = ssl_get_effective_config(c);
+ SSL *ssl = NULL;
+ apr_array_header_t *array = NULL;
+ X509 *xs = NULL;
+ ASN1_OBJECT *oid = NULL;
+ int count = 0, j;
+
+ if (!sslconn || !sslconn->ssl || !extension) {
+ return NULL;
+ }
+ ssl = sslconn->ssl;
+
+ /* We accept the "extension" string to be converted as
+ * a long name (nsComment), short name (DN) or
+ * numeric OID (1.2.3.4).
+ */
+ oid = OBJ_txt2obj(extension, 0);
+ if (!oid) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(01970)
+ "could not parse OID '%s'", extension);
+ ERR_clear_error();
+ return NULL;
+ }
+
+ xs = peer ? SSL_get_peer_certificate(ssl) : SSL_get_certificate(ssl);
+ if (xs == NULL) {
+ return NULL;
+ }
+
+ count = X509_get_ext_count(xs);
+ /* Create an array large enough to accommodate every extension. This is
+ * likely overkill, but safe.
+ */
+ array = apr_array_make(p, count, sizeof(char *));
+ for (j = 0; j < count; j++) {
+ X509_EXTENSION *ext = X509_get_ext(xs, j);
+
+ if (OBJ_cmp(X509_EXTENSION_get_object(ext), oid) == 0) {
+ BIO *bio = BIO_new(BIO_s_mem());
+
+ /* We want to obtain a string representation of the extensions
+ * value and add it to the array we're building.
+ * X509V3_EXT_print() doesn't know about all the possible
+ * data types, but the value is stored as an ASN1_OCTET_STRING
+ * allowing us a fallback in case of X509V3_EXT_print
+ * not knowing how to handle the data.
+ */
+ if (X509V3_EXT_print(bio, ext, 0, 0) == 1 ||
+ dump_extn_value(bio, X509_EXTENSION_get_data(ext)) == 1) {
+ BUF_MEM *buf;
+ char **ptr = apr_array_push(array);
+ BIO_get_mem_ptr(bio, &buf);
+ *ptr = apr_pstrmemdup(p, buf->data, buf->length);
+ } else {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(01971)
+ "Found an extension '%s', but failed to "
+ "create a string from it", extension);
+ }
+ BIO_vfree(bio);
+ }
+ }
+
+ if (array->nelts == 0)
+ array = NULL;
+
+ if (peer) {
+ /* only SSL_get_peer_certificate raises the refcount */
+ X509_free(xs);
+ }
+
+ ASN1_OBJECT_free(oid);
+ ERR_clear_error();
+ return array;
+}
+
+static char *ssl_var_lookup_ssl_compress_meth(SSL *ssl)
+{
+ char *result = "NULL";
+#ifndef OPENSSL_NO_COMP
+ SSL_SESSION *pSession = SSL_get_session(ssl);
+
+ if (pSession) {
+#ifdef OPENSSL_NO_SSL_INTERN
+ switch (SSL_SESSION_get_compress_id(pSession)) {
+#else
+ switch (pSession->compress_meth) {
+#endif
+ case 0:
+ /* default "NULL" already set */
+ break;
+
+ /* Defined by RFC 3749, deflate is coded by "1" */
+ case 1:
+ result = "DEFLATE";
+ break;
+
+ /* IANA assigned compression number for LZS */
+ case 0x40:
+ result = "LZS";
+ break;
+
+ default:
+ result = "UNKNOWN";
+ break;
+ }
+ }
+#endif
+ return result;
+}
+
+/* _________________________________________________________________
+**
+** SSL Extension to mod_log_config
+** _________________________________________________________________
+*/
+
+#include "../../modules/loggers/mod_log_config.h"
+
+static const char *ssl_var_log_handler_c(request_rec *r, char *a);
+static const char *ssl_var_log_handler_x(request_rec *r, char *a);
+
+/*
+ * register us for the mod_log_config function registering phase
+ * to establish %{...}c and to be able to expand %{...}x variables.
+ */
+void ssl_var_log_config_register(apr_pool_t *p)
+{
+ APR_OPTIONAL_FN_TYPE(ap_register_log_handler) *log_pfn_register;
+
+ log_pfn_register = APR_RETRIEVE_OPTIONAL_FN(ap_register_log_handler);
+
+ if (log_pfn_register) {
+ log_pfn_register(p, "c", ssl_var_log_handler_c, 0);
+ log_pfn_register(p, "x", ssl_var_log_handler_x, 0);
+ }
+ return;
+}
+
+/*
+ * implement the %{..}c log function
+ * (we are the only function)
+ */
+static const char *ssl_var_log_handler_c(request_rec *r, char *a)
+{
+ SSLConnRec *sslconn = ssl_get_effective_config(r->connection);
+ char *result;
+
+ if (sslconn == NULL || sslconn->ssl == NULL)
+ return NULL;
+ result = NULL;
+ if (strEQ(a, "version"))
+ result = ssl_var_lookup(r->pool, r->server, r->connection, r, "SSL_PROTOCOL");
+ else if (strEQ(a, "cipher"))
+ result = ssl_var_lookup(r->pool, r->server, r->connection, r, "SSL_CIPHER");
+ else if (strEQ(a, "subjectdn") || strEQ(a, "clientcert"))
+ result = ssl_var_lookup(r->pool, r->server, r->connection, r, "SSL_CLIENT_S_DN");
+ else if (strEQ(a, "issuerdn") || strEQ(a, "cacert"))
+ result = ssl_var_lookup(r->pool, r->server, r->connection, r, "SSL_CLIENT_I_DN");
+ else if (strEQ(a, "errcode"))
+ result = "-";
+ else if (strEQ(a, "errstr"))
+ result = (char *)sslconn->verify_error;
+ if (result != NULL && result[0] == NUL)
+ result = NULL;
+ return result;
+}
+
+/*
+ * extend the implementation of the %{..}x log function
+ * (there can be more functions)
+ */
+static const char *ssl_var_log_handler_x(request_rec *r, char *a)
+{
+ char *result;
+
+ result = ssl_var_lookup(r->pool, r->server, r->connection, r, a);
+ if (result != NULL && result[0] == NUL)
+ result = NULL;
+ return result;
+}
+
+
diff --git a/modules/ssl/ssl_private.h b/modules/ssl/ssl_private.h
new file mode 100644
index 0000000..cd8df07
--- /dev/null
+++ b/modules/ssl/ssl_private.h
@@ -0,0 +1,1174 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SSL_PRIVATE_H
+#define SSL_PRIVATE_H
+
+/**
+ * @file ssl_private.h
+ * @brief Internal interfaces private to mod_ssl.
+ *
+ * @defgroup MOD_SSL_PRIVATE Private
+ * @ingroup MOD_SSL
+ * @{
+ */
+
+/** Apache headers */
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_main.h"
+#include "http_connection.h"
+#include "http_request.h"
+#include "http_protocol.h"
+#include "http_ssl.h"
+#include "http_vhost.h"
+#include "util_script.h"
+#include "util_filter.h"
+#include "util_ebcdic.h"
+#include "util_mutex.h"
+#include "apr.h"
+#include "apr_strings.h"
+#define APR_WANT_STRFUNC
+#define APR_WANT_MEMFUNC
+#include "apr_want.h"
+#include "apr_tables.h"
+#include "apr_lib.h"
+#include "apr_fnmatch.h"
+#include "apr_strings.h"
+#include "apr_global_mutex.h"
+#include "apr_optional.h"
+#include "ap_socache.h"
+#include "mod_auth.h"
+
+/* The #ifdef macros are only defined AFTER including the above
+ * therefore we cannot include these system files at the top :-(
+ */
+#if APR_HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#if APR_HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+#if APR_HAVE_UNISTD_H
+#include <unistd.h> /* needed for STDIN_FILENO et.al., at least on FreeBSD */
+#endif
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+#ifndef TRUE
+#define TRUE !FALSE
+#endif
+
+#ifndef BOOL
+#define BOOL unsigned int
+#endif
+
+#include "ap_expr.h"
+
+/* OpenSSL headers */
+#include <openssl/opensslv.h>
+#if (OPENSSL_VERSION_NUMBER >= 0x10001000)
+/* must be defined before including ssl.h */
+#define OPENSSL_NO_SSL_INTERN
+#endif
+#if OPENSSL_VERSION_NUMBER >= 0x30000000
+#include <openssl/core_names.h>
+#endif
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/x509.h>
+#include <openssl/pem.h>
+#include <openssl/crypto.h>
+#include <openssl/evp.h>
+#include <openssl/rand.h>
+#include <openssl/x509v3.h>
+#include <openssl/x509_vfy.h>
+#include <openssl/ocsp.h>
+
+/* Avoid tripping over an engine build installed globally and detected
+ * when the user points at an explicit non-engine flavor of OpenSSL
+ */
+#if defined(HAVE_OPENSSL_ENGINE_H) && defined(HAVE_ENGINE_INIT)
+#include <openssl/engine.h>
+#endif
+
+#if (OPENSSL_VERSION_NUMBER < 0x0090801f)
+#error mod_ssl requires OpenSSL 0.9.8a or later
+#endif
+
+/**
+ * ...shifting sands of OpenSSL...
+ * Note: when adding support for new OpenSSL features, avoid explicit
+ * version number checks whenever possible, and use "feature-based"
+ * detection instead (check for definitions of constants or functions)
+ */
+#if (OPENSSL_VERSION_NUMBER >= 0x10000000)
+#define MODSSL_SSL_CIPHER_CONST const
+#define MODSSL_SSL_METHOD_CONST const
+#else
+#define MODSSL_SSL_CIPHER_CONST
+#define MODSSL_SSL_METHOD_CONST
+#endif
+
+#if defined(LIBRESSL_VERSION_NUMBER)
+/* Missing from LibreSSL */
+#if LIBRESSL_VERSION_NUMBER < 0x2060000f
+#define SSL_CTRL_SET_MIN_PROTO_VERSION 123
+#define SSL_CTRL_SET_MAX_PROTO_VERSION 124
+#define SSL_CTX_set_min_proto_version(ctx, version) \
+ SSL_CTX_ctrl(ctx, SSL_CTRL_SET_MIN_PROTO_VERSION, version, NULL)
+#define SSL_CTX_set_max_proto_version(ctx, version) \
+ SSL_CTX_ctrl(ctx, SSL_CTRL_SET_MAX_PROTO_VERSION, version, NULL)
+#endif /* LIBRESSL_VERSION_NUMBER < 0x2060000f */
+/* LibreSSL before 2.7 declares OPENSSL_VERSION_NUMBER == 2.0 but does not
+ * include most changes from OpenSSL >= 1.1 (new functions, macros,
+ * deprecations, ...), so we have to work around this...
+ */
+#define MODSSL_USE_OPENSSL_PRE_1_1_API (LIBRESSL_VERSION_NUMBER < 0x2070000f)
+#else /* defined(LIBRESSL_VERSION_NUMBER) */
+#define MODSSL_USE_OPENSSL_PRE_1_1_API (OPENSSL_VERSION_NUMBER < 0x10100000L)
+#endif
+
+#if defined(OPENSSL_FIPS) || OPENSSL_VERSION_NUMBER >= 0x30000000L
+#define HAVE_FIPS
+#endif
+
+#if defined(SSL_OP_NO_TLSv1_2)
+#define HAVE_TLSV1_X
+#endif
+
+#if defined(SSL_CONF_FLAG_FILE)
+#define HAVE_SSL_CONF_CMD
+#endif
+
+/* session id constness */
+#if MODSSL_USE_OPENSSL_PRE_1_1_API
+#define IDCONST
+#else
+#define IDCONST const
+#endif
+
+/**
+ * The following features all depend on TLS extension support.
+ * Within this block, check again for features (not version numbers).
+ */
+#if !defined(OPENSSL_NO_TLSEXT) && defined(SSL_set_tlsext_host_name)
+
+#define HAVE_TLSEXT
+
+/* ECC: make sure we have at least 1.0.0 */
+#if !defined(OPENSSL_NO_EC) && defined(TLSEXT_ECPOINTFORMAT_uncompressed)
+#define HAVE_ECC
+#endif
+
+/* OCSP stapling */
+#if !defined(OPENSSL_NO_OCSP) && defined(SSL_CTX_set_tlsext_status_cb)
+#define HAVE_OCSP_STAPLING
+/* All exist but are no longer macros since OpenSSL 1.1.0 */
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+/* backward compatibility with OpenSSL < 1.0 */
+#ifndef sk_OPENSSL_STRING_num
+#define sk_OPENSSL_STRING_num sk_num
+#endif
+#ifndef sk_OPENSSL_STRING_value
+#define sk_OPENSSL_STRING_value sk_value
+#endif
+#ifndef sk_OPENSSL_STRING_pop
+#define sk_OPENSSL_STRING_pop sk_pop
+#endif
+#endif /* if OPENSSL_VERSION_NUMBER < 0x10100000L */
+#endif /* if !defined(OPENSSL_NO_OCSP) && defined(SSL_CTX_set_tlsext_status_cb) */
+
+/* TLS session tickets */
+#if defined(SSL_CTX_set_tlsext_ticket_key_cb)
+#define HAVE_TLS_SESSION_TICKETS
+#define TLSEXT_TICKET_KEY_LEN 48
+#ifndef tlsext_tick_md
+#ifdef OPENSSL_NO_SHA256
+#define tlsext_tick_md EVP_sha1
+#else
+#define tlsext_tick_md EVP_sha256
+#endif
+#endif
+#endif
+
+/* Secure Remote Password */
+#if !defined(OPENSSL_NO_SRP) && defined(SSL_CTRL_SET_TLS_EXT_SRP_USERNAME_CB)
+#define HAVE_SRP
+#include <openssl/srp.h>
+#endif
+
+/* ALPN Protocol Negotiation */
+#if defined(TLSEXT_TYPE_application_layer_protocol_negotiation)
+#define HAVE_TLS_ALPN
+#endif
+
+#endif /* !defined(OPENSSL_NO_TLSEXT) && defined(SSL_set_tlsext_host_name) */
+
+#if MODSSL_USE_OPENSSL_PRE_1_1_API
+#define BN_get_rfc2409_prime_768 get_rfc2409_prime_768
+#define BN_get_rfc2409_prime_1024 get_rfc2409_prime_1024
+#define BN_get_rfc3526_prime_1536 get_rfc3526_prime_1536
+#define BN_get_rfc3526_prime_2048 get_rfc3526_prime_2048
+#define BN_get_rfc3526_prime_3072 get_rfc3526_prime_3072
+#define BN_get_rfc3526_prime_4096 get_rfc3526_prime_4096
+#define BN_get_rfc3526_prime_6144 get_rfc3526_prime_6144
+#define BN_get_rfc3526_prime_8192 get_rfc3526_prime_8192
+#define BIO_set_init(x,v) (x->init=v)
+#define BIO_get_data(x) (x->ptr)
+#define BIO_set_data(x,v) (x->ptr=v)
+#define BIO_get_shutdown(x) (x->shutdown)
+#define BIO_set_shutdown(x,v) (x->shutdown=v)
+#define DH_bits(x) (BN_num_bits(x->p))
+#else
+void init_bio_methods(void);
+void free_bio_methods(void);
+#endif
+
+#if OPENSSL_VERSION_NUMBER < 0x10002000L || \
+ (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000f)
+#define X509_STORE_CTX_get0_store(x) (x->ctx)
+#endif
+
+#if OPENSSL_VERSION_NUMBER < 0x10000000L
+#ifndef X509_STORE_CTX_get0_current_issuer
+#define X509_STORE_CTX_get0_current_issuer(x) (x->current_issuer)
+#endif
+#endif
+
+#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER)
+#define HAVE_OPENSSL_KEYLOG
+#endif
+
+#ifdef HAVE_FIPS
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+#define modssl_fips_is_enabled() EVP_default_properties_is_fips_enabled(NULL)
+#define modssl_fips_enable(to) EVP_default_properties_enable_fips(NULL, (to))
+#else
+#define modssl_fips_is_enabled() FIPS_mode()
+#define modssl_fips_enable(to) FIPS_mode_set((to))
+#endif
+#endif /* HAVE_FIPS */
+
+/* mod_ssl headers */
+#include "ssl_util_ssl.h"
+
+APLOG_USE_MODULE(ssl);
+
+/*
+ * Provide reasonable default for some defines
+ */
+#ifndef PFALSE
+#define PFALSE ((void *)FALSE)
+#endif
+#ifndef PTRUE
+#define PTRUE ((void *)TRUE)
+#endif
+#ifndef UNSET
+#define UNSET (-1)
+#endif
+#ifndef NUL
+#define NUL '\0'
+#endif
+#ifndef RAND_MAX
+#include <limits.h>
+#define RAND_MAX INT_MAX
+#endif
+
+/**
+ * Provide reasonable defines for some types
+ */
+#ifndef UCHAR
+#define UCHAR unsigned char
+#endif
+
+/**
+ * Provide useful shorthands
+ */
+#define strEQ(s1,s2) (strcmp(s1,s2) == 0)
+#define strNE(s1,s2) (strcmp(s1,s2) != 0)
+#define strEQn(s1,s2,n) (strncmp(s1,s2,n) == 0)
+#define strNEn(s1,s2,n) (strncmp(s1,s2,n) != 0)
+
+#define strcEQ(s1,s2) (strcasecmp(s1,s2) == 0)
+#define strcNE(s1,s2) (strcasecmp(s1,s2) != 0)
+#define strcEQn(s1,s2,n) (strncasecmp(s1,s2,n) == 0)
+#define strcNEn(s1,s2,n) (strncasecmp(s1,s2,n) != 0)
+
+#define strIsEmpty(s) (s == NULL || s[0] == NUL)
+
+#define myConnConfig(c) \
+ ((SSLConnRec *)ap_get_module_config(c->conn_config, &ssl_module))
+#define myConnConfigSet(c, val) \
+ ap_set_module_config(c->conn_config, &ssl_module, val)
+#define mySrvConfig(srv) \
+ ((SSLSrvConfigRec *)ap_get_module_config(srv->module_config, &ssl_module))
+#define myDirConfig(req) \
+ ((SSLDirConfigRec *)ap_get_module_config(req->per_dir_config, &ssl_module))
+#define myConnCtxConfig(c, sc) \
+ (c->outgoing ? myConnConfig(c)->dc->proxy : sc->server)
+#define myModConfig(srv) mySrvConfig((srv))->mc
+#define mySrvFromConn(c) myConnConfig(c)->server
+#define myDirConfigFromConn(c) myConnConfig(c)->dc
+#define mySrvConfigFromConn(c) mySrvConfig(mySrvFromConn(c))
+#define myModConfigFromConn(c) myModConfig(mySrvFromConn(c))
+
+/**
+ * Defaults for the configuration
+ */
+#ifndef SSL_SESSION_CACHE_TIMEOUT
+#define SSL_SESSION_CACHE_TIMEOUT 300
+#endif
+
+/* Default setting for per-dir reneg buffer. */
+#ifndef DEFAULT_RENEG_BUFFER_SIZE
+#define DEFAULT_RENEG_BUFFER_SIZE (128 * 1024)
+#endif
+
+/* Default for OCSP response validity */
+#ifndef DEFAULT_OCSP_MAX_SKEW
+#define DEFAULT_OCSP_MAX_SKEW (60 * 5)
+#endif
+
+/* Default timeout for OCSP queries */
+#ifndef DEFAULT_OCSP_TIMEOUT
+#define DEFAULT_OCSP_TIMEOUT 10
+#endif
+
+/*
+ * For better backwards compatibility with the SSLCertificate[Key]File
+ * and SSLPassPhraseDialog ("exec" type) directives in 2.4.7 and earlier
+ */
+#ifdef HAVE_ECC
+#define CERTKEYS_IDX_MAX 2
+#else
+#define CERTKEYS_IDX_MAX 1
+#endif
+
+/**
+ * Define the SSL options
+ */
+#define SSL_OPT_NONE (0)
+#define SSL_OPT_RELSET (1<<0)
+#define SSL_OPT_STDENVVARS (1<<1)
+#define SSL_OPT_EXPORTCERTDATA (1<<3)
+#define SSL_OPT_FAKEBASICAUTH (1<<4)
+#define SSL_OPT_STRICTREQUIRE (1<<5)
+#define SSL_OPT_OPTRENEGOTIATE (1<<6)
+#define SSL_OPT_LEGACYDNFORMAT (1<<7)
+typedef int ssl_opt_t;
+
+/**
+ * Define the SSL Protocol options
+ */
+#define SSL_PROTOCOL_NONE (0)
+#ifndef OPENSSL_NO_SSL3
+#define SSL_PROTOCOL_SSLV3 (1<<1)
+#endif
+#define SSL_PROTOCOL_TLSV1 (1<<2)
+#ifndef OPENSSL_NO_SSL3
+#define SSL_PROTOCOL_BASIC (SSL_PROTOCOL_SSLV3|SSL_PROTOCOL_TLSV1)
+#else
+#define SSL_PROTOCOL_BASIC (SSL_PROTOCOL_TLSV1)
+#endif
+#ifdef HAVE_TLSV1_X
+#define SSL_PROTOCOL_TLSV1_1 (1<<3)
+#define SSL_PROTOCOL_TLSV1_2 (1<<4)
+#define SSL_PROTOCOL_TLSV1_3 (1<<5)
+
+#ifdef SSL_OP_NO_TLSv1_3
+#define SSL_HAVE_PROTOCOL_TLSV1_3 (1)
+#define SSL_PROTOCOL_ALL (SSL_PROTOCOL_BASIC| \
+ SSL_PROTOCOL_TLSV1_1|SSL_PROTOCOL_TLSV1_2|SSL_PROTOCOL_TLSV1_3)
+#else
+#define SSL_HAVE_PROTOCOL_TLSV1_3 (0)
+#define SSL_PROTOCOL_ALL (SSL_PROTOCOL_BASIC| \
+ SSL_PROTOCOL_TLSV1_1|SSL_PROTOCOL_TLSV1_2)
+#endif
+#else
+#define SSL_PROTOCOL_ALL (SSL_PROTOCOL_BASIC)
+#endif
+#ifndef OPENSSL_NO_SSL3
+#define SSL_PROTOCOL_DEFAULT (SSL_PROTOCOL_ALL & ~SSL_PROTOCOL_SSLV3)
+#else
+#define SSL_PROTOCOL_DEFAULT (SSL_PROTOCOL_ALL)
+#endif
+typedef int ssl_proto_t;
+
+/**
+ * Define the SSL verify levels
+ */
+typedef enum {
+ SSL_CVERIFY_UNSET = UNSET,
+ SSL_CVERIFY_NONE = 0,
+ SSL_CVERIFY_OPTIONAL = 1,
+ SSL_CVERIFY_REQUIRE = 2,
+ SSL_CVERIFY_OPTIONAL_NO_CA = 3
+} ssl_verify_t;
+
+#define SSL_VERIFY_PEER_STRICT \
+ (SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
+
+#define ssl_verify_error_is_optional(errnum) \
+ ((errnum == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) \
+ || (errnum == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) \
+ || (errnum == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) \
+ || (errnum == X509_V_ERR_CERT_UNTRUSTED) \
+ || (errnum == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE))
+
+/**
+ * CRL checking mask (mode | flags)
+ */
+typedef enum {
+ SSL_CRLCHECK_NONE = (0),
+ SSL_CRLCHECK_LEAF = (1 << 0),
+ SSL_CRLCHECK_CHAIN = (1 << 1),
+
+#define SSL_CRLCHECK_FLAGS (~0x3)
+ SSL_CRLCHECK_NO_CRL_FOR_CERT_OK = (1 << 2)
+} ssl_crlcheck_t;
+
+/**
+ * OCSP checking mask (mode | flags)
+ */
+typedef enum {
+ SSL_OCSPCHECK_NONE = (0),
+ SSL_OCSPCHECK_LEAF = (1 << 0),
+ SSL_OCSPCHECK_CHAIN = (1 << 1),
+ SSL_OCSPCHECK_NO_OCSP_FOR_CERT_OK = (1 << 2)
+} ssl_ocspcheck_t;
+
+/**
+ * Define the SSL pass phrase dialog types
+ */
+typedef enum {
+ SSL_PPTYPE_UNSET = UNSET,
+ SSL_PPTYPE_BUILTIN = 0,
+ SSL_PPTYPE_FILTER = 1,
+ SSL_PPTYPE_PIPE = 2
+} ssl_pphrase_t;
+
+/**
+ * Define the Path Checking modes
+ */
+#define SSL_PCM_EXISTS 1
+#define SSL_PCM_ISREG 2
+#define SSL_PCM_ISDIR 4
+#define SSL_PCM_ISNONZERO 8
+typedef unsigned int ssl_pathcheck_t;
+
+/**
+ * Define the SSL enabled state
+ */
+typedef enum {
+ SSL_ENABLED_UNSET = UNSET,
+ SSL_ENABLED_FALSE = 0,
+ SSL_ENABLED_TRUE = 1,
+ SSL_ENABLED_OPTIONAL = 3
+} ssl_enabled_t;
+
+/**
+ * Define the SSL requirement structure
+ */
+typedef struct {
+ const char *cpExpr;
+ ap_expr_info_t *mpExpr;
+} ssl_require_t;
+
+/**
+ * Define the SSL random number generator seeding source
+ */
+typedef enum {
+ SSL_RSCTX_STARTUP = 1,
+ SSL_RSCTX_CONNECT = 2
+} ssl_rsctx_t;
+typedef enum {
+ SSL_RSSRC_BUILTIN = 1,
+ SSL_RSSRC_FILE = 2,
+ SSL_RSSRC_EXEC = 3,
+ SSL_RSSRC_EGD = 4
+} ssl_rssrc_t;
+typedef struct {
+ ssl_rsctx_t nCtx;
+ ssl_rssrc_t nSrc;
+ char *cpPath;
+ int nBytes;
+} ssl_randseed_t;
+
+/**
+ * Define the structure of an ASN.1 anything
+ */
+typedef struct {
+ long int nData;
+ unsigned char *cpData;
+ apr_time_t source_mtime;
+} ssl_asn1_t;
+
+/**
+ * Define the mod_ssl per-module configuration structure
+ * (i.e. the global configuration for each httpd process)
+ */
+
+typedef struct SSLSrvConfigRec SSLSrvConfigRec;
+typedef struct SSLDirConfigRec SSLDirConfigRec;
+
+typedef enum {
+ SSL_SHUTDOWN_TYPE_UNSET,
+ SSL_SHUTDOWN_TYPE_STANDARD,
+ SSL_SHUTDOWN_TYPE_UNCLEAN,
+ SSL_SHUTDOWN_TYPE_ACCURATE
+} ssl_shutdown_type_e;
+
+typedef struct {
+ SSL *ssl;
+ const char *client_dn;
+ X509 *client_cert;
+ ssl_shutdown_type_e shutdown_type;
+ const char *verify_info;
+ const char *verify_error;
+ int verify_depth;
+ int disabled;
+ enum {
+ NON_SSL_OK = 0, /* is SSL request, or error handling completed */
+ NON_SSL_SEND_REQLINE, /* Need to send the fake request line */
+ NON_SSL_SEND_HDR_SEP, /* Need to send the header separator */
+ NON_SSL_SET_ERROR_MSG /* Need to set the error message */
+ } non_ssl_request;
+
+ /* Track the handshake/renegotiation state for the connection so
+ * that all client-initiated renegotiations can be rejected, as a
+ * partial fix for CVE-2009-3555. */
+ enum {
+ RENEG_INIT = 0, /* Before initial handshake */
+ RENEG_REJECT, /* After initial handshake; any client-initiated
+ * renegotiation should be rejected */
+ RENEG_ALLOW, /* A server-initiated renegotiation is taking
+ * place (as dictated by configuration) */
+ RENEG_ABORT /* Renegotiation initiated by client, abort the
+ * connection */
+ } reneg_state;
+
+ server_rec *server;
+ SSLDirConfigRec *dc;
+
+ const char *cipher_suite; /* cipher suite used in last reneg */
+ int service_unavailable; /* thouugh we negotiate SSL, no requests will be served */
+ int vhost_found; /* whether we found vhost from SNI already */
+} SSLConnRec;
+
+/* BIG FAT WARNING: SSLModConfigRec has unusual memory lifetime: it is
+ * allocated out of the "process" pool and only a single such
+ * structure is created and used for the lifetime of the process.
+ * (The process pool is s->process->pool and is stored in the .pPool
+ * field.) Most members of this structure are likewise allocated out
+ * of the process pool, but notably sesscache and sesscache_context
+ * are not.
+ *
+ * The structure is treated as mostly immutable after a single config
+ * parse has completed; the post_config hook (ssl_init_Module) flips
+ * the bFixed flag to true and subsequent invocations of the config
+ * callbacks hence do nothing.
+ *
+ * This odd lifetime strategy is used so that encrypted private keys
+ * can be decrypted once at startup and continue to be used across
+ * subsequent server reloads where the interactive password prompt is
+ * not possible.
+
+ * It is really an ABI nightmare waiting to happen since DSOs are
+ * reloaded across restarts, and nothing prevents the struct type
+ * changing across such reloads, yet the cached structure will be
+ * assumed to match regardless.
+ *
+ * This should really be fixed using a smaller structure which only
+ * stores that which is absolutely necessary (the private keys, maybe
+ * the random seed), and have that structure be strictly ABI-versioned
+ * for safety.
+ */
+typedef struct {
+ pid_t pid;
+ apr_pool_t *pPool;
+ BOOL bFixed;
+
+ /* OpenSSL SSL_SESS_CACHE_* flags: */
+ long sesscache_mode;
+
+ /* The configured provider, and associated private data
+ * structure. */
+ const ap_socache_provider_t *sesscache;
+ ap_socache_instance_t *sesscache_context;
+
+ apr_global_mutex_t *pMutex;
+ apr_array_header_t *aRandSeed;
+ apr_hash_t *tVHostKeys;
+
+ /* A hash table of pointers to ssl_asn1_t structures. The structures
+ * are used to store private keys in raw DER format (serialized OpenSSL
+ * PrivateKey structures). The table is indexed by (vhost-id,
+ * index), for example the string "vhost.example.com:443:0". */
+ apr_hash_t *tPrivateKey;
+
+#if defined(HAVE_OPENSSL_ENGINE_H) && defined(HAVE_ENGINE_INIT)
+ const char *szCryptoDevice;
+#endif
+
+#ifdef HAVE_OCSP_STAPLING
+ const ap_socache_provider_t *stapling_cache;
+ ap_socache_instance_t *stapling_cache_context;
+ apr_global_mutex_t *stapling_cache_mutex;
+ apr_global_mutex_t *stapling_refresh_mutex;
+#endif
+#ifdef HAVE_OPENSSL_KEYLOG
+ /* Used for logging if SSLKEYLOGFILE is set at startup. */
+ apr_file_t *keylog_file;
+#endif
+
+#ifdef HAVE_FIPS
+ BOOL fips;
+#endif
+} SSLModConfigRec;
+
+/** Structure representing configured filenames for certs and keys for
+ * a given vhost */
+typedef struct {
+ /* Lists of configured certs and keys for this server */
+ apr_array_header_t *cert_files;
+ apr_array_header_t *key_files;
+
+ /** Certificates which specify the set of CA names which should be
+ * sent in the CertificateRequest message: */
+ const char *ca_name_path;
+ const char *ca_name_file;
+
+ /* TLS service for this server is suspended */
+ int service_unavailable;
+} modssl_pk_server_t;
+
+typedef struct {
+ /** proxy can have any number of cert/key pairs */
+ const char *cert_file;
+ const char *cert_path;
+ const char *ca_cert_file;
+ /* certs is a stack of configured cert, key pairs. */
+ STACK_OF(X509_INFO) *certs;
+ /* ca_certs contains ONLY chain certs for each item in certs.
+ * ca_certs[n] is a pointer to the (STACK_OF(X509) *) stack which
+ * holds the cert chain for the 'n'th cert in the certs stack, or
+ * NULL if no chain is configured. */
+ STACK_OF(X509) **ca_certs;
+} modssl_pk_proxy_t;
+
+/** stuff related to authentication that can also be per-dir */
+typedef struct {
+ /** known/trusted CAs */
+ const char *ca_cert_path;
+ const char *ca_cert_file;
+
+ const char *cipher_suite;
+
+ /** for client or downstream server authentication */
+ int verify_depth;
+ ssl_verify_t verify_mode;
+
+ /** TLSv1.3 has its separate cipher list, separate from the
+ settings for older TLS protocol versions. Since which one takes
+ effect is a matter of negotiation, we need separate settings */
+ const char *tls13_ciphers;
+} modssl_auth_ctx_t;
+
+#ifdef HAVE_TLS_SESSION_TICKETS
+typedef struct {
+ const char *file_path;
+ unsigned char key_name[16];
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+ unsigned char hmac_secret[16];
+#else
+ OSSL_PARAM mac_params[3];
+#endif
+ unsigned char aes_key[16];
+} modssl_ticket_key_t;
+#endif
+
+#ifdef HAVE_SSL_CONF_CMD
+typedef struct {
+ const char *name;
+ const char *value;
+} ssl_ctx_param_t;
+#endif
+
+typedef struct {
+ SSLSrvConfigRec *sc; /** pointer back to server config */
+ SSL_CTX *ssl_ctx;
+
+ /** we are one or the other */
+ modssl_pk_server_t *pks;
+ modssl_pk_proxy_t *pkp;
+
+#ifdef HAVE_TLS_SESSION_TICKETS
+ modssl_ticket_key_t *ticket_key;
+#endif
+
+ ssl_proto_t protocol;
+ int protocol_set;
+
+ /** config for handling encrypted keys */
+ ssl_pphrase_t pphrase_dialog_type;
+ const char *pphrase_dialog_path;
+
+ const char *cert_chain;
+
+ /** certificate revocation list */
+ const char *crl_path;
+ const char *crl_file;
+ int crl_check_mask;
+
+#ifdef HAVE_OCSP_STAPLING
+ /** OCSP stapling options */
+ BOOL stapling_enabled;
+ long stapling_resptime_skew;
+ long stapling_resp_maxage;
+ int stapling_cache_timeout;
+ BOOL stapling_return_errors;
+ BOOL stapling_fake_trylater;
+ int stapling_errcache_timeout;
+ apr_interval_time_t stapling_responder_timeout;
+ const char *stapling_force_url;
+#endif
+
+#ifdef HAVE_SRP
+ char *srp_vfile;
+ char *srp_unknown_user_seed;
+ SRP_VBASE *srp_vbase;
+#endif
+
+ modssl_auth_ctx_t auth;
+
+ int ocsp_mask;
+ BOOL ocsp_force_default; /* true if the default responder URL is
+ * used regardless of per-cert URL */
+ const char *ocsp_responder; /* default responder URL */
+ long ocsp_resptime_skew;
+ long ocsp_resp_maxage;
+ apr_interval_time_t ocsp_responder_timeout;
+ BOOL ocsp_use_request_nonce;
+ apr_uri_t *proxy_uri;
+
+ BOOL ocsp_noverify; /* true if skipping OCSP certification verification like openssl -noverify */
+ /* Declare variables for using OCSP Responder Certs for OCSP verification */
+ int ocsp_verify_flags; /* Flags to use when verifying OCSP response */
+ const char *ocsp_certs_file; /* OCSP other certificates filename */
+ STACK_OF(X509) *ocsp_certs; /* OCSP other certificates */
+
+#ifdef HAVE_SSL_CONF_CMD
+ SSL_CONF_CTX *ssl_ctx_config; /* Configuration context */
+ apr_array_header_t *ssl_ctx_param; /* parameters to pass to SSL_CTX */
+#endif
+
+ BOOL ssl_check_peer_cn;
+ BOOL ssl_check_peer_name;
+ BOOL ssl_check_peer_expire;
+} modssl_ctx_t;
+
+struct SSLSrvConfigRec {
+ SSLModConfigRec *mc;
+ ssl_enabled_t enabled;
+ const char *vhost_id;
+ int vhost_id_len;
+ int session_cache_timeout;
+ BOOL cipher_server_pref;
+ BOOL insecure_reneg;
+ modssl_ctx_t *server;
+#ifdef HAVE_TLSEXT
+ ssl_enabled_t strict_sni_vhost_check;
+#endif
+#ifndef OPENSSL_NO_COMP
+ BOOL compression;
+#endif
+ BOOL session_tickets;
+};
+
+/**
+ * Define the mod_ssl per-directory configuration structure
+ * (i.e. the local configuration for all &lt;Directory>
+ * and .htaccess contexts)
+ */
+struct SSLDirConfigRec {
+ BOOL bSSLRequired;
+ apr_array_header_t *aRequirement;
+ ssl_opt_t nOptions;
+ ssl_opt_t nOptionsAdd;
+ ssl_opt_t nOptionsDel;
+ const char *szCipherSuite;
+ ssl_verify_t nVerifyClient;
+ int nVerifyDepth;
+ const char *szUserName;
+ apr_size_t nRenegBufferSize;
+
+ modssl_ctx_t *proxy;
+ BOOL proxy_enabled;
+ BOOL proxy_post_config;
+};
+
+/**
+ * function prototypes
+ */
+
+/** API glue structures */
+extern module AP_MODULE_DECLARE_DATA ssl_module;
+
+/** configuration handling */
+SSLModConfigRec *ssl_config_global_create(server_rec *);
+void ssl_config_global_fix(SSLModConfigRec *);
+BOOL ssl_config_global_isfixed(SSLModConfigRec *);
+void *ssl_config_server_create(apr_pool_t *, server_rec *);
+void *ssl_config_server_merge(apr_pool_t *, void *, void *);
+void *ssl_config_perdir_create(apr_pool_t *, char *);
+void *ssl_config_perdir_merge(apr_pool_t *, void *, void *);
+void ssl_config_proxy_merge(apr_pool_t *,
+ SSLDirConfigRec *, SSLDirConfigRec *);
+const char *ssl_cmd_SSLPassPhraseDialog(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLCryptoDevice(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLRandomSeed(cmd_parms *, void *, const char *, const char *, const char *);
+const char *ssl_cmd_SSLEngine(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLCipherSuite(cmd_parms *, void *, const char *, const char *);
+const char *ssl_cmd_SSLCertificateFile(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLCertificateKeyFile(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLCertificateChainFile(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLCACertificatePath(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLCACertificateFile(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLCADNRequestPath(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLCADNRequestFile(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLCARevocationPath(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLCARevocationFile(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLCARevocationCheck(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLHonorCipherOrder(cmd_parms *cmd, void *dcfg, int flag);
+const char *ssl_cmd_SSLCompression(cmd_parms *, void *, int flag);
+const char *ssl_cmd_SSLSessionTickets(cmd_parms *, void *, int flag);
+const char *ssl_cmd_SSLVerifyClient(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLVerifyDepth(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLSessionCache(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLSessionCacheTimeout(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLProtocol(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLOptions(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLRequireSSL(cmd_parms *, void *);
+const char *ssl_cmd_SSLRequire(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLUserName(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLRenegBufferSize(cmd_parms *cmd, void *dcfg, const char *arg);
+const char *ssl_cmd_SSLStrictSNIVHostCheck(cmd_parms *cmd, void *dcfg, int flag);
+const char *ssl_cmd_SSLInsecureRenegotiation(cmd_parms *cmd, void *dcfg, int flag);
+
+const char *ssl_cmd_SSLProxyEngine(cmd_parms *cmd, void *dcfg, int flag);
+const char *ssl_cmd_SSLProxyProtocol(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLProxyCipherSuite(cmd_parms *, void *, const char *, const char *);
+const char *ssl_cmd_SSLProxyVerify(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLProxyVerifyDepth(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLProxyCACertificatePath(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLProxyCACertificateFile(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLProxyCARevocationPath(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLProxyCARevocationFile(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLProxyCARevocationCheck(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLProxyMachineCertificatePath(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLProxyMachineCertificateFile(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLProxyMachineCertificateChainFile(cmd_parms *, void *, const char *);
+#ifdef HAVE_TLS_SESSION_TICKETS
+const char *ssl_cmd_SSLSessionTicketKeyFile(cmd_parms *cmd, void *dcfg, const char *arg);
+#endif
+const char *ssl_cmd_SSLProxyCheckPeerExpire(cmd_parms *cmd, void *dcfg, int flag);
+const char *ssl_cmd_SSLProxyCheckPeerCN(cmd_parms *cmd, void *dcfg, int flag);
+const char *ssl_cmd_SSLProxyCheckPeerName(cmd_parms *cmd, void *dcfg, int flag);
+
+const char *ssl_cmd_SSLOCSPOverrideResponder(cmd_parms *cmd, void *dcfg, int flag);
+const char *ssl_cmd_SSLOCSPDefaultResponder(cmd_parms *cmd, void *dcfg, const char *arg);
+const char *ssl_cmd_SSLOCSPResponseTimeSkew(cmd_parms *cmd, void *dcfg, const char *arg);
+const char *ssl_cmd_SSLOCSPResponseMaxAge(cmd_parms *cmd, void *dcfg, const char *arg);
+const char *ssl_cmd_SSLOCSPResponderTimeout(cmd_parms *cmd, void *dcfg, const char *arg);
+const char *ssl_cmd_SSLOCSPUseRequestNonce(cmd_parms *cmd, void *dcfg, int flag);
+const char *ssl_cmd_SSLOCSPEnable(cmd_parms *cmd, void *dcfg, const char *arg);
+const char *ssl_cmd_SSLOCSPProxyURL(cmd_parms *cmd, void *dcfg, const char *arg);
+
+/* Declare OCSP Responder Certificate Verification Directive */
+const char *ssl_cmd_SSLOCSPNoVerify(cmd_parms *cmd, void *dcfg, int flag);
+/* Declare OCSP Responder Certificate File Directive */
+const char *ssl_cmd_SSLOCSPResponderCertificateFile(cmd_parms *cmd, void *dcfg, const char *arg);
+
+#ifdef HAVE_SSL_CONF_CMD
+const char *ssl_cmd_SSLOpenSSLConfCmd(cmd_parms *cmd, void *dcfg, const char *arg1, const char *arg2);
+#endif
+
+#ifdef HAVE_SRP
+const char *ssl_cmd_SSLSRPVerifierFile(cmd_parms *cmd, void *dcfg, const char *arg);
+const char *ssl_cmd_SSLSRPUnknownUserSeed(cmd_parms *cmd, void *dcfg, const char *arg);
+#endif
+
+const char *ssl_cmd_SSLFIPS(cmd_parms *cmd, void *dcfg, int flag);
+
+/** module initialization */
+apr_status_t ssl_init_Module(apr_pool_t *, apr_pool_t *, apr_pool_t *, server_rec *);
+apr_status_t ssl_init_Engine(server_rec *, apr_pool_t *);
+apr_status_t ssl_init_ConfigureServer(server_rec *, apr_pool_t *, apr_pool_t *, SSLSrvConfigRec *,
+ apr_array_header_t *);
+apr_status_t ssl_init_CheckServers(server_rec *, apr_pool_t *);
+int ssl_proxy_section_post_config(apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s,
+ ap_conf_vector_t *section_config);
+STACK_OF(X509_NAME)
+ *ssl_init_FindCAList(server_rec *, apr_pool_t *, const char *, const char *);
+void ssl_init_Child(apr_pool_t *, server_rec *);
+apr_status_t ssl_init_ModuleKill(void *data);
+
+/** Apache API hooks */
+int ssl_hook_Auth(request_rec *);
+int ssl_hook_UserCheck(request_rec *);
+int ssl_hook_Access(request_rec *);
+int ssl_hook_Fixup(request_rec *);
+int ssl_hook_ReadReq(request_rec *);
+int ssl_hook_Upgrade(request_rec *);
+void ssl_hook_ConfigTest(apr_pool_t *pconf, server_rec *s);
+
+/** Apache authz provisders */
+extern const authz_provider ssl_authz_provider_require_ssl;
+extern const authz_provider ssl_authz_provider_verify_client;
+
+/** OpenSSL callbacks */
+DH *ssl_callback_TmpDH(SSL *, int, int);
+int ssl_callback_SSLVerify(int, X509_STORE_CTX *);
+int ssl_callback_SSLVerify_CRL(int, X509_STORE_CTX *, conn_rec *);
+int ssl_callback_proxy_cert(SSL *ssl, X509 **x509, EVP_PKEY **pkey);
+int ssl_callback_NewSessionCacheEntry(SSL *, SSL_SESSION *);
+SSL_SESSION *ssl_callback_GetSessionCacheEntry(SSL *, IDCONST unsigned char *, int, int *);
+void ssl_callback_DelSessionCacheEntry(SSL_CTX *, SSL_SESSION *);
+void ssl_callback_Info(const SSL *, int, int);
+#ifdef HAVE_TLSEXT
+int ssl_callback_ServerNameIndication(SSL *, int *, modssl_ctx_t *);
+#endif
+#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER)
+int ssl_callback_ClientHello(SSL *, int *, void *);
+#endif
+#ifdef HAVE_TLS_SESSION_TICKETS
+int ssl_callback_SessionTicket(SSL *ssl,
+ unsigned char *keyname,
+ unsigned char *iv,
+ EVP_CIPHER_CTX *cipher_ctx,
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+ HMAC_CTX *hmac_ctx,
+#else
+ EVP_MAC_CTX *mac_ctx,
+#endif
+ int mode);
+#endif
+
+#ifdef HAVE_TLS_ALPN
+int ssl_callback_alpn_select(SSL *ssl, const unsigned char **out,
+ unsigned char *outlen, const unsigned char *in,
+ unsigned int inlen, void *arg);
+#endif
+
+/** Session Cache Support */
+apr_status_t ssl_scache_init(server_rec *, apr_pool_t *);
+void ssl_scache_status_register(apr_pool_t *p);
+void ssl_scache_kill(server_rec *);
+BOOL ssl_scache_store(server_rec *, IDCONST UCHAR *, int,
+ apr_time_t, SSL_SESSION *, apr_pool_t *);
+SSL_SESSION *ssl_scache_retrieve(server_rec *, IDCONST UCHAR *, int, apr_pool_t *);
+void ssl_scache_remove(server_rec *, IDCONST UCHAR *, int,
+ apr_pool_t *);
+
+/** OCSP Stapling Support */
+#ifdef HAVE_OCSP_STAPLING
+const char *ssl_cmd_SSLStaplingCache(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLUseStapling(cmd_parms *, void *, int);
+const char *ssl_cmd_SSLStaplingResponseTimeSkew(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLStaplingResponseMaxAge(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLStaplingStandardCacheTimeout(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLStaplingErrorCacheTimeout(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLStaplingReturnResponderErrors(cmd_parms *, void *, int);
+const char *ssl_cmd_SSLStaplingFakeTryLater(cmd_parms *, void *, int);
+const char *ssl_cmd_SSLStaplingResponderTimeout(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLStaplingForceURL(cmd_parms *, void *, const char *);
+apr_status_t modssl_init_stapling(server_rec *, apr_pool_t *, apr_pool_t *, modssl_ctx_t *);
+void ssl_stapling_certinfo_hash_init(apr_pool_t *);
+int ssl_stapling_init_cert(server_rec *, apr_pool_t *, apr_pool_t *,
+ modssl_ctx_t *, X509 *);
+#endif
+#ifdef HAVE_SRP
+int ssl_callback_SRPServerParams(SSL *, int *, void *);
+#endif
+
+#ifdef HAVE_OPENSSL_KEYLOG
+/* Callback used with SSL_CTX_set_keylog_callback. */
+void modssl_callback_keylog(const SSL *ssl, const char *line);
+#endif
+
+/** I/O */
+void ssl_io_filter_init(conn_rec *, request_rec *r, SSL *);
+void ssl_io_filter_register(apr_pool_t *);
+long ssl_io_data_cb(BIO *, int, const char *, int, long, long);
+
+/* ssl_io_buffer_fill fills the setaside buffering of the HTTP request
+ * to allow an SSL renegotiation to take place. */
+int ssl_io_buffer_fill(request_rec *r, apr_size_t maxlen);
+
+/** PRNG */
+int ssl_rand_seed(server_rec *, apr_pool_t *, ssl_rsctx_t, char *);
+
+/** Utility Functions */
+char *ssl_util_vhostid(apr_pool_t *, server_rec *);
+apr_file_t *ssl_util_ppopen(server_rec *, apr_pool_t *, const char *,
+ const char * const *);
+void ssl_util_ppclose(server_rec *, apr_pool_t *, apr_file_t *);
+char *ssl_util_readfilter(server_rec *, apr_pool_t *, const char *,
+ const char * const *);
+BOOL ssl_util_path_check(ssl_pathcheck_t, const char *, apr_pool_t *);
+#if APR_HAS_THREADS && MODSSL_USE_OPENSSL_PRE_1_1_API
+void ssl_util_thread_setup(apr_pool_t *);
+void ssl_util_thread_id_setup(apr_pool_t *);
+#endif
+int ssl_init_ssl_connection(conn_rec *c, request_rec *r);
+
+BOOL ssl_util_vhost_matches(const char *servername, server_rec *s);
+
+/** Pass Phrase Support */
+apr_status_t ssl_load_encrypted_pkey(server_rec *, apr_pool_t *, int,
+ const char *, apr_array_header_t **);
+
+/* Load public and/or private key from the configured ENGINE. Private
+ * key returned as *pkey. certid can be NULL, in which case *pubkey
+ * is not altered. Errors logged on failure. */
+apr_status_t modssl_load_engine_keypair(server_rec *s, apr_pool_t *p,
+ const char *vhostid,
+ const char *certid, const char *keyid,
+ X509 **pubkey, EVP_PKEY **privkey);
+
+/** Diffie-Hellman Parameter Support */
+DH *ssl_dh_GetParamFromFile(const char *);
+#ifdef HAVE_ECC
+EC_GROUP *ssl_ec_GetParamFromFile(const char *);
+#endif
+
+/* Store the EVP_PKEY key (serialized into DER) in the hash table with
+ * key, returning the ssl_asn1_t structure pointer. */
+ssl_asn1_t *ssl_asn1_table_set(apr_hash_t *table, const char *key,
+ EVP_PKEY *pkey);
+/* Retrieve the ssl_asn1_t structure with given key from the hash. */
+ssl_asn1_t *ssl_asn1_table_get(apr_hash_t *table, const char *key);
+/* Remove and free the ssl_asn1_t structure with given key. */
+void ssl_asn1_table_unset(apr_hash_t *table, const char *key);
+
+/** Mutex Support */
+int ssl_mutex_init(server_rec *, apr_pool_t *);
+int ssl_mutex_reinit(server_rec *, apr_pool_t *);
+int ssl_mutex_on(server_rec *);
+int ssl_mutex_off(server_rec *);
+
+int ssl_stapling_mutex_reinit(server_rec *, apr_pool_t *);
+
+/* mutex type names for Mutex directive */
+#define SSL_CACHE_MUTEX_TYPE "ssl-cache"
+#define SSL_STAPLING_CACHE_MUTEX_TYPE "ssl-stapling"
+#define SSL_STAPLING_REFRESH_MUTEX_TYPE "ssl-stapling-refresh"
+
+apr_status_t ssl_die(server_rec *);
+
+/** Logfile Support */
+void ssl_log_ssl_error(const char *, int, int, server_rec *);
+
+/* ssl_log_xerror, ssl_log_cxerror and ssl_log_rxerror are wrappers for the
+ * respective ap_log_*error functions and take a certificate as an
+ * additional argument (whose details are appended to the log message).
+ * The other arguments are interpreted exactly as with their ap_log_*error
+ * counterparts. */
+void ssl_log_xerror(const char *file, int line, int level,
+ apr_status_t rv, apr_pool_t *p, server_rec *s,
+ X509 *cert, const char *format, ...)
+ __attribute__((format(printf,8,9)));
+
+void ssl_log_cxerror(const char *file, int line, int level,
+ apr_status_t rv, conn_rec *c, X509 *cert,
+ const char *format, ...)
+ __attribute__((format(printf,7,8)));
+
+void ssl_log_rxerror(const char *file, int line, int level,
+ apr_status_t rv, request_rec *r, X509 *cert,
+ const char *format, ...)
+ __attribute__((format(printf,7,8)));
+
+#define SSLLOG_MARK __FILE__,__LINE__
+
+/** Variables */
+
+/* Register variables for the lifetime of the process pool 'p'. */
+void ssl_var_register(apr_pool_t *p);
+char *ssl_var_lookup(apr_pool_t *, server_rec *, conn_rec *, request_rec *, char *);
+apr_array_header_t *ssl_ext_list(apr_pool_t *p, conn_rec *c, int peer, const char *extension);
+
+void ssl_var_log_config_register(apr_pool_t *p);
+
+/* Extract SSL_*_DN_* variables into table 't' from SSL object 'ssl',
+ * allocating from 'p': */
+void modssl_var_extract_dns(apr_table_t *t, SSL *ssl, apr_pool_t *p);
+
+/* Extract SSL_*_SAN_* variables (subjectAltName entries) into table 't'
+ * from SSL object 'ssl', allocating from 'p'. */
+void modssl_var_extract_san_entries(apr_table_t *t, SSL *ssl, apr_pool_t *p);
+
+#ifndef OPENSSL_NO_OCSP
+/* Perform OCSP validation of the current cert in the given context.
+ * Returns non-zero on success or zero on failure. On failure, the
+ * context error code is set. */
+int modssl_verify_ocsp(X509_STORE_CTX *ctx, SSLSrvConfigRec *sc,
+ server_rec *s, conn_rec *c, apr_pool_t *pool);
+
+/* OCSP helper interface; dispatches the given OCSP request to the
+ * responder at the given URI. Returns the decoded OCSP response
+ * object, or NULL on error (in which case, errors will have been
+ * logged). Pool 'p' is used for temporary allocations. */
+OCSP_RESPONSE *modssl_dispatch_ocsp_request(const apr_uri_t *uri,
+ apr_interval_time_t timeout,
+ OCSP_REQUEST *request,
+ conn_rec *c, apr_pool_t *p);
+
+/* Initialize OCSP trusted certificate list */
+void ssl_init_ocsp_certificates(server_rec *s, modssl_ctx_t *mctx);
+
+#endif
+
+#if MODSSL_USE_OPENSSL_PRE_1_1_API
+/* Retrieve DH parameters for given key length. Return value should
+ * be treated as unmutable, since it is stored in process-global
+ * memory. */
+DH *modssl_get_dh_params(unsigned keylen);
+#endif
+
+/* Returns non-zero if the request was made over SSL/TLS. If sslconn
+ * is non-NULL and the request is using SSL/TLS, sets *sslconn to the
+ * corresponding SSLConnRec structure for the connection. */
+int modssl_request_is_tls(const request_rec *r, SSLConnRec **sslconn);
+
+int ssl_is_challenge(conn_rec *c, const char *servername,
+ X509 **pcert, EVP_PKEY **pkey,
+ const char **pcert_file, const char **pkey_file);
+
+/* Returns non-zero if the cert/key filename should be handled through
+ * the configured ENGINE. */
+int modssl_is_engine_id(const char *name);
+
+#endif /* SSL_PRIVATE_H */
+/** @} */
+
diff --git a/modules/ssl/ssl_scache.c b/modules/ssl/ssl_scache.c
new file mode 100644
index 0000000..c0a0950
--- /dev/null
+++ b/modules/ssl/ssl_scache.c
@@ -0,0 +1,239 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* _ _
+ * _ __ ___ ___ __| | ___ ___| | mod_ssl
+ * | '_ ` _ \ / _ \ / _` | / __/ __| | Apache Interface to OpenSSL
+ * | | | | | | (_) | (_| | \__ \__ \ |
+ * |_| |_| |_|\___/ \__,_|___|___/___/_|
+ * |_____|
+ * ssl_scache.c
+ * Session Cache Abstraction
+ */
+ /* ``Open-Source Software: generous
+ programmers from around the world all
+ join forces to help you shoot
+ yourself in the foot for free.''
+ -- Unknown */
+#include "ssl_private.h"
+#include "mod_status.h"
+
+/* _________________________________________________________________
+**
+** Session Cache: Common Abstraction Layer
+** _________________________________________________________________
+*/
+
+apr_status_t ssl_scache_init(server_rec *s, apr_pool_t *p)
+{
+ SSLModConfigRec *mc = myModConfig(s);
+ apr_status_t rv;
+ struct ap_socache_hints hints;
+
+ /* The very first invocation of this function will be the
+ * post_config invocation during server startup; do nothing for
+ * this first (and only the first) time through, since the pool
+ * will be immediately cleared anyway. For every subsequent
+ * invocation, initialize the configured cache. */
+ if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG)
+ return APR_SUCCESS;
+
+#ifdef HAVE_OCSP_STAPLING
+ if (mc->stapling_cache) {
+ memset(&hints, 0, sizeof hints);
+ hints.avg_obj_size = 1500;
+ hints.avg_id_len = 20;
+ hints.expiry_interval = 300;
+
+ rv = mc->stapling_cache->init(mc->stapling_cache_context,
+ "mod_ssl-staple", &hints, s, p);
+ if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(01872)
+ "Could not initialize stapling cache. Exiting.");
+ return ssl_die(s);
+ }
+ }
+#endif
+
+ /*
+ * Warn the user that he should use the session cache.
+ * But we can operate without it, of course.
+ */
+ if (mc->sesscache == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(01873)
+ "Init: Session Cache is not configured "
+ "[hint: SSLSessionCache]");
+ return APR_SUCCESS;
+ }
+
+ memset(&hints, 0, sizeof hints);
+ hints.avg_obj_size = 150;
+ hints.avg_id_len = 30;
+ hints.expiry_interval = 30;
+
+ rv = mc->sesscache->init(mc->sesscache_context, "mod_ssl-sess", &hints, s, p);
+ if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(01874)
+ "Could not initialize session cache. Exiting.");
+ return ssl_die(s);
+ }
+
+ return APR_SUCCESS;
+}
+
+void ssl_scache_kill(server_rec *s)
+{
+ SSLModConfigRec *mc = myModConfig(s);
+
+ if (mc->sesscache) {
+ mc->sesscache->destroy(mc->sesscache_context, s);
+ }
+
+#ifdef HAVE_OCSP_STAPLING
+ if (mc->stapling_cache) {
+ mc->stapling_cache->destroy(mc->stapling_cache_context, s);
+ }
+#endif
+
+}
+
+BOOL ssl_scache_store(server_rec *s, IDCONST UCHAR *id, int idlen,
+ apr_time_t expiry, SSL_SESSION *sess,
+ apr_pool_t *p)
+{
+ SSLModConfigRec *mc = myModConfig(s);
+ unsigned char encoded[MODSSL_SESSION_MAX_DER], *ptr;
+ unsigned int len;
+ apr_status_t rv;
+
+ /* Serialise the session. */
+ len = i2d_SSL_SESSION(sess, NULL);
+ if (len > sizeof encoded) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(01875)
+ "session is too big (%u bytes)", len);
+ return FALSE;
+ }
+
+ ptr = encoded;
+ len = i2d_SSL_SESSION(sess, &ptr);
+
+ if (mc->sesscache->flags & AP_SOCACHE_FLAG_NOTMPSAFE) {
+ ssl_mutex_on(s);
+ }
+
+ rv = mc->sesscache->store(mc->sesscache_context, s, id, idlen,
+ expiry, encoded, len, p);
+
+ if (mc->sesscache->flags & AP_SOCACHE_FLAG_NOTMPSAFE) {
+ ssl_mutex_off(s);
+ }
+
+ return rv == APR_SUCCESS ? TRUE : FALSE;
+}
+
+SSL_SESSION *ssl_scache_retrieve(server_rec *s, IDCONST UCHAR *id, int idlen,
+ apr_pool_t *p)
+{
+ SSLModConfigRec *mc = myModConfig(s);
+ unsigned char dest[MODSSL_SESSION_MAX_DER];
+ unsigned int destlen = MODSSL_SESSION_MAX_DER;
+ const unsigned char *ptr;
+ apr_status_t rv;
+
+ if (mc->sesscache->flags & AP_SOCACHE_FLAG_NOTMPSAFE) {
+ ssl_mutex_on(s);
+ }
+
+ rv = mc->sesscache->retrieve(mc->sesscache_context, s, id, idlen,
+ dest, &destlen, p);
+
+ if (mc->sesscache->flags & AP_SOCACHE_FLAG_NOTMPSAFE) {
+ ssl_mutex_off(s);
+ }
+
+ if (rv != APR_SUCCESS) {
+ return NULL;
+ }
+
+ ptr = dest;
+
+ return d2i_SSL_SESSION(NULL, &ptr, destlen);
+}
+
+void ssl_scache_remove(server_rec *s, IDCONST UCHAR *id, int idlen,
+ apr_pool_t *p)
+{
+ SSLModConfigRec *mc = myModConfig(s);
+
+ if (mc->sesscache->flags & AP_SOCACHE_FLAG_NOTMPSAFE) {
+ ssl_mutex_on(s);
+ }
+
+ mc->sesscache->remove(mc->sesscache_context, s, id, idlen, p);
+
+ if (mc->sesscache->flags & AP_SOCACHE_FLAG_NOTMPSAFE) {
+ ssl_mutex_off(s);
+ }
+}
+
+/* _________________________________________________________________
+**
+** SSL Extension to mod_status
+** _________________________________________________________________
+*/
+static int ssl_ext_status_hook(request_rec *r, int flags)
+{
+ SSLModConfigRec *mc = myModConfig(r->server);
+
+ if (mc == NULL || mc->sesscache == NULL)
+ return OK;
+
+ if (!(flags & AP_STATUS_SHORT)) {
+ ap_rputs("<hr>\n", r);
+ ap_rputs("<table cellspacing=0 cellpadding=0>\n", r);
+ ap_rputs("<tr><td bgcolor=\"#000000\">\n", r);
+ ap_rputs("<b><font color=\"#ffffff\" face=\"Arial,Helvetica\">SSL/TLS Session Cache Status:</font></b>\r", r);
+ ap_rputs("</td></tr>\n", r);
+ ap_rputs("<tr><td bgcolor=\"#ffffff\">\n", r);
+ }
+ else {
+ ap_rputs("TLSSessionCacheStatus\n", r);
+ }
+
+ if (mc->sesscache->flags & AP_SOCACHE_FLAG_NOTMPSAFE) {
+ ssl_mutex_on(r->server);
+ }
+
+ mc->sesscache->status(mc->sesscache_context, r, flags);
+
+ if (mc->sesscache->flags & AP_SOCACHE_FLAG_NOTMPSAFE) {
+ ssl_mutex_off(r->server);
+ }
+
+ if (!(flags & AP_STATUS_SHORT)) {
+ ap_rputs("</td></tr>\n", r);
+ ap_rputs("</table>\n", r);
+ }
+
+ return OK;
+}
+
+void ssl_scache_status_register(apr_pool_t *p)
+{
+ APR_OPTIONAL_HOOK(ap, status_hook, ssl_ext_status_hook, NULL, NULL,
+ APR_HOOK_MIDDLE);
+}
+
diff --git a/modules/ssl/ssl_util.c b/modules/ssl/ssl_util.c
new file mode 100644
index 0000000..c889295
--- /dev/null
+++ b/modules/ssl/ssl_util.c
@@ -0,0 +1,485 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* _ _
+ * _ __ ___ ___ __| | ___ ___| | mod_ssl
+ * | '_ ` _ \ / _ \ / _` | / __/ __| | Apache Interface to OpenSSL
+ * | | | | | | (_) | (_| | \__ \__ \ |
+ * |_| |_| |_|\___/ \__,_|___|___/___/_|
+ * |_____|
+ * ssl_util.c
+ * Utility Functions
+ */
+ /* ``Every day of my life
+ I am forced to add another
+ name to the list of people
+ who piss me off!''
+ -- Calvin */
+
+#include "ssl_private.h"
+#include "ap_mpm.h"
+#include "apr_thread_mutex.h"
+
+/* _________________________________________________________________
+**
+** Utility Functions
+** _________________________________________________________________
+*/
+
+char *ssl_util_vhostid(apr_pool_t *p, server_rec *s)
+{
+ SSLSrvConfigRec *sc;
+ apr_port_t port;
+
+ if (s->port != 0)
+ port = s->port;
+ else {
+ sc = mySrvConfig(s);
+ port = sc->enabled == TRUE ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT;
+ }
+
+ return apr_psprintf(p, "%s:%lu", s->server_hostname, (unsigned long)port);
+}
+
+/*
+ * Return TRUE iff the given servername matches the server record when
+ * selecting virtual hosts.
+ */
+BOOL ssl_util_vhost_matches(const char *servername, server_rec *s)
+{
+ apr_array_header_t *names;
+ int i;
+
+ /* check ServerName */
+ if (!strcasecmp(servername, s->server_hostname)) {
+ return TRUE;
+ }
+
+ /*
+ * if not matched yet, check ServerAlias entries
+ * (adapted from vhost.c:matches_aliases())
+ */
+ names = s->names;
+ if (names) {
+ char **name = (char **)names->elts;
+ for (i = 0; i < names->nelts; ++i) {
+ if (!name[i])
+ continue;
+ if (!strcasecmp(servername, name[i])) {
+ return TRUE;
+ }
+ }
+ }
+
+ /* if still no match, check ServerAlias entries with wildcards */
+ names = s->wild_names;
+ if (names) {
+ char **name = (char **)names->elts;
+ for (i = 0; i < names->nelts; ++i) {
+ if (!name[i])
+ continue;
+ if (!ap_strcasecmp_match(servername, name[i])) {
+ return TRUE;
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+int modssl_request_is_tls(const request_rec *r, SSLConnRec **scout)
+{
+ SSLConnRec *sslconn = myConnConfig(r->connection);
+ SSLSrvConfigRec *sc = mySrvConfig(r->server);
+
+ if (!(sslconn && sslconn->ssl) && r->connection->master) {
+ sslconn = myConnConfig(r->connection->master);
+ }
+
+ if (sc->enabled == SSL_ENABLED_FALSE || !sslconn || !sslconn->ssl)
+ return 0;
+
+ if (scout) *scout = sslconn;
+
+ return 1;
+}
+
+apr_file_t *ssl_util_ppopen(server_rec *s, apr_pool_t *p, const char *cmd,
+ const char * const *argv)
+{
+ apr_procattr_t *procattr;
+ apr_proc_t *proc;
+
+ if (apr_procattr_create(&procattr, p) != APR_SUCCESS)
+ return NULL;
+ if (apr_procattr_io_set(procattr, APR_FULL_BLOCK, APR_FULL_BLOCK,
+ APR_FULL_BLOCK) != APR_SUCCESS)
+ return NULL;
+ if (apr_procattr_dir_set(procattr,
+ ap_make_dirstr_parent(p, cmd)) != APR_SUCCESS)
+ return NULL;
+ if (apr_procattr_cmdtype_set(procattr, APR_PROGRAM) != APR_SUCCESS)
+ return NULL;
+ proc = apr_pcalloc(p, sizeof(apr_proc_t));
+ if (apr_proc_create(proc, cmd, argv, NULL, procattr, p) != APR_SUCCESS)
+ return NULL;
+ return proc->out;
+}
+
+void ssl_util_ppclose(server_rec *s, apr_pool_t *p, apr_file_t *fp)
+{
+ apr_file_close(fp);
+ return;
+}
+
+/*
+ * Run a filter program and read the first line of its stdout output
+ */
+char *ssl_util_readfilter(server_rec *s, apr_pool_t *p, const char *cmd,
+ const char * const *argv)
+{
+ static char buf[MAX_STRING_LEN];
+ apr_file_t *fp;
+ apr_size_t nbytes = 1;
+ char c;
+ int k;
+
+ if ((fp = ssl_util_ppopen(s, p, cmd, argv)) == NULL)
+ return NULL;
+ /* XXX: we are reading 1 byte at a time here */
+ for (k = 0; apr_file_read(fp, &c, &nbytes) == APR_SUCCESS
+ && nbytes == 1 && (k < MAX_STRING_LEN-1) ; ) {
+ if (c == '\n' || c == '\r')
+ break;
+ buf[k++] = c;
+ }
+ buf[k] = NUL;
+ ssl_util_ppclose(s, p, fp);
+
+ return buf;
+}
+
+BOOL ssl_util_path_check(ssl_pathcheck_t pcm, const char *path, apr_pool_t *p)
+{
+ apr_finfo_t finfo;
+
+ if (path == NULL)
+ return FALSE;
+ if (pcm & SSL_PCM_EXISTS && apr_stat(&finfo, path,
+ APR_FINFO_TYPE|APR_FINFO_SIZE, p) != 0)
+ return FALSE;
+ AP_DEBUG_ASSERT((pcm & SSL_PCM_EXISTS) ||
+ !(pcm & (SSL_PCM_ISREG|SSL_PCM_ISDIR|SSL_PCM_ISNONZERO)));
+ if (pcm & SSL_PCM_ISREG && finfo.filetype != APR_REG)
+ return FALSE;
+ if (pcm & SSL_PCM_ISDIR && finfo.filetype != APR_DIR)
+ return FALSE;
+ if (pcm & SSL_PCM_ISNONZERO && finfo.size <= 0)
+ return FALSE;
+ return TRUE;
+}
+
+/* Decrypted private keys are cached to survive restarts. The cached
+ * data must have lifetime of the process (hence malloc/free rather
+ * than pools), and uses raw DER since the EVP_PKEY structure
+ * internals may not survive across a module reload. */
+ssl_asn1_t *ssl_asn1_table_set(apr_hash_t *table, const char *key,
+ EVP_PKEY *pkey)
+{
+ apr_ssize_t klen = strlen(key);
+ ssl_asn1_t *asn1 = apr_hash_get(table, key, klen);
+ apr_size_t length = i2d_PrivateKey(pkey, NULL);
+ unsigned char *p;
+
+ /* Re-use structure if cached previously. */
+ if (asn1) {
+ if (asn1->nData != length) {
+ asn1->cpData = ap_realloc(asn1->cpData, length);
+ }
+ }
+ else {
+ asn1 = ap_malloc(sizeof(*asn1));
+ asn1->source_mtime = 0; /* used as a note for encrypted private keys */
+ asn1->cpData = ap_malloc(length);
+
+ apr_hash_set(table, key, klen, asn1);
+ }
+
+ asn1->nData = length;
+ p = asn1->cpData;
+ i2d_PrivateKey(pkey, &p); /* increases p by length */
+
+ return asn1;
+}
+
+ssl_asn1_t *ssl_asn1_table_get(apr_hash_t *table,
+ const char *key)
+{
+ return (ssl_asn1_t *)apr_hash_get(table, key, APR_HASH_KEY_STRING);
+}
+
+void ssl_asn1_table_unset(apr_hash_t *table,
+ const char *key)
+{
+ apr_ssize_t klen = strlen(key);
+ ssl_asn1_t *asn1 = apr_hash_get(table, key, klen);
+
+ if (!asn1) {
+ return;
+ }
+
+ if (asn1->cpData) {
+ free(asn1->cpData);
+ }
+ free(asn1);
+
+ apr_hash_set(table, key, klen, NULL);
+}
+
+#if APR_HAS_THREADS && MODSSL_USE_OPENSSL_PRE_1_1_API
+
+/*
+ * To ensure thread-safetyness in OpenSSL - work in progress
+ */
+
+static apr_thread_mutex_t **lock_cs;
+static int lock_num_locks;
+
+static void ssl_util_thr_lock(int mode, int type,
+ const char *file, int line)
+{
+ if (type < lock_num_locks) {
+ if (mode & CRYPTO_LOCK) {
+ apr_thread_mutex_lock(lock_cs[type]);
+ }
+ else {
+ apr_thread_mutex_unlock(lock_cs[type]);
+ }
+ }
+}
+
+/* Dynamic lock structure */
+struct CRYPTO_dynlock_value {
+ apr_pool_t *pool;
+ const char* file;
+ int line;
+ apr_thread_mutex_t *mutex;
+};
+
+/* Global reference to the pool passed into ssl_util_thread_setup() */
+apr_pool_t *dynlockpool = NULL;
+
+/*
+ * Dynamic lock creation callback
+ */
+static struct CRYPTO_dynlock_value *ssl_dyn_create_function(const char *file,
+ int line)
+{
+ struct CRYPTO_dynlock_value *value;
+ apr_pool_t *p;
+ apr_status_t rv;
+
+ /*
+ * We need a pool to allocate our mutex. Since we can't clear
+ * allocated memory from a pool, create a subpool that we can blow
+ * away in the destruction callback.
+ */
+ apr_pool_create(&p, dynlockpool);
+ apr_pool_tag(p, "modssl_dynlock_value");
+ ap_log_perror(file, line, APLOG_MODULE_INDEX, APLOG_TRACE1, 0, p,
+ "Creating dynamic lock");
+
+ value = apr_palloc(p, sizeof(struct CRYPTO_dynlock_value));
+ value->pool = p;
+ /* Keep our own copy of the place from which we were created,
+ using our own pool. */
+ value->file = apr_pstrdup(p, file);
+ value->line = line;
+ rv = apr_thread_mutex_create(&(value->mutex), APR_THREAD_MUTEX_DEFAULT,
+ p);
+ if (rv != APR_SUCCESS) {
+ ap_log_perror(file, line, APLOG_MODULE_INDEX, APLOG_ERR, rv, p, APLOGNO(02186)
+ "Failed to create thread mutex for dynamic lock");
+ apr_pool_destroy(p);
+ return NULL;
+ }
+ return value;
+}
+
+/*
+ * Dynamic locking and unlocking function
+ */
+
+static void ssl_dyn_lock_function(int mode, struct CRYPTO_dynlock_value *l,
+ const char *file, int line)
+{
+ apr_status_t rv;
+
+ if (mode & CRYPTO_LOCK) {
+ ap_log_perror(file, line, APLOG_MODULE_INDEX, APLOG_TRACE3, 0, l->pool,
+ "Acquiring mutex %s:%d", l->file, l->line);
+ rv = apr_thread_mutex_lock(l->mutex);
+ ap_log_perror(file, line, APLOG_MODULE_INDEX, APLOG_TRACE3, rv, l->pool,
+ "Mutex %s:%d acquired!", l->file, l->line);
+ }
+ else {
+ ap_log_perror(file, line, APLOG_MODULE_INDEX, APLOG_TRACE3, 0, l->pool,
+ "Releasing mutex %s:%d", l->file, l->line);
+ rv = apr_thread_mutex_unlock(l->mutex);
+ ap_log_perror(file, line, APLOG_MODULE_INDEX, APLOG_TRACE3, rv, l->pool,
+ "Mutex %s:%d released!", l->file, l->line);
+ }
+}
+
+/*
+ * Dynamic lock destruction callback
+ */
+static void ssl_dyn_destroy_function(struct CRYPTO_dynlock_value *l,
+ const char *file, int line)
+{
+ apr_status_t rv;
+
+ ap_log_perror(file, line, APLOG_MODULE_INDEX, APLOG_TRACE1, 0, l->pool,
+ "Destroying dynamic lock %s:%d", l->file, l->line);
+ rv = apr_thread_mutex_destroy(l->mutex);
+ if (rv != APR_SUCCESS) {
+ ap_log_perror(file, line, APLOG_MODULE_INDEX, APLOG_ERR, rv, l->pool,
+ APLOGNO(02192) "Failed to destroy mutex for dynamic "
+ "lock %s:%d", l->file, l->line);
+ }
+
+ /* Trust that whomever owned the CRYPTO_dynlock_value we were
+ * passed has no future use for it...
+ */
+ apr_pool_destroy(l->pool);
+}
+
+#if OPENSSL_VERSION_NUMBER >= 0x10000000L
+
+static void ssl_util_thr_id(CRYPTO_THREADID *id)
+{
+ /* OpenSSL needs this to return an unsigned long. On OS/390, the pthread
+ * id is a structure twice that big. Use the TCB pointer instead as a
+ * unique unsigned long.
+ */
+#ifdef __MVS__
+ struct PSA {
+ char unmapped[540]; /* PSATOLD is at offset 540 in the PSA */
+ unsigned long PSATOLD;
+ } *psaptr = 0; /* PSA is at address 0 */
+
+ CRYPTO_THREADID_set_numeric(id, psaptr->PSATOLD);
+#else
+ CRYPTO_THREADID_set_numeric(id, (unsigned long) apr_os_thread_current());
+#endif
+}
+
+static apr_status_t ssl_util_thr_id_cleanup(void *old)
+{
+ CRYPTO_THREADID_set_callback(old);
+ return APR_SUCCESS;
+}
+
+#else
+
+static unsigned long ssl_util_thr_id(void)
+{
+ /* OpenSSL needs this to return an unsigned long. On OS/390, the pthread
+ * id is a structure twice that big. Use the TCB pointer instead as a
+ * unique unsigned long.
+ */
+#ifdef __MVS__
+ struct PSA {
+ char unmapped[540];
+ unsigned long PSATOLD;
+ } *psaptr = 0;
+
+ return psaptr->PSATOLD;
+#else
+ return (unsigned long) apr_os_thread_current();
+#endif
+}
+
+static apr_status_t ssl_util_thr_id_cleanup(void *old)
+{
+ CRYPTO_set_id_callback(old);
+ return APR_SUCCESS;
+}
+
+#endif
+
+static apr_status_t ssl_util_thread_cleanup(void *data)
+{
+ CRYPTO_set_locking_callback(NULL);
+
+ CRYPTO_set_dynlock_create_callback(NULL);
+ CRYPTO_set_dynlock_lock_callback(NULL);
+ CRYPTO_set_dynlock_destroy_callback(NULL);
+
+ dynlockpool = NULL;
+
+ /* Let the registered mutex cleanups do their own thing
+ */
+ return APR_SUCCESS;
+}
+
+void ssl_util_thread_setup(apr_pool_t *p)
+{
+ int i;
+
+ lock_num_locks = CRYPTO_num_locks();
+ lock_cs = apr_palloc(p, lock_num_locks * sizeof(*lock_cs));
+
+ for (i = 0; i < lock_num_locks; i++) {
+ apr_thread_mutex_create(&(lock_cs[i]), APR_THREAD_MUTEX_DEFAULT, p);
+ }
+
+ CRYPTO_set_locking_callback(ssl_util_thr_lock);
+
+ /* Set up dynamic locking scaffolding for OpenSSL to use at its
+ * convenience.
+ */
+ dynlockpool = p;
+ CRYPTO_set_dynlock_create_callback(ssl_dyn_create_function);
+ CRYPTO_set_dynlock_lock_callback(ssl_dyn_lock_function);
+ CRYPTO_set_dynlock_destroy_callback(ssl_dyn_destroy_function);
+
+ apr_pool_cleanup_register(p, NULL, ssl_util_thread_cleanup,
+ apr_pool_cleanup_null);
+}
+
+void ssl_util_thread_id_setup(apr_pool_t *p)
+{
+#if OPENSSL_VERSION_NUMBER >= 0x10000000L
+ CRYPTO_THREADID_set_callback(ssl_util_thr_id);
+#else
+ CRYPTO_set_id_callback(ssl_util_thr_id);
+#endif
+ apr_pool_cleanup_register(p, NULL, ssl_util_thr_id_cleanup,
+ apr_pool_cleanup_null);
+}
+
+#endif /* #if APR_HAS_THREADS && MODSSL_USE_OPENSSL_PRE_1_1_API */
+
+int modssl_is_engine_id(const char *name)
+{
+#if defined(HAVE_OPENSSL_ENGINE_H) && defined(HAVE_ENGINE_INIT)
+ /* ### Can handle any other special ENGINE key names here? */
+ return strncmp(name, "pkcs11:", 7) == 0;
+#else
+ return 0;
+#endif
+}
diff --git a/modules/ssl/ssl_util_ocsp.c b/modules/ssl/ssl_util_ocsp.c
new file mode 100644
index 0000000..b9c8a0b
--- /dev/null
+++ b/modules/ssl/ssl_util_ocsp.c
@@ -0,0 +1,422 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* This file implements an OCSP client including a toy HTTP/1.0
+ * client. Once httpd depends on a real HTTP client library, most of
+ * this can be thrown away. */
+
+#include "ssl_private.h"
+
+#ifndef OPENSSL_NO_OCSP
+
+#include "apr_buckets.h"
+#include "apr_uri.h"
+
+/* Serialize an OCSP request which will be sent to the responder at
+ * given URI to a memory BIO object, which is returned. */
+static BIO *serialize_request(OCSP_REQUEST *req, const apr_uri_t *uri,
+ const apr_uri_t *proxy_uri)
+{
+ BIO *bio;
+ int len;
+
+ len = i2d_OCSP_REQUEST(req, NULL);
+
+ bio = BIO_new(BIO_s_mem());
+
+ BIO_printf(bio, "POST ");
+ /* Use full URL instead of URI in case of a request through a proxy */
+ if (proxy_uri) {
+ BIO_printf(bio, "http://%s:%d",
+ uri->hostname, uri->port);
+ }
+ BIO_printf(bio, "%s%s%s HTTP/1.0\r\n"
+ "Host: %s:%d\r\n"
+ "Content-Type: application/ocsp-request\r\n"
+ "Connection: close\r\n"
+ "Content-Length: %d\r\n"
+ "\r\n",
+ uri->path ? uri->path : "/",
+ uri->query ? "?" : "", uri->query ? uri->query : "",
+ uri->hostname, uri->port, len);
+
+ if (i2d_OCSP_REQUEST_bio(bio, req) != 1) {
+ BIO_free(bio);
+ return NULL;
+ }
+
+ return bio;
+}
+
+/* Send the OCSP request serialized into BIO 'request' to the
+ * responder at given server given by URI. Returns socket object or
+ * NULL on error. */
+static apr_socket_t *send_request(BIO *request, const apr_uri_t *uri,
+ apr_interval_time_t timeout,
+ conn_rec *c, apr_pool_t *p,
+ const apr_uri_t *proxy_uri)
+{
+ apr_status_t rv;
+ apr_sockaddr_t *sa;
+ apr_socket_t *sd;
+ char buf[HUGE_STRING_LEN];
+ int len;
+ const apr_uri_t *next_hop_uri;
+
+ if (proxy_uri) {
+ next_hop_uri = proxy_uri;
+ }
+ else {
+ next_hop_uri = uri;
+ }
+
+ rv = apr_sockaddr_info_get(&sa, next_hop_uri->hostname, APR_UNSPEC,
+ next_hop_uri->port, 0, p);
+ if (rv) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c, APLOGNO(01972)
+ "could not resolve address of %s %s",
+ proxy_uri ? "proxy" : "OCSP responder",
+ next_hop_uri->hostinfo);
+ return NULL;
+ }
+
+ /* establish a connection to the OCSP responder */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(01973)
+ "connecting to %s '%s'",
+ proxy_uri ? "proxy" : "OCSP responder",
+ uri->hostinfo);
+
+ /* Cycle through address until a connect() succeeds. */
+ for (; sa; sa = sa->next) {
+ rv = apr_socket_create(&sd, sa->family, SOCK_STREAM, APR_PROTO_TCP, p);
+ if (rv == APR_SUCCESS) {
+ apr_socket_timeout_set(sd, timeout);
+
+ rv = apr_socket_connect(sd, sa);
+ if (rv == APR_SUCCESS) {
+ break;
+ }
+ apr_socket_close(sd);
+ }
+ }
+
+ if (sa == NULL) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c, APLOGNO(01974)
+ "could not connect to %s '%s'",
+ proxy_uri ? "proxy" : "OCSP responder",
+ next_hop_uri->hostinfo);
+ return NULL;
+ }
+
+ /* send the request and get a response */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(01975)
+ "sending request to OCSP responder");
+
+ while ((len = BIO_read(request, buf, sizeof buf)) > 0) {
+ char *wbuf = buf;
+ apr_size_t remain = len;
+
+ do {
+ apr_size_t wlen = remain;
+
+ rv = apr_socket_send(sd, wbuf, &wlen);
+ wbuf += remain;
+ remain -= wlen;
+ } while (rv == APR_SUCCESS && remain > 0);
+
+ if (rv) {
+ apr_socket_close(sd);
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c, APLOGNO(01976)
+ "failed to send request to OCSP responder '%s'",
+ uri->hostinfo);
+ return NULL;
+ }
+ }
+
+ return sd;
+}
+
+/* Return a pool-allocated NUL-terminated line, with CRLF stripped,
+ * read from brigade 'bbin' using 'bbout' as temporary storage. */
+static char *get_line(apr_bucket_brigade *bbout, apr_bucket_brigade *bbin,
+ conn_rec *c, apr_pool_t *p)
+{
+ apr_status_t rv;
+ apr_size_t len;
+ char *line;
+
+ apr_brigade_cleanup(bbout);
+
+ rv = apr_brigade_split_line(bbout, bbin, APR_BLOCK_READ, 8192);
+ if (rv) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c, APLOGNO(01977)
+ "failed reading line from OCSP server");
+ return NULL;
+ }
+
+ rv = apr_brigade_pflatten(bbout, &line, &len, p);
+ if (rv) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c, APLOGNO(01978)
+ "failed reading line from OCSP server");
+ return NULL;
+ }
+
+ if (len == 0) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c, APLOGNO(02321)
+ "empty response from OCSP server");
+ return NULL;
+ }
+
+ if (line[len-1] != APR_ASCII_LF) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c, APLOGNO(01979)
+ "response header line too long from OCSP server");
+ return NULL;
+ }
+
+ line[len-1] = '\0';
+ if (len > 1 && line[len-2] == APR_ASCII_CR) {
+ line[len-2] = '\0';
+ }
+
+ return line;
+}
+
+/* Maximum values to prevent eating RAM forever. */
+#define MAX_HEADERS (256)
+#define MAX_CONTENT (2048 * 1024)
+
+/* Read the OCSP response from the socket 'sd', using temporary memory
+ * BIO 'bio', and return the decoded OCSP response object, or NULL on
+ * error. */
+static OCSP_RESPONSE *read_response(apr_socket_t *sd, BIO *bio, conn_rec *c,
+ apr_pool_t *p)
+{
+ apr_bucket_brigade *bb, *tmpbb;
+ OCSP_RESPONSE *response;
+ char *line;
+ apr_size_t count;
+ apr_int64_t code;
+
+ /* Using brigades for response parsing is much simpler than using
+ * apr_socket_* directly. */
+ bb = apr_brigade_create(p, c->bucket_alloc);
+ tmpbb = apr_brigade_create(p, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_socket_create(sd, c->bucket_alloc));
+
+ line = get_line(tmpbb, bb, c, p);
+ if (!line || strncmp(line, "HTTP/", 5)
+ || (line = ap_strchr(line, ' ')) == NULL
+ || (code = apr_atoi64(++line)) < 200 || code > 299) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(01980)
+ "bad response from OCSP server: %s",
+ line ? line : "(none)");
+ return NULL;
+ }
+
+ /* Read till end of headers; don't have to even bother parsing the
+ * Content-Length since the server is obliged to close the
+ * connection after the response anyway for HTTP/1.0. */
+ count = 0;
+ while ((line = get_line(tmpbb, bb, c, p)) != NULL && line[0]
+ && ++count < MAX_HEADERS) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(01981)
+ "OCSP response header: %s", line);
+ }
+
+ if (count == MAX_HEADERS) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(01982)
+ "could not read response headers from OCSP server, "
+ "exceeded maximum count (%u)", MAX_HEADERS);
+ return NULL;
+ }
+ else if (!line) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(01983)
+ "could not read response header from OCSP server");
+ return NULL;
+ }
+
+ /* Read the response body into the memory BIO. */
+ count = 0;
+ while (!APR_BRIGADE_EMPTY(bb)) {
+ const char *data;
+ apr_size_t len;
+ apr_status_t rv;
+ apr_bucket *e = APR_BRIGADE_FIRST(bb);
+
+ rv = apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
+ if (rv == APR_EOF) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(01984)
+ "OCSP response: got EOF");
+ break;
+ }
+ if (rv != APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c, APLOGNO(01985)
+ "error reading response from OCSP server");
+ return NULL;
+ }
+ if (len == 0) {
+ /* Ignore zero-length buckets (possible side-effect of
+ * line splitting). */
+ apr_bucket_delete(e);
+ continue;
+ }
+ count += len;
+ if (count > MAX_CONTENT) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c, APLOGNO(01986)
+ "OCSP response size exceeds %u byte limit",
+ MAX_CONTENT);
+ return NULL;
+ }
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(01987)
+ "OCSP response: got %" APR_SIZE_T_FMT
+ " bytes, %" APR_SIZE_T_FMT " total", len, count);
+
+ BIO_write(bio, data, (int)len);
+ apr_bucket_delete(e);
+ }
+
+ apr_brigade_destroy(bb);
+ apr_brigade_destroy(tmpbb);
+
+ /* Finally decode the OCSP response from what's stored in the
+ * bio. */
+ response = d2i_OCSP_RESPONSE_bio(bio, NULL);
+ if (response == NULL) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(01988)
+ "failed to decode OCSP response data");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, mySrvFromConn(c));
+ }
+
+ return response;
+}
+
+OCSP_RESPONSE *modssl_dispatch_ocsp_request(const apr_uri_t *uri,
+ apr_interval_time_t timeout,
+ OCSP_REQUEST *request,
+ conn_rec *c, apr_pool_t *p)
+{
+ OCSP_RESPONSE *response = NULL;
+ apr_socket_t *sd;
+ BIO *bio;
+ const apr_uri_t *proxy_uri;
+
+ proxy_uri = (mySrvConfigFromConn(c))->server->proxy_uri;
+ bio = serialize_request(request, uri, proxy_uri);
+ if (bio == NULL) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(01989)
+ "could not serialize OCSP request");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, mySrvFromConn(c));
+ return NULL;
+ }
+
+ sd = send_request(bio, uri, timeout, c, p, proxy_uri);
+ if (sd == NULL) {
+ /* Errors already logged. */
+ BIO_free(bio);
+ return NULL;
+ }
+
+ /* Clear the BIO contents, ready for the response. */
+ (void)BIO_reset(bio);
+
+ response = read_response(sd, bio, c, p);
+
+ apr_socket_close(sd);
+ BIO_free(bio);
+
+ return response;
+}
+
+/* _________________________________________________________________
+**
+** OCSP other certificate support
+** _________________________________________________________________
+*/
+
+/*
+ * Read a file that contains certificates in PEM format and
+ * return as a STACK.
+ */
+
+static STACK_OF(X509) *modssl_read_ocsp_certificates(const char *file)
+{
+ BIO *bio;
+ X509 *x509;
+ unsigned long err;
+ STACK_OF(X509) *other_certs = NULL;
+
+ if ((bio = BIO_new(BIO_s_file())) == NULL)
+ return NULL;
+ if (BIO_read_filename(bio, file) <= 0) {
+ BIO_free(bio);
+ return NULL;
+ }
+
+ /* create new extra chain by loading the certs */
+ ERR_clear_error();
+ while ((x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL)) != NULL) {
+ if (!other_certs) {
+ other_certs = sk_X509_new_null();
+ if (!other_certs)
+ return NULL;
+ }
+
+ if (!sk_X509_push(other_certs, x509)) {
+ X509_free(x509);
+ sk_X509_pop_free(other_certs, X509_free);
+ BIO_free(bio);
+ return NULL;
+ }
+ }
+ /* Make sure that only the error is just an EOF */
+ if ((err = ERR_peek_error()) > 0) {
+ if (!( ERR_GET_LIB(err) == ERR_LIB_PEM
+ && ERR_GET_REASON(err) == PEM_R_NO_START_LINE)) {
+ BIO_free(bio);
+ sk_X509_pop_free(other_certs, X509_free);
+ return NULL;
+ }
+ while (ERR_get_error() > 0) ;
+ }
+ BIO_free(bio);
+ return other_certs;
+}
+
+void ssl_init_ocsp_certificates(server_rec *s, modssl_ctx_t *mctx)
+{
+ /*
+ * Configure Trusted OCSP certificates.
+ */
+
+ if (!mctx->ocsp_certs_file) {
+ return;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+ "Configuring Trusted OCSP certificates");
+
+ mctx->ocsp_certs = modssl_read_ocsp_certificates(mctx->ocsp_certs_file);
+
+ if (!mctx->ocsp_certs) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
+ "Unable to configure OCSP Trusted Certificates");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, s);
+ ssl_die(s);
+ }
+ mctx->ocsp_verify_flags |= OCSP_TRUSTOTHER;
+}
+
+#endif /* HAVE_OCSP */
diff --git a/modules/ssl/ssl_util_ssl.c b/modules/ssl/ssl_util_ssl.c
new file mode 100644
index 0000000..38079a9
--- /dev/null
+++ b/modules/ssl/ssl_util_ssl.c
@@ -0,0 +1,591 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* _ _
+ * _ __ ___ ___ __| | ___ ___| | mod_ssl
+ * | '_ ` _ \ / _ \ / _` | / __/ __| | Apache Interface to OpenSSL
+ * | | | | | | (_) | (_| | \__ \__ \ |
+ * |_| |_| |_|\___/ \__,_|___|___/___/_|
+ * |_____|
+ * ssl_util_ssl.c
+ * Additional Utility Functions for OpenSSL
+ */
+
+#include "ssl_private.h"
+
+/* _________________________________________________________________
+**
+** Additional High-Level Functions for OpenSSL
+** _________________________________________________________________
+*/
+
+/* we initialize this index at startup time
+ * and never write to it at request time,
+ * so this static is thread safe.
+ * also note that OpenSSL increments at static variable when
+ * SSL_get_ex_new_index() is called, so we _must_ do this at startup.
+ */
+static int app_data2_idx = -1;
+
+void modssl_init_app_data2_idx(void)
+{
+ int i;
+
+ if (app_data2_idx > -1) {
+ return;
+ }
+
+ /* we _do_ need to call this twice */
+ for (i = 0; i <= 1; i++) {
+ app_data2_idx =
+ SSL_get_ex_new_index(0,
+ "Second Application Data for SSL",
+ NULL, NULL, NULL);
+ }
+}
+
+void *modssl_get_app_data2(SSL *ssl)
+{
+ return (void *)SSL_get_ex_data(ssl, app_data2_idx);
+}
+
+void modssl_set_app_data2(SSL *ssl, void *arg)
+{
+ SSL_set_ex_data(ssl, app_data2_idx, (char *)arg);
+ return;
+}
+
+/* _________________________________________________________________
+**
+** High-Level Private Key Loading
+** _________________________________________________________________
+*/
+
+EVP_PKEY *modssl_read_privatekey(const char *filename, pem_password_cb *cb, void *s)
+{
+ EVP_PKEY *rc;
+ BIO *bioS;
+ BIO *bioF;
+
+ /* 1. try PEM (= DER+Base64+headers) */
+ if ((bioS=BIO_new_file(filename, "r")) == NULL)
+ return NULL;
+ rc = PEM_read_bio_PrivateKey(bioS, NULL, cb, s);
+ BIO_free(bioS);
+
+ if (rc == NULL) {
+ /* 2. try DER+Base64 */
+ if ((bioS = BIO_new_file(filename, "r")) == NULL)
+ return NULL;
+
+ if ((bioF = BIO_new(BIO_f_base64())) == NULL) {
+ BIO_free(bioS);
+ return NULL;
+ }
+ bioS = BIO_push(bioF, bioS);
+ rc = d2i_PrivateKey_bio(bioS, NULL);
+ BIO_free_all(bioS);
+
+ if (rc == NULL) {
+ /* 3. try plain DER */
+ if ((bioS = BIO_new_file(filename, "r")) == NULL)
+ return NULL;
+ rc = d2i_PrivateKey_bio(bioS, NULL);
+ BIO_free(bioS);
+ }
+ }
+ return rc;
+}
+
+/* _________________________________________________________________
+**
+** Smart shutdown
+** _________________________________________________________________
+*/
+
+int modssl_smart_shutdown(SSL *ssl)
+{
+ int i;
+ int rc;
+ int flush;
+
+ /*
+ * Repeat the calls, because SSL_shutdown internally dispatches through a
+ * little state machine. Usually only one or two iterations should be
+ * needed, so we restrict the total number of restrictions in order to
+ * avoid process hangs in case the client played bad with the socket
+ * connection and OpenSSL cannot recognize it.
+ */
+ rc = 0;
+ flush = !(SSL_get_shutdown(ssl) & SSL_SENT_SHUTDOWN);
+ for (i = 0; i < 4 /* max 2x pending + 2x data = 4 */; i++) {
+ rc = SSL_shutdown(ssl);
+ if (rc >= 0 && flush && (SSL_get_shutdown(ssl) & SSL_SENT_SHUTDOWN)) {
+ /* Once the close notify is sent through the output filters,
+ * ensure it is flushed through the socket.
+ */
+ if (BIO_flush(SSL_get_wbio(ssl)) <= 0) {
+ rc = -1;
+ break;
+ }
+ flush = 0;
+ }
+ if (rc != 0)
+ break;
+ }
+ return rc;
+}
+
+/* _________________________________________________________________
+**
+** Certificate Checks
+** _________________________________________________________________
+*/
+
+/* retrieve basic constraints ingredients */
+BOOL modssl_X509_getBC(X509 *cert, int *ca, int *pathlen)
+{
+ BASIC_CONSTRAINTS *bc;
+ BIGNUM *bn = NULL;
+ char *cp;
+
+ bc = X509_get_ext_d2i(cert, NID_basic_constraints, NULL, NULL);
+ if (bc == NULL)
+ return FALSE;
+ *ca = bc->ca;
+ *pathlen = -1 /* unlimited */;
+ if (bc->pathlen != NULL) {
+ if ((bn = ASN1_INTEGER_to_BN(bc->pathlen, NULL)) == NULL) {
+ BASIC_CONSTRAINTS_free(bc);
+ return FALSE;
+ }
+ if ((cp = BN_bn2dec(bn)) == NULL) {
+ BN_free(bn);
+ BASIC_CONSTRAINTS_free(bc);
+ return FALSE;
+ }
+ *pathlen = atoi(cp);
+ OPENSSL_free(cp);
+ BN_free(bn);
+ }
+ BASIC_CONSTRAINTS_free(bc);
+ return TRUE;
+}
+
+char *modssl_bio_free_read(apr_pool_t *p, BIO *bio)
+{
+ int len = BIO_pending(bio);
+ char *result = NULL;
+
+ if (len > 0) {
+ result = apr_palloc(p, len+1);
+ len = BIO_read(bio, result, len);
+ result[len] = NUL;
+ }
+ BIO_free(bio);
+ return result;
+}
+
+/* Convert ASN.1 string to a pool-allocated char * string, escaping
+ * control characters. If raw is zero, convert to UTF-8, otherwise
+ * unchanged from the character set. */
+static char *asn1_string_convert(apr_pool_t *p, ASN1_STRING *asn1str, int raw)
+{
+ BIO *bio;
+ int flags = ASN1_STRFLGS_ESC_CTRL;
+
+ if ((bio = BIO_new(BIO_s_mem())) == NULL)
+ return NULL;
+
+ if (!raw) flags |= ASN1_STRFLGS_UTF8_CONVERT;
+
+ ASN1_STRING_print_ex(bio, asn1str, flags);
+
+ return modssl_bio_free_read(p, bio);
+}
+
+#define asn1_string_to_utf8(p, a) asn1_string_convert(p, a, 0)
+
+/* convert a NAME_ENTRY to UTF8 string */
+char *modssl_X509_NAME_ENTRY_to_string(apr_pool_t *p, X509_NAME_ENTRY *xsne,
+ int raw)
+{
+ char *result = asn1_string_convert(p, X509_NAME_ENTRY_get_data(xsne), raw);
+ ap_xlate_proto_from_ascii(result, len);
+ return result;
+}
+
+/*
+ * convert an X509_NAME to an RFC 2253 formatted string, optionally truncated
+ * to maxlen characters (specify a maxlen of 0 for no length limit)
+ */
+char *modssl_X509_NAME_to_string(apr_pool_t *p, X509_NAME *dn, int maxlen)
+{
+ char *result = NULL;
+ BIO *bio;
+ int len;
+
+ if ((bio = BIO_new(BIO_s_mem())) == NULL)
+ return NULL;
+ X509_NAME_print_ex(bio, dn, 0, XN_FLAG_RFC2253);
+ len = BIO_pending(bio);
+ if (len > 0) {
+ result = apr_palloc(p, (maxlen > 0) ? maxlen+1 : len+1);
+ if (maxlen > 0 && maxlen < len) {
+ len = BIO_read(bio, result, maxlen);
+ if (maxlen > 2) {
+ /* insert trailing ellipsis if there's enough space */
+ apr_snprintf(result + maxlen - 3, 4, "...");
+ }
+ } else {
+ len = BIO_read(bio, result, len);
+ }
+ result[len] = NUL;
+ }
+ BIO_free(bio);
+
+ return result;
+}
+
+static void parse_otherName_value(apr_pool_t *p, ASN1_TYPE *value,
+ const char *onf, apr_array_header_t **entries)
+{
+ const char *str;
+ int nid = onf ? OBJ_txt2nid(onf) : NID_undef;
+
+ if (!value || (nid == NID_undef) || !*entries)
+ return;
+
+ /*
+ * Currently supported otherName forms (values for "onf"):
+ * "msUPN" (1.3.6.1.4.1.311.20.2.3): Microsoft User Principal Name
+ * "id-on-dnsSRV" (1.3.6.1.5.5.7.8.7): SRVName, as specified in RFC 4985
+ */
+ if ((nid == NID_ms_upn) && (value->type == V_ASN1_UTF8STRING) &&
+ (str = asn1_string_to_utf8(p, value->value.utf8string))) {
+ APR_ARRAY_PUSH(*entries, const char *) = str;
+ } else if (strEQ(onf, "id-on-dnsSRV") &&
+ (value->type == V_ASN1_IA5STRING) &&
+ (str = asn1_string_to_utf8(p, value->value.ia5string))) {
+ APR_ARRAY_PUSH(*entries, const char *) = str;
+ }
+}
+
+/*
+ * Return an array of subjectAltName entries of type "type". If idx is -1,
+ * return all entries of the given type, otherwise return an array consisting
+ * of the n-th occurrence of that type only. Currently supported types:
+ * GEN_EMAIL (rfc822Name)
+ * GEN_DNS (dNSName)
+ * GEN_OTHERNAME (requires the otherName form ["onf"] argument to be supplied,
+ * see parse_otherName_value for the currently supported forms)
+ */
+BOOL modssl_X509_getSAN(apr_pool_t *p, X509 *x509, int type, const char *onf,
+ int idx, apr_array_header_t **entries)
+{
+ STACK_OF(GENERAL_NAME) *names;
+ int nid = onf ? OBJ_txt2nid(onf) : NID_undef;
+
+ if (!x509 || (type < GEN_OTHERNAME) ||
+ ((type == GEN_OTHERNAME) && (nid == NID_undef)) ||
+ (type > GEN_RID) || (idx < -1) ||
+ !(*entries = apr_array_make(p, 0, sizeof(char *)))) {
+ *entries = NULL;
+ return FALSE;
+ }
+
+ if ((names = X509_get_ext_d2i(x509, NID_subject_alt_name, NULL, NULL))) {
+ int i, n = 0;
+ GENERAL_NAME *name;
+ const char *utf8str;
+
+ for (i = 0; i < sk_GENERAL_NAME_num(names); i++) {
+ name = sk_GENERAL_NAME_value(names, i);
+
+ if (name->type != type)
+ continue;
+
+ switch (type) {
+ case GEN_EMAIL:
+ case GEN_DNS:
+ if (((idx == -1) || (n == idx)) &&
+ (utf8str = asn1_string_to_utf8(p, name->d.ia5))) {
+ APR_ARRAY_PUSH(*entries, const char *) = utf8str;
+ }
+ n++;
+ break;
+ case GEN_OTHERNAME:
+ if (OBJ_obj2nid(name->d.otherName->type_id) == nid) {
+ if (((idx == -1) || (n == idx))) {
+ parse_otherName_value(p, name->d.otherName->value,
+ onf, entries);
+ }
+ n++;
+ }
+ break;
+ default:
+ /*
+ * Not implemented right now:
+ * GEN_X400 (x400Address)
+ * GEN_DIRNAME (directoryName)
+ * GEN_EDIPARTY (ediPartyName)
+ * GEN_URI (uniformResourceIdentifier)
+ * GEN_IPADD (iPAddress)
+ * GEN_RID (registeredID)
+ */
+ break;
+ }
+
+ if ((idx != -1) && (n > idx))
+ break;
+ }
+
+ sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
+ }
+
+ return apr_is_empty_array(*entries) ? FALSE : TRUE;
+}
+
+/* return an array of (RFC 6125 coined) DNS-IDs and CN-IDs in a certificate */
+static BOOL getIDs(apr_pool_t *p, X509 *x509, apr_array_header_t **ids)
+{
+ X509_NAME *subj;
+ int i = -1;
+
+ /* First, the DNS-IDs (dNSName entries in the subjectAltName extension) */
+ if (!x509 ||
+ (modssl_X509_getSAN(p, x509, GEN_DNS, NULL, -1, ids) == FALSE && !*ids)) {
+ *ids = NULL;
+ return FALSE;
+ }
+
+ /* Second, the CN-IDs (commonName attributes in the subject DN) */
+ subj = X509_get_subject_name(x509);
+ while ((i = X509_NAME_get_index_by_NID(subj, NID_commonName, i)) != -1) {
+ APR_ARRAY_PUSH(*ids, const char *) =
+ modssl_X509_NAME_ENTRY_to_string(p, X509_NAME_get_entry(subj, i), 0);
+ }
+
+ return apr_is_empty_array(*ids) ? FALSE : TRUE;
+}
+
+/*
+ * Check if a certificate matches for a particular name, by iterating over its
+ * DNS-IDs and CN-IDs (RFC 6125), optionally with basic wildcard matching.
+ * If server_rec is non-NULL, some (debug/trace) logging is enabled.
+ */
+BOOL modssl_X509_match_name(apr_pool_t *p, X509 *x509, const char *name,
+ BOOL allow_wildcard, server_rec *s)
+{
+ BOOL matched = FALSE;
+ apr_array_header_t *ids;
+
+ /*
+ * At some day in the future, this might be replaced with X509_check_host()
+ * (available in OpenSSL 1.0.2 and later), but two points should be noted:
+ * 1) wildcard matching in X509_check_host() might yield different
+ * results (by default, it supports a broader set of patterns, e.g.
+ * wildcards in non-initial positions);
+ * 2) we lose the option of logging each DNS- and CN-ID (until a match
+ * is found).
+ */
+
+ if (getIDs(p, x509, &ids)) {
+ const char *cp;
+ int i;
+ char **id = (char **)ids->elts;
+ BOOL is_wildcard;
+
+ for (i = 0; i < ids->nelts; i++) {
+ if (!id[i])
+ continue;
+
+ /*
+ * Determine if it is a wildcard ID - we're restrictive
+ * in the sense that we require the wildcard character to be
+ * THE left-most label (i.e., the ID must start with "*.")
+ */
+ is_wildcard = (*id[i] == '*' && *(id[i]+1) == '.') ? TRUE : FALSE;
+
+ /*
+ * If the ID includes a wildcard character (and the caller is
+ * allowing wildcards), check if it matches for the left-most
+ * DNS label - i.e., the wildcard character is not allowed
+ * to match a dot. Otherwise, try a simple string compare.
+ */
+ if ((allow_wildcard == TRUE && is_wildcard == TRUE &&
+ (cp = ap_strchr_c(name, '.')) && !strcasecmp(id[i]+1, cp)) ||
+ !strcasecmp(id[i], name)) {
+ matched = TRUE;
+ }
+
+ if (s) {
+ ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s,
+ "[%s] modssl_X509_match_name: expecting name '%s', "
+ "%smatched by ID '%s'",
+ (mySrvConfig(s))->vhost_id, name,
+ matched == TRUE ? "" : "NOT ", id[i]);
+ }
+
+ if (matched == TRUE) {
+ break;
+ }
+ }
+
+ }
+
+ if (s) {
+ ssl_log_xerror(SSLLOG_MARK, APLOG_DEBUG, 0, p, s, x509,
+ APLOGNO(02412) "[%s] Cert %s for name '%s'",
+ (mySrvConfig(s))->vhost_id,
+ matched == TRUE ? "matches" : "does not match",
+ name);
+ }
+
+ return matched;
+}
+
+/* _________________________________________________________________
+**
+** Custom (EC)DH parameter support
+** _________________________________________________________________
+*/
+
+DH *ssl_dh_GetParamFromFile(const char *file)
+{
+ DH *dh = NULL;
+ BIO *bio;
+
+ if ((bio = BIO_new_file(file, "r")) == NULL)
+ return NULL;
+ dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
+ BIO_free(bio);
+ return (dh);
+}
+
+#ifdef HAVE_ECC
+EC_GROUP *ssl_ec_GetParamFromFile(const char *file)
+{
+ EC_GROUP *group = NULL;
+ BIO *bio;
+
+ if ((bio = BIO_new_file(file, "r")) == NULL)
+ return NULL;
+ group = PEM_read_bio_ECPKParameters(bio, NULL, NULL, NULL);
+ BIO_free(bio);
+ return (group);
+}
+#endif
+
+/* _________________________________________________________________
+**
+** Session Stuff
+** _________________________________________________________________
+*/
+
+char *modssl_SSL_SESSION_id2sz(IDCONST unsigned char *id, int idlen,
+ char *str, int strsize)
+{
+ if (idlen > SSL_MAX_SSL_SESSION_ID_LENGTH)
+ idlen = SSL_MAX_SSL_SESSION_ID_LENGTH;
+
+ /* We must ensure not to process more than what would fit in the
+ * destination buffer, including terminating NULL */
+ if (idlen > (strsize-1) / 2)
+ idlen = (strsize-1) / 2;
+
+ ap_bin2hex(id, idlen, str);
+
+ return str;
+}
+
+/* _________________________________________________________________
+**
+** Certificate/Key Stuff
+** _________________________________________________________________
+*/
+
+apr_status_t modssl_read_cert(apr_pool_t *p,
+ const char *cert_pem, const char *key_pem,
+ pem_password_cb *cb, void *ud,
+ X509 **pcert, EVP_PKEY **pkey)
+{
+ BIO *in;
+ X509 *x = NULL;
+ EVP_PKEY *key = NULL;
+ apr_status_t rv = APR_SUCCESS;
+
+ in = BIO_new_mem_buf(cert_pem, -1);
+ if (in == NULL) {
+ rv = APR_ENOMEM;
+ goto cleanup;
+ }
+
+ x = PEM_read_bio_X509(in, NULL, cb, ud);
+ if (x == NULL) {
+ rv = APR_ENOENT;
+ goto cleanup;
+ }
+
+ BIO_free(in);
+ in = BIO_new_mem_buf(key_pem? key_pem : cert_pem, -1);
+ if (in == NULL) {
+ rv = APR_ENOMEM;
+ goto cleanup;
+ }
+ key = PEM_read_bio_PrivateKey(in, NULL, cb, ud);
+ if (key == NULL) {
+ rv = APR_ENOENT;
+ goto cleanup;
+ }
+
+cleanup:
+ if (rv == APR_SUCCESS) {
+ *pcert = x;
+ *pkey = key;
+ }
+ else {
+ *pcert = NULL;
+ *pkey = NULL;
+ if (x) X509_free(x);
+ if (key) EVP_PKEY_free(key);
+ }
+ if (in != NULL) BIO_free(in);
+ return rv;
+}
+
+apr_status_t modssl_cert_get_pem(apr_pool_t *p,
+ X509 *cert1, X509 *cert2,
+ const char **ppem)
+{
+ apr_status_t rv = APR_ENOMEM;
+ BIO *bio;
+
+ if ((bio = BIO_new(BIO_s_mem())) == NULL) goto cleanup;
+ if (PEM_write_bio_X509(bio, cert1) != 1) goto cleanup;
+ if (cert2 && PEM_write_bio_X509(bio, cert2) != 1) goto cleanup;
+ rv = APR_SUCCESS;
+
+cleanup:
+ if (rv != APR_SUCCESS) {
+ *ppem = NULL;
+ if (bio) BIO_free(bio);
+ }
+ else {
+ *ppem = modssl_bio_free_read(p, bio);
+ }
+ return rv;
+}
diff --git a/modules/ssl/ssl_util_ssl.h b/modules/ssl/ssl_util_ssl.h
new file mode 100644
index 0000000..443c1b7
--- /dev/null
+++ b/modules/ssl/ssl_util_ssl.h
@@ -0,0 +1,107 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @verbatim
+ _ _
+ _ __ ___ ___ __| | ___ ___| | mod_ssl
+ | '_ ` _ \ / _ \ / _` | / __/ __| | Apache Interface to OpenSSL
+ | | | | | | (_) | (_| | \__ \__ \ |
+ |_| |_| |_|\___/ \__,_|___|___/___/_|
+ |_____|
+ @endverbatim
+ * @file ssl_util_ssl.h
+ * @brief Additional Utility Functions for OpenSSL
+ *
+ * @defgroup MOD_SSL_UTIL Utilities
+ * @ingroup MOD_SSL
+ * @{
+ */
+
+#ifndef __SSL_UTIL_SSL_H__
+#define __SSL_UTIL_SSL_H__
+
+/**
+ * SSL library version number
+ */
+
+#define MODSSL_LIBRARY_VERSION OPENSSL_VERSION_NUMBER
+#define MODSSL_LIBRARY_NAME "OpenSSL"
+#define MODSSL_LIBRARY_TEXT OPENSSL_VERSION_TEXT
+#if MODSSL_USE_OPENSSL_PRE_1_1_API
+#define MODSSL_LIBRARY_DYNTEXT SSLeay_version(SSLEAY_VERSION)
+#else
+#define MODSSL_LIBRARY_DYNTEXT OpenSSL_version(OPENSSL_VERSION)
+#endif
+
+/**
+ * Maximum length of a DER encoded session.
+ * FIXME: There is no define in OpenSSL, but OpenSSL uses 1024*10,
+ * so this value should be ok. Although we have no warm feeling.
+ */
+#define MODSSL_SESSION_MAX_DER 1024*10
+
+/** max length for modssl_SSL_SESSION_id2sz */
+#define MODSSL_SESSION_ID_STRING_LEN \
+ ((SSL_MAX_SSL_SESSION_ID_LENGTH + 1) * 2)
+
+/**
+ * Additional Functions
+ */
+void modssl_init_app_data2_idx(void);
+void *modssl_get_app_data2(SSL *);
+void modssl_set_app_data2(SSL *, void *);
+
+/* Read private key from filename in either PEM or raw base64(DER)
+ * format, using password entry callback cb and userdata. */
+EVP_PKEY *modssl_read_privatekey(const char *filename, pem_password_cb *cb, void *ud);
+
+int modssl_smart_shutdown(SSL *ssl);
+BOOL modssl_X509_getBC(X509 *, int *, int *);
+char *modssl_X509_NAME_ENTRY_to_string(apr_pool_t *p, X509_NAME_ENTRY *xsne,
+ int raw);
+char *modssl_X509_NAME_to_string(apr_pool_t *, X509_NAME *, int);
+BOOL modssl_X509_getSAN(apr_pool_t *, X509 *, int, const char *, int, apr_array_header_t **);
+BOOL modssl_X509_match_name(apr_pool_t *, X509 *, const char *, BOOL, server_rec *);
+char *modssl_SSL_SESSION_id2sz(IDCONST unsigned char *, int, char *, int);
+
+/* Reads the remaining data in BIO, if not empty, and copies it into a
+ * pool-allocated string. If empty, returns NULL. BIO_free(bio) is
+ * called for both cases. */
+char *modssl_bio_free_read(apr_pool_t *p, BIO *bio);
+
+/* Read a single certificate and its private key from the given string in PEM format.
+ * If `key_pem` is NULL, it will expect the key in `cert_pem`.
+ */
+apr_status_t modssl_read_cert(apr_pool_t *p,
+ const char *cert_pem, const char *key_pem,
+ pem_password_cb *cb, void *ud,
+ X509 **pcert, EVP_PKEY **pkey);
+
+/* Convert a certificate (and optionally a second) into a PEM string.
+ * @param p pool for allocations
+ * @param cert1 the certificate to convert
+ * @param cert2 a second cert to add to the PEM afterwards or NULL.
+ * @param ppem the certificate(s) in PEM format, NUL-terminated.
+ * @return APR_SUCCESS if ppem is valid.
+ */
+apr_status_t modssl_cert_get_pem(apr_pool_t *p,
+ X509 *cert1, X509 *cert2,
+ const char **ppem);
+
+#endif /* __SSL_UTIL_SSL_H__ */
+/** @} */
+
diff --git a/modules/ssl/ssl_util_stapling.c b/modules/ssl/ssl_util_stapling.c
new file mode 100644
index 0000000..ab77e4a
--- /dev/null
+++ b/modules/ssl/ssl_util_stapling.c
@@ -0,0 +1,975 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* _ _
+ * _ __ ___ ___ __| | ___ ___| | mod_ssl
+ * | '_ ` _ \ / _ \ / _` | / __/ __| | Apache Interface to OpenSSL
+ * | | | | | | (_) | (_| | \__ \__ \ |
+ * |_| |_| |_|\___/ \__,_|___|___/___/_|
+ * |_____|
+ * ssl_stapling.c
+ * OCSP Stapling Support
+ */
+ /* ``Where's the spoons?
+ Where's the spoons?
+ Where's the bloody spoons?''
+ -- Alexei Sayle */
+
+#include "ssl_private.h"
+#include "ap_mpm.h"
+#include "apr_thread_mutex.h"
+#include "mod_ssl_openssl.h"
+
+APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(ssl, SSL, int, init_stapling_status,
+ (server_rec *s, apr_pool_t *p,
+ X509 *cert, X509 *issuer),
+ (s, p, cert, issuer),
+ DECLINED, DECLINED)
+
+APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(ssl, SSL, int, get_stapling_status,
+ (unsigned char **pder, int *pderlen,
+ conn_rec *c, server_rec *s, X509 *cert),
+ (pder, pderlen, c, s, cert),
+ DECLINED, DECLINED)
+
+
+#ifdef HAVE_OCSP_STAPLING
+
+static int stapling_cache_mutex_on(server_rec *s);
+static int stapling_cache_mutex_off(server_rec *s);
+
+static int stapling_cb(SSL *ssl, void *arg);
+
+/**
+ * Maximum OCSP stapling response size. This should be the response for a
+ * single certificate and will typically include the responder certificate chain
+ * so 10K should be more than enough.
+ *
+ */
+
+#define MAX_STAPLING_DER 10240
+
+/* Cached info stored in the global stapling_certinfo hash. */
+typedef struct {
+ /* Index in session cache (SHA-1 digest of DER encoded certificate) */
+ UCHAR idx[SHA_DIGEST_LENGTH];
+ /* Certificate ID for OCSP request */
+ OCSP_CERTID *cid;
+ /* URI of the OCSP responder */
+ char *uri;
+} certinfo;
+
+static apr_status_t ssl_stapling_certid_free(void *data)
+{
+ OCSP_CERTID *cid = data;
+
+ if (cid) {
+ OCSP_CERTID_free(cid);
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_hash_t *stapling_certinfo;
+
+void ssl_stapling_certinfo_hash_init(apr_pool_t *p)
+{
+ stapling_certinfo = apr_hash_make(p);
+}
+
+static X509 *stapling_get_issuer(modssl_ctx_t *mctx, X509 *x)
+{
+ X509 *issuer = NULL;
+ int i;
+ X509_STORE *st = SSL_CTX_get_cert_store(mctx->ssl_ctx);
+ X509_STORE_CTX *inctx;
+ STACK_OF(X509) *extra_certs = NULL;
+
+#ifdef OPENSSL_NO_SSL_INTERN
+ SSL_CTX_get_extra_chain_certs(mctx->ssl_ctx, &extra_certs);
+#else
+ extra_certs = mctx->ssl_ctx->extra_certs;
+#endif
+
+ for (i = 0; i < sk_X509_num(extra_certs); i++) {
+ issuer = sk_X509_value(extra_certs, i);
+ if (X509_check_issued(issuer, x) == X509_V_OK) {
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ CRYPTO_add(&issuer->references, 1, CRYPTO_LOCK_X509);
+#else
+ X509_up_ref(issuer);
+#endif
+ return issuer;
+ }
+ }
+
+ inctx = X509_STORE_CTX_new();
+ if (!X509_STORE_CTX_init(inctx, st, NULL, NULL))
+ return 0;
+ if (X509_STORE_CTX_get1_issuer(&issuer, inctx, x) <= 0)
+ issuer = NULL;
+ X509_STORE_CTX_cleanup(inctx);
+ X509_STORE_CTX_free(inctx);
+ return issuer;
+}
+
+int ssl_stapling_init_cert(server_rec *s, apr_pool_t *p, apr_pool_t *ptemp,
+ modssl_ctx_t *mctx, X509 *x)
+{
+ UCHAR idx[SHA_DIGEST_LENGTH];
+ certinfo *cinf = NULL;
+ X509 *issuer = NULL;
+ OCSP_CERTID *cid = NULL;
+ STACK_OF(OPENSSL_STRING) *aia = NULL;
+ const char *pem = NULL;
+ int rv = 1; /* until further notice */
+
+ if (x == NULL)
+ return 0;
+
+ if (!(issuer = stapling_get_issuer(mctx, x))) {
+ /* In Apache pre 2.4.40, we use to come here only when mod_ssl stapling
+ * was enabled. With the new hooks, we give other modules the chance
+ * to provide stapling status. However, we do not want to log ssl errors
+ * where we did not do so in the past. */
+ if (mctx->stapling_enabled == TRUE) {
+ ssl_log_xerror(SSLLOG_MARK, APLOG_ERR, 0, ptemp, s, x, APLOGNO(02217)
+ "ssl_stapling_init_cert: can't retrieve issuer "
+ "certificate!");
+ return 0;
+ }
+ return 1;
+ }
+
+ if (X509_digest(x, EVP_sha1(), idx, NULL) != 1) {
+ rv = 0;
+ goto cleanup;
+ }
+
+ if (modssl_cert_get_pem(ptemp, x, issuer, &pem) != APR_SUCCESS) {
+ rv = 0;
+ goto cleanup;
+ }
+
+ if (ap_ssl_ocsp_prime(s, p, (const char*)idx, sizeof(idx), pem) == APR_SUCCESS
+ || ssl_run_init_stapling_status(s, p, x, issuer) == OK) {
+ /* Someone's taken over or mod_ssl's own implementation is not enabled */
+ if (mctx->stapling_enabled != TRUE) {
+ SSL_CTX_set_tlsext_status_cb(mctx->ssl_ctx, stapling_cb);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10177) "OCSP stapling added via hook");
+ }
+ goto cleanup;
+ }
+
+ if (mctx->stapling_enabled != TRUE) {
+ /* mod_ssl's own implementation is not enabled */
+ goto cleanup;
+ }
+
+ cinf = apr_hash_get(stapling_certinfo, idx, sizeof(idx));
+ if (cinf) {
+ /*
+ * We already parsed the certificate, and no OCSP URI was found.
+ * The certificate might be used for multiple vhosts, though,
+ * so we check for a ForceURL for this vhost.
+ */
+ if (!cinf->uri && !mctx->stapling_force_url) {
+ ssl_log_xerror(SSLLOG_MARK, APLOG_ERR, 0, ptemp, s, x,
+ APLOGNO(02814) "ssl_stapling_init_cert: no OCSP URI "
+ "in certificate and no SSLStaplingForceURL "
+ "configured for server %s", mctx->sc->vhost_id);
+ rv = 0;
+ }
+ goto cleanup;
+ }
+
+ cid = OCSP_cert_to_id(NULL, x, issuer);
+ if (!cid) {
+ ssl_log_xerror(SSLLOG_MARK, APLOG_ERR, 0, ptemp, s, x, APLOGNO(02815)
+ "ssl_stapling_init_cert: can't create CertID "
+ "for OCSP request");
+ rv = 0;
+ goto cleanup;
+ }
+
+ aia = X509_get1_ocsp(x);
+ if (!aia && !mctx->stapling_force_url) {
+ OCSP_CERTID_free(cid);
+ ssl_log_xerror(SSLLOG_MARK, APLOG_ERR, 0, ptemp, s, x,
+ APLOGNO(02218) "ssl_stapling_init_cert: no OCSP URI "
+ "in certificate and no SSLStaplingForceURL set");
+ rv = 0;
+ goto cleanup;
+ }
+
+ /* At this point, we have determined that there's something to store */
+ cinf = apr_pcalloc(p, sizeof(certinfo));
+ memcpy (cinf->idx, idx, sizeof(idx));
+ cinf->cid = cid;
+ /* make sure cid is also freed at pool cleanup */
+ apr_pool_cleanup_register(p, cid, ssl_stapling_certid_free,
+ apr_pool_cleanup_null);
+ if (aia) {
+ /* allocate uri from the pconf pool */
+ cinf->uri = apr_pstrdup(p, sk_OPENSSL_STRING_value(aia, 0));
+ X509_email_free(aia);
+ }
+
+ ssl_log_xerror(SSLLOG_MARK, APLOG_TRACE1, 0, ptemp, s, x,
+ "ssl_stapling_init_cert: storing certinfo for server %s",
+ mctx->sc->vhost_id);
+
+ apr_hash_set(stapling_certinfo, cinf->idx, sizeof(cinf->idx), cinf);
+
+cleanup:
+ X509_free(issuer);
+ return rv;
+}
+
+static certinfo *stapling_get_certinfo(server_rec *s, UCHAR *idx, apr_size_t idx_len,
+ modssl_ctx_t *mctx, SSL *ssl)
+{
+ certinfo *cinf;
+ cinf = apr_hash_get(stapling_certinfo, idx, idx_len);
+ if (cinf && cinf->cid)
+ return cinf;
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(01926)
+ "stapling_get_certinfo: stapling not supported for certificate");
+ return NULL;
+}
+
+/*
+ * OCSP response caching code. The response is preceded by a flag value
+ * which indicates whether the response was invalid when it was stored.
+ * the purpose of this flag is to avoid repeated queries to a server
+ * which has given an invalid response while allowing a response which
+ * has subsequently become invalid to be retried immediately.
+ *
+ * The key for the cache is the hash of the certificate the response
+ * is for.
+ */
+static BOOL stapling_cache_response(server_rec *s, modssl_ctx_t *mctx,
+ OCSP_RESPONSE *rsp, certinfo *cinf,
+ BOOL ok, apr_pool_t *pool)
+{
+ SSLModConfigRec *mc = myModConfig(s);
+ unsigned char resp_der[MAX_STAPLING_DER]; /* includes one-byte flag + response */
+ unsigned char *p;
+ int resp_derlen, stored_len;
+ BOOL rv;
+ apr_time_t expiry;
+
+ resp_derlen = i2d_OCSP_RESPONSE(rsp, NULL);
+
+ if (resp_derlen <= 0) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(01927)
+ "OCSP stapling response encode error??");
+ return FALSE;
+ }
+
+ stored_len = resp_derlen + 1; /* response + ok flag */
+ if (stored_len > sizeof resp_der) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(01928)
+ "OCSP stapling response too big (%u bytes)", resp_derlen);
+ return FALSE;
+ }
+
+ p = resp_der;
+
+ /* TODO: potential optimization; _timeout members as apr_interval_time_t */
+ if (ok == TRUE) {
+ *p++ = 1;
+ expiry = apr_time_from_sec(mctx->stapling_cache_timeout);
+ }
+ else {
+ *p++ = 0;
+ expiry = apr_time_from_sec(mctx->stapling_errcache_timeout);
+ }
+
+ expiry += apr_time_now();
+
+ i2d_OCSP_RESPONSE(rsp, &p);
+
+ if (mc->stapling_cache->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
+ stapling_cache_mutex_on(s);
+ rv = mc->stapling_cache->store(mc->stapling_cache_context, s,
+ cinf->idx, sizeof(cinf->idx),
+ expiry, resp_der, stored_len, pool);
+ if (mc->stapling_cache->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
+ stapling_cache_mutex_off(s);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(01929)
+ "stapling_cache_response: OCSP response session store error!");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void stapling_get_cached_response(server_rec *s, OCSP_RESPONSE **prsp,
+ BOOL *pok, certinfo *cinf,
+ apr_pool_t *pool)
+{
+ SSLModConfigRec *mc = myModConfig(s);
+ apr_status_t rv;
+ OCSP_RESPONSE *rsp;
+ unsigned char resp_der[MAX_STAPLING_DER];
+ const unsigned char *p;
+ unsigned int resp_derlen = MAX_STAPLING_DER;
+
+ if (mc->stapling_cache->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
+ stapling_cache_mutex_on(s);
+ rv = mc->stapling_cache->retrieve(mc->stapling_cache_context, s,
+ cinf->idx, sizeof(cinf->idx),
+ resp_der, &resp_derlen, pool);
+ if (mc->stapling_cache->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
+ stapling_cache_mutex_off(s);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01930)
+ "stapling_get_cached_response: cache miss");
+ return;
+ }
+ if (resp_derlen <= 1) {
+ /* should-not-occur; must have at least valid-when-stored flag +
+ * OCSPResponseStatus
+ */
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(01931)
+ "stapling_get_cached_response: response length invalid??");
+ return;
+ }
+ p = resp_der;
+ if (*p) /* valid when stored */
+ *pok = TRUE;
+ else
+ *pok = FALSE;
+ p++;
+ resp_derlen--;
+ rsp = d2i_OCSP_RESPONSE(NULL, &p, resp_derlen);
+ if (!rsp) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(01932)
+ "stapling_get_cached_response: response parse error??");
+ return;
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01933)
+ "stapling_get_cached_response: cache hit");
+
+ *prsp = rsp;
+}
+
+static int stapling_set_response(SSL *ssl, OCSP_RESPONSE *rsp)
+{
+ int rspderlen;
+ unsigned char *rspder = NULL;
+
+ rspderlen = i2d_OCSP_RESPONSE(rsp, &rspder);
+ if (rspderlen <= 0)
+ return 0;
+ SSL_set_tlsext_status_ocsp_resp(ssl, rspder, rspderlen);
+ return 1;
+}
+
+static int stapling_check_response(server_rec *s, modssl_ctx_t *mctx,
+ certinfo *cinf, OCSP_RESPONSE *rsp,
+ BOOL *pok)
+{
+ int status = V_OCSP_CERTSTATUS_UNKNOWN;
+ int reason = OCSP_REVOKED_STATUS_NOSTATUS;
+ OCSP_BASICRESP *bs = NULL;
+ ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd;
+ int response_status = OCSP_response_status(rsp);
+ int rv = SSL_TLSEXT_ERR_OK;
+
+ if (pok)
+ *pok = FALSE;
+ /* Check to see if response is an error. If so we automatically accept
+ * it because it would have expired from the cache if it was time to
+ * retry.
+ */
+ if (response_status != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
+ if (mctx->stapling_return_errors)
+ return SSL_TLSEXT_ERR_OK;
+ else
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+
+ bs = OCSP_response_get1_basic(rsp);
+ if (bs == NULL) {
+ /* If we can't parse response just pass it to client */
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(01934)
+ "stapling_check_response: Error Parsing Response!");
+ return SSL_TLSEXT_ERR_OK;
+ }
+
+ if (!OCSP_resp_find_status(bs, cinf->cid, &status, &reason, &rev,
+ &thisupd, &nextupd)) {
+ /* If ID not present pass back to client (if configured so) */
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(01935)
+ "stapling_check_response: certificate ID not present in response!");
+ if (mctx->stapling_return_errors == FALSE)
+ rv = SSL_TLSEXT_ERR_NOACK;
+ }
+ else {
+ if (OCSP_check_validity(thisupd, nextupd,
+ mctx->stapling_resptime_skew,
+ mctx->stapling_resp_maxage)) {
+ if (pok)
+ *pok = TRUE;
+ }
+ else {
+ /* If pok is not NULL response was direct from a responder and
+ * the times should be valide. If pok is NULL the response was
+ * retrieved from cache and it is expected to subsequently expire
+ */
+ if (pok) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(01936)
+ "stapling_check_response: response times invalid");
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01937)
+ "stapling_check_response: cached response expired");
+ }
+
+ rv = SSL_TLSEXT_ERR_NOACK;
+ }
+
+ if (status != V_OCSP_CERTSTATUS_GOOD) {
+ char snum[MAX_STRING_LEN] = { '\0' };
+ BIO *bio = BIO_new(BIO_s_mem());
+
+ if (bio) {
+ int n;
+ ASN1_INTEGER *pserial;
+ OCSP_id_get0_info(NULL, NULL, NULL, &pserial, cinf->cid);
+ if ((i2a_ASN1_INTEGER(bio, pserial) != -1) &&
+ ((n = BIO_read(bio, snum, sizeof snum - 1)) > 0))
+ snum[n] = '\0';
+ BIO_free(bio);
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(02969)
+ "stapling_check_response: response has certificate "
+ "status %s (reason: %s) for serial number %s",
+ OCSP_cert_status_str(status),
+ (reason != OCSP_REVOKED_STATUS_NOSTATUS) ?
+ OCSP_crl_reason_str(reason) : "n/a",
+ snum[0] ? snum : "[n/a]");
+
+ if (mctx->stapling_return_errors == FALSE) {
+ if (pok)
+ *pok = FALSE;
+ rv = SSL_TLSEXT_ERR_NOACK;
+ }
+ }
+ }
+
+ OCSP_BASICRESP_free(bs);
+
+ return rv;
+}
+
+static BOOL stapling_renew_response(server_rec *s, modssl_ctx_t *mctx, SSL *ssl,
+ certinfo *cinf, OCSP_RESPONSE **prsp,
+ BOOL *pok, apr_pool_t *pool)
+{
+ conn_rec *conn = (conn_rec *)SSL_get_app_data(ssl);
+ apr_pool_t *vpool;
+ OCSP_REQUEST *req = NULL;
+ OCSP_CERTID *id = NULL;
+ STACK_OF(X509_EXTENSION) *exts;
+ int i;
+ BOOL rv = TRUE;
+ const char *ocspuri;
+ apr_uri_t uri;
+
+ *prsp = NULL;
+ /* Build up OCSP query from server certificate info */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01938)
+ "stapling_renew_response: querying responder");
+
+ req = OCSP_REQUEST_new();
+ if (!req)
+ goto err;
+ id = OCSP_CERTID_dup(cinf->cid);
+ if (!id)
+ goto err;
+ if (!OCSP_request_add0_id(req, id))
+ goto err;
+ id = NULL;
+ /* Add any extensions to the request */
+ SSL_get_tlsext_status_exts(ssl, &exts);
+ for (i = 0; i < sk_X509_EXTENSION_num(exts); i++) {
+ X509_EXTENSION *ext = sk_X509_EXTENSION_value(exts, i);
+ if (!OCSP_REQUEST_add_ext(req, ext, -1))
+ goto err;
+ }
+
+ if (mctx->stapling_force_url)
+ ocspuri = mctx->stapling_force_url;
+ else
+ ocspuri = cinf->uri;
+
+ if (!ocspuri) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(02621)
+ "stapling_renew_response: no uri for responder");
+ rv = FALSE;
+ goto done;
+ }
+
+ /* Create a temporary pool to constrain memory use */
+ apr_pool_create(&vpool, conn->pool);
+ apr_pool_tag(vpool, "modssl_stapling_renew");
+
+ if (apr_uri_parse(vpool, ocspuri, &uri) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(01939)
+ "stapling_renew_response: Error parsing uri %s",
+ ocspuri);
+ rv = FALSE;
+ goto done;
+ }
+ else if (strcmp(uri.scheme, "http")) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(01940)
+ "stapling_renew_response: Unsupported uri %s", ocspuri);
+ rv = FALSE;
+ goto done;
+ }
+
+ if (!uri.port) {
+ uri.port = apr_uri_port_of_scheme(uri.scheme);
+ }
+
+ *prsp = modssl_dispatch_ocsp_request(&uri, mctx->stapling_responder_timeout,
+ req, conn, vpool);
+
+ apr_pool_destroy(vpool);
+
+ if (!*prsp) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(01941)
+ "stapling_renew_response: responder error");
+ if (mctx->stapling_fake_trylater) {
+ *prsp = OCSP_response_create(OCSP_RESPONSE_STATUS_TRYLATER, NULL);
+ }
+ else {
+ goto done;
+ }
+ }
+ else {
+ int response_status = OCSP_response_status(*prsp);
+
+ if (response_status == OCSP_RESPONSE_STATUS_SUCCESSFUL) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01942)
+ "stapling_renew_response: query response received");
+ stapling_check_response(s, mctx, cinf, *prsp, pok);
+ if (*pok == FALSE) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(01943)
+ "stapling_renew_response: error in retrieved response!");
+ }
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01944)
+ "stapling_renew_response: responder error %s",
+ OCSP_response_status_str(response_status));
+ *pok = FALSE;
+ }
+ }
+ if (stapling_cache_response(s, mctx, *prsp, cinf, *pok, pool) == FALSE) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(01945)
+ "stapling_renew_response: error caching response!");
+ }
+
+done:
+ if (id)
+ OCSP_CERTID_free(id);
+ if (req)
+ OCSP_REQUEST_free(req);
+ return rv;
+err:
+ rv = FALSE;
+ goto done;
+}
+
+/*
+ * SSL stapling mutex operations. Similar to SSL mutex except mutexes are
+ * mandatory if stapling is enabled.
+ */
+static int ssl_stapling_mutex_init(server_rec *s, apr_pool_t *p)
+{
+ SSLModConfigRec *mc = myModConfig(s);
+ SSLSrvConfigRec *sc = mySrvConfig(s);
+ apr_status_t rv;
+
+ /* already init or stapling not enabled? */
+ if (mc->stapling_refresh_mutex || sc->server->stapling_enabled != TRUE) {
+ return TRUE;
+ }
+
+ /* need a cache mutex? */
+ if (mc->stapling_cache->flags & AP_SOCACHE_FLAG_NOTMPSAFE) {
+ if ((rv = ap_global_mutex_create(&mc->stapling_cache_mutex, NULL,
+ SSL_STAPLING_CACHE_MUTEX_TYPE, NULL, s,
+ s->process->pool, 0)) != APR_SUCCESS) {
+ return FALSE;
+ }
+ }
+
+ /* always need stapling_refresh_mutex */
+ if ((rv = ap_global_mutex_create(&mc->stapling_refresh_mutex, NULL,
+ SSL_STAPLING_REFRESH_MUTEX_TYPE, NULL, s,
+ s->process->pool, 0)) != APR_SUCCESS) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static int stapling_mutex_reinit_helper(server_rec *s, apr_pool_t *p,
+ apr_global_mutex_t **mutex,
+ const char *type)
+{
+ apr_status_t rv;
+ const char *lockfile;
+
+ lockfile = apr_global_mutex_lockfile(*mutex);
+ if ((rv = apr_global_mutex_child_init(mutex,
+ lockfile, p)) != APR_SUCCESS) {
+ if (lockfile) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(01946)
+ "Cannot reinit %s mutex with file `%s'",
+ type, lockfile);
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rv, s, APLOGNO(01947)
+ "Cannot reinit %s mutex", type);
+ }
+ return FALSE;
+ }
+ return TRUE;
+}
+
+int ssl_stapling_mutex_reinit(server_rec *s, apr_pool_t *p)
+{
+ SSLModConfigRec *mc = myModConfig(s);
+
+ if (mc->stapling_cache_mutex != NULL
+ && stapling_mutex_reinit_helper(s, p, &mc->stapling_cache_mutex,
+ SSL_STAPLING_CACHE_MUTEX_TYPE) == FALSE) {
+ return FALSE;
+ }
+
+ if (mc->stapling_refresh_mutex != NULL
+ && stapling_mutex_reinit_helper(s, p, &mc->stapling_refresh_mutex,
+ SSL_STAPLING_REFRESH_MUTEX_TYPE) == FALSE) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static int stapling_mutex_on(server_rec *s, apr_global_mutex_t *mutex,
+ const char *name)
+{
+ apr_status_t rv;
+
+ if ((rv = apr_global_mutex_lock(mutex)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rv, s, APLOGNO(01948)
+ "Failed to acquire OCSP %s lock", name);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static int stapling_mutex_off(server_rec *s, apr_global_mutex_t *mutex,
+ const char *name)
+{
+ apr_status_t rv;
+
+ if ((rv = apr_global_mutex_unlock(mutex)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rv, s, APLOGNO(01949)
+ "Failed to release OCSP %s lock", name);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static int stapling_cache_mutex_on(server_rec *s)
+{
+ SSLModConfigRec *mc = myModConfig(s);
+
+ return stapling_mutex_on(s, mc->stapling_cache_mutex,
+ SSL_STAPLING_CACHE_MUTEX_TYPE);
+}
+
+static int stapling_cache_mutex_off(server_rec *s)
+{
+ SSLModConfigRec *mc = myModConfig(s);
+
+ return stapling_mutex_off(s, mc->stapling_cache_mutex,
+ SSL_STAPLING_CACHE_MUTEX_TYPE);
+}
+
+static int stapling_refresh_mutex_on(server_rec *s)
+{
+ SSLModConfigRec *mc = myModConfig(s);
+
+ return stapling_mutex_on(s, mc->stapling_refresh_mutex,
+ SSL_STAPLING_REFRESH_MUTEX_TYPE);
+}
+
+static int stapling_refresh_mutex_off(server_rec *s)
+{
+ SSLModConfigRec *mc = myModConfig(s);
+
+ return stapling_mutex_off(s, mc->stapling_refresh_mutex,
+ SSL_STAPLING_REFRESH_MUTEX_TYPE);
+}
+
+static int get_and_check_cached_response(server_rec *s, modssl_ctx_t *mctx,
+ OCSP_RESPONSE **rsp, BOOL *pok,
+ certinfo *cinf, apr_pool_t *p)
+{
+ BOOL ok = FALSE;
+ int rv;
+
+ AP_DEBUG_ASSERT(*rsp == NULL);
+
+ /* Check to see if we already have a response for this certificate */
+ stapling_get_cached_response(s, rsp, &ok, cinf, p);
+
+ if (*rsp) {
+ /* see if response is acceptable */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01953)
+ "stapling_cb: retrieved cached response");
+ rv = stapling_check_response(s, mctx, cinf, *rsp, NULL);
+ if (rv == SSL_TLSEXT_ERR_ALERT_FATAL) {
+ OCSP_RESPONSE_free(*rsp);
+ *rsp = NULL;
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+ }
+ else if (rv == SSL_TLSEXT_ERR_NOACK) {
+ /* Error in response. If this error was not present when it was
+ * stored (i.e. response no longer valid) then it can be
+ * renewed straight away.
+ *
+ * If the error *was* present at the time it was stored then we
+ * don't renew the response straight away; we just wait for the
+ * cached response to expire.
+ */
+ if (ok) {
+ OCSP_RESPONSE_free(*rsp);
+ *rsp = NULL;
+ }
+ else if (!mctx->stapling_return_errors) {
+ OCSP_RESPONSE_free(*rsp);
+ *rsp = NULL;
+ *pok = FALSE;
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+ }
+ }
+ return 0;
+}
+
+typedef struct {
+ unsigned char *data;
+ apr_size_t len;
+} ocsp_resp;
+
+static void copy_ocsp_resp(const unsigned char *der, apr_size_t der_len, void *userdata)
+{
+ ocsp_resp *resp = userdata;
+
+ resp->len = 0;
+ resp->data = der? OPENSSL_malloc(der_len) : NULL;
+ if (resp->data) {
+ memcpy(resp->data, der, der_len);
+ resp->len = der_len;
+ }
+}
+
+/* Certificate Status callback. This is called when a client includes a
+ * certificate status request extension.
+ *
+ * Check for cached responses in session cache. If valid send back to
+ * client. If absent or no longer valid, query responder and update
+ * cache.
+ */
+static int stapling_cb(SSL *ssl, void *arg)
+{
+ conn_rec *conn = (conn_rec *)SSL_get_app_data(ssl);
+ server_rec *s = mySrvFromConn(conn);
+ SSLSrvConfigRec *sc = mySrvConfig(s);
+ modssl_ctx_t *mctx = myConnCtxConfig(conn, sc);
+ UCHAR idx[SHA_DIGEST_LENGTH];
+ ocsp_resp resp;
+ certinfo *cinf = NULL;
+ OCSP_RESPONSE *rsp = NULL;
+ int rv;
+ BOOL ok = TRUE;
+ X509 *x;
+ int rspderlen, provided = 0;
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01951)
+ "stapling_cb: OCSP Stapling callback called");
+
+ x = SSL_get_certificate(ssl);
+ if (x == NULL) {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+
+ if (X509_digest(x, EVP_sha1(), idx, NULL) != 1) {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+
+ if (ap_ssl_ocsp_get_resp(s, conn, (const char*)idx, sizeof(idx),
+ copy_ocsp_resp, &resp) == APR_SUCCESS) {
+ provided = 1;
+ }
+ else if (ssl_run_get_stapling_status(&resp.data, &rspderlen, conn, s, x) == APR_SUCCESS) {
+ resp.len = (apr_size_t)rspderlen;
+ provided = 1;
+ }
+
+ if (provided) {
+ /* a hook handles stapling for this certificate and determines the response */
+ if (resp.data == NULL || resp.len == 0) {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+ SSL_set_tlsext_status_ocsp_resp(ssl, resp.data, (int)resp.len);
+ return SSL_TLSEXT_ERR_OK;
+ }
+
+ if (sc->server->stapling_enabled != TRUE) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01950)
+ "stapling_cb: OCSP Stapling disabled");
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+
+ if ((cinf = stapling_get_certinfo(s, idx, sizeof(idx), mctx, ssl)) == NULL) {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01952)
+ "stapling_cb: retrieved cached certificate data");
+
+ rv = get_and_check_cached_response(s, mctx, &rsp, &ok, cinf, conn->pool);
+ if (rv != 0) {
+ return rv;
+ }
+
+ if (rsp == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01954)
+ "stapling_cb: renewing cached response");
+ stapling_refresh_mutex_on(s);
+ /* Maybe another request refreshed the OCSP response while this
+ * thread waited for the mutex. Check again.
+ */
+ rv = get_and_check_cached_response(s, mctx, &rsp, &ok, cinf,
+ conn->pool);
+ if (rv != 0) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03236)
+ "stapling_cb: error checking for cached response "
+ "after obtaining refresh mutex");
+ stapling_refresh_mutex_off(s);
+ return rv;
+ }
+ else if (rsp) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03237)
+ "stapling_cb: don't need to refresh cached response "
+ "after obtaining refresh mutex");
+ stapling_refresh_mutex_off(s);
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03238)
+ "stapling_cb: still must refresh cached response "
+ "after obtaining refresh mutex");
+ rv = stapling_renew_response(s, mctx, ssl, cinf, &rsp, &ok,
+ conn->pool);
+ stapling_refresh_mutex_off(s);
+
+ if (rv == TRUE) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03040)
+ "stapling_cb: success renewing response");
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(01955)
+ "stapling_cb: fatal error renewing response");
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+ }
+ }
+ }
+
+ if (rsp && ((ok == TRUE) || (mctx->stapling_return_errors == TRUE))) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01956)
+ "stapling_cb: setting response");
+ if (!stapling_set_response(ssl, rsp)) {
+ rv = SSL_TLSEXT_ERR_ALERT_FATAL;
+ }
+ else {
+ rv = SSL_TLSEXT_ERR_OK;
+ }
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01957)
+ "stapling_cb: no suitable response available");
+ rv = SSL_TLSEXT_ERR_NOACK;
+ }
+ OCSP_RESPONSE_free(rsp); /* NULL safe */
+
+ return rv;
+}
+
+apr_status_t modssl_init_stapling(server_rec *s, apr_pool_t *p,
+ apr_pool_t *ptemp, modssl_ctx_t *mctx)
+{
+ SSL_CTX *ctx = mctx->ssl_ctx;
+ SSLModConfigRec *mc = myModConfig(s);
+
+ if (mc->stapling_cache == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(01958)
+ "SSLStapling: no stapling cache available");
+ return ssl_die(s);
+ }
+ if (ssl_stapling_mutex_init(s, ptemp) == FALSE) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(01959)
+ "SSLStapling: cannot initialise stapling mutex");
+ return ssl_die(s);
+ }
+ /* Set some default values for parameters if they are not set */
+ if (mctx->stapling_resptime_skew == UNSET) {
+ mctx->stapling_resptime_skew = 60 * 5;
+ }
+ if (mctx->stapling_cache_timeout == UNSET) {
+ mctx->stapling_cache_timeout = 3600;
+ }
+ if (mctx->stapling_return_errors == UNSET) {
+ mctx->stapling_return_errors = TRUE;
+ }
+ if (mctx->stapling_fake_trylater == UNSET) {
+ mctx->stapling_fake_trylater = TRUE;
+ }
+ if (mctx->stapling_errcache_timeout == UNSET) {
+ mctx->stapling_errcache_timeout = 600;
+ }
+ if (mctx->stapling_responder_timeout == UNSET) {
+ mctx->stapling_responder_timeout = 10 * APR_USEC_PER_SEC;
+ }
+
+ SSL_CTX_set_tlsext_status_cb(ctx, stapling_cb);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01960) "OCSP stapling initialized");
+
+ return APR_SUCCESS;
+}
+
+#endif
diff --git a/modules/test/.indent.pro b/modules/test/.indent.pro
new file mode 100644
index 0000000..a9fbe9f
--- /dev/null
+++ b/modules/test/.indent.pro
@@ -0,0 +1,54 @@
+-i4 -npsl -di0 -br -nce -d0 -cli0 -npcs -nfc1
+-TBUFF
+-TFILE
+-TTRANS
+-TUINT4
+-T_trans
+-Tallow_options_t
+-Tapache_sfio
+-Tarray_header
+-Tbool_int
+-Tbuf_area
+-Tbuff_struct
+-Tbuffy
+-Tcmd_how
+-Tcmd_parms
+-Tcommand_rec
+-Tcommand_struct
+-Tconn_rec
+-Tcore_dir_config
+-Tcore_server_config
+-Tdir_maker_func
+-Tevent
+-Tglobals_s
+-Thandler_func
+-Thandler_rec
+-Tjoblist_s
+-Tlisten_rec
+-Tmerger_func
+-Tmode_t
+-Tmodule
+-Tmodule_struct
+-Tmutex
+-Tn_long
+-Tother_child_rec
+-Toverrides_t
+-Tparent_score
+-Tpid_t
+-Tpiped_log
+-Tpool
+-Trequest_rec
+-Trequire_line
+-Trlim_t
+-Tscoreboard
+-Tsemaphore
+-Tserver_addr_rec
+-Tserver_rec
+-Tserver_rec_chain
+-Tshort_score
+-Ttable
+-Ttable_entry
+-Tthread
+-Tu_wide_int
+-Tvtime_t
+-Twide_int
diff --git a/modules/test/Makefile.in b/modules/test/Makefile.in
new file mode 100644
index 0000000..7c5c149
--- /dev/null
+++ b/modules/test/Makefile.in
@@ -0,0 +1,3 @@
+# a modules Makefile has no explicit targets -- they will be defined by
+# whatever modules are enabled. just grab special.mk to deal with this.
+include $(top_srcdir)/build/special.mk
diff --git a/modules/test/NWGNUmakefile b/modules/test/NWGNUmakefile
new file mode 100644
index 0000000..006a519
--- /dev/null
+++ b/modules/test/NWGNUmakefile
@@ -0,0 +1,257 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME =
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION =
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME =
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE =
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+# If there is only one element to build it needs to be included twice
+# in the below target list.
+# Normally if there is only one element to be built within a
+# directory, the makefile for the single element would be called NWGNUmakefile.
+# But if there are multiples, the parent NWGNUmakefile must reference more
+# than one submakefile. Because the experimental directory might vary in the
+# number of submakefiles, but for the moment only contains one, we reference
+# it twice to allow it parent NWGNUmakefile to work properly. If another
+# submakefile is added, the extra reference to the first NLM should be removed.
+TARGET_nlm = \
+ $(OBJDIR)/optfnexport.nlm \
+ $(OBJDIR)/optfnimport.nlm \
+ $(OBJDIR)/opthookexport.nlm \
+ $(OBJDIR)/opthookimport.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+ $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/)
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/test/NWGNUoptfnexport b/modules/test/NWGNUoptfnexport
new file mode 100644
index 0000000..449d26e
--- /dev/null
+++ b/modules/test/NWGNUoptfnexport
@@ -0,0 +1,256 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = optfnexport
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) OptionalFunction Export Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = $(NLM_NAME) Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_optional_fn_export.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ optional_fn_export_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/test/NWGNUoptfnimport b/modules/test/NWGNUoptfnimport
new file mode 100644
index 0000000..cd09d39
--- /dev/null
+++ b/modules/test/NWGNUoptfnimport
@@ -0,0 +1,256 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = optfnimport
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) OptionalFunction Import Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = $(NLM_NAME) Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_optional_fn_import.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ optional_fn_import_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/test/NWGNUopthookexport b/modules/test/NWGNUopthookexport
new file mode 100644
index 0000000..0472386
--- /dev/null
+++ b/modules/test/NWGNUopthookexport
@@ -0,0 +1,256 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = opthookexport
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) OptionalHook Export Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = $(NLM_NAME) Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_optional_hook_export.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ optional_hook_export_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/test/NWGNUopthookimport b/modules/test/NWGNUopthookimport
new file mode 100644
index 0000000..ef4d3dc
--- /dev/null
+++ b/modules/test/NWGNUopthookimport
@@ -0,0 +1,256 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)/build/NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# Make sure all needed macro's are defined
+#
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(APR)/include \
+ $(APRUTIL)/include \
+ $(AP_WORK)/include \
+ $(NWOS) \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = opthookimport
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Apache $(VERSION_STR) OptionalHook Import Module
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = $(NLM_NAME) Module
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)/build/NWGNUenvironment.inc
+#
+NLM_VERSION =
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If these are specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS =
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+ $(OBJDIR)/$(NLM_NAME).nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/mod_optional_hook_import.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ $(PRELUDE) \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ aprlib \
+ libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @aprlib.imp \
+ @httpd.imp \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ optional_hook_import_module \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples)
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(APBUILD)/NWGNUtail.inc
+
+
diff --git a/modules/test/README b/modules/test/README
new file mode 100644
index 0000000..f122368
--- /dev/null
+++ b/modules/test/README
@@ -0,0 +1 @@
+test modules have moved to httpd-test/perl-framework/c-modules
diff --git a/modules/test/config.m4 b/modules/test/config.m4
new file mode 100644
index 0000000..dfcd321
--- /dev/null
+++ b/modules/test/config.m4
@@ -0,0 +1,13 @@
+
+APACHE_MODPATH_INIT(test)
+
+APACHE_MODULE(optional_hook_export, example optional hook exporter, , , no)
+APACHE_MODULE(optional_hook_import, example optional hook importer, , , no)
+APACHE_MODULE(optional_fn_import, example optional function importer, , , no)
+APACHE_MODULE(optional_fn_export, example optional function exporter, , , no)
+
+APACHE_MODULE(dialup, rate limits static files to dialup modem speeds, , , )
+
+APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/$modpath_current])
+
+APACHE_MODPATH_FINISH
diff --git a/modules/test/mod_dialup.c b/modules/test/mod_dialup.c
new file mode 100644
index 0000000..d018d9a
--- /dev/null
+++ b/modules/test/mod_dialup.c
@@ -0,0 +1,306 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+#include "httpd.h"
+#include "http_core.h"
+
+#include "util_filter.h"
+#include "http_log.h"
+#include "http_config.h"
+#include "http_request.h"
+#include "http_protocol.h"
+
+
+
+#include "ap_mpm.h"
+
+module AP_MODULE_DECLARE_DATA dialup_module;
+
+typedef struct dialup_dcfg_t {
+ apr_size_t bytes_per_second;
+} dialup_dcfg_t;
+
+typedef struct dialup_baton_t {
+ apr_size_t bytes_per_second;
+ request_rec *r;
+ apr_file_t *fd;
+ apr_bucket_brigade *bb;
+ apr_bucket_brigade *tmpbb;
+} dialup_baton_t;
+
+static int
+dialup_send_pulse(dialup_baton_t *db)
+{
+ int status;
+ apr_off_t len = 0;
+ apr_size_t bytes_sent = 0;
+
+ while (!APR_BRIGADE_EMPTY(db->bb) && bytes_sent < db->bytes_per_second) {
+ apr_bucket *e;
+
+ if (db->r->connection->aborted) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ status = apr_brigade_partition(db->bb, db->bytes_per_second, &e);
+
+ if (status != APR_SUCCESS && status != APR_INCOMPLETE) {
+ /* XXXXXX: Log me. */
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (e != APR_BRIGADE_SENTINEL(db->bb)) {
+ apr_bucket *f;
+ apr_bucket *b = APR_BUCKET_PREV(e);
+ f = APR_RING_FIRST(&db->bb->list);
+ APR_RING_UNSPLICE(f, b, link);
+ APR_RING_SPLICE_HEAD(&db->tmpbb->list, f, b, apr_bucket, link);
+ }
+ else {
+ APR_BRIGADE_CONCAT(db->tmpbb, db->bb);
+ }
+
+ e = apr_bucket_flush_create(db->r->connection->bucket_alloc);
+
+ APR_BRIGADE_INSERT_TAIL(db->tmpbb, e);
+
+ apr_brigade_length(db->tmpbb, 1, &len);
+ bytes_sent += len;
+ status = ap_pass_brigade(db->r->output_filters, db->tmpbb);
+
+ apr_brigade_cleanup(db->tmpbb);
+
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, db->r, APLOGNO(01867)
+ "dialup: pulse: ap_pass_brigade failed:");
+ return AP_FILTER_ERROR;
+ }
+ }
+
+ if (APR_BRIGADE_EMPTY(db->bb)) {
+ return DONE;
+ }
+ else {
+ return SUSPENDED;
+ }
+}
+
+static void
+dialup_callback(void *baton)
+{
+ int status;
+ dialup_baton_t *db = (dialup_baton_t *)baton;
+
+ apr_thread_mutex_lock(db->r->invoke_mtx);
+
+ status = dialup_send_pulse(db);
+
+ if (status == SUSPENDED) {
+ ap_mpm_register_timed_callback(apr_time_from_sec(1), dialup_callback, baton);
+ }
+ else if (status == DONE) {
+ apr_thread_mutex_unlock(db->r->invoke_mtx);
+ ap_finalize_request_protocol(db->r);
+ ap_process_request_after_handler(db->r);
+ return;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, db->r, APLOGNO(01868)
+ "dialup: pulse returned: %d", status);
+ db->r->status = HTTP_OK;
+ ap_die(status, db->r);
+ }
+
+ apr_thread_mutex_unlock(db->r->invoke_mtx);
+}
+
+static int
+dialup_handler(request_rec *r)
+{
+ int status;
+ apr_status_t rv;
+ dialup_dcfg_t *dcfg;
+ core_dir_config *ccfg;
+ apr_file_t *fd;
+ dialup_baton_t *db;
+ apr_bucket *e;
+
+
+ /* See core.c, default handler for all of the cases we just decline. */
+ if (r->method_number != M_GET ||
+ r->finfo.filetype == APR_NOFILE ||
+ r->finfo.filetype == APR_DIR) {
+ return DECLINED;
+ }
+
+ dcfg = ap_get_module_config(r->per_dir_config,
+ &dialup_module);
+
+ if (dcfg->bytes_per_second == 0) {
+ return DECLINED;
+ }
+
+ ccfg = ap_get_core_module_config(r->per_dir_config);
+
+
+ rv = apr_file_open(&fd, r->filename, APR_READ | APR_BINARY
+#if APR_HAS_SENDFILE
+ | AP_SENDFILE_ENABLED(ccfg->enable_sendfile)
+#endif
+ , 0, r->pool);
+
+ if (rv) {
+ return DECLINED;
+ }
+
+ /* copied from default handler: */
+ ap_update_mtime(r, r->finfo.mtime);
+ ap_set_last_modified(r);
+ ap_set_etag_fd(r, fd);
+ ap_set_accept_ranges(r);
+ ap_set_content_length(r, r->finfo.size);
+
+ status = ap_meets_conditions(r);
+ if (status != OK) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01869)
+ "dialup: declined, meets conditions, good luck core handler");
+ return DECLINED;
+ }
+
+ db = apr_palloc(r->pool, sizeof(dialup_baton_t));
+
+ db->bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+ db->tmpbb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+
+ e = apr_brigade_insert_file(db->bb, fd, 0, r->finfo.size, r->pool);
+
+#if APR_HAS_MMAP
+ if (ccfg->enable_mmap == ENABLE_MMAP_OFF) {
+ apr_bucket_file_enable_mmap(e, 0);
+ }
+#endif
+
+
+ db->bytes_per_second = dcfg->bytes_per_second;
+ db->r = r;
+ db->fd = fd;
+
+ e = apr_bucket_eos_create(r->connection->bucket_alloc);
+
+ APR_BRIGADE_INSERT_TAIL(db->bb, e);
+
+ status = dialup_send_pulse(db);
+ if (status != SUSPENDED && status != DONE) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01870)
+ "dialup: failed, send pulse");
+ return status;
+ }
+
+ ap_mpm_register_timed_callback(apr_time_from_sec(1), dialup_callback, db);
+
+ return SUSPENDED;
+}
+
+
+
+#ifndef APR_HOOK_ALMOST_LAST
+#define APR_HOOK_ALMOST_LAST (APR_HOOK_REALLY_LAST - 1)
+#endif
+
+static void
+dialup_register_hooks(apr_pool_t *p)
+{
+ ap_hook_handler(dialup_handler, NULL, NULL, APR_HOOK_ALMOST_LAST);
+}
+
+typedef struct modem_speed_t {
+ const char *name;
+ apr_size_t bytes_per_second;
+} modem_speed_t;
+
+#ifndef BITRATE_TO_BYTES
+#define BITRATE_TO_BYTES(x) ((1000 * x)/8)
+#endif
+
+static const modem_speed_t modem_bitrates[] =
+{
+ {"V.21", BITRATE_TO_BYTES(0.1)},
+ {"V.26bis", BITRATE_TO_BYTES(2.4)},
+ {"V.32", BITRATE_TO_BYTES(9.6)},
+ {"V.34", BITRATE_TO_BYTES(28.8)},
+ {"V.92", BITRATE_TO_BYTES(56.0)},
+ {"i-was-rich-and-got-a-leased-line", BITRATE_TO_BYTES(1500)},
+ {NULL, 0}
+};
+
+static const char *
+cmd_modem_standard(cmd_parms *cmd,
+ void *dconf,
+ const char *input)
+{
+ const modem_speed_t *standard;
+ int i = 0;
+ dialup_dcfg_t *dcfg = (dialup_dcfg_t*)dconf;
+
+ dcfg->bytes_per_second = 0;
+
+ while (modem_bitrates[i].name != NULL) {
+ standard = &modem_bitrates[i];
+ if (strcasecmp(standard->name, input) == 0) {
+ dcfg->bytes_per_second = standard->bytes_per_second;
+ break;
+ }
+ i++;
+ }
+
+ if (dcfg->bytes_per_second == 0) {
+ return "mod_dialup: Unknown Modem Standard specified.";
+ }
+
+ return NULL;
+}
+
+static void *
+dialup_dcfg_create(apr_pool_t *p, char *dummy)
+{
+ dialup_dcfg_t *cfg = apr_palloc(p, sizeof(dialup_dcfg_t));
+
+ cfg->bytes_per_second = 0;
+
+ return cfg;
+}
+
+
+static const command_rec dialup_cmds[] =
+{
+ AP_INIT_TAKE1("ModemStandard", cmd_modem_standard, NULL, ACCESS_CONF,
+ "Modem Standard to.. simulate. "
+ "Must be one of: 'V.21', 'V.26bis', 'V.32', 'V.34', or 'V.92'"),
+ {NULL}
+};
+
+AP_DECLARE_MODULE(dialup) =
+{
+ STANDARD20_MODULE_STUFF,
+ dialup_dcfg_create,
+ NULL,
+ NULL,
+ NULL,
+ dialup_cmds,
+ dialup_register_hooks
+};
diff --git a/modules/test/mod_optional_fn_export.c b/modules/test/mod_optional_fn_export.c
new file mode 100644
index 0000000..5dccfc9
--- /dev/null
+++ b/modules/test/mod_optional_fn_export.c
@@ -0,0 +1,48 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "mod_optional_fn_export.h"
+
+/* The alert will note a strange mirror-image style resemblance to
+ * mod_optional_hook_import.c. Yes, I _did_ mean import. Think about it.
+ */
+
+static int TestOptionalFn(const char *szStr)
+{
+ ap_log_error(APLOG_MARK,APLOG_ERR,OK,NULL, APLOGNO(01871)
+ "Optional function test said: %s",szStr);
+
+ return OK;
+}
+
+static void ExportRegisterHooks(apr_pool_t *p)
+{
+ APR_REGISTER_OPTIONAL_FN(TestOptionalFn);
+}
+
+AP_DECLARE_MODULE(optional_fn_export) =
+{
+ STANDARD20_MODULE_STUFF,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ ExportRegisterHooks
+};
diff --git a/modules/test/mod_optional_fn_export.h b/modules/test/mod_optional_fn_export.h
new file mode 100644
index 0000000..f30c0b6
--- /dev/null
+++ b/modules/test/mod_optional_fn_export.h
@@ -0,0 +1,19 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr_optional.h"
+
+APR_DECLARE_OPTIONAL_FN(int,TestOptionalFn,(const char *));
diff --git a/modules/test/mod_optional_fn_import.c b/modules/test/mod_optional_fn_import.c
new file mode 100644
index 0000000..aba70e1
--- /dev/null
+++ b/modules/test/mod_optional_fn_import.c
@@ -0,0 +1,55 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "mod_optional_fn_export.h"
+#include "http_protocol.h"
+
+/* The alert will note a strange mirror-image style resemblance to
+ * mod_optional_hook_export.c. Yes, I _did_ mean export. Think about it.
+ */
+
+static APR_OPTIONAL_FN_TYPE(TestOptionalFn) *pfn;
+
+static int ImportLogTransaction(request_rec *r)
+{
+ if (pfn)
+ return pfn(r->the_request);
+ return DECLINED;
+}
+
+static void ImportFnRetrieve(void)
+{
+ pfn = APR_RETRIEVE_OPTIONAL_FN(TestOptionalFn);
+}
+
+static void ImportRegisterHooks(apr_pool_t *p)
+{
+ ap_hook_log_transaction(ImportLogTransaction,NULL,NULL,APR_HOOK_MIDDLE);
+ ap_hook_optional_fn_retrieve(ImportFnRetrieve,NULL,NULL,APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(optional_fn_import) =
+{
+ STANDARD20_MODULE_STUFF,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ ImportRegisterHooks
+};
diff --git a/modules/test/mod_optional_hook_export.c b/modules/test/mod_optional_hook_export.c
new file mode 100644
index 0000000..bb61b43
--- /dev/null
+++ b/modules/test/mod_optional_hook_export.c
@@ -0,0 +1,44 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "mod_optional_hook_export.h"
+#include "http_protocol.h"
+
+AP_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(int,optional_hook_test,(const char *szStr),
+ (szStr),OK,DECLINED)
+
+static int ExportLogTransaction(request_rec *r)
+{
+ return ap_run_optional_hook_test(r->the_request);
+}
+
+static void ExportRegisterHooks(apr_pool_t *p)
+{
+ ap_hook_log_transaction(ExportLogTransaction,NULL,NULL,APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(optional_hook_export) =
+{
+ STANDARD20_MODULE_STUFF,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ ExportRegisterHooks
+};
diff --git a/modules/test/mod_optional_hook_export.h b/modules/test/mod_optional_hook_export.h
new file mode 100644
index 0000000..223f591
--- /dev/null
+++ b/modules/test/mod_optional_hook_export.h
@@ -0,0 +1,24 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MOD_OPTIONAL_HOOK_EXPORT_H
+#define MOD_OPTIONAL_HOOK_EXPORT_H
+
+#include "ap_config.h"
+
+AP_DECLARE_HOOK(int,optional_hook_test,(const char *))
+
+#endif /* def MOD_OPTIONAL_HOOK_EXPORT_H */
diff --git a/modules/test/mod_optional_hook_import.c b/modules/test/mod_optional_hook_import.c
new file mode 100644
index 0000000..f921d64
--- /dev/null
+++ b/modules/test/mod_optional_hook_import.c
@@ -0,0 +1,45 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "mod_optional_hook_export.h"
+
+static int ImportOptionalHookTestHook(const char *szStr)
+{
+ ap_log_error(APLOG_MARK,APLOG_DEBUG,OK,NULL, APLOGNO(01866)
+ "Optional hook test said: %s", szStr);
+
+ return OK;
+}
+
+static void ImportRegisterHooks(apr_pool_t *p)
+{
+ AP_OPTIONAL_HOOK(optional_hook_test,ImportOptionalHookTestHook,NULL,
+ NULL,APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(optional_hook_import) =
+{
+ STANDARD20_MODULE_STUFF,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ ImportRegisterHooks
+};
diff --git a/modules/tls/Makefile.in b/modules/tls/Makefile.in
new file mode 100644
index 0000000..4395bc3
--- /dev/null
+++ b/modules/tls/Makefile.in
@@ -0,0 +1,20 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# standard stuff
+#
+
+include $(top_srcdir)/build/special.mk
diff --git a/modules/tls/config2.m4 b/modules/tls/config2.m4
new file mode 100644
index 0000000..af97178
--- /dev/null
+++ b/modules/tls/config2.m4
@@ -0,0 +1,172 @@
+dnl Licensed to the Apache Software Foundation (ASF) under one or more
+dnl contributor license agreements. See the NOTICE file distributed with
+dnl this work for additional information regarding copyright ownership.
+dnl The ASF licenses this file to You under the Apache License, Version 2.0
+dnl (the "License"); you may not use this file except in compliance with
+dnl the License. You may obtain a copy of the License at
+dnl
+dnl http://www.apache.org/licenses/LICENSE-2.0
+dnl
+dnl Unless required by applicable law or agreed to in writing, software
+dnl distributed under the License is distributed on an "AS IS" BASIS,
+dnl WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+dnl See the License for the specific language governing permissions and
+dnl limitations under the License.
+
+dnl # start of module specific part
+APACHE_MODPATH_INIT(tls)
+
+dnl # list of module object files
+tls_objs="dnl
+mod_tls.lo dnl
+tls_cache.lo dnl
+tls_cert.lo dnl
+tls_conf.lo dnl
+tls_core.lo dnl
+tls_filter.lo dnl
+tls_ocsp.lo dnl
+tls_proto.lo dnl
+tls_util.lo dnl
+tls_var.lo dnl
+"
+
+dnl
+dnl APACHE_CHECK_TLS
+dnl
+dnl Configure for rustls, giving preference to
+dnl "--with-rustls=<path>" if it was specified.
+dnl
+AC_DEFUN([APACHE_CHECK_RUSTLS],[
+ AC_CACHE_CHECK([for rustls], [ac_cv_rustls], [
+ dnl initialise the variables we use
+ ac_cv_rustls=no
+ ap_rustls_found=""
+ ap_rustls_base=""
+ ap_rustls_libs=""
+
+ dnl Determine the rustls base directory, if any
+ AC_MSG_CHECKING([for user-provided rustls base directory])
+ AC_ARG_WITH(rustls, APACHE_HELP_STRING(--with-rustls=PATH, rustls installation directory), [
+ dnl If --with-rustls specifies a directory, we use that directory
+ if test "x$withval" != "xyes" -a "x$withval" != "x"; then
+ dnl This ensures $withval is actually a directory and that it is absolute
+ ap_rustls_base="`cd $withval ; pwd`"
+ fi
+ ])
+ if test "x$ap_rustls_base" = "x"; then
+ AC_MSG_RESULT(none)
+ else
+ AC_MSG_RESULT($ap_rustls_base)
+ fi
+
+ dnl Run header and version checks
+ saved_CPPFLAGS="$CPPFLAGS"
+ saved_LIBS="$LIBS"
+ saved_LDFLAGS="$LDFLAGS"
+
+ dnl Before doing anything else, load in pkg-config variables
+ if test -n "$PKGCONFIG"; then
+ saved_PKG_CONFIG_PATH="$PKG_CONFIG_PATH"
+ AC_MSG_CHECKING([for pkg-config along $PKG_CONFIG_PATH])
+ if test "x$ap_rustls_base" != "x" ; then
+ if test -f "${ap_rustls_base}/lib/pkgconfig/librustls.pc"; then
+ dnl Ensure that the given path is used by pkg-config too, otherwise
+ dnl the system librustls.pc might be picked up instead.
+ PKG_CONFIG_PATH="${ap_rustls_base}/lib/pkgconfig${PKG_CONFIG_PATH+:}${PKG_CONFIG_PATH}"
+ export PKG_CONFIG_PATH
+ elif test -f "${ap_rustls_base}/lib64/pkgconfig/librustls.pc"; then
+ dnl Ensure that the given path is used by pkg-config too, otherwise
+ dnl the system librustls.pc might be picked up instead.
+ PKG_CONFIG_PATH="${ap_rustls_base}/lib64/pkgconfig${PKG_CONFIG_PATH+:}${PKG_CONFIG_PATH}"
+ export PKG_CONFIG_PATH
+ fi
+ fi
+ ap_rustls_libs="`$PKGCONFIG $PKGCONFIG_LIBOPTS --libs-only-l --silence-errors librustls`"
+ if test $? -eq 0; then
+ ap_rustls_found="yes"
+ pkglookup="`$PKGCONFIG --cflags-only-I librustls`"
+ APR_ADDTO(CPPFLAGS, [$pkglookup])
+ APR_ADDTO(MOD_CFLAGS, [$pkglookup])
+ pkglookup="`$PKGCONFIG $PKGCONFIG_LIBOPTS --libs-only-L librustls`"
+ APR_ADDTO(LDFLAGS, [$pkglookup])
+ APR_ADDTO(MOD_LDFLAGS, [$pkglookup])
+ pkglookup="`$PKGCONFIG $PKGCONFIG_LIBOPTS --libs-only-other librustls`"
+ APR_ADDTO(LDFLAGS, [$pkglookup])
+ APR_ADDTO(MOD_LDFLAGS, [$pkglookup])
+ fi
+ PKG_CONFIG_PATH="$saved_PKG_CONFIG_PATH"
+ fi
+
+ dnl fall back to the user-supplied directory if not found via pkg-config
+ if test "x$ap_rustls_base" != "x" -a "x$ap_rustls_found" = "x"; then
+ APR_ADDTO(CPPFLAGS, [-I$ap_rustls_base/include])
+ APR_ADDTO(MOD_CFLAGS, [-I$ap_rustls_base/include])
+ APR_ADDTO(LDFLAGS, [-L$ap_rustls_base/lib])
+ APR_ADDTO(MOD_LDFLAGS, [-L$ap_rustls_base/lib])
+ if test "x$ap_platform_runtime_link_flag" != "x"; then
+ APR_ADDTO(LDFLAGS, [$ap_platform_runtime_link_flag$ap_rustls_base/lib])
+ APR_ADDTO(MOD_LDFLAGS, [$ap_platform_runtime_link_flag$ap_rustls_base/lib])
+ fi
+ fi
+
+ AC_MSG_CHECKING([for rustls version >= 0.8.2])
+ AC_TRY_COMPILE([#include <rustls.h>],[
+rustls_version();
+],
+ [AC_MSG_RESULT(OK)
+ ac_cv_rustls=yes],
+ [AC_MSG_RESULT(FAILED)])
+
+ dnl restore
+ CPPFLAGS="$saved_CPPFLAGS"
+ LIBS="$saved_LIBS"
+ LDFLAGS="$saved_LDFLAGS"
+ ])
+ if test "x$ac_cv_rustls" = "xyes"; then
+ AC_DEFINE(HAVE_RUSTLS, 1, [Define if rustls is available])
+ fi
+])
+
+
+dnl # hook module into the Autoconf mechanism (--enable-http2)
+APACHE_MODULE(tls, [TLS protocol handling using rustls. Implemented by mod_tls.
+This module requires a librustls installation.
+See --with-rustls on how to manage non-standard locations. This module
+is usually linked shared and requires loading. ], $tls_objs, , most, [
+ APACHE_CHECK_RUSTLS
+ if test "$ac_cv_rustls" = "yes" ; then
+ if test "x$enable_tls" = "xshared"; then
+ case `uname` in
+ "Darwin")
+ MOD_TLS_LINK_LIBS="-lrustls -framework Security -framework Foundation"
+ ;;
+ *)
+ MOD_TLS_LINK_LIBS="-lrustls"
+ ;;
+ esac
+
+ # Some rustls versions need an extra -lm when linked
+ # See https://github.com/rustls/rustls-ffi/issues/133
+ rustls_version=`rustc --version`
+ case "$rustls_version" in
+ *1.55*) need_lm="yes" ;;
+ *1.56*) need_lm="yes" ;;
+ *1.57*) need_lm="yes" ;;
+ esac
+ if test "$need_lm" = "yes" ; then
+ MOD_TLS_LINK_LIBS="$MOD_TLS_LINK_LIBS -lm"
+ fi
+
+ # The only symbol which needs to be exported is the module
+ # structure, so ask libtool to hide everything else:
+ APR_ADDTO(MOD_TLS_LDADD, [$MOD_TLS_LINK_LIBS -export-symbols-regex tls_module])
+ fi
+ else
+ enable_tls=no
+ fi
+])
+
+
+dnl # end of module specific part
+APACHE_MODPATH_FINISH
+
diff --git a/modules/tls/mod_tls.c b/modules/tls/mod_tls.c
new file mode 100644
index 0000000..9d79521
--- /dev/null
+++ b/modules/tls/mod_tls.c
@@ -0,0 +1,288 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <assert.h>
+#include <apr_optional.h>
+#include <apr_strings.h>
+
+#include <mpm_common.h>
+#include <httpd.h>
+#include <http_core.h>
+#include <http_connection.h>
+#include <http_log.h>
+#include <http_protocol.h>
+#include <http_ssl.h>
+#include <http_request.h>
+#include <ap_socache.h>
+
+#include <rustls.h>
+
+#include "mod_tls.h"
+#include "tls_conf.h"
+#include "tls_core.h"
+#include "tls_cache.h"
+#include "tls_proto.h"
+#include "tls_filter.h"
+#include "tls_var.h"
+#include "tls_version.h"
+
+#include "mod_proxy.h"
+
+static void tls_hooks(apr_pool_t *pool);
+
+AP_DECLARE_MODULE(tls) = {
+ STANDARD20_MODULE_STUFF,
+ tls_conf_create_dir, /* create per dir config */
+ tls_conf_merge_dir, /* merge per dir config */
+ tls_conf_create_svr, /* create per server config */
+ tls_conf_merge_svr, /* merge per server config (inheritance) */
+ tls_conf_cmds, /* command handlers */
+ tls_hooks,
+#if defined(AP_MODULE_FLAG_NONE)
+ AP_MODULE_FLAG_ALWAYS_MERGE
+#endif
+};
+
+static const char* crustls_version(apr_pool_t *p)
+{
+ struct rustls_str rversion;
+
+ rversion = rustls_version();
+ return apr_pstrndup(p, rversion.data, rversion.len);
+}
+
+static int tls_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp)
+{
+ tls_proto_pre_config(pconf, ptemp);
+ tls_cache_pre_config(pconf, plog, ptemp);
+ return OK;
+}
+
+static apr_status_t tls_post_config(apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ const char *tls_init_key = "mod_tls_init_counter";
+ tls_conf_server_t *sc;
+ void *data = NULL;
+
+ (void)plog;
+ sc = tls_conf_server_get(s);
+ assert(sc);
+ assert(sc->global);
+ sc->global->module_version = "mod_tls/" MOD_TLS_VERSION;
+ sc->global->crustls_version = crustls_version(p);
+
+ apr_pool_userdata_get(&data, tls_init_key, s->process->pool);
+ if (data == NULL) {
+ /* At the first start, httpd makes a config check dry run
+ * to see if the config is ok in principle.
+ */
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, "post config dry run");
+ apr_pool_userdata_set((const void *)1, tls_init_key,
+ apr_pool_cleanup_null, s->process->pool);
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(10365)
+ "%s (%s), initializing...",
+ sc->global->module_version,
+ sc->global->crustls_version);
+ }
+
+ return tls_core_init(p, ptemp, s);
+}
+
+static apr_status_t tls_post_proxy_config(
+ apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
+{
+ tls_conf_server_t *sc = tls_conf_server_get(s);
+ (void)plog;
+ sc->global->mod_proxy_post_config_done = 1;
+ return tls_core_init(p, ptemp, s);
+}
+
+#if AP_MODULE_MAGIC_AT_LEAST(20120211, 109)
+static int tls_ssl_outgoing(conn_rec *c, ap_conf_vector_t *dir_conf, int enable_ssl)
+{
+ /* we are not handling proxy connections - for now */
+ tls_core_conn_bind(c, dir_conf);
+ if (enable_ssl && tls_core_setup_outgoing(c) == OK) {
+ ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, c->base_server,
+ "accepted ssl_bind_outgoing(enable=%d) for %s",
+ enable_ssl, c->base_server->server_hostname);
+ return OK;
+ }
+ tls_core_conn_disable(c);
+ ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, c->base_server,
+ "declined ssl_bind_outgoing(enable=%d) for %s",
+ enable_ssl, c->base_server->server_hostname);
+ return DECLINED;
+}
+
+#else /* #if AP_MODULE_MAGIC_AT_LEAST(20120211, 109) */
+
+APR_DECLARE_OPTIONAL_FN(int, ssl_proxy_enable, (conn_rec *));
+APR_DECLARE_OPTIONAL_FN(int, ssl_engine_disable, (conn_rec *));
+APR_DECLARE_OPTIONAL_FN(int, ssl_engine_set, (conn_rec *,
+ ap_conf_vector_t *,
+ int proxy, int enable));
+static APR_OPTIONAL_FN_TYPE(ssl_engine_set) *module_ssl_engine_set;
+
+static int ssl_engine_set(
+ conn_rec *c, ap_conf_vector_t *dir_conf, int proxy, int enable)
+{
+ ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, c->base_server,
+ "ssl_engine_set(proxy=%d, enable=%d) for %s",
+ proxy, enable, c->base_server->server_hostname);
+ tls_core_conn_bind(c, dir_conf);
+ if (enable && tls_core_setup_outgoing(c) == OK) {
+ if (module_ssl_engine_set) {
+ module_ssl_engine_set(c, dir_conf, proxy, 0);
+ }
+ return 1;
+ }
+ if (proxy || !enable) {
+ /* we are not handling proxy connections - for now */
+ tls_core_conn_disable(c);
+ }
+ if (module_ssl_engine_set) {
+ return module_ssl_engine_set(c, dir_conf, proxy, enable);
+ }
+ return 0;
+}
+
+static int ssl_proxy_enable(conn_rec *c)
+{
+ return ssl_engine_set(c, NULL, 1, 1);
+}
+
+static int ssl_engine_disable(conn_rec *c)
+{
+ return ssl_engine_set(c, NULL, 0, 0);
+}
+
+static apr_status_t tls_post_config_proxy_ssl(
+ apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
+{
+ if (1) {
+ const char *tls_init_key = "mod_tls_proxy_ssl_counter";
+ void *data = NULL;
+ APR_OPTIONAL_FN_TYPE(ssl_engine_set) *fn_ssl_engine_set;
+
+ (void)p;
+ (void)plog;
+ (void)ptemp;
+ apr_pool_userdata_get(&data, tls_init_key, s->process->pool);
+ if (data == NULL) {
+ /* At the first start, httpd makes a config check dry run
+ * to see if the config is ok in principle.
+ */
+ apr_pool_userdata_set((const void *)1, tls_init_key,
+ apr_pool_cleanup_null, s->process->pool);
+ return APR_SUCCESS;
+ }
+
+ /* mod_ssl (if so loaded, has registered its optional functions.
+ * When mod_proxy runs in post-config, it looks up those functions and uses
+ * them to manipulate SSL status for backend connections.
+ * We provide our own implementations to avoid becoming active on such
+ * connections for now.
+ * */
+ fn_ssl_engine_set = APR_RETRIEVE_OPTIONAL_FN(ssl_engine_set);
+ module_ssl_engine_set = (fn_ssl_engine_set
+ && fn_ssl_engine_set != ssl_engine_set)? fn_ssl_engine_set : NULL;
+ APR_REGISTER_OPTIONAL_FN(ssl_engine_set);
+ APR_REGISTER_OPTIONAL_FN(ssl_proxy_enable);
+ APR_REGISTER_OPTIONAL_FN(ssl_engine_disable);
+ }
+ return APR_SUCCESS;
+}
+#endif /* #if AP_MODULE_MAGIC_AT_LEAST(20120211, 109) */
+
+static void tls_init_child(apr_pool_t *p, server_rec *s)
+{
+ tls_cache_init_child(p, s);
+}
+
+static int hook_pre_connection(conn_rec *c, void *csd)
+{
+ (void)csd; /* mpm specific socket data, not used */
+
+ /* are we on a primary connection? */
+ if (c->master) return DECLINED;
+
+ /* Decide connection TLS stats and install our
+ * input/output filters for handling TLS/application data
+ * if enabled.
+ */
+ return tls_filter_pre_conn_init(c);
+}
+
+static int hook_connection(conn_rec* c)
+{
+ tls_filter_conn_init(c);
+ /* we do *not* take over. we are not processing requests. */
+ return DECLINED;
+}
+
+static const char *tls_hook_http_scheme(const request_rec *r)
+{
+ return (tls_conn_check_ssl(r->connection) == OK)? "https" : NULL;
+}
+
+static apr_port_t tls_hook_default_port(const request_rec *r)
+{
+ return (tls_conn_check_ssl(r->connection) == OK) ? 443 : 0;
+}
+
+static const char* const mod_http2[] = { "mod_http2.c", NULL};
+
+static void tls_hooks(apr_pool_t *pool)
+{
+ /* If our request check denies further processing, certain things
+ * need to be in place for the response to be correctly generated. */
+ static const char *dep_req_check[] = { "mod_setenvif.c", NULL };
+ static const char *dep_proxy[] = { "mod_proxy.c", NULL };
+
+ ap_log_perror(APLOG_MARK, APLOG_TRACE1, 0, pool, "installing hooks");
+ tls_filter_register(pool);
+
+ ap_hook_pre_config(tls_pre_config, NULL,NULL, APR_HOOK_MIDDLE);
+ /* run post-config hooks one before, one after mod_proxy, as the
+ * mod_proxy's own one calls us in its "section_post_config" hook. */
+ ap_hook_post_config(tls_post_config, NULL, dep_proxy, APR_HOOK_MIDDLE);
+ APR_OPTIONAL_HOOK(proxy, section_post_config,
+ tls_proxy_section_post_config, NULL, NULL,
+ APR_HOOK_MIDDLE);
+ ap_hook_post_config(tls_post_proxy_config, dep_proxy, NULL, APR_HOOK_MIDDLE);
+ ap_hook_child_init(tls_init_child, NULL,NULL, APR_HOOK_MIDDLE);
+ /* connection things */
+ ap_hook_pre_connection(hook_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_process_connection(hook_connection, NULL, mod_http2, APR_HOOK_MIDDLE);
+ /* request things */
+ ap_hook_default_port(tls_hook_default_port, NULL,NULL, APR_HOOK_MIDDLE);
+ ap_hook_http_scheme(tls_hook_http_scheme, NULL,NULL, APR_HOOK_MIDDLE);
+ ap_hook_post_read_request(tls_core_request_check, dep_req_check, NULL, APR_HOOK_MIDDLE);
+ ap_hook_fixups(tls_var_request_fixup, NULL,NULL, APR_HOOK_MIDDLE);
+
+ ap_hook_ssl_conn_is_ssl(tls_conn_check_ssl, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_ssl_var_lookup(tls_var_lookup, NULL, NULL, APR_HOOK_MIDDLE);
+
+#if AP_MODULE_MAGIC_AT_LEAST(20120211, 109)
+ ap_hook_ssl_bind_outgoing(tls_ssl_outgoing, NULL, NULL, APR_HOOK_MIDDLE);
+#else
+ ap_hook_post_config(tls_post_config_proxy_ssl, NULL, dep_proxy, APR_HOOK_MIDDLE);
+#endif
+
+}
diff --git a/modules/tls/mod_tls.h b/modules/tls/mod_tls.h
new file mode 100644
index 0000000..db7dc41
--- /dev/null
+++ b/modules/tls/mod_tls.h
@@ -0,0 +1,19 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef mod_tls_h
+#define mod_tls_h
+
+#endif /* mod_tls_h */ \ No newline at end of file
diff --git a/modules/tls/tls_cache.c b/modules/tls/tls_cache.c
new file mode 100644
index 0000000..de4be18
--- /dev/null
+++ b/modules/tls/tls_cache.c
@@ -0,0 +1,310 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <assert.h>
+#include <apr_lib.h>
+#include <apr_strings.h>
+#include <apr_hash.h>
+
+#include <httpd.h>
+#include <http_connection.h>
+#include <http_log.h>
+#include <ap_socache.h>
+#include <util_mutex.h>
+
+#include <rustls.h>
+
+#include "tls_conf.h"
+#include "tls_core.h"
+#include "tls_cache.h"
+
+extern module AP_MODULE_DECLARE_DATA tls_module;
+APLOG_USE_MODULE(tls);
+
+#define TLS_CACHE_DEF_PROVIDER "shmcb"
+#define TLS_CACHE_DEF_DIR "tls"
+#define TLS_CACHE_DEF_FILE "session_cache"
+#define TLS_CACHE_DEF_SIZE 512000
+
+static const char *cache_provider_unknown(const char *name, apr_pool_t *p)
+{
+ apr_array_header_t *known;
+ const char *known_names;
+
+ known = ap_list_provider_names(p, AP_SOCACHE_PROVIDER_GROUP,
+ AP_SOCACHE_PROVIDER_VERSION);
+ known_names = apr_array_pstrcat(p, known, ',');
+ return apr_psprintf(p, "cache type '%s' not supported "
+ "(known names: %s). Maybe you need to load the "
+ "appropriate socache module (mod_socache_%s?).",
+ name, known_names, name);
+}
+
+void tls_cache_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp)
+{
+ (void)plog;
+ (void)ptemp;
+ /* we make this visible, in case someone wants to configure it.
+ * this does not mean that we will really use it, which is determined
+ * by configuration and cache provider capabilities. */
+ ap_mutex_register(pconf, TLS_SESSION_CACHE_MUTEX_TYPE, NULL, APR_LOCK_DEFAULT, 0);
+}
+
+static const char *cache_init(tls_conf_global_t *gconf, apr_pool_t *p, apr_pool_t *ptemp)
+{
+ const char *err = NULL;
+ const char *name, *args = NULL;
+ apr_status_t rv;
+
+ if (gconf->session_cache) {
+ goto cleanup;
+ }
+ else if (!apr_strnatcasecmp("none", gconf->session_cache_spec)) {
+ gconf->session_cache_provider = NULL;
+ gconf->session_cache = NULL;
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, gconf->ap_server, APLOGNO(10346)
+ "session cache explicitly disabled");
+ goto cleanup;
+ }
+ else if (!apr_strnatcasecmp("default", gconf->session_cache_spec)) {
+ const char *path = TLS_CACHE_DEF_DIR;
+
+#if AP_MODULE_MAGIC_AT_LEAST(20180906, 2)
+ path = ap_state_dir_relative(p, path);
+#endif
+ gconf->session_cache_spec = apr_psprintf(p, "%s:%s/%s(%ld)",
+ TLS_CACHE_DEF_PROVIDER, path, TLS_CACHE_DEF_FILE, (long)TLS_CACHE_DEF_SIZE);
+ gconf->session_cache_spec = "shmcb:mod_tls-sesss(64000)";
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, gconf->ap_server, APLOGNO(10347)
+ "Using session cache: %s", gconf->session_cache_spec);
+ name = gconf->session_cache_spec;
+ args = ap_strchr((char*)name, ':');
+ if (args) {
+ name = apr_pstrmemdup(p, name, (apr_size_t)(args - name));
+ ++args;
+ }
+ gconf->session_cache_provider = ap_lookup_provider(AP_SOCACHE_PROVIDER_GROUP,
+ name, AP_SOCACHE_PROVIDER_VERSION);
+ if (!gconf->session_cache_provider) {
+ err = cache_provider_unknown(name, p);
+ goto cleanup;
+ }
+ err = gconf->session_cache_provider->create(&gconf->session_cache, args, ptemp, p);
+ if (err != NULL) goto cleanup;
+
+ if (gconf->session_cache_provider->flags & AP_SOCACHE_FLAG_NOTMPSAFE
+ && !gconf->session_cache_mutex) {
+ /* we need a global lock to access the cache */
+ rv = ap_global_mutex_create(&gconf->session_cache_mutex, NULL,
+ TLS_SESSION_CACHE_MUTEX_TYPE, NULL, gconf->ap_server, p, 0);
+ if (APR_SUCCESS != rv) {
+ err = apr_psprintf(p, "error setting up global %s mutex: %d",
+ TLS_SESSION_CACHE_MUTEX_TYPE, rv);
+ gconf->session_cache_mutex = NULL;
+ goto cleanup;
+ }
+ }
+
+cleanup:
+ if (NULL != err) {
+ gconf->session_cache_provider = NULL;
+ gconf->session_cache = NULL;
+ }
+ return err;
+}
+
+const char *tls_cache_set_specification(
+ const char *spec, tls_conf_global_t *gconf, apr_pool_t *p, apr_pool_t *ptemp)
+{
+ gconf->session_cache_spec = spec;
+ return cache_init(gconf, p, ptemp);
+}
+
+apr_status_t tls_cache_post_config(apr_pool_t *p, apr_pool_t *ptemp, server_rec *s)
+{
+ tls_conf_server_t *sc = tls_conf_server_get(s);
+ const char *err;
+ apr_status_t rv = APR_SUCCESS;
+
+ err = cache_init(sc->global, p, ptemp);
+ if (err) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(10348)
+ "session cache [%s] could not be initialized, will continue "
+ "without session one. Since this will impact performance, "
+ "consider making use of the 'TLSSessionCache' directive. The "
+ "error was: %s", sc->global->session_cache_spec, err);
+ }
+
+ if (sc->global->session_cache) {
+ struct ap_socache_hints hints;
+
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, "provider init session cache [%s]",
+ sc->global->session_cache_spec);
+ memset(&hints, 0, sizeof(hints));
+ hints.avg_obj_size = 100;
+ hints.avg_id_len = 33;
+ hints.expiry_interval = 30;
+
+ rv = sc->global->session_cache_provider->init(
+ sc->global->session_cache, "mod_tls-sess", &hints, s, p);
+ if (APR_SUCCESS != rv) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10349)
+ "error initializing session cache.");
+ }
+ }
+ return rv;
+}
+
+void tls_cache_init_child(apr_pool_t *p, server_rec *s)
+{
+ tls_conf_server_t *sc = tls_conf_server_get(s);
+ const char *lockfile;
+ apr_status_t rv;
+
+ if (sc->global->session_cache_mutex) {
+ lockfile = apr_global_mutex_lockfile(sc->global->session_cache_mutex);
+ rv = apr_global_mutex_child_init(&sc->global->session_cache_mutex, lockfile, p);
+ if (APR_SUCCESS != rv) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10350)
+ "Cannot reinit %s mutex (file `%s`)",
+ TLS_SESSION_CACHE_MUTEX_TYPE, lockfile? lockfile : "-");
+ }
+ }
+}
+
+void tls_cache_free(server_rec *s)
+{
+ tls_conf_server_t *sc = tls_conf_server_get(s);
+ if (sc->global->session_cache_provider) {
+ sc->global->session_cache_provider->destroy(sc->global->session_cache, s);
+ }
+}
+
+static void tls_cache_lock(tls_conf_global_t *gconf)
+{
+ if (gconf->session_cache_mutex) {
+ apr_status_t rv = apr_global_mutex_lock(gconf->session_cache_mutex);
+ if (APR_SUCCESS != rv) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rv, gconf->ap_server, APLOGNO(10351)
+ "Failed to acquire TLS session cache lock");
+ }
+ }
+}
+
+static void tls_cache_unlock(tls_conf_global_t *gconf)
+{
+ if (gconf->session_cache_mutex) {
+ apr_status_t rv = apr_global_mutex_unlock(gconf->session_cache_mutex);
+ if (APR_SUCCESS != rv) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rv, gconf->ap_server, APLOGNO(10352)
+ "Failed to release TLS session cache lock");
+ }
+ }
+}
+
+static rustls_result tls_cache_get(
+ void *userdata,
+ const rustls_slice_bytes *key,
+ int remove_after,
+ unsigned char *buf,
+ size_t count,
+ size_t *out_n)
+{
+ conn_rec *c = userdata;
+ tls_conf_conn_t *cc = tls_conf_conn_get(c);
+ tls_conf_server_t *sc = tls_conf_server_get(cc->server);
+ apr_status_t rv = APR_ENOENT;
+ unsigned int vlen, klen;
+ const unsigned char *kdata;
+
+ if (!sc->global->session_cache) goto not_found;
+ tls_cache_lock(sc->global);
+
+ kdata = key->data;
+ klen = (unsigned int)key->len;
+ vlen = (unsigned int)count;
+ rv = sc->global->session_cache_provider->retrieve(
+ sc->global->session_cache, cc->server, kdata, klen, buf, &vlen, c->pool);
+
+ if (APLOGctrace4(c)) {
+ apr_ssize_t n = klen;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE4, rv, c, "retrieve key %d[%8x], found %d val",
+ klen, apr_hashfunc_default((const char*)kdata, &n), vlen);
+ }
+ if (remove_after || (APR_SUCCESS != rv && !APR_STATUS_IS_NOTFOUND(rv))) {
+ sc->global->session_cache_provider->remove(
+ sc->global->session_cache, cc->server, key->data, klen, c->pool);
+ }
+
+ tls_cache_unlock(sc->global);
+ if (APR_SUCCESS != rv) goto not_found;
+ cc->session_id_cache_hit = 1;
+ *out_n = count;
+ return RUSTLS_RESULT_OK;
+
+not_found:
+ *out_n = 0;
+ return RUSTLS_RESULT_NOT_FOUND;
+}
+
+static rustls_result tls_cache_put(
+ void *userdata,
+ const rustls_slice_bytes *key,
+ const rustls_slice_bytes *val)
+{
+ conn_rec *c = userdata;
+ tls_conf_conn_t *cc = tls_conf_conn_get(c);
+ tls_conf_server_t *sc = tls_conf_server_get(cc->server);
+ apr_status_t rv = APR_ENOENT;
+ apr_time_t expires_at;
+ unsigned int klen, vlen;
+ const unsigned char *kdata;
+
+ if (!sc->global->session_cache) goto not_stored;
+ tls_cache_lock(sc->global);
+
+ expires_at = apr_time_now() + apr_time_from_sec(300);
+ kdata = key->data;
+ klen = (unsigned int)key->len;
+ vlen = (unsigned int)val->len;
+ rv = sc->global->session_cache_provider->store(sc->global->session_cache, cc->server,
+ kdata, klen, expires_at,
+ (unsigned char*)val->data, vlen, c->pool);
+ if (APLOGctrace4(c)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE4, rv, c,
+ "stored %d key bytes, with %d val bytes", klen, vlen);
+ }
+ tls_cache_unlock(sc->global);
+ if (APR_SUCCESS != rv) goto not_stored;
+ return RUSTLS_RESULT_OK;
+
+not_stored:
+ return RUSTLS_RESULT_NOT_FOUND;
+}
+
+apr_status_t tls_cache_init_server(
+ rustls_server_config_builder *builder, server_rec *s)
+{
+ tls_conf_server_t *sc = tls_conf_server_get(s);
+
+ if (sc && sc->global->session_cache) {
+ ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s, "adding session persistence to rustls");
+ rustls_server_config_builder_set_persistence(
+ builder, tls_cache_get, tls_cache_put);
+ }
+ return APR_SUCCESS;
+}
diff --git a/modules/tls/tls_cache.h b/modules/tls/tls_cache.h
new file mode 100644
index 0000000..64ca077
--- /dev/null
+++ b/modules/tls/tls_cache.h
@@ -0,0 +1,63 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef tls_cache_h
+#define tls_cache_h
+
+/* name of the global session cache mutex, should we need it */
+#define TLS_SESSION_CACHE_MUTEX_TYPE "tls-session-cache"
+
+
+/**
+ * Set the specification of the session cache to use. The syntax is
+ * "default|none|<provider_name>(:<arguments>)?"
+ *
+ * @param spec the cache specification
+ * @param gconf the modules global configuration
+ * @param p pool for permanent allocations
+ * @param ptemp pool for temporary allocations
+ * @return NULL on success or an error message
+ */
+const char *tls_cache_set_specification(
+ const char *spec, tls_conf_global_t *gconf, apr_pool_t *p, apr_pool_t *ptemp);
+
+/**
+ * Setup before configuration runs, announces our potential global mutex.
+ */
+void tls_cache_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp);
+
+/**
+ * Verify the cache settings at the end of the configuration and
+ * create the default session cache, if not already done.
+ */
+apr_status_t tls_cache_post_config(apr_pool_t *p, apr_pool_t *ptemp, server_rec *s);
+
+/**
+ * Started a new child, make sure that global mutex we might use is set up.
+ */
+void tls_cache_init_child(apr_pool_t *p, server_rec *s);
+
+/**
+ * Free all cache related resources.
+ */
+void tls_cache_free(server_rec *s);
+
+/**
+ * Initialize the session store for the server's config builder.
+ */
+apr_status_t tls_cache_init_server(
+ rustls_server_config_builder *builder, server_rec *s);
+
+#endif /* tls_cache_h */ \ No newline at end of file
diff --git a/modules/tls/tls_cert.c b/modules/tls/tls_cert.c
new file mode 100644
index 0000000..624535a
--- /dev/null
+++ b/modules/tls/tls_cert.c
@@ -0,0 +1,564 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <assert.h>
+#include <apr_lib.h>
+#include <apr_encode.h>
+#include <apr_strings.h>
+
+#include <httpd.h>
+#include <http_connection.h>
+#include <http_core.h>
+#include <http_log.h>
+
+#include <rustls.h>
+
+#include "tls_cert.h"
+#include "tls_util.h"
+
+extern module AP_MODULE_DECLARE_DATA tls_module;
+APLOG_USE_MODULE(tls);
+
+
+apr_status_t tls_cert_load_pem(
+ apr_pool_t *p, const tls_cert_spec_t *cert, tls_cert_pem_t **ppem)
+{
+ apr_status_t rv;
+ const char *fpath;
+ tls_cert_pem_t *cpem;
+
+ ap_assert(cert->cert_file);
+ cpem = apr_pcalloc(p, sizeof(*cpem));
+ fpath = ap_server_root_relative(p, cert->cert_file);
+ if (NULL == fpath) {
+ rv = APR_ENOENT; goto cleanup;
+ }
+ rv = tls_util_file_load(p, fpath, 0, 100*1024, &cpem->cert_pem);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ if (cert->pkey_file) {
+ fpath = ap_server_root_relative(p, cert->pkey_file);
+ if (NULL == fpath) {
+ rv = APR_ENOENT; goto cleanup;
+ }
+ rv = tls_util_file_load(p, fpath, 0, 100*1024, &cpem->pkey_pem);
+ if (APR_SUCCESS != rv) goto cleanup;
+ }
+ else {
+ cpem->pkey_pem = cpem->cert_pem;
+ }
+cleanup:
+ *ppem = (APR_SUCCESS == rv)? cpem : NULL;
+ return rv;
+}
+
+#define PEM_IN_CHUNK 48 /* PEM demands at most 64 chars per line */
+
+static apr_status_t tls_der_to_pem(
+ const char **ppem, apr_pool_t *p,
+ const unsigned char *der_data, apr_size_t der_len,
+ const char *header, const char *footer)
+{
+ apr_status_t rv = APR_SUCCESS;
+ char *pem = NULL, *s;
+ apr_size_t b64_len, n, hd_len, ft_len;
+ apr_ssize_t in_len, i;
+
+ if (der_len > INT_MAX) {
+ rv = APR_ENOMEM;
+ goto cleanup;
+ }
+ in_len = (apr_ssize_t)der_len;
+ rv = apr_encode_base64(NULL, (const char*)der_data, in_len, APR_ENCODE_NONE, &b64_len);
+ if (APR_SUCCESS != rv) goto cleanup;
+ if (b64_len > INT_MAX) {
+ rv = APR_ENOMEM;
+ goto cleanup;
+ }
+ hd_len = header? strlen(header) : 0;
+ ft_len = footer? strlen(footer) : 0;
+ s = pem = apr_pcalloc(p,
+ + b64_len + (der_len/PEM_IN_CHUNK) + 1 /* \n per chunk */
+ + hd_len +1 + ft_len + 1 /* adding \n */
+ + 1); /* NUL-terminated */
+ if (header) {
+ strcpy(s, header);
+ s += hd_len;
+ *s++ = '\n';
+ }
+ for (i = 0; in_len > 0; i += PEM_IN_CHUNK, in_len -= PEM_IN_CHUNK) {
+ rv = apr_encode_base64(s,
+ (const char*)der_data + i, in_len > PEM_IN_CHUNK? PEM_IN_CHUNK : in_len,
+ APR_ENCODE_NONE, &n);
+ s += n;
+ *s++ = '\n';
+ }
+ if (footer) {
+ strcpy(s, footer);
+ s += ft_len;
+ *s++ = '\n';
+ }
+cleanup:
+ *ppem = (APR_SUCCESS == rv)? pem : NULL;
+ return rv;
+}
+
+#define PEM_CERT_HD "-----BEGIN CERTIFICATE-----"
+#define PEM_CERT_FT "-----END CERTIFICATE-----"
+
+apr_status_t tls_cert_to_pem(const char **ppem, apr_pool_t *p, const rustls_certificate *cert)
+{
+ const unsigned char* der_data;
+ size_t der_len;
+ rustls_result rr = RUSTLS_RESULT_OK;
+ apr_status_t rv = APR_SUCCESS;
+ const char *pem = NULL;
+
+ rr = rustls_certificate_get_der(cert, &der_data, &der_len);
+ if (RUSTLS_RESULT_OK != rr) goto cleanup;
+ rv = tls_der_to_pem(&pem, p, der_data, der_len, PEM_CERT_HD, PEM_CERT_FT);
+cleanup:
+ if (RUSTLS_RESULT_OK != rr) {
+ rv = tls_util_rustls_error(p, rr, NULL);
+ }
+ *ppem = (APR_SUCCESS == rv)? pem : NULL;
+ return rv;
+}
+
+static void nullify_key_pem(tls_cert_pem_t *pems)
+{
+ if (pems->pkey_pem.len) {
+ memset((void*)pems->pkey_pem.data, 0, pems->pkey_pem.len);
+ }
+}
+
+static apr_status_t make_certified_key(
+ apr_pool_t *p, const char *name,
+ const tls_data_t *cert_pem, const tls_data_t *pkey_pem,
+ const rustls_certified_key **pckey)
+{
+ const rustls_certified_key *ckey = NULL;
+ rustls_result rr = RUSTLS_RESULT_OK;
+ apr_status_t rv = APR_SUCCESS;
+
+ rr = rustls_certified_key_build(
+ cert_pem->data, cert_pem->len,
+ pkey_pem->data, pkey_pem->len,
+ &ckey);
+
+ if (RUSTLS_RESULT_OK != rr) {
+ const char *err_descr;
+ rv = tls_util_rustls_error(p, rr, &err_descr);
+ ap_log_perror(APLOG_MARK, APLOG_ERR, rv, p, APLOGNO(10363)
+ "Failed to load certified key %s: [%d] %s",
+ name, (int)rr, err_descr);
+ }
+ if (APR_SUCCESS == rv) {
+ *pckey = ckey;
+ }
+ else if (ckey) {
+ rustls_certified_key_free(ckey);
+ }
+ return rv;
+}
+
+apr_status_t tls_cert_load_cert_key(
+ apr_pool_t *p, const tls_cert_spec_t *spec,
+ const char **pcert_pem, const rustls_certified_key **pckey)
+{
+ apr_status_t rv = APR_SUCCESS;
+
+ if (spec->cert_file) {
+ tls_cert_pem_t *pems;
+
+ rv = tls_cert_load_pem(p, spec, &pems);
+ if (APR_SUCCESS != rv) goto cleanup;
+ if (pcert_pem) *pcert_pem = tls_data_to_str(p, &pems->cert_pem);
+ rv = make_certified_key(p, spec->cert_file, &pems->cert_pem, &pems->pkey_pem, pckey);
+ /* dont want them hanging around in memory unnecessarily. */
+ nullify_key_pem(pems);
+ }
+ else if (spec->cert_pem) {
+ tls_data_t pkey_pem, pem;
+ pem = tls_data_from_str(spec->cert_pem);
+ if (spec->pkey_pem) {
+ pkey_pem = tls_data_from_str(spec->pkey_pem);
+ }
+ else {
+ pkey_pem = pem;
+ }
+ if (pcert_pem) *pcert_pem = spec->cert_pem;
+ rv = make_certified_key(p, "memory", &pem, &pkey_pem, pckey);
+ /* pems provided from outside are responsibility of the caller */
+ }
+ else {
+ rv = APR_ENOENT; goto cleanup;
+ }
+cleanup:
+ return rv;
+}
+
+typedef struct {
+ const char *id;
+ const char *cert_pem;
+ server_rec *server;
+ const rustls_certified_key *certified_key;
+} tls_cert_reg_entry_t;
+
+static int reg_entry_cleanup(void *ctx, const void *key, apr_ssize_t klen, const void *val)
+{
+ tls_cert_reg_entry_t *entry = (tls_cert_reg_entry_t*)val;
+ (void)ctx; (void)key; (void)klen;
+ if (entry->certified_key) {
+ rustls_certified_key_free(entry->certified_key);
+ entry->certified_key = NULL;
+ }
+ return 1;
+}
+
+static apr_status_t reg_cleanup(void *data)
+{
+ tls_cert_reg_t *reg = data;
+ if (reg->id2entry) {
+ apr_hash_do(reg_entry_cleanup, reg, reg->id2entry);
+ apr_hash_clear(reg->id2entry);
+ if (reg->key2entry) apr_hash_clear(reg->key2entry);
+ }
+ return APR_SUCCESS;
+}
+
+tls_cert_reg_t *tls_cert_reg_make(apr_pool_t *p)
+{
+ tls_cert_reg_t *reg;
+
+ reg = apr_pcalloc(p, sizeof(*reg));
+ reg->pool = p;
+ reg->id2entry = apr_hash_make(p);
+ reg->key2entry = apr_hash_make(p);
+ apr_pool_cleanup_register(p, reg, reg_cleanup, apr_pool_cleanup_null);
+ return reg;
+}
+
+apr_size_t tls_cert_reg_count(tls_cert_reg_t *reg)
+{
+ return apr_hash_count(reg->id2entry);
+}
+
+static const char *cert_spec_to_id(const tls_cert_spec_t *spec)
+{
+ if (spec->cert_file) return spec->cert_file;
+ if (spec->cert_pem) return spec->cert_pem;
+ return NULL;
+}
+
+apr_status_t tls_cert_reg_get_certified_key(
+ tls_cert_reg_t *reg, server_rec *s, const tls_cert_spec_t *spec,
+ const rustls_certified_key **pckey)
+{
+ apr_status_t rv = APR_SUCCESS;
+ const char *id;
+ tls_cert_reg_entry_t *entry;
+
+ id = cert_spec_to_id(spec);
+ assert(id);
+ entry = apr_hash_get(reg->id2entry, id, APR_HASH_KEY_STRING);
+ if (!entry) {
+ const rustls_certified_key *certified_key;
+ const char *cert_pem;
+ rv = tls_cert_load_cert_key(reg->pool, spec, &cert_pem, &certified_key);
+ if (APR_SUCCESS != rv) goto cleanup;
+ entry = apr_pcalloc(reg->pool, sizeof(*entry));
+ entry->id = apr_pstrdup(reg->pool, id);
+ entry->cert_pem = cert_pem;
+ entry->server = s;
+ entry->certified_key = certified_key;
+ apr_hash_set(reg->id2entry, entry->id, APR_HASH_KEY_STRING, entry);
+ /* associates the pointer value */
+ apr_hash_set(reg->key2entry, &entry->certified_key, sizeof(entry->certified_key), entry);
+ }
+
+cleanup:
+ if (APR_SUCCESS == rv) {
+ *pckey = entry->certified_key;
+ }
+ else {
+ *pckey = NULL;
+ }
+ return rv;
+}
+
+typedef struct {
+ void *userdata;
+ tls_cert_reg_visitor *visitor;
+} reg_visit_ctx_t;
+
+static int reg_visit(void *vctx, const void *key, apr_ssize_t klen, const void *val)
+{
+ reg_visit_ctx_t *ctx = vctx;
+ tls_cert_reg_entry_t *entry = (tls_cert_reg_entry_t*)val;
+
+ (void)key; (void)klen;
+ return ctx->visitor(ctx->userdata, entry->server, entry->id, entry->cert_pem, entry->certified_key);
+}
+
+void tls_cert_reg_do(
+ tls_cert_reg_visitor *visitor, void *userdata, tls_cert_reg_t *reg)
+{
+ reg_visit_ctx_t ctx;
+ ctx.visitor = visitor;
+ ctx.userdata = userdata;
+ apr_hash_do(reg_visit, &ctx, reg->id2entry);
+}
+
+const char *tls_cert_reg_get_id(tls_cert_reg_t *reg, const rustls_certified_key *certified_key)
+{
+ tls_cert_reg_entry_t *entry;
+
+ entry = apr_hash_get(reg->key2entry, &certified_key, sizeof(certified_key));
+ return entry? entry->id : NULL;
+}
+
+apr_status_t tls_cert_load_root_store(
+ apr_pool_t *p, const char *store_file, rustls_root_cert_store **pstore)
+{
+ const char *fpath;
+ tls_data_t pem;
+ rustls_root_cert_store *store = NULL;
+ rustls_result rr = RUSTLS_RESULT_OK;
+ apr_pool_t *ptemp = NULL;
+ apr_status_t rv;
+
+ ap_assert(store_file);
+
+ rv = apr_pool_create(&ptemp, p);
+ if (APR_SUCCESS != rv) goto cleanup;
+ apr_pool_tag(ptemp, "tls_load_root_cert_store");
+ fpath = ap_server_root_relative(ptemp, store_file);
+ if (NULL == fpath) {
+ rv = APR_ENOENT; goto cleanup;
+ }
+ /* we use this for client auth CAs. 1MB seems large enough. */
+ rv = tls_util_file_load(ptemp, fpath, 0, 1024*1024, &pem);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ store = rustls_root_cert_store_new();
+ rr = rustls_root_cert_store_add_pem(store, pem.data, pem.len, 1);
+ if (RUSTLS_RESULT_OK != rr) goto cleanup;
+
+cleanup:
+ if (RUSTLS_RESULT_OK != rr) {
+ const char *err_descr;
+ rv = tls_util_rustls_error(p, rr, &err_descr);
+ ap_log_perror(APLOG_MARK, APLOG_ERR, rv, p, APLOGNO(10364)
+ "Failed to load root store %s: [%d] %s",
+ store_file, (int)rr, err_descr);
+ }
+ if (APR_SUCCESS == rv) {
+ *pstore = store;
+ }
+ else {
+ *pstore = NULL;
+ if (store) rustls_root_cert_store_free(store);
+ }
+ if (ptemp) apr_pool_destroy(ptemp);
+ return rv;
+}
+
+typedef struct {
+ const char *id;
+ rustls_root_cert_store *store;
+} tls_cert_root_stores_entry_t;
+
+static int stores_entry_cleanup(void *ctx, const void *key, apr_ssize_t klen, const void *val)
+{
+ tls_cert_root_stores_entry_t *entry = (tls_cert_root_stores_entry_t*)val;
+ (void)ctx; (void)key; (void)klen;
+ if (entry->store) {
+ rustls_root_cert_store_free(entry->store);
+ entry->store = NULL;
+ }
+ return 1;
+}
+
+static apr_status_t stores_cleanup(void *data)
+{
+ tls_cert_root_stores_t *stores = data;
+ tls_cert_root_stores_clear(stores);
+ return APR_SUCCESS;
+}
+
+tls_cert_root_stores_t *tls_cert_root_stores_make(apr_pool_t *p)
+{
+ tls_cert_root_stores_t *stores;
+
+ stores = apr_pcalloc(p, sizeof(*stores));
+ stores->pool = p;
+ stores->file2store = apr_hash_make(p);
+ apr_pool_cleanup_register(p, stores, stores_cleanup, apr_pool_cleanup_null);
+ return stores;
+}
+
+void tls_cert_root_stores_clear(tls_cert_root_stores_t *stores)
+{
+ if (stores->file2store) {
+ apr_hash_do(stores_entry_cleanup, stores, stores->file2store);
+ apr_hash_clear(stores->file2store);
+ }
+}
+
+apr_status_t tls_cert_root_stores_get(
+ tls_cert_root_stores_t *stores,
+ const char *store_file,
+ rustls_root_cert_store **pstore)
+{
+ apr_status_t rv = APR_SUCCESS;
+ tls_cert_root_stores_entry_t *entry;
+
+ entry = apr_hash_get(stores->file2store, store_file, APR_HASH_KEY_STRING);
+ if (!entry) {
+ rustls_root_cert_store *store;
+ rv = tls_cert_load_root_store(stores->pool, store_file, &store);
+ if (APR_SUCCESS != rv) goto cleanup;
+ entry = apr_pcalloc(stores->pool, sizeof(*entry));
+ entry->id = apr_pstrdup(stores->pool, store_file);
+ entry->store = store;
+ apr_hash_set(stores->file2store, entry->id, APR_HASH_KEY_STRING, entry);
+ }
+
+cleanup:
+ if (APR_SUCCESS == rv) {
+ *pstore = entry->store;
+ }
+ else {
+ *pstore = NULL;
+ }
+ return rv;
+}
+
+typedef struct {
+ const char *id;
+ const rustls_client_cert_verifier *client_verifier;
+ const rustls_client_cert_verifier_optional *client_verifier_opt;
+} tls_cert_verifiers_entry_t;
+
+static int verifiers_entry_cleanup(void *ctx, const void *key, apr_ssize_t klen, const void *val)
+{
+ tls_cert_verifiers_entry_t *entry = (tls_cert_verifiers_entry_t*)val;
+ (void)ctx; (void)key; (void)klen;
+ if (entry->client_verifier) {
+ rustls_client_cert_verifier_free(entry->client_verifier);
+ entry->client_verifier = NULL;
+ }
+ if (entry->client_verifier_opt) {
+ rustls_client_cert_verifier_optional_free(entry->client_verifier_opt);
+ entry->client_verifier_opt = NULL;
+ }
+ return 1;
+}
+
+static apr_status_t verifiers_cleanup(void *data)
+{
+ tls_cert_verifiers_t *verifiers = data;
+ tls_cert_verifiers_clear(verifiers);
+ return APR_SUCCESS;
+}
+
+tls_cert_verifiers_t *tls_cert_verifiers_make(
+ apr_pool_t *p, tls_cert_root_stores_t *stores)
+{
+ tls_cert_verifiers_t *verifiers;
+
+ verifiers = apr_pcalloc(p, sizeof(*verifiers));
+ verifiers->pool = p;
+ verifiers->stores = stores;
+ verifiers->file2verifier = apr_hash_make(p);
+ apr_pool_cleanup_register(p, verifiers, verifiers_cleanup, apr_pool_cleanup_null);
+ return verifiers;
+}
+
+void tls_cert_verifiers_clear(tls_cert_verifiers_t *verifiers)
+{
+ if (verifiers->file2verifier) {
+ apr_hash_do(verifiers_entry_cleanup, verifiers, verifiers->file2verifier);
+ apr_hash_clear(verifiers->file2verifier);
+ }
+}
+
+static tls_cert_verifiers_entry_t * verifiers_get_or_make_entry(
+ tls_cert_verifiers_t *verifiers,
+ const char *store_file)
+{
+ tls_cert_verifiers_entry_t *entry;
+
+ entry = apr_hash_get(verifiers->file2verifier, store_file, APR_HASH_KEY_STRING);
+ if (!entry) {
+ entry = apr_pcalloc(verifiers->pool, sizeof(*entry));
+ entry->id = apr_pstrdup(verifiers->pool, store_file);
+ apr_hash_set(verifiers->file2verifier, entry->id, APR_HASH_KEY_STRING, entry);
+ }
+ return entry;
+}
+
+apr_status_t tls_cert_client_verifiers_get(
+ tls_cert_verifiers_t *verifiers,
+ const char *store_file,
+ const rustls_client_cert_verifier **pverifier)
+{
+ apr_status_t rv = APR_SUCCESS;
+ tls_cert_verifiers_entry_t *entry;
+
+ entry = verifiers_get_or_make_entry(verifiers, store_file);
+ if (!entry->client_verifier) {
+ rustls_root_cert_store *store;
+ rv = tls_cert_root_stores_get(verifiers->stores, store_file, &store);
+ if (APR_SUCCESS != rv) goto cleanup;
+ entry->client_verifier = rustls_client_cert_verifier_new(store);
+ }
+
+cleanup:
+ if (APR_SUCCESS == rv) {
+ *pverifier = entry->client_verifier;
+ }
+ else {
+ *pverifier = NULL;
+ }
+ return rv;
+}
+
+apr_status_t tls_cert_client_verifiers_get_optional(
+ tls_cert_verifiers_t *verifiers,
+ const char *store_file,
+ const rustls_client_cert_verifier_optional **pverifier)
+{
+ apr_status_t rv = APR_SUCCESS;
+ tls_cert_verifiers_entry_t *entry;
+
+ entry = verifiers_get_or_make_entry(verifiers, store_file);
+ if (!entry->client_verifier_opt) {
+ rustls_root_cert_store *store;
+ rv = tls_cert_root_stores_get(verifiers->stores, store_file, &store);
+ if (APR_SUCCESS != rv) goto cleanup;
+ entry->client_verifier_opt = rustls_client_cert_verifier_optional_new(store);
+ }
+
+cleanup:
+ if (APR_SUCCESS == rv) {
+ *pverifier = entry->client_verifier_opt;
+ }
+ else {
+ *pverifier = NULL;
+ }
+ return rv;
+}
diff --git a/modules/tls/tls_cert.h b/modules/tls/tls_cert.h
new file mode 100644
index 0000000..6ab3f48
--- /dev/null
+++ b/modules/tls/tls_cert.h
@@ -0,0 +1,211 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef tls_cert_h
+#define tls_cert_h
+
+#include "tls_util.h"
+
+/**
+ * The PEM data of a certificate and its key.
+ */
+typedef struct {
+ tls_data_t cert_pem;
+ tls_data_t pkey_pem;
+} tls_cert_pem_t;
+
+/**
+ * Specify a certificate via files or PEM data.
+ */
+typedef struct {
+ const char *cert_file; /* file path, relative to ap_root */
+ const char *pkey_file; /* file path, relative to ap_root */
+ const char *cert_pem; /* NUL-terminated PEM string */
+ const char *pkey_pem; /* NUL-terminated PEM string */
+} tls_cert_spec_t;
+
+/**
+ * Load the PEM data for a certificate file and key file as given in `cert`.
+ */
+apr_status_t tls_cert_load_pem(
+ apr_pool_t *p, const tls_cert_spec_t *cert, tls_cert_pem_t **ppem);
+
+apr_status_t tls_cert_to_pem(const char **ppem, apr_pool_t *p, const rustls_certificate *cert);
+
+/**
+ * Load a rustls certified key from a certificate specification.
+ * The returned `rustls_certified_key` is owned by the caller.
+ * @param p the memory pool to use
+ * @param spec the specification for the certificate (file or PEM data)
+ * @param cert_pem return the PEM data used for loading the certificates, optional
+ * @param pckey the loaded certified key on return
+ */
+apr_status_t tls_cert_load_cert_key(
+ apr_pool_t *p, const tls_cert_spec_t *spec,
+ const char **pcert_pem, const rustls_certified_key **pckey);
+
+/**
+ * A registry of rustls_certified_key* by identifier.
+ */
+typedef struct tls_cert_reg_t tls_cert_reg_t;
+struct tls_cert_reg_t{
+ apr_pool_t *pool;
+ apr_hash_t *id2entry;
+ apr_hash_t *key2entry;
+};
+
+/**
+ * Create a new registry with lifetime based on the memory pool.
+ * The registry will take care of its memory and allocated keys when
+ * the pool is destroyed.
+ */
+tls_cert_reg_t *tls_cert_reg_make(apr_pool_t *p);
+
+/**
+ * Return the number of certified keys in the registry.
+ */
+apr_size_t tls_cert_reg_count(tls_cert_reg_t *reg);
+
+/**
+ * Get a the `rustls_certified_key` identified by `spec` from the registry.
+ * This will load the key the first time it is requested.
+ * The returned `rustls_certified_key` is owned by the registry.
+ * @param reg the certified key registry
+ * @param s the server_rec this is loaded into, useful for error logging
+ * @param spec the specification of the certified key
+ * @param pckey the certified key instance on return
+ */
+apr_status_t tls_cert_reg_get_certified_key(
+ tls_cert_reg_t *reg, server_rec *s, const tls_cert_spec_t *spec, const rustls_certified_key **pckey);
+
+/**
+ * Visit all certified keys in the registry.
+ * The callback may return 0 to abort the iteration.
+ * @param userdata supplied by the visit invocation
+ * @param s the server_rec the certified was load into first
+ * @param id internal identifier of the certified key
+ * @param cert_pem the PEM data of the certificate and its chain
+ * @param certified_key the key instance itself
+ */
+typedef int tls_cert_reg_visitor(
+ void *userdata, server_rec *s,
+ const char *id, const char *cert_pem, const rustls_certified_key *certified_key);
+
+/**
+ * Visit all certified_key entries in the registry.
+ * @param visitor callback invoked on each entry until it returns 0.
+ * @param userdata passed to callback
+ * @param reg the registry to iterate over
+ */
+void tls_cert_reg_do(
+ tls_cert_reg_visitor *visitor, void *userdata, tls_cert_reg_t *reg);
+
+/**
+ * Get the identity assigned to a loaded, certified key. Returns NULL, if the
+ * key is not part of the registry. The returned bytes are owned by the registry
+ * entry.
+ * @param reg the registry to look in.
+ * @param certified_key the key to get the identifier for
+ */
+const char *tls_cert_reg_get_id(tls_cert_reg_t *reg, const rustls_certified_key *certified_key);
+
+/**
+ * Load all root certificates from a PEM file into a rustls_root_cert_store.
+ * @param p the memory pool to use
+ * @param store_file the (server relative) path of the PEM file
+ * @param pstore the loaded root store on success
+ */
+apr_status_t tls_cert_load_root_store(
+ apr_pool_t *p, const char *store_file, rustls_root_cert_store **pstore);
+
+typedef struct tls_cert_root_stores_t tls_cert_root_stores_t;
+struct tls_cert_root_stores_t {
+ apr_pool_t *pool;
+ apr_hash_t *file2store;
+};
+
+/**
+ * Create a new root stores registry with lifetime based on the memory pool.
+ * The registry will take care of its memory and allocated stores when
+ * the pool is destroyed.
+ */
+tls_cert_root_stores_t *tls_cert_root_stores_make(apr_pool_t *p);
+
+/**
+ * Clear the root stores registry, freeing all stores.
+ */
+void tls_cert_root_stores_clear(tls_cert_root_stores_t *stores);
+
+/**
+ * Load all root certificates from a PEM file into a rustls_root_cert_store.
+ * @param p the memory pool to use
+ * @param store_file the (server relative) path of the PEM file
+ * @param pstore the loaded root store on success
+ */
+apr_status_t tls_cert_root_stores_get(
+ tls_cert_root_stores_t *stores,
+ const char *store_file,
+ rustls_root_cert_store **pstore);
+
+typedef struct tls_cert_verifiers_t tls_cert_verifiers_t;
+struct tls_cert_verifiers_t {
+ apr_pool_t *pool;
+ tls_cert_root_stores_t *stores;
+ apr_hash_t *file2verifier;
+};
+
+/**
+ * Create a new registry for certificate verifiers with lifetime based on the memory pool.
+ * The registry will take care of its memory and allocated verifiers when
+ * the pool is destroyed.
+ * @param p the memory pool to use
+ * @param stores the store registry for lookups
+ */
+tls_cert_verifiers_t *tls_cert_verifiers_make(
+ apr_pool_t *p, tls_cert_root_stores_t *stores);
+
+/**
+ * Clear the verifiers registry, freeing all verifiers.
+ */
+void tls_cert_verifiers_clear(
+ tls_cert_verifiers_t *verifiers);
+
+/**
+ * Get the mandatory client certificate verifier for the
+ * root certificate store in `store_file`. Will create
+ * the verifier if not already known.
+ * @param verifiers the registry of certificate verifiers
+ * @param store_file the (server relative) path of the PEM file with certificates
+ * @param pverifiers the verifier on success
+ */
+apr_status_t tls_cert_client_verifiers_get(
+ tls_cert_verifiers_t *verifiers,
+ const char *store_file,
+ const rustls_client_cert_verifier **pverifier);
+
+/**
+ * Get the optional client certificate verifier for the
+ * root certificate store in `store_file`. Will create
+ * the verifier if not already known.
+ * @param verifiers the registry of certificate verifiers
+ * @param store_file the (server relative) path of the PEM file with certificates
+ * @param pverifiers the verifier on success
+ */
+apr_status_t tls_cert_client_verifiers_get_optional(
+ tls_cert_verifiers_t *verifiers,
+ const char *store_file,
+ const rustls_client_cert_verifier_optional **pverifier);
+
+#endif /* tls_cert_h */ \ No newline at end of file
diff --git a/modules/tls/tls_conf.c b/modules/tls/tls_conf.c
new file mode 100644
index 0000000..a9f27de
--- /dev/null
+++ b/modules/tls/tls_conf.c
@@ -0,0 +1,780 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <assert.h>
+#include <apr_lib.h>
+#include <apr_strings.h>
+#include <apr_version.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_config.h>
+#include <http_log.h>
+#include <http_main.h>
+#include <ap_socache.h>
+
+#include <rustls.h>
+
+#include "tls_cert.h"
+#include "tls_proto.h"
+#include "tls_conf.h"
+#include "tls_util.h"
+#include "tls_var.h"
+#include "tls_cache.h"
+
+
+extern module AP_MODULE_DECLARE_DATA tls_module;
+APLOG_USE_MODULE(tls);
+
+static tls_conf_global_t *conf_global_get_or_make(apr_pool_t *pool, server_rec *s)
+{
+ tls_conf_global_t *gconf;
+
+ /* we create this only once for apache's one ap_server_conf.
+ * If this gets called for another server, we should already have
+ * done it for ap_server_conf. */
+ if (ap_server_conf && s != ap_server_conf) {
+ tls_conf_server_t *sconf = tls_conf_server_get(ap_server_conf);
+ ap_assert(sconf);
+ ap_assert(sconf->global);
+ return sconf->global;
+ }
+
+ gconf = apr_pcalloc(pool, sizeof(*gconf));
+ gconf->ap_server = ap_server_conf;
+ gconf->status = TLS_CONF_ST_INIT;
+ gconf->proto = tls_proto_init(pool, s);
+ gconf->proxy_configs = apr_array_make(pool, 10, sizeof(tls_conf_proxy_t*));
+
+ gconf->var_lookups = apr_hash_make(pool);
+ tls_var_init_lookup_hash(pool, gconf->var_lookups);
+ gconf->session_cache_spec = "default";
+
+ return gconf;
+}
+
+tls_conf_server_t *tls_conf_server_get(server_rec *s)
+{
+ tls_conf_server_t *sc = ap_get_module_config(s->module_config, &tls_module);
+ ap_assert(sc);
+ return sc;
+}
+
+
+#define CONF_S_NAME(s) (s && s->server_hostname? s->server_hostname : "default")
+
+void *tls_conf_create_svr(apr_pool_t *pool, server_rec *s)
+{
+ tls_conf_server_t *conf;
+
+ conf = apr_pcalloc(pool, sizeof(*conf));
+ conf->global = conf_global_get_or_make(pool, s);
+ conf->server = s;
+
+ conf->enabled = TLS_FLAG_UNSET;
+ conf->cert_specs = apr_array_make(pool, 3, sizeof(tls_cert_spec_t*));
+ conf->honor_client_order = TLS_FLAG_UNSET;
+ conf->strict_sni = TLS_FLAG_UNSET;
+ conf->tls_protocol_min = TLS_FLAG_UNSET;
+ conf->tls_pref_ciphers = apr_array_make(pool, 3, sizeof(apr_uint16_t));;
+ conf->tls_supp_ciphers = apr_array_make(pool, 3, sizeof(apr_uint16_t));;
+ return conf;
+}
+
+#define MERGE_INT(base, add, field) \
+ (add->field == TLS_FLAG_UNSET)? base->field : add->field;
+
+void *tls_conf_merge_svr(apr_pool_t *pool, void *basev, void *addv)
+{
+ tls_conf_server_t *base = basev;
+ tls_conf_server_t *add = addv;
+ tls_conf_server_t *nconf;
+
+ nconf = apr_pcalloc(pool, sizeof(*nconf));
+ nconf->server = add->server;
+ nconf->global = add->global? add->global : base->global;
+
+ nconf->enabled = MERGE_INT(base, add, enabled);
+ nconf->cert_specs = apr_array_append(pool, base->cert_specs, add->cert_specs);
+ nconf->tls_protocol_min = MERGE_INT(base, add, tls_protocol_min);
+ nconf->tls_pref_ciphers = add->tls_pref_ciphers->nelts?
+ add->tls_pref_ciphers : base->tls_pref_ciphers;
+ nconf->tls_supp_ciphers = add->tls_supp_ciphers->nelts?
+ add->tls_supp_ciphers : base->tls_supp_ciphers;
+ nconf->honor_client_order = MERGE_INT(base, add, honor_client_order);
+ nconf->client_ca = add->client_ca? add->client_ca : base->client_ca;
+ nconf->client_auth = (add->client_auth != TLS_CLIENT_AUTH_UNSET)?
+ add->client_auth : base->client_auth;
+ nconf->var_user_name = add->var_user_name? add->var_user_name : base->var_user_name;
+ return nconf;
+}
+
+tls_conf_dir_t *tls_conf_dir_get(request_rec *r)
+{
+ tls_conf_dir_t *dc = ap_get_module_config(r->per_dir_config, &tls_module);
+ ap_assert(dc);
+ return dc;
+}
+
+tls_conf_dir_t *tls_conf_dir_server_get(server_rec *s)
+{
+ tls_conf_dir_t *dc = ap_get_module_config(s->lookup_defaults, &tls_module);
+ ap_assert(dc);
+ return dc;
+}
+
+void *tls_conf_create_dir(apr_pool_t *pool, char *dir)
+{
+ tls_conf_dir_t *conf;
+
+ (void)dir;
+ conf = apr_pcalloc(pool, sizeof(*conf));
+ conf->std_env_vars = TLS_FLAG_UNSET;
+ conf->proxy_enabled = TLS_FLAG_UNSET;
+ conf->proxy_protocol_min = TLS_FLAG_UNSET;
+ conf->proxy_pref_ciphers = apr_array_make(pool, 3, sizeof(apr_uint16_t));;
+ conf->proxy_supp_ciphers = apr_array_make(pool, 3, sizeof(apr_uint16_t));;
+ conf->proxy_machine_cert_specs = apr_array_make(pool, 3, sizeof(tls_cert_spec_t*));
+ return conf;
+}
+
+
+static int same_proxy_settings(tls_conf_dir_t *a, tls_conf_dir_t *b)
+{
+ return a->proxy_ca == b->proxy_ca;
+}
+
+static void dir_assign_merge(
+ tls_conf_dir_t *dest, apr_pool_t *pool, tls_conf_dir_t *base, tls_conf_dir_t *add)
+{
+ tls_conf_dir_t local;
+
+ memset(&local, 0, sizeof(local));
+ local.std_env_vars = MERGE_INT(base, add, std_env_vars);
+ local.export_cert_vars = MERGE_INT(base, add, export_cert_vars);
+ local.proxy_enabled = MERGE_INT(base, add, proxy_enabled);
+ local.proxy_ca = add->proxy_ca? add->proxy_ca : base->proxy_ca;
+ local.proxy_protocol_min = MERGE_INT(base, add, proxy_protocol_min);
+ local.proxy_pref_ciphers = add->proxy_pref_ciphers->nelts?
+ add->proxy_pref_ciphers : base->proxy_pref_ciphers;
+ local.proxy_supp_ciphers = add->proxy_supp_ciphers->nelts?
+ add->proxy_supp_ciphers : base->proxy_supp_ciphers;
+ local.proxy_machine_cert_specs = apr_array_append(pool,
+ base->proxy_machine_cert_specs, add->proxy_machine_cert_specs);
+ if (local.proxy_enabled == TLS_FLAG_TRUE) {
+ if (add->proxy_config) {
+ local.proxy_config = same_proxy_settings(&local, add)? add->proxy_config : NULL;
+ }
+ else if (base->proxy_config) {
+ local.proxy_config = same_proxy_settings(&local, base)? add->proxy_config : NULL;
+ }
+ }
+ memcpy(dest, &local, sizeof(*dest));
+}
+
+void *tls_conf_merge_dir(apr_pool_t *pool, void *basev, void *addv)
+{
+ tls_conf_dir_t *base = basev;
+ tls_conf_dir_t *add = addv;
+ tls_conf_dir_t *nconf = apr_pcalloc(pool, sizeof(*nconf));
+ dir_assign_merge(nconf, pool, base, add);
+ return nconf;
+}
+
+static void tls_conf_dir_set_options_defaults(apr_pool_t *pool, tls_conf_dir_t *dc)
+{
+ (void)pool;
+ dc->std_env_vars = TLS_FLAG_FALSE;
+ dc->export_cert_vars = TLS_FLAG_FALSE;
+}
+
+apr_status_t tls_conf_server_apply_defaults(tls_conf_server_t *sc, apr_pool_t *p)
+{
+ (void)p;
+ if (sc->enabled == TLS_FLAG_UNSET) sc->enabled = TLS_FLAG_FALSE;
+ if (sc->tls_protocol_min == TLS_FLAG_UNSET) sc->tls_protocol_min = 0;
+ if (sc->honor_client_order == TLS_FLAG_UNSET) sc->honor_client_order = TLS_FLAG_TRUE;
+ if (sc->strict_sni == TLS_FLAG_UNSET) sc->strict_sni = TLS_FLAG_TRUE;
+ if (sc->client_auth == TLS_CLIENT_AUTH_UNSET) sc->client_auth = TLS_CLIENT_AUTH_NONE;
+ return APR_SUCCESS;
+}
+
+apr_status_t tls_conf_dir_apply_defaults(tls_conf_dir_t *dc, apr_pool_t *p)
+{
+ (void)p;
+ if (dc->std_env_vars == TLS_FLAG_UNSET) dc->std_env_vars = TLS_FLAG_FALSE;
+ if (dc->export_cert_vars == TLS_FLAG_UNSET) dc->export_cert_vars = TLS_FLAG_FALSE;
+ if (dc->proxy_enabled == TLS_FLAG_UNSET) dc->proxy_enabled = TLS_FLAG_FALSE;
+ return APR_SUCCESS;
+}
+
+tls_conf_proxy_t *tls_conf_proxy_make(
+ apr_pool_t *p, tls_conf_dir_t *dc, tls_conf_global_t *gc, server_rec *s)
+{
+ tls_conf_proxy_t *pc = apr_pcalloc(p, sizeof(*pc));
+ pc->defined_in = s;
+ pc->global = gc;
+ pc->proxy_ca = dc->proxy_ca;
+ pc->proxy_protocol_min = dc->proxy_protocol_min;
+ pc->proxy_pref_ciphers = dc->proxy_pref_ciphers;
+ pc->proxy_supp_ciphers = dc->proxy_supp_ciphers;
+ pc->machine_cert_specs = dc->proxy_machine_cert_specs;
+ pc->machine_certified_keys = apr_array_make(p, 3, sizeof(const rustls_certified_key*));
+ return pc;
+}
+
+int tls_proxy_section_post_config(
+ apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s,
+ ap_conf_vector_t *section_config)
+{
+ tls_conf_dir_t *proxy_dc, *server_dc;
+ tls_conf_server_t *sc;
+
+ /* mod_proxy collects the <Proxy>...</Proxy> sections per server (base server or virtualhost)
+ * and in its post_config hook, calls our function registered at its hook for each with
+ * s - the server they were define in
+ * section_config - the set of dir_configs for a <Proxy> section
+ *
+ * If none of _our_ config directives had been used, here or in the server, we get a NULL.
+ * Which means we have to do nothing. Otherwise, we add to `proxy_dc` the
+ * settings from `server_dc` - since this is not automagically done by apache.
+ *
+ * `proxy_dc` is then complete and tells us if we handle outgoing connections
+ * here and with what parameter settings.
+ */
+ (void)ptemp; (void)plog;
+ ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s,
+ "%s: tls_proxy_section_post_config called", s->server_hostname);
+ proxy_dc = ap_get_module_config(section_config, &tls_module);
+ if (!proxy_dc) goto cleanup;
+ server_dc = ap_get_module_config(s->lookup_defaults, &tls_module);
+ ap_assert(server_dc);
+ dir_assign_merge(proxy_dc, p, server_dc, proxy_dc);
+ tls_conf_dir_apply_defaults(proxy_dc, p);
+ if (proxy_dc->proxy_enabled && !proxy_dc->proxy_config) {
+ /* remember `proxy_dc` for subsequent configuration of outoing TLS setups */
+ sc = tls_conf_server_get(s);
+ proxy_dc->proxy_config = tls_conf_proxy_make(p, proxy_dc, sc->global, s);
+ ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s,
+ "%s: adding proxy_conf to globals in proxy_post_config_section",
+ s->server_hostname);
+ APR_ARRAY_PUSH(sc->global->proxy_configs, tls_conf_proxy_t*) = proxy_dc->proxy_config;
+ }
+cleanup:
+ return OK;
+}
+
+static const char *cmd_check_file(cmd_parms *cmd, const char *fpath)
+{
+ char *real_path;
+
+ /* just a dump of the configuration, dont resolve/check */
+ if (ap_state_query(AP_SQ_RUN_MODE) == AP_SQ_RM_CONFIG_DUMP) {
+ return NULL;
+ }
+ real_path = ap_server_root_relative(cmd->pool, fpath);
+ if (!real_path) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ ": Invalid file path ", fpath, NULL);
+ }
+ if (!tls_util_is_file(cmd->pool, real_path)) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ ": file '", real_path,
+ "' does not exist or is empty", NULL);
+ }
+ return NULL;
+}
+
+static const char *tls_conf_add_engine(cmd_parms *cmd, void *dc, const char*v)
+{
+ tls_conf_server_t *sc = tls_conf_server_get(cmd->server);
+ tls_conf_global_t *gc = sc->global;
+ const char *err = NULL;
+ char *host, *scope_id;
+ apr_port_t port;
+ apr_sockaddr_t *sa;
+ server_addr_rec *sar;
+ apr_status_t rv;
+
+ (void)dc;
+ /* Example of use:
+ * TLSEngine 443
+ * TLSEngine hostname:443
+ * TLSEngine 91.0.0.1:443
+ * TLSEngine [::0]:443
+ */
+ rv = apr_parse_addr_port(&host, &scope_id, &port, v, cmd->pool);
+ if (APR_SUCCESS != rv) {
+ err = apr_pstrcat(cmd->pool, cmd->cmd->name,
+ ": invalid address/port in '", v, "'", NULL);
+ goto cleanup;
+ }
+
+ /* translate host/port to a sockaddr that we can match with incoming connections */
+ rv = apr_sockaddr_info_get(&sa, host, APR_UNSPEC, port, 0, cmd->pool);
+ if (APR_SUCCESS != rv) {
+ err = apr_pstrcat(cmd->pool, cmd->cmd->name,
+ ": unable to get sockaddr for '", host, "'", NULL);
+ goto cleanup;
+ }
+
+ if (scope_id) {
+#if APR_VERSION_AT_LEAST(1,7,0)
+ rv = apr_sockaddr_zone_set(sa, scope_id);
+ if (APR_SUCCESS != rv) {
+ err = apr_pstrcat(cmd->pool, cmd->cmd->name,
+ ": error setting ipv6 scope id: '", scope_id, "'", NULL);
+ goto cleanup;
+ }
+#else
+ err = apr_pstrcat(cmd->pool, cmd->cmd->name,
+ ": IPv6 scopes not supported by your APR: '", scope_id, "'", NULL);
+ goto cleanup;
+#endif
+ }
+
+ sar = apr_pcalloc(cmd->pool, sizeof(*sar));
+ sar->host_addr = sa;
+ sar->virthost = host;
+ sar->host_port = port;
+
+ sar->next = gc->tls_addresses;
+ gc->tls_addresses = sar;
+cleanup:
+ return err;
+}
+
+static int flag_value(
+ const char *arg)
+{
+ if (!strcasecmp(arg, "On")) {
+ return TLS_FLAG_TRUE;
+ }
+ else if (!strcasecmp(arg, "Off")) {
+ return TLS_FLAG_FALSE;
+ }
+ return TLS_FLAG_UNSET;
+}
+
+static const char *flag_err(
+ cmd_parms *cmd, const char *v)
+{
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ ": value must be 'On' or 'Off': '", v, "'", NULL);
+}
+
+static const char *tls_conf_add_certificate(
+ cmd_parms *cmd, void *dc, const char *cert_file, const char *pkey_file)
+{
+ tls_conf_server_t *sc = tls_conf_server_get(cmd->server);
+ const char *err = NULL, *fpath;
+ tls_cert_spec_t *cert;
+
+ (void)dc;
+ if (NULL != (err = cmd_check_file(cmd, cert_file))) goto cleanup;
+ /* key file may be NULL, in which case cert_file must contain the key PEM */
+ if (pkey_file && NULL != (err = cmd_check_file(cmd, pkey_file))) goto cleanup;
+
+ cert = apr_pcalloc(cmd->pool, sizeof(*cert));
+ fpath = ap_server_root_relative(cmd->pool, cert_file);
+ if (!tls_util_is_file(cmd->pool, fpath)) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ ": unable to find certificate file: '", fpath, "'", NULL);
+ }
+ cert->cert_file = cert_file;
+ if (pkey_file) {
+ fpath = ap_server_root_relative(cmd->pool, pkey_file);
+ if (!tls_util_is_file(cmd->pool, fpath)) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ ": unable to find certificate key file: '", fpath, "'", NULL);
+ }
+ }
+ cert->pkey_file = pkey_file;
+ *(const tls_cert_spec_t **)apr_array_push(sc->cert_specs) = cert;
+
+cleanup:
+ return err;
+}
+
+static const char *parse_ciphers(
+ cmd_parms *cmd,
+ tls_conf_global_t *gc,
+ const char *nop_name,
+ int argc, char *const argv[],
+ apr_array_header_t *ciphers)
+{
+ apr_array_clear(ciphers);
+ if (argc > 1 || apr_strnatcasecmp(nop_name, argv[0])) {
+ apr_uint16_t cipher;
+ int i;
+
+ for (i = 0; i < argc; ++i) {
+ char *name, *last = NULL;
+ const char *value = argv[i];
+
+ name = apr_strtok(apr_pstrdup(cmd->pool, value), ":", &last);
+ while (name) {
+ if (tls_proto_get_cipher_by_name(gc->proto, name, &cipher) != APR_SUCCESS) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ ": cipher not recognized '", name, "'", NULL);
+ }
+ APR_ARRAY_PUSH(ciphers, apr_uint16_t) = cipher;
+ name = apr_strtok(NULL, ":", &last);
+ }
+ }
+ }
+ return NULL;
+}
+
+static const char *tls_conf_set_preferred_ciphers(
+ cmd_parms *cmd, void *dc, int argc, char *const argv[])
+{
+ tls_conf_server_t *sc = tls_conf_server_get(cmd->server);
+ const char *err = NULL;
+
+ (void)dc;
+ if (!argc) {
+ err = "specify the TLS ciphers to prefer or 'default' for the rustls default ordering.";
+ goto cleanup;
+ }
+ err = parse_ciphers(cmd, sc->global, "default", argc, argv, sc->tls_pref_ciphers);
+cleanup:
+ return err;
+}
+
+static const char *tls_conf_set_suppressed_ciphers(
+ cmd_parms *cmd, void *dc, int argc, char *const argv[])
+{
+ tls_conf_server_t *sc = tls_conf_server_get(cmd->server);
+ const char *err = NULL;
+
+ (void)dc;
+ if (!argc) {
+ err = "specify the TLS ciphers to never use or 'none'.";
+ goto cleanup;
+ }
+ err = parse_ciphers(cmd, sc->global, "none", argc, argv, sc->tls_supp_ciphers);
+cleanup:
+ return err;
+}
+
+static const char *tls_conf_set_honor_client_order(
+ cmd_parms *cmd, void *dc, const char *v)
+{
+ tls_conf_server_t *sc = tls_conf_server_get(cmd->server);
+ int flag = flag_value(v);
+
+ (void)dc;
+ if (TLS_FLAG_UNSET == flag) return flag_err(cmd, v);
+ sc->honor_client_order = flag;
+ return NULL;
+}
+
+static const char *tls_conf_set_strict_sni(
+ cmd_parms *cmd, void *dc, const char *v)
+{
+ tls_conf_server_t *sc = tls_conf_server_get(cmd->server);
+ int flag = flag_value(v);
+
+ (void)dc;
+ if (TLS_FLAG_UNSET == flag) return flag_err(cmd, v);
+ sc->strict_sni = flag;
+ return NULL;
+}
+
+static const char *get_min_protocol(
+ cmd_parms *cmd, const char *v, int *pmin)
+{
+ tls_conf_server_t *sc = tls_conf_server_get(cmd->server);
+ const char *err = NULL;
+
+ if (!apr_strnatcasecmp("default", v)) {
+ *pmin = 0;
+ }
+ else if (*v && v[strlen(v)-1] == '+') {
+ char *name = apr_pstrdup(cmd->pool, v);
+ name[strlen(name)-1] = '\0';
+ *pmin = tls_proto_get_version_by_name(sc->global->proto, name);
+ if (!*pmin) {
+ err = apr_pstrcat(cmd->pool, cmd->cmd->name,
+ ": unrecognized protocol version specifier (try TLSv1.2+ or TLSv1.3+): '", v, "'", NULL);
+ goto cleanup;
+ }
+ }
+ else {
+ err = apr_pstrcat(cmd->pool, cmd->cmd->name,
+ ": value must be 'default', 'TLSv1.2+' or 'TLSv1.3+': '", v, "'", NULL);
+ goto cleanup;
+ }
+cleanup:
+ return err;
+}
+
+static const char *tls_conf_set_protocol(
+ cmd_parms *cmd, void *dc, const char *v)
+{
+ tls_conf_server_t *sc = tls_conf_server_get(cmd->server);
+ (void)dc;
+ return get_min_protocol(cmd, v, &sc->tls_protocol_min);
+}
+
+static const char *tls_conf_set_options(
+ cmd_parms *cmd, void *dcv, int argc, char *const argv[])
+{
+ tls_conf_dir_t *dc = dcv;
+ const char *err = NULL, *option;
+ int i, val;
+
+ /* Are we only having deltas (+/-) or do we reset the options? */
+ for (i = 0; i < argc; ++i) {
+ if (argv[i][0] != '+' && argv[i][0] != '-') {
+ tls_conf_dir_set_options_defaults(cmd->pool, dc);
+ break;
+ }
+ }
+
+ for (i = 0; i < argc; ++i) {
+ option = argv[i];
+ if (!apr_strnatcasecmp("Defaults", option)) {
+ dc->std_env_vars = TLS_FLAG_FALSE;
+ dc->export_cert_vars = TLS_FLAG_FALSE;
+ }
+ else {
+ val = TLS_FLAG_TRUE;
+ if (*option == '+' || *option == '-') {
+ val = (*option == '+')? TLS_FLAG_TRUE : TLS_FLAG_FALSE;
+ ++option;
+ }
+
+ if (!apr_strnatcasecmp("StdEnvVars", option)) {
+ dc->std_env_vars = val;
+ }
+ else if (!apr_strnatcasecmp("ExportCertData", option)) {
+ dc->export_cert_vars = val;
+ }
+ else {
+ err = apr_pstrcat(cmd->pool, cmd->cmd->name,
+ ": unknown option '", option, "'", NULL);
+ goto cleanup;
+ }
+ }
+ }
+cleanup:
+ return err;
+}
+
+static const char *tls_conf_set_session_cache(
+ cmd_parms *cmd, void *dc, const char *value)
+{
+ tls_conf_server_t *sc = tls_conf_server_get(cmd->server);
+ const char *err = NULL;
+
+ (void)dc;
+ if ((err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) goto cleanup;
+
+ err = tls_cache_set_specification(value, sc->global, cmd->pool, cmd->temp_pool);
+cleanup:
+ return err;
+}
+
+static const char *tls_conf_set_proxy_engine(cmd_parms *cmd, void *dir_conf, int flag)
+{
+ tls_conf_dir_t *dc = dir_conf;
+ (void)cmd;
+ dc->proxy_enabled = flag ? TLS_FLAG_TRUE : TLS_FLAG_FALSE;
+ return NULL;
+}
+
+static const char *tls_conf_set_proxy_ca(
+ cmd_parms *cmd, void *dir_conf, const char *proxy_ca)
+{
+ tls_conf_dir_t *dc = dir_conf;
+ const char *err = NULL;
+
+ if (strcasecmp(proxy_ca, "default") && NULL != (err = cmd_check_file(cmd, proxy_ca))) goto cleanup;
+ dc->proxy_ca = proxy_ca;
+cleanup:
+ return err;
+}
+
+static const char *tls_conf_set_proxy_protocol(
+ cmd_parms *cmd, void *dir_conf, const char *v)
+{
+ tls_conf_dir_t *dc = dir_conf;
+ return get_min_protocol(cmd, v, &dc->proxy_protocol_min);
+}
+
+static const char *tls_conf_set_proxy_preferred_ciphers(
+ cmd_parms *cmd, void *dir_conf, int argc, char *const argv[])
+{
+ tls_conf_server_t *sc = tls_conf_server_get(cmd->server);
+ tls_conf_dir_t *dc = dir_conf;
+ const char *err = NULL;
+
+ if (!argc) {
+ err = "specify the proxy TLS ciphers to prefer or 'default' for the rustls default ordering.";
+ goto cleanup;
+ }
+ err = parse_ciphers(cmd, sc->global, "default", argc, argv, dc->proxy_pref_ciphers);
+cleanup:
+ return err;
+}
+
+static const char *tls_conf_set_proxy_suppressed_ciphers(
+ cmd_parms *cmd, void *dir_conf, int argc, char *const argv[])
+{
+ tls_conf_server_t *sc = tls_conf_server_get(cmd->server);
+ tls_conf_dir_t *dc = dir_conf;
+ const char *err = NULL;
+
+ if (!argc) {
+ err = "specify the proxy TLS ciphers to never use or 'none'.";
+ goto cleanup;
+ }
+ err = parse_ciphers(cmd, sc->global, "none", argc, argv, dc->proxy_supp_ciphers);
+cleanup:
+ return err;
+}
+
+#if TLS_CLIENT_CERTS
+
+static const char *tls_conf_set_client_ca(
+ cmd_parms *cmd, void *dc, const char *client_ca)
+{
+ tls_conf_server_t *sc = tls_conf_server_get(cmd->server);
+ const char *err;
+
+ (void)dc;
+ if (NULL != (err = cmd_check_file(cmd, client_ca))) goto cleanup;
+ sc->client_ca = client_ca;
+cleanup:
+ return err;
+}
+
+static const char *tls_conf_set_client_auth(
+ cmd_parms *cmd, void *dc, const char *mode)
+{
+ tls_conf_server_t *sc = tls_conf_server_get(cmd->server);
+ const char *err = NULL;
+ (void)dc;
+ if (!strcasecmp(mode, "required")) {
+ sc->client_auth = TLS_CLIENT_AUTH_REQUIRED;
+ }
+ else if (!strcasecmp(mode, "optional")) {
+ sc->client_auth = TLS_CLIENT_AUTH_OPTIONAL;
+ }
+ else if (!strcasecmp(mode, "none")) {
+ sc->client_auth = TLS_CLIENT_AUTH_NONE;
+ }
+ else {
+ err = apr_pstrcat(cmd->pool, cmd->cmd->name,
+ ": unknown value: '", mode, "', use required/optional/none.", NULL);
+ }
+ return err;
+}
+
+static const char *tls_conf_set_user_name(
+ cmd_parms *cmd, void *dc, const char *var_user_name)
+{
+ tls_conf_server_t *sc = tls_conf_server_get(cmd->server);
+ (void)dc;
+ sc->var_user_name = var_user_name;
+ return NULL;
+}
+
+#endif /* if TLS_CLIENT_CERTS */
+
+#if TLS_MACHINE_CERTS
+
+static const char *tls_conf_add_proxy_machine_certificate(
+ cmd_parms *cmd, void *dir_conf, const char *cert_file, const char *pkey_file)
+{
+ tls_conf_dir_t *dc = dir_conf;
+ const char *err = NULL, *fpath;
+ tls_cert_spec_t *cert;
+
+ (void)dc;
+ if (NULL != (err = cmd_check_file(cmd, cert_file))) goto cleanup;
+ /* key file may be NULL, in which case cert_file must contain the key PEM */
+ if (pkey_file && NULL != (err = cmd_check_file(cmd, pkey_file))) goto cleanup;
+
+ cert = apr_pcalloc(cmd->pool, sizeof(*cert));
+ fpath = ap_server_root_relative(cmd->pool, cert_file);
+ if (!tls_util_is_file(cmd->pool, fpath)) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ ": unable to find certificate file: '", fpath, "'", NULL);
+ }
+ cert->cert_file = cert_file;
+ if (pkey_file) {
+ fpath = ap_server_root_relative(cmd->pool, pkey_file);
+ if (!tls_util_is_file(cmd->pool, fpath)) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ ": unable to find certificate key file: '", fpath, "'", NULL);
+ }
+ }
+ cert->pkey_file = pkey_file;
+ *(const tls_cert_spec_t **)apr_array_push(dc->proxy_machine_cert_specs) = cert;
+
+cleanup:
+ return err;
+}
+
+#endif /* if TLS_MACHINE_CERTS */
+
+const command_rec tls_conf_cmds[] = {
+ AP_INIT_TAKE12("TLSCertificate", tls_conf_add_certificate, NULL, RSRC_CONF,
+ "Add a certificate to the server by specifying a file containing the "
+ "certificate PEM, followed by its chain PEMs. The PEM of the key must "
+ "either also be there or can be given as a separate file."),
+ AP_INIT_TAKE_ARGV("TLSCiphersPrefer", tls_conf_set_preferred_ciphers, NULL, RSRC_CONF,
+ "Set the TLS ciphers to prefer when negotiating with a client."),
+ AP_INIT_TAKE_ARGV("TLSCiphersSuppress", tls_conf_set_suppressed_ciphers, NULL, RSRC_CONF,
+ "Set the TLS ciphers to never use when negotiating with a client."),
+ AP_INIT_TAKE1("TLSHonorClientOrder", tls_conf_set_honor_client_order, NULL, RSRC_CONF,
+ "Set 'on' to have the server honor client preferences in cipher suites, default off."),
+ AP_INIT_TAKE1("TLSEngine", tls_conf_add_engine, NULL, RSRC_CONF,
+ "Specify an address+port where the module shall handle incoming TLS connections."),
+ AP_INIT_TAKE_ARGV("TLSOptions", tls_conf_set_options, NULL, OR_OPTIONS,
+ "En-/disables optional features in the module."),
+ AP_INIT_TAKE1("TLSProtocol", tls_conf_set_protocol, NULL, RSRC_CONF,
+ "Set the minimum TLS protocol version to use."),
+ AP_INIT_TAKE1("TLSStrictSNI", tls_conf_set_strict_sni, NULL, RSRC_CONF,
+ "Set strictness of client server name (SNI) check against hosts, default on."),
+ AP_INIT_TAKE1("TLSSessionCache", tls_conf_set_session_cache, NULL, RSRC_CONF,
+ "Set which cache to use for TLS sessions."),
+ AP_INIT_FLAG("TLSProxyEngine", tls_conf_set_proxy_engine, NULL, RSRC_CONF|PROXY_CONF,
+ "Enable TLS encryption of outgoing connections in this location/server."),
+ AP_INIT_TAKE1("TLSProxyCA", tls_conf_set_proxy_ca, NULL, RSRC_CONF|PROXY_CONF,
+ "Set the trust anchors for certificates from proxied backend servers from a PEM file."),
+ AP_INIT_TAKE1("TLSProxyProtocol", tls_conf_set_proxy_protocol, NULL, RSRC_CONF|PROXY_CONF,
+ "Set the minimum TLS protocol version to use for proxy connections."),
+ AP_INIT_TAKE_ARGV("TLSProxyCiphersPrefer", tls_conf_set_proxy_preferred_ciphers, NULL, RSRC_CONF|PROXY_CONF,
+ "Set the TLS ciphers to prefer when negotiating a proxy connection."),
+ AP_INIT_TAKE_ARGV("TLSProxyCiphersSuppress", tls_conf_set_proxy_suppressed_ciphers, NULL, RSRC_CONF|PROXY_CONF,
+ "Set the TLS ciphers to never use when negotiating a proxy connection."),
+#if TLS_CLIENT_CERTS
+ AP_INIT_TAKE1("TLSClientCA", tls_conf_set_client_ca, NULL, RSRC_CONF,
+ "Set the trust anchors for client certificates from a PEM file."),
+ AP_INIT_TAKE1("TLSClientCertificate", tls_conf_set_client_auth, NULL, RSRC_CONF,
+ "If TLS client authentication is 'required', 'optional' or 'none'."),
+ AP_INIT_TAKE1("TLSUserName", tls_conf_set_user_name, NULL, RSRC_CONF,
+ "Set the SSL variable to be used as user name."),
+#endif /* if TLS_CLIENT_CERTS */
+#if TLS_MACHINE_CERTS
+ AP_INIT_TAKE12("TLSProxyMachineCertificate", tls_conf_add_proxy_machine_certificate, NULL, RSRC_CONF|PROXY_CONF,
+ "Add a certificate to be used as client certificate on a proxy connection. "),
+#endif /* if TLS_MACHINE_CERTS */
+ AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL)
+};
diff --git a/modules/tls/tls_conf.h b/modules/tls/tls_conf.h
new file mode 100644
index 0000000..e924412
--- /dev/null
+++ b/modules/tls/tls_conf.h
@@ -0,0 +1,185 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef tls_conf_h
+#define tls_conf_h
+
+/* Configuration flags */
+#define TLS_FLAG_UNSET (-1)
+#define TLS_FLAG_FALSE (0)
+#define TLS_FLAG_TRUE (1)
+
+struct tls_proto_conf_t;
+struct tls_cert_reg_t;
+struct tls_cert_root_stores_t;
+struct tls_cert_verifiers_t;
+struct ap_socache_instance_t;
+struct ap_socache_provider_t;
+struct apr_global_mutex_t;
+
+
+/* disabled, since rustls support is lacking
+ * - x.509 retrieval of certificate fields and extensions
+ * - certificate revocation lists (CRL)
+ * - x.509 access to issuer of trust chain in x.509 CA store:
+ * server CA has ca1, ca2, ca3
+ * client present certA
+ * rustls verifies that it is signed by *one of* ca* certs
+ * OCSP check needs (certA, issuing cert) for query
+ */
+#define TLS_CLIENT_CERTS 0
+
+/* support for this exists as PR <https://github.com/rustls/rustls-ffi/pull/128>
+ */
+#define TLS_MACHINE_CERTS 1
+
+
+typedef enum {
+ TLS_CLIENT_AUTH_UNSET,
+ TLS_CLIENT_AUTH_NONE,
+ TLS_CLIENT_AUTH_REQUIRED,
+ TLS_CLIENT_AUTH_OPTIONAL,
+} tls_client_auth_t;
+
+typedef enum {
+ TLS_CONF_ST_INIT,
+ TLS_CONF_ST_INCOMING_DONE,
+ TLS_CONF_ST_OUTGOING_DONE,
+ TLS_CONF_ST_DONE,
+} tls_conf_status_t;
+
+/* The global module configuration, created after post-config
+ * and then readonly.
+ */
+typedef struct {
+ server_rec *ap_server; /* the global server we initialized on */
+ const char *module_version;
+ const char *crustls_version;
+
+ tls_conf_status_t status;
+ int mod_proxy_post_config_done; /* if mod_proxy did its post-config things */
+
+ server_addr_rec *tls_addresses; /* the addresses/ports our engine is enabled on */
+ apr_array_header_t *proxy_configs; /* tls_conf_proxy_t* collected from everywhere */
+
+ struct tls_proto_conf_t *proto; /* TLS protocol/rustls specific globals */
+ apr_hash_t *var_lookups; /* variable lookup functions by var name */
+ struct tls_cert_reg_t *cert_reg; /* all certified keys loaded */
+ struct tls_cert_root_stores_t *stores; /* loaded certificate stores */
+ struct tls_cert_verifiers_t *verifiers; /* registry of certificate verifiers */
+
+ const char *session_cache_spec; /* how the session cache was specified */
+ const struct ap_socache_provider_t *session_cache_provider; /* provider used for session cache */
+ struct ap_socache_instance_t *session_cache; /* session cache instance */
+ struct apr_global_mutex_t *session_cache_mutex; /* global mutex for access to session cache */
+
+ const rustls_server_config *rustls_hello_config; /* used for initial client hello parsing */
+} tls_conf_global_t;
+
+/* The module configuration for a server (vhost).
+ * Populated during config parsing, merged and completed
+ * in the post config phase. Readonly after that.
+ */
+typedef struct {
+ server_rec *server; /* server this config belongs to */
+ tls_conf_global_t *global; /* global module config, singleton */
+
+ int enabled; /* TLS_FLAG_TRUE if mod_tls is active on this server */
+ apr_array_header_t *cert_specs; /* array of (tls_cert_spec_t*) of configured certificates */
+ int tls_protocol_min; /* the minimum TLS protocol version to use */
+ apr_array_header_t *tls_pref_ciphers; /* List of apr_uint16_t cipher ids to prefer */
+ apr_array_header_t *tls_supp_ciphers; /* List of apr_uint16_t cipher ids to suppress */
+ const apr_array_header_t *ciphersuites; /* Computed post-config, ordered list of rustls cipher suites */
+ int honor_client_order; /* honor client cipher ordering */
+ int strict_sni;
+
+ const char *client_ca; /* PEM file with trust anchors for client certs */
+ tls_client_auth_t client_auth; /* how client authentication with certificates is used */
+ const char *var_user_name; /* which SSL variable to use as user name */
+
+ apr_array_header_t *certified_keys; /* rustls_certified_key list configured */
+ int base_server; /* != 0 iff this is the base server */
+ int service_unavailable; /* TLS not trustworthy configured, return 503s */
+} tls_conf_server_t;
+
+typedef struct {
+ server_rec *defined_in; /* the server/host defining this dir_conf */
+ tls_conf_global_t *global; /* global module config, singleton */
+ const char *proxy_ca; /* PEM file with trust anchors for proxied remote server certs */
+ int proxy_protocol_min; /* the minimum TLS protocol version to use for proxy connections */
+ apr_array_header_t *proxy_pref_ciphers; /* List of apr_uint16_t cipher ids to prefer */
+ apr_array_header_t *proxy_supp_ciphers; /* List of apr_uint16_t cipher ids to suppress */
+ apr_array_header_t *machine_cert_specs; /* configured machine certificates specs */
+ apr_array_header_t *machine_certified_keys; /* rustls_certified_key list */
+ const rustls_client_config *rustls_config;
+} tls_conf_proxy_t;
+
+typedef struct {
+ int std_env_vars;
+ int export_cert_vars;
+ int proxy_enabled; /* TLS_FLAG_TRUE if mod_tls is active on outgoing connections */
+ const char *proxy_ca; /* PEM file with trust anchors for proxied remote server certs */
+ int proxy_protocol_min; /* the minimum TLS protocol version to use for proxy connections */
+ apr_array_header_t *proxy_pref_ciphers; /* List of apr_uint16_t cipher ids to prefer */
+ apr_array_header_t *proxy_supp_ciphers; /* List of apr_uint16_t cipher ids to suppress */
+ apr_array_header_t *proxy_machine_cert_specs; /* configured machine certificates specs */
+
+ tls_conf_proxy_t *proxy_config;
+} tls_conf_dir_t;
+
+/* our static registry of configuration directives. */
+extern const command_rec tls_conf_cmds[];
+
+/* create the modules configuration for a server_rec. */
+void *tls_conf_create_svr(apr_pool_t *pool, server_rec *s);
+
+/* merge (inherit) server configurations for the module.
+ * Settings in 'add' overwrite the ones in 'base' and unspecified
+ * settings shine through. */
+void *tls_conf_merge_svr(apr_pool_t *pool, void *basev, void *addv);
+
+/* create the modules configuration for a directory. */
+void *tls_conf_create_dir(apr_pool_t *pool, char *dir);
+
+/* merge (inherit) directory configurations for the module.
+ * Settings in 'add' overwrite the ones in 'base' and unspecified
+ * settings shine through. */
+void *tls_conf_merge_dir(apr_pool_t *pool, void *basev, void *addv);
+
+
+/* Get the server specific module configuration. */
+tls_conf_server_t *tls_conf_server_get(server_rec *s);
+
+/* Get the directory specific module configuration for the request. */
+tls_conf_dir_t *tls_conf_dir_get(request_rec *r);
+
+/* Get the directory specific module configuration for the server. */
+tls_conf_dir_t *tls_conf_dir_server_get(server_rec *s);
+
+/* If any configuration values are unset, supply the global server defaults. */
+apr_status_t tls_conf_server_apply_defaults(tls_conf_server_t *sc, apr_pool_t *p);
+
+/* If any configuration values are unset, supply the global dir defaults. */
+apr_status_t tls_conf_dir_apply_defaults(tls_conf_dir_t *dc, apr_pool_t *p);
+
+/* create a new proxy configuration from directory config in server */
+tls_conf_proxy_t *tls_conf_proxy_make(
+ apr_pool_t *p, tls_conf_dir_t *dc, tls_conf_global_t *gc, server_rec *s);
+
+int tls_proxy_section_post_config(
+ apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s,
+ ap_conf_vector_t *section_config);
+
+#endif /* tls_conf_h */
diff --git a/modules/tls/tls_core.c b/modules/tls/tls_core.c
new file mode 100644
index 0000000..38c8873
--- /dev/null
+++ b/modules/tls/tls_core.c
@@ -0,0 +1,1433 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <assert.h>
+#include <apr_lib.h>
+#include <apr_strings.h>
+#include <apr_network_io.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_log.h>
+#include <http_protocol.h>
+#include <http_ssl.h>
+#include <http_vhost.h>
+#include <http_main.h>
+#include <ap_socache.h>
+
+#include <rustls.h>
+
+#include "tls_proto.h"
+#include "tls_cert.h"
+#include "tls_conf.h"
+#include "tls_core.h"
+#include "tls_ocsp.h"
+#include "tls_util.h"
+#include "tls_cache.h"
+#include "tls_var.h"
+
+
+extern module AP_MODULE_DECLARE_DATA tls_module;
+APLOG_USE_MODULE(tls);
+
+tls_conf_conn_t *tls_conf_conn_get(conn_rec *c)
+{
+ return ap_get_module_config(c->conn_config, &tls_module);
+}
+
+void tls_conf_conn_set(conn_rec *c, tls_conf_conn_t *cc)
+{
+ ap_set_module_config(c->conn_config, &tls_module, cc);
+}
+
+int tls_conn_check_ssl(conn_rec *c)
+{
+ tls_conf_conn_t *cc = tls_conf_conn_get(c->master? c->master : c);
+ if (TLS_CONN_ST_IS_ENABLED(cc)) {
+ return OK;
+ }
+ return DECLINED;
+}
+
+static int we_listen_on(tls_conf_global_t *gc, server_rec *s, tls_conf_server_t *sc)
+{
+ server_addr_rec *sa, *la;
+
+ if (gc->tls_addresses && sc->base_server) {
+ /* The base server listens to every port and may be selected via SNI */
+ return 1;
+ }
+ for (la = gc->tls_addresses; la; la = la->next) {
+ for (sa = s->addrs; sa; sa = sa->next) {
+ if (la->host_port == sa->host_port
+ && la->host_addr->ipaddr_len == sa->host_addr->ipaddr_len
+ && !memcmp(la->host_addr->ipaddr_ptr,
+ la->host_addr->ipaddr_ptr, (size_t)la->host_addr->ipaddr_len)) {
+ /* exact match */
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+static apr_status_t tls_core_free(void *data)
+{
+ server_rec *base_server = (server_rec *)data;
+ tls_conf_server_t *sc = tls_conf_server_get(base_server);
+
+ if (sc && sc->global && sc->global->rustls_hello_config) {
+ rustls_server_config_free(sc->global->rustls_hello_config);
+ sc->global->rustls_hello_config = NULL;
+ }
+ tls_cache_free(base_server);
+ return APR_SUCCESS;
+}
+
+static apr_status_t load_certified_keys(
+ apr_array_header_t *keys, server_rec *s,
+ apr_array_header_t *cert_specs,
+ tls_cert_reg_t *cert_reg)
+{
+ apr_status_t rv = APR_SUCCESS;
+ const rustls_certified_key *ckey;
+ tls_cert_spec_t *spec;
+ int i;
+
+ if (cert_specs && cert_specs->nelts > 0) {
+ for (i = 0; i < cert_specs->nelts; ++i) {
+ spec = APR_ARRAY_IDX(cert_specs, i, tls_cert_spec_t*);
+ rv = tls_cert_reg_get_certified_key(cert_reg, s, spec, &ckey);
+ if (APR_SUCCESS != rv) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10318)
+ "Failed to load certificate %d[cert=%s(%d), key=%s(%d)] for %s",
+ i, spec->cert_file, (int)(spec->cert_pem? strlen(spec->cert_pem) : 0),
+ spec->pkey_file, (int)(spec->pkey_pem? strlen(spec->pkey_pem) : 0),
+ s->server_hostname);
+ goto cleanup;
+ }
+ assert(ckey);
+ APR_ARRAY_PUSH(keys, const rustls_certified_key*) = ckey;
+ }
+ }
+cleanup:
+ return rv;
+}
+
+static apr_status_t use_local_key(
+ conn_rec *c, const char *cert_pem, const char *pkey_pem)
+{
+ tls_conf_conn_t *cc = tls_conf_conn_get(c);
+ const rustls_certified_key *ckey = NULL;
+ tls_cert_spec_t spec;
+ apr_status_t rv = APR_SUCCESS;
+
+ memset(&spec, 0, sizeof(spec));
+ spec.cert_pem = cert_pem;
+ spec.pkey_pem = pkey_pem;
+ rv = tls_cert_load_cert_key(c->pool, &spec, NULL, &ckey);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ cc->local_keys = apr_array_make(c->pool, 2, sizeof(const rustls_certified_key*));
+ APR_ARRAY_PUSH(cc->local_keys, const rustls_certified_key*) = ckey;
+ ckey = NULL;
+
+cleanup:
+ if (ckey != NULL) rustls_certified_key_free(ckey);
+ return rv;
+}
+
+static void add_file_specs(
+ apr_array_header_t *certificates,
+ apr_pool_t *p,
+ apr_array_header_t *cert_files,
+ apr_array_header_t *key_files)
+{
+ tls_cert_spec_t *spec;
+ int i;
+
+ for (i = 0; i < cert_files->nelts; ++i) {
+ spec = apr_pcalloc(p, sizeof(*spec));
+ spec->cert_file = APR_ARRAY_IDX(cert_files, i, const char*);
+ spec->pkey_file = (i < key_files->nelts)? APR_ARRAY_IDX(key_files, i, const char*) : NULL;
+ *(const tls_cert_spec_t**)apr_array_push(certificates) = spec;
+ }
+}
+
+static apr_status_t calc_ciphers(
+ apr_pool_t *pool,
+ server_rec *s,
+ tls_conf_global_t *gc,
+ const char *proxy,
+ apr_array_header_t *pref_ciphers,
+ apr_array_header_t *supp_ciphers,
+ const apr_array_header_t **pciphers)
+{
+ apr_array_header_t *ordered_ciphers;
+ const apr_array_header_t *ciphers;
+ apr_array_header_t *unsupported = NULL;
+ rustls_result rr = RUSTLS_RESULT_OK;
+ apr_status_t rv = APR_SUCCESS;
+ apr_uint16_t id;
+ int i;
+
+
+ /* remove all suppressed ciphers from the ones supported by rustls */
+ ciphers = tls_util_array_uint16_remove(pool, gc->proto->supported_cipher_ids, supp_ciphers);
+ ordered_ciphers = NULL;
+ /* if preferred ciphers are actually still present in allowed_ciphers, put
+ * them into `ciphers` in this order */
+ for (i = 0; i < pref_ciphers->nelts; ++i) {
+ id = APR_ARRAY_IDX(pref_ciphers, i, apr_uint16_t);
+ ap_log_error(APLOG_MARK, APLOG_TRACE4, rv, s,
+ "checking preferred cipher %s: %d",
+ s->server_hostname, id);
+ if (tls_util_array_uint16_contains(ciphers, id)) {
+ ap_log_error(APLOG_MARK, APLOG_TRACE4, rv, s,
+ "checking preferred cipher %s: %d is known",
+ s->server_hostname, id);
+ if (ordered_ciphers == NULL) {
+ ordered_ciphers = apr_array_make(pool, ciphers->nelts, sizeof(apr_uint16_t));
+ }
+ APR_ARRAY_PUSH(ordered_ciphers, apr_uint16_t) = id;
+ }
+ else if (!tls_proto_is_cipher_supported(gc->proto, id)) {
+ ap_log_error(APLOG_MARK, APLOG_TRACE4, rv, s,
+ "checking preferred cipher %s: %d is unsupported",
+ s->server_hostname, id);
+ if (!unsupported) unsupported = apr_array_make(pool, 5, sizeof(apr_uint16_t));
+ APR_ARRAY_PUSH(unsupported, apr_uint16_t) = id;
+ }
+ }
+ /* if we found ciphers with preference among allowed_ciphers,
+ * append all other allowed ciphers. */
+ if (ordered_ciphers) {
+ for (i = 0; i < ciphers->nelts; ++i) {
+ id = APR_ARRAY_IDX(ciphers, i, apr_uint16_t);
+ if (!tls_util_array_uint16_contains(ordered_ciphers, id)) {
+ APR_ARRAY_PUSH(ordered_ciphers, apr_uint16_t) = id;
+ }
+ }
+ ciphers = ordered_ciphers;
+ }
+
+ if (ciphers == gc->proto->supported_cipher_ids) {
+ ciphers = NULL;
+ }
+
+ if (unsupported && unsupported->nelts) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rv, s, APLOGNO(10319)
+ "Server '%s' has TLS%sCiphersPrefer configured that are not "
+ "supported by rustls. These will not have an effect: %s",
+ s->server_hostname, proxy,
+ tls_proto_get_cipher_names(gc->proto, unsupported, pool));
+ }
+
+ if (RUSTLS_RESULT_OK != rr) {
+ const char *err_descr;
+ rv = tls_util_rustls_error(pool, rr, &err_descr);
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10320)
+ "Failed to configure ciphers %s: [%d] %s",
+ s->server_hostname, (int)rr, err_descr);
+ }
+ *pciphers = (APR_SUCCESS == rv)? ciphers : NULL;
+ return rv;
+}
+
+static apr_status_t get_server_ciphersuites(
+ const apr_array_header_t **pciphersuites,
+ apr_pool_t *pool, tls_conf_server_t *sc)
+{
+ const apr_array_header_t *ciphers, *suites = NULL;
+ apr_status_t rv = APR_SUCCESS;
+
+ rv = calc_ciphers(pool, sc->server, sc->global,
+ "", sc->tls_pref_ciphers, sc->tls_supp_ciphers,
+ &ciphers);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ if (ciphers) {
+ suites = tls_proto_get_rustls_suites(
+ sc->global->proto, ciphers, pool);
+ if (APLOGtrace2(sc->server)) {
+ tls_proto_conf_t *conf = sc->global->proto;
+ ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, sc->server,
+ "tls ciphers configured[%s]: %s",
+ sc->server->server_hostname,
+ tls_proto_get_cipher_names(conf, ciphers, pool));
+ }
+ }
+
+cleanup:
+ *pciphersuites = (APR_SUCCESS == rv)? suites : NULL;
+ return rv;
+}
+
+static apr_array_header_t *complete_cert_specs(
+ apr_pool_t *p, tls_conf_server_t *sc)
+{
+ apr_array_header_t *cert_adds, *key_adds, *specs;
+
+ /* Take the configured certificate specifications and ask
+ * around for other modules to add specifications to this server.
+ * This is the way mod_md provides certificates.
+ *
+ * If the server then still has no cert specifications, ask
+ * around for `fallback` certificates which are commonly self-signed,
+ * temporary ones which let the server startup in order to
+ * obtain the `real` certificates from sources like ACME.
+ * Servers will fallbacks will answer all requests with 503.
+ */
+ specs = apr_array_copy(p, sc->cert_specs);
+ cert_adds = apr_array_make(p, 2, sizeof(const char*));
+ key_adds = apr_array_make(p, 2, sizeof(const char*));
+
+ ap_ssl_add_cert_files(sc->server, p, cert_adds, key_adds);
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, sc->server,
+ "init server: complete_cert_specs added %d certs", cert_adds->nelts);
+ add_file_specs(specs, p, cert_adds, key_adds);
+
+ if (apr_is_empty_array(specs)) {
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, sc->server,
+ "init server: no certs configured, looking for fallback");
+ ap_ssl_add_fallback_cert_files(sc->server, p, cert_adds, key_adds);
+ if (cert_adds->nelts > 0) {
+ add_file_specs(specs, p, cert_adds, key_adds);
+ sc->service_unavailable = 1;
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, sc->server, APLOGNO(10321)
+ "Init: %s will respond with '503 Service Unavailable' for now. There "
+ "are no SSL certificates configured and no other module contributed any.",
+ sc->server->server_hostname);
+ }
+ else if (!sc->base_server) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, sc->server, APLOGNO(10322)
+ "Init: %s has no certificates configured. Use 'TLSCertificate' to "
+ "configure a certificate and key file.",
+ sc->server->server_hostname);
+ }
+ }
+ return specs;
+}
+
+static const rustls_certified_key *select_certified_key(
+ void* userdata, const rustls_client_hello *hello)
+{
+ conn_rec *c = userdata;
+ tls_conf_conn_t *cc;
+ tls_conf_server_t *sc;
+ apr_array_header_t *keys;
+ const rustls_certified_key *clone;
+ rustls_result rr = RUSTLS_RESULT_OK;
+ apr_status_t rv;
+
+ ap_assert(c);
+ cc = tls_conf_conn_get(c);
+ ap_assert(cc);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "client hello select certified key");
+ if (!cc || !cc->server) goto cleanup;
+ sc = tls_conf_server_get(cc->server);
+ if (!sc) goto cleanup;
+
+ cc->key = NULL;
+ cc->key_cloned = 0;
+ if (cc->local_keys && cc->local_keys->nelts > 0) {
+ keys = cc->local_keys;
+ }
+ else {
+ keys = sc->certified_keys;
+ }
+ if (!keys || keys->nelts <= 0) goto cleanup;
+
+ rr = rustls_client_hello_select_certified_key(hello,
+ (const rustls_certified_key**)keys->elts, (size_t)keys->nelts, &cc->key);
+ if (RUSTLS_RESULT_OK != rr) goto cleanup;
+
+ if (APR_SUCCESS == tls_ocsp_update_key(c, cc->key, &clone)) {
+ /* got OCSP response data for it, meaning the key was cloned and we need to remember */
+ cc->key_cloned = 1;
+ cc->key = clone;
+ }
+ if (APLOGctrace2(c)) {
+ const char *key_id = tls_cert_reg_get_id(sc->global->cert_reg, cc->key);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, APLOGNO(10323)
+ "client hello selected key: %s", key_id? key_id : "unknown");
+ }
+ return cc->key;
+
+cleanup:
+ if (RUSTLS_RESULT_OK != rr) {
+ const char *err_descr;
+ rv = tls_util_rustls_error(c->pool, rr, &err_descr);
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c, APLOGNO(10324)
+ "Failed to select certified key: [%d] %s", (int)rr, err_descr);
+ }
+ return NULL;
+}
+
+static apr_status_t server_conf_setup(
+ apr_pool_t *p, apr_pool_t *ptemp, tls_conf_server_t *sc, tls_conf_global_t *gc)
+{
+ apr_array_header_t *cert_specs;
+ apr_status_t rv = APR_SUCCESS;
+
+ /* TODO: most code has been stripped here with the changes in rustls-ffi v0.8.0
+ * and this means that any errors are only happening at connection setup, which
+ * is too late.
+ */
+ (void)p;
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, sc->server,
+ "init server: %s", sc->server->server_hostname);
+
+ if (sc->client_auth != TLS_CLIENT_AUTH_NONE && !sc->client_ca) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, sc->server, APLOGNO(10325)
+ "TLSClientAuthentication is enabled for %s, but no client CA file is set. "
+ "Use 'TLSClientCA <file>' to specify the trust anchors.",
+ sc->server->server_hostname);
+ rv = APR_EINVAL; goto cleanup;
+ }
+
+ cert_specs = complete_cert_specs(ptemp, sc);
+ sc->certified_keys = apr_array_make(p, 3, sizeof(rustls_certified_key *));
+ rv = load_certified_keys(sc->certified_keys, sc->server, cert_specs, gc->cert_reg);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ rv = get_server_ciphersuites(&sc->ciphersuites, p, sc);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, sc->server,
+ "init server: %s with %d certificates loaded",
+ sc->server->server_hostname, sc->certified_keys->nelts);
+cleanup:
+ return rv;
+}
+
+static apr_status_t get_proxy_ciphers(const apr_array_header_t **pciphersuites,
+ apr_pool_t *pool, tls_conf_proxy_t *pc)
+{
+ const apr_array_header_t *ciphers, *suites = NULL;
+ apr_status_t rv = APR_SUCCESS;
+
+ rv = calc_ciphers(pool, pc->defined_in, pc->global,
+ "", pc->proxy_pref_ciphers, pc->proxy_supp_ciphers, &ciphers);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ if (ciphers) {
+ suites = tls_proto_get_rustls_suites(pc->global->proto, ciphers, pool);
+ /* this changed the default rustls ciphers, configure it. */
+ if (APLOGtrace2(pc->defined_in)) {
+ tls_proto_conf_t *conf = pc->global->proto;
+ ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, pc->defined_in,
+ "tls proxy ciphers configured[%s]: %s",
+ pc->defined_in->server_hostname,
+ tls_proto_get_cipher_names(conf, ciphers, pool));
+ }
+ }
+
+cleanup:
+ *pciphersuites = (APR_SUCCESS == rv)? suites : NULL;
+ return rv;
+}
+
+static apr_status_t proxy_conf_setup(
+ apr_pool_t *p, apr_pool_t *ptemp, tls_conf_proxy_t *pc, tls_conf_global_t *gc)
+{
+ apr_status_t rv = APR_SUCCESS;
+
+ (void)p; (void)ptemp;
+ ap_assert(pc->defined_in);
+ pc->global = gc;
+
+ if (pc->proxy_ca && strcasecmp(pc->proxy_ca, "default")) {
+ ap_log_error(APLOG_MARK, APLOG_TRACE2, rv, pc->defined_in,
+ "proxy: will use roots in %s from %s",
+ pc->defined_in->server_hostname, pc->proxy_ca);
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rv, pc->defined_in,
+ "proxy: there is no TLSProxyCA configured in %s which means "
+ "the certificates of remote servers contacted from here will not be trusted.",
+ pc->defined_in->server_hostname);
+ }
+
+ if (pc->proxy_protocol_min > 0) {
+ apr_array_header_t *tls_versions;
+
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, pc->defined_in,
+ "init server: set proxy protocol min version %04x", pc->proxy_protocol_min);
+ tls_versions = tls_proto_create_versions_plus(
+ gc->proto, (apr_uint16_t)pc->proxy_protocol_min, ptemp);
+ if (tls_versions->nelts > 0) {
+ if (pc->proxy_protocol_min != APR_ARRAY_IDX(tls_versions, 0, apr_uint16_t)) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, pc->defined_in, APLOGNO(10326)
+ "Init: the minimum proxy protocol version configured for %s (%04x) "
+ "is not supported and version %04x was selected instead.",
+ pc->defined_in->server_hostname, pc->proxy_protocol_min,
+ APR_ARRAY_IDX(tls_versions, 0, apr_uint16_t));
+ }
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, pc->defined_in, APLOGNO(10327)
+ "Unable to configure the proxy protocol version for %s: "
+ "neither the configured minimum version (%04x), nor any higher one is "
+ "available.", pc->defined_in->server_hostname, pc->proxy_protocol_min);
+ rv = APR_ENOTIMPL; goto cleanup;
+ }
+ }
+
+#if TLS_MACHINE_CERTS
+ rv = load_certified_keys(pc->machine_certified_keys, pc->defined_in,
+ pc->machine_cert_specs, gc->cert_reg);
+ if (APR_SUCCESS != rv) goto cleanup;
+#endif
+
+cleanup:
+ return rv;
+}
+
+static const rustls_certified_key *extract_client_hello_values(
+ void* userdata, const rustls_client_hello *hello)
+{
+ conn_rec *c = userdata;
+ tls_conf_conn_t *cc = tls_conf_conn_get(c);
+ size_t i, len;
+ unsigned short n;
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "extract client hello values");
+ if (!cc) goto cleanup;
+ cc->client_hello_seen = 1;
+ if (hello->sni_name.len > 0) {
+ cc->sni_hostname = apr_pstrndup(c->pool, hello->sni_name.data, hello->sni_name.len);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "sni detected: %s", cc->sni_hostname);
+ }
+ else {
+ cc->sni_hostname = NULL;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "no sni from client");
+ }
+ if (APLOGctrace4(c) && hello->signature_schemes.len > 0) {
+ for (i = 0; i < hello->signature_schemes.len; ++i) {
+ n = hello->signature_schemes.data[i];
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, c,
+ "client supports signature scheme: %x", (int)n);
+ }
+ }
+ if ((len = rustls_slice_slice_bytes_len(hello->alpn)) > 0) {
+ apr_array_header_t *alpn = apr_array_make(c->pool, 5, sizeof(const char*));
+ const char *protocol;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "ALPN: client proposes %d protocols", (int)len);
+ for (i = 0; i < len; ++i) {
+ rustls_slice_bytes rs = rustls_slice_slice_bytes_get(hello->alpn, i);
+ protocol = apr_pstrndup(c->pool, (const char*)rs.data, rs.len);
+ APR_ARRAY_PUSH(alpn, const char*) = protocol;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "ALPN: client proposes %d: `%s`", (int)i, protocol);
+ }
+ cc->alpn = alpn;
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, "ALPN: no alpn proposed by client");
+ }
+cleanup:
+ return NULL;
+}
+
+static apr_status_t setup_hello_config(apr_pool_t *p, server_rec *base_server, tls_conf_global_t *gc)
+{
+ rustls_server_config_builder *builder;
+ rustls_result rr = RUSTLS_RESULT_OK;
+ apr_status_t rv = APR_SUCCESS;
+
+ builder = rustls_server_config_builder_new();
+ if (!builder) {
+ rr = RUSTLS_RESULT_PANIC; goto cleanup;
+ }
+ rustls_server_config_builder_set_hello_callback(builder, extract_client_hello_values);
+ gc->rustls_hello_config = rustls_server_config_builder_build(builder);
+ if (!gc->rustls_hello_config) {
+ rr = RUSTLS_RESULT_PANIC; goto cleanup;
+ }
+
+cleanup:
+ if (RUSTLS_RESULT_OK != rr) {
+ const char *err_descr = NULL;
+ rv = tls_util_rustls_error(p, rr, &err_descr);
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, base_server, APLOGNO(10328)
+ "Failed to init generic hello config: [%d] %s", (int)rr, err_descr);
+ goto cleanup;
+ }
+ return rv;
+}
+
+static apr_status_t init_incoming(apr_pool_t *p, apr_pool_t *ptemp, server_rec *base_server)
+{
+ tls_conf_server_t *sc = tls_conf_server_get(base_server);
+ tls_conf_global_t *gc = sc->global;
+ server_rec *s;
+ apr_status_t rv = APR_ENOMEM;
+
+ ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, base_server, "tls_core_init incoming");
+ apr_pool_cleanup_register(p, base_server, tls_core_free,
+ apr_pool_cleanup_null);
+
+ rv = tls_proto_post_config(p, ptemp, base_server);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ for (s = base_server; s; s = s->next) {
+ sc = tls_conf_server_get(s);
+ assert(sc);
+ ap_assert(sc->global == gc);
+
+ /* If 'TLSEngine' has been configured, use those addresses to
+ * decide if we are enabled on this server. */
+ sc->base_server = (s == base_server);
+ sc->enabled = we_listen_on(gc, s, sc)? TLS_FLAG_TRUE : TLS_FLAG_FALSE;
+ }
+
+ rv = tls_cache_post_config(p, ptemp, base_server);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ rv = setup_hello_config(p, base_server, gc);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ /* Setup server configs and collect all certificates we use. */
+ gc->cert_reg = tls_cert_reg_make(p);
+ gc->stores = tls_cert_root_stores_make(p);
+ gc->verifiers = tls_cert_verifiers_make(p, gc->stores);
+ for (s = base_server; s; s = s->next) {
+ sc = tls_conf_server_get(s);
+ rv = tls_conf_server_apply_defaults(sc, p);
+ if (APR_SUCCESS != rv) goto cleanup;
+ if (sc->enabled != TLS_FLAG_TRUE) continue;
+ rv = server_conf_setup(p, ptemp, sc, gc);
+ if (APR_SUCCESS != rv) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, "server setup failed: %s",
+ s->server_hostname);
+ goto cleanup;
+ }
+ }
+
+cleanup:
+ if (APR_SUCCESS != rv) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, base_server, "error during post_config");
+ }
+ return rv;
+}
+
+static apr_status_t init_outgoing(apr_pool_t *p, apr_pool_t *ptemp, server_rec *base_server)
+{
+ tls_conf_server_t *sc = tls_conf_server_get(base_server);
+ tls_conf_global_t *gc = sc->global;
+ tls_conf_dir_t *dc;
+ tls_conf_proxy_t *pc;
+ server_rec *s;
+ apr_status_t rv = APR_SUCCESS;
+ int i;
+
+ (void)p; (void)ptemp;
+ (void)gc;
+ ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, base_server, "tls_core_init outgoing");
+ ap_assert(gc->mod_proxy_post_config_done);
+ /* Collect all proxy'ing default server dir configs.
+ * All <Proxy> section dir_configs should already be there - if there were any. */
+ for (s = base_server; s; s = s->next) {
+ dc = tls_conf_dir_server_get(s);
+ rv = tls_conf_dir_apply_defaults(dc, p);
+ if (APR_SUCCESS != rv) goto cleanup;
+ if (dc->proxy_enabled != TLS_FLAG_TRUE) continue;
+ dc->proxy_config = tls_conf_proxy_make(p, dc, gc, s);
+ ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s, "%s: adding proxy_conf to globals",
+ s->server_hostname);
+ APR_ARRAY_PUSH(gc->proxy_configs, tls_conf_proxy_t*) = dc->proxy_config;
+ }
+ /* Now gc->proxy_configs contains all configurations we need to possibly
+ * act on for outgoing connections. */
+ for (i = 0; i < gc->proxy_configs->nelts; ++i) {
+ pc = APR_ARRAY_IDX(gc->proxy_configs, i, tls_conf_proxy_t*);
+ rv = proxy_conf_setup(p, ptemp, pc, gc);
+ if (APR_SUCCESS != rv) goto cleanup;
+ }
+
+cleanup:
+ return rv;
+}
+
+apr_status_t tls_core_init(apr_pool_t *p, apr_pool_t *ptemp, server_rec *base_server)
+{
+ tls_conf_server_t *sc = tls_conf_server_get(base_server);
+ tls_conf_global_t *gc = sc->global;
+ apr_status_t rv = APR_SUCCESS;
+
+ ap_assert(gc);
+ if (TLS_CONF_ST_INIT == gc->status) {
+ rv = init_incoming(p, ptemp, base_server);
+ if (APR_SUCCESS != rv) goto cleanup;
+ gc->status = TLS_CONF_ST_INCOMING_DONE;
+ }
+ if (TLS_CONF_ST_INCOMING_DONE == gc->status) {
+ if (!gc->mod_proxy_post_config_done) goto cleanup;
+
+ rv = init_outgoing(p, ptemp, base_server);
+ if (APR_SUCCESS != rv) goto cleanup;
+ gc->status = TLS_CONF_ST_OUTGOING_DONE;
+ }
+ if (TLS_CONF_ST_OUTGOING_DONE == gc->status) {
+ /* register all loaded certificates for OCSP stapling */
+ rv = tls_ocsp_prime_certs(gc, p, base_server);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ if (gc->verifiers) tls_cert_verifiers_clear(gc->verifiers);
+ if (gc->stores) tls_cert_root_stores_clear(gc->stores);
+ gc->status = TLS_CONF_ST_DONE;
+ }
+cleanup:
+ return rv;
+}
+
+static apr_status_t tls_core_conn_free(void *data)
+{
+ tls_conf_conn_t *cc = data;
+
+ /* free all rustls things we are owning. */
+ if (cc->rustls_connection) {
+ rustls_connection_free(cc->rustls_connection);
+ cc->rustls_connection = NULL;
+ }
+ if (cc->rustls_server_config) {
+ rustls_server_config_free(cc->rustls_server_config);
+ cc->rustls_server_config = NULL;
+ }
+ if (cc->rustls_client_config) {
+ rustls_client_config_free(cc->rustls_client_config);
+ cc->rustls_client_config = NULL;
+ }
+ if (cc->key_cloned && cc->key) {
+ rustls_certified_key_free(cc->key);
+ cc->key = NULL;
+ }
+ if (cc->local_keys) {
+ const rustls_certified_key *key;
+ int i;
+
+ for (i = 0; i < cc->local_keys->nelts; ++i) {
+ key = APR_ARRAY_IDX(cc->local_keys, i, const rustls_certified_key*);
+ rustls_certified_key_free(key);
+ }
+ apr_array_clear(cc->local_keys);
+ }
+ return APR_SUCCESS;
+}
+
+static tls_conf_conn_t *cc_get_or_make(conn_rec *c)
+{
+ tls_conf_conn_t *cc = tls_conf_conn_get(c);
+ if (!cc) {
+ cc = apr_pcalloc(c->pool, sizeof(*cc));
+ cc->server = c->base_server;
+ cc->state = TLS_CONN_ST_INIT;
+ tls_conf_conn_set(c, cc);
+ apr_pool_cleanup_register(c->pool, cc, tls_core_conn_free,
+ apr_pool_cleanup_null);
+ }
+ return cc;
+}
+
+void tls_core_conn_disable(conn_rec *c)
+{
+ tls_conf_conn_t *cc;
+ cc = cc_get_or_make(c);
+ if (cc->state == TLS_CONN_ST_INIT) {
+ cc->state = TLS_CONN_ST_DISABLED;
+ }
+}
+
+void tls_core_conn_bind(conn_rec *c, ap_conf_vector_t *dir_conf)
+{
+ tls_conf_conn_t *cc = cc_get_or_make(c);
+ cc->dc = dir_conf? ap_get_module_config(dir_conf, &tls_module) : NULL;
+}
+
+
+static apr_status_t init_outgoing_connection(conn_rec *c)
+{
+ tls_conf_conn_t *cc = tls_conf_conn_get(c);
+ tls_conf_proxy_t *pc;
+ const apr_array_header_t *ciphersuites = NULL;
+ apr_array_header_t *tls_versions = NULL;
+ rustls_client_config_builder *builder = NULL;
+ rustls_root_cert_store *ca_store = NULL;
+ const char *hostname = NULL, *alpn_note = NULL;
+ rustls_result rr = RUSTLS_RESULT_OK;
+ apr_status_t rv = APR_SUCCESS;
+
+ ap_assert(cc->outgoing);
+ ap_assert(cc->dc);
+ pc = cc->dc->proxy_config;
+ ap_assert(pc);
+
+ hostname = apr_table_get(c->notes, "proxy-request-hostname");
+ alpn_note = apr_table_get(c->notes, "proxy-request-alpn-protos");
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, c->base_server,
+ "setup_outgoing: to %s [ALPN: %s] from configuration in %s"
+ " using CA %s", hostname, alpn_note, pc->defined_in->server_hostname, pc->proxy_ca);
+
+ rv = get_proxy_ciphers(&ciphersuites, c->pool, pc);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ if (pc->proxy_protocol_min > 0) {
+ tls_versions = tls_proto_create_versions_plus(
+ pc->global->proto, (apr_uint16_t)pc->proxy_protocol_min, c->pool);
+ }
+
+ if (ciphersuites && ciphersuites->nelts > 0
+ && tls_versions && tls_versions->nelts >= 0) {
+ rr = rustls_client_config_builder_new_custom(
+ (const struct rustls_supported_ciphersuite *const *)ciphersuites->elts,
+ (size_t)ciphersuites->nelts,
+ (const uint16_t *)tls_versions->elts, (size_t)tls_versions->nelts,
+ &builder);
+ if (RUSTLS_RESULT_OK != rr) goto cleanup;
+ }
+ else {
+ builder = rustls_client_config_builder_new();
+ if (NULL == builder) {
+ rv = APR_ENOMEM;
+ goto cleanup;
+ }
+ }
+
+ if (pc->proxy_ca && strcasecmp(pc->proxy_ca, "default")) {
+ rv = tls_cert_root_stores_get(pc->global->stores, pc->proxy_ca, &ca_store);
+ if (APR_SUCCESS != rv) goto cleanup;
+ rustls_client_config_builder_use_roots(builder, ca_store);
+ }
+
+#if TLS_MACHINE_CERTS
+ if (pc->machine_certified_keys->nelts > 0) {
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, c->base_server,
+ "setup_outgoing: adding %d client certificate", (int)pc->machine_certified_keys->nelts);
+ rr = rustls_client_config_builder_set_certified_key(
+ builder, (const rustls_certified_key**)pc->machine_certified_keys->elts,
+ (size_t)pc->machine_certified_keys->nelts);
+ if (RUSTLS_RESULT_OK != rr) goto cleanup;
+ }
+#endif
+
+ if (hostname) {
+ rustls_client_config_builder_set_enable_sni(builder, true);
+ }
+ else {
+ hostname = "unknown.proxy.local";
+ rustls_client_config_builder_set_enable_sni(builder, false);
+ }
+
+ if (alpn_note) {
+ apr_array_header_t *alpn_proposed = NULL;
+ char *p, *last;
+ apr_size_t len;
+
+ alpn_proposed = apr_array_make(c->pool, 3, sizeof(const char*));
+ p = apr_pstrdup(c->pool, alpn_note);
+ while ((p = apr_strtok(p, ", ", &last))) {
+ len = (apr_size_t)(last - p - (*last? 1 : 0));
+ if (len > 255) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(10329)
+ "ALPN proxy protocol identifier too long: %s", p);
+ rv = APR_EGENERAL;
+ goto cleanup;
+ }
+ APR_ARRAY_PUSH(alpn_proposed, const char*) = apr_pstrndup(c->pool, p, len);
+ p = NULL;
+ }
+ if (alpn_proposed->nelts > 0) {
+ apr_array_header_t *rustls_protocols;
+ const char* proto;
+ rustls_slice_bytes bytes;
+ int i;
+
+ rustls_protocols = apr_array_make(c->pool, alpn_proposed->nelts, sizeof(rustls_slice_bytes));
+ for (i = 0; i < alpn_proposed->nelts; ++i) {
+ proto = APR_ARRAY_IDX(alpn_proposed, i, const char*);
+ bytes.data = (const unsigned char*)proto;
+ bytes.len = strlen(proto);
+ APR_ARRAY_PUSH(rustls_protocols, rustls_slice_bytes) = bytes;
+ }
+
+ rr = rustls_client_config_builder_set_alpn_protocols(builder,
+ (rustls_slice_bytes*)rustls_protocols->elts, (size_t)rustls_protocols->nelts);
+ if (RUSTLS_RESULT_OK != rr) goto cleanup;
+
+ ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, c->base_server,
+ "setup_outgoing: to %s, added %d ALPN protocols from %s",
+ hostname, rustls_protocols->nelts, alpn_note);
+ }
+ }
+
+ cc->rustls_client_config = rustls_client_config_builder_build(builder);
+ builder = NULL;
+
+ rr = rustls_client_connection_new(cc->rustls_client_config, hostname, &cc->rustls_connection);
+ if (RUSTLS_RESULT_OK != rr) goto cleanup;
+ rustls_connection_set_userdata(cc->rustls_connection, c);
+
+cleanup:
+ if (builder != NULL) rustls_client_config_builder_free(builder);
+ if (RUSTLS_RESULT_OK != rr) {
+ const char *err_descr = NULL;
+ rv = tls_util_rustls_error(c->pool, rr, &err_descr);
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, cc->server, APLOGNO(10330)
+ "Failed to init pre_session for outgoing %s to %s: [%d] %s",
+ cc->server->server_hostname, hostname, (int)rr, err_descr);
+ c->aborted = 1;
+ cc->state = TLS_CONN_ST_DISABLED;
+ goto cleanup;
+ }
+ return rv;
+}
+
+int tls_core_pre_conn_init(conn_rec *c)
+{
+ tls_conf_server_t *sc = tls_conf_server_get(c->base_server);
+ tls_conf_conn_t *cc;
+
+ cc = cc_get_or_make(c);
+ if (cc->state == TLS_CONN_ST_INIT) {
+ /* Need to decide if we TLS this connection or not */
+ int enabled =
+#if AP_MODULE_MAGIC_AT_LEAST(20120211, 109)
+ !c->outgoing &&
+#endif
+ sc->enabled == TLS_FLAG_TRUE;
+ cc->state = enabled? TLS_CONN_ST_CLIENT_HELLO : TLS_CONN_ST_DISABLED;
+ cc->client_auth = sc->client_auth;
+ ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, c->base_server,
+ "tls_core_conn_init: %s for tls: %s",
+ enabled? "enabled" : "disabled", c->base_server->server_hostname);
+ }
+ else if (cc->state == TLS_CONN_ST_DISABLED) {
+ ap_log_error(APLOG_MARK, APLOG_TRACE4, 0, c->base_server,
+ "tls_core_conn_init, not our connection: %s",
+ c->base_server->server_hostname);
+ goto cleanup;
+ }
+
+cleanup:
+ return TLS_CONN_ST_IS_ENABLED(cc)? OK : DECLINED;
+}
+
+apr_status_t tls_core_conn_init(conn_rec *c)
+{
+ tls_conf_server_t *sc = tls_conf_server_get(c->base_server);
+ tls_conf_conn_t *cc;
+ rustls_result rr = RUSTLS_RESULT_OK;
+ apr_status_t rv = APR_SUCCESS;
+
+ cc = tls_conf_conn_get(c);
+ if (cc && TLS_CONN_ST_IS_ENABLED(cc) && !cc->rustls_connection) {
+ if (cc->outgoing) {
+ rv = init_outgoing_connection(c);
+ if (APR_SUCCESS != rv) goto cleanup;
+ }
+ else {
+ /* Use a generic rustls_connection with its defaults, which we feed
+ * the first TLS bytes from the client. Its Hello message will trigger
+ * our callback where we can inspect the (possibly) supplied SNI and
+ * select another server.
+ */
+ rr = rustls_server_connection_new(sc->global->rustls_hello_config, &cc->rustls_connection);
+ if (RUSTLS_RESULT_OK != rr) goto cleanup;
+ /* we might refuse requests on this connection, e.g. ACME challenge */
+ cc->service_unavailable = sc->service_unavailable;
+ }
+ rustls_connection_set_userdata(cc->rustls_connection, c);
+ }
+
+cleanup:
+ if (RUSTLS_RESULT_OK != rr) {
+ const char *err_descr = NULL;
+ rv = tls_util_rustls_error(c->pool, rr, &err_descr);
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, sc->server, APLOGNO(10331)
+ "Failed to init TLS connection for server %s: [%d] %s",
+ sc->server->server_hostname, (int)rr, err_descr);
+ c->aborted = 1;
+ cc->state = TLS_CONN_ST_DISABLED;
+ goto cleanup;
+ }
+ return rv;
+}
+
+static int find_vhost(void *sni_hostname, conn_rec *c, server_rec *s)
+{
+ if (tls_util_name_matches_server(sni_hostname, s)) {
+ tls_conf_conn_t *cc = tls_conf_conn_get(c);
+ cc->server = s;
+ return 1;
+ }
+ return 0;
+}
+
+static apr_status_t select_application_protocol(
+ conn_rec *c, server_rec *s, rustls_server_config_builder *builder)
+{
+ tls_conf_conn_t *cc = tls_conf_conn_get(c);
+ const char *proposed;
+ rustls_result rr = RUSTLS_RESULT_OK;
+ apr_status_t rv = APR_SUCCESS;
+
+ /* The server always has a protocol it uses, normally "http/1.1".
+ * if the client, via ALPN, proposes protocols, they are in
+ * order of preference.
+ * We propose those to modules registered in the server and
+ * get the protocol back that someone is willing to run on this
+ * connection.
+ * If this is different from what the connection already does,
+ * we tell the server (and all protocol modules) to switch.
+ * If successful, we announce that protocol back to the client as
+ * our only ALPN protocol and then do the 'real' handshake.
+ */
+ cc->application_protocol = ap_get_protocol(c);
+ if (cc->alpn && cc->alpn->nelts > 0) {
+ rustls_slice_bytes rsb;
+
+ proposed = ap_select_protocol(c, NULL, s, cc->alpn);
+ if (!proposed) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, c,
+ "ALPN: no protocol selected in server");
+ goto cleanup;
+ }
+
+ if (strcmp(proposed, cc->application_protocol)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, c,
+ "ALPN: switching protocol from `%s` to `%s`", cc->application_protocol, proposed);
+ rv = ap_switch_protocol(c, NULL, cc->server, proposed);
+ if (APR_SUCCESS != rv) goto cleanup;
+ }
+
+ rsb.data = (const unsigned char*)proposed;
+ rsb.len = strlen(proposed);
+ rr = rustls_server_config_builder_set_alpn_protocols(builder, &rsb, 1);
+ if (RUSTLS_RESULT_OK != rr) goto cleanup;
+
+ cc->application_protocol = proposed;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, c,
+ "ALPN: using connection protocol `%s`", cc->application_protocol);
+
+ /* protocol was switched, this could be a challenge protocol
+ * such as "acme-tls/1". Give handlers the opportunity to
+ * override the certificate for this connection. */
+ if (strcmp("h2", proposed) && strcmp("http/1.1", proposed)) {
+ const char *cert_pem = NULL, *key_pem = NULL;
+ if (ap_ssl_answer_challenge(c, cc->sni_hostname, &cert_pem, &key_pem)) {
+ /* With ACME we can have challenge connections to a unknown domains
+ * that need to be answered with a special certificate and will
+ * otherwise not answer any requests. See RFC 8555 */
+ rv = use_local_key(c, cert_pem, key_pem);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ cc->service_unavailable = 1;
+ cc->client_auth = TLS_CLIENT_AUTH_NONE;
+ }
+ }
+ }
+
+cleanup:
+ if (rr != RUSTLS_RESULT_OK) {
+ const char *err_descr = NULL;
+ rv = tls_util_rustls_error(c->pool, rr, &err_descr);
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10332)
+ "Failed to init session for server %s: [%d] %s",
+ s->server_hostname, (int)rr, err_descr);
+ c->aborted = 1;
+ goto cleanup;
+ }
+ return rv;
+}
+
+static apr_status_t build_server_connection(rustls_connection **pconnection,
+ const rustls_server_config **pconfig,
+ conn_rec *c)
+{
+ tls_conf_conn_t *cc = tls_conf_conn_get(c);
+ tls_conf_server_t *sc;
+ const apr_array_header_t *tls_versions = NULL;
+ rustls_server_config_builder *builder = NULL;
+ const rustls_server_config *config = NULL;
+ rustls_connection *rconnection = NULL;
+ rustls_result rr = RUSTLS_RESULT_OK;
+ apr_status_t rv = APR_SUCCESS;
+
+ sc = tls_conf_server_get(cc->server);
+
+ if (sc->tls_protocol_min > 0) {
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, sc->server,
+ "init server: set protocol min version %04x", sc->tls_protocol_min);
+ tls_versions = tls_proto_create_versions_plus(
+ sc->global->proto, (apr_uint16_t)sc->tls_protocol_min, c->pool);
+ if (tls_versions->nelts > 0) {
+ if (sc->tls_protocol_min != APR_ARRAY_IDX(tls_versions, 0, apr_uint16_t)) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, sc->server, APLOGNO(10333)
+ "Init: the minimum protocol version configured for %s (%04x) "
+ "is not supported and version %04x was selected instead.",
+ sc->server->server_hostname, sc->tls_protocol_min,
+ APR_ARRAY_IDX(tls_versions, 0, apr_uint16_t));
+ }
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, sc->server, APLOGNO(10334)
+ "Unable to configure the protocol version for %s: "
+ "neither the configured minimum version (%04x), nor any higher one is "
+ "available.", sc->server->server_hostname, sc->tls_protocol_min);
+ rv = APR_ENOTIMPL; goto cleanup;
+ }
+ }
+ else if (sc->ciphersuites && sc->ciphersuites->nelts > 0) {
+ /* FIXME: rustls-ffi current has not way to make a builder with ALL_PROTOCOL_VERSIONS */
+ tls_versions = tls_proto_create_versions_plus(sc->global->proto, 0, c->pool);
+ }
+
+ if (sc->ciphersuites && sc->ciphersuites->nelts > 0
+ && tls_versions && tls_versions->nelts >= 0) {
+ rr = rustls_server_config_builder_new_custom(
+ (const struct rustls_supported_ciphersuite *const *)sc->ciphersuites->elts,
+ (size_t)sc->ciphersuites->nelts,
+ (const uint16_t *)tls_versions->elts, (size_t)tls_versions->nelts,
+ &builder);
+ if (RUSTLS_RESULT_OK != rr) goto cleanup;
+ }
+ else {
+ builder = rustls_server_config_builder_new();
+ if (NULL == builder) {
+ rv = APR_ENOMEM;
+ goto cleanup;
+ }
+ }
+
+ /* decide on the application protocol, this may change other
+ * settings like client_auth. */
+ rv = select_application_protocol(c, cc->server, builder);
+
+ if (cc->client_auth != TLS_CLIENT_AUTH_NONE) {
+ ap_assert(sc->client_ca); /* checked in server_setup */
+ if (cc->client_auth == TLS_CLIENT_AUTH_REQUIRED) {
+ const rustls_client_cert_verifier *verifier;
+ rv = tls_cert_client_verifiers_get(sc->global->verifiers, sc->client_ca, &verifier);
+ if (APR_SUCCESS != rv) goto cleanup;
+ rustls_server_config_builder_set_client_verifier(builder, verifier);
+ }
+ else {
+ const rustls_client_cert_verifier_optional *verifier;
+ rv = tls_cert_client_verifiers_get_optional(sc->global->verifiers, sc->client_ca, &verifier);
+ if (APR_SUCCESS != rv) goto cleanup;
+ rustls_server_config_builder_set_client_verifier_optional(builder, verifier);
+ }
+ }
+
+ rustls_server_config_builder_set_hello_callback(builder, select_certified_key);
+
+ rr = rustls_server_config_builder_set_ignore_client_order(
+ builder, sc->honor_client_order == TLS_FLAG_FALSE);
+ if (RUSTLS_RESULT_OK != rr) goto cleanup;
+
+ rv = tls_cache_init_server(builder, sc->server);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ config = rustls_server_config_builder_build(builder);
+ builder = NULL;
+ if (!config) {
+ rv = APR_ENOMEM; goto cleanup;
+ }
+
+ rr = rustls_server_connection_new(config, &rconnection);
+ if (RUSTLS_RESULT_OK != rr) goto cleanup;
+ rustls_connection_set_userdata(rconnection, c);
+
+cleanup:
+ if (rr != RUSTLS_RESULT_OK) {
+ const char *err_descr = NULL;
+ rv = tls_util_rustls_error(c->pool, rr, &err_descr);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, sc->server, APLOGNO(10335)
+ "Failed to init session for server %s: [%d] %s",
+ sc->server->server_hostname, (int)rr, err_descr);
+ }
+ if (APR_SUCCESS == rv) {
+ *pconfig = config;
+ *pconnection = rconnection;
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, sc->server,
+ "tls_core_conn_server_init done: %s",
+ sc->server->server_hostname);
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, sc->server, APLOGNO(10336)
+ "Failed to init session for server %s",
+ sc->server->server_hostname);
+ c->aborted = 1;
+ if (config) rustls_server_config_free(config);
+ if (builder) rustls_server_config_builder_free(builder);
+ }
+ return rv;
+}
+
+apr_status_t tls_core_conn_seen_client_hello(conn_rec *c)
+{
+ tls_conf_conn_t *cc = tls_conf_conn_get(c);
+ tls_conf_server_t *sc;
+ apr_status_t rv = APR_SUCCESS;
+ int sni_match = 0;
+
+ /* The initial rustls generic session has been fed the client hello and
+ * we have extracted SNI and ALPN values (so present).
+ * Time to select the actual server_rec and application protocol that
+ * will be used on this connection. */
+ ap_assert(cc);
+ sc = tls_conf_server_get(cc->server);
+ if (!cc->client_hello_seen) goto cleanup;
+
+ if (cc->sni_hostname) {
+ if (ap_vhost_iterate_given_conn(c, find_vhost, (void*)cc->sni_hostname)) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c, APLOGNO(10337)
+ "vhost_init: virtual host found for SNI '%s'", cc->sni_hostname);
+ sni_match = 1;
+ }
+ else if (tls_util_name_matches_server(cc->sni_hostname, ap_server_conf)) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c, APLOGNO(10338)
+ "vhost_init: virtual host NOT found, but base server[%s] matches SNI '%s'",
+ ap_server_conf->server_hostname, cc->sni_hostname);
+ cc->server = ap_server_conf;
+ sni_match = 1;
+ }
+ else if (sc->strict_sni == TLS_FLAG_FALSE) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c, APLOGNO(10339)
+ "vhost_init: no virtual host found, relaxed SNI checking enabled, SNI '%s'",
+ cc->sni_hostname);
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c, APLOGNO(10340)
+ "vhost_init: no virtual host, nor base server[%s] matches SNI '%s'",
+ c->base_server->server_hostname, cc->sni_hostname);
+ cc->server = sc->global->ap_server;
+ rv = APR_NOTFOUND; goto cleanup;
+ }
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10341)
+ "vhost_init: no SNI hostname provided by client");
+ }
+
+ /* reinit, we might have a new server selected */
+ sc = tls_conf_server_get(cc->server);
+ /* on relaxed SNI matches, we do not enforce the 503 of fallback
+ * certificates. */
+ if (!cc->service_unavailable) {
+ cc->service_unavailable = sni_match? sc->service_unavailable : 0;
+ }
+
+ /* if found or not, cc->server will be the server we use now to do
+ * the real handshake and, if successful, the traffic after that.
+ * Free the current session and create the real one for the
+ * selected server. */
+ if (cc->rustls_server_config) {
+ rustls_server_config_free(cc->rustls_server_config);
+ cc->rustls_server_config = NULL;
+ }
+ rustls_connection_free(cc->rustls_connection);
+ cc->rustls_connection = NULL;
+
+ rv = build_server_connection(&cc->rustls_connection, &cc->rustls_server_config, c);
+
+cleanup:
+ return rv;
+}
+
+apr_status_t tls_core_conn_post_handshake(conn_rec *c)
+{
+ tls_conf_conn_t *cc = tls_conf_conn_get(c);
+ tls_conf_server_t *sc = tls_conf_server_get(cc->server);
+ const rustls_supported_ciphersuite *rsuite;
+ const rustls_certificate *cert;
+ apr_status_t rv = APR_SUCCESS;
+
+ if (rustls_connection_is_handshaking(cc->rustls_connection)) {
+ rv = APR_EGENERAL;
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, cc->server, APLOGNO(10342)
+ "post handshake, but rustls claims to still be handshaking: %s",
+ cc->server->server_hostname);
+ goto cleanup;
+ }
+
+ cc->tls_protocol_id = rustls_connection_get_protocol_version(cc->rustls_connection);
+ cc->tls_protocol_name = tls_proto_get_version_name(sc->global->proto,
+ cc->tls_protocol_id, c->pool);
+ rsuite = rustls_connection_get_negotiated_ciphersuite(cc->rustls_connection);
+ if (!rsuite) {
+ rv = APR_EGENERAL;
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, cc->server, APLOGNO(10343)
+ "post handshake, but rustls does not report negotiated cipher suite: %s",
+ cc->server->server_hostname);
+ goto cleanup;
+ }
+ cc->tls_cipher_id = rustls_supported_ciphersuite_get_suite(rsuite);
+ cc->tls_cipher_name = tls_proto_get_cipher_name(sc->global->proto,
+ cc->tls_cipher_id, c->pool);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "post_handshake %s: %s [%s]",
+ cc->server->server_hostname, cc->tls_protocol_name, cc->tls_cipher_name);
+
+ cert = rustls_connection_get_peer_certificate(cc->rustls_connection, 0);
+ if (cert) {
+ size_t i = 0;
+
+ cc->peer_certs = apr_array_make(c->pool, 5, sizeof(const rustls_certificate*));
+ while (cert) {
+ APR_ARRAY_PUSH(cc->peer_certs, const rustls_certificate*) = cert;
+ cert = rustls_connection_get_peer_certificate(cc->rustls_connection, ++i);
+ }
+ }
+ if (!cc->peer_certs && sc->client_auth == TLS_CLIENT_AUTH_REQUIRED) {
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(10344)
+ "A client certificate is required, but no acceptable certificate was presented.");
+ rv = APR_ECONNABORTED;
+ }
+
+ rv = tls_var_handshake_done(c);
+cleanup:
+ return rv;
+}
+
+/**
+ * Return != 0, if a connection also serve requests for server <other>.
+ */
+static int tls_conn_compatible_for(tls_conf_conn_t *cc, server_rec *other)
+{
+ tls_conf_server_t *oc, *sc;
+ const rustls_certified_key *sk, *ok;
+ int i;
+
+ /* - differences in certificates are the responsibility of the client.
+ * if it thinks the SNI server works for r->server, we are fine with that.
+ * - if there are differences in requirements to client certificates, we
+ * need to deny the request.
+ */
+ if (!cc->server || !other) return 0;
+ if (cc->server == other) return 1;
+ oc = tls_conf_server_get(other);
+ if (!oc) return 0;
+ sc = tls_conf_server_get(cc->server);
+ if (!sc) return 0;
+
+ /* same certified keys used? */
+ if (sc->certified_keys->nelts != oc->certified_keys->nelts) return 0;
+ for (i = 0; i < sc->certified_keys->nelts; ++i) {
+ sk = APR_ARRAY_IDX(sc->certified_keys, i, const rustls_certified_key*);
+ ok = APR_ARRAY_IDX(oc->certified_keys, i, const rustls_certified_key*);
+ if (sk != ok) return 0;
+ }
+
+ /* If the connection TLS version is below other other min one, no */
+ if (oc->tls_protocol_min > 0 && cc->tls_protocol_id < oc->tls_protocol_min) return 0;
+ /* If the connection TLS cipher is listed as suppressed by other, no */
+ if (oc->tls_supp_ciphers && tls_util_array_uint16_contains(
+ oc->tls_supp_ciphers, cc->tls_cipher_id)) return 0;
+ return 1;
+}
+
+int tls_core_request_check(request_rec *r)
+{
+ conn_rec *c = r->connection;
+ tls_conf_conn_t *cc = tls_conf_conn_get(c->master? c->master : c);
+ int rv = DECLINED; /* do not object to the request */
+
+ /* If we are not enabled on this connection, leave. We are not renegotiating.
+ * Otherwise:
+ * - service is unavailable when we have only a fallback certificate or
+ * when a challenge protocol is active (ACME tls-alpn-01 for example).
+ * - with vhosts configured and no SNI from the client, deny access.
+ * - are servers compatible for connection sharing?
+ */
+ if (!TLS_CONN_ST_IS_ENABLED(cc)) goto cleanup;
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
+ "tls_core_request_check[%s, %d]: %s", r->hostname,
+ cc? cc->service_unavailable : 2, r->the_request);
+ if (cc->service_unavailable) {
+ rv = HTTP_SERVICE_UNAVAILABLE; goto cleanup;
+ }
+ if (!cc->sni_hostname && r->connection->vhost_lookup_data) {
+ rv = HTTP_FORBIDDEN; goto cleanup;
+ }
+ if (!tls_conn_compatible_for(cc, r->server)) {
+ rv = HTTP_MISDIRECTED_REQUEST;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10345)
+ "Connection host %s, selected via SNI, and request host %s"
+ " have incompatible TLS configurations.",
+ cc->server->server_hostname, r->hostname);
+ goto cleanup;
+ }
+cleanup:
+ return rv;
+}
+
+apr_status_t tls_core_error(conn_rec *c, rustls_result rr, const char **perrstr)
+{
+ tls_conf_conn_t *cc = tls_conf_conn_get(c);
+ apr_status_t rv;
+
+ rv = tls_util_rustls_error(c->pool, rr, perrstr);
+ if (cc) {
+ cc->last_error = rr;
+ cc->last_error_descr = *perrstr;
+ }
+ return rv;
+}
+
+int tls_core_setup_outgoing(conn_rec *c)
+{
+ tls_conf_conn_t *cc;
+ int rv = DECLINED;
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
+ "tls_core_setup_outgoing called");
+#if AP_MODULE_MAGIC_AT_LEAST(20120211, 109)
+ if (!c->outgoing) goto cleanup;
+#endif
+ cc = cc_get_or_make(c);
+ if (cc->state == TLS_CONN_ST_DISABLED) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
+ "tls_core_setup_outgoing: already disabled");
+ goto cleanup;
+ }
+ if (TLS_CONN_ST_IS_ENABLED(cc)) {
+ /* we already handle it, allow repeated calls */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
+ "tls_core_setup_outgoing: already enabled");
+ rv = OK; goto cleanup;
+ }
+ cc->outgoing = 1;
+ if (!cc->dc) {
+ /* In case there is not dir_conf bound for this connection, we fallback
+ * to the defaults in the base server (we have no virtual host config to use) */
+ cc->dc = ap_get_module_config(c->base_server->lookup_defaults, &tls_module);
+ }
+ if (cc->dc->proxy_enabled != TLS_FLAG_TRUE) {
+ cc->state = TLS_CONN_ST_DISABLED;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
+ "tls_core_setup_outgoing: TLSProxyEngine not configured");
+ goto cleanup;
+ }
+ /* we handle this connection */
+ cc->state = TLS_CONN_ST_CLIENT_HELLO;
+ rv = OK;
+
+cleanup:
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
+ "tls_core_setup_outgoing returns %s", rv == OK? "OK" : "DECLINED");
+ return rv;
+}
diff --git a/modules/tls/tls_core.h b/modules/tls/tls_core.h
new file mode 100644
index 0000000..6ee1713
--- /dev/null
+++ b/modules/tls/tls_core.h
@@ -0,0 +1,184 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef tls_core_h
+#define tls_core_h
+
+/* The module's state handling of a connection in normal chronological order,
+ */
+typedef enum {
+ TLS_CONN_ST_INIT, /* being initialized */
+ TLS_CONN_ST_DISABLED, /* TLS is disabled here */
+ TLS_CONN_ST_CLIENT_HELLO, /* TLS is enabled, prep handshake */
+ TLS_CONN_ST_HANDSHAKE, /* TLS is enabled, handshake ongonig */
+ TLS_CONN_ST_TRAFFIC, /* TLS is enabled, handshake done */
+ TLS_CONN_ST_NOTIFIED, /* TLS is enabled, notification to end sent */
+ TLS_CONN_ST_DONE, /* TLS is enabled, TLS has shut down */
+} tls_conn_state_t;
+
+#define TLS_CONN_ST_IS_ENABLED(cc) (cc && cc->state >= TLS_CONN_ST_CLIENT_HELLO)
+
+struct tls_filter_ctx_t;
+
+/* The modules configuration for a connection. Created at connection
+ * start and mutable during the lifetime of the connection.
+ * (A conn_rec is only ever processed by one thread at a time.)
+ */
+typedef struct {
+ server_rec *server; /* the server_rec selected for this connection,
+ * initially c->base_server, to be negotiated via SNI. */
+ tls_conf_dir_t *dc; /* directory config applying here */
+ tls_conn_state_t state;
+ int outgoing; /* != 0 iff outgoing connection (redundant once c->outgoing is everywhere) */
+ int service_unavailable; /* we 503 all requests on this connection */
+ tls_client_auth_t client_auth; /* how client authentication with certificates is used */
+ int client_hello_seen; /* the client hello has been inspected */
+
+ rustls_connection *rustls_connection; /* the session used on this connection or NULL */
+ const rustls_server_config *rustls_server_config; /* the config made for this connection (incoming) or NULL */
+ const rustls_client_config *rustls_client_config; /* the config made for this connection (outgoing) or NULL */
+ struct tls_filter_ctx_t *filter_ctx; /* the context used by this connection's tls filters */
+
+ apr_array_header_t *local_keys; /* rustls_certified_key* array of connection specific keys */
+ const rustls_certified_key *key; /* the key selected for the session */
+ int key_cloned; /* != 0 iff the key is a unique clone, to be freed */
+ apr_array_header_t *peer_certs; /* handshaked peer ceritificates or NULL */
+ const char *sni_hostname; /* the SNI value from the client hello, or NULL */
+ const apr_array_header_t *alpn; /* the protocols proposed via ALPN by the client */
+ const char *application_protocol; /* the ALPN selected protocol or NULL */
+
+ int session_id_cache_hit; /* if a submitted session id was found in our cache */
+
+ apr_uint16_t tls_protocol_id; /* the TLS version negotiated */
+ const char *tls_protocol_name; /* the name of the TLS version negotiated */
+ apr_uint16_t tls_cipher_id; /* the TLS cipher suite negotiated */
+ const char *tls_cipher_name; /* the name of TLS cipher suite negotiated */
+
+ const char *user_name; /* != NULL if we derived a TLSUserName from the client_cert */
+ apr_table_t *subprocess_env; /* common TLS variables for this connection */
+
+ rustls_result last_error;
+ const char *last_error_descr;
+
+} tls_conf_conn_t;
+
+/* Get the connection specific module configuration. */
+tls_conf_conn_t *tls_conf_conn_get(conn_rec *c);
+
+/* Set the module configuration for a connection. */
+void tls_conf_conn_set(conn_rec *c, tls_conf_conn_t *cc);
+
+/* Return OK iff this connection is a TSL connection (or a secondary on a TLS connection). */
+int tls_conn_check_ssl(conn_rec *c);
+
+/**
+ * Initialize the module's global and server specific settings. This runs
+ * in Apache's "post-config" phase, meaning the configuration has been read
+ * and checked for syntactic and other easily verifiable errors and now
+ * it is time to load everything in and make it ready for traffic.
+ * <p> a memory pool staying with us the whole time until the server stops/reloads.
+ * <ptemp> a temporary pool as a scratch buffer that will be destroyed shortly after.
+ * <base_server> the server for the global configuration which links -> next to
+ * all contained virtual hosts configured.
+ */
+apr_status_t tls_core_init(apr_pool_t *p, apr_pool_t *ptemp, server_rec *base_server);
+
+/**
+ * Initialize the module's outgoing connection settings. This runs
+ * in Apache's "post-config" phase after mod_proxy.
+ */
+apr_status_t tls_core_init_outgoing(apr_pool_t *p, apr_pool_t *ptemp, server_rec *base_server);
+
+/**
+ * Supply a directory configuration for the connection to work with. This
+ * maybe NULL. This can be called several times during the lifetime of a
+ * connection and must not change the current TLS state.
+ * @param c the connection
+ * @param dir_conf optional directory configuration that applies
+ */
+void tls_core_conn_bind(conn_rec *c, ap_conf_vector_t *dir_conf);
+
+/**
+ * Disable TLS on a new connection. Will do nothing on already initialized
+ * connections.
+ * @param c a new connection
+ */
+void tls_core_conn_disable(conn_rec *c);
+
+/**
+ * Initialize the tls_conf_connt_t for the connection
+ * and decide if TLS is enabled or not.
+ * @return OK if enabled, DECLINED otherwise
+ */
+int tls_core_pre_conn_init(conn_rec *c);
+
+/**
+ * Initialize the module for a TLS enabled connection.
+ * @param c a new connection
+ */
+apr_status_t tls_core_conn_init(conn_rec *c);
+
+/**
+ * Called when the ClientHello has been received and values from it
+ * have been extracted into the `tls_conf_conn_t` of the connection.
+ *
+ * Decides:
+ * - which `server_rec` this connection is for (SNI)
+ * - which application protocol to use (ALPN)
+ * This may be unsuccessful for several reasons. The SNI
+ * from the client may not be known or the selected server
+ * has not certificates available. etc.
+ * On success, a proper `rustls_connection` will have been
+ * created and set in the `tls_conf_conn_t` of the connection.
+ */
+apr_status_t tls_core_conn_seen_client_hello(conn_rec *c);
+
+/**
+ * The TLS handshake for the connection has been successfully performed.
+ * This means that TLS related properties, such as TLS version and cipher,
+ * are known and the props in `tls_conf_conn_t` of the connection
+ * can be set.
+ */
+apr_status_t tls_core_conn_post_handshake(conn_rec *c);
+
+/**
+ * After a request has been read, but before processing is started, we
+ * check if everything looks good to us:
+ * - was an SNI hostname provided by the client when we have vhosts to choose from?
+ * if not, we deny it.
+ * - if the SNI hostname and request host are not the same, are they - from TLS
+ * point of view - 'compatible' enough? For example, if one server requires
+ * client certificates and the other not (or with different settings), such
+ * a request will also be denied.
+ * returns DECLINED if everything is ok, otherwise an HTTP response code to
+ * generate an error page for.
+ */
+int tls_core_request_check(request_rec *r);
+
+/**
+ * A Rustls error happened while processing the connection. Look up an
+ * error description, determine the apr_status_t to use for it and remember
+ * this as the last error at tls_conf_conn_t.
+ */
+apr_status_t tls_core_error(conn_rec *c, rustls_result rr, const char **perrstr);
+
+/**
+ * Determine if we handle the TLS for an outgoing connection or not.
+ * @param c the connection
+ * @return OK if we handle the TLS, DECLINED otherwise.
+ */
+int tls_core_setup_outgoing(conn_rec *c);
+
+#endif /* tls_core_h */
diff --git a/modules/tls/tls_filter.c b/modules/tls/tls_filter.c
new file mode 100644
index 0000000..0ee6be6
--- /dev/null
+++ b/modules/tls/tls_filter.c
@@ -0,0 +1,1017 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <assert.h>
+#include <apr_lib.h>
+#include <apr_strings.h>
+
+#include <httpd.h>
+#include <http_connection.h>
+#include <http_core.h>
+#include <http_request.h>
+#include <http_log.h>
+#include <ap_socache.h>
+
+#include <rustls.h>
+
+#include "tls_proto.h"
+#include "tls_conf.h"
+#include "tls_core.h"
+#include "tls_filter.h"
+#include "tls_util.h"
+
+
+extern module AP_MODULE_DECLARE_DATA tls_module;
+APLOG_USE_MODULE(tls);
+
+
+static rustls_io_result tls_read_callback(
+ void *userdata, unsigned char *buf, size_t n, size_t *out_n)
+{
+ tls_data_t *d = userdata;
+ size_t len = d->len > n? n : d->len;
+ memcpy(buf, d->data, len);
+ *out_n = len;
+ return 0;
+}
+
+/**
+ * Provide TLS encrypted data to the rustls server_session in <fctx->cc->rustls_connection>.
+ *
+ * If <fctx->fin_tls_bb> holds data, take it from there. Otherwise perform a
+ * read via the network filters below us into that brigade.
+ *
+ * <fctx->fin_block> determines if we do a blocking read inititally or not.
+ * If the first read did to not produce enough data, any secondary read is done
+ * non-blocking.
+ *
+ * Had any data been added to <fctx->cc->rustls_connection>, call its "processing"
+ * function to handle the added data before leaving.
+ */
+static apr_status_t read_tls_to_rustls(
+ tls_filter_ctx_t *fctx, apr_size_t len, apr_read_type_e block, int errors_expected)
+{
+ tls_data_t d;
+ apr_size_t rlen;
+ apr_off_t passed = 0;
+ rustls_result rr = RUSTLS_RESULT_OK;
+ int os_err;
+ apr_status_t rv = APR_SUCCESS;
+
+ if (APR_BRIGADE_EMPTY(fctx->fin_tls_bb)) {
+ ap_log_error(APLOG_MARK, APLOG_TRACE2, rv, fctx->cc->server,
+ "read_tls_to_rustls, get data from network, block=%d", block);
+ rv = ap_get_brigade(fctx->fin_ctx->next, fctx->fin_tls_bb,
+ AP_MODE_READBYTES, block, (apr_off_t)len);
+ if (APR_SUCCESS != rv) {
+ goto cleanup;
+ }
+ }
+
+ while (!APR_BRIGADE_EMPTY(fctx->fin_tls_bb) && passed < (apr_off_t)len) {
+ apr_bucket *b = APR_BRIGADE_FIRST(fctx->fin_tls_bb);
+
+ if (APR_BUCKET_IS_EOS(b)) {
+ ap_log_error(APLOG_MARK, APLOG_TRACE2, rv, fctx->cc->server,
+ "read_tls_to_rustls, EOS");
+ if (fctx->fin_tls_buffer_bb) {
+ apr_brigade_cleanup(fctx->fin_tls_buffer_bb);
+ }
+ rv = APR_EOF; goto cleanup;
+ }
+
+ rv = apr_bucket_read(b, (const char**)&d.data, &d.len, block);
+ if (APR_STATUS_IS_EOF(rv)) {
+ apr_bucket_delete(b);
+ continue;
+ }
+ else if (APR_SUCCESS != rv) {
+ goto cleanup;
+ }
+
+ if (d.len > 0) {
+ /* got something, do not block on getting more */
+ block = APR_NONBLOCK_READ;
+
+ os_err = rustls_connection_read_tls(fctx->cc->rustls_connection,
+ tls_read_callback, &d, &rlen);
+ if (os_err) {
+ rv = APR_FROM_OS_ERROR(os_err);
+ goto cleanup;
+ }
+
+ if (fctx->fin_tls_buffer_bb) {
+ /* we buffer for later replay on the 'real' rustls_connection */
+ apr_brigade_write(fctx->fin_tls_buffer_bb, NULL, NULL, (const char*)d.data, rlen);
+ }
+ if (rlen >= d.len) {
+ apr_bucket_delete(b);
+ }
+ else {
+ b->start += (apr_off_t)rlen;
+ b->length -= rlen;
+ }
+ fctx->fin_bytes_in_rustls += (apr_off_t)d.len;
+ passed += (apr_off_t)rlen;
+ }
+ else if (d.len == 0) {
+ apr_bucket_delete(b);
+ }
+ }
+
+ if (passed > 0) {
+ rr = rustls_connection_process_new_packets(fctx->cc->rustls_connection);
+ if (rr != RUSTLS_RESULT_OK) goto cleanup;
+ }
+
+cleanup:
+ if (rr != RUSTLS_RESULT_OK) {
+ rv = APR_ECONNRESET;
+ if (!errors_expected) {
+ const char *err_descr = "";
+ rv = tls_core_error(fctx->c, rr, &err_descr);
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, rv, fctx->c, APLOGNO(10353)
+ "processing TLS data: [%d] %s", (int)rr, err_descr);
+ }
+ }
+ else if (APR_STATUS_IS_EOF(rv) && passed > 0) {
+ /* encountering EOF while actually having read sth is a success. */
+ rv = APR_SUCCESS;
+ }
+ else if (APR_SUCCESS == rv && passed == 0 && fctx->fin_block == APR_NONBLOCK_READ) {
+ rv = APR_EAGAIN;
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_TRACE2, rv, fctx->cc->server,
+ "read_tls_to_rustls, passed %ld bytes to rustls", (long)passed);
+ }
+ return rv;
+}
+
+static apr_status_t fout_pass_tls_to_net(tls_filter_ctx_t *fctx)
+{
+ apr_status_t rv = APR_SUCCESS;
+
+ if (!APR_BRIGADE_EMPTY(fctx->fout_tls_bb)) {
+ rv = ap_pass_brigade(fctx->fout_ctx->next, fctx->fout_tls_bb);
+ if (APR_SUCCESS == rv && fctx->c->aborted) {
+ rv = APR_ECONNRESET;
+ }
+ fctx->fout_bytes_in_tls_bb = 0;
+ apr_brigade_cleanup(fctx->fout_tls_bb);
+ }
+ return rv;
+}
+
+static apr_status_t fout_pass_all_to_net(
+ tls_filter_ctx_t *fctx, int flush);
+
+static apr_status_t filter_abort(
+ tls_filter_ctx_t *fctx)
+{
+ apr_status_t rv;
+
+ if (fctx->cc->state != TLS_CONN_ST_DONE) {
+ if (fctx->cc->state > TLS_CONN_ST_CLIENT_HELLO) {
+ rustls_connection_send_close_notify(fctx->cc->rustls_connection);
+ rv = fout_pass_all_to_net(fctx, 1);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, fctx->c, "filter_abort, flushed output");
+ }
+ fctx->c->aborted = 1;
+ fctx->cc->state = TLS_CONN_ST_DONE;
+ }
+ return APR_ECONNABORTED;
+}
+
+static apr_status_t filter_recv_client_hello(tls_filter_ctx_t *fctx)
+{
+ apr_status_t rv = APR_SUCCESS;
+
+ ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, fctx->cc->server,
+ "tls_filter, server=%s, recv client hello", fctx->cc->server->server_hostname);
+ /* only for incoming connections */
+ ap_assert(!fctx->cc->outgoing);
+
+ if (rustls_connection_is_handshaking(fctx->cc->rustls_connection)) {
+ apr_bucket_brigade *bb_tmp;
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, fctx->c, "filter_recv_client_hello: start");
+ fctx->fin_tls_buffer_bb = apr_brigade_create(fctx->c->pool, fctx->c->bucket_alloc);
+ do {
+ if (rustls_connection_wants_read(fctx->cc->rustls_connection)) {
+ rv = read_tls_to_rustls(fctx, fctx->fin_max_in_rustls, APR_BLOCK_READ, 1);
+ if (APR_SUCCESS != rv) {
+ if (fctx->cc->client_hello_seen) {
+ rv = APR_EAGAIN; /* we got what we needed */
+ break;
+ }
+ /* Something went wrong before we saw the client hello.
+ * This is a real error on which we should not continue. */
+ goto cleanup;
+ }
+ }
+ /* Notice: we never write here to the client. We just want to inspect
+ * the client hello. */
+ } while (!fctx->cc->client_hello_seen);
+
+ /* We have seen the client hello and selected the server (vhost) to use
+ * on this connection. Set up the 'real' rustls_connection based on the
+ * servers 'real' rustls_config. */
+ rv = tls_core_conn_seen_client_hello(fctx->c);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ bb_tmp = fctx->fin_tls_bb; /* data we have yet to feed to rustls */
+ fctx->fin_tls_bb = fctx->fin_tls_buffer_bb; /* data we already fed to the pre_session */
+ fctx->fin_tls_buffer_bb = NULL;
+ APR_BRIGADE_CONCAT(fctx->fin_tls_bb, bb_tmp); /* all tls data from the client so far, reloaded */
+ apr_brigade_destroy(bb_tmp);
+ rv = APR_SUCCESS;
+ }
+
+cleanup:
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, fctx->c, "filter_recv_client_hello: done");
+ return rv;
+}
+
+static apr_status_t filter_send_client_hello(tls_filter_ctx_t *fctx)
+{
+ apr_status_t rv = APR_SUCCESS;
+
+ ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, fctx->cc->server,
+ "tls_filter, server=%s, send client hello", fctx->cc->server->server_hostname);
+ /* Only for outgoing connections */
+ ap_assert(fctx->cc->outgoing);
+ if (rustls_connection_is_handshaking(fctx->cc->rustls_connection)) {
+ while (rustls_connection_wants_write(fctx->cc->rustls_connection)) {
+ /* write flushed, so it really gets out */
+ rv = fout_pass_all_to_net(fctx, 1);
+ if (APR_SUCCESS != rv) goto cleanup;
+ }
+ }
+
+cleanup:
+ return rv;
+}
+
+/**
+ * While <fctx->cc->rustls_connection> indicates that a handshake is ongoing,
+ * write TLS data from and read network TLS data to the server session.
+ *
+ * @return APR_SUCCESS when the handshake is completed
+ */
+static apr_status_t filter_do_handshake(
+ tls_filter_ctx_t *fctx)
+{
+ apr_status_t rv = APR_SUCCESS;
+
+ ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, fctx->cc->server,
+ "tls_filter, server=%s, do handshake", fctx->cc->server->server_hostname);
+ if (rustls_connection_is_handshaking(fctx->cc->rustls_connection)) {
+ do {
+ if (rustls_connection_wants_write(fctx->cc->rustls_connection)) {
+ rv = fout_pass_all_to_net(fctx, 1);
+ if (APR_SUCCESS != rv) goto cleanup;
+ }
+ else if (rustls_connection_wants_read(fctx->cc->rustls_connection)) {
+ rv = read_tls_to_rustls(fctx, fctx->fin_max_in_rustls, APR_BLOCK_READ, 0);
+ if (APR_SUCCESS != rv) goto cleanup;
+ }
+ }
+ while (rustls_connection_is_handshaking(fctx->cc->rustls_connection));
+
+ /* rustls reports the TLS handshake to be done, when it *internally* has
+ * processed everything into its buffers. Not when the buffers have been
+ * send to the other side. */
+ if (rustls_connection_wants_write(fctx->cc->rustls_connection)) {
+ rv = fout_pass_all_to_net(fctx, 1);
+ if (APR_SUCCESS != rv) goto cleanup;
+ }
+ }
+cleanup:
+ ap_log_error(APLOG_MARK, APLOG_TRACE2, rv, fctx->cc->server,
+ "tls_filter, server=%s, handshake done", fctx->cc->server->server_hostname);
+ if (APR_SUCCESS != rv) {
+ if (fctx->cc->last_error_descr) {
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, APR_ECONNABORTED, fctx->c, APLOGNO(10354)
+ "handshake failed: %s", fctx->cc->last_error_descr);
+ }
+ }
+ return rv;
+}
+
+static apr_status_t progress_tls_atleast_to(tls_filter_ctx_t *fctx, tls_conn_state_t state)
+{
+ apr_status_t rv = APR_SUCCESS;
+
+ /* handle termination immediately */
+ if (state == TLS_CONN_ST_DONE) {
+ rv = APR_ECONNABORTED;
+ goto cleanup;
+ }
+
+ if (state > TLS_CONN_ST_CLIENT_HELLO
+ && TLS_CONN_ST_CLIENT_HELLO == fctx->cc->state) {
+ rv = tls_core_conn_init(fctx->c);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ if (fctx->cc->outgoing) {
+ rv = filter_send_client_hello(fctx);
+ }
+ else {
+ rv = filter_recv_client_hello(fctx);
+ }
+ if (APR_SUCCESS != rv) goto cleanup;
+ fctx->cc->state = TLS_CONN_ST_HANDSHAKE;
+ }
+
+ if (state > TLS_CONN_ST_HANDSHAKE
+ && TLS_CONN_ST_HANDSHAKE== fctx->cc->state) {
+ rv = filter_do_handshake(fctx);
+ if (APR_SUCCESS != rv) goto cleanup;
+ rv = tls_core_conn_post_handshake(fctx->c);
+ if (APR_SUCCESS != rv) goto cleanup;
+ fctx->cc->state = TLS_CONN_ST_TRAFFIC;
+ }
+
+ if (state < fctx->cc->state) {
+ rv = APR_ECONNABORTED;
+ }
+
+cleanup:
+ if (APR_SUCCESS != rv) {
+ filter_abort(fctx); /* does change the state itself */
+ }
+ return rv;
+}
+
+/**
+ * The connection filter converting TLS encrypted network data into plain, unencrpyted
+ * traffic data to be processed by filters above it in the filter chain.
+ *
+ * Unfortunately, Apache's filter infrastructure places a heavy implementation
+ * complexity on its input filters for the various use cases its HTTP/1.x parser
+ * (mainly) finds convenient:
+ *
+ * <bb> the bucket brigade to place the data into.
+ * <mode> one of
+ * - AP_MODE_READBYTES: just add up to <readbytes> data into <bb>
+ * - AP_MODE_GETLINE: make a best effort to get data up to and including a CRLF.
+ * it can be less, but not more t than that.
+ * - AP_MODE_EATCRLF: never used, we puke on it.
+ * - AP_MODE_SPECULATIVE: read data without consuming it.
+ * - AP_MODE_EXHAUSTIVE: never used, we puke on it.
+ * - AP_MODE_INIT: called once on a connection. needs to pass down the filter
+ * chain, giving every filter the change to "INIT".
+ * <block> do blocking or non-blocking reads
+ * <readbytes> max amount of data to add to <bb>, seems to be 0 for GETLINE
+ */
+static apr_status_t filter_conn_input(
+ ap_filter_t *f, apr_bucket_brigade *bb, ap_input_mode_t mode,
+ apr_read_type_e block, apr_off_t readbytes)
+{
+ tls_filter_ctx_t *fctx = f->ctx;
+ apr_status_t rv = APR_SUCCESS;
+ apr_off_t passed = 0, nlen;
+ rustls_result rr = RUSTLS_RESULT_OK;
+ apr_size_t in_buf_len;
+ char *in_buf = NULL;
+
+ fctx->fin_block = block;
+ if (f->c->aborted) {
+ rv = filter_abort(fctx); goto cleanup;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, fctx->cc->server,
+ "tls_filter_conn_input, server=%s, mode=%d, block=%d, readbytes=%ld",
+ fctx->cc->server->server_hostname, mode, block, (long)readbytes);
+
+ rv = progress_tls_atleast_to(fctx, TLS_CONN_ST_TRAFFIC);
+ if (APR_SUCCESS != rv) goto cleanup; /* this also leaves on APR_EAGAIN */
+
+ if (!fctx->cc->rustls_connection) {
+ return ap_get_brigade(f->next, bb, mode, block, readbytes);
+ }
+
+#if AP_MODULE_MAGIC_AT_LEAST(20200420, 1)
+ ap_filter_reinstate_brigade(f, fctx->fin_plain_bb, NULL);
+#endif
+
+ if (AP_MODE_INIT == mode) {
+ /* INIT is used to trigger the handshake, it does not return any traffic data. */
+ goto cleanup;
+ }
+
+ /* If we have nothing buffered, try getting more input.
+ * a) ask rustls_connection for decrypted data, if it has any.
+ * Note that only full records can be decrypted. We might have
+ * written TLS data to the session, but that does not mean it
+ * can give unencryted data out again.
+ * b) read TLS bytes from the network and feed them to the rustls session.
+ * c) go back to a) if b) added data.
+ */
+ while (APR_BRIGADE_EMPTY(fctx->fin_plain_bb)) {
+ apr_size_t rlen = 0;
+ apr_bucket *b;
+
+ if (fctx->fin_bytes_in_rustls > 0) {
+ in_buf_len = APR_BUCKET_BUFF_SIZE;
+ in_buf = ap_calloc(in_buf_len, sizeof(char));
+ rr = rustls_connection_read(fctx->cc->rustls_connection,
+ (unsigned char*)in_buf, in_buf_len, &rlen);
+ if (rr == RUSTLS_RESULT_PLAINTEXT_EMPTY) {
+ rr = RUSTLS_RESULT_OK;
+ rlen = 0;
+ }
+ if (rr != RUSTLS_RESULT_OK) goto cleanup;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, fctx->c,
+ "tls_filter_conn_input: got %ld plain bytes from rustls", (long)rlen);
+ if (rlen > 0) {
+ b = apr_bucket_heap_create(in_buf, rlen, free, fctx->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(fctx->fin_plain_bb, b);
+ }
+ else {
+ free(in_buf);
+ }
+ in_buf = NULL;
+ }
+ if (rlen == 0) {
+ /* that did not produce anything either. try getting more
+ * TLS data from the network into the rustls session. */
+ fctx->fin_bytes_in_rustls = 0;
+ rv = read_tls_to_rustls(fctx, fctx->fin_max_in_rustls, block, 0);
+ if (APR_SUCCESS != rv) goto cleanup; /* this also leave on APR_EAGAIN */
+ }
+ }
+
+ if (AP_MODE_GETLINE == mode) {
+ if (readbytes <= 0) readbytes = HUGE_STRING_LEN;
+ rv = tls_util_brigade_split_line(bb, fctx->fin_plain_bb, block, readbytes, &nlen);
+ if (APR_SUCCESS != rv) goto cleanup;
+ passed += nlen;
+ }
+ else if (AP_MODE_READBYTES == mode) {
+ ap_assert(readbytes > 0);
+ rv = tls_util_brigade_transfer(bb, fctx->fin_plain_bb, readbytes, &nlen);
+ if (APR_SUCCESS != rv) goto cleanup;
+ passed += nlen;
+ }
+ else if (AP_MODE_SPECULATIVE == mode) {
+ ap_assert(readbytes > 0);
+ rv = tls_util_brigade_copy(bb, fctx->fin_plain_bb, readbytes, &nlen);
+ if (APR_SUCCESS != rv) goto cleanup;
+ passed += nlen;
+ }
+ else if (AP_MODE_EXHAUSTIVE == mode) {
+ /* return all we have */
+ APR_BRIGADE_CONCAT(bb, fctx->fin_plain_bb);
+ }
+ else {
+ /* We do support any other mode */
+ rv = APR_ENOTIMPL; goto cleanup;
+ }
+
+ fout_pass_all_to_net(fctx, 0);
+
+cleanup:
+ if (NULL != in_buf) free(in_buf);
+
+ if (APLOGctrace3(fctx->c)) {
+ tls_util_bb_log(fctx->c, APLOG_TRACE3, "tls_input, fctx->fin_plain_bb", fctx->fin_plain_bb);
+ tls_util_bb_log(fctx->c, APLOG_TRACE3, "tls_input, bb", bb);
+ }
+ if (rr != RUSTLS_RESULT_OK) {
+ const char *err_descr = "";
+
+ rv = tls_core_error(fctx->c, rr, &err_descr);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, fctx->c, APLOGNO(10355)
+ "tls_filter_conn_input: [%d] %s", (int)rr, err_descr);
+ }
+ else if (APR_STATUS_IS_EAGAIN(rv)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE4, rv, fctx->c,
+ "tls_filter_conn_input: no data available");
+ }
+ else if (APR_SUCCESS != rv) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, fctx->c, APLOGNO(10356)
+ "tls_filter_conn_input");
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, fctx->c,
+ "tls_filter_conn_input: passed %ld bytes", (long)passed);
+ }
+
+#if AP_MODULE_MAGIC_AT_LEAST(20200420, 1)
+ if (APR_SUCCESS == rv || APR_STATUS_IS_EAGAIN(rv)) {
+ ap_filter_setaside_brigade(f, fctx->fin_plain_bb);
+ }
+#endif
+ return rv;
+}
+
+static rustls_io_result tls_write_callback(
+ void *userdata, const unsigned char *buf, size_t n, size_t *out_n)
+{
+ tls_filter_ctx_t *fctx = userdata;
+ apr_status_t rv;
+
+ if ((apr_off_t)n + fctx->fout_bytes_in_tls_bb >= (apr_off_t)fctx->fout_auto_flush_size) {
+ apr_bucket *b = apr_bucket_transient_create((const char*)buf, n, fctx->fout_tls_bb->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(fctx->fout_tls_bb, b);
+ fctx->fout_bytes_in_tls_bb += (apr_off_t)n;
+ rv = fout_pass_tls_to_net(fctx);
+ *out_n = n;
+ }
+ else {
+ rv = apr_brigade_write(fctx->fout_tls_bb, NULL, NULL, (const char*)buf, n);
+ if (APR_SUCCESS != rv) goto cleanup;
+ fctx->fout_bytes_in_tls_bb += (apr_off_t)n;
+ *out_n = n;
+ }
+cleanup:
+ ap_log_error(APLOG_MARK, APLOG_TRACE5, rv, fctx->cc->server,
+ "tls_write_callback: %ld bytes", (long)n);
+ return APR_TO_OS_ERROR(rv);
+}
+
+static rustls_io_result tls_write_vectored_callback(
+ void *userdata, const rustls_iovec *riov, size_t count, size_t *out_n)
+{
+ tls_filter_ctx_t *fctx = userdata;
+ const struct iovec *iov = (const struct iovec*)riov;
+ apr_status_t rv;
+ size_t i, n = 0;
+ apr_bucket *b;
+
+ for (i = 0; i < count; ++i, ++iov) {
+ b = apr_bucket_transient_create((const char*)iov->iov_base, iov->iov_len, fctx->fout_tls_bb->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(fctx->fout_tls_bb, b);
+ n += iov->iov_len;
+ }
+ fctx->fout_bytes_in_tls_bb += (apr_off_t)n;
+ rv = fout_pass_tls_to_net(fctx);
+ *out_n = n;
+ ap_log_error(APLOG_MARK, APLOG_TRACE5, rv, fctx->cc->server,
+ "tls_write_vectored_callback: %ld bytes in %d slices", (long)n, (int)count);
+ return APR_TO_OS_ERROR(rv);
+}
+
+#define TLS_WRITE_VECTORED 1
+/**
+ * Read TLS encrypted data from <fctx->cc->rustls_connection> and pass it down
+ * Apache's filter chain to the network.
+ *
+ * For now, we always FLUSH the data, since that is what we need during handshakes.
+ */
+static apr_status_t fout_pass_rustls_to_tls(tls_filter_ctx_t *fctx)
+{
+ apr_status_t rv = APR_SUCCESS;
+
+ if (rustls_connection_wants_write(fctx->cc->rustls_connection)) {
+ size_t dlen;
+ int os_err;
+
+ if (TLS_WRITE_VECTORED) {
+ do {
+ os_err = rustls_connection_write_tls_vectored(
+ fctx->cc->rustls_connection, tls_write_vectored_callback, fctx, &dlen);
+ if (os_err) {
+ rv = APR_FROM_OS_ERROR(os_err);
+ goto cleanup;
+ }
+ }
+ while (rustls_connection_wants_write(fctx->cc->rustls_connection));
+ }
+ else {
+ do {
+ os_err = rustls_connection_write_tls(
+ fctx->cc->rustls_connection, tls_write_callback, fctx, &dlen);
+ if (os_err) {
+ rv = APR_FROM_OS_ERROR(os_err);
+ goto cleanup;
+ }
+ }
+ while (rustls_connection_wants_write(fctx->cc->rustls_connection));
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, rv, fctx->c,
+ "fout_pass_rustls_to_tls, %ld bytes ready for network", (long)fctx->fout_bytes_in_tls_bb);
+ fctx->fout_bytes_in_rustls = 0;
+ }
+ }
+cleanup:
+ return rv;
+}
+
+static apr_status_t fout_pass_buf_to_rustls(
+ tls_filter_ctx_t *fctx, const char *buf, apr_size_t len)
+{
+ apr_status_t rv = APR_SUCCESS;
+ rustls_result rr = RUSTLS_RESULT_OK;
+ apr_size_t written;
+
+ while (len) {
+ /* check if we will exceed the limit of data in rustls.
+ * rustls does not guarantuee that it will accept all data, so we
+ * iterate and flush when needed. */
+ if (fctx->fout_bytes_in_rustls + (apr_off_t)len > (apr_off_t)fctx->fout_max_in_rustls) {
+ rv = fout_pass_rustls_to_tls(fctx);
+ if (APR_SUCCESS != rv) goto cleanup;
+ }
+
+ rr = rustls_connection_write(fctx->cc->rustls_connection,
+ (const unsigned char*)buf, len, &written);
+ if (rr != RUSTLS_RESULT_OK) goto cleanup;
+ ap_assert(written <= len);
+ fctx->fout_bytes_in_rustls += (apr_off_t)written;
+ buf += written;
+ len -= written;
+ if (written == 0) {
+ rv = APR_EAGAIN;
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, fctx->c, APLOGNO(10357)
+ "fout_pass_buf_to_rustls: not read by rustls at all");
+ goto cleanup;
+ }
+ }
+cleanup:
+ if (rr != RUSTLS_RESULT_OK) {
+ const char *err_descr = "";
+ rv = tls_core_error(fctx->c, rr, &err_descr);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, fctx->c, APLOGNO(10358)
+ "fout_pass_buf_to_tls to rustls: [%d] %s", (int)rr, err_descr);
+ }
+ return rv;
+}
+
+static apr_status_t fout_pass_all_to_tls(tls_filter_ctx_t *fctx)
+{
+ apr_status_t rv = APR_SUCCESS;
+
+ if (fctx->fout_buf_plain_len) {
+ rv = fout_pass_buf_to_rustls(fctx, fctx->fout_buf_plain, fctx->fout_buf_plain_len);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, fctx->c,
+ "fout_pass_all_to_tls: %ld plain bytes written to rustls",
+ (long)fctx->fout_buf_plain_len);
+ if (APR_SUCCESS != rv) goto cleanup;
+ fctx->fout_buf_plain_len = 0;
+ }
+
+ rv = fout_pass_rustls_to_tls(fctx);
+cleanup:
+ return rv;
+}
+
+static apr_status_t fout_pass_all_to_net(tls_filter_ctx_t *fctx, int flush)
+{
+ apr_status_t rv;
+
+ rv = fout_pass_all_to_tls(fctx);
+ if (APR_SUCCESS != rv) goto cleanup;
+ if (flush) {
+ apr_bucket *b = apr_bucket_flush_create(fctx->fout_tls_bb->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(fctx->fout_tls_bb, b);
+ }
+ rv = fout_pass_tls_to_net(fctx);
+cleanup:
+ return rv;
+}
+
+static apr_status_t fout_add_bucket_to_plain(tls_filter_ctx_t *fctx, apr_bucket *b)
+{
+ const char *data;
+ apr_size_t dlen, buf_remain;
+ apr_status_t rv = APR_SUCCESS;
+
+ ap_assert((apr_size_t)-1 != b->length);
+ if (b->length == 0) {
+ apr_bucket_delete(b);
+ goto cleanup;
+ }
+
+ buf_remain = fctx->fout_buf_plain_size - fctx->fout_buf_plain_len;
+ if (buf_remain == 0) {
+ rv = fout_pass_all_to_tls(fctx);
+ if (APR_SUCCESS != rv) goto cleanup;
+ buf_remain = fctx->fout_buf_plain_size - fctx->fout_buf_plain_len;
+ ap_assert(buf_remain > 0);
+ }
+ if (b->length > buf_remain) {
+ apr_bucket_split(b, buf_remain);
+ }
+ rv = apr_bucket_read(b, &data, &dlen, APR_BLOCK_READ);
+ if (APR_SUCCESS != rv) goto cleanup;
+ /*if (dlen > TLS_PREF_PLAIN_CHUNK_SIZE)*/
+ ap_assert(dlen <= buf_remain);
+ memcpy(fctx->fout_buf_plain + fctx->fout_buf_plain_len, data, dlen);
+ fctx->fout_buf_plain_len += dlen;
+ apr_bucket_delete(b);
+cleanup:
+ return rv;
+}
+
+static apr_status_t fout_add_bucket_to_tls(tls_filter_ctx_t *fctx, apr_bucket *b)
+{
+ apr_status_t rv;
+
+ rv = fout_pass_all_to_tls(fctx);
+ if (APR_SUCCESS != rv) goto cleanup;
+ APR_BUCKET_REMOVE(b);
+ APR_BRIGADE_INSERT_TAIL(fctx->fout_tls_bb, b);
+ if (AP_BUCKET_IS_EOC(b)) {
+ rustls_connection_send_close_notify(fctx->cc->rustls_connection);
+ fctx->cc->state = TLS_CONN_ST_NOTIFIED;
+ rv = fout_pass_rustls_to_tls(fctx);
+ if (APR_SUCCESS != rv) goto cleanup;
+ }
+cleanup:
+ return rv;
+}
+
+static apr_status_t fout_append_plain(tls_filter_ctx_t *fctx, apr_bucket *b)
+{
+ const char *data;
+ apr_size_t dlen, buf_remain;
+ rustls_result rr = RUSTLS_RESULT_OK;
+ apr_status_t rv = APR_SUCCESS;
+ const char *lbuf = NULL;
+ int flush = 0;
+
+ if (b) {
+ /* if our plain buffer is full, now is a good time to flush it. */
+ buf_remain = fctx->fout_buf_plain_size - fctx->fout_buf_plain_len;
+ if (buf_remain == 0) {
+ rv = fout_pass_all_to_tls(fctx);
+ if (APR_SUCCESS != rv) goto cleanup;
+ buf_remain = fctx->fout_buf_plain_size - fctx->fout_buf_plain_len;
+ ap_assert(buf_remain > 0);
+ }
+
+ /* Resolve any indeterminate bucket to a "real" one by reading it. */
+ if ((apr_size_t)-1 == b->length) {
+ rv = apr_bucket_read(b, &data, &dlen, APR_BLOCK_READ);
+ if (APR_STATUS_IS_EOF(rv)) {
+ apr_bucket_delete(b);
+ goto maybe_flush;
+ }
+ else if (APR_SUCCESS != rv) goto cleanup;
+ }
+ /* Now `b` is the bucket that we need to append and consume */
+ if (APR_BUCKET_IS_METADATA(b)) {
+ /* outgoing buckets:
+ * [PLAINDATA META PLAINDATA META META]
+ * need to become:
+ * [TLSDATA META TLSDATA META META]
+ * because we need to send the meta buckets down the
+ * network filters. */
+ rv = fout_add_bucket_to_tls(fctx, b);
+ flush = 1;
+ }
+ else if (b->length == 0) {
+ apr_bucket_delete(b);
+ }
+ else if (b->length < 1024 || fctx->fout_buf_plain_len > 0) {
+ /* we want to buffer small chunks to create larger TLS records and
+ * not leak security relevant information. So, we buffer small
+ * chunks and add (parts of) later, larger chunks if the plain
+ * buffer contains data. */
+ rv = fout_add_bucket_to_plain(fctx, b);
+ if (APR_SUCCESS != rv) goto cleanup;
+ }
+ else {
+ /* we have a large chunk and our plain buffer is empty, write it
+ * directly into rustls. */
+#define TLS_FILE_CHUNK_SIZE 4 * TLS_PREF_PLAIN_CHUNK_SIZE
+ if (b->length > TLS_FILE_CHUNK_SIZE) {
+ apr_bucket_split(b, TLS_FILE_CHUNK_SIZE);
+ }
+
+ if (APR_BUCKET_IS_FILE(b)
+ && (lbuf = malloc(b->length))) {
+ /* A file bucket is a most wonderous thing. Since the dawn of time,
+ * it has been subject to many optimizations for efficient handling
+ * of large data in the server:
+ * - unless one reads from it, it will just consist of a file handle
+ * and the offset+length information.
+ * - a apr_bucket_read() will transform itself to a bucket holding
+ * some 8000 bytes of data (APR_BUCKET_BUFF_SIZE), plus a following
+ * bucket that continues to hold the file handle and updated offsets/length
+ * information.
+ * Using standard bucket brigade handling, one would send 8000 bytes
+ * chunks to the network and that is fine for many occasions.
+ * - to have improved performance, the http: network handler takes
+ * the file handle directly and uses sendfile() when the OS supports it.
+ * - But there is not sendfile() for TLS (netflix did some experiments).
+ * So.
+ * rustls will try to collect max length traffic data into ont TLS
+ * message, but it can only work with what we gave it. If we give it buffers
+ * that fit what it wants to assemble already, its work is much easier.
+ *
+ * We can read file buckets in large chunks than APR_BUCKET_BUFF_SIZE,
+ * with a bit of knowledge about how they work.
+ */
+ apr_bucket_file *f = (apr_bucket_file *)b->data;
+ apr_file_t *fd = f->fd;
+ apr_off_t offset = b->start;
+
+ dlen = b->length;
+ rv = apr_file_seek(fd, APR_SET, &offset);
+ if (APR_SUCCESS != rv) goto cleanup;
+ rv = apr_file_read(fd, (void*)lbuf, &dlen);
+ if (APR_SUCCESS != rv && !APR_STATUS_IS_EOF(rv)) goto cleanup;
+ rv = fout_pass_buf_to_rustls(fctx, lbuf, dlen);
+ if (APR_SUCCESS != rv) goto cleanup;
+ apr_bucket_delete(b);
+ }
+ else {
+ rv = apr_bucket_read(b, &data, &dlen, APR_BLOCK_READ);
+ if (APR_SUCCESS != rv) goto cleanup;
+ rv = fout_pass_buf_to_rustls(fctx, data, dlen);
+ if (APR_SUCCESS != rv) goto cleanup;
+ apr_bucket_delete(b);
+ }
+ }
+ }
+
+maybe_flush:
+ if (flush) {
+ rv = fout_pass_all_to_net(fctx, 1);
+ if (APR_SUCCESS != rv) goto cleanup;
+ }
+
+cleanup:
+ if (lbuf) free((void*)lbuf);
+ if (rr != RUSTLS_RESULT_OK) {
+ const char *err_descr = "";
+ rv = tls_core_error(fctx->c, rr, &err_descr);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, fctx->c, APLOGNO(10359)
+ "write_bucket_to_rustls: [%d] %s", (int)rr, err_descr);
+ }
+ return rv;
+}
+
+/**
+ * The connection filter converting plain, unencrypted traffic data into TLS
+ * encrypted bytes and send the down the Apache filter chain out to the network.
+ *
+ * <bb> the data to send, including "meta data" such as FLUSH indicators
+ * to force filters to write any data set aside (an apache term for
+ * 'buffering').
+ * The buckets in <bb> need to be completely consumed, e.g. <bb> will be
+ * empty on a successful return. but unless FLUSHed, filters may hold
+ * buckets back internally, for various reasons. However they always
+ * need to be processed in the order they arrive.
+ */
+static apr_status_t filter_conn_output(
+ ap_filter_t *f, apr_bucket_brigade *bb)
+{
+ tls_filter_ctx_t *fctx = f->ctx;
+ apr_status_t rv = APR_SUCCESS;
+ rustls_result rr = RUSTLS_RESULT_OK;
+
+ if (f->c->aborted) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, fctx->c,
+ "tls_filter_conn_output: aborted conn");
+ apr_brigade_cleanup(bb);
+ rv = APR_ECONNABORTED; goto cleanup;
+ }
+
+ rv = progress_tls_atleast_to(fctx, TLS_CONN_ST_TRAFFIC);
+ if (APR_SUCCESS != rv) goto cleanup; /* this also leaves on APR_EAGAIN */
+
+ if (fctx->cc->state == TLS_CONN_ST_DONE) {
+ /* have done everything, just pass through */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, fctx->c,
+ "tls_filter_conn_output: tls session is already done");
+ rv = ap_pass_brigade(f->next, bb);
+ goto cleanup;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, fctx->cc->server,
+ "tls_filter_conn_output, server=%s", fctx->cc->server->server_hostname);
+ if (APLOGctrace5(fctx->c)) {
+ tls_util_bb_log(fctx->c, APLOG_TRACE5, "filter_conn_output", bb);
+ }
+
+ while (!APR_BRIGADE_EMPTY(bb)) {
+ rv = fout_append_plain(fctx, APR_BRIGADE_FIRST(bb));
+ if (APR_SUCCESS != rv) goto cleanup;
+ }
+
+ if (APLOGctrace5(fctx->c)) {
+ tls_util_bb_log(fctx->c, APLOG_TRACE5, "filter_conn_output, processed plain", bb);
+ tls_util_bb_log(fctx->c, APLOG_TRACE5, "filter_conn_output, tls", fctx->fout_tls_bb);
+ }
+
+cleanup:
+ if (rr != RUSTLS_RESULT_OK) {
+ const char *err_descr = "";
+ rv = tls_core_error(fctx->c, rr, &err_descr);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, fctx->c, APLOGNO(10360)
+ "tls_filter_conn_output: [%d] %s", (int)rr, err_descr);
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, fctx->c,
+ "tls_filter_conn_output: done");
+ }
+ return rv;
+}
+
+int tls_filter_pre_conn_init(conn_rec *c)
+{
+ tls_conf_conn_t *cc;
+ tls_filter_ctx_t *fctx;
+
+ if (OK != tls_core_pre_conn_init(c)) {
+ return DECLINED;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, c->base_server,
+ "tls_filter_pre_conn_init on %s", c->base_server->server_hostname);
+
+ cc = tls_conf_conn_get(c);
+ ap_assert(cc);
+
+ fctx = apr_pcalloc(c->pool, sizeof(*fctx));
+ fctx->c = c;
+ fctx->cc = cc;
+ cc->filter_ctx = fctx;
+
+ /* a bit tricky: registering out filters returns the ap_filter_t*
+ * that it created for it. The ->next field points always
+ * to the filter "below" our filter. That will be other registered
+ * filters and last, but not least, the network filter on the socket.
+ *
+ * Therefore, wenn we need to read/write TLS data during handshake, we can
+ * pass the data to/call on ->next- Since ->next can change during the setup of
+ * a connections (other modules register also sth.), we keep the ap_filter_t*
+ * returned here, since httpd core will update the ->next whenever someone
+ * adds a filter or removes one. This can potentially happen all the time.
+ */
+ fctx->fin_ctx = ap_add_input_filter(TLS_FILTER_RAW, fctx, NULL, c);
+ fctx->fin_tls_bb = apr_brigade_create(c->pool, c->bucket_alloc);
+ fctx->fin_tls_buffer_bb = NULL;
+ fctx->fin_plain_bb = apr_brigade_create(c->pool, c->bucket_alloc);
+ fctx->fout_ctx = ap_add_output_filter(TLS_FILTER_RAW, fctx, NULL, c);
+ fctx->fout_tls_bb = apr_brigade_create(c->pool, c->bucket_alloc);
+ fctx->fout_buf_plain_size = APR_BUCKET_BUFF_SIZE;
+ fctx->fout_buf_plain = apr_pcalloc(c->pool, fctx->fout_buf_plain_size);
+ fctx->fout_buf_plain_len = 0;
+
+ /* Let the filters have 2 max-length TLS Messages in the rustls buffers.
+ * The effects we would like to achieve here are:
+ * 1. pass data out, so that every bucket becomes its own TLS message.
+ * This hides, if possible, the length of response parts.
+ * If we give rustls enough plain data, it will use the max TLS message
+ * size and things are more hidden. But we can only write what the application
+ * or protocol gives us.
+ * 2. max length records result in less overhead for all layers involved.
+ * 3. a TLS message from the client can only be decrypted when it has
+ * completely arrived. If we provide rustls with enough data (if the
+ * network has it for us), it should always be able to decrypt at least
+ * one TLS message and we have plain bytes to forward to the protocol
+ * handler.
+ */
+ fctx->fin_max_in_rustls = 4 * TLS_REC_MAX_SIZE;
+ fctx->fout_max_in_rustls = 4 * TLS_PREF_PLAIN_CHUNK_SIZE;
+ fctx->fout_auto_flush_size = 2 * TLS_REC_MAX_SIZE;
+
+ return OK;
+}
+
+void tls_filter_conn_init(conn_rec *c)
+{
+ tls_conf_conn_t *cc = tls_conf_conn_get(c);
+
+ if (cc && cc->filter_ctx && !cc->outgoing) {
+ /* We are one in a row of hooks that - possibly - want to process this
+ * connection, the (HTTP) protocol handlers among them.
+ *
+ * For incoming connections, we need to select the protocol to use NOW,
+ * so that the later protocol handlers do the right thing.
+ * Send an INIT down the input filter chain to trigger the TLS handshake,
+ * which will select a protocol via ALPN. */
+ apr_bucket_brigade* temp;
+
+ ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, c->base_server,
+ "tls_filter_conn_init on %s, triggering handshake", c->base_server->server_hostname);
+ temp = apr_brigade_create(c->pool, c->bucket_alloc);
+ ap_get_brigade(c->input_filters, temp, AP_MODE_INIT, APR_BLOCK_READ, 0);
+ apr_brigade_destroy(temp);
+ }
+}
+
+void tls_filter_register(
+ apr_pool_t *pool)
+{
+ (void)pool;
+ ap_register_input_filter(TLS_FILTER_RAW, filter_conn_input, NULL, AP_FTYPE_CONNECTION + 5);
+ ap_register_output_filter(TLS_FILTER_RAW, filter_conn_output, NULL, AP_FTYPE_CONNECTION + 5);
+}
diff --git a/modules/tls/tls_filter.h b/modules/tls/tls_filter.h
new file mode 100644
index 0000000..4f3d38b
--- /dev/null
+++ b/modules/tls/tls_filter.h
@@ -0,0 +1,90 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef tls_filter_h
+#define tls_filter_h
+
+#define TLS_FILTER_RAW "TLS raw"
+
+typedef struct tls_filter_ctx_t tls_filter_ctx_t;
+
+struct tls_filter_ctx_t {
+ conn_rec *c; /* connection this context is for */
+ tls_conf_conn_t *cc; /* tls module configuration of connection */
+
+ ap_filter_t *fin_ctx; /* Apache's entry into the input filter chain */
+ apr_bucket_brigade *fin_tls_bb; /* TLS encrypted, incoming network data */
+ apr_bucket_brigade *fin_tls_buffer_bb; /* TLS encrypted, incoming network data buffering */
+ apr_bucket_brigade *fin_plain_bb; /* decrypted, incoming traffic data */
+ apr_off_t fin_bytes_in_rustls; /* # of input TLS bytes in rustls_connection */
+ apr_read_type_e fin_block; /* Do we block on input reads or not? */
+
+ ap_filter_t *fout_ctx; /* Apache's entry into the output filter chain */
+ char *fout_buf_plain; /* a buffer to collect plain bytes for output */
+ apr_size_t fout_buf_plain_len; /* the amount of bytes in the buffer */
+ apr_size_t fout_buf_plain_size; /* the total size of the buffer */
+ apr_bucket_brigade *fout_tls_bb; /* TLS encrypted, outgoing network data */
+ apr_off_t fout_bytes_in_rustls; /* # of output plain bytes in rustls_connection */
+ apr_off_t fout_bytes_in_tls_bb; /* # of output tls bytes in our brigade */
+
+ apr_size_t fin_max_in_rustls; /* how much tls we like to read into rustls */
+ apr_size_t fout_max_in_rustls; /* how much plain bytes we like in rustls */
+ apr_size_t fout_max_bucket_size; /* how large bucket chunks we handle before splitting */
+ apr_size_t fout_auto_flush_size; /* on much outoing TLS data we flush to network */
+};
+
+/**
+ * Register the in-/output filters for converting TLS to application data and vice versa.
+ */
+void tls_filter_register(apr_pool_t *pool);
+
+/**
+ * Initialize the pre_connection state. Install all filters.
+ *
+ * @return OK if TLS on connection is enabled, DECLINED otherwise
+ */
+int tls_filter_pre_conn_init(conn_rec *c);
+
+/**
+ * Initialize the connection for use, perform the TLS handshake.
+ *
+ * Any failure will lead to the connection becoming aborted.
+ */
+void tls_filter_conn_init(conn_rec *c);
+
+/*
+ * <https://tools.ietf.org/html/rfc8449> says:
+ * "For large data transfers, small record sizes can materially affect performance."
+ * and
+ * "For TLS 1.2 and earlier, that limit is 2^14 octets. TLS 1.3 uses a limit of
+ * 2^14+1 octets."
+ * Maybe future TLS versions will raise that value, but for now these limits stand.
+ * Given the choice, we would like rustls to provide traffic data in those chunks.
+ */
+#define TLS_PREF_PLAIN_CHUNK_SIZE (16384)
+
+/*
+ * When retrieving TLS chunks for rustls, or providing it a buffer
+ * to pass out TLS chunks (which are then bucketed and written to the
+ * network filters), we ideally would do that in multiples of TLS
+ * messages sizes.
+ * That would be TLS_PREF_WRITE_SIZE + TLS Message Overhead, such as
+ * MAC and padding. But these vary with protocol and ciphers chosen, so
+ * we define something which should be "large enough", but not overly so.
+ */
+#define TLS_REC_EXTRA (1024)
+#define TLS_REC_MAX_SIZE (TLS_PREF_PLAIN_CHUNK_SIZE + TLS_REC_EXTRA)
+
+#endif /* tls_filter_h */ \ No newline at end of file
diff --git a/modules/tls/tls_ocsp.c b/modules/tls/tls_ocsp.c
new file mode 100644
index 0000000..37e95b1
--- /dev/null
+++ b/modules/tls/tls_ocsp.c
@@ -0,0 +1,120 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <assert.h>
+#include <apr_lib.h>
+#include <apr_strings.h>
+
+#include <httpd.h>
+#include <http_connection.h>
+#include <http_core.h>
+#include <http_log.h>
+#include <http_ssl.h>
+
+#include <rustls.h>
+
+#include "tls_cert.h"
+#include "tls_conf.h"
+#include "tls_core.h"
+#include "tls_proto.h"
+#include "tls_ocsp.h"
+
+extern module AP_MODULE_DECLARE_DATA tls_module;
+APLOG_USE_MODULE(tls);
+
+
+static int prime_cert(
+ void *userdata, server_rec *s, const char *cert_id, const char *cert_pem,
+ const rustls_certified_key *certified_key)
+{
+ apr_pool_t *p = userdata;
+ apr_status_t rv;
+
+ (void)certified_key;
+ rv = ap_ssl_ocsp_prime(s, p, cert_id, strlen(cert_id), cert_pem);
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, s, "ocsp prime of cert [%s] from %s",
+ cert_id, s->server_hostname);
+ return 1;
+}
+
+apr_status_t tls_ocsp_prime_certs(tls_conf_global_t *gc, apr_pool_t *p, server_rec *s)
+{
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, "ocsp priming of %d certs",
+ (int)tls_cert_reg_count(gc->cert_reg));
+ tls_cert_reg_do(prime_cert, p, gc->cert_reg);
+ return APR_SUCCESS;
+}
+
+typedef struct {
+ conn_rec *c;
+ const rustls_certified_key *key_in;
+ const rustls_certified_key *key_out;
+} ocsp_copy_ctx_t;
+
+static void ocsp_clone_key(const unsigned char *der, apr_size_t der_len, void *userdata)
+{
+ ocsp_copy_ctx_t *ctx = userdata;
+ rustls_slice_bytes rslice;
+ rustls_result rr;
+
+ rslice.data = der;
+ rslice.len = der_len;
+
+ rr = rustls_certified_key_clone_with_ocsp(ctx->key_in, der_len? &rslice : NULL, &ctx->key_out);
+ if (RUSTLS_RESULT_OK != rr) {
+ const char *err_descr = NULL;
+ apr_status_t rv = tls_util_rustls_error(ctx->c->pool, rr, &err_descr);
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, ctx->c, APLOGNO(10362)
+ "Failed add OCSP data to certificate: [%d] %s", (int)rr, err_descr);
+ }
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, ctx->c,
+ "provided %ld bytes of ocsp response DER data to key.", (long)der_len);
+ }
+}
+
+apr_status_t tls_ocsp_update_key(
+ conn_rec *c, const rustls_certified_key *certified_key,
+ const rustls_certified_key **pkey_out)
+{
+ tls_conf_conn_t *cc = tls_conf_conn_get(c);
+ tls_conf_server_t *sc;
+ const char *key_id;
+ apr_status_t rv = APR_SUCCESS;
+ ocsp_copy_ctx_t ctx;
+
+ assert(cc);
+ assert(cc->server);
+ sc = tls_conf_server_get(cc->server);
+ key_id = tls_cert_reg_get_id(sc->global->cert_reg, certified_key);
+ if (!key_id) {
+ rv = APR_ENOENT;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c, "certified key not registered");
+ goto cleanup;
+ }
+
+ ctx.c = c;
+ ctx.key_in = certified_key;
+ ctx.key_out = NULL;
+ rv = ap_ssl_ocsp_get_resp(cc->server, c, key_id, strlen(key_id), ocsp_clone_key, &ctx);
+ if (APR_SUCCESS != rv) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c,
+ "ocsp response not available for cert %s", key_id);
+ }
+
+cleanup:
+ *pkey_out = (APR_SUCCESS == rv)? ctx.key_out : NULL;
+ return rv;
+}
diff --git a/modules/tls/tls_ocsp.h b/modules/tls/tls_ocsp.h
new file mode 100644
index 0000000..60770a9
--- /dev/null
+++ b/modules/tls/tls_ocsp.h
@@ -0,0 +1,47 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef tls_ocsp_h
+#define tls_ocsp_h
+
+/**
+ * Prime the collected certified keys for OCSP response provisioning (aka. Stapling).
+ *
+ * To be called in the post-config phase of the server before connections are handled.
+ * @param gc the global module configuration with the certified_key registry
+ * @param p the pool to use for allocations
+ * @param s the base server record
+ */
+apr_status_t tls_ocsp_prime_certs(tls_conf_global_t *gc, apr_pool_t *p, server_rec *s);
+
+/**
+ * Provide the OCSP response data for the certified_key into the offered buffer,
+ * so available.
+ * If not data is available `out_n` is set to 0. Same, if the offered buffer
+ * is not large enough to hold the complete response.
+ * If OCSP response DER data is copied, the number of copied bytes is given in `out_n`.
+ *
+ * Note that only keys that have been primed initially will have OCSP data available.
+ * @param c the current connection
+ * @param certified_key the key to get the OCSP response data for
+ * @param buf a buffer which can hold up to `buf_len` bytes
+ * @param buf_len the length of `buf`
+ * @param out_n the number of OCSP response DER bytes copied or 0.
+ */
+apr_status_t tls_ocsp_update_key(
+ conn_rec *c, const rustls_certified_key *certified_key,
+ const rustls_certified_key **key_out);
+
+#endif /* tls_ocsp_h */
diff --git a/modules/tls/tls_proto.c b/modules/tls/tls_proto.c
new file mode 100644
index 0000000..95a903b
--- /dev/null
+++ b/modules/tls/tls_proto.c
@@ -0,0 +1,603 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <assert.h>
+#include <apr_lib.h>
+#include <apr_strings.h>
+
+#include <httpd.h>
+#include <http_connection.h>
+#include <http_core.h>
+#include <http_log.h>
+
+#include <rustls.h>
+
+#include "tls_proto.h"
+#include "tls_conf.h"
+#include "tls_util.h"
+
+extern module AP_MODULE_DECLARE_DATA tls_module;
+APLOG_USE_MODULE(tls);
+
+
+
+/**
+ * Known cipher as registered in
+ * <https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-4>
+ */
+static tls_cipher_t KNOWN_CIPHERS[] = {
+ { 0x0000, "TLS_NULL_WITH_NULL_NULL", NULL },
+ { 0x0001, "TLS_RSA_WITH_NULL_MD5", NULL },
+ { 0x0002, "TLS_RSA_WITH_NULL_SHA", NULL },
+ { 0x0003, "TLS_RSA_EXPORT_WITH_RC4_40_MD5", NULL },
+ { 0x0004, "TLS_RSA_WITH_RC4_128_MD5", NULL },
+ { 0x0005, "TLS_RSA_WITH_RC4_128_SHA", NULL },
+ { 0x0006, "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", NULL },
+ { 0x0007, "TLS_RSA_WITH_IDEA_CBC_SHA", NULL },
+ { 0x0008, "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", NULL },
+ { 0x0009, "TLS_RSA_WITH_DES_CBC_SHA", NULL },
+ { 0x000a, "TLS_RSA_WITH_3DES_EDE_CBC_SHA", NULL },
+ { 0x000b, "TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA", NULL },
+ { 0x000c, "TLS_DH_DSS_WITH_DES_CBC_SHA", NULL },
+ { 0x000d, "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", NULL },
+ { 0x000e, "TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA", NULL },
+ { 0x000f, "TLS_DH_RSA_WITH_DES_CBC_SHA", NULL },
+ { 0x0010, "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", NULL },
+ { 0x0011, "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", NULL },
+ { 0x0012, "TLS_DHE_DSS_WITH_DES_CBC_SHA", NULL },
+ { 0x0013, "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", NULL },
+ { 0x0014, "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", NULL },
+ { 0x0015, "TLS_DHE_RSA_WITH_DES_CBC_SHA", NULL },
+ { 0x0016, "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", NULL },
+ { 0x0017, "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", NULL },
+ { 0x0018, "TLS_DH_anon_WITH_RC4_128_MD5", NULL },
+ { 0x0019, "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", NULL },
+ { 0x001a, "TLS_DH_anon_WITH_DES_CBC_SHA", NULL },
+ { 0x001b, "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", NULL },
+ { 0x001c, "SSL_FORTEZZA_KEA_WITH_NULL_SHA", NULL },
+ { 0x001d, "SSL_FORTEZZA_KEA_WITH_FORTEZZA_CBC_SHA", NULL },
+ { 0x001e, "TLS_KRB5_WITH_DES_CBC_SHA_or_SSL_FORTEZZA_KEA_WITH_RC4_128_SHA", NULL },
+ { 0x001f, "TLS_KRB5_WITH_3DES_EDE_CBC_SHA", NULL },
+ { 0x0020, "TLS_KRB5_WITH_RC4_128_SHA", NULL },
+ { 0x0021, "TLS_KRB5_WITH_IDEA_CBC_SHA", NULL },
+ { 0x0022, "TLS_KRB5_WITH_DES_CBC_MD5", NULL },
+ { 0x0023, "TLS_KRB5_WITH_3DES_EDE_CBC_MD5", NULL },
+ { 0x0024, "TLS_KRB5_WITH_RC4_128_MD5", NULL },
+ { 0x0025, "TLS_KRB5_WITH_IDEA_CBC_MD5", NULL },
+ { 0x0026, "TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA", NULL },
+ { 0x0027, "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA", NULL },
+ { 0x0028, "TLS_KRB5_EXPORT_WITH_RC4_40_SHA", NULL },
+ { 0x0029, "TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5", NULL },
+ { 0x002a, "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5", NULL },
+ { 0x002b, "TLS_KRB5_EXPORT_WITH_RC4_40_MD5", NULL },
+ { 0x002c, "TLS_PSK_WITH_NULL_SHA", NULL },
+ { 0x002d, "TLS_DHE_PSK_WITH_NULL_SHA", NULL },
+ { 0x002e, "TLS_RSA_PSK_WITH_NULL_SHA", NULL },
+ { 0x002f, "TLS_RSA_WITH_AES_128_CBC_SHA", NULL },
+ { 0x0030, "TLS_DH_DSS_WITH_AES_128_CBC_SHA", NULL },
+ { 0x0031, "TLS_DH_RSA_WITH_AES_128_CBC_SHA", NULL },
+ { 0x0032, "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", NULL },
+ { 0x0033, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", NULL },
+ { 0x0034, "TLS_DH_anon_WITH_AES_128_CBC_SHA", NULL },
+ { 0x0035, "TLS_RSA_WITH_AES_256_CBC_SHA", NULL },
+ { 0x0036, "TLS_DH_DSS_WITH_AES_256_CBC_SHA", NULL },
+ { 0x0037, "TLS_DH_RSA_WITH_AES_256_CBC_SHA", NULL },
+ { 0x0038, "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", NULL },
+ { 0x0039, "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", NULL },
+ { 0x003a, "TLS_DH_anon_WITH_AES_256_CBC_SHA", NULL },
+ { 0x003b, "TLS_RSA_WITH_NULL_SHA256", "NULL-SHA256" },
+ { 0x003c, "TLS_RSA_WITH_AES_128_CBC_SHA256", "AES128-SHA256" },
+ { 0x003d, "TLS_RSA_WITH_AES_256_CBC_SHA256", "AES256-SHA256" },
+ { 0x003e, "TLS_DH_DSS_WITH_AES_128_CBC_SHA256", "DH-DSS-AES128-SHA256" },
+ { 0x003f, "TLS_DH_RSA_WITH_AES_128_CBC_SHA256", "DH-RSA-AES128-SHA256" },
+ { 0x0040, "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", "DHE-DSS-AES128-SHA256" },
+ { 0x0041, "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", NULL },
+ { 0x0042, "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", NULL },
+ { 0x0043, "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", NULL },
+ { 0x0044, "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", NULL },
+ { 0x0045, "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", NULL },
+ { 0x0046, "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", NULL },
+ { 0x0047, "TLS_ECDH_ECDSA_WITH_NULL_SHA_draft", NULL },
+ { 0x0048, "TLS_ECDH_ECDSA_WITH_RC4_128_SHA_draft", NULL },
+ { 0x0049, "TLS_ECDH_ECDSA_WITH_DES_CBC_SHA_draft", NULL },
+ { 0x004a, "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA_draft", NULL },
+ { 0x004b, "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA_draft", NULL },
+ { 0x004c, "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA_draft", NULL },
+ { 0x004d, "TLS_ECDH_ECNRA_WITH_DES_CBC_SHA_draft", NULL },
+ { 0x004e, "TLS_ECDH_ECNRA_WITH_3DES_EDE_CBC_SHA_draft", NULL },
+ { 0x004f, "TLS_ECMQV_ECDSA_NULL_SHA_draft", NULL },
+ { 0x0050, "TLS_ECMQV_ECDSA_WITH_RC4_128_SHA_draft", NULL },
+ { 0x0051, "TLS_ECMQV_ECDSA_WITH_DES_CBC_SHA_draft", NULL },
+ { 0x0052, "TLS_ECMQV_ECDSA_WITH_3DES_EDE_CBC_SHA_draft", NULL },
+ { 0x0053, "TLS_ECMQV_ECNRA_NULL_SHA_draft", NULL },
+ { 0x0054, "TLS_ECMQV_ECNRA_WITH_RC4_128_SHA_draft", NULL },
+ { 0x0055, "TLS_ECMQV_ECNRA_WITH_DES_CBC_SHA_draft", NULL },
+ { 0x0056, "TLS_ECMQV_ECNRA_WITH_3DES_EDE_CBC_SHA_draft", NULL },
+ { 0x0057, "TLS_ECDH_anon_NULL_WITH_SHA_draft", NULL },
+ { 0x0058, "TLS_ECDH_anon_WITH_RC4_128_SHA_draft", NULL },
+ { 0x0059, "TLS_ECDH_anon_WITH_DES_CBC_SHA_draft", NULL },
+ { 0x005a, "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA_draft", NULL },
+ { 0x005b, "TLS_ECDH_anon_EXPORT_WITH_DES40_CBC_SHA_draft", NULL },
+ { 0x005c, "TLS_ECDH_anon_EXPORT_WITH_RC4_40_SHA_draft", NULL },
+ { 0x0060, "TLS_RSA_EXPORT1024_WITH_RC4_56_MD5", NULL },
+ { 0x0061, "TLS_RSA_EXPORT1024_WITH_RC2_CBC_56_MD5", NULL },
+ { 0x0062, "TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA", NULL },
+ { 0x0063, "TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA", NULL },
+ { 0x0064, "TLS_RSA_EXPORT1024_WITH_RC4_56_SHA", NULL },
+ { 0x0065, "TLS_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA", NULL },
+ { 0x0066, "TLS_DHE_DSS_WITH_RC4_128_SHA", NULL },
+ { 0x0067, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", "DHE-RSA-AES128-SHA256" },
+ { 0x0068, "TLS_DH_DSS_WITH_AES_256_CBC_SHA256", "DH-DSS-AES256-SHA256" },
+ { 0x0069, "TLS_DH_RSA_WITH_AES_256_CBC_SHA256", "DH-RSA-AES256-SHA256" },
+ { 0x006a, "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", "DHE-DSS-AES256-SHA256" },
+ { 0x006b, "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", "DHE-RSA-AES256-SHA256" },
+ { 0x006c, "TLS_DH_anon_WITH_AES_128_CBC_SHA256", "ADH-AES128-SHA256" },
+ { 0x006d, "TLS_DH_anon_WITH_AES_256_CBC_SHA256", "ADH-AES256-SHA256" },
+ { 0x0072, "TLS_DHE_DSS_WITH_3DES_EDE_CBC_RMD", NULL },
+ { 0x0073, "TLS_DHE_DSS_WITH_AES_128_CBC_RMD", NULL },
+ { 0x0074, "TLS_DHE_DSS_WITH_AES_256_CBC_RMD", NULL },
+ { 0x0077, "TLS_DHE_RSA_WITH_3DES_EDE_CBC_RMD", NULL },
+ { 0x0078, "TLS_DHE_RSA_WITH_AES_128_CBC_RMD", NULL },
+ { 0x0079, "TLS_DHE_RSA_WITH_AES_256_CBC_RMD", NULL },
+ { 0x007c, "TLS_RSA_WITH_3DES_EDE_CBC_RMD", NULL },
+ { 0x007d, "TLS_RSA_WITH_AES_128_CBC_RMD", NULL },
+ { 0x007e, "TLS_RSA_WITH_AES_256_CBC_RMD", NULL },
+ { 0x0080, "TLS_GOSTR341094_WITH_28147_CNT_IMIT", NULL },
+ { 0x0081, "TLS_GOSTR341001_WITH_28147_CNT_IMIT", NULL },
+ { 0x0082, "TLS_GOSTR341094_WITH_NULL_GOSTR3411", NULL },
+ { 0x0083, "TLS_GOSTR341001_WITH_NULL_GOSTR3411", NULL },
+ { 0x0084, "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", NULL },
+ { 0x0085, "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", NULL },
+ { 0x0086, "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", NULL },
+ { 0x0087, "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", NULL },
+ { 0x0088, "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", NULL },
+ { 0x0089, "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", NULL },
+ { 0x008a, "TLS_PSK_WITH_RC4_128_SHA", "PSK-RC4-SHA" },
+ { 0x008b, "TLS_PSK_WITH_3DES_EDE_CBC_SHA", "PSK-3DES-EDE-CBC-SHA" },
+ { 0x008c, "TLS_PSK_WITH_AES_128_CBC_SHA", NULL },
+ { 0x008d, "TLS_PSK_WITH_AES_256_CBC_SHA", NULL },
+ { 0x008e, "TLS_DHE_PSK_WITH_RC4_128_SHA", NULL },
+ { 0x008f, "TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA", NULL },
+ { 0x0090, "TLS_DHE_PSK_WITH_AES_128_CBC_SHA", NULL },
+ { 0x0091, "TLS_DHE_PSK_WITH_AES_256_CBC_SHA", NULL },
+ { 0x0092, "TLS_RSA_PSK_WITH_RC4_128_SHA", NULL },
+ { 0x0093, "TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA", NULL },
+ { 0x0094, "TLS_RSA_PSK_WITH_AES_128_CBC_SHA", NULL },
+ { 0x0095, "TLS_RSA_PSK_WITH_AES_256_CBC_SHA", NULL },
+ { 0x0096, "TLS_RSA_WITH_SEED_CBC_SHA", NULL },
+ { 0x0097, "TLS_DH_DSS_WITH_SEED_CBC_SHA", NULL },
+ { 0x0098, "TLS_DH_RSA_WITH_SEED_CBC_SHA", NULL },
+ { 0x0099, "TLS_DHE_DSS_WITH_SEED_CBC_SHA", NULL },
+ { 0x009a, "TLS_DHE_RSA_WITH_SEED_CBC_SHA", NULL },
+ { 0x009b, "TLS_DH_anon_WITH_SEED_CBC_SHA", NULL },
+ { 0x009c, "TLS_RSA_WITH_AES_128_GCM_SHA256", "AES128-GCM-SHA256" },
+ { 0x009d, "TLS_RSA_WITH_AES_256_GCM_SHA384", "AES256-GCM-SHA384" },
+ { 0x009e, "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", "DHE-RSA-AES128-GCM-SHA256" },
+ { 0x009f, "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "DHE-RSA-AES256-GCM-SHA384" },
+ { 0x00a0, "TLS_DH_RSA_WITH_AES_128_GCM_SHA256", "DH-RSA-AES128-GCM-SHA256" },
+ { 0x00a1, "TLS_DH_RSA_WITH_AES_256_GCM_SHA384", "DH-RSA-AES256-GCM-SHA384" },
+ { 0x00a2, "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", "DHE-DSS-AES128-GCM-SHA256" },
+ { 0x00a3, "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", "DHE-DSS-AES256-GCM-SHA384" },
+ { 0x00a4, "TLS_DH_DSS_WITH_AES_128_GCM_SHA256", "DH-DSS-AES128-GCM-SHA256" },
+ { 0x00a5, "TLS_DH_DSS_WITH_AES_256_GCM_SHA384", "DH-DSS-AES256-GCM-SHA384" },
+ { 0x00a6, "TLS_DH_anon_WITH_AES_128_GCM_SHA256", "ADH-AES128-GCM-SHA256" },
+ { 0x00a7, "TLS_DH_anon_WITH_AES_256_GCM_SHA384", "ADH-AES256-GCM-SHA384" },
+ { 0x00a8, "TLS_PSK_WITH_AES_128_GCM_SHA256", NULL },
+ { 0x00a9, "TLS_PSK_WITH_AES_256_GCM_SHA384", NULL },
+ { 0x00aa, "TLS_DHE_PSK_WITH_AES_128_GCM_SHA256", NULL },
+ { 0x00ab, "TLS_DHE_PSK_WITH_AES_256_GCM_SHA384", NULL },
+ { 0x00ac, "TLS_RSA_PSK_WITH_AES_128_GCM_SHA256", NULL },
+ { 0x00ad, "TLS_RSA_PSK_WITH_AES_256_GCM_SHA384", NULL },
+ { 0x00ae, "TLS_PSK_WITH_AES_128_CBC_SHA256", "PSK-AES128-CBC-SHA" },
+ { 0x00af, "TLS_PSK_WITH_AES_256_CBC_SHA384", "PSK-AES256-CBC-SHA" },
+ { 0x00b0, "TLS_PSK_WITH_NULL_SHA256", NULL },
+ { 0x00b1, "TLS_PSK_WITH_NULL_SHA384", NULL },
+ { 0x00b2, "TLS_DHE_PSK_WITH_AES_128_CBC_SHA256", NULL },
+ { 0x00b3, "TLS_DHE_PSK_WITH_AES_256_CBC_SHA384", NULL },
+ { 0x00b4, "TLS_DHE_PSK_WITH_NULL_SHA256", NULL },
+ { 0x00b5, "TLS_DHE_PSK_WITH_NULL_SHA384", NULL },
+ { 0x00b6, "TLS_RSA_PSK_WITH_AES_128_CBC_SHA256", NULL },
+ { 0x00b7, "TLS_RSA_PSK_WITH_AES_256_CBC_SHA384", NULL },
+ { 0x00b8, "TLS_RSA_PSK_WITH_NULL_SHA256", NULL },
+ { 0x00b9, "TLS_RSA_PSK_WITH_NULL_SHA384", NULL },
+ { 0x00ba, "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256", NULL },
+ { 0x00bb, "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256", NULL },
+ { 0x00bc, "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256", NULL },
+ { 0x00bd, "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256", NULL },
+ { 0x00be, "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", NULL },
+ { 0x00bf, "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256", NULL },
+ { 0x00c0, "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256", NULL },
+ { 0x00c1, "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256", NULL },
+ { 0x00c2, "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256", NULL },
+ { 0x00c3, "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256", NULL },
+ { 0x00c4, "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256", NULL },
+ { 0x00c5, "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256", NULL },
+ { 0x00ff, "TLS_EMPTY_RENEGOTIATION_INFO_SCSV", NULL },
+ { 0x1301, "TLS_AES_128_GCM_SHA256", "TLS13_AES_128_GCM_SHA256" },
+ { 0x1302, "TLS_AES_256_GCM_SHA384", "TLS13_AES_256_GCM_SHA384" },
+ { 0x1303, "TLS_CHACHA20_POLY1305_SHA256", "TLS13_CHACHA20_POLY1305_SHA256" },
+ { 0x1304, "TLS_AES_128_CCM_SHA256", "TLS13_AES_128_CCM_SHA256" },
+ { 0x1305, "TLS_AES_128_CCM_8_SHA256", "TLS13_AES_128_CCM_8_SHA256" },
+ { 0xc001, "TLS_ECDH_ECDSA_WITH_NULL_SHA", NULL },
+ { 0xc002, "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", NULL },
+ { 0xc003, "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", NULL },
+ { 0xc004, "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", NULL },
+ { 0xc005, "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", NULL },
+ { 0xc006, "TLS_ECDHE_ECDSA_WITH_NULL_SHA", NULL },
+ { 0xc007, "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", NULL },
+ { 0xc008, "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", NULL },
+ { 0xc009, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", NULL },
+ { 0xc00a, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", NULL },
+ { 0xc00b, "TLS_ECDH_RSA_WITH_NULL_SHA", NULL },
+ { 0xc00c, "TLS_ECDH_RSA_WITH_RC4_128_SHA", NULL },
+ { 0xc00d, "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", NULL },
+ { 0xc00e, "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", NULL },
+ { 0xc00f, "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", NULL },
+ { 0xc010, "TLS_ECDHE_RSA_WITH_NULL_SHA", NULL },
+ { 0xc011, "TLS_ECDHE_RSA_WITH_RC4_128_SHA", NULL },
+ { 0xc012, "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", NULL },
+ { 0xc013, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", NULL },
+ { 0xc014, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", NULL },
+ { 0xc015, "TLS_ECDH_anon_WITH_NULL_SHA", NULL },
+ { 0xc016, "TLS_ECDH_anon_WITH_RC4_128_SHA", NULL },
+ { 0xc017, "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", NULL },
+ { 0xc018, "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", NULL },
+ { 0xc019, "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", NULL },
+ { 0xc01a, "TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA", NULL },
+ { 0xc01b, "TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA", NULL },
+ { 0xc01c, "TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA", NULL },
+ { 0xc01d, "TLS_SRP_SHA_WITH_AES_128_CBC_SHA", NULL },
+ { 0xc01e, "TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA", NULL },
+ { 0xc01f, "TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA", NULL },
+ { 0xc020, "TLS_SRP_SHA_WITH_AES_256_CBC_SHA", NULL },
+ { 0xc021, "TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA", NULL },
+ { 0xc022, "TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA", NULL },
+ { 0xc023, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "ECDHE-ECDSA-AES128-SHA256" },
+ { 0xc024, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "ECDHE-ECDSA-AES256-SHA384" },
+ { 0xc025, "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256", "ECDH-ECDSA-AES128-SHA256" },
+ { 0xc026, "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384", "ECDH-ECDSA-AES256-SHA384" },
+ { 0xc027, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "ECDHE-RSA-AES128-SHA256" },
+ { 0xc028, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "ECDHE-RSA-AES256-SHA384" },
+ { 0xc029, "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256", "ECDH-RSA-AES128-SHA256" },
+ { 0xc02a, "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384", "ECDH-RSA-AES256-SHA384" },
+ { 0xc02b, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "ECDHE-ECDSA-AES128-GCM-SHA256" },
+ { 0xc02c, "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "ECDHE-ECDSA-AES256-GCM-SHA384" },
+ { 0xc02d, "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", "ECDH-ECDSA-AES128-GCM-SHA256" },
+ { 0xc02e, "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384", "ECDH-ECDSA-AES256-GCM-SHA384" },
+ { 0xc02f, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "ECDHE-RSA-AES128-GCM-SHA256" },
+ { 0xc030, "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "ECDHE-RSA-AES256-GCM-SHA384" },
+ { 0xc031, "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256", "ECDH-RSA-AES128-GCM-SHA256" },
+ { 0xc032, "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384", "ECDH-RSA-AES256-GCM-SHA384" },
+ { 0xc033, "TLS_ECDHE_PSK_WITH_RC4_128_SHA", NULL },
+ { 0xc034, "TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA", NULL },
+ { 0xc035, "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA", NULL },
+ { 0xc036, "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA", NULL },
+ { 0xc037, "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256", NULL },
+ { 0xc038, "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384", NULL },
+ { 0xc039, "TLS_ECDHE_PSK_WITH_NULL_SHA", NULL },
+ { 0xc03a, "TLS_ECDHE_PSK_WITH_NULL_SHA256", NULL },
+ { 0xc03b, "TLS_ECDHE_PSK_WITH_NULL_SHA384", NULL },
+ { 0xc03c, "TLS_RSA_WITH_ARIA_128_CBC_SHA256", NULL },
+ { 0xc03d, "TLS_RSA_WITH_ARIA_256_CBC_SHA384", NULL },
+ { 0xc03e, "TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256", NULL },
+ { 0xc03f, "TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384", NULL },
+ { 0xc040, "TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256", NULL },
+ { 0xc041, "TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384", NULL },
+ { 0xc042, "TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256", NULL },
+ { 0xc043, "TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384", NULL },
+ { 0xc044, "TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256", NULL },
+ { 0xc045, "TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384", NULL },
+ { 0xc046, "TLS_DH_anon_WITH_ARIA_128_CBC_SHA256", NULL },
+ { 0xc047, "TLS_DH_anon_WITH_ARIA_256_CBC_SHA384", NULL },
+ { 0xc048, "TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256", NULL },
+ { 0xc049, "TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384", NULL },
+ { 0xc04a, "TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256", NULL },
+ { 0xc04b, "TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384", NULL },
+ { 0xc04c, "TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256", NULL },
+ { 0xc04d, "TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384", NULL },
+ { 0xc04e, "TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256", NULL },
+ { 0xc04f, "TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384", NULL },
+ { 0xc050, "TLS_RSA_WITH_ARIA_128_GCM_SHA256", NULL },
+ { 0xc051, "TLS_RSA_WITH_ARIA_256_GCM_SHA384", NULL },
+ { 0xc052, "TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256", NULL },
+ { 0xc053, "TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384", NULL },
+ { 0xc054, "TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256", NULL },
+ { 0xc055, "TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384", NULL },
+ { 0xc056, "TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256", NULL },
+ { 0xc057, "TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384", NULL },
+ { 0xc058, "TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256", NULL },
+ { 0xc059, "TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384", NULL },
+ { 0xc05a, "TLS_DH_anon_WITH_ARIA_128_GCM_SHA256", NULL },
+ { 0xc05b, "TLS_DH_anon_WITH_ARIA_256_GCM_SHA384", NULL },
+ { 0xc05c, "TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256", NULL },
+ { 0xc05d, "TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384", NULL },
+ { 0xc05e, "TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256", NULL },
+ { 0xc05f, "TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384", NULL },
+ { 0xc060, "TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256", NULL },
+ { 0xc061, "TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384", NULL },
+ { 0xc062, "TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256", NULL },
+ { 0xc063, "TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384", NULL },
+ { 0xc064, "TLS_PSK_WITH_ARIA_128_CBC_SHA256", NULL },
+ { 0xc065, "TLS_PSK_WITH_ARIA_256_CBC_SHA384", NULL },
+ { 0xc066, "TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256", NULL },
+ { 0xc067, "TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384", NULL },
+ { 0xc068, "TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256", NULL },
+ { 0xc069, "TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384", NULL },
+ { 0xc06a, "TLS_PSK_WITH_ARIA_128_GCM_SHA256", NULL },
+ { 0xc06b, "TLS_PSK_WITH_ARIA_256_GCM_SHA384", NULL },
+ { 0xc06c, "TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256", NULL },
+ { 0xc06d, "TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384", NULL },
+ { 0xc06e, "TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256", NULL },
+ { 0xc06f, "TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384", NULL },
+ { 0xc070, "TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256", NULL },
+ { 0xc071, "TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384", NULL },
+ { 0xc072, "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", NULL },
+ { 0xc073, "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", NULL },
+ { 0xc074, "TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", NULL },
+ { 0xc075, "TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", NULL },
+ { 0xc076, "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", NULL },
+ { 0xc077, "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384", NULL },
+ { 0xc078, "TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256", NULL },
+ { 0xc079, "TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384", NULL },
+ { 0xc07a, "TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256", NULL },
+ { 0xc07b, "TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384", NULL },
+ { 0xc07c, "TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256", NULL },
+ { 0xc07d, "TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384", NULL },
+ { 0xc07e, "TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256", NULL },
+ { 0xc07f, "TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384", NULL },
+ { 0xc080, "TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256", NULL },
+ { 0xc081, "TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384", NULL },
+ { 0xc082, "TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256", NULL },
+ { 0xc083, "TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384", NULL },
+ { 0xc084, "TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256", NULL },
+ { 0xc085, "TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384", NULL },
+ { 0xc086, "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256", NULL },
+ { 0xc087, "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384", NULL },
+ { 0xc088, "TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256", NULL },
+ { 0xc089, "TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384", NULL },
+ { 0xc08a, "TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256", NULL },
+ { 0xc08b, "TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384", NULL },
+ { 0xc08c, "TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256", NULL },
+ { 0xc08d, "TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384", NULL },
+ { 0xc08e, "TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256", NULL },
+ { 0xc08f, "TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384", NULL },
+ { 0xc090, "TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256", NULL },
+ { 0xc091, "TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384", NULL },
+ { 0xc092, "TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256", NULL },
+ { 0xc093, "TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384", NULL },
+ { 0xc094, "TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256", NULL },
+ { 0xc095, "TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384", NULL },
+ { 0xc096, "TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", NULL },
+ { 0xc097, "TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", NULL },
+ { 0xc098, "TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256", NULL },
+ { 0xc099, "TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384", NULL },
+ { 0xc09a, "TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", NULL },
+ { 0xc09b, "TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", NULL },
+ { 0xc09c, "TLS_RSA_WITH_AES_128_CCM", NULL },
+ { 0xc09d, "TLS_RSA_WITH_AES_256_CCM", NULL },
+ { 0xc09e, "TLS_DHE_RSA_WITH_AES_128_CCM", NULL },
+ { 0xc09f, "TLS_DHE_RSA_WITH_AES_256_CCM", NULL },
+ { 0xc0a0, "TLS_RSA_WITH_AES_128_CCM_8", NULL },
+ { 0xc0a1, "TLS_RSA_WITH_AES_256_CCM_8", NULL },
+ { 0xc0a2, "TLS_DHE_RSA_WITH_AES_128_CCM_8", NULL },
+ { 0xc0a3, "TLS_DHE_RSA_WITH_AES_256_CCM_8", NULL },
+ { 0xc0a4, "TLS_PSK_WITH_AES_128_CCM", NULL },
+ { 0xc0a5, "TLS_PSK_WITH_AES_256_CCM", NULL },
+ { 0xc0a6, "TLS_DHE_PSK_WITH_AES_128_CCM", NULL },
+ { 0xc0a7, "TLS_DHE_PSK_WITH_AES_256_CCM", NULL },
+ { 0xc0a8, "TLS_PSK_WITH_AES_128_CCM_8", NULL },
+ { 0xc0a9, "TLS_PSK_WITH_AES_256_CCM_8", NULL },
+ { 0xc0aa, "TLS_PSK_DHE_WITH_AES_128_CCM_8", NULL },
+ { 0xc0ab, "TLS_PSK_DHE_WITH_AES_256_CCM_8", NULL },
+ { 0xcca8, "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", "ECDHE-RSA-CHACHA20-POLY1305" },
+ { 0xcca9, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", "ECDHE-ECDSA-CHACHA20-POLY1305" },
+ { 0xccaa, "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256", "DHE-RSA-CHACHA20-POLY1305" },
+ { 0xccab, "TLS_PSK_WITH_CHACHA20_POLY1305_SHA256", "PSK-CHACHA20-POLY1305" },
+ { 0xccac, "TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256", "ECDHE-PSK-CHACHA20-POLY1305" },
+ { 0xccad, "TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256", "DHE-PSK-CHACHA20-POLY1305" },
+ { 0xccae, "TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256", "RSA-PSK-CHACHA20-POLY1305" },
+ { 0xfefe, "SSL_RSA_FIPS_WITH_DES_CBC_SHA", NULL },
+ { 0xfeff, "SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA", NULL },
+};
+
+typedef struct {
+ apr_uint16_t id;
+ const rustls_supported_ciphersuite *rustls_suite;
+} rustls_cipher_t;
+
+tls_proto_conf_t *tls_proto_init(apr_pool_t *pool, server_rec *s)
+{
+ tls_proto_conf_t *conf;
+ tls_cipher_t *cipher;
+ const rustls_supported_ciphersuite *rustls_suite;
+ rustls_cipher_t *rcipher;
+ apr_uint16_t id;
+ apr_size_t i;
+
+ (void)s;
+ conf = apr_pcalloc(pool, sizeof(*conf));
+
+ conf->supported_versions = apr_array_make(pool, 3, sizeof(apr_uint16_t));
+ /* Until we can look that up at crustls, we assume what we currently know */
+ APR_ARRAY_PUSH(conf->supported_versions, apr_uint16_t) = TLS_VERSION_1_2;
+ APR_ARRAY_PUSH(conf->supported_versions, apr_uint16_t) = TLS_VERSION_1_3;
+
+ conf->known_ciphers_by_name = apr_hash_make(pool);
+ conf->known_ciphers_by_id = apr_hash_make(pool);
+ for (i = 0; i < TLS_DIM(KNOWN_CIPHERS); ++i) {
+ cipher = &KNOWN_CIPHERS[i];
+ apr_hash_set(conf->known_ciphers_by_id, &cipher->id, sizeof(apr_uint16_t), cipher);
+ apr_hash_set(conf->known_ciphers_by_name, cipher->name, APR_HASH_KEY_STRING, cipher);
+ if (cipher->alias) {
+ apr_hash_set(conf->known_ciphers_by_name, cipher->alias, APR_HASH_KEY_STRING, cipher);
+ }
+ }
+
+ conf->supported_cipher_ids = apr_array_make(pool, 10, sizeof(apr_uint16_t));
+ conf->rustls_ciphers_by_id = apr_hash_make(pool);
+ i = 0;
+ while ((rustls_suite = rustls_all_ciphersuites_get_entry(i++))) {
+ id = rustls_supported_ciphersuite_get_suite(rustls_suite);
+ rcipher = apr_pcalloc(pool, sizeof(*rcipher));
+ rcipher->id = id;
+ rcipher->rustls_suite = rustls_suite;
+ APR_ARRAY_PUSH(conf->supported_cipher_ids, apr_uint16_t) = id;
+ apr_hash_set(conf->rustls_ciphers_by_id, &rcipher->id, sizeof(apr_uint16_t), rcipher);
+
+ }
+
+ return conf;
+}
+
+const char *tls_proto_get_cipher_names(
+ tls_proto_conf_t *conf, const apr_array_header_t *ciphers, apr_pool_t *pool)
+{
+ apr_array_header_t *names;
+ int n;
+
+ names = apr_array_make(pool, ciphers->nelts, sizeof(const char*));
+ for (n = 0; n < ciphers->nelts; ++n) {
+ apr_uint16_t id = APR_ARRAY_IDX(ciphers, n, apr_uint16_t);
+ APR_ARRAY_PUSH(names, const char *) = tls_proto_get_cipher_name(conf, id, pool);
+ }
+ return apr_array_pstrcat(pool, names, ':');
+}
+
+apr_status_t tls_proto_pre_config(apr_pool_t *pool, apr_pool_t *ptemp)
+{
+ (void)pool;
+ (void)ptemp;
+ return APR_SUCCESS;
+}
+
+apr_status_t tls_proto_post_config(apr_pool_t *pool, apr_pool_t *ptemp, server_rec *s)
+{
+ tls_conf_server_t *sc = tls_conf_server_get(s);
+ tls_proto_conf_t *conf = sc->global->proto;
+
+ (void)pool;
+ if (APLOGdebug(s)) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10314)
+ "tls ciphers supported: %s",
+ tls_proto_get_cipher_names(conf, conf->supported_cipher_ids, ptemp));
+ }
+ return APR_SUCCESS;
+}
+
+static apr_status_t get_uint16_from(const char *name, const char *prefix, apr_uint16_t *pint)
+{
+ apr_size_t plen = strlen(prefix);
+ if (strlen(name) == plen+4 && !strncmp(name, prefix, plen)) {
+ /* may be a hex notation cipher id */
+ char *end = NULL;
+ apr_int64_t code = apr_strtoi64(name + plen, &end, 16);
+ if ((!end || !*end) && code && code <= APR_UINT16_MAX) {
+ *pint = (apr_uint16_t)code;
+ return APR_SUCCESS;
+ }
+ }
+ return APR_ENOENT;
+}
+
+apr_uint16_t tls_proto_get_version_by_name(tls_proto_conf_t *conf, const char *name)
+{
+ apr_uint16_t version;
+ (void)conf;
+ if (!apr_strnatcasecmp(name, "TLSv1.2")) {
+ return TLS_VERSION_1_2;
+ }
+ else if (!apr_strnatcasecmp(name, "TLSv1.3")) {
+ return TLS_VERSION_1_3;
+ }
+ if (APR_SUCCESS == get_uint16_from(name, "TLSv0x", &version)) {
+ return version;
+ }
+ return 0;
+}
+
+const char *tls_proto_get_version_name(
+ tls_proto_conf_t *conf, apr_uint16_t id, apr_pool_t *pool)
+{
+ (void)conf;
+ switch (id) {
+ case TLS_VERSION_1_2:
+ return "TLSv1.2";
+ case TLS_VERSION_1_3:
+ return "TLSv1.3";
+ default:
+ return apr_psprintf(pool, "TLSv0x%04x", id);
+ }
+}
+
+apr_array_header_t *tls_proto_create_versions_plus(
+ tls_proto_conf_t *conf, apr_uint16_t min_version, apr_pool_t *pool)
+{
+ apr_array_header_t *versions = apr_array_make(pool, 3, sizeof(apr_uint16_t));
+ apr_uint16_t version;
+ int i;
+
+ for (i = 0; i < conf->supported_versions->nelts; ++i) {
+ version = APR_ARRAY_IDX(conf->supported_versions, i, apr_uint16_t);
+ if (version >= min_version) {
+ APR_ARRAY_PUSH(versions, apr_uint16_t) = version;
+ }
+ }
+ return versions;
+}
+
+int tls_proto_is_cipher_supported(tls_proto_conf_t *conf, apr_uint16_t cipher)
+{
+ return tls_util_array_uint16_contains(conf->supported_cipher_ids, cipher);
+}
+
+apr_status_t tls_proto_get_cipher_by_name(
+ tls_proto_conf_t *conf, const char *name, apr_uint16_t *pcipher)
+{
+ tls_cipher_t *cipher = apr_hash_get(conf->known_ciphers_by_name, name, APR_HASH_KEY_STRING);
+ if (cipher) {
+ *pcipher = cipher->id;
+ return APR_SUCCESS;
+ }
+ return get_uint16_from(name, "TLS_CIPHER_0x", pcipher);
+}
+
+const char *tls_proto_get_cipher_name(
+ tls_proto_conf_t *conf, apr_uint16_t id, apr_pool_t *pool)
+{
+ tls_cipher_t *cipher = apr_hash_get(conf->known_ciphers_by_id, &id, sizeof(apr_uint16_t));
+ if (cipher) {
+ return cipher->name;
+ }
+ return apr_psprintf(pool, "TLS_CIPHER_0x%04x", id);
+}
+
+apr_array_header_t *tls_proto_get_rustls_suites(
+ tls_proto_conf_t *conf, const apr_array_header_t *ids, apr_pool_t *pool)
+{
+ apr_array_header_t *suites;
+ rustls_cipher_t *rcipher;
+ apr_uint16_t id;
+ int i;
+
+ suites = apr_array_make(pool, ids->nelts, sizeof(const rustls_supported_ciphersuite*));
+ for (i = 0; i < ids->nelts; ++i) {
+ id = APR_ARRAY_IDX(ids, i, apr_uint16_t);
+ rcipher = apr_hash_get(conf->rustls_ciphers_by_id, &id, sizeof(apr_uint16_t));
+ if (rcipher) {
+ APR_ARRAY_PUSH(suites, const rustls_supported_ciphersuite *) = rcipher->rustls_suite;
+ }
+ }
+ return suites;
+}
diff --git a/modules/tls/tls_proto.h b/modules/tls/tls_proto.h
new file mode 100644
index 0000000..a3fe881
--- /dev/null
+++ b/modules/tls/tls_proto.h
@@ -0,0 +1,124 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef tls_proto_h
+#define tls_proto_h
+
+#include "tls_util.h"
+
+
+#define TLS_VERSION_1_2 0x0303
+#define TLS_VERSION_1_3 0x0304
+
+/**
+ * Specification of a TLS cipher by name, possible alias and its 16 bit value
+ * as assigned by IANA.
+ */
+typedef struct {
+ apr_uint16_t id; /* IANA 16-bit assigned value as used on the wire */
+ const char *name; /* IANA given name of the cipher */
+ const char *alias; /* Optional, commonly known alternate name */
+} tls_cipher_t;
+
+/**
+ * TLS protocol related definitions constructed
+ * by querying crustls lib.
+ */
+typedef struct tls_proto_conf_t tls_proto_conf_t;
+struct tls_proto_conf_t {
+ apr_array_header_t *supported_versions; /* supported protocol versions (apr_uint16_t) */
+ apr_hash_t *known_ciphers_by_name; /* hash by name of known tls_cipher_t* */
+ apr_hash_t *known_ciphers_by_id; /* hash by id of known tls_cipher_t* */
+ apr_hash_t *rustls_ciphers_by_id; /* hash by id of rustls rustls_supported_ciphersuite* */
+ apr_array_header_t *supported_cipher_ids; /* cipher ids (apr_uint16_t) supported by rustls */
+ const rustls_root_cert_store *native_roots;
+};
+
+/**
+ * Create and populate the protocol configuration.
+ */
+tls_proto_conf_t *tls_proto_init(apr_pool_t *p, server_rec *s);
+
+/**
+ * Called during pre-config phase to start initialization
+ * of the tls protocol configuration.
+ */
+apr_status_t tls_proto_pre_config(apr_pool_t *pool, apr_pool_t *ptemp);
+
+/**
+ * Called during post-config phase to conclude the initialization
+ * of the tls protocol configuration.
+ */
+apr_status_t tls_proto_post_config(apr_pool_t *p, apr_pool_t *ptemp, server_rec *s);
+
+/**
+ * Get the TLS protocol identifier (as used on the wire) for the TLS
+ * protocol of the given name. Returns 0 if protocol is unknown.
+ */
+apr_uint16_t tls_proto_get_version_by_name(tls_proto_conf_t *conf, const char *name);
+
+/**
+ * Get the name of the protocol version identified by its identifier. This
+ * will return the name from the protocol configuration or, if unknown, create
+ * the string `TLSv0x%04x` from the 16bit identifier.
+ */
+const char *tls_proto_get_version_name(
+ tls_proto_conf_t *conf, apr_uint16_t id, apr_pool_t *pool);
+
+/**
+ * Create an array of the given TLS protocol version identifier `min_version`
+ * and all supported new ones. The array carries apr_uint16_t values.
+ */
+apr_array_header_t *tls_proto_create_versions_plus(
+ tls_proto_conf_t *conf, apr_uint16_t min_version, apr_pool_t *pool);
+
+/**
+ * Get a TLS cipher spec by name/alias.
+ */
+apr_status_t tls_proto_get_cipher_by_name(
+ tls_proto_conf_t *conf, const char *name, apr_uint16_t *pcipher);
+
+/**
+ * Return != 0 iff the cipher is supported by the rustls library.
+ */
+int tls_proto_is_cipher_supported(tls_proto_conf_t *conf, apr_uint16_t cipher);
+
+/**
+ * Get the name of a TLS cipher for the IANA assigned 16bit value. This will
+ * return the name in the protocol configuration, if the cipher is known, and
+ * create the string `TLS_CIPHER_0x%04x` for the 16bit cipher value.
+ */
+const char *tls_proto_get_cipher_name(
+ tls_proto_conf_t *conf, apr_uint16_t cipher, apr_pool_t *pool);
+
+/**
+ * Get the concatenated names with ':' as separator of all TLS cipher identifiers
+ * as given in `ciphers`.
+ * @param conf the TLS protocol configuration
+ * @param ciphers the 16bit values of the TLS ciphers
+ * @param pool to use for allocation the string.
+ */
+const char *tls_proto_get_cipher_names(
+ tls_proto_conf_t *conf, const apr_array_header_t *ciphers, apr_pool_t *pool);
+
+/**
+ * Convert an array of TLS cipher 16bit identifiers into the `rustls_supported_ciphersuite`
+ * instances that can be passed to crustls in session configurations.
+ * Any cipher identifier not supported by rustls we be silently omitted.
+ */
+apr_array_header_t *tls_proto_get_rustls_suites(
+ tls_proto_conf_t *conf, const apr_array_header_t *ids, apr_pool_t *pool);
+
+#endif /* tls_proto_h */
diff --git a/modules/tls/tls_util.c b/modules/tls/tls_util.c
new file mode 100644
index 0000000..9eac212
--- /dev/null
+++ b/modules/tls/tls_util.c
@@ -0,0 +1,367 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <assert.h>
+#include <apr_lib.h>
+#include <apr_file_info.h>
+#include <apr_strings.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_log.h>
+
+#include <rustls.h>
+
+#include "tls_proto.h"
+#include "tls_util.h"
+
+
+extern module AP_MODULE_DECLARE_DATA tls_module;
+APLOG_USE_MODULE(tls);
+
+
+tls_data_t tls_data_from_str(const char *s)
+{
+ tls_data_t d;
+ d.data = (const unsigned char*)s;
+ d.len = s? strlen(s) : 0;
+ return d;
+}
+
+tls_data_t tls_data_assign_copy(apr_pool_t *p, const tls_data_t *d)
+{
+ tls_data_t copy;
+ copy.data = apr_pmemdup(p, d->data, d->len);
+ copy.len = d->len;
+ return copy;
+}
+
+tls_data_t *tls_data_copy(apr_pool_t *p, const tls_data_t *d)
+{
+ tls_data_t *copy;
+ copy = apr_pcalloc(p, sizeof(*copy));
+ *copy = tls_data_assign_copy(p, d);
+ return copy;
+}
+
+const char *tls_data_to_str(apr_pool_t *p, const tls_data_t *d)
+{
+ char *s = apr_pcalloc(p, d->len+1);
+ memcpy(s, d->data, d->len);
+ return s;
+}
+
+apr_status_t tls_util_rustls_error(
+ apr_pool_t *p, rustls_result rr, const char **perr_descr)
+{
+ if (perr_descr) {
+ char buffer[HUGE_STRING_LEN];
+ apr_size_t len = 0;
+
+ rustls_error(rr, buffer, sizeof(buffer), &len);
+ *perr_descr = apr_pstrndup(p, buffer, len);
+ }
+ return APR_EGENERAL;
+}
+
+int tls_util_is_file(
+ apr_pool_t *p, const char *fpath)
+{
+ apr_finfo_t finfo;
+
+ return (fpath != NULL
+ && apr_stat(&finfo, fpath, APR_FINFO_TYPE|APR_FINFO_SIZE, p) == 0
+ && finfo.filetype == APR_REG);
+}
+
+apr_status_t tls_util_file_load(
+ apr_pool_t *p, const char *fpath, apr_size_t min_len, apr_size_t max_len, tls_data_t *data)
+{
+ apr_finfo_t finfo;
+ apr_status_t rv;
+ apr_file_t *f = NULL;
+ unsigned char *buffer;
+ apr_size_t len;
+ const char *err = NULL;
+ tls_data_t *d;
+
+ rv = apr_stat(&finfo, fpath, APR_FINFO_TYPE|APR_FINFO_SIZE, p);
+ if (APR_SUCCESS != rv) {
+ err = "cannot stat"; goto cleanup;
+ }
+ if (finfo.filetype != APR_REG) {
+ err = "not a plain file";
+ rv = APR_EINVAL; goto cleanup;
+ }
+ if (finfo.size > LONG_MAX) {
+ err = "file is too large";
+ rv = APR_EINVAL; goto cleanup;
+ }
+ len = (apr_size_t)finfo.size;
+ if (len < min_len || len > max_len) {
+ err = "file size not in allowed range";
+ rv = APR_EINVAL; goto cleanup;
+ }
+ d = apr_pcalloc(p, sizeof(*d));
+ buffer = apr_pcalloc(p, len+1); /* keep it NUL terminated in any case */
+ rv = apr_file_open(&f, fpath, APR_FOPEN_READ, 0, p);
+ if (APR_SUCCESS != rv) {
+ err = "error opening"; goto cleanup;
+ }
+ rv = apr_file_read(f, buffer, &len);
+ if (APR_SUCCESS != rv) {
+ err = "error reading"; goto cleanup;
+ }
+cleanup:
+ if (f) apr_file_close(f);
+ if (APR_SUCCESS == rv) {
+ data->data = buffer;
+ data->len = len;
+ }
+ else {
+ memset(data, 0, sizeof(*data));
+ ap_log_perror(APLOG_MARK, APLOG_ERR, rv, p, APLOGNO(10361)
+ "Failed to load file %s: %s", fpath, err? err: "-");
+ }
+ return rv;
+}
+
+int tls_util_array_uint16_contains(const apr_array_header_t* a, apr_uint16_t n)
+{
+ int i;
+ for (i = 0; i < a->nelts; ++i) {
+ if (APR_ARRAY_IDX(a, i, apr_uint16_t) == n) return 1;
+ }
+ return 0;
+}
+
+const apr_array_header_t *tls_util_array_uint16_remove(
+ apr_pool_t *pool, const apr_array_header_t* from, const apr_array_header_t* others)
+{
+ apr_array_header_t *na = NULL;
+ apr_uint16_t id;
+ int i, j;
+
+ for (i = 0; i < from->nelts; ++i) {
+ id = APR_ARRAY_IDX(from, i, apr_uint16_t);
+ if (tls_util_array_uint16_contains(others, id)) {
+ if (na == NULL) {
+ /* first removal, make a new result array, copy elements before */
+ na = apr_array_make(pool, from->nelts, sizeof(apr_uint16_t));
+ for (j = 0; j < i; ++j) {
+ APR_ARRAY_PUSH(na, apr_uint16_t) = APR_ARRAY_IDX(from, j, apr_uint16_t);
+ }
+ }
+ }
+ else if (na) {
+ APR_ARRAY_PUSH(na, apr_uint16_t) = id;
+ }
+ }
+ return na? na : from;
+}
+
+apr_status_t tls_util_brigade_transfer(
+ apr_bucket_brigade *dest, apr_bucket_brigade *src, apr_off_t length,
+ apr_off_t *pnout)
+{
+ apr_bucket *b;
+ apr_off_t remain = length;
+ apr_status_t rv = APR_SUCCESS;
+ const char *ign;
+ apr_size_t ilen;
+
+ *pnout = 0;
+ while (!APR_BRIGADE_EMPTY(src)) {
+ b = APR_BRIGADE_FIRST(src);
+
+ if (APR_BUCKET_IS_METADATA(b)) {
+ APR_BUCKET_REMOVE(b);
+ APR_BRIGADE_INSERT_TAIL(dest, b);
+ }
+ else {
+ if (remain == (apr_off_t)b->length) {
+ /* fall through */
+ }
+ else if (remain <= 0) {
+ goto cleanup;
+ }
+ else {
+ if (b->length == ((apr_size_t)-1)) {
+ rv= apr_bucket_read(b, &ign, &ilen, APR_BLOCK_READ);
+ if (APR_SUCCESS != rv) goto cleanup;
+ }
+ if (remain < (apr_off_t)b->length) {
+ apr_bucket_split(b, (apr_size_t)remain);
+ }
+ }
+ APR_BUCKET_REMOVE(b);
+ APR_BRIGADE_INSERT_TAIL(dest, b);
+ remain -= (apr_off_t)b->length;
+ *pnout += (apr_off_t)b->length;
+ }
+ }
+cleanup:
+ return rv;
+}
+
+apr_status_t tls_util_brigade_copy(
+ apr_bucket_brigade *dest, apr_bucket_brigade *src, apr_off_t length,
+ apr_off_t *pnout)
+{
+ apr_bucket *b, *next;
+ apr_off_t remain = length;
+ apr_status_t rv = APR_SUCCESS;
+ const char *ign;
+ apr_size_t ilen;
+
+ *pnout = 0;
+ for (b = APR_BRIGADE_FIRST(src);
+ b != APR_BRIGADE_SENTINEL(src);
+ b = next) {
+ next = APR_BUCKET_NEXT(b);
+
+ if (APR_BUCKET_IS_METADATA(b)) {
+ /* fall through */
+ }
+ else {
+ if (remain == (apr_off_t)b->length) {
+ /* fall through */
+ }
+ else if (remain <= 0) {
+ goto cleanup;
+ }
+ else {
+ if (b->length == ((apr_size_t)-1)) {
+ rv = apr_bucket_read(b, &ign, &ilen, APR_BLOCK_READ);
+ if (APR_SUCCESS != rv) goto cleanup;
+ }
+ if (remain < (apr_off_t)b->length) {
+ apr_bucket_split(b, (apr_size_t)remain);
+ }
+ }
+ }
+ rv = apr_bucket_copy(b, &b);
+ if (APR_SUCCESS != rv) goto cleanup;
+ APR_BRIGADE_INSERT_TAIL(dest, b);
+ remain -= (apr_off_t)b->length;
+ *pnout += (apr_off_t)b->length;
+ }
+cleanup:
+ return rv;
+}
+
+apr_status_t tls_util_brigade_split_line(
+ apr_bucket_brigade *dest, apr_bucket_brigade *src,
+ apr_read_type_e block, apr_off_t length,
+ apr_off_t *pnout)
+{
+ apr_off_t nstart, nend;
+ apr_status_t rv;
+
+ apr_brigade_length(dest, 0, &nstart);
+ rv = apr_brigade_split_line(dest, src, block, length);
+ if (APR_SUCCESS != rv) goto cleanup;
+ apr_brigade_length(dest, 0, &nend);
+ /* apr_brigade_split_line() has the nasty habit of leaving a 0-length bucket
+ * at the start of the brigade when it transferred the whole content. Get rid of it.
+ */
+ if (!APR_BRIGADE_EMPTY(src)) {
+ apr_bucket *b = APR_BRIGADE_FIRST(src);
+ if (!APR_BUCKET_IS_METADATA(b) && 0 == b->length) {
+ APR_BUCKET_REMOVE(b);
+ apr_bucket_delete(b);
+ }
+ }
+cleanup:
+ *pnout = (APR_SUCCESS == rv)? (nend - nstart) : 0;
+ return rv;
+}
+
+int tls_util_name_matches_server(const char *name, server_rec *s)
+{
+ apr_array_header_t *names;
+ char **alias;
+ int i;
+
+ if (!s || !s->server_hostname) return 0;
+ if (!strcasecmp(name, s->server_hostname)) return 1;
+ /* first the fast equality match, then the pattern wild_name matches */
+ names = s->names;
+ if (!names) return 0;
+ alias = (char **)names->elts;
+ for (i = 0; i < names->nelts; ++i) {
+ if (alias[i] && !strcasecmp(name, alias[i])) return 1;
+ }
+ names = s->wild_names;
+ if (!names) return 0;
+ alias = (char **)names->elts;
+ for (i = 0; i < names->nelts; ++i) {
+ if (alias[i] && !ap_strcasecmp_match(name, alias[i])) return 1;
+ }
+ return 0;
+}
+
+apr_size_t tls_util_bucket_print(char *buffer, apr_size_t bmax,
+ apr_bucket *b, const char *sep)
+{
+ apr_size_t off = 0;
+ if (sep && *sep) {
+ off += (size_t)apr_snprintf(buffer+off, bmax-off, "%s", sep);
+ }
+
+ if (bmax <= off) {
+ return off;
+ }
+ else if (APR_BUCKET_IS_METADATA(b)) {
+ off += (size_t)apr_snprintf(buffer+off, bmax-off, "%s", b->type->name);
+ }
+ else if (bmax > off) {
+ off += (size_t)apr_snprintf(buffer+off, bmax-off, "%s[%ld]",
+ b->type->name, (long)(b->length == ((apr_size_t)-1)?
+ -1 : (int)b->length));
+ }
+ return off;
+}
+
+apr_size_t tls_util_bb_print(char *buffer, apr_size_t bmax,
+ const char *tag, const char *sep,
+ apr_bucket_brigade *bb)
+{
+ apr_size_t off = 0;
+ const char *sp = "";
+ apr_bucket *b;
+
+ if (bmax > 1) {
+ if (bb) {
+ memset(buffer, 0, bmax--);
+ off += (size_t)apr_snprintf(buffer+off, bmax-off, "%s(", tag);
+ for (b = APR_BRIGADE_FIRST(bb);
+ (bmax > off) && (b != APR_BRIGADE_SENTINEL(bb));
+ b = APR_BUCKET_NEXT(b)) {
+
+ off += tls_util_bucket_print(buffer+off, bmax-off, b, sp);
+ sp = " ";
+ }
+ if (bmax > off) {
+ off += (size_t)apr_snprintf(buffer+off, bmax-off, ")%s", sep);
+ }
+ }
+ else {
+ off += (size_t)apr_snprintf(buffer+off, bmax-off, "%s(null)%s", tag, sep);
+ }
+ }
+ return off;
+}
+
diff --git a/modules/tls/tls_util.h b/modules/tls/tls_util.h
new file mode 100644
index 0000000..18ae4df
--- /dev/null
+++ b/modules/tls/tls_util.h
@@ -0,0 +1,157 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef tls_util_h
+#define tls_util_h
+
+#define TLS_DIM(a) (sizeof(a)/sizeof(a[0]))
+
+
+/**
+ * Simple struct to hold a range of bytes and its length together.
+ */
+typedef struct tls_data_t tls_data_t;
+struct tls_data_t {
+ const unsigned char* data;
+ apr_size_t len;
+};
+
+/**
+ * Return a tls_data_t for a string.
+ */
+tls_data_t tls_data_from_str(const char *s);
+
+/**
+ * Create a copy of a tls_data_t using the given pool.
+ */
+tls_data_t *tls_data_copy(apr_pool_t *p, const tls_data_t *d);
+
+/**
+ * Return a copy of a tls_data_t bytes allocated from pool.
+ */
+tls_data_t tls_data_assign_copy(apr_pool_t *p, const tls_data_t *d);
+
+/**
+ * Convert the data bytes in `d` into a NUL-terminated string.
+ * There is no check if the data bytes already contain NUL.
+ */
+const char *tls_data_to_str(apr_pool_t *p, const tls_data_t *d);
+
+/**
+ * Return != 0 if fpath is a 'real' file.
+ */
+int tls_util_is_file(apr_pool_t *p, const char *fpath);
+
+/**
+ * Inspect a 'rustls_result', retrieve the error description for it and
+ * return the apr_status_t to use as our error status.
+ */
+apr_status_t tls_util_rustls_error(apr_pool_t *p, rustls_result rr, const char **perr_descr);
+
+/**
+ * Load up to `max_len` bytes into a buffer allocated from the pool.
+ * @return ARP_SUCCESS on successful load.
+ * APR_EINVAL when the file was not a regular file or is too large.
+ */
+apr_status_t tls_util_file_load(
+ apr_pool_t *p, const char *fpath, size_t min_len, size_t max_len, tls_data_t *data);
+
+/**
+ * Return != 0 iff the array of apr_uint16_t contains value n.
+ */
+int tls_util_array_uint16_contains(const apr_array_header_t* a, apr_uint16_t n);
+
+/**
+ * Remove all apr_uint16_t in `others` from array `from`.
+ * Returns the new array or, if no overlap was found, the `from` array unchanged.
+ */
+const apr_array_header_t *tls_util_array_uint16_remove(
+ apr_pool_t *pool, const apr_array_header_t* from, const apr_array_header_t* others);
+
+/**
+ * Transfer up to <length> bytes from <src> to <dest>, including all
+ * encountered meta data buckets. The transferred buckets/data are
+ * removed from <src>.
+ * Return the actual byte count transferred in <pnout>.
+ */
+apr_status_t tls_util_brigade_transfer(
+ apr_bucket_brigade *dest, apr_bucket_brigade *src, apr_off_t length,
+ apr_off_t *pnout);
+
+/**
+ * Copy up to <length> bytes from <src> to <dest>, including all
+ * encountered meta data buckets. <src> remains semantically unchaanged,
+ * meaning there might have been buckets split or changed while reading
+ * their content.
+ * Return the actual byte count copied in <pnout>.
+ */
+apr_status_t tls_util_brigade_copy(
+ apr_bucket_brigade *dest, apr_bucket_brigade *src, apr_off_t length,
+ apr_off_t *pnout);
+
+/**
+ * Get a line of max `length` bytes from `src` into `dest`.
+ * Return the number of bytes transferred in `pnout`.
+ */
+apr_status_t tls_util_brigade_split_line(
+ apr_bucket_brigade *dest, apr_bucket_brigade *src,
+ apr_read_type_e block, apr_off_t length,
+ apr_off_t *pnout);
+
+/**
+ * Return != 0 iff the given <name> matches the configured 'ServerName'
+ * or one of the 'ServerAlias' name of <s>, including wildcard patterns
+ * as understood by ap_strcasecmp_match().
+ */
+int tls_util_name_matches_server(const char *name, server_rec *s);
+
+
+/**
+ * Print a bucket's meta data (type and length) to the buffer.
+ * @return number of characters printed
+ */
+apr_size_t tls_util_bucket_print(char *buffer, apr_size_t bmax,
+ apr_bucket *b, const char *sep);
+
+/**
+ * Prints the brigade bucket types and lengths into the given buffer
+ * up to bmax.
+ * @return number of characters printed
+ */
+apr_size_t tls_util_bb_print(char *buffer, apr_size_t bmax,
+ const char *tag, const char *sep,
+ apr_bucket_brigade *bb);
+/**
+ * Logs the bucket brigade (which bucket types with what length)
+ * to the log at the given level.
+ * @param c the connection to log for
+ * @param sid the stream identifier this brigade belongs to
+ * @param level the log level (as in APLOG_*)
+ * @param tag a short message text about the context
+ * @param bb the brigade to log
+ */
+#define tls_util_bb_log(c, level, tag, bb) \
+do { \
+ char buffer[4 * 1024]; \
+ const char *line = "(null)"; \
+ apr_size_t len, bmax = sizeof(buffer)/sizeof(buffer[0]); \
+ len = tls_util_bb_print(buffer, bmax, (tag), "", (bb)); \
+ ap_log_cerror(APLOG_MARK, level, 0, (c), "bb_dump(%ld): %s", \
+ ((c)->master? (c)->master->id : (c)->id), (len? buffer : line)); \
+} while(0)
+
+
+
+#endif /* tls_util_h */
diff --git a/modules/tls/tls_var.c b/modules/tls/tls_var.c
new file mode 100644
index 0000000..fa4ae2a
--- /dev/null
+++ b/modules/tls/tls_var.c
@@ -0,0 +1,397 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <assert.h>
+#include <apr_lib.h>
+#include <apr_strings.h>
+
+#include <httpd.h>
+#include <http_connection.h>
+#include <http_core.h>
+#include <http_main.h>
+#include <http_log.h>
+#include <ap_socache.h>
+
+#include <rustls.h>
+
+#include "tls_conf.h"
+#include "tls_core.h"
+#include "tls_cert.h"
+#include "tls_util.h"
+#include "tls_var.h"
+#include "tls_version.h"
+
+
+extern module AP_MODULE_DECLARE_DATA tls_module;
+APLOG_USE_MODULE(tls);
+
+typedef struct {
+ apr_pool_t *p;
+ server_rec *s;
+ conn_rec *c;
+ request_rec *r;
+ tls_conf_conn_t *cc;
+ const char *name;
+ const char *arg_s;
+ int arg_i;
+} tls_var_lookup_ctx_t;
+
+typedef const char *var_lookup(const tls_var_lookup_ctx_t *ctx);
+
+static const char *var_get_ssl_protocol(const tls_var_lookup_ctx_t *ctx)
+{
+ return ctx->cc->tls_protocol_name;
+}
+
+static const char *var_get_ssl_cipher(const tls_var_lookup_ctx_t *ctx)
+{
+ return ctx->cc->tls_cipher_name;
+}
+
+static const char *var_get_sni_hostname(const tls_var_lookup_ctx_t *ctx)
+{
+ return ctx->cc->sni_hostname;
+}
+
+static const char *var_get_version_interface(const tls_var_lookup_ctx_t *ctx)
+{
+ tls_conf_server_t *sc = tls_conf_server_get(ctx->s);
+ return sc->global->module_version;
+}
+
+static const char *var_get_version_library(const tls_var_lookup_ctx_t *ctx)
+{
+ tls_conf_server_t *sc = tls_conf_server_get(ctx->s);
+ return sc->global->crustls_version;
+}
+
+static const char *var_get_false(const tls_var_lookup_ctx_t *ctx)
+{
+ (void)ctx;
+ return "false";
+}
+
+static const char *var_get_null(const tls_var_lookup_ctx_t *ctx)
+{
+ (void)ctx;
+ return "NULL";
+}
+
+static const char *var_get_client_s_dn_cn(const tls_var_lookup_ctx_t *ctx)
+{
+ /* There is no support in the crustls/rustls/webpki APIs to
+ * parse X.509 certificates and extract information about
+ * subject, issuer, etc. */
+ if (!ctx->cc->peer_certs || !ctx->cc->peer_certs->nelts) return NULL;
+ return "Not Implemented";
+}
+
+static const char *var_get_client_verify(const tls_var_lookup_ctx_t *ctx)
+{
+ return ctx->cc->peer_certs? "SUCCESS" : "NONE";
+}
+
+static const char *var_get_session_resumed(const tls_var_lookup_ctx_t *ctx)
+{
+ return ctx->cc->session_id_cache_hit? "Resumed" : "Initial";
+}
+
+static const char *var_get_client_cert(const tls_var_lookup_ctx_t *ctx)
+{
+ const rustls_certificate *cert;
+ const char *pem;
+ apr_status_t rv;
+ int cert_idx = 0;
+
+ if (ctx->arg_s) {
+ if (strcmp(ctx->arg_s, "chain")) return NULL;
+ /* ctx->arg_i'th chain cert, which is in out list as */
+ cert_idx = ctx->arg_i + 1;
+ }
+ if (!ctx->cc->peer_certs || cert_idx >= ctx->cc->peer_certs->nelts) return NULL;
+ cert = APR_ARRAY_IDX(ctx->cc->peer_certs, cert_idx, const rustls_certificate*);
+ if (APR_SUCCESS != (rv = tls_cert_to_pem(&pem, ctx->p, cert))) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, ctx->s, APLOGNO(10315)
+ "Failed to create client certificate PEM");
+ return NULL;
+ }
+ return pem;
+}
+
+static const char *var_get_server_cert(const tls_var_lookup_ctx_t *ctx)
+{
+ const rustls_certificate *cert;
+ const char *pem;
+ apr_status_t rv;
+
+ if (!ctx->cc->key) return NULL;
+ cert = rustls_certified_key_get_certificate(ctx->cc->key, 0);
+ if (!cert) return NULL;
+ if (APR_SUCCESS != (rv = tls_cert_to_pem(&pem, ctx->p, cert))) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, ctx->s, APLOGNO(10316)
+ "Failed to create server certificate PEM");
+ return NULL;
+ }
+ return pem;
+}
+
+typedef struct {
+ const char *name;
+ var_lookup* fn;
+ const char *arg_s;
+ int arg_i;
+} var_def_t;
+
+static const var_def_t VAR_DEFS[] = {
+ { "SSL_PROTOCOL", var_get_ssl_protocol, NULL, 0 },
+ { "SSL_CIPHER", var_get_ssl_cipher, NULL, 0 },
+ { "SSL_TLS_SNI", var_get_sni_hostname, NULL, 0 },
+ { "SSL_CLIENT_S_DN_CN", var_get_client_s_dn_cn, NULL, 0 },
+ { "SSL_VERSION_INTERFACE", var_get_version_interface, NULL, 0 },
+ { "SSL_VERSION_LIBRARY", var_get_version_library, NULL, 0 },
+ { "SSL_SECURE_RENEG", var_get_false, NULL, 0 },
+ { "SSL_COMPRESS_METHOD", var_get_null, NULL, 0 },
+ { "SSL_CIPHER_EXPORT", var_get_false, NULL, 0 },
+ { "SSL_CLIENT_VERIFY", var_get_client_verify, NULL, 0 },
+ { "SSL_SESSION_RESUMED", var_get_session_resumed, NULL, 0 },
+ { "SSL_CLIENT_CERT", var_get_client_cert, NULL, 0 },
+ { "SSL_CLIENT_CHAIN_0", var_get_client_cert, "chain", 0 },
+ { "SSL_CLIENT_CHAIN_1", var_get_client_cert, "chain", 1 },
+ { "SSL_CLIENT_CHAIN_2", var_get_client_cert, "chain", 2 },
+ { "SSL_CLIENT_CHAIN_3", var_get_client_cert, "chain", 3 },
+ { "SSL_CLIENT_CHAIN_4", var_get_client_cert, "chain", 4 },
+ { "SSL_CLIENT_CHAIN_5", var_get_client_cert, "chain", 5 },
+ { "SSL_CLIENT_CHAIN_6", var_get_client_cert, "chain", 6 },
+ { "SSL_CLIENT_CHAIN_7", var_get_client_cert, "chain", 7 },
+ { "SSL_CLIENT_CHAIN_8", var_get_client_cert, "chain", 8 },
+ { "SSL_CLIENT_CHAIN_9", var_get_client_cert, "chain", 9 },
+ { "SSL_SERVER_CERT", var_get_server_cert, NULL, 0 },
+};
+
+static const char *const TlsAlwaysVars[] = {
+ "SSL_TLS_SNI",
+ "SSL_PROTOCOL",
+ "SSL_CIPHER",
+ "SSL_CLIENT_S_DN_CN",
+};
+
+/* what mod_ssl defines, plus server cert and client cert DN and SAN entries */
+static const char *const StdEnvVars[] = {
+ "SSL_VERSION_INTERFACE", /* implemented: module version string */
+ "SSL_VERSION_LIBRARY", /* implemented: crustls/rustls version string */
+ "SSL_SECURE_RENEG", /* implemented: always "false" */
+ "SSL_COMPRESS_METHOD", /* implemented: always "NULL" */
+ "SSL_CIPHER_EXPORT", /* implemented: always "false" */
+ "SSL_CIPHER_USEKEYSIZE",
+ "SSL_CIPHER_ALGKEYSIZE",
+ "SSL_CLIENT_VERIFY", /* implemented: always "SUCCESS" or "NONE" */
+ "SSL_CLIENT_M_VERSION",
+ "SSL_CLIENT_M_SERIAL",
+ "SSL_CLIENT_V_START",
+ "SSL_CLIENT_V_END",
+ "SSL_CLIENT_V_REMAIN",
+ "SSL_CLIENT_S_DN",
+ "SSL_CLIENT_I_DN",
+ "SSL_CLIENT_A_KEY",
+ "SSL_CLIENT_A_SIG",
+ "SSL_CLIENT_CERT_RFC4523_CEA",
+ "SSL_SERVER_M_VERSION",
+ "SSL_SERVER_M_SERIAL",
+ "SSL_SERVER_V_START",
+ "SSL_SERVER_V_END",
+ "SSL_SERVER_S_DN",
+ "SSL_SERVER_I_DN",
+ "SSL_SERVER_A_KEY",
+ "SSL_SERVER_A_SIG",
+ "SSL_SESSION_ID", /* not implemented: highly sensitive data we do not expose */
+ "SSL_SESSION_RESUMED", /* implemented: if our cache was hit successfully */
+};
+
+/* Cert related variables, export when TLSOption ExportCertData is set */
+static const char *const ExportCertVars[] = {
+ "SSL_CLIENT_CERT", /* implemented: */
+ "SSL_CLIENT_CHAIN_0", /* implemented: */
+ "SSL_CLIENT_CHAIN_1", /* implemented: */
+ "SSL_CLIENT_CHAIN_2", /* implemented: */
+ "SSL_CLIENT_CHAIN_3", /* implemented: */
+ "SSL_CLIENT_CHAIN_4", /* implemented: */
+ "SSL_CLIENT_CHAIN_5", /* implemented: */
+ "SSL_CLIENT_CHAIN_6", /* implemented: */
+ "SSL_CLIENT_CHAIN_7", /* implemented: */
+ "SSL_CLIENT_CHAIN_8", /* implemented: */
+ "SSL_CLIENT_CHAIN_9", /* implemented: */
+ "SSL_SERVER_CERT", /* implemented: */
+};
+
+void tls_var_init_lookup_hash(apr_pool_t *pool, apr_hash_t *map)
+{
+ const var_def_t *def;
+ apr_size_t i;
+
+ (void)pool;
+ for (i = 0; i < TLS_DIM(VAR_DEFS); ++i) {
+ def = &VAR_DEFS[i];
+ apr_hash_set(map, def->name, APR_HASH_KEY_STRING, def);
+ }
+}
+
+static const char *invoke(var_def_t* def, tls_var_lookup_ctx_t *ctx)
+{
+ if (TLS_CONN_ST_IS_ENABLED(ctx->cc)) {
+ const char *val = ctx->cc->subprocess_env?
+ apr_table_get(ctx->cc->subprocess_env, def->name) : NULL;
+ if (val && *val) return val;
+ ctx->arg_s = def->arg_s;
+ ctx->arg_i = def->arg_i;
+ return def->fn(ctx);
+ }
+ return NULL;
+}
+
+static void set_var(
+ tls_var_lookup_ctx_t *ctx, apr_hash_t *lookups, apr_table_t *table)
+{
+ var_def_t* def = apr_hash_get(lookups, ctx->name, APR_HASH_KEY_STRING);
+ if (def) {
+ const char *val = invoke(def, ctx);
+ if (val && *val) {
+ apr_table_setn(table, ctx->name, val);
+ }
+ }
+}
+
+const char *tls_var_lookup(
+ apr_pool_t *p, server_rec *s, conn_rec *c, request_rec *r, const char *name)
+{
+ const char *val = NULL;
+ tls_conf_server_t *sc;
+ var_def_t* def;
+
+ ap_assert(p);
+ ap_assert(name);
+ s = s? s : (r? r->server : (c? c->base_server : NULL));
+ c = c? c : (r? r->connection : NULL);
+
+ sc = tls_conf_server_get(s? s : ap_server_conf);
+ def = apr_hash_get(sc->global->var_lookups, name, APR_HASH_KEY_STRING);
+ if (def) {
+ tls_var_lookup_ctx_t ctx;
+ ctx.p = p;
+ ctx.s = s;
+ ctx.c = c;
+ ctx.r = r;
+ ctx.cc = c? tls_conf_conn_get(c->master? c->master : c) : NULL;
+ ctx.cc = c? tls_conf_conn_get(c->master? c->master : c) : NULL;
+ ctx.name = name;
+ val = invoke(def, &ctx);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c, "tls lookup of var '%s' -> '%s'", name, val);
+ }
+ return val;
+}
+
+static void add_vars(apr_table_t *env, conn_rec *c, server_rec *s, request_rec *r)
+{
+ tls_conf_server_t *sc;
+ tls_conf_dir_t *dc, *sdc;
+ tls_var_lookup_ctx_t ctx;
+ apr_size_t i;
+ int overlap;
+
+ sc = tls_conf_server_get(s);
+ dc = r? tls_conf_dir_get(r) : tls_conf_dir_server_get(s);
+ sdc = r? tls_conf_dir_server_get(s): dc;
+ ctx.p = r? r->pool : c->pool;
+ ctx.s = s;
+ ctx.c = c;
+ ctx.r = r;
+ ctx.cc = tls_conf_conn_get(c->master? c->master : c);
+ /* Can we re-use the precomputed connection values? */
+ overlap = (r && ctx.cc->subprocess_env && r->server == ctx.cc->server);
+ if (overlap) {
+ apr_table_overlap(env, ctx.cc->subprocess_env, APR_OVERLAP_TABLES_SET);
+ }
+ else {
+ apr_table_setn(env, "HTTPS", "on");
+ for (i = 0; i < TLS_DIM(TlsAlwaysVars); ++i) {
+ ctx.name = TlsAlwaysVars[i];
+ set_var(&ctx, sc->global->var_lookups, env);
+ }
+ }
+ if (dc->std_env_vars == TLS_FLAG_TRUE) {
+ for (i = 0; i < TLS_DIM(StdEnvVars); ++i) {
+ ctx.name = StdEnvVars[i];
+ set_var(&ctx, sc->global->var_lookups, env);
+ }
+ }
+ else if (overlap && sdc->std_env_vars == TLS_FLAG_TRUE) {
+ /* Remove variables added on connection init that are disabled here */
+ for (i = 0; i < TLS_DIM(StdEnvVars); ++i) {
+ apr_table_unset(env, StdEnvVars[i]);
+ }
+ }
+ if (dc->export_cert_vars == TLS_FLAG_TRUE) {
+ for (i = 0; i < TLS_DIM(ExportCertVars); ++i) {
+ ctx.name = ExportCertVars[i];
+ set_var(&ctx, sc->global->var_lookups, env);
+ }
+ }
+ else if (overlap && sdc->std_env_vars == TLS_FLAG_TRUE) {
+ /* Remove variables added on connection init that are disabled here */
+ for (i = 0; i < TLS_DIM(ExportCertVars); ++i) {
+ apr_table_unset(env, ExportCertVars[i]);
+ }
+ }
+ }
+
+apr_status_t tls_var_handshake_done(conn_rec *c)
+{
+ tls_conf_conn_t *cc;
+ tls_conf_server_t *sc;
+ apr_status_t rv = APR_SUCCESS;
+
+ cc = tls_conf_conn_get(c);
+ if (!TLS_CONN_ST_IS_ENABLED(cc)) goto cleanup;
+
+ sc = tls_conf_server_get(cc->server);
+ if (cc->peer_certs && sc->var_user_name) {
+ cc->user_name = tls_var_lookup(c->pool, cc->server, c, NULL, sc->var_user_name);
+ if (!cc->user_name) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cc->server, APLOGNO(10317)
+ "Failed to set r->user to '%s'", sc->var_user_name);
+ }
+ }
+ cc->subprocess_env = apr_table_make(c->pool, 5);
+ add_vars(cc->subprocess_env, c, cc->server, NULL);
+
+cleanup:
+ return rv;
+}
+
+int tls_var_request_fixup(request_rec *r)
+{
+ conn_rec *c = r->connection;
+ tls_conf_conn_t *cc;
+
+ cc = tls_conf_conn_get(c->master? c->master : c);
+ if (!TLS_CONN_ST_IS_ENABLED(cc)) goto cleanup;
+ if (cc->user_name) {
+ /* why is r->user a char* and not const? */
+ r->user = apr_pstrdup(r->pool, cc->user_name);
+ }
+ add_vars(r->subprocess_env, c, r->server, r);
+
+cleanup:
+ return DECLINED;
+}
diff --git a/modules/tls/tls_var.h b/modules/tls/tls_var.h
new file mode 100644
index 0000000..2e8c0bb
--- /dev/null
+++ b/modules/tls/tls_var.h
@@ -0,0 +1,39 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef tls_var_h
+#define tls_var_h
+
+void tls_var_init_lookup_hash(apr_pool_t *pool, apr_hash_t *map);
+
+/**
+ * Callback for installation in Apache's 'ssl_var_lookup' hook to provide
+ * SSL related variable lookups to other modules.
+ */
+const char *tls_var_lookup(
+ apr_pool_t *p, server_rec *s, conn_rec *c, request_rec *r, const char *name);
+
+/**
+ * A connection has been handshaked. Prepare commond TLS variables on this connection.
+ */
+apr_status_t tls_var_handshake_done(conn_rec *c);
+
+/**
+ * A request is ready for processing, add TLS variables r->subprocess_env if applicable.
+ * This is a hook function returning OK/DECLINED.
+ */
+int tls_var_request_fixup(request_rec *r);
+
+#endif /* tls_var_h */ \ No newline at end of file
diff --git a/modules/tls/tls_version.h b/modules/tls/tls_version.h
new file mode 100644
index 0000000..811d6f1
--- /dev/null
+++ b/modules/tls/tls_version.h
@@ -0,0 +1,39 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef mod_tls_version_h
+#define mod_tls_version_h
+
+#undef PACKAGE_VERSION
+#undef PACKAGE_TARNAME
+#undef PACKAGE_STRING
+#undef PACKAGE_NAME
+#undef PACKAGE_BUGREPORT
+
+/**
+ * @macro
+ * Version number of the md module as c string
+ */
+#define MOD_TLS_VERSION "0.8.3"
+
+/**
+ * @macro
+ * Numerical representation of the version number of the md module
+ * release. This is a 24 bit number with 8 bits for major number, 8 bits
+ * for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203.
+ */
+#define MOD_TLS_VERSION_NUM 0x000802
+
+#endif /* mod_md_md_version_h */